diff --git a/src/单兵终端APP/android/.idea/misc.xml b/src/单兵终端APP/android/.idea/misc.xml index f104487..d7916a5 100644 --- a/src/单兵终端APP/android/.idea/misc.xml +++ b/src/单兵终端APP/android/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/src/单兵终端APP/android/app/src/main/AndroidManifest.xml b/src/单兵终端APP/android/app/src/main/AndroidManifest.xml index b47a763..7258258 100644 --- a/src/单兵终端APP/android/app/src/main/AndroidManifest.xml +++ b/src/单兵终端APP/android/app/src/main/AndroidManifest.xml @@ -7,7 +7,8 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" - android:theme="@style/AppTheme"> + android:theme="@style/AppTheme" + android:usesCleartextTraffic="true"> - +
-
-
🎯 推荐投放点列表
-
- +
+ +
+
+ + +
+ +
+
+ + +
+
+
🗺️
+
点击地图选择投放点
+
+
+ + +
+
📍 已选位置
+
请点击地图选择投放点
+
--
+
--
+
+ + +
+
🎯 附近推荐投放点
+
+ +
+
+ +
+
-
@@ -303,7 +337,7 @@
- +
diff --git a/src/单兵终端APP/js/app.js b/src/单兵终端APP/js/app.js index 36c9cdc..c5afd32 100644 --- a/src/单兵终端APP/js/app.js +++ b/src/单兵终端APP/js/app.js @@ -17,6 +17,7 @@ const App = (() => { let currentPage = 'home'; let pageStack = ['home']; let selectedDropPoint = null; + let mapSelectedPoint = null; // 地图选中的投放点 let pollTimer = null; // ===== 页面映射(用于Tab导航显示控制) ===== @@ -128,7 +129,8 @@ const App = (() => { updateHomeLocation(); break; case 'drop': - loadDropPoints(); + // 延迟初始化,确保页面完全显示后再加载地图 + setTimeout(() => loadDropPoints(), 500); break; case 'task': loadTaskInfo(); @@ -344,8 +346,24 @@ const App = (() => { } } - // ===== 加载投放点 ===== + // ===== 加载投放点(含地图选点) ===== async function loadDropPoints() { + // 初始化地图选点 + setTimeout(async () => { + await LocationModule.initPickerMap('drop-map', (point) => { + mapSelectedPoint = point; + // 更新UI显示 + const nameEl = document.getElementById('drop-selected-name'); + const addrEl = document.getElementById('drop-selected-address'); + const coordEl = document.getElementById('drop-selected-coords'); + if (nameEl) nameEl.textContent = point.name; + if (addrEl) addrEl.textContent = point.address; + if (coordEl) coordEl.textContent = `${point.lat.toFixed(6)}, ${point.lng.toFixed(6)}`; + showToast('📍 已选择:' + point.name); + }); + }, 300); + + // 加载推荐列表 const list = document.getElementById('drop-point-list'); list.innerHTML = '
加载中...
'; @@ -359,16 +377,16 @@ const App = (() => { list.innerHTML = points.map((p, i) => { const isSafe = p.safety_score >= 70; return ` -
+
📍 ${p.name}
安全系数: ${p.safety_score}% 距离: ${p.distance}m
${p.reason}
- +
+ ${isSafe ? '✅ 推荐投放' : '❌ 危险区域'} +
`; }).join(''); @@ -377,27 +395,119 @@ const App = (() => { } } - // ===== 选择投放点 ===== + // ===== 搜索地点 ===== + function searchDropPoint() { + const input = document.getElementById('drop-search-input'); + const resultsDiv = document.getElementById('drop-search-results'); + const keyword = input ? input.value.trim() : ''; + if (!keyword) { + showToast('请输入搜索关键词'); + return; + } + + resultsDiv.innerHTML = '
搜索中...
'; + + LocationModule.searchPlace(keyword, (err, pois) => { + if (err || pois.length === 0) { + resultsDiv.innerHTML = '
未找到相关地点
'; + return; + } + + resultsDiv.innerHTML = pois.map((p, i) => ` +
+
${p.name}
+
${p.address}
+
+ `).join(''); + + // 存储搜索结果供点击使用 + window._searchResults = pois; + }); + } + + // 选择搜索结果 + async function selectSearchResult(index) { + const pois = window._searchResults || []; + const p = pois[index]; + if (!p) return; + + // 在地图上定位 + await LocationModule.setPickerPosition(p.lat, p.lng, p.name, p.address); + + // 更新选中状态 + mapSelectedPoint = { + lat: p.lat, + lng: p.lng, + name: p.name, + address: p.address + }; + + // 更新UI + const nameEl = document.getElementById('drop-selected-name'); + const addrEl = document.getElementById('drop-selected-address'); + const coordEl = document.getElementById('drop-selected-coords'); + if (nameEl) nameEl.textContent = p.name; + if (addrEl) addrEl.textContent = p.address; + if (coordEl) coordEl.textContent = `${p.lat.toFixed(6)}, ${p.lng.toFixed(6)}`; + + // 清空搜索结果 + const resultsDiv = document.getElementById('drop-search-results'); + if (resultsDiv) resultsDiv.innerHTML = ''; + + showToast('📍 已定位:' + p.name); + } + + // ===== 选择投放点(从列表) ===== function selectDropPoint(index) { - API.getDropPoints().then(points => { + API.getDropPoints().then(async points => { const p = points[index]; if (p.safety_score < 70) { - showToast('已标记避开此点'); + showToast('⚠️ 该区域危险,建议选择其他投放点'); return; } selectedDropPoint = p; + + // 同时在地图上标记 + await LocationModule.setPickerPosition(p.lat, p.lng, p.name, p.address); + + // 更新选中显示 + mapSelectedPoint = { + lat: p.lat, + lng: p.lng, + name: p.name, + address: p.address || p.name + }; + const nameEl = document.getElementById('drop-selected-name'); + const addrEl = document.getElementById('drop-selected-address'); + const coordEl = document.getElementById('drop-selected-coords'); + if (nameEl) nameEl.textContent = p.name; + if (addrEl) addrEl.textContent = p.address || '安全系数 ' + p.safety_score + '%'; + if (coordEl) coordEl.textContent = `${p.lat.toFixed(6)}, ${p.lng.toFixed(6)}`; + showToast('已选择:' + p.name); - router('demand'); - document.getElementById('demand-drop-display').textContent = p.name + '(安全系数' + p.safety_score + '%)'; }); } function confirmDropPoint() { - if (!selectedDropPoint) { - showToast('请先选择一个投放点'); + // 优先使用地图选中的点 + if (mapSelectedPoint) { + selectedDropPoint = mapSelectedPoint; + router('demand'); + const display = document.getElementById('demand-drop-display'); + if (display) display.textContent = mapSelectedPoint.name; + showToast('✅ 已确认投放点:' + mapSelectedPoint.name); + return; + } + // fallback到列表选中的点 + if (selectedDropPoint) { + router('demand'); + const display = document.getElementById('demand-drop-display'); + if (display) display.textContent = selectedDropPoint.name; return; } - router('demand'); + showToast('请先点击地图或列表选择一个投放点'); } // ===== 加载任务信息 ===== @@ -478,30 +588,68 @@ const App = (() => { } } + // ===== 手动修正位置 ===== + function manualSetLocation() { + const input = prompt('请输入您的坐标(格式:纬度,经度)\n示例:28.2280,112.9388\n长沙大约:28.2280,112.9388'); + if (!input) return; + const parts = input.split(',').map(s => parseFloat(s.trim())); + if (parts.length === 2 && !isNaN(parts[0]) && !isNaN(parts[1])) { + const pos = { lat: parts[0], lng: parts[1], accuracy: 10, source: 'manual' }; + LocationModule.lastPosition = pos; + const curEl = document.getElementById('loc-current'); + if (curEl) curEl.textContent = `${pos.lat.toFixed(6)}, ${pos.lng.toFixed(6)}`; + const accEl = document.getElementById('loc-accuracy'); + if (accEl) accEl.textContent = '手动设置 · 精确'; + updateHomeLocation(); + showToast('位置已手动修正'); + // 刷新地图 + const mapContainer = document.getElementById('loc-map'); + if (mapContainer) { + mapContainer.innerHTML = ''; + setTimeout(() => LocationModule.showMap('loc-map', pos.lat, pos.lng), 100); + } + } else { + showToast('格式错误,请使用:纬度,经度'); + } + } + // ===== 刷新位置 ===== async function refreshLocation() { const curEl = document.getElementById('loc-current'); if (curEl) curEl.textContent = '定位中...'; + try { const pos = await LocationModule.getCurrentPosition(); + const sourceText = LocationModule.getSourceText(pos.source); + + // 更新当前位置显示 if (curEl) { - const sourceText = LocationModule.getSourceText(pos.source); if (pos.source === 'default') { - curEl.innerHTML = `${pos.lat.toFixed(6)}, ${pos.lng.toFixed(6)} (${sourceText})`; + curEl.innerHTML = `${pos.lat.toFixed(4)}, ${pos.lng.toFixed(4)}`; + } else if (pos.source === 'ip') { + curEl.innerHTML = `${pos.lat.toFixed(4)}, ${pos.lng.toFixed(4)} (${sourceText})`; } else { curEl.textContent = `${pos.lat.toFixed(6)}, ${pos.lng.toFixed(6)}`; } } + // 更新精度显示 const accEl = document.getElementById('loc-accuracy'); if (accEl) { - const sourceText = LocationModule.getSourceText(pos.source); - accEl.textContent = LocationModule.formatAccuracy(pos.accuracy) + ' · ' + sourceText; + let text = LocationModule.formatAccuracy(pos.accuracy) + ' · ' + sourceText; + if (pos.source === 'default') { + text = '❌ 定位失败 · 使用默认坐标'; + } else if (pos.source === 'ip') { + text = '📡 ' + LocationModule.formatAccuracy(pos.accuracy) + ' · ' + sourceText + (pos.city ? ' (' + pos.city + ')' : ''); + } + accEl.textContent = text; } + // 更新上报时间 const reportEl = document.getElementById('loc-last-report'); if (reportEl) reportEl.textContent = new Date().toTimeString().split(' ')[0]; + // 更新偏移距离 const offsetEl = document.getElementById('loc-offset'); if (offsetEl) { offsetEl.textContent = '计算中...'; @@ -512,24 +660,35 @@ const App = (() => { }, 500); } - // 初始化高德地图 + // 初始化/更新地图 const mapContainer = document.getElementById('loc-map'); - if (mapContainer && pos.source !== 'default') { - mapContainer.innerHTML = ''; - mapContainer.style.display = 'block'; - mapContainer.style.border = 'none'; - setTimeout(() => { - LocationModule.initAmap('loc-map', pos.lat, pos.lng); - }, 100); - } else if (mapContainer && pos.source === 'default') { - mapContainer.innerHTML = '
🚫 定位失败,无法加载地图
请检查GPS权限和定位服务
'; + if (mapContainer) { + if (pos.source === 'default') { + // 定位完全失败,显示诊断信息 + const reasons = await LocationModule.getLocationErrorReason(); + mapContainer.innerHTML = ` +
+
🚫
+
定位失败
+
+ ${reasons.map(r => '• ' + r).join('
')} +
+
+ 💡 建议:开启WiFi + GPS + 到窗边 +
+
`; + } else { + mapContainer.innerHTML = ''; + mapContainer.style.border = 'none'; + setTimeout(() => LocationModule.showMap('loc-map', pos.lat, pos.lng), 100); + } } - // 更新首页位置显示 updateHomeLocation(); } catch (e) { - if (curEl) curEl.textContent = '定位失败'; + if (curEl) curEl.textContent = '定位异常'; + console.error('refreshLocation错误:', e); } } @@ -601,10 +760,13 @@ const App = (() => { submitDemand, selectDropPoint, confirmDropPoint, + searchDropPoint, + selectSearchResult, updateDropPoint, addAnnotate, triggerSOS, refreshLocation, + manualSetLocation, showToast, showServerConfig, toggleSwitch, diff --git a/src/单兵终端APP/js/location.js b/src/单兵终端APP/js/location.js index 938c2e2..eddcf63 100644 --- a/src/单兵终端APP/js/location.js +++ b/src/单兵终端APP/js/location.js @@ -1,21 +1,157 @@ /** - * GPS定位模块 - * 优先使用高德地图定位,fallback到Capacitor原生定位 + * GPS定位模块 + 高德地图动态显示 + * + * 地图显示方案: + * 1. 优先使用高德JS动态地图(支持缩放、拖动、标记) + * 2. 动态地图失败时fallback到静态地图图片 + * + * 关键修复(基于搜索结果): + * - 使用AMapLoader异步加载JS API,确保完全加载后再初始化 + * - 确保地图容器有明确宽高(SPA页面切换常见问题) + * - AndroidManifest.xml添加usesCleartextTraffic + * - 页面可见后再初始化地图 */ const LocationModule = (() => { let lastPosition = null; - let watchId = null; let reportTimer = null; let isReporting = false; - let amapKeyValid = null; // null=未检测, true=有效, false=无效 + let amapLoaded = false; // JS API是否加载完成 + let amapLoading = false; // 是否正在加载 + let currentMap = null; // 当前地图实例 + const AMAP_KEY = 'c014127be1ea5a1efead8419c94fbaba'; - // 检查是否在Capacitor环境 + // ========== 高德JS API异步加载 ========== + // 高德JS API异步加载(关键:使用callback参数确保完全加载) + function loadAmapScript() { + return new Promise((resolve, reject) => { + // 已经加载过 + if (typeof AMap !== 'undefined' && AMap.Map) { + amapLoaded = true; + resolve(AMap); + return; + } + // 正在加载中,等待 + if (amapLoading) { + const checkInterval = setInterval(() => { + if (typeof AMap !== 'undefined' && AMap.Map) { + clearInterval(checkInterval); + amapLoaded = true; + resolve(AMap); + } + }, 200); + setTimeout(() => { + clearInterval(checkInterval); + reject(new Error('高德JS加载超时')); + }, 15000); + return; + } + + amapLoading = true; + + // 关键修复:使用callback参数!高德JS API 2.0必须通过callback通知加载完成 + window._amapCallback = function() { + amapLoaded = true; + amapLoading = false; + if (typeof AMap !== 'undefined') { + resolve(AMap); + } else { + reject(new Error('AMap未定义')); + } + }; + + const script = document.createElement('script'); + script.type = 'text/javascript'; + script.charset = 'utf-8'; + script.src = `https://webapi.amap.com/maps?v=2.0&key=${AMAP_KEY}&callback=_amapCallback&plugin=AMap.Geolocation,AMap.Scale,AMap.Marker,AMap.Geocoder,AMap.PlaceSearch`; + script.onerror = () => { + amapLoading = false; + reject(new Error('高德JS加载失败')); + }; + document.head.appendChild(script); + }); + } + + // ========== 动态地图初始化 ========== + async function initDynamicMap(containerId, lat, lng) { + try { + const AMap = await loadAmapScript(); + const container = document.getElementById(containerId); + if (!container) throw new Error('地图容器不存在'); + + // 关键修复:确保容器有明确宽高 + container.style.width = '100%'; + container.style.height = '200px'; + container.style.minHeight = '200px'; + container.style.display = 'block'; + container.style.border = 'none'; + container.style.background = '#f5f5f5'; + + // 销毁旧地图 + if (currentMap) { + currentMap.destroy(); + currentMap = null; + } + + // 初始化地图 + currentMap = new AMap.Map(containerId, { + zoom: 15, + center: [lng, lat], + viewMode: '2D', + resizeEnable: true + }); + + // 添加标记 + new AMap.Marker({ + position: [lng, lat], + map: currentMap, + title: '当前位置' + }); + + // 添加缩放控件 + currentMap.addControl(new AMap.Scale()); + + return currentMap; + } catch (e) { + console.error('动态地图初始化失败:', e); + throw e; + } + } + + // ========== 静态地图图片(fallback)========== + function showStaticMap(containerId, lat, lng) { + const container = document.getElementById(containerId); + if (!container) return; + const w = container.clientWidth || 350; + const h = 200; + const imgUrl = `https://restapi.amap.com/v3/staticmap?location=${lng},${lat}&zoom=15&size=${w}*${h}&markers=mid,,A:${lng},${lat}&key=${AMAP_KEY}`; + container.innerHTML = ``; + } + + // ========== 统一地图显示入口 ========== + async function showMap(containerId, lat, lng) { + const container = document.getElementById(containerId); + if (!container) return; + + // 先清空容器 + container.innerHTML = '
🗺️
地图加载中...
'; + + try { + // 尝试动态地图 + await initDynamicMap(containerId, lat, lng); + console.log('✅ 动态地图加载成功'); + } catch (e) { + console.log('动态地图失败,使用静态地图:', e.message); + // fallback到静态地图 + showStaticMap(containerId, lat, lng); + } + } + + // ========== 定位相关 ========== function isCapacitor() { return typeof Capacitor !== 'undefined' && Capacitor.isNativePlatform && Capacitor.isNativePlatform(); } - // 获取Geolocation插件 function getGeolocation() { if (isCapacitor() && Capacitor.Plugins && Capacitor.Plugins.Geolocation) { return Capacitor.Plugins.Geolocation; @@ -23,23 +159,11 @@ const LocationModule = (() => { return null; } - // 检查高德Key是否有效 - function checkAmapKey() { - const script = document.querySelector('script[src*="webapi.amap.com"]'); - if (!script) return false; - const src = script.getAttribute('src'); - return src && !src.includes('YOUR_AMAP_KEY'); - } - - // 高德地图定位 + // 高德定位 async function getAmapPosition() { + const AMap = await loadAmapScript(); return new Promise((resolve, reject) => { - if (typeof AMap === 'undefined') { - reject(new Error('高德JS API未加载')); - return; - } - - const geolocation = new AMap.Geolocation({ + const geo = new AMap.Geolocation({ enableHighAccuracy: true, timeout: 15000, showButton: false, @@ -48,220 +172,393 @@ const LocationModule = (() => { panToLocation: false, zoomToAccuracy: false }); - - geolocation.getCurrentPosition((status, result) => { + geo.getCurrentPosition((status, result) => { if (status === 'complete' && result.position) { resolve({ lat: result.position.lat, lng: result.position.lng, accuracy: result.accuracy || 50, - altitude: null, - speed: null, - timestamp: Date.now(), source: 'amap' }); } else { - reject(new Error('高德定位失败: ' + (result.message || '未知错误'))); + reject(new Error(result.message || '高德定位失败')); } }); }); } - // 检查定位权限 - async function checkPermission() { - if (!isCapacitor()) { - return { location: 'granted' }; - } - try { - const geo = getGeolocation(); - if (geo && geo.requestPermissions) { - const perm = await geo.requestPermissions(); - return perm; + // Capacitor原生定位 + async function getCapacitorPosition() { + const geo = getGeolocation(); + if (!geo) throw new Error('Capacitor Geolocation不可用'); + const result = await geo.getCurrentPosition({ + enableHighAccuracy: true, + timeout: 15000, + enableLocationFallback: true + }); + return { + lat: result.coords.latitude, + lng: result.coords.longitude, + accuracy: result.coords.accuracy, + source: 'native' + }; + } + + // 浏览器定位 + async function getBrowserPosition() { + return new Promise((resolve, reject) => { + if (!navigator.geolocation) { + reject(new Error('浏览器不支持定位')); + return; + } + navigator.geolocation.getCurrentPosition( + (result) => { + resolve({ + lat: result.coords.latitude, + lng: result.coords.longitude, + accuracy: result.coords.accuracy, + source: 'browser' + }); + }, + (err) => reject(new Error(err.message)), + { enableHighAccuracy: true, timeout: 15000, maximumAge: 0 } + ); + }); + } + + // IP定位 + async function getIpPosition() { + const services = [ + { url: 'https://ipapi.co/json/', parse: (d) => ({ lat: d.latitude, lng: d.longitude, city: d.city }) }, + { url: 'https://ip-api.com/json/', parse: (d) => ({ lat: d.lat, lng: d.lon, city: d.city }) } + ]; + for (const svc of services) { + try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 3000); + const resp = await fetch(svc.url, { signal: controller.signal }); + clearTimeout(timeoutId); + const data = await resp.json(); + const parsed = svc.parse(data); + if (parsed.lat && parsed.lng) { + return { + lat: parsed.lat, + lng: parsed.lng, + accuracy: 5000, + source: 'ip', + city: parsed.city + }; + } + } catch (e) { + console.log('IP定位失败:', svc.url); } - return { location: 'granted' }; - } catch (e) { - console.error('权限检查失败:', e); - return { location: 'denied' }; } + throw new Error('IP定位失败'); } - // 原生GPS定位 - async function getNativePosition() { - const geo = getGeolocation(); - let pos; + // 统一入口 + async function getCurrentPosition() { + const errors = []; - if (geo) { - const result = await geo.getCurrentPosition({ - enableHighAccuracy: true, - timeout: 10000 + // 高德定位 + try { + const pos = await getAmapPosition(); + lastPosition = pos; + console.log('✅ 高德定位:', pos.lat.toFixed(4), pos.lng.toFixed(4)); + return pos; + } catch (e) { errors.push('高德:' + e.message); } + + // 原生定位 + try { + const pos = await getCapacitorPosition(); + lastPosition = pos; + console.log('✅ 原生定位:', pos.lat.toFixed(4), pos.lng.toFixed(4)); + return pos; + } catch (e) { errors.push('原生:' + e.message); } + + // 浏览器定位 + try { + const pos = await getBrowserPosition(); + lastPosition = pos; + console.log('✅ 浏览器定位:', pos.lat.toFixed(4), pos.lng.toFixed(4)); + return pos; + } catch (e) { errors.push('浏览器:' + e.message); } + + // IP定位 + try { + const pos = await getIpPosition(); + lastPosition = pos; + console.log('✅ IP定位:', pos.lat.toFixed(4), pos.lng.toFixed(4), pos.city); + return pos; + } catch (e) { errors.push('IP:' + e.message); } + + // 默认 + console.error('❌ 全部失败:', errors.join('; ')); + lastPosition = { lat: 30.2500, lng: 120.1600, accuracy: 100, source: 'default' }; + return lastPosition; + } + + // ========== 地图选点功能 ========== + let pickerMap = null; + let pickerMarker = null; + let pickerGeocoder = null; + + // 初始化选点地图 + async function initPickerMap(containerId, onSelectCallback) { + try { + console.log('开始加载高德JS API...'); + const AMap = await loadAmapScript(); + console.log('高德JS API加载完成'); + + const container = document.getElementById(containerId); + if (!container) { + console.error('地图容器不存在:', containerId); + return null; + } + + // 关键:确保容器可见且有明确尺寸 + container.style.width = '100%'; + container.style.height = '280px'; + container.style.minHeight = '280px'; + container.style.display = 'block'; + container.style.position = 'relative'; + container.innerHTML = ''; + + // 检查容器尺寸 + const rect = container.getBoundingClientRect(); + console.log('容器尺寸:', rect.width, 'x', rect.height); + if (rect.width === 0 || rect.height === 0) { + console.warn('容器尺寸为0,延迟初始化'); + // 如果尺寸为0,延迟100ms再试 + await new Promise(r => setTimeout(r, 300)); + } + + // 销毁旧地图 + if (pickerMap) { + pickerMap.destroy(); + pickerMap = null; + pickerMarker = null; + } + + // 获取当前位置作为中心点 + const center = lastPosition || { lat: 30.2500, lng: 120.1600 }; + console.log('地图中心:', center.lng, center.lat); + + // 初始化地图 + pickerMap = new AMap.Map(containerId, { + zoom: 15, + center: [center.lng, center.lat], + viewMode: '2D', + resizeEnable: true }); - pos = { - lat: result.coords.latitude, - lng: result.coords.longitude, - accuracy: result.coords.accuracy, - altitude: result.coords.altitude, - speed: result.coords.speed, - timestamp: result.timestamp, - source: 'native' - }; - } else { - pos = await new Promise((resolve, reject) => { - navigator.geolocation.getCurrentPosition( - (result) => { - resolve({ - lat: result.coords.latitude, - lng: result.coords.longitude, - accuracy: result.coords.accuracy, - altitude: result.coords.altitude, - speed: result.coords.speed, - timestamp: result.timestamp, - source: 'browser' + + console.log('地图实例创建成功'); + + // 等待地图加载完成 + pickerMap.on('complete', () => { + console.log('地图加载完成'); + }); + + // 添加当前位置标记(蓝色) + new AMap.Marker({ + position: [center.lng, center.lat], + map: pickerMap, + title: '当前位置', + icon: new AMap.Icon({ + size: new AMap.Size(25, 34), + image: 'https://webapi.amap.com/theme/v1.3/markers/n/mark_b.png', + imageSize: new AMap.Size(25, 34) + }) + }); + + // 初始化地理编码插件 + pickerGeocoder = new AMap.Geocoder({ + radius: 1000, + extensions: 'all' + }); + + // 绑定点击事件 + pickerMap.on('click', (e) => { + const lng = e.lnglat.lng; + const lat = e.lnglat.lat; + console.log('地图点击:', lat, lng); + + // 清除旧标记 + if (pickerMarker) { + pickerMarker.setMap(null); + } + + // 添加新标记(红色) + pickerMarker = new AMap.Marker({ + position: [lng, lat], + map: pickerMap, + title: '投放点', + animation: 'AMAP_ANIMATION_DROP', + icon: new AMap.Icon({ + size: new AMap.Size(25, 34), + image: 'https://webapi.amap.com/theme/v1.3/markers/n/mark_r.png', + imageSize: new AMap.Size(25, 34) + }) + }); + + // 逆地理编码获取地址 + pickerGeocoder.getAddress([lng, lat], (status, result) => { + let address = ''; + let name = ''; + if (status === 'complete' && result.regeocode) { + address = result.regeocode.formattedAddress; + const comp = result.regeocode.addressComponent; + name = comp.building || comp.street || comp.township || '选定位置'; + } + if (onSelectCallback) { + onSelectCallback({ + lat, lng, + name: name || '选定位置', + address: address || `${lat.toFixed(6)}, ${lng.toFixed(6)}` }); - }, - (err) => reject(err), - { enableHighAccuracy: true, timeout: 10000, maximumAge: 0 } - ); + } + }); + + pickerMap.setCenter([lng, lat]); }); - } - return pos; - } - // 初始化高德地图(在位置更新页面显示) - function initAmap(containerId, lat, lng) { - if (typeof AMap === 'undefined') { - console.log('高德JS API未加载,无法显示地图'); + pickerMap.addControl(new AMap.Scale()); + console.log('选点地图初始化成功'); + return pickerMap; + } catch (e) { + console.error('选点地图初始化失败:', e); + const center = lastPosition || { lat: 30.2500, lng: 120.1600 }; + showStaticMap(containerId, center.lat, center.lng); return null; } + } + + // 搜索地点 + async function searchPlace(keyword, callback) { try { - const map = new AMap.Map(containerId, { - zoom: 15, - center: [lng, lat], - viewMode: '2D' + const AMap = await loadAmapScript(); + const placeSearch = new AMap.PlaceSearch({ + pageSize: 5, + pageIndex: 1, + extensions: 'all' }); - new AMap.Marker({ - position: [lng, lat], - map: map + placeSearch.search(keyword, (status, result) => { + if (status === 'complete' && result.info === 'OK') { + const pois = result.poiList.pois.map(poi => ({ + name: poi.name, + address: poi.address, + lat: poi.location.lat, + lng: poi.location.lng, + type: poi.type + })); + callback(null, pois); + } else { + callback(new Error('未找到相关地点'), []); + } }); - return map; } catch (e) { - console.error('高德地图初始化失败:', e); - return null; + callback(e, []); } } - // 获取当前位置(优先高德,fallback原生) - async function getCurrentPosition() { - // 检测高德Key - if (amapKeyValid === null) { - amapKeyValid = checkAmapKey(); + // 在选点地图上定位到指定坐标 + async function setPickerPosition(lat, lng, name, address) { + if (!pickerMap) return; + try { + const AMap = await loadAmapScript(); + if (pickerMarker) pickerMarker.setMap(null); + pickerMarker = new AMap.Marker({ + position: [lng, lat], + map: pickerMap, + title: name || '投放点', + animation: 'AMAP_ANIMATION_DROP', + icon: new AMap.Icon({ + size: new AMap.Size(25, 34), + image: 'https://webapi.amap.com/theme/v1.3/markers/n/mark_r.png', + imageSize: new AMap.Size(25, 34) + }) + }); + pickerMap.setCenter([lng, lat]); + pickerMap.setZoom(16); + } catch (e) { + console.error('设置选点位置失败:', e); } + } - // 优先尝试高德定位 - if (amapKeyValid) { + // 定位失败原因 + async function getLocationErrorReason() { + const reasons = []; + if (isCapacitor()) { try { - const pos = await getAmapPosition(); - lastPosition = pos; - console.log('高德定位成功:', pos.lat.toFixed(6), pos.lng.toFixed(6)); - return pos; - } catch (e) { - console.log('高德定位失败,尝试原生定位:', e.message); - } + const geo = getGeolocation(); + if (geo && geo.checkPermissions) { + const perm = await geo.checkPermissions(); + if (perm.location !== 'granted') reasons.push('App定位权限未授予'); + } + } catch (e) {} } - - // Fallback到原生定位 - try { - const pos = await getNativePosition(); - lastPosition = pos; - console.log('原生定位成功:', pos.lat.toFixed(6), pos.lng.toFixed(6)); - return pos; - } catch (e) { - console.error('原生定位也失败:', e); - // 返回默认位置(杭州附近) - lastPosition = { lat: 30.2500, lng: 120.1600, accuracy: 100, source: 'default' }; - return lastPosition; + if (!navigator.onLine) reasons.push('设备未连接网络'); + if (reasons.length === 0) { + reasons.push('GPS信号弱(请靠近窗户或到室外)'); + reasons.push('WiFi未开启(vivo需要WiFi辅助定位)'); } + return reasons; } - // 开始持续定位并上报 + // 上报 async function startReporting(soldierId, name, intervalMs = 10000) { if (isReporting) return; isReporting = true; - if (reportTimer) clearInterval(reportTimer); - - // 立即上报一次 await reportOnce(soldierId, name); - - // 定时上报 reportTimer = setInterval(async () => { if (!isReporting) return; await reportOnce(soldierId, name); }, intervalMs); - - console.log('位置自动上报已启动,间隔:', intervalMs, 'ms'); } - // 单次上报 async function reportOnce(soldierId, name) { try { const pos = await getCurrentPosition(); - await API.updateLocation({ - id: soldierId, - name: name, - lat: pos.lat, - lng: pos.lng - }); - console.log('位置已上报:', pos.lat.toFixed(6), pos.lng.toFixed(6), '来源:', pos.source || 'unknown'); + await API.updateLocation({ id: soldierId, name: name, lat: pos.lat, lng: pos.lng }); return pos; } catch (e) { - console.error('位置上报失败:', e); return null; } } - // 停止上报 function stopReporting() { isReporting = false; - if (reportTimer) { - clearInterval(reportTimer); - reportTimer = null; - } - console.log('位置自动上报已停止'); + if (reportTimer) { clearInterval(reportTimer); reportTimer = null; } } - // 获取最后已知位置 - function getLastPosition() { - return lastPosition; - } + // 工具 + function getLastPosition() { return lastPosition; } - // 计算两点距离(米) function calcDistance(lat1, lng1, lat2, lng2) { const R = 6371000; const dLat = (lat2 - lat1) * Math.PI / 180; const dLng = (lng2 - lng1) * Math.PI / 180; - const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + - Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * - Math.sin(dLng / 2) * Math.sin(dLng / 2); - const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - return R * c; + const a = Math.sin(dLat/2)**2 + Math.cos(lat1 * Math.PI/180) * Math.cos(lat2 * Math.PI/180) * Math.sin(dLng/2)**2; + return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); } - // 格式化精度显示 function formatAccuracy(acc) { if (!acc || acc <= 0) return '未知'; if (acc < 10) return '极高 (' + Math.round(acc) + 'm)'; if (acc < 50) return '高 (' + Math.round(acc) + 'm)'; - if (acc < 100) return '中 (' + Math.round(acc) + 'm)'; + if (acc < 200) return '中 (' + Math.round(acc) + 'm)'; return '低 (' + Math.round(acc) + 'm)'; } - // 获取定位来源描述 function getSourceText(source) { const map = { 'amap': '高德定位', 'native': 'GPS定位', 'browser': '浏览器定位', - 'default': '默认位置' + 'ip': '网络定位', + 'default': '默认位置', + 'manual': '手动设置' }; return map[source] || '未知'; } @@ -272,11 +569,12 @@ const LocationModule = (() => { stopReporting, getLastPosition, calcDistance, - isCapacitor, - checkPermission, formatAccuracy, getSourceText, - checkAmapKey, - initAmap + showMap, + getLocationErrorReason, + initPickerMap, + searchPlace, + setPickerPosition }; })(); diff --git a/src/单兵终端APP/www/index.html b/src/单兵终端APP/www/index.html index 26f3ea8..e3d81db 100644 --- a/src/单兵终端APP/www/index.html +++ b/src/单兵终端APP/www/index.html @@ -176,19 +176,53 @@
- +
-
-
🎯 推荐投放点列表
-
- +
+ +
+
+ + +
+ +
+
+ + +
+
+
🗺️
+
点击地图选择投放点
+
+
+ + +
+
📍 已选位置
+
请点击地图选择投放点
+
--
+
--
+
+ + +
+
🎯 附近推荐投放点
+
+ +
+
+ +
+
-
@@ -303,7 +337,7 @@
- +
diff --git a/src/单兵终端APP/www/js/app.js b/src/单兵终端APP/www/js/app.js index 36c9cdc..c5afd32 100644 --- a/src/单兵终端APP/www/js/app.js +++ b/src/单兵终端APP/www/js/app.js @@ -17,6 +17,7 @@ const App = (() => { let currentPage = 'home'; let pageStack = ['home']; let selectedDropPoint = null; + let mapSelectedPoint = null; // 地图选中的投放点 let pollTimer = null; // ===== 页面映射(用于Tab导航显示控制) ===== @@ -128,7 +129,8 @@ const App = (() => { updateHomeLocation(); break; case 'drop': - loadDropPoints(); + // 延迟初始化,确保页面完全显示后再加载地图 + setTimeout(() => loadDropPoints(), 500); break; case 'task': loadTaskInfo(); @@ -344,8 +346,24 @@ const App = (() => { } } - // ===== 加载投放点 ===== + // ===== 加载投放点(含地图选点) ===== async function loadDropPoints() { + // 初始化地图选点 + setTimeout(async () => { + await LocationModule.initPickerMap('drop-map', (point) => { + mapSelectedPoint = point; + // 更新UI显示 + const nameEl = document.getElementById('drop-selected-name'); + const addrEl = document.getElementById('drop-selected-address'); + const coordEl = document.getElementById('drop-selected-coords'); + if (nameEl) nameEl.textContent = point.name; + if (addrEl) addrEl.textContent = point.address; + if (coordEl) coordEl.textContent = `${point.lat.toFixed(6)}, ${point.lng.toFixed(6)}`; + showToast('📍 已选择:' + point.name); + }); + }, 300); + + // 加载推荐列表 const list = document.getElementById('drop-point-list'); list.innerHTML = '
加载中...
'; @@ -359,16 +377,16 @@ const App = (() => { list.innerHTML = points.map((p, i) => { const isSafe = p.safety_score >= 70; return ` -
+
📍 ${p.name}
安全系数: ${p.safety_score}% 距离: ${p.distance}m
${p.reason}
- +
+ ${isSafe ? '✅ 推荐投放' : '❌ 危险区域'} +
`; }).join(''); @@ -377,27 +395,119 @@ const App = (() => { } } - // ===== 选择投放点 ===== + // ===== 搜索地点 ===== + function searchDropPoint() { + const input = document.getElementById('drop-search-input'); + const resultsDiv = document.getElementById('drop-search-results'); + const keyword = input ? input.value.trim() : ''; + if (!keyword) { + showToast('请输入搜索关键词'); + return; + } + + resultsDiv.innerHTML = '
搜索中...
'; + + LocationModule.searchPlace(keyword, (err, pois) => { + if (err || pois.length === 0) { + resultsDiv.innerHTML = '
未找到相关地点
'; + return; + } + + resultsDiv.innerHTML = pois.map((p, i) => ` +
+
${p.name}
+
${p.address}
+
+ `).join(''); + + // 存储搜索结果供点击使用 + window._searchResults = pois; + }); + } + + // 选择搜索结果 + async function selectSearchResult(index) { + const pois = window._searchResults || []; + const p = pois[index]; + if (!p) return; + + // 在地图上定位 + await LocationModule.setPickerPosition(p.lat, p.lng, p.name, p.address); + + // 更新选中状态 + mapSelectedPoint = { + lat: p.lat, + lng: p.lng, + name: p.name, + address: p.address + }; + + // 更新UI + const nameEl = document.getElementById('drop-selected-name'); + const addrEl = document.getElementById('drop-selected-address'); + const coordEl = document.getElementById('drop-selected-coords'); + if (nameEl) nameEl.textContent = p.name; + if (addrEl) addrEl.textContent = p.address; + if (coordEl) coordEl.textContent = `${p.lat.toFixed(6)}, ${p.lng.toFixed(6)}`; + + // 清空搜索结果 + const resultsDiv = document.getElementById('drop-search-results'); + if (resultsDiv) resultsDiv.innerHTML = ''; + + showToast('📍 已定位:' + p.name); + } + + // ===== 选择投放点(从列表) ===== function selectDropPoint(index) { - API.getDropPoints().then(points => { + API.getDropPoints().then(async points => { const p = points[index]; if (p.safety_score < 70) { - showToast('已标记避开此点'); + showToast('⚠️ 该区域危险,建议选择其他投放点'); return; } selectedDropPoint = p; + + // 同时在地图上标记 + await LocationModule.setPickerPosition(p.lat, p.lng, p.name, p.address); + + // 更新选中显示 + mapSelectedPoint = { + lat: p.lat, + lng: p.lng, + name: p.name, + address: p.address || p.name + }; + const nameEl = document.getElementById('drop-selected-name'); + const addrEl = document.getElementById('drop-selected-address'); + const coordEl = document.getElementById('drop-selected-coords'); + if (nameEl) nameEl.textContent = p.name; + if (addrEl) addrEl.textContent = p.address || '安全系数 ' + p.safety_score + '%'; + if (coordEl) coordEl.textContent = `${p.lat.toFixed(6)}, ${p.lng.toFixed(6)}`; + showToast('已选择:' + p.name); - router('demand'); - document.getElementById('demand-drop-display').textContent = p.name + '(安全系数' + p.safety_score + '%)'; }); } function confirmDropPoint() { - if (!selectedDropPoint) { - showToast('请先选择一个投放点'); + // 优先使用地图选中的点 + if (mapSelectedPoint) { + selectedDropPoint = mapSelectedPoint; + router('demand'); + const display = document.getElementById('demand-drop-display'); + if (display) display.textContent = mapSelectedPoint.name; + showToast('✅ 已确认投放点:' + mapSelectedPoint.name); + return; + } + // fallback到列表选中的点 + if (selectedDropPoint) { + router('demand'); + const display = document.getElementById('demand-drop-display'); + if (display) display.textContent = selectedDropPoint.name; return; } - router('demand'); + showToast('请先点击地图或列表选择一个投放点'); } // ===== 加载任务信息 ===== @@ -478,30 +588,68 @@ const App = (() => { } } + // ===== 手动修正位置 ===== + function manualSetLocation() { + const input = prompt('请输入您的坐标(格式:纬度,经度)\n示例:28.2280,112.9388\n长沙大约:28.2280,112.9388'); + if (!input) return; + const parts = input.split(',').map(s => parseFloat(s.trim())); + if (parts.length === 2 && !isNaN(parts[0]) && !isNaN(parts[1])) { + const pos = { lat: parts[0], lng: parts[1], accuracy: 10, source: 'manual' }; + LocationModule.lastPosition = pos; + const curEl = document.getElementById('loc-current'); + if (curEl) curEl.textContent = `${pos.lat.toFixed(6)}, ${pos.lng.toFixed(6)}`; + const accEl = document.getElementById('loc-accuracy'); + if (accEl) accEl.textContent = '手动设置 · 精确'; + updateHomeLocation(); + showToast('位置已手动修正'); + // 刷新地图 + const mapContainer = document.getElementById('loc-map'); + if (mapContainer) { + mapContainer.innerHTML = ''; + setTimeout(() => LocationModule.showMap('loc-map', pos.lat, pos.lng), 100); + } + } else { + showToast('格式错误,请使用:纬度,经度'); + } + } + // ===== 刷新位置 ===== async function refreshLocation() { const curEl = document.getElementById('loc-current'); if (curEl) curEl.textContent = '定位中...'; + try { const pos = await LocationModule.getCurrentPosition(); + const sourceText = LocationModule.getSourceText(pos.source); + + // 更新当前位置显示 if (curEl) { - const sourceText = LocationModule.getSourceText(pos.source); if (pos.source === 'default') { - curEl.innerHTML = `${pos.lat.toFixed(6)}, ${pos.lng.toFixed(6)} (${sourceText})`; + curEl.innerHTML = `${pos.lat.toFixed(4)}, ${pos.lng.toFixed(4)}`; + } else if (pos.source === 'ip') { + curEl.innerHTML = `${pos.lat.toFixed(4)}, ${pos.lng.toFixed(4)} (${sourceText})`; } else { curEl.textContent = `${pos.lat.toFixed(6)}, ${pos.lng.toFixed(6)}`; } } + // 更新精度显示 const accEl = document.getElementById('loc-accuracy'); if (accEl) { - const sourceText = LocationModule.getSourceText(pos.source); - accEl.textContent = LocationModule.formatAccuracy(pos.accuracy) + ' · ' + sourceText; + let text = LocationModule.formatAccuracy(pos.accuracy) + ' · ' + sourceText; + if (pos.source === 'default') { + text = '❌ 定位失败 · 使用默认坐标'; + } else if (pos.source === 'ip') { + text = '📡 ' + LocationModule.formatAccuracy(pos.accuracy) + ' · ' + sourceText + (pos.city ? ' (' + pos.city + ')' : ''); + } + accEl.textContent = text; } + // 更新上报时间 const reportEl = document.getElementById('loc-last-report'); if (reportEl) reportEl.textContent = new Date().toTimeString().split(' ')[0]; + // 更新偏移距离 const offsetEl = document.getElementById('loc-offset'); if (offsetEl) { offsetEl.textContent = '计算中...'; @@ -512,24 +660,35 @@ const App = (() => { }, 500); } - // 初始化高德地图 + // 初始化/更新地图 const mapContainer = document.getElementById('loc-map'); - if (mapContainer && pos.source !== 'default') { - mapContainer.innerHTML = ''; - mapContainer.style.display = 'block'; - mapContainer.style.border = 'none'; - setTimeout(() => { - LocationModule.initAmap('loc-map', pos.lat, pos.lng); - }, 100); - } else if (mapContainer && pos.source === 'default') { - mapContainer.innerHTML = '
🚫 定位失败,无法加载地图
请检查GPS权限和定位服务
'; + if (mapContainer) { + if (pos.source === 'default') { + // 定位完全失败,显示诊断信息 + const reasons = await LocationModule.getLocationErrorReason(); + mapContainer.innerHTML = ` +
+
🚫
+
定位失败
+
+ ${reasons.map(r => '• ' + r).join('
')} +
+
+ 💡 建议:开启WiFi + GPS + 到窗边 +
+
`; + } else { + mapContainer.innerHTML = ''; + mapContainer.style.border = 'none'; + setTimeout(() => LocationModule.showMap('loc-map', pos.lat, pos.lng), 100); + } } - // 更新首页位置显示 updateHomeLocation(); } catch (e) { - if (curEl) curEl.textContent = '定位失败'; + if (curEl) curEl.textContent = '定位异常'; + console.error('refreshLocation错误:', e); } } @@ -601,10 +760,13 @@ const App = (() => { submitDemand, selectDropPoint, confirmDropPoint, + searchDropPoint, + selectSearchResult, updateDropPoint, addAnnotate, triggerSOS, refreshLocation, + manualSetLocation, showToast, showServerConfig, toggleSwitch, diff --git a/src/单兵终端APP/www/js/location.js b/src/单兵终端APP/www/js/location.js index 938c2e2..eddcf63 100644 --- a/src/单兵终端APP/www/js/location.js +++ b/src/单兵终端APP/www/js/location.js @@ -1,21 +1,157 @@ /** - * GPS定位模块 - * 优先使用高德地图定位,fallback到Capacitor原生定位 + * GPS定位模块 + 高德地图动态显示 + * + * 地图显示方案: + * 1. 优先使用高德JS动态地图(支持缩放、拖动、标记) + * 2. 动态地图失败时fallback到静态地图图片 + * + * 关键修复(基于搜索结果): + * - 使用AMapLoader异步加载JS API,确保完全加载后再初始化 + * - 确保地图容器有明确宽高(SPA页面切换常见问题) + * - AndroidManifest.xml添加usesCleartextTraffic + * - 页面可见后再初始化地图 */ const LocationModule = (() => { let lastPosition = null; - let watchId = null; let reportTimer = null; let isReporting = false; - let amapKeyValid = null; // null=未检测, true=有效, false=无效 + let amapLoaded = false; // JS API是否加载完成 + let amapLoading = false; // 是否正在加载 + let currentMap = null; // 当前地图实例 + const AMAP_KEY = 'c014127be1ea5a1efead8419c94fbaba'; - // 检查是否在Capacitor环境 + // ========== 高德JS API异步加载 ========== + // 高德JS API异步加载(关键:使用callback参数确保完全加载) + function loadAmapScript() { + return new Promise((resolve, reject) => { + // 已经加载过 + if (typeof AMap !== 'undefined' && AMap.Map) { + amapLoaded = true; + resolve(AMap); + return; + } + // 正在加载中,等待 + if (amapLoading) { + const checkInterval = setInterval(() => { + if (typeof AMap !== 'undefined' && AMap.Map) { + clearInterval(checkInterval); + amapLoaded = true; + resolve(AMap); + } + }, 200); + setTimeout(() => { + clearInterval(checkInterval); + reject(new Error('高德JS加载超时')); + }, 15000); + return; + } + + amapLoading = true; + + // 关键修复:使用callback参数!高德JS API 2.0必须通过callback通知加载完成 + window._amapCallback = function() { + amapLoaded = true; + amapLoading = false; + if (typeof AMap !== 'undefined') { + resolve(AMap); + } else { + reject(new Error('AMap未定义')); + } + }; + + const script = document.createElement('script'); + script.type = 'text/javascript'; + script.charset = 'utf-8'; + script.src = `https://webapi.amap.com/maps?v=2.0&key=${AMAP_KEY}&callback=_amapCallback&plugin=AMap.Geolocation,AMap.Scale,AMap.Marker,AMap.Geocoder,AMap.PlaceSearch`; + script.onerror = () => { + amapLoading = false; + reject(new Error('高德JS加载失败')); + }; + document.head.appendChild(script); + }); + } + + // ========== 动态地图初始化 ========== + async function initDynamicMap(containerId, lat, lng) { + try { + const AMap = await loadAmapScript(); + const container = document.getElementById(containerId); + if (!container) throw new Error('地图容器不存在'); + + // 关键修复:确保容器有明确宽高 + container.style.width = '100%'; + container.style.height = '200px'; + container.style.minHeight = '200px'; + container.style.display = 'block'; + container.style.border = 'none'; + container.style.background = '#f5f5f5'; + + // 销毁旧地图 + if (currentMap) { + currentMap.destroy(); + currentMap = null; + } + + // 初始化地图 + currentMap = new AMap.Map(containerId, { + zoom: 15, + center: [lng, lat], + viewMode: '2D', + resizeEnable: true + }); + + // 添加标记 + new AMap.Marker({ + position: [lng, lat], + map: currentMap, + title: '当前位置' + }); + + // 添加缩放控件 + currentMap.addControl(new AMap.Scale()); + + return currentMap; + } catch (e) { + console.error('动态地图初始化失败:', e); + throw e; + } + } + + // ========== 静态地图图片(fallback)========== + function showStaticMap(containerId, lat, lng) { + const container = document.getElementById(containerId); + if (!container) return; + const w = container.clientWidth || 350; + const h = 200; + const imgUrl = `https://restapi.amap.com/v3/staticmap?location=${lng},${lat}&zoom=15&size=${w}*${h}&markers=mid,,A:${lng},${lat}&key=${AMAP_KEY}`; + container.innerHTML = ``; + } + + // ========== 统一地图显示入口 ========== + async function showMap(containerId, lat, lng) { + const container = document.getElementById(containerId); + if (!container) return; + + // 先清空容器 + container.innerHTML = '
🗺️
地图加载中...
'; + + try { + // 尝试动态地图 + await initDynamicMap(containerId, lat, lng); + console.log('✅ 动态地图加载成功'); + } catch (e) { + console.log('动态地图失败,使用静态地图:', e.message); + // fallback到静态地图 + showStaticMap(containerId, lat, lng); + } + } + + // ========== 定位相关 ========== function isCapacitor() { return typeof Capacitor !== 'undefined' && Capacitor.isNativePlatform && Capacitor.isNativePlatform(); } - // 获取Geolocation插件 function getGeolocation() { if (isCapacitor() && Capacitor.Plugins && Capacitor.Plugins.Geolocation) { return Capacitor.Plugins.Geolocation; @@ -23,23 +159,11 @@ const LocationModule = (() => { return null; } - // 检查高德Key是否有效 - function checkAmapKey() { - const script = document.querySelector('script[src*="webapi.amap.com"]'); - if (!script) return false; - const src = script.getAttribute('src'); - return src && !src.includes('YOUR_AMAP_KEY'); - } - - // 高德地图定位 + // 高德定位 async function getAmapPosition() { + const AMap = await loadAmapScript(); return new Promise((resolve, reject) => { - if (typeof AMap === 'undefined') { - reject(new Error('高德JS API未加载')); - return; - } - - const geolocation = new AMap.Geolocation({ + const geo = new AMap.Geolocation({ enableHighAccuracy: true, timeout: 15000, showButton: false, @@ -48,220 +172,393 @@ const LocationModule = (() => { panToLocation: false, zoomToAccuracy: false }); - - geolocation.getCurrentPosition((status, result) => { + geo.getCurrentPosition((status, result) => { if (status === 'complete' && result.position) { resolve({ lat: result.position.lat, lng: result.position.lng, accuracy: result.accuracy || 50, - altitude: null, - speed: null, - timestamp: Date.now(), source: 'amap' }); } else { - reject(new Error('高德定位失败: ' + (result.message || '未知错误'))); + reject(new Error(result.message || '高德定位失败')); } }); }); } - // 检查定位权限 - async function checkPermission() { - if (!isCapacitor()) { - return { location: 'granted' }; - } - try { - const geo = getGeolocation(); - if (geo && geo.requestPermissions) { - const perm = await geo.requestPermissions(); - return perm; + // Capacitor原生定位 + async function getCapacitorPosition() { + const geo = getGeolocation(); + if (!geo) throw new Error('Capacitor Geolocation不可用'); + const result = await geo.getCurrentPosition({ + enableHighAccuracy: true, + timeout: 15000, + enableLocationFallback: true + }); + return { + lat: result.coords.latitude, + lng: result.coords.longitude, + accuracy: result.coords.accuracy, + source: 'native' + }; + } + + // 浏览器定位 + async function getBrowserPosition() { + return new Promise((resolve, reject) => { + if (!navigator.geolocation) { + reject(new Error('浏览器不支持定位')); + return; + } + navigator.geolocation.getCurrentPosition( + (result) => { + resolve({ + lat: result.coords.latitude, + lng: result.coords.longitude, + accuracy: result.coords.accuracy, + source: 'browser' + }); + }, + (err) => reject(new Error(err.message)), + { enableHighAccuracy: true, timeout: 15000, maximumAge: 0 } + ); + }); + } + + // IP定位 + async function getIpPosition() { + const services = [ + { url: 'https://ipapi.co/json/', parse: (d) => ({ lat: d.latitude, lng: d.longitude, city: d.city }) }, + { url: 'https://ip-api.com/json/', parse: (d) => ({ lat: d.lat, lng: d.lon, city: d.city }) } + ]; + for (const svc of services) { + try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 3000); + const resp = await fetch(svc.url, { signal: controller.signal }); + clearTimeout(timeoutId); + const data = await resp.json(); + const parsed = svc.parse(data); + if (parsed.lat && parsed.lng) { + return { + lat: parsed.lat, + lng: parsed.lng, + accuracy: 5000, + source: 'ip', + city: parsed.city + }; + } + } catch (e) { + console.log('IP定位失败:', svc.url); } - return { location: 'granted' }; - } catch (e) { - console.error('权限检查失败:', e); - return { location: 'denied' }; } + throw new Error('IP定位失败'); } - // 原生GPS定位 - async function getNativePosition() { - const geo = getGeolocation(); - let pos; + // 统一入口 + async function getCurrentPosition() { + const errors = []; - if (geo) { - const result = await geo.getCurrentPosition({ - enableHighAccuracy: true, - timeout: 10000 + // 高德定位 + try { + const pos = await getAmapPosition(); + lastPosition = pos; + console.log('✅ 高德定位:', pos.lat.toFixed(4), pos.lng.toFixed(4)); + return pos; + } catch (e) { errors.push('高德:' + e.message); } + + // 原生定位 + try { + const pos = await getCapacitorPosition(); + lastPosition = pos; + console.log('✅ 原生定位:', pos.lat.toFixed(4), pos.lng.toFixed(4)); + return pos; + } catch (e) { errors.push('原生:' + e.message); } + + // 浏览器定位 + try { + const pos = await getBrowserPosition(); + lastPosition = pos; + console.log('✅ 浏览器定位:', pos.lat.toFixed(4), pos.lng.toFixed(4)); + return pos; + } catch (e) { errors.push('浏览器:' + e.message); } + + // IP定位 + try { + const pos = await getIpPosition(); + lastPosition = pos; + console.log('✅ IP定位:', pos.lat.toFixed(4), pos.lng.toFixed(4), pos.city); + return pos; + } catch (e) { errors.push('IP:' + e.message); } + + // 默认 + console.error('❌ 全部失败:', errors.join('; ')); + lastPosition = { lat: 30.2500, lng: 120.1600, accuracy: 100, source: 'default' }; + return lastPosition; + } + + // ========== 地图选点功能 ========== + let pickerMap = null; + let pickerMarker = null; + let pickerGeocoder = null; + + // 初始化选点地图 + async function initPickerMap(containerId, onSelectCallback) { + try { + console.log('开始加载高德JS API...'); + const AMap = await loadAmapScript(); + console.log('高德JS API加载完成'); + + const container = document.getElementById(containerId); + if (!container) { + console.error('地图容器不存在:', containerId); + return null; + } + + // 关键:确保容器可见且有明确尺寸 + container.style.width = '100%'; + container.style.height = '280px'; + container.style.minHeight = '280px'; + container.style.display = 'block'; + container.style.position = 'relative'; + container.innerHTML = ''; + + // 检查容器尺寸 + const rect = container.getBoundingClientRect(); + console.log('容器尺寸:', rect.width, 'x', rect.height); + if (rect.width === 0 || rect.height === 0) { + console.warn('容器尺寸为0,延迟初始化'); + // 如果尺寸为0,延迟100ms再试 + await new Promise(r => setTimeout(r, 300)); + } + + // 销毁旧地图 + if (pickerMap) { + pickerMap.destroy(); + pickerMap = null; + pickerMarker = null; + } + + // 获取当前位置作为中心点 + const center = lastPosition || { lat: 30.2500, lng: 120.1600 }; + console.log('地图中心:', center.lng, center.lat); + + // 初始化地图 + pickerMap = new AMap.Map(containerId, { + zoom: 15, + center: [center.lng, center.lat], + viewMode: '2D', + resizeEnable: true }); - pos = { - lat: result.coords.latitude, - lng: result.coords.longitude, - accuracy: result.coords.accuracy, - altitude: result.coords.altitude, - speed: result.coords.speed, - timestamp: result.timestamp, - source: 'native' - }; - } else { - pos = await new Promise((resolve, reject) => { - navigator.geolocation.getCurrentPosition( - (result) => { - resolve({ - lat: result.coords.latitude, - lng: result.coords.longitude, - accuracy: result.coords.accuracy, - altitude: result.coords.altitude, - speed: result.coords.speed, - timestamp: result.timestamp, - source: 'browser' + + console.log('地图实例创建成功'); + + // 等待地图加载完成 + pickerMap.on('complete', () => { + console.log('地图加载完成'); + }); + + // 添加当前位置标记(蓝色) + new AMap.Marker({ + position: [center.lng, center.lat], + map: pickerMap, + title: '当前位置', + icon: new AMap.Icon({ + size: new AMap.Size(25, 34), + image: 'https://webapi.amap.com/theme/v1.3/markers/n/mark_b.png', + imageSize: new AMap.Size(25, 34) + }) + }); + + // 初始化地理编码插件 + pickerGeocoder = new AMap.Geocoder({ + radius: 1000, + extensions: 'all' + }); + + // 绑定点击事件 + pickerMap.on('click', (e) => { + const lng = e.lnglat.lng; + const lat = e.lnglat.lat; + console.log('地图点击:', lat, lng); + + // 清除旧标记 + if (pickerMarker) { + pickerMarker.setMap(null); + } + + // 添加新标记(红色) + pickerMarker = new AMap.Marker({ + position: [lng, lat], + map: pickerMap, + title: '投放点', + animation: 'AMAP_ANIMATION_DROP', + icon: new AMap.Icon({ + size: new AMap.Size(25, 34), + image: 'https://webapi.amap.com/theme/v1.3/markers/n/mark_r.png', + imageSize: new AMap.Size(25, 34) + }) + }); + + // 逆地理编码获取地址 + pickerGeocoder.getAddress([lng, lat], (status, result) => { + let address = ''; + let name = ''; + if (status === 'complete' && result.regeocode) { + address = result.regeocode.formattedAddress; + const comp = result.regeocode.addressComponent; + name = comp.building || comp.street || comp.township || '选定位置'; + } + if (onSelectCallback) { + onSelectCallback({ + lat, lng, + name: name || '选定位置', + address: address || `${lat.toFixed(6)}, ${lng.toFixed(6)}` }); - }, - (err) => reject(err), - { enableHighAccuracy: true, timeout: 10000, maximumAge: 0 } - ); + } + }); + + pickerMap.setCenter([lng, lat]); }); - } - return pos; - } - // 初始化高德地图(在位置更新页面显示) - function initAmap(containerId, lat, lng) { - if (typeof AMap === 'undefined') { - console.log('高德JS API未加载,无法显示地图'); + pickerMap.addControl(new AMap.Scale()); + console.log('选点地图初始化成功'); + return pickerMap; + } catch (e) { + console.error('选点地图初始化失败:', e); + const center = lastPosition || { lat: 30.2500, lng: 120.1600 }; + showStaticMap(containerId, center.lat, center.lng); return null; } + } + + // 搜索地点 + async function searchPlace(keyword, callback) { try { - const map = new AMap.Map(containerId, { - zoom: 15, - center: [lng, lat], - viewMode: '2D' + const AMap = await loadAmapScript(); + const placeSearch = new AMap.PlaceSearch({ + pageSize: 5, + pageIndex: 1, + extensions: 'all' }); - new AMap.Marker({ - position: [lng, lat], - map: map + placeSearch.search(keyword, (status, result) => { + if (status === 'complete' && result.info === 'OK') { + const pois = result.poiList.pois.map(poi => ({ + name: poi.name, + address: poi.address, + lat: poi.location.lat, + lng: poi.location.lng, + type: poi.type + })); + callback(null, pois); + } else { + callback(new Error('未找到相关地点'), []); + } }); - return map; } catch (e) { - console.error('高德地图初始化失败:', e); - return null; + callback(e, []); } } - // 获取当前位置(优先高德,fallback原生) - async function getCurrentPosition() { - // 检测高德Key - if (amapKeyValid === null) { - amapKeyValid = checkAmapKey(); + // 在选点地图上定位到指定坐标 + async function setPickerPosition(lat, lng, name, address) { + if (!pickerMap) return; + try { + const AMap = await loadAmapScript(); + if (pickerMarker) pickerMarker.setMap(null); + pickerMarker = new AMap.Marker({ + position: [lng, lat], + map: pickerMap, + title: name || '投放点', + animation: 'AMAP_ANIMATION_DROP', + icon: new AMap.Icon({ + size: new AMap.Size(25, 34), + image: 'https://webapi.amap.com/theme/v1.3/markers/n/mark_r.png', + imageSize: new AMap.Size(25, 34) + }) + }); + pickerMap.setCenter([lng, lat]); + pickerMap.setZoom(16); + } catch (e) { + console.error('设置选点位置失败:', e); } + } - // 优先尝试高德定位 - if (amapKeyValid) { + // 定位失败原因 + async function getLocationErrorReason() { + const reasons = []; + if (isCapacitor()) { try { - const pos = await getAmapPosition(); - lastPosition = pos; - console.log('高德定位成功:', pos.lat.toFixed(6), pos.lng.toFixed(6)); - return pos; - } catch (e) { - console.log('高德定位失败,尝试原生定位:', e.message); - } + const geo = getGeolocation(); + if (geo && geo.checkPermissions) { + const perm = await geo.checkPermissions(); + if (perm.location !== 'granted') reasons.push('App定位权限未授予'); + } + } catch (e) {} } - - // Fallback到原生定位 - try { - const pos = await getNativePosition(); - lastPosition = pos; - console.log('原生定位成功:', pos.lat.toFixed(6), pos.lng.toFixed(6)); - return pos; - } catch (e) { - console.error('原生定位也失败:', e); - // 返回默认位置(杭州附近) - lastPosition = { lat: 30.2500, lng: 120.1600, accuracy: 100, source: 'default' }; - return lastPosition; + if (!navigator.onLine) reasons.push('设备未连接网络'); + if (reasons.length === 0) { + reasons.push('GPS信号弱(请靠近窗户或到室外)'); + reasons.push('WiFi未开启(vivo需要WiFi辅助定位)'); } + return reasons; } - // 开始持续定位并上报 + // 上报 async function startReporting(soldierId, name, intervalMs = 10000) { if (isReporting) return; isReporting = true; - if (reportTimer) clearInterval(reportTimer); - - // 立即上报一次 await reportOnce(soldierId, name); - - // 定时上报 reportTimer = setInterval(async () => { if (!isReporting) return; await reportOnce(soldierId, name); }, intervalMs); - - console.log('位置自动上报已启动,间隔:', intervalMs, 'ms'); } - // 单次上报 async function reportOnce(soldierId, name) { try { const pos = await getCurrentPosition(); - await API.updateLocation({ - id: soldierId, - name: name, - lat: pos.lat, - lng: pos.lng - }); - console.log('位置已上报:', pos.lat.toFixed(6), pos.lng.toFixed(6), '来源:', pos.source || 'unknown'); + await API.updateLocation({ id: soldierId, name: name, lat: pos.lat, lng: pos.lng }); return pos; } catch (e) { - console.error('位置上报失败:', e); return null; } } - // 停止上报 function stopReporting() { isReporting = false; - if (reportTimer) { - clearInterval(reportTimer); - reportTimer = null; - } - console.log('位置自动上报已停止'); + if (reportTimer) { clearInterval(reportTimer); reportTimer = null; } } - // 获取最后已知位置 - function getLastPosition() { - return lastPosition; - } + // 工具 + function getLastPosition() { return lastPosition; } - // 计算两点距离(米) function calcDistance(lat1, lng1, lat2, lng2) { const R = 6371000; const dLat = (lat2 - lat1) * Math.PI / 180; const dLng = (lng2 - lng1) * Math.PI / 180; - const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + - Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * - Math.sin(dLng / 2) * Math.sin(dLng / 2); - const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - return R * c; + const a = Math.sin(dLat/2)**2 + Math.cos(lat1 * Math.PI/180) * Math.cos(lat2 * Math.PI/180) * Math.sin(dLng/2)**2; + return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); } - // 格式化精度显示 function formatAccuracy(acc) { if (!acc || acc <= 0) return '未知'; if (acc < 10) return '极高 (' + Math.round(acc) + 'm)'; if (acc < 50) return '高 (' + Math.round(acc) + 'm)'; - if (acc < 100) return '中 (' + Math.round(acc) + 'm)'; + if (acc < 200) return '中 (' + Math.round(acc) + 'm)'; return '低 (' + Math.round(acc) + 'm)'; } - // 获取定位来源描述 function getSourceText(source) { const map = { 'amap': '高德定位', 'native': 'GPS定位', 'browser': '浏览器定位', - 'default': '默认位置' + 'ip': '网络定位', + 'default': '默认位置', + 'manual': '手动设置' }; return map[source] || '未知'; } @@ -272,11 +569,12 @@ const LocationModule = (() => { stopReporting, getLastPosition, calcDistance, - isCapacitor, - checkPermission, formatAccuracy, getSourceText, - checkAmapKey, - initAmap + showMap, + getLocationErrorReason, + initPickerMap, + searchPlace, + setPickerPosition }; })();