142 lines
5.2 KiB
JavaScript
142 lines
5.2 KiB
JavaScript
import { defineAsyncComponent, h, onMounted, defineComponent } from 'vue'
|
|
import { ElEmpty, ElButton } from 'element-plus'
|
|
import { i18n } from '@/locales'
|
|
|
|
/**
|
|
* 增强的异步组件工厂函数
|
|
* 解决 PWA/Cloudflare 环境下分块加载失败导致的白屏问题
|
|
*/
|
|
export function createAsyncComponent(loader) {
|
|
return defineAsyncComponent({
|
|
loader: async () => {
|
|
console.log('[AsyncComponent] Starting to load component...')
|
|
try {
|
|
const comp = await loader()
|
|
console.log('[AsyncComponent] Component loaded successfully')
|
|
return comp
|
|
} catch (err) {
|
|
console.error('[AsyncComponent] Component load failed:', err)
|
|
throw err
|
|
}
|
|
},
|
|
|
|
// 加载中占位
|
|
loadingComponent: defineComponent({
|
|
name: 'AsyncLoading',
|
|
setup() {
|
|
const t = (key) => {
|
|
try {
|
|
return i18n.global.t(key)
|
|
} catch (e) {
|
|
return key
|
|
}
|
|
}
|
|
|
|
// 使用 CSS 关键帧动画注入(最轻量、无副作用)
|
|
const injectStyles = () => {
|
|
if (document.getElementById('async-loading-styles')) return
|
|
const style = document.createElement('style')
|
|
style.id = 'async-loading-styles'
|
|
style.textContent = `
|
|
@keyframes async-progress-flow {
|
|
0% { transform: translateX(-100%); }
|
|
100% { transform: translateX(100%); }
|
|
}
|
|
.async-progress-bar {
|
|
width: 200px;
|
|
height: 3px;
|
|
background: rgba(64, 158, 255, 0.1);
|
|
border-radius: 4px;
|
|
overflow: hidden;
|
|
position: relative;
|
|
margin-top: 16px;
|
|
}
|
|
.async-progress-inner {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 50%;
|
|
height: 100%;
|
|
background: var(--el-color-primary);
|
|
border-radius: 4px;
|
|
animation: async-progress-flow 1.5s infinite ease-in-out;
|
|
box-shadow: 0 0 8px var(--el-color-primary);
|
|
}
|
|
`
|
|
document.head.appendChild(style)
|
|
}
|
|
|
|
onMounted(injectStyles)
|
|
|
|
return () => h('div', {
|
|
style: {
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
minHeight: '400px',
|
|
width: '100%',
|
|
backgroundColor: 'transparent'
|
|
}
|
|
}, [
|
|
// 文案优化 - 适配多语言
|
|
h('div', {
|
|
style: {
|
|
fontSize: '16px',
|
|
color: 'var(--el-text-color-regular)',
|
|
fontWeight: '500',
|
|
letterSpacing: '1px'
|
|
}
|
|
}, t('common.resource_loading')),
|
|
// 线性进度条
|
|
h('div', { class: 'async-progress-bar' }, [
|
|
h('div', { class: 'async-progress-inner' })
|
|
])
|
|
])
|
|
}
|
|
}),
|
|
|
|
// 失败占位
|
|
errorComponent: defineComponent({
|
|
name: 'AsyncError',
|
|
props: ['error'],
|
|
setup(props) {
|
|
console.error('[AsyncComponent] Rendering error component:', props.error)
|
|
|
|
const t = (key) => {
|
|
try {
|
|
return i18n.global.t(key)
|
|
} catch (e) {
|
|
return key
|
|
}
|
|
}
|
|
|
|
return () => h('div', {
|
|
style: { padding: '40px', textAlign: 'center' }
|
|
}, [
|
|
h(ElEmpty, { description: t('common.loading_failed') }, {
|
|
default: () => h(ElButton, {
|
|
type: 'primary',
|
|
onClick: () => window.location.reload()
|
|
}, t('common.refresh_now'))
|
|
})
|
|
])
|
|
}
|
|
}),
|
|
|
|
// 延迟显示加载组件 (防止闪烁)
|
|
delay: 200,
|
|
// 超时设置 (15秒)
|
|
timeout: 15000,
|
|
// 错误重试逻辑
|
|
onError(error, retry, fail, attempts) {
|
|
if (attempts <= 2) {
|
|
console.warn(`[AsyncComponent] Loading failed, retrying (${attempts}/2)...`, error)
|
|
setTimeout(() => retry(), 500)
|
|
} else {
|
|
fail()
|
|
}
|
|
}
|
|
})
|
|
}
|