diff --git a/client/electron/test/harmony-nav.test.mjs b/client/electron/test/harmony-nav.test.mjs index 8d5a05d..e66cfa1 100644 --- a/client/electron/test/harmony-nav.test.mjs +++ b/client/electron/test/harmony-nav.test.mjs @@ -505,3 +505,35 @@ test('★ 滚动列表上下边缘渐隐:所有列表容器都挂 fadingEdge assert.match(main, /import\s*\{[^}]*LengthMetrics[^}]*\}\s*from\s*'@kit\.ArkUI'/, 'MainPage 要从 @kit.ArkUI import LengthMetrics'); }); + +/* + * ★ 悬浮加号必须待在 Navigation 的**列表侧**,不能当 Navigation 的兄弟。 + * + * 症状(2026-09-17 模拟器截图硬证):窄屏 Stack 模式下点开邮件详情,右下角 + * **同时**出现两个圆形按钮 —— 详情自己的「回复」(蓝色胶囊)与外壳的 + * compose 加号,后者悬空压在详情上。 + * + * 根因:加号原先是 `Navigation` 的**兄弟**,平级放在外层 Stack 里。 + * 详情画在 `Navigation` 内部,而兄弟节点画在 `Navigation` 之上 ⇒ 永远压住详情。 + * + * WebUI 的对应物是 `.comm-pane`(`App.tsx`):`{listBody}` —— + * 加号在**列表窗格内部**,窄屏滑上来的详情层把列表整块(含加号)盖住, + * 详情自己的回复按钮才露得出来。Split 模式下加号仍在左栏,与 WebUI 两栏并排一致。 + * + * 这条判据盯的是**层级**(在 Navigation 内部),不是"存在一个 compose 按钮" —— + * 后者在 bug 版本里也成立(所以它才漏过)。 + */ +test('★ 悬浮加号在 Navigation 内部(列表侧),不能当 Navigation 的兄弟压在详情上', () => { + const body = braceBody(main, 'Navigation(this.navPathStack)'); + assert.ok(body.length > 200, '要能取到 Navigation 的构建体'); + assert.match(body, /iconName:\s*'compose'/, + '★ compose 悬浮加号要在 `Navigation(this.navPathStack) { ... }` 的**构建体内**。' + + '放在外面(兄弟位置)会在窄屏 Stack 详情页上悬空压住详情自己的「回复」按钮 —— ' + + '2026-09-17 模拟器截图硬证。'); + assert.match(body, /borderRadius\(28\)/, '加号仍是 56 圆(直径 56 ⇒ 半径 28)'); + + // 反向:Navigation 之后(`.navDestination(...)` 那一段)不该再冒出 compose 按钮 + const afterNav = main.slice(main.indexOf('.navDestination(this.DestinationBuilder)')); + assert.ok(!/iconName:\s*'compose'/.test(afterNav), + '★ `.navDestination(...)` 之后不该再有 compose 加号 —— 那等于又放回了兄弟位置'); +}); diff --git a/client/harmony/entry/src/main/ets/pages/MainPage.ets b/client/harmony/entry/src/main/ets/pages/MainPage.ets index b84171f..840c746 100644 --- a/client/harmony/entry/src/main/ets/pages/MainPage.ets +++ b/client/harmony/entry/src/main/ets/pages/MainPage.ets @@ -1225,8 +1225,21 @@ struct CommPage { } build() { - Stack({ alignContent: Alignment.BottomEnd }) { - Navigation(this.navPathStack) { + Navigation(this.navPathStack) { + /* + * ★ 悬浮加号必须待在 **导航栏内容(列表侧)内部**,不能当 `Navigation` 的兄弟。 + * + * 原来它和 `Navigation` 平级放在外层 Stack 里 —— 窄屏 Stack 模式 push 详情时, + * 详情是画在 `Navigation` **里面**的,而外层那个加号画在 `Navigation` **之上**, + * 于是详情页右下多出一个悬空的 compose 圆,正好压在详情自己的「回复」按钮上 + * (2026-09-17 模拟器截图硬证)。 + * + * WebUI 的对应物是 `.comm-pane`:加号挂在**列表窗格内部** + * (`App.tsx` 的 `{listBody}`),所以窄屏滑上来的详情层 + * 把列表整块(含加号)盖住 —— 详情自己的回复按钮才露得出来。 + * Split 模式下加号仍留在左栏(与 WebUI 的两栏并排一致)。 + */ + Stack({ alignContent: Alignment.BottomEnd }) { Column() { this.CommTabBar() @@ -1245,31 +1258,35 @@ struct CommPage { } } .width('100%').height('100%') - } - .navDestination(this.DestinationBuilder) - .mode(NavigationMode.Auto) - /* WebUI 邮件列表栏固定约 320px;Auto 的断点 = 列表最小宽 + 详情最小宽。 */ - .navBarWidth(320) - .navBarWidthRange([280, 360]) - .minContentWidth(360) - .hideTitleBar(true) - .width('100%').height('100%') - /* - * 悬浮的圆形加号(用户点名要的形状):56 圆、品牌色、右下角。 - * 放在通信页这一层,所以三个栏里都在 —— 新建邮件这件事不挑栏。 - */ - Button() { - AmIcon({ iconName: 'compose', iconSize: 24, iconColor: Theme.surface }) + /* + * 悬浮的圆形加号(用户点名要的形状):56 圆、品牌色、右下角。 + * 三个栏里都在(新建邮件这件事不挑栏),但**只在列表窗格内**。 + */ + Button() { + AmIcon({ iconName: 'compose', iconSize: 24, iconColor: Theme.surface }) + } + .width(56).height(56) + .borderRadius(28) + .backgroundColor(Theme.accent) + .margin({ right: 16, bottom: 16 }) + .onClick(() => { this.openCompose(); }) } - .width(56).height(56) - .borderRadius(28) - .backgroundColor(Theme.accent) - .margin({ right: 16, bottom: 16 }) - .onClick(() => { this.openCompose(); }) + .width('100%').height('100%') + /* + * 背景开关仍在这一层(它是通信页的根):卡不透明、容器透明, + * 壁纸才从卡片间与顶/底边缘透出来(与 WebUI 的 `.comm-pane` 同构)。 + */ + .backgroundColor(this.bgActive ? Color.Transparent : Theme.pageBg) } + .navDestination(this.DestinationBuilder) + .mode(NavigationMode.Auto) + /* WebUI 邮件列表栏固定约 320px;Auto 的断点 = 列表最小宽 + 详情最小宽。 */ + .navBarWidth(320) + .navBarWidthRange([280, 360]) + .minContentWidth(360) + .hideTitleBar(true) .width('100%').height('100%') - .backgroundColor(this.bgActive ? Color.Transparent : Theme.pageBg) } }