mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-06-25 17:44:13 +08:00
更新插件组件文档,调整多个组件以支持关闭功能,增强用户交互体验,并修正配置示例以反映最新的代码结构和依赖关系。
This commit is contained in:
@@ -2,8 +2,6 @@
|
||||
|
||||
本文档提供了针对 MoviePilot 项目中使用模块联邦时可能遇到的常见问题及解决方案。
|
||||
|
||||
关联阅读后端插件开发文档:[第三方插件开发说明](https://github.com/jxxghp/MoviePilot-Plugins/blob/main/README.md)
|
||||
|
||||
## 远程组件注册机制
|
||||
|
||||
MoviePilot 使用自动注册机制来加载远程组件:
|
||||
@@ -104,90 +102,9 @@ localStorage.setItem('debug', 'vite:*')
|
||||
|
||||
创建一个独立的简单页面来测试插件组件,排除主应用的干扰因素。
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 插件组件项目配置
|
||||
|
||||
```js
|
||||
// vite.config.js
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import federation from '@originjs/vite-plugin-federation'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
federation({
|
||||
name: 'remoteApp',
|
||||
filename: 'remoteEntry.js',
|
||||
exposes: {
|
||||
'./PluginComponent': './src/App.vue',
|
||||
},
|
||||
shared: {
|
||||
vue: {
|
||||
singleton: true,
|
||||
requiredVersion: false
|
||||
}
|
||||
}
|
||||
})
|
||||
],
|
||||
build: {
|
||||
target: 'esnext',
|
||||
minify: false, // 开发阶段禁用最小化,方便调试
|
||||
cssCodeSplit: false,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
minifyInternalExports: false
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### 插件组件代码
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="plugin-container">
|
||||
<!-- 组件内容 -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
// 向主应用发送事件
|
||||
const emit = defineEmits(['action'])
|
||||
|
||||
// 初始化逻辑...
|
||||
|
||||
// 通知主应用
|
||||
function notifyHost() {
|
||||
emit('action')
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### 显式依赖声明
|
||||
|
||||
在插件组件的 `package.json` 中准确声明所有依赖:
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"vue": "^3.3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@originjs/vite-plugin-federation": "^1.3.5",
|
||||
"@vitejs/plugin-vue": "^4.4.0",
|
||||
"vite": "^5.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 其他资源
|
||||
|
||||
- [MoviePilot 插件组件示例](../examples/plugin-component/)
|
||||
- [Vite 模块联邦插件文档](https://github.com/originjs/vite-plugin-federation)
|
||||
- [Vite 官方文档](https://vitejs.dev/guide/build.html)
|
||||
- [Origin.js 模块联邦示例](https://github.com/originjs/vite-plugin-federation/tree/main/packages/examples)
|
||||
- [MoviePilot 插件组件示例](../examples/plugin-component/)
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
|
||||
MoviePilot前端采用模块联邦(Module Federation)技术实现插件的动态加载和集成。本文档详细说明如何开发符合要求的远程模块,以便在MoviePilot中作为插件使用。
|
||||
|
||||
关联阅读后端插件开发文档:[第三方插件开发说明](https://github.com/jxxghp/MoviePilot-Plugins/blob/main/README.md)
|
||||
|
||||
|
||||
## 2. 技术要求
|
||||
|
||||
- Node.js 16+
|
||||
@@ -33,13 +36,7 @@ npm create vite@latest my-plugin -- --template vue-ts
|
||||
cd my-plugin
|
||||
|
||||
# 安装依赖
|
||||
npm install
|
||||
|
||||
# 安装模块联邦插件
|
||||
npm install @originjs/vite-plugin-federation --save-dev
|
||||
|
||||
# 安装Vuetify(可选)
|
||||
npm install vuetify
|
||||
yarn
|
||||
```
|
||||
|
||||
### 配置vite.config.ts
|
||||
@@ -53,31 +50,27 @@ export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
federation({
|
||||
name: 'my_plugin', // 插件名称,建议与插件ID保持一致
|
||||
name: 'LogsClean',
|
||||
filename: 'remoteEntry.js',
|
||||
exposes: {
|
||||
'./Page': './src/components/Page.vue',
|
||||
'./Config': './src/components/Config.vue',
|
||||
'./Dashboard': './src/components/Dashboard.vue',
|
||||
},
|
||||
shared: {
|
||||
vue: { requiredVersion: false },
|
||||
vuetify: { requiredVersion: false }
|
||||
}
|
||||
shared: ['vue', 'vuetify'],
|
||||
format: 'esm'
|
||||
})
|
||||
],
|
||||
build: {
|
||||
target: 'esnext', // 必须设置为esnext以支持顶层await
|
||||
minify: false, // 开发阶段建议关闭混淆
|
||||
cssCodeSplit: false,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
format: 'esm', // 必须使用ESM格式
|
||||
entryFileNames: '[name].js',
|
||||
chunkFileNames: '[name].js',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
server: {
|
||||
port: 5001, // 使用不同于主应用的端口
|
||||
cors: true, // 启用CORS
|
||||
origin: 'http://localhost:5001'
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
@@ -88,7 +81,15 @@ export default defineConfig({
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
// 自定义事件,用于通知主应用刷新数据
|
||||
const emit = defineEmits(['action', 'switch'])
|
||||
const emit = defineEmits(['action', 'switch', 'close'])
|
||||
|
||||
// 接收API对象
|
||||
const props = defineProps({
|
||||
api: {
|
||||
type: any,
|
||||
default: () => {}
|
||||
}
|
||||
})
|
||||
|
||||
// 页面逻辑代码...
|
||||
|
||||
@@ -110,7 +111,7 @@ function notifyClose() {
|
||||
|
||||
<template>
|
||||
<div class="plugin-page">
|
||||
<!-- 插件详情页面内容 -->
|
||||
<!-- 插件详情页面操作按钮 -->
|
||||
<v-btn @click="notifyRefresh">刷新数据</v-btn>
|
||||
<v-btn @click="notifySwitch">配置插件</v-btn>
|
||||
<v-btn @click="notifyClose">关闭页面</v-btn>
|
||||
@@ -122,11 +123,15 @@ function notifyClose() {
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
// 接收初始配置
|
||||
// 接收初始配置和API对象
|
||||
const props = defineProps({
|
||||
initialConfig: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
api: {
|
||||
type: any,
|
||||
default: () => {}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -134,7 +139,7 @@ const props = defineProps({
|
||||
const config = ref({...props.initialConfig})
|
||||
|
||||
// 自定义事件,用于保存配置
|
||||
const emit = defineEmits(['save'])
|
||||
const emit = defineEmits(['save', 'close', 'switch'])
|
||||
|
||||
// 保存配置
|
||||
function saveConfig() {
|
||||
@@ -206,10 +211,21 @@ const props = defineProps({
|
||||
### 构建项目
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
yarn build
|
||||
```
|
||||
|
||||
将生成的dist文件夹上传到插件后端目录下(默认为`dist/assets`),在插件的后端代码中,实现以下方法来集成远程组件:
|
||||
|
||||
```python
|
||||
def get_render_mode() -> Tuple[str, str]:
|
||||
"""
|
||||
获取插件渲染模式
|
||||
:return: 1、渲染模式,支持:vue/vuetify,默认vuetify
|
||||
:return: 2、组件路径,默认 dist/assets
|
||||
"""
|
||||
return "vue", "dist/assets"
|
||||
```
|
||||
|
||||
将生成的dist文件夹上传到插件后端,并配置插件后端API路径。
|
||||
|
||||
## 7. 调试与排错
|
||||
|
||||
@@ -232,9 +248,9 @@ npm run build
|
||||
4. **"Top-level await is not available"**
|
||||
- 确保`build.target`设置为`esnext`
|
||||
|
||||
## 9. 高级配置
|
||||
## 8. 高级配置
|
||||
|
||||
### 9.1 CSS隔离
|
||||
### 8.1 CSS隔离
|
||||
|
||||
为防止样式冲突,建议使用CSS Modules或scoped样式:
|
||||
|
||||
@@ -244,7 +260,7 @@ npm run build
|
||||
</style>
|
||||
```
|
||||
|
||||
### 9.2 共享更多依赖
|
||||
### 8.2 共享更多依赖
|
||||
|
||||
如果您的插件需要共享更多依赖,可以扩展shared配置:
|
||||
|
||||
@@ -257,7 +273,7 @@ shared: {
|
||||
}
|
||||
```
|
||||
|
||||
### 9.3 开发环境测试
|
||||
### 8.3 开发环境测试
|
||||
|
||||
开发期间可以使用以下配置在本地测试:
|
||||
|
||||
@@ -272,11 +288,12 @@ export default defineConfig({
|
||||
})
|
||||
```
|
||||
|
||||
## 10. 示例代码
|
||||
## 9. 示例代码
|
||||
|
||||
- [插件远程组件示例](../examples/plugin-component/) - 开发插件组件的完整示例项目
|
||||
- [模块联邦问题排查指南](./federation-troubleshooting.md) - 常见问题排查
|
||||
|
||||
## 11. 参考资料
|
||||
## 10. 参考资料
|
||||
|
||||
- [Vite Plugin Federation](https://github.com/originjs/vite-plugin-federation)
|
||||
- [Vue 3官方文档](https://vuejs.org/)
|
||||
|
||||
@@ -36,175 +36,7 @@ plugin-component/
|
||||
└── package.json # 依赖配置
|
||||
```
|
||||
|
||||
## 3. 配置说明
|
||||
## 3. 开发指引
|
||||
|
||||
### vite.config.js
|
||||
|
||||
```js
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import federation from '@originjs/vite-plugin-federation'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
federation({
|
||||
name: 'my_plugin', // 插件名称,建议与插件ID保持一致
|
||||
filename: 'remoteEntry.js',
|
||||
exposes: {
|
||||
'./Page': './src/components/Page.vue',
|
||||
'./Config': './src/components/Config.vue',
|
||||
'./Dashboard': './src/components/Dashboard.vue',
|
||||
},
|
||||
shared: {
|
||||
vue: { requiredVersion: false },
|
||||
vuetify: { requiredVersion: false }
|
||||
}
|
||||
})
|
||||
],
|
||||
build: {
|
||||
target: 'esnext', // 必须设置为esnext以支持顶层await
|
||||
minify: false, // 开发阶段建议关闭混淆
|
||||
cssCodeSplit: false,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
format: 'esm', // 必须使用ESM格式
|
||||
entryFileNames: '[name].js',
|
||||
chunkFileNames: '[name].js',
|
||||
}
|
||||
}
|
||||
},
|
||||
server: {
|
||||
port: 5001, // 使用不同于主应用的端口
|
||||
cors: true, // 启用CORS
|
||||
origin: 'http://localhost:5001'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## 4. 组件规范
|
||||
|
||||
### Page.vue(详情页面)
|
||||
|
||||
详情页面用于展示插件的数据和状态:
|
||||
|
||||
- 接收 `action` 事件用于通知主应用刷新数据
|
||||
- 可以包含交互功能和数据展示
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
// 自定义事件,用于通知主应用刷新数据
|
||||
const emit = defineEmits(['action'])
|
||||
|
||||
// 通知主应用刷新数据
|
||||
function notifyRefresh() {
|
||||
emit('action')
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### Config.vue(配置页面)
|
||||
|
||||
配置页面用于接收和保存插件配置:
|
||||
|
||||
- 接收 `initialConfig` 属性获取初始配置
|
||||
- 发出 `save` 事件保存配置数据
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
// 接收初始配置
|
||||
const props = defineProps({
|
||||
initialConfig: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
// 自定义事件,用于保存配置
|
||||
const emit = defineEmits(['save'])
|
||||
|
||||
// 保存配置
|
||||
function saveConfig() {
|
||||
emit('save', configData)
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### Dashboard.vue(仪表板组件)
|
||||
|
||||
仪表板组件用于在主页上显示插件数据:
|
||||
|
||||
- 接收 `config` 属性获取仪表板配置
|
||||
- 接收 `allowRefresh` 属性控制是否允许自动刷新
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
// 接收配置和刷新控制
|
||||
const props = defineProps({
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
allowRefresh: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
## 5. 构建和部署
|
||||
|
||||
### 构建生产版本
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
# 或
|
||||
yarn build
|
||||
```
|
||||
|
||||
构建后在 `dist` 目录生成以下关键文件:
|
||||
|
||||
- `remoteEntry.js` - 模块联邦入口文件
|
||||
- `Page.js` - 详情页面组件
|
||||
- `Config.js` - 配置页面组件
|
||||
- `Dashboard.js` - 仪表板组件
|
||||
|
||||
### 部署到插件后端
|
||||
|
||||
将构建后的文件部署到插件后端插件目录下,并上传到Github仓库。
|
||||
|
||||
## 6. 插件后端集成
|
||||
|
||||
在插件的后端代码中,实现以下方法来集成远程组件:
|
||||
|
||||
```python
|
||||
def get_render_mode() -> Tuple[str, str]:
|
||||
"""
|
||||
获取插件渲染模式
|
||||
:return: 1、渲染模式,支持:vue/vuetify,默认vuetify
|
||||
:return: 2、组件路径,默认 dist/assets
|
||||
"""
|
||||
return "vue", "dist/assets"
|
||||
```
|
||||
|
||||
## 7. 常见问题排查
|
||||
|
||||
### 模块加载问题
|
||||
|
||||
如果遇到模块加载问题,请检查:
|
||||
|
||||
1. 确保 `build.target` 设置为 `esnext`
|
||||
2. 验证共享依赖配置是否正确
|
||||
3. 检查网络请求是否成功
|
||||
4. 查看浏览器控制台错误信息
|
||||
|
||||
### 代码调试
|
||||
|
||||
在开发阶段可以:
|
||||
|
||||
1. 使用浏览器开发者工具进行调试
|
||||
2. 启用 Vite 的详细日志:`localStorage.setItem('debug', 'vite:*')`
|
||||
3. 使用 `console.log` 输出调试信息
|
||||
|
||||
更多详细说明请参考 [模块联邦开发指南](../../docs/module-federation-guide.md) 和 [模块联邦问题排查指南](../../docs/federation-troubleshooting.md)。
|
||||
- [模块联邦开发指南](../../docs/module-federation-guide.md)
|
||||
- [模块联邦问题排查指南](../../docs/federation-troubleshooting.md)。
|
||||
|
||||
@@ -99,6 +99,7 @@
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="secondary" variant="outlined" @click="resetForm">重置</v-btn>
|
||||
<v-btn color="primary" :disabled="!isFormValid" @click="saveConfig" :loading="saving">保存配置</v-btn>
|
||||
<v-btn color="primary" @click="notifyClose">关闭</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</div>
|
||||
@@ -113,6 +114,10 @@ const props = defineProps({
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
api: {
|
||||
type: any,
|
||||
default: () => {},
|
||||
},
|
||||
})
|
||||
|
||||
// 表单状态
|
||||
@@ -162,7 +167,7 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
// 自定义事件,用于保存配置
|
||||
const emit = defineEmits(['save'])
|
||||
const emit = defineEmits(['save', 'close', 'switch'])
|
||||
|
||||
// 保存配置
|
||||
async function saveConfig() {
|
||||
@@ -198,6 +203,11 @@ function resetForm() {
|
||||
form.value.resetValidation()
|
||||
}
|
||||
}
|
||||
|
||||
// 通知主应用关闭组件
|
||||
function notifyClose() {
|
||||
emit('close')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -59,6 +59,10 @@
|
||||
<v-icon left>mdi-cog</v-icon>
|
||||
配置
|
||||
</v-btn>
|
||||
<v-btn color="primary" @click="notifyClose">
|
||||
<v-icon left>mdi-close</v-icon>
|
||||
关闭
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</div>
|
||||
@@ -67,6 +71,14 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
// 接收初始配置
|
||||
const props = defineProps({
|
||||
api: {
|
||||
type: any,
|
||||
default: () => {},
|
||||
},
|
||||
})
|
||||
|
||||
// 组件状态
|
||||
const title = ref('插件详情页面')
|
||||
const loading = ref(true)
|
||||
@@ -77,7 +89,7 @@ const status = ref('running')
|
||||
const lastUpdated = ref('')
|
||||
|
||||
// 自定义事件,用于通知主应用刷新数据
|
||||
const emit = defineEmits(['action', 'switch'])
|
||||
const emit = defineEmits(['action', 'switch', 'close'])
|
||||
|
||||
// 获取状态图标
|
||||
function getItemIcon(type) {
|
||||
@@ -147,6 +159,11 @@ function notifySwitch() {
|
||||
emit('switch')
|
||||
}
|
||||
|
||||
// 通知主应用关闭组件
|
||||
function notifyClose() {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
// 组件挂载时加载数据
|
||||
onMounted(() => {
|
||||
refreshData()
|
||||
|
||||
@@ -6,7 +6,7 @@ export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
federation({
|
||||
name: 'LogsClean',
|
||||
name: 'MyPlugin',
|
||||
filename: 'remoteEntry.js',
|
||||
exposes: {
|
||||
'./Page': './src/components/Page.vue',
|
||||
@@ -27,9 +27,4 @@ export default defineConfig({
|
||||
cors: true, // 启用CORS
|
||||
origin: 'http://localhost:5001'
|
||||
},
|
||||
preview: {
|
||||
port: 5001, // 保持与server相同的端口
|
||||
cors: true, // 启用CORS
|
||||
origin: 'http://localhost:5001'
|
||||
}
|
||||
})
|
||||
|
||||
@@ -186,6 +186,7 @@ onBeforeMount(async () => {
|
||||
<component
|
||||
:is="dynamicComponent"
|
||||
:initial-config="pluginConfigForm"
|
||||
:api="api"
|
||||
@save="handleVueComponentSave"
|
||||
@switch="emit('switch')"
|
||||
@close="emit('close')"
|
||||
|
||||
@@ -142,7 +142,7 @@ onMounted(() => {
|
||||
</VCard>
|
||||
<!-- Vue 渲染模式 -->
|
||||
<div v-else-if="renderMode === 'vue'">
|
||||
<component :is="dynamicComponent" @action="handleAction" @switch="emit('switch')" @close="emit('close')" />
|
||||
<component :is="dynamicComponent" :api="api" @action="handleAction" @switch="emit('switch')" @close="emit('close')" />
|
||||
</div>
|
||||
</VDialog>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user