Files
novalon-website/docs/cms/api-contract.md
T
张翔 636bc4ecde docs(test): 添加设计文档、测试规范与 E2E 测试套件
- 新增 ADR 架构决策记录 (Design DNA 集成与深化)
- 新增 CMS 系统设计文档
- 新增实施计划文档 (Phase1-3)
- 新增 Bain 品牌升级设计规格
- 新增 E2E 分层测试套件 (P1-P4)
- 新增视觉回归测试配置
- 新增光效分析、视觉验证等辅助脚本
- 更新验收测试报告
2026-07-07 06:54:25 +08:00

951 lines
25 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.
# 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` 选择器下