/**
|
* 百度地图 SDK 加载工具
|
*
|
* 加载地址优先级(按顺序尝试,都失败才报错):
|
* 1. /bmap-api/getscript —— 本站 nginx 反向代理,强制回源百度 IPv4
|
* 2. https://api.map.baidu.com/getscript —— 直连百度
|
*
|
* 为什么要代理(实测结论):
|
* 该部署环境(含客户端所在网络)IPv6 不通,而 api.map.baidu.com 的 DNS
|
* 优先返回 AAAA(IPv6)记录,浏览器优先连 IPv6 时立即失败——
|
* script 的 error 事件在 1 秒内就触发,根本不是超时。
|
* 服务器侧验证:curl -4 得到 HTTP 200 / 0.94s,curl -6 直接失败。
|
* 走同域代理后,客户端只访问本机 9023,由服务器用固定 IPv4 回源,
|
* 实测 HTTP 200 / 879KB / 0.28s。
|
*
|
* 其他踩过的坑:
|
* - 不能用 api?v=1.0&type=webgl(返回 document.write 包装,动态脚本里会被浏览器拦截)
|
* - 加载前要设 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('脚本已下载但未挂载地图对象'));
|
return;
|
}
|
resolve();
|
});
|
|
// 事件名是 error,不是 onerror(写 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 = 'https';
|
window.BMapGL_loadScriptTime = Date.now();
|
|
const query = `type=webgl&v=1.0&ak=${accessKey}&services=&t=${Date.now()}`;
|
// 优先同域反代(绕过客户端 IPv6 不通),再回退直连
|
const candidates = [
|
`/bmap-api/getscript?${query}`,
|
`https://api.map.baidu.com/getscript?${query}`,
|
];
|
|
loadPromise = (async () => {
|
let lastError: any;
|
for (const url of candidates) {
|
try {
|
await loadFrom(url, timeout);
|
return;
|
} catch (error) {
|
lastError = error;
|
}
|
}
|
loadPromise = null;
|
throw new Error(
|
`百度地图 SDK 加载失败(已尝试代理与直连):${lastError?.message ?? ''}`,
|
);
|
})();
|
|
return loadPromise;
|
};
|