# 原型自测记录（QA）

> 执行日期：2026-11-04
> 环境：Node.js v24.21.0 · Python Playwright 1.63.0 / Chromium · 本地静态服务 `python3 -m http.server 8765`
> 范围：`prototype/` 下 18 个业务 HTML 页面 + 1 个总览页 + 5 个 CSS 文件。所有数据为本地演示数据。

## 一、自动检查

| 检查 | 命令 | 结果 |
|---|---|---|
| 设计系统 / 页面质量 | `node tools/design-check.mjs . --strict` | **100 分** · 24 个文件 · 令牌引用 506 次 · 硬编码颜色 0 处 |
| 需求追溯（平台工具） | `node tools/trace.mjs pc50` | **11 / 11 条**（100%），F1–F11 均有原型引用 |
| 先逛后登（平台工具） | `node tools/audit-guest-first.mjs http://127.0.0.1:8765 /prototype/index.html /prototype/h5/services.html /prototype/h5/service.html /prototype/h5/time.html /prototype/mp/services.html /prototype/mp/service.html /prototype/mp/time.html` | **7 / 7** 公开页免登录可看，0 登录墙 |
| 链接 | Playwright 遍历页面内 `a/link/script`（167 个链接） | 0 个 404 |
| 多宽度页面加载 | 18 页 × 375 / 768 / 1440 | 每页 1 个 h1；0 横向溢出；0 console error / pageerror |
| 总览页 | `index.html` × 375 / 768 / 1440 | 0 溢出；0 报错；46 个入口链接 |

## 二、主流程实测

### 1. H5 顾客：游客浏览 → 选时段 → 登录 → 提交 → 我的预约

- 服务列表：7 个上架项目；点「精致洗车（轿车）」进详情。
- 选时间：选 11/05（明天）10:00；09:00 / 15:00 等已占用或已过时段为 `aria-disabled=true`，不可选。
- 未登录点「登录并提交预约」：跳 `login.html?next=time.html?service=s-wash&date=2026-11-05&time=10:00`。
- 登录（13900001111 / 8888）后回到原页：已选日期、时段仍高亮（`aria-pressed=true`），项目/价格未丢。
- 再点提交：生成订单 `CG2611040101`，跳结果页；结果页显示订单号、服务、11月5日 10:00–10:45、金山店、¥58、待确认、已订阅微信提醒。
- 「查看我的预约」：4 条记录（3 条演示 + 1 条新建），累计预约 4、已完成 1；点「刷新状态」出现成功 toast。
- 失败路径：验证码填 1234 提示「验证码不对，演示验证码是 8888」；点「先不登录，返回上一步」回到 time 页且选择保留。
- 重复提交：同一用户/项目/日期/时段再次调用 `createBooking` 返回 `duplicate: true`，复用 `CG2611040101`，不新增订单。

### 2. 微信小程序：游客 → 微信登录 → 提交 → 我的预约

- 服务列表 7 张卡片；点「内饰深度清洁」（120 分钟）→ 选 11/05 12:00 → 提交时跳微信登录。
- 点「微信手机号快捷登录」后回原页，已选时段保留；提交生成新订单。
- 结果页、我的预约（4 条，累计 4 / 已完成 1）均正常；全流程 0 console error。

### 3. 门店后台：登录 → 名单 → 详情 → 状态

- 登录：账号 13800000002 / 123456 + 图形验证码。验证码错提示；连续错 5 次提示「锁定 1 分钟」且按钮禁用。
- 名单：默认 11 条 / 2 页，每页 8 条；按今天筛选 4 条；日期 + 项目 + 状态组合筛选 1 条。
- 空态：今天 + 精致洗车 + 已取消 → 表格隐藏、空态出现、分页隐藏，提示清除筛选。
- 详情抽屉：王女士 `CG2611040001`，会员信息显示累计预约 3 次、已完成 1 次、最近一次 11/06 10:00。
- 状态处理：待确认→确认预约成功，徽标变「已确认」，提示「已确认预约，微信提醒已发送（演示）」；状态记录追加时间。
- 非法流转：筛选已取消（周女士），点「确认预约」被拦截，提示「操作被拦截：已取消的预约不能再确认或完成，请让顾客重新预约。」
- 跨门店：用仓山店订单 `CG2611040012` 打开详情，提示「找不到这条预约，或它不属于金山店」，抽屉不打开。
- 跨端同步：同一浏览器里 H5 新建 11/05 12:00 订单 → 门店后台确认 → H5「我的预约」刷新，该单状态为「已确认」。

### 4. 服务项目：改价 / 新增 / 下架 / 校验

- 编辑「精致洗车（轿车）」58 → 68，保存后列表显示 ¥68；H5 顾客端首项立即显示 ¥68。
- 新增项目价格填 `0`：提示「价格必须大于 0；填 0 或负数不会保存」，URL 仍在编辑页，不落库。
- 价格填 `0.001`：按分取整后为 0，同样拦截。
- 价格填 88、时长 30、简介填写后保存：列表出现「测试项目」。
- 把「测试项目」改为下架：列表显示下架，H5 顾客端项目数仍为 7，不出现「测试项目」。
- 名称留空：提示「请填写服务项目名称」，不保存。

