Files
tunji2/docs/DESIGN.md
2026-06-01 17:28:00 +08:00

35 KiB
Raw Permalink Blame History

Tunji - 笔记应用设计文档

类印象笔记 + Typora 的桌面笔记应用,基于 Electron + Vue 3 + Tailwind CSS + PrimeVue (Unstyled) 构建


一、技术栈

层级 技术 说明
桌面框架 Electron 跨平台桌面应用
前端框架 Vue 3 (Composition API) 响应式 UI
路由 Vue Router 4 SPA 路由管理
状态管理 Pinia 全局状态管理
样式 Tailwind CSS 4 原子化 CSS
组件库 PrimeVue (Unstyled Mode) 无样式组件,配合 Tailwind 自定义样式
图标 lucide-vue-next 轻量级图标库
Markdown 处理 unified (remark + rehype) Markdown 解析 / 渲染 / 编辑
编辑器引擎 ProseMirror / Tiptap 富文本块编辑Typora 风格)
本地数据库 better-sqlite3 (主进程) 搜索索引、元数据缓存
文件监控 chokidar 工作目录文件变更监听
同步引擎 自研抽象层 支持 Git / WebDAV / S3 / 坚果云

二、主题色系统

参考 PrimeVue Lara 主题设计,采用蓝色为主色调,搭配中性灰色系。

2.1 主色 (Primary)

变量名 色值 用途
--p-primary-50 #eff6ff 最浅背景
--p-primary-100 #dbeafe 浅色背景 / hover 背景
--p-primary-200 #bfdbfe 边框 / 分割线
--p-primary-300 #93c5fd 禁用状态
--p-primary-400 #60a5fa hover 边框
--p-primary-500 #3b82f6 默认按钮 / 链接
--p-primary-600 #2563eb 按钮 hover
--p-primary-700 #1d4ed8 按钮 active / pressed
--p-primary-800 #1e40af 深色文字
--p-primary-900 #1e3a8a 最深色

2.2 表面色 (Surface)

变量名 色值 用途
--p-surface-0 #ffffff 页面背景 / 卡片背景
--p-surface-50 #f8fafc 左侧栏背景
--p-surface-100 #f1f5f9 次级背景 / hover 背景
--p-surface-200 #e2e8f0 分割线 / 边框
--p-surface-300 #cbd5e1 禁用边框
--p-surface-400 #94a3b8 占位符文字
--p-surface-500 #64748b 次要文字
--p-surface-600 #475569 正文文字
--p-surface-700 #334155 标题文字
--p-surface-800 #1e293b 主标题文字
--p-surface-900 #0f172a 最深文字

2.3 语义色

语义 色值 说明
--p-success-500 #22c55e 成功 / 新建
--p-success-600 #16a34a 成功 hover
--p-warn-500 #f59e0b 警告
--p-warn-600 #d97706 警告 hover
--p-danger-500 #ef4444 危险 / 删除
--p-danger-600 #dc2626 危险 hover
--p-info-500 #06b6d4 信息提示

2.4 暗色模式

暗色模式下 surface 反转primary 色保持不变但降低饱和度:

变量名 色值 用途
--p-surface-0 (dark) #0f172a 页面背景
--p-surface-50 (dark) #1e293b 左侧栏背景
--p-surface-100 (dark) #334155 卡片背景
--p-surface-200 (dark) #475569 边框 / 分割线
--p-surface-700 (dark) #e2e8f0 标题文字
--p-surface-800 (dark) #f1f5f9 主标题文字

三、工作目录设计

3.1 核心概念

参考 Obsidian 的 Vault 模型:用户首次启动时必须选择一个工作目录,所有数据(笔记、笔记本、标签、配置、插件)都在该目录下管理。

3.2 目录结构

