359 lines
8.2 KiB
TypeScript
359 lines
8.2 KiB
TypeScript
/**
|
|
* 组件懒加载工具
|
|
*/
|
|
import { defineAsyncComponent, Component } from 'vue';
|
|
import { LoadingOutlined } from '@ant-design/icons-vue';
|
|
import { h } from 'vue';
|
|
|
|
// 加载状态组件
|
|
const LoadingComponent = {
|
|
setup() {
|
|
return () =>
|
|
h(
|
|
'div',
|
|
{
|
|
style: {
|
|
display: 'flex',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
minHeight: '200px',
|
|
fontSize: '16px',
|
|
color: '#999'
|
|
}
|
|
},
|
|
[h(LoadingOutlined, { style: { marginRight: '8px' } }), '加载中...']
|
|
);
|
|
}
|
|
};
|
|
|
|
// 错误状态组件
|
|
const ErrorComponent = {
|
|
props: ['error'],
|
|
setup(props: { error: Error }) {
|
|
return () =>
|
|
h(
|
|
'div',
|
|
{
|
|
style: {
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
minHeight: '200px',
|
|
padding: '20px',
|
|
color: '#ff4d4f',
|
|
backgroundColor: '#fff2f0',
|
|
border: '1px solid #ffccc7',
|
|
borderRadius: '6px'
|
|
}
|
|
},
|
|
[
|
|
h(
|
|
'div',
|
|
{ style: { fontSize: '16px', marginBottom: '8px' } },
|
|
'组件加载失败'
|
|
),
|
|
h(
|
|
'div',
|
|
{ style: { fontSize: '12px', color: '#999' } },
|
|
props.error.message
|
|
),
|
|
h(
|
|
'button',
|
|
{
|
|
style: {
|
|
marginTop: '12px',
|
|
padding: '4px 12px',
|
|
border: '1px solid #d9d9d9',
|
|
borderRadius: '4px',
|
|
backgroundColor: '#fff',
|
|
cursor: 'pointer'
|
|
},
|
|
onClick: () => window.location.reload()
|
|
},
|
|
'重新加载'
|
|
)
|
|
]
|
|
);
|
|
}
|
|
};
|
|
|
|
// 懒加载配置选项
|
|
interface LazyLoadOptions {
|
|
loading?: Component;
|
|
error?: Component;
|
|
delay?: number;
|
|
timeout?: number;
|
|
retries?: number;
|
|
retryDelay?: number;
|
|
}
|
|
|
|
// 默认配置
|
|
const defaultOptions: LazyLoadOptions = {
|
|
loading: LoadingComponent,
|
|
error: ErrorComponent,
|
|
delay: 200,
|
|
timeout: 30000,
|
|
retries: 3,
|
|
retryDelay: 1000
|
|
};
|
|
|
|
/**
|
|
* 创建懒加载组件
|
|
* @param loader 组件加载函数
|
|
* @param options 配置选项
|
|
*/
|
|
export function createLazyComponent(
|
|
loader: () => Promise<any>,
|
|
options: LazyLoadOptions = {}
|
|
) {
|
|
const config = { ...defaultOptions, ...options };
|
|
|
|
return defineAsyncComponent({
|
|
loader: createRetryLoader(loader, config.retries!, config.retryDelay!),
|
|
loadingComponent: config.loading,
|
|
errorComponent: config.error,
|
|
delay: config.delay,
|
|
timeout: config.timeout
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 创建带重试机制的加载器
|
|
*/
|
|
function createRetryLoader(
|
|
loader: () => Promise<any>,
|
|
retries: number,
|
|
retryDelay: number
|
|
) {
|
|
return async () => {
|
|
let lastError: Error;
|
|
|
|
for (let i = 0; i <= retries; i++) {
|
|
try {
|
|
return await loader();
|
|
} catch (error) {
|
|
lastError = error as Error;
|
|
|
|
if (i < retries) {
|
|
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
console.warn(`组件加载失败,正在重试 (${i + 1}/${retries}):`, error);
|
|
}
|
|
}
|
|
}
|
|
|
|
throw lastError!;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 路由懒加载
|
|
*/
|
|
export function lazyRoute(
|
|
loader: () => Promise<any>,
|
|
options?: LazyLoadOptions
|
|
) {
|
|
return createLazyComponent(loader, {
|
|
...options,
|
|
loading: options?.loading || {
|
|
setup() {
|
|
return () =>
|
|
h(
|
|
'div',
|
|
{
|
|
style: {
|
|
display: 'flex',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
minHeight: '60vh',
|
|
fontSize: '16px',
|
|
color: '#999'
|
|
}
|
|
},
|
|
[
|
|
h(LoadingOutlined, { style: { marginRight: '8px' } }),
|
|
'页面加载中...'
|
|
]
|
|
);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 模态框懒加载
|
|
*/
|
|
export function lazyModal(
|
|
loader: () => Promise<any>,
|
|
options?: LazyLoadOptions
|
|
) {
|
|
return createLazyComponent(loader, {
|
|
...options,
|
|
loading: options?.loading || {
|
|
setup() {
|
|
return () =>
|
|
h(
|
|
'div',
|
|
{
|
|
style: {
|
|
display: 'flex',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
minHeight: '300px',
|
|
fontSize: '14px',
|
|
color: '#999'
|
|
}
|
|
},
|
|
[h(LoadingOutlined, { style: { marginRight: '8px' } }), '加载中...']
|
|
);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 图表懒加载
|
|
*/
|
|
export function lazyChart(
|
|
loader: () => Promise<any>,
|
|
options?: LazyLoadOptions
|
|
) {
|
|
return createLazyComponent(loader, {
|
|
...options,
|
|
loading: options?.loading || {
|
|
setup() {
|
|
return () =>
|
|
h(
|
|
'div',
|
|
{
|
|
style: {
|
|
display: 'flex',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
minHeight: '400px',
|
|
backgroundColor: '#fafafa',
|
|
border: '1px dashed #d9d9d9',
|
|
borderRadius: '6px',
|
|
fontSize: '14px',
|
|
color: '#999'
|
|
}
|
|
},
|
|
[
|
|
h(LoadingOutlined, { style: { marginRight: '8px' } }),
|
|
'图表加载中...'
|
|
]
|
|
);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 预加载组件
|
|
*/
|
|
export class ComponentPreloader {
|
|
private preloadedComponents = new Map<string, Promise<any>>();
|
|
|
|
/**
|
|
* 预加载组件
|
|
*/
|
|
preload(key: string, loader: () => Promise<any>) {
|
|
if (!this.preloadedComponents.has(key)) {
|
|
this.preloadedComponents.set(key, loader());
|
|
}
|
|
return this.preloadedComponents.get(key)!;
|
|
}
|
|
|
|
/**
|
|
* 获取预加载的组件
|
|
*/
|
|
get(key: string) {
|
|
return this.preloadedComponents.get(key);
|
|
}
|
|
|
|
/**
|
|
* 清除预加载的组件
|
|
*/
|
|
clear(key?: string) {
|
|
if (key) {
|
|
this.preloadedComponents.delete(key);
|
|
} else {
|
|
this.preloadedComponents.clear();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 批量预加载
|
|
*/
|
|
batchPreload(components: Record<string, () => Promise<any>>) {
|
|
Object.entries(components).forEach(([key, loader]) => {
|
|
this.preload(key, loader);
|
|
});
|
|
}
|
|
}
|
|
|
|
// 全局预加载器实例
|
|
export const componentPreloader = new ComponentPreloader();
|
|
|
|
/**
|
|
* 智能懒加载 - 根据网络状况调整策略
|
|
*/
|
|
export function smartLazyComponent(
|
|
loader: () => Promise<any>,
|
|
options: LazyLoadOptions = {}
|
|
) {
|
|
// 检测网络状况
|
|
const connection = (navigator as any).connection;
|
|
const isSlowNetwork =
|
|
connection &&
|
|
(connection.effectiveType === 'slow-2g' ||
|
|
connection.effectiveType === '2g' ||
|
|
connection.saveData);
|
|
|
|
// 根据网络状况调整配置
|
|
const smartOptions = {
|
|
...options,
|
|
timeout: isSlowNetwork ? 60000 : options.timeout || 30000,
|
|
retries: isSlowNetwork ? 5 : options.retries || 3,
|
|
retryDelay: isSlowNetwork ? 2000 : options.retryDelay || 1000
|
|
};
|
|
|
|
return createLazyComponent(loader, smartOptions);
|
|
}
|
|
|
|
/**
|
|
* 可见性懒加载 - 只有当组件进入视口时才加载
|
|
*/
|
|
export function visibilityLazyComponent(
|
|
loader: () => Promise<any>,
|
|
options: LazyLoadOptions = {}
|
|
) {
|
|
return defineAsyncComponent({
|
|
loader: () => {
|
|
return new Promise((resolve, reject) => {
|
|
const observer = new IntersectionObserver((entries) => {
|
|
if (entries[0].isIntersecting) {
|
|
observer.disconnect();
|
|
loader().then(resolve).catch(reject);
|
|
}
|
|
});
|
|
|
|
// 创建一个占位元素来观察
|
|
const placeholder = document.createElement('div');
|
|
document.body.appendChild(placeholder);
|
|
observer.observe(placeholder);
|
|
|
|
// 清理函数
|
|
setTimeout(() => {
|
|
observer.disconnect();
|
|
document.body.removeChild(placeholder);
|
|
reject(new Error('Visibility timeout'));
|
|
}, options.timeout || 30000);
|
|
});
|
|
},
|
|
loadingComponent: options.loading || LoadingComponent,
|
|
errorComponent: options.error || ErrorComponent,
|
|
delay: options.delay || 200
|
|
});
|
|
}
|