# electron-notebook **Repository Path**: moqi-y/electron-notebook ## Basic Information - **Project Name**: electron-notebook - **Description**: electron-notebook 是一款简洁的桌面笔记/文档管理应用,采用现代化的技术栈开发,提供了良好的用户体验和流畅的操作界面。 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-07-16 - **Last Updated**: 2026-08-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: Electron, Vue, TypeScript ## README # 📓 小鹿记 · electron-notebook > 一款基于 **Electron + Vue 3 + TypeScript + SQLite** 的本地优先(local-first)桌面笔记应用。 > 主进程使用 **Repository 模式 + 事件路由** 的清晰分层,渲染端采用 **Vue 3 Composition API + Pinia + Vue Router**, > 强调**架构可演进、类型安全、数据自主可控**。 ![status](https://img.shields.io/badge/status-active-brightgreen) ![electron](https://img.shields.io/badge/electron-39.x-blue) ![vue](https://img.shields.io/badge/vue-3.5-brightgreen) ![typescript](https://img.shields.io/badge/typescript-5.9-blue) ![license](https://img.shields.io/badge/license-MIT-lightgrey) --- ## 🎯 项目亮点 | 维度 | 亮点 | |---|---| | **架构** | 主进程三层分层(Router → Repository → SQL),渲染端基于 `contextBridge` 安全桥接 | | **数据** | 本地 SQLite 存储,`WAL` 模式 + 外键级联 + 索引,单文件 DB,随用随走 | | **通信** | 统一 IPC 入口(Front Controller 模式)+ 类型化 RPC 协议,渲染端零 try/catch | | **体验** | 富文本编辑器(wangeditor)+ 800ms 节流自动保存 + 路由驱动的状态切换 | | **工程** | 严格的 TypeScript(双端 typecheck 全过),ESLint + Prettier,electron-vite 现代化构建 | --- ## 🧱 技术栈与选型理由 | 技术 | 用途 | 选型理由 | |---|---|---| | **Electron 39** | 跨平台桌面壳 | 团队熟悉度最高、生态最广、原生能力完整 | | **Vue 3 + TS** | 渲染端 | Composition API 复用更自然,TS 类型推导对编辑器友好 | | **electron-vite** | 构建工具 | 主/预加载/渲染端三段独立 HMR,比 webpack 配置简单 10× | | **better-sqlite3** | 嵌入式 DB | 同步 API + prepared statement 防止 SQL 注入,单文件零部署 | | **Pinia** | 状态管理 | 取代 Vuex,TS 推断完善,组合式 API 风格 | | **Element Plus** | UI 组件库 | 中文文档完整、企业级组件齐全、按需引入可控 | | **wangeditor** | 富文本 | 国产开源、API 简洁、Vue 3 集成包开箱即用 | | **vue-router 5 (Vue3 兼容版)** | 路由 | hash 模式适合 Electron 文件协议(无需配 history fallback) | --- ## 🏛 架构总览 ### 进程划分 ``` ┌─────────────────── 渲染进程 (Vue 3, 无 Node 能力) ───────────────────┐ │ │ │ Views / Components │ │ │ │ │ ▼ │ │ Pinia Stores ──► utils/ipc.ts (callMain + api) │ │ │ │ └─────────────────────────┼────────────────────────────────────────────┘ │ ipcRenderer.invoke('call-main', {name, payload}) ▼ ┌─────────────────────── 主进程 (Node, 全权限) ─────────────────────────┐ │ │ │ EventRouter (Front Controller 模式: 一个入口 + 路由分发) │ │ │ │ │ ▼ │ │ router.template.ts (类型化路由表,薄 handler) │ │ │ │ │ ▼ │ │ Repositories (knowledge.repo / document.repo) │ │ │ ↑ │ │ │ ├── Entity → DTO 转换 │ │ │ └── 业务参数校验 │ │ ▼ │ │ crud.ts (通用 SQL 工具) → schema.ts (建表 SQL 集中管理) │ │ │ │ │ ▼ │ │ better-sqlite3 ──► ./notebook.db (WAL 模式) │ │ │ └───────────────────────────────────────────────────────────────────────┘ ``` ### 核心设计模式 1. **Front Controller(前端控制器)** 所有 IPC 走 `call-main` 一个通道,payload 带 `name` 字段路由。日志、权限、监控都集中加在一个地方。 2. **Repository 模式** 路由只问 repo "给我所有知识库",repo 决定怎么查。未来换 MySQL/Postgres 只改 repo,路由和 UI 都不动。 3. **Entity / DTO 分离** 数据库用 `snake_case`(SQL 传统),业务层用 `camelCase`(JS/TS 习惯),repo 层做转换。外部世界不需要知道 DB 列名。 4. **统一错误协议** 业务方只看到 `{ ok: true, data }` 或 `{ ok: false, message }`,永远收不到异常。比 try/catch 散在各处干净 10×。 --- ## 📂 项目结构 ``` electron-notebook/ ├── src/ │ ├── main/ # ⚙️ Electron 主进程(Node 环境) │ │ ├── index.ts # 启动入口 │ │ ├── frame/MainFrame.ts # 窗口管理 │ │ ├── router/ # 事件路由(IPC 分发) │ │ │ ├── EventRoute.ts │ │ │ ├── EventRouter.ts │ │ │ └── router.template.ts # 🟢 所有 RPC 路由在这里注册 │ │ └── database/ # 数据访问层 │ │ ├── config.ts # DB 连接 + pragma │ │ ├── schema.ts # 🟢 所有建表 SQL 集中 │ │ ├── migrate.ts # 启动时迁移 │ │ ├── crud.ts # 通用 SQL 工具 │ │ ├── types.ts # Entity + DTO 类型 │ │ └── repositories/ # 🟢 业务级数据访问 │ │ ├── knowledge.repo.ts │ │ └── document.repo.ts │ ├── preload/ # 🔌 预加载脚本 │ │ ├── index.ts # contextBridge 安全桥 │ │ └── index.d.ts # 类型声明 │ └── renderer/ # 🎨 渲染进程(Vue 应用) │ └── src/ │ ├── main.ts # Vue 应用入口 │ ├── App.vue # 根布局 │ ├── views/ # 页面 │ │ ├── Home.vue # 首页(最近编辑) │ │ ├── Books.vue # 知识库列表 │ │ ├── BookDetail.vue # 知识库详情(DocumentMenu + Edit) │ │ ├── Notes.vue # 小记 │ │ ├── Stars.vue # 收藏 │ │ └── DocumentDetail.vue │ ├── components/ # 通用组件 │ ├── layout/BaseNavbar.vue │ ├── store/ # Pinia │ ├── router/ # Vue Router │ └── utils/ # 🟢 渲染端工具 │ ├── ipc.ts # 类型化 RPC 客户端 │ └── types.ts # DTO 类型 ├── docs/ │ └── ROADMAP.md # 🛣 详细改进路线图 ├── build/ # 打包资源(icon / 权限) ├── electron.vite.config.ts ├── electron-builder.yml └── package.json ``` --- ## 💾 数据模型 ```sql -- 知识库 CREATE TABLE knowledges ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, description TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); -- 文档 / 小记(统一表,knowledge_id 为 NULL 表示"小记") CREATE TABLE documents ( id INTEGER PRIMARY KEY AUTOINCREMENT, knowledge_id INTEGER, title TEXT NOT NULL DEFAULT '未命名文档', content TEXT NOT NULL DEFAULT '', is_starred INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, last_edited_at TEXT NOT NULL, FOREIGN KEY (knowledge_id) REFERENCES knowledges(id) ON DELETE CASCADE ); -- 关键索引 CREATE INDEX idx_documents_knowledge_id ON documents(knowledge_id); CREATE INDEX idx_documents_is_starred ON documents(is_starred); CREATE INDEX idx_documents_last_edited_at ON documents(last_edited_at DESC); ``` **设计要点**: - 「小记」= `knowledge_id IS NULL`,「收藏」= `is_starred = 1`,用一张表管两种内容,零冗余。 - `ON DELETE CASCADE` 删除知识库时自动删除其下文档,避免孤儿数据。 - `last_edited_at` 单独拆出,方便首页「最近编辑」排序走索引。 --- ## 🚀 快速开始 ### 环境要求 - Node.js ≥ 18 - npm ≥ 9 - Windows / macOS / Linux 任一 ### 安装与启动 ```bash # 1. 安装依赖(better-sqlite3 需要原生编译,postinstall 会自动跑) npm install # 2. 启动开发模式(主进程 + 渲染端 HMR) npm run dev # 3. 类型检查(推荐每次提交前跑一次) npm run typecheck # 4. 打包成安装包 npm run build:win # Windows npm run build:mac # macOS npm run build:linux # Linux ``` > 💡 第一次启动时,主进程会自动执行 `runMigrations()` 创建表结构。开发模式下数据库位于 `src/db/notebook.db`,打包后在 `userData` 目录。 --- ## 🧪 关键代码示例 ### 1. 主进程注册一个路由(router.template.ts) ```typescript new EventRoute('createKnowledge', (payload: { name: string; description?: string }): RpcResponse => { try { if (!payload?.name?.trim()) return fail('知识库名称不能为空') const data = knowledgeRepo.create(payload) return ok(data, '知识库创建成功') } catch (e) { return fail(`知识库创建失败: ${(e as Error).message}`) } }), ``` ### 2. Repository 层处理 SQL 三值逻辑坑(document.repo.ts) ```typescript // knowledge_id = NULL 永远查不到行,必须用 IS NULL if (filter.knowledgeId !== undefined) { if (filter.knowledgeId === null) { conditions.push('knowledge_id IS NULL') } else { conditions.push('knowledge_id = ?') values.push(filter.knowledgeId) } } ``` ### 3. 渲染端统一 RPC 客户端(utils/ipc.ts) ```typescript const res = await api.createKnowledge({ name: '我的知识库' }) if (res.ok) { ElMessage.success('创建成功') } else { ElMessage.error(res.message) // 统一处理错误信息 } ``` ### 4. 自动保存节流(Edit.vue) ```typescript let saveTimer: ReturnType | null = null const handleChange = () => { if (saveTimer) clearTimeout(saveTimer) saveTimer = setTimeout(() => { if (valueHtml.value !== lastSavedContent) saveContent() }, 800) // 延迟 800ms 才真正发写入请求,防止频繁请求 } ``` --- ## 🛣 路线图 详细改进计划见 **[docs/ROADMAP.md](./docs/ROADMAP.md)**,包含优先级、背景、价值、参考实现、验收标准。 当前重点候选: - 🟡 P1 全文搜索(SQLite FTS5) - 🟡 P1 全局错误边界 - 🟢 P2 自动更新(electron-updater) - 🟢 P2 Web Worker 跑大文档解析 - 🟢 P2 主题切换 - 🔵 P3 多窗口 / 标签页 - 🔵 P3 数据导入导出 --- ## 📊 已知限制 - 暂未做全文搜索(只支持标题 LIKE) - 文档内容没有版本历史 - 没有多端同步(本地优先,刻意为之) - WAL 文件不会自动清理(长期使用需要手动 `VACUUM`) - `crud.ts` 的 `find/update/delete` 仍使用字段名拼接,**未做白名单校验**——目前所有调用方都是内部 repo,暂无注入风险,但建议加白名单 --- ## 📜 许可证 MIT License © 2026 --- ## 🙏 致谢 - [electron-vite](https://electron-vite.org) 提供的现代化模板 - [wangeditor](https://www.wangeditor.com) 富文本编辑器 - [better-sqlite3](https://github.com/WiseLibs/better-sqlite3) 同步 SQLite 客户端 - [Element Plus](https://element-plus.org) 组件库