<用户选择的工作目录>/
├── .tunji/                          # 应用元数据目录(隐藏)
│   ├── config.json                  # 应用配置(主题、编辑器设置等)
│   ├── tags.json                    # 标签定义(全局标签库)
│   ├── notebooks.json               # 笔记本元数据(排序、颜色等)
│   ├── sync.json                    # 同步配置WebDAV/S3/Git 凭据引用)
│   ├── plugins/                     # 插件目录(未来扩展)
│   │   └── ...
│   ├── trash/                       # 废纸篓(已删除笔记的临时存放)
│   │   ├── <note-id>.md
│   │   └── ...
│   ├── versions/                    # 本地版本快照Git-like 内容寻址)
│   │   ├── <hash1>                  # 文件内容的 SHA-256 哈希
│   │   ├── <hash2>
│   │   └── ...
│   └── index.sqlite                 # 本地搜索索引(不同步!)
│
├── 笔记本A/                         # 用户的笔记本(即文件夹)
│   ├── 笔记1.md                     # 笔记文件
│   ├── 笔记1.assets/                # 笔记1 的专属附件目录(同名配对)
│   │   ├── 架构图.png
│   │   └── 需求文档.pdf
│   ├── 笔记2.md
│   ├── 笔记2.assets/                # 笔记2 的专属附件目录
│   │   └── screenshot.jpg
│   ├── 子笔记本C/                   # 笔记本支持嵌套
│   │   ├── 笔记3.md
│   │   ├── 笔记3.assets/
│   │   │   └── 流程图.png
│   │   └── ...
│   └── ...
│
├── 笔记本B/
│   ├── 笔记4.md
│   ├── 笔记4.assets/
│   │   └── 演示视频.mp4
│   └── ...
│
└── 无归属笔记.md                    # 根目录下不属于任何笔记本的笔记
    └── 无归属笔记.assets/
        └── logo.png

3.3 笔记文件格式

每个 .md 文件使用 YAML Front Matter 存储元数据:

---
id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
title: "笔记标题"
created: "2026-06-01T10:00:00.000Z"
updated: "2026-06-01T12:30:00.000Z"
tags: ["工作", "会议", "项目A"]
notebook: "笔记本A"
pinned: false
favorited: false
---

# 笔记标题

这是笔记正文内容...

3.4 附件存储设计

设计原则

每个笔记文件拥有一个同名的 .assets/ 目录,附件与笔记一一配对。这是 VS Code Markdown 预览和 Typora 验证过的成熟模式。

命名规则:笔记名.md笔记名.assets/

存储方式

笔记本A/
├── 笔记1.md                       # 引用: ![架构图](笔记1.assets/架构图.png)
├── 笔记1.assets/                  # 笔记1 的专属附件
│   ├── 架构图.png                 # 保留用户原始文件名
│   ├── 架构图(1).png              # 同名文件自动编号
│   └── 需求文档.pdf
├── 笔记2.md
├── 笔记2.assets/
│   └── screenshot.jpg
└── 子笔记本C/
    ├── 笔记3.md
    ├── 笔记3.assets/
    │   └── 流程图.png
    └── ...

