#3.1

点点餐 Web 管理端 产品需求文档(PRD)

约 1.1 万字 · 在 GitHub 上查看源码

版本:当前实现
面向系统:BiteGo 点点餐(Web 管理端)
项目定位:自助点餐系统的后台管理端 技术约束:Web 管理端 React + Material UI + TypeScript;服务端 Node.js + Express + TypeScript;前后端分离、REST 接口交互,使用 Yarn 作为包管理器。


1. 背景与问题

自助点餐系统需要“前台点餐能力 + 后台运营管理能力”的完整闭环。对门店而言,菜品分类、菜品与商品(SKU)的上架与库存、订单状态流转、桌台状态管理、门店基础信息维护,构成了日常运营的核心动作。

本项目的交付策略是:先搭建完整的 Web 管理端(前后端均具备),把数据模型与核心业务流程跑通,再开始开发面向顾客的前台点餐端(小程序)。


2. 产品目标

2.1 业务目标

  • 建立“菜品/商品(SKU)—库存/价格—订单—桌台—门店信息”的后台管理闭环,支撑门店日常运营。
  • 提供可控、可追溯的订单状态管理,减少误操作与重复操作带来的经营风险。

2.2 工程目标

  • 管理端具备基础数据管理能力(菜品信息管理、订单信息查看等)。
  • 模块划分清晰、可扩展;接口采用 REST 风格;数据库设计规范,满足一致性与完整性。
  • 系统在常见误操作下具备基本容错能力(例如重复提交、空数据提交)。

3. 范围说明(当前实现)

3.1 功能范围

  • 元数据管理:菜品分类管理
  • 菜品与商品管理:
    • 菜品基础信息管理(名称、描述、图片、销量)
    • 规格管理(支持无规格/单规格/多规格;区分库存规格组与非库存规格组)
    • 共享非库存规格组管理(可被多个菜品复用)
    • SKU 管理(以 SKU 为单位管理价格、库存、上下架状态)
  • 订单管理:
    • 订单列表与详情(查看、筛选)
    • 订单状态流转(创建、支付、制作、完成、取消、退款)
  • 桌台管理:
    • 桌台基础信息(桌号、桌台 ID)
    • 桌台状态(空闲/占用)与会话管理(清台/强制清台/重置会话)
  • 门店管理:
    • 门店基础信息维护(名称、Logo、电话、地址、描述)
    • 全店导出/全店恢复(用于初始化/迁移/恢复)
  • 平台多租户(SaaS)管理能力:
    • 平台级(SUPER_ADMIN):租户/门店/用户管理,平台 Branding 配置与平台概览
    • 租户级(TENANT_ADMIN):租户门店管理,共享基础数据维护与同步状态查看
    • 门店级(STORE_ADMIN):门店级数据管理;共享基础数据只读(UI 禁用 + 服务端强校验)

多租户总体设计与关键边界见 7.6-核心实现-平台多租户.md

3.2 明确非范围

  • 复杂促销(满减、券、会员积分)
  • 完整财务对账、发票、进销存
  • 用户评价与内容审核
  • 配送与骑手系统

4. 术语与定义

  • 菜品(Good):顾客看到的“菜”或“饮品”概念,如“奶茶”“童子鸡”。包含描述、图片、所属分类、销量等。
  • 规格(Spec/Option):菜品的可选项,如“中杯/大杯”“加糖/不加糖”“辣度”等。规格按“规格组”组织,并分为“库存规格组/非库存规格组”。
  • 共享非库存规格组(Shared Non-Stock SpecGroup):可被多个菜品复用的“非库存规格组”,在“规格管理”中统一维护;菜品仅绑定共享规格组,不重复配置规格项。
  • 共享规格组描述:共享规格组可配置描述文本,仅用于菜品编辑页选择共享规格组时提示(不透出到小程序端)。
  • 商品(SKU):仅由“库存规格组”的规格组合生成的可售卖单元,是库存、上下架状态的管理单元(SKU 数量不因非库存规格膨胀)。例如“奶茶 + 中杯”是一个 SKU。
  • 价格项(PI / Price Item):订单/购物车中的最终计价单元,可关联 SKU + 非库存规格选择 + 营销优惠等;用于记录“非库存规格加价”等导致的最终单价。
  • 桌台(Table):堂食场景下用于关联点餐过程的实体,具有编号与状态,可能关联“当前购物车/草稿单/未结单”。

5. 用户角色与权限模型(当前实现)

5.1 身份类型(登录主体)

  • 后端管理员(ADMIN):可登录 Web 管理端;其“管理能力”由授权范围(AdminScope)确定的角色层级与按场景计算的 canManageSharedCatalog 能力位共同决定。
  • 前端用户(CUSTOMER):小程序用户,不参与 Web 管理端权限体系。

5.2 管理域授权角色(AdminScope.role)

  • 平台超级管理员(SUPER_ADMIN):平台维度授权;具备全平台权限,并可在任意租户/门店上下文进行管理。
  • 租户管理员(TENANT_ADMIN):租户维度授权;具备该租户管理能力,并自动具备该租户下所有门店管理能力。
  • 门店管理员(STORE_ADMIN):门店维度授权;仅具备指定门店管理能力。

5.3 权限矩阵(按模块)

模块 SUPER_ADMIN TENANT_ADMIN STORE_ADMIN
平台概览(所有门店 block)
租户管理
用户管理(全平台)
连锁门店管理(tenant 下 store) ✅(仅本 tenant)
共享基础数据(分类/菜品/规格) ✅(仅本 tenant) ❌(只读)
门店级数据(桌台/订单/SKU 库存与上下架) ✅(仅本 tenant 可选具体 store) ✅(仅本 store)

