Skip to content

笔记应用开发实践:从编辑器到可靠保存 ​

笔记应用最重要的承诺是:用户写下的内容能保存、能找回,切换页面和设备后也不会悄悄变回旧版本。

这个承诺贯穿整个系统。编辑器要区分当前输入和已保存快照,请求层要识别旧会话,数据库要拒绝过期写入,发布流程还要照顾一直没关闭的浏览器页面。

本系列来自一个 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数据加载、更新与会话重置
标题、正文、未保存父级选择编辑会话打开某一条内容到离开
未提交草稿本机存储按账号和内容范围保留
当前弹窗、侧栏、loadingPC UI 层当前页面或客户端实例
用户数据版本与删除状态服务端存储以事务确认的持久状态

当前编辑会话集中处理表单、加载、保存快照和草稿,页面保留“是否离开、是否覆盖、是否放弃”的用户决策。这样的分法比把一个大页面机械拆成十个文件更有意义。

Vue 官方把 composable 定义为封装和复用有状态逻辑的函数;生命周期副作用仍需有对应清理。把代码放进 composable,本身不会自动消除竞态。Vue Composables

相似界面不一定共用一种保存语义 ​

当前系统里有几类容易混淆的操作:

操作当前实现成功意味着什么
普通笔记编辑本地更新后进入自动同步链路服务端确认对应快照
知识库文档编辑手动保存,另有防抖本地草稿本次提交内容保存成功
看板内部卡片或列拖拽修改条目正文中的结构化内容仍需保存这条看板
知识库目录移动针对条目提交位置修改服务端确认目录变化
AI 生成先得到可预览结果还没有等同于采纳与保存

这也是为什么不能给所有模块简单套上一个“自动保存成功”的提示。用户看到的文案应该能回答:保存在这台设备,还是已经上传;保存了哪一版;是否仍有后续修改。

后端的公共入口负责装配,事务负责一致性 ​

当前后端将应用构造与启动入口拆开:前者组装中间件、路由和错误处理,后者负责环境加载、数据库初始化与监听。公共路由负责认证和输入边界,存储层组织数据库操作。

这个拆分有利于在受控环境构造应用,但不能把导入应用模块等同于完全隔离数据库与外部服务。自动化验证仍需确认依赖配置和注入方式,不能拿开发入口直接连到未知环境。

普通笔记采用账号级快照版本;知识库条目采用覆盖内容时间和目录位置的状态版本。两者解决的冲突粒度不同,详见同步与并发。

把失败路径当成正常产品流程 ​

这些实现里反复出现的模式,是在失败后保留用户继续操作所需的上下文:

  • 保存失败保留待提交内容,下一次能重试;
  • 读取响应过期时丢弃结果,避免回填别的文档;
  • 版本冲突带回当前服务端状态,让用户处理差异;
  • 已删除内容不被旧快照自动恢复,未同步修改可以另存副本;
  • 页面资源加载失败保留目标地址,让用户决定是否重新加载。

这不等于已经实现实时协作、任意离线操作或无损自动合并。当前模式主要解决单人多窗口、多设备和弱网下的可靠性;如果要加入多人字符级协作,需要另行设计操作合并、权限与历史模型。

开发一项功能时的检查顺序 ​

  1. 写出用户动作和期望状态,例如“移动目录后继续保存正文”。
  2. 画清它会改变表单、共享缓存还是服务端数据。
  3. 标明每一次异步操作开始时的账号、内容标识与版本。
  4. 规定失败后保留什么,用户如何重试或放弃。
  5. 在受控环境验证交错顺序,而不只检查一次成功请求。
  6. 最后核对发布与旧客户端兼容,避免代码正确但资源不可用。

下面从编辑会话与本地草稿开始,把“保存成功”拆成可以核对的状态变化。

为复用而记录,为理解而整理。