From 5f8de01033299176564a11674b32c27a043aeb69 Mon Sep 17 00:00:00 2001 From: laoyang Date: Thu, 5 Nov 2026 20:53:53 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=EF=BC=9A=E7=82=B9=E5=B7=B2=E6=9C=89?= =?UTF-8?q?=E7=BA=AA=E5=BF=B5=E6=97=A5=E7=BC=96=E8=BE=91=E6=97=B6=E5=85=B3?= =?UTF-8?q?=E8=81=94=E4=BA=BA=E4=B8=80=E7=89=87=E7=A9=BA=E7=99=BD=20(#91b)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户 2026-11-05:「刚才关联人还有1人呢,现在关联人怎么没了?是个空的。」 真根因(两层,缺一个都不会有这现象): ① `dayRoster` 只在 `openDayCreate()`(**新建**入口)里加载 `openDayDetail()`(**点已有纪念日**)从不加载它 ⇒ roster 恒为空数组 ⇒ 编辑框里按 id 查不到人。 ⇒ 抽出 `ensureDayRoster()`,两个入口都调。 `openDayDetail` 里**不 await**:先让卡片弹出来(点击要即时反馈), 名册回来后 `@Param` 变化会自动重画 chips。 ② `pickedPeople()` 是"查不到就 skip" roster 为空时返回 `[]` ⇒ chips 区什么都不画 ⇒ 看着像关联关系被清空了。 而这数据其实一直在 `personIds` 里,保存也不会丢 —— 典型的"查不到就静默丢弃",最吓人的那种:数据没坏,但界面像是坏了。 ⇒ 新增 `pickedSlots()`:roster 里查不到**也产出占位项** (名字写「成员 #id」、known=false、灰色字),至少让人看到"关联了一个人"。 ⇒ 判断分支改看 `personIds.length`(**事实**), 而不是 `pickedPeople()`(可能因 roster 没到而为空)。 顺手删掉 `pickedPeople()`(改成 slots 后没人用了,712 字符)。 ⚠️ 派生(生日)走只读卡不需要名册,但 `openDayDetail` 统一加载也无害 (`ensureDayRoster` 有缓存判断,第二次直接返回)。 编译通过,已装真机。 --- .../main/ets/components/FbDayEditSheet.ets | 84 ++++++++++++++----- entry/src/main/ets/pages/home/FbHomePage.ets | 54 +++++++++--- 2 files changed, 108 insertions(+), 30 deletions(-) diff --git a/entry/src/main/ets/components/FbDayEditSheet.ets b/entry/src/main/ets/components/FbDayEditSheet.ets index 1ba3723..51e4039 100644 --- a/entry/src/main/ets/components/FbDayEditSheet.ets +++ b/entry/src/main/ets/components/FbDayEditSheet.ets @@ -39,6 +39,16 @@ const REPEAT_LABELS: string[] = ['每年(阳历)', '每年(农历)', ' /** 预置分类(服务端 `PRESET_KINDS` 的同一份;用户还能自己填) */ const PRESET_KINDS: string[] = ['生日', '结婚纪念日', '相识纪念日', '忌日', '节日', '其它']; +/** 关联人 chip 的展示数据(#91b):见 `pickedSlots()` 的说明 */ +interface FbPickSlot { + id: number; + name: string; + avatarKey: string; + color: string; + /** roster 里有这个人(false = 只有 id,名字头像是占位) */ + known: boolean; +} + @ComponentV2 export struct FbDayEditSheet { /** 模块主题色(纪念日 = 陶土橙) */ @@ -103,25 +113,39 @@ export struct FbDayEditSheet { } /** - * 已选的关联人(按 `personIds` 的顺序返回,找不到的跳过)。 + * 关联人 chip 的数据(#91b)。 * - * 🔴 #91(用户 2026-11-05):「关联人这个地方也显示出来:这个人已经关联的人 - * 是谁?名字、头像。」 + * 与 `FbRosterPerson` 的区别:**roster 里查不到时也产出**一条占位记录, + * 只把 `name` 写成「成员 #id」、`known = false`。 * - * ⚠️ 返回**对象数组**而不是名字字符串:调用方要画头像(`avatar`/`avatarColor`)。 - * ⚠️ 原来还有个 `peopleText()` 生成「张三、李四」这种纯文字串 —— - 改成 chips 之后**没人用了**,已删。别再写回来: - 名字串在人多时要靠省略号截断,看不出「关联了几个」。 - * ⚠️ 顺序按 `personIds`(用户在选人弹窗里的点选顺序), - * 不按名册顺序 —— 否则"我刚加的人跑到最后去了",看着像没加上。 + * 🔴 为什么需要这个(用户 2026-11-05:「刚才关联人还有1人呢,现在怎么没了?」): + * `roster` 是懒加载的,编辑框打开那一瞬可能还是空数组。 + * 原来直接"查不到就跳过" ⇒ chips 区一片空白 ⇒ 看着像数据丢了。 + * 可数据其实一直在 `personIds` 里。 */ - private pickedPeople(): FbRosterPerson[] { - const out: FbRosterPerson[] = []; + private pickedSlots(): FbPickSlot[] { + const out: FbPickSlot[] = []; for (const id of this.personIds) { const p: FbRosterPerson | undefined = this.roster.find((x: FbRosterPerson): boolean => x.id === id); if (p !== undefined) { - out.push(p); + out.push({ + id: id, + name: p.shown !== '' ? p.shown : p.name, + avatarKey: p.avatar, + color: p.avatarColor, + known: true + }); + } else { + /* ⚠️ 占位:名字写成「成员 #id」而不是留空 —— + 空白 chip 看着像渲染坏了,有个编号至少能确认"确实关联了一个人"。 */ + out.push({ + id: id, + name: '成员 #' + id.toString(), + avatarKey: '', + color: '', + known: false + }); } } return out; @@ -334,6 +358,25 @@ export struct FbDayEditSheet { 一眼看出关联了谁。一个都没关联时显示「全家的事」(语义同前)。 */ Column({ space: 6 }) { this.fieldLb('关联人') + /* 🔴🔴 #91b(用户 2026-11-05):「刚才关联人还有1人呢, + 现在关联人怎么没了?是个空的。」 + + **根因**:`pickedPeople()` 是"按 id 去 roster 里找,找不到就 skip"。 + 而 `roster`(`dayRoster`)是**懒加载**的 —— 首页那份要等 + `ensureDayRoster()` 拉完才有内容;编辑框打开的那一瞬间它可能 + 还是空数组 ⇒ `pickedPeople()` 返回 `[]` ⇒ chips 区什么都不画 + ⇒ 看着像"关联人被清空了"。 + + ⚠️ 更糟的是:这种情况下**看不出任何异常** —— 用户只会以为 + 数据丢了。而这数据其实还在 `personIds` 里,保存也不会丢。 + 这是典型的"查不到就静默丢弃",本项目踩过多次。 + + ⇒ 三处兜底: + ① 判断分支改看 `personIds.length`(**事实**), + 而不是 `pickedPeople()`(可能因为 roster 没到而为空) + ② 查不到 roster 条目时,**仍然画出这个人**(用 id 占位), + 只是没有名字/头像 —— 至少让人看到"关联了一个人" + ③ roster 后到时 `@Param` 变化会触发重画,名字头像自动补上 */ if (this.personIds.length === 0) { Text('全家的事') .fontSize(FbMetric.fontSmall) @@ -349,18 +392,21 @@ export struct FbDayEditSheet { } else { /* 多个关联人:换行平铺的 chips(同分类的排法) */ Flex({ wrap: FlexWrap.Wrap }) { - ForEach(this.pickedPeople(), (p: FbRosterPerson) => { + /* #91b:用 pickedSlots() —— 它对"roster 里查不到"也产出占位项, + 不会像 pickedPeople() 那样悄悄丢掉(见那个方法的注释)。 */ + ForEach(this.pickedSlots(), (slot: FbPickSlot) => { Row({ space: 6 }) { FbAvatar({ - name: p.shown !== '' ? p.shown : p.name, - color: p.avatarColor !== '' ? p.avatarColor : this.accent, - imageUrl: p.avatar !== '' ? fbImageSrc(p.avatar, this.token()) : '', + name: slot.name, + color: slot.color !== '' ? slot.color : this.accent, + imageUrl: slot.avatarKey !== '' + ? fbImageSrc(slot.avatarKey, this.token()) : '', dia: 24, fontSizeVp: 11 }) - Text(p.shown !== '' ? p.shown : p.name) + Text(slot.name) .fontSize(FbMetric.fontSmall) - .fontColor(FbColors.ink) + .fontColor(slot.known ? FbColors.ink : FbColors.muted) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) .constraintSize({ maxWidth: 110 }) @@ -369,7 +415,7 @@ export struct FbDayEditSheet { .backgroundColor(FbColors.inputBg) .borderRadius(FbMetric.radiusPill) .margin({ right: 8, bottom: 8 }) - }, (p: FbRosterPerson): string => 'pp_' + p.id.toString()) + }, (slot: FbPickSlot): string => 'pp_' + slot.id.toString()) } .width('100%') } diff --git a/entry/src/main/ets/pages/home/FbHomePage.ets b/entry/src/main/ets/pages/home/FbHomePage.ets index c8e487a..4d120d7 100644 --- a/entry/src/main/ets/pages/home/FbHomePage.ets +++ b/entry/src/main/ets/pages/home/FbHomePage.ets @@ -701,8 +701,19 @@ export struct FbHomePage { } /** 打开纪念日详情(#63)。 */ + /** + * 打开纪念日详情(#63)。 + * + * ⚠️ #91c:**必须先加载名册** —— 自建纪念日的编辑框要显示「关联人」 + * (头像 + 名字)。原来这里不加载,roster 恒空 ⇒ 关联人区域空白。 + * ⚠️ 派生(生日)走只读卡,不需要名册 —— 但这里统一加载也无害 + * (一次调用,有缓存)。 + */ private openDayDetail(d: FbRecentDay): void { this.dayDetail = d; + /* 不 await:先让卡片弹出来(用户点击要即时反馈), + 名册回来后 `@Param` 变化会自动重画关联人 chips。 */ + this.ensureDayRoster(); } private closeDayDetail(): void { @@ -940,21 +951,42 @@ export struct FbHomePage { * 本地库由 `syncAll` 定期同步,所以第一次装机时可能为空 —— * 那时仍然要能建(纪念日本身不依赖人)。 */ + /** + * 确保名册已加载(纪念日「关联人」要用)。 + * + * 🔴🔴 #91c(用户 2026-11-05):「刚才关联人还有1人呢,现在关联人怎么没了? + * 是个空的。」 + * + * **真根因**:这段加载原来**写在 `openDayCreate()` 里面** —— + * 也就是说只有"新建纪念日"会去拉名册; + * **点一条已有纪念日(`openDayDetail`)走的是另一条路,从不加载** ⇒ + * `dayRoster` 恒为空数组 ⇒ 编辑框里按 id 查不到人 ⇒ 关联人区域一片空白。 + * + * ⚠️ 数据没丢(`personIds` 一直是好的),只是**画不出来** —— + * 所以更吓人:看着像关联关系被清空了。 + * ⚠️ 抽成独立方法,两个入口都调;`dayLoadingRoster` 防并发重复拉。 + * ⚠️ 失败只记日志、不打断:纪念日本身不依赖人(名册为空也能建/能改)。 + */ + private async ensureDayRoster(): Promise { + if (this.dayRoster.length > 0 || this.dayLoadingRoster) { + return; + } + this.dayLoadingRoster = true; + try { + this.dayRoster = await FbPersonRepo.inst().list(); + } catch (err) { + hilog.warn(DOMAIN, TAG, '首页拉名册失败(纪念日关联人):%{public}s', JSON.stringify(err)); + this.dayRoster = []; + } finally { + this.dayLoadingRoster = false; + } + } + private async openDayCreate(): Promise { if (this.dayCreating) { return; } - if (this.dayRoster.length === 0 && !this.dayLoadingRoster) { - this.dayLoadingRoster = true; - try { - this.dayRoster = await FbPersonRepo.inst().list(); - } catch (err) { - hilog.warn(DOMAIN, TAG, '首页拉名册失败(纪念日关联人):%{public}s', JSON.stringify(err)); - this.dayRoster = []; - } finally { - this.dayLoadingRoster = false; - } - } + await this.ensureDayRoster(); this.dayCreating = true; }