From 8267102cd663e2a3dd05c0490c02efa6204cc1db Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Tue, 22 Sep 2026 08:58:56 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B7=A8=E7=AB=AF:=20=E4=BF=AE=E3=80=8C?= =?UTF-8?q?=E6=88=91=E7=9A=84=E3=80=8D=E9=A1=B5=E6=98=BE=E5=BE=97=E9=9D=9E?= =?UTF-8?q?=E5=B8=B8=E6=8C=A4=EF=BC=88=E4=B8=89=E5=A4=84=E7=9C=9F=E5=9B=A0?= =?UTF-8?q?=EF=BC=89+=20=E9=A1=BA=E6=89=8B=E6=B8=85=E6=8E=89=203=20?= =?UTF-8?q?=E6=9D=A1=E5=B7=B2=E4=BF=AE=E7=9A=84=E8=87=AA=E6=9C=89=20ArkTS?= =?UTF-8?q?=20=E5=91=8A=E8=AD=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户:「「我的」页面显得非常挤」 先量再改。取了宽屏(3184px)与窄屏(1008px)两态,用 `dumpLayout` 拿真实 bounds, 反推 density=2.875。三个真因,**宽度是主因**。 ## ① 内容列没有宽度上限(主因) WebUI `AccountPage.tsx:104`:
`max-w-3xl` = **48rem = 768px** + `mx-auto` 居中。 鸿蒙这边原来只有 `.width('100%')` —— **没有任何上限**。 后果(实测):宽屏下卡片铺满 **2923px(1376vp)**,而里面全是 "标签 + 短值"的两列行,文字只占左边一小块 ⇒ 右边一大片留白, 整页读起来像一条被拉长的表格。这正是"挤"的观感来源 —— 不是行距小(实测行距 30vp / 字 14vp,其实偏松),是**横向没有收拢**。 修后实测:内容列 **2208px = 768.0vp**(与目标逐位吻合), 中心 1692.0 vs 父容器 1692.5 ⇒ 居中生效。 ★ 形状:`Scroll > Row(居中容器)> Column(带上限)`。 **不能把居中挂在 `Scroll` 上** —— 官方 `ScrollAttribute` 没有 `.justifyContent()`。我第一版就是直接给 Scroll 挂,那是个编出来的属性。 ArkUI 没有 `mx-auto` 的直接对应物,"居中"必须靠一个容器节点。 ★ 窄屏(1008px = 350vp < 768)下约束**自动不生效**,逐像素确认窄屏形态未变。 ## ② 段间距只有 WebUI 的 1/3 WebUI `space-y-6` = **24px**;我们每张卡各写 `margin({ top: 8 })`(7 处) 与 `margin({ top: 10 })`(1 处)—— 既不统一,也贴得太近, 卡片挨在一起时"段"的边界看不出来,整页就是一大块密集表单。 ⇒ 收成一个 `SECTION_GAP = 24` 常量,8 处全部改用它。 实测卡片间实测 **69px = 24.0vp** ✓ ## ③ `SectionTitle()` 定义了却只调用一次 WebUI `AccountPage` 有 **5 个 `

