Files
WonderQ-Project/docs/home-api.md
duanshuwen 7ba92f3c5d feat: 新增客片案例与团队共创模块,重构玩法与配置
- 新增客片案例全流程功能,包含前台页面、管理端配置、后端API、数据库迁移与文档
- 新增团队共创详情页面与对应接口
- 重构玩法模块:将静态playData替换为动态API获取,拆分类型定义到playTypes.ts
- 优化vite构建配置与环境变量处理,调整详情页操作栏文案
- 更新全量相关文档与配图,新增客片案例、团队共创API文档
- 新增测试用例,完善数据归一化逻辑
- 调整路由配置与环境变量示例文件
2026-08-19 20:23:04 +08:00

20 KiB
Raw Blame History

首页内容管理 Admin API

适用端:WonderQ-AdminWonderQ-Admin-UI

状态:已实现。本文件约定首页内容的 Admin API并记录 MiniAPP 使用的对应 Public API。首页玩法推荐不复制玩法文案而是关联 WanfaCategory

领域边界

首页内容管理维护四类首页内容:

  • 体验推荐:徽标、分类、标题、英文标题、封面和需求关键词。
  • 团队共创:标签、标题、描述、封面、需求关键词、详情副标题和详情正文段落。
  • 极境视界:案例标题、封面、详情图片和需求关键词;首页卡片可进入客片案例详情。
  • 玩法推荐:已关联的玩法分类、启用状态和首页展示顺序;分类名称及路线由玩法领域维护。

本领域不负责:

  • 顶部轮播、车辆选项、需求表单或其他站点模块;这些内容遵循 Admin API 主契约
  • 商品、Product、ProductImage、详情、价格、库存、订单或预订。
  • 线索创建和线索跟进。

demandKeyword 只是点击卡片后预填需求页的普通字符串,不是商品 ID、路线 ID、订单 ID也不建立数据库外键。

当前首页通过 GET /api/public/home 消费四类内容;三个卡片 mock 数组仍作为接口失败、空响应或字段缺失时的前台 fallback玩法推荐无本地模拟数据时保持空态。Admin UI 通过本文件列出的 Admin API 维护正式数据。

接口清单

API 前缀为 /api/admin,除登录接口外均需要后台 JWT。

方法 路径 用途
GET /api/admin/home/experiences 获取全部体验推荐卡片
POST /api/admin/home/experiences 新增体验推荐卡片
PATCH /api/admin/home/experiences/{experienceId} 编辑体验推荐卡片
DELETE /api/admin/home/experiences/{experienceId} 删除体验推荐卡片
PATCH /api/admin/home/experiences/reorder 调整体验推荐卡片顺序
GET /api/admin/home/team-buildings 获取全部团队共创卡片
POST /api/admin/home/team-buildings 新增团队共创卡片
PATCH /api/admin/home/team-buildings/{teamBuildingId} 编辑团队共创卡片
DELETE /api/admin/home/team-buildings/{teamBuildingId} 删除团队共创卡片
PATCH /api/admin/home/team-buildings/reorder 调整团队共创卡片顺序
GET /api/admin/home/wild-archives 获取全部极境视界案例
POST /api/admin/home/wild-archives 新增极境视界案例
PATCH /api/admin/home/wild-archives/{archiveId} 编辑极境视界案例
DELETE /api/admin/home/wild-archives/{archiveId} 删除极境视界案例
PATCH /api/admin/home/wild-archives/reorder 调整极境视界案例顺序
GET /api/admin/home/play-recommendations 获取首页已关联的玩法分类
POST /api/admin/home/play-recommendations 关联一个玩法分类到首页
PATCH /api/admin/home/play-recommendations/{recommendationId} 修改关联的启用状态或玩法分类
DELETE /api/admin/home/play-recommendations/{recommendationId} 移除首页玩法分类关联,不删除玩法分类
PATCH /api/admin/home/play-recommendations/reorder 调整首页玩法推荐顺序

MiniAPP Public API