补充说明(实现口径):

  • 统一以 /api/v1/admin/me/scopes 返回的 effectiveRolecanManageSharedCatalog 做前后端权限校验;禁止使用 /api/v1/users/merole 字段做任何“管理端权限判断”。
  • 权限校验时按上下文(X-Tenant-Id / X-Store-Id / X-Board)解析出当前作用域下的 effectiveRole,再与接口所需最小角色 rank 做比较;共享菜单写类操作再额外检查 canManageSharedCatalog
  • 支持混合授权组合:同一用户可同时拥有 “A 租户全量(含所有门店) + B 租户某门店” 等组合。

6. 全局交互与信息架构

6.1 左侧导航(建议)

  • 概览
  • 元数据管理
    • 菜品分类
    • 规格管理(共享非库存规格组)
  • 菜品与商品
    • 菜品管理
    • SKU 管理(可合并在菜品详情内)
  • 订单管理
    • 订单列表
  • 桌台管理
    • 桌台列表
  • 用户管理
  • 门店管理

6.2 列表页通用规范

  • 支持分页、关键字段搜索、筛选、排序(按创建时间默认倒序)。
  • 表格列支持“复制 ID / 复制编号”等快速操作(如实现成本较高可延后)。
  • 删除类操作默认“软删除”,并提供明确提示。

7. 功能需求(详细)

7.0 概览(后厨工作台 + 桌台总览)

概览页用于门店高频场景:快速查看全店桌台占用情况,以及后厨快速处理“待上菜商品”。页面采用左右分屏布局,默认实时刷新/实时推送(允许 1–3 秒延迟),并提供手动刷新兜底。

概览页增强(消息与播报):

  • 顶部右侧增加通知中心入口(铃铛),与用户头像昵称并列。
  • 管理员可在概览页开启/关闭“播报”开关:当收到新下单/退款申请事件时,语音播报并在页面展示字幕;并发事件使用队列顺序播报。

7.0.1 布局与入口

  • 页面入口:左侧导航「概览」
  • 页面布局:左右两列(约 1:2)
    • 左屏:桌台总览(全桌台状态)
    • 右屏上:活跃订单(未上菜商品列表,供后厨使用)
    • 右屏下:所有订单(仅在选中某个桌台后展示;展示该桌台当前会话的全部订单与菜品明细,并汇总总数量/总金额,排除已退款订单)

7.0.2 左屏:桌台总览

展示目标

  • 一屏看清全店桌台“空闲/占用”状态
  • 通过 hover 快速了解该桌台的开台时间、当前购物车、关联订单

桌台卡片(列表/宫格)

每个桌台至少展示:

  • 桌号(Table.code)
  • 状态:空闲 / 占用(强视觉区分)
  • 在线人数(实时):该桌台当前活跃会话连接数(用于门店判断是否有人在点餐)
  • 快速摘要(建议):
    • 开台时间(openedAt)
    • 购物车数量/金额(cartItemCount/cartTotalAmount)
    • 未完成订单数(activeOrderCount)
    • 该桌台总订单数(totalOrderCount)
    • 该桌台总金额(totalAmountExRefunded,不含已退款订单)

hover(或点击浮层)展示:

  • 开台时间 openedAt
  • 当前购物车摘要:
    • SKU/规格文本、数量、小计(支持滚动)
    • 添加人提示(若小程序端支持展示“谁加了什么”,则 Web 端也必须可见):添加人昵称/头像(快照或实时用户信息)
    • 更新时间 updatedAt
  • 关联订单列表(最近 N 条,默认 N=3,可配置):
    • 订单号(短号)、状态、创建时间、金额
    • 点击可跳转到订单详情

开台时间定义

openedAt 作为“桌台会话”的开始时间,取以下事件中最早发生的时间:

  • 该桌台购物车首次从空变为非空的时间;若无购物车,则取该桌台第一笔订单创建时间

可操作项

  • 点击桌台卡片:跳转至“桌台列表”并自动定位/筛选该桌台(或进入桌台详情页,若实现)
  • 桌台总览表格模式(若采用表格):点击桌号可快捷复制 tableId 到剪贴板,并通过全局提示反馈结果。
  • 桌台总览表格模式(若采用表格):点击整行可切换“选中桌台”状态;右侧“活跃订单”仅展示该桌台下的活跃订单;再次点击可取消选中并恢复全量展示。
  • 可选快捷操作(仅在 hover 浮层中提供):
    • 刷新该桌台数据
    • 重置桌台购物车(遵循 7.6.3 风险控制规则)
    • 关台(清台):仅当该桌台无未完成订单时可操作
    • 强制清台:存在未完成订单时的兜底操作,需二次确认并提示风险

交互补充(易用性):

  • 桌台总览数据量较大时,表格表头需吸附(sticky)在顶部,避免滚动后看不到列含义。
  • 为保证吸顶效果稳定,桌台总览建议在固定高度的内容区内滚动(而非整页滚动)。

7.x 账号与用户管理

顶部用户入口

  • 顶部右侧“退出登录”替换为「用户头像 + 用户昵称」,点击弹出菜单:
    • 个人信息:进入个人信息页
    • 退出登录:清除 token 并回到登录页

7.x.1 共享数据同步状态(租户主门店)

说明:当前“共享数据同步”采用手动批量触发(非每次变更自动触发)。租户主门店需要清晰感知“是否存在未同步变更”,并可一键触发同步任务。

  • 展示位置:Web 管理端右上角 App Bar(与通知铃铛、用户头像同一行),仅在以下条件展示:
    • 当前登录人角色为 TENANT_ADMIN(或 SUPER_ADMIN 以 tenant 视角进入)
    • 当前上下文为“连锁主门店(Primary Store)”
  • 状态类型(视觉可用颜色/图标区分):
    • 已同步:无未同步变更
    • 待同步:存在未同步变更(展示红点/Badge)
    • 同步中:存在进行中的同步任务(展示 loading/转圈)
    • 同步失败:存在失败任务(展示错误态)
  • hover(或点击浮层)展示详情:
    • 未同步变更摘要:分类/菜品/规格组等的新增/更新数量(按模块统计)
    • 变更详情(最多展示最近 N 条,默认 N=10):对象类型、名称、变更时间、变更人(若可得)
    • 同步任务摘要:目标门店数量、PENDING/RUNNING/SUCCEEDED/FAILED 数量、上次同步时间
    • 操作按钮:
      • 「立即同步」:触发一次批量同步任务(并展示“已提交任务/同步中”反馈)
      • 「查看详情」:进入“同步详情页/弹窗”,可查看更完整的变更清单与任务执行结果(可后续扩展)
  • 刷新策略(与当前实现一致):
    • WebSocket 连接可用时:订阅同步状态快照,由服务端推送更新
    • WebSocket 不可用时:按 30 秒间隔轮询接口刷新

