跨端: 补附件区(两端一直都有这个功能,我上次误判成"死代码")+ 修两个真 bug
══ ① 更正我 2026-09-19 的一个**错误结论**(已写进 docs/DEBTS.json 留档) 那天我审计后写下:`GET /me/mail/inbox` 的回包**既没有 `attachments` 也没有 `has_attachments`** ⇒ `MailList.tsx:261` 的 `mail.attachments?.length ?? 0` 恒为 0、WebUI 那个 📎 是**死代码**。并据此在鸿蒙侧**有意不抄**这个标记。 **这个结论是错的**,错在取证方法:我**只看了一封没有附件的邮件**, 看到 key 不在,就断言服务端从不返回它。事实: · 服务端 `GetInbox`(`mail.go:586`)**明确调了 `fillAttachments`**,注释还写着 理由:「Agent 靠收件箱列表得知有哪些附件可下载,否则它不知道该调 attachment_id」 · `Mail.Attachments` 的 tag 带 **`omitempty`** ⇒ **没有附件的邮件根本不输出这个 key** 实测 `limit=200`(96 封):带 `attachments` 的 **2 封**,正是真有附件那两封。 ★ 教训:**`omitempty` 字段的"缺失"不等于"服务端不返回"**。 判「某字段有没有」必须拿**确实有值的那条**去验,而不是拿一条恰好为空的数据。 这与同一天那个白屏崩溃(`session_workspace` 缺失 → `undefined` → 抛) 是**同一个坑的两面** —— 那天是"缺失 → 客户端崩",今天是"缺失 → 我误判成不返回"。 ══ ② 补附件区(鸿蒙原来完全没有) · `model/Attachment.ts`(新)—— `formatSize` / `attachmentLabel`,纯逻辑无 SDK 依赖, 逐字对齐 WebUI `api/client.ts:390`(三档 + 保留一位小数)。 · `MailApi.downloadAttachment` —— 走 `getBytes`(不是 `get<T>`:后者假定 JSON, 取二进制会炸;壁纸当初踩过)。 · `IcsFile.saveBinaryFile` —— 与既有 `saveIcsText` 同一套流程,只是写 `ArrayBuffer`。 · `MailDetailPage` 正文之后渲染附件清单(回形针 + 文件名 + 大小 + 下载), 位置/形态对齐 WebUI `Attachments.tsx`(无附件时**整块不渲染**)。 · 列表行的 📎 + 数字(`attach_count`,与 `cc_count` 同形状派生)。 ══ ③ 顺带撞出并修掉两个**真 bug** **bug A(差一点就是 94/96 必崩)**:我第一版写 `mail.attachments.length` —— 而 `Attachments` 带 `omitempty`,96 封里只有 2 封有这个 key ⇒ 其余 94 封是 `undefined` ⇒ `.length` 抛。**与当天早些时候那个白屏崩溃是同一个坑, 我刚修过、还在 `Models.ets` 里写了一大段注释,然后加新字段时照踩。** ⇒ 说明"记住别这么写"不管用,要在每个真正读的地方把 `?? []` 写出来。 **bug B(潜在白屏)**:`PermissionTab` 读 `req.session_alias` —— 而服务端 `PermissionRequest` struct **根本没有这个字段**(`models.go:239-255`), `ListPendingPermissionsFor` 的 SELECT 也没查它,WebUI 的类型里同样没有。 它是我照"授权卡总得显示会话名"的直觉加出来的。⇒ 恒 `undefined`, 一旦有待办就抛。**一直没暴露只因为当前待办数一直是 0**(实测 `{"requests":[]}`)。 ⇒ 改成服务端确实有的 `agent_name`,并**删掉那个字段声明**: 让误用变成**编译错**,而不是运行时白屏。 同样是 `body_preview`(`omitempty`,值是 `Body` 的截断)—— 空正文 ⇒ 空串 ⇒ 服务端省略 key ⇒ `undefined.length` 抛。那批 96 封恰好都有正文, 所以"看起来没问题"——那正是这个坑的形态。已加 `?? ''`。 ══ ④ 新判据:`omitempty` 字段的读法(形状,不是实例) 从 `server/internal/models` **算出**"只以 omitempty 形式出现过"的字段名 (不在判据里手抄名单),再扫鸿蒙侧对它们的裸成员调用。 ★ 关键:**不能按字段名一刀切** —— 我第一版就是这么写的,报了 6 处、4 处误报: `session_alias` 在服务端有**两个**声明(`Mail` 上带 omitempty、`repo.Contact` 上不带), 鸿蒙那 4 处读的全是 `Contact` ⇒ 恒有值、不是 bug。 ⇒ 只扫"从未不带 omitempty 出现过"的名字,那 4 处自动排除。 已逐个核实 4 处豁免(每条都写了取证理由,不是"看着像就放过")。 变异验证:把 `?? []` 去掉 → 判据转红,且**正是**报 `MailStore.ets: mail.attach_count = mail.attachments.length`。 ══ ⑤ 数据路径已实测(模拟器) 临时把 `INBOX_PAGE_SIZE` 提到 200(因为有附件那两封在下标 51/52, 默认 limit=50 **根本取不到** —— 这也解释了为什么之前一直没发现), 加临时 hilog 后拿到: AttProbe: mail=531a1629-… attach=1 AttProbe: mail=b68cbbe8-… attach=1 正好是那两封。验完已撤掉探针、`INBOX_PAGE_SIZE` 恢复 50。 ══ ⚠️ 本轮**未能**完成设备端视觉验收 模拟器已卡死(`hdc` 能连上但 `shell` 超时;进程 152% CPU、已跑 32 小时), 导致套件里的设备判据各跑 836 秒后失败("要能拉起应用")。 主机可用内存只剩 ~3.7GB。附件区的**渲染**(清单外观、下载落盘) 尚未在设备上看过 —— 待模拟器恢复后补。
This commit is contained in:
@ -33,6 +33,12 @@ const COMM_TS = join(HARMONY_ETS, 'model/CommTabs.ts');
|
||||
|
||||
/** 被测对象:鸿蒙客户端真正引用的那份逻辑(不是复制品) */
|
||||
const H = await import(pathToFileURL(MODULE_TS).href);
|
||||
/*
|
||||
* 附件格式化(`model/Attachment.ts`,同样是纯逻辑、无 SDK 依赖)。
|
||||
* ★ 直接执行鸿蒙那一份代码 —— 与 `H`/`C` 同一手法,验的是**行为**。
|
||||
*/
|
||||
const ATT_TS = join(HARMONY_ETS, 'model/Attachment.ts');
|
||||
const ATT = await import(pathToFileURL(ATT_TS).href);
|
||||
const C = await import(pathToFileURL(COMM_TS).href);
|
||||
|
||||
const page = code(join(HARMONY_ETS, 'pages/MainPage.ets'));
|
||||
@ -871,3 +877,44 @@ test('★ 详情页折叠头部的展开区要有足够高度(14px 的横条
|
||||
assert.ok(Number(h[1]) >= 32,
|
||||
`★ 展开行的高度应 ≥32vp(实测 14px 太薄、点不中;修成 36 后实测可点区 43px)。实际 ${h[1]}`);
|
||||
});
|
||||
|
||||
test('★ 附件区:字节数格式化与 WebUI 逐字一致(三档 + 保留一位小数)', () => {
|
||||
/*
|
||||
* ★★ 2026-09-20 加。鸿蒙原来**完全没有附件区** ——
|
||||
* `MailDetail.attachments` 一直在模型里、服务端也在返回,界面一个都没画。
|
||||
* WebUI `MailView.tsx:148/692` 两处都调 `AttachmentList`。
|
||||
*
|
||||
* 这里验的是格式化:期望值照 WebUI `api/client.ts:390 formatSize` **逐字**写,
|
||||
* 不是"大概像就行" —— 两端同一个文件名旁边显示 `512 B` 与 `512.0 B`
|
||||
* 会显得是两个不同的软件。
|
||||
*
|
||||
* ★ 边界各取一个,而不是只测"正常值":
|
||||
* · `0` —— 0 字节文件真实存在(空文件),不能显示成空串
|
||||
* · `1023` —— 最后一个 B 档(`< 1024`,不加小数)
|
||||
* · `1024` —— 第一个 KB 档(**跨越点**,最容易差一位)
|
||||
* · `1048575`—— 最后一个 KB 档
|
||||
* · `1048576`—— 第一个 MB 档(第二个跨越点)
|
||||
*/
|
||||
assert.equal(ATT.formatSize(0), '0 B', '0 字节要显示成 "0 B",不能是空串');
|
||||
assert.equal(ATT.formatSize(512), '512 B', 'B 档是整数、不加小数(与 WebUI 同)');
|
||||
assert.equal(ATT.formatSize(1023), '1023 B', '1023 是最后一个 B 档');
|
||||
assert.equal(ATT.formatSize(1024), '1.0 KB', '1024 跨到 KB 档,且保留一位小数');
|
||||
assert.equal(ATT.formatSize(1536), '1.5 KB', 'KB 档保留一位小数');
|
||||
assert.equal(ATT.formatSize(1048575), '1024.0 KB', '最后一个 KB 档(WebUI 也是 1024.0 KB,不提前进位)');
|
||||
assert.equal(ATT.formatSize(1048576), '1.0 MB', '1048576 跨到 MB 档');
|
||||
assert.equal(ATT.formatSize(3 * 1024 * 1024), '3.0 MB', 'MB 档保留一位小数');
|
||||
});
|
||||
|
||||
test('★ 附件区:空文件名要显式占位,不能留一条看起来坏掉的空行', () => {
|
||||
/*
|
||||
* 服务端理论上不会给空文件名,但真出现时界面上会是一条**看不出来是错的**
|
||||
* 空行(比显式占位更难诊断)。占位符不是文案偏好,是**可诊断性**。
|
||||
*/
|
||||
const ok = ATT.attachmentLabel('report.pdf', 2048);
|
||||
assert.equal(ok.filename, 'report.pdf');
|
||||
assert.equal(ok.size, '2.0 KB');
|
||||
|
||||
const empty = ATT.attachmentLabel('', 100);
|
||||
assert.notEqual(empty.filename, '', '空文件名要有占位符,不能是空串');
|
||||
assert.equal(empty.size, '100 B', '空文件名不影响大小显示');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user