1330 lines
45 KiB
JavaScript
1330 lines
45 KiB
JavaScript
// 地图工具函数 - 内联腾讯地图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] || ''
|
||
}
|
||
|
||
// 腾讯地图Key
|
||
const QQMAP_KEY = 'RO5BZ-ECZ63-7US3C-RT5QW-TIDZE-2FF35';
|
||
|
||
// 高德地图配置(支付宝小程序专用)
|
||
const AMAP_KEY = 'a07af802ff0a04f012954ff4e69b36d0';
|
||
const AMAP_SECURITY_KEY = '119f36535ab42b8b2c857f29f9320b06';
|
||
const AMAP_BASE_URL = 'https://restapi.amap.com/v3';
|
||
const AMAP_PATH_PREFIX = '/v3';
|
||
|
||
// 运行环境标识(编译期指令)
|
||
const isAlipayEnv = (() => {
|
||
let flag = false;
|
||
// #ifdef MP-ALIPAY
|
||
flag = true;
|
||
// #endif
|
||
return flag;
|
||
})();
|
||
|
||
// 轻量级 MD5 实现(用于生成高德安全 sig)
|
||
function md5(string) {
|
||
function RotateLeft(lValue, iShiftBits) {
|
||
return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
|
||
}
|
||
function AddUnsigned(lX, lY) {
|
||
const lX4 = lX & 0x40000000;
|
||
const lY4 = lY & 0x40000000;
|
||
const lX8 = lX & 0x80000000;
|
||
const lY8 = lY & 0x80000000;
|
||
const lResult = (lX & 0x3fffffff) + (lY & 0x3fffffff);
|
||
if (lX4 & lY4) {
|
||
return lResult ^ 0x80000000 ^ lX8 ^ lY8;
|
||
}
|
||
if (lX4 | lY4) {
|
||
if (lResult & 0x40000000) {
|
||
return lResult ^ 0xc0000000 ^ lX8 ^ lY8;
|
||
}
|
||
return lResult ^ 0x40000000 ^ lX8 ^ lY8;
|
||
}
|
||
return lResult ^ lX8 ^ lY8;
|
||
}
|
||
function F(x, y, z) { return (x & y) | (~x & z); }
|
||
function G(x, y, z) { return (x & z) | (y & ~z); }
|
||
function H(x, y, z) { return x ^ y ^ z; }
|
||
function I(x, y, z) { return y ^ (x | ~z); }
|
||
function FF(a, b, c, d, x, s, ac) {
|
||
a = AddUnsigned(a, AddUnsigned(AddUnsigned(F(b, c, d), x), ac));
|
||
return AddUnsigned(RotateLeft(a, s), b);
|
||
}
|
||
function GG(a, b, c, d, x, s, ac) {
|
||
a = AddUnsigned(a, AddUnsigned(AddUnsigned(G(b, c, d), x), ac));
|
||
return AddUnsigned(RotateLeft(a, s), b);
|
||
}
|
||
function HH(a, b, c, d, x, s, ac) {
|
||
a = AddUnsigned(a, AddUnsigned(AddUnsigned(H(b, c, d), x), ac));
|
||
return AddUnsigned(RotateLeft(a, s), b);
|
||
}
|
||
function II(a, b, c, d, x, s, ac) {
|
||
a = AddUnsigned(a, AddUnsigned(AddUnsigned(I(b, c, d), x), ac));
|
||
return AddUnsigned(RotateLeft(a, s), b);
|
||
}
|
||
function ConvertToWordArray(str) {
|
||
const lWordArray = [];
|
||
let lMessageLength = str.length;
|
||
let lNumberOfWordsTempOne = lMessageLength + 8;
|
||
const lNumberOfWordsTempTwo = (lNumberOfWordsTempOne - (lNumberOfWordsTempOne % 64)) / 64;
|
||
const lNumberOfWords = (lNumberOfWordsTempTwo + 1) * 16;
|
||
let lBytePosition = 0;
|
||
let lByteCount = 0;
|
||
while (lByteCount < lMessageLength) {
|
||
const lWordCount = (lByteCount - (lByteCount % 4)) / 4;
|
||
lBytePosition = (lByteCount % 4) * 8;
|
||
lWordArray[lWordCount] = (lWordArray[lWordCount] || 0) | (str.charCodeAt(lByteCount) << lBytePosition);
|
||
lByteCount++;
|
||
}
|
||
const lWordCount = (lByteCount - (lByteCount % 4)) / 4;
|
||
lBytePosition = (lByteCount % 4) * 8;
|
||
lWordArray[lWordCount] = lWordArray[lWordCount] || 0;
|
||
lWordArray[lWordCount] |= 0x80 << lBytePosition;
|
||
lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
|
||
lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
|
||
return lWordArray;
|
||
}
|
||
function WordToHex(lValue) {
|
||
let WordToHexValue = '';
|
||
for (let lCount = 0; lCount <= 3; lCount++) {
|
||
const lByte = (lValue >>> (lCount * 8)) & 255;
|
||
const WordToHexValueTemp = '0' + lByte.toString(16);
|
||
WordToHexValue += WordToHexValueTemp.substr(WordToHexValueTemp.length - 2, 2);
|
||
}
|
||
return WordToHexValue;
|
||
}
|
||
function Utf8Encode(str) {
|
||
str = str.replace(/\r\n/g, '\n');
|
||
let utftext = '';
|
||
for (let n = 0; n < str.length; n++) {
|
||
const c = str.charCodeAt(n);
|
||
if (c < 128) {
|
||
utftext += String.fromCharCode(c);
|
||
} else if (c > 127 && c < 2048) {
|
||
utftext += String.fromCharCode((c >> 6) | 192);
|
||
utftext += String.fromCharCode((c & 63) | 128);
|
||
} else {
|
||
utftext += String.fromCharCode((c >> 12) | 224);
|
||
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
|
||
utftext += String.fromCharCode((c & 63) | 128);
|
||
}
|
||
}
|
||
return utftext;
|
||
}
|
||
|
||
let x = [];
|
||
let k, AA, BB, CC, DD, a, b, c, d;
|
||
const S11 = 7, S12 = 12, S13 = 17, S14 = 22;
|
||
const S21 = 5, S22 = 9, S23 = 14, S24 = 20;
|
||
const S31 = 4, S32 = 11, S33 = 16, S34 = 23;
|
||
const S41 = 6, S42 = 10, S43 = 15, S44 = 21;
|
||
|
||
string = Utf8Encode(String(string));
|
||
x = ConvertToWordArray(string);
|
||
a = 0x67452301;
|
||
b = 0xefcdab89;
|
||
c = 0x98badcfe;
|
||
d = 0x10325476;
|
||
|
||
for (k = 0; k < x.length; k += 16) {
|
||
AA = a; BB = b; CC = c; DD = d;
|
||
a = FF(a, b, c, d, x[k + 0], S11, 0xd76aa478);
|
||
d = FF(d, a, b, c, x[k + 1], S12, 0xe8c7b756);
|
||
c = FF(c, d, a, b, x[k + 2], S13, 0x242070db);
|
||
b = FF(b, c, d, a, x[k + 3], S14, 0xc1bdceee);
|
||
a = FF(a, b, c, d, x[k + 4], S11, 0xf57c0faf);
|
||
d = FF(d, a, b, c, x[k + 5], S12, 0x4787c62a);
|
||
c = FF(c, d, a, b, x[k + 6], S13, 0xa8304613);
|
||
b = FF(b, c, d, a, x[k + 7], S14, 0xfd469501);
|
||
a = FF(a, b, c, d, x[k + 8], S11, 0x698098d8);
|
||
d = FF(d, a, b, c, x[k + 9], S12, 0x8b44f7af);
|
||
c = FF(c, d, a, b, x[k + 10], S13, 0xffff5bb1);
|
||
b = FF(b, c, d, a, x[k + 11], S14, 0x895cd7be);
|
||
a = FF(a, b, c, d, x[k + 12], S11, 0x6b901122);
|
||
d = FF(d, a, b, c, x[k + 13], S12, 0xfd987193);
|
||
c = FF(c, d, a, b, x[k + 14], S13, 0xa679438e);
|
||
b = FF(b, c, d, a, x[k + 15], S14, 0x49b40821);
|
||
a = GG(a, b, c, d, x[k + 1], S21, 0xf61e2562);
|
||
d = GG(d, a, b, c, x[k + 6], S22, 0xc040b340);
|
||
c = GG(c, d, a, b, x[k + 11], S23, 0x265e5a51);
|
||
b = GG(b, c, d, a, x[k + 0], S24, 0xe9b6c7aa);
|
||
a = GG(a, b, c, d, x[k + 5], S21, 0xd62f105d);
|
||
d = GG(d, a, b, c, x[k + 10], S22, 0x2441453);
|
||
c = GG(c, d, a, b, x[k + 15], S23, 0xd8a1e681);
|
||
b = GG(b, c, d, a, x[k + 4], S24, 0xe7d3fbc8);
|
||
a = GG(a, b, c, d, x[k + 9], S21, 0x21e1cde6);
|
||
d = GG(d, a, b, c, x[k + 14], S22, 0xc33707d6);
|
||
c = GG(c, d, a, b, x[k + 3], S23, 0xf4d50d87);
|
||
b = GG(b, c, d, a, x[k + 8], S24, 0x455a14ed);
|
||
a = GG(a, b, c, d, x[k + 13], S21, 0xa9e3e905);
|
||
d = GG(d, a, b, c, x[k + 2], S22, 0xfcefa3f8);
|
||
c = GG(c, d, a, b, x[k + 7], S23, 0x676f02d9);
|
||
b = GG(b, c, d, a, x[k + 12], S24, 0x8d2a4c8a);
|
||
a = HH(a, b, c, d, x[k + 5], S31, 0xfffa3942);
|
||
d = HH(d, a, b, c, x[k + 8], S32, 0x8771f681);
|
||
c = HH(c, d, a, b, x[k + 11], S33, 0x6d9d6122);
|
||
b = HH(b, c, d, a, x[k + 14], S34, 0xfde5380c);
|
||
a = HH(a, b, c, d, x[k + 1], S31, 0xa4beea44);
|
||
d = HH(d, a, b, c, x[k + 4], S32, 0x4bdecfa9);
|
||
c = HH(c, d, a, b, x[k + 7], S33, 0xf6bb4b60);
|
||
b = HH(b, c, d, a, x[k + 10], S34, 0xbebfbc70);
|
||
a = HH(a, b, c, d, x[k + 13], S31, 0x289b7ec6);
|
||
d = HH(d, a, b, c, x[k + 0], S32, 0xeaa127fa);
|
||
c = HH(c, d, a, b, x[k + 3], S33, 0xd4ef3085);
|
||
b = HH(b, c, d, a, x[k + 6], S34, 0x4881d05);
|
||
a = HH(a, b, c, d, x[k + 9], S31, 0xd9d4d039);
|
||
d = HH(d, a, b, c, x[k + 12], S32, 0xe6db99e5);
|
||
c = HH(c, d, a, b, x[k + 15], S33, 0x1fa27cf8);
|
||
b = HH(b, c, d, a, x[k + 2], S34, 0xc4ac5665);
|
||
a = II(a, b, c, d, x[k + 0], S41, 0xf4292244);
|
||
d = II(d, a, b, c, x[k + 7], S42, 0x432aff97);
|
||
c = II(c, d, a, b, x[k + 14], S43, 0xab9423a7);
|
||
b = II(b, c, d, a, x[k + 5], S44, 0xfc93a039);
|
||
a = II(a, b, c, d, x[k + 12], S41, 0x655b59c3);
|
||
d = II(d, a, b, c, x[k + 3], S42, 0x8f0ccc92);
|
||
c = II(c, d, a, b, x[k + 10], S43, 0xffeff47d);
|
||
b = II(b, c, d, a, x[k + 1], S44, 0x85845dd1);
|
||
a = II(a, b, c, d, x[k + 8], S41, 0x6fa87e4f);
|
||
d = II(d, a, b, c, x[k + 15], S42, 0xfe2ce6e0);
|
||
c = II(c, d, a, b, x[k + 6], S43, 0xa3014314);
|
||
b = II(b, c, d, a, x[k + 13], S44, 0x4e0811a1);
|
||
a = II(a, b, c, d, x[k + 4], S41, 0xf7537e82);
|
||
d = II(d, a, b, c, x[k + 11], S42, 0xbd3af235);
|
||
c = II(c, d, a, b, x[k + 2], S43, 0x2ad7d2bb);
|
||
b = II(b, c, d, a, x[k + 9], S44, 0xeb86d391);
|
||
a = AddUnsigned(a, AA);
|
||
b = AddUnsigned(b, BB);
|
||
c = AddUnsigned(c, CC);
|
||
d = AddUnsigned(d, DD);
|
||
}
|
||
|
||
const temp = WordToHex(a) + WordToHex(b) + WordToHex(c) + WordToHex(d);
|
||
return temp.toLowerCase();
|
||
}
|
||
|
||
// 将参数按字典序拼装为查询字符串
|
||
function buildSortedQuery(params) {
|
||
const sortedKeys = Object.keys(params || {}).sort();
|
||
return sortedKeys.map(key => `${key}=${params[key] !== undefined ? params[key] : ''}`).join('&');
|
||
}
|
||
|
||
// 调试开关:如需打印签名原串和 sig,临时改为 true
|
||
const DEBUG_AMAP_SIG = false;
|
||
|
||
// 构造高德 API 签名(正确规则:参数字符串 + 私钥,不含路径)
|
||
function buildAmapSig(params) {
|
||
const query = buildSortedQuery(params);
|
||
const raw = `${query}${AMAP_SECURITY_KEY}`;
|
||
if (DEBUG_AMAP_SIG) {
|
||
console.log('[AMap Sig Debug] query:', query, 'raw:', raw, 'sig:', md5(raw));
|
||
}
|
||
return md5(raw);
|
||
}
|
||
|
||
// 过滤掉 undefined / null 参数,避免签名与请求不一致
|
||
function sanitizeParams(obj = {}) {
|
||
const cleaned = {};
|
||
Object.keys(obj).forEach((k) => {
|
||
const v = obj[k];
|
||
if (v !== undefined && v !== null) {
|
||
cleaned[k] = v;
|
||
}
|
||
});
|
||
return cleaned;
|
||
}
|
||
|
||
// 统一的高德 API 请求
|
||
function requestAmap(path, params = {}) {
|
||
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
||
const baseParams = sanitizeParams({
|
||
key: AMAP_KEY,
|
||
...params
|
||
});
|
||
const sig = buildAmapSig(baseParams);
|
||
const queryParams = sanitizeParams({
|
||
...baseParams,
|
||
sig
|
||
});
|
||
|
||
return new Promise((resolve, reject) => {
|
||
uni.request({
|
||
url: `${AMAP_BASE_URL}${normalizedPath}`,
|
||
method: 'GET',
|
||
data: queryParams,
|
||
success: (res) => {
|
||
if (res.data && res.data.status === '1') {
|
||
resolve(res.data);
|
||
} else {
|
||
reject(res.data || { message: 'AMap request failed' });
|
||
}
|
||
},
|
||
fail: reject
|
||
});
|
||
});
|
||
}
|
||
|
||
// 将 WGS84 转 GCJ02(支付宝 my.getLocation 返回 WGS84)
|
||
async function convertToGcj02(longitude, latitude) {
|
||
if (!isAlipayEnv) {
|
||
return { longitude, latitude };
|
||
}
|
||
try {
|
||
const data = await requestAmap('/assistant/coordinate/convert', {
|
||
locations: `${longitude},${latitude}`,
|
||
coordsys: 'gps' // gps 即 WGS84
|
||
});
|
||
const locStr = (data.locations || '').split(';')[0] || '';
|
||
const [lngStr, latStr] = locStr.split(',');
|
||
const lng = Number(lngStr);
|
||
const lat = Number(latStr);
|
||
if (!isNaN(lng) && !isNaN(lat)) {
|
||
return { longitude: lng, latitude: lat };
|
||
}
|
||
} catch (err) {
|
||
console.warn('坐标转换失败,使用原始坐标:', err);
|
||
}
|
||
return { longitude, latitude };
|
||
}
|
||
|
||
// 内联腾讯地图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) {
|
||
// 微信小程序使用 wx.getLocation
|
||
// #ifdef MP-WEIXIN
|
||
wx.getLocation({
|
||
type: 'gcj02',
|
||
success: locationsuccess,
|
||
fail: locationfail,
|
||
complete: locationcomplete
|
||
});
|
||
// #endif
|
||
|
||
// 支付宝小程序使用 my.getLocation(不支持 type)
|
||
// #ifdef MP-ALIPAY
|
||
my.getLocation({
|
||
success: (res) => {
|
||
// 确保经纬度是数字类型
|
||
const result = {
|
||
...res,
|
||
longitude: Number(res.longitude),
|
||
latitude: Number(res.latitude)
|
||
};
|
||
locationsuccess(result);
|
||
},
|
||
fail: locationfail,
|
||
complete: locationcomplete
|
||
});
|
||
// #endif
|
||
|
||
// 其他平台使用 uni.getLocation
|
||
// #ifndef MP-WEIXIN
|
||
// #ifndef MP-ALIPAY
|
||
uni.getLocation({
|
||
type: 'gcj02',
|
||
success: locationsuccess,
|
||
fail: locationfail,
|
||
complete: locationcomplete
|
||
});
|
||
// #endif
|
||
// #endif
|
||
} else if (that.checkLocation(param)) {
|
||
const location = Utils.getLocationParam(param.location);
|
||
locationsuccess(location);
|
||
}
|
||
},
|
||
|
||
// 构造腾讯地图API请求参数(跨平台兼容)
|
||
buildWxRequestConfig(param, options, feature) {
|
||
const that = this;
|
||
// GET请求不需要设置content-type,uni.request会自动处理
|
||
options.header = options.header || {};
|
||
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;
|
||
// 调用腾讯地图API - 逆地理编码接口
|
||
// 使用 uni.request 跨平台API,在支付宝小程序中自动转换为 my.request
|
||
uni.request(Utils.buildWxRequestConfig(options, {
|
||
url: URL_GET_GEOCODER, // 腾讯地图API地址:https://apis.map.qq.com/ws/geocoder/v1/
|
||
data: requestParam
|
||
}, 'reverseGeocoder'));
|
||
};
|
||
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 + ")";
|
||
// 调用腾讯地图API - POI周边检索接口
|
||
// 使用 uni.request 跨平台API,在支付宝小程序中自动转换为 my.request
|
||
uni.request(Utils.buildWxRequestConfig(options, {
|
||
url: URL_SEARCH, // 腾讯地图API地址:https://apis.map.qq.com/ws/place/v1/search
|
||
data: requestParam
|
||
}, 'search'));
|
||
};
|
||
|
||
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;
|
||
// 调用腾讯地图API - 地点搜索建议接口
|
||
// 使用 uni.request 跨平台API,在支付宝小程序中自动转换为 my.request
|
||
uni.request(Utils.buildWxRequestConfig(options, {
|
||
url: URL_SUGGESTION, // 腾讯地图API地址:https://apis.map.qq.com/ws/place/v1/suggestion
|
||
data: requestParam
|
||
}, "suggest"));
|
||
};
|
||
Utils.locationProcess(options, locationsuccess);
|
||
} else {
|
||
// 调用腾讯地图API - 地点搜索建议接口(无位置信息)
|
||
// 使用 uni.request 跨平台API,在支付宝小程序中自动转换为 my.request
|
||
uni.request(Utils.buildWxRequestConfig(options, {
|
||
url: URL_SUGGESTION, // 腾讯地图API地址:https://apis.map.qq.com/ws/place/v1/suggestion
|
||
data: requestParam
|
||
}, "suggest"));
|
||
}
|
||
}
|
||
|
||
// 距离计算
|
||
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;
|
||
// 调用腾讯地图API - 距离计算接口
|
||
// 使用 uni.request 跨平台API,在支付宝小程序中自动转换为 my.request
|
||
uni.request(Utils.buildWxRequestConfig(options, {
|
||
url: URL_DISTANCE, // 腾讯地图API地址:https://apis.map.qq.com/ws/distance/v1/
|
||
data: requestParam
|
||
}, 'calculateDistance'));
|
||
};
|
||
|
||
Utils.locationProcess(options, locationsuccess);
|
||
}
|
||
}
|
||
|
||
return QQMapWX;
|
||
})();
|
||
|
||
// 全局QQMap实例
|
||
let qqmapInstance = null;
|
||
|
||
// 初始化腾讯地图SDK
|
||
function initQQMap() {
|
||
if (isAlipayEnv) {
|
||
// 支付宝端改用高德地图服务
|
||
return null;
|
||
}
|
||
if (!qqmapInstance) {
|
||
try {
|
||
qqmapInstance = new QQMapWX({
|
||
key: QQMAP_KEY
|
||
});
|
||
console.log('腾讯地图SDK初始化成功');
|
||
} catch (err) {
|
||
console.error('初始化腾讯地图SDK失败:', err);
|
||
}
|
||
}
|
||
return qqmapInstance;
|
||
}
|
||
|
||
// 获取腾讯地图SDK实例
|
||
function getQQMapInstance() {
|
||
if (isAlipayEnv) {
|
||
return null;
|
||
}
|
||
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 = parseFloat(Number(res.longitude).toFixed(5));
|
||
const latitude = parseFloat(Number(res.latitude).toFixed(5));
|
||
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 = parseFloat(Number(res.longitude).toFixed(5));
|
||
const latitude = parseFloat(Number(res.latitude).toFixed(5));
|
||
console.log('地址获取成功');
|
||
resolve({
|
||
longitude,
|
||
latitude
|
||
});
|
||
},
|
||
fail: (error) => {
|
||
console.error('获取位置失败:', error);
|
||
reject(error);
|
||
}
|
||
});
|
||
}
|
||
});
|
||
// #endif
|
||
|
||
// 支付宝小程序:使用 my.getLocation API,不支持 type 参数
|
||
// #ifdef MP-ALIPAY
|
||
// 支付宝小程序权限检查
|
||
my.getSetting({
|
||
success: (settingRes) => {
|
||
const authSetting = settingRes.authSetting || {};
|
||
const hasLocationAuth = Object.prototype.hasOwnProperty.call(authSetting, 'location');
|
||
const locationAuth = authSetting['location'];
|
||
|
||
// 已明确拒绝定位
|
||
if (locationAuth === false) {
|
||
my.showModal({
|
||
title: getPermissionText('locationTitle'),
|
||
content: getPermissionText('locationNeed'),
|
||
confirmText: getPermissionText('goToSettings'),
|
||
cancelText: getPermissionText('later'),
|
||
success: (modalRes) => {
|
||
if (modalRes.confirm) {
|
||
my.openSetting({});
|
||
}
|
||
}
|
||
});
|
||
reject({
|
||
code: 'LOCATION_DENIED',
|
||
errMsg: 'user denied location permission'
|
||
});
|
||
return;
|
||
}
|
||
|
||
const doGetLocation = () => {
|
||
// 支付宝小程序使用 my.getLocation,不支持 type 参数
|
||
my.getLocation({
|
||
success: (res) => {
|
||
// 支付宝小程序返回的经纬度可能是字符串,需要先转换为数字
|
||
// 注意:支付宝返回的是 WGS84 坐标系,如需 GCJ02 需要转换
|
||
const longitude = parseFloat(Number(res.longitude).toFixed(5));
|
||
const latitude = parseFloat(Number(res.latitude).toFixed(5));
|
||
console.log('支付宝地址获取成功');
|
||
resolve({
|
||
longitude,
|
||
latitude
|
||
});
|
||
},
|
||
fail: (error) => {
|
||
console.error('获取位置失败:', error);
|
||
reject(error);
|
||
}
|
||
});
|
||
};
|
||
|
||
// 未显式授权时,主动申请一次权限
|
||
if (!hasLocationAuth || locationAuth === undefined) {
|
||
my.authorize({
|
||
scope: 'scope.userLocation',
|
||
success: () => {
|
||
doGetLocation();
|
||
},
|
||
fail: (authErr) => {
|
||
console.error('定位授权失败:', authErr);
|
||
my.showModal({
|
||
title: getPermissionText('locationTitle'),
|
||
content: getPermissionText('locationDenied'),
|
||
confirmText: getPermissionText('gotIt'),
|
||
showCancel: false
|
||
});
|
||
reject({
|
||
code: 'LOCATION_AUTH_FAIL',
|
||
errMsg: authErr.errorMessage || 'authorize location fail'
|
||
});
|
||
}
|
||
});
|
||
} else {
|
||
// 已授权,直接获取定位
|
||
doGetLocation();
|
||
}
|
||
},
|
||
fail: (err) => {
|
||
console.warn('获取授权设置失败,直接尝试定位:', err);
|
||
// 支付宝小程序使用 my.getLocation,不支持 type 参数
|
||
my.getLocation({
|
||
success: (res) => {
|
||
// 支付宝小程序返回的经纬度可能是字符串,需要先转换为数字
|
||
const longitude = parseFloat(Number(res.longitude).toFixed(5));
|
||
const latitude = parseFloat(Number(res.latitude).toFixed(5));
|
||
console.log('支付宝地址获取成功');
|
||
resolve({
|
||
longitude,
|
||
latitude
|
||
});
|
||
},
|
||
fail: (error) => {
|
||
console.error('获取位置失败:', error);
|
||
reject(error);
|
||
}
|
||
});
|
||
}
|
||
});
|
||
// #endif
|
||
|
||
// 其他非微信、非支付宝小程序平台
|
||
// #ifndef MP-WEIXIN
|
||
// #ifndef MP-ALIPAY
|
||
uni.getLocation({
|
||
type: 'gcj02',
|
||
success: (res) => {
|
||
// 确保经纬度是数字类型
|
||
const longitude = parseFloat(Number(res.longitude).toFixed(5));
|
||
const latitude = parseFloat(Number(res.latitude).toFixed(5));
|
||
console.log('地址获取成功');
|
||
resolve({
|
||
longitude,
|
||
latitude
|
||
});
|
||
},
|
||
fail: (error) => {
|
||
console.error('获取位置失败:', error);
|
||
reject(error);
|
||
}
|
||
});
|
||
// #endif
|
||
// #endif
|
||
});
|
||
}
|
||
|
||
// 逆地理编码 - 根据经纬度获取地址信息
|
||
function getRegeo(longitude, latitude) {
|
||
return new Promise((resolve, reject) => {
|
||
// 支付宝端使用高德逆地理编码
|
||
if (isAlipayEnv) {
|
||
(async () => {
|
||
try {
|
||
const gcj = await convertToGcj02(longitude, latitude);
|
||
const data = await requestAmap('/geocode/regeo', {
|
||
location: `${gcj.longitude},${gcj.latitude}`,
|
||
extensions: 'all',
|
||
batch: 'false'
|
||
});
|
||
const info = data.regeocode || {};
|
||
const comp = info.addressComponent || {};
|
||
resolve({
|
||
success: true,
|
||
data: {
|
||
formatted_address: info.formatted_address || '',
|
||
addressComponent: {
|
||
city: comp.city || comp.province || '',
|
||
district: comp.district || '',
|
||
province: comp.province || '',
|
||
street: comp.streetNumber?.street || comp.street || '',
|
||
street_number: comp.streetNumber?.number || comp.streetNumber?.street_number || ''
|
||
}
|
||
}
|
||
});
|
||
} catch (error) {
|
||
console.error('高德逆地理编码失败:', error);
|
||
reject({ success: false, message: error.message || '逆地理编码失败' });
|
||
}
|
||
})();
|
||
return;
|
||
}
|
||
|
||
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 || '逆地理编码失败' });
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
// 搜索周边POI
|
||
function getPoiAround(longitude, latitude, keyword = '', radius = 1000) {
|
||
return new Promise((resolve, reject) => {
|
||
// 支付宝端使用高德周边搜索
|
||
if (isAlipayEnv) {
|
||
(async () => {
|
||
try {
|
||
const gcj = await convertToGcj02(longitude, latitude);
|
||
const data = await requestAmap('/place/around', {
|
||
location: `${gcj.longitude},${gcj.latitude}`,
|
||
keywords: keyword,
|
||
radius,
|
||
offset: 20,
|
||
page: 1,
|
||
extensions: 'base',
|
||
sortrule: 'distance'
|
||
});
|
||
const pois = data.pois || [];
|
||
const normalized = pois.map((item, idx) => ({
|
||
id: item.id || idx + 1,
|
||
title: item.name || '',
|
||
latitude: Number(item.location?.split(',')[1] || item.location?.lat || 0),
|
||
longitude: Number(item.location?.split(',')[0] || item.location?.lng || 0),
|
||
address: item.address || '',
|
||
category: item.type || '',
|
||
tel: item.tel || '',
|
||
city: item.cityname || '',
|
||
district: item.adname || '',
|
||
province: item.pname || ''
|
||
}));
|
||
resolve({
|
||
success: true,
|
||
data: normalized
|
||
});
|
||
} catch (error) {
|
||
console.error('高德周边搜索失败:', error);
|
||
reject({ success: false, message: error.message || '搜索POI失败' });
|
||
}
|
||
})();
|
||
return;
|
||
}
|
||
|
||
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失败' });
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
// 计算距离(异步)
|
||
function calculateDistance(from, to) {
|
||
return new Promise((resolve, reject) => {
|
||
// 支付宝端使用高德距离测量接口
|
||
if (isAlipayEnv) {
|
||
// to 支持数组
|
||
const targets = Array.isArray(to) ? to : [to];
|
||
if (!targets.length) {
|
||
reject({ success: false, message: '缺少目标坐标' });
|
||
return;
|
||
}
|
||
|
||
const doRequest = async (originPoint) => {
|
||
if (!originPoint || originPoint.longitude === undefined || originPoint.latitude === undefined) {
|
||
reject({ success: false, message: '缺少起点坐标' });
|
||
return;
|
||
}
|
||
try {
|
||
const originGcj = await convertToGcj02(originPoint.longitude || originPoint.lng, originPoint.latitude || originPoint.lat);
|
||
const convertedTargets = await Promise.all(targets.map(async (item) => {
|
||
const lng = item.longitude || item.lng;
|
||
const lat = item.latitude || item.lat;
|
||
const res = await convertToGcj02(lng, lat);
|
||
return `${res.longitude},${res.latitude}`;
|
||
}));
|
||
const origins = convertedTargets.join('|');
|
||
const destination = `${originGcj.longitude},${originGcj.latitude}`;
|
||
const data = await requestAmap('/distance', {
|
||
origins,
|
||
destination,
|
||
type: 3 // 3:步行距离
|
||
});
|
||
const results = data.results || [];
|
||
const distance = results.map(r => Number(r.distance || 0));
|
||
resolve({
|
||
success: true,
|
||
data: distance
|
||
});
|
||
} catch (error) {
|
||
console.error('高德距离计算失败:', error);
|
||
reject({ success: false, message: error.message || '计算距离失败' });
|
||
}
|
||
};
|
||
|
||
if (from && typeof from === 'object') {
|
||
doRequest(from);
|
||
} else {
|
||
// 未传入起点时,使用用户当前定位
|
||
getUserLocation().then(loc => {
|
||
doRequest({
|
||
longitude: loc.longitude,
|
||
latitude: loc.latitude
|
||
});
|
||
}).catch(err => {
|
||
reject({ success: false, message: err.message || '获取起点位置失败' });
|
||
});
|
||
}
|
||
return;
|
||
}
|
||
|
||
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 || '计算距离失败' });
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
// 计算距离(同步,使用球面距离公式)
|
||
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) => {
|
||
// 支付宝端使用高德输入提示
|
||
if (isAlipayEnv) {
|
||
requestAmap('/assistant/inputtips', {
|
||
keywords: keyword,
|
||
city: region === '全国' ? undefined : region,
|
||
datatype: 'all'
|
||
}).then((data) => {
|
||
const tips = data.tips || [];
|
||
const normalized = tips.map((item, idx) => ({
|
||
id: item.id || idx + 1,
|
||
title: item.name || '',
|
||
adcode: item.adcode || '',
|
||
address: item.address || '',
|
||
category: item.type || '',
|
||
city: item.city || '',
|
||
district: item.district || '',
|
||
latitude: item.location ? Number(item.location.split(',')[1]) : null,
|
||
longitude: item.location ? Number(item.location.split(',')[0]) : null,
|
||
province: item.province || ''
|
||
}));
|
||
resolve({
|
||
success: true,
|
||
data: normalized
|
||
});
|
||
}).catch((error) => {
|
||
console.error('高德关键词提示失败:', error);
|
||
reject({ success: false, message: error.message || '关键词提示失败' });
|
||
});
|
||
return;
|
||
}
|
||
|
||
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 || '关键词提示失败' });
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
// 导出函数
|
||
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;
|
||
}
|