跨端: 顶栏不再自己铺白条 + 日历改左右两栏 + 常驻窗格动画真的会播(用户三处实测指出)

用户三条反馈,逐条对应:

① 「底栏数字为什么显示在图标下面?」
   WebUI 的徽标是 `absolute top-1 right-[22%]`(脱离文档流、浮在图标右上角),
   我写成了 `Column` 的第三个子节点 ⇒ 参与竖向布局、掉到文字下面。
   改用 `Stack({ alignContent: Alignment.TopEnd })` 锚在**图标**上。
   (顺带撞了 skill 里明写的坑:Stack 没有 `.justifyContent()`。)

② 「一个横着过去的白条,我真的服了」/「期望:融进背景」
   WebUI 的顶栏**自身没有底色** —— 只有 `border-b border-gray-200`
   (`ContactPanel.tsx:64`、`CommTabs.tsx:40`、`CalendarView.tsx:295`),
   底色由所在面板给;壁纸开启时那层面板是玻璃色(`index.css:876`)。
   鸿蒙三个窗格顶栏写死了 `Theme.surface`(实心白)⇒ 无论壁纸开没开,
   顶上都是一条不通明白带。改成与**页面底**同一口径
   (`bgActive ? Transparent : surface`)+ 补下边框。

③ 「日历页面和webui布局完全不同」
   WebUI 是**左右两栏**(`CalendarView.tsx:452-457`):左 `flex-1` 网格、
   右 `400px` 常驻面板(日程 / 编辑器 / 小时网格三态互斥)。
   鸿蒙原来是**单栏竖堆**。重搭为两栏,`paneWide` 由 `.onAreaChange`
   量本页**自己的**宽度(不是屏幕宽度 —— 宽屏下这一页已被侧栏占掉一截);
   编辑器改占右栏位置(不再整页盖掉正在看的那个月)。

