主题
管理后台 — 产品管理
上级文档:管理后台 Web
模块概述
产品管理负责健身房所有可售卖产品套餐的全生命周期管理,包括创建、编辑、上下架、价格管理。产品是订单系统的上游,是会员资格验证的依据。
页面路由
| 路由 | 页面 | 说明 |
|---|---|---|
/products | 产品列表 | 所有产品套餐列表,支持筛选与排序 |
/products/new | 新增产品 | 创建新产品套餐 |
/products/:id/edit | 编辑产品 | 编辑已有产品信息 |
产品列表页
列表字段定义
| 列名 | 字段 key | 类型 | 说明 | 排序 |
|---|---|---|---|---|
| 产品 ID | id | String | 系统唯一标识 | — |
| 产品名称 | name | String | 如「月卡」「次卡10次」「新用户体验卡」 | ✅ |
| 产品类型 | type | Enum | 月卡 / 季卡 / 年卡 / 次卡 / 体验卡 | ✅ |
| 售价 | price | Decimal | 当前售价,元 | ✅ |
| 划线价 | originalPrice | Decimal? | 展示用划线价;无则显示 — | — |
| 有效期 | durationDays | Int | 天数;月卡=30,季卡=90,年卡=365 | — |
| 次数 | timesTotal | Int? | 次卡总次数;不限次显示 不限次 | — |
| 适用门店 | storeId | String? | 通用(所有门店可用)/ 门店名称 | — |
| 上架状态 | isOnSale | Boolean | 上架中 / 已下架 | ✅ |
| 排序 | sortOrder | Int | 小程序展示顺序,越小越靠前 | ✅ |
| 已售数量 | soldCount | Int | 该产品已创建的有效订单数(统计值) | — |
| 创建时间 | createdAt | DateTime | YYYY-MM-DD HH:mm:ss | ✅ |
产品类型枚举
| 类型值 | 中文标签 | 说明 |
|---|---|---|
monthly | 月卡 | 有效期内无限次进入 |
quarterly | 季卡 | 有效期内无限次进入 |
yearly | 年卡 | 有效期内无限次进入 |
times | 次卡 | 固定次数,有效期内使用 |
experience | 体验卡 | 首次用户限购 1 次,支持条件退款 |
off_peak | 闲时卡 | 仅限闲时时段进入,价格低于同时长普通卡 |
闲时卡设计
闲时卡是一种特殊的产品类型,允许用户以更优惠的价格购买限时时段使用的会员卡。每个门店可以独立配置自己的闲时时段。
闲时卡与普通卡的区别
| 维度 | 普通卡(月卡/季卡/年卡) | 闲时卡 |
|---|---|---|
| 进入时段 | 全天可用(或产品级时段限制) | 仅限门店配置的闲时时段 |
| 价格 | 标准价 | 低于同时长普通卡(如月卡 ¥99 → 闲时月卡 ¥59) |
| 闲时时段来源 | 产品级配置(全局统一) | 门店级配置(每个门店可独立设置闲时时段) |
| 门店配置 | 无需额外配置 | 门店需先在门店管理中配置闲时时段 |
门店闲时时段配置
门店的闲时时段在门店管理 → 基本信息中配置,属于门店维度的运营配置。
typescript
interface StoreOffPeakConfig {
storeId: string
isEnabled: boolean // 是否启用闲时卡
offPeakSlots: { // 闲时时段列表
dayOfWeek: 1-7 // 周几
startTime: string // 如 "06:00"
endTime: string // 如 "14:00"
}[]
}设计说明:闲时时段由门店维度定义,而非产品维度。原因:不同门店的客流高峰不同,商场店可能下午闲,写字楼店可能上午闲。产品只需标记为「闲时卡」类型,具体时段读取目标门店的配置。
闲时卡产品表单新增字段
| 字段 | key | 类型 | 必填 | 条件显示 | 说明 |
|---|---|---|---|---|---|
| 闲时卡提示文案 | offPeakHint | String | ❌ | 仅类型=闲时卡时显示 | 小程序端展示的闲时说明,如「本卡仅限 06:00-14:00 使用」 |
闲时卡提示文案为多语言字段。若留空,系统根据门店闲时时段自动生成,如「仅限周一至周五 06:00-14:00 可用」。
闲时卡校验规则
- 进店校验:用户刷脸进店时,服务端检查当前时间是否在目标门店的闲时时段内。非闲时时段拒绝开门,返回提示「闲时卡仅在 XX:XX-XX:XX 可用」。
- 闲时时段变更:门店修改闲时时段后,已售出的闲时卡不受影响(按购买时的时段规则执行),新购买的闲时卡按新时段执行。
- 跨门店:闲时卡为通用产品时,各门店的闲时时段可能不同。用户在不同门店使用时,按该门店的闲时时段校验。
- 闲时卡购买前提:门店必须已配置闲时时段,小程序端才展示该门店可用的闲时卡产品。未配置闲时时段的门店,闲时卡不展示。
筛选条件
| 筛选项 | 类型 | 说明 |
|---|---|---|
| 关键词 | 输入框 | 产品名称模糊搜索 |
| 产品类型 | 多选 | 月卡 / 季卡 / 年卡 / 次卡 / 体验卡 / 闲时卡 |
| 上架状态 | 单选 | 全部 / 上架中 / 已下架 |
| 适用门店 | 下拉 | 全部 / 通用 / 指定门店 |
新增 / 编辑产品页
表单字段
基本信息
| 字段 | 组件 | 必填 | 校验规则 | 说明 |
|---|---|---|---|---|
| 产品名称 | 文本输入 | ✅ | 2-20 字符 | 如「月卡」「次卡10次」 |
| 产品类型 | 单选 | ✅ | 枚举值 | 月卡/季卡/年卡/次卡/体验卡 |
| 售价 | 数字输入 | ✅ | ≥ 0.01,2 位小数 | 当前售价 |
| 划线价 | 数字输入 | ❌ | ≥ 售价,2 位小数 | 展示用划线价,营造折扣感 |
有效期与次数
| 字段 | 组件 | 必填 | 校验规则 | 条件显示 |
|---|---|---|---|---|
| 有效期天数 | 数字输入 | ✅ | ≥ 1 | 始终显示 |
| 总次数 | 数字输入 | ❌ | ≥ 1 | 仅类型=次卡/体验卡时显示 |
| 退款窗口(分钟) | 数字输入 | ❌ | ≥ 1 | 仅类型=体验卡时显示 |
[建议值] 体验卡退款窗口默认 30 分钟。
使用限制
| 字段 | 组件 | 必填 | 校验规则 | 说明 |
|---|---|---|---|---|
| 允许进入时段 | 时间范围 | ❌ | — | 如 06:00-22:00;留空=全天可用 |
| 适用门店 | 下拉选择 | ✅ | — | 通用(所有门店)或选择指定门店 |
展示配置
| 字段 | 组件 | 必填 | 校验规则 | 说明 |
|---|---|---|---|---|
| 产品描述 | 多行文本 | ❌ | ≤ 500 字 | 小程序端展示的产品介绍文案 |
| 排序权重 | 数字输入 | ✅ | ≥ 0 | 越小越靠前 |
| 是否上架 | 开关 | ✅ | — | 新建时默认下架,手动上架 |
表单交互规则
- 产品类型切换时,动态显示/隐藏相关字段(总次数、退款窗口)
- 售价修改时,若已有用户购买该产品,需弹出提示:「修改价格不影响已购订单,仅对新订单生效」
- 下架操作:已上架产品点击下架时,弹出确认:「下架后新用户将无法在小程序中看到该产品。已购订单不受影响。确认下架?」
- 删除限制:已产生订单的产品不允许删除,仅支持下架。无订单的产品可删除。
权限控制
| 操作 | 老板 | 财务 | 店长 | 客服 | 说明 |
|---|---|---|---|---|---|
| 查看产品列表 | ✅ | ✅(只读) | ✅(只读) | ❌ | — |
| 新增产品 | ✅ | ❌ | ❌ | ❌ | 产品创建涉及定价策略,仅总部可操作 |
| 编辑产品信息 | ✅ | ❌ | ❌ | ❌ | — |
| 上架/下架 | ✅ | ❌ | ❌ | ❌ | — |
| 删除产品 | ✅ | ❌ | ❌ | ❌ | 无订单的产品才可删除 |
| 查看价格历史 | ✅ | ✅ | ❌ | ❌ | — |
产品管理是全局配置,不受门店上下文影响。但可创建门店专属产品。
价格变更记录
每次修改产品售价时,自动记录价格变更历史,供审计和财务分析使用。
价格历史表结构
| 字段 | 说明 |
|---|---|
| 产品 ID | 关联产品 |
| 变更前价格 | 旧价格 |
| 变更后价格 | 新价格 |
| 操作人 | 管理员账号 |
| 变更时间 | 时间戳 |
| 备注 | 操作备注(可选) |
展示方式:在编辑产品页面底部以时间线形式展示价格变更历史。
小程序展示规则(管理后台侧配置)
| 配置项 | 说明 | 默认值 |
|---|---|---|
| 体验卡限购 | 每个用户仅可购买 1 次体验卡 | [建议值] 开启 |
| 通用产品优先 | 通用产品排在门店专属产品前面 | [建议值] 开启 |
| 排序规则 | 按 sortOrder 升序排列 | 升序 |
| 划线价展示 | 小程序端是否展示划线价 | [建议值] 开启 |
用户故事
US-PRD-01:管理员创建新产品套餐
作为管理员,我想创建一个新的产品套餐,设置名称、类型、价格、有效期,以便在小程序端售卖。
验收标准:
- [ ] 表单校验完整:名称必填、价格 ≥ 0.01、有效期 ≥ 1 天
- [ ] 产品类型切换时动态显示/隐藏相关字段
- [ ] 保存后返回产品列表,新记录出现在列表中
- [ ] 新建产品默认为「已下架」状态
- [ ] 可选择「保存并上架」一键完成
US-PRD-02:管理员调整产品价格
作为管理员,我想修改现有产品的售价,系统应记录价格变更历史,且不影响已购买用户。
验收标准:
- [ ] 修改价格后,已存在的订单不受影响
- [ ] 自动生成价格变更记录(变更前/变更后/操作人/时间)
- [ ] 编辑页底部可查看价格变更历史时间线
- [ ] 若产品已售出,修改价格时弹出提示
US-PRD-03:管理员下架产品
作为管理员,我想下架某个产品套餐,使新用户无法在小程序看到和购买。
验收标准:
- [ ] 下架操作需二次确认
- [ ] 下架后小程序端不再展示该产品
- [ ] 已购买该产品的用户权益不受影响
- [ ] 下架产品可随时重新上架
US-PRD-04:运营人员查看各产品销售情况
作为运营人员,我想在产品列表中看到各产品的已售数量,以便了解哪些产品更受欢迎。
验收标准:
- [ ] 产品列表展示「已售数量」列
- [ ] 已售数量 = 该产品所有非 CANCELLED 状态的订单数
- [ ] 支持按已售数量排序
API 接口规划
# 产品列表
GET /api/v1/admin/products
Query: { keyword?, type[], isOnSale?, storeId?, sortBy, sortOrder }
Response: { items: Product[], total: number }
# 产品详情
GET /api/v1/admin/products/:id
Response: Product { ...全部字段, soldCount: number }
# 创建产品
POST /api/v1/admin/products
Body: ProductCreateDTO
Response: Product
# 更新产品
PUT /api/v1/admin/products/:id
Body: ProductUpdateDTO
Response: Product
# 上下架切换
PATCH /api/v1/admin/products/:id/sale-status
Body: { isOnSale: boolean }
Response: { success: boolean }
# 删除产品(仅无订单时)
DELETE /api/v1/admin/products/:id
Response: { success: boolean, message: string }
# 价格变更历史
GET /api/v1/admin/products/:id/price-history
Response: PriceChangeRecord[]