251 lines
12 KiB
Markdown
251 lines
12 KiB
Markdown
# 架构设计
|
||
|
||
## 一、架构全景
|
||
|
||
```
|
||
+==================================================================+
|
||
| 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 控制排列顺序 |
|
||
| **隔离渲染** | 每个插槽内的插件组件有独立作用域,避免样式/状态污染 |
|
||
| **优雅降级** | 插件组件报错不影响宿主 UI(ErrorBoundary 包裹) |
|
||
|
||
## 五、关键改造点对比
|
||
|
||
### 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>
|
||
```
|