状态:设计中(未实现)。本文记录 backoffice 邮件模板页(
/email/template/index)“保存草稿 / 保存并生效”的需求讨论与已定方向,供后续落地对齐。 交互原型:doc/prototypes/marketing/email-template-prototype.html(已按本方案改造,可交互预览)。
1. 背景与问题
现状(单记录模型):每条邮件模板一条 EmailTemplate 记录。
- 「保存草稿」= 写这条记录、不改
enabled; - 「保存并生效」= 写这条记录 +
enabled = true。
二者操作的是同一条记录,唯一差别是 enabled 标志。带来两个问题:
-
对”已启用”的模板点「保存草稿」会立即改动线上真正发出去的内容——“草稿”名不副实,没有”改到一半不影响线上”的安全感。
-
曾设想复用
enabled当”草稿 / 线上”开关(下称 A’),但这会把两个正交概念压成一个标志:- 启用 / 禁用——这类邮件到底发不发(业务开关);
- 草稿 / 已发布——当前内容是编辑中的草稿,还是已发布的线上版。
商户”禁用某类邮件”和”改了但还没发布”是两回事,混在一个标志里必然混淆。故 A’ 否决。
2. 核心模型(方案 B:草稿 / 线上分离)
两个维度相互独立、互不影响:
| 维度 | 含义 | 存储 |
|---|---|---|
| 启用 / 禁用 | 这类邮件发 / 不发(业务开关) | enabled(bool,保持不变) |
| 草稿 / 已发布 | 内容版本 | 独立的草稿列 |
数据模型(同表加草稿列,不新建表):EmailTemplate 上把”内容”分成两套列——
- 线上(已发布)列:
subject/content(htmlContent) /senderName/senderEmail… —— 发送时只读这套。 - 草稿列:
draftSubject/draftContent/draftHtmlContent/draftSenderName/draftSenderEmail+draftUpdatedAt(有值即表示”有未发布草稿”)。 enabled原样保留,只表示”这类邮件启不启用”,与草稿无关。
选”镜像列”而非独立草稿表:模板是 8 类固定小集合,镜像列省一张表和一个 join。
发送邮件:始终读线上列。(只要保证”发布”才写线上列,发送链路无需改动。)
3. 交互
| 操作 | 行为 |
|---|---|
| 打开编辑 | 有草稿 → 载入草稿列 + 顶部横幅”正在编辑未发布草稿(保存于 {draftUpdatedAt}),线上版本不受影响”;无草稿 → 载入线上版起手(此时草稿副本 = 线上) |
| 预览 | 预览面板加「草稿 / 线上版」切换:草稿 = 当前编辑内容;线上版 = 已发布内容(客户实际收到的),并提示”编辑不影响它,除非点保存并生效”。用于回答”编辑看草稿时如何看原本内容” |
| 保存草稿 | 只写 draft* 列 + draftUpdatedAt;不动线上列、不动 enabled |
| 保存并生效 | 把 draft* 覆盖到线上列(= 发布),清空草稿标记 |
| 放弃草稿(可选) | 清 draft*,下次以线上版起手 |
| 列表 / 卡片 | 启用 / 禁用 Tag 之外,对有草稿的模板加独立的「草稿」徽标(两者并存不混淆) |
四种状态组合(禁用与草稿可并存,证明两维度不混淆):启用+草稿 / 启用无草稿 / 禁用+草稿 / 禁用无草稿。
4. 待决策(open questions)
Q1 草稿门控范围:哪些字段进草稿?
- A(推荐):模板名称 / 描述即时更新(后台标签,客户看不到,改了不影响任何一封邮件);主题 / 正文 / 发件人走草稿。
- 好处:改名不用”发布”;列表卡片显示的名字始终最新(不涉及”草稿名 vs 线上名”的显示难题)。
- B:名称、描述、主题、正文全部草稿化,“保存草稿”一律不动线上,“保存并生效”才整体发布。
- 好处:语义最一致(“草稿 = 我未发布的全部改动”)。
- 代价:改名也要发布;列表要决定显示”线上名”还是”草稿名”,更复杂。
建议 A。 原则:草稿门控的是”收件人能看到的内容”;后台元数据即时生效。
Q2 测试发送发哪个版本?
- 选项:线上版(客户实际收到的)/ 草稿版(未发布)/ 有草稿时让用户选。
- 建议:测试弹窗内提供「线上版 / 草稿版」选择(仅有草稿时出现),默认线上版(“客户收到的”规范测试),可切到草稿版验证未发布改动。
Q3 「保存并生效」是否顺带启用(enabled=true)?
- 建议:生效只发布内容,不改启用开关(保持正交);如确需”生效即上线发送”,另加”保存并启用”。
5. 落地影响范围(实现时)
- 实体:
src/common/data/shared-entities(EmailTemplate)加draft*列 +draftUpdatedAt。 - 映射:
src/common/data/portal/mappings.ts(email_template表)同步加列。 - 控制器:
src/backoffice/controllers/email/email-template.controller.tssave写草稿;publish发布(草稿覆盖线上 + 清草稿);discard放弃草稿;list/item读线上列 + 返回”有草稿”标记。
- 前端:
src/backoffice/public/pages/email/template/index.tsx- 三按钮(保存草稿 / 保存并生效 / 放弃草稿)、卡片草稿徽标、编辑横幅、预览「草稿 / 线上版」切换、测试弹窗版本选择。
- 发送链路:
renderEmail读线上列(不变,只要保证仅”发布”写线上列)。
6. 关联
- 原型:
doc/prototypes/marketing/email-template-prototype.html - 实体 / 映射 / 控制器 / 前端:见上文路径
- 数据访问约定:
data-context-lifecycleskill(上下文经装饰器取得、响应结束统一 close)