Files
lixi b81c050e03
CI / docs-build (push) Successful in 58s
docs: M2 第三波收口——报告 22~27 入档挂导航
- 22~26 Flutter 接入五单报告(T2-11~14 + 白名单扩充,flutter 测试 64→272)
- 27 第三波收口总表:demo 数据消亡、四态纪律、埋点端到端贯通、DEBT-1 偿还
- 三次 compose 实测无契约偏差;波内 agent 中断续跑事故记录在案

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-08 14:10:36 +08:00

18 KiB
Raw Permalink Blame History

T2-14 健康时间线与提醒页接入 + T2-13 遗留收尾(交付报告)

执行日期2026-09-08 角色Frontend DeveloperFlutter 工单:T2-14(M,第三波收尾单)+ 25 号报告 §7 移交遗留①②③ + T2-17 前端半边收尾(health_record 域) 依据01 号拆解 T2-14 节、23/25 号页面交付先例、05 号 UI 规范、06 号埋点规划、冻结契约 openapi.yaml v1.2.0 提交patbond-flutter dev@ba50332(基线 c91f18a,已 push origin dev),拆 2 个逻辑提交:

提交 内容
e186ba3 时间线半边:健康事件时间线(月分组 + cursor 分页)+ 事件录入/编辑(顶层 PATCH + 40902 重提)+ 档案页月度花费卡(tz 透传)+ edit 事件封装
ba50332 提醒半边:照护提醒列表(过滤 + 逾期标识)+ 创建 + 完成/忽略流转 + 档案页提醒卡真实数据驱动 + 疫苗流转遗留①②

