fix(harmony): 图标按 px→vp 正确缩放 + 底栏四入口 + 卡片 10vp 缩进

图标过小的根因(实图硬证):`Path.commands` 的坐标按 **px** 解释,不是 vp。
- `Shape.viewPort` 只裁剪不缩放(官方样例"缩放成立"是因为它用 Rect/Circle
  自带 vp 宽高,不是裸 Path.commands);
- 改动前的 `Path.scale({iconSize/24})` 对 iconSize=24 等于 1.0,同样没缩放。
  症状:FAB 内 compose 只画 24px(应 24vp=84px),且偏在盒子左上角。

修法:`scale({vp2px(iconSize)/24})` 把 24 单位路径放大到 iconSize vp;
`scale` 会连带放大描边,故 `strokeWidth = strokeWeight / vp2px(1)` 抵消,
否则 1.8vp 描边会变成 ~7.3vp 的"实心块"(实图已验证)。
用 `this.getUIContext().vp2px()` 而非全局 `vp2px`——后者已废弃,
`harmony-system-api` 判据对全局调用默认判红。

验收(模拟器实图,密度 3.489):
- FAB 内 compose 图标 84px = 24vp ✓(原 24px)
- 底栏图标描边 7-8px ≈ 1.8/24×28vp=7.3px ✓(原为实心块)
- 四个图标均为清晰描边,与 WebUI icons.tsx 同几何

底栏四入口:新增"我的"(person → pages/SettingsPage),对齐 WebUI
通信/日历/联系人/我的;NAV_CONTENT_ITEMS 保持宽屏侧栏三项,
避免宽屏出现重复 person 图标。

卡片缩进:`width('100%') + margin({left:10,right:10})` 在 ArkUI 里不会
缩到 90%——margin 加在宽度外面,卡片顶出父容器(实测左缘只留 ~1px,
列表卡才有正确 10vp)。改为外层容器加 padding。验收:页头卡与邮件卡
左缘同为 x=35(10.0vp)、右缘 x=1219。

推送:reportToken 先申请通知权限再取 token(部分设备权限未开时
getToken 返回空 / 报 1600004),并补两条文件形状判据钉住
client_id 配置与调用顺序。

测试:198 passed / 0 failed。
This commit is contained in:
2026-09-17 16:27:27 +08:00
parent a5f680a147
commit ff75339baa
10 changed files with 301 additions and 179 deletions

View File

@ -141,3 +141,49 @@ test('★ DELETE 也带 JSON body(不是 query / 不是 path 参数)—— A
'DELETE 端点要 JSON body,而`del`只有 path ⇒ 推送的注销调用会 400/无效。' +
'**正确修法**:给 del 加可选 body(向后兼容)。**最常见的错误修法**:把 token 拼进 URL(那不是线上形状)。');
});
/*
* ★ 取 token 的两个硬前提(2026-09-17 实测根因,pi 邮件线上链路):
*
* 症状:鸿蒙 App 连上了、登录了(服务端收到 GET /me/devices/push-token 200),
* 但**从来没有 POST** —— getToken() 返回空串,reportToken 直接 return。
*
* 根因 ①:module.json5 的 module.metadata **没有配 client_id**。
* Push Kit 必须先读到这个 client_id 才能向华为推送服务器申请 token ——
* 缺了它 getToken() 永远返回空(且不报错,静默)。
* 业界示例(dev.to 的 HarmonyOS Next PushKit 集成)与之互证:
* "获取 token 前需在 module.json5 的 module.metadata 中配置 client_id(来自 AGC)"。
*
* 根因 ②:通知权限申请必须在取 token **之前**。
* 部分设备上通知权限没开时 getToken 会返回空 / 报 1600004。
*
* 这两条都不需要设备就能钉(文件形状),且是最容易悄悄回归的地方。
*/
test('★ 取 token 前提①:module.json5 的 module.metadata 必须配 client_id(缺了 getToken 恒空)', () => {
const mod = code(join(HERE, '..', '..', 'harmony', 'entry', 'src', 'main', 'module.json5'));
// module 级 metadata(不是 abilities/extensionAbilities 里的)
assert.match(mod, /"metadata":\s*\[\s*\{\s*"name":\s*"client_id"\s*,\s*"value":\s*"[0-9]+"\s*\}/,
'module.metadata 里必须有 {"name":"client_id","value":"<数字>"}。' +
'这是 Push Kit 申请 token 的硬前提;缺了它 getToken() 静默返回空串 —— ' +
'症状是"App 能连服务端、能 GET,但永远不 POST token"。');
// client_id 的值要跟 agconnect-services.json 里的一致(不是随手编一个数)
const agc = code(join(HERE, '..', '..', 'harmony', 'entry', 'src', 'main', 'resources', 'rawfile', 'agconnect-services.json'));
const m = agc.match(/"client_id"\s*:\s*"(\d+)"/);
assert.ok(m, 'agconnect-services.json 里要能找到 client_id');
assert.ok(mod.includes(`"value": "${m[1]}"`),
`module.json5 的 client_id 必须等于 agconnect-services.json 里的 ${m[1]}(两处不一致时 Push Kit 认不出应用)`);
});
test('★ 取 token 前提②:reportToken 里 requestEnableNotification 在 getToken 之前', () => {
const svc = code(join(HERE, '..', '..', 'harmony', 'entry', 'src', 'main', 'ets', 'api', 'PushService.ets'));
const lines = svc.split('\n');
const start = lines.findIndex(l => /async reportToken\(/.test(l));
assert.ok(start > 0, '要能找到 reportToken');
const body = lines.slice(start, start + 60).join('\n');
const permAt = body.indexOf('requestEnableNotification');
const tokenAt = body.indexOf('this.getToken()');
assert.ok(permAt > 0, 'reportToken 里要有 requestEnableNotification');
assert.ok(tokenAt > 0, 'reportToken 里要有 getToken');
assert.ok(permAt < tokenAt,
'通知权限申请必须在 getToken **之前** —— 部分设备上权限未开时 getToken 返回空 / 报 1600004(2026-09-17 实测)');
});