/**
|
* 百度地图 SDK 加载工具
|
*
|
* 加载方式说明(踩过的坑):
|
*
|
* 1. 不能用 api?v=1.0&type=webgl&ak=xxx 这个地址。
|
* 它返回的是一段 document.write('<script src=".../getscript?...">'),
|
* 而通过 document.body.append(script) 动态插入的脚本里 document.write 会被浏览器拦截,
|
* 导致真正的 SDK 从未执行、window.BMapGL 一直是空;但 api 的 callback 仍会被调用,
|
* 调用方误判成功,接着 new BMapGL.Map() 就抛
|
* "Cannot read properties of null (reading 'Map')"。
|
*
|
* 2. 必须直连 getscript,它是 875KB 的真实 SDK。
|
*
|
* 3. 加载前要设置 window.BMAP_PROTOCOL = 'https'(百度官方要求),
|
* 否则 SDK 内部按默认协议拼子资源地址,HTTPS 页面下会被当作混合内容拦截。
|
*
|
* 4. 不要给 script 加 async = true:SDK 内部用 document.currentScript 定位自身,
|
* async 脚本执行期间 currentScript 为 null,会让它走到 fallback 分支甚至拿不到自身地址。
|
*
|
* 5. 失败重试一次:偶发网络抖动(尤其是跨省/移动网络)重试往往就能过。
|
*/
|
|
// 扩展 Window 接口以包含百度地图 GL API
|
declare global {
|
interface Window {
|
BMapGL: any;
|
BMAP_PROTOCOL: any;
|
BMapGL_loadScriptTime: any;
|
}
|
}
|
|
// SDK 加载状态
|
let loadPromise: null | Promise<void> = null;
|
// 加载失败次数,用于决定是否重试
|
let failCount = 0;
|
|
/** 单次尝试加载 */
|
function loadOnce(accessKey: string, timeout: number): Promise<void> {
|
return new Promise((resolve, reject) => {
|
const timeoutId = setTimeout(() => {
|
reject(new Error('百度地图 SDK 加载超时,请检查网络'));
|
}, timeout);
|
|
const script = document.createElement('script');
|
script.src = `https://api.map.baidu.com/getscript?type=webgl&v=1.0&ak=${accessKey}&services=&t=${Date.now()}`;
|
// 注意:不要设 async,SDK 依赖 document.currentScript 定位自身
|
|
script.addEventListener('load', () => {
|
clearTimeout(timeoutId);
|
if (!window.BMapGL) {
|
// ak 无效 / 域名未授权 / 被拦截时,脚本能加载但不会挂载 BMapGL
|
reject(
|
new Error('百度地图加载异常:未获取到地图对象,请检查 Key 或域名授权'),
|
);
|
return;
|
}
|
resolve();
|
});
|
|
// 事件名是 error,不是 onerror(写 onerror 永远不会触发)
|
script.addEventListener('error', () => {
|
clearTimeout(timeoutId);
|
reject(
|
new Error(
|
'百度地图 SDK 加载失败(无法访问 api.map.baidu.com,可能是网络限制或插件拦截)',
|
),
|
);
|
});
|
|
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();
|
}
|
|
// 正在加载中,返回同一个 Promise
|
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 子资源地址
|
if (typeof window !== 'undefined') {
|
window.BMAP_PROTOCOL = 'https';
|
window.BMapGL_loadScriptTime = Date.now();
|
}
|
|
loadPromise = (async () => {
|
try {
|
await loadOnce(accessKey, timeout);
|
} catch (error) {
|
// 首次失败重试一次,应对偶发网络抖动
|
if (failCount < 1) {
|
failCount += 1;
|
await loadOnce(accessKey, timeout);
|
} else {
|
failCount = 0;
|
loadPromise = null;
|
throw error;
|
}
|
}
|
})();
|
|
return loadPromise;
|
};
|