跳转至

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
调用 APIGET /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
调用 APIGET /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
调用 APIPUT /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
调用 APIPOST /cpt/orders/{id}/attachments

当前 R2 对象存储未实现,API 仅登记元数据并返回占位 URL,重点验证: - 调用相机 / 相册(expo-image-picker 权限弹窗) - 照片缩略图显示 - 附件元数据登记成功

操作步骤: 1. 点击「拍照上传」或「从相册选择」 2. 确认权限弹窗 3. 选择 / 拍摄照片 4. 添加备注(如:安装前 / 安装后) 5. 点击「上传」

验证要点: - [ ] 首次调用触发相机 / 相册权限弹窗 - [ ] 照片选择后缩略图正确显示 - [ ] 上传后附件记录出现在订单详情 → 附件列表 - [ ] 同一订单上传多张照片不重复报错

3.8 WKR-07:完工登记(P0)

路径:/order/[id]/index.tsx(详情页操作区)
调用 APIPOST /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(详情页操作区「上报异常」按钮)
调用 APIPOST /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
调用 APIGET /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
调用 APIGET /cst/categories

操作步骤: 1. 登录后进入首页,显示 8 大品类卡片 2. 点击某个品类(如「空调安装」)进入询价表单

验证要点: - [ ] 8 大品类图标 + 名称 + 描述正确显示(与 MDS → 服务品类列表对比) - [ ] 点击品类跳转 /inquiry/{categoryId},categoryId 正确传递 - [ ] 品类加载失败有错误提示和重试按钮

4.4 CST-03:询价表单(P0)

路径:/inquiry/[categoryId].tsx
调用 APIPOST /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
调用 APIGET /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
调用 APIPOST /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。