diff --git a/entry/src/main/ets/components/FbTaskPanel.ets b/entry/src/main/ets/components/FbTaskPanel.ets index 9a46e27..8d79618 100644 --- a/entry/src/main/ets/components/FbTaskPanel.ets +++ b/entry/src/main/ets/components/FbTaskPanel.ets @@ -783,7 +783,7 @@ export struct FbTaskPanel { } .width('100%') .alignItems(VerticalAlign.Center) - }, (k: FbTaskKid): string => 'pk_' + k.id.toString()) + }, (k: FbTaskKid): string => 'pk_' + k.id.toString() + '_' + k.status) /* 加一条:输入框 + 「加」 */ Row({ space: 8 }) { diff --git a/entry/src/main/ets/model/Types.ets b/entry/src/main/ets/model/Types.ets index e73f42c..c5d6c58 100644 --- a/entry/src/main/ets/model/Types.ets +++ b/entry/src/main/ets/model/Types.ets @@ -688,6 +688,35 @@ export interface FbTaskItem { dueAt: string; } +/** + * 任务列表 `ForEach` 的**生成键**(首页与任务页共用)。 + * + * 🔴🔴 为什么不能只用 id(v79 自查出来的真隐患,编译通过、断言全绿): + * ArkUI 的 `ForEach` 用生成键做 diff —— **键没变就复用旧节点、不重跑生成器**。 + * 而任务行是 `@Builder taskRow(t: FbTaskItem, …)`:入参是**按值传递、不参与状态观察**的 + * (本仓库文件头第 A 条反模式)。两件事撞在一起就是: + * 改了状态 / 挪了看板 → 重拉列表 → `this.tasks` 换成新对象 + * → 但键还是 `htk_7` ⇒ ArkUI 认为"这条没变" ⇒ **卡片仍显示旧状态**。 + * 用户看到的是"面板里改成已完成了,关掉面板那张卡还是待办"。 + * + * ⇒ 键里必须带**所有会被画到行上的字段**(状态/看板/截止/逾期/子任务进度/标题)。 + * 只要有一个变了,键就变 ⇒ ForEach 重建这一行。 + * ⚠️ 代价是状态一变这一行会重建(丢的是这一行的局部动画,不是滚动位置), + * 这是对的取舍:**宁可重画,也不要不刷新**。 + * + * ⚠️ 日程页 `cell_' + iso + '_' + this.pickedIso` 是同一个道理的另一个写法 + * (那是"选区变了、格子要重画"),别以为是随手的字符串拼接。 + */ +export function taskKey(t: FbTaskItem): string { + return 'tk_' + t.id.toString() + + '_' + t.status + + '_' + t.boardId.toString() + + '_' + t.dueAt + + '_' + (t.overdue ? '1' : '0') + + '_' + t.childDone.toString() + 'of' + t.childTotal.toString() + + '_' + t.title; +} + /** * 任务看板选项(新建/编辑任务时挑"归到哪个看板")。 * diff --git a/entry/src/main/ets/pages/home/FbHomePage.ets b/entry/src/main/ets/pages/home/FbHomePage.ets index 1068088..b3b42c5 100644 --- a/entry/src/main/ets/pages/home/FbHomePage.ets +++ b/entry/src/main/ets/pages/home/FbHomePage.ets @@ -40,7 +40,7 @@ import { FbColors, FbMetric, FB_MASK } from '../../common/Theme'; import { FbAppState, fbAppState } from '../../common/Store'; import { FbCalEvent, FbCatOpt, FbHomeData, FbMark, FbMemoryItem, FbRecentDay, FbTaskItem, - FbBoardOpt, FbTaskDetail, FbTaskMeta, + FbBoardOpt, FbTaskDetail, FbTaskMeta, taskKey, FbWeather, isEditableEvent } from '../../model/Types'; import { common } from '@kit.AbilityKit'; import { BusinessError } from '@kit.BasicServicesKit'; @@ -723,7 +723,10 @@ export struct FbHomePage { } else { ForEach(this.tasks, (t: FbTaskItem, idx: number) => { this.taskRow(t, idx === this.tasks.length - 1) - }, (t: FbTaskItem, idx: number): string => 'htk_' + t.id.toString()) + /* 🔴 v79:键必须带状态等**会被画到行上的字段**(`taskKey` 的注释有完整推导)。 + 只用 id 的后果:面板里改成已完成 → 重拉 → 对象换了但键没变 + ⇒ ArkUI 复用旧节点 ⇒ 首页这张卡还是"待办"。 */ + }, (t: FbTaskItem, idx: number): string => taskKey(t)) } } .width('100%') diff --git a/entry/src/main/ets/pages/tasks/FbTasksPage.ets b/entry/src/main/ets/pages/tasks/FbTasksPage.ets index 8d9e0c9..54a44b6 100644 --- a/entry/src/main/ets/pages/tasks/FbTasksPage.ets +++ b/entry/src/main/ets/pages/tasks/FbTasksPage.ets @@ -11,7 +11,7 @@ import { FbColors, FbMetric, fbTint, FbTint, FB_MASK, fbAlpha } from '../../common/Theme'; import { FbAppState, fbAppState } from '../../common/Store'; -import { FbBoardOpt, FbCatOpt, FbGroup, FbTaskItem, FbTaskDetail, FbTaskMeta } from '../../model/Types'; +import { FbBoardOpt, FbCatOpt, FbGroup, FbTaskItem, FbTaskDetail, FbTaskMeta, taskKey } from '../../model/Types'; import { fbGroupTask } from '../../model/DemoData'; import { parseTasks, parseTaskList, parseTaskMeta } from '../../data/FbParse'; import { loadModule, loadPath } from '../../data/FbModuleLoad'; @@ -446,7 +446,10 @@ export struct FbTasksPage { ListItem() { this.taskCard(t) } - }, (t: FbTaskItem): string => 'tk_' + t.id.toString()) + /* 🔴 v79:键必须带状态/看板/截止/子任务进度 —— 见 `taskKey()` 的注释。 + 只用 id 时"勾完成→卡片仍显示待办"(任务行是按值参数的 @Builder, + 键不变 ArkUI 就复用旧节点、不重跑生成器)。 */ + }, (t: FbTaskItem): string => taskKey(t)) } }, (g: FbGroup): string => 'grp_' + g.key) }