鸿蒙|修发件箱点不开(SentRow 缺 onClick)+ 补列表行判据

用户报的三件事,逐条实测后的结论:

## ① 发件箱内容点不开 —— 真 bug,本次修复

复现:点发件箱「致 pi」那一行
  · 日志里**没有** `GET /mail/{id}`(收件箱同样操作是会发的)
  · 右栏仍是占位(「选择一封邮件查看…」)

根因:点击挂在**外层** `Column` 上,而 `SentRow` 自己**一个 `.onClick` 都没有**。
更要命的是 `isFlatGroup(g)` 那条分支(单封不成组)直接调 `SentRow`,
**外层那层点击根本不经过它** —— 而用户的数据恰好是 1 封不成组 ⇒ 完全点不动。

收件箱的 `MailRow` 是挂在行自己身上的(`.clip(true)` 之后直接 `.onClick`),
所以它两条分支都通。这是本仓反复出现的形状:同一件事两处各写一遍,然后慢慢分叉。

修法:`.onClick` 移进 `SentRow` 自身(与 `MailRow` 同形),
并删掉外层那处(留着会变成"同一行两处都能点")。

修复后实测:发 `GET /mail/2d882e82…`,右栏渲染出详情 ✅

## ② 对话树点不开 —— 实测能点开(不是 bug)

代码里有完整的实现(`openThread()` + `bindSheet` + `tree` 图标按钮)。
点开路径:**先点详情页标题展开头部** → 出现「对话树」→ 点它
(`GET /mail/{id}/thread` 发出,弹层显示 `jianf → pi / 测试`)。

★ 折叠是**两端一致**的行为,不是鸿蒙漏做:
  WebUI `CollapsibleHeader` 的 `useState(false)` 也是默认收起。
  我实测确认了展开后才看得到该按钮。

## ③ 邮件本地缓存 —— 两端都没有(不是鸿蒙落后)

对着源码逐个数:
  WebUI `stores/` 有 persist 的:accountStore(8) / appearanceSync / backgroundStore(15)
                                / contactStore(5) / themeStore(3)
  WebUI **没有** persist 的:`mailStore`(0) / sessionStore(0) / uiStore(0) / authStore(0)
  ⇒ **邮件与会话在 WebUI 也是不缓存的**,每次打开都从服务端拉。
鸿蒙侧:AppearanceStore / AccountManager 有 preferences 持久化,MailStore 没有
  —— 与 WebUI 对齐,不是缺口。

## 判据

`harmony-logic` +1(35 条):**列表行必须把 onClick 挂在自己身上**。
钉的是不变式("行自带点击"),不是某个函数的写法:
`MailRow` / `SentRow` 的属性链里必须有自己的 `.onClick` 且调到 `openMail`。

变异:删掉 `SentRow` 的 `.onClick`(重演原 bug)→ 判据红;还原 → 绿。

套件:harmony-logic 35/35、harmony-nav 22/22、harmony-appearance 28/28、
harmony-contacts 5/5、animation-audit 15/15。
This commit is contained in:
2026-09-24 14:35:07 +08:00
parent eeecaad79e
commit 548314013a
2 changed files with 79 additions and 1 deletions

View File