0. 结论摘要

  • 健康事件时间线(六类事件、occurred_at DESC cursor 分页、按月分组)与照护提醒(status 过滤、due_at ASC、逾期红标、完成/忽略流转)全链路走 T2-11 数据层,页面零直连 ApiClient;
  • 金额以元展示 / 整数分传输:录入、编辑、时间线尾值、月度花费卡四处全部经 money.dart 换算,单测锁定双向换算与往返一致(§2);
  • 档案页「月度花费」卡接 summary.monthlyExpensetz 透传设备时区固定偏移(T2-13 遗留③闭环,测试锁定格式与实值);
  • demo 硬编码的「健康提醒:已经半年没有进行体内外驱虫」语义位改为真实待办提醒驱动的 alert 卡(最近到期一条,逾期切警示形态;无待办不渲染占位);
  • T2-13 遗留①②收尾:疫苗 scheduled 行「标记完成 / 取消登记」PATCH 流转 + 完成时厂商/批号补录(契约字段存在,已做);
  • 埋点:health_record 域 7 事件 6 挂通 / 1 留待deleted 因 M2 契约无删除端点无挂接点,§3);
  • 四态硬要求达成:时间线、提醒列表、事件表单内无独立网络面、档案页两个新增面(月度花费随摘要卡行、提醒入口副行)均齐备且有 widget 测试;无提醒/无事件空态正确(含过滤空态无 CTA);
  • 测试 224 → 272 全绿(+48flutter analyze 0 问题,dart format 无 diff(两个提交逐个通过门禁:e186ba3 时点 250 全绿);
  • compose 真实后端实测:事件创建/分页/顶层 PATCH/40902、提醒状态机全矩阵(42202 三路)、summary tz 双口径、疫苗完成补录、第二账号 40401 防枚举,逐一符合契约(§5)。

1. 页面与四态覆盖表

页面 / 网络面 loading empty error retry 测试文件
健康时间线(health_events_page.dart 居中转圈 EmptyStateIllustration「还没有健康记录」+ 录入 CTA(canWrite InlineErrorBanner 分档文案 重试按钮 + 下拉刷新 health_events_page_test.dart7
时间线分页(同页「加载更多」) 行内小转圈 末页收起按钮 翻页失败 SnackBar、按钮保留 可再点 同上(cursor 透传/追加不重不漏有测试)
照护提醒列表(care_reminders_page.dart 居中转圈 「还没有照护提醒」+ CTA过滤空态「暂无「某状态」提醒」无 CTA 横幅 重试按钮 + 下拉刷新 care_reminders_page_test.dart8
档案页月度花费卡(摘要卡行第 4 列) 随卡行小转圈 monthlyExpense 恒非 null,¥0 弱化视觉 随卡行行内错误 行内重试 pet_detail_page_test.dart 复用摘要面测试
档案页提醒 alert 卡 / 入口副行 副行「加载中…」 无待办 → 无 alert 卡(无 demo 占位)+「暂无待办提醒」 副行「提醒加载失败,点击查看」,不阻塞主链路 点入口进提醒页(页内自带重试) pet_detail_page_test.dart 增量(4

页面结构与导航:

P2 宠物详情(pet_detail)
  ├─ 健康数据卡行(四卡:最新体重 / 疫苗进度 / 下一针 / 本月花费)
  │    └─ 本月花费卡 ──→ 健康时间线
  ├─ 健康提醒 alert 卡(真实待办驱动,最近到期一条,逾期警示形态)──→ 照护提醒页
  └─ 记录导航区
       ├─ 健康时间线(六类事件) ──→ 时间线页
       │     ├─ + → 事件录入表单(路由名 record_form)
       │     └─ 点条目(canWrite)→ 事件编辑页(无路由名,T2-12 先例)
       └─ 照护提醒(副行:N 条待办 / 暂无 / 失败降级) ──→ 提醒页
             ├─ + → 提醒创建表单(路由名 record_form)
             └─ 待办行「标记完成 / 忽略」(完成对话框支持补记日期)
  • 从时间线返回详情重拉摘要(月度花费实时聚合);从提醒页返回重拉待办;
  • 权限:录入/编辑/流转均 WRITE 档,viewer 在时间线(无+、点条目不进编辑)、提醒页(无+、无完成/忽略)、疫苗列表(无流转动作)全部前置隐藏(有测试)。

关键实现决策:

  1. 六类事件的 RecordTypeDot 映射RecordType 增补 feeding/grooming/measurement 三型(色族复用 05 §2 已审计四色对,仅图标/文案区分,对比度结论不变;8 图标彼此不重,测试锁定);note 归「其他」族。映射唯一出口 recordTypeForHealthEventhealth_record_display.dart 纯函数,6 分支测试)。
  2. 事件编辑的 40902 路径:契约无按 id 读取端点,照 T2-12「明确提示 + 自动取新 version(保留输入)+ 重提」模式,最新版本经时间线 cursor 分页检索取回(上限 10 页防御截断;检索不到按已删除处理)。测试锁定重提序列 [3, 7] 与 conflict 失败事件。
  3. 提醒流转的错误矩阵42202(状态-completedAt 一致性,前端已按状态结构化发字段,兜底提示后重拉)、40902(条件更新守卫落空 =「已在其他设备被处理」重拉)、40402 重拉——三路均有测试与 compose 实测。
  4. 时间约定沿 T2-13:事件发生时刻 / 提醒到期 / 完成补记均为「今日取此刻、历史(或未来)日期取当日 12:00」转 UTC 带 Z 上送。
  5. 创建成功后重拉首页而非本地插入(时间线月分组与提醒 due_at ASC 的排序键都在服务端),与疫苗列表先例一致。

2. 金额换算证据(工单硬项)

  • DTO 层保持整数分(HealthEvent.amountCents、请求体 amountCents),换算只发生在 UI 边界,出口唯一为 lib/features/pets/money.dart
  • 消费点:事件录入表单(元输入 → parseYuanToCents)、事件编辑页(分回显 formatCentsAsYuan + 元输入回传)、时间线尾值(¥128.50)、档案页月度花费卡(¥ + 分→元);
  • 单测锁定(money_test.dart 6 例,T2-11 交付、本单消费):整元不带小数(12800→"128")、非整元固定两位(12850→"128.50")、负数抛错、非法输入(三位小数/字符/负号)返回 null、往返一致 format(parse(x))
  • widget 级锁定:表单提交 amountCents: 12850(输入 "128.50")、无金额键整体缺席(非 0 非 null)、编辑差量 {version:3, title:…, amountCents:9900} 精确形状、金额非法("12.345")本地拦截不发请求;
  • compose 实测:服务端对小数金额 12.5 拒绝 400/40000(不静默截断),与前端拦截口径互为冗余(§5 步骤 3)。

3. 埋点挂接总表(health_record 域 7 事件盘点,T2-17 前端半边收官)

封装唯一出口 lib/features/pets/health_record_analytics.dart(枚举编译期锁死;后端白名单 dev@64c9b72 已含全部 7 事件):

# 事件 状态 recordType 覆盖 挂接点 测试
1 health_record_create_started 挂通(本波补全) weight/vaccineT2-13+ health_event/reminder(本单) 各表单首次输入去重上报,entryPoint=record_list 去重
2 health_record_create_succeeded 挂通(本波补全) 同上四值 创建接口 code=0durationMs/photoCount=0 属性齐备
3 health_record_create_failed 挂通(本波补全) 同上四值 失败响应/本地校验/网络(failureReason 六值 + errorCode/httpStatus/attemptSeq 多路径
4 health_record_viewed 挂通(本波补全) 同上四值 各列表页每次进入首个成功加载一次(source=pet_detailT2-13 口径沿用) 仅一次
5 health_record_edit_succeeded 本单新挂 health_event(编辑保存)+ vaccine(标记完成/取消登记,遗留①指定) PATCH code=0fieldCount=差量键数(不含 version fieldCount
6 health_record_edit_failed 本单新挂 health_event + vaccine 编辑/流转失败;failureReason 含 conflict40902——M2「并发冲突明确」验收的数据面;属性集无 attemptSeq(对齐 06 §1.5 白名单) conflict/notFound 等
7 health_record_deleted 留待 M2 契约无任何删除端点pets 域 12 路径均无 DELETE),无删除交互即无挂接点;白名单已就绪,随删除功能(05 §4.3 P3 提案含删除入口,待拍板)落地即挂

结论:7 事件 6 挂通 / 1 留待(deleted。口径决策(报数据侧知悉):

  1. 提醒完成/忽略不埋事件:06 §7 缺口 3 既定取舍——提醒完成率从 care_reminders 事实表(status/completed_at)出数;本单遵循,未给 pending→completed/dismissed 挂 edit 事件(页内注释注明 M3+ 推送实验时增补 reminder_completed 的复活条件)。因此 edit_* 的 recordType 实际取值为 health_event/vaccine 两种。
  2. HealthRecordFailureReason 枚举增 conflict06 §1.4 edit_failed 属性原文),创建链路不产生该值(创建无版本语义)。
  3. 时间线/提醒列表页与 T2-13 同理不设 page_viewed(字典 v2 无 record_list 页名),曝光由 viewed 承载;两个创建表单带 record_form 路由名走既有 RouteObserver(测试锁定),编辑页不带路由名(record_form 专属创建漏斗到达段,T2-12 决策 3 沿用)。

4. T2-13 移交遗留处理结果

# 遗留(25 号 §7 处理
① 疫苗 scheduled→completed/cancelled 列表操作 完成。scheduled 行「标记完成」(对话框:接种日期默认今天 + 可选下次接种,日期规则复用 vaccinationDateRuleError 前置拦截 42201)与「取消登记」(确认对话框,仅发 {version, status:cancelled},测试锁定精确形状);挂 health_record_edit_succeeded/failed(recordType=vaccine);40902 提示「已在其他设备被修改」+ 重拉取新 version 后由用户重试(列表行动作与表单场景不同,不做静默自动重提);42201/40402/40300/网络兜底齐备
② 完成时厂商/批号补录 完成(契约有字段:UpdateVaccinationRequest.manufacturer/batchNo 可选)。标记完成对话框含两个可选输入,既有值预填、空值不发键;compose 实测补录回读一致(§5 步骤 8)
③ summary tz 透传 完成。getPetSummary(tz: tzOffsetQueryValue(设备偏移)),固定偏移形如 +08:00(契约明示接受;Flutter 无 IANA 名可取,语义等价——tz 只作用月度窗口)。纯函数测试覆盖正/负/零/半小时偏移;widget 测试锁定实际透传值

5. compose 真实后端实测记录

环境:patbond-api dev@64c9b72JAVA_HOME=java-17 ./mvnw -DskipTests package + docker compose up -d --buildauth :8081 / pet :8083)。curl 按页面实际请求复演,测试账号随机生成、token 不落盘留存、用后随 docker compose down 丢弃;patbond-api 仓库零改动:

步骤 请求 结果
1 注册 → POST /pets(「验收豆豆二号」自定义品种) code=0petId=01a07f9e…(UUIDv7
2 POST health-eventsmedical + amountCents=12850(带 Idempotency-Key);grooming 无金额 两条 code=0;无金额回读 amountCents=null
3 POST health-events 携带小数金额 12.5 40000 参数校验失败——不静默截断,与前端元→分整数换算拦截互为冗余
4 GET health-events?limit=1 → 携 nextCursor 翻页 页1「皮肤检查」hasMore=true → 页2「洗澡美容」hasMore=falseoccurred_at DESC
5 PATCH /health-events/{id}version=0title+amountCents code=0title=皮肤复查、amount=9900、version→1
6 同 PATCH 旧 version=0 重放 40902 数据已被修改,请刷新后重试——编辑页冲突路径对齐
7 POST care-reminders ×2(未来到期 + 过去到期)→ GET ?status=pending 创建恒 pending;待办视图 due_at ASC(逾期「年度体检」在前)——逾期标识与排序依据
8 提醒状态机矩阵:dismissed 带 completedAt / completed 缺 completedAt / 终态回退 pending 三路均 42202(文案逐条明确);正常 completed(补记 completedAt)与 dismissed 均 code=0
9 GET summary?tz=%2B08:00 与缺省 {month: 2026-09, timezone: +08:00, amountCents: 9900} / {…, timezone: UTC, …}——固定偏移被接受、金额随事件编辑实时聚合
10 疫苗 scheduled 登记 → PATCH {version:0, status:completed, administeredOn, nextDueOn, manufacturer:硕腾, batchNo:LOT-2026-09} code=0status=completed、厂商/批号回读一致、version→1——遗留①②链路
11 第二账号 GET 时间线 / 提醒 40401 宠物不存在(防枚举)——越权拒绝

结论:时间线分页/编辑冲突、提醒状态机全矩阵、tz 双口径、疫苗完成补录在真实后端逐条通过;未发现契约偏差

6. 测试数变化

时点 测试数 说明
基线(dev@c91f18a 224 T2-13 交付
时间线半边(dev@e186ba3 250+26,全绿) 分提交门禁
本单(dev@ba50332 272+48,全绿) 见下分布
文件 数量 覆盖
health_events_page_test.dart 7(新) 四态、月分组组头、金额元展示、类型 TagPill、cursor 透传/追加/末页收起/翻页失败保留、录入闭环(record_form 路由名 + 重拉)、编辑闭环(无路由名 + 就地替换)、viewer 三重隐藏、viewed 一次
health_event_form_page_test.dart 5(新) 类型/标题/金额三重本地拦截不发请求、请求形状(eventType/UTC 时间戳/元→分/无金额键缺席)、started 去重、40300/40000/网络兜底 + 失败事件
health_event_edit_page_test.dart 5(新) 预填(分→元回显)、差量精确形状 + fieldCount、无变更不发 PATCH(清空视为不变更)、40902 检索取新 version 重提序列 [3,7] + conflict 事件、40402/40300/网络 + SnackBar 重试接线
care_reminders_page_test.dart 8(新) 四态(含过滤空态无 CTA)、status 参数透传、逾期/待办/已完成三态标签、创建闭环(校验拦截 + 请求形状 + 三事件)、完成(completedAt UTC 必带)/忽略(键缺席)精确形状 + 不埋 edit 事件断言、42202/40902 兜底重拉、viewer 无动作
care_reminder_form_page_test.dart 2(新) started 去重 + 四类型齐备、40300/网络兜底 + 失败事件属性全形状
vaccination_records_page_test.dart 增量 +5 标记完成(厂商/批号补录请求形状 + fieldCount=4 + 动作仅 scheduled 行)、取消登记(精确 {version, status} 形状)、40902 conflict 事件 + 重拉、42201 兜底、viewer 无流转动作
pet_detail_page_test.dart 增量 +6 四卡取数(¥128.50 + tz 实值与格式)、花费卡→时间线 + 返回重拉、时间线入口 viewer 透传、alert 卡真实数据(最近到期 + 待办数 + 点卡导航 + 返回重拉待办)、逾期警示形态、无待办无占位、失败降级不阻塞
health_record_display_test.dart 增量 +7 六类映射齐备、六类文案、月组头、tz 偏移四象限、提醒四类文案与映射、逾期判定(终态不算逾期)+ 标签/基色切换、时间副行三态
health_record_analytics_test.dart 增量 +3 editSucceeded 形状、editFailed conflict + httpStatus 推导 + 无 attemptSeq、可空属性缺席
record_type_dot_test.dart 更新 全类型映射齐备(8 型)+ 图标互异断言

质量门禁:flutter test 272/272 全绿;flutter analyze No issues founddart format --set-exit-if-changed 无 diff(两个提交逐个通过)。

7. 决策与遗留

# 事项 说明
1 health_record_deleted 未挂 M2 契约无删除端点(本单核对 12 路径),留待删除交互(05 §4.3 P3 提案)落地,白名单已就绪
2 提醒完成/忽略不埋事件 06 §7 缺口 3 既定取舍,完成率走事实表;M3+ 推送实验需增补 reminder_completed
3 档案页摘要卡行为四卡 05 §4.2 正典第三卡即「本月花费」;25 号 §8 交接指定「加一列」;窄屏靠 ellipsis 兜底
4 时间线未做类型筛选 chips 05 §6 D2 待拍板项,工单验收不含;拍板后为小改动(列表已按类型渲染标签)
5 提醒改期 契约明示无 title/dueAt 编辑端点,路径为忽略后重建(提醒页忽略文案已引导)
6 AppState demo 清理 时间线/提醒页零 AppState 依赖,无需清理;AppState.pet 残余消费方仍为首页问候卡/创作页/主壳头像(T2-12 报告 §7 既有标记),属其他 Tab demo 家具,本单未越界
7 归档入口(T2-12 遗留 5 仍留收口单

8. 交接 T2-18E2E 烟囱)

  • 本单 §5 的 curl 序列可直接并入烟囱脚本(事件分页/PATCH 冲突、提醒状态机矩阵、tz 双口径、疫苗完成补录、第二账号拒绝);
  • M2 四条验收标准的前端证据位:跨设备(T2-13 §6 + 本单数据全走服务端)、无权限拒绝(§5 步骤 11 + viewer 前置隐藏测试)、并发冲突明确(事件编辑 40902 重提 + 疫苗/提醒 40902 提示重拉,conflict 事件落数据面)、双端测试齐备(后端 95 + 前端 272)。

Frontend Developer · 2026-09-08 · patbond-flutter dev@ba50332