方法 路径 用途
GET /api/public/home 获取已启用的首页内容和玩法推荐
GET /api/public/home/team-buildings/{teamBuildingId} 获取单个团队共创详情
GET /api/public/home/wild-archives 获取客片案例更多列表
GET /api/public/home/wild-archives/{archiveId} 获取单个客片案例详情

通用约定

  • 请求和响应使用 JSON字段使用 camelCase。
  • 所有管理接口需要 Authorization: Bearer <admin-jwt>
  • GET 返回启用和停用的全部记录,按 sortOrder 升序返回,供 Admin UI 完整维护。
  • 创建、编辑、删除和排序成功后写入审计日志,再提交事务。
  • 创建和编辑返回最新记录;排序接口返回排序后的 items
  • ID 由后端生成并作为非空字符串返回。初始化迁移时应优先保留三个 mock 文件中的稳定 ID。
  • 空集合返回 [],不能返回 null 或省略字段。
  • 所有资源的 sortOrder0 开始,数值越小越靠前;新增未传排序时追加到末尾。
  • 失败响应沿用 Admin API 主契约,包含 messagecode 和可选的 details

数据类型

体验推荐

homeExperienceData.ts 已包含前台渲染类型和 API 过渡类型。Admin API 的完整记录应补充管理元数据:

type HomeExperience = {
  id: string;
  badge: string;
  category: string;
  title: string;
  englishTitle: string;
  image: string;
  demandKeyword: string;
};

type HomeExperienceApiItem = {
  id?: string | null;
  badge?: string | null;
  category?: string | null;
  title?: string | null;
  englishTitle?: string | null;
  image?: string | null;
  demandKeyword?: string | null;
  isActive?: boolean | null;
  sortOrder?: number | null;
};

type HomeExperienceRecord = HomeExperience & {
  isActive: boolean;
  sortOrder: number;
  createdAt: string;
  updatedAt: string;
};

type HomeExperienceCreate = Omit<HomeExperience, "id"> & {
  isActive?: boolean;
  sortOrder?: number;
};

type HomeExperiencePatch = Partial<HomeExperienceCreate>;

团队共创

homeTeamBuildingData.ts 当前只包含前台渲染字段,管理端记录增加状态、排序和审计时间:

type HomeTeamBuilding = {
  id: string;
  tag: string;
  title: string;
  description: string;
  image: string;
  demandKeyword: string;
  detailSubtitle: string;
  detailParagraphs: string[];
};

type HomeTeamBuildingRecord = HomeTeamBuilding & {
  isActive: boolean;
  sortOrder: number;
  createdAt: string;
  updatedAt: string;
};

type HomeTeamBuildingCreate = Omit<HomeTeamBuilding, "id"> & {
  isActive?: boolean;
  sortOrder?: number;
};

type HomeTeamBuildingPatch = Partial<HomeTeamBuildingCreate>;

极境视界

homeWildArchivesData.ts 当前只包含前台渲染字段,管理端记录增加状态、排序和审计时间:

type HomeWildArchive = {
  id: string;
  title: string;
  image: string;
  images: string[];
  demandKeyword: string;
};

type HomeWildArchiveRecord = HomeWildArchive & {
  isActive: boolean;
  sortOrder: number;
  createdAt: string;
  updatedAt: string;
};

type HomeWildArchiveCreate = Omit<HomeWildArchive, "id"> & {
  isActive?: boolean;
  sortOrder?: number;
};

type HomeWildArchivePatch = Partial<HomeWildArchiveCreate>;

列表响应和排序请求统一使用以下结构:

type HomeListResponse<T> = {
  items: T[];
};

type HomeReorderRequest = {
  itemIds: string[];
};

字段约束

