/**
|
* 百度地图 SDK 加载工具
|
*
|
* ⚠️ 本部署环境(含客户端所在网络)IPv6 不通,而百度所有服务域名的 DNS
|
* 都优先返回 AAAA(IPv6)记录,浏览器优先连 IPv6 会立即失败——
|
* 表现为 script 的 error 事件 1 秒内触发,地图完全加载不出来。
|
* 服务器侧实测:curl -4 得到 HTTP 200 / 0.94s;curl -6 直接失败。
|
*
|
* 因此这里加载的是「本站改写版 SDK」/bmap-sdk.js:
|
* 把 SDK 内部所有百度域名(api.map.baidu.com、apimaponline0-3.bdimg.com、
|
* apisv0/1.bdimg.com、webmap0.bdimg.com、map.baidu.com …)
|
* 全部替换为 window.location.host + /bmap-xxx,
|
* 由服务器 nginx 用固定 IPv4 回源,客户端只访问本站 9023。
|
* (改写脚本生成,见 nginx bmap-proxy.conf 与 public/bmap-sdk.js)
|
*
|
* 加载顺序:
|
* 1. /bmap-sdk.js —— 本站改写版(瓦片、接口也全部走代理)
|
* 2. /bmap-api/getscript —— 本站未改写代理(兜底)
|
* 3. https://api.map.baidu.com/getscript —— 直连百度(IPv6 通的环境可用)
|
*
|
* 其他注意:加载前要设 window.BMAP_PROTOCOL = 'https';
|
* 不要给 script 加 async(SDK 依赖 document.currentScript);
|
* script 的事件名是 error 不是 onerror。
|
*/
|
|
// 扩展 Window 接口以包含百度地图 GL API
|
declare global {
|
interface Window {
|
BMapGL: any;
|
BMAP_PROTOCOL: any;
|
BMapGL_loadScriptTime: any;
|
}
|
}
|
|
// SDK 加载状态
|
let loadPromise: null | Promise<void> = null;
|
|
/** 用指定地址加载一次 */
|
function loadFrom(url: string, timeout: number): Promise<void> {
|
return new Promise((resolve, reject) => {
|
const timeoutId = setTimeout(() => {
|
reject(new Error(`百度地图 SDK 加载超时:${url}`));
|
}, timeout);
|
|
const script = document.createElement('script');
|
script.src = url;
|
// 不要设 async:SDK 依赖 document.currentScript 定位自身
|
|
script.addEventListener('load', () => {
|
clearTimeout(timeoutId);
|
if (!window.BMapGL) {
|
reject(new Error(`脚本已下载但未挂载地图对象:${url}`));
|
return;
|
}
|
resolve();
|
});
|
|
// 事件名是 error,不是 onerror
|
script.addEventListener('error', () => {
|
clearTimeout(timeoutId);
|
reject(new Error(`加载失败:${url}`));
|
});
|
|
document.body.append(script);
|
});
|
}
|
|
/**
|
* 加载百度地图 GL SDK
|
* @param timeout 单次尝试的超时时间(毫秒),默认 10000
|
* @returns Promise<void>
|
*/
|
export const loadBaiduMapSdk = (timeout = 10_000): Promise<void> => {
|
if (window.BMapGL) {
|
return Promise.resolve();
|
}
|
if (loadPromise) {
|
return loadPromise;
|
}
|
|
const accessKey = String(import.meta.env.VITE_BAIDU_MAP_KEY ?? '').trim();
|
if (!accessKey) {
|
return Promise.reject(new Error('未配置 VITE_BAIDU_MAP_KEY'));
|
}
|
|
// 百度官方要求:先声明协议,避免 HTTPS 页面下 SDK 拼出 http 子资源地址
|
window.BMAP_PROTOCOL = window.location.protocol.replace(':', '');
|
window.BMapGL_loadScriptTime = Date.now();
|
|
const query = `type=webgl&v=1.0&ak=${accessKey}&services=&t=${Date.now()}`;
|
const candidates = [
|
// 本站改写版:内部域名全部指向本站代理,瓦片/接口都走 IPv4
|
'/bmap-sdk.js',
|
// 本站代理的官方 SDK(未改写,瓦片仍指向百度,仅作兜底)
|
`/bmap-api/getscript?${query}`,
|
// 直连百度(仅 IPv6 正常的环境有效)
|
`https://api.map.baidu.com/getscript?${query}`,
|
];
|
|
loadPromise = (async () => {
|
const errors: string[] = [];
|
for (const url of candidates) {
|
try {
|
await loadFrom(url, timeout);
|
return;
|
} catch (error: any) {
|
errors.push(error?.message ?? String(error));
|
}
|
}
|
loadPromise = null;
|
throw new Error(`百度地图 SDK 加载失败:${errors.join(' | ')}`);
|
})();
|
|
return loadPromise;
|
};
|