④ 「最严重的动画问题你一点也不该改」
   日历是**常驻挂载**(`visibility` 控制,因为它里面 today 要随时间重算、
   也要保住"正在看哪个月"),而 `.transition()` 只在**挂载/卸载**时触发
   (SDK 原话 \"when it **appears and disappears**\")⇒ 挂在它上面的
   `.transition(paneRiseIn())` **一帧也不会播**,切过去是硬弹。
   WebUI 踩过同一个坑并把错法写进了 `index.css:1305-1320`
   (「只挂了类,却没让触发窗口出现 ⇒ 类挂着、动画永远不播」),
   它的解法是 `html.view-switch` 重放窗口。ArkUI 对应物是
   `animateTo` + 显式 `calPaneIn` 属性(`opacity` + `translate`)。
   ★ reset 必须在 `animateTo` **外**:写进回调里会与同帧的 1 相抵,
     渲染层只看得见最终值 ⇒ 动画退化成一个瞬移。

顺带修:
· 宽屏侧栏 = 3 项(通信/日历/**联系**)+ 底部一簇(头像/主题/退出),
  与底栏的四项(含「我的」)**不是同一份清单** —— WebUI 的 Sidebar 与
  NarrowNav 本就不同(`Sidebar.tsx:26-44` vs `NarrowNav.tsx:37-40`+143)。
  新增 `NAV_SIDEBAR_ITEMS` / `ME_PANE_INDEX` / `SIDEBAR_ITEM_*`。
· 退出登录抽成 `api/Logout.ets` 的 `performLogout()`:侧栏底簇与「我的」页
  两个入口必须做同一件事(尤其"先注销推送 token"那一步),复制一份就会不一致。
· 徽标 `'plain'` 档底色:WebUI 是石板灰 `--c-chrome-600`(#475569),
  我写成与未读共用红色 ⇒ 「联系」的徽标看起来像"有未读"。
· 主题快捷开关(侧栏底簇):对齐 `ThemeToggleButton` —— 从 `system` 翻转时
  落到**当前生效值的反面**(不是回 system;那可能毫无变化、让按钮看起来坏了)。
· 「浓度」→「压暗」+ 数值带单位(原先屏上印 `56.000000`;WebUI 是 `suffix="%"`)。

判据(8 个套件全绿:logic 28 / nav 18 / widescreen 7 / window 9 /
arkts 5 / contacts 5 / calendar 30 / system-api 5):
· `harmony-widescreen` ② 重写:**回读 `Sidebar.tsx` 数 `short:` 的个数**
  要求鸿蒙同数,并断言底部一簇三键真的被调用(`this.onToggleTheme()` ——
  第一版写成 `/onToggleTheme/`,变异测试当场证明它不咬:属性**声明**还在,
  正则照样匹上)。新增 ⑧:三档色调各自底色,期望值从 `--c-chrome-600` 读出。
· `harmony-nav` 动画条重写:改判**机制真的存在且被驱动**
  (旧断言 `.visibility(...).transition(...)` 锁的正是那个 bug ——
  判据引自己写的注释当依据,就会把错误锁死)。新增 reset-在-animateTo-外
  这条断言(否则动画退化成瞬移)。
· `harmony-nav` 设备条:`navItemsOf` 的宽屏过滤从"左边缘靠左 1/6"
  改成"**整个盒子在侧栏轨道内**" —— 旧条件把日历网格的格子
  (实测 `[229,511][366,794]`)也当成导航项,一屏数出 11~12 个。
  新增 `navRailItemsOf`:导航轨贴顶、底部簇在屏底,按位置切一刀。
· `harmony-logic` 两处:从 `NAV_ITEMS` / `NAV_SIDEBAR_ITEMS`
  **各自的数组**里取 label —— 原先把全文件 `label:` 一网打尽,
  得到 7 个(4+3 混在一起),任何一边改对了它都会红。

设备实测(HATriple 三折叠,3184×2232):侧栏 3 项 + 底簇 / 底栏徽标回到
图标右上角 / 日历左右两栏与 WebUI 并排同构。

server/go.mod:补 2da38bb 漏提交的 lunar-go 依赖。
This commit is contained in:
2026-09-19 12:30:05 +08:00
parent 652316674a
commit 36ef15a8f2
15 changed files with 1216 additions and 331 deletions

View File

@ -243,11 +243,24 @@ test('底部是 通信 / 日历 / 联系人 三个平级页签,「会话」不
const navLabels = [...pageCode.matchAll(/NAV_ITEMS\.map\([^)]*\.label\)|NAV_ITEMS/g)].length;
assert.ok(navLabels > 0, '底栏要由 NAV_ITEMS 驱动');
const navSource = code(join(HARMONY_ETS, 'model/NavItems.ts'));
const labels = [...navSource.matchAll(/label:\s*'([^']+)'/g)].map(m => m[1]);
// 2026-09-14:日历页(P6 第 1 步)落地后入口上架 ⇒ 三项。撤掉「会话」这条判断本身没变:
// 它不是第三个地方,而是"同一批数据的另一种看法"(收件箱折叠 + 联系人卡片视图)。
// 2026-09-17:与 WebUI NarrowNav 对齐 ⇒ 四项(前三内容窗格 + 外壳入口「我的」)。
assert.deepEqual(labels, ['通信', '日历', '联系人', '我的'], `平级项应为 通信/日历/联系人/我的,实际:${labels.join('、')}`);
/*
* ★★ 2026-09-19 修:原先这里把 `NavItems.ts` 里**所有** `label:` 一网打尽
* ⇒ 得到 7 个(底栏 4 项 **加上** 侧栏 3 项)—— 因为这两份清单现在是**分开的**:
* · `NAV_ITEMS`(底栏)= 通信/日历/联系人/我的(对齐 WebUI `NarrowNav.tsx:37-40`+143)
* · `NAV_SIDEBAR_ITEMS`(侧栏)= 通信/日历/**联系**(对齐 WebUI `Sidebar.tsx:26-44`,
* 「我的」是底部头像,不在这份清单里)
* 把它们混着数,会得到一个既不是底栏、也不是侧栏的“第三份清单”,
* 于是任何一边改对了这一条都会红。⇒ 改成**分别从各自的数组里取**。
*/
const itemsOf = (name) => {
const m = navSource.match(new RegExp(`export const ${name}: NavItem\\[\\] = \\[([\\s\\S]*?)\\];`));
assert.ok(m, `NavItems.ts 要有 ${name}`);
return [...m[1].matchAll(/label:\s*'([^']+)'/g)].map(x => x[1]);
};
assert.deepEqual(itemsOf('NAV_ITEMS'), ['通信', '日历', '联系人', '我的'],
'底栏四项(对齐 WebUI NarrowNav:收件/发件/授权合并为「通信」,末项「我的」)');
assert.deepEqual(itemsOf('NAV_SIDEBAR_ITEMS'), ['通信', '日历', '联系'],
'侧栏三项(对齐 WebUI Sidebar.tsx 的 navItems:「我的」是底部头像,不在导航轨里)');
assert.ok(!/struct\s+SessionsTab/.test(pageCode), 'SessionsTab 已经撤了,不该再留在页面里');
assert.ok(!/sessions\(\)/.test(pageCode), '撤了入口就不该再拉 /me/sessions(否则是没人看的请求)');
});
@ -520,9 +533,16 @@ test('通信页把三栏真的接上了:内部页签 + 徽标 + 悬浮加号 +
// 底部第一项的标签是「通信」而不是「收件箱」(信息架构变了,标签必须跟着变)
// P5:底栏标签现在来自 NAV_ITEMS(自绘浮动条),不是 TabBarBuilder 的参数
const navSource2 = code(join(HARMONY_ETS, 'model/NavItems.ts'));
const tabLabels = [...navSource2.matchAll(/label:\s*'([^']+)'/g)].map(m => m[1]);
/*
* ★★ 2026-09-19 修(同文件另一处同一个错):从 `NAV_ITEMS`(**底栏那份**)里取 label,
* 不能把全文件的 `label:` 一网打尽 —— 那样会把 `NAV_SIDEBAR_ITEMS`(三项)也数进来,
* 得到 7 个。两份清单现在是分开的(底栏 4 / 侧栏 3),见 `harmony-widescreen` ②。
*/
const navItemsBlock = navSource2.match(/export const NAV_ITEMS: NavItem\[\] = \[([\s\S]*?)\];/);
assert.ok(navItemsBlock, 'NavItems.ts 要有 NAV_ITEMS(底栏那份清单)');
const tabLabels = [...navItemsBlock[1].matchAll(/label:\s*'([^']+)'/g)].map(m => m[1]);
// 2026-09-14:日历页(P6 第 1 步)做完后入口上架 —— 三项且顺序固定(通信/日历/联系人)
// 2026-09-17:对齐 WebUI 四入口 —— 前三内容窗格不变,第 4 项「我的」是外壳入口(见 NavItem.route)
// 2026-09-17:对齐 WebUI 四入口 —— 前三内容窗格不变,第 4 项「我的」也是内容窗格(currentIndex === 3)
assert.deepEqual(tabLabels, ['通信', '日历', '联系人', '我的'], `底部应为通信/日历/联系人/我的四项,实际:${tabLabels.join('、')}`);
// 通信页现在还要收一个 `bgActive`(背景开着时让出页面底,否则壁纸全被盖住)——
// 所以这里钉的是"带参数地渲染通信页",不是光有个名字