From f1fed91675a588d7602f664fc3f3645c9579a857 Mon Sep 17 00:00:00 2001 From: duanshuwen Date: Sat, 4 Jul 2026 10:50:35 +0800 Subject: [PATCH] chore: clean up unused docs and stray system files Remove deprecated project documentation files and accidental system-committed binary files from the repository. --- .../Picface/Cloud/sgim_picface_cloud.bin | Bin 172152 -> 0 bytes .../Picface/Cloud/sgim_picface_cloud_bak.bin | Bin 172152 -> 0 bytes AGENTS.md | 152 ---- docs/README.md | 8 - docs/admin-backend-plan.md | 405 --------- docs/admin-module-config-api.md | 801 ------------------ 6 files changed, 1366 deletions(-) delete mode 100644 %SystemDrive%/ProgramData/SogouInput/Components/Picface/Cloud/sgim_picface_cloud.bin delete mode 100644 %SystemDrive%/ProgramData/SogouInput/Components/Picface/Cloud/sgim_picface_cloud_bak.bin delete mode 100644 AGENTS.md delete mode 100644 docs/README.md delete mode 100644 docs/admin-backend-plan.md delete mode 100644 docs/admin-module-config-api.md diff --git a/%SystemDrive%/ProgramData/SogouInput/Components/Picface/Cloud/sgim_picface_cloud.bin b/%SystemDrive%/ProgramData/SogouInput/Components/Picface/Cloud/sgim_picface_cloud.bin deleted file mode 100644 index 306921d17ea0219b7c0867ebd380f5d568685811..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 172152 zcmeIup$&jQ3 API["Public API"] - Admin["后台管理端"] --> AdminAPI["Admin API"] - API --> DB[("PostgreSQL")] - AdminAPI --> DB - AdminAPI --> OSS["对象存储"] - AdminAPI --> Redis[("Redis")] - CDN["CDN"] --> H5 - CDN --> OSS -``` - -## 后台功能模块 - -### 1. 工作台 - -- 今日新增需求、待跟进需求、待确认订单、上架线路数。 -- 最近发布记录、失败任务、异常日志。 -- 快捷入口:新建线路、新建活动、发布首页、导出需求。 - -### 2. 首页装修 - -对应当前 `src/content.ts`。 - -- 轮播管理:图片、标题、副标题、跳转类型、排序、上下架。 -- 锚点分类:探索世界、主题甄选、精选线路、一生一次、鸿鹄稀缺。 -- 目的地宫格:名称、图片、跳转关键词、排序、是否推荐。 -- 主题卡片:主题名、封面、关联线路或活动专题。 -- 线路分区:分区标题图、分区名称、展示线路、排序规则。 -- 底部 CTA:权益卡、公众号、目的地搜索、需求表单等入口。 -- 页脚与品牌图:图片、服务承诺、备案信息。 -- 预览与发布:草稿预览、定时发布、回滚上一版。 - -### 3. 线路产品管理 - -对应当前 `src/generated-products.json` 和产品详情页。 - -- 基础信息:标题、副标题、目的地、产品类型、价格、起价单位、标签。 -- 封面与图集:列表图、详情头图、行程图片。 -- 详情内容:特色、玩法、美食、行程日程、费用包含/不含、预订须知。 -- 售卖配置:上架状态、推荐权重、活动归属、库存/席位说明。 -- 搜索配置:关键词、别名、目的地关联、主题标签。 -- SEO/分享:分享标题、描述、海报图。 -- 批量能力:导入、导出、批量上下架、批量改标签。 - -### 4. 目的地管理 - -对应当前首页目的地、目的地搜索页和搜索别名逻辑。 - -- 国家/城市/地区层级。 -- 热门目的地、出境/国内分类。 -- 搜索别名:例如 `马代 -> 马尔代夫`、`内蒙 -> 内蒙古`。 -- 目的地封面图、推荐线路、活动专题绑定。 -- 排序和上下架。 - -### 5. 活动专题管理 - -对应当前端午甄选、早鸟尊享、鸿鹄逸游等活动页。 - -- 专题标题、封面、介绍文案、活动时间。 -- 关联线路列表。 -- CTA:提交需求、联系客服、跳转线路。 -- 发布状态:草稿、待审核、已发布、已下线。 -- 合规文案:例如活动免责声明。 - -### 6. 客户需求管理 - -对应当前 `DemandPage`,上线后这是最重要的转化入口。 - -- 表单字段:目的地、手机号、出行时间、预算、人数、备注、来源页面、来源线路。 -- 线索状态:新建、已分配、已联系、方案中、已成交、无效。 -- 客户经理分配:手动分配、自动轮询分配。 -- 跟进记录:电话、微信、备注、下次提醒。 -- 防刷:手机号频控、验证码、IP 风控、黑名单。 -- 导出:Excel/CSV,按时间、状态、顾问筛选。 - -### 7. 客户与订单管理 - -对应当前消息、订单中心的真实业务化。 - -- 客户档案:手机号、姓名、微信、偏好、历史需求。 -- 订单/方案:关联客户、线路、顾问、出行时间、人数、报价、状态。 -- 状态流转:需求单 -> 方案 -> 合同/支付 -> 出行中 -> 已完成/售后。 -- 附件:合同、方案 PDF、发票、签证材料。 -- 订单备注和操作日志。 - -### 8. 消息与客服配置 - -- 后台通知:需求分配、订单状态变化、发布失败。 -- 前台消息:活动通知、顾问消息、订单提醒。 -- 客服入口:在线联系、电话、服务时间、企业微信/IM 配置。 -- 短信:验证码、需求提交确认、顾问跟进提醒。 - -### 9. 媒体库 - -- 图片上传、裁剪、压缩、WebP 转换。 -- 图片分组:轮播、目的地、线路、活动、品牌。 -- 文件引用关系:防止删除正在使用的图片。 -- CDN URL、缩略图、多尺寸版本。 - -### 10. 权限与审计 - -- 角色:超级管理员、运营、产品、客服主管、客户经理、只读审计。 -- 权限粒度:菜单权限、按钮权限、数据范围权限。 -- 审核流:重要内容先提交审核再发布。 -- 操作日志:谁在什么时候改了什么,从什么值改到什么值。 -- 登录安全:强密码、二次验证可选、登录 IP 记录。 - -## 核心数据模型 - -第一期建议覆盖这些表: - -| 表 | 用途 | -| --- | --- | -| `admin_users` | 后台用户 | -| `roles` / `permissions` | RBAC 权限 | -| `media_assets` | 图片和文件资源 | -| `site_versions` | 首页/站点配置发布版本 | -| `home_sections` | 首页模块配置 | -| `hero_slides` | 首页轮播 | -| `destinations` | 目的地 | -| `map_images` | 贵州地图单图配置 | -| `destination_aliases` | 搜索别名 | -| `themes` | 主题甄选 | -| `products` | 线路产品 | -| `product_images` | 产品图片 | -| `product_itineraries` | 行程日程 | -| `product_fee_items` | 费用说明 | -| `campaigns` | 活动专题 | -| `campaign_products` | 专题与线路关联 | -| `leads` | 客户需求/线索 | -| `lead_followups` | 跟进记录 | -| `customers` | 客户档案 | -| `orders` | 订单/方案 | -| `messages` | 前台消息 | -| `audit_logs` | 操作审计 | - -### 示例:产品表 - -```sql -products ( - id uuid primary key, - title text not null, - subtitle text, - destination_id uuid, - price_amount integer, - price_unit varchar(20), - tags text[], - cover_asset_id uuid, - summary text, - status varchar(20), -- draft, published, archived - sort_weight integer default 0, - published_at timestamptz, - created_at timestamptz not null, - updated_at timestamptz not null -) -``` - -### 示例:线索表 - -```sql -leads ( - id uuid primary key, - destination text, - phone varchar(30) not null, - travel_date date, - people_count integer, - budget_min integer, - budget_max integer, - source_page text, - source_product_id uuid, - status varchar(30) not null, - assigned_user_id uuid, - created_at timestamptz not null, - updated_at timestamptz not null -) -``` - -## API 设计 - -### Public API 给 H5 使用 - -- `GET /api/public/site-config`:首页配置、模块排序、底部导航。 -- `GET /api/public/products`:线路列表,支持目的地、主题、活动、关键词筛选。 -- `GET /api/public/products/:id`:线路详情。 -- `GET /api/public/destinations`:目的地和热门分类。 -- `GET /api/public/campaigns/:slug`:活动专题详情。 -- `POST /api/public/leads`:提交出行需求。 -- `POST /api/public/sms/send-code`:发送验证码。 - -### Admin API 给后台使用 - -- `POST /api/admin/auth/login` -- `GET /api/admin/dashboard` -- `CRUD /api/admin/products` -- `CRUD /api/admin/destinations` -- `CRUD /api/admin/campaigns` -- `CRUD /api/admin/site-config/:module`:页面模块配置,覆盖 `heroSlides`、`destinations`、`map`、`themes`、`campaigns`、`ctaBanners` -- `CRUD /api/admin/media-assets` -- `GET /api/admin/leads` -- `PATCH /api/admin/leads/:id/status` -- `POST /api/admin/leads/:id/followups` -- `CRUD /api/admin/orders` -- `GET /api/admin/audit-logs` -- `POST /api/admin/publish` -- `POST /api/admin/rollback` - -## 前台改造路径 - -### 第一步:数据层抽离 - -先不大改 UI,把硬编码内容替换成数据适配层。 - -- 新建 `src/api/client.ts` 和 `src/api/types.ts`。 -- 新建 `src/adapters/siteConfig.ts`,把接口数据转成当前组件需要的结构。 -- 保留本地 JSON fallback,便于本地开发和接口故障降级。 -- 把 `heroSlides`、`destinations`、`map`、`themeCards`、`campaigns`、`sectionHeaders`、`bottomCtas` 从静态 import 改成接口加载。 - -### 第二步:产品和目的地接口化 - -- `generated-products.json` 改为接口 seed 数据。 -- 搜索逻辑从前端 `keywordAliases` 迁移到后端。 -- 产品详情页从 `getDetailMeta` 这类本地推断改为真实详情字段。 - -### 第三步:需求表单真实提交 - -- `DemandPage` 接入 `POST /api/public/leads`。 -- 增加手机号校验、验证码、提交成功页。 -- 后台线索列表能看到来源页面和来源产品。 - -### 第四步:消息与订单真实化 - -- 登录态、客户身份、订单列表需要独立规划。 -- 如果短期不做会员体系,可先保留“提交需求后的查询链接/手机号验证码查询”。 - -## 发布与运维 - -### 发布策略 - -- 内容发布与代码发布分离。 -- 运营修改内容后生成草稿版本。 -- 审核通过后写入 `site_versions`,Public API 默认返回当前发布版本。 -- CDN 缓存使用短 TTL 或发布后主动刷新。 -- 每次发布可回滚到上一版本。 - -### 环境 - -| 环境 | 用途 | -| --- | --- | -| local | 本地开发 | -| dev | 联调环境 | -| staging | 预发布,连接准生产数据或脱敏数据 | -| production | 正式环境 | - -### 监控与备份 - -- API 错误率、响应时间、数据库连接数。 -- 需求提交成功率、短信发送成功率。 -- 每日数据库自动备份,至少保留 14 到 30 天。 -- 对象存储开启版本控制或回收站。 -- 关键操作日志不可被普通管理员删除。 - -### 安全 - -- 后台必须 HTTPS。 -- 管理端登录限流。 -- 所有 Admin API 校验权限。 -- Public API 做参数校验和频控。 -- 手机号等隐私信息后台脱敏展示,导出需要高权限。 -- 防止任意文件上传:限制 MIME、大小、后缀,图片重新编码。 - -## 分阶段交付计划 - -### Phase 1:后台 MVP,约 2 到 3 周 - -目标:运营可以维护首页和线路,H5 可以读取接口。 - -- 搭建 monorepo、API、Admin、数据库。 -- 建立媒体库、产品、目的地、首页配置数据表。 -- 写 seed 脚本,把当前 `content.ts` 和 `generated-products.json` 导入数据库。 -- 后台完成登录、产品列表/编辑、首页配置、图片上传。 -- H5 接入 `site-config` 和 `products`,保留本地 fallback。 - -### Phase 2:线索闭环,约 2 周 - -目标:出行需求可以真实提交、分配、跟进。 - -- 需求表单接口。 -- 后台线索列表、状态流转、跟进记录。 -- 客户经理分配。 -- 短信验证码和提交通知。 -- 导出和基础统计。 - -### Phase 3:活动和发布体系,约 2 到 3 周 - -目标:专题页和首页可以安全发布。 - -- 活动专题管理。 -- 草稿/审核/发布/回滚。 -- 发布日志和操作审计。 -- CDN 刷新。 -- 预发布环境验收。 - -### Phase 4:订单、客户和消息,约 3 到 5 周 - -目标:从线索进一步进入服务履约。 - -- 客户档案。 -- 订单/方案管理。 -- 前台消息和订单查询。 -- 附件上传。 -- 顾问工作台。 - -### Phase 5:生产加固,持续迭代 - -目标:稳定上线维护。 - -- 监控告警、备份恢复演练。 -- 权限细化、数据脱敏。 -- 性能优化和缓存。 -- 自动化测试、CI/CD。 -- 数据分析看板。 - -## MVP 优先级 - -必须先做: - -1. 后台登录与权限。 -2. 媒体库。 -3. 线路产品管理。 -4. 首页配置管理。 -5. H5 读取接口并保留 fallback。 -6. 需求表单真实提交。 -7. 线索后台跟进。 -8. 发布、回滚、操作日志。 - -可以后做: - -1. 完整会员登录。 -2. 真实支付。 -3. 复杂订单履约。 -4. 企业微信深度集成。 -5. 多语言/多品牌站点。 - -## 近期可执行清单 - -1. 建立 `apps/h5`、`apps/admin`、`apps/api` 结构。 -2. 设计 Prisma schema,并先覆盖产品、首页、媒体、目的地、线索。 -3. 编写 seed 脚本,把现有静态内容导入数据库。 -4. 做 Public API,先返回与当前 H5 兼容的数据。 -5. 改造 H5 数据加载层,保证 UI 不变。 -6. 做 Admin 的产品管理和首页装修。 -7. 接通需求表单和后台线索列表。 -8. 补充发布版本、回滚、审计日志。 - -## 风险与建议 - -- 不建议一开始就做完整电商订单和支付,旅游定制业务更适合先做线索和顾问跟进闭环。 -- 不建议让后台直接编辑任意 JSON,短期快但长期容易把数据结构搞乱。 -- 不建议 H5 只依赖实时接口,首页配置应支持发布版本和缓存,避免后台故障影响前台展示。 -- 不建议图片继续只放在代码仓库里,真实上线后要进入对象存储和 CDN。 -- 如果预算有限,第一期可以先用单台云服务器 + PostgreSQL + 对象存储;但备份、HTTPS、日志和权限不能省。 diff --git a/docs/admin-module-config-api.md b/docs/admin-module-config-api.md deleted file mode 100644 index 51e2845..0000000 --- a/docs/admin-module-config-api.md +++ /dev/null @@ -1,801 +0,0 @@ -# 页面模块配置 Admin API 契约 - -本文档定义 WonderQ-Admin 后端需要为 WonderQ-Admin-UI 实现的页面模块配置 CRUD 接口。接口用于维护小程序/H5 前台页面模块中的配置数据,例如首页轮播、目的地宫格、贵州地图、主题卡片、特价优惠、精选线路分组、特色酒店、万趣用车和更多服务。 - -## 适用模块 - -当前一期只纳入结构化页面配置模块: - -| module | 名称 | 用途 | -| -------------- | ------------ | ---------------------------------------------- | -| `heroSlides` | 顶部轮播 | 首页首屏轮播图、标题短文案、展示排序和启用状态 | -| `destinations` | 目的地 | 首页/目的地页展示、搜索入口和热门标记 | -| `map` | 贵州地图 | 首页「探索贵州」区域内的地图图片素材 | -| `themes` | 主题甄选 | 首页主题卡片和跳转 | -| `campaigns` | 特价优惠 | 首页特价优惠活动元信息 | -| `routeSections` | 精选线路子分组 | 首页“精选线路”按运营任务新增分组,维护标题、副文案、启用状态和关联商品 | -| `hotelGroups` | 特色酒店 | 首页“特色酒店”卡片,维护标题、描述、价格、标签、封面图、启用状态和排序 | -| `vehicleOptions` | 万趣用车 | 首页“万趣用车”卡片,维护标题、描述、封面图、启用状态和排序 | -| `ctaBanners` | 更多服务 | 权益、管家、目的地和需求入口等更多服务卡片 | - -商品本体、活动商品池和线索跟进继续走独立业务接口,不混入本契约。`routeSections` 只维护精选线路子分组和商品 ID 关联,不重复编辑商品详情;`hotelGroups` 和 `vehicleOptions` 只维护首页卡片内容,不绑定商品本体。 - -## 通用约定 - -- 所有接口前缀为 `/api/admin`。 -- 所有接口需要校验 `Authorization: Bearer `。 -- 请求和响应均为 `application/json`。 -- 字段使用 camelCase。 -- `PATCH` 为部分更新,只修改请求体中出现的字段。 -- 删除接口返回 JSON,不能返回空 body,因为当前前端请求封装会读取 JSON。 - -错误响应保持当前前端兼容格式: - -```json -{ - "message": "模块不存在或无权限操作", - "code": "MODULE_CONFIG_FORBIDDEN", - "details": {} -} -``` - -## 后端实现重点 - -WonderQ-Admin 后端实现页面模块配置接口时,需要把 `map` 和 `campaigns` 作为正式模块接入,而不是只在前端展示: - -- 模块白名单必须包含 `heroSlides`、`destinations`、`map`、`themes`、`campaigns`、`routeSections`、`hotelGroups`、`vehicleOptions`、`ctaBanners`。 -- 权限校验、模块路由、服务层分发和数据模型映射都必须识别 `map` 和 `campaigns`,否则前端会收到 `MODULE_CONFIG_FORBIDDEN` 并以 toast 展示失败原因。 -- `GET /api/admin/site-config` 即使没有地图数据,也必须返回 `map: []`,不要省略 `map` 字段。 -- `GET /api/admin/site-config` 即使没有特价优惠数据,也必须返回 `campaigns: []`,不要省略 `campaigns` 字段。 -- `GET /api/admin/site-config` 必须返回 `routeSections`,包含未启用分组和后台已配置的全部商品 ID。`hotelGroups`、`vehicleOptions` 也必须稳定返回数组,无数据时返回 `[]`。 -- `map` 只维护一张图片,只需要支持查询、创建、更新、删除,不需要排序接口。 -- `campaigns` 维护活动元信息,只需要支持查询、创建、更新、删除,不需要排序接口。 -- `routeSections` 允许按运营任务新增多个子分组;已创建分组支持更新字段、商品关联、删除和分组顺序。`hotelGroups`、`vehicleOptions` 支持新增、更新、删除和排序。 -- 同一商品不能同时出现在多个 `routeSections` 子分组;更新 `productIds` 时后端需要校验互斥。 -- 图片上传仍走 `POST /api/admin/media-assets/upload`,模块保存接口只接收上传结果里的 OSS `url` 字段并写入 `image`。 - -## 类型定义 - -```ts -type SiteModule = "heroSlides" | "destinations" | "map" | "themes" | "campaigns" | "routeSections" | "hotelGroups" | "vehicleOptions" | "ctaBanners"; - -type SiteItemPatch = { - title?: string; - subtitle?: string | null; - kicker?: string; - name?: string; - slug?: string; - region?: string | null; - label?: string; - alt?: string; - image?: string | null; - description?: string | null; - coverImage?: string | null; - targetType?: string | null; - targetValue?: string | null; - isHot?: boolean; - isActive?: boolean; - sortOrder?: number; - productIds?: string[]; - status?: "draft" | "published"; - startsAt?: string | null; - endsAt?: string | null; -}; - -type HeroSlide = { - id: string; - title: string; - kicker: string | null; - image: string | null; - isActive: boolean; - sortOrder: number; - createdAt?: string; - updatedAt?: string; -}; - -type HeroSlideCreateInput = { - title: string; - kicker?: string | null; - image?: string | null; - isActive?: boolean; - sortOrder?: number; -}; - -type HeroSlideUpdateInput = Partial; - -type MapImage = { - id: string; - image: string | null; - isActive: boolean; - createdAt?: string; - updatedAt?: string; -}; - -type MapImageCreateInput = { - image: string; - isActive?: boolean; -}; - -type MapImageUpdateInput = Partial; - -type Campaign = { - id: string; - slug: string; - title: string; - description: string | null; - coverImage: string | null; - priceAmount: number | null; - priceUnit: string | null; - tags: string[]; - status: "draft" | "published"; - startsAt: string | null; - endsAt: string | null; - createdAt?: string; - updatedAt?: string; -}; - -type CampaignCreateInput = { - title: string; - slug: string; - description?: string | null; - coverImage?: string | null; - priceAmount?: number | null; - priceUnit?: string | null; - tags?: string[]; - status?: "draft" | "published"; - startsAt?: string | null; - endsAt?: string | null; -}; - -type CampaignUpdateInput = Partial; - -type RouteSection = { - id: string; - title: string; - subtitle: string | null; - productIds: string[]; - isActive: boolean; - sortOrder: number; - createdAt?: string; - updatedAt?: string; -}; - -type SiteCardItem = { - id: string; - title: string; - description: string | null; - image: string | null; - isActive: boolean; - sortOrder: number; - createdAt?: string; - updatedAt?: string; -}; - -type HotelGroupItem = SiteCardItem & { - coverImage?: string | null; - priceAmount?: number | null; - priceUnit?: string | null; - tags?: string[]; - status?: "draft" | "published"; -}; - -type CtaBanner = { - id: string; - alt: string; - image: string; - targetType: string | null; - targetValue: string | null; - isActive: boolean; - sortOrder: number; - createdAt?: string; - updatedAt?: string; -}; -``` - -各模块字段要求: - -| module | 创建必填 | 可选字段 | -| -------------- | -------- | ------------------------------------------------------------- | -| `heroSlides` | `title` | `kicker`、`image`、`isActive`、`sortOrder` | -| `destinations` | `name` | `slug`、`region`、`image`、`isHot`、`isActive`、`sortOrder` | -| `map` | `image` | `isActive` | -| `themes` | `label` | `image`、`targetType`、`targetValue`、`isActive`、`sortOrder` | -| `campaigns` | `title`、`slug` | `description`、`coverImage`、`priceAmount`、`priceUnit`、`tags`、`status`、`startsAt`、`endsAt` | -| `routeSections` | `title` | `subtitle`、`productIds`、`isActive`、`sortOrder` | -| `hotelGroups` | `title` | `description`、`image`、`coverImage`、`priceAmount`、`priceUnit`、`tags`、`status`、`isActive`、`sortOrder` | -| `vehicleOptions` | `title` | `description`、`image`、`isActive`、`sortOrder` | -| `ctaBanners` | `alt`(服务标题) | `image`、`targetType`、`targetValue`、`isActive`、`sortOrder` | - -后端可以在创建时补全 `id`、默认 `isActive=true`、默认 `sortOrder=当前模块最后一位`。 -`campaigns` 创建时默认 `status="draft"`,不会进入 Public `site-config.campaigns`;只有 `status="published"` 的活动会进入 H5 Public API。 -Admin UI 面向运营只展示活动标题、活动描述、封面图和前台启用状态;`slug` 是接口必填技术标识,前端可在新建时自动生成,编辑时复用原值。 - -## 顶部轮播 `heroSlides` 专用契约 - -顶部轮播对应当前管理端抽屉中的 3 个区域: - -- 展示内容:`title`、`kicker` -- 资源图片:`image` -- 排序与状态:`sortOrder`、`isActive` - -字段语义: - -| 字段 | 类型 | 创建 | 更新 | 说明 | -| ----------- | ---------------- | -------- | ---------- | ---------------------------------------------------------------------- | -| `id` | `string` | 后端生成 | 不允许修改 | 顶部轮播配置项唯一 id | -| `title` | `string` | 必填 | 可选 | 前台轮播主标题,提交时需要去除首尾空格,不能为空 | -| `kicker` | `string \| null` | 可选 | 可选 | 副标题/短文案,空字符串可归一化为 `null` 或 `""`,前后端需保持响应一致 | -| `image` | `string \| null` | 可选 | 可选 | 单张轮播图地址或素材 URL;未上传时为 `null` | -| `sortOrder` | `number` | 可选 | 可选 | 展示顺序,整数;未传时追加到当前模块末尾 | -| `isActive` | `boolean` | 可选 | 可选 | 前台是否展示;未传时默认 `true` | -| `createdAt` | `string` | 后端生成 | 后端维护 | ISO 时间字符串,可选返回 | -| `updatedAt` | `string` | 后端生成 | 后端维护 | ISO 时间字符串,可选返回 | - -顶部轮播不定义跳转能力。`heroSlides` 的创建、更新、查询响应中不要返回 `targetType`、`targetValue`;兼容期如果请求体携带这两个字段,后端可以忽略,但不要写入顶部轮播业务数据。 - -### 顶部轮播 CRUD - -新增顶部轮播: - -```http -POST /api/admin/site-config/heroSlides -``` - -请求体: - -```json -{ - "title": "新轮播", - "kicker": "贵州小包团首选", - "image": "/assets/source/hero.jpg", - "isActive": true, - "sortOrder": 0 -} -``` - -响应状态码 `201`: - -```json -{ - "id": "slide_001", - "title": "新轮播", - "kicker": "贵州小包团首选", - "image": "/assets/source/hero.jpg", - "isActive": true, - "sortOrder": 0, - "createdAt": "2026-07-01T08:00:00.000Z", - "updatedAt": "2026-07-01T08:00:00.000Z" -} -``` - -更新顶部轮播: - -```http -PATCH /api/admin/site-config/heroSlides/:id -``` - -请求体为 `HeroSlideUpdateInput`,只提交需要修改的字段: - -```json -{ - "title": "夏日贵州小包团", - "image": null, - "isActive": false -} -``` - -响应状态码 `200`,响应体返回更新后的完整 `HeroSlide`。 - -删除顶部轮播: - -```http -DELETE /api/admin/site-config/heroSlides/:id -``` - -响应状态码 `200`: - -```json -{ - "id": "slide_001" -} -``` - -删除后后端需要重新整理 `heroSlides` 内剩余项的 `sortOrder`。 - -调整顶部轮播顺序: - -```http -PATCH /api/admin/site-config/heroSlides/reorder -``` - -请求体: - -```json -{ - "itemIds": ["slide_002", "slide_001", "slide_003"] -} -``` - -响应状态码 `200`: - -```json -{ - "items": [ - { - "id": "slide_002", - "title": "第二张", - "kicker": null, - "image": null, - "sortOrder": 0, - "isActive": true - }, - { - "id": "slide_001", - "title": "第一张", - "kicker": null, - "image": null, - "sortOrder": 1, - "isActive": true - } - ] -} -``` - -## 贵州地图 `map` 专用契约 - -贵州地图模块对应当前管理端抽屉中的 2 个区域: - -- 地图图片:`image` -- 显示状态:`isActive` - -字段语义: - -| 字段 | 类型 | 创建 | 更新 | 说明 | -| ----------- | ---------------- | -------- | ---------- | ----------------------------------------- | -| `id` | `string` | 后端生成 | 不允许修改 | 地图图片配置项唯一 id | -| `image` | `string \| null` | 必填 | 可选 | 单张地图图片地址或素材 URL,创建时不能为空 | -| `isActive` | `boolean` | 可选 | 可选 | 前台是否展示;未传时默认 `true` | -| `createdAt` | `string` | 后端生成 | 后端维护 | ISO 时间字符串,可选返回 | -| `updatedAt` | `string` | 后端生成 | 后端维护 | ISO 时间字符串,可选返回 | - -贵州地图只维护一张图片,不提供排序能力。`map` 的查询响应返回数组是为了复用现有站点配置结构,但最多返回 1 项。创建第二张地图图片时,后端应返回 409 或改为更新当前唯一图片,具体以后端实现保持一致。贵州地图不定义标题、文案和跳转能力。`map` 的创建、更新、查询响应中不要返回 `title`、`name`、`label`、`alt`、`targetType`、`targetValue`、`sortOrder`。 - -后端推荐策略:`POST /api/admin/site-config/map` 在不存在地图图片时创建;已存在时返回 `409 MAP_IMAGE_ALREADY_EXISTS`,或直接更新当前唯一图片。无论选择哪种策略,都要保证 `PATCH /api/admin/site-config/map/:id` 可以按 id 更新当前图片。 - -### 贵州地图 CRUD - -新增贵州地图图片: - -```http -POST /api/admin/site-config/map -``` - -请求体: - -```json -{ - "image": "https://bucket.oss-cn-example.aliyuncs.com/admin/map/2026/07/01/guizhou-map.webp", - "isActive": false -} -``` - -响应状态码 `201`: - -```json -{ - "id": "map_001", - "image": "https://bucket.oss-cn-example.aliyuncs.com/admin/map/2026/07/01/guizhou-map.webp", - "isActive": false, - "createdAt": "2026-07-01T08:00:00.000Z", - "updatedAt": "2026-07-01T08:00:00.000Z" -} -``` - -更新贵州地图图片: - -```http -PATCH /api/admin/site-config/map/:id -``` - -请求体为 `MapImageUpdateInput`,只提交需要修改的字段。 - -删除贵州地图图片: - -```http -DELETE /api/admin/site-config/map/:id -``` - -## 特价优惠 `campaigns` 专用契约 - -特价优惠模块对应 H5 Public API 的 `site-config.campaigns`,只维护活动元信息,不维护活动商品关联列表。 - -字段语义: - -| 字段 | 类型 | 创建 | 更新 | 说明 | -| --- | --- | --- | --- | --- | -| `id` | `string` | 后端生成 | 不允许修改 | 活动唯一 id | -| `slug` | `string` | 必填 | 可选 | 活动标识,需要全局唯一 | -| `title` | `string` | 必填 | 可选 | 活动标题,后台列表主标题 | -| `description` | `string \| null` | 可选 | 可选 | 活动描述,后台列表副文案 | -| `coverImage` | `string \| null` | 可选 | 可选 | 活动封面图 OSS URL | -| `priceAmount` | `number \| null` | 可选 | 可选 | 参考起价,单位按 `priceUnit` 展示 | -| `priceUnit` | `string \| null` | 可选 | 可选 | 价格单位文案,默认 `起/人` | -| `tags` | `string[]` | 可选 | 可选 | 活动卡片标签,最多 3 个 | -| `status` | `"draft" \| "published"` | 可选 | 可选 | 新建默认 `draft`;Public API 只返回 `published` | -| `startsAt` | `string \| null` | 可选 | 可选 | 活动开始时间 | -| `endsAt` | `string \| null` | 可选 | 可选 | 活动结束时间 | -| `createdAt` | `string` | 后端生成 | 后端维护 | ISO 时间字符串,可选返回 | -| `updatedAt` | `string` | 后端生成 | 后端维护 | ISO 时间字符串,可选返回 | - -### 特价优惠 CRUD - -新增特价优惠: - -```http -POST /api/admin/site-config/campaigns -``` - -请求体: - -```json -{ - "slug": "classic-deal", - "title": "经典打卡特惠", - "description": "经典首游活动", - "coverImage": "https://bucket.oss-cn-example.aliyuncs.com/admin/campaigns/2026/07/02/classic.webp", - "priceAmount": 162500, - "priceUnit": "起/人", - "tags": ["臻藏旅位", "赛事庆典"], - "status": "draft", - "startsAt": null, - "endsAt": null -} -``` - -更新特价优惠: - -```http -PATCH /api/admin/site-config/campaigns/:id -``` - -删除特价优惠: - -```http -DELETE /api/admin/site-config/campaigns/:id -``` - -特价优惠不定义 `sortOrder`、`isActive`、`targetType` 和 `targetValue`;兼容期如果请求体携带这些字段,后端可以忽略,但不要写入 `Campaign` 业务数据。 -`tags` 保存前需要 trim 并过滤空字符串;有效标签超过 3 个时返回 `422 MODULE_CONFIG_VALIDATION_ERROR`,`details` 为 `{ "field": "tags", "max": 3 }`。 - -## 精选线路子分组 `routeSections` 专用契约 - -`routeSections` 对应首页“精选线路”的运营分组。运营可按任务新增分组,并维护分组标题、副文案、启用状态、分组顺序、关联线路商品和商品顺序。 - -字段语义: - -| 字段 | 类型 | 更新 | 说明 | -| --- | --- | --- | --- | -| `id` | `string` | 不允许修改 | 系统生成分组 ID | -| `title` | `string` | 可选 | 分组标题,不能为空 | -| `subtitle` | `string \| null` | 可选 | 分组副文案 | -| `productIds` | `string[]` | 可选 | 关联商品 ID,按数组顺序展示 | -| `isActive` | `boolean` | 可选 | 用户侧是否展示该分组 | -| `sortOrder` | `number` | 可选 | 分组展示顺序 | - -更新分组: - -```http -POST /api/admin/site-config/routeSections -``` - -请求体示例: - -```json -{ - "title": "经典人文打卡线路", - "subtitle": "黄果树、荔波小七孔、千户苗寨、镇远古城、梵净山一次串联" -} -``` - -响应状态码 `201`,返回创建后的子分组。`id` 由后端生成;精选线路分组按运营任务动态新增,不再限制为固定三组,也不再使用 `routes` / `routes-outdoor` / `routes-mix` 作为固定槽位。 - -```http -PATCH /api/admin/site-config/routeSections/:id -``` - -请求体示例: - -```json -{ - "title": "经典人文打卡线路", - "subtitle": "黄果树、荔波小七孔、千户苗寨、镇远古城、梵净山一次串联", - "isActive": true, - "productIds": ["product-uuid-1", "product-uuid-2"] -} -``` - -调整分组顺序: - -```http -PATCH /api/admin/site-config/routeSections/reorder -``` - -约束: - -- `productIds` 中的商品必须存在。 -- 同一商品不能出现在其他精选线路子分组;冲突时返回 `409 ROUTE_SECTION_PRODUCT_CONFLICT`。 -- `DELETE /api/admin/site-config/routeSections/:id` 删除分组配置并返回被删除 ID;只移除首页分组,不删除关联商品本体。 - -## 特色酒店 `hotelGroups` 专用契约 - -`hotelGroups` 对应首页“特色酒店”卡片。它复用“特价优惠”的数据配置体验,支持新增、编辑、删除、排序,并维护标题、描述、价格、标签、封面图、启用状态;不维护商品详情和商品关联。 - -字段语义: - -| 字段 | 类型 | 创建 | 更新 | 说明 | -| --- | --- | --- | --- | --- | -| `id` | `string` | 后端生成 | 不允许修改 | 酒店卡片配置项 ID | -| `title` | `string` | 必填 | 可选 | 酒店卡片标题,保存时去掉首尾空格,不能为空 | -| `description` | `string \| null` | 可选 | 可选 | 酒店卡片描述,空字符串可归一为 `null` | -| `image` | `string \| null` | 可选 | 可选 | 兼容旧字段;后端应与 `coverImage` 保持一致 | -| `coverImage` | `string \| null` | 可选 | 可选 | 酒店封面图主字段,管理端上传组件写入该字段 | -| `priceAmount` | `number \| null` | 可选 | 可选 | 价格数值 | -| `priceUnit` | `string \| null` | 可选 | 可选 | 价格单位文案,默认建议 `起/晚` | -| `tags` | `string[]` | 可选 | 可选 | 标签数组,最多 3 个,保存时去掉空标签 | -| `status` | `"draft" \| "published"` | 可选 | 可选 | 发布状态;管理端“前台启用”开关同步维护该字段 | -| `isActive` | `boolean` | 可选 | 可选 | 前台启用状态;Public API 只返回 `status="published"` 且 `isActive=true` 的项 | -| `sortOrder` | `number` | 可选 | 可选 | 首页展示顺序 | - -```http -POST /api/admin/site-config/hotelGroups -PATCH /api/admin/site-config/hotelGroups/:id -DELETE /api/admin/site-config/hotelGroups/:id -PATCH /api/admin/site-config/hotelGroups/reorder -``` - -删除只删除首页酒店卡片配置,不删除任何商品、目的地或素材库资源。Public API 只返回 `published` 且启用项,并按 `sortOrder` 升序输出;无启用项时 MiniAPP 使用本地 `src/content.ts` 兜底内容。 - -## 万趣用车 `vehicleOptions` 专用契约 - -`vehicleOptions` 对应首页“万趣用车”卡片,仍是普通首页内容卡片,不维护商品详情和商品关联。 - -字段语义: - -| 字段 | 类型 | 创建 | 更新 | 说明 | -| --- | --- | --- | --- | --- | -| `id` | `string` | 后端生成 | 不允许修改 | 用车卡片配置项 ID | -| `title` | `string` | 必填 | 可选 | 卡片标题,保存时去掉首尾空格,不能为空 | -| `description` | `string \| null` | 可选 | 可选 | 卡片描述,空字符串可归一为 `null` | -| `image` | `string \| null` | 可选 | 可选 | 卡片封面图 URL | -| `isActive` | `boolean` | 可选 | 可选 | 前台启用状态 | -| `sortOrder` | `number` | 可选 | 可选 | 首页展示顺序 | -| `createdAt` | `string` | 后端生成 | 后端维护 | ISO 时间字符串,可选返回 | -| `updatedAt` | `string` | 后端生成 | 后端维护 | ISO 时间字符串,可选返回 | - -```http -POST /api/admin/site-config/vehicleOptions -PATCH /api/admin/site-config/vehicleOptions/:id -DELETE /api/admin/site-config/vehicleOptions/:id -PATCH /api/admin/site-config/vehicleOptions/reorder -``` - -删除只删除首页用车卡片配置,不删除任何商品、目的地或素材库资源。Public API 只返回启用项,并按 `sortOrder` 升序输出;无启用项时 MiniAPP 使用本地 `src/content.ts` 兜底内容。 - -## 更多服务 `ctaBanners` 专用契约 - -`ctaBanners` 对应首页“更多服务”模块,用于维护权益、管家、目的地和需求入口等服务卡片。管理端复用顶部轮播的列表式操作:新增卡片、编辑标题和背景图、删除卡片、上移/下移排序,以及控制前台启用状态。 - -字段语义: - -| 字段 | 类型 | 创建 | 更新 | 说明 | -| --- | --- | --- | --- | --- | -| `id` | `string` | 后端生成 | 不允许修改 | 服务卡片唯一 id | -| `alt` | `string` | 必填 | 可选 | 服务标题,展示在“更多服务”卡片上,提交时 trim 后不能为空 | -| `image` | `string \| null` | 可选 | 可选 | 服务卡片背景图 OSS URL;上传仍走媒体接口 | -| `targetType` | `string \| null` | 可选 | 可选 | 点击目标类型,例如权益、管家、目的地或需求入口 | -| `targetValue` | `string \| null` | 可选 | 可选 | 点击目标值;无额外参数时可为空 | -| `isActive` | `boolean` | 可选 | 可选 | 用户侧是否展示;未传默认 `true` | -| `sortOrder` | `number` | 可选 | 可选 | 展示顺序;未传时追加到模块末尾 | - -复用通用接口: - -```http -POST /api/admin/site-config/ctaBanners -PATCH /api/admin/site-config/ctaBanners/:id -DELETE /api/admin/site-config/ctaBanners/:id -PATCH /api/admin/site-config/ctaBanners/reorder -``` - -删除只删除首页“更多服务”卡片配置,不删除任何素材库资源。Public API 只返回启用项,并按 `sortOrder` 升序输出;无启用项时 MiniAPP 使用本地 `src/content.ts` 兜底内容。 - -## 接口列表 - -以下路径由九类页面模块复用;`heroSlides`、`map`、`campaigns`、`routeSections`、`hotelGroups`、`vehicleOptions`、`ctaBanners` 的请求体和响应体以各自专用契约为准。 - -### 获取完整站点配置 - -```http -GET /api/admin/site-config -``` - -响应: - -```ts -type SiteConfig = { - heroSlides: HeroSlide[]; - destinations: Destination[]; - map: MapImage[]; - themes: ThemeCard[]; - campaigns: Campaign[]; - routeSections: RouteSection[]; - hotelGroups: HotelGroupItem[]; - vehicleOptions: SiteCardItem[]; - ctaBanners: CtaBanner[]; -}; -``` - -`map` 字段必须稳定返回数组;无数据时返回空数组 `[]`。 -`campaigns` 字段必须稳定返回数组;无数据时返回空数组 `[]`。 -`routeSections` 字段必须稳定返回数组;无数据时返回 `[]`,由管理端通过“新增”逐个创建子分组。`hotelGroups`、`vehicleOptions` 字段也必须稳定返回数组;无数据时返回 `[]`。 - -### 新增模块配置项 - -```http -POST /api/admin/site-config/:module -``` - -请求体为 `SiteItemPatch`。响应状态码 `201`,响应体返回创建后的完整配置项: - -```json -{ - "id": "slide_001", - "title": "新轮播", - "kicker": "", - "image": null, - "isActive": true, - "sortOrder": 5 -} -``` - -### 更新模块配置项 - -```http -PATCH /api/admin/site-config/:module/:id -``` - -请求体为 `SiteItemPatch`。响应状态码 `200`,响应体返回更新后的完整配置项。 - -### 删除模块配置项 - -```http -DELETE /api/admin/site-config/:module/:id -``` - -响应状态码 `200`,响应体: - -```json -{ - "id": "slide_001" -} -``` - -删除后后端需要重新整理同模块内剩余项的 `sortOrder`。 - -### 调整模块配置顺序 - -```http -PATCH /api/admin/site-config/:module/reorder -``` - -请求体: - -```json -{ - "itemIds": ["slide_002", "slide_001", "slide_003"] -} -``` - -约束: - -- `itemIds` 必须包含该模块当前全部配置项 id。 -- 不允许重复 id。 -- 不允许混入其他模块 id。 -- 后端按数组顺序写入 `sortOrder`,从 0 开始。 - -`map` 和 `campaigns` 模块不提供排序能力。若收到 `PATCH /api/admin/site-config/map/reorder` 或 `PATCH /api/admin/site-config/campaigns/reorder`,后端应返回 `400` 或 `405`,不要创建任何排序数据。`routeSections`、`hotelGroups`、`vehicleOptions` 支持排序,但 `itemIds` 必须刚好包含当前已保存的同模块配置项 ID。 - -响应状态码 `200`: - -```json -{ - "items": [ - { "id": "slide_002", "title": "第二张", "sortOrder": 0, "isActive": true }, - { "id": "slide_001", "title": "第一张", "sortOrder": 1, "isActive": true } - ] -} -``` - -如果后端使用 Express/Fastify 等路由,`/:module/reorder` 需要注册在 `/:module/:id` 之前,避免 `reorder` 被当成 id。 - -## 状态码 - -| 状态码 | 场景 | -| ------ | ------------------------------------------------ | -| `200` | 查询、更新、删除、排序成功 | -| `201` | 创建成功 | -| `400` | module 非法、请求体格式错误、排序 id 不完整 | -| `401` | 未登录或 token 无效 | -| `403` | 无权限维护页面配置 | -| `404` | 配置项不存在 | -| `409` | 删除被发布版本、商品或活动引用的配置项时发生冲突 | -| `422` | 字段校验失败,例如必填标题为空 | -| `500` | 服务端异常 | - -## 前端联调入口 - -WonderQ-Admin-UI 当前调用函数位于 `src/api.ts`: - -- `getSiteConfig()` -- `createSiteConfigItem(module, input)` -- `updateSiteConfigItem(module, id, input)` -- `deleteSiteConfigItem(module, id)` -- `reorderSiteConfigItems(module, itemIds)` - -维护地图 UI 位于 `src/pages/structure/StructurePage.tsx`。 - -## 图片素材上传 - -用于 WonderQ-Admin-UI 在维护页面模块图片时上传本地图片,并把返回的 OSS URL 写入对应模块图片字段,例如 `image` 或 `coverImage`。 - -```http -POST /api/admin/media-assets/upload -Content-Type: multipart/form-data -Authorization: Bearer -``` - -表单字段: - -| 字段 | 类型 | 必填 | 说明 | -| --- | --- | --- | --- | -| `file` | `File` | 是 | 图片文件,仅支持 JPG、PNG、WebP、GIF | -| `group` | `string` | 否 | 素材分组,只能包含字母、数字、下划线和中划线;默认 `general` | - -限制: - -- 单个文件最大 5MB。 -- 后端会校验 `Content-Type` 和文件头魔数,拒绝 SVG、非图片文件和伪装类型。 -- 后端将文件上传到 OSS,返回可用于前台展示的 URL,并写入 `MediaAsset` 素材记录。 - -响应状态码 `201`: - -```json -{ - "id": "asset_001", - "url": "https://bucket.oss-cn-example.aliyuncs.com/admin/heroSlides/2026/07/01/example.png", - "name": "hero.png", - "mimeType": "image/png", - "sizeBytes": 102400, - "group": "heroSlides", - "createdAt": "2026-07-01T08:00:00", - "updatedAt": "2026-07-01T08:00:00" -} -``` - -常见错误: - -| 状态码 | `code` | 场景 | -| --- | --- | --- | -| `400` | `MEDIA_UPLOAD_INVALID_TYPE` | 文件不是允许的图片类型 | -| `400` | `MEDIA_UPLOAD_TYPE_MISMATCH` | `Content-Type` 与文件头不一致 | -| `400` | `MEDIA_UPLOAD_INVALID_GROUP` | `group` 格式非法 | -| `413` | `MEDIA_UPLOAD_TOO_LARGE` | 文件超过 5MB | -| `503` | `MEDIA_STORAGE_NOT_CONFIGURED` | OSS 环境配置不完整 | -| `502` | `MEDIA_STORAGE_UPLOAD_FAILED` | OSS 上传失败 | - -前端封装位于 `src/api.ts`: - -```ts -uploadMediaAsset(file: File, group?: string): Promise -``` - -`src/components/admin/SingleImageUploader.tsx` 已使用该封装;结构维护页会把当前模块 id 作为 `group` 上传。