From 60851596949cc9dfd18ad11dcb2c0bf57d5b1b62 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Sun, 20 Sep 2026 13:33:13 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B7=A8=E7=AB=AF:=205=20=E5=A4=84=20Unicode?= =?UTF-8?q?=20=E7=AC=A6=E5=8F=B7=E5=BD=93=E5=9B=BE=E6=A0=87=E6=8D=A2?= =?UTF-8?q?=E6=88=90=20AmIcon=EF=BC=88=E5=B9=B6=E6=8A=8A=E5=88=A4=E6=8D=AE?= =?UTF-8?q?=E6=89=A9=E5=88=B0"=E6=8E=92=E7=89=88=E7=AC=A6=E5=8F=B7"?= =?UTF-8?q?=E8=BF=99=E4=B8=80=E7=B1=BB=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 起因:做邮件详情时顺手把 `Text('‹')` 换成 `AmIcon('chevronLeft')`, 想着"本仓不是有这条判据吗,怎么没抓到" —— 去看了判据,发现它**只扫 emoji 那一段**。 判据(`harmony-arkts.test.mjs:168`)扫的是 U+2600–27BF / U+2B00–2BFF / U+FE0F, 而漏网的 5 处全在这三段**之外**: MainPage.ets Text('‹') 返回键(会话视图) MainPage.ets Text('›') 会话别名前的小箭头 SettingsPage.ets Text('›') 「管理」的进入下一级指示 ComposePage.ets Text(' ▾') 账号下拉指示 MailDetailPage.ets Text('‹') 返回键 ★ 它们与 emoji 那类**问题不同、但同样是"看着像图标其实不是"**: · 字形宽窄由**字体**决定,与旁边 20vp 的 `AmIcon` 对不齐; · 而且**WebUI 用的根本不是字符** —— `icons.tsx:135` 是 `ChevronRightIcon`(SVG)、`AccountSwitcher.tsx:84` 是它**转 90°**。 所以这同时是"两端不一致",不只是"字形不好看"。 修法:5 处一律换 `AmIcon`,并按 WebUI 的**同一形状**给: · 两个返回键 → `chevronLeft` 20vp、可点区 `HEADER_BACK_HIT`(与 `AppHeader` 同值) · 会话别名 / 「管理」→ `chevronRight` · 账号下拉 → `chevronRight` + `rotate(open ? 90 : 0)`(照 `AccountSwitcher` 那句) ★ 判据扩了一类(`GLYPH_ISH`:U+2039/203A/00AB/00BB/25A0–25CF/25B2/25BC/25C0/25B6), 并保持原有那条边界不被破坏:**U+2190–21FF 基本箭头仍不扫** —— `→`/`←` 在正文里是标点,扫进来会误伤大量正常文案(原注释里已经踩过这个坑)。 这次的符号是"单个字符整体",所以 `Text('a → b')` 这类多字符本来也不匹配。 ★ 扩完之后判据**当场又抓到一处我自己没注意的**(`SettingsPage.ets` 的 `›`)—— 这正是"把判据从'枚举实例'扩到'枚举类'立刻多抓一个"的现场证据, 也说明原来那三条区间是照"已经出现过的实例"框的,不是照"这类东西的共同形状"框的。 --- client/electron/test/harmony-arkts.test.mjs | 25 ++++++++++++++++++- .../entry/src/main/ets/pages/ComposePage.ets | 10 +++++++- .../src/main/ets/pages/MailDetailPage.ets | 22 +++++++++++----- .../entry/src/main/ets/pages/MainPage.ets | 14 ++++++++--- .../entry/src/main/ets/pages/SettingsPage.ets | 4 ++- 5 files changed, 62 insertions(+), 13 deletions(-) diff --git a/client/electron/test/harmony-arkts.test.mjs b/client/electron/test/harmony-arkts.test.mjs index 8fe0874..0654246 100644 --- a/client/electron/test/harmony-arkts.test.mjs +++ b/client/electron/test/harmony-arkts.test.mjs @@ -193,6 +193,28 @@ test('★ 图标不许用 Unicode 符号充当(`Text(\'✉\')` 会被系统渲 * `!EMOJI_ISH.test('→')` 不成立 —— 断言写错就是断言写错,不能靠放宽它来「修」。) */ const EMOJI_ISH = /^[\u2600-\u27BF\u2B00-\u2BFF\uFE0F]$/; + /* + * ★★ 2026-09-20 补第二类:**排版符号当图标**(上面那一段只覆盖 emoji 那一类)。 + * + * 实测漏网 4 处:`Text('‹')`(两处返回键)、`Text('›')`(会话别名前的小箭头)、 + * `Text(' ▾')`(账号下拉指示)—— 全都在上面那三个区间**之外**,所以一条都没抓到。 + * + * 它们的问题与 emoji 不同、但同样是"看着像图标其实不是": + * · `‹`/`›` 的字形宽窄**由字体决定**,与旁边 20 的 `AmIcon` 对不齐; + * 而且它们**不是** WebUI 用的东西 —— WebUI 那里是 + * `ChevronLeftIcon` / `ChevronRightIcon`(SVG,见 `icons.tsx:135`), + * 所以这属于"两端不一致",不只是"字形不好看"。 + * · `▾`(U+25BE)同理,WebUI `AccountSwitcher.tsx:84` 用的是 + * 同一个 `ChevronRightIcon` **转 90°**(不是一个专门的"下箭头")。 + * + * 覆盖 U+2039/203A(single/double guillemets)、U+25B2–U+25CF(几何图形: + * ▴▾◂▪● 这一类常被拿来当箭头/圆点)、U+2190–U+21FF 里的**单字符箭头** + * 仍**不**扫(`→`/`←` 在正文里是标点,上面那段注释已经说明为什么不能扫)。 + * ★ 注意 `→` 在 `Text('a → b')` 这种**多条字符**里本来就不匹配 + * (正则要求 1–3 个字符**整体**是个符号),所以这里加 `←→` 也不会误伤; + * 但为了不与上面那段注释自相矛盾,这一段仍然只管"几何/引号类"。 + */ + const GLYPH_ISH = /^[\u2039\u203A\u00AB\u00BB\u25A0-\u25CF\u25B2\u25BC\u25C0\u25B6]$/; const hits = []; const walkDir = (dir) => { for (const e of readdirSync(dir, { withFileTypes: true })) { @@ -202,7 +224,8 @@ test('★ 图标不许用 Unicode 符号充当(`Text(\'✉\')` 会被系统渲 const src = code(full); for (const m of src.matchAll(/Text\(\s*'([^']{1,3})'\s*\)/g)) { const ch = m[1]; - if (EMOJI_ISH.test(ch)) { + const trimmed = ch.trim(); + if (EMOJI_ISH.test(ch) || GLYPH_ISH.test(trimmed)) { hits.push(`${e.name}: Text('${ch}')(U+${ch.codePointAt(0).toString(16).toUpperCase()})`); } } diff --git a/client/harmony/entry/src/main/ets/pages/ComposePage.ets b/client/harmony/entry/src/main/ets/pages/ComposePage.ets index ddb9c93..7d02f1f 100644 --- a/client/harmony/entry/src/main/ets/pages/ComposePage.ets +++ b/client/harmony/entry/src/main/ets/pages/ComposePage.ets @@ -309,7 +309,15 @@ export struct ComposeView { .fontSize(13).fontColor(Theme.accentFor()) .layoutWeight(1) .onClick(() => { this.showAccountPicker = !this.showAccountPicker; }) - Text(' ▾').fontSize(12).fontColor(Theme.textSubtleFor()) + /* + * 下拉指示 —— 与 WebUI `AccountSwitcher.tsx:84` 逐项对齐: + * + * 即**同一个右尖括号图标,展开时转 90°**(不是一个专门的"下箭头")。 + * 原来这里写的是 `Text(' ▾')` —— 一个 Unicode 字符(本仓禁用当图标)。 + */ + AmIcon({ iconName: 'chevronRight', iconSize: 12, iconColor: Theme.textSubtleFor() }) + .rotate({ angle: this.showAccountPicker ? 90 : 0 }) .onClick(() => { this.showAccountPicker = !this.showAccountPicker; }) } .width('100%').height(40).padding({ left: 12, right: 12 }) diff --git a/client/harmony/entry/src/main/ets/pages/MailDetailPage.ets b/client/harmony/entry/src/main/ets/pages/MailDetailPage.ets index 4bffde5..2b74b7f 100644 --- a/client/harmony/entry/src/main/ets/pages/MailDetailPage.ets +++ b/client/harmony/entry/src/main/ets/pages/MailDetailPage.ets @@ -16,7 +16,7 @@ import { MailDetail, SendMailRequest, ForwardMailRequest, Address } from '../mod import { MailDetailParams } from '../model/RouteParams'; import { AmIcon } from '../common/Icons'; import { permissionLabel } from '../model/MailGrouping'; -import { LIST_FADE_LENGTH } from '../model/NavItems'; +import { LIST_FADE_LENGTH, HEADER_BACK_HIT } from '../model/NavItems'; import { LengthMetrics } from '@kit.ArkUI'; import { participantAddress, @@ -651,11 +651,21 @@ export struct MailDetailView { */ Column() { Row() { - Text('‹') - .fontSize(24).fontColor(Theme.accentFor()) - .width(36).height(36) - .textAlign(TextAlign.Center) - .onClick(() => { this.goBack(); }) + /* + * 返回键 —— **用图标,不用 `‹` 这个 Unicode 字符**。 + * + * ★ 本仓明令禁止把 Unicode 符号当图标(渲染依赖字体、各家字形宽窄不一, + * 还会被部分主题替换成 emoji 字形)。全仓已统一走 `AmIcon` + `ICON_PATHS`, + * 这里是**最后一处漏网**(其它页面 2026-09-19 那批已经换过)。 + * + * 尺寸按 `NAV_ITEM_MIN_HIT` 的口径给足 44vp 可点区 —— + * `AppHeader` 的返回键就是这个尺寸,两处要对齐。 + */ + Stack({ alignContent: Alignment.Center }) { + AmIcon({ iconName: 'chevronLeft', iconSize: 20, iconColor: Theme.accentFor() }) + } + .width(HEADER_BACK_HIT).height(HEADER_BACK_HIT) + .onClick(() => { this.goBack(); }) Row() { Text(this.subject) diff --git a/client/harmony/entry/src/main/ets/pages/MainPage.ets b/client/harmony/entry/src/main/ets/pages/MainPage.ets index 2d7cfb9..1d8c2fc 100644 --- a/client/harmony/entry/src/main/ets/pages/MainPage.ets +++ b/client/harmony/entry/src/main/ets/pages/MainPage.ets @@ -88,6 +88,7 @@ import { NAV_CONTENT_RESERVE, NAV_ITEM_MIN_HIT, NAV_ITEMS, + HEADER_BACK_HIT, TAB_BAR_RADIUS, TAB_BAR_SIDE, TAB_BAR_TOP, @@ -2198,9 +2199,12 @@ struct ContactsTab { Column() { /* 返回行:与 WebUI 的 `BackButton label="会话"` 同一语义 */ Row() { - Text('‹').fontSize(24).fontColor(Theme.accentFor()).width(36).height(36) - .textAlign(TextAlign.Center) - .onClick(() => { this.closeSession(); }) + /* 返回键用图标,不用 `‹`(本仓禁 Unicode 符号当图标 —— 见 Icons.ets) */ + Stack({ alignContent: Alignment.Center }) { + AmIcon({ iconName: 'chevronLeft', iconSize: 20, iconColor: Theme.accentFor() }) + } + .width(HEADER_BACK_HIT).height(HEADER_BACK_HIT) + .onClick(() => { this.closeSession(); }) Text(this.openSessionTitle) .fontSize(14).fontWeight(FontWeight.Bold).fontColor(Theme.textPrimary) .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }) @@ -2292,7 +2296,9 @@ struct ContactsTab { .width('100%') Row() { - Text('›').fontSize(12).fontColor(Theme.accentFor()) + /* WebUI `ContactPanel.tsx:247` 这里是 `` + —— 一个 SVG 图标,不是 `›` 这个字符。逐项对齐。 */ + AmIcon({ iconName: 'chevronRight', iconSize: 12, iconColor: Theme.accentFor() }) Text(c.session_alias.length > 0 ? c.session_alias : '(未命名会话)') .fontSize(11).fontColor(Theme.accentFor()) .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }) diff --git a/client/harmony/entry/src/main/ets/pages/SettingsPage.ets b/client/harmony/entry/src/main/ets/pages/SettingsPage.ets index 5810634..2d7f140 100644 --- a/client/harmony/entry/src/main/ets/pages/SettingsPage.ets +++ b/client/harmony/entry/src/main/ets/pages/SettingsPage.ets @@ -1188,7 +1188,9 @@ export struct SettingsPane { Row() { Text('管理').fontSize(Theme.fontBody).fontColor(Theme.textPrimary) .layoutWeight(1) - Text('›').fontSize(Theme.fontBody).fontColor(Theme.textSubtleFor()) + /* 「进入下一级」指示:用图标不用 `›`(本仓禁 Unicode 符号当图标; + 口径与 `MailList`/`ContactPanel` 的 chevron 一致) */ + AmIcon({ iconName: 'chevronRight', iconSize: 14, iconColor: Theme.textSubtleFor() }) } .width('100%') Text('用户管理(新建 / 编辑 / 启停 / 重置密码)')