Private
Public Access
1
0
Files
u-desk/docs/04-功能迭代/GO-DESK-9.插件系统/设计文档/架构设计.md

251 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 架构设计
## 一、架构全景
```
+==================================================================+
| u-desk Application |
+===================================================================+
| Core (Go) Plugin Manager |
| - App facade - Registry / Lifecycle / Loader |
| - ConfigStore (SQLite) - Builtin Plugins (编译时) |
| - EventBus (Wails Events) - External Plugins (运行时) |
| - UpdateEngine - Marketplace Client |
+----------+----------+---------+-----------+-----------+ |
| | | | | |
+-------+ +------+ +-----+ +--------+ +------+ +----+ |
|builtin: |builtin: |builtin | builtin: |extern:|extern |
| file-sys| md-edit| drawio | db-cli | user-A| user-B |
+---------+---------+--------+-----------+--------+--------------+
|
+=================================================================+|
| Frontend (Vue 3) ||
| PluginRegistry (TS) ||
| - TabProviders → 替代 App.vue 硬编码映射 ||
| - FilePreviewHandlers → 替代 FileEditorPanel if/else 链 ||
| - ComponentLoader → defineAsyncComponent 懒加载 ||
| - PluginSettingsUI → 设置面板插件管理区块 ||
+=================================================================+
```
## 二、两层插件体系
| 维度 | 内置插件 (Builtin) | 外部插件 (External/Market) |
|------|-------------------|--------------------------|
| **来源** | 编译时打包进二进制 | 运行时从市场安装 |
| **位置** | `internal/plugin/builtin/` | 用户数据目录 `plugins/` |
| **生命周期** | 随应用启动 | 用户控制 |
| **管理操作** | 启用 / 禁用 | 安装 / 卸载 / 启用 / 禁用 / 更新 |
| **更新方式** | 随应用版本更新 | 独立更新(复用 UpdateAPI |
| **安全级别** | 完全信任(同进程) | 沙箱隔离Phase 5 |
| **示例** | 文件系统、Markdown、Draw.io | 第三方预览器、云存储同步 |
## 三、设置面板形态(最终态)
```
┌─ 设置 ──────────────────────────────────────────────┐
│ │
│ ┌─ Tab 配置 ─┐ ┌─ 版本更新 ─┐ ┌─ 插件管理 ─┐ │
│ │ │ │ │ │ │ │
│ │ 拖拽排序 │ │ 自动检查 │ │ 内置插件 │ │
│ │ 显隐控制 │ │ 手动检查 │ │ ✓ Draw.io │ │
│ │ 默认选择 │ │ 下载/安装 │ │ ✓ Markdown │ │
│ │ │ │ │ │ ○ 数据库CLI │ │
│ └─────────────┘ └─────────────┘ │ │ │
│ │ 外部插件 │ │
│ │ ✓ 云盘同步 v2.1│ │
│ │ ○ 思维导图(停用)│ │
│ │ │ │
│ │ [浏览插件市场] │ │
│ └──────────────┘ │
└──────────────────────────────────────────────────────┘
```
## 四、UI 插槽体系Slot System
### 4.1 当前布局(硬编码)
```
┌─────────────────────────────────┐
│ 标题栏 + Tab 导航 + 窗口控制 │ ← 固定区域,不可扩展
├─────────────────────────────────┤
│ │
│ <component :is="activeTab"> │ ← 内容区getComponent() 硬编码
│ │
├─────────────────────────────────┤
│ 更新通知 / 设置抽屉 │ ← 固定区域
└─────────────────────────────────┘
```
**问题**:所有 UI 区域都是硬编码的。新插件无法在标题栏加按钮、无法在工具栏扩展、无法在侧边栏注入面板。
### 4.2 插件化后的插槽布局
```
┌── slot: titlebar-extra ──────────────────────────────┐
│ [u-desk] [文件管理 | Markdown] [🔌插件A] [🔌插件B] [—][□][×] │
├──────────────────────────────────────────────────────┤
│ slot: sidebar-left │ slot: main-content │ slot: sidebar-right │
│ │ │ │
│ [内置侧边栏] │ <component :is="activeTab"> │ (预留) │
│ ───────────── │ │ │
│ [插件侧边面板] │ │ │
│ │ │ │
├────────────────────┴───────────────────────────────────┴──────────────────────┤
│ slot: toolbar-extra: [📋] [🔍] [🔌插件工具按钮...] │
├──────────────────────────────────────────────────────────────────────────────┤
│ slot: status-bar: [就绪] [行: 128] [编码: UTF-8] [🔌插件状态项...] │
└──────────────────────────────────────────────────────────────────────────────┘
右键菜单 → slot: context-menu-extra追加菜单项
设置面板 → slot: settings-panel内嵌区块
```
### 4.3 插槽定义表
| 插槽 ID | 位置 | 控制方式 | 谁可注册 | Phase |
|---------|------|---------|---------|-------|
| `titlebar-extra` | 标题栏右侧(窗口控制按钮左侧) | 声明式PluginMetadata.uiSlots[] | 内置 + 插件 | 3 |
| `tab-bar` | Tab 导航条内部 | 自动CapabilityTabProvider 插件自动合并 | PluginManager 驱动 | 1 |
| `main-content` | 中央主内容区 | 自动getComponent() 查 registry | registry 驱动 | 1 |
| `sidebar-left` | 左侧边栏底部FileSystem Sidebar 之后) | 声明式 | 插件 | 3 |
| `sidebar-right` | 右侧边栏(预览区旁) | 声明式 | 插件 | 3 |
| `toolbar-extra` | 工具栏末尾追加按钮 | 声明式 | 插件 | 2 |
| `status-bar` | 底部状态栏追加信息 | 声明式 | 插件 | 3 |
| `context-menu-extra` | 右键菜单追加项 | 声明式 | 插件 | 2 |
| `settings-panel` | 设置面板内嵌区块 | 声明式CapabilitySettings | 插件 | 3 |
### 4.4 插槽注册机制(前端)
```typescript
// frontend/src/plugin/slots.ts
interface SlotDefinition {
id: string // 插槽 ID
component?: Component // 注入的 Vue 组件
position?: 'prepend' | 'append' | 'before' | 'after' // 插入位置
order?: number // 同一插槽内的排序权重
}
interface UISlotRegistry {
/** 插件声明要占用哪些插槽 */
registerSlot(pluginId: string, def: SlotDefinition): void
/** App.vue 查询某插槽有哪些组件 */
getSlotComponents(slotId: string): SlotDefinition[]
/** 所有已注册的插槽概览 */
getAllSlots(): Map<string, SlotDefinition[]>
}
```
### 4.5 App.vue 插槽改造示意
```vue
<template>
<!-- 标题栏 -->
<div class="titlebar">
<div class="titlebar-left">u-desk</div>
<a-tabs v-model="activeTab" class="header-tabs">...</a-tabs>
<!-- 插槽: titlebar-extra -->
<component
v-for="slot in getSlotComponents('titlebar-extra')"
:key="slot.id"
:is="slot.component"
/>
<WindowControls />
</div>
<!-- 主区域 -->
<div class="main-layout">
<!-- 插槽: sidebar-left -->
<component
v-for="slot in getSlotComponents('sidebar-left')"
:is="slot.component"
/>
<!-- 插槽: main-content核心 -->
<KeepAlive :include="cacheableComponents">
<component :is="getComponent(activeTab)" />
</KeepAlive>
</div>
<!-- 工具栏插槽等... -->
</template>
```
### 4.6 后端接口支持
```go
// PluginMetadata 新增字段
type PluginMetadata struct {
// ... 现有字段 ...
UISlots []string `json:"ui_slots,omitempty"` // 声明占用的 UI 插槽列表
}
// Manager 新增方法
func (m *Manager) GetUISlotContents(slotID string) []UISlotEntry
```
### 4.7 切割原则
| 原则 | 说明 |
|------|------|
| **最小暴露** | 只开放必要的插槽,不把整个布局变成"配置驱动" |
| **位置固定** | 每个插槽的位置在 App.vue 中固定,插件只能决定"往里放什么",不能移动插槽本身 |
| **顺序可控** | 同一插槽多插件通过 order 控制排列顺序 |
| **隔离渲染** | 每个插槽内的插件组件有独立作用域,避免样式/状态污染 |
| **优雅降级** | 插件组件报错不影响宿主 UIErrorBoundary 包裹) |
## 五、关键改造点对比
### 5.1 App.vue 改造Phase 3
**改造前**
```typescript
import FileSystem from './components/FileSystem/index.vue'
import MarkdownEditor from './views/markdown-editor/index.vue'
const getComponent = (key: string) => ({
'file-system': FileSystem,
'markdown-editor': MarkdownEditor
}[key] || null)
```
**改造后**
```typescript
import '@/plugin/built-in' // 副作用:执行所有内置插件注册
import { getTabComponent } from '@/plugin/registry'
const getComponent = (key: string) => {
const loader = getTabComponent(key)
return loader ? defineAsyncComponent(loader) : null
}
```
### 5.2 FileEditorPanel.vue 改造Phase 2
**改造前**10 层 v-if/v-else-if 链binary/image/video/audio/pdf/excel/word/csv/html/md/text
**改造后**
```vue
<template>
<div class="editor-content">
<iframe v-if="renderConfig?.type === 'iframe'" :src="renderConfig.src" />
<component v-else-if="previewComponent" :is="previewComponent" v-bind="previewProps" />
<CodeEditor v-else ... />
</div>
</template>
<script setup lang="ts">
import { resolvePreviewHandler } from '@/plugin/registry'
import { computed } from 'vue'
const handler = computed(() =>
props.config.currentFileName ? resolvePreviewHandler(props.config.currentFileName) : null
)
const previewComponent = computed(() => handler.value?.getComponent?.())
const renderConfig = computed(() => handler.value?.getRenderConfig?.(props.filePath ?? ''))
</script>
```