feat: 新增路线详情关联管家顾问及相关功能
详细变更如下: - 更新 .gitignore 文件,添加 pnpm-store 忽略规则 - 新增数据库迁移脚本,为 DetailRecord 添加可空的 conciergeAdvisorId 字段用于关联管家顾问 - 完善 Admin 后台玩法详情编辑器,支持选择关联的管家顾问并校验合法性 - 公共 API 支持返回已启用的管家顾问完整数据,不在详情表中冗余存储管家资料 - 小程序端新增详情页联系管家入口、个人页最近浏览历史功能 - 更新所有相关文档与测试用例,修复下拉选择框的 z-index 样式问题
This commit is contained in:
@@ -23,6 +23,12 @@ Admin 顾问记录的 `id` 为服务端生成的稳定 UUID 字符串;MiniAPP
|
||||
- 订单、预订、商品和商品图片关联。
|
||||
- 管家页面 Hero 文案和服务原则内容。
|
||||
|
||||
## 路线详情关联
|
||||
|
||||
路线详情可以关联一名管家顾问。`DetailRecord` 只保存可空的 `conciergeAdvisorId`,不建立数据库外键,也不复制头像、二维码或服务详情。玩法详情由详情接口维护,管家资料仍由本领域的 Admin CRUD 和 Public API 维护。
|
||||
|
||||
`GET /api/public/details/{key}` 会在关联顾问存在且启用时嵌入最新的 `conciergeAdvisor`;未配置、顾问已删除或已停用时返回 `conciergeAdvisor: null`,不会阻塞路线详情读取。Admin UI 选择顾问时使用本接口返回的稳定 `id`,清空选择即解除关联。
|
||||
|
||||
当前 `WonderQ-MiniAPP/src/pages/concierge/index.vue` 中的 Hero 和服务原则仍是本地内容;顾问卡片由 Admin UI 维护,MiniAPP 通过独立的 Public API 消费已启用顾问。
|
||||
|
||||
## 接口清单
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
- 详情页识别键、标题眉标、出行时长和标题文案。
|
||||
- 详情介绍、行程亮点、费用包含、费用不含和注意事项。
|
||||
- 详情页图片画廊及图片顺序。
|
||||
- 详情页可选的联系管家顾问 ID;顾问资料仍由管家领域维护。
|
||||
- 启用状态和详情列表顺序。
|
||||
|
||||
本接口不负责:
|
||||
@@ -18,7 +19,7 @@
|
||||
- 商品、商品价格、商品库存或商品详情表。
|
||||
- Product、ProductImage 或任何商品外键。
|
||||
- 订单、预订、收藏、评价或线索。
|
||||
- 详情页底部的电话、管家联系和预订动作。
|
||||
- 管家顾问的头像、二维码、服务详情和管家 CRUD;详情只保存顾问 ID。
|
||||
|
||||
`key` 固定使用玩法路线 ID(即服务端生成的 `WanfaRoute.id` UUID),但 `DetailRecord` 不建立数据库外键。详情页通过 `/pages/detail/index?routeId={key}` 定位内容;详情记录删除不会跨领域级联删除路线或其他数据。
|
||||
|
||||
@@ -80,6 +81,7 @@ type DetailPresentation = {
|
||||
type DetailRecord = DetailPresentation & {
|
||||
id: string;
|
||||
key: string;
|
||||
conciergeAdvisorId: string | null;
|
||||
isActive: boolean;
|
||||
sortOrder: number;
|
||||
createdAt: string;
|
||||
@@ -88,10 +90,20 @@ type DetailRecord = DetailPresentation & {
|
||||
|
||||
type PublicDetail = Omit<DetailPresentation, "key"> & {
|
||||
key: string;
|
||||
conciergeAdvisor: PublicConciergeAdvisor | null;
|
||||
};
|
||||
|
||||
type PublicConciergeAdvisor = {
|
||||
avatar: string;
|
||||
name: string;
|
||||
role: string;
|
||||
details: Array<{ icon: string; label: string }>;
|
||||
qrImage: string;
|
||||
};
|
||||
|
||||
type DetailCreate = DetailPresentation & {
|
||||
key: string;
|
||||
conciergeAdvisorId?: string | null;
|
||||
isActive?: boolean;
|
||||
sortOrder?: number;
|
||||
};
|
||||
@@ -123,6 +135,7 @@ type DetailReorderRequest = {
|
||||
| `excluded` | `string[]` | 是 | “费用不含”列表,保留数组顺序。 |
|
||||
| `notes` | `string[]` | 是 | “注意事项”列表,保留数组顺序。 |
|
||||
| `gallery` | `string[]` | 是 | 详情图片 URL 列表,按展示顺序返回;建议最多 6 张以匹配当前前台逻辑。 |
|
||||
| `conciergeAdvisorId` | `string \| null` | 否 | 关联的管家顾问 ID;不建立数据库外键,空字符串保存为 `null`。 |
|
||||
| `isActive` | `boolean` | 响应必填 | 是否进入已发布前台内容,创建默认 `true`。 |
|
||||
| `sortOrder` | `number` | 响应必填 | 非负整数,数值越小越靠前;创建时未传则追加到末尾。 |
|
||||
| `createdAt` | `string` | 响应必填 | ISO 8601 创建时间。 |
|
||||
@@ -187,7 +200,7 @@ Authorization: Bearer <admin-jwt>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
请求体为 `DetailCreate`。成功返回 `201` 和 `data` 内新建的 `DetailRecord`;未传 `sortOrder` 时追加到当前列表末尾。
|
||||
请求体为 `DetailCreate`。成功返回 `201` 和 `data` 内新建的 `DetailRecord`;未传 `sortOrder` 时追加到当前列表末尾。`conciergeAdvisorId` 传空字符串时归一化为 `null`,传入不存在的顾问 ID 返回 `422 DETAIL_CONCIERGE_ADVISOR_INVALID`。
|
||||
|
||||
### 编辑详情
|
||||
|
||||
@@ -267,10 +280,11 @@ type PublicDetail = {
|
||||
excluded: string[];
|
||||
notes: string[];
|
||||
gallery: string[];
|
||||
conciergeAdvisor: PublicConciergeAdvisor | null;
|
||||
};
|
||||
```
|
||||
|
||||
不存在、停用或未配置详情时返回 `404`。MiniAPP 请求失败或字段不完整时,按 `key` 查找本地 fallback;找不到 fallback 时展示未找到和重试状态。该页面暂不提供价格、收藏、在线订阅、预订、订单或管家联系动作。
|
||||
不存在、停用或未配置详情时返回 `404`。详情未配置顾问、顾问不存在或顾问已停用时,详情仍正常返回,但 `conciergeAdvisor` 为 `null`。MiniAPP 请求失败或字段不完整时,按 `key` 查找本地 fallback;fallback 不伪造管家数据,找不到 fallback 时展示未找到和重试状态。详情页仅在返回有效顾问时展示“联系管家”入口。
|
||||
|
||||
## 图片与素材
|
||||
|
||||
@@ -288,14 +302,14 @@ POST /api/admin/media-assets/upload
|
||||
|
||||
Admin UI 应按以下方式调用:
|
||||
|
||||
1. 玩法页加载时同时调用玩法分类和 `GET /api/admin/details`;详情编辑器嵌入现有路线编辑抽屉,不新增侧边菜单。
|
||||
1. 玩法页加载时同时调用玩法分类、`GET /api/admin/details` 和 `GET /api/admin/concierge/advisors`;详情编辑器嵌入现有路线编辑抽屉,不新增侧边菜单。
|
||||
2. 新增和编辑表单维护眉标、时长、标题、副标题、介绍和四组列表文案;保存路线时以已保存的路线 ID 作为详情 `key`。
|
||||
3. `highlights`、`included`、`excluded`、`notes` 使用可增删的重复字段编辑器,提交时保留数组顺序,不拼接成换行字符串。
|
||||
4. 使用图片上传接口维护 `gallery`,支持新增、删除和调整图片顺序。
|
||||
5. 上移或下移详情时提交完整详情 ID 列表,不直接修改本地 `sortOrder` 后假设保存成功。
|
||||
6. 删除前要求二次确认;删除成功后以接口返回或重新查询的数据更新列表。
|
||||
7. 处理 `401`、`404`、`409`、`422` 和 `5xx`,保存、上传或排序进行中禁用重复提交。
|
||||
8. 详情编辑器不得出现 Product ID、ProductImage ID、库存、订单、价格或预订字段;详情保存失败时明确提示路线摘要已保存、详情需要重试。
|
||||
8. 详情编辑器不得出现 Product ID、ProductImage ID、库存、订单、价格或预订字段;联系管家使用顾问 ID 选择器;详情保存失败时明确提示路线摘要已保存、详情需要重试。
|
||||
|
||||
建议的 Admin UI API 封装函数:
|
||||
|
||||
@@ -310,7 +324,7 @@ reorderDetails(itemIds: string[]);
|
||||
|
||||
## 与前台展示模型的映射
|
||||
|
||||
Admin API 返回的 `DetailRecord` 可以映射为 `DetailPresentation`:
|
||||
Public API 返回的 `PublicDetail` 可以直接映射为 MiniAPP 的 `DetailPresentation`;Admin API 返回的 `DetailRecord` 只包含 `conciergeAdvisorId`,不复制管家资料:
|
||||
|
||||
```ts
|
||||
const presentation: DetailPresentation = {
|
||||
@@ -325,6 +339,7 @@ const presentation: DetailPresentation = {
|
||||
excluded: record.excluded,
|
||||
notes: record.notes,
|
||||
gallery: record.gallery,
|
||||
conciergeAdvisor: publicDetail.conciergeAdvisor ?? null,
|
||||
};
|
||||
```
|
||||
|
||||
@@ -332,7 +347,7 @@ const presentation: DetailPresentation = {
|
||||
|
||||
## 后端落地边界
|
||||
|
||||
当前实现由 `WonderQ-Admin` 的 `DetailRecord` 模型、`0021_detail_records` 迁移、Admin/Public 路由和审计日志提供能力;由 `WonderQ-Admin-UI` 在玩法路线编辑抽屉中维护详情;由 `WonderQ-MiniAPP` 调用 Public 详情接口并按路线 ID fallback。迁移文件只负责生成和初始化详情记录,不自动执行数据库升级。
|
||||
当前实现由 `WonderQ-Admin` 的 `DetailRecord` 模型、`0021_detail_records`、`0022_opaque_ids` 和 `0023_detail_concierge_advisor` 迁移、Admin/Public 路由和审计日志提供能力;由 `WonderQ-Admin-UI` 在玩法路线编辑抽屉中维护详情及顾问 ID;由 `WonderQ-MiniAPP` 调用 Public 详情接口并按路线 ID fallback。迁移文件只负责生成和初始化详情记录,不自动执行数据库升级。顾问资料始终从管家领域实时读取,不复制到详情表。
|
||||
|
||||
相关文档:
|
||||
|
||||
|
||||
@@ -94,10 +94,11 @@ MiniAPP 联调重点:
|
||||
路线详情使用独立 `DetailRecord`,不修改 `WanfaRoute` 表结构,也不建立商品、订单或预订关联。联调顺序如下:
|
||||
|
||||
1. 执行数据库迁移,确认 `0021_detail_records` 已创建详情表并为已有路线生成基础记录;确认 `0022_opaque_ids` 已将历史语义 ID 转换为稳定 UUID,并同步玩法外键、详情 `key` 和审计引用。生产环境执行前按迁移规范单独确认。
|
||||
2. 在 Admin UI 进入“玩法”,编辑路线摘要和详情字段,保存时先保存路线,再以路线 ID 作为 `DetailRecord.key` 创建或更新详情。
|
||||
2. 在 Admin UI 进入“玩法”,编辑路线摘要、详情字段和联系管家,保存时先保存路线,再以路线 ID 作为 `DetailRecord.key` 创建或更新详情;顾问选项来自 `GET /api/admin/concierge/advisors`。
|
||||
3. 检查 `GET /api/public/wanfa/categories` 仍只返回路线摘要;检查 `GET /api/public/home` 的玩法推荐携带关联路线摘要。
|
||||
4. 在 MiniAPP 首页玩法推荐或玩法页点击路线,确认跳转 `/pages/detail/index?routeId={routeId}`,并请求 `GET /api/public/details/{routeId}`。
|
||||
5. 修改 Admin UI 的详情内容后刷新 MiniAPP,确认标题、正文、费用说明、注意事项和画廊更新;停用或删除详情时确认 Public API 返回 `404`。
|
||||
5. 修改 Admin UI 的详情内容或联系管家后刷新 MiniAPP,确认标题、正文、费用说明、注意事项、画廊和顾问资料更新;停用或删除详情时确认 Public API 返回 `404`。
|
||||
6. 在管家页面停用或删除已关联顾问,再刷新路线详情,确认详情仍可读取且 `conciergeAdvisor` 为 `null`,联系入口隐藏;重新启用顾问后确认入口和弹窗资料恢复。
|
||||
6. 关闭后端接口,确认 MiniAPP 按路线 ID 展示本地网络图片和模拟文案,并提示当前为模拟数据;未知路线展示未找到和重试状态。
|
||||
|
||||
## 稳定 ID 联调检查
|
||||
@@ -107,7 +108,7 @@ MiniAPP 联调重点:
|
||||
3. 检查首页玩法推荐的 `categoryId`、路线 `id` 与 `GET /api/public/details/{key}` 的 `key` 关联正确。
|
||||
4. MiniAPP 本地 fallback 的语义 ID 只在接口失败时使用,不得覆盖接口成功返回的 UUID。
|
||||
|
||||
路线详情页当前不包含价格、收藏、在线订阅、预订、订单或管家联系动作。字段和错误约定以 `detail-api.md`、`public-api.md` 为准。
|
||||
路线详情页不包含在线订阅、收藏、预订或订单动作;联系管家入口由 `conciergeAdvisor` 是否有效决定。字段和错误约定以 `detail-api.md`、`public-api.md` 和 `concierge-api.md` 为准。
|
||||
|
||||
## 接口变更流程
|
||||
|
||||
|
||||
@@ -93,10 +93,17 @@ type PublicDetail = {
|
||||
excluded: string[];
|
||||
notes: string[];
|
||||
gallery: string[];
|
||||
conciergeAdvisor: {
|
||||
avatar: string;
|
||||
name: string;
|
||||
role: string;
|
||||
details: Array<{ icon: string; label: string }>;
|
||||
qrImage: string;
|
||||
} | null;
|
||||
};
|
||||
```
|
||||
|
||||
不存在、停用或未配置详情返回 `404`。MiniAPP 路线详情页通过 `/pages/detail/index?routeId={key}` 进入;接口失败或字段不完整时按路线 ID 使用本地网络图片和模拟文案。该详情页暂不提供价格、收藏、在线订阅、预订、订单或管家联系动作。
|
||||
不存在、停用或未配置详情返回 `404`。关联管家未配置、已删除或已停用时 `conciergeAdvisor` 为 `null`。MiniAPP 路线详情页通过 `/pages/detail/index?routeId={key}` 进入;接口失败或字段不完整时按路线 ID 使用本地网络图片和模拟文案,fallback 不伪造管家数据。仅当 `conciergeAdvisor` 有效时展示联系管家入口。
|
||||
|
||||
## 管家展示
|
||||
|
||||
|
||||
Reference in New Issue
Block a user