更新多个组件以支持新的事件通知机制,添加切换到配置页面的功能,调整文档以反映组件文件名的变化,提升用户交互体验。

This commit is contained in:
jxxghp
2025-05-06 23:56:28 +08:00
parent 4586f6982a
commit 146a1fe23d
4 changed files with 63 additions and 54 deletions

View File

@@ -17,9 +17,9 @@ MoviePilot前端采用模块联邦(Module Federation)技术实现插件的动态
| 组件名称 | 文件名 | 用途 |
|---------|-------|------|
| Page | Page.js | 插件详情页面 |
| Config | Config.js | 插件配置页面 |
| Dashboard | Dashboard.js | 仪表板组件 |
| Page | Page.vue | 插件详情页面 |
| Config | Config.vue | 插件配置页面 |
| Dashboard | Dashboard.vue | 仪表板组件 |
## 4. 快速开始
@@ -88,7 +88,7 @@ export default defineConfig({
```vue
<script setup lang="ts">
// 自定义事件,用于通知主应用刷新数据
const emit = defineEmits(['action'])
const emit = defineEmits(['action', 'switch'])
// 页面逻辑代码...
@@ -96,12 +96,18 @@ const emit = defineEmits(['action'])
function notifyRefresh() {
emit('action')
}
// 通知主应用切换到配置页面
function notifySwitch() {
emit('switch')
}
</script>
<template>
<div class="plugin-page">
<!-- 插件详情页面内容 -->
<v-btn @click="notifyRefresh">刷新数据</v-btn>
<v-btn @click="notifySwitch">配置插件</v-btn>
</div>
</template>
```
@@ -181,39 +187,9 @@ const props = defineProps({
npm run build
```
## 7. 后端API要求
将生成的dist文件夹上传到插件后端并配置插件后端API路径。
### 7.1 注册远程组件API
后端需要实现以下API用于注册远程组件已公共实现插件后端按第三方插件开发要求实现即可
```
GET /api/plugins/remotes
```
返回结构:
```json
[
{
"id": "my-plugin", // 插件ID必需
"url": "/custom/path/to/plugin" // 自定义组件路径,可选
},
{
"id": "another-plugin" // 使用默认路径
}
]
```
### 7.2 组件访问路径
指定了`url`后使用:
- `{url}/remoteEntry.js`
- `{url}/Page.js`
- `{url}/Config.js`
- `{url}/Dashboard.js`
## 8. 调试与排错
## 7. 调试与排错
### 常见问题

View File

@@ -55,6 +55,10 @@
<v-icon left>mdi-refresh</v-icon>
刷新数据
</v-btn>
<v-btn color="primary" @click="notifySwitch">
<v-icon left>mdi-cog</v-icon>
配置
</v-btn>
</v-card-actions>
</v-card>
</div>
@@ -73,7 +77,7 @@ const status = ref('running')
const lastUpdated = ref('')
// 自定义事件,用于通知主应用刷新数据
const emit = defineEmits(['action'])
const emit = defineEmits(['action', 'switch'])
// 获取状态图标
function getItemIcon(type) {
@@ -138,6 +142,11 @@ async function refreshData() {
}
}
// 通知主应用切换到配置页面
function notifySwitch() {
emit('switch')
}
// 组件挂载时加载数据
onMounted(() => {
refreshData()

View File

@@ -149,20 +149,20 @@ onBeforeMount(async () => {
</script>
<template>
<VDialog scrollable max-width="60rem" :fullscreen="!display.mdAndUp.value">
<VCard :title="`${props.plugin?.plugin_name} - ${t('dialog.pluginConfig.title')}`" class="rounded-t">
<!-- Vuetify 渲染模式 -->
<VCard
v-if="renderMode === 'vuetify'"
:title="`${props.plugin?.plugin_name} - ${t('dialog.pluginConfig.title')}`"
class="rounded-t"
>
<VDialogCloseBtn @click="emit('close')" />
<VDivider />
<LoadingBanner v-if="!isRefreshed" class="mt-5" />
<VCardText v-else="isRefreshed">
<!-- Vuetify 渲染模式 -->
<div v-if="renderMode === 'vuetify'">
<div>
<FormRender v-for="(item, index) in pluginFormItems" :key="index" :config="item" :model="pluginConfigForm" />
<div v-if="!pluginFormItems || pluginFormItems.length === 0">此插件没有可配置项</div>
</div>
<!-- Vue 渲染模式 -->
<div v-else-if="renderMode === 'vue'">
<component :is="dynamicComponent" :initial-config="pluginConfigForm" @save="handleVueComponentSave" />
</div>
</VCardText>
<VCardActions class="pt-3">
<VBtn v-if="props.plugin?.has_page" @click="emit('switch')" variant="outlined" color="info">
@@ -181,6 +181,10 @@ onBeforeMount(async () => {
</VBtn>
</VCardActions>
</VCard>
<!-- Vue 渲染模式 -->
<div v-else-if="renderMode === 'vue'">
<component :is="dynamicComponent" :initial-config="pluginConfigForm" @save="handleVueComponentSave" />
</div>
<!-- 进度框 -->
<ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="progressText" />
</VDialog>

View File

@@ -22,6 +22,10 @@ const appMode = inject('pwaMode') && display.mdAndDown.value
// 是否刷新
const isRefreshed = ref(false)
// 组件是否已加载成功
const componentLoaded = ref(false)
// 是否正在加载数据
const isLoading = ref(false)
// 渲染模式: 'vuetify' 或 'vue'
const renderMode = ref('vuetify')
@@ -40,10 +44,11 @@ const dynamicComponent = defineAsyncComponent({
// 动态加载远程组件
const module = await loadRemoteComponent(props.plugin.id, 'Page')
componentLoaded.value = true
return module
} catch (error) {
console.error('加载远程组件失败:', error)
componentLoaded.value = false
}
},
// 加载中显示的组件
@@ -66,11 +71,21 @@ const dynamicComponent = defineAsyncComponent({
// 调用API读取数据页面UI
async function loadPluginUIData() {
// 如果正在加载,则不重复加载
if (isLoading.value) return
isLoading.value = true
isRefreshed.value = false
pluginPageItems.value = []
renderMode.value = 'vuetify'
try {
// 如果已经是vue模式且组件已加载成功不需要再请求模式
if (renderMode.value === 'vue' && componentLoaded.value) {
isRefreshed.value = true
isLoading.value = false
return
}
const result: { [key: string]: any } = await api.get(`plugin/page/${props.plugin?.id}`)
if (!result || !result.render_mode) {
console.error(`插件 ${props.plugin?.plugin_name} UI数据加载失败无效的响应`)
@@ -85,11 +100,16 @@ async function loadPluginUIData() {
console.error(error)
} finally {
isRefreshed.value = true
isLoading.value = false
}
}
// 重新加载数据(可由 PageRender 或 Vue component 触发)
function handleAction() {
function handleAction(event: any) {
// 避免在组件已加载的情况下重复调用loadPluginUIData
if (renderMode.value === 'vue' && componentLoaded.value) {
return
}
loadPluginUIData()
}
@@ -99,19 +119,15 @@ onMounted(() => {
</script>
<template>
<VDialog scrollable max-width="80rem" :fullscreen="!display.mdAndUp.value">
<VCard :title="`${props.plugin?.plugin_name}`" class="rounded-t">
<!-- Vuetify 渲染模式 -->
<VCard v-if="renderMode === 'vuetify'" :title="`${props.plugin?.plugin_name}`" class="rounded-t">
<VDialogCloseBtn @click="emit('close')" />
<LoadingBanner v-if="!isRefreshed" class="mt-5" />
<VCardText v-else class="min-h-40">
<!-- Vuetify 渲染模式 -->
<div v-if="renderMode === 'vuetify'">
<div>
<PageRender @action="handleAction" v-for="(item, index) in pluginPageItems" :key="index" :config="item" />
<div v-if="!pluginPageItems || pluginPageItems.length === 0">此插件没有详情页面</div>
</div>
<!-- Vue 渲染模式 -->
<div v-else-if="renderMode === 'vue'">
<component :is="dynamicComponent" @action="handleAction" />
</div>
</VCardText>
<VFab
icon="mdi-cog"
@@ -124,5 +140,9 @@ onMounted(() => {
:class="{ 'mb-10': appMode }"
/>
</VCard>
<!-- Vue 渲染模式 -->
<div v-else-if="renderMode === 'vue'">
<component :is="dynamicComponent" @action="handleAction" @switch="emit('switch')" />
</div>
</VDialog>
</template>