From b660a986dcf2f2cd620d08eaf8d444a7c34f55e5 Mon Sep 17 00:00:00 2001 From: laoyang Date: Wed, 4 Nov 2026 07:10:28 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E5=8D=A1=E7=89=87):=20=E6=89=93=E5=8B=BE?= =?UTF-8?q?=E5=90=8E=E5=88=B7=E6=96=B0=E6=89=80=E6=9C=89=E5=8D=A1=E7=89=87?= =?UTF-8?q?=EF=BC=8C=E8=A7=A3=E5=86=B3"=E5=88=87=E6=8D=A2=E5=8D=A1?= =?UTF-8?q?=E7=89=87=E4=BB=8D=E6=98=AF=E6=97=A7=E6=95=B0=E6=8D=AE"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 问题 在 A 卡片上完成一个待办,划到 B 卡片,B 里还是那条待办; 必须在 B 上再完成一个,B 才刷新。 ## 根因 `completeTodoFromCard(formId, localId)` 两处推送都只针对 `formId`: this.pushDataExcludingTodo(formId, localId); this.pushData(formId); 每张卡片是**独立的 formId 实例、各持一份数据**,`updateForm` 只作用于传入的那一个。 所以其它卡片完全收不到更新,直到它们各自被触发(用户点了别的待办)才重拉。 ## 解法 改用 `CardDataService.pushToAllForms()`,它会遍历所有已注册 formId 逐个推送。 两个关键点: **① 乐观更新必须保留** 点勾要立刻有视觉反馈。`pushToAllForms` 会重新查库,此刻库里还是未完成状态, 直接推会让该条又冒出来 ⇒ 看起来"点了没反应"。 所以给 `pushToAllForms` 增加可选参数 `excludeTodoId`,先推一次"排除该条"的数据 (对所有卡片生效)。 **② 时序** 点击 → pushToAllForms(excludeId) 所有卡片立即移除该条 → setCompleted(GET+PUT) 真正落库推服务器 → pushToAllForms() 所有卡片推真实数据 成功:该条已完成,不在未完成列表 失败:该条重新出现,用户能看出没成功 ## 顺带修掉一个隐患 `completeTodoFromCard` 从 void 改为 async Promise 后,`onFormEvent`(同步回调) 里的调用必须兜住 reject —— 否则未捕获的 Promise 会让 FormExtension 进程异常 (该进程生命周期只有 10 秒,异常会直接导致本次卡片更新丢失)。已补 `.catch`。 ## 未改的地方(有意为之) `onAddForm` / `onUpdateForm` / 翻页(`onFormEvent` 的 prev/next/today)仍只推单卡: - 翻页只该影响被翻的那张卡,不该把所有卡片拽到同一天 - 系统定时刷新由各卡片各自的 formId 触发,天然独立 数据最终一致性由"同步完成 → pushToAllForms"兜底。 ## 编译 `BUILD SUCCESSFUL` ## 未验证(需桌面上有卡片) 本机当前没有注册的卡片(formIds 为空),无法触发 `onFormEvent` 实测。 逻辑改动明确(单卡推送 → 全卡推送),但"切换卡片是否立即同步"需在 添加 ≥2 张待办卡片后实测确认。 --- entry/src/main/ets/common/CardDataService.ets | 8 +++- .../ets/entryformability/EntryFormAbility.ets | 39 ++++++++++++------- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/entry/src/main/ets/common/CardDataService.ets b/entry/src/main/ets/common/CardDataService.ets index e51f20b..61ef669 100644 --- a/entry/src/main/ets/common/CardDataService.ets +++ b/entry/src/main/ets/common/CardDataService.ets @@ -168,7 +168,11 @@ export class CardDataService { ev.todoTodayCount = td.todoTodayCount; } - static async pushToAllForms(context: common.Context): Promise { + /** + * @param excludeTodoId 乐观更新用:临时把该待办从**所有卡片**的列表与计数中排除 + * (只影响卡片显示,不动数据库)。0 表示不排除。 + */ + static async pushToAllForms(context: common.Context, excludeTodoId: number = 0): Promise { // 先恢复持久化的 formId(App 重启后内存注册表为空) await CardDataService.loadPersistedFormIds(context); if (CardDataService.formIds.length === 0) { @@ -177,7 +181,7 @@ export class CardDataService { // ⭐ 待办数据与 dayOffset 无关(待办不翻页),只构建一次 let todoData: CardData | null = null; try { - todoData = await CardDataService.buildTodoCardData(context); + todoData = await CardDataService.buildTodoCardData(context, excludeTodoId); } catch (err) { console.error(`待办卡片数据构建失败: ${(err as BusinessError).message}`); } diff --git a/entry/src/main/ets/entryformability/EntryFormAbility.ets b/entry/src/main/ets/entryformability/EntryFormAbility.ets index d37a956..29714a5 100644 --- a/entry/src/main/ets/entryformability/EntryFormAbility.ets +++ b/entry/src/main/ets/entryformability/EntryFormAbility.ets @@ -118,7 +118,11 @@ export default class EntryFormAbility extends FormExtensionAbility { const localId: number = m !== null ? parseInt(m[1], 10) : 0; LogUtil.write(`卡片打勾待办: formId=${formId} localId=${localId} msg=${message}`); if (localId > 0) { - this.completeTodoFromCard(formId, localId); + // onFormEvent 是同步回调,这里异步执行但必须兜住 reject, + // 否则未捕获的 Promise 会让进程异常(FormExtension 生命周期只有 10 秒) + this.completeTodoFromCard(formId, localId).catch((err: BusinessError): void => { + LogUtil.write('卡片打勾未捕获异常: ' + err.message); + }); } return; } @@ -153,18 +157,27 @@ export default class EntryFormAbility extends FormExtensionAbility { * FormExtensionAbility 生命周期回调只有 10 秒窗口, * GET+PUT 两步网络在慢网下可能 10 秒以上,卡片会先"卡住不动"再消失,体验很差。 */ - private completeTodoFromCard(formId: string, localId: number): void { - // ① 乐观移除:立即推送不含该条的列表 - this.pushDataExcludingTodo(formId, localId); - // ② 真正落库 + 推送服务器 - TodoService.setCompleted(this.context, localId, true).then((ok: boolean): void => { - LogUtil.write(`卡片打勾结果: localId=${localId} ${ok ? '成功' : '失败'}`); - // ③ 无论成败都推一次真实数据:成功则列表已无该条;失败则该条重新出现 - this.pushData(formId); - }).catch((err: BusinessError): void => { - LogUtil.write(`卡片打勾异常: localId=${localId} ${err.message}`); - this.pushData(formId); - }); + private async completeTodoFromCard(formId: string, localId: number): Promise { + // ⭐ 关键:推给**所有**卡片,不只 formId 这一张。 + // 原来只推 formId ⇒ 用户在 A 卡片打勾后,B 卡片仍显示那条待办, + // 必须在 B 上再点一次才刷新(用户实测反馈)。 + // 每张卡片是独立实例、各持一份数据,必须逐个 updateForm。 + + // ① 乐观移除:所有卡片立即不含该条(先给视觉反馈,不等网络) + await CardDataService.pushToAllForms(this.context, localId); + // ② 真正落库 + 推送服务器(GET 原 ics → 改写 → PUT) + let ok: boolean = false; + try { + ok = await TodoService.setCompleted(this.context, localId, true); + LogUtil.write('卡片打勾结果: localId=' + localId + ' ' + (ok ? '成功' : '失败')); + } catch (err) { + const e = err as BusinessError; + LogUtil.write('卡片打勾异常: localId=' + localId + ' ' + e.message); + } + // ③ 无论成败都再推一次真实数据: + // 成功 → 该条已 completed,本就不在未完成列表里,各卡片保持一致 + // 失败 → 该条重新出现,用户能看出没成功,可重试 + await CardDataService.pushToAllForms(this.context); } /** 推送"排除某条待办"的卡片数据(乐观更新用,只改卡片视图,不动数据库) */