fix(harmony): ★★ 三页 AppHeader 顶栏避让硬编码 0 ⇒ 顶栏压进系统状态栏(真机实测)
设备:HUAWEI MatePad Pro(MRDI-W00),HarmonyOS NEXT,API 26,
`hdc tconn 192.168.2.87:43679`(此前一直无真机,本条挂了 5 天)。
## 症状(用户报:「左上角全屏状态下不应该显示全屏,会与顶部系统顶栏冲突」)
截图像看是状态栏压住顶栏。**实测证伪了这个读法**:像素扫描 + `uitest dumpLayout`
显示顶栏文字在 y=134..166、系统状态栏止于 y=82,**两者不重叠**。
真正被切掉的是**列表第一封邮件的标题**(y=294..319,只剩一条细线)。
⇒ 两处独立问题,第二个(窗格头 y=222..320 与列表首行 y=294..319 重叠)
**不是顶栏避让**造成的,本次未修,见下。
## 已修:AppHeader 那一半的避让确实是坏的
`EntryAbility` 早就把避让读到了(真机日志 `insets: statusBar=38.588235
navIndicator=27.764706 windowDecor=37`),`CommPage`/`MainPage` 内容层也用了
(`top: max(statusBar, windowDecor) + paneGap`)。**但 `AppHeader` 是另一个消费者,
三处都写死了 `topInsetPx: 0`**:
· `SentTab`(发件箱) MainPage.ets:1658
· `SettingsPage`(我的) SettingsPage.ets:687
· `PermissionTab`(授权) PermissionTab.ets:487
只有 `AdminUsersPage` 是对的(`topInset(this.windowInsets)`)—— 抄它。
## 为什么已有的判据没抓到(这才是关键)
`harmony-window.test.mjs` 接线⑤「每一个 @Entry 页都要消费避让」是**绿的**,
因为它的 `consumes()` 认两种形状,其中一种只要**文件里出现过**
`this.windowInsets.statusBar` 就算过 —— 而 `MainPage` 的**内容层**正好读了它。
⇒ 页面上有**两个**避让消费者,判据只问"页里有没有出现过那个字段",
于是 `AppHeader` 里那个 0 被完全放过。
新判据(判据 10)改成**逐个消费者问**:任何传给 `AppHeader` 的 `topInsetPx`
不得是字面量 0。自检要求扫到 ≥4 处,防"遍历写错 ⇒ 永远绿"。
★ 这条判据自己先犯过一次同类错并当场被抓:第一版用 `prose()`(含注释),
把**我自己写进注释里的**「原先 topInsetPx: 0」抓成了红 ——
判据在判自己的注释。改用本文件已有的 `stripped()`(其注释原话:
「判据的锚不能落在被守对象的自述上」)。红绿已验:把 SettingsPage 退回
缺陷版 ⇒ 红;恢复 ⇒ 绿。
## 编译期抓到的一个坑
`MainPage.ets:1658` 的 AppHeader 在 **`SentTab`** 里,而 `windowInsets` 原本
只声明在 `CommPage` 上。ArkTS 报 `Property 'windowInsets' does not exist on
type 'SentTab'`。⇒ `@StorageLink` 是**每个组件各自**订阅 `AppStorage` 的,
父组件的不会自动传给子组件;直接各自订阅同一把键(比"父传子"少一层)。
## 真机验证
装机后重跑 dumpLayout:发件箱顶栏 `y=134..166`(状态栏底 y=82,间隙 52px),
截图确认「发件箱」完整显示、不再被压。
## 未修(诚实登记)
列表第一行标题被窗格头盖住(`List` 首项 y=294..319 落在窗格头 y=222..320 内),
与顶栏避让**无关**,本次未动。根因待查:`MailRow` 是 `.height(64)` 定高,
而 List 容器从 y=320 起算,首项被画到容器上方。
This commit is contained in:
@ -304,3 +304,58 @@ test('★ 行为(设备):全屏后窗口的避让区读得到(真机口
|
||||
*/
|
||||
assert.equal(fg, ourBundle());
|
||||
});
|
||||
|
||||
/*
|
||||
* ★★ 2026-10-01 真机实测抓到的漏洞(MatePad Pro / MRDI-W00,HarmonyOS NEXT API 26):
|
||||
*
|
||||
* 上一条判据("每个 `@Entry` 页都要消费 windowInsets")在 `MainPage` 上是**绿的**,
|
||||
* 而真机上顶栏被系统状态栏压住了。**为什么绿**:
|
||||
*
|
||||
* `consumes()` 认两种形状 —— `topInset(this.windowInsets)` 或
|
||||
* `this.windowInsets.statusBar`。`MainPage` 的**内容层**确实读了
|
||||
* `this.windowInsets.statusBar`(`top: max(statusBar, windowDecor) + paneGap`),
|
||||
* 于是整页被判为"已消费" ⇒ 绿。
|
||||
*
|
||||
* ★ 但那说的是**内容层**,而 `AppHeader({ topInsetPx: 0 })` 是**另一个消费者**,
|
||||
* 它硬编码 0。页面上有两处顶栏避让的消费者,判据只问"页里有没有出现过那个字段"。
|
||||
*
|
||||
* ⇒ 修法:判据改成**逐个消费者问**,而不是逐页问一次。
|
||||
* 任何传给 `AppHeader` 的 `topInsetPx` 都不得是字面量 0 ——
|
||||
* 要么走 `topInset(this.windowInsets)`,要么显式说明为什么可以给 0。
|
||||
*/
|
||||
test('★★ AppHeader 的 topInsetPx 不得硬编码 0(真机:顶栏被系统状态栏压住)', () => {
|
||||
const pageDir = join(HERE, '..', '..', 'harmony', 'entry', 'src', 'main', 'ets', 'pages');
|
||||
const files = readdirSync(pageDir).filter(f => f.endsWith('.ets'));
|
||||
|
||||
// 自检:这条必须真的扫到东西,否则改名后它会变成"永远绿"的空判据。
|
||||
let seenAppHeader = 0;
|
||||
|
||||
const bad = [];
|
||||
for (const f of files) {
|
||||
// ★ 必须用 `stripped` 而不是 `prose`:判据要锚在**代码**上,
|
||||
// 不是锚在代码对自己的描述上(见本文件 `stripped` 的注释)。
|
||||
// 我第一版用了 `prose`,结果把**我刚写进注释里的**「原先 topInsetPx: 0」
|
||||
// 抓成了红 —— 判据在判自己的注释。(同一类错:注释里那个串不是代码。)
|
||||
const src = stripped(prose(join(pageDir, f)));
|
||||
// 抓所有 `topInsetPx: <表达式>`,逐个看表达式是不是字面量 0。
|
||||
const re = /topInsetPx:\s*([^,\n}]+)/g;
|
||||
let m;
|
||||
while ((m = re.exec(src)) !== null) {
|
||||
seenAppHeader++;
|
||||
const expr = m[1].trim();
|
||||
if (/^0$/.test(expr)) {
|
||||
bad.push(`${f}: topInsetPx: 0 —— 顶栏不避让,会被系统状态栏压住`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert.ok(seenAppHeader >= 4,
|
||||
`自检:要扫到至少 4 处 topInsetPx(实际 ${seenAppHeader} 处)——` +
|
||||
'扫不到说明遍历写错了,这条会变成永远绿的空判据');
|
||||
|
||||
assert.deepEqual(bad, [],
|
||||
'这些页把 AppHeader 的状态栏避让硬编码成 0:\n · ' + bad.join('\n · ') +
|
||||
'\n\n真机实测(MatePad Pro,API 26):EntryAbility 已读到 insets: statusBar=38.588235,' +
|
||||
'\n但 MainPage 的 AppHeader 收到的是 0 ⇒ 顶栏「收件箱/发件箱/授权」压进状态栏。' +
|
||||
'\n修法:传 topInset(this.windowInsets)(见 AdminUsersPage 的写法)。');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user