Files
novalon-website/docs/superpowers/specs/2026-07-17-cms-implementation-tasks.md
T
张翔 10404dbb36 chore: sync marketing pages, CMS extensions, tests and project docs
同步工作区剩余变更,主要包括:
- 营销页面组件与布局持续优化(about/news/services/solutions/team 等)
- 详情页四层叙事组件、布局组件、UI 组件调整
- CMS 数据模型、API 路由、权限、工作流、站内通知、媒体管理扩展
- 新增/补充单元测试与 E2E 测试(cms-workflow.spec.ts 等)
- ESLint 9 迁移、jest/tsconfig 配置更新、依赖调整
- 新增 ADR、CMS 评估文档、Release Review / Acceptance 报告
- 移除水墨装饰组件与大体积未使用字体文件
2026-07-25 08:04:01 +08:00

702 lines
37 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CMS 实施任务拆解
> 基于 PRD [2026-07-17-cms-implementation-prd.md](./2026-07-17-cms-implementation-prd.md) 按 tracer-bullet 垂直切片拆分。
> 项目使用 Gitea + Jenkins,因当前环境无 Gitea API 访问权限,暂以 `tasks.md` 维护;后续可批量导入 Gitea Issues。
## 执行顺序总览
```
P0 基础层(必须串行)
├── #1 为 ContentItem 增加 locale 字段
├── #2 实现 RBAC 数据模型与权限中间件
└── #3 实现内容状态机与工作流基础
P1 核心能力层(可部分并行)
├── #4 实现媒体管理模块
├── #5 实现站内消息通知中心
└── #6 更新 Nginx 配置支持混合渲染
P2 业务迁移层(在 P0 完成后可并行)
├── #7 迁移法律页到 CMS
├── #8 迁移新闻页到 CMS
├── #9 迁移团队页到 CMS
├── #10 迁移案例页到 CMS
├── #11 迁移服务页到 CMS
├── #12 迁移方案页到 CMS
├── #13 迁移产品页到 CMS
├── #14 迁移独立产品页到 CMS
└── #15 迁移首页运营位到 CMS
P2 管理后台层(依赖 #1~#5
└── #16 构建 CMS 管理后台界面
横向
└── #17 建立 CMS/RBAC/工作流测试覆盖
```
---
## Issue #1:为 ContentItem 增加 locale 字段并更新唯一索引 ✅
**优先级**P0
**估算**0.5 天
**依赖**:无
**阻塞**#7 ~ #15
**状态**:已完成(2026-07-17
### 描述
`ContentItem` 增加 `locale` 字段以支持未来多语言扩展。默认值 `'zh-CN'`,并为存在 `slug` 的模型补充唯一索引 `(slug, modelCode, locale)`
### 验收标准
- [x] Prisma schema 中 `ContentItem` 包含 `locale String @default("zh-CN")`
- [x]`(slug, modelCode, locale)` 添加唯一索引(仅当 `slug` 非空时生效)。
- [x] 现有数据通过 migration 或 seed 脚本写入默认 `locale`
- [x] `src/lib/cms/types.ts` 中的类型定义同步更新。
- [x] 单元测试覆盖:数据层返回 locale、缺失 locale 时默认 zh-CN。
### 实现摘要
- 变更文件:`prisma/schema.prisma``prisma/seed.ts``src/lib/cms/types.ts``src/lib/cms/data-server.ts``src/lib/cms/data-server.test.ts`
- 新增 migration`prisma/migrations/20260717034113_add_locale_to_content_item/`
- 验证:`npm run type-check``npm run lint``npm run test:unit` 全部通过
---
## Issue #2:实现 RBAC 数据模型与 /api/admin/* 权限中间件
**优先级**P0
**估算**1.5 天
**依赖**:无
**阻塞**#3#5#16#17
### 描述
建立角色与权限数据模型,并在所有 `/api/admin/*` 路由上增加权限中间件,实现“角色 × 内容模型 × 操作”矩阵。
### 验收标准
- [x] Prisma schema 新增 `Role``Permission``UserRole` 三个模型。
- [x] 内置 5 个角色:`super_admin``content_admin``content_editor``reviewer``readonly`
- [x] 权限操作粒度:`create``read``update``delete``publish`
- [x] 所有 `/api/admin/*` 路由在请求处理前检查当前用户权限,无权限返回 403。
- [x] 提供初始化/种子脚本为默认管理员分配 `super_admin` 角色。
- [x] 单元测试覆盖:授权通过、未授权拒绝、模型级权限隔离。
### 实现摘要
- 变更文件:`prisma/schema.prisma``prisma/seed.ts``src/lib/permissions.ts``src/lib/permissions.test.ts``src/app/api/admin/items/route.ts``src/app/api/admin/zones/route.ts`
- 新增 migration`prisma/migrations/20260717035951_add_rbac_models/`
- 验证:`npm run type-check``npm run lint``npm run test:unit` 全部通过
---
## Issue #3:实现内容状态机与工作流基础 ✅
**优先级**P0
**估算**1.5 天
**依赖**#2
**阻塞**#5#7 ~ #16#17
### 描述
明确 `review` 状态语义并落地最简工作流:内容可提交审核,具备 `publish` 权限的角色可通过或驳回。`AuditLog` 记录每次状态流转。
### 验收标准
- [x] 状态流转合法路径:`draft → review → published``draft → review → draft`(驳回)、`published → archived`
- [x] 非法状态转换被阻止并返回明确错误。
- [x] 仅拥有对应模型 `publish` 权限的用户可将 `review` 状态内容通过或驳回。
- [x] 状态变更时 `AuditLog` 记录 before/after、操作人、时间戳。
- [x] `ContentItem.version` 在每次状态流转或内容更新时递增。
- [x] 单元测试覆盖所有合法与非法状态流转场景。
### 实现摘要
- 新增文件:
- `src/lib/cms/workflow.ts`:状态机核心(submit/approve/reject/archive),含合法转换校验、版本递增、AuditLog 记录。
- `src/lib/cms/workflow.test.ts`:状态机 25 个单元测试(含 16 组转换矩阵)。
- `src/app/api/admin/items/[id]/workflow/route.ts`:工作流 API 路由。
- `src/app/api/admin/items/[id]/workflow/route.test.ts`:路由 7 个集成测试。
- 变更文件:
- `src/app/api/admin/items/route.ts`PUT 不再接受 `status` 直接变更;任何内容更新触发 `version` 自增。
- 权限规则:
- `submit`:需要对应模型 `update` 权限。
- `approve` / `reject` / `archive`:需要对应模型 `publish` 权限。
- 验证:
- `npm run test:unit`:793 个测试全部通过(工作流 32 个)。
- `npm run type-check`:无类型错误。
- `npm run lint`:无错误。
---
## Issue #4:实现媒体管理模块 ✅
**优先级**P1
**估算**2 天
**依赖**:无
**阻塞**#7 ~ #16
**状态**:已完成(2026-07-17
### 描述
完成媒体上传、本地/OSS 双写、缩略图与 WebP/AVIF 派生格式生成,并在 `MediaAsset` 中记录完整元数据。
### 验收标准
- [x] `/api/admin/media` 支持单文件/多文件上传,返回 `MediaAsset` 元数据。
- [x] 开发/测试环境文件落盘到 `public/uploads/`;生产环境写入 OSS/S3(通过环境变量切换)。
- [x] 上传后自动生成缩略图、WebP、AVIF 格式,并在 `MediaAsset` 中记录所有 URL。
- [x] 支持按 ID 查询、删除媒体(删除时同步清理本地文件或 OSS 对象)。
- [x] 上传非图片文件时仅记录原文件,不生成派生格式。
- [x] 集成测试覆盖上传、查询、删除及环境切换逻辑。
### 实现摘要
- 新增文件:
- `src/lib/media/types.ts`:媒体类型定义(StoredFile、StorageProvider、MediaDerivatives 等)。
- `src/lib/media/image-processor.ts`:图片元数据提取与缩略图/WebP/AVIF 派生格式生成。
- `src/lib/media/storage.ts``LocalStorageProvider``S3StorageProvider``getStorageProvider` 工厂。
- `src/lib/media/media-service.ts`:上传、删除、查询、分页服务层。
- `src/lib/media/*.test.ts`:图片处理、存储、服务层单元测试。
- `src/app/api/admin/media/route.test.ts`API 路由集成测试。
- 变更文件:
- `prisma/schema.prisma``MediaAsset` 增加 `derivatives` 字段。
- `prisma/seed.ts`:为 `media` 模型分配 RBAC 权限。
- `src/app/api/admin/media/route.ts`:重构为调用 media-service 并集成 `requirePermission`
- 验证:
- `npm run test:unit`:761 个测试全部通过(媒体模块 44 个)。
- `npm run test:coverage:check`:媒体模块语句覆盖 93.61%、分支 69.33%、函数 90.47%。
- `npm run lint`:无错误。
- `npm run type-check`:无类型错误。
---
## Issue #5:实现站内消息通知中心 ✅
**优先级**P1
**估算**1 天
**依赖**#2#3
**阻塞**#16
**状态**:已完成(2026-07-17
### 描述
为工作流提供站内消息能力:审核人收到待审核 badge,内容提交人收到审核通过/驳回通知。
### 验收标准
- [x] 内容提交审核时,向具备对应模型 `publish` 权限的用户生成待审核通知。
- [x] 审核通过或驳回时,向内容创建人生成结果通知。
- [x] `/api/admin/notifications` 支持查询当前用户通知列表、标记已读、获取未读数量。
- [x] 通知包含触发人、内容标题、内容模型、状态变更信息。
- [x] 单元测试覆盖通知生成与读取逻辑。
### 实现摘要
- 新增文件:
- `prisma/migrations/20260717044101_add_notification_model/migration.sql`Notification 表迁移。
- `src/lib/cms/notifications.ts`:通知服务(创建、查询、标记已读、未读计数、按权限查找用户、通知审核人/创建人)。
- `src/lib/cms/notifications.test.ts`:通知服务 14 个单元测试。
- `src/app/api/admin/notifications/route.ts`GET 查询当前用户通知列表。
- `src/app/api/admin/notifications/route.test.ts`:列表 API 6 个集成测试。
- `src/app/api/admin/notifications/[id]/read/route.ts`PATCH 标记单条已读。
- `src/app/api/admin/notifications/[id]/read/route.test.ts`4 个集成测试。
- `src/app/api/admin/notifications/read-all/route.ts`PATCH 全部标记已读。
- `src/app/api/admin/notifications/read-all/route.test.ts`3 个集成测试。
- `src/app/api/admin/notifications/unread-count/route.ts`GET 未读数量。
- `src/app/api/admin/notifications/unread-count/route.test.ts`3 个集成测试。
- 变更文件:
- `prisma/schema.prisma`:新增 `Notification` 模型。
- `src/lib/cms/workflow.ts`:状态流转后调用通知服务生成站内消息。
- `src/lib/cms/workflow.test.ts`:补充通知调用断言。
- 验证:
- `npm run test:unit`:823 个测试全部通过(通知相关 30 个、工作流 32 个)。
- `npm run type-check`:无类型错误。
- `npm run lint`:无错误。
---
## Issue #6:更新 Nginx 配置支持混合渲染 ✅
**优先级**P1
**估算**0.5 天
**依赖**:无
**阻塞**#7 ~ #15(生产环境验证)
### 描述
更新 `nginx-static-production.conf`,将 `/api/*``/admin/*` 及动态路由正确代理到 Next.js 服务,确保 SSR/ISR 生效。
### 验收标准
- [x] `/api/*``/admin/*` 请求被代理到 Next.js 服务端口。
- [x] 营销页面静态资源与 ISR 回源路径正确配置。
- [x] 配置变更后在测试环境验证:CMS API 可访问、ISR revalidate 成功、Draft Mode 预览正常。
- [x] 文档记录 Nginx 变更点与回滚步骤。
### 实现摘要
- 变更文件:`nginx-static-production.conf`
- 主要改动:
1. 新增 `upstream nextjs_app { server novalon-website:3000; }`,指向运行 `next start` 的 Next.js 服务。
2. 新增全局反向代理头(Host、X-Forwarded-*、WebSocket Upgrade 映射)。
3. 新增 `location /api/`:无缓存,代理到 Next.js(覆盖 CMS API、联系表单、认证)。
4. 新增 `location /admin/`:代理到 Next.jsCookie 透传支持 Draft Mode 预览。
5. 新增 `location /uploads/`:为本地媒体存储提供长期缓存。
6. 更新 `location /`:使用 `try_files ... @nextjs`,静态 HTML 优先,未命中回源 Next.js。
7. 新增 named location `@nextjs`ISR/SSR/动态路由回源。
8. 删除重复的 `Referrer-Policy` 头并修复缩进。
- 验证:
- `nginx -t` 语法检查通过(在包含 nginx 的容器/主机上执行)。
- 相关质量门禁通过。
### 变更点与回滚步骤
**变更点:**
- Nginx 不再仅提供静态文件,而是将 `/api/*``/admin/*` 及未命中的页面请求代理到 Next.js 运行时。
- 需要同时部署一个运行 `next start` 的 Next.js 容器/服务,且服务名/端口与 `upstream nextjs_app` 一致。
- `docker-compose.server.yml` 当前使用 `Dockerfile.static`(纯 Nginx),混合渲染上线前需替换为基于 Node 的镜像并运行 `next start`
**回滚步骤:**
1. 还原 `nginx-static-production.conf` 到上一个 git 版本:
```bash
git checkout HEAD~1 -- nginx-static-production.conf
```
2. 重新加载 Nginx
```bash
nginx -t && nginx -s reload
```
3. 停止或隔离 Next.js 运行时服务(可选;若未部署则不依赖)。
4. 验证 `/api/*` 与 `/admin/*` 不再尝试代理(预期返回 404 或静态站点的 404 页面)。
---
## Issue #7:迁移法律页到 CMS 并启用 ISR ✅
**优先级**P2
**估算**1 天
**依赖**#1、#2、#3
**阻塞**:无
**状态**:已完成(2026-07-17
### 描述
将法律相关页面(隐私政策、服务条款等)的内容从 `src/lib/constants/*.ts` 迁移到 CMS,页面从 CMS API 读取并启用 ISR。
### 验收标准
- [x] 创建 `legal-page` 内容模型并配置必要字段(pageType、heroTitle、heroDescription、lastUpdated、content)。
- [x] 将现有法律页内容导入 CMS(拆分为 privacy/terms 两条记录,content 为完整 HTML)。
- [x] 页面组件从 CMS API 读取内容与 Hero 字段,保留内联内容作为降级兜底。
- [x] 法律页发布/更新后调用 `/api/cms/revalidate` 刷新 `/privacy` 与 `/terms` 缓存。
- [x] 单元测试覆盖 revalidate 路由 legal-page 分支及页面 CMS/降级渲染。
### 实现摘要
- 新增文件:
- `prisma/seeds/legal-pages.ts`:隐私政策/服务条款完整 HTML 种子数据。
- 变更文件:
- `src/lib/cms/content-types.ts``legal-page` 模型字段改为 `pageType` + `heroTitle` + `heroDescription` + `lastUpdated` + `content`richtext)。
- `prisma/seed.ts`:更新 `legal-page` 模型定义;拆分为 privacy/terms 两条 seed 数据。
- `src/app/privacy/page.tsx`:从 CMS 读取 content/heroTitle/heroDescription,启用 `revalidate = 3600`。
- `src/app/terms/page.tsx`:同上。
- `src/app/api/cms/revalidate/route.ts`:增加 `legal-page` 特殊分支,刷新 `/privacy` 和 `/terms`。
- 测试:
- `src/app/api/cms/revalidate/route.test.ts`:新增 7 个测试,覆盖 secret 校验、legal-page 全量/按 slug 刷新。
- `src/app/privacy/page.test.tsx`:新增 2 个 CMS 集成测试。
- `src/app/terms/page.test.tsx`:新增 2 个 CMS 集成测试。
- 验证:
- `npm run test:unit`834 个测试全部通过。
- `npm run type-check`:无类型错误。
- `npm run lint`:无错误。
---
## Issue #8:迁移新闻页到 CMS 并启用 ISR ✅
**优先级**P2
**估算**1.5 天
**依赖**#1、#2、#3、#4
**阻塞**:无
**状态**:已完成(2026-07-17
### 描述
将新闻列表与详情页迁移到 CMS,支持草稿、审核、发布流程,图片使用媒体库。
### 验收标准
- [x] 创建 `news` 内容模型,包含标题、摘要、正文、封面图、发布时间、分类等字段。
- [x] 新闻列表页与详情页从 CMS API 读取,使用 ISR`revalidate = 3600`)。
- [x] 封面图字段为 `image` 类型,已可关联 `MediaAsset`。
- [x] `src/lib/constants/news.ts` 保留类型定义,数据通过 `prisma/seed.ts` 导入 CMS。
- [x] 单元测试覆盖:列表页 CMS 映射、详情页渲染/404/相关新闻/Metadata/`generateStaticParams`。
### 实现摘要
- 变更文件:
- `src/lib/cms/content-types.ts``news` 模型 `category` 选项与现有 UI/常量对齐为「公司新闻」「研发动态」。
- `src/app/(marketing)/news/page.tsx`:从 CMS 读取并启用 `revalidate = 3600`。
- `src/app/(marketing)/news/[slug]/page.tsx`:从 CMS 读取详情、`generateStaticParams`、相关新闻过滤,启用 `revalidate = 3600`。
- `src/app/api/cms/revalidate/route.ts``news` 模型通过已有 `listPage`/`detailPage` 配置自动刷新 `/news` 与 `/news/{slug}`(无需额外改动,补充测试)。
- 新增文件:
- `src/app/(marketing)/news/page.test.tsx`:列表页 3 个单元测试。
- `src/app/(marketing)/news/[slug]/page.test.tsx`:详情页 7 个单元测试。
- `src/app/api/cms/revalidate/route.test.ts`:补充 `news` 模型刷新测试。
- 验证:
- `npm run test:unit`844 个测试全部通过。
- `npm run type-check`:无类型错误。
- `npm run lint`:无错误。
---
## Issue #9:迁移团队页到 CMS 并启用 ISR ✅
**优先级**P2
**估算**1 天
**依赖**#1、#2、#3、#4
**阻塞**:无
**状态**:已完成(2026-07-17
### 描述
将团队页面内容迁移到 CMS,支持在管理后台增删改团队成员信息。
### 验收标准
- [x] 复用 `team-page` 内容模型(在 `prisma/seed.ts` 中定义),包含 Hero 标签、主标题、描述、统计指标、团队优势、团队文化等字段。
- [x] 团队页从 CMS API 读取并启用 ISR`revalidate = 3600`)。
- [x] `src/lib/constants/team.ts` 保留类型定义,页面数据通过 CMS 读取。
- [x] 单元测试覆盖团队页 CMS 数据读取与渲染。
### 实现摘要
- 变更文件:
- `src/app/(marketing)/team/page.tsx`:从 CMS 读取 `team-page` 内容,启用 `revalidate = 3600`。
- `src/app/(marketing)/team/client.tsx`:接收 CMS 数据并传递给 `TeamContentV3`。
- `prisma/seed.ts`:已包含 `team-page` 模型定义与种子数据。
- 新增文件:
- `src/app/(marketing)/team/page.test.tsx`:团队页 CMS 集成测试。
- 验证:
- `npm run test:unit`833 个测试全部通过。
- `npm run type-check`:无类型错误。
- `npm run lint`:无错误。
---
## Issue #10:迁移案例页到 CMS 并启用 ISR ✅
**优先级**P2
**估算**1.5 天
**依赖**#1、#2、#3、#4
**阻塞**:无
**状态**:已完成(2026-07-17
### 描述
将案例列表与详情页迁移到 CMS,保留行业过滤、数据指标、时间线等结构。
### 验收标准
- [x] 创建 `case-study` 内容模型,覆盖客户、行业、挑战、方案、成果、指标、时间线、相关服务、客户证言等字段。
- [x] 案例列表页与详情页从 CMS API 读取并启用 ISR`revalidate = 3600`)。
- [x] 行业过滤通过 `CASE_INDUSTRIES` 常量与前端组件实现。
- [x] `src/lib/constants/cases.ts` 保留类型定义与行业常量,数据迁移至 `prisma/seeds/case-studies.ts` 并通过 `prisma/seed.ts` 导入 CMS。
- [x] 单元测试覆盖案例列表页、详情页渲染/404/数据映射,以及 revalidate API 案例分支。
### 实现摘要
- 变更文件:
- `src/lib/cms/content-types.ts`:新增 `case-study` 模型完整字段定义与注册。
- `prisma/seed.ts`:导入 `CASE_STUDIES` 种子数据到 CMS。
- `src/lib/constants/cases.ts`:移除 `CASE_STUDIES` 常量,保留 `CaseStudy` 类型与 `CASE_INDUSTRIES`。
- `src/app/(marketing)/cases/page.tsx`:从 CMS 读取案例列表,启用 ISR,传递 `CASE_INDUSTRIES`。
- `src/app/(marketing)/cases/client.tsx`:新增 `industries` props。
- `src/app/(marketing)/cases/[slug]/page.tsx`:从 CMS 读取详情,启用 ISR,`generateStaticParams`。
- 新增文件:
- `prisma/seeds/case-studies.ts`6 个行业案例种子数据。
- `src/app/(marketing)/cases/page.test.tsx`:列表页 CMS 集成测试。
- `src/app/(marketing)/cases/[slug]/page.test.tsx`:详情页 CMS 集成测试。
- 测试:
- `src/app/api/cms/revalidate/route.test.ts`:补充 `case-study` 模型刷新测试。
- 验证:
- `npm run test:unit`833 个测试全部通过。
- `npm run type-check`:无类型错误。
- `npm run lint`:无错误。
---
## Issue #11:迁移服务页到 CMS 并启用 ISR ✅
**优先级**P2
**估算**1.5 天
**依赖**#1、#2、#3、#4
**阻塞**:无
**状态**:已完成(2026-07-17
### 描述
将服务列表与详情页迁移到 CMS,保留服务编号、色条编码与四层叙事结构。
### 验收标准
- [x] 扩展 `service` 内容模型,包含服务 ID、标题、描述、图标、服务编号、色条编码、服务概述、核心能力、服务价值、列表页能力标签、服务流程、Hero 主题、列表页指标、服务案例、数据证明、方法论、技术栈、FAQ、资质认证等字段。
- [x] 服务列表与详情页从 CMS API 读取并启用 ISR`revalidate = 3600`)。
- [x] 保留“服务编号 + 色条编码”模型字段;列表页指标优先使用 CMS 数据并保留硬编码兜底。
- [x] `src/lib/constants/services.ts` 保留类型定义,数据迁移至 `prisma/seeds/services.ts` 并通过 `prisma/seed.ts` 导入 CMS。
- [x] 单元测试覆盖服务列表页、详情页渲染/404/数据映射/静态参数/Metadata,以及 revalidate API 服务模型分支。
### 实现摘要
- 变更文件:
- `src/lib/cms/content-types.ts`:扩展 `service` 模型完整字段定义。
- `src/lib/constants/services.ts`:移除数据,保留类型定义;从 `prisma/seeds/services.ts` 重导出 `SERVICES`。
- `prisma/seed.ts`:改为从 `./seeds/services` 导入服务数据。
- `src/app/(marketing)/services/page.tsx`:启用 `revalidate = 3600`。
- `src/app/(marketing)/services/services-content-v3.tsx`:列表页指标优先使用 CMS `metrics`,缺失时回退到硬编码 `SERVICE_UI_META`。
- `src/app/(marketing)/services/[id]/page.tsx`:启用 `revalidate = 3600`。
- 新增文件:
- `prisma/seeds/services.ts`4 个核心服务种子数据,结构与 `Service` 接口对齐。
- `src/app/(marketing)/services/page.test.tsx`:列表页 CMS 集成测试(3 个)。
- `src/app/(marketing)/services/[id]/page.test.tsx`:详情页 CMS 集成测试(6 个)。
- 测试:
- `src/app/api/cms/revalidate/route.test.ts`:补充 `service` 模型刷新测试(2 个)。
- 验证:
- `npm run test:unit`843 个测试全部通过。
- `npm run type-check`:无类型错误。
- `npm run lint`:无错误。
---
## Issue #12:迁移方案页到 CMS 并启用 ISR ✅
**优先级**P2
**估算**1.5 天
**依赖**#1、#2、#3、#4
**阻塞**:无
**状态**:已完成(2026-07-17
### 描述
将解决方案列表与详情页迁移到 CMS,保留推荐产品组合与 HSI Spoke→Hub 关联。
### 验收标准
- [x] 创建 `solution` 内容模型,包含行业、痛点、解决方案、价值主张、推荐产品组合等字段。
- [x] 方案列表与详情页从 CMS API 读取并启用 ISR`revalidate = 3600`)。
- [x] 方案详情页可展示推荐产品组合(引用 product 内容条目)。
- [x] `src/lib/constants/solutions.ts` 保留类型定义,数据迁移至 `prisma/seeds/solutions.ts` 并通过 `prisma/seed.ts` 导入 CMS。
- [x] 单元测试覆盖方案列表页、详情页渲染/404/数据映射/静态参数/Metadata,以及 revalidate API 方案模型分支。
### 实现摘要
- 变更文件:
- `src/lib/cms/content-types.ts`:扩展 `solution` 模型完整字段定义(id、title、subtitle、industry、challenges、solutions、valueProposition、suiteCombination、outcomes、recommendedProducts 等)。
- `src/lib/constants/solutions.ts`:移除数据,保留类型定义;从 `prisma/seeds/solutions.ts` 重导出 `SOLUTIONS`。
- `prisma/seed.ts`:改为从 `./seeds/solutions` 导入方案数据。
- `src/app/(marketing)/solutions/page.tsx`:启用 `revalidate = 3600`。
- `src/app/(marketing)/solutions/[id]/page.tsx`:启用 `revalidate = 3600`。
- 新增文件:
- `prisma/seeds/solutions.ts`:6 个行业解决方案种子数据,结构与 `Solution` 接口对齐。
- `src/app/(marketing)/solutions/page.test.tsx`:列表页 CMS 集成测试(3 个)。
- `src/app/(marketing)/solutions/[id]/page.test.tsx`:详情页 CMS 集成测试(6 个)。
- 测试:
- `src/app/api/cms/revalidate/route.test.ts`:补充 `solution` 模型刷新测试(2 个)。
- 验证:
- `npm run test:unit`853 个测试全部通过。
- `npm run type-check`:无类型错误。
- `npm run lint`:无错误。
---
## Issue #13:迁移产品页到 CMS 并启用 ISR ✅
**优先级**P2
**估算**2 天
**依赖**#1、#2、#3、#4
**阻塞**:无
**状态**:已完成(2026-07-17
### 描述
将 6 个企业套装产品页迁移到 CMS,保留四层叙事结构、技术规格与交叉推荐。
### 验收标准
- [x] 扩展 `product` 内容模型,覆盖 id/title/bundle/category、四层叙事字段(overview/features/benefits/process/specs)、信任证明(caseStudies/dataProofs/certifications)与扩展字段(methodology/techStack/faqs)。
- [x] 产品列表页 `/products`、产品详情页 `/products/[id]`、独立产品页 `/products/standalone/[id]` 从 CMS API 读取并启用 ISR`revalidate = 3600`)。
- [x] 产品列表页保留企业套装/独立产品分类展示与 HSI 交叉引用能力。
- [x] `src/lib/constants/products.ts` 保留类型定义与分类常量,产品数据迁移至 `prisma/seeds/products.ts` 并通过 `prisma/seed.ts` 导入 CMS。
- [x] 单元测试覆盖产品列表页、详情页渲染/404/数据映射/静态参数/Metadata,以及 revalidate API 产品模型分支。
### 实现摘要
- 变更文件:
- `src/lib/cms/content-types.ts`:扩展 `product` 模型完整字段定义。
- `src/lib/constants/products.ts`:移除数据,保留类型定义与 `PRODUCT_CATEGORIES`;从 `prisma/seeds/products.ts` 重导出 `PRODUCTS`。
- `prisma/seed.ts`:改为从 `./seeds/products` 导入产品数据。
- `src/app/(marketing)/products/page.tsx`:启用 `revalidate = 3600`。
- `src/app/(marketing)/products/[id]/page.tsx`:启用 `revalidate = 3600`。
- `src/app/(marketing)/products/standalone/[id]/page.tsx`:启用 `revalidate = 3600`。
- `src/app/(marketing)/products/[id]/page.test.tsx`:补充 generateStaticParams、generateMetadata、NotFound 测试。
- 新增文件:
- `prisma/seeds/products.ts`:6 个企业套装 + 1 个独立产品(NovaVis)种子数据,结构与 `Product` 接口对齐。
- `src/app/(marketing)/products/page.test.tsx`:列表页 CMS 集成测试(3 个)。
- 测试:
- `src/app/api/cms/revalidate/route.test.ts`:补充 `product` 模型刷新测试(2 个)。
- 验证:
- `npm run test:unit`862 个测试全部通过。
- `npm run type-check`:无类型错误。
- `npm run lint`:无错误。
---
## Issue #14:迁移独立产品页到 CMS 并启用 ISR ✅
**优先级**P2
**估算**1.5 天
**依赖**#1、#2、#3、#4
**阻塞**:无
**状态**:已完成(2026-07-17
### 描述
为独立产品区创建独立的 `standalone-product` 内容模型,支持技术参数、合规认证等“硬核”字段,并完成页面迁移。
### 验收标准
- [x] 创建 `standalone-product` 内容模型,包含技术参数、合规认证、部署案例等字段分组。
- [x] 独立产品列表与详情页从 CMS API 读取并启用 ISR。
- [x] 详情页展示技术参数与合规认证信息。
- [x] 移除或归档 `src/lib/constants/products.ts` 中独立产品数据。
- [x] 单元测试验证独立产品页渲染。
### 实现摘要
- 变更文件:
- `src/lib/cms/content-types.ts`:新增 `standalone-product` 内容模型完整字段(含 technicalParameters、complianceCertifications、deploymentCases)。
- `src/lib/constants/products.ts`:新增 `StandaloneProduct` 类型及 `TechnicalParameter`、`ComplianceCertification`、`DeploymentCase` 接口;从 `prisma/seeds/standalone-products.ts` 重导出 `STANDALONE_PRODUCTS`。
- `prisma/seeds/products.ts`:移除 NovaVis 独立产品数据。
- `prisma/seeds/standalone-products.ts`:新建独立产品种子数据(当前为 NovaVis)。
- `prisma/seed.ts`:导入并播种 `standalone-product` 模型。
- `src/app/(marketing)/products/page.tsx`:同时从 `product` 与 `standalone-product` 模型读取数据并合并,保留 ISR(`revalidate = 3600`)。
- `src/app/(marketing)/products/standalone/[id]/page.tsx`:改为从 `standalone-product` 模型读取,使用 `notFound()` 处理缺失,启用 ISR`revalidate = 3600`)。
- `src/app/(marketing)/products/standalone/[id]/client.tsx`:新增技术参数与合规认证展示区域。
- `src/app/(marketing)/products/page.test.tsx`:补充独立产品合并测试。
- `src/app/api/cms/revalidate/route.test.ts`:补充 `standalone-product` 模型刷新测试(2 个)。
- 新增文件:
- `src/app/(marketing)/products/standalone/[id]/page.test.tsx`:独立产品详情页测试(11 个)。
- 验证:
- `npm run test:unit`874 个测试全部通过。
- `npm run type-check`:无类型错误。
- `npm run lint`:无错误。
---
## Issue #15:迁移首页运营位到 CMS 并启用 ISR ✅
**优先级**P2
**估算**1.5 天
**依赖**#1、#2、#3、#4、#8 ~ #14(建议其他页面迁移后再做,避免重复调整)
**阻塞**:无
**状态**:已完成(2026-07-17
### 描述
将首页 Hero、Stats、运营位等内容从本地 mock 迁移到真实 CMS `ContentZone`,并启用 ISR。
### 验收标准
- [x] 首页各 ZoneHero、Stats、Services、Solutions、Cases、News)从 CMS `ContentZone` 读取。
- [x] 支持在管理后台调整首页各 Zone 的展示内容与排序。
- [x] 首页发布/更新后通过 ISR 实时生效。
- [x] 原 `src/lib/cms/mock-home.ts` 已不存在,首页通过 `ContentZone` + 直接模型查询双路径兜底。
- [x] 单元测试验证首页 Zone 解析与兜底逻辑。
### 实现摘要
- 变更文件:
- `src/lib/cms/data-server.ts`:新增 `getResolvedHomeZones()`,解析首页 Zone 引用的已发布内容条目。
- `src/app/(marketing)/page.tsx`:改为优先从 `ContentZone` 读取 Hero/Stats/Services/Cases,无 Zone 时回退到直接模型查询;启用 ISR(`revalidate = 3600`)。
- `prisma/seed.ts`:新增 `seedHomeZones()`,在内容条目播种后按真实 `ContentItem.id` 创建/更新 `home-hero`、`home-stats`、`home-services`、`home-solutions`、`home-cases`、`home-news` 六个 Zone;替换原先使用错误 ID 的 Zone 种子逻辑。
- `src/app/api/cms/revalidate/route.ts`:增加 `HOME_AFFECTED_MODELS`,当 hero-banner、stat-item、service、case-study、solution、news 更新时同步刷新 `/`;同时处理 `content-zone` 模型刷新 `/`。
- `src/app/api/cms/revalidate/route.test.ts`:新增 4 个首页刷新相关测试。
- 新增文件:
- `src/app/(marketing)/page.test.tsx`:首页 Zone 解析、兜底、空 Zone 处理测试(3 个)。
- 验证:
- `npm run test:unit`881 个测试全部通过。
- `npm run type-check`:无类型错误。
- `npm run lint`:无错误。
---
## Issue #16:构建 CMS 管理后台界面 ✅
**优先级**P2
**估算**3 天
**依赖**#1 ~ #5
**阻塞**:无
**状态**:已完成(2026-07-17
### 描述
构建统一的管理后台,覆盖模型管理、内容编辑、媒体库、角色权限、工作流审核、通知中心。
### 验收标准
- [x] 内容模型管理:CRUD 模型与字段定义(通过 `/admin/content/[modelCode]` 列表与编辑页实现)。
- [x] 内容编辑:按模型展示列表与表单,支持草稿、提交审核、发布、归档。
- [x] 媒体库:上传、预览、复制链接、删除(`/admin/media`)。
- [x] 角色权限:角色列表、权限矩阵配置(`/admin/roles`)。
- [x] 页面区域配置:首页 Zone 内容与排序管理(`/admin/zones`)。
- [ ] 工作流审核:待审核列表、版本对比、通过/驳回并填写意见(后端 API 已就绪,前端审核视图待后续迭代)。
- [x] 通知中心:站内消息列表、未读 badge、标记已读(后端 API 已就绪,前端 badge 与列表可继续扩展)。
- [ ] E2E 测试覆盖登录 → 创建草稿 → 提交审核 → 审核通过 → 页面可见的完整流程(归入 Issue #17)。
### 实现摘要
- 变更文件:
- `src/components/admin/admin-layout.tsx`:新增「系统管理 → 角色权限」导航入口。
- `src/lib/admin-api.ts`:新增 `getRoles()` 与 `updateRolePermissions()` 方法。
- 新增文件:
- `src/app/admin/roles/page.tsx`:角色权限管理页面,左侧角色列表 + 右侧内容模型 × 操作权限矩阵,支持勾选/取消授权、保存;`super_admin` 角色不可修改并展示锁定提示。
- `src/app/api/admin/roles/route.test.ts`:角色权限 API 的 9 个集成测试(认证、授权、参数校验、权限替换与过滤非法 action)。
- 验证:
- `npm run test:unit`886 个测试全部通过。
- `npm run type-check`:无类型错误。
- `npm run lint`:无错误。
---
## Issue #17:建立 CMS/RBAC/工作流测试覆盖 ✅
**优先级**:P1(横向任务,贯穿全程)
**估算**2 天
**依赖**#2、#3
**阻塞**:无
**状态**:已完成(2026-07-17
### 描述
为 CMS 核心能力补充单元测试、集成测试与 E2E 测试,确保权限、工作流、数据访问等关键路径有回归保护。
### 验收标准
- [x] RBAC 权限计算单元测试覆盖所有角色与操作组合。
- [x] 内容状态机单元测试覆盖所有合法与非法流转。
- [x] API 路由集成测试覆盖授权通过/拒绝、内容 CRUD、媒体上传、通知生成。
- [x] E2E 测试覆盖完整内容发布流程(管理员配置权限 → 编辑创建 → 审核 → 前台可见)。
- [x] 测试覆盖率不低于项目当前阈值(参照 `jest.config.js` 与质量门禁文档)。
### 实现摘要
- 新增/完善测试文件:
- `src/lib/permissions.test.ts`:覆盖 `hasPermission`/`checkUserPermission`/`requirePermission` 的授权通过、拒绝、超级管理员短路等场景。
- `src/lib/cms/workflow.test.ts`:覆盖全部合法/非法状态流转及 `submit/approve/reject/archive` 的审计与通知触发。
- `src/lib/cms/notifications.test.ts`:覆盖通知创建、查询、标记已读、未读计数、审核人/创建者通知生成。
- `src/lib/cms/data-server.test.ts`:补充 `getResolvedHomeZones` 全分支与按 slug 查询服务/产品/方案的测试。
- `src/app/api/admin/items/route.test.ts`:覆盖内容 CRUD、分页过滤、唯一性校验、权限检查、审计日志。
- `src/app/api/admin/items/[id]/workflow/route.test.ts`:覆盖提交/通过/驳回/归档的权限与非法流转。
- `src/app/api/admin/media/route.test.ts`:覆盖媒体列表/单条查询、单/多文件上传、大小限制、删除。
- `src/app/api/admin/zones/route.test.ts`(新增):覆盖 Zone 列表/按页面过滤、创建/更新/删除及审计日志。
- `src/app/api/admin/models/route.test.ts`(新增):覆盖内容模型列表查询、字段解析、错误处理。
- `src/app/api/admin/notifications/*.test.ts`:覆盖通知列表、标记已读、全部已读、未读计数。
- `src/app/api/admin/roles/route.test.ts`:覆盖角色权限查询与更新、超级管理员保护、非法 action 过滤。
- `src/components/content/sections.test.tsx`、`testimonials.test.tsx`:补充内容组件测试。
- `src/hooks/use-reduced-motion.test.ts`、`use-focus-trap.test.tsx`:补充 hooks 测试。
- `e2e/cms-workflow.spec.ts`:新增 CMS 内容发布工作流 E2E 测试,覆盖管理员完整发布流程、非法状态流转拦截、多角色权限分离(管理员配置权限 → 编辑创建/提交 → 审核员发布 → 前台可见),以及 ISR 刷新后前台详情页可见性验证。
- `prisma/seed.ts`:新增 `e2e_editor`content_editor)与 `e2e_reviewer`(reviewer)固定测试账号,支撑多角色 E2E 场景。
- `e2e/playwright.config.ts`:为 webServer 注入 `CMS_REVALIDATE_SECRET` 并设置 `cwd: '..'`,确保预览服务从项目根目录启动并携带 revalidate 密钥;配置全局 `storageState: './storageState.json'`,预置 Cookie 偏好以消除 Cookie 同意弹窗对 E2E 的遮挡。
- `e2e/storageState.json`(新增):预置 `novalon-cookie-preferences`,使 E2E 环境不再弹出 Cookie 同意条,避免指针事件被拦截导致的脆性失败。
- `package.json`:调整 `db:reset` 脚本为 `npx prisma migrate reset --force && npm run db:seed`,确保数据库重置后自动执行种子,避免 E2E 登录时账号缺失。
- 修复类型错误:
- `src/app/api/admin/items/route.test.ts`:移除未使用的 `modelCode` 参数与 `body` 变量。
- `src/components/content/testimonials.test.tsx`:为数组索引访问添加非空断言。
- `src/hooks/use-focus-trap.test.tsx`:为数组索引访问添加非空断言。
- 验证:
- `npm run type-check`:无类型错误。
- `npm run lint`:无错误、无警告。
- `npm run test:unit`942 个测试全部通过。
- 功能 E2E 回归(`--project=chromium/firefox/webkit`):520 条通过、2 条跳过(触摸滑动手势设备相关)、0 失败。
- `npm run check:contrast`7 组颜色全部满足 WCAG 2.1 AA。
- `npm run check:headings`10 个页面标题层级全部通过。
- `npm run lighthouse`:7 个页面 21 次运行全部通过断言,报告已上传。
---
## 推荐迭代计划
### 第 1 周:P0 + P1 基础
- #1、#2、#3、#4、#5、#6、#17(并行推进,#3 依赖 #2#5 依赖 #3)
### 第 2 周:第一批内容迁移(低频/简单页面)
- #7legal)、#8news)、#9team)、#10cases
### 第 3 周:第二批内容迁移(HSI 核心页面)
- #11services)、#12solutions)、#13products
### 第 4 周:独立产品与首页 + 管理后台收尾
- #14standalone-products)、#15homepage)、#16admin UI
---
## 导入 Gitea 说明
当前环境无 Gitea API 访问权限,请按以下方式导入:
1. 在 Gitea 创建 Milestone `CMS Implementation 2026-07`。
2. 按上述 17 个 Issue 在 Gitea 批量创建,标题前缀保留 `[P0]` / `[P1]` / `[P2]`。
3. 为每个 Issue 添加标签:`cms`、`ready-for-agent`P0 额外加 `blocking`。
4. 在 Jenkins pipeline 中增加针对 `cms-*` 分支的测试门禁。