# 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-banner(Hero 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() lib/cms/mock-data.ts(allMockItems,server-safe 同步数据源) ↓ getMockItems(modelCode) / getMockItemBySlug / getMockItemById lib/cms/client.ts(cmsClient,运行时数据访问层) ↓ getContentItems / getItem / getZone src/app/(marketing)/*/page.tsx(server 端同步预取 ContentItem) ↓ 传 ContentItem prop 给 client 组件 src/app/(marketing)/*/client.tsx(client 端接收 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` 选择器下