跨端: 12 处「深色下字看不见」的真 bug + 判据基建补上「看像素」这一层
## 一、判据基建:本目录终于能**看像素**了 此前只能靠 `dumpLayout` —— 那是**结构化描述**,报的是"组件声明了什么", 不是"屏幕上画成什么样"。两者会分叉,而观感类结论只能在像素上得出来。 新增 `lib/harmony-device.mjs`:`screenshot()` / `pixelAt()` / `hexToRgb()` / `closeColor()`(用 ffmpeg 转 1×1 原始 RGB,不引依赖)。 **它当场证明了它的价值**:`cross-client-theme` 新增的设备判据 用真实像素抓到下面这个 bug —— 静态判据全绿时它藏得好好的。 ## 二、真 bug:**12 处**把 `Theme.surface` 当前景色用 `Theme.surface` 是 `sys.color.ohos_id_color_list_card_bg` —— 一个**跟随系统主题翻转**的 Resource:浅色近白、**深色近黑**。 - 浅色下当白字用**碰巧对**(白字压蓝底) - **深色下字变成黑的**,压在品牌蓝 / danger 红 / warn 琥珀上**几乎看不见** 设备现场:写邮件悬浮球是品牌蓝 `#2563EB`,截图里那个铅笔图标**几乎是隐形的**; 读圆心像素得到 `rgb(32,34,36)`。往左偏 50px 读到底色才见 `rgb(36,99,235)`。 `Theme.accentFg`(`#FFFFFF`)的注释原话就是「品牌底上的文字」—— 为这个场景存在, 却**一处都没用**。 修:12 处 `fontColor/iconColor(Theme.surface)` → `Theme.accentFg` (`MainPage` 10 + `InboxPage` 1 + `SessionsPage` 1)。改完全仓 0 处残留。 另在 `Theme.ets` 给 `surface` / `accentFg` 都补上"能当什么、不能当什么"的注释。 ## 三、判据(两条,都做了变异验证) 1. **设备条**(`cross-client-theme`):读悬浮球像素 —— ① 品牌色**真的画成** `#2563EB`(声明 ≠ 渲染); ② 球上图标与底色 **WCAG 对比度 ≥3:1**(压在上面的东西得看得见)。 把 `.accentFg` 改回 `.surface` ⇒ **判红**;还原 ⇒ 绿。 2. **静态防线**(同文件):全局 grep「`fontColor/iconColor(Theme.surface)`」一处不许有。 设备条只能看一处,而这个错法有 12 处 —— 静态防线管住整类。 ## 四、判据自身踩的三个坑(都写进注释了) - **采样点撞上图标**:第一版取球心,读到 `rgb(32,34,36)`,差点当成"品牌色没渲染"。 截图一看球是蓝的,深色那点是**铅笔图标**。⇒ 往中心左偏 30% 球宽。 - **假设错了 FAB 的位置**:按"屏幕右下角"找(`x1 > 屏宽*0.6`), 实测 `[942,1997]`(`x1=942` vs 阈值 1910)⇒ 永远找不到、**静默跳过**。 原因是列表窗格是**左栏**,球在"左栏的右下角"。⇒ 形状只用站得住的那部分(下半部)。 - **设备判据要自己搭现场**:不加自导航时它**永远跳过**(前面的判据把前台留在管理页), 而那看起来像"功能没了"。加自导航后立刻开始工作并抓到 bug。 ## 五、欠账 - `harmony-maildetail-missing-three` → **count 0(结算)**:三块都做完了 (转发 `b7c5d8b` / 改名建议 `c2f35d1`+`e79a86a` / 往返预算 `ac62daf`)。 如实记着**未验**的那点:预算条的**点击**没在设备上走通 (模拟器顶部 155px 是系统手势区,折叠头部恰在其中)。 - `static-criteria` 5:`cross-client-theme` **升级了一半**,仍留在名单里 —— `.ets` 那半只有悬浮球这一处上了设备,其余令牌仍是静态对齐。 - `debt-visibility` 登记 `cross-client-theme` 1 处边界声明(带出处)。 `run-all.mjs` → `checks=513 pass=513 fail=0 skip=0 red=0 broken=0 unreported=0`; Go 侧 `./internal/repo/...` 通过。
This commit is contained in:
@ -39,7 +39,25 @@ export class Theme {
|
||||
|
||||
/** 页面底色(系统 `ohos_id_color_background`:浅色白/深色深灰,自动跟随主题) */
|
||||
static readonly pageBg: Resource = $r('sys.color.ohos_id_color_background');
|
||||
/** 承载文字的面 = **列表卡片底色**(对应"每项一张卡/气泡",而不是通栏底色) */
|
||||
/**
|
||||
* 承载文字的**面** = 列表卡片底色(对应"每项一张卡/气泡")。
|
||||
*
|
||||
* ⚠️⚠️ **它只能当背景色,绝不能当前景色(`fontColor` / `iconColor`)**。
|
||||
*
|
||||
* 它是一个**跟随系统主题翻转**的 Resource:浅色下接近白、深色下接近黑。
|
||||
* 我们曾经在 **12 处**把它当"压在彩色底上的字/图标色"用 ——
|
||||
* 浅色下碰巧对(白字压蓝底),**深色下字变成黑的**,看起来像元素消失了。
|
||||
*
|
||||
* 2026-09-19 设备实测到的现场:写邮件悬浮球是品牌蓝 `#2563EB`,
|
||||
* 但球心读出来是 `rgb(32,34,36)`(近黑)—— 那是深色主题下的 `surface`。
|
||||
* 截图里那个铅笔图标几乎是隐形的。
|
||||
*
|
||||
* 压在**品牌色**底上要用 `accentFg`;压在别的语义色(danger/warn/approve)
|
||||
* 底上同理 —— 那些底都是深色,前景都该是浅的。
|
||||
*
|
||||
* 判据:`cross-client-theme` 的设备条会读悬浮球**圆心**的像素,
|
||||
* 要求图标与底色 WCAG 对比度 ≥3:1。
|
||||
*/
|
||||
static readonly surface: Resource = $r('sys.color.ohos_id_color_list_card_bg');
|
||||
/** 次级面(分组底、列表行 hover) */
|
||||
static readonly surfaceMuted: Resource = $r('sys.color.ohos_id_color_sub_background');
|
||||
@ -192,6 +210,16 @@ export class Theme {
|
||||
* 这是整个文件里**唯一必须与 WebUI 逐字一致**的取值(判据钉住)。
|
||||
*/
|
||||
static readonly accent: string = '#2563EB';
|
||||
/**
|
||||
* 品牌底上的字/图标 —— **浅色**(白色)。
|
||||
*
|
||||
* ★ 底色深、前景浅。`danger` / `warnFg` / `approve` 那些语义色底同属这一类,
|
||||
* 所以压在它们上面的字/图标**也用这个**(写 `accentFg` 比另起一个
|
||||
* `onDangerFg` 更实际:此项目里那些底的颜色都够深,一个白就够)。
|
||||
*
|
||||
* ⚠️ 别写成 `Theme.surface` —— 那是**面**(会跟随系统主题翻转),
|
||||
* 2026-09-19 有 12 处这么写错了(见 `surface` 的注释)。
|
||||
*/
|
||||
static readonly accentFg: string = '#FFFFFF';
|
||||
/** 品牌蓝的浅底 / 深前景(与 WebUI 的 blue-50 / blue-700 成对) */
|
||||
static readonly accentSoft: string = '#EFF6FF';
|
||||
|
||||
@ -94,7 +94,7 @@ struct InboxPage {
|
||||
Blank()
|
||||
if (this.unread > 0) {
|
||||
Text(this.unread + ' 未读')
|
||||
.fontSize(13).fontColor(Theme.surface)
|
||||
.fontSize(13).fontColor(Theme.accentFg)
|
||||
.backgroundColor(Theme.danger)
|
||||
.borderRadius(10)
|
||||
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
|
||||
|
||||
@ -418,7 +418,7 @@ struct InboxTab {
|
||||
}
|
||||
if (this.unread > 0) {
|
||||
Text(this.unread > 99 ? '99+' : this.unread.toString())
|
||||
.fontSize(10).fontWeight(FontWeight.Bold).fontColor(Theme.surface)
|
||||
.fontSize(10).fontWeight(FontWeight.Bold).fontColor(Theme.accentFg)
|
||||
.backgroundColor(Theme.danger).borderRadius(9)
|
||||
.constraintSize({ minWidth: 18 }).height(18)
|
||||
.textAlign(TextAlign.Center).margin({ left: 8 })
|
||||
@ -585,7 +585,7 @@ struct InboxTab {
|
||||
Row() {
|
||||
if (g.unreadCount > 0) {
|
||||
Text(g.unreadCount > 99 ? '99+' : g.unreadCount.toString())
|
||||
.fontSize(9).fontWeight(FontWeight.Bold).fontColor(Theme.surface)
|
||||
.fontSize(9).fontWeight(FontWeight.Bold).fontColor(Theme.accentFg)
|
||||
.backgroundColor(Theme.accent).borderRadius(8)
|
||||
.constraintSize({ minWidth: 16 }).height(16).textAlign(TextAlign.Center)
|
||||
.margin({ right: 6 })
|
||||
@ -1108,7 +1108,7 @@ struct PermissionTab {
|
||||
Row() {
|
||||
Button('同意')
|
||||
.fontSize(13).height(36).layoutWeight(1)
|
||||
.backgroundColor(Theme.approve).fontColor(Theme.surface)
|
||||
.backgroundColor(Theme.approve).fontColor(Theme.accentFg)
|
||||
.onClick(() => { this.decide(req, 'allow', ''); })
|
||||
Text('拒绝')
|
||||
.fontSize(13).height(36).layoutWeight(1)
|
||||
@ -1148,7 +1148,7 @@ struct PermissionTab {
|
||||
if (this.requests.length > 0) {
|
||||
// 待决策的橙色徽标:这不是"有东西要读",是"有人被卡住"
|
||||
Text(this.requests.length + ' 待决策')
|
||||
.fontSize(12).fontColor(Theme.surface)
|
||||
.fontSize(12).fontColor(Theme.accentFg)
|
||||
.backgroundColor(Theme.warnFg)
|
||||
.borderRadius(10)
|
||||
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
|
||||
@ -1387,7 +1387,7 @@ struct CommPage {
|
||||
.fontColor(this.commTab === key ? Theme.accent : Theme.textMuted)
|
||||
if (badgeText(badgeCount(key, this.unreadCount, this.pendingCount)).length > 0) {
|
||||
Text(badgeText(badgeCount(key, this.unreadCount, this.pendingCount)))
|
||||
.fontSize(10).fontColor(Theme.surface)
|
||||
.fontSize(10).fontColor(Theme.accentFg)
|
||||
// 红=有东西要读、橙=有人被卡住(更急);色值来自 Theme,页面不自己挑
|
||||
.backgroundColor(badgeTone(key) === 'warn' ? Theme.warnFg : Theme.danger)
|
||||
.borderRadius(9)
|
||||
@ -1472,7 +1472,7 @@ struct CommPage {
|
||||
* 离底留白 + 余量(`navReserve`)—— 否则它会压在条上。
|
||||
*/
|
||||
Button() {
|
||||
AmIcon({ iconName: 'compose', iconSize: 24, iconColor: Theme.surface })
|
||||
AmIcon({ iconName: 'compose', iconSize: 24, iconColor: Theme.accentFg })
|
||||
}
|
||||
.width(56).height(56)
|
||||
.borderRadius(28)
|
||||
@ -1940,7 +1940,7 @@ struct ContactsTab {
|
||||
.margin({ left: 6 }).layoutWeight(1)
|
||||
if (c.unread_count > 0) {
|
||||
Text(c.unread_count + '')
|
||||
.fontSize(10).fontColor(Theme.surface)
|
||||
.fontSize(10).fontColor(Theme.accentFg)
|
||||
.backgroundColor(Theme.accent)
|
||||
.borderRadius(9).width(18).height(18)
|
||||
.textAlign(TextAlign.Center)
|
||||
@ -2083,8 +2083,8 @@ struct ContactsTab {
|
||||
.width('100%')
|
||||
Row() {
|
||||
Row() {
|
||||
AmIcon({ iconName: 'check', iconSize: 12, iconColor: Theme.surface })
|
||||
Text('确认归档').fontSize(12).fontColor(Theme.surface).margin({ left: 4 })
|
||||
AmIcon({ iconName: 'check', iconSize: 12, iconColor: Theme.accentFg })
|
||||
Text('确认归档').fontSize(12).fontColor(Theme.accentFg).margin({ left: 4 })
|
||||
}
|
||||
.height(30).padding({ left: 12, right: 12 })
|
||||
.borderRadius(Theme.radiusControl)
|
||||
@ -2121,7 +2121,7 @@ struct ContactsTab {
|
||||
Blank()
|
||||
if (c.unread_count > 0) {
|
||||
Text(c.unread_count + '')
|
||||
.fontSize(11).fontColor(Theme.surface)
|
||||
.fontSize(11).fontColor(Theme.accentFg)
|
||||
.backgroundColor(Theme.danger)
|
||||
.borderRadius(10).width(20).height(20)
|
||||
.textAlign(TextAlign.Center)
|
||||
|
||||
@ -138,7 +138,7 @@ struct SessionsPage {
|
||||
// 未读
|
||||
if (s.unread_count > 0) {
|
||||
Text(s.unread_count + '')
|
||||
.fontSize(11).fontColor(Theme.surface)
|
||||
.fontSize(11).fontColor(Theme.accentFg)
|
||||
.backgroundColor(Theme.danger)
|
||||
.borderRadius(10).width(20).height(20)
|
||||
.textAlign(TextAlign.Center)
|
||||
|
||||
Reference in New Issue
Block a user