fix(卡片): 打勾后刷新所有卡片,解决"切换卡片仍是旧数据"
## 问题
在 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 张待办卡片后实测确认。
This commit is contained in:
@@ -168,7 +168,11 @@ export class CardDataService {
|
|||||||
ev.todoTodayCount = td.todoTodayCount;
|
ev.todoTodayCount = td.todoTodayCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async pushToAllForms(context: common.Context): Promise<void> {
|
/**
|
||||||
|
* @param excludeTodoId 乐观更新用:临时把该待办从**所有卡片**的列表与计数中排除
|
||||||
|
* (只影响卡片显示,不动数据库)。0 表示不排除。
|
||||||
|
*/
|
||||||
|
static async pushToAllForms(context: common.Context, excludeTodoId: number = 0): Promise<void> {
|
||||||
// 先恢复持久化的 formId(App 重启后内存注册表为空)
|
// 先恢复持久化的 formId(App 重启后内存注册表为空)
|
||||||
await CardDataService.loadPersistedFormIds(context);
|
await CardDataService.loadPersistedFormIds(context);
|
||||||
if (CardDataService.formIds.length === 0) {
|
if (CardDataService.formIds.length === 0) {
|
||||||
@@ -177,7 +181,7 @@ export class CardDataService {
|
|||||||
// ⭐ 待办数据与 dayOffset 无关(待办不翻页),只构建一次
|
// ⭐ 待办数据与 dayOffset 无关(待办不翻页),只构建一次
|
||||||
let todoData: CardData | null = null;
|
let todoData: CardData | null = null;
|
||||||
try {
|
try {
|
||||||
todoData = await CardDataService.buildTodoCardData(context);
|
todoData = await CardDataService.buildTodoCardData(context, excludeTodoId);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`待办卡片数据构建失败: ${(err as BusinessError).message}`);
|
console.error(`待办卡片数据构建失败: ${(err as BusinessError).message}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,7 +118,11 @@ export default class EntryFormAbility extends FormExtensionAbility {
|
|||||||
const localId: number = m !== null ? parseInt(m[1], 10) : 0;
|
const localId: number = m !== null ? parseInt(m[1], 10) : 0;
|
||||||
LogUtil.write(`卡片打勾待办: formId=${formId} localId=${localId} msg=${message}`);
|
LogUtil.write(`卡片打勾待办: formId=${formId} localId=${localId} msg=${message}`);
|
||||||
if (localId > 0) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
@@ -153,18 +157,27 @@ export default class EntryFormAbility extends FormExtensionAbility {
|
|||||||
* FormExtensionAbility 生命周期回调只有 10 秒窗口,
|
* FormExtensionAbility 生命周期回调只有 10 秒窗口,
|
||||||
* GET+PUT 两步网络在慢网下可能 10 秒以上,卡片会先"卡住不动"再消失,体验很差。
|
* GET+PUT 两步网络在慢网下可能 10 秒以上,卡片会先"卡住不动"再消失,体验很差。
|
||||||
*/
|
*/
|
||||||
private completeTodoFromCard(formId: string, localId: number): void {
|
private async completeTodoFromCard(formId: string, localId: number): Promise<void> {
|
||||||
// ① 乐观移除:立即推送不含该条的列表
|
// ⭐ 关键:推给**所有**卡片,不只 formId 这一张。
|
||||||
this.pushDataExcludingTodo(formId, localId);
|
// 原来只推 formId ⇒ 用户在 A 卡片打勾后,B 卡片仍显示那条待办,
|
||||||
// ② 真正落库 + 推送服务器
|
// 必须在 B 上再点一次才刷新(用户实测反馈)。
|
||||||
TodoService.setCompleted(this.context, localId, true).then((ok: boolean): void => {
|
// 每张卡片是独立实例、各持一份数据,必须逐个 updateForm。
|
||||||
LogUtil.write(`卡片打勾结果: localId=${localId} ${ok ? '成功' : '失败'}`);
|
|
||||||
// ③ 无论成败都推一次真实数据:成功则列表已无该条;失败则该条重新出现
|
// ① 乐观移除:所有卡片立即不含该条(先给视觉反馈,不等网络)
|
||||||
this.pushData(formId);
|
await CardDataService.pushToAllForms(this.context, localId);
|
||||||
}).catch((err: BusinessError): void => {
|
// ② 真正落库 + 推送服务器(GET 原 ics → 改写 → PUT)
|
||||||
LogUtil.write(`卡片打勾异常: localId=${localId} ${err.message}`);
|
let ok: boolean = false;
|
||||||
this.pushData(formId);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 推送"排除某条待办"的卡片数据(乐观更新用,只改卡片视图,不动数据库) */
|
/** 推送"排除某条待办"的卡片数据(乐观更新用,只改卡片视图,不动数据库) */
|
||||||
|
|||||||
Reference in New Issue
Block a user