# M7 展示层与内容接入从联调壳到真实页面：为什么先接稳结构再升级视觉

到了 `M7`，这个项目已经不缺“能跑起来的壳”了。

前面的里程碑已经逐步把工程、鉴权、内容、AI、导出和上线门禁立起来了，但仍然存在一个很明显的断层：

- `admin` 虽然能登录，却还不是真正可编辑的后台
- `web` 虽然能访问，却还只是粗粒度展示壳
- `server` 里的简历内容还停留在占位示例，而不是真实旧版简历
- `web / admin` 的展示层基础表达还各自分散，后续 UI 升级缺少清晰边界

换句话说，项目虽然已经“通了”，但还没有真正走进“这就是我的简历重构项目”的阶段。

所以 `M7` 要解决的，不是继续往上叠更多功能，而是把项目从联调验证壳推进到真实内容进入真实页面的状态。

这篇文章就围绕 `M7` 讲清楚三件事：

1. 为什么这一步要先收联调基线和真实内容流
2. 为什么要先把页面结构接稳，而不是直接做视觉重设计
3. 为什么共享展示边界应该先从 tokens 开始，而不是一上来做组件库

## 一、为什么 `M7` 是“从能跑到像样”的关键一步

`M6` 结束后，项目已经具备了很强的工程闭环能力：

- 有统一质量门禁
- 有 GitHub Actions
- 有最小上线路径

但从产品感受上看，它还缺少一个关键转折点：真实内容和真实页面还没有真正接在一起。

如果没有 `M7`，项目会长期处在一种尴尬状态里：

- 后台能登录，但不像一个真正能编辑内容的后台
- 公开站能打开，但看起来像演示页，不像真实简历
- 导出和发布链路已经存在，但消费的还是占位数据

对教程型项目来说，这种状态不够好。因为读者已经走过了很多工程和架构层面的内容，如果这时还看不到“真实页面”和“真实内容”落在一起，整个重构就会缺少很重要的产品感。

所以 `M7` 的价值，不在于它第一次让页面变好看，而在于它第一次让系统真正进入“真实内容被真实消费”的阶段。

## 二、`M7` 这轮到底要交付什么

这一轮的交付目标可以概括成一句话：

让读者能看到项目如何从联调验证壳出发，接通后台真草稿、迁移旧版真实简历内容、完成公开页模块化渲染，并顺手把共享展示边界收住。

这里面有五个关键边界。

### 1. 先把联调阶段的临时修正正式收束

前面为验证三端是否真的能跑通，我们已经做过一轮联调修正，比如统一端口、把后台发布按钮接到真实发布接口。

这些改动如果不单独收成正式 issue，就会留下一个问题：仓库虽然“已经变了”，但没人能清楚解释它为什么这样变。

所以 `M7` 的第一步，其实是把这些联调阶段的关键修正正式化，让后续真实页面工作有一个稳定起点。

### 2. 后台先进入真草稿读取与保存

后台要想从演示壳走向真实后台，第一步不该是做一个巨大的信息架构，而是先把最小草稿编辑态打通。

这一步的核心价值在于正式立住两条语义：

- 后台编辑的是草稿
- 公开站读取的是已发布内容

也就是说，“保存”与“发布”必须继续是两个分开的动作。

### 3. 旧版简历内容必须迁移进标准模型

如果 `server` 里继续只有示例简历，`web`、`admin`、`export` 再怎么升级，也仍然只是围绕占位数据打转。

所以 `M7` 很关键的一步，是把旧版 Vue 简历中的真实内容迁移到新的 `StandardResume` 双语模型里。

而且这里最重要的不是“复制旧页面”，而是做结构映射：

- 旧版是页面组织方式
- 新版是领域数据模型

这一步做对了，后面所有页面与导出都能围绕同一份真实数据继续长。

### 4. 公开页先模块化，再重设计

当真实内容已经进入系统后，公开页的下一个问题不是颜色、字体和视觉风格，而是结构能不能稳定维护。

如果页面还停留在一个粗粒度大组件里，后面每次 UI 升级都会很痛苦。所以 `M7` 选择先把页面按 `profile / experiences / projects / education / skills / highlights` 模块化。

这样后续不管做主题升级、模板扩展还是视觉重设计，都有了清晰边界。

### 5. 共享展示层先抽 tokens，不急着抽组件

当 `web` 和 `admin` 都开始走向真实页面后，重复的展示表达会越来越多，比如字体、背景、卡片、弱文案、阴影等。

这时确实该开始收共享层，但 `M7` 的判断是：

- 值得先抽的是基础 tokens 和基础样式
- 暂时不值得先抽的是业务层 React 组件

