mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-10 17:42:50 +08:00
增强远程组件注册机制,添加动态注册和获取功能,更新相关文档以指导插件开发者。调整多个组件以支持新注册逻辑。
This commit is contained in:
@@ -4,6 +4,16 @@
|
||||
|
||||
关联阅读后端插件开发文档:[第三方插件开发说明](https://github.com/jxxghp/MoviePilot-Plugins/blob/main/README.md)
|
||||
|
||||
## 远程组件注册机制
|
||||
|
||||
MoviePilot 使用自动注册机制来加载远程组件:
|
||||
|
||||
1. 对于使用 Vue 渲染模式的插件,自动注册其远程组件
|
||||
2. 每个远程组件根据插件 ID 唯一标识,确保不会冲突
|
||||
3. 在需要加载组件时,会优先检查已注册的组件信息
|
||||
|
||||
这种设计使得插件开发者只需专注于组件开发,而不需要担心加载机制的复杂性。
|
||||
|
||||
## 常见错误
|
||||
|
||||
### 1. "Module name 'vue' does not resolve to a valid URL"
|
||||
|
||||
@@ -116,6 +116,33 @@ yarn build
|
||||
|
||||
构建后的 `dist/remoteEntry.js` 是远程组件的入口文件,需要配置到后端让 MoviePilot 能够访问。
|
||||
|
||||
## 插件后端配置
|
||||
|
||||
在插件的后端代码中,需要实现以下方法来提供组件信息:
|
||||
|
||||
```python
|
||||
def get_render_mode() -> str:
|
||||
"""
|
||||
获取插件渲染模式
|
||||
:return: 渲染模式,支持:vue/vuetify,默认vuetify
|
||||
"""
|
||||
return "vue"
|
||||
|
||||
def get_form_file() -> Tuple[str, Dict[str, Any]]:
|
||||
"""
|
||||
获取插件配置页面JS代码源文件(与get_from二选一使用)
|
||||
:return: 1、编译后的JS代码插件目录下相对路径;2、默认数据结构
|
||||
"""
|
||||
return "/dist/page.js", {}
|
||||
|
||||
def get_page_file() -> Optional[str]:
|
||||
"""
|
||||
获取插件数据页面JS代码源文件(与get_page二选一使用)
|
||||
:return: 编译后的JS代码插件目录下相对路径
|
||||
"""
|
||||
return "/dist/config.js", {}
|
||||
```
|
||||
|
||||
## 排查常见问题
|
||||
|
||||
### 顶层 await 报错
|
||||
|
||||
@@ -7,7 +7,12 @@ import { useToast } from 'vue-toast-notification'
|
||||
import FormRender from '../render/FormRender.vue'
|
||||
import ProgressDialog from '../dialog/ProgressDialog.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { loadRemoteComponent, clearRemoteComponentCache } from '@/utils/remoteFederationLoader'
|
||||
import {
|
||||
loadRemoteComponent,
|
||||
clearRemoteComponentCache,
|
||||
registerRemoteComponent,
|
||||
getRemoteComponent,
|
||||
} from '@/utils/remoteFederationLoader'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
@@ -52,6 +57,14 @@ const vueComponentUrl = ref<string | null>(null)
|
||||
// Vue 模式:动态加载的组件
|
||||
const dynamicComponent = computed(() => {
|
||||
if (renderMode.value === 'vue' && vueComponentUrl.value) {
|
||||
// 检查是否已经注册,如果没有则进行注册
|
||||
const remoteInfo = props.plugin?.id ? getRemoteComponent(props.plugin.id) : null
|
||||
if (!remoteInfo && props.plugin?.id) {
|
||||
// 动态注册远程组件
|
||||
registerRemoteComponent(props.plugin.id, vueComponentUrl.value)
|
||||
}
|
||||
|
||||
// 加载远程组件
|
||||
return loadRemoteComponent(vueComponentUrl.value, {
|
||||
onError: error => {
|
||||
console.error(`加载插件组件失败: ${vueComponentUrl.value}`, error)
|
||||
|
||||
@@ -4,7 +4,12 @@ import type { Plugin } from '@/api/types'
|
||||
import PageRender from '@/components/render/PageRender.vue'
|
||||
import api from '@/api'
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import { loadRemoteComponent, clearRemoteComponentCache } from '@/utils/remoteFederationLoader'
|
||||
import {
|
||||
loadRemoteComponent,
|
||||
clearRemoteComponentCache,
|
||||
registerRemoteComponent,
|
||||
getRemoteComponent,
|
||||
} from '@/utils/remoteFederationLoader'
|
||||
|
||||
// 输入参数
|
||||
const props = defineProps({
|
||||
@@ -39,6 +44,14 @@ let pluginPageItems = ref([])
|
||||
// Vue 模式:动态加载的组件
|
||||
const dynamicComponent = computed(() => {
|
||||
if (renderMode.value === 'vue' && vueComponentUrl.value) {
|
||||
// 检查是否已经注册,如果没有则进行注册
|
||||
const remoteInfo = props.plugin?.id ? getRemoteComponent(props.plugin.id) : null
|
||||
if (!remoteInfo && props.plugin?.id) {
|
||||
// 动态注册远程组件
|
||||
registerRemoteComponent(props.plugin.id, vueComponentUrl.value)
|
||||
}
|
||||
|
||||
// 加载远程组件
|
||||
return loadRemoteComponent(vueComponentUrl.value, {
|
||||
onError: error => {
|
||||
console.error(`加载插件组件失败: ${vueComponentUrl.value}`, error)
|
||||
|
||||
@@ -12,8 +12,12 @@ 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 api from '@/api'
|
||||
import { loadRemoteComponent, clearRemoteComponentCache } from '@/utils/remoteFederationLoader'
|
||||
import {
|
||||
loadRemoteComponent,
|
||||
clearRemoteComponentCache,
|
||||
registerRemoteComponent,
|
||||
getRemoteComponent,
|
||||
} from '@/utils/remoteFederationLoader'
|
||||
|
||||
// 输入参数
|
||||
const props = defineProps({
|
||||
@@ -37,6 +41,16 @@ const pluginRenderMode = computed(() => props.config?.render_mode || 'vuetify')
|
||||
const dynamicPluginComponent = computed(() => {
|
||||
// 确保 config 存在并且 component_url 也存在
|
||||
if (pluginRenderMode.value === 'vue' && props.config?.component_url) {
|
||||
// 如果有插件ID,尝试注册远程组件
|
||||
if (props.config.id) {
|
||||
const remoteInfo = getRemoteComponent(props.config.id)
|
||||
if (!remoteInfo) {
|
||||
// 动态注册远程组件
|
||||
registerRemoteComponent(props.config.id, props.config.component_url)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载远程组件
|
||||
return loadRemoteComponent(props.config.component_url, {
|
||||
onError: error => {
|
||||
console.error(`加载插件组件失败: ${props.config?.component_url}`, error)
|
||||
|
||||
@@ -16,6 +16,51 @@ interface RemoteModuleState {
|
||||
// 已加载组件的缓存
|
||||
const loadedRemotes: Record<string, RemoteModuleState> = {}
|
||||
|
||||
// 动态注册的远程组件映射
|
||||
interface RemotePluginInfo {
|
||||
url: string
|
||||
pluginId: string
|
||||
moduleName: string
|
||||
}
|
||||
|
||||
// 已注册的远程模块
|
||||
const registeredRemotes: Record<string, RemotePluginInfo> = {}
|
||||
|
||||
/**
|
||||
* 动态注册远程组件
|
||||
* @param pluginId 插件ID
|
||||
* @param remoteUrl 远程组件URL
|
||||
* @returns 注册成功返回true,否则返回false
|
||||
*/
|
||||
export async function registerRemoteComponent(pluginId: string, remoteUrl: string): Promise<boolean> {
|
||||
try {
|
||||
// 生成远程模块名称(使用插件ID作为标识)
|
||||
const moduleName = `plugin_${pluginId.replace(/[^a-zA-Z0-9_]/g, '_')}`
|
||||
|
||||
// 注册到映射表中
|
||||
registeredRemotes[pluginId] = {
|
||||
url: remoteUrl,
|
||||
pluginId,
|
||||
moduleName,
|
||||
}
|
||||
|
||||
console.log(`已注册远程组件: ${pluginId} -> ${moduleName} (${remoteUrl})`)
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error(`注册远程组件失败: ${pluginId}`, error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取远程组件信息
|
||||
* @param pluginId 插件ID
|
||||
* @returns 远程组件信息
|
||||
*/
|
||||
export function getRemoteComponent(pluginId: string): RemotePluginInfo | null {
|
||||
return registeredRemotes[pluginId] || null
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取和初始化远程组件
|
||||
* @param remoteUrl 远程组件URL
|
||||
@@ -127,3 +172,13 @@ export function clearRemoteComponentCache(remoteUrl?: string) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 取消注册远程组件
|
||||
export function unregisterRemoteComponent(pluginId: string) {
|
||||
if (registeredRemotes[pluginId]) {
|
||||
delete registeredRemotes[pluginId]
|
||||
console.log(`已取消注册远程组件: ${pluginId}`)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user