feat: 新增路线详情关联管家顾问及相关功能

详细变更如下:
- 更新 .gitignore 文件,添加 pnpm-store 忽略规则
- 新增数据库迁移脚本,为 DetailRecord 添加可空的 conciergeAdvisorId 字段用于关联管家顾问
- 完善 Admin 后台玩法详情编辑器,支持选择关联的管家顾问并校验合法性
- 公共 API 支持返回已启用的管家顾问完整数据,不在详情表中冗余存储管家资料
- 小程序端新增详情页联系管家入口、个人页最近浏览历史功能
- 更新所有相关文档与测试用例,修复下拉选择框的 z-index 样式问题
This commit is contained in:
duanshuwen
2026-08-22 08:26:23 +08:00
parent e7b8c5bf43
commit b7a81964c9
34 changed files with 739 additions and 63 deletions

View File

@@ -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 消费已启用顾问。
## 接口清单

View File

@@ -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` 查找本地 fallbackfallback 不伪造管家数据,找不到 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。迁移文件只负责生成和初始化详情记录不自动执行数据库升级。顾问资料始终从管家领域实时读取,不复制到详情表。
相关文档:

View File

@@ -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` 为准。
## 接口变更流程

View File

@@ -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` 有效时展示联系管家入口
## 管家展示