2026-04-28 09:38:18 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 懒加载工具模块
|
|
|
|
|
|
*
|
|
|
|
|
|
* 提供按需动态导入辅助函数,支持:
|
|
|
|
|
|
* 1. 首次加载时自动调用初始化函数(如 bind*Controls)
|
|
|
|
|
|
* 2. 包裹目标函数,实现 window.xxx = (...args) => 自动加载并调用
|
|
|
|
|
|
* 3. 模块缓存机制,避免重复加载
|
|
|
|
|
|
* 4. Alpine 组件懒加载:返回同步 stub,在 $watch 触发时异步加载真实组件
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建一个可延迟加载的模块引用。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {() => Promise<*>} importFn 动态 import 工厂函数
|
|
|
|
|
|
* @param {(module: *) => void} [initFn] 首次加载成功后调用的初始化回调
|
|
|
|
|
|
* @returns {{ load: () => Promise<*>, wrap: (name: string) => Function, get: (name: string) => Function }}
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function createLazyModule(importFn, initFn) {
|
|
|
|
|
|
/** @type {Promise<*>|null} */
|
|
|
|
|
|
let promise = null;
|
|
|
|
|
|
let initialized = false;
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 确保模块已加载,返回模块对象。
|
|
|
|
|
|
* @returns {Promise<*>}
|
|
|
|
|
|
*/
|
|
|
|
|
|
async load() {
|
|
|
|
|
|
if (!promise) {
|
|
|
|
|
|
promise = importFn().then((mod) => {
|
|
|
|
|
|
if (!initialized && initFn) {
|
|
|
|
|
|
initialized = true;
|
|
|
|
|
|
initFn(mod);
|
|
|
|
|
|
}
|
|
|
|
|
|
return mod;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
return promise;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建一个延迟执行的目标函数包装器。
|
|
|
|
|
|
* 首次调用时自动加载模块,之后直接调用缓存。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {string} fnName 模块中导出的函数名
|
|
|
|
|
|
* @returns {Function}
|
|
|
|
|
|
*/
|
|
|
|
|
|
wrap(fnName) {
|
|
|
|
|
|
return async (...args) => {
|
|
|
|
|
|
const mod = await this.load();
|
|
|
|
|
|
const fn = mod[fnName];
|
|
|
|
|
|
if (typeof fn !== "function") {
|
|
|
|
|
|
throw new Error(
|
|
|
|
|
|
`懒加载模块中找不到函数 "${fnName}"`,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
return fn(...args);
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 返回一个返回模块导出的 getter 函数。
|
|
|
|
|
|
* 适用于返回非函数值(如 Alpine 组件对象)。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {string} name 模块中导出的名称
|
|
|
|
|
|
* @returns {Function}
|
|
|
|
|
|
*/
|
|
|
|
|
|
get(name) {
|
|
|
|
|
|
return async () => {
|
|
|
|
|
|
const mod = await this.load();
|
|
|
|
|
|
return mod[name];
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建 Alpine 组件延迟加载包装器。
|
|
|
|
|
|
*
|
|
|
|
|
|
* Alpine 的 x-data="ComponentName()" 要求工厂函数返回一个同步对象。
|
|
|
|
|
|
* 本函数返回一个函数,该函数:
|
|
|
|
|
|
* 1. 立即返回一个包含安全默认值的 stub 对象
|
|
|
|
|
|
* 2. 通过 Alpine 的 $watch 监听显示状态变化
|
|
|
|
|
|
* 3. 仅当组件变为可见(show/showUserModal 变为 true)时,才异步加载真实模块
|
|
|
|
|
|
* 4. 通过 Alpine 的响应式代理(this)写入真实数据,触发模板重新渲染
|
|
|
|
|
|
*
|
|
|
|
|
|
* 这实现了"真懒加载"——用户若不打开面板,对应代码块永远不会下载。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {() => Promise<*>} importFn 动态 import 工厂函数
|
|
|
|
|
|
* @param {string} exportName 模块导出的组件工厂函数名
|
|
|
|
|
|
* @param {Record<string, any>} [defaults={}] 安全默认值
|
|
|
|
|
|
* @param {string} [watchKey='show'] 用于触发懒加载的 $watch 属性名
|
|
|
|
|
|
* @returns {Function} Alpine 组件工厂函数
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function createLazyAlpineComponent(importFn, exportName, defaults = {}, watchKey = "show") {
|
|
|
|
|
|
return function (...args) {
|
|
|
|
|
|
const stub = {
|
|
|
|
|
|
[watchKey]: false,
|
|
|
|
|
|
...defaults,
|
|
|
|
|
|
init() {
|
|
|
|
|
|
const proxy = this;
|
|
|
|
|
|
let loaded = false;
|
|
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
|
watchKey in proxy &&
|
|
|
|
|
|
typeof proxy.$watch === "function"
|
|
|
|
|
|
) {
|
2026-04-28 09:42:18 +08:00
|
|
|
|
proxy.$watch(watchKey, (value) => {
|
2026-04-28 09:38:18 +08:00
|
|
|
|
if (value && !loaded) {
|
|
|
|
|
|
loaded = true;
|
|
|
|
|
|
importFn()
|
|
|
|
|
|
.then((mod) => {
|
|
|
|
|
|
const componentFn = mod[exportName];
|
|
|
|
|
|
const realData =
|
|
|
|
|
|
typeof componentFn === "function"
|
|
|
|
|
|
? componentFn(...args)
|
|
|
|
|
|
: componentFn;
|
|
|
|
|
|
|
|
|
|
|
|
Object.keys(realData).forEach((key) => {
|
|
|
|
|
|
if (key !== "init") {
|
|
|
|
|
|
proxy[key] = realData[key];
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
for (const key in realData) {
|
|
|
|
|
|
if (!(key in proxy)) {
|
|
|
|
|
|
proxy[key] = realData[key];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
|
typeof proxy.init === "function" &&
|
|
|
|
|
|
proxy.init !== stub.init
|
|
|
|
|
|
) {
|
|
|
|
|
|
proxy.init.call(proxy);
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch((err) => {
|
|
|
|
|
|
console.error(
|
|
|
|
|
|
`[懒加载] Alpine 组件 "${exportName}" 加载失败:`,
|
|
|
|
|
|
err,
|
|
|
|
|
|
);
|
2026-04-28 09:42:18 +08:00
|
|
|
|
loaded = false;
|
2026-04-28 09:38:18 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-28 09:42:18 +08:00
|
|
|
|
return stub;
|
2026-04-28 09:38:18 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|