笔记应用开发实践:从编辑器到可靠保存
笔记应用最重要的承诺是:用户写下的内容能保存、能找回,切换页面和设备后也不会悄悄变回旧版本。
这个承诺贯穿整个系统。编辑器要区分当前输入和已保存快照,请求层要识别旧会话,数据库要拒绝过期写入,发布流程还要照顾一直没关闭的浏览器页面。
本系列来自一个 Vue 3 PC 客户端及其 Express、MySQL 服务端的本地源码梳理,整理日期为 2026-10-22。前端使用 TypeScript、Pinia、Vue Router、Vite 和 Tiptap,通过 pnpm workspace 复用数据层;后端独立维护,通过 HTTP 与客户端通信。
“当前实现”指本次所读源码能证明的行为,不代表已在生产环境验证。示例为重新编写的教学片段,目录、接口、数据与配置均做了通用化处理。验证清单是读者落地时的建议。
按遇到的问题阅读
| 问题 | 文章 | 核心经验 |
|---|---|---|
| 保存成功后,新输入却被标成已保存 | 编辑会话与本地草稿 | 确认的是一次提交快照,并非当前整个表单 |
| 多个窗口互相覆盖,删除的数据又回来 | 快照同步与并发冲突 | 事务锁、版本检查和删除状态各负其责 |
| 粘贴后结构变了,附件过几天打不开 | 富文本与附件生命周期 | 分清输入格式、持久格式、资源身份和临时地址 |
| AI 生成很久,取消或采纳后正文异常 | AI 编辑的可靠性边界 | 生成、预览、采纳、保存是不同阶段 |
| 发布后旧标签页点导航没反应 | 懒加载与发布恢复 | 先发布资源,再切入口,并提供有保护的恢复路径 |
先看本文的边界,再挑一条正在遇到的问题即可。
共享数据能力,把界面留给客户端
当前前端不是一个把所有逻辑塞进 PC 页面的项目。它把类型、请求、认证、普通笔记与知识库 store 放在共享包;PC 包负责页面、编辑器、浏览器交互和自己的 UI 状态。
text
PC 页面、组件与路由
│
编辑会话、离开保护、上传交互
│
共享 store、同步协调器、请求与会话
│ HTTP
路由与身份检查
│
业务存储、事务与版本检查
│
MySQL / 对象存储 / 模型服务共享包需要通知“同步失败”,但不该决定 PC 端显示哪种提示条。当前实现由客户端入口注入 loading 和同步状态回调,共享层只发通知。这让数据逻辑可以复用,也避免为了显示提示而反向依赖某个端的组件。
这个设计有一个适用前提:每个运行上下文只装配自己的一套处理器。如果以后需要同页多应用实例或并行隔离运行,建议把模块级回调改成实例依赖;本次源码不能据此宣称已经支持多实例隔离。
按状态的寿命划边界
“这个变量应该放组件还是 Pinia”可以换成更具体的问题:它什么时候产生,什么时候失效,谁有资格修改它?
| 状态 | 当前承担职责的位置 | 生命周期 |
|---|---|---|
| 当前账号与会话代次 | 认证与会话层 | 登录、退出、失效或换号 |
| 已确认的目录与条目 | 共享 store | 数据加载、更新与会话重置 |
| 标题、正文、未保存父级选择 | 编辑会话 | 打开某一条内容到离开 |
| 未提交草稿 | 本机存储 | 按账号和内容范围保留 |
| 当前弹窗、侧栏、loading | PC UI 层 | 当前页面或客户端实例 |
| 用户数据版本与删除状态 | 服务端存储 | 以事务确认的持久状态 |
当前编辑会话集中处理表单、加载、保存快照和草稿,页面保留“是否离开、是否覆盖、是否放弃”的用户决策。这样的分法比把一个大页面机械拆成十个文件更有意义。
Vue 官方把 composable 定义为封装和复用有状态逻辑的函数;生命周期副作用仍需有对应清理。把代码放进 composable,本身不会自动消除竞态。Vue Composables
相似界面不一定共用一种保存语义
当前系统里有几类容易混淆的操作:
| 操作 | 当前实现 | 成功意味着什么 |
|---|---|---|
| 普通笔记编辑 | 本地更新后进入自动同步链路 | 服务端确认对应快照 |
| 知识库文档编辑 | 手动保存,另有防抖本地草稿 | 本次提交内容保存成功 |
| 看板内部卡片或列拖拽 | 修改条目正文中的结构化内容 | 仍需保存这条看板 |
| 知识库目录移动 | 针对条目提交位置修改 | 服务端确认目录变化 |
| AI 生成 | 先得到可预览结果 | 还没有等同于采纳与保存 |
这也是为什么不能给所有模块简单套上一个“自动保存成功”的提示。用户看到的文案应该能回答:保存在这台设备,还是已经上传;保存了哪一版;是否仍有后续修改。
后端的公共入口负责装配,事务负责一致性
当前后端将应用构造与启动入口拆开:前者组装中间件、路由和错误处理,后者负责环境加载、数据库初始化与监听。公共路由负责认证和输入边界,存储层组织数据库操作。
这个拆分有利于在受控环境构造应用,但不能把导入应用模块等同于完全隔离数据库与外部服务。自动化验证仍需确认依赖配置和注入方式,不能拿开发入口直接连到未知环境。
普通笔记采用账号级快照版本;知识库条目采用覆盖内容时间和目录位置的状态版本。两者解决的冲突粒度不同,详见同步与并发。
把失败路径当成正常产品流程
这些实现里反复出现的模式,是在失败后保留用户继续操作所需的上下文:
- 保存失败保留待提交内容,下一次能重试;
- 读取响应过期时丢弃结果,避免回填别的文档;
- 版本冲突带回当前服务端状态,让用户处理差异;
- 已删除内容不被旧快照自动恢复,未同步修改可以另存副本;
- 页面资源加载失败保留目标地址,让用户决定是否重新加载。
这不等于已经实现实时协作、任意离线操作或无损自动合并。当前模式主要解决单人多窗口、多设备和弱网下的可靠性;如果要加入多人字符级协作,需要另行设计操作合并、权限与历史模型。
开发一项功能时的检查顺序
- 写出用户动作和期望状态,例如“移动目录后继续保存正文”。
- 画清它会改变表单、共享缓存还是服务端数据。
- 标明每一次异步操作开始时的账号、内容标识与版本。
- 规定失败后保留什么,用户如何重试或放弃。
- 在受控环境验证交错顺序,而不只检查一次成功请求。
- 最后核对发布与旧客户端兼容,避免代码正确但资源不可用。
下面从编辑会话与本地草稿开始,把“保存成功”拆成可以核对的状态变化。