touyun-elderly-care/docs/superpowers/plans/2026-10-15-护理员APP-Flutter.md
liuyun e8a702eb93 docs: 护理员 APP Flutter 实施计划
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-15 08:42:47 +08:00

160 lines
8.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 护理员 APP(Flutter)实施计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 在 `sources/ec_flutter`(全新 Flutter 工程)落地护理员 APP:登录、M1 今日任务、M2 服务执行(签到/勾项目/拍照/签退)、异常上报、扑空、离线队列+自动补传。
**Architecture:** 全新客户端,feature-first。Riverpod(状态)+ dio(HTTP)+ sqflite(离线队列)+ go_router(路由)+ shared_preferences(token)。对接 C3 后端(无后端改动)。
**Tech Stack:** Flutter 3.38 + Dart 3.10、riverpod、dio、sqflite、go_router。
**Spec:** `openspec/changes/nurse-app-flutter/`;设计背景 `docs/superpowers/specs/2026-10-15-护理员APP-Flutter-design.md`。
## Global Constraints
- 后端 base `http://localhost:3000/api/v1`(用 `--dart-define=API_BASE_URL=` 可覆盖)。
- 统一响应 `{code,message,data}`:code=0 成功、非 0 抛业务错误(message 含规则号 3001/1001/2001/3003)。
- token 走 `Authorization: Bearer`;token 落 shared_preferences。
- 签到/签退时间**不传客户端值**(服务器取时);定位失败不阻塞(标「未获取」)。
- exec_result 枚举 `done/undo_not_done/undo_refused`(禁用 not_done)。
- 必拍未拍:前端签退按钮前置校验 + 后端 3001 双保险;拒拍出路(选原因放行)。
- 措辞纪律:留痕口径「签到/签退/服务执行」,禁「打卡/考勤」;异常不禁「报警」。
- 界面大字 ≥20pt、常态 ≤3 击、首屏即任务、误操作可撤销。
## Review Focus
1. **服务器取时**:checkin/checkout 请求体不得含 checkin_time/checkout_time/duration_min。
2. **必拍未拍**:必拍项未拍照时签退按钮禁用;拒拍出路选原因后放行。
3. **离线幂等**:每条离线记录生成 client_md5(内容 hash),补传按 md5 幂等,不重复。
4. **exec_result 措辞**:未执行用 undo_not_done(非 not_done);老人拒做用 undo_refused。
5. **扑空证据**:abnormal_type=not_met 时到访时间/定位/原因必填,缺一不可提交。
---
### Task 1: 工程骨架(依赖 + core)
**Files:** `sources/ec_flutter/pubspec.yaml`、`lib/core/**`、`lib/main.dart`。
- [ ] **Step 1: pubspec.yaml 加依赖**
```yaml
dependencies:
flutter: { sdk: flutter }
cupertino_icons: ^1.0.8
flutter_riverpod: ^2.5.1
dio: ^5.4.0
sqflite: ^2.3.0
shared_preferences: ^2.2.2
go_router: ^14.0.0
path_provider: ^2.1.2
path: ^1.9.0
image_picker: ^1.0.7
geolocator: ^12.0.0
crypto: ^3.0.3
```
- [ ] **Step 2: `lib/core/api/api_client.dart`**:dio 实例(baseUrl 从 `String.fromEnvironment('API_BASE_URL', defaultValue:'http://localhost:3000/api/v1')`);拦截器加 `Authorization: Bearer <token>`;响应拦截器解包 `{code,message,data}`(code≠0 抛 `ApiException(code,message)`)。
- [ ] **Step 3: `lib/core/api/`**:`auth_api.dart`(login)、`order_api.dart`(today)、`record_api.dart`(checkin/itemSave/photoUpload/checkout/offlineSync)、`abnormal_api.dart`(report)。
- [ ] **Step 4: `lib/core/auth/auth_provider.dart`**:token 存取(shared_preferences)+ 登录状态 provider。
- [ ] **Step 5: `lib/core/router.dart`**:go_router 路由(login/today/service/abnormal)。
- [ ] **Step 6: `lib/core/theme.dart`**:Material 3 主题,正文 ≥20pt。
- [ ] **Step 7: `lib/main.dart`**:ProviderScope + MaterialApp.router。
- [ ] **Step 8: `flutter analyze` 干净 + commit**
```bash
git -C sources/ec_flutter add -A
git -C sources/ec_flutter commit -m "feat(nurse): 工程骨架(dio/riverpod/sqflite/router/theme)"
```
---
### Task 2: 登录 + M1 今日任务
**Files:** `lib/features/auth/**`、`lib/features/today/**`。
- [ ] **Step 1: 登录页**:租户 code + 账号 + 密码三个输入框 → `POST /auth/login`(body `{tenantCode, username, password}`,返回 token)→ 存 token → `context.go('/today')`。登录失败显示 `ApiException.message`。
- [ ] **Step 2: 今日任务页**:`GET /order/today` → 卡片列表(老人名/时段/项目名/工单状态),空态「今日暂无任务」,点卡片 `context.go('/service?orderId=...')`。
- [ ] **Step 3: flutter analyze + widget test(登录/今日任务基本渲染)+ commit**
```bash
git -C sources/ec_flutter commit -am "feat(nurse): 登录 + M1 今日任务"
```
---
### Task 3: M2 服务执行(签到/勾项目/拍照/签退)
**Files:** `lib/features/service/**`。
- [ ] **Step 1: 签到**:超大「签到」按钮 → geolocator 取定位(失败标「未获取」不阻塞)→ `POST /record/checkin`(body `{orderId, locateStatus, checkinLat?, checkinLng?}`,**不传 checkin_time**)→ 记 recordId。
- [ ] **Step 2: 勾项目**:项目列表(从 order.itemList 或 GET /order/:id),默认全勾 `done`,可改为 `undo_not_done`/`undo_refused`(老人拒做)→ `POST /record/item/save`(body `{recordId, items:[{svcId, execResult}]}`)。
- [ ] **Step 3: 拍照**:必拍项(photoTier==='must')显式标「需拍照」→ image_picker 拍照 + 压缩 ≤500KB → `POST /record/photo/upload`(multipart,file + recordId + svcId)→ 压疮项附结构化标签(部位 9 选/分期 6 选,可跳过);「无法拍摄」出路(选原因)→ 该项放行。
- [ ] **Step 4: 签退**:超大「签退」按钮——前置校验「必拍项已拍或已拒拍」,未满足则禁用+提示「这项需要拍一张照片」→ `POST /record/checkout`(body `{recordId}`,**不传 checkout_time/duration_min**)→ 成功回今日任务。
- [ ] **Step 5: 测试 + analyze + commit**
```bash
git -C sources/ec_flutter commit -am "feat(nurse): M2 服务执行(签到/勾项目/拍照/签退)"
```
---
### Task 4: 异常上报 + 扑空
**Files:** `lib/features/abnormal/**`。
- [ ] **Step 1: 异常上报**:9 宫格分类(必选,dict abnormal_type)+ 描述/语音/照片(可选)→ `POST /abnormal`(body `{abnormalType, abnormalDesc?, recordId?}`)。
- [ ] **Step 2: 扑空**:服务执行页「未遇」入口 → 到访时间(自动)/定位(自动)/原因(必选下拉:老人不在/拒绝服务/临时改期)→ `POST /abnormal`(body `{abnormalType:'not_met', orderId, elderId, visitTime, visitLat, visitLng, abnormalDesc}`)→ 工单转 not_met。
- [ ] **Step 3: 测试 + commit**
```bash
git -C sources/ec_flutter commit -am "feat(nurse): 异常上报 + 扑空"
```
---
### Task 5: 离线队列
**Files:** `lib/core/db/**`、`lib/core/offline/**`。
- [ ] **Step 1: sqflite 队列表**:`offline_queue(id, type, payload_json, client_md5, created_at, synced)`;本地照片文件存 path_provider 临时目录。
- [ ] **Step 2: 落队逻辑**:签到/签退/勾项目/照片/异常在无网时写本地队列 + 生成 `client_md5`(crypto md5 对 payload 内容 hash);有网时直接走正常 API。
- [ ] **Step 3: 补传**:联网检测(dio 错误判定)→ 逐条 `POST /record/offline/sync`(body 含 client_md5 + 完整记录)→ 成功标 synced;照片补传后保留 ≥7 天(本地文件)。
- [ ] **Step 4: 测试 + commit**
```bash
git -C sources/ec_flutter commit -am "feat(nurse): 离线队列 + 自动补传"
```
---
### Task 6: 验证
- [ ] **Step 1:** `flutter analyze` 0 error + `flutter test` 全绿。
- [ ] **Step 2:** 起后端,用 `nurse01`/`Touyun@123`/`TY20261015` 冒烟(模拟器或 web):登录 → 今日任务(张三工单)→ 签到 → 勾项目 → 拍照 → 签退 → 后端留痕 submitted;异常上报/扑空落库;断网签到恢复补传。
- [ ] **Step 3:** 停后端清理。
---
## Self-Review Notes
- Spec 覆盖:frontend/nurse-app 7 Requirement → T2(登录/M1)、T3(M2/拍照)、T4(异常/扑空)、T5(离线)。
- Review Focus 5 条均有测试/校验(T3/T4/T5)。
- 类型一致:`ApiClient`/`ApiException`/API 方法名在 T1 定义、T2–T5 使用一致。