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

25 KiB
Raw Blame History

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)
  • 统一响应结构:
{
  "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

响应结构:

{
  "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(内容模型表)

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(内容项表)

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(内容版本表)

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(内容区域表)

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(主题配置表)

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(媒体资源表)

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(审计日志表)

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 结构:

{
  "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

请求体:

{
  "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 结构:

{
  "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

请求体:

{
  "modelCode": "case-study",
  "title": "某制造企业 ERP 升级",
  "slug": "manufacturing-erp-upgrade",
  "data": {
    "client": "某上市制造企业",
    "industry": "制造业"
  }
}

说明:

  • 创建时状态默认为 draft
  • 版本号默认为 1
  • 自动创建第一个版本记录

响应 data:创建后的 ContentItem 对象

3.2.5 更新内容项

PUT /api/cms/items/{id}

请求体:

{
  "title": "新标题",
  "data": {
    "client": "新客户名称"
  },
  "changeLog": "更新客户名称"
}

说明:

  • 更新时版本号 +1
  • 自动保存到版本历史表
  • 如果当前状态是 published,更新后变为 draft(可选配置)

响应 data:更新后的 ContentItem 对象

3.2.6 发布内容

POST /api/cms/items/{id}/publish

请求体(可选):

{
  "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}

请求体:

{
  "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 结构:

{
  "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

请求体:

{
  "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<T>()
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 选择器下