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:
@ -70,12 +70,14 @@ test('① 点击:每个导航项点下去落到**它自己**那个 index(配
|
||||
* 只看"出现过 normalizeNavIndex"不够(可以写 `normalizeNavIndex(0)` 骗过去),
|
||||
* 所以要求括号里的实参就是 `index`。
|
||||
*/
|
||||
const assigns = [...item.matchAll(/this\.currentIndex\s*=\s*([^;]+);/g)].map(m => m[1].trim());
|
||||
assert.equal(assigns.length, 1, `导航项里应当恰好有一次点击赋值(实际 ${assigns.length} 次)`);
|
||||
// 只数**赋值**(`= `),不数三元式里的 `===` —— 外壳入口不赋值,所以正文里仍只能有一次赋值
|
||||
const assigns = [...item.matchAll(/(?<!=)this\.currentIndex\s*=\s*([^;=]+);/g)].map(m => m[1].trim());
|
||||
assert.equal(assigns.length, 1, `导航项里应当恰好一次 currentIndex 赋值(实际 ${assigns.length} 次)—— 外壳入口(route 非空)走路由,不赋 currentIndex`);
|
||||
assert.match(assigns[0], /normalizeNavIndex\(\s*index\s*\)/,
|
||||
`点击要落到本项自己的 index 并过归一化,现在赋的是:${assigns[0]}`);
|
||||
// 处理器必须挂在**这一项**上(不是别的成员上)
|
||||
assert.match(item, /\.onClick\(\(\)\s*=>\s*\{[\s\S]{0,120}?this\.currentIndex/, '点击处理器要挂在本项上');
|
||||
// 外壳入口(route 非空)走路由、内容窗格才赋 currentIndex —— 两者都走在本项的 onClick 里
|
||||
assert.match(item, /item\.route.*pushUrl|pushUrl.*item\.route|if \(item\.route[\s\S]{0,80}pushUrl/,
|
||||
'外壳入口(route 非空)要路由到 @Entry 页(与 WideSidebar 的设置按钮同一行为)');
|
||||
// ForEach 要真的把 index 传进来(否则上面那句 index 无从谈起)
|
||||
const bar = builderBody(main, 'NavBar() {');
|
||||
assert.match(bar, /ForEach\(NAV_ITEMS,\s*\(item: NavItem,\s*index: number\)/, 'ForEach 要带 index 参数');
|
||||
@ -120,12 +122,20 @@ test('② 挂载:index 决定挂哪个页面(点第 2 项必须挂联系人
|
||||
assert.ok(!/if \(this\.currentIndex === 1\)/.test(root),
|
||||
'日历不该写成 if/else 的一支:它是常驻 pane(卸载重挂会丢掉"在看哪个月",且 today 只能靠挂载重算)');
|
||||
/*
|
||||
* 分派必须**穷尽** NAV_ITEMS。2026-09-14 日历入口上架时这条**如约变红**
|
||||
* (当时是 `length === 2` + 两支 if/else)—— 加项必须同时加分派,否则第 3 项点了没内容。
|
||||
* 现在的形状与"三项"绑定:通信(0) / 日历(1) / 联系人(2),日历那一支**常驻挂载**。
|
||||
* 分派必须**穷尽内容窗格**(`NAV_CONTENT_COUNT` = 3:通信/日历/联系人),但
|
||||
* **不**把外壳入口(第 4 项「我的」)算进去 —— 它是 `@Entry` 页,点击走路由,
|
||||
* 不在 currentIndex 分派里挂内容。2026-09-14 日历入口上架时这条**如约变红**
|
||||
* (当时是 `length === 2` + 两支 if/else)—— 加**窗格**必须同时加分派。
|
||||
* 2026-09-17:加了第 4 枚图标「我的」(外壳入口,与 WebUI 四入口对齐),
|
||||
* 所以这里判的是 **NAV_CONTENT_COUNT** 与分派一致,而 NAV_ITEMS 是 4。
|
||||
*/
|
||||
assert.equal(N.NAV_ITEMS.length, 3,
|
||||
`NAV_ITEMS 现在是 ${N.NAV_ITEMS.length} 项,与分派(通信/日历/联系人)不匹配 —— 加了项必须同时加分派`);
|
||||
assert.equal(N.NAV_CONTENT_COUNT, 3, `内容窗格应为 3(通信/日历/联系人),实际 ${N.NAV_CONTENT_COUNT}`);
|
||||
assert.equal(N.NAV_ITEMS.length, N.NAV_CONTENT_COUNT + 1,
|
||||
`NAV_ITEMS 现在是 ${N.NAV_ITEMS.length} 项;应为 3 个内容窗格 + 1 个外壳入口(我的)`);
|
||||
// 第 4 项必须是外壳入口(带 route),否则它会被 currentIndex 分派静默漏掉
|
||||
const shell = N.NAV_ITEMS[N.NAV_CONTENT_COUNT];
|
||||
assert.equal(shell.key, 'me', '第 4 项应是「我的」');
|
||||
assert.equal(shell.route, N.NAV_SETTINGS_ROUTE, '「我的」要路由到设置页');
|
||||
// 内容挂在浮动条**下面**(先内容后条),否则条会被内容盖住、点不到
|
||||
const navAt = stack.indexOf('this.NavBar()');
|
||||
const contentAt = stack.indexOf('CommPage({ bgActive: this.bgActive })');
|
||||
@ -244,7 +254,7 @@ test('★ 系统 `Tabs` 的 bar 已经不在(这一期换的就是它),且
|
||||
* 注意:**通信页内部**的三栏仍然用系统 `Tabs`(那是页面内部页签,不是根导航)——
|
||||
* 所以这条只在"根组件"的范围内断言,别扫全文。
|
||||
*/
|
||||
const root = main.slice(main.indexOf('build() {\n /*\n * 底部三项'));
|
||||
const root = main.slice(main.indexOf('build() {\n /*\n * 底部四枚图标'));
|
||||
assert.ok(root.length > 300, '要能取到根 build() 的正文');
|
||||
const navCode = stripComments(root);
|
||||
assert.ok(!/\bTabs\(/.test(navCode), '根导航里不该再有系统 Tabs');
|
||||
@ -253,16 +263,19 @@ test('★ 系统 `Tabs` 的 bar 已经不在(这一期换的就是它),且
|
||||
// 反面之二:也不许把旧 builder 留着不用(留着就是死代码,下一个人会以为它还在生效)
|
||||
assert.ok(!/TabBarBuilder/.test(stripComments(main)), 'TabBarBuilder 已被 NavBar 取代,不该留在文件里');
|
||||
|
||||
// 平级项:通信/日历/联系人(日历是 P6 第 1 步做完后上架的,顺序与 WebUI 一致)
|
||||
assert.deepEqual(N.NAV_ITEMS.map(i => i.label), ['通信', '日历', '联系人'], '平级项与顺序');
|
||||
// 平级项:通信/日历/联系人 + 外壳入口「我的」(与 WebUI NarrowNav 的四入口一致)
|
||||
assert.deepEqual(N.NAV_ITEMS.map(i => i.label), ['通信', '日历', '联系人', '我的'], '底栏四入口与顺序');
|
||||
assert.equal(new Set(N.NAV_ITEMS.map(i => i.key)).size, N.NAV_ITEMS.length, '键要唯一(ForEach 的 key)');
|
||||
assert.deepEqual(N.NAV_ITEMS.map(i => N.navKeyAt(N.NAV_ITEMS.indexOf(i))), N.NAV_ITEMS.map(i => i.key),
|
||||
'navKeyAt 与清单一致');
|
||||
// navKeyAt 只覆盖内容窗格(前三项),越界回 0 —— 外壳入口不在此函数范围内
|
||||
assert.deepEqual(N.NAV_CONTENT_ITEMS.map(i => N.navKeyAt(N.NAV_CONTENT_ITEMS.indexOf(i))), N.NAV_CONTENT_ITEMS.map(i => i.key),
|
||||
'navKeyAt 与内容窗格清单一致');
|
||||
// 归一化:越界/负数都退回第一项(点击与外部传值都过它)
|
||||
assert.equal(N.normalizeNavIndex(-1), 0);
|
||||
assert.equal(N.normalizeNavIndex(99), 0);
|
||||
assert.equal(N.normalizeNavIndex(1), 1);
|
||||
assert.equal(N.normalizeNavIndex(2), 2, '第三项(联系人)也必须归一到自己');
|
||||
// 外壳入口(index 3)不归一到自己 —— 它不进 currentIndex 分派,normalize 越界即回 0
|
||||
assert.equal(N.normalizeNavIndex(3), 0, '外壳入口(我的)不该被当作内容窗格下标');
|
||||
assert.throws(() => N.navKeyAt(1.5) === undefined, undefined, '非整数下标不该悄悄生效');
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user