DIP1-P3-B 移动端联调操作指南(师傅端 + 客户端)¶
文档编号:DWLG-20260811-01
简称:P3-B MOB-GUIDE
版本:V1.0
日期:2026-08-11
前置条件:P3-A(OPR Web × 后端)联调已通过 R3,后端服务 http://localhost:8000 健康运行,50 单 UAT 测试数据已生成
一、阶段总览¶
1.1 P3-B 联调范围¶
graph TD
subgraph P3A["🟢 P3-A 已完成:OPR Web × 后端"]
OPR["运营后台 15+ 页"]
BE["后端 5+1 域 65 API"]
OPR --> BE
end
subgraph P3B["🟡 P3-B 进行中:移动端 × 移动认证 × 推送"]
WKR["🧑🔧 师傅端 WKR App\n8 页面"]
CST["👤 客户端 CST App\n8 页面"]
MAUTH["📱 移动认证\nOTP 登录"]
PUSH["🔔 推送服务\nExpo Push"]
end
WKR --> MAUTH
WKR --> BE
WKR --> PUSH
CST --> MAUTH
CST --> BE
CST --> PUSH
联调目标:
| 模块 | 页数 / 端点 | 核心流程 | 优先级 |
|---|---|---|---|
| 师傅端 WKR | 8 页 | 登录 → 看板 → 接单 → L6 作业 → 完工登记 → 异常上报 | P0 |
| 客户端 CST | 8 页 | 登录 → 询价 → 报价确认 → 跟踪进度 → 师傅信息 → 评价 → 推荐 | P0 |
| 移动认证 MAUTH | 3 端点 | OTP 发送 → OTP 验证 → JWT 签发 | P0 |
| 推送 PUSH | 2 端点 | Expo Push Token 注册 → 订单状态变更推送 | P1 |
二、环境准备¶
2.1 后端与数据确认¶
先确认后端服务和 50 单 UAT 数据就绪:
# 1. 后端健康检查
curl http://localhost:8000/health
# 期望:{"status":"ok","version":"..."}
# 2. 确认 UAT50 数据(用冒烟测试脚本)
cd dip1/backend
.venv\Scripts\activate
pytest tests/smoke/smoke_12_endpoints.py -v
# 期望:16 passed
2.2 移动端 API 地址配置(关键!)¶
移动端通过 EXPO_PUBLIC_API_URL 环境变量指向后端,不同运行方式地址不同:
| 运行环境 | API 地址 | 配置方式 |
|---|---|---|
| iOS 模拟器 | http://localhost:8000/api/v1 |
默认值,无需配置 |
| Web 预览 (Expo Web) | http://localhost:8000/api/v1 |
默认值,无需配置 |
| Android 模拟器 | http://10.0.2.2:8000/api/v1 |
必须配置,localhost 指向模拟器自身 |
| iOS/Android 真机 | http://<电脑局域网IP>:8000/api/v1 |
必须配置,手机和电脑同 WiFi |
配置步骤:
# 在对应 app 目录创建 .env 文件
# --- 师傅端 ---
cd dip1/frontend/apps/wkr-app
@"
EXPO_PUBLIC_API_URL=http://10.0.2.2:8000/api/v1
"@ | Out-File -Encoding utf8 .env
# --- 客户端 ---
cd dip1/frontend/apps/cst-app
@"
EXPO_PUBLIC_API_URL=http://10.0.2.2:8000/api/v1
"@ | Out-File -Encoding utf8 .env
# 真机调试请改为电脑局域网 IP,例如:
# EXPO_PUBLIC_API_URL=http://192.168.1.100:8000/api/v1
⚠️ 修改 .env 后必须重启 Expo(Ctrl+C 停掉再
npm start),环境变量不会热更新。
2.3 启动移动端开发服务¶
# 师傅端
cd dip1/frontend/apps/wkr-app
npm install # 首次或依赖变更时
npm start # 启动 Expo Dev Server,默认端口 8081
# 新开一个终端,客户端
cd dip1/frontend/apps/cst-app
npm install
npm start # 会自动切换端口(如 19002),注意端口冲突
Expo 启动后终端会显示二维码和选项:
- 按 a → 打开 Android 模拟器
- 按 i → 打开 iOS 模拟器(仅 macOS)
- 按 w → 打开 Web 预览(最快验证方式,推荐首次联调先用 Web)
- 手机扫码 → 真机运行(需安装「Expo Go」App)
三、师傅端(WKR App)联调步骤¶
3.1 页面地图与 API 映射¶
graph LR
subgraph Auth["认证组 (2页)"]
L["(auth)/login.tsx\n登录页"]
L -->|OTP| A1["POST /auth/otp-send"]
L -->|验证| A2["POST /auth/otp-verify"]
L -->|账密| A3["POST /auth/login"]
end
subgraph Tabs["底部 Tab (4页)"]
H["(tabs)/index.tsx\n首页看板"] --> W1["GET /cpt/orders/board"]
O["(tabs)/orders.tsx\n订单列表"] --> W2["GET /cpt/orders"]
S["(tabs)/schedule.tsx\n排班"] --> W3["(预留)"]
P["(tabs)/profile.tsx\n个人中心"] --> W4["GET /auth/me"]
end
subgraph OrderFlow["订单子流 (3页)"]
OD["order/[id]/index.tsx\n订单详情"] --> W5["GET /cpt/orders/{id}"]
OD --> W6["POST /cpt/orders/{id}/transition"]
CK["order/[id]/checklist.tsx\nL6 作业清单"] --> W7["PUT /cpt/orders/{id}/stages/L6/data"]
PH["order/[id]/photos.tsx\n照片上传"] --> W8["POST /cpt/orders/{id}/attachments"]
end
Auth --> Tabs --> OrderFlow
3.2 WKR-01:登录认证(P0)¶
两种登录方式:
| 方式 | 适用场景 | 操作步骤 | 预期结果 |
|---|---|---|---|
| 账密登录(开发联调推荐) | 快速登录,绕过 OTP | 切换到「账密」Tab → 输入 admin / admin123 → 点击「登录」 |
跳转到底部 Tab 首页,SecureStore 保存 token |
| 手机号 OTP(正式流程) | 验证真实登录流 | 切换到「OTP」Tab → 输入师傅手机号(见下方测试账号表)→ 发送验证码 → 输入开发环境回显的 6 位 OTP → 验证登录 | 验证码页面显示回显值,验证通过进入首页 |
测试师傅账号(来自 UAT50 种子数据):
# 查询 UAT50 生成的测试师傅手机号(可在 OPR Web → 师傅列表查看)
# 或者直接用后端 API:
curl http://localhost:8000/api/v1/mds/workers -H "Authorization: Bearer <ADMIN_TOKEN>"
# 示例输出:7 位师傅,手机号从 59920001 到 59920007
验证要点:
- [ ] 账密登录 admin/admin123 成功
- [ ] 登录后跳转 /(tabs) 不回退
- [ ] 冷启动 App(杀掉重开)自动恢复登录态(SecureStore hydrate)
- [ ] 401 时自动清除登录态跳回登录页(可临时后端停掉模拟)
- [ ] 手机号 OTP 发送 → 页面回显开发验证码 → 验证成功
3.3 WKR-02:首页看板(P0)¶
路径:/(tabs)/index.tsx
调用 API:GET /cpt/orders/board
看板按 pool_label 分 4 组: - 待指派池(L2 报价完成待指派师傅) - 待接单池(L3 已指派待师傅确认) - 进行中池(L4-L7 执行中) - 已完成池(L8 已完结)
操作步骤: 1. 登录后自动进入首页 2. 下拉刷新(如已实现) 3. 点击各池中的「订单卡片」进入详情
验证要点:
- [ ] 4 个跟踪池正确分组,数量与 OPR Web 看板一致
- [ ] 订单卡片显示:客户名、地址、品类、阶段标签
- [ ] 点击卡片正确跳转到 /order/{id}
- [ ] RLS 权限验证:仅显示当前登录师傅被指派的订单(用不同师傅账号登录对比)
3.4 WKR-03:订单列表页(P0)¶
路径:/(tabs)/orders.tsx
调用 API:GET /cpt/orders?stage=...&q=...
操作步骤: 1. 点击底部 Tab「订单」 2. 切换阶段筛选 Tab(全部/待接单/进行中/已完成) 3. 搜索框输入关键词搜索 4. 下拉刷新、上滑分页
验证要点: - [ ] 阶段筛选与后端 stage 参数正确联动 - [ ] 搜索功能(客户名/地址/订单号)返回正确结果 - [ ] 分页加载(page_size=50,UAT50 数据足够验证)
3.5 WKR-04:订单详情 + 接单(P0)¶
路径:/order/[id]/index.tsx
调用 API:
- GET /cpt/orders/{id} — 详情 + 阶段时间线
- POST /cpt/orders/{id}/transition — 阶段流转
操作步骤(用 L3 阶段订单测试): 1. 从看板「待接单池」点击一个 L3 订单 2. 查看订单详情:客户信息、楼宇地址、品类、报价金额、阶段时间线 3. 点击「确认接单」按钮 → 触发 transition(L3→L4) 4. 查看阶段时间线更新
验证要点: - [ ] 订单详情字段完整渲染(与 OPR Web 详情页对比) - [ ] 阶段时间线节点正确,当前阶段高亮 - [ ] 接单后(L3→L4):当前阶段更新,按钮变为「开始作业」,订单从待接单池消失 - [ ] 重复接单:第二次点击按钮应报错或按钮已禁用(状态机防重)
3.6 WKR-05:L6 作业清单(P0 ⭐核心流程)¶
路径:/order/[id]/checklist.tsx
调用 API:PUT /cpt/orders/{id}/stages/L6/data
L6 作业规范 6 节点(穿鞋套 → 地面保护 → 开箱验货 → 安装作业 → 清洁现场 → 垃圾带走),每项需勾选 + 记录工时。
操作步骤(用 L5 已上门待作业订单测试): 1. 从详情页点击「进入 L6 作业」 2. 逐项勾选 6S 检查项 3. 填写实际工时(小时) 4. 点击「提交作业记录」 5. 返回详情页确认 L6 数据已回填
验证要点: - [ ] 6 项全部勾选后提交按钮才可用 - [ ] 工时字段必填且为正数 - [ ] 提交后 L6 阶段状态变为 completed - [ ] OPR Web 同一订单详情页查看 L6 数据是否同步(数据一致性验证)
3.7 WKR-06:照片上传(P0)¶
路径:/order/[id]/photos.tsx
调用 API:POST /cpt/orders/{id}/attachments
当前 R2 对象存储未实现,API 仅登记元数据并返回占位 URL,重点验证: - 调用相机 / 相册(expo-image-picker 权限弹窗) - 照片缩略图显示 - 附件元数据登记成功
操作步骤: 1. 点击「拍照上传」或「从相册选择」 2. 确认权限弹窗 3. 选择 / 拍摄照片 4. 添加备注(如:安装前 / 安装后) 5. 点击「上传」
验证要点: - [ ] 首次调用触发相机 / 相册权限弹窗 - [ ] 照片选择后缩略图正确显示 - [ ] 上传后附件记录出现在订单详情 → 附件列表 - [ ] 同一订单上传多张照片不重复报错
3.8 WKR-07:完工登记(P0)¶
路径:/order/[id]/index.tsx(详情页操作区)
调用 API:POST /cpt/orders/{id}/transition (L6→L7→L8)
操作步骤: 1. L6 作业完成后,回到订单详情 2. 点击「申请完工」(L6→L7,等待客户确认) 3. 或者用 L7 阶段订单,客户确认后后端进入 L8(师傅端刷新显示已完成)
验证要点: - [ ] 完工申请后状态从 L6 → L7 - [ ] 订单从「进行中池」移到「已完成池」(L8 时) - [ ] 完工后 L6 数据、照片、工时均可在详情页追溯
3.9 WKR-08:异常上报(P1)¶
路径:/order/[id]/index.tsx(详情页操作区「上报异常」按钮)
调用 API:POST /cpt/orders/{id}/transition (带 exception 参数)
操作步骤: 1. 打开一个 L4/L5/L6 阶段订单 2. 点击「上报异常」 3. 选择异常等级(轻微/严重/紧急) 4. 填写异常描述 + 上传异常照片 5. 提交
验证要点: - [ ] 异常上报后订单进入 EXCEPTION 池(OPR Web 看板可查) - [ ] 异常等级、描述、附件记录完整 - [ ] 异常处理完成后可恢复正常流转(需 OPR Web 后台操作解除)
3.10 WKR-09:个人中心(P1)¶
路径:/(tabs)/profile.tsx
调用 API:GET /auth/me
验证要点: - [ ] 师傅头像、姓名、手机号、等级(三级体系)正确显示 - [ ] 退出登录按钮:清除 SecureStore,跳回登录页 - [ ] 推送开关(如已实现):调用 /cst/push-preferences 或 FL 对应端点
四、客户端(CST App)联调步骤¶
4.1 页面地图与 API 映射¶
graph LR
subgraph AuthC["认证组 (1页)"]
CL["(auth)/login.tsx\n登录页"]
CL -->|OTP发送| C1["POST /auth/otp-send"]
CL -->|OTP验证| C2["POST /auth/otp-verify"]
end
subgraph TabsC["底部 Tab (4页)"]
CI["(tabs)/index.tsx\n首页/询价入口"] --> C3["GET /cst/categories"]
CO["(tabs)/orders.tsx\n我的订单"] --> C4["GET /cst/orders"]
CR["(tabs)/referral.tsx\n推荐中心"] --> C5["POST /cst/referral/code"]
CR --> C6["GET /cst/referral/rewards"]
CP["(tabs)/profile.tsx\n个人中心"] --> C7["GET /cst/profile"]
CP --> C8["PUT /cst/profile"]
end
subgraph FlowC["业务子流 (4页)"]
INQ["inquiry/[categoryId].tsx\n询价表单"] --> C9["POST /cst/inquiries"]
QT["quote/[orderId].tsx\n报价确认"] --> C10["GET /cst/orders/{id}/quote"]
QT --> C11["POST /cst/orders/{id}/quote/confirm"]
OT["order/[id]/index.tsx\n订单跟踪"] --> C12["GET /cst/orders/{id}"]
OT --> C13["GET /cst/orders/{id}/worker"]
RW["order/[id]/review.tsx\n评价页"] --> C14["POST /cst/orders/{id}/review"]
end
AuthC --> TabsC --> FlowC
4.2 CST-01:OTP 登录(P0)¶
客户端仅有 OTP 登录(无账密入口),首次登录自动创建客户档案。
测试客户手机号(UAT50 种子数据):
# 27 位测试客户,手机号从 59920101 到 59920127
# 或用全新手机号(会自动创建新客户记录)
操作步骤: 1. 打开 App 自动跳转 /(auth)/login 2. 输入手机号(如 59920101) 3. 点击「发送验证码」 4. 页面下方回显开发环境 OTP(6 位数字) 5. 输入 OTP → 点击「验证并登录」 6. 首次登录:自动创建客户 → 跳转首页 7. 二次登录:直接进入首页
验证要点:
- [ ] OTP 发送成功,回显值显示在页面下方(开发环境特性)
- [ ] 60 秒倒计时,期间「重发」按钮禁用
- [ ] 错误 OTP → 报错提示(如 OTP 过期 / 不匹配)
- [ ] 全新手机号登录后,OPR Web → 客户列表可查到新增记录
- [ ] 推荐码字段:如果带 referral_code 参数,确认推荐关系链建立
- [ ] 冷启动自动恢复登录态(SecureStore hydrate)
4.3 CST-02:首页 + 品类选择(P0)¶
路径:/(tabs)/index.tsx
调用 API:GET /cst/categories
操作步骤: 1. 登录后进入首页,显示 8 大品类卡片 2. 点击某个品类(如「空调安装」)进入询价表单
验证要点:
- [ ] 8 大品类图标 + 名称 + 描述正确显示(与 MDS → 服务品类列表对比)
- [ ] 点击品类跳转 /inquiry/{categoryId},categoryId 正确传递
- [ ] 品类加载失败有错误提示和重试按钮
4.4 CST-03:询价表单(P0)¶
路径:/inquiry/[categoryId].tsx
调用 API:POST /cst/inquiries
操作步骤: 1. 填写表单字段(根据品类不同字段略有差异): - 安装地址(楼宇类型 + 楼层 + 详细地址) - 产品参数(匹数/规格/数量,来自 SCL 特征库) - 期望上门时间 - 备注说明 2. 点击「提交询价」
验证要点: - [ ] 表单校验:必填字段为空时报错提示 - [ ] 提交成功跳转:L1 线索 → 自动生成订单号 → 跳转到报价等待页 - [ ] OPR Web → 线索列表 / 订单列表出现新增记录 - [ ] 报价自动计算:后端 QSV 引擎生成报价(可在 OPR Web 报价详情查看)
4.5 CST-04:报价确认页(P0)¶
路径:/quote/[orderId].tsx
调用 API:
- GET /cst/orders/{id}/quote — 获取报价明细
- POST /cst/orders/{id}/quote/confirm — 确认/拒绝报价
操作步骤(用 L2 待客户确认报价的订单测试): 1. 从订单列表进入一个 L2 订单 2. 跳转报价页,查看报价明细(基础费 + 楼宇附加 + 产品附加 + 封顶价) 3. 点击「确认报价」→ 状态 L2→L3 4. 或点击「拒绝报价」→ 填写拒绝原因 → 订单进入 REJECTED 状态
验证要点: - [ ] 报价金额与 OPR Web 同订单报价金额完全一致(核心一致性验证!) - [ ] 报价明细展开:各费用项名称、金额、计算依据 - [ ] 确认后:订单状态更新,进入 L3 师傅指派阶段 - [ ] 拒绝后:拒绝原因记录在订单审计日志 - [ ] 重复确认/拒绝:二次操作报错(状态机幂等)
4.6 CST-05:我的订单列表(P0)¶
路径:/(tabs)/orders.tsx
调用 API:GET /cst/orders?status=...
操作步骤: 1. 底部 Tab 点击「我的订单」 2. 切换筛选:进行中 / 已完成 / 全部 3. 点击某订单进入详情
验证要点:
- [ ] RLS 权限核心验证:客户 A 登录只能看到客户 A 的订单,看不到 B 的(用 2 个账号对比)
- [ ] 筛选 Tab 正确分组(进行中=未到 L8,已完成=L8)
- [ ] 订单卡片显示:订单号、品类、当前阶段、金额
- [ ] 点击跳转到 /order/{id} 正确
4.7 CST-06:订单跟踪 + 师傅信息(P0)¶
路径:/order/[id]/index.tsx
调用 API:
- GET /cst/orders/{id} — 阶段时间线 + 基本信息
- GET /cst/orders/{id}/worker — 师傅资料(L3 指派后可用)
操作步骤: 1. 从订单列表进入一个 L4+ 阶段的订单 2. 查看阶段时间线(L1→当前),每个节点显示时间戳 3. 点击「师傅信息」卡片,查看师傅头像、姓名、等级、评分、历史接单量 4. L6 阶段:查看作业照片和 L6 检查项记录
验证要点: - [ ] 阶段时间线节点与 WKR 端/OPR Web 端同一订单完全一致 - [ ] 师傅信息字段完整(注意隐私:不显示完整手机号) - [ ] L6 作业照片:缩略图可点击放大 - [ ] 当前阶段高亮,已完成阶段灰显
4.8 CST-07:服务评价(P0)¶
路径:/order/[id]/review.tsx
调用 API:POST /cst/orders/{id}/review
操作步骤(用 L7 待客户确认的订单测试): 1. 订单进入 L7 后,订单详情页显示「确认完工并评价」按钮 2. 点击进入评价页 3. 星级评分(1-5 星)+ NPS 分数(0-10)+ 文字评价 + 照片 4. 提交
验证要点: - [ ] 星级和 NPS 为必填 - [ ] 提交后订单 L7→L8(完结) - [ ] 评分数据回填到 WPL 师傅画像:师傅评分、NPS、累计评价数 - [ ] OPR Web → 师傅详情页评分同步更新
4.9 CST-08:推荐中心(P1)¶
路径:/(tabs)/referral.tsx
调用 API:
- POST /cst/referral/code — 生成/获取推荐码
- GET /cst/referral/rewards — 推荐奖励记录
操作步骤: 1. 底部 Tab 进入「推荐中心」 2. 点击「生成我的推荐码」 3. 复制推荐码或分享按钮 4. 查看推荐奖励记录列表
验证要点: - [ ] 推荐码生成后,第二次点击「生成」返回同一个(幂等) - [ ] 推荐码格式符合规则(如 6 位字母数字) - [ ] 新客户注册时使用该推荐码 → 本账号奖励记录新增一条 - [ ] 奖励金额、状态(待发放/已发放)正确显示
4.10 CST-09:个人中心(P1)¶
路径:/(tabs)/profile.tsx
调用 API:
- GET /cst/profile — 客户资料
- PUT /cst/profile — 修改资料
- GET /cst/push-preferences + PUT — 推送偏好
验证要点: - [ ] 客户姓名、手机号、地址显示正确 - [ ] 修改姓名 / 地址 → 保存成功 → 刷新后数据持久化 - [ ] 修改手机号 → 触发 OTP 重新验证(need_otp_reverify=true) - [ ] 推送偏好开关(订单进展推送 / 营销推送)修改后立即生效 - [ ] 退出登录:清除 token,跳回登录页
五、移动认证(MAUTH)联调¶
5.1 端点清单¶
| 端点 | 方法 | WKR 用 | CST 用 | 说明 |
|---|---|---|---|---|
/auth/otp-send |
POST | ✅ | ✅ | 发送 WhatsApp OTP,开发环境回显 otp 值 |
/auth/otp-verify |
POST | ✅ | ✅ | 验证 OTP,签发对应角色 JWT(FL / CUSTOMER) |
/auth/login |
POST | ✅(开发) | ❌ | 账密登录,仅限开发期 admin/admin123,生产禁用 |
/auth/me |
GET | ✅ | ❌(用 /cst/profile) | 获取当前登录用户信息 |
5.2 OTP 频控验证(P1)¶
# 连续 2 次发送 OTP,第二次应报 429 Too Many Requests
curl -X POST http://localhost:8000/api/v1/auth/otp-send `
-H "Content-Type: application/json" `
-d '{"whatsapp":"59920101"}'
# 间隔 < 60 秒再发一次 → 429
# 前端应显示 60s 倒计时阻止用户点击
5.3 Token 安全验证¶
- JWT token 存储在 SecureStore(iOS Keychain / Android Keystore),不泄露到日志
- 登录失败响应不提示「手机号不存在」vs「OTP 错误」(防信息泄露,统一错误信息)
- 退出登录时 refresh token 黑名单(如后端已实现)
六、推送服务(PUSH)联调¶
6.1 端点清单¶
| 端点 | 方法 | 说明 |
|---|---|---|
/push/register |
POST | 注册 Expo Push Token,关联 user_id/customer_id + device |
/push/send |
POST | 测试推送发送(预留,当前可能未实现业务触发) |
6.2 注册流程验证¶
App 启动时(或首次登录后)自动获取 Expo Push Token 并调用 /push/register:
// 伪代码:push.ts 中的注册逻辑
const { status: existingStatus } = await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus === 'granted') {
const token = (await Notifications.getExpoPushTokenAsync()).data;
await registerPushToken({ push_token: token, platform: 'IOS', app_type: 'WKR' });
}
验证要点:
- [ ] 首次启动请求推送权限弹窗
- [ ] mobile_push_devices 表新增记录:push_token + user_id + platform
- [ ] 同一设备二次启动不重复插入(按 push_token 去重)
6.3 业务推送场景(P1,可选)¶
触发订单状态变更时推送(需后端实现业务事件监听):
| 场景 | 接收方 | 推送内容示例 |
|---|---|---|
| L3 师傅被指派 | 师傅端 | 您有新订单待接单:客户 XXX,空调安装,HK$XXX |
| L2 报价生成 | 客户端 | 您的询价报价已生成,点击确认报价 |
| L6 作业完成 | 客户端 | 师傅已完成作业,请确认完工 |
| L8 订单完结 | 客户端 + 师傅 | 订单已完成,感谢使用织布鸟 |
当前阶段说明:如业务推送暂未接通,仅验证 Push Token 注册成功即可(P1 不阻塞 P3 放行)。
七、P3-B 联调清单(Checklist)¶
7.1 师傅端 WKR(共 24 项,P0=17 / P1=7)¶
| 编号 | 验证项 | 优先级 | 操作人 | 结果 | 备注 |
|---|---|---|---|---|---|
| W01 | 账密登录 admin/admin123 成功 | P0 | |||
| W02 | OTP 发送 → 回显 → 验证登录成功 | P0 | |||
| W03 | 冷启动恢复登录态(SecureStore hydrate) | P0 | |||
| W04 | 401 自动清态跳登录页 | P0 | |||
| W05 | 首页看板 4 池分组正确,数量与 OPR 一致 | P0 | |||
| W06 | RLS:仅看自己被指派的订单 | P0 | |||
| W07 | 订单列表筛选 + 搜索正确 | P0 | |||
| W08 | 订单详情字段完整(与 OPR Web 对比) | P0 | |||
| W09 | 接单(L3→L4)状态正确流转 | P0 | |||
| W10 | L6 作业 6S 勾选 + 工时提交 | P0 | |||
| W11 | L6 数据同步到 OPR Web 详情页 | P0 | |||
| W12 | 照片选择/拍摄 → 缩略图显示 | P0 | |||
| W13 | 照片附件登记成功(订单详情可见) | P0 | |||
| W14 | 完工登记(L6→L7→L8)流转 | P0 | |||
| W15 | 异常上报(等级 + 描述 + 照片) | P0 | |||
| W16 | 异常上报后订单进入 EXCEPTION 池 | P0 | |||
| W17 | 重复操作状态机不报错且幂等 | P0 | |||
| W18 | 个人中心资料正确显示 | P1 | |||
| W19 | 退出登录清除 token 跳回登录页 | P1 | |||
| W20 | 推送权限弹窗 + Token 注册成功 | P1 | |||
| W21 | 排班页(如已实现)基本渲染 | P1 | |||
| W22 | 首页下拉刷新 | P1 | |||
| W23 | 无网络时错误提示友好 | P1 | |||
| W24 | 切换不同师傅账号登录对比 RLS | P1 |
7.2 客户端 CST(共 24 项,P0=18 / P1=6)¶
| 编号 | 验证项 | 优先级 | 操作人 | 结果 | 备注 |
|---|---|---|---|---|---|
| C01 | 新手机号 OTP 登录 → 自动建客户档案 | P0 | |||
| C02 | 已有客户 OTP 登录成功 | P0 | |||
| C03 | OTP 60s 倒计时 + 重发禁用 | P0 | |||
| C04 | 错误 OTP 友好报错 | P0 | |||
| C05 | 首页 8 大品类正确渲染(与 MDS 一致) | P0 | |||
| C06 | 品类点击跳转询价表单,categoryId 正确 | P0 | |||
| C07 | 询价表单校验 + 提交成功 | P0 | |||
| C08 | 询价提交后 OPR Web 出现线索 + 订单 | P0 | |||
| C09 | 报价金额与 OPR Web 完全一致 | P0 | ⭐核心一致性 | ||
| C10 | 确认报价 → L2→L3 流转 | P0 | |||
| C11 | 拒绝报价(含原因)记录正确 | P0 | |||
| C12 | RLS:只能看到自己的订单(2 账号对比) | P0 | ⭐安全红线 | ||
| C13 | 订单列表筛选(进行中/已完成/全部) | P0 | |||
| C14 | 订单详情阶段时间线与 WKR/OPR 一致 | P0 | |||
| C15 | 师傅信息显示(注意隐私不含完整手机号) | P0 | |||
| C16 | L6 作业照片 + 检查项可见 | P0 | |||
| C17 | 评价提交(星级 + NPS + 文字) | P0 | |||
| C18 | 评价后订单 L7→L8,师傅评分同步更新 | P0 | |||
| C19 | 推荐码生成幂等(二次生成相同) | P1 | |||
| C20 | 推荐关系链 + 奖励记录正确 | P1 | |||
| C21 | 个人资料修改保存成功 | P1 | |||
| C22 | 修改手机号触发 OTP 重验证 | P1 | |||
| C23 | 推送偏好开关修改生效 | P1 | |||
| C24 | 退出登录清态 | P1 |
7.3 系统级(共 6 项,P0=5 / P1=1)¶
| 编号 | 验证项 | 优先级 | 操作人 | 结果 | 备注 |
|---|---|---|---|---|---|
| S01 | Android 模拟器 API 地址正确(10.0.2.2) | P0 | 常见坑 | ||
| S02 | iOS / Web 模拟器 localhost 正常 | P0 | |||
| S03 | OTP 60s 频控 429 正确返回 | P0 | |||
| S04 | JWT 存 SecureStore,日志中不泄露 | P0 | |||
| S05 | Push Token 注册成功(DB 可见) | P0 | |||
| S06 | 断网 / 弱网错误提示友好 | P1 |
八、三端数据一致性核心验证(⭐重中之重)¶
P3-B 联调的核心不是单端功能跑通,而是 OPR Web(运营)、WKR App(师傅)、CST App(客户)三端看到同一订单数据完全一致。
验证方法:选 3 个关键阶段订单,三端同时对照:
| 订单阶段 | OPR Web 验证点 | WKR App 验证点 | CST App 验证点 | 三端一致? |
|---|---|---|---|---|
| L2 待确认报价 | 订单详情→报价金额=X | - | 报价页→金额=X | ☐ |
| L6 作业中 | 详情→L6 检查项勾选状态 | checklist 页→勾选状态 | 跟踪页→L6 时间戳 | ☐ |
| L8 已完结 | 详情→评分=N,NPS=M | 个人中心→订单数+1 | 评价页→星级=N,状态=已完成 | ☐ |
如发现不一致: 1. 先看后端 API 原始响应(排除前端渲染问题) 2. 再看 DB 数据(排除 API 组装问题) 3. 最后对比前端字段映射(最常见 bug:category_id vs category_code 这种)
九、常见问题排查手册¶
Q1. 移动端无法连接后端(Network Error / ECONNABORTED)¶
99% 的情况是 API 地址配置错,按以下排查:
现象:页面一直 loading,最终报错「无法连接服务器」
排查步骤:
1. 确认后端是否真的在运行:电脑浏览器打开 http://localhost:8000/health
2. 确认 .env 文件存在且路径正确(在 app 根目录,不是 frontend 根目录)
3. 确认 API 地址格式:
✅ Android 模拟器:http://10.0.2.2:8000/api/v1
❌ Android 模拟器:http://localhost:8000/api/v1(错!指向模拟器自己)
✅ 真机:http://192.168.x.x:8000/api/v1(电脑局域网 IP)
❌ 真机:http://localhost:8000/api/v1(错!指向手机自己)
4. 真机:确认手机和电脑在同一个 WiFi,电脑防火墙没有拦截 8000 端口
5. 修改 .env 后必须重启 Expo!(Ctrl+C → npm start)
Q2. Expo 启动端口冲突¶
现象:Port 8081 is already in use
解决:
# 师傅端
npm start -- --port 8081
# 客户端(必须不同端口)
npm start -- --port 8082
或设置环境变量:set PORT=8082 && npm start
Q3. OTP 发送后页面没有回显验证码¶
现象:点击发送 OTP 成功,但页面下方不显示回显值
原因:后端 ENV !== development,不回显 otp 字段(安全设计)
解决:
1. 确认后端环境变量 ENV=development(或查看启动日志)
2. 或者查后端日志直接搜索 otp 明文输出(测试环境会打日志)
3. 或者改为账密登录模式绕过(WKR 端支持)
Q4. 登录成功但看板没有订单¶
现象:师傅登录后,首页看板全是空的
排查步骤:
1. 用 admin 登录 OPR Web → 订单列表,筛选阶段=L3(待接单)
2. 查看这些 L3 订单的「指派师傅」字段,是否是当前登录师傅
3. 如果不是:用 OPR Web 编辑订单 → 指派师傅 → 选择当前师傅 → 保存
4. WKR App 下拉刷新看板,订单出现
5. 本质:RLS!师傅只能看到自己被指派的订单,不是 bug 是设计
Q5. RLS 验证失败:客户 A 看到客户 B 的订单¶
如果出现这个问题 → 立即停止联调,记录为 P0 BLOCKER!
这是严重安全漏洞,必须修复后才能继续。
临时排查:
1. curl 方式直接调后端 API,用客户 A token 访问客户 B order_id
curl http://localhost:8000/api/v1/cst/orders/{ORDER_B_ID} -H "Authorization: Bearer <TOKEN_A>"
期望:403 / 404
如果返回 200 且数据正确 → DB 层 RLS 策略 bug
2. 检查 PostgreSQL RLS 是否真的启用:
SELECT relname, rowsecurity FROM pg_class WHERE relname LIKE 'cpt_%';
rowsecurity 必须是 true
十、联调执行记录模板¶
复制以下表格填写每轮结果(DWLG 编号对应归档):
| 轮次 | 日期 | 范围 | 总项 | 通过 | 失败 | 阻塞 | 判定 | 报告编号 |
|---|---|---|---|---|---|---|---|---|
| R1 | 2026-08-__ | WKR 24 + CST 24 + SYS 6 = 54 | ☐ PASS / ☐ FAIL | DWLG-202608__-__ | ||||
| R2 | ||||||||
| R3 |
放行标准:P0 项 100% 通过,P1 项通过 ≥ 80%,无安全类 P0 BLOCKER。