diff --git a/src/static/image/reset.png b/src/static/image/reset.png new file mode 100644 index 0000000..6a73d83 Binary files /dev/null and b/src/static/image/reset.png differ diff --git a/src/static/map.html b/src/static/map.html index cfc140f..909744a 100644 --- a/src/static/map.html +++ b/src/static/map.html @@ -32,6 +32,8 @@ padding: 0; overflow: hidden; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + /* 确保 body 背景色与地图底色一致,防止加载前的白屏 */ + background-color: #F5F3F0; } #map-container { @@ -386,6 +388,34 @@ transform: scale(0.95); } + /* 重置地图按钮 */ + .reset-map-btn { + position: fixed; + bottom: 70px; /* 定位在定位按钮上方:20px + 32px + 18px间距 */ + right: 20px; + width: 32px; + height: 32px; + background: rgba(255, 255, 255, 0.95) url('./image/reset.png') center center / 70% no-repeat; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + z-index: 998; + pointer-events: auto; + transition: transform 0.2s, box-shadow 0.2s; + } + + .reset-map-btn:hover { + transform: scale(1.1); + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4); + } + + .reset-map-btn:active { + transform: scale(0.95); + } + /* 地图 Label 样式 - 提取至 CSS */ .map-project-label { color: #002db6 !important; @@ -466,7 +496,6 @@
-
@@ -485,7 +514,7 @@
AR
- +
@@ -597,7 +626,7 @@ if (xhr.status >= 200 && xhr.status < 300) { const res = Utils.safeParseJSON(encodeURIComponent(xhr.responseText)); resolve(res?.code >= 200 && res?.code < 300 ? (res.data || []) : []); - } else { + } else { resolve([]); } } @@ -629,24 +658,28 @@ watchPositionId: null, watchOrientationId: null, watchLocationTimer: null, - mapLoaded: false + mapLoaded: false, + // 新增:网络状态处理相关 + networkDebounceTimer: null, + isNetworkRecovering: false }, locationIconCache: null, baiduGeolocation: null, - lastHeading: null, // 记录上一次的方向,用于避免频繁更新图标 - updateMarkerTimer: null, // 防抖定时器 - headingHistory: [], // 方向历史记录,用于平滑滤波(移动平均) - headingHistorySize: 5, // 历史记录大小(5个样本的移动平均) - lastOrientationUpdateTime: 0, // 上次方向更新时间,用于节流 - headingOffset: 0, // 方向偏移量,用于校正方向偏差(度),如果需要反转方向可以设置为180 - lastLocationTime: 0, // 上次位置更新时间 - lastLocationPoint: null, // 上次位置点,用于计算移动速度 - isMoving: false, // 是否在移动状态 + lastHeading: null, + updateMarkerTimer: null, + headingHistory: [], + headingHistorySize: 5, + lastOrientationUpdateTime: 0, + headingOffset: 0, + lastLocationTime: 0, + lastLocationPoint: null, + isMoving: false, elements: { panel: null, tree: null, closeBtn: null, - projectListContainer: null + projectListContainer: null, + resetBtn: null }, init() { @@ -670,6 +703,9 @@ // 处理 URL Action this.handleUrlActions(params); + + // 初始化网络监听优化 + this.setupNetworkListeners(); }, cacheElements() { @@ -681,6 +717,7 @@ this.elements.deselectAllBtn = document.getElementById('deselect-all-btn'); this.elements.arBtn = document.getElementById('ar-btn'); this.elements.locationCenterBtn = document.getElementById('location-center-btn'); + this.elements.resetBtn = document.getElementById('reset-map-btn'); }, bindEvents() { @@ -708,6 +745,11 @@ this.elements.locationCenterBtn.addEventListener('click', () => this.centerToLocation()); } + // 重置地图按钮 + if (this.elements.resetBtn) { + this.elements.resetBtn.addEventListener('click', () => this.resetMap()); + } + // 页面卸载时清理定位监听 window.addEventListener('beforeunload', () => this.stopLocationTracking()); @@ -729,10 +771,66 @@ window.drawModel = this.drawModel.bind(this); }, + /** + * 优化后的网络状态监听:防抖处理,避免频繁闪烁 + */ + setupNetworkListeners() { + window.addEventListener('online', () => { + console.log('网络连接已恢复,等待稳定...'); + + // 清除之前的定时器,防止在不稳定时频繁触发 + if (this.state.networkDebounceTimer) { + clearTimeout(this.state.networkDebounceTimer); + } + + // 设置防抖,等待2秒钟确认网络稳定后再刷新 + this.state.networkDebounceTimer = setTimeout(() => { + if (this.map && !this.state.isNetworkRecovering) { + console.log('网络稳定,执行地图刷新'); + this.state.isNetworkRecovering = true; + + // 记录当前状态 + const zoom = this.map.getZoom(); + const center = this.map.getCenter(); + + // 触发瓦片重载的轻微变动 (只变动一点点,减少视觉跳跃) + // 使用 panBy(0,0) 有时无法触发瓦片重载,setZoom 是最暴力的有效方法 + // 这里使用极其微小的缩放变化,并快速还原 + this.map.setZoom(zoom + 0.000001); + + setTimeout(() => { + this.map.setZoom(zoom); + this.state.isNetworkRecovering = false; + }, 200); + } + }, 2000); // 2秒防抖时间 + }); + + window.addEventListener('offline', () => { + console.log('网络已断开'); + // 如果在等待刷新期间断网,取消刷新 + if (this.state.networkDebounceTimer) { + clearTimeout(this.state.networkDebounceTimer); + this.state.networkDebounceTimer = null; + } + }); + }, + + /** + * 重置地图(解决网络差导致的黑屏/闪烁问题) + * 强制刷新当前页面 + */ + resetMap() { + console.log('用户请求重置地图...'); + // 在 WebGL 地图环境(特别是内嵌 WebView),如果因网络问题导致 Context 丢失或瓦片加载卡死, + // 简单的 destroy() 可能不够彻底,直接 location.reload() 是最稳妥的修复方式。 + window.location.reload(); + }, + initMap(projectInfo) { this.map = new BMapGL.Map('map-container', { enableHighResolution: true, // 开启高清适配 - backgroundColor: [245, 243, 240, 255] // [R, G, B, A] 显式设置 GL 引擎的清屏颜色 + backgroundColor: [245, 243, 240, 255] // [R, G, B, A] 显式设置 GL 引擎的清屏颜色,与CSS一致 }) const center = projectInfo[0] ? new BMapGL.Point(projectInfo[0].lng, projectInfo[0].lat) : new BMapGL.Point(116.404, 39.915); this.map.centerAndZoom(center, 12); @@ -759,22 +857,15 @@ this.initLocationTracking(); } }); - - // 网络状态监听(如果运行在 UniApp 环境中) - window.addEventListener('online', () => { - if (App.map) { - // 网络恢复时,稍微缩放一下地图,触发瓦片重新加载,避免卡死在白屏状态 - const zoom = App.map.getZoom(); - App.map.setZoom(zoom + 0.01); - setTimeout(() => App.map.setZoom(zoom), 100); - } - }); + + // 注意:原有的 window.addEventListener('online') 已移至 setupNetworkListeners 统一管理 }, /** * 初始化定位跟踪 */ initLocationTracking() { + // ... (保持原有代码不变) // 等待地图加载完成 if (!this.map || !this.state.mapLoaded) { console.log('等待地图加载完成...'); @@ -793,7 +884,7 @@ // 开始监听设备方向变化(使用设备方向传感器) this.startWatchingOrientation(); }, - + /** * 创建定位标记图标(使用 real_postion.png) */ @@ -1071,239 +1162,6 @@ }); }, - /** - * 使用uni-app GPS定位(WGS84坐标,最准确) - */ - startUniAppGPSGeolocation() { - console.log('使用uni-app GPS定位(WGS84)'); - // 先获取一次当前位置 - uni.getLocation({ - type: 'wgs84', // 使用WGS84坐标系(GPS原始坐标),然后转换为BD09 - altitude: false, - geocode: false, - highAccuracyExpireTime: 10000, // 增加等待时间,获取GPS定位 - success: (res) => { - console.log('uni-app GPS定位成功:', res.longitude, res.latitude, '精度:', res.accuracy); - // uni-app返回的是WGS84坐标,需要转换为BD09 - this.wgs84ToBd09(res.longitude, res.latitude, (bd09) => { - if (bd09 && bd09.lng && bd09.lat) { - this.state.currentLocation = { - lat: bd09.lat, - lng: bd09.lng, - accuracy: res.accuracy || 0 - }; - console.log('GPS坐标转换完成 - BD09:', bd09.lng, bd09.lat); - this.updateLocationMarker(); - } else { - console.error('GPS坐标转换失败,使用GCJ02定位'); - this.startUniAppGeolocation(); - } - }); - }, - fail: (err) => { - console.error('uni-app GPS定位失败:', err); - // 降级到GCJ02定位 - this.startUniAppGeolocation(); - } - }); - - // 持续监听位置变化(改为每5秒更新一次) - this.state.watchLocationTimer = setInterval(() => { - uni.getLocation({ - type: 'wgs84', - altitude: false, - geocode: false, - highAccuracyExpireTime: 10000, - success: (res) => { - this.wgs84ToBd09(res.longitude, res.latitude, (bd09) => { - if (bd09 && bd09.lng && bd09.lat) { - // 只更新精度更好的位置 - if (!this.state.currentLocation || res.accuracy < this.state.currentLocation.accuracy || res.accuracy < 20) { - this.state.currentLocation = { - lat: bd09.lat, - lng: bd09.lng, - accuracy: res.accuracy || 0 - }; - this.updateLocationMarker(); - } - } - }); - }, - fail: (err) => { - console.error('uni-app GPS定位更新失败:', err); - } - }); - }, 3000); // 每3秒更新一次 - }, - - /** - * 使用uni-app定位(GCJ02坐标) - */ - startUniAppGeolocation() { - // 先获取一次当前位置 - uni.getLocation({ - type: 'gcj02', // 使用GCJ02坐标系(火星坐标),然后转换为BD09 - altitude: false, - geocode: false, - highAccuracyExpireTime: 4000, - success: (res) => { - console.log('uni-app定位成功:', res.longitude, res.latitude, '精度:', res.accuracy); - // uni-app返回的是GCJ02坐标,需要转换为BD09 - this.gcj02ToBd09(res.longitude, res.latitude, (bd09) => { - if (bd09 && bd09.lng && bd09.lat) { - this.state.currentLocation = { - lat: bd09.lat, - lng: bd09.lng, - accuracy: res.accuracy || 0 - }; - console.log('坐标转换完成 - BD09:', bd09.lng, bd09.lat); - this.updateLocationMarker(); - } else { - console.error('坐标转换失败,使用备用方案'); - // 如果转换失败,尝试使用百度地图定位 - if (typeof BMapGL !== 'undefined' && BMapGL.Geolocation) { - this.startBaiduGeolocation(); - } else { - this.startBrowserGeolocation(); - } - } - }); - }, - fail: (err) => { - console.error('uni-app定位失败:', err); - // 降级到百度地图定位或浏览器定位 - if (typeof BMapGL !== 'undefined' && BMapGL.Geolocation) { - this.startBaiduGeolocation(); - } else { - this.startBrowserGeolocation(); - } - } - }); - - // 持续监听位置变化(改为每5秒更新一次) - this.state.watchLocationTimer = setInterval(() => { - uni.getLocation({ - type: 'gcj02', - altitude: false, - geocode: false, - highAccuracyExpireTime: 4000, - success: (res) => { - this.gcj02ToBd09(res.longitude, res.latitude, (bd09) => { - if (bd09 && bd09.lng && bd09.lat) { - // 只更新精度更好的位置 - if (!this.state.currentLocation || res.accuracy < this.state.currentLocation.accuracy || res.accuracy < 50) { - this.state.currentLocation = { - lat: bd09.lat, - lng: bd09.lng, - accuracy: res.accuracy || 0 - }; - this.updateLocationMarker(); - } - } - }); - }, - fail: (err) => { - console.error('uni-app定位更新失败:', err); - } - }); - }, 3000); // 每3秒更新一次位置 - }, - - /** - * 使用5+定位 - */ - startPlusGeolocation() { - const geolocation = plus.geolocation; - const options = { - enableHighAccuracy: true, - timeout: 10000, - maximumAge: 0 - }; - - // 获取当前位置 - geolocation.getCurrentPosition( - (position) => { - console.log('5+定位成功:', position); - // 5+返回的是WGS84坐标,需要转换为BD09 - this.wgs84ToBd09(position.coords.longitude, position.coords.latitude, (bd09) => { - this.state.currentLocation = { - lat: bd09.lat, - lng: bd09.lng, - accuracy: position.coords.accuracy || 0 - }; - this.updateLocationMarker(); - }); - }, - (error) => { - console.error('5+定位失败:', error); - this.startBrowserGeolocation(); - }, - options - ); - - // 持续监听位置变化 - this.state.watchPositionId = geolocation.watchPosition( - (position) => { - this.wgs84ToBd09(position.coords.longitude, position.coords.latitude, (bd09) => { - this.state.currentLocation = { - lat: bd09.lat, - lng: bd09.lng, - accuracy: position.coords.accuracy || 0 - }; - this.updateLocationMarker(); - }); - }, - (error) => { - console.error('5+定位监听错误:', error); - }, - options - ); - }, - - /** - * 使用浏览器定位 - */ - startBrowserGeolocation() { - if (!navigator.geolocation) { - console.error('浏览器不支持定位'); - return; - } - - const options = { - enableHighAccuracy: true, // 高精度定位(使用GPS) - timeout: 20000, // 增加超时时间到20秒,确保获取高精度定位 - maximumAge: 0 // 不使用缓存,始终获取最新位置 - }; - - console.log('开始浏览器定位,选项:', options); - - // 先获取一次当前位置 - navigator.geolocation.getCurrentPosition( - (position) => { - console.log('获取到初始位置:', position.coords); - this.updateLocation(position); - }, - (error) => { - console.error('获取初始位置失败:', error); - this.handleLocationError(error); - }, - options - ); - - // 持续监听位置变化 - this.state.watchPositionId = navigator.geolocation.watchPosition( - (position) => { - console.log('位置更新:', position.coords); - this.updateLocation(position); - }, - (error) => { - console.error('位置监听错误:', error); - this.handleLocationError(error); - }, - options - ); - }, - /** * GCJ02坐标转BD09坐标(百度坐标) */ @@ -1554,14 +1412,14 @@ updateLocationMarker() { // 确保地图已加载完成 if (!this.map || !this.state.mapLoaded) { - return; - } - + return; + } + // 如果没有位置信息,静默返回 if (!this.state.currentLocation) { - return; - } - + return; + } + // 如果已经有待处理的更新,取消它,重新调度(确保使用最新数据) if (this.updateMarkerTimer) { cancelAnimationFrame(this.updateMarkerTimer); @@ -1825,7 +1683,7 @@ const targetHistorySize = this.isMoving ? 15 : 7; if (!this.headingHistorySize) { this.headingHistorySize = targetHistorySize; - } else { + } else { // 动态调整历史记录大小 this.headingHistorySize = targetHistorySize; } @@ -2138,6 +1996,12 @@ clearInterval(this.state.watchLocationTimer); this.state.watchLocationTimer = null; } + + // 清理网络防抖定时器 + if (this.state.networkDebounceTimer) { + clearTimeout(this.state.networkDebounceTimer); + this.state.networkDebounceTimer = null; + } // 清理更新标记的定时器 if (this.updateMarkerTimer !== null) { @@ -2230,7 +2094,7 @@ this.elements.projectListContainer.innerHTML = '
暂无工程数据
'; return; } - + const fragment = document.createDocumentFragment(); projectInfo.forEach((project, index) => { const projectId = project.id || `project-${index}`; @@ -2513,13 +2377,13 @@ }, renderTreeNode(node, container, level) { - const nodeDiv = document.createElement('div'); - nodeDiv.className = 'tree-node'; + const nodeDiv = document.createElement('div'); + nodeDiv.className = 'tree-node'; nodeDiv.style.paddingLeft = (level * 20) + 'px'; - - const itemDiv = document.createElement('div'); - itemDiv.className = 'tree-node-item'; - + + const itemDiv = document.createElement('div'); + itemDiv.className = 'tree-node-item'; + const hasChildren = node.children && node.children.length > 0; // 展开图标 @@ -2546,21 +2410,21 @@ } // 复选框 - const checkbox = document.createElement('input'); - checkbox.type = 'checkbox'; - checkbox.className = 'tree-node-checkbox'; + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.className = 'tree-node-checkbox'; checkbox.dataset.id = node.id; // 添加data-id用于全选/全不选时更新UI checkbox.checked = this.state.checkedNodeIds.includes(node.id); checkbox.addEventListener('change', (e) => this.handleCheck(node, e.target.checked)); itemDiv.appendChild(checkbox); - + // 文本 - const label = document.createElement('span'); - label.className = 'tree-node-label'; + const label = document.createElement('span'); + label.className = 'tree-node-label'; label.textContent = node.nodeName || node.name || `Node ${node.id}`; - itemDiv.appendChild(label); + itemDiv.appendChild(label); - nodeDiv.appendChild(itemDiv); + nodeDiv.appendChild(itemDiv); // 子容器 if (hasChildren) {