1422 lines
48 KiB
JavaScript
1422 lines
48 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] || ''
|
||
}
|
||
|
||
// 兼容多端:部分平台(如支付宝小程序)经纬度可能返回字符串
|
||
// 统一先转为 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'),
|
||
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;
|
||
}
|