### 5. 管理后台：订单筛选 / 分页 / 导出

- 登录：admin / admin2026 + 图形验证码。
- 订单列表：16 条，金额合计 ¥2,308；每页 6 条，第 1 页 6 行，翻到第 2 页仍 6 行。
- 状态筛选：待确认 7 条 / 2 页；修改筛选后不点「查询订单」直接点导出，弹窗仍按当前筛选实时计算为 7 条。
- 导出：下载 `车管家洗车_预约名单_20261104.csv`，UTF-8 BOM，10 个字段；CSV 共 8 行（表头 + 7 条待确认），与筛选结果一致。
- 无结果：开始日期填 2030-01-01 → 列表空态，导出弹窗提示「当前筛选没有可导出的记录，未生成文件。」，页面上没有下载按钮。

## 三、响应式目视检查

| 宽度 | 重点看 | 结论 |
|---|---|---|
| 1440 | 后台侧栏 / 表格 / 详情抽屉 / 导出弹窗；H5/小程序居中手机壳 | 正常，表格不溢出，抽屉 480px |
| 768 | 后台侧栏折成顶部导航；筛选换行；表格 720px 最小宽 | 正常，预约表 6 列可见；订单表可横向滑动 |
| 375 | 后台筛选堆叠、表格横向滚动；H5/小程序全宽；Tabbar 吸底 | 正常，无横向页面溢出；PC 表在 `.table-wrap` 内滚动 |

已目视的页面包括：`index`、`h5/services`、`h5/service`、`h5/time`、`h5/login`、`h5/result`、`h5/my`、`mp/services`、`mp/time`、`mp/login`、`mp/result`、`mp/my`、`pc/login`、`pc/admin-login`、`pc/bookings`（含抽屉）、`pc/services`、`pc/service-edit`、`pc/orders`（含导出弹窗）。

## 四、无障碍 / 交互自查

- 每页一个 h1；表单控件都有可见 label；图标按钮都有 `aria-label`。
- 表单错误用 `role="alert"`；状态更新有 toast `role="status"`。
- 全局 `:focus-visible` 焦点环；抽屉打开后焦点移到关闭按钮，Esc 关闭，Tab 焦点限制在抽屉内。
- 动效只用 transform / opacity / 背景色；有 `prefers-reduced-motion: reduce` 兜底；没有 `transition: all`。
- 颜色全部走 `var(--token)`；`prototype/` 内硬编码颜色 0 处。
- 不依赖 emoji 图标；SVG 图标 `aria-hidden="true"`。

## 五、已知边界（原型不做 / 开发阶段补）

| 项目 | 原型处理 | 开发阶段 |
|---|---|---|
| 真实后端 / SQLite | 无，数据在 localStorage | 按 constitution 用 node:sqlite + WAL，接口层做权限 |
| 微信授权 / 手机号解密 | 小程序点按钮模拟 | 接 `wx.login` + `getPhoneNumber` |
| 短信验证码 | H5 固定 8888 | 接短信服务，验证码有效期 / 频控 |
| 微信预约提醒 | 结果页与后台显示「已订阅 / 已发送（演示）」 | 接订阅消息模板与发送记录 |
| 支付 | 不涉及（PRD 第一版不做在线支付） | 如后续加收款，统一走商云客 SYK |
| 多门店切换 | 不做切换器，顾客端固定金山店 | 按客户确认结果做 |
| 并发编辑 / 状态并发 | 单浏览器最后一次写入生效 | 接口层乐观锁 / 版本号，按 PRD 提示 |
| 分页越界 | 按钮在边界禁用；代码把越界页夹到最后一页 | 接口按 PRD 返回空页 |
| 导出大数据量 / 中断 | 弹窗写明重试与不产生残缺文件；本地 CSV 即时生成 | 服务端生成临时文件，完成后才给下载 |
| 真实门店名 / 营业时段 | 演示值：金山 / 仓山 / 台江，09:00–18:00，整点 | 客户确认后替换 |
| 浏览器禁用 localStorage | 演示数据无法跨页保留；页面内操作仍可完成 | 正式系统走服务端会话，不受影响 |

## 六、交付三问（Aegis）

1. **这个改动有没有必要？** 只做了 PRD 与报价内的 18 个原型页面；没有加支付、评价、优惠券等第一版不做的模块。会员信息、预约提醒按报价做成页内区块 / 状态，没有额外造页面。
2. **旧路径删干净了吗？** 原型从空目录开始，没有旧业务代码。`prototype/assets/tokens.css` 的重复令牌文件已删除，统一引用根目录 `assets/tokens.css`；没有遗留幽灵入口。
3. **验证证据在哪？** 本文件第一节的自动检查结果、第二节的 Playwright 断言输出、第三节的多宽度目视记录；命令可复跑。设计检查 100 分、追溯 11/11、公开页 7/7、链接 167/167。
