mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-11 18:10:49 +08:00
更新组件声明,移除 LocaleSwitcher 和 ThemeSwitcher,更新 README 文档以增强模块联邦功能的描述,并调整 Vite 配置以支持 ESNext 目标。
This commit is contained in:
141
examples/plugin-component/README.md
Normal file
141
examples/plugin-component/README.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# MoviePilot 插件远程组件示例
|
||||
|
||||
这是 MoviePilot 插件远程组件的示例项目,展示了如何正确配置和开发与主应用兼容的远程组件。
|
||||
|
||||
## 开发环境准备
|
||||
|
||||
1. 安装依赖:
|
||||
```bash
|
||||
npm install
|
||||
# 或
|
||||
yarn
|
||||
```
|
||||
|
||||
2. 开发模式运行:
|
||||
```bash
|
||||
npm run dev
|
||||
# 或
|
||||
yarn dev
|
||||
```
|
||||
|
||||
## 配置说明
|
||||
|
||||
### 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: 'remoteApp',
|
||||
filename: 'remoteEntry.js',
|
||||
exposes: {
|
||||
'./PluginComponent': './src/App.vue', // 暴露组件
|
||||
},
|
||||
shared: {
|
||||
vue: {
|
||||
singleton: true,
|
||||
requiredVersion: false
|
||||
}
|
||||
}
|
||||
})
|
||||
],
|
||||
build: {
|
||||
target: 'esnext', // 支持顶层await
|
||||
minify: false,
|
||||
cssCodeSplit: false,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
minifyInternalExports: false
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### 组件开发
|
||||
|
||||
主组件 (src/App.vue) 需要遵循以下规则:
|
||||
- 使用 Vue 3 组合式 API
|
||||
- 注册 `action` 事件用于与主应用通信
|
||||
- 避免直接使用 Vue Router 等全局依赖
|
||||
|
||||
示例组件结构:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="plugin-component">
|
||||
<h2>{{ title }}</h2>
|
||||
<div v-if="loading">加载中...</div>
|
||||
<div v-else>
|
||||
<!-- 组件内容 -->
|
||||
<pre>{{ data }}</pre>
|
||||
<button @click="refreshData">刷新</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
// 组件状态
|
||||
const title = ref('插件示例')
|
||||
const loading = ref(true)
|
||||
const data = ref(null)
|
||||
|
||||
// 向主应用发送事件
|
||||
const emit = defineEmits(['action'])
|
||||
|
||||
// 刷新数据
|
||||
async function refreshData() {
|
||||
loading.value = true
|
||||
// API 调用...
|
||||
loading.value = false
|
||||
// 通知主应用
|
||||
emit('action')
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
refreshData()
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
## 构建生产版本
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
# 或
|
||||
yarn build
|
||||
```
|
||||
|
||||
构建后的 `dist/remoteEntry.js` 是远程组件的入口文件,需要配置到后端让 MoviePilot 能够访问。
|
||||
|
||||
## 排查常见问题
|
||||
|
||||
### 顶层 await 报错
|
||||
|
||||
确保在 `vite.config.js` 中设置 `build.target` 为 `'esnext'`。
|
||||
|
||||
### 共享依赖加载失败
|
||||
|
||||
确保正确配置共享依赖:
|
||||
```js
|
||||
shared: {
|
||||
vue: {
|
||||
singleton: true,
|
||||
requiredVersion: false // 关闭版本检查
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 组件无法加载
|
||||
|
||||
1. 检查网络请求是否成功
|
||||
2. 确认 JS 文件可以被正确访问
|
||||
3. 查看浏览器控制台是否有详细错误信息
|
||||
12
examples/plugin-component/index.html
Normal file
12
examples/plugin-component/index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>MoviePilot 插件组件示例</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
19
examples/plugin-component/package.json
Normal file
19
examples/plugin-component/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "moviepilot-plugin-component",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@originjs/vite-plugin-federation": "^1.3.5",
|
||||
"@vitejs/plugin-vue": "^4.4.0",
|
||||
"vite": "^5.0.0"
|
||||
}
|
||||
}
|
||||
93
examples/plugin-component/src/App.vue
Normal file
93
examples/plugin-component/src/App.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div class="plugin-component pa-4">
|
||||
<v-card>
|
||||
<v-card-title>{{ title }}</v-card-title>
|
||||
<v-card-text>
|
||||
<v-progress-circular v-if="loading" indeterminate color="primary"></v-progress-circular>
|
||||
<div v-else>
|
||||
<v-alert v-if="error" type="error" class="mb-4">{{ error }}</v-alert>
|
||||
<v-simple-table v-if="data && data.stats">
|
||||
<template v-slot:default>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>类型</th>
|
||||
<th>数量</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(value, key) in data.stats" :key="key">
|
||||
<td>{{ key }}</td>
|
||||
<td>{{ value }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</template>
|
||||
</v-simple-table>
|
||||
<div v-if="data">
|
||||
<div><strong>状态:</strong> {{ data.status }}</div>
|
||||
<div><strong>最后更新:</strong> {{ data.last_updated }}</div>
|
||||
</div>
|
||||
<div v-else>无数据</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="primary" @click="refreshData"> 刷新 </v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
// 组件状态
|
||||
const title = ref('插件数据示例')
|
||||
const loading = ref(true)
|
||||
const data = ref(null)
|
||||
const error = ref(null)
|
||||
|
||||
// 向主应用发送事件
|
||||
const emit = defineEmits(['action'])
|
||||
|
||||
// 获取和刷新数据
|
||||
async function refreshData() {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
// 模拟API调用 - 实际开发中应使用 fetch 调用真实API
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
|
||||
// 模拟数据
|
||||
data.value = {
|
||||
status: 'running',
|
||||
stats: {
|
||||
'电影': Math.floor(Math.random() * 100) + 50,
|
||||
'电视剧': Math.floor(Math.random() * 100) + 30,
|
||||
'动漫': Math.floor(Math.random() * 100) + 20,
|
||||
'纪录片': Math.floor(Math.random() * 100) + 10,
|
||||
'综艺': Math.floor(Math.random() * 100) + 5,
|
||||
},
|
||||
last_updated: new Date().toLocaleString(),
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('获取数据失败:', err)
|
||||
error.value = err.message || '获取数据失败'
|
||||
} finally {
|
||||
loading.value = false
|
||||
// 通知主应用组件已更新
|
||||
emit('action')
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时加载数据
|
||||
onMounted(() => {
|
||||
refreshData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.plugin-component {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
5
examples/plugin-component/src/main.js
Normal file
5
examples/plugin-component/src/main.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
const app = createApp(App)
|
||||
app.mount('#app')
|
||||
32
examples/plugin-component/vite.config.js
Normal file
32
examples/plugin-component/vite.config.js
Normal file
@@ -0,0 +1,32 @@
|
||||
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', // 支持顶层await
|
||||
minify: false,
|
||||
cssCodeSplit: false,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
minifyInternalExports: false
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user