MVP 功能清单与开发方案
文档 QX-MVP-2026-001 │ V1.0 │ 2026-11-10 │ 目标:给 agent 一份能直接开工的规格,一周内出一个能在店里演 3 分钟的 demo
| 产品 | 汽修门店数字化系统(单店版),多租户,一店一租户 |
|---|---|
| 本阶段目标 | 不是功能齐全,是演示时老板会说"这个多少钱" |
| 后端 | 已实现并在线:/api/v1 真实接口(SQLite 落盘、三租户种子数据),附 接口文档;前端按第 11 节契约对接,不需要再等后端 |
| 交付物 | 可访问的 H5 demo + 源码 + 3 个种子租户 + 演示脚本 |
| 不在本阶段 | 真实支付、发票、保险理赔、OBD、连锁总部、AI 改代码 |
13800000000(可切换三家店);前台 13800000001(只能看 t1)。
验证码任意 6 位。这份 demo 直接连第 11 节的真接口,不是假数据;断网会自动切到只读快照。1 这份文档怎么用
把下面这段直接发给 agent(连同本页 URL),它就能开工:
src/api/index.ts,它下面挂两个同签名的适配器——
默认走线上真接口 https://ssa.agent2.0755xzb.club/api/v1(契约见第 11 节),
断网时切到 mock.ts(localStorage 持久化,字段与接口逐字段一致)。
页面里不许出现 fetch,也不许出现写死的假数组。
多租户用 /t/:tenantId/ 路由前缀,与接口 /api/v1/t/{tenantId}/ 一一对应。
只做第 3 节的 P0 功能、第 4 节的 8 个页面,P1 只做工作台的两张小图表。
严格按第 9 节验收,跑不通的项不许标完成。先交可点的页面,再补交互,最后接数据。api.orders.create(),
不知道背后是 HTTP 还是内存。这份文档第 11 节的接口已经在线,直接当默认实现用。三条纪律,写进 agent 的 prompt 里:
- 演示优先于完备:任何功能如果 3 分钟演示里用不到,这一版不做。
- 不许留"开发中"占位页:做不了的入口直接隐藏,不要放一个点进去是空白页的按钮。
- 手机优先:所有页面先在 390px 宽下可用,再考虑桌面。
2 先定 3 分钟演示脚本
MVP 的范围不是"想到什么做什么",是被这 3 分钟倒推出来的。演示场景:老板坐在他自己的前台,你拿手机,用他店里的车、他店里的工单演。
| 时间 | 你做什么 | 老板看到什么 | 对应功能 |
|---|---|---|---|
| 0:00–0:30 | 打开工作台,不解释,让他自己看 | 今天 8 台进场、3 台待结算、2 条超期提醒 | F1 工作台 |
| 0:30–1:20 | 输一个他熟悉的车牌,点"接车" | 客户名、车型、上次保养里程、上次换的机油自动带出;勾 3 个项目,报价 380 元 | F2 接车开单 + F3 报价 |
| 1:20–2:00 | 用技师视角点"开工"→"完工",拍一张照片 | 工单状态从"施工中"变"待结算",前台那边同步变了 | F4 施工报工 |
| 2:00–2:40 | 点结算,选微信收款 | "本次 380,累计消费 4,260,下次保养 2027-03-12";一条给车主的微信消息草稿 | F5 结算 + F6 客户档案 |
| 2:40–3:00 | 切到提醒页 | 下个月 37 条到期提醒已经排好,"这条不发,就是丢单" | F7 到期提醒 |
3 MVP 功能清单
3.1 P0(必须做完,缺一个演示就断)
| 编号 | 模块 | 必须做到什么 | 验收点 |
|---|---|---|---|
| F1 | 工作台 | 今日进场台次、待结算、待提醒三个数字 + 最近 5 条工单 | 打开 1 秒内出数,不转圈 |
| F2 | 接车开单 | 输车牌 → 自动带出客户/车型/上次里程;选项目、改数量、看合计;保存生成工单号 | 30 秒内开完一单 |
| F3 | 报价确认 | 生成一张可发微信的报价(图或链接),带"客户已确认"标记 | 能点"模拟发送",状态变已确认 |
| F4 | 施工报工 | 技师点开工/完工,传 1 张照片,状态机流转 | 状态变更后工作台数字同步 |
| F5 | 结算收银 | 合计、折扣、支付方式、开单完成;显示下次保养日期 | 结算后工单不可再改 |
| F6 | 客户与车辆 | 一车一档:基本信息、历史工单、累计消费、保养周期 | 从车牌能查到全部历史 |
| F7 | 到期提醒 | 按保养日期/里程生成的待提醒列表,可标记已联系 | 列表条数与工作台一致 |
| F8 | 设置 | 门店名/电话、工位数量、员工 3 人、常用项目价目表 | 改价目表后开单价格跟着变 |
| F9 | 配件与领料 | 配件库(SKU/规格/成本/售价/安全库存)、入库盘点、技师按工单领料、退料 | 领料后库存立刻减、该单成本立刻加;领超库存被拒 |
| F10 | 老板大屏 | 一屏看完:今日营业额、进场、待结算、客单价、毛利、7 天趋势、最近工单、库存预警;30 秒自动刷新 | 1440×900 与 iPad 横竖屏都不滚动、不溢出 |
| F11 | 员工提成 | 按分类的提成规则(工时费 / 项目金额 × 比例)、结算自动计提、按月汇总与明细、技师只看自己 | 改规则不影响历史;换比例后新单按新规则算 |
| F12 | 客户看板 | 挂店里给车主看:在店台数、工位空闲数、繁忙度;三列(施工中 / 待派工 / 可交车)、预计完工时间、超时变红;车牌只留后四位,不出现任何金额与车主全名 | 陌生人盯着看也拿不到价格和电话;同一台车只出现在一列 |
| F13 | 交车确认 | 已收款未交车的单独立成「待交车」一档,列表可筛;点「确认交车」记交付时间,交车后进「已完成」,客户看板不再显示;未收款也能交车(挂账),那种单留在「待结算」 | 交两次返回 409;未完工不能交车;交车后不能再领料/退料;已交车未结算仍在待结算里 |
| F14 | 预约到店 | 按天看预约(时间、车牌、项目、预计工时与完工时间);到店一键转工单;过期未到高亮提醒;可改时间/取消 | 预约里的项目、时长、金额和到店开出来的工单完全一致 |
| F15 | 客户自助 H5 | 客户在微信里自己约时间(选项目自动算时长和价),以及用「车牌 + 手机后四位」查自己车的进度;全程无金额、无其他客户信息 | 预约免登录可提交;查别人车返回 403;页面里搜不到任何价格 |
| F16 | 打印 | A5 接车单(含里程、车架号、随车物品勾选、签字位)、A4 结算单(含配件、优惠、实收、大写金额);走浏览器打印,不接打印机 SDK | A5/A4 各一页,不跨页;灰色背景不出现在纸上 |
| F17 | 导入客户 | Excel 另存 CSV 后上传或粘贴,自动识别表头;按车牌/手机号去重,重复导入只更新不新建 | 同一份导两次,第二次新建 0 条;手机号/车牌格式错的行单独列出 |
/qx/book.html?t=t1(预约)、/qx/progress.html?t=t1(查进度)。要让「关注后免输入车牌」生效还需要三步:① 公众号后台拿到 AppID / AppSecret; ② 配置「网页授权域名」为本站域名;③ 后端用 code 换 openid,然后
POST /pub/{tid}/bind 存下 openid,
之后 GET /pub/{tid}/progress?openid=… 就能直接出结果。AppSecret 只留在服务端,浏览器不接触。菜单在微信里打开时把 openid 拼进链接即可:
/qx/progress.html?t=t1&openid=oXXXX;
没绑过会退回「车牌 + 手机后四位」表单,查成功一次自动绑定。
role != owner 的请求里
直接从响应里去掉(cost_cents / parts_cost_cents / gross_cents 返回 null 或不存在)。
不是前端 v-if 藏起来——师傅打开 DevTools 也拿不到。前台该看到的是"领了哪些、谁领的、库存还剩几个"。3.2 P1(有余力再做,只为演示加分)
| 编号 | 模块 | 做到什么程度就够 |
|---|---|---|
| P1-1 | 工作台迷你图 | 近 7 天进场台次柱状图(纯 SVG,不引图表库) |
| P1-2 | 库存简版 | 只做"机油剩 3 桶"的预警角标,不做入库出库 |
| P1-3 | 多租户切换器 | 演示模式可见,生产模式隐藏(见第 6 节) |
| P1-4 | 深色模式 | 跟随后系统,只保证工作台不刺眼 |
3.3 这一版明确不做
财务对账 · 电子发票 · 保险理赔对接 · 配件采购单与供应商对账(只做了入库与领料,没做采购流程)· 员工提成 · 连锁总部 · 会员储值 · 微信真实支付 · 短信真实发送 · OBD/诊断仪 · 多语言(只做简体中文)· 权限体系(demo 只区分"前台/技师"两个角色)。
4 页面与路由
全部挂在 /t/:tenantId/ 下,共 8 个页面 + 1 个登录。手机优先,底部四个 Tab:工作台 / 开单 / 客户 / 我的。
| 路由 | 页面 | 关键元素 | 空状态文案 |
|---|---|---|---|
/login | 登录 | 手机号 + 验证码(demo 任意 6 位通过)+ 演示租户一键进入 | — |
/t/:id/ | 工作台 | 三个数字卡、最近工单、7 天柱图、待提醒角标 | "今天还没有车进场" |
/t/:id/new | 接车开单 | 车牌键盘、客户卡片、项目选择、合计条、保存 | "输入车牌开始" |
/t/:id/order/:no | 工单详情 | 状态条、项目明细、照片、操作按钮(发报价/开工/完工/结算) | "这单还没开始" |
/t/:id/orders | 工单列表 | 按状态筛选(待施工/施工中/待结算/已完成) | "没有符合条件的工单" |
/t/:id/customers | 客户列表 | 搜索(姓名/手机/车牌)、按最近到店排序 | "还没有客户,开第一单就有了" |
/t/:id/customer/:cid | 车辆档案 | 车主信息、车辆、历史工单、累计消费、下次保养 | — |
/t/:id/reminders | 到期提醒 | 本月/下月分组、一键标记已联系、消息模板预览 | "这个月没有到期的车" |
/t/:id/settings | 设置 | 门店信息、工位、员工、价目表 | — |
4.1 每个页面必须有的四样东西
- 骨架屏:mock 加 300ms 延迟,页面先出骨架再出数——没有骨架的 demo 看起来像网页,不像产品。
- 空状态:每个列表都要有,文案用上面表格里的,不要写"暂无数据"。
- 操作反馈:保存/结算后必须有 toast + 状态变化,不能点完没反应。
- 返回和面包屑:手机端左上角返回,桌面端顶部路径,别让老板迷路。
5 数据模型
八张表,字段按 demo 够用即可;tenant_id 是所有表的第一个字段。
| 表 | 字段 | 说明 |
|---|---|---|
| tenant | id, name, phone, address, plan, created_at | 一个租户 = 一家门店 |
| staff | id, tenant_id, name, role(前台/技师), phone | demo 不做权限,只做视角切换 |
| customer | id, tenant_id, name, phone, wechat, level, total_amount | 手机号唯一(租户内) |
| vehicle | id, tenant_id, customer_id, plate, brand, model, vin, mileage, last_service_at | 车牌唯一(租户内) |
| service_item | id, tenant_id, name, price, cost, duration_min, category | 价目表,开单直接引用 |
| work_order | id, tenant_id, no, vehicle_id, customer_id, status, total, discount, paid, pay_method, created_at, finished_at | 状态:待施工→施工中→待结算→已完成 |
| order_item | id, tenant_id, order_id, item_id, name, price, qty, tech_id | 下单时快照价格,改价目表不影响历史单 |
| reminder | id, tenant_id, vehicle_id, type(保养/保险), due_at, status(待提醒/已联系/已到店) | 按 last_service_at + 里程推算 |
6 多租户方案
你的判断是对的:一店一租户,天然隔离,也天然好卖——"你的数据只有你能看到"这句话老板听得懂, 比讲"行级权限"有用。
6.1 demo 阶段(这一版)
| 层 | 做法 | 为什么够用 |
|---|---|---|
| 路由 | /t/:tenantId/... | 换 URL 就是换店,演示时可以直接切 |
| 存储 | localStorage key 前缀 qx:{tenantId}: | 切租户 = 换命名空间,物理上不串数据 |
| mock 接口 | 所有请求自动带 X-Tenant-Id,mock 层按它取数据 | 提前把接口形态定对,后面换真后端不用改页面 |
| 切换器 | 顶部下拉,仅 ?demo=1 时显示 | 给老板看时不会露馅 |
| 一人多店 | user_tenant 关联表:一个账号可挂多店,老板账号挂 3 家;切换只换路由,令牌不变 | 演示"换一家店数据全变",也对应连锁的真实场景 |
6.2 生产阶段(demo 之后才做)
| 等级 | 隔离方式 | 成本 | 什么时候上 |
|---|---|---|---|
| S1 | 共享库,行级 tenant_id + 全表强制过滤 | 最低 | 前 20 家 |
| S2 | 共享库,每租户独立 schema | 中 | 20–100 家,或有客户明确要求 |
| S3 | 独立数据库/容器,一客一实例 | 高 | 连锁客户、要求本地部署的 |
tenant_id 必须由服务端从登录态解析,
永远不接受前端传上来的 tenant_id。demo 阶段可以放在 header 里图方便,
但要在代码里留一行 // TODO: 生产环境改为从 JWT 解析,别让它混进正式版。7 技术选型与约定
| 层 | 选什么 | 不选什么 |
|---|---|---|
| 框架 | Vue 3 + Vite + TypeScript | 不要 Nuxt(SSR 对 demo 没用,还多一层坑) |
| 状态 | Pinia,按租户分 store 实例 | 不要 Vuex |
| 路由 | Vue Router 4,history 模式 | — |
| UI | 自己写 8 个组件:按钮/输入/卡片/列表项/状态标签/空状态/骨架/toast | 不要引 Vant/Element:默认样式太重,改起来比写还慢 |
| 样式 | CSS 变量 + 单文件 scoped,按 ui.html 的令牌取值 | 不要 Tailwind(agent 容易写出无法维护的长串) |
| mock | 一个 src/mock/ 目录:与真接口同签名的适配器,localStorage 持久化,仅作离线兜底 | 不要让它成为唯一数据源,也不要把写死的数据塞进组件 |
| 图标 | 内联 SVG,不超过 10 个 | 不要图标库 |
7.1 目录结构(强制)
src/
main.ts
router/index.ts # /t/:tenantId 嵌套路由 + 守卫
stores/ # tenant.ts order.ts customer.ts
mock/
db.ts # 种子数据:3 个租户 × 全量
api.ts # 统一出口:get/post,300ms 延迟,按 tenant 隔离
components/ # 8 个基础组件
pages/
Login.vue Dashboard.vue NewOrder.vue OrderDetail.vue
OrderList.vue Customers.vue CustomerDetail.vue Reminders.vue Settings.vue
styles/tokens.css
7.2 组件清单(只做这 8 个)
| 组件 | 用途 | 关键要求 |
|---|---|---|
| QxButton | 主/次/危险三种 | 高度 44px,禁用态可见 |
| QxInput | 文本/数字/车牌 | 聚焦有 2px 描边,错误有红框 + 提示 |
| QxCard | 内容容器 | 圆角 12,边框 1px,内边距 16 |
| QxCell | 列表项 | 左标题右值,可带箭头 |
| QxTag | 工单状态 | 四种状态四种色,浅底深字 |
| QxEmpty | 空状态 | 一句人话 + 一个行动按钮 |
| QxSkeleton | 骨架屏 | 至少列表和卡片两种 |
| QxToast | 操作反馈 | 1.8 秒自动消失,不遮挡底部 Tab |
8 给 Agent 的开发任务
分五个迭代,每个迭代跑通再进下一个。每次交付都要给一个能打开的链接,不许只给代码。
| 迭代 | 做什么 | 完成标准(DoD) |
|---|---|---|
| I1 骨架 | Vite 工程、路由、Token CSS、8 个组件、Tab 布局 | 8 个页面都能点开,全是空状态,不像半成品 |
| I2 开单主链路 | 接车开单 → 工单详情 → 结算,全走 mock | 能从输车牌一路走到"已完成",刷新后数据还在 |
| I3 客户与提醒 | 客户列表/档案、到期提醒、工作台取数 | 结算一单后,客户累计消费和提醒日期自动更新 |
| I4 多租户 | tenantId 路由、命名空间隔离、切换器 (已完成) | 切到另一个租户看不到上一家的任何数据 |
| I5 打磨 | 骨架屏、toast、空状态文案、手机适配、P1 小图 | 390px 下无横向滚动,3 分钟演示一遍不断 |
| I6 接真接口 | 默认接第 11 节的真 API,mock 降级为离线兜底;报价链接与车主确认 (已完成) | 断网/401/409 都有提示,不再落回空白页 |
| I7 平板适配 | 按第 12 节做 834 与 1194 两档:列表 + 详情双栏、左侧导航固定 (已完成) | iPad 横屏不出现"手机放大版" |
| I8 配件与领料 | 配件库、入库盘点、工单内领料与退料、成本进毛利 (已完成) | 领 1 桶机油,库存 -1、该单成本 +220、结算毛利跟着少 220 |
| I9 权限与大屏 | 成本/毛利按角色服务端脱敏;老板营业大屏(挂墙)(已完成) | 前台登录看不到任何成本;大屏 1440 与 iPad 都一屏放得下 |
| I10 员工提成 | 提成规则表、结算自动计提、按月汇总与明细、技师只看自己 (已完成) | 结算一单,提成页立刻多一条;改比例不动历史 |
| I11 客户看板 | 面向车主的进度大屏:繁忙度、空闲工位、三列进度,服务端脱敏(车牌后四位、无金额无姓名) (已完成) | 前台/技师/老板三个账号打开看到的是同一份脱敏数据 |
| I12 预约到店 | 按天预约表、到店开单、过期提醒;工时(分钟)在价目表里维护,是全系统时间估算的唯一来源 (已完成) | 预约 2 个项目,开出来的工单项目和金额一模一样 |
9 验收清单
十二条,逐条对,不允许"基本完成"。
| # | 验收项 | 通过标准 |
|---|---|---|
| 1 | 3 分钟演示 | 从工作台到提醒,一次走完不卡壳、不报错 |
| 2 | 手机可用 | 390×844 下无横向滚动,按钮不重叠 |
| 3 | 开单速度 | 熟手 30 秒内完成一单 |
| 4 | 数据持久化 | 刷新、关浏览器再开,数据还在 |
| 5 | 租户隔离 | 切租户后工作台、客户、工单全部换掉 |
| 6 | 状态机 | 待施工→施工中→待结算→待交车→已完成。前四步由 status 走,待交车是「已收款 + 未交车」推出来的:已交车未收款仍算待结算,已收款未交车才算待交车 |
| 7 | 金额一致性 | 明细合计 = 工单总额 = 结算金额 |
| 8 | 空状态 | 每个列表都有,文案是人话 |
| 9 | 加载态 | 有骨架屏,不是白屏转圈 |
| 10 | 操作反馈 | 每个写操作都有 toast |
| 11 | 价格快照 | 改价目表后,历史工单金额不变 |
| 12 | 无死链 | 点不到"开发中"空白页 |
| 13 | 越权拦截 | 用 t1 的令牌请求 /t/t2/... 返回 403,页面提示"无权访问" |
| 14 | 状态机 | 跳过"施工中"直接完工返回 409,页面给出人话提示 |
| 15 | 金额口径 | 接口只传「分」,页面显示前才除以 100,全程不出现 380.0000001 |
| 16 | 平板可用 | 1024×768 与 1194×834 下双栏不挤压、表格不横向滚动 |
10 假数据规格
种子数据决定 demo 像不像真的。三个租户,一个是他演示用的"主店",另两个用来演示隔离。
| 租户 | 门店 | 数据量 | 用途 |
|---|---|---|---|
| T1 | 鑫达汽修(宝安店) | 客户 24、车辆 28、工单 46(含今日 8)、提醒 37 | 主演示 |
| T2 | 鑫达汽修(龙华店) | 客户 11、车辆 12、工单 15 | 演示"多店数据分开" |
| T3 | 顺发快修 | 客户 5、车辆 5、工单 6 | 演示"换一家店是另一套数据" |
工单要有真实感:项目名用行业说法(小保养、四轮定位、更换前刹车片、空调清洗、变速箱油), 金额落在 180–2,800 之间,不能全是整数;车牌用粤 B/粤 S 开头;客户名用"张先生/李女士"这类称呼。 今日 8 台车里,要包含 1 台事故车、1 台只做洗车、1 台待结算超 3 天。
11 API 契约(已实现)
这一版不是"先 mock、后补接口"。后端已经写完并在线跑着,前端照这份契约对接,后面不用重做。 写 mock 只是为了让你在没有网络时也能点,数据结构必须和真接口一模一样。
https://ssa.agent2.0755xzb.club/api/v1
人读文档 /api/v1/docs
机器可读 /api/v1/openapi.json
健康检查 /api/v1/health11.1 通用约定
| 项 | 约定 | 为什么 |
|---|---|---|
| 信封 | {"ok":true,"data":…} / {"ok":false,"error":{"code","message"}} | 前端只写一处解包和一处报错,页面里不出现 if (res.code === 200) |
| 金额 | 一律「分」,字段名带 _cents | 0.1+0.2 的坑永远不踩;显示时再除 100 |
| 鉴权 | Authorization: Bearer <token>,token 7 天有效 | token 绑员工,员工绑租户 |
| 租户 | 路径 /api/v1/t/{tenantId}/…,且必须与 token 所属租户一致 | 前端传什么都不算数,越权直接 403 |
| 分页 | ?page=1&size=20 → {items,total,page,size} | 列表页统一,不用每个接口各写一套 |
| 时间 | 2026-11-10 14:32:05(本地时间) | 老板只看本地时间,不出现 UTC |
| 错误码 | VALIDATION 400 · UNAUTHORIZED 401 · TENANT_FORBIDDEN 403 · NOT_FOUND 404 · STATE_CONFLICT 409 · RATE_LIMIT 429 | 前端按 code 映射中文提示,不直接弹 message |
11.2 演示账号(种子数据已在库里)
| 门店 | tenantId | 手机号 | 角色 |
|---|---|---|---|
| 鑫达汽修(宝安店) | t1 | 13800000001 / 02 / 03 | 前台 / 技师长 / 技师 |
| 鑫达汽修(龙华店) | t2 | 13800000004 / 05 / 06 | 同上 |
| 顺发快修(虎门) | t3 | 13800000007 / 08 / 09 | 同上 |
| 陈老板(三家店) | t1 t2 t3 | 13800000000 | 老板,可切换门店 |
验证码在演示环境填任意 6 位数字即可。t1 里有 24 个客户、28 台车、46 张工单(含今天 8 张)、37 条到期提醒,
车牌从 粤B12000 起,可直接用来演。
11.3 接口清单
| 方法 | 路径(省略 /api/v1) | 说明 |
|---|---|---|
| POST | /auth/login | 登录换 token |
| POST | /auth/logout | 注销,token 立即失效 |
| GET | /me | 当前员工 + 所属租户 |
| GET | /t/{tid}/dashboard | 今日进场、待结算、30 天提醒、7 日曲线、低库存 |
| GET | /t/{tid}/settings | 门店信息 + 员工 |
| PUT | /t/{tid}/settings | 改门店名/电话/地址/工位数 |
| GET | /t/{tid}/items | 价目表 |
| POST | /t/{tid}/items | 新增项目 |
| PATCH | /t/{tid}/items/{id} | 改价(不影响历史工单) |
| GET | /t/{tid}/customers?q=&page= | 客户列表,按姓名/手机搜 |
| GET | /t/{tid}/customers/{id} | 客户档案 + 车辆 + 近 20 单 |
| GET | /t/{tid}/vehicles/lookup?plate= | 演示的关键接口:输车牌带出客户、上次保养、上次项目 |
| GET | /t/{tid}/orders?status= | 工单列表,按状态筛 |
| POST | /t/{tid}/orders | 接车开单(无此牌自动建客户+车辆) |
| GET | /t/{tid}/orders/{no} | 工单详情(含明细、照片、报价确认时间) |
| POST | /t/{tid}/orders/{no}/quote | 生成报价分享 token(发微信用) |
| POST | /t/{tid}/orders/{no}/confirm | 客户确认报价(演示用模拟点击) |
| PATCH | /t/{tid}/orders/{no}/status | 状态流转,不能跳步 |
| POST | /t/{tid}/orders/{no}/settle | 结算,自动更新客户累计并生成下次保养提醒 |
| GET | /t/{tid}/reminders?month= | 到期提醒 |
| POST | /t/{tid}/reminders/{id} | 标记已联系 / 已到店 |
| GET | /q/{quote_token} | 车主看的报价页(公开,无需登录),手机上直接打开 |
| POST | /q/{quote_token}/confirm | 车主点"确认这份报价",回写到工单 |
| GET | /t/{tid}/parts?q=&low=1 | 配件库(含低库存筛选、库存成本合计) |
| POST | /t/{tid}/parts | 新增配件 |
| PATCH | /t/{tid}/parts/{id} | 改价 / 改安全库存 |
| POST | /t/{tid}/parts/{id}/stock | 入库 / 盘点 / 报损(写流水) |
| POST | /t/{tid}/orders/{no}/requisition | 技师领料:扣库存 + 计入该单成本,库存不足返回 409 |
| POST | /t/{tid}/orders/{no}/returns | 退料:库存加回、成本冲减 |
| GET | /t/{tid}/screen | 老板大屏:营业额/进场/待结算/客单价/毛利/7 天趋势/最近工单 |
| GET | /t/{tid}/commission?month= | 提成汇总(老板看全员,技师只返回自己) |
| GET | /t/{tid}/commission/items?month=&staff_id= | 提成明细(含计提基数与比例) |
| PUT | /t/{tid}/commission/rules | 改提成规则(仅老板,403 拦非老板) |
| GET | /t/{tid}/board | 客户看板:在店台数、空闲工位、繁忙度、三列车辆(车牌脱敏,无金额、无车主全名) |
| POST | /t/{tid}/orders/{no}/deliver | 交车确认:写 delivered_at;重复交车 409,未完工 409 |
| GET | /t/{tid}/appointments?from=&to= | 预约列表(按天/区间),含项目、工时合计、状态 |
| POST | /t/{tid}/appointments | 新建预约,项目工时自动加总成预计时长 |
| PATCH | /t/{tid}/appointments/{id} | 改时间 / 取消(status=cancelled)/ 标记未到店 |
| POST | /t/{tid}/appointments/{id}/checkin | 到店开单:预约转工单,幂等(重复点返回同一张单) |
| PUT | /t/{tid}/items/{id} | 改价目表:售价/成本/工时(分钟)/工时费,仅老板 |
| GET | /pub/{tid}/info | 公开:门店信息、价目表、当前繁忙度(客户 H5 用) |
| POST | /pub/{tid}/book | 公开:客户自助预约(限频 6 次/小时/IP,同车牌不重复建) |
| GET | /pub/{tid}/progress?plate=&tail= | 公开:车牌 + 手机后四位查进度,只返回状态与时间,无金额 |
| POST | /t/{tid}/import/customers | 导入客户:CSV 文本进,返回新建/更新/跳过明细;技师 403 |
11.4 三个关键调用(下面是真实返回)
① 登录
POST /api/v1/auth/login
{"phone":"13800000001","code":"123456"}
{"ok":true,"data":{"token":"d3e0b41d…","expires_at":"2026-11-17 11:46:51",
"user":{"id":1,"name":"前台小周","role":"front"},
"tenants":[{"id":"t1","name":"鑫达汽修(宝安店)","city":"深圳"}]}}
② 输车牌带出(演示的"哇"时刻)
GET /api/v1/t/t1/vehicles/lookup?plate=粤B12000
{"ok":true,"data":{
"found":true,
"vehicle":{"plate":"粤B12000","brand":"大众","model":"朗逸","mileage":20000,
"last_service_at":"2026-05-23 05:47:30","next_due_at":"2026-11-19"},
"customer":{"id":1,"name":"张伟","phone":"13710000000","level":"普通",
"total_cents":4000,"visits":3},
"last_order":{"no":"T00001","status_text":"待施工","total_cents":56000},
"last_items":["小保养(全合成机油+机滤)","四轮定位"]}}
这一条回来,前台不用问一句"您贵姓",页面直接显示"张先生,上次 5 月 23 日保养,已经 6 个月了"。 整个 demo 的说服力,一半在这个接口上。
③ 结算(会同时写客户累计和下次提醒)
POST /api/v1/t/t1/orders/261110009/settle
{"discount_cents":2000,"pay_method":"微信"}
{"ok":true,"data":{"no":"261110009","total_cents":56000,"discount_cents":2000,
"paid_cents":54000,"pay_method":"微信",
"customer_total_cents":54000,"customer_visits":1,"next_due_at":"2027-05-09"}}
返回里的 next_due_at 就是"下次保养提醒"的日期,前端直接显示在结算成功页上。
一单结算 = 客户档案 + 累计消费 + 未来提醒,三件事一次做完,这是老板最能感知的价值。
11.5 多租户怎么验(当场就能证明)
老板最常问的一句是"我这店的数据别人看不看得到"。不用解释,敲给他看:
# 用 t1 前台的令牌,去访问 t2 的数据
curl -H "Authorization: Bearer <t1的token>" \
https://ssa.agent2.0755xzb.club/api/v1/t/t2/dashboard
{"ok":false,"error":{"code":"TENANT_FORBIDDEN","message":"无权访问该租户的数据"}} # 403
越权是服务端拦的,不是前端藏起来的。这句话比任何"数据安全"承诺都硬。
11.6 前端怎么接(不要绕过这一层)
| 文件 | 职责 | 硬性要求 |
|---|---|---|
src/api/http.ts | fetch 封装:拼 baseURL、带 token、解信封、抛 AppError | 全站只有一个地方发请求,组件里不出现 fetch |
src/api/index.ts | 按资源分方法:api.orders.create()、api.vehicles.lookup() | 方法签名即契约,mock 和真接口共用 |
src/api/mock.ts | 同样的方法签名,返回同样的结构,数据来自 fixtures | 开关 VITE_USE_MOCK,一行切换,页面代码不动 |
错误码到界面的映射(写死在 http.ts 里,别散落在页面):
| code | 界面表现 |
|---|---|
| VALIDATION | 对应输入框标红 + 行内提示,不弹窗 |
| UNAUTHORIZED | 清 token,回登录页,提示"登录已过期" |
| TENANT_FORBIDDEN | 整页空状态:"这个门店不属于当前账号" |
| STATE_CONFLICT | toast 显示后端那句中文(已经是人话),并刷新当前工单 |
| 网络失败 | 顶部条"网络断了,点这里重试",不清空已填内容 |
api.*,
要么走 mock.ts 里同样签名的方法。这条不守,等你换真接口时等于重写一遍。12 手机与平板布局
你说得对,这一版前端要兼顾手机和平板——因为前台用手机,老板看店用平板, 店里墙上挂一台 iPad 比给老板装个 App 更现实。但两端的版式不是"放大缩小",是两套信息密度。
12.1 三档版式
| 宽度 | 版式 | 导航 | 典型页面怎么摆 |
|---|---|---|---|
| <600(手机竖屏) | 单列,一屏一件事 | 底部 4 Tab | 列表点进去整页替换,左上角出现返回键 |
| 600–899(平板竖屏) | 两栏:列表 38% + 详情 62% | 底部 Tab 保留,或左侧窄栏 | 左点单右看单,不用来回跳 |
| 900–1199(平板横屏) | 三栏:导航 200 + 列表 320 + 详情自适应 | 左侧固定竖排导航 | 工作台放 4 张卡一行;工单列表与详情并排 |
| ≥1200(门店大屏/笔记本) | 同三栏,内容最大宽 1440 居中 | 左侧固定 | 表格显示更多列,行高压缩到 44 |
工单与客户两个模块已按这张表实现:834 起左列表右详情(点击不跳页,选中行左侧高亮), 手机端退回单列并在标题栏给一个返回键。
12.2 手机端(390 宽)必须守的六条
- 底部安全区:Tab 与提交栏加
padding-bottom: env(safe-area-inset-bottom),iPhone 上不被小横条压住。 - 输入不缩放:输入框字号 ≥16px,否则 iOS 聚焦时页面自己放大,老板会以为页面坏了。
- 键盘遮挡:聚焦输入框时
scrollIntoView({block:'center'}),别让金额输入框藏在键盘后面。 - 点得到:所有可点区域 ≥44×44,一行里不放两个小按钮。
- 单手:主操作放右下或底部,左上角只放返回。
- 不横滚:宽表格在容器里
overflow-x:auto,页面本身绝不能横向滚动。
12.3 平板端(834 / 1194)必须守的四条
- 不要拉伸:卡片和表单单行最宽 720,否则 iPad 横屏上一行字横跨 1000px,眼睛找不到下一行。
- 列表点选,不跳页:右栏原地换内容,选中行左侧加 3px 主色竖条——这是平板和手机最大的体验差别。
- 横竖屏都要能用:834×1194 与 1194×834 都测;横屏时双栏,竖屏时也双栏(不要退回单列)。
- 触控与鼠标共用:hover 效果只在
@media (hover:hover)里生效,否则平板上点完状态就卡住。
12.4 演示设备清单
| 设备 | 尺寸 | 演示什么 |
|---|---|---|
| 你自己的手机 | 390×844 | 主链路:接车 → 报价 → 报工 → 结算 |
| 老板的手机 | 360–430 宽 | 让他自己点一下"我的车",比你说十句有用 |
| iPad(或安卓平板) | 834×1194 / 1194×834 | 挂在墙上:今天的车、待结算、到期提醒 |
| 店里的旧笔记本 | 1280×800 | 前台开单,键盘输车牌比手机快 |
本文档 V1.0,与需求说明书(35 条 FR)、开发设计文档、界面规范、AI 能力方案配套使用; 本阶段只取其中 P0 子集。文档编号 QX-MVP-2026-001。