130 lines
3.3 KiB
TypeScript
130 lines
3.3 KiB
TypeScript
/**
|
||
* 文件职责:按需加载腾讯地图 JS SDK。
|
||
* 1. 使用全局 callback + 单例 Promise,避免重复注入脚本。
|
||
* 2. 与 AdminUI 的加载策略保持一致,降低多实例冲突风险。
|
||
*/
|
||
|
||
declare global {
|
||
interface Window {
|
||
TMap?: any;
|
||
__tenantTencentMapInit?: () => void;
|
||
}
|
||
}
|
||
|
||
const SCRIPT_ID = 'tenant-tencent-map-gljs-sdk';
|
||
const CALLBACK_NAME = '__tenantTencentMapInit';
|
||
const SCRIPT_LOAD_TIMEOUT_MS = 12_000;
|
||
const TENCENT_MAP_LIBRARIES = 'visualization,geometry,vector,tools,service';
|
||
|
||
let mapSdkPromise: null | Promise<any> = null;
|
||
let scriptLoading = false;
|
||
const pendingResolvers: Array<(value: any) => void> = [];
|
||
const pendingRejectors: Array<(error: Error) => void> = [];
|
||
|
||
function getTencentMapKey() {
|
||
return (import.meta.env.VITE_TENCENT_MAP_KEY as string | undefined)?.trim();
|
||
}
|
||
|
||
function flushSuccess(tmap: any) {
|
||
const resolvers = pendingResolvers.splice(0);
|
||
pendingRejectors.splice(0);
|
||
resolvers.forEach((resolve) => resolve(tmap));
|
||
}
|
||
|
||
function flushError(error: Error) {
|
||
const rejectors = pendingRejectors.splice(0);
|
||
pendingResolvers.splice(0);
|
||
rejectors.forEach((reject) => reject(error));
|
||
}
|
||
|
||
function buildScriptUrl(mapKey: string) {
|
||
return `https://map.qq.com/api/gljs?v=1.exp&key=${encodeURIComponent(
|
||
mapKey,
|
||
)}&libraries=${TENCENT_MAP_LIBRARIES}&callback=${CALLBACK_NAME}`;
|
||
}
|
||
|
||
export async function loadTencentMapSdk() {
|
||
if (typeof window === 'undefined') {
|
||
throw new TypeError('当前环境不支持加载地图');
|
||
}
|
||
|
||
if (window.TMap) {
|
||
return window.TMap;
|
||
}
|
||
|
||
const mapKey = getTencentMapKey();
|
||
if (!mapKey) {
|
||
throw new Error('未配置腾讯地图 Key(VITE_TENCENT_MAP_KEY)');
|
||
}
|
||
|
||
if (mapSdkPromise) {
|
||
return mapSdkPromise;
|
||
}
|
||
|
||
mapSdkPromise = new Promise<any>((resolve, reject) => {
|
||
pendingResolvers.push(resolve);
|
||
pendingRejectors.push(reject);
|
||
|
||
if (scriptLoading) {
|
||
return;
|
||
}
|
||
scriptLoading = true;
|
||
|
||
const completeWithError = (error: Error) => {
|
||
scriptLoading = false;
|
||
mapSdkPromise = null;
|
||
flushError(error);
|
||
};
|
||
|
||
const timeoutHandle = window.setTimeout(() => {
|
||
completeWithError(new Error('腾讯地图 SDK 加载超时'));
|
||
}, SCRIPT_LOAD_TIMEOUT_MS);
|
||
|
||
window[CALLBACK_NAME] = () => {
|
||
window.clearTimeout(timeoutHandle);
|
||
scriptLoading = false;
|
||
|
||
if (!window.TMap) {
|
||
completeWithError(new Error('腾讯地图 SDK 加载失败'));
|
||
return;
|
||
}
|
||
|
||
flushSuccess(window.TMap);
|
||
};
|
||
|
||
const existingScript = document.querySelector<HTMLScriptElement>(
|
||
`#${SCRIPT_ID}`,
|
||
);
|
||
if (existingScript) {
|
||
existingScript.addEventListener(
|
||
'error',
|
||
() => {
|
||
window.clearTimeout(timeoutHandle);
|
||
completeWithError(new Error('腾讯地图 SDK 加载失败'));
|
||
},
|
||
{ once: true },
|
||
);
|
||
return;
|
||
}
|
||
|
||
const script = document.createElement('script');
|
||
script.id = SCRIPT_ID;
|
||
script.type = 'text/javascript';
|
||
script.async = true;
|
||
script.defer = true;
|
||
script.src = buildScriptUrl(mapKey);
|
||
script.addEventListener(
|
||
'error',
|
||
() => {
|
||
window.clearTimeout(timeoutHandle);
|
||
completeWithError(new Error('腾讯地图 SDK 加载失败'));
|
||
},
|
||
{ once: true },
|
||
);
|
||
|
||
document.body.append(script);
|
||
});
|
||
|
||
return mapSdkPromise;
|
||
}
|