# 架构设计 ## 一、架构全景 ``` +==================================================================+ | 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 导航 + 窗口控制 │ ← 固定区域,不可扩展 ├─────────────────────────────────┤ │ │ │ │ ← 内容区,getComponent() 硬编码 │ │ ├─────────────────────────────────┤ │ 更新通知 / 设置抽屉 │ ← 固定区域 └─────────────────────────────────┘ ``` **问题**:所有 UI 区域都是硬编码的。新插件无法在标题栏加按钮、无法在工具栏扩展、无法在侧边栏注入面板。 ### 4.2 插件化后的插槽布局 ``` ┌── slot: titlebar-extra ──────────────────────────────┐ │ [u-desk] [文件管理 | Markdown] [🔌插件A] [🔌插件B] [—][□][×] │ ├──────────────────────────────────────────────────────┤ │ slot: sidebar-left │ slot: main-content │ slot: sidebar-right │ │ │ │ │ │ [内置侧边栏] │ │ (预留) │ │ ───────────── │ │ │ │ [插件侧边面板] │ │ │ │ │ │ │ ├────────────────────┴───────────────────────────────────┴──────────────────────┤ │ 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 } ``` ### 4.5 App.vue 插槽改造示意 ```vue ``` ### 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