mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-27 03:09:45 +08:00
优化多个组件的远程加载逻辑,移除不必要的属性绑定,增强错误处理机制,提升用户体验。
This commit is contained in:
@@ -1,14 +1,11 @@
|
||||
import type { ValidationRule } from 'vuetify/types/services/validation'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 必输校验
|
||||
export const requiredValidator: ValidationRule = (value: any) => {
|
||||
const { t } = useI18n()
|
||||
return !!value || t('validators.required')
|
||||
return !!value
|
||||
}
|
||||
|
||||
// 数字校验
|
||||
export const numberValidator: ValidationRule = (value: any) => {
|
||||
const { t } = useI18n()
|
||||
return !isNaN(value) || t('validators.number')
|
||||
return !isNaN(value)
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ const getImgUrl = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VHover v-bind="props">
|
||||
<VHover>
|
||||
<template #default="hover">
|
||||
<VCard
|
||||
v-bind="hover.props"
|
||||
|
||||
@@ -151,7 +151,7 @@ onMounted(async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VHover v-bind="props" :height="props.height" :width="props.width">
|
||||
<VHover>
|
||||
<template #default="hover">
|
||||
<VCard
|
||||
v-bind="hover.props"
|
||||
|
||||
@@ -37,7 +37,7 @@ function goPlay(isHovering: boolean | null = false) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VHover v-bind="props">
|
||||
<VHover>
|
||||
<template #default="hover">
|
||||
<VCard
|
||||
v-bind="hover.props"
|
||||
|
||||
@@ -46,9 +46,6 @@ const isRefreshed = ref(false)
|
||||
// 渲染模式: 'vuetify' 或 'vue'
|
||||
const renderMode = ref('vuetify')
|
||||
|
||||
// 远程组件加载错误
|
||||
const remoteComponentError = ref<Error | string | null>(null)
|
||||
|
||||
// Vue 模式:动态加载的组件
|
||||
const dynamicComponent = defineAsyncComponent({
|
||||
// 工厂函数
|
||||
@@ -61,46 +58,28 @@ const dynamicComponent = defineAsyncComponent({
|
||||
// 动态加载远程组件
|
||||
const module = await loadRemoteComponent(props.plugin.id, 'Config')
|
||||
|
||||
// 返回组件
|
||||
return module.default
|
||||
// 直接返回加载的组件,无需再获取default
|
||||
return module
|
||||
} catch (error) {
|
||||
console.error('加载远程组件失败:', error)
|
||||
remoteComponentError.value = error instanceof Error ? error.message : String(error)
|
||||
// 返回一个简单的错误组件
|
||||
return {
|
||||
template: `
|
||||
<div class="pa-4">
|
||||
<VAlert type="error" title="组件加载失败">
|
||||
无法加载远程组件: {{ error }}
|
||||
</VAlert>
|
||||
</div>
|
||||
`,
|
||||
props: ['error'],
|
||||
setup() {
|
||||
return { error: remoteComponentError.value }
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
// 加载中显示的组件
|
||||
loadingComponent: {
|
||||
template: '<VSkeletonLoader type="card"></VSkeletonLoader>',
|
||||
},
|
||||
// 如果加载组件超时
|
||||
timeout: 10000,
|
||||
// 在显示loadingComponent之前的延迟 | 默认值:200(毫秒)
|
||||
delay: 200,
|
||||
// 定义组件是否可挂起 | 默认值:true
|
||||
suspensible: false,
|
||||
onError(error, retry, fail, attempts) {
|
||||
if (attempts <= 3) {
|
||||
// 重试3次
|
||||
retry()
|
||||
} else {
|
||||
// 超过重试次数后不再重试
|
||||
fail()
|
||||
}
|
||||
// 添加错误处理
|
||||
errorComponent: {
|
||||
template: `
|
||||
<div class="pa-4">
|
||||
<VAlert type="error" title="组件加载错误">
|
||||
无法加载组件,请稍后再试
|
||||
</VAlert>
|
||||
</div>
|
||||
`,
|
||||
},
|
||||
// 添加超时设置
|
||||
timeout: 20000,
|
||||
})
|
||||
|
||||
//调用API读取UI和配置数据
|
||||
@@ -110,7 +89,6 @@ async function loadPluginUIData() {
|
||||
pluginFormItems = []
|
||||
pluginConfigForm.value = {}
|
||||
renderMode.value = 'vuetify'
|
||||
remoteComponentError.value = null
|
||||
|
||||
try {
|
||||
// 获取UI定义
|
||||
@@ -184,9 +162,6 @@ onBeforeMount(async () => {
|
||||
<!-- 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">
|
||||
|
||||
@@ -29,9 +29,6 @@ const renderMode = ref('vuetify')
|
||||
// 插件数据页面配置项
|
||||
let pluginPageItems = ref([])
|
||||
|
||||
// 远程组件加载错误
|
||||
const remoteComponentError = ref<Error | string | null>(null)
|
||||
|
||||
// Vue 模式:动态加载的组件
|
||||
const dynamicComponent = defineAsyncComponent({
|
||||
// 工厂函数
|
||||
@@ -44,46 +41,27 @@ const dynamicComponent = defineAsyncComponent({
|
||||
// 动态加载远程组件
|
||||
const module = await loadRemoteComponent(props.plugin.id, 'Page')
|
||||
|
||||
// 返回组件
|
||||
return module.default
|
||||
return module
|
||||
} catch (error) {
|
||||
console.error('加载远程组件失败:', error)
|
||||
remoteComponentError.value = error instanceof Error ? error.message : String(error)
|
||||
// 返回一个简单的错误组件
|
||||
return {
|
||||
template: `
|
||||
<div class="pa-4">
|
||||
<VAlert type="error" title="组件加载失败">
|
||||
无法加载远程组件: {{ error }}
|
||||
</VAlert>
|
||||
</div>
|
||||
`,
|
||||
props: ['error'],
|
||||
setup() {
|
||||
return { error: remoteComponentError.value }
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
// 加载中显示的组件
|
||||
loadingComponent: {
|
||||
template: '<VSkeletonLoader type="card"></VSkeletonLoader>',
|
||||
},
|
||||
// 如果加载组件超时
|
||||
timeout: 10000,
|
||||
// 在显示loadingComponent之前的延迟 | 默认值:200(毫秒)
|
||||
delay: 200,
|
||||
// 定义组件是否可挂起 | 默认值:true
|
||||
suspensible: false,
|
||||
onError(error, retry, fail, attempts) {
|
||||
if (attempts <= 3) {
|
||||
// 重试3次
|
||||
retry()
|
||||
} else {
|
||||
// 超过重试次数后不再重试
|
||||
fail()
|
||||
}
|
||||
// 添加错误处理
|
||||
errorComponent: {
|
||||
template: `
|
||||
<div class="pa-4">
|
||||
<VAlert type="error" title="组件加载错误">
|
||||
无法加载组件,请稍后再试
|
||||
</VAlert>
|
||||
</div>
|
||||
`,
|
||||
},
|
||||
// 添加超时设置
|
||||
timeout: 20000,
|
||||
})
|
||||
|
||||
// 调用API读取数据页面UI
|
||||
@@ -91,7 +69,6 @@ async function loadPluginUIData() {
|
||||
isRefreshed.value = false
|
||||
pluginPageItems.value = []
|
||||
renderMode.value = 'vuetify'
|
||||
remoteComponentError.value = null
|
||||
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get(`plugin/page/${props.plugin?.id}`)
|
||||
@@ -134,9 +111,6 @@ onMounted(() => {
|
||||
<!-- 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
|
||||
|
||||
@@ -33,9 +33,6 @@ const emit = defineEmits(['update:refreshStatus'])
|
||||
// 插件UI渲染模式 ('vuetify' 或 'vue')
|
||||
const pluginRenderMode = computed(() => props.config?.render_mode || 'vuetify')
|
||||
|
||||
// 远程组件加载错误
|
||||
const remoteComponentError = ref<Error | string | null>(null)
|
||||
|
||||
// Vue 模式:动态加载的组件
|
||||
const dynamicPluginComponent = defineAsyncComponent({
|
||||
// 工厂函数
|
||||
@@ -48,45 +45,25 @@ const dynamicPluginComponent = defineAsyncComponent({
|
||||
// 动态加载远程组件
|
||||
const module = await loadRemoteComponent(props.config.id, 'Dashboard')
|
||||
|
||||
// 返回组件
|
||||
return module.default
|
||||
// 直接返回加载的组件,无需再获取default
|
||||
return module
|
||||
} catch (error) {
|
||||
console.error('加载远程组件失败:', error)
|
||||
remoteComponentError.value = error instanceof Error ? error.message : String(error)
|
||||
// 返回一个简单的错误组件
|
||||
return {
|
||||
template: `
|
||||
<div class="pa-4">
|
||||
<VAlert type="error" title="组件加载失败">
|
||||
无法加载远程组件: {{ error }}
|
||||
</VAlert>
|
||||
</div>
|
||||
`,
|
||||
props: ['error'],
|
||||
setup() {
|
||||
return { error: remoteComponentError.value }
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
// 加载中显示的组件
|
||||
loadingComponent: {
|
||||
template: '<VSkeletonLoader type="card"></VSkeletonLoader>',
|
||||
},
|
||||
// 如果加载组件超时
|
||||
timeout: 10000,
|
||||
// 在显示loadingComponent之前的延迟 | 默认值:200(毫秒)
|
||||
delay: 200,
|
||||
// 定义组件是否可挂起 | 默认值:true
|
||||
suspensible: false,
|
||||
onError(error, retry, fail, attempts) {
|
||||
if (attempts <= 3) {
|
||||
// 重试3次
|
||||
retry()
|
||||
} else {
|
||||
// 超过重试次数后不再重试
|
||||
fail()
|
||||
}
|
||||
// 添加错误处理
|
||||
errorComponent: {
|
||||
template: `
|
||||
<div class="pa-4">
|
||||
<VAlert type="error" title="组件加载错误">
|
||||
无法加载组件,请稍后再试
|
||||
</VAlert>
|
||||
</div>
|
||||
`,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -112,9 +89,6 @@ 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>
|
||||
|
||||
@@ -7,20 +7,16 @@ import { requiredValidator } from '@/@validators'
|
||||
import api from '@/api'
|
||||
import router from '@/router'
|
||||
import logo from '@images/logo.png'
|
||||
import { useTheme } from 'vuetify'
|
||||
import { urlBase64ToUint8Array } from '@/@core/utils/navigator'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { SUPPORTED_LOCALES, SupportedLocale } from '@/types/i18n'
|
||||
import { getCurrentLocale, setI18nLanguage } from '@/plugins/i18n'
|
||||
|
||||
// 主题
|
||||
const { global: globalTheme } = useTheme()
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
// 认证 Store
|
||||
const authStore = useAuthStore()
|
||||
//用户 Store
|
||||
const userStore = useUserStore()
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
// 表单
|
||||
const form = ref({
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import api from '@/api'
|
||||
|
||||
// 为Window接口扩展__FEDERATION__属性
|
||||
declare global {
|
||||
interface Window {
|
||||
__FEDERATION__: Record<string, { url: string }>
|
||||
}
|
||||
}
|
||||
import {
|
||||
__federation_method_setRemote,
|
||||
__federation_method_getRemote,
|
||||
__federation_method_unwrapDefault,
|
||||
// @ts-ignore
|
||||
} from 'virtual:__federation__'
|
||||
|
||||
// 定义远程模块接口
|
||||
interface RemoteModule {
|
||||
@@ -13,6 +12,21 @@ interface RemoteModule {
|
||||
url: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载远程组件
|
||||
* @param id 远程模块ID
|
||||
* @param componentName 组件名称 (如 'Page')
|
||||
*/
|
||||
export async function loadRemoteComponent(id: string, componentName: string = 'Page') {
|
||||
try {
|
||||
const module = await __federation_method_getRemote(id, `./${componentName}`)
|
||||
return __federation_method_unwrapDefault(module)
|
||||
} catch (error) {
|
||||
console.error(`加载远程组件失败: ${id}/${componentName}`, error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从API获取远程模块列表
|
||||
*/
|
||||
@@ -30,56 +44,19 @@ async function fetchRemoteModules(): Promise<RemoteModule[]> {
|
||||
* 动态注入Federation Remote模块
|
||||
* @param modules 远程模块列表
|
||||
*/
|
||||
function injectRemoteModules(modules: RemoteModule[]): void {
|
||||
if (!modules || modules.length === 0) return
|
||||
|
||||
// 创建Federation变量
|
||||
const federation: Record<string, { url: string }> = {}
|
||||
|
||||
// 设置每个远程模块
|
||||
modules.forEach(module => {
|
||||
federation[module.id] = { url: module.url }
|
||||
function injectRemoteModule(module: RemoteModule): void {
|
||||
__federation_method_setRemote(module.id, {
|
||||
url: () => Promise.resolve(`${import.meta.env.VITE_API_BASE_URL}/${module.url}`),
|
||||
format: 'esm',
|
||||
from: 'vite',
|
||||
})
|
||||
|
||||
// 全局注入Federation变量供加载远程模块使用
|
||||
window.__FEDERATION__ = federation
|
||||
|
||||
console.log('已注入远程模块:', federation)
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载远程组件
|
||||
* @param id 远程模块ID
|
||||
* @param componentName 组件名称 (如 'Page')
|
||||
*/
|
||||
export async function loadRemoteComponent(id: string, componentName: string = 'Page') {
|
||||
try {
|
||||
// 检查远程模块是否已经注册
|
||||
if (!window.__FEDERATION__ || !window.__FEDERATION__[id]) {
|
||||
// 如果未注册,尝试重新初始化
|
||||
const modules = await fetchRemoteModules()
|
||||
injectRemoteModules(modules)
|
||||
|
||||
// 再次检查
|
||||
if (!window.__FEDERATION__ || !window.__FEDERATION__[id]) {
|
||||
throw new Error(`远程模块 ${id} 未注册`)
|
||||
}
|
||||
}
|
||||
|
||||
// 模块联邦格式导入,不需要.vue扩展名
|
||||
console.log(`正在加载远程组件: ${id}/${componentName}`)
|
||||
// @ts-ignore - 动态导入远程模块
|
||||
return await import(`${id}/${componentName}`)
|
||||
} catch (error) {
|
||||
console.error(`加载远程组件失败: ${id}/${componentName}`, error)
|
||||
throw error
|
||||
}
|
||||
console.log('已注入远程模块:', module)
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化并加载所有远程组件
|
||||
*/
|
||||
export async function loadRemoteComponents(): Promise<boolean> {
|
||||
export async function loadRemoteComponents(): Promise<void> {
|
||||
try {
|
||||
// 获取远程模块列表
|
||||
const modules = await fetchRemoteModules()
|
||||
@@ -87,14 +64,13 @@ export async function loadRemoteComponents(): Promise<boolean> {
|
||||
// 确保有模块才注入
|
||||
if (modules && modules.length > 0) {
|
||||
// 注入远程模块
|
||||
injectRemoteModules(modules)
|
||||
return true
|
||||
modules.forEach(module => {
|
||||
injectRemoteModule(module)
|
||||
})
|
||||
} else {
|
||||
console.log('没有发现可用的远程模块')
|
||||
return false
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载远程组件失败:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user