文件名策略(不用 UUID

场景 处理方式 示例
首次保存 保留原始文件名 架构图.png
同名冲突 自动追加数字后缀 架构图(1).png架构图(2).png
粘贴/截图 使用时间戳命名 image-20260601-143022.png
剪贴板图片 使用时间戳命名 clipboard-20260601-143022.png

引用方式

笔记中引用附件使用相对于自身的路径:

![架构图](笔记1.assets/架构图.png)
[需求文档](笔记1.assets/需求文档.pdf)

移动笔记时的附件处理

这是本设计的核心优势——移动笔记零成本

移动前:
笔记本A/
├── 会议纪要.md
└── 会议纪要.assets/
    └── 白板照片.jpg

移动后拖拽到笔记本B
笔记本B/
├── 会议纪要.md          # 引用路径不变:![照片](会议纪要.assets/白板照片.jpg)
└── 会议纪要.assets/
    └── 白板照片.jpg

笔记本A/
└── (干净,无残留)

只需要移动两个东西

  1. 会议纪要.md
  2. 会议纪要.assets/ 目录

引用路径 会议纪要.assets/白板照片.jpg 是相对于 .md 文件的,所以完全不需要修改笔记内容

重命名笔记时的附件处理

重命名笔记时自动重命名对应的 .assets/ 目录:

重命名前笔记1.md + 笔记1.assets/
重命名后:项目方案.md + 项目方案.assets/

同时批量替换笔记内所有 笔记1.assets/项目方案.assets/ 的引用。

删除笔记时的附件处理

  • 删除笔记 → .md 文件 + 同名 .assets/ 目录一起移入废纸篓
  • 恢复笔记 → 一起恢复
  • 彻底删除 → 一起彻底删除
  • 不存在孤儿附件文件

对比其他方案

方案 移动笔记 重命名笔记 删除笔记 用户可读性 同步冲突
全局附件 + UUID 需迁移附件+改引用 需改引用 需扫描孤儿文件 UUID 高(大目录)
每笔记本 assets 需迁移附件+改引用 需改引用 需扫描孤儿文件
每笔记 .assets推荐 移动两个文件/目录即可 改目录名+批量替换引用 自动跟随 低(分散)

3.5 笔记本 vs 文件夹

  • 笔记本 = 文件夹:直接映射,用户在文件管理器中看到的结构就是应用中的结构
  • 支持无限层级嵌套
  • .tunji/ 目录下的 notebooks.json 存储笔记本的额外元数据(颜色、图标、排序)

四、同步架构设计

4.1 设计原则

  1. 数据库不同步index.sqlite 纯本地,同步后在本地重建
  2. 版本快照不同步.tunji/versions/ 纯本地
  3. 配置可选同步.tunji/config.json 可选是否同步
  4. 只同步用户内容.md 文件 + 同名 .assets/ 目录 + .tunji/tags.json + .tunji/notebooks.json

4.2 同步抽象层

┌─────────────────────────────────────────┐
│              SyncManager                 │
│  (事件驱动,统一接口)                      │
├─────────────────────────────────────────┤
│  - sync()           触发全量同步          │
│  - pull()           拉取远程变更          │
│  - push()           推送本地变更          │
│  - getConflicts()   获取冲突列表          │
│  - resolve()        解决冲突              │
│  - getStatus()      同步状态              │
├──────────┬──────────┬──────────┬─────────┤
│ GitSync  │WebDavSync│ S3Sync   │坚果云Sync│
│ (libgit2)│(webdav)  │(aws-sdk) │(webdav) │
└──────────┴──────────┴──────────┴─────────┘

4.3 同步策略

Git 同步

// 策略:自动 commit + push冲突时提示用户
// config 示例:
{
  type: 'git',
  repoUrl: 'https://github.com/user/notes.git',
  branch: 'main',          // 默认 main
  autoCommit: true,        // 每次变更自动 commit
  autoPush: true,          // 每次 commit 后自动 push
  commitMessage: '{{date}} {{action}}', // 支持模板
  sshKeyPath: '',          // SSH 私钥路径(可选)
  token: ''                // HTTPS token可选
}

WebDAV 同步(坚果云等)

// config 示例:
{
  type: 'webdav',
  serverUrl: 'https://dav.jianguoyun.com/dav/',
  username: 'user@example.com',
  password: 'app-specific-password',  // 应用专用密码
  remotePath: '/tunji-notes/',        // 远程目录路径
  syncInterval: 300                   // 自动同步间隔(秒)
}

S3 同步

// config 示例:
{
  type: 's3',
  endpoint: 'https://s3.amazonaws.com', // S3 兼容端点
  bucket: 'my-notes',
  accessKey: 'AKIAIOSFODNN7EXAMPLE',
  secretKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
  region: 'us-east-1',
  prefix: 'tunji/'                     // 对象前缀
}

4.4 冲突解决策略

场景 策略
仅本地修改 直接推送
仅远程修改 直接拉取
双方都修改 冲突:生成冲突副本 笔记名(冲突-本地).md + 笔记名(冲突-远程).md,提示用户手动合并
本地删除 vs 远程修改 提示用户确认:恢复 or 确认删除
远程删除 vs 本地修改 提示用户确认:重新推送 or 确认删除

4.5 同步状态机

IDLE → SYNCING → SUCCESS
                 → CONFLICT (需用户介入)
                 → ERROR (网络错误等,自动重试)

五、本地数据库设计

5.1 为什么需要本地数据库

  1. 全文搜索:纯文件遍历搜索太慢,需要倒排索引
  2. 元数据查询:按标签、创建时间、更新时间等维度快速筛选
  3. 排序和分页:大量笔记的列表展示需要高效查询
  4. 跨笔记本搜索:模糊搜索需要全局索引

5.2 数据库选型

better-sqlite3Electron 主进程)

  • 纯本地,无需网络
  • 同步到工作目录 .tunji/index.sqlite
  • 不同步到远程

