# 首页内容管理 Admin API > 适用端:`WonderQ-Admin`、`WonderQ-Admin-UI`。 > > 状态:待实现契约。本文件参考 `WonderQ-MiniAPP/src/pages/home/components/homeExperienceData.ts`、`homeTeamBuildingData.ts` 和 `homeWildArchivesData.ts` 定义首页卡片内容,以及管理端需要的查询、维护和排序接口。它是首页内容的 Admin API 补充契约,不是 MiniAPP Public API 文档。 ## 领域边界 首页内容管理只维护三类首页卡片: - 体验推荐:徽标、分类、标题、英文标题、封面和需求关键词。 - 团队共创:标签、标题、描述、封面和需求关键词。 - 极境视界:案例标题、封面和需求关键词。 本领域不负责: - 顶部轮播、车辆选项、需求表单或其他站点模块;这些内容遵循 [Admin API 主契约](./admin-api-requirements.md)。 - 商品、Product、ProductImage、详情、价格、库存、订单或预订。 - 线索创建和线索跟进。 `demandKeyword` 只是点击卡片后预填需求页的普通字符串,不是商品 ID、路线 ID、订单 ID,也不建立数据库外键。 当前首页仍直接使用三个文件中的 mock 数组。Admin API 接入前,这些数组继续作为前台 fallback;本文件不代表接口已经在 `WonderQ-Admin` 或 `WonderQ-Admin-UI` 中实现。 ## 接口清单 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` | 调整极境视界案例顺序 | ## 通用约定 - 请求和响应使用 JSON,字段使用 camelCase。 - 所有管理接口需要 `Authorization: Bearer `。 - `GET` 返回启用和停用的全部记录,按 `sortOrder` 升序返回,供 Admin UI 完整维护。 - 创建、编辑、删除和排序成功后写入审计日志,再提交事务。 - 创建和编辑返回最新记录;排序接口返回排序后的 `items`。 - ID 由后端生成并作为非空字符串返回。初始化迁移时应优先保留三个 mock 文件中的稳定 ID。 - 空集合返回 `[]`,不能返回 `null` 或省略字段。 - 所有资源的 `sortOrder` 从 `0` 开始,数值越小越靠前;新增未传排序时追加到末尾。 - 失败响应沿用 Admin API 主契约,包含 `message`、`code` 和可选的 `details`。 ## 数据类型 ### 体验推荐 `homeExperienceData.ts` 已包含前台渲染类型和 API 过渡类型。Admin API 的完整记录应补充管理元数据: ```ts 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 & { isActive?: boolean; sortOrder?: number; }; type HomeExperiencePatch = Partial; ``` ### 团队共创 `homeTeamBuildingData.ts` 当前只包含前台渲染字段,管理端记录增加状态、排序和审计时间: ```ts type HomeTeamBuilding = { id: string; tag: string; title: string; description: string; image: string; demandKeyword: string; }; type HomeTeamBuildingRecord = HomeTeamBuilding & { isActive: boolean; sortOrder: number; createdAt: string; updatedAt: string; }; type HomeTeamBuildingCreate = Omit & { isActive?: boolean; sortOrder?: number; }; type HomeTeamBuildingPatch = Partial; ``` ### 极境视界 `homeWildArchivesData.ts` 当前只包含前台渲染字段,管理端记录增加状态、排序和审计时间: ```ts type HomeWildArchive = { id: string; title: string; image: string; demandKeyword: string; }; type HomeWildArchiveRecord = HomeWildArchive & { isActive: boolean; sortOrder: number; createdAt: string; updatedAt: string; }; type HomeWildArchiveCreate = Omit & { isActive?: boolean; sortOrder?: number; }; type HomeWildArchivePatch = Partial; ``` 列表响应和排序请求统一使用以下结构: ```ts type HomeListResponse = { 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` | 是 | 点击后预填需求页的关键词。 | | 极境视界 | `title` | `string` | 是 | 案例标题,去除首尾空白后不得为空。 | | 极境视界 | `image` | `string` | 是 | 案例图片 URL。 | | 极境视界 | `demandKeyword` | `string` | 是 | 点击后预填需求页的关键词。 | | 全部资源 | `isActive` | `boolean` | 响应必填 | 是否进入已发布前台内容,创建默认 `true`。 | | 全部资源 | `sortOrder` | `number` | 响应必填 | 非负整数,数值越小越靠前。 | | 全部资源 | `createdAt` | `string` | 响应必填 | ISO 8601 创建时间。 | | 全部资源 | `updatedAt` | `string` | 响应必填 | ISO 8601 最后更新时间。 | 服务端应校验所有必填文本、URL 格式和非负整数排序值;具体文本最大长度由后端 schema 统一定义,并同步到 Admin UI 表单校验。图片字段只保存最终 URL,不接受 base64,不在首页内容记录中保存图片二进制。 ## 接口详情 以下规则适用于三类资源;路径中的资源名和 ID 参数以接口清单为准。 ### 获取列表 ```http GET /api/admin/home/experiences Authorization: Bearer ``` 团队共创和极境视界分别使用 `/api/admin/home/team-buildings`、`/api/admin/home/wild-archives`。 成功响应示例: ```json { "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}` 只能是 `experiences`、`team-buildings` 或 `wild-archives`;对应路径参数分别为 `experienceId`、`teamBuildingId`、`archiveId`。删除不触发商品、订单、预订或线索级联操作。 ### 调整顺序 ```http PATCH /api/admin/home/experiences/reorder Authorization: Bearer Content-Type: application/json ``` 团队共创和极境视界分别使用 `/api/admin/home/team-buildings/reorder`、`/api/admin/home/wild-archives/reorder`。请求必须完整包含当前资源的全部 ID,不能重复: ```json { "itemIds": ["cave-exploration", "waterfall-descent"] } ``` 成功响应为 `{ "items": [] }`,其中 `items` 是更新 `sortOrder` 后的完整记录列表。缺少 ID、包含未知 ID 或出现重复 ID 时返回 `400 HOME_REORDER_INVALID`。 ## 当前 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: ```http POST /api/admin/media-assets/upload ``` 建议首页内容使用 `group=home`,上传成功后将返回的 `url` 写入对应的 `image` 字段。接口只保存 URL,不接受 base64,也不创建 ProductImage 或商品图片关联。 ## Admin UI 对接要求 1. 进入首页内容管理时分别请求三个列表接口,按 `sortOrder` 渲染对应分组。 2. 体验推荐表单维护 `badge`、`category`、`title`、`englishTitle`、`image` 和 `demandKeyword`。 3. 团队共创表单维护 `tag`、`title`、`description`、`image` 和 `demandKeyword`。 4. 极境视界表单维护 `title`、`image` 和 `demandKeyword`。 5. 三类资源都提供启用/停用、编辑、删除和上移/下移操作;排序时提交完整 ID 列表。 6. 每次变更成功后以接口返回的记录或列表更新本地状态,不直接假设本地修改已经保存。 7. 删除前要求二次确认;删除成功后接受服务端返回的重新排序结果。 8. 处理 `401`、`404`、`409`、`422` 和 `5xx`,保存、上传和排序进行中禁用重复提交。 9. 不在首页表单中出现 Product ID、ProductImage ID、价格、库存、订单或预订字段。 10. 预览点击行为使用 `demandKeyword`;该字段为空时禁止提交,而不是由前端猜测或拼接商品标识。 建议的 Admin UI API 封装函数: ```ts 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[]); ``` 当前首页的“查看更多”按钮固定跳转需求关键词“极境视界”,不属于 `HomeWildArchive` 记录字段;如果未来需要后台配置该按钮,应另行增加首页 CTA 配置契约。 ## 前后台数据边界 - Admin API 返回启用和停用的完整记录,供管理端维护。 - 未来 Public API 只返回已发布且启用的首页内容;Public API 的响应字段应与本契约的渲染字段兼容,但不应暴露 `createdAt`、`updatedAt` 等管理元数据。 - MiniAPP 接入时再同步更新 `src/lib/types.ts`、`src/lib/data.ts`、首页组件和 `docs/public-api.md`;本次文档不改变现有 mock 消费路径。 - 后端不得把 `demandKeyword` 解析成 Product 或订单关联;如需线路、详情或预订能力,应另立领域契约。 ## 后端落地边界 本契约落地时需要由 `WonderQ-Admin` 补充对应 ORM 模型、迁移、schema、路由、序列化和审计日志;由 `WonderQ-Admin-UI` 补充 API 类型、请求封装、首页内容列表、表单、图片上传和排序交互。当前后端没有 `/api/admin/home/*` 路由,Admin UI 也未接入这三类首页数据。 相关文档: - [Admin API 主契约](./admin-api-requirements.md) - [页面模块配置契约](./module-config-api.md) - [Public API 契约](./public-api.md) - [体验推荐数据](../WonderQ-MiniAPP/src/pages/home/components/homeExperienceData.ts) - [团队共创数据](../WonderQ-MiniAPP/src/pages/home/components/homeTeamBuildingData.ts) - [极境视界数据](../WonderQ-MiniAPP/src/pages/home/components/homeWildArchivesData.ts)