个人信息页(管理员)

  • 支持修改头像、昵称
  • 支持修改密码(旧密码校验 + 设置新密码)
  • 保存后应立即生效(顶部头像昵称同步更新)

用户管理页

  • 支持筛选查看:
    • 小程序普通用户(CUSTOMER)
    • Web 管理端管理员(ADMIN)
  • 三个视角(platform / tenant / store)统一使用同一套 UI 交互:
    • 顶部仅有单一入口「添加成员」,点击弹出 AddMemberDialog,内含双 Tab:
      • 「新建账号」:录入用户名/密码/昵称(用户名提交前实时校验可用性),成功后直接在当前作用域授予默认角色(platform 视角可一次性勾选多条租户/门店 scope;tenant 视角默认 TENANT_ADMIN;store 视角默认 STORE_ADMIN)。
      • 「从已有账号选择」:按用户名/昵称搜索已有账号并为其在当前作用域新增 scope,避免重复创建账号导致账号膨胀。
    • 列表行点击进入右侧 UserDetailDrawer:展示基础资料与该账号在可见范围内的全部 scope(平台视角列出全部;租户视角仅列当前租户下的;门店视角仅列当前门店的)。
    • Drawer 内支持两类维护操作:编辑对方昵称/头像(走 PUT /api/v1/admin/users/:userId/profile)与重置密码(走 POST /api/v1/admin/users/:userId/reset-password)。两者均在服务端按"目标用户需在调用者作用域内至少有一条 ACTIVE scope"做授权校验,避免跨租户/跨门店越权操作他人账号。
    • Drawer 内的"作用域列表"采用只读 Chip + Tooltip 展示(租户/门店名称、角色、创建时间),不提供单行删除按钮,以防止高频场景下的误删;作用域的授予/撤销只能从 platform 视角的专属权限页进行。
  • 角色选择器约束:
    • STORE_ADMIN 仅适用于 CHAIN 租户的分店;当平台视角为一个 SINGLE 租户新增 scope 时,STORE_ADMIN 选项自动置灰并以 hover Tooltip 说明"单店租户由租户管理员统一管理"。
    • 租户/门店视角的角色默认值分别固定为 TENANT_ADMIN / STORE_ADMIN,不提供降级选项。

租户/门店创建流程约束:

  • 新建连锁与新建单店时不创建管理员账号;账号与权限通过"用户管理"显式创建/绑定。

7.y 通知中心(可扩展)

入口与展示

  • 入口:页面顶部右侧铃铛按钮(在用户头像旁)
  • 点击弹出通知列表(支持滚动,展示最近 N 条)
  • 通知包含:下单、退款申请等事件(后续可扩展更多类型)

交互

  • 通知展示基础字段:标题、摘要、时间、关联订单(若有)
  • 支持标记已读
  • 支持“一键已读”:将当前所有未读通知批量标记为已读
  • 对需要处理的通知(如退款申请)提供“去处理”入口(跳转订单详情/订单列表进行审核处理)

7.0.3 右屏:活跃订单(未上菜商品,后厨用)

展示目标

  • 面向后厨,将“需要制作/上菜的商品”集中展示,并能快速标记“已上菜”
  • 减少在订单列表中逐单点开的成本

活跃范围定义

活跃订单指需要后厨处理的订单,满足任一条件:

  • 订单状态为 Paid 或 Making
  • 订单存在未上菜商品(OrderItem.servedQty < OrderItem.qty)

右屏默认仅展示“未上菜商品”,并按订单聚合展示;可提供“按商品聚合”切换作为扩展能力。

列表结构(按订单聚合)

每个订单块至少展示:

  • 桌号、订单号、下单时间/支付时间(可选)
  • 订单状态(Paid/Making)
  • 未上菜商品列表:
    • 菜品名称(快照)、规格文本(快照)
    • 待上菜数量(pendingQty = qty - servedQty)
    • 快捷按钮:上菜(+1)、全部上齐(将该条明细 servedQty 置为 qty)
  • 订单级快捷操作(可选):
    • 一键上齐本单(将本订单所有明细 servedQty 置为 qty)
    • 进入订单详情

上菜与订单状态自动流转规则

  • 上菜动作本质为更新订单明细的 servedQty(或 served 状态),需要幂等且可并发。
  • 自动状态流转:
    • 当某订单首次发生上菜(任一明细 servedQty 从 0 变为 >0)且订单状态为 Paid,则订单状态自动流转为 Making。
    • 当某订单所有明细均已上齐(对所有 OrderItem:servedQty == qty),订单状态自动流转为 Completed。
  • 退款与取消约束:
    • 对状态为 Canceled/Refunded/Completed 的订单,禁止上菜操作,并在右屏不展示或展示为不可操作的历史状态。

权限与审计

  • 右屏上菜操作允许具备门店级(STORE_ADMIN 及以上)作用域的管理员执行。
  • 上菜操作需记录:操作者、时间、订单号、明细项、变更前后 servedQty。

7.1 元数据管理 —— 菜品分类