资源 字段 类型 必填 约束和用途
体验推荐 badge string 卡片徽标,去除首尾空白后不得为空。
体验推荐 category string 体验分类文案,去除首尾空白后不得为空。
体验推荐 title string 卡片主标题,去除首尾空白后不得为空。
体验推荐 englishTitle string 卡片英文标题,去除首尾空白后不得为空。
体验推荐 image string 可直接用于图片组件的封面 URL。
体验推荐 demandKeyword string 点击后预填需求页的关键词。
团队共创 tag string 卡片标签,去除首尾空白后不得为空。
团队共创 title string 卡片标题,去除首尾空白后不得为空。
团队共创 description string 卡片描述,去除首尾空白后不得为空。
团队共创 image string 可直接用于图片组件的封面 URL。
团队共创 demandKeyword string 点击后预填需求页的关键词。
团队共创 detailSubtitle string 详情页首屏副标题;旧数据回退为 description
团队共创 detailParagraphs string[] 详情页正文段落;至少一段,旧数据回退为 [description]
极境视界 title string 案例标题,去除首尾空白后不得为空。
极境视界 image string 案例封面图片 URL。
极境视界 images string[] 案例详情图片 URL 列表,至少一张;第一张建议与封面一致。
极境视界 demandKeyword string 点击后预填需求页的关键词。
全部资源 isActive boolean 响应必填 是否进入已发布前台内容,创建默认 true
全部资源 sortOrder number 响应必填 非负整数,数值越小越靠前。
全部资源 createdAt string 响应必填 ISO 8601 创建时间。
全部资源 updatedAt string 响应必填 ISO 8601 最后更新时间。

服务端应校验所有必填文本、URL 格式和非负整数排序值;具体文本最大长度由后端 schema 统一定义,并同步到 Admin UI 表单校验。图片字段只保存最终 URL不接受 base64不在首页内容记录中保存图片二进制。

接口详情

以下规则适用于三类资源;路径中的资源名和 ID 参数以接口清单为准。

获取列表

GET /api/admin/home/experiences
Authorization: Bearer <admin-jwt>

团队共创和极境视界分别使用 /api/admin/home/team-buildings/api/admin/home/wild-archives

成功响应示例:

{
  "items": [
    {
      "id": "waterfall-descent",
      "badge": "玩过推荐",
      "category": "瀑降体验",
      "title": "悬崖瀑降",
      "englishTitle": "WATERFALL DESCENT",
      "image": "https://example.test/assets/waterfall-descent.jpg",
      "demandKeyword": "悬崖瀑降",
      "isActive": true,
      "sortOrder": 0,
      "createdAt": "2026-01-01T00:00:00Z",
      "updatedAt": "2026-01-01T00:00:00Z"
    }
  ]
}

接口返回启用和停用的全部记录Admin UI 负责显示状态,不能让后端默认隐藏停用记录。

新增、编辑与删除

  • POST /api/admin/home/{resource}:请求体为对应资源的 Create 类型,成功返回 201 和新建记录;未传 sortOrder 时追加到末尾。
  • PATCH /api/admin/home/{resource}/{id}:请求体为对应资源的 Patch 类型,只更新提交字段;成功返回更新后的记录,不存在返回 404
  • DELETE /api/admin/home/{resource}/{id}:成功返回 { "id": "..." },删除后重新规范化同一资源剩余记录的 sortOrder

其中 {resource} 只能是 experiencesteam-buildingswild-archives;对应路径参数分别为 experienceIdteamBuildingIdarchiveId。删除不触发商品、订单、预订或线索级联操作。

调整顺序

PATCH /api/admin/home/experiences/reorder
Authorization: Bearer <admin-jwt>
Content-Type: application/json

团队共创和极境视界分别使用 /api/admin/home/team-buildings/reorder/api/admin/home/wild-archives/reorder。请求必须完整包含当前资源的全部 ID不能重复

{ "itemIds": ["cave-exploration", "waterfall-descent"] }

成功响应为 { "items": [] },其中 items 是更新 sortOrder 后的完整记录列表。缺少 ID、包含未知 ID 或出现重复 ID 时返回 400 HOME_REORDER_INVALID

MiniAPP Public API

GET /api/public/home

无需鉴权。接口只返回 isActive === true 的记录,首页卡片和玩法推荐分别按 sortOrder 升序返回,并移除管理端状态、排序和审计时间字段:

type PublicHomeResponse = {
  experiences: HomeExperience[];
  teamBuildings: HomeTeamBuilding[];
  wildArchives: HomeWildArchive[];
  playRecommendations: HomeWanfaRecommendation[];
};

