1
This commit is contained in:
358
src/utils/lazy-load.ts
Normal file
358
src/utils/lazy-load.ts
Normal file
@@ -0,0 +1,358 @@
|
||||
/**
|
||||
* 组件懒加载工具
|
||||
*/
|
||||
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
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user