7.1.1 分类字段

  • 分类 ID(系统生成,不可编辑)
  • 分类名称(必填,1–20 字,门店内唯一)
  • 分类副标题(可选,0–50 字)
  • 分类小标签(可选,0–10 字):用于点餐页分类电梯角标展示
  • 分类排序(整数,默认 0;数字越大越靠前)
    • 说明:当前实现的列表默认只展示“启用(ACTIVE)”分类;停用通过“删除(软停用)”实现(见下方)。

7.1.2 功能点

  • 新增分类:校验名称唯一;创建成功后可继续新增。
  • 编辑分类:允许修改名称、副标题、小标签与排序。
  • 删除分类(软停用):从“启用分类列表”移除,用于前台隐藏该分类。
  • 排序:支持编辑排序值;支持拖拽排序(拖拽后会自动重算所有分类排序值)。

7.1.3 验收标准

  • 能创建/编辑/停用分类;分类名称唯一性校验生效。
  • 分类停用后,菜品列表筛选与前台可见性规则一致(由后端统一控制)。

7.2 菜品管理(Good)

7.2.1 菜品字段(基础信息)

  • 菜品 ID(系统生成)
  • 菜品名称(必填,1–40 字)
  • 菜品描述(可选,0–200 字):一句话描述,用于点餐页列表等紧凑区域展示
  • 菜品详情(可选,Markdown 富文本):用于点餐页菜品七分屏展示更详细的介绍(支持标题/列表/图片等)
  • 菜品图片(可选,多张,仅上传添加)
    • 允许上传/维护多张图片(用于详情轮播/展示);列表封面默认取第 1 张图
    • 图片编辑交互参考微信朋友圈:正方形缩略图网格、支持拖拽排序、右上角可删除、末尾为虚线加号格(点击唤起上传)
    • 支持“设为封面图”(将该图移动到第 1 位)
    • 图片上传通过后端文件上传接口获取 URL,并保存到菜品信息中
    • 支持 JPG/PNG/GIF 等格式,单文件建议 ≤ 10MB
  • 所属分类(必选,可多选)
    • 支持同一菜品绑定多个分类,用于“本店甄选/活动推荐”等高优分类复用并高亮同一菜品
  • 销量(数值,默认 0)
    • 推荐规则:销量为“有效订单明细累计销量”的派生值
      • 下单成功(Paid)时累加对应菜品的下单数量
      • 若订单后续被取消/退款,则回滚扣减对应数量(避免销量长期偏大)
    • 如需支持手工修正,可提供“初始化销量”字段或允许管理员编辑。
  • 菜品状态(建议):启用/停用(与 SKU 上下架区分)
  • 创建/更新时间(只读)

7.2.2 菜品列表页

  • 搜索:按菜品名称模糊搜索
  • 筛选:分类、菜品状态、是否有规格(可选)
  • 筛选记忆:状态、分类、名称、当前页与每页条数按当前页面(平台共享、租户共享、门店三套入口分别记忆)写入 sessionStorage,进入菜品详情后返回列表,筛选条件保持不变;浏览器关闭/Tab 关闭后自动失效
  • 列表展示:图片缩略图(可点开预览)、名称、分类、状态(中文展示)、起步价/基础价、销量、操作
  • 分类内排序:当筛选了具体分类时,列表进入“分类内排序模式”,提供 DragIndicator 列与排序值列,支持拖拽调整该分类下菜品展示顺序(仅在当前分类内生效)
  • 操作:
    • 新增菜品
    • 编辑菜品
    • 进入“规格与 SKU 管理”
    • 停用/启用菜品(停用后所有 SKU 自动下架或前台不可售,需定义一致规则)
    • 批量操作(推荐):批量上架/下架、批量调整分类
    • SKU 概览(推荐):在菜品列表支持展开行展示 SKU 列表(只读:规格组合/价格/库存/状态),SKU 的编辑仍在菜品详情页完成

7.2.3 菜品详情页(规格与 SKU 管理入口)

菜品详情至少包含:

  • 基础信息编辑区
  • 规格管理区(见 7.3)
  • SKU 列表区(见 7.4)

7.3 规格管理(Spec)

7.3.1 规格模型

一个菜品可配置 0~N 个选项组(OptionGroup),每个选项组包含若干选项(Option)。选项可配置“加价”,并参与 SKU 自动生成与最终价格计算。除“自定义规格组”外,支持绑定“共享非库存规格组”,其规格项在“规格管理”中维护。

  • 选项组(OptionGroup)
    • 选项组名称(必填,1–10 字,如“杯型”“甜度”“辣度”)
    • 是否必选 isRequired
    • 最小选择数 minSelection / 最大选择数 maxSelection
    • 选项列表
  • 共享非库存规格组(Shared SpecGroup)
    • 仅支持“非库存”类型,不参与 SKU 组合生成
    • 规格项/默认选项/选择数规则在“规格管理”中统一配置,菜品仅做绑定
  • 选项(Option)
    • 选项名称(必填,1–10 字,如“大杯”“不加糖”)
    • 加价 price(以分为单位,>= 0)
    • 状态:启用/停用(停用会影响 SKU 生成)

多选规格组规则:

  • 可选组/必选组使用 isRequired 区分,允许 maxSelection > 1
  • 选择规则:
    • isRequired=true:最少选择数 minSelection >= 1
    • isRequired=false:最少选择数 minSelection = 0
    • minSelection <= maxSelection <= options.length
  • 使用场景示例:
    • “加料/配料”多选:可选组,min=0,max=2(最多加两种)
    • “双拼口味”必选多选:必选组,min=2,max=2(必须选两种口味)
    • “辣度/甜度/杯型”单选:必选组,min=1,max=1