type HomeWanfaRecommendation = {
  id: string;
  categoryId: string;
  label: string;
  routes: WanfaRoute[];
};

type WanfaRoute = {
  id: string;
  title: string;
  subtitle: string;
  image: string;
  routeCount: number;
  demandKeyword: string;
};

四组列表始终返回数组没有可用内容时返回空数组。玩法推荐只返回启用的关联记录并展开关联分类当前的路线。MiniAPP 应在数据层归一化字段并保留对应 mock fallback不在页面组件内直接请求接口。

玩法推荐关联

POST /api/admin/home/play-recommendations 请求体:

type HomeWanfaRecommendationCreate = {
  categoryId: string;
  isActive?: boolean;
  sortOrder?: number;
};

type HomeWanfaRecommendationPatch = Partial<HomeWanfaRecommendationCreate>;

Admin 响应记录包含 idcategoryIdcategoryLabelrouteCountisActivesortOrdercreatedAtupdatedAt。同一个玩法分类只能关联一次;分类不存在或已关联时分别返回 404409 HOME_WANFA_CATEGORY_DUPLICATE。移除关联不会删除 WanfaCategory 或其路线。被首页推荐关联的玩法分类不能直接删除,需先移除首页关联。

当前 fallback 与迁移映射

迁移初始数据时应保留以下稳定 ID、字段值和当前数组顺序

体验推荐

顺序 ID 标题 需求关键词
0 waterfall-descent 悬崖瀑降 悬崖瀑降
1 cave-exploration 森林&探洞 地心探险

homeExperienceData.ts 当前的 normalizeHomeExperiences 具有以下过渡行为:

  • isActive === false 的 API 项被过滤。
  • 缺少有效 title 的 API 项被过滤。
  • 其余项目按 sortOrder 升序排列,未传排序时保持 API 原顺序。
  • 文案和图片字段为空时使用当前 fallback 对应位置的值。
  • API 没有有效项目时返回当前 homeExperienceMocks 的副本。

这些规则用于前台接入过渡不应替代服务端校验。后端返回正式记录后Admin UI 应提交完整字段,避免依赖位置 fallback。

团队共创

顺序 ID 标题 需求关键词
0 wild-challenge 山野挑战,共创极境 户外团建
1 canyon-teamwork 峡谷溯溪,默契同行 峡谷团建
2 village-gathering 苗寨共聚,认识彼此 贵州团建

极境视界

顺序 ID 标题 需求关键词
0 hundred-meter-descent 百米自降 悬崖瀑降
1 shilong-cave 石龙洞 地心探险
2 cliff-current 绝壁迎流 峡谷探险
3 canyon-streaming 峡谷溯溪 峡谷溯溪

三个 mock 文件中的图片 URL 只作为初始化内容来源。正式数据应通过媒体上传接口获得最终 URL不要把 mock 文件中的远程图片地址当成图片存储协议。

图片与素材

Admin UI 使用现有素材上传接口获取图片 URL

POST /api/admin/media-assets/upload

建议首页内容使用 group=home,上传成功后将返回的 url 写入对应的 image 字段。接口只保存 URL不接受 base64也不创建 ProductImage 或商品图片关联。

Admin UI 对接要求

  1. 进入首页内容管理时请求三类首页卡片列表,并同时请求玩法分类和首页玩法推荐关联,按 sortOrder 渲染对应分组。
  2. 体验推荐表单维护 badgecategorytitleenglishTitleimagedemandKeyword
  3. 团队共创表单维护 tagtitledescriptionimagedemandKeyworddetailSubtitledetailParagraphs;正文使用空行分隔多个段落。
  4. 极境视界表单维护 titleimageimagesdemandKeyword;详情图片按一行一个 URL 编辑。
  5. 三类首页卡片提供启用/停用、编辑、删除和上移/下移操作;玩法推荐提供启用/停用、移除和上移/下移操作;排序时提交完整 ID 列表。
  6. 每次变更成功后以接口返回的记录或列表更新本地状态,不直接假设本地修改已经保存。
  7. 删除前要求二次确认;删除成功后接受服务端返回的重新排序结果。
  8. 处理 4014044094225xx,保存、上传和排序进行中禁用重复提交。
  9. 不在首页表单中出现 Product ID、ProductImage ID、价格、库存、订单或预订字段。
  10. 预览点击行为使用 demandKeyword;该字段为空时禁止提交,而不是由前端猜测或拼接商品标识。
  11. 玩法推荐管理应先加载玩法分类,再加载首页关联;新增只能从未关联分类中选择,移除只取消关联,不能删除玩法分类。