因为当前两端的页面职责差异还很大，过早组件化只会把共享边界抽糊。

## 三、`M7` 为什么要按这 5 个 issue 来拆

这一轮的拆法，其实是在回答一个很典型的产品推进问题：

当工程边界已经稳住之后，怎样把项目从“联调壳”推进到“真实页面”，又不一下子冲进大规模视觉重构？

`M7` 的答案是：

先收联调基线，再收真实后台入口，再收真实数据，再收页面结构，最后再收共享展示边界。

### issue-30：先收联调基线

第一步不是做新页面，而是把之前为了联调做的关键修正正式化：

- 统一端口
- 真发布按钮
- 文档、环境变量和测试同步

这一步看起来小，但它决定了后面所有展示层任务都站在什么基线之上继续长。

### issue-31：后台先读写真草稿

后台从演示壳进入真实内容流，第一步不是做完整后台，而是先能读写最小 profile 草稿。

这样读者第一次能看到：

- 后台真的在改真实数据
- 保存和发布不是一回事
- 后台开始有“内容入口”的意义

### issue-32：把旧版真实简历迁进标准模型

这一步的关键不是“导入旧内容”，而是把旧版 Vue 页面里的真实信息重新组织成新的标准模型。

它解决的是：

- 三端终于围绕真实简历内容工作
- 导出和页面开始真正面向“个人简历项目”而不是示例数据

### issue-33：公开页模块化渲染

当真实内容进入系统后，公开页就不能再停留在一个大壳组件里。

这一步通过模块化 section，把页面结构正式和领域内容模块对齐，为后续视觉升级留下足够清晰的边界。

### issue-34：展示层整理与共享 tokens

最后才轮到共享展示边界。

这一步不是组件库建设，而是识别重复、语义稳定的基础表达，先把 `web / admin` 共同的展示 tokens 收到 `packages/ui`。

这让项目第一次拥有了“可继续演进的共享展示入口”，但又没有过早把业务组件抽乱。

## 四、这一轮最重要的设计取舍

### 1. 为什么先接内容，再谈美化

很多项目一进入页面阶段，就会自然想先做漂亮的界面。但 `M7` 刻意没有这么做。

因为当前真正更重要的是：

- 真实内容有没有进入系统
- `admin / web / export` 是否开始围绕同一份数据工作
- 页面结构和数据结构有没有对齐

如果这些还没稳住，先做美化只会让后续每次改版都牵动更多结构层问题。

### 2. 为什么先模块化，再做视觉升级

模块化不是视觉结果，而是演进条件。

先模块化的价值在于：

- 页面结构能被测试稳定感知
- 领域内容与展示 section 开始一一对应
- 后续主题、模板、重设计都能控制改动范围

这比直接跳进“做一版更漂亮页面”更适合教程型节奏。

### 3. 为什么旧版内容迁移要映射结构，而不是照搬页面

旧版 Vue 简历是页面组织方式，不是新的领域模型。

如果简单照搬，新的 `StandardResume` 很快就会被旧页面结构牵着走。这样后面多语言、导出、AI 和后台编辑都会越来越难统一。

所以 `M7` 很重要的一条判断是：

- 从旧页面内容反推新模型
- 用新模型承载旧内容

而不是让新模型迁就旧页面。

### 4. 为什么共享层先抽 tokens，不先抽组件库

这是 `M7` 最关键的抽象时机判断之一。

当前 `web` 和 `admin` 虽然都在使用卡片、文案、背景、阴影等基础表达，但它们的业务语义差异仍然很大：

- `web` 偏公开展示
- `admin` 偏后台编辑和操作

所以现在最适合共享的是：

- tokens
- 基础样式
- 稳定、低风险、重复出现的视觉基础表达

而不是把业务组件也一口气拉进共享包。

## 五、关键文件应该怎么读

如果你想顺着代码和文档复盘 `M7`，建议优先看这几组材料。

### 联调收束与真实发布入口

- `apps/admin/lib/auth-api.ts`
- `apps/admin/components/admin-dashboard-shell.tsx`
- `apps/web/lib/env.ts`
- `apps/admin/lib/env.ts`

这里解决的是“联调阶段的临时修正如何正式成为后续基线”。

### 真草稿读取与保存

- `apps/admin/lib/resume-draft-api.ts`
- `apps/admin/components/resume-draft-editor-panel.tsx`

这里解决的是“后台如何首次进入真实草稿编辑态”。

### 真实内容迁移

- `apps/server/src/modules/resume/domain/standard-resume.ts`

