跨端: 登录页那个"emoji"是 Unicode 符号 —— 顺手修好图标贴左上角(共 4 处)
用户两条反馈,都是**看着界面**报出来的,而编译器与所有既有判据全绿:
①「为什么登陆页不是app图标,而是一个emojy?」
②「你自己看看那个图标的位置正常吗?」
## ① `Text('✉')` 被系统渲染成彩色 emoji
登录页的品牌标识原先写的是 `Text('✉')`(Unicode U+2709)。HarmonyOS 字体链里有
**彩色 emoji 字体**,U+2709 自带 emoji 字形 ⇒ 渲染成一枚黄白色风信封 emoji:
- `.fontColor(Theme.accent)` 对彩色 emoji **无效**(界面显示的是 emoji 自带颜色);
- 与底栏/侧栏那些 `AmIcon` 线描图标不是同一套视觉语言;
- 实测截图硬证:大屏下那枚 emoji 比旁边的文字还显眼。
而本仓**早就有** `ICON_PATHS.brandMark`(就是 App 图标上那个信封,专为品牌标识画的)。
换成 `AmIcon({ iconName: 'brandMark' })` 即可 —— 走 `Path.stroke()`,跟主题色走。
## ② 图标贴在卡片左上角(量出来的)
`AmIcon` 内部那个 `Stack` 是 `iconSize` 那么大、**默认靠左上**排版。调用方写
`AmIcon({…}).width(48).height(48)` 想要个大点的可上色盒子时,**外层盒子变大、图标不动**。
实测(三折叠 3.5 密度,`dumpLayout` 读的**实际 bounds**):
卡片 [1523,521][1661,659] 138×138px
图标 [1526,524][1589,587] 63×63px ← 左边距/上边距都只有 3px
⇒ 图标中心偏 10vp
**不是一处**:全仓扫出 4 个同样写法。修法是套一层
`Stack({ alignContent: Alignment.Center })`(仓里回复球与悬浮加号本来就是这么写的,
所以它们一直是对的):
| 位置 | 图标 | 盒子 | 原状态 |
|---|---|---|---|
| 登录页品牌卡 | brandMark 24 | 48×48 | 贴左上 ✗ |
| 用户管理刷新键 | repeat 20 | 40×40 | 贴左上 ✗ |
| 联系人视图切换 | cardView 18 | 40×40 | 贴左上 ✗ |
| 收件箱组头箭头 | chevronRight 12 | 20 槽位 | 贴左 ✗ |
| 回复球 / 悬浮加号 | chatBubble / compose | 56×56 | 本来就对(尺寸在外层 Button 上) |
修后实测:偏移 **−34.5px → 0.5px**(0.14vp,亚像素级)。
## 判据(harmony-arkts 3 → 5 条)
- **图标不许用 Unicode 符号充当**:扫 `Text('…')` 里单个符号的情况。
★ 只框 U+2600–U+27BF / U+2B00–U+2BFF / U+FE0F,**刻意不含基本箭头段**(U+2190–U+21FF)——
`→` 在正文里是标点不是图标,框进来会误伤大量正常文案。
(我第一版把箭头段也框了,结果自检自己先红 —— 断言写错就是写错,不靠放宽它来「修」。)
- **尺寸不许直接链在 `AmIcon` 上**:把"图标贴左上角"这个坑的**形状**钉住,
并自检"套了 `Stack` 的正确写法不许被误判"。
**两个变异方向都跑过**:放回 `Text('✉')` ⇒ 红 ✓;给 `AmIcon` 直接加 `.width(48)` ⇒ 红 ✓。
This commit is contained in:
@ -157,3 +157,109 @@ test('ArkTS 词汇层硬坑:全仓 .ets 不许出现解构 / any / unknown /
|
||||
}
|
||||
assert.deepEqual(bad, [], `★ ArkTS 硬坑(编译不过):\n ${bad.join('\n ')}`);
|
||||
});
|
||||
|
||||
/* ─────────────────── 图标:不许用 Unicode 符号,且尺寸不许直接加在 AmIcon 上 ─────────────────── */
|
||||
|
||||
/**
|
||||
* 这两条都是 2026-09-18 用户**看着界面**报出来的,而且都不是「代码不合法」——
|
||||
* 编译器与所有既有判据全绿,只有屏幕上是错的。
|
||||
*/
|
||||
|
||||
test('★ 图标不许用 Unicode 符号充当(`Text(\'✉\')` 会被系统渲染成彩色 emoji)', () => {
|
||||
/*
|
||||
* 登录页原本写的是 `Text('✉')`(U+2709)当品牌图标。
|
||||
*
|
||||
* HarmonyOS 的字体链里有**彩色 emoji 字体**,U+2709 自带 emoji 字形 ⇒
|
||||
* 它被渲染成一枚**彩色 emoji**(黄白色风信封)而不是单色图标:
|
||||
* ① `.fontColor(Theme.accent)` 对彩色 emoji **无效**(界面显示的是 emoji 自带颜色);
|
||||
* ② 与底栏/侧栏那些 `AmIcon` 线描图标不是同一套视觉语言。
|
||||
* 实测截图硬证:大屏下那枚 emoji 比旁边的文字还显眼。
|
||||
*
|
||||
* 本仓**早就有** `ICON_PATHS.brandMark`(就是 App 图标那个信封),
|
||||
* 所以这条纪律的成本是零:界面里所有图标都走 `AmIcon`。
|
||||
*
|
||||
* 判据只扫 `Text('…')` 里**单独一个**符号的情况 —— 正文里的标点/箭头不算
|
||||
* (例如提示语里的「·」或「→」),否则会误伤大量正常文案。
|
||||
*/
|
||||
const ETS = join(ROOT, 'client', 'harmony', 'entry', 'src', 'main', 'ets');
|
||||
/*
|
||||
* 会被 emoji 字体接管的**典型图标类**符号:
|
||||
* U+2600–U+27BF(Misc Symbols / Dingbats:☀ ☂ ✈ ✉ ✏ ✔ ❤ …)
|
||||
* U+2B00–U+2BFF(杂项符号与箭头)
|
||||
* U+FE0F(变体选择符,把字符变成 emoji 呈现)
|
||||
* ★ 刻意**不含 U+2190–U+21FF(基本箭头)**:`→`/`←` 在正文里是**标点**,
|
||||
* 不是图标 —— 把它们也扫进来会误伤大量正常文案。
|
||||
* (我第一版把箭头段也框了进去,结果自检自己就先红了:
|
||||
* `!EMOJI_ISH.test('→')` 不成立 —— 断言写错就是断言写错,不能靠放宽它来「修」。)
|
||||
*/
|
||||
const EMOJI_ISH = /^[\u2600-\u27BF\u2B00-\u2BFF\uFE0F]$/;
|
||||
const hits = [];
|
||||
const walkDir = (dir) => {
|
||||
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
||||
const full = join(dir, e.name);
|
||||
if (e.isDirectory()) { walkDir(full); continue; }
|
||||
if (!/\.ets$/.test(e.name)) continue;
|
||||
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)) {
|
||||
hits.push(`${e.name}: Text('${ch}')(U+${ch.codePointAt(0).toString(16).toUpperCase()})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
walkDir(ETS);
|
||||
assert.deepEqual(hits, [],
|
||||
`用 Unicode 符号当图标会被系统渲染成 emoji,且不吃 fontColor:\n ${hits.join('\n ')}\n` +
|
||||
'改用 AmIcon(图标表在 common/Icons.ets,品牌标是 brandMark)');
|
||||
// 自检:探测器要真能认出这个形状(否则"没有命中"与"探测器坏了"结果一样)
|
||||
assert.ok(EMOJI_ISH.test('\u2709') && EMOJI_ISH.test('\u2764'),
|
||||
'探测器要认得出 U+2709 / U+2764 这类符号');
|
||||
assert.ok(!EMOJI_ISH.test('·') && !EMOJI_ISH.test('→'),
|
||||
'探测器不许把正文标点当成图标(会误伤大量文案)');
|
||||
});
|
||||
|
||||
test('★ 尺寸/底色不许直接链在 `AmIcon` 上(内层容器固定 iconSize 且靠左上 ⇒ 图标贴左上角)', () => {
|
||||
/*
|
||||
* `AmIcon` 内部那个 `Stack` 是 `iconSize` 那么大、默认靠左上排版。
|
||||
* 调用方写 `AmIcon({…}).width(48).height(48)` 想要个大点的可上色盒子时,
|
||||
* **外层盒子变大、图标不动** ⇒ 图标贴在盒子左上角。
|
||||
*
|
||||
* 实测(登录页品牌卡,三折叠 3.5 密度,`dumpLayout` 读的实际 bounds):
|
||||
* 卡片 [1523,521][1661,659] 138×138px
|
||||
* 图标 [1526,524][1589,587] 63×63px ← 左边距/上边距都只有 3px
|
||||
* 中心偏 10vp。用户原话:「你自己看看那个图标的位置正常吗」。
|
||||
*
|
||||
* 正确写法是套一层居中的容器(仓里回复球与悬浮加号本来就是这么做的):
|
||||
* Stack({ alignContent: Alignment.Center }) { AmIcon({…}) }.width(48).height(48)
|
||||
*
|
||||
* 判据形状:同一表达式里,`AmIcon({…})` 之后**紧跟着** `.width(` / `.height(`
|
||||
* 就算命中(中间只允许换行与空白)。套了 `Stack` 的写法中间隔着 `}`,
|
||||
* 所以不会被误判。
|
||||
*/
|
||||
const ETS = join(ROOT, 'client', 'harmony', 'entry', 'src', 'main', 'ets');
|
||||
const hits = [];
|
||||
const walkDir = (dir) => {
|
||||
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
||||
const full = join(dir, e.name);
|
||||
if (e.isDirectory()) { walkDir(full); continue; }
|
||||
if (!/\.ets$/.test(e.name)) continue;
|
||||
const src = code(full);
|
||||
/* `AmIcon({ ... })` 后面直接跟 `.width(` 或 `.height(` */
|
||||
for (const m of src.matchAll(/AmIcon\(\{[\s\S]{0,200}?\}\)\s*\.\s*(width|height)\s*\(/g)) {
|
||||
const line = src.slice(0, m.index).split('\n').length;
|
||||
hits.push(`${e.name}:${line} → .${m[1]}(`);
|
||||
}
|
||||
}
|
||||
};
|
||||
walkDir(ETS);
|
||||
assert.deepEqual(hits, [],
|
||||
`尺寸直接加在 AmIcon 上会让图标贴左上角(内层容器固定 iconSize):\n ${hits.join('\n ')}\n` +
|
||||
'改成 Stack({ alignContent: Alignment.Center }) { AmIcon({…}) }.width(N).height(N)');
|
||||
// 自检:探测器必须认得这个形状,且不误伤正确的嵌套写法
|
||||
const bad = "AmIcon({ iconName: 'x', iconSize: 24 }).width(48)";
|
||||
const good = "Stack({ alignContent: Alignment.Center }) {\n AmIcon({ iconName: 'x', iconSize: 24 })\n}\n.width(48)";
|
||||
const probe = (t) => [...t.matchAll(/AmIcon\(\{[\s\S]{0,200}?\}\)\s*\.\s*(width|height)\s*\(/g)].length;
|
||||
assert.equal(probe(bad), 1, '探测器要认得出错误写法');
|
||||
assert.equal(probe(good), 0, '探测器不许误伤套了 Stack 的正确写法');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user