建议的 Admin UI API 封装函数:

getHomeExperiences();
createHomeExperience(input: HomeExperienceCreate);
updateHomeExperience(experienceId: string, input: HomeExperiencePatch);
deleteHomeExperience(experienceId: string);
reorderHomeExperiences(itemIds: string[]);

getHomeTeamBuildings();
createHomeTeamBuilding(input: HomeTeamBuildingCreate);
updateHomeTeamBuilding(teamBuildingId: string, input: HomeTeamBuildingPatch);
deleteHomeTeamBuilding(teamBuildingId: string);
reorderHomeTeamBuildings(itemIds: string[]);

getHomeWildArchives();
createHomeWildArchive(input: HomeWildArchiveCreate);
updateHomeWildArchive(archiveId: string, input: HomeWildArchivePatch);
deleteHomeWildArchive(archiveId: string);
reorderHomeWildArchives(itemIds: string[]);

getHomeWanfaRecommendations();
createHomeWanfaRecommendation(input: HomeWanfaRecommendationCreate);
updateHomeWanfaRecommendation(recommendationId: string, input: HomeWanfaRecommendationPatch);
deleteHomeWanfaRecommendation(recommendationId: string);
reorderHomeWanfaRecommendations(itemIds: string[]);

当前首页的“查看更多”按钮跳转客片案例列表;首页案例卡片跳转 /pages/wild-archives/detail?id={archiveId},不再跳转需求页。

前后台数据边界

  • Admin API 返回启用和停用的完整记录,供管理端维护。
  • 未来 Public API 只返回已发布且启用的首页内容Public API 的响应字段应与本契约的渲染字段兼容,但不应暴露 createdAtupdatedAt 等管理元数据。
  • MiniAPP 已在 src/lib/api.tssrc/lib/types.tssrc/lib/store.ts 接入 Public API首页组件通过共享状态消费归一化后的四类内容玩法推荐按关联分类展示。
  • 后端不得把 demandKeyword 解析成 Product 或订单关联;如需线路、详情或预订能力,应另立领域契约。

团队共创详情

团队共创首页卡片点击后跳转 MiniAPP /pages/team-buildings/detail?id={teamBuildingId},详情页再按 ID 请求 Public API。首页接口只返回卡片摘要不携带正文段落。

GET /api/public/home/team-buildings/{teamBuildingId}

成功响应在首页摘要基础上增加详情字段:

type PublicHomeTeamBuildingDetail = HomeTeamBuilding & {
  detailSubtitle: string;
  detailParagraphs: string[];
};

规则:

  • 仅返回 isActive === true 的团队共创;不存在或已停用返回 404
  • detailSubtitle 为空时回退为 description
  • detailParagraphs 为空时回退为 [description]
  • MiniAPP 请求失败时按 teamBuildingId 使用本地模拟详情,并提示当前为模拟数据;找不到对应 fallback 时展示未找到状态。
  • 详情封面复用 image 字段,不新增独立 hero 图片或详情表。

后端落地边界

当前实现由 WonderQ-Admin 提供三张首页内容表、HomeWanfaRecommendation 关联表、0017_home_content0018_home_wanfa_recommendations0019_home_wild_archive_images0020_home_team_building_details 迁移、schema、Admin/Public 路由、序列化和审计日志;由 WonderQ-Admin-UI 提供首页玩法推荐入口、分类关联、启停、移除、排序、客片详情图片和团队共创详情维护;由 WonderQ-MiniAPP 调用 /api/public/home、团队共创详情和客片案例列表/详情接口。

相关文档: