优化 Vite 配置,移除不再使用的代理规则,更新多个组件以增强远程组件加载逻辑,添加错误处理和加载状态显示,提升用户体验。

This commit is contained in:
jxxghp
2025-05-06 21:34:49 +08:00
parent 05cc160311
commit 703204c69a
7 changed files with 214 additions and 447 deletions

View File

@@ -181,24 +181,6 @@ const props = defineProps({
npm run build
```
### 输出文件
构建后会在`dist`目录生成以下核心文件:
- `remoteEntry.js` - 模块联邦入口文件
- `Page.js` - 详情页面组件
- `Config.js` - 配置页面组件
- `Dashboard.js` - 仪表板组件
### 部署要求
这些文件需要部署到后端并通过以下URL可访问
- `/api/v1/plugin/file/{插件ID}/dist/remoteEntry.js`
- `/api/v1/plugin/file/{插件ID}/dist/Page.js`
- `/api/v1/plugin/file/{插件ID}/dist/Config.js`
- `/api/v1/plugin/file/{插件ID}/dist/Dashboard.js`
## 7. 后端API要求
### 7.1 注册远程组件API

View File

@@ -6,14 +6,15 @@ export default defineConfig({
plugins: [
vue(),
federation({
name: 'my_plugin',
name: 'LogsClean',
filename: 'remoteEntry.js',
exposes: {
'./Page': './src/components/Page.vue',
'./Config': './src/components/Config.vue',
'./Dashboard': './src/components/Dashboard.vue',
},
shared: ['vue', 'vuetify']
shared: ['vue', 'vuetify'],
format: 'esm'
})
],
build: {
@@ -25,5 +26,10 @@ export default defineConfig({
port: 5001, // 使用不同于主应用的端口
cors: true, // 启用CORS
origin: 'http://localhost:5001'
},
preview: {
port: 5001, // 保持与server相同的端口
cors: true, // 启用CORS
origin: 'http://localhost:5001'
}
})

View File

@@ -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">

View File

@@ -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

View File

@@ -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>

View File

@@ -1,333 +1,100 @@
/**
* 动态模块联邦加载器
* 支持运行时动态注册和加载远程模块联邦组件
*/
import api from '@/api'
import type { Component } from 'vue'
// 远程组件配置接口
export interface RemoteComponentConfig {
id: string
url?: string
[key: string]: any
}
// 组件类型
export enum ComponentType {
PAGE = 'page',
CONFIG = 'config',
DASHBOARD = 'dashboard',
}
// 远程组件映射
interface RemoteModuleMap {
[pluginId: string]: {
[componentType in ComponentType]?: {
loaded: boolean
loading: boolean
error: Error | null
}
}
}
// 已加载的远程组件状态
const remoteModules: RemoteModuleMap = {}
// 全局联邦容器
// 为Window接口扩展__FEDERATION__属性
declare global {
interface Window {
__FEDERATION__: Record<string, any>
__FEDERATION__: Record<string, { url: string }>
}
}
// 确保全局联邦对象存在
if (!window.__FEDERATION__) {
window.__FEDERATION__ = {}
// 定义远程模块接口
interface RemoteModule {
id: string
url: string
}
/**
* 加载远程模块的入口文件
* @param url 远程模块URL
* @returns Promise<void>
* 从API获取远程模块列表
*/
async function loadRemoteEntry(url: string): Promise<void> {
return new Promise((resolve, reject) => {
// 创建script标签
const script = document.createElement('script')
script.src = `${import.meta.env.VITE_API_BASE_URL}${url}`
script.type = 'text/javascript'
script.async = true
async function fetchRemoteModules(): Promise<RemoteModule[]> {
try {
const response = await api.get('plugin/remotes?token=moviepilot')
return (response as any) || []
} catch (error) {
console.error('获取远程模块列表失败:', error)
return []
}
}
// 加载成功
script.onload = () => {
resolve()
}
/**
* 动态注入Federation Remote模块
* @param modules 远程模块列表
*/
function injectRemoteModules(modules: RemoteModule[]): void {
if (!modules || modules.length === 0) return
// 加载失败
script.onerror = error => {
console.error(`远程模块加载失败: ${url}`, error)
reject(new Error(`远程模块加载失败: ${url}`))
}
// 创建Federation变量
const federation: Record<string, { url: string }> = {}
// 添加到文档
document.head.appendChild(script)
// 设置每个远程模块
modules.forEach(module => {
federation[module.id] = { url: module.url }
})
// 全局注入Federation变量供加载远程模块使用
window.__FEDERATION__ = federation
console.log('已注入远程模块:', federation)
}
/**
* 获取模块联邦远程URL的标准化地址
* @param pluginId 插件ID
* @param url 组件URL (可选)
* @returns 完整的远程组件URL
* 加载远程组件
* @param id 远程模块ID
* @param componentName 组件名称 (如 'Page')
*/
function getRemoteEntryUrl(pluginId: string, url?: string): string {
// 如果提供了完整URL则直接使用
if (url && (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('/'))) {
return url
}
// 否则使用默认路径格式
return `/api/v1/plugin/file/${pluginId.toLowerCase()}/dist/remoteEntry.js`
}
/**
* 获取组件类型对应的模块名称
* @param componentType 组件类型
* @returns 模块名称
*/
function getComponentModule(componentType: ComponentType): string {
switch (componentType) {
case ComponentType.PAGE:
return './Page'
case ComponentType.CONFIG:
return './Config'
case ComponentType.DASHBOARD:
return './Dashboard'
default:
return './Component'
}
}
/**
* 从后端API获取远程组件列表并加载
* @returns Promise<boolean> 是否加载成功
*/
export async function loadRemoteComponents(): Promise<boolean> {
export async function loadRemoteComponent(id: string, componentName: string = 'Page') {
try {
// 调用后端API获取远程组件列表
const result = await api.get('plugin/remotes?token=moviepilot')
// 检查远程模块是否已经注册
if (!window.__FEDERATION__ || !window.__FEDERATION__[id]) {
// 如果未注册,尝试重新初始化
const modules = await fetchRemoteModules()
injectRemoteModules(modules)
if (!result || !Array.isArray(result)) {
console.error('获取远程组件列表失败:无效的响应格式')
return false
// 再次检查
if (!window.__FEDERATION__ || !window.__FEDERATION__[id]) {
throw new Error(`远程模块 ${id} 未注册`)
}
}
// 加载所有远程组件
const remotes = result as RemoteComponentConfig[]
const loadPromises = remotes.map(remote => {
if (remote.id) {
return registerRemotePlugin(remote.id, remote.url).catch(err => {
console.error(`注册插件失败: ${remote.id}`, err)
return false
})
}
return Promise.resolve(false)
})
await Promise.allSettled(loadPromises)
console.log(`已加载远程组件列表, 成功加载: ${loadPromises.length}`)
return true
} catch (error) {
console.error('加载远程组件列表失败', error)
return false
}
}
/**
* 注册远程插件
* @param pluginId 插件ID
* @param url 远程URL (可选)
* @returns Promise<boolean> 是否注册成功
*/
export async function registerRemotePlugin(pluginId: string, url?: string): Promise<boolean> {
try {
// 生成远程插件的标识符
const remoteId = `plugin_${pluginId.replace(/[^a-zA-Z0-9_]/g, '_')}`
// 获取完整的远程入口URL
const remoteEntryUrl = getRemoteEntryUrl(pluginId, url)
// 加载远程入口
await loadRemoteEntry(remoteEntryUrl)
// 初始化插件状态
if (!remoteModules[pluginId]) {
remoteModules[pluginId] = {}
}
// 注册到全局联邦对象 (如果尚未注册)
if (!window.__FEDERATION__[remoteId]) {
// 在这里我们假设远程模块已经挂载到全局作用域
// 真实环境中这部分可能需要根据模块联邦的实际工作方式调整
window.__FEDERATION__[remoteId] = {
get: (module: string) => {
// 动态导入远程模块
return async () => {
// 这里可能需要根据实际情况调整
const moduleName = module.replace('./', '')
try {
// 理论上这里应该使用模块联邦的import机制
// 但由于我们是运行时加载,需要用一种变通方式
const moduleUrl = `${remoteEntryUrl.replace('remoteEntry.js', '')}${moduleName}.js`
// 使用动态导入
const moduleExports = await import(/* @vite-ignore */ moduleUrl)
return { default: moduleExports.default }
} catch (error) {
console.error(`加载远程模块失败: ${remoteId}/${moduleName}`, error)
throw error
}
}
},
}
}
console.log(`已注册远程插件: ${pluginId} (${remoteId})`)
return true
} catch (error) {
console.error(`注册远程插件失败: ${pluginId}`, error)
return false
}
}
/**
* 异步加载远程组件
* @param pluginId 插件ID
* @param componentType 组件类型
* @returns Promise<Component> 组件
*/
export async function loadRemoteComponent(pluginId: string, componentType: ComponentType): Promise<Component | null> {
try {
// 检查插件是否已初始化
if (!remoteModules[pluginId]) {
remoteModules[pluginId] = {}
}
// 检查组件状态
const componentState = remoteModules[pluginId][componentType]
// 如果已经在加载中,等待加载完成
if (componentState?.loading) {
while (componentState.loading && !componentState.loaded) {
await new Promise(resolve => setTimeout(resolve, 100))
}
if (componentState.error) {
throw componentState.error
}
return await loadRemoteComponentModule(pluginId, componentType)
}
// 标记为正在加载
remoteModules[pluginId][componentType] = {
loading: true,
loaded: false,
error: null,
}
// 加载组件
const component = await loadRemoteComponentModule(pluginId, componentType)
// 更新状态
remoteModules[pluginId][componentType] = {
loading: false,
loaded: true,
error: null,
}
return component
} catch (error: any) {
// 更新错误状态
if (remoteModules[pluginId]?.[componentType]) {
remoteModules[pluginId][componentType] = {
loading: false,
loaded: false,
error: error,
}
}
return null
}
}
/**
* 从远程模块加载特定组件
* @param pluginId 插件ID
* @param componentType 组件类型
* @returns Promise<Component> 组件
*/
async function loadRemoteComponentModule(pluginId: string, componentType: ComponentType): Promise<Component | null> {
// 生成远程插件的标识符
const remoteId = `plugin_${pluginId.replace(/[^a-zA-Z0-9_]/g, '_')}`
// 获取组件模块名称
const moduleName = getComponentModule(componentType)
try {
// 通过模块联邦获取组件
const factory = window.__FEDERATION__[remoteId].get(moduleName)
const Module = await factory()
// 返回组件
return Module.default
// 模块联邦格式导入,不需要.vue扩展名
console.log(`正在加载远程组件: ${id}/${componentName}`)
// @ts-ignore - 动态导入远程模块
return await import(`${id}/${componentName}`)
} catch (error) {
console.error(`加载远程组件失败: ${id}/${componentName}`, error)
throw error
}
}
/**
* 检查远程组件是否已加载
* @param pluginId 插件ID
* @param componentType 组件类型
* @returns boolean 是否已加载
* 初始化并加载所有远程组件
*/
export function isRemoteComponentLoaded(pluginId: string, componentType: ComponentType): boolean {
return !!remoteModules[pluginId]?.[componentType]?.loaded
}
export async function loadRemoteComponents(): Promise<boolean> {
try {
// 获取远程模块列表
const modules = await fetchRemoteModules()
/**
* 获取远程组件加载错误
* @param pluginId 插件ID
* @param componentType 组件类型
* @returns Error | null 错误
*/
export function getRemoteComponentError(pluginId: string, componentType: ComponentType): Error | null {
return remoteModules[pluginId]?.[componentType]?.error || null
}
/**
* 清除远程组件缓存
* @param pluginId 插件ID (可选,不提供则清除所有)
* @param componentType 组件类型 (可选,不提供则清除插件的所有组件)
*/
export function clearRemoteComponentCache(pluginId?: string, componentType?: ComponentType): void {
if (!pluginId) {
// 清除所有缓存
Object.keys(remoteModules).forEach(id => {
delete remoteModules[id]
})
return
}
if (!remoteModules[pluginId]) {
return
}
if (!componentType) {
// 清除插件的所有组件
delete remoteModules[pluginId]
return
}
// 清除特定组件
if (remoteModules[pluginId][componentType]) {
delete remoteModules[pluginId][componentType]
// 确保有模块才注入
if (modules && modules.length > 0) {
// 注入远程模块
injectRemoteModules(modules)
return true
} else {
console.log('没有发现可用的远程模块')
return false
}
} catch (error) {
console.error('加载远程组件失败:', error)
return false
}
}

View File

@@ -199,12 +199,6 @@ export default defineConfig({
secure: false,
cookieDomainRewrite: 'localhost',
},
'/plugin_static': {
target: 'http://localhost:3001',
changeOrigin: true,
secure: false,
rewrite: path => path.replace(/^\/plugin_static/, '/api/v1/plugin/file'),
},
},
},
css: {