docs(test): 添加设计文档、测试规范与 E2E 测试套件

- 新增 ADR 架构决策记录 (Design DNA 集成与深化)
- 新增 CMS 系统设计文档
- 新增实施计划文档 (Phase1-3)
- 新增 Bain 品牌升级设计规格
- 新增 E2E 分层测试套件 (P1-P4)
- 新增视觉回归测试配置
- 新增光效分析、视觉验证等辅助脚本
- 更新验收测试报告
This commit is contained in:
张翔
2026-07-07 06:54:25 +08:00
parent 8def296301
commit 636bc4ecde
31 changed files with 9262 additions and 41 deletions
+950
View File
@@ -0,0 +1,950 @@
# Novalon CMS 后端 API 设计规范
> **版本**v1.1
> **日期**2026-07-02
> **技术栈**Java 21 + Spring Boot WebFlux + PostgreSQL + Flyway
> **前端对接**Next.js 14 + CMS SDK`src/lib/cms/client.ts`
---
## 一、总体设计原则
### 1.1 API 风格
- **风格**RESTful API
- **数据格式**JSON
- **字符编码**UTF-8
- **时间格式**ISO 8601`2024-01-01T00:00:00Z`
- **统一响应结构**
```json
{
"code": 0,
"message": "success",
"data": {},
"timestamp": "2024-01-01T00:00:00Z"
}
```
| 字段 | 类型 | 说明 |
|------|------|------|
| code | int | 业务状态码,0 表示成功 |
| message | string | 提示信息 |
| data | object/array/null | 响应数据 |
| timestamp | string | 服务器时间 |
### 1.2 分页统一格式
**请求参数**
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| page | int | 1 | 页码,从 1 开始 |
| pageSize | int | 20 | 每页数量,最大 100 |
| sortBy | string | created_at | 排序字段 |
| sortOrder | string | desc | 排序方向:asc/desc |
**响应结构**
```json
{
"code": 0,
"message": "success",
"data": {
"items": [],
"total": 100,
"page": 1,
"pageSize": 20,
"totalPages": 5
},
"timestamp": "2024-01-01T00:00:00Z"
}
```
### 1.3 错误码规范
| 错误码 | 说明 | HTTP 状态码 |
|--------|------|------------|
| 0 | 成功 | 200 |
| 40000 | 请求参数错误 | 400 |
| 40100 | 未授权 | 401 |
| 40300 | 无权限 | 403 |
| 40400 | 资源不存在 | 404 |
| 40900 | 资源冲突(如 code 重复) | 409 |
| 50000 | 服务器内部错误 | 500 |
---
## 二、数据库表结构
### 2.1 cms_content_model(内容模型表)
```sql
CREATE TABLE cms_content_model (
id BIGSERIAL PRIMARY KEY,
code VARCHAR(100) NOT NULL UNIQUE,
name VARCHAR(200) NOT NULL,
description VARCHAR(500),
fields_json JSONB NOT NULL DEFAULT '[]'::jsonb,
is_page_type BOOLEAN NOT NULL DEFAULT FALSE,
url_pattern VARCHAR(200),
has_versions BOOLEAN NOT NULL DEFAULT TRUE,
has_draft BOOLEAN NOT NULL DEFAULT TRUE,
icon VARCHAR(50),
created_by VARCHAR(100) NOT NULL,
updated_by VARCHAR(100) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted BOOLEAN NOT NULL DEFAULT FALSE
);
CREATE INDEX idx_cms_content_model_code ON cms_content_model(code) WHERE deleted = FALSE;
```
### 2.2 cms_content_item(内容项表)
```sql
CREATE TABLE cms_content_item (
id BIGSERIAL PRIMARY KEY,
model_id BIGINT NOT NULL REFERENCES cms_content_model(id),
model_code VARCHAR(100) NOT NULL,
title VARCHAR(500) NOT NULL,
slug VARCHAR(200),
status VARCHAR(20) NOT NULL DEFAULT 'draft',
data_json JSONB NOT NULL DEFAULT '{}'::jsonb,
version INT NOT NULL DEFAULT 1,
published_at TIMESTAMP,
created_by VARCHAR(100) NOT NULL,
updated_by VARCHAR(100) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted BOOLEAN NOT NULL DEFAULT FALSE
);
CREATE UNIQUE INDEX idx_cms_content_item_model_slug
ON cms_content_item(model_code, slug) WHERE deleted = FALSE;
CREATE INDEX idx_cms_content_item_model_status
ON cms_content_item(model_code, status, created_at DESC) WHERE deleted = FALSE;
CREATE INDEX idx_cms_content_item_data ON cms_content_item USING GIN (data_json) WHERE deleted = FALSE;
```
### 2.3 cms_content_version(内容版本表)
```sql
CREATE TABLE cms_content_version (
id BIGSERIAL PRIMARY KEY,
item_id BIGINT NOT NULL REFERENCES cms_content_item(id),
version INT NOT NULL,
data_json JSONB NOT NULL,
status VARCHAR(20) NOT NULL,
change_log VARCHAR(500),
created_by VARCHAR(100) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_cms_content_version_item
ON cms_content_version(item_id, version DESC);
```
### 2.4 cms_content_zone(内容区域表)
```sql
CREATE TABLE cms_content_zone (
id BIGSERIAL PRIMARY KEY,
code VARCHAR(100) NOT NULL UNIQUE,
name VARCHAR(200) NOT NULL,
description VARCHAR(500),
page_code VARCHAR(100),
allowed_models VARCHAR(500),
items_json JSONB NOT NULL DEFAULT '[]'::jsonb,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_cms_content_zone_page ON cms_content_zone(page_code);
```
### 2.5 cms_theme_config(主题配置表)
```sql
CREATE TABLE cms_theme_config (
id BIGSERIAL PRIMARY KEY,
config_json JSONB NOT NULL DEFAULT '{}'::jsonb,
updated_by VARCHAR(100) NOT NULL,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- 初始插入一条默认记录(id=1)
INSERT INTO cms_theme_config (id, config_json, updated_by)
VALUES (1, '{}', 'system');
```
### 2.6 cms_media_asset(媒体资源表)
```sql
CREATE TABLE cms_media_asset (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(200) NOT NULL,
path VARCHAR(500) NOT NULL,
url VARCHAR(500) NOT NULL,
mime_type VARCHAR(100) NOT NULL,
size BIGINT NOT NULL,
width INT,
height INT,
alt VARCHAR(200),
storage_type VARCHAR(20) NOT NULL DEFAULT 'local',
created_by VARCHAR(100) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted BOOLEAN NOT NULL DEFAULT FALSE
);
CREATE INDEX idx_cms_media_mime ON cms_media_asset(mime_type) WHERE deleted = FALSE;
```
### 2.7 cms_audit_log(审计日志表)
```sql
CREATE TABLE cms_audit_log (
id BIGSERIAL PRIMARY KEY,
module VARCHAR(50) NOT NULL,
target_id BIGINT NOT NULL,
action VARCHAR(50) NOT NULL,
operator VARCHAR(100) NOT NULL,
before_json JSONB,
after_json JSONB,
ip VARCHAR(50),
user_agent VARCHAR(500),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_cms_audit_module ON cms_audit_log(module, target_id, created_at DESC);
CREATE INDEX idx_cms_audit_operator ON cms_audit_log(operator, created_at DESC);
```
---
## 三、API 接口详细设计
### 3.1 内容模型管理
#### 3.1.1 获取内容模型列表
```
GET /api/cms/models
```
**查询参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| page | int | 否 | 页码,默认 1 |
| pageSize | int | 否 | 每页数量,默认 20 |
| keyword | string | 否 | 搜索关键词(按 name/code 搜索) |
| isPageType | boolean | 否 | 是否页面类型 |
**响应 data**:分页 + ContentModel 数组
**ContentModel 结构**
```json
{
"id": "1",
"code": "case-study",
"name": "案例研究",
"description": "客户成功案例",
"fields": [...],
"isPageType": true,
"urlPattern": "/cases/{slug}",
"hasVersions": true,
"hasDraft": true,
"icon": "briefcase",
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T00:00:00Z"
}
```
#### 3.1.2 获取单个内容模型
```
GET /api/cms/models/{code}
```
**路径参数**`code` - 模型编码
**响应 data**ContentModel 对象
#### 3.1.3 创建内容模型
```
POST /api/cms/models
```
**请求体**
```json
{
"code": "case-study",
"name": "案例研究",
"description": "客户成功案例",
"fields": [...],
"isPageType": true,
"urlPattern": "/cases/{slug}",
"hasVersions": true,
"hasDraft": true,
"icon": "briefcase"
}
```
**响应 data**:创建后的 ContentModel 对象
#### 3.1.4 更新内容模型
```
PUT /api/cms/models/{code}
```
**请求体**:同创建接口
**响应 data**:更新后的 ContentModel 对象
#### 3.1.5 删除内容模型
```
DELETE /api/cms/models/{code}
```
**说明**:逻辑删除,同时删除该模型下的所有内容项(逻辑删除)
---
### 3.2 内容项管理
#### 3.2.1 分页查询内容项
```
GET /api/cms/items
```
**查询参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| modelCode | string | 是 | 内容模型编码 |
| status | string | 否 | 状态过滤:draft/review/published/archived |
| page | int | 否 | 页码 |
| pageSize | int | 否 | 每页数量 |
| sortBy | string | 否 | 排序字段,默认 created_at |
| sortOrder | string | 否 | 排序方向,默认 desc |
| search | string | 否 | 搜索关键词(按 title) |
| filters | object | 否 | 动态字段过滤,JSON 格式 |
**响应 data**:分页 + ContentItem 数组
**ContentItem 结构**
```json
{
"id": "1",
"modelId": "1",
"modelCode": "case-study",
"title": "某制造企业 ERP 升级",
"slug": "manufacturing-erp-upgrade",
"status": "published",
"data": {
"client": "某上市制造企业",
"industry": "制造业",
"metrics": [...]
},
"version": 3,
"publishedAt": "2024-01-15T10:00:00Z",
"createdBy": "admin",
"updatedBy": "admin",
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-15T10:00:00Z"
}
```
#### 3.2.2 获取内容项详情
```
GET /api/cms/items/{id}
```
**路径参数**`id` - 内容项 ID
**响应 data**ContentItem 对象
#### 3.2.3 按 slug 获取内容项
```
GET /api/cms/items/slug/{modelCode}/{slug}
```
**路径参数**
- `modelCode` - 模型编码
- `slug` - 内容 slug
**响应 data**ContentItem 对象
#### 3.2.4 创建内容项
```
POST /api/cms/items
```
**请求体**
```json
{
"modelCode": "case-study",
"title": "某制造企业 ERP 升级",
"slug": "manufacturing-erp-upgrade",
"data": {
"client": "某上市制造企业",
"industry": "制造业"
}
}
```
**说明**
- 创建时状态默认为 `draft`
- 版本号默认为 1
- 自动创建第一个版本记录
**响应 data**:创建后的 ContentItem 对象
#### 3.2.5 更新内容项
```
PUT /api/cms/items/{id}
```
**请求体**
```json
{
"title": "新标题",
"data": {
"client": "新客户名称"
},
"changeLog": "更新客户名称"
}
```
**说明**
- 更新时版本号 +1
- 自动保存到版本历史表
- 如果当前状态是 published,更新后变为 draft(可选配置)
**响应 data**:更新后的 ContentItem 对象
#### 3.2.6 发布内容
```
POST /api/cms/items/{id}/publish
```
**请求体**(可选):
```json
{
"changeLog": "发布正式版本"
}
```
**说明**
- 将状态从 `draft`/`review` 改为 `published`
- 设置 `publishedAt` 时间
- 版本号不变(发布当前版本)
**响应 data**:发布后的 ContentItem 对象
#### 3.2.7 下架内容
```
POST /api/cms/items/{id}/unpublish
```
**说明**:将状态从 `published` 改为 `archived`
**响应 data**:下架后的 ContentItem 对象
#### 3.2.8 获取版本历史
```
GET /api/cms/items/{id}/versions
```
**响应 data**ContentVersion 数组
#### 3.2.9 回滚到指定版本
```
POST /api/cms/items/{id}/versions/{version}/rollback
```
**说明**:创建新版本,内容为指定版本的快照,版本号 +1
**响应 data**:回滚后的 ContentItem 对象
#### 3.2.10 删除内容项
```
DELETE /api/cms/items/{id}
```
**说明**:逻辑删除
---
### 3.3 内容区域管理
#### 3.3.1 获取单个内容区域
```
GET /api/cms/zones/{code}
```
**路径参数**`code` - 区域编码
**响应 data**ContentZone 对象(含 items 详情)
#### 3.3.2 获取页面的所有内容区域
```
GET /api/cms/zones/page/{pageCode}
```
**路径参数**`pageCode` - 页面编码
**响应 data**ContentZone 数组
#### 3.3.3 更新内容区域配置
```
PUT /api/cms/zones/{code}
```
**请求体**
```json
{
"name": "首页 Hero 区",
"description": "首页顶部大 Banner 区域",
"allowedModels": ["hero-banner", "video-banner"],
"items": [
{
"itemId": "101",
"sortOrder": 1,
"config": {
"fullWidth": true
}
},
{
"itemId": "102",
"sortOrder": 2,
"config": {}
}
]
}
```
**响应 data**:更新后的 ContentZone 对象
---
### 3.4 主题配置管理
#### 3.4.1 获取主题配置
```
GET /api/cms/theme
```
**响应 data**ThemeConfig 对象
**ThemeConfig 结构**
```json
{
"id": "1",
"siteName": "Novalon",
"siteDescription": "企业数字化转型服务商",
"logo": "/images/logo.svg",
"favicon": "/favicon.ico",
"colors": {
"primary": "#00D084",
"secondary": "#6C8CFF",
"accent": "#FFB800",
"background": "#FFFFFF",
"text": "#1A1A1A"
},
"seo": {
"defaultTitle": "Novalon - 企业数字化转型",
"defaultDescription": "Novalon 致力于帮助企业实现数字化转型",
"defaultKeywords": "数字化转型,ERP,CRM",
"ogImage": "/images/og-default.png"
},
"social": {
"wechat": "novalon",
"weibo": "@novalon",
"linkedin": "novalon",
"github": "novalon"
},
"settings": {
"enableDarkMode": true,
"defaultTheme": "light"
}
}
```
#### 3.4.2 更新主题配置
```
PUT /api/cms/theme
```
**请求体**:完整的 ThemeConfig 对象(或部分字段)
**响应 data**:更新后的 ThemeConfig 对象
---
### 3.5 媒体资源管理
#### 3.5.1 获取媒体列表
```
GET /api/cms/media
```
**查询参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| page | int | 否 | 页码 |
| pageSize | int | 否 | 每页数量 |
| mimeType | string | 否 | MIME 类型过滤 |
| search | string | 否 | 按名称搜索 |
**响应 data**:分页 + MediaAsset 数组
#### 3.5.2 上传媒体文件
```
POST /api/cms/media/upload
```
**Content-Type**`multipart/form-data`
**表单字段**
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| file | file | 是 | 文件内容 |
| alt | string | 否 | 替代文本 |
| folder | string | 否 | 存储目录 |
**响应 data**MediaAsset 对象
#### 3.5.3 获取媒体详情
```
GET /api/cms/media/{id}
```
#### 3.5.4 删除媒体资源
```
DELETE /api/cms/media/{id}
```
**说明**:逻辑删除,实际文件不删除
---
### 3.6 审计日志
#### 3.6.1 获取审计日志列表
```
GET /api/cms/audit-logs
```
**查询参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| page | int | 否 | 页码 |
| pageSize | int | 否 | 每页数量 |
| module | string | 否 | 模块过滤 |
| targetId | string | 否 | 目标 ID |
| operator | string | 否 | 操作人 |
| action | string | 否 | 操作类型 |
| startTime | string | 否 | 开始时间 |
| endTime | string | 否 | 结束时间 |
**响应 data**:分页 + AuditLog 数组
---
## 四、前端公开 API(无需鉴权)
> 以下 API 用于前端网站展示,仅返回 `published` 状态的内容,无需登录。
> 建议使用 API Key 进行简单的访问频率限制。
### 4.1 获取内容项列表
```
GET /api/cms/public/items
```
**查询参数**:同 3.2.1,但 `status` 固定为 `published`
### 4.2 按 slug 获取内容项
```
GET /api/cms/public/items/slug/{modelCode}/{slug}
```
### 4.3 获取内容区域
```
GET /api/cms/public/zones/{code}
```
### 4.4 获取主题配置
```
GET /api/cms/public/theme
```
---
## 五、Webhook 通知(可选)
内容发布/更新时,可配置 Webhook 通知前端触发 ISR 重新生成:
```
POST https://website.example.com/api/revalidate
```
**请求体**
```json
{
"event": "content.published",
"modelCode": "case-study",
"itemId": "100",
"slug": "manufacturing-erp-upgrade",
"timestamp": "2024-01-15T10:00:00Z"
}
```
---
## 六、性能优化建议
1. **热点内容缓存**:使用 Redis 缓存 `published` 状态的内容,TTL 5-10 分钟
2. **数据库索引**
- `data_json` 字段使用 GIN 索引支持 JSON 查询
- 常用查询组合建立联合索引
3. **读写分离**:读操作走从库,写操作走主库
4. **分页优化**:深分页使用游标分页(seek pagination
5. **CDN 加速**:媒体资源上传到 OSS/S3,走 CDN
---
## 七、安全要求
1. **管理端 API**:必须通过 SSO 鉴权,并有角色权限控制
2. **公开 API**
- 只能访问 `published` 状态的内容
- 使用 API Key 进行限流
- 禁止查询敏感字段(如内部备注)
3. **审计日志**:所有写操作必须记录审计日志
4. **输入校验**
- `code` 字段必须匹配正则:`^[a-z][a-z0-9-]*$`
- JSON 字段大小限制(如 1MB)
- XSS 防护:富文本内容存储前进行净化
5. **SQL 注入防护**:使用参数化查询,禁止拼接 SQL
---
## 八、数据一致性约束
1. **model_code 冗余**`cms_content_item.model_code``cms_content_model.code` 保持一致(冗余设计,提升查询性能)
2. **版本号递增**:每次更新内容项,`version` 必须 +1
3. **状态流转**
- `draft``review``published``archived`
- `published``draft`(修改时)
- 任意状态 → `archived`
---
## 九、前端 Mock 数据字段定义(对接参考)
> **目的**:以下字段定义与前端 Mock 数据保持一致,后端实现时需按此结构创建 `fields_json`。
> **源码位置**`src/lib/cms/mock-data.ts`case-study)、`src/lib/cms/content-types.ts`(其余 6 个模型)。
> **字段类型**`text` | `textarea` | `richtext` | `number` | `boolean` | `date` | `select` | `image` | `array` | `object`
### 9.0 模型总览
| model code | 名称 | 页面类型 | URL 模式 | Mock 数据条数 | 数据源 |
|------------|------|---------|----------|--------------|--------|
| `case-study` | 案例研究 | 是 | `/cases/{slug}` | 6 | `lib/constants/cases.ts` |
| `news` | 新闻资讯 | 是 | `/news/{slug}` | 2 | `lib/constants/news.ts` |
| `service` | 服务 | 是 | `/services/{slug}` | 4 | `lib/constants/services.ts` |
| `product` | 产品 | 是 | `/products/{slug}` | 7 | `lib/constants/products.ts` |
| `solution` | 解决方案 | 是 | `/solutions/{slug}` | 4 | `lib/constants/solutions.ts` |
| `hero-banner` | Hero Banner | 否 | - | 0(首页 CMS 注入) | `lib/cms/mock-home.ts` |
| `stat-item` | 数据指标 | 否 | - | 0(首页 CMS 注入) | `lib/cms/mock-home.ts` |
> **说明**`hero-banner` 和 `stat-item` 不是页面类型,作为首页内容区域(zone)的组件,通过 `ContentZoneRenderer` 渲染。
### 9.1 case-study(案例研究)
| 字段名 | 类型 | 必填 | 标签 | 说明 |
|--------|------|------|------|------|
| `client` | text | 是 | 客户名称 | 客户公司或机构名称 |
| `industry` | text | 是 | 所属行业 | 客户所属行业领域 |
| `companySize` | text | 是 | 公司规模 | 客户公司人员规模 |
| `subtitle` | text | 是 | 副标题 | 案例副标题,简要概括项目价值 |
| `challenge` | textarea | 是 | 挑战 | 客户面临的业务挑战与痛点 |
| `solution` | textarea | 是 | 解决方案 | 我们提供的解决方案与实施路径 |
| `result` | textarea | 是 | 实施成果 | 项目交付后的业务成果与价值 |
| `metrics` | array | 否 | 关键指标 | 项目关键量化指标,子字段:`value`(text)、`label`(text)、`highlight`(boolean) |
| `timeline` | array | 否 | 项目时间线 | 子字段:`phase`(text)、`duration`(text)、`description`(textarea) |
| `services` | array | 否 | 相关服务 | 子字段:`id`(text)、`title`(text)、`description`(textarea) |
| `testimonial` | object | 否 | 客户评价 | 子字段:`quote`(textarea)、`author`(text)、`role`(text)、`company`(text) |
| `color` | select | 是 | 主题色 | 选项:brand / blue / teal / amber / purple,默认 brand |
| `featured` | boolean | 否 | 精选案例 | 是否在首页/推荐位展示,默认 false |
### 9.2 news(新闻资讯)
| 字段名 | 类型 | 必填 | 标签 | 说明 |
|--------|------|------|------|------|
| `excerpt` | textarea | 是 | 摘要 | 新闻摘要,用于列表页展示 |
| `date` | date | 是 | 发布日期 | - |
| `category` | select | 是 | 分类 | 选项:company(公司动态) / industry(行业资讯) / tech(技术分享) |
| `image` | image | 是 | 封面图 | - |
| `content` | richtext | 是 | 正文内容 | 富文本(HTML),通过 TipTap 编辑器产出 |
| `featured` | boolean | 否 | 精选推荐 | 是否在首页精选区展示,默认 false |
### 9.3 service(服务)
| 字段名 | 类型 | 必填 | 标签 | 说明 |
|--------|------|------|------|------|
| `description` | textarea | 是 | 简短描述 | - |
| `icon` | text | 是 | 图标标识 | 图标名称,对应图标库 |
| `overview` | textarea | 是 | 服务概述 | - |
| `features` | array | 否 | 核心能力 | 子字段:`name`(text) |
| `benefits` | array | 否 | 服务价值 | 子字段:`name`(text) |
| `process` | array | 否 | 服务流程 | 子字段:`step`(text) |
| `heroThemeId` | text | 否 | Hero 主题 ID | 关联的 Hero 区域主题配置 |
### 9.4 product(产品)
| 字段名 | 类型 | 必填 | 标签 | 说明 |
|--------|------|------|------|------|
| `description` | textarea | 是 | 产品简介 | - |
| `image` | image | 是 | 产品图片 | - |
| `category` | text | 是 | 分类名称 | - |
| `categoryId` | select | 是 | 分类 ID | 选项:enterprise(企业套装) / specialized(专业产品) |
| `status` | select | 是 | 产品状态 | 选项:研发中 / 内测中 / 已发布 |
| `overview` | textarea | 是 | 产品概述 | - |
| `features` | array | 否 | 核心功能 | 子字段:`name`(text) |
| `benefits` | array | 否 | 产品价值 | 子字段:`name`(text) |
| `tags` | array | 否 | 标签 | 子字段:`name`(text) |
| `featured` | boolean | 否 | 精选产品 | 默认 false |
### 9.5 solution(解决方案)
| 字段名 | 类型 | 必填 | 标签 | 说明 |
|--------|------|------|------|------|
| `description` | textarea | 是 | 方案简介 | - |
| `icon` | text | 是 | 图标标识 | - |
| `industry` | text | 否 | 适用行业 | - |
| `overview` | textarea | 是 | 方案概述 | - |
| `painPoints` | array | 否 | 行业痛点 | 子字段:`text`(text) |
| `valueProps` | array | 否 | 方案价值 | 子字段:`text`(text) |
| `featured` | boolean | 否 | 精选方案 | 默认 false |
### 9.6 hero-bannerHero Banner
> 非页面类型,作为首页内容区域组件使用。
| 字段名 | 类型 | 必填 | 标签 | 默认值 |
|--------|------|------|------|--------|
| `heading` | text | 是 | 主标题 | - |
| `subheading` | textarea | 是 | 副标题 | - |
| `description` | textarea | 否 | 描述文本 | - |
| `primaryCtaText` | text | 否 | 主按钮文案 | 了解更多 |
| `primaryCtaLink` | text | 否 | 主按钮链接 | /solutions |
| `secondaryCtaText` | text | 否 | 副按钮文案 | 联系我们 |
| `secondaryCtaLink` | text | 否 | 副按钮链接 | /contact |
| `theme` | select | 否 | 主题风格 | brand |
| `sortOrder` | number | 否 | 排序 | 0 |
**theme 选项**brand / blue / teal / amber / purple
### 9.7 stat-item(数据指标)
> 非页面类型,作为首页数据展示区组件使用。
| 字段名 | 类型 | 必填 | 标签 | 默认值 |
|--------|------|------|------|--------|
| `icon` | text | 是 | 图标 | - |
| `label` | text | 是 | 指标名称 | - |
| `value` | number | 是 | 数值 | - |
| `suffix` | text | 否 | 后缀 | - |
| `prefix` | text | 否 | 前缀 | - |
| `decimals` | number | 否 | 小数位数 | 0 |
| `description` | textarea | 否 | 描述说明 | - |
| `trendValue` | text | 否 | 趋势值 | - |
| `trendDirection` | select | 否 | 趋势方向 | up |
| `accentColor` | select | 否 | 强调色 | brand |
| `sortOrder` | number | 否 | 排序 | 0 |
**trendDirection 选项**up(上升) / down(下降) / neutral(持平)
**accentColor 选项**brand / blue / teal / amber / purple
---
## 十、前端 Mock 模式架构说明
### 10.1 Mock 数据流转
```
lib/constants/*.ts(原始业务数据)
↓ toContentItem<T>()
lib/cms/mock-data.tsallMockItemsserver-safe 同步数据源)
↓ getMockItems(modelCode) / getMockItemBySlug / getMockItemById
lib/cms/client.tscmsClient,运行时数据访问层)
↓ getContentItems / getItem / getZone
src/app/(marketing)/*/page.tsxserver 端同步预取 ContentItem
↓ 传 ContentItem prop 给 client 组件
src/app/(marketing)/*/client.tsxclient 端接收 prop 渲染)
```
### 10.2 切换到真实 API 的步骤
1.`src/lib/cms/client.ts` 中将 `CMS_MODE``'mock'` 改为 `'api'`
2. 配置 `CMS_API_BASE_URL` 环境变量指向 Java 后端
3. `cmsClient``getItems/getItem/getItemBySlug/getZone` 会自动切换为 HTTP 请求
4. 业务层代码(page.tsx、client.tsx)无需任何改动,ContentItem 数据结构保持一致
### 10.3 静态导出兼容性
- Mock 数据源(`mock-data.ts`)不标记 `'use client'`,提供同步函数
- `generateStaticParams``generateMetadata` 在 server 端同步调用 `getMockItems` / `getMockItemBySlug`
- 静态导出(`output: 'export'`)模式下,构建时预渲染所有页面路径
- CMS API 路由(`/api/cms/draft/*``/api/cms/revalidate`)已改为 POST 方法,避免静态导出预渲染时报错(GET + searchParams 在 `output: 'export'` 下不支持)
### 10.4 富文本编辑器(TipTap
- CMS Studio 的 `richtext` 字段使用 TipTap 富文本编辑器(`src/components/cms/RichTextEditor.tsx`
- 支持:加粗、斜体、H2/H3、有序/无序列表、引用、链接、撤销/重做
- `immediatelyRender: false` 避免 SSR hydration mismatch
- 编辑器产出的 HTML 存储在 ContentItem.data 的对应字段中(如 news.content
- 样式定义在 `src/app/globals.css``.rich-text-editor-content` 选择器下