7.3.2 规格配置规则

  • 无规格菜品:不配置任何规格组,此时系统自动生成 1 个 SKU(默认 SKU)。
  • 有规格菜品:选项组数量 ≥ 1;SKU 仅由“库存规格组”的启用选项做笛卡尔积生成。
  • 多选规格组:先在单个选项组内生成“组选组合”(组合大小 k ∈ [minSelection,maxSelection]),再对各组选组合做笛卡尔积生成 SKU。
  • SKU 唯一性:同一 Good 下,同一组合(specKey)仅允许一个 SKU。
  • 价格计算规则:
    • Good 维护 basePriceCents(基础价格,单位:分)
  • SKU 价格 = Good.basePrice + “库存规格组”选项加价之和(单位:分)
  • PI 单价 = SKU 价格 + “非库存规格组”选项加价之和(单位:分)
  • 规格变更的 SKU 影响规则:
    • 新增选项:允许一键“重新生成 SKU”,并提示将新增若干 SKU(初始库存为 0、状态为下架)。
    • 删除/停用规格值:不允许硬删除;停用后相关 SKU 自动下架且不可售卖;历史订单仍可展示该规格文本(订单快照字段)。
    • 修改规格名称:不影响历史订单展示(历史订单展示使用下单时的快照)。

界面操作指引(Web 管理端):

  • 在“规格组”中配置:
    • 规格组名称
    • 规格组类型:库存规格组 / 非库存规格组 / 共享非库存规格组
    • 是否必选(必选/可选)
    • 最少选择数、最多选择数
    • 选项名称与加价
    • 规格组排序:创建/编辑时支持调整顺序(影响前台展示顺序)
  • 当选择“共享非库存规格组”时:
    • “规格组名称”改为下拉选择共享规格组
    • 允许额外配置“禁用规格项”“默认规格覆盖”(不设置则使用共享规格组自身默认规格;若覆盖项被删除则使用共享规格组自身默认规格)
    • 规格组自身配置项(必选/最少/最多/默认选项/选项加价)在菜品页只读并隐藏,由共享规格组自身配置决定
    • 共享规格组可在“规格管理”页面统一维护,并显示关联菜品数量
  • 保存后由服务端按“库存规格组”自动重新生成 SKU;SKU 列表支持批量上架/下架与批量设置库存。
  • 若对“已有菜品”进行“库存规格组”的结构变更(影响 SKU 组合集合,如增删库存规格组/规格值、调整必选与选择数量约束、切换规格组类型等),属于破坏性操作:将删除该菜品所有历史 SKU,并清空库存/上下架等 SKU 级设置;界面必须弹出确认对话框后才允许提交。仅修改加价/名称/排序不会触发破坏性确认与重建。

7.4 SKU 管理(库存 / 价格 / 状态)

7.4.1 SKU 字段

  • SKU ID(系统生成)
  • 关联菜品 ID
  • SKU 规格组合展示(如“中杯 / 不加糖”)
  • 售价(必填,>= 0)
    • 服务端存储与计算统一使用“分(cents)”的整数
    • Web 管理端录入可按“元(两位小数)”展示与输入,提交时由接口层完成转换
  • 库存(整数,>= 0)
  • 状态:上架 / 下架 / 删除(删除为软删除)
  • 条码/编码(可选)
  • 创建/更新时间

交互补充:

  • SKU 价格在管理端只读展示(由基础价 + 选项加价规则计算),不提供手工编辑入口
  • SKU 支持批量操作:批量上架/下架、批量设置库存
  • 默认规格:每个规格组支持配置默认选项(也可不配置)。管理端在“规格组编辑”中设置:
    • 单选规格组:最多选择 1 个默认选项
    • 多选规格组:最多选择 maxSelection 个默认选项
    • 前台默认选中逻辑以规格组默认选项为准

7.4.2 SKU 列表(在菜品详情页内或独立页面)

  • 分页:每页 10 条
  • 搜索筛选:支持按“规格组合/skuId”关键字筛选
  • 支持批量操作:
    • 批量上架/下架(仅对未删除 SKU 生效)
    • 批量设置价格(可选)
    • 批量调整库存(建议支持“增减库存”与“设置库存”两种)
  • 上架校验:
    • 价格必须已设置
    • 库存 > 0(当前实现默认要求;库存为 0 的 SKU 不可售)
  • 删除规则:
    • 若 SKU 存在历史订单引用,仍允许软删除,但需要提示“仅影响未来售卖,不影响历史订单展示”。

7.4.3 验收标准

  • 支持无规格菜品自动生成默认 SKU。
  • 支持多规格组笛卡尔积生成 SKU,并能按 SKU 维度管理库存/价格/上下架。
  • 上架规则校验生效;删除为软删除且不会破坏历史订单展示。

7.5 订单管理

7.5.1 订单状态定义

订单状态包含:

  • 创建(Created):订单已生成但未支付(或未确认支付)。
  • 支付(Paid):订单支付成功。
  • 制作(Making):门店开始制作/出餐。
  • 完成(Completed):订单已完成(顾客取餐/核销)。
  • 取消(Canceled):订单被取消(可能发生在未支付或支付后特殊场景)。
  • 退款中(Refunding):订单已发起退款申请,等待门店审核/退款处理完成。
  • 退款(Refunded):订单已退款完成(可由取消后进入或支付后直接进入)。

7.5.2 状态流转规则(建议)

  • Created → Paid → Making → Completed(主链路)
  • Created → Canceled(未支付取消)
  • Paid → Refunding → Refunded(支付后退款:申请退款 → 审核通过并退款完成)
  • Refunding → Paid(审核不通过)
  • Paid → Canceled(若业务允许“支付后取消”,则 Canceled 与 Refunded 需区分;当前实现中支付后取消通过退款流程表达)
  • Any →(不可逆)Completed/Refunded(终态)

要求:

  • 每次状态变更必须记录:操作者、时间、原因(可选)、备注(可选)。
  • 状态变更需要二次确认弹窗,避免误操作。
  • 状态变更需具备幂等:重复点击不会产生多次变更或异常数据。

