mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-06-08 01:00:36 +08:00
优化 Vite 配置,移除不再使用的代理规则,更新多个组件以增强远程组件加载逻辑,添加错误处理和加载状态显示,提升用户体验。
This commit is contained in:
@@ -7,8 +7,7 @@ import { useToast } from 'vue-toast-notification'
|
||||
import FormRender from '../render/FormRender.vue'
|
||||
import ProgressDialog from '../dialog/ProgressDialog.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import { loadRemoteComponent, clearRemoteComponentCache, ComponentType } from '@/utils/federationLoader'
|
||||
import { loadRemoteComponent } from '@/utils/federationLoader'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
@@ -47,46 +46,60 @@ const isRefreshed = ref(false)
|
||||
// 渲染模式: 'vuetify' 或 'vue'
|
||||
const renderMode = ref('vuetify')
|
||||
|
||||
// 挂载状态
|
||||
const componentMounted = ref(false)
|
||||
// 远程组件加载错误
|
||||
const remoteComponentError = ref<Error | string | null>(null)
|
||||
|
||||
// Vue 模式:动态加载的组件
|
||||
const dynamicComponent = defineAsyncComponent({
|
||||
// 工厂函数
|
||||
loader: async () => {
|
||||
if (renderMode.value !== 'vue' || !props.plugin?.id) {
|
||||
return { render: () => null }
|
||||
}
|
||||
|
||||
try {
|
||||
// 加载配置组件
|
||||
componentMounted.value = false
|
||||
const component = await loadRemoteComponent(props.plugin.id, ComponentType.CONFIG)
|
||||
componentMounted.value = true
|
||||
|
||||
if (!component) {
|
||||
throw new Error('组件加载失败')
|
||||
if (!props.plugin?.id) {
|
||||
throw new Error('插件ID不存在')
|
||||
}
|
||||
|
||||
return component
|
||||
} catch (error: any) {
|
||||
console.error(`加载插件配置组件失败: ${props.plugin.id}`, error)
|
||||
// 动态加载远程组件
|
||||
const module = await loadRemoteComponent(props.plugin.id, 'Config')
|
||||
|
||||
// 返回组件
|
||||
return module.default
|
||||
} catch (error) {
|
||||
console.error('加载远程组件失败:', error)
|
||||
remoteComponentError.value = error instanceof Error ? error.message : String(error)
|
||||
// 返回一个简单的错误组件
|
||||
return {
|
||||
render: () => h('div', { class: 'text-error pa-4' }, `加载失败: ${error.message || '未知错误'}`),
|
||||
template: `
|
||||
<div class="pa-4">
|
||||
<VAlert type="error" title="组件加载失败">
|
||||
无法加载远程组件: {{ error }}
|
||||
</VAlert>
|
||||
</div>
|
||||
`,
|
||||
props: ['error'],
|
||||
setup() {
|
||||
return { error: remoteComponentError.value }
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
// 加载中显示的组件
|
||||
loadingComponent: {
|
||||
render: () =>
|
||||
h('div', { class: 'text-center pa-4' }, [
|
||||
h('VProgressCircular', { indeterminate: true, class: 'mr-2' }),
|
||||
'加载组件中...',
|
||||
]),
|
||||
template: '<VSkeletonLoader type="card"></VSkeletonLoader>',
|
||||
},
|
||||
errorComponent: {
|
||||
render: () => h('div', { class: 'text-error pa-4 text-center' }, '组件加载失败'),
|
||||
},
|
||||
onError: error => {
|
||||
console.error('加载插件组件出错', error)
|
||||
// 如果加载组件超时
|
||||
timeout: 10000,
|
||||
// 在显示loadingComponent之前的延迟 | 默认值:200(毫秒)
|
||||
delay: 200,
|
||||
// 定义组件是否可挂起 | 默认值:true
|
||||
suspensible: false,
|
||||
onError(error, retry, fail, attempts) {
|
||||
if (attempts <= 3) {
|
||||
// 重试3次
|
||||
retry()
|
||||
} else {
|
||||
// 超过重试次数后不再重试
|
||||
fail()
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -97,11 +110,7 @@ async function loadPluginUIData() {
|
||||
pluginFormItems = []
|
||||
pluginConfigForm.value = {}
|
||||
renderMode.value = 'vuetify'
|
||||
|
||||
// 清除组件缓存
|
||||
if (props.plugin?.id) {
|
||||
clearRemoteComponentCache(props.plugin.id)
|
||||
}
|
||||
remoteComponentError.value = null
|
||||
|
||||
try {
|
||||
// 获取UI定义
|
||||
@@ -159,13 +168,6 @@ async function savePluginConf() {
|
||||
onBeforeMount(async () => {
|
||||
await loadPluginUIData()
|
||||
})
|
||||
|
||||
// 组件卸载时清理资源
|
||||
onUnmounted(() => {
|
||||
if (props.plugin?.id) {
|
||||
clearRemoteComponentCache(props.plugin.id, ComponentType.CONFIG)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<VDialog scrollable max-width="60rem" :fullscreen="!display.mdAndUp.value">
|
||||
@@ -182,6 +184,9 @@ onUnmounted(() => {
|
||||
<!-- Vue 渲染模式 -->
|
||||
<div v-else-if="renderMode === 'vue'">
|
||||
<component :is="dynamicComponent" :initial-config="pluginConfigForm" @save="handleVueComponentSave" />
|
||||
<div v-if="remoteComponentError">
|
||||
<v-alert type="error" title="组件加载失败"> 无法加载远程组件: {{ remoteComponentError }} </v-alert>
|
||||
</div>
|
||||
</div>
|
||||
</VCardText>
|
||||
<VCardActions class="pt-3">
|
||||
|
||||
@@ -3,9 +3,7 @@ import { useDisplay } from 'vuetify'
|
||||
import type { Plugin } from '@/api/types'
|
||||
import PageRender from '@/components/render/PageRender.vue'
|
||||
import api from '@/api'
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import { loadRemoteComponent, clearRemoteComponentCache, ComponentType } from '@/utils/federationLoader'
|
||||
import { loadRemoteComponent } from '@/utils/federationLoader'
|
||||
|
||||
// 输入参数
|
||||
const props = defineProps({
|
||||
@@ -22,58 +20,69 @@ const display = useDisplay()
|
||||
// APP
|
||||
const appMode = inject('pwaMode') && display.mdAndDown.value
|
||||
|
||||
// 提示框
|
||||
const $toast = useToast()
|
||||
|
||||
// 是否刷新
|
||||
const isRefreshed = ref(false)
|
||||
|
||||
// 渲染模式: 'vuetify' 或 'vue'
|
||||
const renderMode = ref('vuetify')
|
||||
|
||||
// 挂载状态
|
||||
const componentMounted = ref(false)
|
||||
|
||||
// 插件数据页面配置项
|
||||
let pluginPageItems = ref([])
|
||||
|
||||
// 远程组件加载错误
|
||||
const remoteComponentError = ref<Error | string | null>(null)
|
||||
|
||||
// Vue 模式:动态加载的组件
|
||||
const dynamicComponent = defineAsyncComponent({
|
||||
// 工厂函数
|
||||
loader: async () => {
|
||||
if (renderMode.value !== 'vue' || !props.plugin?.id) {
|
||||
return { render: () => null }
|
||||
}
|
||||
|
||||
try {
|
||||
// 加载页面组件
|
||||
componentMounted.value = false
|
||||
const component = await loadRemoteComponent(props.plugin.id, ComponentType.PAGE)
|
||||
componentMounted.value = true
|
||||
|
||||
if (!component) {
|
||||
throw new Error('组件加载失败')
|
||||
if (!props.plugin?.id) {
|
||||
throw new Error('插件ID不存在')
|
||||
}
|
||||
|
||||
return component
|
||||
} catch (error: any) {
|
||||
console.error(`加载插件页面组件失败: ${props.plugin.id}`, error)
|
||||
// 动态加载远程组件
|
||||
const module = await loadRemoteComponent(props.plugin.id, 'Page')
|
||||
|
||||
// 返回组件
|
||||
return module.default
|
||||
} catch (error) {
|
||||
console.error('加载远程组件失败:', error)
|
||||
remoteComponentError.value = error instanceof Error ? error.message : String(error)
|
||||
// 返回一个简单的错误组件
|
||||
return {
|
||||
render: () => h('div', { class: 'text-error pa-4' }, `加载失败: ${error.message || '未知错误'}`),
|
||||
template: `
|
||||
<div class="pa-4">
|
||||
<VAlert type="error" title="组件加载失败">
|
||||
无法加载远程组件: {{ error }}
|
||||
</VAlert>
|
||||
</div>
|
||||
`,
|
||||
props: ['error'],
|
||||
setup() {
|
||||
return { error: remoteComponentError.value }
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
// 加载中显示的组件
|
||||
loadingComponent: {
|
||||
render: () =>
|
||||
h('div', { class: 'text-center pa-4' }, [
|
||||
h('VProgressCircular', { indeterminate: true, class: 'mr-2' }),
|
||||
'加载组件中...',
|
||||
]),
|
||||
template: '<VSkeletonLoader type="card"></VSkeletonLoader>',
|
||||
},
|
||||
errorComponent: {
|
||||
render: () => h('div', { class: 'text-error pa-4 text-center' }, '组件加载失败'),
|
||||
},
|
||||
onError: error => {
|
||||
console.error('加载插件组件出错', error)
|
||||
// 如果加载组件超时
|
||||
timeout: 10000,
|
||||
// 在显示loadingComponent之前的延迟 | 默认值:200(毫秒)
|
||||
delay: 200,
|
||||
// 定义组件是否可挂起 | 默认值:true
|
||||
suspensible: false,
|
||||
onError(error, retry, fail, attempts) {
|
||||
if (attempts <= 3) {
|
||||
// 重试3次
|
||||
retry()
|
||||
} else {
|
||||
// 超过重试次数后不再重试
|
||||
fail()
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -82,11 +91,7 @@ async function loadPluginUIData() {
|
||||
isRefreshed.value = false
|
||||
pluginPageItems.value = []
|
||||
renderMode.value = 'vuetify'
|
||||
|
||||
// 清除组件缓存
|
||||
if (props.plugin?.id) {
|
||||
clearRemoteComponentCache(props.plugin.id)
|
||||
}
|
||||
remoteComponentError.value = null
|
||||
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get(`plugin/page/${props.plugin?.id}`)
|
||||
@@ -114,13 +119,6 @@ function handleAction() {
|
||||
onMounted(() => {
|
||||
loadPluginUIData()
|
||||
})
|
||||
|
||||
// 组件卸载时清理资源
|
||||
onUnmounted(() => {
|
||||
if (props.plugin?.id) {
|
||||
clearRemoteComponentCache(props.plugin.id, ComponentType.PAGE)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<VDialog scrollable max-width="80rem" :fullscreen="!display.mdAndUp.value">
|
||||
@@ -136,6 +134,9 @@ onUnmounted(() => {
|
||||
<!-- Vue 渲染模式 -->
|
||||
<div v-else-if="renderMode === 'vue'">
|
||||
<component :is="dynamicComponent" @action="handleAction" />
|
||||
<div v-if="remoteComponentError">
|
||||
<v-alert type="error" title="组件加载失败"> 无法加载远程组件: {{ remoteComponentError }} </v-alert>
|
||||
</div>
|
||||
</div>
|
||||
</VCardText>
|
||||
<VFab
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, defineAsyncComponent } from 'vue'
|
||||
import { DashboardItem } from '@/api/types'
|
||||
import AnalyticsMediaStatistic from '@/views/dashboard/AnalyticsMediaStatistic.vue'
|
||||
import AnalyticsScheduler from '@/views/dashboard/AnalyticsScheduler.vue'
|
||||
@@ -12,8 +13,7 @@ import MediaServerLibrary from '@/views/dashboard/MediaServerLibrary.vue'
|
||||
import MediaServerPlaying from '@/views/dashboard/MediaServerPlaying.vue'
|
||||
import DashboardRender from '@/components/render/DashboardRender.vue'
|
||||
import { isNullOrEmptyObject } from '@/@core/utils'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import { loadRemoteComponent, clearRemoteComponentCache, ComponentType } from '@/utils/federationLoader'
|
||||
import { loadRemoteComponent } from '@/utils/federationLoader'
|
||||
|
||||
// 输入参数
|
||||
const props = defineProps({
|
||||
@@ -33,57 +33,66 @@ const emit = defineEmits(['update:refreshStatus'])
|
||||
// 插件UI渲染模式 ('vuetify' 或 'vue')
|
||||
const pluginRenderMode = computed(() => props.config?.render_mode || 'vuetify')
|
||||
|
||||
// 挂载状态
|
||||
const componentMounted = ref(false)
|
||||
// 远程组件加载错误
|
||||
const remoteComponentError = ref<Error | string | null>(null)
|
||||
|
||||
// Vue 模式:动态加载的组件
|
||||
const dynamicPluginComponent = defineAsyncComponent({
|
||||
// 工厂函数
|
||||
loader: async () => {
|
||||
if (pluginRenderMode.value !== 'vue' || !props.config?.id) {
|
||||
return { render: () => null }
|
||||
}
|
||||
|
||||
try {
|
||||
// 加载仪表板组件
|
||||
componentMounted.value = false
|
||||
const component = await loadRemoteComponent(props.config.id, ComponentType.DASHBOARD)
|
||||
componentMounted.value = true
|
||||
|
||||
if (!component) {
|
||||
throw new Error('组件加载失败')
|
||||
if (!props.config?.id) {
|
||||
throw new Error('插件ID不存在')
|
||||
}
|
||||
|
||||
return component
|
||||
} catch (error: any) {
|
||||
console.error(`加载插件仪表板组件失败: ${props.config.id}`, error)
|
||||
// 动态加载远程组件
|
||||
const module = await loadRemoteComponent(props.config.id, 'Dashboard')
|
||||
|
||||
// 返回组件
|
||||
return module.default
|
||||
} catch (error) {
|
||||
console.error('加载远程组件失败:', error)
|
||||
remoteComponentError.value = error instanceof Error ? error.message : String(error)
|
||||
// 返回一个简单的错误组件
|
||||
return {
|
||||
render: () => h('div', { class: 'text-error pa-4' }, `加载失败: ${error.message || '未知错误'}`),
|
||||
template: `
|
||||
<div class="pa-4">
|
||||
<VAlert type="error" title="组件加载失败">
|
||||
无法加载远程组件: {{ error }}
|
||||
</VAlert>
|
||||
</div>
|
||||
`,
|
||||
props: ['error'],
|
||||
setup() {
|
||||
return { error: remoteComponentError.value }
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
// 加载中显示的组件
|
||||
loadingComponent: {
|
||||
render: () =>
|
||||
h('div', { class: 'text-center pa-4' }, [
|
||||
h('VProgressCircular', { indeterminate: true, class: 'mr-2' }),
|
||||
'加载组件中...',
|
||||
]),
|
||||
template: '<VSkeletonLoader type="card"></VSkeletonLoader>',
|
||||
},
|
||||
errorComponent: {
|
||||
render: () => h('div', { class: 'text-error pa-4 text-center' }, '组件加载失败'),
|
||||
},
|
||||
onError: error => {
|
||||
console.error('加载插件组件出错', error)
|
||||
// 如果加载组件超时
|
||||
timeout: 10000,
|
||||
// 在显示loadingComponent之前的延迟 | 默认值:200(毫秒)
|
||||
delay: 200,
|
||||
// 定义组件是否可挂起 | 默认值:true
|
||||
suspensible: false,
|
||||
onError(error, retry, fail, attempts) {
|
||||
if (attempts <= 3) {
|
||||
// 重试3次
|
||||
retry()
|
||||
} else {
|
||||
// 超过重试次数后不再重试
|
||||
fail()
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
// 组件卸载时禁用刷新状态
|
||||
emit('update:refreshStatus', false)
|
||||
|
||||
// 清理远程组件缓存
|
||||
if (props.config?.id) {
|
||||
clearRemoteComponentCache(props.config.id, ComponentType.DASHBOARD)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
@@ -103,6 +112,9 @@ onUnmounted(() => {
|
||||
<!-- Vue 渲染模式 -->
|
||||
<div v-if="pluginRenderMode === 'vue'">
|
||||
<component :is="dynamicPluginComponent" :config="props.config" :allow-refresh="props.allowRefresh" />
|
||||
<div v-if="remoteComponentError" class="mt-2">
|
||||
<VAlert type="error" title="组件加载失败"> 无法加载远程组件: {{ remoteComponentError }} </VAlert>
|
||||
</div>
|
||||
<!-- Vue 模式下也可以显示拖拽句柄 -->
|
||||
<div class="absolute right-5 top-5">
|
||||
<VIcon class="cursor-move">mdi-drag</VIcon>
|
||||
|
||||
Reference in New Issue
Block a user