Files
uni-fans-score/utils/mapUtils.js
T
2026-03-09 09:07:58 +08:00

1422 lines
48 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 地图工具函数 - 内联腾讯地图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/'
// 轻量 MD5RFC1321)实现:用于高德 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
// 高德 WebServicestatus '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'),
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)}&region=${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;
}