模拟支付/退款口径(需对齐小程序端)

  • 模拟支付:用户在小程序端“提交订单”后,服务端创建订单并立即模拟支付成功,使订单状态直接进入 PaidCreated 仅作为服务端瞬时中间态,前端默认不展示“待支付”)。
  • 模拟退款(含审核):用户在小程序端发起“申请退款”后,订单状态进入 Refunding,服务端创建退款流水进入 REVIEWING;Web 管理端审核通过后退款流水进入 PENDING,10 秒后自动完成退款并将订单状态流转为 Refunded;审核不通过则订单状态变更为 Paid,并记录驳回原因用于追溯。
  • 流水表要求:即使为模拟支付/退款,也必须记录完整支付流水与退款流水(用于审计、排障与后续接入真实支付的平滑演进)。

7.5.3 订单列表页

  • 筛选:
    • 状态(多选)
    • 下单时间区间
    • 桌台(可选)
    • 订单号(精确/模糊)
  • 列表列:
    • 订单号、桌台、总金额、商品数量、状态、创建时间/支付时间(可选)、操作
  • 操作:
    • 查看详情
    • 修改状态(按状态显示可用动作按钮)
    • 导出(CSV/Excel)

7.5.4 订单详情页

  • 订单基础信息:
    • 订单号、状态、下单时间、支付时间、桌台信息、金额(应付/实付/退款)、备注
  • 商品明细:
    • 菜品名称(快照)、规格快照、单价、数量、小计
  • 状态流转记录:
    • 操作人、时间、从何状态到何状态、备注
  • 上菜明细(建议):
    • 展示每个 OrderItem 的已上菜数量 servedQty 与待上菜数量
    • 展示上菜操作记录(若实现审计日志)
  • 操作:
    • 打印:调用浏览器打印,打印模板包含门店信息、订单号、时间、桌台、明细、合计
    • 导出:导出当前订单明细(CSV)

7.5.5 导出字段规范

订单导出(列表级)字段至少包含:

  • 订单号、状态、桌台编号、下单时间、支付时间、总金额、商品总数

订单明细导出字段至少包含:

  • 订单号、菜品名称、规格、SKU 标识(可选)、单价、数量、小计

7.5.6 与小程序端联动(订单同步)

Web 管理端作为门店侧的“订单处理端”,其订单状态变更需要与小程序端保持一致并可及时感知:

  • 状态一致性:订单状态机定义与流转规则必须与小程序端一致(Created/Paid/Making/Completed/Canceled/Refunded)。
  • 状态一致性:订单状态机定义与流转规则必须与小程序端一致(Created/Paid/Making/Completed/Canceled/Refunding/Refunded)。
  • 实时可见性:后台对订单进行状态变更(例如 Paid→Making→Completed、Paid→Refunded)后,小程序端的“桌台订单页”和“历史订单详情页”需同步更新,允许 1–3 秒延迟。
  • 幂等与防误操作:
    • 订单状态变更接口必须幂等(重复点击不产生多次变更)。
    • 状态变更需二次确认;对非法流转给出明确提示并拒绝。

7.6 桌台管理

7.6.1 桌台字段

  • 桌台 ID(系统生成)
  • 桌台编号(必填,1–10 字/数字,如“A01”“1号桌”,门店内唯一)
  • 桌台状态:空闲 / 占用
  • 桌台二维码链接(系统生成,HTTPS 静态链接,用于小程序扫码入桌)
  • 关联信息(只读,按实现选择):
    • 当前购物车/草稿单 ID(可选)
    • 当前未结订单号(可选)
  • 更新时间

7.6.2 桌台列表页

  • 搜索:桌台编号
  • 筛选:状态
  • 操作:
    • 新增桌台
    • 查看/复制二维码链接(用于发给门店打印或生成桌贴)
    • 下载/打印桌台二维码(可选:打印模板包含门店名 + 桌号 + 二维码)
    • 编辑桌台编号
    • 切换状态(空闲 ↔ 占用)
    • 重置桌台购物车
    • 清台
    • 强制清台

交互补充:

  • 新增/编辑桌台时默认创建为空闲态(FREE),不要求在表单中选择状态
  • 空闲态桌台不展示“强制清台”按钮(避免误操作)

7.6.3 桌台状态与购物车规则

  • 桌台“占用”的判定:
    • 桌台存在未结订单(Created/Paid/Making)或存在购物车草稿数据,则为占用。
    • 若通过手动切换状态,也必须同步处理“是否允许在空闲状态保留购物车”等规则(当前实现为空闲状态不保留会话购物车)。
  • 重置桌台购物车:
    • 清空该桌台的购物车/草稿数据(不影响已生成订单)。
    • 若桌台存在未结订单,重置操作需二次确认并提示风险。

清台

为与小程序端“桌台会话/浮窗”一致,建议明确清台语义:

  • 清台 = 将桌台状态置为“空闲” + 清空桌台购物车 + 结束当前桌台会话
  • 清台前置条件:
    • 桌台不存在未结订单(Created/Paid/Making),否则禁止清台并提示“存在未完成订单”
    • 若仅存在已完成/已取消/已退款订单,允许清台
  • 清台后联动:
    • 小程序端桌台会话失效,桌台浮窗自动隐藏;再次点餐需重新扫码开台

强制清台(必做)

允许管理员在“存在未结订单”或“用户端异常无法退出”等情况下强制结束桌台会话,用于快速止损与恢复门店运营。

  • 行为定义:
    • 强制清台 = 将桌台状态置为“空闲” + 清空桌台购物车 + 强制结束当前桌台会话(会话版本号 +1)
    • 不自动取消/退款已有订单:订单仍保留原 tableId 与会话版本信息,后厨/门店可继续处理;新扫码进入桌台会话的用户不会看到上一会话的订单列表
  • 风险提示:
    • 操作必须二次确认,并提示“将强制踢出该桌台所有在线用户端”
    • 需记录操作日志(操作者、时间、桌台、原因)
  • 端侧联动:
    • 小程序端收到“桌台已关闭”事件或接口返回后,展示“桌台已关闭”提示页,引导用户返回主页重新扫码入桌
    • Web 概览页与后厨工作台应实时更新该桌台状态与关联视图(允许 1–3 秒延迟)

