// 地图工具函数 - 内联腾讯地图SDK核心代码 const DEFAULT_LOCALE = 'zh-CN' const permissionTexts = { 'zh-CN': { locationTitle: '位置信息授权', locationNeed: '需要获取您的位置信息以展示附近设备,请在"设置-权限管理"中开启定位权限。', locationDenied: '未授权定位,将无法展示附近设备。您可以稍后在"设置-权限管理"中重新开启定位权限。', goToSettings: '去设置', later: '暂不', gotIt: '知道了' }, 'en-US': { locationTitle: 'Location Permission', locationNeed: 'We need your location to show nearby devices. Please enable location in "Settings > Permissions".', locationDenied: 'Location access is disabled. You can re-enable it later in "Settings > Permissions".', goToSettings: 'Set', later: 'Skip', gotIt: 'OK' } } const getCurrentLocale = () => { try { const saved = uni.getStorageSync('language') if (saved && permissionTexts[saved]) { return saved } } catch (_) {} return DEFAULT_LOCALE } const getPermissionText = (key) => { const locale = getCurrentLocale() const texts = permissionTexts[locale] || permissionTexts[DEFAULT_LOCALE] return texts[key] || permissionTexts[DEFAULT_LOCALE][key] || '' } // 兼容多端:部分平台(如支付宝小程序)经纬度可能返回字符串 // 统一先转为 Number 再做 toFixed,避免 "toFixed is not a function" const toFixedNumber = (value, digits = 5) => { const n = Number(value) if (!Number.isFinite(n)) return null return Number(n.toFixed(digits)) } // ============================= // 支付宝小程序:高德地图 WebService(需要 key + 安全密钥签名) // 说明:支付宝小程序下仅使用高德 key(你提供的 key/secret),避免腾讯地图 key 不可用的问题 // ============================= // #ifdef MP-ALIPAY const AMAP_KEY = '1c6df40606891377b33576e7876af6ac' const AMAP_SECRET = '00ea790d0b24190174c598199b183750' const AMAP_BASE_URL = 'https://restapi.amap.com/v3/' // 轻量 MD5(RFC1321)实现:用于高德 WebService 的 sig 参数 // 仅在 MP-ALIPAY 编译进包,避免影响其它端体积 function md5(str) { /* eslint-disable */ function cmn(q, a, b, x, s, t) { a = add32(add32(a, q), add32(x, t)); return add32((a << s) | (a >>> (32 - s)), b); } function ff(a, b, c, d, x, s, t) { return cmn((b & c) | ((~b) & d), a, b, x, s, t); } function gg(a, b, c, d, x, s, t) { return cmn((b & d) | (c & (~d)), a, b, x, s, t); } function hh(a, b, c, d, x, s, t) { return cmn(b ^ c ^ d, a, b, x, s, t); } function ii(a, b, c, d, x, s, t) { return cmn(c ^ (b | (~d)), a, b, x, s, t); } function md5cycle(x, k) { let a = x[0], b = x[1], c = x[2], d = x[3]; a = ff(a, b, c, d, k[0], 7, -680876936); d = ff(d, a, b, c, k[1], 12, -389564586); c = ff(c, d, a, b, k[2], 17, 606105819); b = ff(b, c, d, a, k[3], 22, -1044525330); a = ff(a, b, c, d, k[4], 7, -176418897); d = ff(d, a, b, c, k[5], 12, 1200080426); c = ff(c, d, a, b, k[6], 17, -1473231341); b = ff(b, c, d, a, k[7], 22, -45705983); a = ff(a, b, c, d, k[8], 7, 1770035416); d = ff(d, a, b, c, k[9], 12, -1958414417); c = ff(c, d, a, b, k[10], 17, -42063); b = ff(b, c, d, a, k[11], 22, -1990404162); a = ff(a, b, c, d, k[12], 7, 1804603682); d = ff(d, a, b, c, k[13], 12, -40341101); c = ff(c, d, a, b, k[14], 17, -1502002290); b = ff(b, c, d, a, k[15], 22, 1236535329); a = gg(a, b, c, d, k[1], 5, -165796510); d = gg(d, a, b, c, k[6], 9, -1069501632); c = gg(c, d, a, b, k[11], 14, 643717713); b = gg(b, c, d, a, k[0], 20, -373897302); a = gg(a, b, c, d, k[5], 5, -701558691); d = gg(d, a, b, c, k[10], 9, 38016083); c = gg(c, d, a, b, k[15], 14, -660478335); b = gg(b, c, d, a, k[4], 20, -405537848); a = gg(a, b, c, d, k[9], 5, 568446438); d = gg(d, a, b, c, k[14], 9, -1019803690); c = gg(c, d, a, b, k[3], 14, -187363961); b = gg(b, c, d, a, k[8], 20, 1163531501); a = gg(a, b, c, d, k[13], 5, -1444681467); d = gg(d, a, b, c, k[2], 9, -51403784); c = gg(c, d, a, b, k[7], 14, 1735328473); b = gg(b, c, d, a, k[12], 20, -1926607734); a = hh(a, b, c, d, k[5], 4, -378558); d = hh(d, a, b, c, k[8], 11, -2022574463); c = hh(c, d, a, b, k[11], 16, 1839030562); b = hh(b, c, d, a, k[14], 23, -35309556); a = hh(a, b, c, d, k[1], 4, -1530992060); d = hh(d, a, b, c, k[4], 11, 1272893353); c = hh(c, d, a, b, k[7], 16, -155497632); b = hh(b, c, d, a, k[10], 23, -1094730640); a = hh(a, b, c, d, k[13], 4, 681279174); d = hh(d, a, b, c, k[0], 11, -358537222); c = hh(c, d, a, b, k[3], 16, -722521979); b = hh(b, c, d, a, k[6], 23, 76029189); a = hh(a, b, c, d, k[9], 4, -640364487); d = hh(d, a, b, c, k[12], 11, -421815835); c = hh(c, d, a, b, k[15], 16, 530742520); b = hh(b, c, d, a, k[2], 23, -995338651); a = ii(a, b, c, d, k[0], 6, -198630844); d = ii(d, a, b, c, k[7], 10, 1126891415); c = ii(c, d, a, b, k[14], 15, -1416354905); b = ii(b, c, d, a, k[5], 21, -57434055); a = ii(a, b, c, d, k[12], 6, 1700485571); d = ii(d, a, b, c, k[3], 10, -1894986606); c = ii(c, d, a, b, k[10], 15, -1051523); b = ii(b, c, d, a, k[1], 21, -2054922799); a = ii(a, b, c, d, k[8], 6, 1873313359); d = ii(d, a, b, c, k[15], 10, -30611744); c = ii(c, d, a, b, k[6], 15, -1560198380); b = ii(b, c, d, a, k[13], 21, 1309151649); a = ii(a, b, c, d, k[4], 6, -145523070); d = ii(d, a, b, c, k[11], 10, -1120210379); c = ii(c, d, a, b, k[2], 15, 718787259); b = ii(b, c, d, a, k[9], 21, -343485551); x[0] = add32(a, x[0]); x[1] = add32(b, x[1]); x[2] = add32(c, x[2]); x[3] = add32(d, x[3]); } function md5blk(s) { const md5blks = []; for (let i = 0; i < 64; i += 4) { md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24); } return md5blks; } function md5blk_array(a) { const md5blks = []; for (let i = 0; i < 64; i += 4) { md5blks[i >> 2] = a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24); } return md5blks; } function md51(s) { let n = s.length; let state = [1732584193, -271733879, -1732584194, 271733878]; let i; for (i = 64; i <= n; i += 64) { md5cycle(state, md5blk(s.substring(i - 64, i))); } s = s.substring(i - 64); const tail = new Array(64).fill(0); for (i = 0; i < s.length; i++) tail[i] = s.charCodeAt(i); tail[i] = 0x80; if (i > 55) { md5cycle(state, md5blk_array(tail)); for (i = 0; i < 64; i++) tail[i] = 0; } const tmp = n * 8; tail[56] = tmp & 0xFF; tail[57] = (tmp >>> 8) & 0xFF; tail[58] = (tmp >>> 16) & 0xFF; tail[59] = (tmp >>> 24) & 0xFF; md5cycle(state, md5blk_array(tail)); return state; } function rhex(n) { const s = '0123456789abcdef'; let j, out = ''; for (j = 0; j < 4; j++) { out += s.charAt((n >> (j * 8 + 4)) & 0x0F) + s.charAt((n >> (j * 8)) & 0x0F); } return out; } function hex(x) { return x.map(rhex).join(''); } function add32(a, b) { return (a + b) & 0xFFFFFFFF; } return hex(md51(str)); /* eslint-enable */ } function buildAmapQuery(params) { const clean = {} Object.keys(params || {}).forEach((k) => { const v = params[k] if (v === undefined || v === null || v === '') return clean[k] = String(v) }) // 必须包含 key if (!clean.key) clean.key = AMAP_KEY const keys = Object.keys(clean).sort() const query = keys.map((k) => `${k}=${encodeURIComponent(clean[k])}`).join('&') const sig = md5(query + AMAP_SECRET) return `${query}&sig=${sig}` } function amapGet(path, params) { return new Promise((resolve, reject) => { const query = buildAmapQuery(params) uni.request({ url: `${AMAP_BASE_URL}${path}?${query}`, method: 'GET', header: { 'content-type': 'application/json' }, success: (res) => { const data = res && res.data // 高德 WebService:status '1' 表示成功 if (data && String(data.status) === '1') { resolve(data) } else { reject(data || { status: '0', info: '请求失败' }) } }, fail: (err) => reject(err) }) }) } // #endif // 腾讯地图Key const QQMAP_KEY = // #ifdef H5 'DJQBZ-WB53Q-WPS5B-4S6J7-53RMS-X4FJ2' // #endif // #ifndef H5 'RO5BZ-ECZ63-7US3C-RT5QW-TIDZE-2FF35' // #endif ; // 内联腾讯地图SDK核心代码 const QQMapWX = (function() { // 错误配置 const ERROR_CONF = { KEY_ERR: 311, KEY_ERR_MSG: 'key格式错误', PARAM_ERR: 310, PARAM_ERR_MSG: '请求参数信息有误', SYSTEM_ERR: 600, SYSTEM_ERR_MSG: '系统错误', WX_ERR_CODE: 1000, WX_OK_CODE: 200 }; // API基础URL const BASE_URL = 'https://apis.map.qq.com/ws/'; const URL_SEARCH = BASE_URL + 'place/v1/search'; const URL_SUGGESTION = BASE_URL + 'place/v1/suggestion'; const URL_GET_GEOCODER = BASE_URL + 'geocoder/v1/'; const URL_DISTANCE = BASE_URL + 'distance/v1/'; // 工具函数 const Utils = { // 获取location参数 getLocationParam(location) { if (typeof location == 'string') { const locationArr = location.split(','); if (locationArr.length === 2) { location = { latitude: location.split(',')[0], longitude: location.split(',')[1] }; } else { location = {}; } } return location; }, // 验证location值 checkLocation(param) { const location = this.getLocationParam(param.location); if (!location || !location.latitude || !location.longitude) { const errconf = this.buildErrorConfig(ERROR_CONF.PARAM_ERR, ERROR_CONF.PARAM_ERR_MSG + ' location参数格式有误'); param.fail(errconf); param.complete(errconf); return false; } return true; }, // 构造错误数据结构 buildErrorConfig(errCode, errMsg) { return { status: errCode, message: errMsg }; }, // 回调函数默认处理 polyfillParam(param) { param.success = param.success || function () { }; param.fail = param.fail || function () { }; param.complete = param.complete || function () { }; }, // 处理用户参数是否传入坐标进行不同的处理 locationProcess(param, locationsuccess, locationfail, locationcomplete) { const that = this; locationfail = locationfail || function (res) { res.statusCode = ERROR_CONF.WX_ERR_CODE; param.fail(that.buildErrorConfig(ERROR_CONF.WX_ERR_CODE, res.errMsg)); }; locationcomplete = locationcomplete || function (res) { if (res.statusCode == ERROR_CONF.WX_ERR_CODE) { param.complete(that.buildErrorConfig(ERROR_CONF.WX_ERR_CODE, res.errMsg)); } }; if (!param.location) { uni.getLocation({ type: 'gcj02', success: locationsuccess, fail: locationfail, complete: locationcomplete }); } else if (that.checkLocation(param)) { const location = Utils.getLocationParam(param.location); locationsuccess(location); } }, // 构造微信请求参数 buildWxRequestConfig(param, options, feature) { const that = this; options.header = { "content-type": "application/json" }; options.method = 'GET'; options.success = function (res) { const data = res.data; if (data.status === 0) { that.handleData(param, data, feature); } else { param.fail(data); } }; options.fail = function (res) { res.statusCode = ERROR_CONF.WX_ERR_CODE; param.fail(that.buildErrorConfig(ERROR_CONF.WX_ERR_CODE, res.errMsg)); }; options.complete = function (res) { const statusCode = +res.statusCode; switch(statusCode) { case ERROR_CONF.WX_ERR_CODE: { param.complete(that.buildErrorConfig(ERROR_CONF.WX_ERR_CODE, res.errMsg)); break; } case ERROR_CONF.WX_OK_CODE: { const data = res.data; if (data.status === 0) { param.complete(data); } else { param.complete(that.buildErrorConfig(data.status, data.message)); } break; } default:{ param.complete(that.buildErrorConfig(ERROR_CONF.SYSTEM_ERR, ERROR_CONF.SYSTEM_ERR_MSG)); } } }; return options; }, // 数据处理函数 handleData(param, data, feature) { if (feature == 'search') { const searchResult = data.data; const searchSimplify = []; for (let i = 0; i < searchResult.length; i++) { searchSimplify.push({ id: searchResult[i].id || null, title: searchResult[i].title || null, latitude: searchResult[i].location && searchResult[i].location.lat || null, longitude: searchResult[i].location && searchResult[i].location.lng || null, address: searchResult[i].address || null, category: searchResult[i].category || null, tel: searchResult[i].tel || null, adcode: searchResult[i].ad_info && searchResult[i].ad_info.adcode || null, city: searchResult[i].ad_info && searchResult[i].ad_info.city || null, district: searchResult[i].ad_info && searchResult[i].ad_info.district || null, province: searchResult[i].ad_info && searchResult[i].ad_info.province || null }); } param.success(data, { searchResult: searchResult, searchSimplify: searchSimplify }); } else if (feature == 'suggest') { const suggestResult = data.data; const suggestSimplify = []; for (let i = 0; i < suggestResult.length; i++) { suggestSimplify.push({ adcode: suggestResult[i].adcode || null, address: suggestResult[i].address || null, category: suggestResult[i].category || null, city: suggestResult[i].city || null, district: suggestResult[i].district || null, id: suggestResult[i].id || null, latitude: suggestResult[i].location && suggestResult[i].location.lat || null, longitude: suggestResult[i].location && suggestResult[i].location.lng || null, province: suggestResult[i].province || null, title: suggestResult[i].title || null, type: suggestResult[i].type || null }); } param.success(data, { suggestResult: suggestResult, suggestSimplify: suggestSimplify }); } else if (feature == 'reverseGeocoder') { const reverseGeocoderResult = data.result; const reverseGeocoderSimplify = { address: reverseGeocoderResult.address || null, latitude: reverseGeocoderResult.location && reverseGeocoderResult.location.lat || null, longitude: reverseGeocoderResult.location && reverseGeocoderResult.location.lng || null, adcode: reverseGeocoderResult.ad_info && reverseGeocoderResult.ad_info.adcode || null, city: reverseGeocoderResult.address_component && reverseGeocoderResult.address_component.city || null, district: reverseGeocoderResult.address_component && reverseGeocoderResult.address_component.district || null, nation: reverseGeocoderResult.address_component && reverseGeocoderResult.address_component.nation || null, province: reverseGeocoderResult.address_component && reverseGeocoderResult.address_component.province || null, street: reverseGeocoderResult.address_component && reverseGeocoderResult.address_component.street || null, street_number: reverseGeocoderResult.address_component && reverseGeocoderResult.address_component.street_number || null, recommend: reverseGeocoderResult.formatted_addresses && reverseGeocoderResult.formatted_addresses.recommend || null, rough: reverseGeocoderResult.formatted_addresses && reverseGeocoderResult.formatted_addresses.rough || null }; param.success(data, { reverseGeocoderResult: reverseGeocoderResult, reverseGeocoderSimplify: reverseGeocoderSimplify }); } else if (feature == 'calculateDistance') { const calculateDistanceResult = data.result.elements; const distance = []; for (let i = 0; i < calculateDistanceResult.length; i++){ distance.push(calculateDistanceResult[i].distance); } param.success(data, { calculateDistanceResult: calculateDistanceResult, distance: distance }); } else { param.success(data); } } }; // QQMapWX类 class QQMapWX { constructor(options) { if (!options.key) { throw Error('key值不能为空'); } this.key = options.key; } // 逆地址解析 reverseGeocoder(options) { const that = this; options = options || {}; Utils.polyfillParam(options); const requestParam = { coord_type: options.coord_type || 5, get_poi: options.get_poi || 0, output: 'json', key: that.key }; if (options.poi_options) { requestParam.poi_options = options.poi_options; } const locationsuccess = function (result) { requestParam.location = result.latitude + ',' + result.longitude; // #ifdef H5 // H5环境使用JSONP方式 that._requestJsonp(URL_GET_GEOCODER, requestParam, options, 'reverseGeocoder'); // #endif // #ifndef H5 uni.request(Utils.buildWxRequestConfig(options, { url: URL_GET_GEOCODER, data: requestParam }, 'reverseGeocoder')); // #endif }; Utils.locationProcess(options, locationsuccess); } // POI周边检索 search(options) { const that = this; options = options || {}; Utils.polyfillParam(options); if (!options.keyword) { const errconf = Utils.buildErrorConfig(ERROR_CONF.PARAM_ERR, ERROR_CONF.PARAM_ERR_MSG + ' keyword参数格式有误'); options.fail(errconf); options.complete(errconf); return; } const requestParam = { keyword: options.keyword, orderby: options.orderby || '_distance', page_size: options.page_size || 10, page_index: options.page_index || 1, output: 'json', key: that.key }; if (options.address_format) { requestParam.address_format = options.address_format; } if (options.filter) { requestParam.filter = options.filter; } const distance = options.distance || "1000"; const auto_extend = options.auto_extend || 1; const locationsuccess = function (result) { requestParam.boundary = "nearby(" + result.latitude + "," + result.longitude + "," + distance + "," + auto_extend + ")"; // #ifdef H5 // H5环境使用JSONP方式 that._requestJsonp(URL_SEARCH, requestParam, options, 'search'); // #endif // #ifndef H5 uni.request(Utils.buildWxRequestConfig(options, { url: URL_SEARCH, data: requestParam }, 'search')); // #endif }; Utils.locationProcess(options, locationsuccess); } // sug模糊检索 getSuggestion(options) { const that = this; options = options || {}; Utils.polyfillParam(options); if (!options.keyword) { const errconf = Utils.buildErrorConfig(ERROR_CONF.PARAM_ERR, ERROR_CONF.PARAM_ERR_MSG + ' keyword参数格式有误'); options.fail(errconf); options.complete(errconf); return; } const requestParam = { keyword: options.keyword, region: options.region || '全国', region_fix: options.region_fix || 0, policy: options.policy || 0, page_size: options.page_size || 10, page_index: options.page_index || 1, get_subpois: options.get_subpois || 0, output: 'json', key: that.key }; if (options.address_format) { requestParam.address_format = options.address_format; } if (options.filter) { requestParam.filter = options.filter; } if (options.location) { const locationsuccess = function (result) { requestParam.location = result.latitude + ',' + result.longitude; // #ifdef H5 that._requestJsonp(URL_SUGGESTION, requestParam, options, "suggest"); // #endif // #ifndef H5 uni.request(Utils.buildWxRequestConfig(options, { url: URL_SUGGESTION, data: requestParam }, "suggest")); // #endif }; Utils.locationProcess(options, locationsuccess); } else { // #ifdef H5 that._requestJsonp(URL_SUGGESTION, requestParam, options, "suggest"); // #endif // #ifndef H5 uni.request(Utils.buildWxRequestConfig(options, { url: URL_SUGGESTION, data: requestParam }, "suggest")); // #endif } } // 距离计算 calculateDistance(options) { const that = this; options = options || {}; Utils.polyfillParam(options); if (!options.to) { const errconf = Utils.buildErrorConfig(ERROR_CONF.PARAM_ERR, ERROR_CONF.PARAM_ERR_MSG + ' to参数格式有误'); options.fail(errconf); options.complete(errconf); return; } const requestParam = { mode: options.mode || 'walking', to: options.to, output: 'json', key: that.key }; if (options.from) { options.location = options.from; } const locationsuccess = function (result) { requestParam.from = result.latitude + ',' + result.longitude; // #ifdef H5 that._requestJsonp(URL_DISTANCE, requestParam, options, 'calculateDistance'); // #endif // #ifndef H5 uni.request(Utils.buildWxRequestConfig(options, { url: URL_DISTANCE, data: requestParam }, 'calculateDistance')); // #endif }; Utils.locationProcess(options, locationsuccess); } // H5环境JSONP请求方法 _requestJsonp(url, params, options, feature) { const callbackName = `qqmap_callback_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // 构建URL参数 const queryString = Object.keys(params) .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`) .join('&'); const fullUrl = `${url}?${queryString}&output=jsonp&callback=${callbackName}`; // 创建全局回调函数 window[callbackName] = (data) => { // 清理回调函数和script标签 delete window[callbackName]; const script = document.getElementById(callbackName); if (script && script.parentNode) { script.parentNode.removeChild(script); } if (data.status === 0) { Utils.handleData(options, data, feature); } else { options.fail(data); } options.complete(data); }; // 创建script标签进行JSONP请求 const script = document.createElement('script'); script.id = callbackName; script.src = fullUrl; script.onerror = () => { delete window[callbackName]; const scriptEl = document.getElementById(callbackName); if (scriptEl && scriptEl.parentNode) { scriptEl.parentNode.removeChild(scriptEl); } const error = { status: 600, message: '请求失败' }; options.fail(error); options.complete(error); }; document.head.appendChild(script); } } return QQMapWX; })(); // 全局QQMap实例 let qqmapInstance = null; // 初始化腾讯地图SDK function initQQMap() { if (!qqmapInstance) { try { qqmapInstance = new QQMapWX({ key: QQMAP_KEY }); console.log('腾讯地图SDK初始化成功'); } catch (err) { console.error('初始化腾讯地图SDK失败:', err); } } return qqmapInstance; } // 获取腾讯地图SDK实例 function getQQMapInstance() { return qqmapInstance || initQQMap(); } // 获取用户位置(统一处理小程序定位授权 && 实际定位) function getUserLocation() { return new Promise((resolve, reject) => { // 小程序端优先通过 getSetting 判断权限状态 // 这里只考虑 mp-weixin 场景,其它平台回退到直接调用 getLocation // #ifdef MP-WEIXIN uni.getSetting({ success: (settingRes) => { const authSetting = settingRes.authSetting || {}; // 兼容多种定位 scope(微信近几次版本变更比较多) const hasUserLocation = Object.prototype.hasOwnProperty.call(authSetting, 'scope.userLocation'); const userLocationAuth = authSetting['scope.userLocation']; const fuzzyLocationAuth = authSetting['scope.userFuzzyLocation']; const bgLocationAuth = authSetting['scope.userLocationBackground']; // 已明确拒绝定位 if ( userLocationAuth === false || fuzzyLocationAuth === false || bgLocationAuth === false ) { uni.showModal({ title: getPermissionText('locationTitle'), content: getPermissionText('locationNeed'), confirmText: getPermissionText('goToSettings'), cancelText: getPermissionText('later'), success: (modalRes) => { if (modalRes.confirm) { uni.openSetting({}); } } }); reject({ code: 'LOCATION_DENIED', errMsg: 'user denied location permission' }); return; } const doGetLocation = () => { wx.getLocation({ type: 'gcj02', success: (res) => { const longitude = toFixedNumber(res.longitude, 5) const latitude = toFixedNumber(res.latitude, 5) if (longitude === null || latitude === null) { reject({ code: 'INVALID_COORD', errMsg: 'invalid longitude/latitude from getLocation' }) return } console.log('地址获取成功'); resolve({ longitude, latitude }); }, fail: (error) => { console.error('获取位置失败:', error); reject(error); } }); }; // 未显式授权时,主动申请一次权限(老版本 scope 为 scope.userLocation) if (!hasUserLocation || userLocationAuth === undefined) { uni.authorize({ scope: 'scope.userLocation', success: () => { doGetLocation(); }, fail: (authErr) => { console.error('定位授权失败:', authErr); uni.showModal({ title: getPermissionText('locationTitle'), content: getPermissionText('locationDenied'), confirmText: getPermissionText('gotIt'), showCancel: false }); reject({ code: 'LOCATION_AUTH_FAIL', errMsg: authErr.errMsg || 'authorize location fail' }); } }); } else { // 已授权,直接获取定位 doGetLocation(); } }, fail: (err) => { console.warn('获取授权设置失败,直接尝试定位:', err); wx.getLocation({ type: 'gcj02', success: (res) => { const longitude = toFixedNumber(res.longitude, 5) const latitude = toFixedNumber(res.latitude, 5) if (longitude === null || latitude === null) { reject({ code: 'INVALID_COORD', errMsg: 'invalid longitude/latitude from getLocation' }) return } console.log('地址获取成功'); resolve({ longitude, latitude }); }, fail: (error) => { console.error('获取位置失败:', error); reject(error); } }); } }); // #endif // 非微信小程序平台:根据平台使用不同的定位方式 // #ifndef MP-WEIXIN // 统一使用 uni.getLocation,框架会根据环境自动选择最佳定位方式 uni.getLocation({ type: 'gcj02', success: (res) => { const longitude = toFixedNumber(res.longitude, 5) const latitude = toFixedNumber(res.latitude, 5) if (longitude === null || latitude === null) { reject({ code: 'INVALID_COORD', errMsg: 'invalid longitude/latitude from getLocation' }) return } console.log('地址获取成功'); resolve({ longitude, latitude }); }, fail: (error) => { console.error('获取位置失败:', error); // H5 环境下的特殊错误提示 // #ifdef H5 if (error.errMsg && error.errMsg.indexOf('permission denied') !== -1) { uni.showModal({ title: getPermissionText('locationTitle'), content: getPermissionText('locationNeed'), confirmText: getPermissionText('gotIt'), showCancel: false }); } // #endif reject(error); } }); // #endif }); } // 逆地理编码 - 根据经纬度获取地址信息 function getRegeo(longitude, latitude) { return new Promise((resolve, reject) => { // #ifdef MP-ALIPAY // 支付宝小程序:高德逆地理编码 const lng = toFixedNumber(longitude, 6) const lat = toFixedNumber(latitude, 6) if (lng === null || lat === null) { reject({ success: false, message: '无效经纬度' }) return } amapGet('geocode/regeo', { location: `${lng},${lat}`, radius: 1000, extensions: 'base' }).then((data) => { const regeocode = data.regeocode || {} const ac = regeocode.addressComponent || {} resolve({ success: true, data: { formatted_address: regeocode.formatted_address || '', addressComponent: { city: Array.isArray(ac.city) ? '' : (ac.city || ''), district: ac.district || '', province: ac.province || '', street: (ac.streetNumber && ac.streetNumber.street) || '', street_number: (ac.streetNumber && ac.streetNumber.number) || '' } } }) }).catch((err) => { console.error('支付宝-高德逆地理编码失败:', err) reject({ success: false, message: err.info || err.message || '逆地理编码失败' }) }) return // #endif // #ifdef H5 // H5环境:使用JSONP方式调用腾讯地图API,避免跨域问题 const callbackName = `qqmap_geocoder_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // 创建全局回调函数 window[callbackName] = (data) => { // 清理回调函数和script标签 delete window[callbackName]; const script = document.getElementById(callbackName); if (script && script.parentNode) { script.parentNode.removeChild(script); } if (data.status === 0) { const result = data.result; resolve({ success: true, data: { formatted_address: result.address, addressComponent: { city: result.address_component.city, district: result.address_component.district, province: result.address_component.province, street: result.address_component.street, street_number: result.address_component.street_number } } }); } else { console.error('H5逆地理编码失败:', data); reject({ success: false, message: data.message || '逆地理编码失败' }); } }; // 创建script标签进行JSONP请求 const script = document.createElement('script'); script.id = callbackName; script.src = `https://apis.map.qq.com/ws/geocoder/v1/?location=${latitude},${longitude}&key=${QQMAP_KEY}&output=jsonp&callback=${callbackName}`; script.onerror = () => { delete window[callbackName]; const scriptEl = document.getElementById(callbackName); if (scriptEl && scriptEl.parentNode) { scriptEl.parentNode.removeChild(scriptEl); } reject({ success: false, message: '逆地理编码请求失败' }); }; document.head.appendChild(script); // #endif // #ifdef MP-WEIXIN // 小程序环境:使用 QQMapWX SDK const qqmap = getQQMapInstance(); if (!qqmap) { reject({ success: false, message: '腾讯地图SDK未初始化' }); return; } qqmap.reverseGeocoder({ location: { latitude, longitude }, success: (data, result) => { // 官方SDK返回的数据结构:data是原始数据,result是简化数据 const reverseGeocoderSimplify = result.reverseGeocoderSimplify; resolve({ success: true, data: { formatted_address: reverseGeocoderSimplify.address, addressComponent: { city: reverseGeocoderSimplify.city, district: reverseGeocoderSimplify.district, province: reverseGeocoderSimplify.province, street: reverseGeocoderSimplify.street, street_number: reverseGeocoderSimplify.street_number } } }); }, fail: (error) => { console.error('逆地理编码失败:', error); reject({ success: false, message: error.message || '逆地理编码失败' }); } }); // #endif }); } // 搜索周边POI function getPoiAround(longitude, latitude, keyword = '', radius = 1000) { return new Promise((resolve, reject) => { // #ifdef MP-ALIPAY // 支付宝小程序:高德周边检索 const lng = toFixedNumber(longitude, 6) const lat = toFixedNumber(latitude, 6) if (lng === null || lat === null) { reject({ success: false, message: '无效经纬度' }) return } amapGet('place/around', { location: `${lng},${lat}`, keywords: keyword || '', radius: radius || 1000, sortrule: 'distance', offset: 10, page: 1, extensions: 'base' }).then((data) => { const pois = Array.isArray(data.pois) ? data.pois : [] const list = pois.map((p) => { const loc = (p.location || '').split(',') const plng = toFixedNumber(loc[0], 6) const plat = toFixedNumber(loc[1], 6) return { id: p.id || null, title: p.name || null, latitude: plat, longitude: plng, address: p.address || null, category: p.type || null, tel: p.tel || null, adcode: p.adcode || null, city: p.cityname || null, district: p.adname || null, province: p.pname || null } }) resolve({ success: true, data: list }) }).catch((err) => { console.error('支付宝-高德搜索POI失败:', err) reject({ success: false, message: err.info || err.message || '搜索POI失败' }) }) return // #endif // #ifdef H5 // H5环境:使用JSONP方式调用腾讯地图API const callbackName = `qqmap_search_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // 创建全局回调函数 window[callbackName] = (data) => { // 清理回调函数和script标签 delete window[callbackName]; const script = document.getElementById(callbackName); if (script && script.parentNode) { script.parentNode.removeChild(script); } if (data.status === 0) { const searchSimplify = data.data.map(item => ({ id: item.id || null, title: item.title || null, latitude: item.location && item.location.lat || null, longitude: item.location && item.location.lng || null, address: item.address || null, category: item.category || null, tel: item.tel || null, adcode: item.ad_info && item.ad_info.adcode || null, city: item.ad_info && item.ad_info.city || null, district: item.ad_info && item.ad_info.district || null, province: item.ad_info && item.ad_info.province || null })); resolve({ success: true, data: searchSimplify }); } else { console.error('H5搜索POI失败:', data); reject({ success: false, message: data.message || '搜索POI失败' }); } }; // 创建script标签进行JSONP请求 const script = document.createElement('script'); script.id = callbackName; script.src = `https://apis.map.qq.com/ws/place/v1/search?boundary=nearby(${latitude},${longitude},${radius})&keyword=${encodeURIComponent(keyword)}&orderby=_distance&page_size=10&page_index=1&key=${QQMAP_KEY}&output=jsonp&callback=${callbackName}`; script.onerror = () => { delete window[callbackName]; const scriptEl = document.getElementById(callbackName); if (scriptEl && scriptEl.parentNode) { scriptEl.parentNode.removeChild(scriptEl); } reject({ success: false, message: '搜索POI请求失败' }); }; document.head.appendChild(script); // #endif // #ifdef MP-WEIXIN // 小程序环境:使用 QQMapWX SDK const qqmap = getQQMapInstance(); if (!qqmap) { reject({ success: false, message: '腾讯地图SDK未初始化' }); return; } qqmap.search({ keyword: keyword, location: { latitude, longitude }, distance: radius, success: (data, result) => { const searchSimplify = result.searchSimplify; resolve({ success: true, data: searchSimplify }); }, fail: (error) => { console.error('搜索POI失败:', error); reject({ success: false, message: error.message || '搜索POI失败' }); } }); // #endif }); } // 计算距离(异步) function calculateDistance(from, to) { return new Promise((resolve, reject) => { // #ifdef MP-ALIPAY // 支付宝小程序:高德距离计算(支持多个目的地) if (!from || !to) { reject({ success: false, message: '参数缺失' }) return } const fromLng = toFixedNumber((from && (from.longitude !== undefined ? from.longitude : from.lng)), 6) const fromLat = toFixedNumber((from && (from.latitude !== undefined ? from.latitude : from.lat)), 6) if (fromLng === null || fromLat === null) { reject({ success: false, message: '无效起点坐标' }) return } const toArr = Array.isArray(to) ? to : [to] const origins = toArr.map((p) => { const lng = toFixedNumber((p && (p.longitude !== undefined ? p.longitude : p.lng)), 6) const lat = toFixedNumber((p && (p.latitude !== undefined ? p.latitude : p.lat)), 6) return (lng === null || lat === null) ? null : `${lng},${lat}` }).filter(Boolean) if (!origins.length) { reject({ success: false, message: '无效终点坐标' }) return } // 高德接口:origins(多个) + destination(单个) amapGet('distance', { origins: origins.join('|'), destination: `${fromLng},${fromLat}`, type: 0 // 0:驾车距离;步行/骑行需要其它接口,这里保持与原来“直线/近似”用途一致 }).then((data) => { const results = Array.isArray(data.results) ? data.results : [] const distances = results.map((r) => Number(r.distance)).filter((n) => Number.isFinite(n)) // 保持与原实现一致:始终返回数组(即使只传了一个目的地) resolve({ success: true, data: distances }) }).catch((err) => { console.error('支付宝-高德计算距离失败:', err) reject({ success: false, message: err.info || err.message || '计算距离失败' }) }) return // #endif // #ifdef H5 // H5环境:使用JSONP方式调用腾讯地图API const callbackName = `qqmap_distance_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // 创建全局回调函数 window[callbackName] = (data) => { // 清理回调函数和script标签 delete window[callbackName]; const script = document.getElementById(callbackName); if (script && script.parentNode) { script.parentNode.removeChild(script); } if (data.status === 0) { const distances = data.result.elements.map(element => element.distance); resolve({ success: true, data: distances }); } else { console.error('H5计算距离失败:', data); reject({ success: false, message: data.message || '计算距离失败' }); } }; // 创建script标签进行JSONP请求 const script = document.createElement('script'); script.id = callbackName; // 构建to参数 let toStr = ''; if (Array.isArray(to)) { toStr = to.map(p => `${p.latitude},${p.longitude}`).join(';'); } else { toStr = `${to.latitude},${to.longitude}`; } const fromStr = `${from.latitude},${from.longitude}`; script.src = `https://apis.map.qq.com/ws/distance/v1/?mode=walking&from=${fromStr}&to=${toStr}&key=${QQMAP_KEY}&output=jsonp&callback=${callbackName}`; script.onerror = () => { delete window[callbackName]; const scriptEl = document.getElementById(callbackName); if (scriptEl && scriptEl.parentNode) { scriptEl.parentNode.removeChild(scriptEl); } reject({ success: false, message: '计算距离请求失败' }); }; document.head.appendChild(script); // #endif // #ifdef MP-WEIXIN // 小程序环境:使用 QQMapWX SDK const qqmap = getQQMapInstance(); if (!qqmap) { reject({ success: false, message: '腾讯地图SDK未初始化' }); return; } qqmap.calculateDistance({ from: from, to: to, mode: 'walking', success: (data, result) => { const distance = result.distance; resolve({ success: true, data: distance }); }, fail: (error) => { console.error('计算距离失败:', error); reject({ success: false, message: error.message || '计算距离失败' }); } }); // #endif }); } // 计算距离(同步,使用球面距离公式) function calculateDistanceSync(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 Math.round(R * c); // 返回距离,单位为米 } // 关键词提示 function getSuggestion(keyword, region = '全国') { return new Promise((resolve, reject) => { // #ifdef MP-ALIPAY // 支付宝小程序:高德输入提示 if (!keyword) { resolve({ success: true, data: [] }) return } amapGet('assistant/inputtips', { keywords: keyword, city: region && region !== '全国' ? region : '', citylimit: region && region !== '全国' ? 1 : 0 }).then((data) => { const tips = Array.isArray(data.tips) ? data.tips : [] const list = tips.map((t) => { const loc = (t.location || '').split(',') const lng = toFixedNumber(loc[0], 6) const lat = toFixedNumber(loc[1], 6) return { adcode: t.adcode || null, address: t.address || null, category: t.type || null, city: t.cityname || null, district: t.district || null, id: t.id || null, latitude: lat, longitude: lng, province: t.pname || null, title: t.name || null, type: t.type || null } }) resolve({ success: true, data: list }) }).catch((err) => { console.error('支付宝-高德关键词提示失败:', err) reject({ success: false, message: err.info || err.message || '关键词提示失败' }) }) return // #endif // #ifdef H5 // H5环境:使用JSONP方式调用腾讯地图API const callbackName = `qqmap_suggestion_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // 创建全局回调函数 window[callbackName] = (data) => { // 清理回调函数和script标签 delete window[callbackName]; const script = document.getElementById(callbackName); if (script && script.parentNode) { script.parentNode.removeChild(script); } if (data.status === 0) { const suggestSimplify = data.data.map(item => ({ adcode: item.adcode || null, address: item.address || null, category: item.category || null, city: item.city || null, district: item.district || null, id: item.id || null, latitude: item.location && item.location.lat || null, longitude: item.location && item.location.lng || null, province: item.province || null, title: item.title || null, type: item.type || null })); resolve({ success: true, data: suggestSimplify }); } else { console.error('H5关键词提示失败:', data); reject({ success: false, message: data.message || '关键词提示失败' }); } }; // 创建script标签进行JSONP请求 const script = document.createElement('script'); script.id = callbackName; script.src = `https://apis.map.qq.com/ws/place/v1/suggestion?keyword=${encodeURIComponent(keyword)}®ion=${encodeURIComponent(region)}&page_size=10&page_index=1&key=${QQMAP_KEY}&output=jsonp&callback=${callbackName}`; script.onerror = () => { delete window[callbackName]; const scriptEl = document.getElementById(callbackName); if (scriptEl && scriptEl.parentNode) { scriptEl.parentNode.removeChild(scriptEl); } reject({ success: false, message: '关键词提示请求失败' }); }; document.head.appendChild(script); // #endif // #ifdef MP-WEIXIN // 小程序环境:使用 QQMapWX SDK const qqmap = getQQMapInstance(); if (!qqmap) { reject({ success: false, message: '腾讯地图SDK未初始化' }); return; } qqmap.getSuggestion({ keyword: keyword, region: region, success: (data, result) => { const suggestSimplify = result.suggestSimplify; resolve({ success: true, data: suggestSimplify }); }, fail: (error) => { console.error('关键词提示失败:', error); reject({ success: false, message: error.message || '关键词提示失败' }); } }); // #endif }); } // 导出函数 export { getUserLocation, getRegeo, getPoiAround, calculateDistance, calculateDistanceSync, getSuggestion, initQQMap, getQQMapInstance }; // 测试距离计算函数(开发调试用) export function testDistanceCalculation() { // 测试用例:北京天安门到故宫的距离(约1.5公里) const tiananmen = { lat: 39.908823, lng: 116.397470 }; const gugong = { lat: 39.916527, lng: 116.397128 }; const distance = calculateDistanceSync(tiananmen.lat, tiananmen.lng, gugong.lat, gugong.lng); console.log('天安门到故宫的距离:', distance, '米'); console.log('转换为公里:', (distance / 1000).toFixed(2), '公里'); return distance; }