weizong song 3 weeks ago
parent 56bc9271ed
commit 3ccced9c01

@ -78,6 +78,8 @@
</view>
</view>
<button class="location-refresh-btn location-refresh-btn--standalone" @click="refreshUserLocation"></button>
<!-- 过闸日期选择 -->
<view class="card">
<view class="card-title">过闸日期</view>
@ -106,6 +108,7 @@
<view style="position:absolute;left:0;top:0;right:0;bottom:0;z-index:2;" @tap="toggleAgreeNotice"></view>
</view>
</view>
</view>
<view class="reservation-bottom-bar">
<button class="reservation-btn" @click="onReserve"></button>
@ -121,7 +124,8 @@ import wx from 'jweixin-module'
// #endif
export default {
name: 'ReservationPage',
geofenceToleranceMeters: 20,
geofenceToleranceMeters: 1000,
geofenceAccuracyWarningMeters: 1000,
components: { NavBar },
data() {
return {
@ -149,6 +153,17 @@ export default {
userLocation: null, // {latitude, longitude}
isInGeofence: null, // nulltruefalse
geofenceRequestSeq: 0, //
geofenceDebug: {
direction: '',
locationSource: '',
stage: '',
result: null,
message: '',
updatedAt: '',
location: null,
rawGeofences: null,
evaluations: [],
},
unitPrice: '', //
calculationDescription: '', //
}
@ -241,6 +256,36 @@ export default {
// #endif
return Promise.resolve(null);
},
handleLocationAccuracyNotice(location) {
const accuracy = location && typeof location.accuracy === 'number' ? location.accuracy : null;
if (accuracy !== null && accuracy > (this.$options.geofenceAccuracyWarningMeters || 1000)) {
uni.showToast({
title: `当前定位误差约${accuracy.toFixed(0)}`,
icon: 'none',
duration: 2500
});
}
},
async refreshUserLocation() {
uni.showLoading({ title: '重新获取定位...' });
const location = await this.getUserLocation();
uni.hideLoading();
if (!location) {
uni.showToast({ title: '重新获取定位失败', icon: 'none' });
return;
}
this.handleLocationAccuracyNotice(location);
if (this.direction) {
await this.fetchGeofenceByDirection({
showModal: false,
direction: this.direction,
location,
});
}
},
// H5
getWeixinLocation() {
// #ifdef H5
@ -309,11 +354,25 @@ export default {
accuracy: res.accuracy
};
this.userLocation = location;
this.updateGeofenceDebug({
locationSource: '微信 JSSDK gcj02',
location,
message: '微信定位成功',
stage: '定位完成',
});
this.handleLocationAccuracyNotice(location);
console.log('[WeixinLocation] 已保存 userLocation =', this.userLocation);
resolve(location);
},
fail: (err) => {
console.error('[WeixinLocation] wx.getLocation fail:', err);
this.updateGeofenceDebug({
locationSource: '微信 JSSDK gcj02',
location: null,
result: null,
message: '微信定位失败',
stage: '定位失败',
});
uni.showModal({
title: '提示',
content: '获取位置失败,请允许访问位置信息',
@ -352,11 +411,25 @@ export default {
speed: position.coords.speed
};
this.userLocation = location;
this.updateGeofenceDebug({
locationSource: '浏览器 geolocation',
location,
message: '浏览器定位成功',
stage: '定位完成',
});
this.handleLocationAccuracyNotice(location);
console.log('浏览器获取位置成功:', this.userLocation);
resolve(location);
},
(error) => {
console.error('浏览器获取位置失败:', error);
this.updateGeofenceDebug({
locationSource: '浏览器 geolocation',
location: null,
result: null,
message: '浏览器定位失败:' + (error && error.message ? error.message : '未知错误'),
stage: '定位失败',
});
let errorMsg = '获取位置失败';
switch(error.code) {
case error.PERMISSION_DENIED:
@ -400,11 +473,25 @@ export default {
accuracy: res.accuracy
};
this.userLocation = location;
this.updateGeofenceDebug({
locationSource: '微信小程序 gcj02',
location,
message: '小程序定位成功',
stage: '定位完成',
});
this.handleLocationAccuracyNotice(location);
console.log('小程序获取位置成功:', this.userLocation);
resolve(location);
},
fail: (err) => {
console.error('小程序获取位置失败:', err);
this.updateGeofenceDebug({
locationSource: '微信小程序 gcj02',
location: null,
result: null,
message: '小程序定位失败',
stage: '定位失败',
});
uni.showModal({
title: '提示',
content: '获取位置失败,请在设置中允许位置权限',
@ -443,6 +530,11 @@ export default {
if (!token || !direction) {
return null;
}
this.updateGeofenceDebug({
direction,
stage: '请求围栏',
location: location || null,
});
try {
const res = await new Promise((resolve, reject) => {
uni.request({
@ -455,6 +547,13 @@ export default {
if (res.data && res.data.errcode === 0) {
const geofenceData = res.data.data;
console.log(geofenceData);
this.updateGeofenceDebug({
direction,
rawGeofences: geofenceData,
stage: '围栏已返回',
location: location || null,
message: `获取到 ${Array.isArray(geofenceData) ? geofenceData.length : 0} 个围栏`,
});
const result = this.checkLocationInGeofence(geofenceData, {
showModal,
location,
@ -476,6 +575,12 @@ export default {
if (requestSeq === null || requestSeq === this.geofenceRequestSeq) {
this.isInGeofence = null;
}
this.updateGeofenceDebug({
direction,
result: null,
stage: '围栏请求失败',
message: e && e.message ? e.message : '围栏请求失败',
});
return null;
}
return null;
@ -489,6 +594,13 @@ export default {
//
if (!location || !location.latitude || !location.longitude) {
console.warn('用户位置信息不存在,无法进行范围判断');
this.updateGeofenceDebug({
location: location || null,
result: null,
stage: '无法计算',
message: '用户位置信息不存在,无法进行范围判断',
evaluations: [],
});
return null;
}
@ -497,20 +609,54 @@ export default {
//
if (!geofenceData || !Array.isArray(geofenceData) || geofenceData.length === 0) {
this.updateGeofenceDebug({
location,
rawGeofences: geofenceData,
result: true,
stage: '围栏计算完成',
message: '未配置围栏,默认放行',
evaluations: [],
});
return true;
}
const evaluations = [];
// coordinates
for (let i = 0; i < geofenceData.length; i++) {
const item = geofenceData[i];
if (item && item.coordinates && Array.isArray(item.coordinates) && item.coordinates.length > 0) {
//
if (this.isPointInPolygon(userLng, userLat, item.coordinates)) {
const evaluation = this.evaluatePointAgainstPolygon(userLng, userLat, item.coordinates);
evaluations.push({
geofenceId: item.id || null,
geofenceName: item.name || '',
pointCount: item.coordinates.length,
...evaluation
});
if (evaluation.inside) {
console.log('用户位置在围栏范围内');
this.updateGeofenceDebug({
location,
rawGeofences: geofenceData,
result: true,
stage: '围栏计算完成',
message: `命中围栏 ${item.name || item.id || i + 1}`,
evaluations,
});
return true;
}
}
}
this.updateGeofenceDebug({
location,
rawGeofences: geofenceData,
result: false,
stage: '围栏计算完成',
message: '未命中任何围栏',
evaluations,
});
// showModaltrueshowModalfalseonReserve
if (showModal) {
@ -524,11 +670,22 @@ export default {
},
// 使线
isPointInPolygon(lng, lat, coordinates) {
return this.evaluatePointAgainstPolygon(lng, lat, coordinates).inside;
},
evaluatePointAgainstPolygon(lng, lat, coordinates) {
const toleranceMeters = this.$options.geofenceToleranceMeters || 20;
if (!coordinates || coordinates.length < 3) {
return false;
return {
inside: false,
toleranceMeters,
polygonPointCount: 0,
onBoundary: false,
withinTolerance: false,
rayCastingInside: false,
minDistanceMeters: null,
};
}
//
const polygon = coordinates.map(coord => {
if (Array.isArray(coord) && coord.length >= 2) {
return [
@ -538,42 +695,71 @@ export default {
}
return null;
}).filter(coord => coord !== null);
if (polygon.length < 3) {
return false;
return {
inside: false,
toleranceMeters,
polygonPointCount: polygon.length,
onBoundary: false,
withinTolerance: false,
rayCastingInside: false,
minDistanceMeters: null,
};
}
if (this.isPointOnPolygonBoundary(lng, lat, polygon)) {
return true;
}
const boundaryInfo = this.inspectPolygonBoundary(lng, lat, polygon, toleranceMeters);
const rayCastingInside = this.isPointInsidePolygonByRayCasting(lng, lat, polygon);
return {
inside: boundaryInfo.onBoundary || boundaryInfo.withinTolerance || rayCastingInside,
toleranceMeters,
polygonPointCount: polygon.length,
onBoundary: boundaryInfo.onBoundary,
withinTolerance: boundaryInfo.withinTolerance,
nearestSegmentIndex: boundaryInfo.nearestSegmentIndex,
minDistanceMeters: boundaryInfo.minDistanceMeters,
rayCastingInside,
};
},
isPointInsidePolygonByRayCasting(lng, lat, polygon) {
let inside = false;
const x = lng;
const y = lat;
// 线线
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
const xi = polygon[i][0];
const yi = polygon[i][1];
const xj = polygon[j][0];
const yj = polygon[j][1];
// 线
const intersect = ((yi > y) !== (yj > y)) &&
const intersect = ((yi > y) !== (yj > y)) &&
(x < (xj - xi) * (y - yi) / (yj - yi) + xi);
if (intersect) {
inside = !inside;
}
}
return inside;
},
isPointOnPolygonBoundary(lng, lat, polygon, toleranceMeters = this.$options.geofenceToleranceMeters || 20) {
const boundaryInfo = this.inspectPolygonBoundary(lng, lat, polygon, toleranceMeters);
return boundaryInfo.onBoundary || boundaryInfo.withinTolerance;
},
inspectPolygonBoundary(lng, lat, polygon, toleranceMeters = this.$options.geofenceToleranceMeters || 20) {
if (!polygon || polygon.length < 2) {
return false;
return {
onBoundary: false,
withinTolerance: false,
minDistanceMeters: null,
nearestSegmentIndex: null,
};
}
let minDistanceMeters = null;
let nearestSegmentIndex = null;
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
const start = polygon[j];
const end = polygon[i];
@ -583,15 +769,27 @@ export default {
}
if (this.isPointOnSegment(lng, lat, start[0], start[1], end[0], end[1])) {
return true;
return {
onBoundary: true,
withinTolerance: true,
minDistanceMeters: 0,
nearestSegmentIndex: i,
};
}
if (this.getDistanceToSegmentMeters(lng, lat, start[0], start[1], end[0], end[1]) <= toleranceMeters) {
return true;
const distanceMeters = this.getDistanceToSegmentMeters(lng, lat, start[0], start[1], end[0], end[1]);
if (minDistanceMeters === null || distanceMeters < minDistanceMeters) {
minDistanceMeters = distanceMeters;
nearestSegmentIndex = i;
}
}
return false;
return {
onBoundary: false,
withinTolerance: minDistanceMeters !== null && minDistanceMeters <= toleranceMeters,
minDistanceMeters: minDistanceMeters !== null ? Number(minDistanceMeters.toFixed(3)) : null,
nearestSegmentIndex,
};
},
isPointOnSegment(px, py, x1, y1, x2, y2, epsilon = 1e-10) {
const cross = (px - x1) * (y2 - y1) - (py - y1) * (x2 - x1);
@ -634,6 +832,23 @@ export default {
return Math.hypot(pxMeters - closestX, pyMeters - closestY);
},
updateGeofenceDebug(payload = {}) {
this.geofenceDebug = {
...this.geofenceDebug,
...payload,
updatedAt: new Date().toLocaleString(),
};
},
formatDebug(value) {
if (value === null || typeof value === 'undefined') {
return 'null';
}
try {
return JSON.stringify(value, null, 2);
} catch (error) {
return String(value);
}
},
async fetchDailyReservationDeadline() {
const token = uni.getStorageSync('token');
if (!token) return;
@ -1099,6 +1314,21 @@ export default {
font-size: 24rpx;
font-weight: 600;
}
.location-refresh-btn {
margin-top: 20rpx;
height: 76rpx;
line-height: 76rpx;
border-radius: 999rpx;
background: #fff7ed;
color: #c2410c;
border: 1rpx solid #fff7ed;
font-size: 26rpx;
}
.location-refresh-btn--standalone {
display: block;
width: calc(100% - 64rpx);
margin: 20rpx 32rpx 0;
}
.water-info-row {
display: flex;
justify-content: center;

@ -1,2 +1,2 @@
<!DOCTYPE html><html lang=zh-CN><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><title>胥口闸站购票</title><script>var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))
document.write('<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' + (coverSupport ? ', viewport-fit=cover' : '') + '" />')</script><link rel=stylesheet href=/h5/static/index.cc298949.css></head><body><noscript><strong>Please enable JavaScript to continue.</strong></noscript><div id=app></div><script src=/h5/static/js/chunk-vendors.84c26d58.js></script><script src=/h5/static/js/index.0b6926c6.js></script></body></html>
document.write('<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' + (coverSupport ? ', viewport-fit=cover' : '') + '" />')</script><link rel=stylesheet href=/h5/static/index.cc298949.css></head><body><noscript><strong>Please enable JavaScript to continue.</strong></noscript><div id=app></div><script src=/h5/static/js/chunk-vendors.84c26d58.js></script><script src=/h5/static/js/index.b1d5013e.js></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save