5.3 数据库 Schema

-- 笔记索引表(从 .md 文件解析的元数据)
CREATE TABLE notes (
  id TEXT PRIMARY KEY,              -- UUID
  title TEXT NOT NULL,
  file_path TEXT NOT NULL UNIQUE,   -- 相对于工作目录的路径
  notebook_path TEXT,               -- 所属笔记本路径
  created_at TEXT,                  -- ISO 8601
  updated_at TEXT,
  pinned INTEGER DEFAULT 0,
  favorited INTEGER DEFAULT 0,
  word_count INTEGER DEFAULT 0,
  is_deleted INTEGER DEFAULT 0      -- 软删除(废纸篓)
);

-- 标签表
CREATE TABLE tags (
  id TEXT PRIMARY KEY,
  name TEXT NOT NULL UNIQUE,
  color TEXT                        -- 标签颜色
);

-- 笔记-标签关联表
CREATE TABLE note_tags (
  note_id TEXT NOT NULL,
  tag_id TEXT NOT NULL,
  PRIMARY KEY (note_id, tag_id),
  FOREIGN KEY (note_id) REFERENCES notes(id),
  FOREIGN KEY (tag_id) REFERENCES tags(id)
);

-- 全文搜索虚拟表FTS5
CREATE VIRTUAL TABLE notes_fts USING fts5(
  title,
  content,                          -- 笔记正文(不含 front matter
  note_id UNINDEXED,
  tokenize='unicode61'
);

-- 笔记本元数据表
CREATE TABLE notebooks (
  path TEXT PRIMARY KEY,            -- 相对于工作目录的路径
  name TEXT NOT NULL,
  parent_path TEXT,
  color TEXT,
  icon TEXT,
  sort_order INTEGER DEFAULT 0
);

-- 同步状态表
CREATE TABLE sync_state (
  key TEXT PRIMARY KEY,
  value TEXT
);

5.4 索引重建流程

当用户在其他设备同步后打开应用,或删除了 index.sqlite

1. 扫描工作目录下所有 .md 文件
2. 解析 front matter 元数据
3. 提取正文内容
4. 批量写入 notes 表
5. 批量写入 notes_fts 全文索引
6. 解析 tags.json写入 tags 表
7. 建立 note_tags 关联
8. 完成,进入正常模式

六、版本控制与文件防丢失

6.1 本地版本快照(不依赖 Git

原理:每次保存笔记时,将旧内容的 SHA-256 哈希作为文件名,存入 .tunji/versions/ 目录。

.tunji/versions/
├── a1b2c3d4...     # 笔记某次修改的内容快照
├── e5f6g7h8...
└── ...

.tunji/versions-manifest.json  # 版本清单
{
  "note-id-1": [
    { "hash": "a1b2c3d4...", "timestamp": "2026-06-01T10:00:00Z", "message": "自动保存" },
    { "hash": "e5f6g7h8...", "timestamp": "2026-06-01T09:30:00Z", "message": "自动保存" }
  ]
}

6.2 版本回退机制

用户点击"版本历史"
  → 读取 versions-manifest.json 获取该笔记的所有版本
  → 按时间倒序展示版本列表
  → 用户选择某个版本
  → 从 versions/<hash> 读取内容
  → 预览差异diff view
  → 确认回退
  → 将当前版本存为新快照(可撤销)
  → 将选中版本内容写回 .md 文件
  → 更新数据库索引

6.3 防丢失策略

策略 说明
自动保存 编辑器内容每 5 秒自动保存到 .md 文件
版本快照 每次保存前,旧版本自动存入 versions/
废纸篓 删除笔记不直接删除,移入 .tunji/trash/,保留 30 天
崩溃恢复 Electron 主进程监听 crash 事件,编辑器状态暂存 localStorage
同步前备份 同步拉取远程变更前,先备份本地当前版本

6.4 版本快照清理策略

  • 保留最近 50 个版本
  • 超过 30 天的版本,仅保留每天第一个
  • 超过 90 天的版本,仅保留每周第一个
  • 用户可手动清理所有历史版本

七、页面布局设计

7.1 整体布局

┌──────────────────────────────────────────────────────────┐
│                    Electron Title Bar                     │
├──────────┬───────────────────────────────────────────────┤
│          │                                               │
│  LEFT    │              RIGHT AREA                       │
│  AREA    │              (Router View)                    │
│          │                                               │
│  240-    │  ┌─────────────────────────────────────────┐  │
│  480px   │  │  Notes View / Tags View / Note Detail   │  │
│  可拖拽   │  │                                         │  │
│          │  │                                         │  │
│          │  │                                         │  │
│          │  │                                         │  │
│          │  └─────────────────────────────────────────┘  │
│          │                                               │
└──────────┴───────────────────────────────────────────────┘

7.2 左侧栏 (LeftArea) 详细设计

┌────────────────────────────┐
│ 👤 用户名称          [◀]   │  ← 第一行:头像 + 名称 + 折叠按钮
├────────────────────────────┤
│ 🔍 搜索笔记...        [+]  │  ← 第二行:搜索框 + 新建按钮
├────────────────────────────┤
│ 📝 笔记                     │  ← 第三行Menu Item - 跳转笔记列表
├────────────────────────────┤
│ 🏷️ 标签                     │  ← 第四行Menu Item - 跳转标签页
├────────────────────────────┤
│ 📚 笔记本                   │  ← 第五行Menu Label
│ ├─ 📂 笔记本A              │  ← PanelMenu可展开/折叠
│ │  ├─ 📄 笔记1             │
│ │  ├─ 📄 笔记2             │
│ │  └─ 📂 子笔记本C         │
│ │     └─ 📄 笔记3          │
│ └─ 📂 笔记本B              │
│    └─ 📄 笔记4             │
│                            │
│                            │
│                            │
├────────────────────────────┤
│ ⚙️ 设置              🗑️ 废纸篓│  ← 底部固定:设置 + 废纸篓
└────────────────────────────┘

交互细节

  • 折叠按钮:点击后左侧栏收起为 0px 或 48px仅显示图标右侧区域全屏
  • 搜索框实时搜索debounce 300ms结果在右侧区域展示
  • 新建按钮:点击后在当前选中的笔记本下创建新笔记(若未选中笔记本则创建在根目录)
  • 笔记本 PanelMenu:点击笔记本切换展开/折叠;点击笔记在右侧打开详情
  • 拖拽调整宽度:左侧栏右边框可拖拽,范围 240px ~ 480px

7.3 右侧区域 (RightArea) 路由设计

路由 视图 说明
/ 首页 / 欢迎页 首次打开或未选中笔记时展示
/notes 笔记列表 工作目录下所有笔记,支持列表/卡片视图切换
/notes?notebook=<path> 笔记列表(过滤) 指定笔记本下的笔记
/tags 标签列表 展示所有标签,点击标签查看关联笔记
/tags/:tagId 标签详情 某个标签下的所有笔记,支持列表/卡片视图
/note/:id 笔记详情 笔记编辑/预览/双栏视图
/search?q=xxx 搜索结果 全局搜索结果
/settings 设置页面 应用设置
/trash 废纸篓 已删除笔记列表

7.4 笔记详情页 (NoteDetail) 详细设计

┌──────────────────────────────────────────────────────────┐
│  笔记标题(可编辑)                                        │
├──────────────────────────────────────────────────────────┤
│  [编辑] [预览] [双栏]  │  🏷️ 标签  │  ⋯ 更多操作          │
├──────────────────────────────────────────────────────────┤
│                                                          │
│  ┌────────────────────────────────────────────────────┐  │
│  │                                                    │  │
│  │              笔记内容区域                            │  │
│  │                                                    │  │
│  │  支持:                                             │  │
│  │  - Markdown 实时渲染                                 │  │
│  │  - 块级编辑Typora 风格)                           │  │
│  │  - 拖拽移动块                                       │  │
│  │  - 图片粘贴 / 拖入                                   │  │
│  │  - 代码块高亮                                       │  │
│  │  - 表格编辑                                         │  │
│  │  - 数学公式 (KaTeX)                                 │  │
│  │  - Mermaid 图表                                     │  │
│  │                                                    │  │
│  └────────────────────────────────────────────────────┘  │
│                                                          │
└──────────────────────────────────────────────────────────┘

三种视图模式

模式 说明
编辑模式 纯 Markdown 编辑(类似 VS Code
预览模式 纯渲染预览(只读)
双栏模式 左侧 Markdown 源码,右侧实时预览(类似 Typora 的源码模式)

注:后期实现块编辑后,默认进入"所见即所得"模式,编辑/预览/双栏作为切换选项


八、组件架构

8.1 核心组件树

App.vue
├── MainLayout
│   ├── LeftArea
│   │   ├── UserProfile           # 头像 + 用户名
│   │   ├── CollapseButton        # 折叠按钮
│   │   ├── SearchInput           # 搜索框
│   │   ├── AddNoteButton         # 新建笔记按钮
│   │   ├── MenuItem (笔记)       # 路由跳转
│   │   ├── MenuItem (标签)       # 路由跳转
│   │   ├── NotebookTree          # 笔记本树PanelMenu
│   │   │   ├── NotebookNode      # 笔记本节点(可展开)
│   │   │   │   ├── NoteItem      # 笔记条目
│   │   │   │   └── NotebookNode  # 递归嵌套
│   │   ├── SettingsLink          # 设置入口
│   │   └── TrashLink             # 废纸篓入口
│   │
│   ├── ResizeHandle              # 拖拽调整宽度
│   │
│   └── RightArea (RouterView)
│       ├── WelcomeView           # 欢迎页
│       ├── NotesListView         # 笔记列表
│       │   ├── NoteCard          # 卡片模式
│       │   └── NoteListItem      # 列表模式
│       ├── TagsView              # 标签列表
│       ├── TagDetailView         # 标签详情
│       ├── NoteDetailView        # 笔记详情
│       │   ├── NoteTitle         # 标题编辑
│       │   ├── NoteToolbar       # 操作栏
│       │   ├── NoteEditor        # 编辑器
│       │   │   ├── TiptapEditor  # Tiptap 编辑器实例
│       │   │   └── BlockMenu     # 块操作菜单
│       │   └── NotePreview       # 渲染预览
│       ├── SearchView            # 搜索结果
│       ├── SettingsView          # 设置页面
│       └── TrashView             # 废纸篓
│
└── StatusBar                     # 底部状态栏(可选)

8.2 Electron 进程架构

┌─────────────────────────────────────────────────────────┐
│                    Main Process                          │
│                                                         │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐              │
│  │ FileManager│ │ Database  │ │ SyncEngine│              │
│  │ (chokidar)│ │(sqlite3) │ │ (各后端)   │              │
│  └──────────┘  └──────────┘  └──────────┘              │
│        │              │              │                   │
│        └──────────────┼──────────────┘                   │
│                       │ IPC Bridge                       │
├───────────────────────┼─────────────────────────────────┤
│                       │                                  │
│              Renderer Process (Vue 3)                    │
│                       │                                  │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐              │
│  │ fileStore │ │ noteStore │ │ syncStore │              │
│  │  (Pinia)  │ │  (Pinia)  │ │  (Pinia)  │              │
│  └──────────┘  └──────────┘  └──────────┘              │
│                                                         │
└─────────────────────────────────────────────────────────┘

8.3 IPC 通信接口

// preload.js 暴露到渲染进程的 API
// 通过 contextBridge 暴露,渲染进程通过 window.tunjiAPI 调用

// 文件操作
window.tunjiAPI.file.read(filePath)          // → string
window.tunjiAPI.file.write(filePath, content) // → void
window.tunjiAPI.file.delete(filePath)         // → void
window.tunjiAPI.file.rename(oldPath, newPath) // → void
window.tunjiAPI.file.list(dirPath)            // → FileEntry[]
window.tunjiAPI.file.watch(callback)          // 监听文件变更事件

// 数据库操作
window.tunjiAPI.db.searchNotes(query)         // → NoteSearchResult[]
window.tunjiAPI.db.getNotes(filter)           // → NoteMeta[]
window.tunjiAPI.db.getNote(id)                // → NoteMeta
window.tunjiAPI.db.updateNoteMeta(id, meta)   // → void
window.tunjiAPI.db.getTags()                  // → Tag[]
window.tunjiAPI.db.getNotebooks()             // → Notebook[]
window.tunjiAPI.db.rebuildIndex()             // → void

// 同步操作
window.tunjiAPI.sync.getStatus()              // → SyncStatus
window.tunjiAPI.sync.start()                  // → void
window.tunjiAPI.sync.configure(config)        // → void
window.tunjiAPI.sync.resolveConflict(noteId, resolution) // resolution: 'local'|'remote'|'merge'

// 工作目录
window.tunjiAPI.workspace.select()            // → string|null  打开目录选择对话框
window.tunjiAPI.workspace.get()               // → string|null  获取当前工作目录
window.tunjiAPI.workspace.set(path)           // → void         设置工作目录

// 系统
window.tunjiAPI.system.getAppVersion()        // → string
window.tunjiAPI.system.getPlatform()          // → string
window.tunjiAPI.system.openExternal(url)      // → void
window.tunjiAPI.system.showItemInFolder(path) // → void

九、Pinia Store 设计

所有 Store 使用 Vue 3 Composition API 风格(setup 函数),纯 JavaScript。

// stores/workspace.js - 工作目录状态
// state: { path, isReady }
// actions: selectWorkspace(), initWorkspace(path)

// stores/notes.js - 笔记状态
// state: { notes, currentNote, viewMode, filter }
// actions: fetchNotes(filter), openNote(id), createNote(notebookPath),
//          deleteNote(id), saveNote(id, content)

// stores/notebooks.js - 笔记本状态
// state: { notebooks, expandedPaths }
// actions: fetchNotebooks(), toggleExpand(path), createNotebook(name, parentPath),
//          renameNotebook(path, newName), deleteNotebook(path)

// stores/tags.js - 标签状态
// state: { tags }
// actions: fetchTags(), createTag(name, color), deleteTag(id),
//          addTagToNote(noteId, tagId), removeTagFromNote(noteId, tagId)

// stores/sync.js - 同步状态
// state: { status, config, conflicts }
// actions: startSync(), configureSync(config), resolveConflict(noteId, resolution)

// stores/ui.js - UI 状态
// state: { leftCollapsed, leftWidth, theme }
// actions: toggleLeft(), setLeftWidth(width), setTheme(theme)

十、项目结构

tunji/
├── docs/
│   └── DESIGN.md                # 本文档
│
├── electron/
│   ├── main.js                  # Electron 主进程入口
│   ├── preload.js               # 预加载脚本IPC 桥接)
│   ├── ipc/
│   │   ├── file.ipc.js          # 文件操作 IPC 处理
│   │   ├── db.ipc.js            # 数据库 IPC 处理
│   │   ├── sync.ipc.js          # 同步 IPC 处理
│   │   └── workspace.ipc.js     # 工作目录 IPC 处理
│   ├── services/
│   │   ├── file-manager.js      # 文件管理服务
│   │   ├── database.js          # SQLite 数据库服务
│   │   ├── index-builder.js     # 索引构建服务
│   │   ├── version-manager.js   # 版本管理服务
│   │   └── sync/
│   │       ├── sync-manager.js  # 同步管理器
│   │       ├── git-sync.js      # Git 同步实现
│   │       ├── webdav-sync.js   # WebDAV 同步实现
│   │       └── s3-sync.js       # S3 同步实现
│   └── utils/
│       ├── markdown-parser.js   # Front matter 解析
│       └── file-hash.js         # 文件哈希计算
│
├── src/
│   ├── App.vue                  # 根组件
│   ├── main.js                  # Vue 入口
│   ├── router/
│   │   └── index.js             # 路由配置
│   ├── stores/                  # Pinia 状态管理
│   │   ├── workspace.js
│   │   ├── notes.js
│   │   ├── notebooks.js
│   │   ├── tags.js
│   │   ├── sync.js
│   │   └── ui.js
│   ├── layouts/
│   │   └── MainLayout.vue       # 主布局
│   ├── components/
│   │   ├── left-area/
│   │   │   ├── LeftArea.vue
│   │   │   ├── UserProfile.vue
│   │   │   ├── SearchInput.vue
│   │   │   ├── AddNoteButton.vue
│   │   │   ├── MenuItem.vue
│   │   │   ├── NotebookTree.vue
│   │   │   ├── NotebookNode.vue
│   │   │   └── NoteItem.vue
│   │   ├── right-area/
│   │   │   ├── NotesListView.vue
│   │   │   ├── NoteCard.vue
│   │   │   ├── NoteListItem.vue
│   │   │   ├── TagsView.vue
│   │   │   ├── TagDetailView.vue
│   │   │   ├── NoteDetailView.vue
│   │   │   ├── SearchView.vue
│   │   │   ├── SettingsView.vue
│   │   │   └── TrashView.vue
│   │   ├── editor/
│   │   │   ├── NoteEditor.vue
│   │   │   ├── NotePreview.vue
│   │   │   ├── TiptapEditor.vue
│   │   │   └── BlockMenu.vue
│   │   └── common/
│   │       ├── ResizeHandle.vue
│   │       ├── ViewToggle.vue    # 列表/卡片视图切换
│   │       └── EmptyState.vue
│   ├── composables/
│   │   ├── useDragResize.js     # 拖拽调整宽度
│   │   ├── useSearch.js         # 搜索逻辑
│   │   ├── useAutoSave.js       # 自动保存
│   │   └── useVersionHistory.js # 版本历史
│   └── styles/
│       ├── theme.css            # 主题变量定义
│       └── tailwind.css         # Tailwind 入口
│
├── index.html                   # HTML 入口
├── package.json
├── vite.config.js
├── tailwind.config.js
├── electron-builder.json        # Electron 打包配置
└── README.md

十一、开发路线图

Phase 1基础骨架MVP

  • Electron + Vue 3 + Vite 项目初始化
  • Tailwind CSS + PrimeVue (Unstyled) 集成
  • 主题色系统实现
  • 工作目录选择功能
  • 两栏布局 + 拖拽调整宽度
  • 左侧栏基础组件
  • 路由基础配置

Phase 2核心笔记功能

  • Markdown 文件读写
  • Front matter 解析
  • 笔记创建 / 编辑 / 删除
  • 笔记本(文件夹)管理
  • Tiptap 编辑器集成
  • Markdown 实时预览

Phase 3索引与搜索

  • SQLite 数据库集成
  • 笔记索引构建
  • 全文搜索
  • 标签系统

Phase 4同步功能

  • 同步抽象层
  • Git 同步
  • WebDAV 同步
  • S3 同步
  • 冲突解决

Phase 5高级编辑

  • Typora 风格块编辑
  • 拖拽排序
  • 图片管理
  • 代码高亮
  • 数学公式
  • Mermaid 图表

Phase 6完善与优化

  • 版本历史 / 回退
  • 废纸篓
  • 暗色模式
  • 插件系统
  • 性能优化
  • 打包发布