修:点已有纪念日编辑时关联人一片空白 (#91b)
用户 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` 有缓存判断,第二次直接返回)。
编译通过,已装真机。
This commit is contained in:
@@ -39,6 +39,16 @@ const REPEAT_LABELS: string[] = ['每年(阳历)', '每年(农历)', '
|
|||||||
/** 预置分类(服务端 `PRESET_KINDS` 的同一份;用户还能自己填) */
|
/** 预置分类(服务端 `PRESET_KINDS` 的同一份;用户还能自己填) */
|
||||||
const PRESET_KINDS: string[] = ['生日', '结婚纪念日', '相识纪念日', '忌日', '节日', '其它'];
|
const PRESET_KINDS: string[] = ['生日', '结婚纪念日', '相识纪念日', '忌日', '节日', '其它'];
|
||||||
|
|
||||||
|
/** 关联人 chip 的展示数据(#91b):见 `pickedSlots()` 的说明 */
|
||||||
|
interface FbPickSlot {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
avatarKey: string;
|
||||||
|
color: string;
|
||||||
|
/** roster 里有这个人(false = 只有 id,名字头像是占位) */
|
||||||
|
known: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
@ComponentV2
|
@ComponentV2
|
||||||
export struct FbDayEditSheet {
|
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`)。
|
* 🔴 为什么需要这个(用户 2026-11-05:「刚才关联人还有1人呢,现在怎么没了?」):
|
||||||
* ⚠️ 原来还有个 `peopleText()` 生成「张三、李四」这种纯文字串 ——
|
* `roster` 是懒加载的,编辑框打开那一瞬可能还是空数组。
|
||||||
改成 chips 之后**没人用了**,已删。别再写回来:
|
* 原来直接"查不到就跳过" ⇒ chips 区一片空白 ⇒ 看着像数据丢了。
|
||||||
名字串在人多时要靠省略号截断,看不出「关联了几个」。
|
* 可数据其实一直在 `personIds` 里。
|
||||||
* ⚠️ 顺序按 `personIds`(用户在选人弹窗里的点选顺序),
|
|
||||||
* 不按名册顺序 —— 否则"我刚加的人跑到最后去了",看着像没加上。
|
|
||||||
*/
|
*/
|
||||||
private pickedPeople(): FbRosterPerson[] {
|
private pickedSlots(): FbPickSlot[] {
|
||||||
const out: FbRosterPerson[] = [];
|
const out: FbPickSlot[] = [];
|
||||||
for (const id of this.personIds) {
|
for (const id of this.personIds) {
|
||||||
const p: FbRosterPerson | undefined =
|
const p: FbRosterPerson | undefined =
|
||||||
this.roster.find((x: FbRosterPerson): boolean => x.id === id);
|
this.roster.find((x: FbRosterPerson): boolean => x.id === id);
|
||||||
if (p !== undefined) {
|
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;
|
return out;
|
||||||
@@ -334,6 +358,25 @@ export struct FbDayEditSheet {
|
|||||||
一眼看出关联了谁。一个都没关联时显示「全家的事」(语义同前)。 */
|
一眼看出关联了谁。一个都没关联时显示「全家的事」(语义同前)。 */
|
||||||
Column({ space: 6 }) {
|
Column({ space: 6 }) {
|
||||||
this.fieldLb('关联人')
|
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) {
|
if (this.personIds.length === 0) {
|
||||||
Text('全家的事')
|
Text('全家的事')
|
||||||
.fontSize(FbMetric.fontSmall)
|
.fontSize(FbMetric.fontSmall)
|
||||||
@@ -349,18 +392,21 @@ export struct FbDayEditSheet {
|
|||||||
} else {
|
} else {
|
||||||
/* 多个关联人:换行平铺的 chips(同分类的排法) */
|
/* 多个关联人:换行平铺的 chips(同分类的排法) */
|
||||||
Flex({ wrap: FlexWrap.Wrap }) {
|
Flex({ wrap: FlexWrap.Wrap }) {
|
||||||
ForEach(this.pickedPeople(), (p: FbRosterPerson) => {
|
/* #91b:用 pickedSlots() —— 它对"roster 里查不到"也产出占位项,
|
||||||
|
不会像 pickedPeople() 那样悄悄丢掉(见那个方法的注释)。 */
|
||||||
|
ForEach(this.pickedSlots(), (slot: FbPickSlot) => {
|
||||||
Row({ space: 6 }) {
|
Row({ space: 6 }) {
|
||||||
FbAvatar({
|
FbAvatar({
|
||||||
name: p.shown !== '' ? p.shown : p.name,
|
name: slot.name,
|
||||||
color: p.avatarColor !== '' ? p.avatarColor : this.accent,
|
color: slot.color !== '' ? slot.color : this.accent,
|
||||||
imageUrl: p.avatar !== '' ? fbImageSrc(p.avatar, this.token()) : '',
|
imageUrl: slot.avatarKey !== ''
|
||||||
|
? fbImageSrc(slot.avatarKey, this.token()) : '',
|
||||||
dia: 24,
|
dia: 24,
|
||||||
fontSizeVp: 11
|
fontSizeVp: 11
|
||||||
})
|
})
|
||||||
Text(p.shown !== '' ? p.shown : p.name)
|
Text(slot.name)
|
||||||
.fontSize(FbMetric.fontSmall)
|
.fontSize(FbMetric.fontSmall)
|
||||||
.fontColor(FbColors.ink)
|
.fontColor(slot.known ? FbColors.ink : FbColors.muted)
|
||||||
.maxLines(1)
|
.maxLines(1)
|
||||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||||
.constraintSize({ maxWidth: 110 })
|
.constraintSize({ maxWidth: 110 })
|
||||||
@@ -369,7 +415,7 @@ export struct FbDayEditSheet {
|
|||||||
.backgroundColor(FbColors.inputBg)
|
.backgroundColor(FbColors.inputBg)
|
||||||
.borderRadius(FbMetric.radiusPill)
|
.borderRadius(FbMetric.radiusPill)
|
||||||
.margin({ right: 8, bottom: 8 })
|
.margin({ right: 8, bottom: 8 })
|
||||||
}, (p: FbRosterPerson): string => 'pp_' + p.id.toString())
|
}, (slot: FbPickSlot): string => 'pp_' + slot.id.toString())
|
||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -701,8 +701,19 @@ export struct FbHomePage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 打开纪念日详情(#63)。 */
|
/** 打开纪念日详情(#63)。 */
|
||||||
|
/**
|
||||||
|
* 打开纪念日详情(#63)。
|
||||||
|
*
|
||||||
|
* ⚠️ #91c:**必须先加载名册** —— 自建纪念日的编辑框要显示「关联人」
|
||||||
|
* (头像 + 名字)。原来这里不加载,roster 恒空 ⇒ 关联人区域空白。
|
||||||
|
* ⚠️ 派生(生日)走只读卡,不需要名册 —— 但这里统一加载也无害
|
||||||
|
* (一次调用,有缓存)。
|
||||||
|
*/
|
||||||
private openDayDetail(d: FbRecentDay): void {
|
private openDayDetail(d: FbRecentDay): void {
|
||||||
this.dayDetail = d;
|
this.dayDetail = d;
|
||||||
|
/* 不 await:先让卡片弹出来(用户点击要即时反馈),
|
||||||
|
名册回来后 `@Param` 变化会自动重画关联人 chips。 */
|
||||||
|
this.ensureDayRoster();
|
||||||
}
|
}
|
||||||
|
|
||||||
private closeDayDetail(): void {
|
private closeDayDetail(): void {
|
||||||
@@ -940,21 +951,42 @@ export struct FbHomePage {
|
|||||||
* 本地库由 `syncAll` 定期同步,所以第一次装机时可能为空 ——
|
* 本地库由 `syncAll` 定期同步,所以第一次装机时可能为空 ——
|
||||||
* 那时仍然要能建(纪念日本身不依赖人)。
|
* 那时仍然要能建(纪念日本身不依赖人)。
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* 确保名册已加载(纪念日「关联人」要用)。
|
||||||
|
*
|
||||||
|
* 🔴🔴 #91c(用户 2026-11-05):「刚才关联人还有1人呢,现在关联人怎么没了?
|
||||||
|
* 是个空的。」
|
||||||
|
*
|
||||||
|
* **真根因**:这段加载原来**写在 `openDayCreate()` 里面** ——
|
||||||
|
* 也就是说只有"新建纪念日"会去拉名册;
|
||||||
|
* **点一条已有纪念日(`openDayDetail`)走的是另一条路,从不加载** ⇒
|
||||||
|
* `dayRoster` 恒为空数组 ⇒ 编辑框里按 id 查不到人 ⇒ 关联人区域一片空白。
|
||||||
|
*
|
||||||
|
* ⚠️ 数据没丢(`personIds` 一直是好的),只是**画不出来** ——
|
||||||
|
* 所以更吓人:看着像关联关系被清空了。
|
||||||
|
* ⚠️ 抽成独立方法,两个入口都调;`dayLoadingRoster` 防并发重复拉。
|
||||||
|
* ⚠️ 失败只记日志、不打断:纪念日本身不依赖人(名册为空也能建/能改)。
|
||||||
|
*/
|
||||||
|
private async ensureDayRoster(): Promise<void> {
|
||||||
|
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<void> {
|
private async openDayCreate(): Promise<void> {
|
||||||
if (this.dayCreating) {
|
if (this.dayCreating) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.dayRoster.length === 0 && !this.dayLoadingRoster) {
|
await this.ensureDayRoster();
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.dayCreating = true;
|
this.dayCreating = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user