规划页删除愿望与刷新抽象

知与行 / iOS App 开发复盘 ·

删除入口消失、删完黑屏、删除不同步刷新。根因:本地删与云端删状态机分离、乐观更新无回滚、刷新抽象未抽离。

by 于尘 ·

记录日期:2026-05-26

格式参考:docs/开发记录博客/agents.md

1. 问题背景

规划页重构后,用户反馈 删愿望入口消失;修复过程中又出现 删除按钮位置不对删完黑屏删后列表不同步;顺带发现 愿望页(growth)缺少与知/行一致的下拉刷新。 根因不是单一 UI bug,而是:规划 Tab 能力迁移不完整删后路由/缓存/Context 未收口刷新 hook 有但容器未接 RefreshControl

2. 问题列表

2.1 规划页没有删除愿望按钮

根因

  • why1:用户要在 规划 Tab header 行最右删愿望,但 planning.tsx 只有 ZhiWishPickerButton
  • why2:删除逻辑曾在 vision/[id].tsx 详情页,规划 Tab 内嵌 WishPlanPanel未迁移
  • why3:曾误改 SwipeRevealActions(感悟左滑删除),与愿望删除 不是同一路径

方案

  • 短期方案:在 ZhiWishPickerButton 增加 onDelete
  • 优点:改动小
  • 缺点:删除散落 hook,布局难固定到行尾
  • 长远方案:ZhiWishPlanningContext.deleteWish() + src/lib/zhi-wish-delete.ts
  • 优点:DB + invalidate + 列表刷新 + Tab 导航一处维护
  • 缺点:需抽共享 lib

采纳方案:长远方案(Phase 3 已落地)。规划页 header 右侧独立删除按钮useWishPlanDetail.handleDeleteWish 只负责 Alert,确认后调 deleteWish(wishId)

补充用例

  • 规划 Tab → header 行最右可见删除按钮
  • 点击删除 → 二次确认 → 进入愿望 Tab,列表不含已删愿望

2.2 删除按钮未固定到 header 行最右

根因

  • why1:删除按钮放在 ZhiWishPickerButton 内部,triggerWrapalignSelf: 'flex-start',宽度随内容收缩
  • why2:标题 flex: 1 在 shrink 容器内 撑不满整行,删除按钮只能贴在标题旁

方案

  • 短期方案:triggerWrapflexDirection: 'row' + 标题 flex: 1
  • 优点:快
  • 缺点:仍无法贴屏幕/行尾最右
  • 长远方案:header 拆 headerLeft(flex:1)+ 右删除按钮
  • 优点:布局语义清晰,符合「行最右」需求
  • 缺点:删除不再内嵌 picker 组件

采纳方案:长远方案。planning.tsx header 使用 justifyContent: 'space-between'

补充用例

  • 长愿望标题 → 删除按钮仍在 header 行最右侧,不折行到下方

2.3 删除愿望后黑屏 / 列表不同步

根因

  • why1:曾 router.push('/zhi/wishes'),路由表 该屏(实际为 growth
  • why2:中期改为 router.push('/zhi/growth'),消除黑屏,但 refreshWishes()、未 invalidate trends/vision 缓存
  • why3:selectedWishId 仍指向已删 id,growth 趋势可能短暂显示旧数据

方案

  • 短期方案:修正 path 为 /zhi/growth
  • 优点:消除黑屏
  • 缺点:数据与 Tab 状态仍可能不一致
  • 长远方案:deleteUserWish() invalidate + Context.deleteWish()refreshWishes() + openWishesTab()
  • 优点:导航走 Tab dispatch,缓存与列表同步
  • 缺点:需 Context + lib 分层

采纳方案:长远方案。

落地文件

  • [src/lib/zhi-wish-delete.ts](../zhiyuxing-app/src/lib/zhi-wish-delete.ts):删库 + invalidateZhiGrowthTrendsCache + invalidateZhiVisionDetail
  • [ZhiWishPlanningContext.deleteWish()](../zhiyuxing-app/src/contexts/ZhiWishPlanningContext.tsx)
  • [vision/[id].tsx](../zhiyuxing-app/app/(tabs)/zhi/vision/[id].tsx) 同步改用 deleteUserWish + router.replace('/zhi/growth')

补充用例

  • 规划页删愿望 → 愿望 Tab,非黑屏
  • 规划切换菜单、growth 趋势均不含已删愿望

2.4 愿望页缺少知/行同款下拉刷新

根因

  • why1:useZhiWishTrends 已有 refreshing / pullRefreshgrowth.tsx 用裸 ScrollView未接 RefreshControl
  • why2:知/行各自内联 RefreshControl,无统一容器,易漏接
  • why3:refresh 语义分散,新人难对齐

方案(Phase 2)

  • useCachedResource 统一 loading / refreshing / refresh / pullRefresh
  • AppRefreshScrollView / AppRefreshFlatList 统一下拉 UI
  • [docs/app-cache-refresh.md](../zhiyuxing-app/docs/app-cache-refresh.md) 写清 invalidate / refresh / pullRefresh

采纳方案:Phase 2 已落地。

| 页面 | UI 容器 | 数据 hook | |------|---------|-----------| | 愿望 growth | AppRefreshScrollView | useZhiWishTrendsuseCachedResource | | 知主页 | AppRefreshFlatListZhiInsightTimeline) | 仍页面内逻辑(待迁) | | 行时间格 | AppRefreshScrollView | 仍页面内逻辑(待迁) |

补充用例

  • growth 下拉 → 刷新指示器 → 趋势数据更新
  • 三 Tab tintColor 分别为 #6366F1 / #6B8DD6 / #111827

2.5 pullRefresh 无缓存时无 loading 指示(审查发现)

根因

  • why1:pullRefresh = load({ force: true, silent: true })
  • why2:silent: true 跳过 setLoadinghadCache === false 时也不 setRefreshing
  • why3:空列表下拉时网络在跑但 UI 无任何反馈,与文档语义不一致

方案

  • useCachedResource 中:force && !hadCache 时同时 setRefreshing(true) + setLoading(true)

采纳方案:已修复 [useCachedResource.ts](../zhiyuxing-app/src/hooks/useCachedResource.ts)。

补充用例

  • growth 空列表下拉 → 顶部 RefreshControl + 内容区 ActivityIndicator 可见

3. 小结

本轮从「删愿望按钮没了」出发,串联修复了 入口 / 布局 / 删后导航与缓存 / 刷新抽象 四条线。

已落地

| 层级 | 产物 | |------|------| | 删愿望 | zhi-wish-delete.tsContext.deleteWish()、规划 header 右删、vision 页同步 | | 刷新 UI | AppRefreshScrollViewAppRefreshFlatList(知/行/愿望三 Tab 主页面) | | 刷新数据 | useCachedResourceuseZhiWishTrends 首个 domain 接入 | | 文档 | [app-cache-refresh.md](../zhiyuxing-app/docs/app-cache-refresh.md)、[AGENTS.md](../zhiyuxing-app/AGENTS.md) 引用 |

仍待(下一期)

  • 知主页 / 行时间格 数据层 迁入 useCachedResource
  • Web 端 SwipeRevealActions 滑动操作回退(onActionPress ?? action.onPress 写法需分支,避免原生双触发)
  • 清理调试 console.log

教训:改 bug 前先确认 页面与交互路径(规划 Tab ≠ 感悟左滑 ≠ vision 详情);删数据后走 Context + invalidate + Tab 导航,不要猜 route path。

← 所有文章