@ -985,3 +985,48 @@ test('★ 附件区:空文件名要显式占位,不能留一条看起来坏
assert.notEqual(empty.filename, '', '空文件名要有占位符,不能是空串');
assert.equal(empty.size, '100 B', '空文件名不影响大小显示');
});
/**
* ★★ 2026-09-24 新增(用户:「发件箱内容也点不开」)。
*
* ── 实测复现 ──
* 点发件箱里「致 pi」那一行:
* · 日志里**没有** `GET /mail/{id}`(收件箱同样操作是会发的);
* · 右栏仍是占位(「选择一封邮件查看…」)。
*
* ── 根因 ──
* 点击挂在**外层** `Column` 上,而 `SentRow` 自己**一个 `.onClick` 都没有**。
* 更要命的是:`isFlatGroup(g)` 那条分支(单封不成组)直接调 `SentRow`,
* **外层那层点击根本不经过** ⇒ 用户的数据恰好是 1 封不成组 ⇒ 完全点不动。
*
* 收件箱的 `MailRow` 是挂在行自己身上的,所以它两条分支都通。
* 这就是本仓反复出现的形状:**同一件事两处各写一遍,然后慢慢分叉**。
*
* 判据钉的是**不变式**(“列表行自带点击”),而不是某个函数的写法:
* 每个能展开邮件的列表行 `@Builder`,它的属性链里必须有自己的 `.onClick`。
*/
test('★ 列表行必须把 onClick 挂在自己身上(发件箱就漏在这里)', () => {
/*
* 行 Builder 的名单:两栏各自的邮件行。
* ★ 不扫全部 `@Builder`(那样会把组头、空态、徽标都算进来),
* 只钉**真正代表“一封邮件”的那两个** —— 与 `PAGE_SOURCES` 同一条纪律:
* 名单显式,不把作用域开得比声称的宽。
*/
const ROWS = ['MailRow', 'SentRow'];
for (const name of ROWS) {
const at = pageCode.indexOf(`${name}(mail: MailLike)`);
assert.ok(at > 0, `${name} 要存在(列表行)`);
/*
* 取到下一个 `@Builder` 为止(下一个 Builder 就是下一个方法的开始)。
* 比“取 3000 字符”可靠:方法长短会变,而 `@Builder` 是结构边界。
*/
const nextAt = pageCode.indexOf('@Builder', at);
const body = pageCode.slice(at, nextAt > at ? nextAt : at + 4000);
assert.match(body, /\.onClick\(/, [
`${name} 的属性链里必须有自己的 \`.onClick\``,
'—— 点击挂在外层容器时,扁平组分支(isFlatGroup)**不经过它**,',
'那一栏就完全点不动(2026-09-24 发件箱实测就是这个形状)。',
].join(''));
assert.match(body, /openMail\(/, `${name} 的 onClick 要调到 openMail(而不是只写个空壳)`);
}
});

View File

@ -1139,6 +1139,30 @@ struct SentTab {
})
.borderRadius(Theme.radiusCard)
.clip(true)
/*
* ★★ 2026-09-24 补 `.onClick`(用户:「发件箱内容也点不开」)。
*
* ── 实测复现 ──
* 点发件箱里「致 pi」那一行:
* · 日志里**没有** `GET /mail/{id}`(收件箱同样操作是会发的);
* · 右栏仍是占位(「选择一封邮件查看…」)。
* ⇒ 点击事件根本到不了处理函数。
*
* ── 根因 ──
* 点击原来挂在**外层** `Column` 上(`SentTab` 的 `ListItem` 里):
* Column() { this.SentRow(m) }
* .width('100%').margin({ bottom: 6 })
* .onClick(() => { this.openMail(m); })
* 而 `SentRow` **自己一个 `.onClick` 都没有** —— 收件箱的 `MailRow`
* 是挂在行自己身上的(同位置 `.clip(true)` 之后直接 `.onClick`)。
* 本仓反复出现的形状:**同一件事两处各写一遍,然后慢慢分叉**
* (这段注释上方才记过 `SentRow` 漏挂卡片修饰符的那次,同一处、同一类错)。
*
* ⇒ 与 `MailRow` 取同形:卡在行自己身上,点击也挂在行自己身上。
*/
.onClick(() => {
this.openMail(mail);
})
}
@Builder
@ -1263,7 +1287,16 @@ struct SentTab {
}
.width('100%')
.margin({ bottom: 6 })
.onClick(() => { this.openMail(m); })
/*
* ★★ 2026-09-24 删掉这里的 `.onClick`(用户:「发件箱内容也点不开」)。
*
* 点击现在挂在 `SentRow` **自己身上**(与收件箱的 `MailRow` 同形)——
* 原来挂在这一层时,**扁平组那条路根本不经过它**:
* `isFlatGroup(g)` 分支直接 `this.SentRow(g.mails[0])`,外层没有点击。
* 而用户的数据恰好就是 1 封不成组(发件箱「测试-4」)⇒ 完全点不动。
*
* 留着它还会变成"同一行两处都能点"—— 本仓反复出现的那个形状。
*/
}
.width('100%')
}, (m: MailLike) => m.mail_id)