7.6.4 验收标准

  • 桌台编号唯一性校验生效。
  • 能完成桌台状态轮转;支持重置购物车并符合风险控制规则。

7.6.5 与小程序端联动(桌台会话与共享购物车)

小程序端以“桌台”为协同上下文支持多人同时点餐,因此 Web 管理端需要具备对桌台会话数据的可视化与实时感知能力,用于门店/后厨协同:

  • 桌台购物车实时展示:
    • Web 管理端需能查看每个桌台当前购物车(SKU 明细、规格文本、数量、金额、更新时间)。
    • 购物车明细需包含“添加人”信息:用于支持小程序端展示“订单/购物车内商品由谁添加”,并便于门店排查协同操作。
    • 桌台购物车为“桌台共享草稿”,多人加购/删改数量会实时同步到该桌台的所有端(小程序多端 + Web 端)。
    • 允许 1–3 秒延迟;异常时需提供“手动刷新”能力。
  • 桌台订单聚合展示:
    • Web 管理端需能查看桌台关联订单列表(尤其是未结算/未完成:Created/Paid/Making)。
    • 支持从桌台维度快速进入订单详情与处理(制作/完成/退款)。
  • 并发与库存校验口径:
    • 购物车编辑与下单提交时均以服务端校验为准;当库存不足或 SKU 下架导致操作失败时,Web 端与小程序端需能看到一致的失败原因与最新数据。

7.7 门店管理

7.7.1 字段

  • 门店名称(必填,1–40 字)
    • 连锁门店对外展示名由服务端统一拼装为「品牌名(门店名)」,管理端在输入框下方显示实时预览提示以降低命名歧义。
  • 门店照片(可选:URL 或上传)
    • 图片上传通过后端文件上传接口获取 URL,并保存到门店信息中
    • 连锁门店(tenantType='CHAIN')的门店 Logo 不可单独编辑,由租户“品牌 Logo”统一同步:品牌 Logo 修改时级联覆盖全部门店 logoUrl;新建子门店时直接采用当前品牌 Logo。管理端在此场景禁用 Logo 输入与上传按钮并给出指引。
  • 联系电话(必填,格式校验)
  • 门店地址(必填,0–100 字)
  • 门店描述(可选,0–500 字)
  • 营业时间(可选)

7.7.2 功能点

  • 查看门店信息
  • 编辑与保存(仅管理员)
    • 连锁租户门店保存时服务端会校验 Logo 字段,若与当前值不一致将拒绝写入。
  • 门店信息在“打印模板/导出”中复用(如订单小票抬头)
  • 门店展示名由服务端计算并写入 displayName,所有使用方(管理端表格、概览、扫码落地页、小程序门店头部、历史订单等)直接消费该字段,避免端侧拼装不一致。

7.7.3 数据搬家(平台 / 连锁 / 门店)

背景:测试环境易出现测试数据污染;生产环节也可能需要“初始化/搬家/恢复”能力。当前实现将数据搬家统一为三层入口:

  • 平台搬家(平台设置内):
    • 导出平台快照 / 清空平台 / 恢复平台(完全还原,保留 ID)
    • 恢复/清空期间平台进入维护态并强制 token 失效(需重新登录)
  • 连锁搬家(品牌管理内,tenant 视角):
    • 导出租户快照 / 清空租户 / 恢复租户(克隆式:重建配置类 ID、重建关联、重建桌台二维码)
    • 恢复/清空期间租户进入维护态:禁写 + 断连 + 阻止新连接;AppBar 展示「恢复中」(不强制 token 失效)
    • 从独立门店恢复到连锁主门店(用于“独立店升级为连锁”):
      • 管理端上传“独立门店导出文件(全店导出)”
      • 服务端自动执行:清空并删除当前租户下所有子门店与旧主门店 → 新建一个干净的主门店 → 将独立门店数据导入到新主门店
      • 风险提示:该操作会改变主门店 storeId;恢复完成后需重新选择主门店上下文
  • 门店搬家(门店管理内,store 视角):
    • 入口展示约束:仅单店租户(tenantType='SINGLE',即“独立门店”)展示该入口
    • 导出门店配置 / 清空门店 / 恢复门店(克隆式:重建配置类 ID、重建关联、重建桌台二维码)
    • 恢复/清空会清理该门店业务数据与配置数据,不影响平台顾客用户与管理员账号
    • 恢复/清空期间门店进入维护态:禁写 + 断连 + 阻止新连接;AppBar 展示「恢复中」(不强制 token 失效)

8. 数据模型(概念级)

8.1 核心实体

  • Store:门店
  • Tenant:租户/连锁品牌
  • UserRole:用户-租户/门店角色映射
  • PlatformConfig:平台信息(平台名称/Logo)
  • Category:分类
  • Good:菜品
  • SpecGroup:规格组(归属 Good)
  • SpecOption:规格值(归属 SpecGroup)
  • SKU:商品(归属 Good,包含规格组合)
  • Table:桌台
  • TableCart:桌台购物车(桌台共享草稿)
  • TableCartItem:桌台购物车明细(按 SKU 聚合)
  • Order:订单(可关联 Table)
  • OrderItem:订单明细(快照 GoodName / SpecText / UnitPrice)
  • OrderItem.servedQty:已上菜数量(用于后厨工作台与自动流转)

8.2 关键约束

  • 单门店场景:Category.name 门店内唯一;Table.code(桌台编号)门店内唯一
  • 多租户场景:
    • 所有业务数据必须归属到 tenantId/storeId(共享数据按 tenant 归属,门店数据按 store 归属)
    • Category.name 在 tenant 范围内唯一(共享分类);门店可读但不可写
    • Table.code 在 store 范围内唯一(门店级)
  • SKU 唯一性:同一 Good 下,同一规格组合仅允许一个 SKU
  • TableCart 版本控制:桌台购物车建议带 version 字段,支持并发编辑与幂等操作
  • TableCartItem 归并规则:同一桌台下,若需要展示“添加人”,则购物车明细的聚合键至少包含 skuId + addedByUserId(避免不同人加的同款被合并后丢失归属)
  • 订单明细必须保存快照字段,避免后续改名影响历史订单展示
  • OrderItem.servedQty 约束:0 <= servedQty <= qty,且并发更新不可超量