这里解决的是“旧版真实简历内容如何进入标准模型”。

### 公开页模块化

- `apps/web/components/published-resume-shell.tsx`
- `apps/web/components/published-resume/`

这里解决的是“公开站如何从粗粒度壳演进到稳定模块结构”。

### 共享展示边界

- `packages/ui/src/display.css`
- `apps/web/app/globals.css`
- `apps/admin/app/globals.css`

这里解决的是“什么值得先进入共享层，什么还不该抽得太早”。

## 六、`M7` 的测试重点是什么

`M7` 的测试重点不是“页面是不是已经很高级”，而是内容流和页面结构有没有被稳定接上。

### 1. 先锁联调基线

这里至少要证明：

- 默认端口常量一致
- 后台发布按钮真的指向真实发布接口
- 相关测试断言和文档示例没有继续漂移

### 2. 再锁后台草稿编辑态

这一层要证明：

- 后台能读取草稿
- 后台能保存草稿
- Bearer Token 会被正确带上
- `viewer` 不会进入真实编辑流

### 3. 再锁真实内容迁移后的导出与模型合法性

这一层要证明：

- 标准模型仍然合法
- Markdown / PDF 导出仍然可用
- 测试不再依赖旧示例内容

### 4. 最后锁公开页模块化与共享样式引入后的稳定性

这一层要证明：

- 各 section 能正常渲染
- `zh / en` 与 `light / dark` 仍然成立
- 共享展示 tokens 引入后，现有构建与测试不被破坏

## 七、这一轮最容易踩的坑

### 1. 把联调修正留在“临时状态”

这样后面所有人都会享受联调带来的结果，却说不清这些基线为什么成立。

### 2. 后台保存草稿和公开发布语义混在一起

这会直接破坏 `M3` 已经建立起来的草稿 / 已发布边界。

### 3. 旧版内容迁移时照搬旧页面结构

这样会让新的领域模型失去独立性，后续导出、AI 和后台编辑都会越来越难统一。

### 4. 一看到重复就立刻做组件库

这往往会在共享边界还不稳的时候，把差异也一起抽糊，后续反而更难维护。

## 八、为什么 `M7` 完成后，项目会第一次有明显的“产品感”

前面的里程碑已经把工程和功能边界收得很稳了。

而 `M7` 做的事情，是让这些边界第一次真正落在“真实内容”和“真实页面”上。

这意味着项目终于不再只是：

- 有功能
- 有接口
- 有导出

而开始变成：

- 后台能编辑真实草稿
- 公开站能展示完整真实简历
- 导出消费真实内容
- 展示层也有了可继续演进的共享入口

这一步带来的，不只是页面更完整，而是整个项目第一次有了很强的产品实感。

## 九、如果你要自己复现 `M7`，建议按这个顺序

1. 先收联调基线，让三端约定稳定下来
2. 再让后台进入真草稿读写
3. 再把旧版真实简历迁进标准模型
4. 再把公开页拆成稳定模块
5. 最后再收共享展示 tokens

这个顺序的好处是，项目每往前走一步，都在先稳住一层更基础的东西：

- 联调基线
- 真内容入口
- 真数据来源
- 真页面结构
- 共享展示基础

这样不会一开始就把视觉升级和共享抽象全部压到同一轮里。

## 十、这篇文章对应的原始材料

如果你想继续往下看每个 issue 的实现细节，可以配合这些开发日志一起阅读：

- `docs/30-开发日志/M7-issue-30-联调收束-统一端口与真实发布按钮.md`
- `docs/30-开发日志/M7-issue-31-admin-真草稿读取与保存.md`
- `docs/30-开发日志/M7-issue-32-旧版简历内容迁移到标准模型.md`
- `docs/30-开发日志/M7-issue-33-web-公开简历页面模块化渲染.md`
- `docs/30-开发日志/M7-issue-34-展示层整理为后续-UI-升级预留边界.md`
- `docs/40-教程与博客/M7-展示层与内容接入从联调壳到真实页面-教程大纲.md`

这些记录更适合看“每一步具体做了什么”，而本文更希望回答“为什么 `M7` 必须先接稳结构，再进入视觉升级”。

## 小结

`M7` 做的不是一轮大改版，而是先把展示层与内容接入最关键的五个边界收住：

- 联调基线正式化
- 后台真草稿入口
- 旧版真实内容进入标准模型
- 公开页模块化渲染
- 共享展示 tokens 的最小边界

这些边界一旦成立，后面的持久化、共享契约、主题升级、视觉精修和更成熟的 UI 系统，都会更像是在已有结构之上继续增强，而不是重新拆一遍页面和数据层。