`** 段标题(基本资料 / 权限范围 / 修改密码 / 登录状态 / 管理),鸿蒙这边只有 **1 个**('权限范围')—— 基本资料那六行是裸放在卡片里的,与下面的权限范围只靠一条 `Divider` 硬分。 ⇒ 补「基本资料」标题。没有标题就没有"这是另一段"的语义,只能靠线。 ## ④ 顺手清掉 3 条自有 ArkTS 告警(37 → 34) 上一条提交我说"`fill` 那 2 处留给下一轮"—— 不该推,这轮做完: - **2× `fill` API 是 SDK 26.0.0**(`Circle().fill()`,在 WideSidebar 连接点 与 InboxPage/MainPage 未读点)。查 SDK 头文件确认不是误报: `circle.d.ts` 的 `CircleAttribute.fill()` 标 **@since 26.0.0**, 而基类 `CommonShapeMethod.fill()` 才是 @since 11 —— 子类重载**遮蔽**了它。 设备实测点**确实渲染**(那是碰巧兼容),但契约上它比编译目标(23)新。 ⇒ 换成与 `CalendarPage` 小圆点同一形状(普通容器 + width/height + borderRadius), 只用 API 11 起的通用属性。 - **1× `'packing' has been deprecated`** → `packToData`(SDK 明确给了 `@useinstead image.ImagePacker#packToData`;两者签名逐字相同: `(PixelMap, PackingOption) => Promise`,只改名字)。 - **1× `This API is unavailable to 2in1`** → 加 `deviceInfo.deviceType !== '2in1'` 守卫。SDK 原文:「From API version 12, this API does not take effect on 2-in-1 devices.」告警仍在(静态检查不看运行时分支),但行为已正确分流 —— 留着这条注释说明为什么不断言消失。 ★ 这条告警的噪声价值:同一批里就藏着 `fill` 那个真隐患。 逐条筛之前,它只是"每次重编都出现的一行字"。 ## 验证 ✓ 宽屏:内容列 768.0vp、居中、段间距 24.0vp(`dumpLayout` 实测 bounds) ✓ 窄屏:约束不生效、形态与改前一致(像素对照) ✓ 自有 ArkTS 告警 37 → 34(`fill`×2 与 `packing` 消失) ✓ 编译通过、设备安装后进程存活 --- .../src/main/ets/common/BackgroundPicker.ets | 14 ++- .../main/ets/entryability/EntryAbility.ets | 25 +++++- .../entry/src/main/ets/pages/InboxPage.ets | 25 +++++- .../entry/src/main/ets/pages/MainPage.ets | 9 +- .../entry/src/main/ets/pages/SettingsPage.ets | 89 ++++++++++++++++--- .../entry/src/main/ets/pages/WideSidebar.ets | 14 ++- 6 files changed, 155 insertions(+), 21 deletions(-) diff --git a/client/harmony/entry/src/main/ets/common/BackgroundPicker.ets b/client/harmony/entry/src/main/ets/common/BackgroundPicker.ets index 561e4d1..5918845 100644 --- a/client/harmony/entry/src/main/ets/common/BackgroundPicker.ets +++ b/client/harmony/entry/src/main/ets/common/BackgroundPicker.ets @@ -406,7 +406,19 @@ async function packJpeg(pm: image.PixelMap, quality: number): Promise + * packToData(pm: PixelMap, options: PackingOption): Promise + * ⇒ 改名字就行,返回值/异常语义/调用方全部不变。 + */ + return await packer.packToData(pm, opt); } finally { // ★ `release()` 也是 `Promise`(SDK 两个重载:callback 版与 Promise 版)—— // 第 22 条判据把这一处抓出来了:不 await 的话它是一个"没人管的 promise", diff --git a/client/harmony/entry/src/main/ets/entryability/EntryAbility.ets b/client/harmony/entry/src/main/ets/entryability/EntryAbility.ets index c8e66a0..f405617 100644 --- a/client/harmony/entry/src/main/ets/entryability/EntryAbility.ets +++ b/client/harmony/entry/src/main/ets/entryability/EntryAbility.ets @@ -29,7 +29,7 @@ import { hilog } from '@kit.PerformanceAnalysisKit'; * 两个同名枚举撞在一起,是本条最费时的一步。 */ import { KeyboardAvoidMode, window } from '@kit.ArkUI'; -import { BusinessError } from '@kit.BasicServicesKit'; +import { BusinessError, deviceInfo } from '@kit.BasicServicesKit'; import { ApiClient } from '../api/ApiClient'; import { PushService } from '../api/PushService'; import { NotificationLedger } from '../model/PushContract'; @@ -225,10 +225,27 @@ export default class EntryAbility extends UIAbility { * 下面这条 `setWindowSystemBarEnable(['status'])` 是上一位留下的: * 它对状态栏仍有效,且留着不会更糟 —— 但它**不是**消黑边的手段 * (实测没能消掉底部那 60px + 20px)。一并保留,不再靠它。 + * + * ★★ 2026-09-21 加设备类型守卫(编译器告警:`This API is unavailable to 2in1.`)。 + * + * 查了 SDK 头文件确认不是误报。`api/@ohos.window.d.ts` 上该方法的注释原文: + * * From API version 12, + * this API does not take effect on 2-in-1 devices. + * ⇒ 在 2in1(PC/平板键盘态)上**从 API 12 起就不生效**,而我们是 23。 + * + * ── 为什么不是"反正有 catch,不管了" ── + * 当前它只是"发个不生效的请求"(catch 收个错、日志里多一行), + * 危害确实小。但这条告警会在每次完整重编时**重复出现**, + * 而告警列表里混着真实隐患(同一批里就有 `fill` 那个 SDK 26 的 API)⇒ + * 噪声会把真信号淹死。今天正是先 `touch` 重编、再逐条筛,才把 `fill` 挑出来。 + * + * ⇒ 按设备类型分流:2in1 上直接不调,其余平台行为一字不变。 */ - win.setWindowSystemBarEnable(['status']).catch((err: BusinessError) => { - hilog.error(DOMAIN, 'testTag', 'setWindowSystemBarEnable failed: %{public}s', JSON.stringify(err)); - }); + if (deviceInfo.deviceType !== '2in1') { + win.setWindowSystemBarEnable(['status']).catch((err: BusinessError) => { + hilog.error(DOMAIN, 'testTag', 'setWindowSystemBarEnable failed: %{public}s', JSON.stringify(err)); + }); + } /* ── ② 读避让 + 监听变化 ── */ this.publishInsets(win); try { diff --git a/client/harmony/entry/src/main/ets/pages/InboxPage.ets b/client/harmony/entry/src/main/ets/pages/InboxPage.ets index 718490b..eac5f4c 100644 --- a/client/harmony/entry/src/main/ets/pages/InboxPage.ets +++ b/client/harmony/entry/src/main/ets/pages/InboxPage.ets @@ -193,7 +193,30 @@ struct InboxPage { // 未读圆点 Column() { if (mail.status === 'unread') { - Circle({ width: 8, height: 8 }).fill(Theme.accent) + /* + * ★★ 2026-09-21 修:`Circle().fill()` → 普通容器 + 圆角。 + * + * 编译器告警(自有源码 2 条中的一条): + * The 'fill' API is supported since SDK version 26.0.0. + * However, the current compatible SDK version is 6.1.0(23). + * + * 查了 SDK 头文件确认这不是误报: + * component/circle.d.ts 的 `CircleAttribute.fill()` 标着 @since 26.0.0 + * 而基类 `CommonShapeMethod.fill()` 才是 @since 11。 + * 子类重载**遮蔽**了基类那个 ⇒ 当前编译目标(23)下它其实是**未来 API**。 + * + * 设备实测(宽屏 3184)黄点/蓝点**确实渲染出来了** —— + * 所以现在无害。但那是"碰巧实现里兼容",不是契约: + * 升 SDK 或换个渲染后端就可能变静默不出点。 + * + * ⇒ 换成与 `CalendarPage` 小圆点**同一形状**(本仓已有): + * 普通容器 + width/height + borderRadius = 半径 + * 只用 API 11 起的通用属性,不碰 `fill`。 + */ + Column() + .width(8).height(8) + .borderRadius(4) + .backgroundColor(Theme.accent) } } .width(20).height('100%') diff --git a/client/harmony/entry/src/main/ets/pages/MainPage.ets b/client/harmony/entry/src/main/ets/pages/MainPage.ets index a487866..80d6b3c 100644 --- a/client/harmony/entry/src/main/ets/pages/MainPage.ets +++ b/client/harmony/entry/src/main/ets/pages/MainPage.ets @@ -1008,7 +1008,14 @@ struct InboxTab { Row() { Column() { if (mail.status === 'unread') { - Circle({ width: 8, height: 8 }).fill(Theme.accent) + /* + * ★★ 2026-09-21 修:`Circle().fill()` 是 SDK 26 的 API + * (见 `InboxPage.MailItem` 那段完整说明)。换成通用容器画点。 + */ + Column() + .width(8).height(8) + .borderRadius(4) + .backgroundColor(Theme.accent) } } .width(20).height('100%').justifyContent(FlexAlign.Center) diff --git a/client/harmony/entry/src/main/ets/pages/SettingsPage.ets b/client/harmony/entry/src/main/ets/pages/SettingsPage.ets index c542f33..0f7a2a7 100644 --- a/client/harmony/entry/src/main/ets/pages/SettingsPage.ets +++ b/client/harmony/entry/src/main/ets/pages/SettingsPage.ets @@ -32,6 +32,37 @@ import { PushService } from '../api/PushService'; import { performLogout } from '../api/Logout'; import { PushTokenList } from '../model/PushContract'; +/** + * 内容列宽度上限(vp)—— 对齐 WebUI `AccountPage.tsx:104` 的 `max-w-3xl`。 + * + * tailwind 的 `max-w-3xl` = `48rem` = **768px**。 + * + * ★★ 2026-09-21 新增(用户:「「我的」页面显得非常挤」): + * 没有这个上限时,宽屏(3184px)下卡片铺满整个窗格, + * 而里面全是"标签 + 短值"的两列行 —— 文字只占左边一小块, + * 右边大片留白,整体读起来像一条被拉长的表格。 + * + * 为什么是 768 而不是"窗格宽度的百分之多少": + * WebUI 是固定值(响应式只改左右 padding),我们跟它一致; + * 按比例算会让"同一张卡在宽屏上又变宽",就治不了这个病。 + */ +const CONTENT_MAX_WIDTH: number = 768; + +/** + * 段与段之间的竖向间距(vp)—— 对齐 WebUI `AccountPage.tsx:104` 的 `space-y-6`。 + * + * tailwind `space-y-6` = **24px**。 + * + * ★★ 2026-09-21 新增(用户:「「我的」页面显得非常挤」): + * 原来每张卡各自写 `margin({ top: 8 })` / `({ top: 10 })` —— + * 散在 8 个 `@Builder` 的尾部,既不统一(8 与 10 混用) + * 也只有 WebUI 的 **1/3**。卡片贴得太近时,"段"的边界看不出来, + * 整页读起来就是一大块密集表单。 + * + * 收成一个常量:下次改间距只改这一处,不会再出现 6 个 8、1 个 10 的散开状态。 + */ +const SECTION_GAP: number = 24; + @Component export struct SettingsPane { @@ -632,9 +663,28 @@ export struct SettingsPane { * 没有任何办法滚到」。账号越多 / 屏越矮时越明显,而「退出登录」正好在最下面。 */ Scroll() { - Column() { - /* 基本资料 / 权限范围(对齐 WebUI 的前两个 section) */ - this.ProfileSection() + /* + * ★★ 2026-09-21 修(用户:「「我的」页面显得非常挤」)—— + * + * 原来内容列只有 `.width('100%')`:**没有任何宽度上限**。 + * 宽屏(3184px)下卡片直接铺满 ~1900px,而所有文字内容只有 ~200px 宽 + * 全挤在左边 —— 右边一大片空白,整体看着像"被拉长的一条"。 + * + * WebUI `AccountPage.tsx:104` 是: + *
+ * `max-w-3xl` = **48rem = 768px**,`mx-auto` 居中。 + * + * ⇒ 形状:`Scroll > Row(居中容器)> Column(带上限)`。 + * + * ★ 为什么不把居中写在 `Scroll` 上:**`Scroll` 没有 `.justifyContent()`** + * (官方 `ScrollAttribute` 里没有这个属性)。 + * 我第一版就是直接给 Scroll 挂 `justifyContent` —— 那是个编出来的属性。 + * ArkUI 没有 `mx-auto` 的直接对应物,"居中"必须靠一个容器节点。 + */ + Row() { + Column() { + /* 基本资料 / 权限范围(对齐 WebUI 的前两个 section) */ + this.ProfileSection() /* 修改密码 */ this.PasswordSection() @@ -673,7 +723,7 @@ export struct SettingsPane { } } .width('100%').alignItems(HorizontalAlign.Start) - .padding(16).margin({ top: 8 }) + .padding(16).margin({ top: SECTION_GAP }) .attributeModifier(GlassCardModifier.of(this.bgActive)) .borderRadius(Theme.radiusCard) @@ -699,8 +749,12 @@ export struct SettingsPane { this.AdminSection() Blank().height(24) + } + .constraintSize({ maxWidth: CONTENT_MAX_WIDTH }) + .width('100%') } .width('100%') + .justifyContent(FlexAlign.Center) } .width('100%').layoutWeight(1) .scrollBar(BarState.Off) @@ -827,6 +881,19 @@ export struct SettingsPane { if (this.profile === null) { Text('加载中…').fontSize(13).fontColor(Theme.textMuted).width('100%') } else { + /* + * ★★ 2026-09-21 补:「基本资料」这个**段标题**。 + * + * WebUI `AccountPage.tsx:107` 有 `

基本资料

`,而鸿蒙这边缺 —— + * `SectionTitle()` 定义了却**只被调用一次**('权限范围'), + * 于是基本资料那六行直接裸放在卡片里,与下面的「权限范围」 + * 靠一条 `Divider` 硬分 —— 没有标题就没有"这是另一段"的语义。 + * + * 用户说的「显得非常挤」,宽度是主因,这一条是次因: + * 段落边界只能靠线,读起来就是一整块。 + * WebUI 全文 5 个 h3,我们只有 1 个。 + */ + this.SectionTitle('基本资料') this.InfoRow('用户名', this.profile.username) this.InfoRow('显示名', this.profile.display_name) this.InfoRow('角色', this.profile.role === 'admin' ? '管理员' : '普通用户') @@ -854,7 +921,7 @@ export struct SettingsPane { } } .width('100%').alignItems(HorizontalAlign.Start) - .padding(16).margin({ top: 10 }) + .padding(16).margin({ top: SECTION_GAP }) .attributeModifier(GlassCardModifier.of(this.bgActive)) .borderRadius(Theme.radiusCard) } @@ -925,7 +992,7 @@ export struct SettingsPane { }) } .width('100%').alignItems(HorizontalAlign.Start) - .padding(16).margin({ top: 8 }) + .padding(16).margin({ top: SECTION_GAP }) .attributeModifier(GlassCardModifier.of(this.bgActive)) .borderRadius(Theme.radiusCard) } @@ -962,7 +1029,7 @@ export struct SettingsPane { .fontSize(11).fontColor(Theme.textSubtleFor()).margin({ top: 6 }) } .width('100%').alignItems(HorizontalAlign.Start) - .padding(16).margin({ top: 8 }) + .padding(16).margin({ top: SECTION_GAP }) .attributeModifier(GlassCardModifier.of(this.bgActive)) .borderRadius(Theme.radiusCard) } @@ -1047,7 +1114,7 @@ export struct SettingsPane { } } .width('100%').alignItems(HorizontalAlign.Start) - .padding(16).margin({ top: 8 }) + .padding(16).margin({ top: SECTION_GAP }) .attributeModifier(GlassCardModifier.of(this.bgActive)) .borderRadius(Theme.radiusCard) } @@ -1172,7 +1239,7 @@ export struct SettingsPane { } .width('100%').alignItems(HorizontalAlign.Start) - .padding(16).margin({ top: 8 }) + .padding(16).margin({ top: SECTION_GAP }) .attributeModifier(GlassCardModifier.of(this.bgActive)) .borderRadius(Theme.radiusCard) } @@ -1205,7 +1272,7 @@ export struct SettingsPane { .onClick(() => { this.doLogout(); }) } .width('100%').alignItems(HorizontalAlign.Start) - .padding(16).margin({ top: 8 }) + .padding(16).margin({ top: SECTION_GAP }) .attributeModifier(GlassCardModifier.of(this.bgActive)) .borderRadius(Theme.radiusCard) } @@ -1234,7 +1301,7 @@ export struct SettingsPane { .width('100%').margin({ top: 4 }) } .width('100%').alignItems(HorizontalAlign.Start) - .padding(16).margin({ top: 8 }) + .padding(16).margin({ top: SECTION_GAP }) .attributeModifier(GlassCardModifier.of(this.bgActive)) .borderRadius(Theme.radiusCard) .attributeModifier(PressEffectModifier.of()) diff --git a/client/harmony/entry/src/main/ets/pages/WideSidebar.ets b/client/harmony/entry/src/main/ets/pages/WideSidebar.ets index 0f44185..8067db2 100644 --- a/client/harmony/entry/src/main/ets/pages/WideSidebar.ets +++ b/client/harmony/entry/src/main/ets/pages/WideSidebar.ets @@ -253,9 +253,17 @@ export struct WideSidebar { .borderRadius(18) .backgroundColor(this.currentIndex === ME_PANE_INDEX ? Theme.navActiveBg : Theme.border) .textAlign(TextAlign.Center) - /* 连接状态点:不遮挡文字,贴在右下角(WebUI `-bottom-0.5 -right-0.5`) */ - Circle({ width: 8, height: 8 }) - .fill(this.sseColorOf(this.sseStatus)) + /* + * 连接状态点:不遮挡文字,贴在右下角(WebUI `-bottom-0.5 -right-0.5`)。 + * + * ★★ 2026-09-21 修:`Circle().fill()` 是 SDK 26 的 API + * (`circle.d.ts` 的 `fill` 标 @since 26.0.0,遮蔽了基类 API 11 那个)。 + * 实测能渲染,但那是碰巧 —— 换通用容器画,只用 API 11 起的属性。 + */ + Column() + .width(8).height(8) + .borderRadius(4) + .backgroundColor(this.sseColorOf(this.sseStatus)) .margin({ right: 1, bottom: 1 }) } .width(40).height(40)