From 7d081095836a6a462413bb7e3f9e6652646d3ac4 Mon Sep 17 00:00:00 2001 From: dsh Date: Thu, 1 Oct 2026 20:37:19 +0800 Subject: [PATCH] =?UTF-8?q?fix(harmony):=20=E2=98=85=E2=98=85=20=E9=A1=B6?= =?UTF-8?q?=E9=83=A8=E6=96=87=E6=A1=88=E7=94=A8=20windowDecor=20=E5=88=A4?= =?UTF-8?q?=202in1=20=E2=87=92=20=E5=B9=B3=E6=9D=BF=E4=B8=8A=E6=B5=AE?= =?UTF-8?q?=E5=9C=A8=E7=B3=BB=E7=BB=9F=E7=8A=B6=E6=80=81=E6=A0=8F=E4=B8=8A?= =?UTF-8?q?=EF=BC=88=E7=9C=9F=E6=9C=BA=E5=AE=9E=E6=B5=8B=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户:「不是没显示,而是顶部与三键和状态栏重合,我觉得只应该在 2in1」。 ## 根因:一个**假信号** 顶部那行文案(一言 + 签名,`TopbarStore`)的渲染条件写的是 if (this.topbarTexts().length > 0 && this.windowInsets.windowDecor > 0) 本意"有装饰带才显示",在模拟器(真 2in1)上一直成立。**真机平板把它证伪了**: 实测 insets: statusBar=38.588235 navIndicator=27.764706 windowDecor=37 param get const.product.devicetype = tablet `getWindowDecorHeight()` 在平板上**照样返回 37vp** —— 官方给的是"全屏悬浮态 固定 37vp"这个下限值,它衡量的是"系统浮层厚度",**不是"有没有三键区"**。 平板没有三键区,但 windowDecor 恒为 37 ⇒ 条件成立 ⇒ 文案渲染出来, 位置又按"三键在右边"算(`.height(this.windowInsets.windowDecor)`), 于是整行浮在**系统状态栏**上,与时钟/电量重叠。 ## 改法 条件换成形态真值: if (this.topbarTexts().length > 0 && deviceInfo.deviceType === '2in1') `deviceInfo` 取自 `@kit.BasicServicesKit`,与 `EntryAbility.ets:310` 判 2in1 用的是同一个来源 —— 形态判据全仓只此一处口径,不让两处各判一套。 `.height(this.windowInsets.windowDecor)` 保留不动:2in1 上它是对的, 非 2in1 上整块不渲染、根本走不到(`harmony-2in1` 那条仍钉着它)。 ## 判据 `harmony-2in1.test.mjs` 新增一条:顶部文案必须挂在 `deviceType === '2in1'` 上, 且**不许再出现 `windowInsets.windowDecor` 与 0 的比较**(那是假信号)。 红绿已验:退回 `windowDecor > 0` ⇒ 红;恢复 ⇒ 绿。 ★ 这条判据第一版也犯了同类错:用 `prose`(含注释)会把**我自己写进注释里的** 旧写法 `windowDecor > 0` 判红。改用 `lib/read.mjs` 的 `code()`(读时剥注释)。 与上一条(`harmony-window` 判据 10)同形——同一天犯两次,已成惯例性陷阱。 ## 环境 `devecocli` 的 npm 包在本会话中途被卸载(`/usr/bin/devecocli` 成断链), hvigor 直跑缺它注入的 SDK 环境 ⇒ 三个 SDK 路径都报 "SDK component missing"。 已 `npm i -g @deveco/deveco-cli` 装回(6s,252 包),`devecocli build` 恢复可用。 ## 真机验证 装 `entry-default-signed.hap`(26.0.0 Beta2,debug 签名)后截图: 顶部只剩系统状态栏(18:34 / 浏览器 / 信号 / 电量),其下是干净的壁纸带, 再下方才是页签条「收件箱 20 / 发件箱 / 授权1」—— 重叠消失。 ## 回归 `run-all.mjs`:files=35 checks=584 pass=580 fail=2 skip=2 red=4。 与本改动前的基线(checks=583 fail=2 red=3)比,**fail/red 未增加**: 那 2 个 fail 是设备判据并行争用(单独跑全绿),3 个 red 是「静态判据到期」 的既有债务(5 个文件在真机出现后被判到期,本轮未处理)。 --- client/electron/test/harmony-2in1.test.mjs | 40 +++++++++++++++++++ .../entry/src/main/ets/pages/MainPage.ets | 25 ++++++++++-- 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/client/electron/test/harmony-2in1.test.mjs b/client/electron/test/harmony-2in1.test.mjs index b2da8fd..4ace644 100644 --- a/client/electron/test/harmony-2in1.test.mjs +++ b/client/electron/test/harmony-2in1.test.mjs @@ -785,3 +785,43 @@ test('2in1|一言与签名要在客户端缓存(账号级),离线也能 assert.match(STORE, /'\/me\/topbar'/, '要打服务端那个端点(/me/topbar)'); }); + +/* + * ★★ 2026-10-01 真机实测抓到的假判据(MatePad Pro / MRDI-W00,HarmonyOS NEXT API 26)。 + * + * 顶部那行文案(一言 + 签名,`TopbarStore`)的渲染条件原来写的是 + * `this.windowInsets.windowDecor > 0` + * 本意"有装饰带才显示",在模拟器(真 2in1)上一直成立。 + * + * ★ 真机平板把它证伪了 —— 实测日志: + * `insets: statusBar=38.588235 navIndicator=27.764706 windowDecor=37` + * `param get const.product.devicetype` = **tablet** + * + * `getWindowDecorHeight()` 在平板上**照样返回 37vp**(官方给的是"全屏悬浮态 + * 固定 37vp"这个下限值),但平板**根本没有三键区** ⇒ 条件成立、文案渲染, + * 位置却按"三键在右边"算,于是浮在**系统状态栏**上(用户:与时钟/电量重叠)。 + * + * ⇒ `windowDecor > 0` 在平板上是个**恒为真的假信号**,不能当形态判据用。 + * 形态的真值是 `deviceInfo.deviceType === '2in1'`。 + * + * 本条钉的就是这个:**不许再用 windowDecor 的高度/正负当"是不是 2in1"。** + */ +test('★★ 形态判据必须用 deviceInfo.deviceType,不许拿 windowDecor 当 2in1 的信号', () => { + // ★ 用 `code()`(读时剥注释)而不是 `prose`:判据的锚要落在**代码**上, + // 不是落在代码对自己的描述上 —— 否则我写进注释里的旧写法会被自己判红。 + const code_ = code(join(HERE, '..', '..', 'harmony', 'entry', 'src', 'main', 'ets', 'pages', 'MainPage.ets')); + + // 那行文案必须挂在 2in1 判据上。 + const guard = /if \(this\.topbarTexts\(\)\.length > 0 && deviceInfo\.deviceType === '2in1'\)/.test(code_); + assert.ok(guard, + '顶部文案必须只在 2in1 出现:`if (topbarTexts().length > 0 && deviceInfo.deviceType === \'2in1\')`\n' + + '真机实测(MatePad Pro,API 26):windowDecor=37(**非 0**)但 devicetype=tablet,\n' + + '旧条件 windowDecor > 0 在平板上恒成立 ⇒ 文案浮在系统状态栏上。'); + + // 反向:条件里不得再用 windowDecor 的正负/高度判形态。 + const bad = /windowInsets\.windowDecor\s*(>|!=|===|!==|==)\s*0/.exec(code_); + assert.equal(bad, null, + `别再用 ${bad ? bad[0] : 'windowDecor 与 0 比较'} 判「是不是 2in1」——\n` + + '真机平板上它恒为 37(官方"全屏悬浮态固定值"),不代表有三键区。\n' + + '用 deviceInfo.deviceType === \'2in1\'(与 EntryAbility 同一来源)。'); +}); diff --git a/client/harmony/entry/src/main/ets/pages/MainPage.ets b/client/harmony/entry/src/main/ets/pages/MainPage.ets index 92378f3..0abb43e 100644 --- a/client/harmony/entry/src/main/ets/pages/MainPage.ets +++ b/client/harmony/entry/src/main/ets/pages/MainPage.ets @@ -7,6 +7,7 @@ * 已完成,所以入口现在上;日历是整个客户端里**常驻挂载**的那一个 pane(见 build() 里的说明)。 */ import { hilog } from '@kit.PerformanceAnalysisKit'; +import { deviceInfo } from '@kit.BasicServicesKit'; import { ApiClient, ApiError } from '../api/ApiClient'; import { Theme } from '../common/Theme'; import { AppHeader, CompositeModifier, GlassCardModifier, PaneModifier, PressEffectModifier, TintModifier } from '../common/Surface'; @@ -4303,11 +4304,27 @@ struct MainPage { * 而用户要的是自动轮播,不需要手动入口。 */ /* - * 只在**有装饰带**(2in1/PC)且**有内容**时显示: - * 手机形态 windowDecor=0(没有三键、也就没有那条带),此时不该出现 —— - * 否则它会浮在状态栏上,而手机状态栏本来就窄。 + * ★★ 2026-10-01 真机改判据(用户:「顶部与三键和状态栏重合,我觉得只应该在 2in1」)。 + * + * 原来写的是 `this.windowInsets.windowDecor > 0`,本意是"有装饰带才显示", + * 在模拟器(真 2in1)上成立。**真机平板把它证伪了**: + * + * 实测 insets: statusBar=38.588235 navIndicator=27.764706 **windowDecor=37** + * 设备类型 = **tablet**(不是 2in1) + * + * ⇒ `getWindowDecorHeight()` 在平板上**照样返回 37vp**(官方给的是 + * 「全屏悬浮态固定 37vp」这个下限值),但平板**根本没有三键区** —— + * 于是条件成立、文案渲染出来,位置却按"三键在右边"算, + * 结果浮在**系统状态栏**上(用户截图:与时钟/电量重叠)。 + * + * ★ 所以判据必须问「**是不是 2in1 形态**」,而不是「装饰带高不高」: + * 后者在这台设备上是个**恒为 37 的假信号**。 + * `deviceInfo.deviceType` 才是形态的真值(EntryAbility 判 2in1 用的同一个)。 + * + * 与 `EntryAbility.ets` 那句 `deviceInfo.deviceType !== '2in1'` 同源同口径 —— + * 形态判据全仓只用这一个来源,不让两处各判一套。 */ - if (this.topbarTexts().length > 0 && this.windowInsets.windowDecor > 0) { + if (this.topbarTexts().length > 0 && deviceInfo.deviceType === '2in1') { Row() { Text(this.topbarTexts()[this.topIndex % this.topbarTexts().length]) /*