9. 非功能性需求

9.1 性能与体验

  • 管理端列表页在“千级数据量”下分页查询可用,提供分页选择器(页码切换 + 每页条数选择),默认每页 50(可选 10/20/50/100)。
  • 核心操作(上架/下架/状态变更)给出明确成功/失败反馈,并避免重复提交。

9.2 安全与审计

  • 登录态采用 Token(如 JWT 或 Session),接口必须鉴权。
  • 关键操作(订单状态变更、库存变更、删除)建议记录操作日志(操作者、时间、对象、前后值)。
  • 输入校验:价格/库存/手机号/必填字段校验在前后端同时存在,以后端为准。

9.3 可靠性与容错

  • 订单状态变更接口必须幂等。
  • 桌台购物车编辑接口必须幂等,避免重复点击导致多次叠加。
  • 对常见误操作(空字段提交、重复点击、非法状态流转)给出可理解的错误提示。
  • 桌台购物车与订单状态变更需支持实时订阅/推送,允许 1–3 秒延迟,并提供回源刷新能力。
  • 上菜操作需支持并发与幂等:同一明细的 servedQty 更新必须避免超出 qty,并对并发更新给出确定性结果。

10. 验收清单

  • 概览:

    • 左右分屏可用;左屏可展示全桌台空闲/占用状态并可 hover 查看开台时间、购物车、关联订单。
    • 右屏可展示所有活跃订单的未上菜商品,并支持快捷上菜;一单商品全部上齐后订单状态自动流转。
  • 分类:新增/编辑/停用可用,名称唯一性校验正确。

  • 菜品:新增/编辑/查询可用;图片字段可录入;分类绑定正确。

  • 规格与 SKU:

    • 无规格菜品自动生成默认 SKU;
    • 多规格组生成 SKU,SKU 可设置价格/库存/上架下架;
    • 上架校验生效(价格/库存)。
  • 订单:

    • 列表筛选、查看详情可用;
    • 状态流转符合规则且有二次确认;
    • 支持导出与打印(至少 CSV + 浏览器打印)。
  • 桌台:

    • 新增/编辑/状态切换可用;
    • 支持重置购物车并有风险控制策略。
  • 门店:

    • 门店基础信息可维护;打印模板中可展示门店信息。
  • 多租户与权限:

    • SUPER_ADMIN:可查看平台概览;可管理租户/门店/用户;可配置平台名称/Logo(供小程序首页首次展示)
    • TENANT_ADMIN:可管理租户下门店;可维护共享基础数据;子门店管理员只读
    • STORE_ADMIN:可管理门店级数据(桌台/订单/库存/上下架),共享数据不可写

12. 附录A:统一数据模型标准(Web/小程序共用)

本附录用于对齐 Web 管理端与小程序端在数据模型层的字段口径、约束与关系结构,作为两端 PRD 的共同标准。若后续迭代新增字段,两端 PRD 需同时更新本附录并保持一致。

12.1 标识与字段类型约定

  • 所有主键 ID:string(推荐 UUID/雪花 ID),不可复用
  • 金额:服务端/数据库统一使用 BIGINT(分 cents),不可用 float
  • 数量:int(>= 0)
  • 时间:ISO8601 字符串或毫秒时间戳(两端统一一种)

12.2 核心实体字段(概念级)

Store(门店)

  • storeId、name、logoUrl、phone、address、description、createdAt、updatedAt

Table(桌台)

  • tableId、storeId、code(桌号,门店内唯一)、status(FREE/OCCUPIED)、createdAt、updatedAt

TableCart(桌台购物车,会话级共享草稿)

  • cartId、storeId、tableId、openedAt、version(int,单调递增)、items[]、updatedAt

TableCartItem(桌台购物车明细)

  • cartItemId、cartId、skuId、goodId
  • goodNameSnapshot、specTextSnapshot、unitPriceSnapshot、qty
  • addedByUserId、addedByNicknameSnapshot、addedByAvatarSnapshot
  • createdAt、updatedAt

Order(订单)

  • orderId、orderNo(展示用)、storeId、tableId
  • status(Created/Paid/Making/Completed/Canceled/Refunding/Refunded)
  • remark(可选)、paymentMethod(WECHAT/ALIPAY/OTHER,按实现)
  • totalAmount、paidAmount、refundedAmount
  • createdAt、paidAt(可选)、completedAt(可选)、canceledAt(可选)、refundedAt(可选)

OrderItem(订单明细)

  • orderItemId、orderId、skuId、goodId
  • goodNameSnapshot、specTextSnapshot、unitPriceSnapshot、qty
  • servedQty(int,默认 0)
  • addedByUserId、addedByNicknameSnapshot、addedByAvatarSnapshot

12.3 关键约束与一致性规则

  • 唯一性:
    • Table.code 在 storeId 范围内唯一
    • Category.name 在 storeId 范围内唯一
    • SKU 在同一 goodId 下按“规格组合”唯一
  • 快照:
    • 订单与订单明细必须使用下单时快照字段展示,后台改名/改价不影响历史订单
  • 并发与幂等:
    • TableCart 编辑建议采用 opId 幂等 + version 乐观并发控制
    • 创建订单建议采用 clientRequestId 或(tableId + cartVersion)幂等键,避免重复下单
  • 上菜与自动流转:
    • 0 <= servedQty <= qty
    • servedQty 首次从 0 变为 >0 且订单为 Paid 时,订单自动流转 Making(若启用该规则)
    • 所有明细 servedQty == qty 时订单自动流转 Completed(若启用该规则)