diff --git a/client/electron/test/cross-client-theme.test.mjs b/client/electron/test/cross-client-theme.test.mjs index 8979897..3f12efd 100644 --- a/client/electron/test/cross-client-theme.test.mjs +++ b/client/electron/test/cross-client-theme.test.mjs @@ -245,6 +245,16 @@ const SELF_OWNED_COLORS = [ * 各条取值的理由都已写在 `Theme.ets` 各自的注释里(本判据的要求)。 */ 'navActiveBg', 'navBrandFg', 'badgePlain', 'accentSoftDark', 'accentDark', + /* + * 语义色的深色档(2026-09-19 加)—— 与 `accentDark` 同一个问题、同一批证据: + * WebUI 的语义色也是**双通道**,`.dark` 段把 red/green/amber-700 换成浅色, + * 否则深底上的红字/绿字读不动(设备实测「归档」按钮 2.47:1)。 + * · `dangerDark` #F8A4A4 ← WebUI `.dark --c-red-700`(`248 164 164`) + * · `approveDark` #76DB9B ← WebUI `.dark --c-green-700`(`118 219 155`) + * · `warnFgDark` #F9C368 ← WebUI `.dark --c-amber-700`(`249 195 104`) + * 入口是 `Theme.dangerFor()` / `approveFor()` / `warnFgFor()`。 + */ + 'dangerDark', 'approveDark', 'warnFgDark', /* * SSE 连接指示器的四个状态色(`WideSidebar.ets` 的 `sseColorOf`)。 * 逐档对齐 WebUI 的 Tailwind 类(`ConnectionIndicator.tsx:22-25`): @@ -1295,23 +1305,69 @@ test('★ 设备:深色下短文本必须都读得动(扫一屏,不只查 if (w < 20 || h < 14 || w > 700 || h > 130) continue; if (x2 > img0.w - 4 || y2 > img0.h - 4) continue; - let lo = null; - let hi = null; - const sx = Math.max(2, Math.floor(w / 14)); - const sy = Math.max(2, Math.floor(h / 7)); - for (let y = y1 + 1; y < y2; y += sy) { - for (let x = x1 + 1; x < x2; x += sx) { + /* + * ★★ 第三版取样法(前两版都错,记下来): + * + * ① 只取**中心一个像素** ⇒ 多半落在笔画之间,读到的是底色 ⇒ + * ratio≈1.00 的假红一片。 + * ② **全局最暗/最亮**(我上一版)⇒ 会采到**两个不同东西**: + * 细字的抗锯齿**中间色**(既非墨也非底)。 + * 实测假红:「日」按钮报 ink `rgb(32,34,35)` / bg `rgb(98,100,101)` + * 2.69:1 —— 而截图显示那个按钮要么是蓝底白字、要么是深底浅字, + * 两种都远高于 3:1。`rgb(98,100,101)` 是白字压在深色上的**边缘混色**。 + * ★ 反复核对才确认是**判据自己的错**,不是界面的错 —— + * 这正是"假红比漏报更费时间"的又一例。 + * ③(本版)**众数 = 底色**,再找离它最远的颜色当墨。 + * 理由:一个文字框里**面积最大的一定是底**(笔画只占少数像素), + * 而"离底色最远"就是笔画最实的那部分 —— 不会再被边缘混色骗。 + * 这一版对"细字 + 粗采样"稳,且不依赖步长调得多细。 + */ + /* + * ★★ 第四版:**逐像素**(不再抽步长)。 + * + * 第三版虽然避免了"取中心/取全局极值",但仍然**步长采样** —— + * 于是第三次假红:周表头的「一」报 2.03:1,而逐像素一看, + * 真正的墨是 `rgb(166,167,167)`(**6.6:1,完全没问题**), + * 我的稀疏网格**整个跳过了笔画**,只采到抗锯齿的中间色。 + * + * ⇒ 教训:**小字 + 稀疏采样 = 采不到字**。笔画宽度可能只有 2–3px, + * 而步长 9 会稳定地跨过去。这不是调参能修好的(字越小越糟), + * 只能逐像素。 + * + * ★ 逐像素现在是**负担得起的**:整屏已经解码在内存里(`readPixels`), + * 一个框最大 700×130=9.1 万像素,几十个框也就几百万次数组下标读取 —— + * 毫秒级。**不要**为了省这点开销再退回抽样(前两版都因此假红)。 + */ + const counts = new Map(); + for (let y = y1 + 1; y < y2; y++) { + for (let x = x1 + 1; x < x2; x++) { const p = img0.at(x, y); if (!p) continue; - const L = lum(p); - if (!lo || L < lo.L) lo = { L, p }; - if (!hi || L > hi.L) hi = { L, p }; + /* 量化到 8 级,避免抗锯齿把众数切碎(同色系会被合成一档) */ + const key = `${p.r >> 5},${p.g >> 5},${p.b >> 5}`; + const e = counts.get(key); + if (e) { e.n++; } + else { counts.set(key, { n: 1, r: p.r, g: p.g, b: p.b }); } } } + if (counts.size < 2) continue; + const sorted = [...counts.values()].sort((a, b) => b.n - a.n); + const bg = sorted[0]; + const bgL = lum(bg); + /* 离底色最远的那个(按对比度算,而不是按亮度差 —— 亮度差会偏向同向的色) */ + let ink = null; + let best = 0; + for (const c of sorted.slice(1)) { + const L = lum(c); + const r = (Math.max(L, bgL) + 0.05) / (Math.min(L, bgL) + 0.05); + if (r > best) { best = r; ink = { L, p: c }; } + } /* 没有真实墨迹对比的框跳过(见上面 ③) */ - if (!lo || !hi || hi.L - lo.L < 0.02) continue; + if (!ink || best < 1.2) continue; scanned++; - const ratio = (hi.L + 0.05) / (lo.L + 0.05); + const ratio = best; + const lo = { L: Math.min(ink.L, bgL), p: ink.L < bgL ? ink.p : bg }; + const hi = { L: Math.max(ink.L, bgL), p: ink.L < bgL ? bg : ink.p }; if (ratio < 3) { bad.push(`「${txt}」 对比度 ${ratio.toFixed(2)}:1 ` + `墨 rgb(${lo.p.r},${lo.p.g},${lo.p.b}) / 底 rgb(${hi.p.r},${hi.p.g},${hi.p.b}) ${a.bounds}`); diff --git a/client/electron/test/mutants/baseline.sha b/client/electron/test/mutants/baseline.sha index ceba0bd..6cdf609 100644 --- a/client/electron/test/mutants/baseline.sha +++ b/client/electron/test/mutants/baseline.sha @@ -48,10 +48,20 @@ # ⇒ 这是 `stale`(底本过期),不是 `residue`(变异残留)。 # ★ 三个都逐个跑过 `git diff --quiet HEAD -- ` 取证,没有一个是"改动忘了还原"。 # ★ 这正是这份文件里那条纪律的第 3 次执行:"重算必须是一次有记录的动作"。 +# +# 2026-09-19 20:2x baseline 重算(第 4 次,**仍先核过不是残留**): +# ① `pages/AdminUsersPage.ets` / `pages/SettingsPage.ets` / `common/BackgroundPicker.ets` +# —— 本次的**有意编辑**:语义色前景接入 `dangerFor()/approveFor()/warnFgFor()` +# (深色下红/绿/琥珀要提亮,对齐 WebUI `.dark --c-red/green/amber-700`), +# 以及三级文字接入 `textSubtleFor()`(深色下换二级,见 Theme.ets 里那段理由)。 +# ② 三个都逐个跑过 `git diff --quiet HEAD -- ` ⇒ 全部**非空**(工作树有改动) +# ⇒ 是"我改的",不是"变异忘了还原"。 +# ★ 取证命令留在这里,下次照着做: +# git diff --quiet HEAD -- && echo STALE || echo INTENTIONAL 4f3e0802346ba93740d7a6989fa6a9ef7dce16d1db59ea7402ff554127b07e3e client/harmony/entry/src/main/ets/model/AdminUsers.ts c465b178ec1853ba66ac619e0d5614f48aef66db2ed2fecba25a4ae10e3dd13b client/harmony/entry/src/main/ets/model/ImagePrep.ts -efd598149cb52400f1120f1ba4c7fb41ad9706f82005770479f571614a539e73 client/harmony/entry/src/main/ets/pages/AdminUsersPage.ets -4681c5f2d57870d64b09a10433f5ea518d777a27f6cb7ece3bbcc29c5f11012a client/harmony/entry/src/main/ets/common/BackgroundPicker.ets -3baed1fac54fef5f924c17f658ad05105b8be3ac35b110e2277b1365a84bb15a client/harmony/entry/src/main/ets/pages/SettingsPage.ets +ceec4a5aae5e9064d9ab57ef24acd4b7f185506de8582ae2c67e35f634ff8593 client/harmony/entry/src/main/ets/pages/AdminUsersPage.ets +73c66c7045f579c3eb8b8e0aa07803e7c9363b7ae8375972b251633d3ce969be client/harmony/entry/src/main/ets/common/BackgroundPicker.ets +e773fd556f4454efbb204ff72841b63a0a584f7ca7827c73142884af21328ba5 client/harmony/entry/src/main/ets/pages/SettingsPage.ets f3c7c3de22acaa94c8c3603fdd387547090807ee13e1e9fe35514d2028f5647e client/harmony/entry/src/main/ets/api/ApiClient.ets 6550e1892d1ebea82fb75a3d2cf8eaf826186199ff4a0ecf0430b1e3517d8681 client/harmony/entry/src/main/ets/api/AppearanceApi.ets diff --git a/client/harmony/entry/src/main/ets/common/BackgroundPicker.ets b/client/harmony/entry/src/main/ets/common/BackgroundPicker.ets index 8b4e62a..6aff946 100644 --- a/client/harmony/entry/src/main/ets/common/BackgroundPicker.ets +++ b/client/harmony/entry/src/main/ets/common/BackgroundPicker.ets @@ -259,7 +259,7 @@ export struct BackgroundPicker { Column() { Row() { Text('背景').fontSize(Theme.fontBody).fontColor(Theme.textPrimary).layoutWeight(1) - Text(this.statusText).fontSize(Theme.fontTiny).fontColor(Theme.textSubtle) + Text(this.statusText).fontSize(Theme.fontTiny).fontColor(Theme.textSubtleFor()) } .width('100%') @@ -302,17 +302,17 @@ export struct BackgroundPicker { .onClick(() => { this.pickAndUpload(); }) if (this.progress.length > 0) { - Text(this.progress).fontSize(Theme.fontTiny).fontColor(Theme.textSubtle) + Text(this.progress).fontSize(Theme.fontTiny).fontColor(Theme.textSubtleFor()) .width('100%').margin({ top: 6 }) } if (this.uploadError.length > 0) { Text(this.uploadError) - .fontSize(Theme.fontTiny).fontColor(Theme.danger) + .fontSize(Theme.fontTiny).fontColor(Theme.dangerFor()) .width('100%').padding(8).margin({ top: 6 }) .backgroundColor(Theme.dangerBg).borderRadius(Theme.radiusControl) } Text('上传前会先压到最长边 ' + MAX_EDGE + ' 像素(手机直出照片 4–8MB,服务端上限 4MB)。') - .fontSize(Theme.fontTiny).fontColor(Theme.textSubtle) + .fontSize(Theme.fontTiny).fontColor(Theme.textSubtleFor()) .width('100%').margin({ top: 6 }) } .width('100%').alignItems(HorizontalAlign.Start).margin({ top: 8 }) @@ -352,7 +352,7 @@ export struct BackgroundPicker { */ Text('压暗').fontSize(Theme.fontTiny).fontColor(Theme.textMuted) Blank() - Text(Math.round(this.bgDim) + '%').fontSize(Theme.fontTiny).fontColor(Theme.textSubtle) + Text(Math.round(this.bgDim) + '%').fontSize(Theme.fontTiny).fontColor(Theme.textSubtleFor()) } .width('100%') Slider({ value: this.bgDim, min: 0, max: 80, step: 1 }) @@ -369,7 +369,7 @@ export struct BackgroundPicker { Text('模糊').fontSize(Theme.fontTiny).fontColor(Theme.textMuted) Blank() /* 同 ①:原先印 `3.000000`;WebUI 是 `suffix="px"` ⇒ `3px` */ - Text(Math.round(this.bgBlur) + 'px').fontSize(Theme.fontTiny).fontColor(Theme.textSubtle) + Text(Math.round(this.bgBlur) + 'px').fontSize(Theme.fontTiny).fontColor(Theme.textSubtleFor()) } .width('100%') Slider({ value: this.bgBlur, min: 0, max: 40, step: 1 }) diff --git a/client/harmony/entry/src/main/ets/common/Theme.ets b/client/harmony/entry/src/main/ets/common/Theme.ets index 4567656..ad3b2bd 100644 --- a/client/harmony/entry/src/main/ets/common/Theme.ets +++ b/client/harmony/entry/src/main/ets/common/Theme.ets @@ -70,6 +70,47 @@ export class Theme { static readonly textMuted: Resource = $r('sys.color.ohos_id_color_text_secondary'); static readonly textSubtle: Resource = $r('sys.color.ohos_id_color_text_tertiary'); + /* + * ──────── 三级文字在**深色**下的可读性下限(2026-09-19 加)──────── + * + * ★★ 问题(设备实测,不是推测): + * 系统三级色 `ohos_id_color_text_tertiary` 在**深色**下是 `rgb(102,103,105)`, + * 压在卡片面 `rgb(32,34,36)` 上只有 **2.82–3.05:1**。 + * 它被用在**小字**上(10–11px 的时间戳、农历、空态、辅助说明,全局 66 处) + * ⇒ 远低于 WCAG AA 对正文的 4.5:1。 + * + * ★ 这不是鸿蒙独有的问题 —— WebUI 早就遇到过并写下了结论 + * (`client/electron/src/index.css` 的 `.dark` 段注释,逐字引用): + * + * 「`gray-400/500`(次要文字)→ **提亮**。深底上的浅色 gray-400 + * 只有约 2:1 对比度,远低于 WCAG AA 的 4.5:1 —— **看得见但读不动**。」 + * + * 它的做法是在 `.dark` 段把 gray-400 从 `107 114 128` 提亮到 `138 146 161`, + * gray-500 从 `90 98 112` 提到 `165 173 186`。实测那两组压在深卡片上 + * 分别是 **5.51:1 / 7.63:1**,都在 AA 之上。 + * + * ★ 系统色为什么没有这层保护:`ohos_id_color_text_tertiary` 的语义是 + * 「比二级更淡」——它只保证**层次关系**,不保证可读性下限。 + * 当"更淡"用在浅底上时刚好合适;深底上同样的亮度就掉下去了。 + * (这与 `Theme.surface` 当前景色是同一类:**系统语义 ≠ 我们的可读性要求**。) + * + * ★ 修法:**不换掉系统令牌**(上面那条"系统拥有的维度"纪律照旧), + * 而是提供一个「按主题取三级文字」的入口:深色下改用二级色。 + * 之所以用二级而不是另写一个白: + * · 二级色深色下实测 `rgb(166,167,167)` ≈ 7.15:1,在 AA 之上; + * · 它与 WebUI 的 `gray-500` 是同一个档位(两端视觉层次一致); + * · 不引入新的手写色(不用再过登记表)。 + * + * ★ 浅色下**不改**:浅色下系统三级色压在浅底上够用(与 WebUI 的 + * gray-400 `107 114 128` 同量级),换了反而会让层次变平。 + * + * ★ 使用入口是 `Theme.textSubtleFor()` —— 与 `accentFor` / `accentSoftFor` + * 同一条纪律:**别在页面里自己写 `isDark ? a : b`**。 + */ + static textSubtleFor(dark?: boolean): Resource { + return Theme.isDarkNow(dark) ? Theme.textMuted : Theme.textSubtle; + } + // ─────────────── 遮罩与材质:交给系统 ─────────────── /** @@ -161,6 +202,15 @@ export class Theme { * ★ 不登记会怎样:深色下选中卡片是接近纯白的浅蓝, * 在深色页面上刺眼得像 bug(2026-09-19 设备实测到) * · accentStrong #1D4ED8 品牌深色变体(选中文字/边框) + * · dangerDark #F8A4A4 危险/拒绝在**深色**下当前景的取值 —— + * 对齐 WebUI `.dark --c-red-700`(`248 164 164`)。 + * ★ 入口 `Theme.dangerFor()`。 + * 不登记会怎样:深色下「归档」按钮只有 2.47:1 + * (2026-09-19 设备实测,联系人页 7 处) + * · approveDark #76DB9B 同意在**深色**下当前景的取值 —— + * 对齐 WebUI `.dark --c-green-700`。入口 `approveFor()`。 + * · warnFgDark #F9C368 警示文字在**深色**下的取值 —— + * 对齐 WebUI `.dark --c-amber-700`。入口 `warnFgFor()`。 * · accentDark #80AFF9 品牌色在**深色**下当前景用的取值 —— * 对齐 WebUI `index.css:481` 的 * `.dark --c-blue-600`(`128 175 249`)。 @@ -420,6 +470,45 @@ export class Theme { static readonly warnBg: string = '#FFFBEB'; static readonly warnFg: string = '#B45309'; + /* + * ──── 语义色的**深色档**(2026-09-19 加)──── + * + * ★★ 和 `accentDark` 完全同一个问题、同一套证据:WebUI 的语义色也是 + * **双通道**。浅色下 `red-700` 是 `185 28 28`(深红,给白底用); + * 深色下它被换成 `248 164 164`(**浅红**),否则深底上读不动。 + * + * 实测(设备,深色主题):联系人页的「归档」按钮是 + * `Theme.danger` = `#B91C1C` 压在卡片面上 ⇒ **2.47:1**, + * 低于 WCAG 图形下限 3:1。同样的还有日历页的 + * 「今天」标(`accentStrong` 底 + 深字)等 4 处。 + * + * ★ 取值直接对 WebUI `index.css` 的 `.dark` 段(`grep '^ --c-'` 可得): + * red-700 `248 164 164` + * green-700 `118 219 155` + * amber-700 `249 195 104` + * + * ★ 入口是下面那组 `*For(dark?)` —— 与 `accentFor` 同一条纪律: + * **别在页面里自己 `isDark ? a : b`**(约定已经漏过 8 处)。 + */ + static readonly dangerDark: string = '#F8A4A4'; + static readonly approveDark: string = '#76DB9B'; + static readonly warnFgDark: string = '#F9C368'; + + /** 按主题给拒绝/危险**前景**色(压在面上的红字/红图标)。 */ + static dangerFor(dark?: boolean): string { + return Theme.isDarkNow(dark) ? Theme.dangerDark : Theme.danger; + } + + /** 按主题给同意**前景**色。 */ + static approveFor(dark?: boolean): string { + return Theme.isDarkNow(dark) ? Theme.approveDark : Theme.approve; + } + + /** 按主题给警示**前景**色。 */ + static warnFgFor(dark?: boolean): string { + return Theme.isDarkNow(dark) ? Theme.warnFgDark : Theme.warnFg; + } + /** * 权限档位 → 徽标底色 / 文字色。 * diff --git a/client/harmony/entry/src/main/ets/pages/AdminUsersPage.ets b/client/harmony/entry/src/main/ets/pages/AdminUsersPage.ets index 0d9c631..d299ecc 100644 --- a/client/harmony/entry/src/main/ets/pages/AdminUsersPage.ets +++ b/client/harmony/entry/src/main/ets/pages/AdminUsersPage.ets @@ -288,13 +288,13 @@ struct AdminUsersPage { Text('管理入口仅管理员可见').fontSize(Theme.fontBody).fontColor(Theme.textMuted).margin({ left: 6 }) } Text('当前账号的角色不是管理员。') - .fontSize(Theme.fontSmall).fontColor(Theme.textSubtle).margin({ top: 6 }) + .fontSize(Theme.fontSmall).fontColor(Theme.textSubtleFor()).margin({ top: 6 }) } .width('100%').layoutWeight(1).justifyContent(FlexAlign.Center) } else { if (this.errorText.length > 0) { Text(this.errorText) - .fontSize(Theme.fontSmall).fontColor(Theme.danger) + .fontSize(Theme.fontSmall).fontColor(Theme.dangerFor()) .width('100%').padding(10).margin({ left: 12, right: 12, top: 8 }) .backgroundColor(Theme.dangerBg) .borderRadius(Theme.radiusControl) @@ -302,7 +302,7 @@ struct AdminUsersPage { Row() { Text('用户管理').fontSize(Theme.fontSmall).fontColor(Theme.textMuted) - Text(' ' + this.users.length).fontSize(Theme.fontSmall).fontColor(Theme.textSubtle) + Text(' ' + this.users.length).fontSize(Theme.fontSmall).fontColor(Theme.textSubtleFor()) Blank() Button(this.showCreate ? '收起' : '新建用户') .height(34).fontSize(Theme.fontSmall) @@ -316,7 +316,7 @@ struct AdminUsersPage { } if (this.loading && this.users.length === 0) { - Text('加载中…').fontSize(Theme.fontSmall).fontColor(Theme.textSubtle) + Text('加载中…').fontSize(Theme.fontSmall).fontColor(Theme.textSubtleFor()) .width('100%').textAlign(TextAlign.Center).margin({ top: 24 }) } else { List({ space: 8 }) { @@ -377,7 +377,7 @@ struct AdminUsersPage { Column() { Row() { Text(this.editingId === user.user_id ? '⌄' : '›') - .fontSize(Theme.fontSmall).fontColor(Theme.textSubtle).width(18) + .fontSize(Theme.fontSmall).fontColor(Theme.textSubtleFor()).width(18) Text(user.username) .fontSize(Theme.fontBody).fontColor(Theme.textPrimary).layoutWeight(1) .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }) @@ -404,10 +404,10 @@ struct AdminUsersPage { Row() { Text(user.display_name.length > 0 ? user.display_name : user.username) - .fontSize(Theme.fontSmall).fontColor(Theme.textSubtle).layoutWeight(1) + .fontSize(Theme.fontSmall).fontColor(Theme.textSubtleFor()).layoutWeight(1) .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }) Text(lastLoginLabel(user)) - .fontSize(Theme.fontTiny).fontColor(Theme.textSubtle) + .fontSize(Theme.fontTiny).fontColor(Theme.textSubtleFor()) } .width('100%').margin({ top: 4 }) @@ -467,14 +467,14 @@ struct AdminUsersPage { this.ScopePick('可访问目录', this.scopes.paths, this.editPaths, false) } else { Text('管理员不受白名单限制(服务端对管理员忽略这两项)。') - .fontSize(Theme.fontTiny).fontColor(Theme.textSubtle) + .fontSize(Theme.fontTiny).fontColor(Theme.textSubtleFor()) .width('100%').margin({ top: 8 }) } Row() { Button(user.status === 'active' ? '禁用' : '启用') .height(34).fontSize(Theme.fontSmall) - .backgroundColor(Theme.surfaceMuted).fontColor(Theme.danger) + .backgroundColor(Theme.surfaceMuted).fontColor(Theme.dangerFor()) .onClick(() => { this.setStatus(user, user.status === 'active' ? 'disabled' : 'active'); }) @@ -530,14 +530,14 @@ struct AdminUsersPage { Row() { Text(label).fontSize(Theme.fontTiny).fontColor(Theme.textMuted) Text(selected.length > 0 ? '(已选 ' + selected.length + ' 项)' : '') - .fontSize(Theme.fontTiny).fontColor(Theme.textSubtle) + .fontSize(Theme.fontTiny).fontColor(Theme.textSubtleFor()) Blank() - Text('未勾选 = 不限').fontSize(Theme.fontTiny).fontColor(Theme.textSubtle) + Text('未勾选 = 不限').fontSize(Theme.fontTiny).fontColor(Theme.textSubtleFor()) } .width('100%').margin({ top: 10 }) if (items.length === 0) { - Text('(服务端没给出候选)').fontSize(Theme.fontTiny).fontColor(Theme.textSubtle) + Text('(服务端没给出候选)').fontSize(Theme.fontTiny).fontColor(Theme.textSubtleFor()) .width('100%').margin({ top: 4 }) } else { Flex({ wrap: FlexWrap.Wrap }) { @@ -600,7 +600,7 @@ struct AdminUsersPage { .width('100%').margin({ top: 10 }) Text('白名单建完再配:创建后在新用户那张卡里勾「可调用 Agent / 可访问目录」。未勾 = 不限。') - .fontSize(Theme.fontTiny).fontColor(Theme.textSubtle) + .fontSize(Theme.fontTiny).fontColor(Theme.textSubtleFor()) .width('100%').margin({ top: 8 }) Row() { diff --git a/client/harmony/entry/src/main/ets/pages/CalendarPage.ets b/client/harmony/entry/src/main/ets/pages/CalendarPage.ets index 8c30b24..56505d5 100644 --- a/client/harmony/entry/src/main/ets/pages/CalendarPage.ets +++ b/client/harmony/entry/src/main/ets/pages/CalendarPage.ets @@ -862,7 +862,20 @@ export struct CalendarPage { */ Text(this.lunarTextOf(cell.iso)) .fontSize(9) - .fontColor(cell.iso === this.selectedIso ? Theme.accentFg : Theme.textSubtle) + /* + * ★ ★ 2026-09-19 改:`textSubtle`(系统三级文字)在**深色**下太暗 —— + * 设备实测农历"二十"只有 **2.49:1**(`rgb(93,95,96)` 底上)。 + * + * WebUI 用的是 `text-gray-500`(`CalendarView.tsx` 农历那行), + * 而它在 `.dark` 段被**提亮**到 `165 173 186`(见 `index.css`)—— + * 正是 `--c-gray-*` 那组注释说的"深底上的浅色 gray-400 只有约 2:1, + * 远低于 WCAG AA,看得见但读不动"。 + * + * 系统三级色没有这层适配(它只管"比二级更淡",不管可读性下限), + * ⇒ 深色下用它当**小字**会掉到 2.5:1。改用二级(`textMuted`), + * 与 WebUI 的 gray-500 档位一致。 + */ + .fontColor(cell.iso === this.selectedIso ? Theme.accentFg : Theme.textMuted) .maxLines(1) if (this.eventCountOf(cell.iso) > 0) { Column() @@ -1077,7 +1090,7 @@ export struct CalendarPage { if (this.formError.length > 0) { Text(this.formError) .fontSize(Theme.fontSmall) - .fontColor(Theme.danger) + .fontColor(Theme.dangerFor()) .width('100%') .margin({ top: 8 }) } @@ -1094,7 +1107,7 @@ export struct CalendarPage { Button(this.confirmDelete ? '确认删除' : '删除') .fontSize(Theme.fontSmall) .backgroundColor(Theme.dangerBg) - .fontColor(Theme.danger) + .fontColor(Theme.dangerFor()) .margin({ left: 6 }) .onClick(() => { if (this.confirmDelete) { @@ -1177,10 +1190,26 @@ export struct CalendarPage { .padding({ left: 6, right: 6, top: 4, bottom: 0 }) Row() { + /* + * ★★ 2026-09-19 改:这个按钮**不该是品牌蓝**。 + * + * WebUI 原样(`CalendarView.tsx:313-316`): + * className="px-2.5 py-1 text-xs border border-gray-300 + * rounded hover:bg-gray-50 text-gray-700" + * ⇒ 是**中性灰**(白底/灰边/深灰字),不是蓝底。 + * + * 鸿蒙这边原来写成 `accentSoft` 底 + `accentStrong` 字 —— + * 把"次要的中性按钮"画成了"品牌色按钮",视觉重量跟主操作一样。 + * + * ★ 这也是设备实测发现的:深色下它读出来 2.38:1 + * (`accentStrong` #1D4ED8 压在 `accentSoftDark` #1C2536 上), + * 而根因不是"选错了深色档",是**这一整块的配色语义就抄错了**。 + */ Button('今天') .fontSize(Theme.fontSmall) - .backgroundColor(Theme.accentSoftFor(this.isDarkNow)) - .fontColor(Theme.accentStrong) + .backgroundColor(Color.Transparent) + .fontColor(Theme.textPrimary) + .border({ width: 1, color: Theme.border }) .onClick(() => this.goToday()) Blank() /* @@ -1191,7 +1220,15 @@ export struct CalendarPage { Text(scaleLabel(s)) .fontSize(Theme.fontSmall) .fontColor(this.calScale === s ? Theme.accentFg : Theme.textPrimary) - .backgroundColor(this.calScale === s ? Theme.accent : Theme.accentSoftFor(this.isDarkNow)) + /* + * ★ 未选中档的底色是 **`surface`(面)**,不是 `accentSoft`。 + * WebUI 原样(`CalendarView.tsx:331`): + * 未选中 `bg-white text-gray-700` / 选中 `bg-blue-600 text-white` + * `bg-white` 在这套 token 里就是"卡片面"。 + * 原来用 `accentSoft` 等于把未选中项也染成品牌浅蓝 —— + * 三个档看起来"都像选中",只剩深浅差别。 + */ + .backgroundColor(this.calScale === s ? Theme.accent : Theme.surface) .textAlign(TextAlign.Center) .width(34) .height(26) @@ -1294,7 +1331,7 @@ export struct CalendarPage { if (this.loading) { Text('加载中…') .fontSize(Theme.fontTiny) - .fontColor(Theme.textSubtle) + .fontColor(Theme.textSubtleFor()) } } .width('100%') @@ -1310,7 +1347,7 @@ export struct CalendarPage { Column() { Text('这一天没有日程') .fontSize(Theme.fontSmall) - .fontColor(Theme.textSubtle) + .fontColor(Theme.textSubtleFor()) } .width('100%') .layoutWeight(1) diff --git a/client/harmony/entry/src/main/ets/pages/ComposePage.ets b/client/harmony/entry/src/main/ets/pages/ComposePage.ets index 033afdb..73fa783 100644 --- a/client/harmony/entry/src/main/ets/pages/ComposePage.ets +++ b/client/harmony/entry/src/main/ets/pages/ComposePage.ets @@ -209,7 +209,7 @@ struct ComposePage { // 顶栏 Row() { Text('取消') - .fontSize(15).fontColor(Theme.danger) + .fontSize(15).fontColor(Theme.dangerFor()) .onClick(() => { this.getUIContext().getRouter().back(); }) Blank() Text('写邮件').fontSize(16).fontWeight(FontWeight.Bold).fontColor(Theme.textPrimary) @@ -236,13 +236,13 @@ struct ComposePage { // 发信账号选择(多账号时显示) if (this.accountList.length > 1) { Row() { - Text('发信账号').fontSize(13).fontColor(Theme.textSubtle).width(72) + Text('发信账号').fontSize(13).fontColor(Theme.textSubtleFor()).width(72) // 显示当前选中的账号名 Text(this.getSelectedAccountName()) .fontSize(13).fontColor(Theme.accentFor()) .layoutWeight(1) .onClick(() => { this.showAccountPicker = !this.showAccountPicker; }) - Text(' ▾').fontSize(12).fontColor(Theme.textSubtle) + Text(' ▾').fontSize(12).fontColor(Theme.textSubtleFor()) .onClick(() => { this.showAccountPicker = !this.showAccountPicker; }) } .width('100%').height(40).padding({ left: 12, right: 12 }) @@ -288,7 +288,7 @@ struct ComposePage { // 收件人 Row() { - Text('收件人').fontSize(14).fontColor(Theme.textSubtle).width(60) + Text('收件人').fontSize(14).fontColor(Theme.textSubtleFor()).width(60) TextInput({ placeholder: 'name@path.session', text: this.to }) .layoutWeight(1).fontSize(14).backgroundColor(Color.Transparent) .onChange((v: string) => { this.to = v; }) @@ -300,7 +300,7 @@ struct ComposePage { // 主题 Row() { - Text('主题').fontSize(14).fontColor(Theme.textSubtle).width(60) + Text('主题').fontSize(14).fontColor(Theme.textSubtleFor()).width(60) TextInput({ placeholder: '邮件主题', text: this.subject }) .layoutWeight(1).fontSize(14).backgroundColor(Color.Transparent) .onChange((v: string) => { this.subject = v; }) @@ -328,7 +328,7 @@ struct ComposePage { .margin({ left: 6 }) .layoutWeight(1) .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }) - AmIcon({ iconName: 'close', iconSize: 14, iconColor: Theme.danger }) + AmIcon({ iconName: 'close', iconSize: 14, iconColor: Theme.dangerFor() }) .onClick(() => { this.removeAttachment(idx); }) } .width('100%').height(32) @@ -354,7 +354,7 @@ struct ComposePage { .margin({ left: 12 }) } else if (this.status.length > 0) { Text(this.status) - .fontSize(11).fontColor(Theme.approve) + .fontSize(11).fontColor(Theme.approveFor()) .margin({ left: 12 }) } } diff --git a/client/harmony/entry/src/main/ets/pages/InboxPage.ets b/client/harmony/entry/src/main/ets/pages/InboxPage.ets index 1ae39be..b83b63b 100644 --- a/client/harmony/entry/src/main/ets/pages/InboxPage.ets +++ b/client/harmony/entry/src/main/ets/pages/InboxPage.ets @@ -110,20 +110,20 @@ struct InboxPage { if (this.loading) { Column() { LoadingProgress().width(40).height(40) - Text('加载中…').fontSize(14).fontColor(Theme.textSubtle).margin({ top: 8 }) + Text('加载中…').fontSize(14).fontColor(Theme.textSubtleFor()).margin({ top: 8 }) } .width('100%').height('80%') .justifyContent(FlexAlign.Center) } else if (this.error.length > 0) { Column() { - Text(this.error).fontSize(14).fontColor(Theme.danger) + Text(this.error).fontSize(14).fontColor(Theme.dangerFor()) Button('重试').margin({ top: 12 }).onClick(() => { this.loadInbox(); }) } .width('100%').height('80%') .justifyContent(FlexAlign.Center) } else if (this.mails.length === 0) { Column() { - Text('收件箱为空').fontSize(16).fontColor(Theme.textSubtle) + Text('收件箱为空').fontSize(16).fontColor(Theme.textSubtleFor()) } .width('100%').height('80%') .justifyContent(FlexAlign.Center) @@ -204,14 +204,14 @@ struct InboxPage { Row() { Text(mail.from_name).fontSize(14).fontWeight(mail.is_read ? FontWeight.Normal : FontWeight.Bold).fontColor(Theme.textPrimary) Blank() - Text(mail.created_at).fontSize(11).fontColor(Theme.textSubtle) + Text(mail.created_at).fontSize(11).fontColor(Theme.textSubtleFor()) } .width('100%') Text(mail.subject).fontSize(13).fontColor(Theme.textPrimary) .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }) .margin({ top: 2 }) - Text(mail.body_preview).fontSize(12).fontColor(Theme.textSubtle) + Text(mail.body_preview).fontSize(12).fontColor(Theme.textSubtleFor()) .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }) .margin({ top: 2 }) } diff --git a/client/harmony/entry/src/main/ets/pages/LoginPage.ets b/client/harmony/entry/src/main/ets/pages/LoginPage.ets index 32b7c4d..524cadd 100644 --- a/client/harmony/entry/src/main/ets/pages/LoginPage.ets +++ b/client/harmony/entry/src/main/ets/pages/LoginPage.ets @@ -321,7 +321,7 @@ struct LoginPage { if (this.addrWarning.length > 0) { Text(this.addrWarning) .fontSize(11) - .fontColor(Theme.warnFg) + .fontColor(Theme.warnFgFor()) .width('100%') .margin({ top: 6 }) } @@ -383,7 +383,7 @@ struct LoginPage { .type(InputType.Password) .onChange((v: string) => { this.userKey = v; }) Text('密钥仅保存在本机,用于客户端访问你的邮箱。') - .width('100%').fontSize(10).fontColor(Theme.textSubtle) + .width('100%').fontSize(10).fontColor(Theme.textSubtleFor()) .lineHeight(15).margin({ top: 5 }) } @@ -405,7 +405,7 @@ struct LoginPage { Text('地址须以 /api/v1 结尾;模拟器 NAT 不通时可用 ' + EMULATOR_HOST_BASE) .width('100%') .fontSize(10) - .fontColor(Theme.textSubtle) + .fontColor(Theme.textSubtleFor()) .textAlign(TextAlign.Center) .lineHeight(15) .margin({ top: 12 }) diff --git a/client/harmony/entry/src/main/ets/pages/MailDetailPage.ets b/client/harmony/entry/src/main/ets/pages/MailDetailPage.ets index 8844603..3835377 100644 --- a/client/harmony/entry/src/main/ets/pages/MailDetailPage.ets +++ b/client/harmony/entry/src/main/ets/pages/MailDetailPage.ets @@ -357,7 +357,7 @@ export struct MailDetailView { MetaRow(label: string, value: string) { Row() { Text(label) - .fontSize(11).fontColor(Theme.textSubtle) + .fontSize(11).fontColor(Theme.textSubtleFor()) .width(36).flexShrink(0) Text(value) .fontSize(12).fontColor(Theme.textMuted) @@ -380,7 +380,7 @@ export struct MailDetailView { PermissionRow() { Row() { Text('权限') - .fontSize(11).fontColor(Theme.textSubtle) + .fontSize(11).fontColor(Theme.textSubtleFor()) .width(36).flexShrink(0) if (this.switchingPerm) { LoadingProgress().width(16).height(16) @@ -414,12 +414,12 @@ export struct MailDetailView { BudgetRow() { Row() { Text('预算') - .fontSize(11).fontColor(Theme.textSubtle) + .fontSize(11).fontColor(Theme.textSubtleFor()) .width(36).flexShrink(0) if (this.budget === null) { /* 还没读到就不显示 —— 不为一块可选信息留空壳 */ - Text('—').fontSize(11).fontColor(Theme.textSubtle) + Text('—').fontSize(11).fontColor(Theme.textSubtleFor()) } else if (this.budgetEditing) { /* 编辑态:空串 = 不限(WebUI 同口径,`placeholder` 也是「不限」) */ TextInput({ text: this.budgetDraft, placeholder: '不限' }) @@ -453,7 +453,7 @@ export struct MailDetailView { */ Text(this.budgetLabel()) .fontSize(11) - .fontColor(this.budgetExhausted() ? Theme.danger : Theme.textMuted) + .fontColor(this.budgetExhausted() ? Theme.dangerFor() : Theme.textMuted) .backgroundColor(this.budgetExhausted() ? Theme.dangerBg : Theme.chipNeutralBg) .borderRadius(4) .padding({ left: 8, right: 8, top: 3, bottom: 3 }) @@ -577,13 +577,13 @@ export struct MailDetailView { } if (this.mailType === 'permission_request') { Text('权限请求') - .fontSize(10).fontColor(Theme.warnFg) + .fontSize(10).fontColor(Theme.warnFgFor()) .backgroundColor(Theme.warnBg).borderRadius(4) .padding({ left: 6, right: 6, top: 2, bottom: 2 }) .margin({ left: 6 }) } - AmIcon({ iconName: 'chevronRight', iconSize: 16, iconColor: Theme.textSubtle }) + AmIcon({ iconName: 'chevronRight', iconSize: 16, iconColor: Theme.textSubtleFor() }) .rotate({ angle: this.headerOpen ? 90 : 0 }) .margin({ left: 6 }) } @@ -636,13 +636,13 @@ export struct MailDetailView { if (this.loading) { Column() { LoadingProgress().width(40).height(40) - Text('加载中…').fontSize(14).fontColor(Theme.textSubtle).margin({ top: 8 }) + Text('加载中…').fontSize(14).fontColor(Theme.textSubtleFor()).margin({ top: 8 }) } .width('100%').layoutWeight(1) .justifyContent(FlexAlign.Center) } else if (this.error.length > 0) { Column() { - Text(this.error).fontSize(14).fontColor(Theme.danger) + Text(this.error).fontSize(14).fontColor(Theme.dangerFor()) Button('重试').margin({ top: 12 }).onClick(() => { this.loadMail(this.mailId); }) } .width('100%').layoutWeight(1) diff --git a/client/harmony/entry/src/main/ets/pages/MainPage.ets b/client/harmony/entry/src/main/ets/pages/MainPage.ets index 5e92f34..0296d94 100644 --- a/client/harmony/entry/src/main/ets/pages/MainPage.ets +++ b/client/harmony/entry/src/main/ets/pages/MainPage.ets @@ -409,7 +409,7 @@ struct InboxTab { Row() { Text(this.accountFilter === 'all' ? '全部邮箱' : this.accountName) .fontSize(11).fontColor(Theme.textMuted) - AmIcon({ iconName: 'chevronRight', iconSize: 12, iconColor: Theme.textSubtle }) + AmIcon({ iconName: 'chevronRight', iconSize: 12, iconColor: Theme.textSubtleFor() }) .rotate({ angle: 90 }) .margin({ left: 3 }) } @@ -456,7 +456,7 @@ struct InboxTab { .fontColor(this.accountFilter === acct.id ? Theme.accent : Theme.textPrimary) .fontWeight(this.accountFilter === acct.id ? FontWeight.Bold : FontWeight.Normal) Text(acct.username) - .fontSize(11).fontColor(Theme.textSubtle) + .fontSize(11).fontColor(Theme.textSubtleFor()) } .alignItems(HorizontalAlign.Start) .layoutWeight(1) @@ -483,13 +483,13 @@ struct InboxTab { .width('100%').layoutWeight(1).justifyContent(FlexAlign.Center) } else if (this.error.length > 0) { Column() { - Text(this.error).fontSize(14).fontColor(Theme.danger) + Text(this.error).fontSize(14).fontColor(Theme.dangerFor()) Button('重试').margin({ top: 12 }).onClick(() => { this.loadData(); }) } .width('100%').layoutWeight(1).justifyContent(FlexAlign.Center) } else if (this.mails.length === 0) { Column() { - Text('收件箱为空').fontSize(16).fontColor(Theme.textSubtle) + Text('收件箱为空').fontSize(16).fontColor(Theme.textSubtleFor()) } .width('100%').layoutWeight(1).justifyContent(FlexAlign.Center) } else { @@ -568,7 +568,7 @@ struct InboxTab { * `rotate` 留在图标上(转的是箭头本身,不是那 20vp 的槽位)。 */ Stack({ alignContent: Alignment.Center }) { - AmIcon({ iconName: 'chevronRight', iconSize: 12, iconColor: Theme.textSubtle }) + AmIcon({ iconName: 'chevronRight', iconSize: 12, iconColor: Theme.textSubtleFor() }) .rotate({ angle: this.isExpanded(g.key) ? 90 : 0 }) } .width(20) @@ -579,7 +579,7 @@ struct InboxTab { .fontColor(Theme.textPrimary) .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }).layoutWeight(1) Text(compactMailTime(g.latest?.created_at ?? '')) - .fontSize(10).fontColor(Theme.textSubtle).margin({ left: 8 }) + .fontSize(10).fontColor(Theme.textSubtleFor()).margin({ left: 8 }) } .width('100%') Row() { @@ -600,7 +600,7 @@ struct InboxTab { Text(g.alias.length > 0 ? '.' + g.alias : '(未命名会话)') .fontSize(10).fontColor(Theme.accentFor()) .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }).layoutWeight(1) - Text(g.mails.length + ' 封').fontSize(10).fontColor(Theme.textSubtle).margin({ left: 8 }) + Text(g.mails.length + ' 封').fontSize(10).fontColor(Theme.textSubtleFor()).margin({ left: 8 }) } .width('100%').margin({ top: 2 }) } @@ -639,14 +639,14 @@ struct InboxTab { .margin({ left: 6 }) } Blank() - Text(compactMailTime(mail.created_at)).fontSize(10).fontColor(Theme.textSubtle) + Text(compactMailTime(mail.created_at)).fontSize(10).fontColor(Theme.textSubtleFor()) } .width('100%') Text(mail.subject).fontSize(13).fontColor(Theme.textPrimary) .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }) .margin({ top: 2 }) - Text(mail.body_preview).fontSize(12).fontColor(Theme.textSubtle) + Text(mail.body_preview).fontSize(12).fontColor(Theme.textSubtleFor()) .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }) .margin({ top: 2 }) /* @@ -668,7 +668,7 @@ struct InboxTab { */ if (mail.cc_count > 0) { Text('抄送 ' + mail.cc_count) - .fontSize(10).fontColor(Theme.textSubtle) + .fontSize(10).fontColor(Theme.textSubtleFor()) .margin({ top: 3 }) } } @@ -811,7 +811,7 @@ struct SentTab { .fontSize(13).fontWeight(FontWeight.Medium).fontColor(Theme.textPrimary) .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }) .layoutWeight(1) - Text(mail.created_at).fontSize(10).fontColor(Theme.textSubtle) + Text(mail.created_at).fontSize(10).fontColor(Theme.textSubtleFor()) } .width('100%') @@ -846,7 +846,7 @@ struct SentTab { .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }) .layoutWeight(1) Blank() - Text(g.mails.length + ' 封').fontSize(11).fontColor(Theme.textSubtle) + Text(g.mails.length + ' 封').fontSize(11).fontColor(Theme.textSubtleFor()) } .width('100%').margin({ top: 2 }) } @@ -868,7 +868,7 @@ struct SentTab { Text('发件箱').fontSize(20).fontWeight(FontWeight.Bold).fontColor(Theme.textPrimary) Blank() if (this.loaded > 0) { - Text(this.loaded + ' 封').fontSize(12).fontColor(Theme.textSubtle) + Text(this.loaded + ' 封').fontSize(12).fontColor(Theme.textSubtleFor()) } } .width('100%').height(56).padding({ left: 16, right: 16 }) @@ -899,7 +899,7 @@ struct SentTab { Column() { LoadingProgress().width(32).height(32) } .width('100%').layoutWeight(1).justifyContent(FlexAlign.Center) } else if (this.error.length > 0) { - Column() { Text(this.error).fontSize(13).fontColor(Theme.danger) } + Column() { Text(this.error).fontSize(13).fontColor(Theme.dangerFor()) } .width('100%').layoutWeight(1).justifyContent(FlexAlign.Center) } else if (this.groups.length === 0) { /* @@ -908,9 +908,9 @@ struct SentTab { * 副句说清这一栏是干什么的。 */ Column() { - AmIcon({ iconName: 'sent', iconSize: 36, iconColor: Theme.textSubtle }).margin({ bottom: 8 }) + AmIcon({ iconName: 'sent', iconSize: 36, iconColor: Theme.textSubtleFor() }).margin({ bottom: 8 }) Text(emptyTitle('sent')).fontSize(15).fontColor(Theme.textMuted) - Text(emptyHint('sent')).fontSize(12).fontColor(Theme.textSubtle).margin({ top: 6 }) + Text(emptyHint('sent')).fontSize(12).fontColor(Theme.textSubtleFor()).margin({ top: 6 }) } .width('100%').layoutWeight(1).justifyContent(FlexAlign.Center) } else { @@ -1092,12 +1092,12 @@ struct PermissionTab { if (req.context.length > 0) { Text(req.context) - .fontSize(11).fontColor(Theme.textSubtle) + .fontSize(11).fontColor(Theme.textSubtleFor()) .maxLines(3).textOverflow({ overflow: TextOverflow.Ellipsis }) .margin({ top: 4 }) } - Text(req.created_at).fontSize(10).fontColor(Theme.textSubtle).margin({ top: 6 }) + Text(req.created_at).fontSize(10).fontColor(Theme.textSubtleFor()).margin({ top: 6 }) if (this.noteFor === req.mail_id) { TextInput({ placeholder: '备注(会随决策一起送给 Agent)', text: this.noteText }) @@ -1113,7 +1113,7 @@ struct PermissionTab { Text('拒绝') .fontSize(13).height(36).layoutWeight(1) .textAlign(TextAlign.Center) - .backgroundColor(Theme.dangerBg).fontColor(Theme.danger) + .backgroundColor(Theme.dangerBg).fontColor(Theme.dangerFor()) .borderRadius(Theme.radiusControl) // 拒绝先展开备注框,而不是直接拒:拒绝往往要说明理由,而理由是给模型看的 .onClick(() => { @@ -1130,7 +1130,7 @@ struct PermissionTab { if (this.noteFor === req.mail_id) { Text(this.noteText.length > 0 ? '再点一次「拒绝」即送出(带备注)' : '可填备注,再点一次「拒绝」送出') - .fontSize(10).fontColor(Theme.textSubtle).margin({ top: 4 }) + .fontSize(10).fontColor(Theme.textSubtleFor()).margin({ top: 4 }) } } .width('100%').alignItems(HorizontalAlign.Start) @@ -1182,13 +1182,13 @@ struct PermissionTab { Column() { LoadingProgress().width(32).height(32) } .width('100%').layoutWeight(1).justifyContent(FlexAlign.Center) } else if (this.error.length > 0) { - Column() { Text(this.error).fontSize(13).fontColor(Theme.danger) } + Column() { Text(this.error).fontSize(13).fontColor(Theme.dangerFor()) } .width('100%').layoutWeight(1).justifyContent(FlexAlign.Center) } else if (this.requests.length === 0) { Column() { - AmIcon({ iconName: 'shield', iconSize: 36, iconColor: Theme.textSubtle }).margin({ bottom: 8 }) + AmIcon({ iconName: 'shield', iconSize: 36, iconColor: Theme.textSubtleFor() }).margin({ bottom: 8 }) Text(emptyTitle('permissions')).fontSize(15).fontColor(Theme.textMuted) - Text(emptyHint('permissions')).fontSize(12).fontColor(Theme.textSubtle).margin({ top: 6 }) + Text(emptyHint('permissions')).fontSize(12).fontColor(Theme.textSubtleFor()).margin({ top: 6 }) } .width('100%').layoutWeight(1).justifyContent(FlexAlign.Center) } else { @@ -1756,13 +1756,13 @@ struct ContactsTab { .width('100%').layoutWeight(1).justifyContent(FlexAlign.Center) } else if (this.error.length > 0) { Column() { - Text(this.error).fontSize(14).fontColor(Theme.danger) + Text(this.error).fontSize(14).fontColor(Theme.dangerFor()) Button('重试').margin({ top: 12 }).onClick(() => { this.loadData(); }) } .width('100%').layoutWeight(1).justifyContent(FlexAlign.Center) } else if (this.contacts.length === 0) { Column() { - Text('暂无联系人').fontSize(16).fontColor(Theme.textSubtle) + Text('暂无联系人').fontSize(16).fontColor(Theme.textSubtleFor()) } .width('100%').layoutWeight(1).justifyContent(FlexAlign.Center) } else if (this.contactView === 'card') { @@ -1873,7 +1873,7 @@ struct ContactsTab { .width('100%').layoutWeight(1).justifyContent(FlexAlign.Center) } else if (this.sessionMails.length === 0) { Column() { - Text('这条会话没有邮件').fontSize(14).fontColor(Theme.textSubtle) + Text('这条会话没有邮件').fontSize(14).fontColor(Theme.textSubtleFor()) } .width('100%').layoutWeight(1).justifyContent(FlexAlign.Center) } else { @@ -1887,7 +1887,7 @@ struct ContactsTab { .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }) Blank() Text(compactMailTime(m.created_at)) - .fontSize(10).fontColor(Theme.textSubtle) + .fontSize(10).fontColor(Theme.textSubtleFor()) } .width('100%') Text(m.subject) @@ -1897,7 +1897,7 @@ struct ContactsTab { .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }) .width('100%').margin({ top: 4 }) Text(m.body_preview) - .fontSize(11).fontColor(Theme.textSubtle) + .fontSize(11).fontColor(Theme.textSubtleFor()) .maxLines(2).textOverflow({ overflow: TextOverflow.Ellipsis }) .width('100%').margin({ top: 2 }) } @@ -1935,7 +1935,7 @@ struct ContactsTab { .fontSize(13).fontWeight(FontWeight.Bold).fontColor(Theme.textPrimary) .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }) Text(c.path) - .fontSize(11).fontColor(Theme.textSubtle) + .fontSize(11).fontColor(Theme.textSubtleFor()) .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }) .margin({ left: 6 }).layoutWeight(1) if (c.unread_count > 0) { @@ -1981,7 +1981,7 @@ struct ContactsTab { Row() { Text(c.mail_count + ' 封 · ' + shortTimeOf(c.last_activity)) - .fontSize(11).fontColor(Theme.textSubtle) + .fontSize(11).fontColor(Theme.textSubtleFor()) Blank() /* * 权限档位徽标:**档位 + 强制力标记**,点它弹出"平台实际做到了什么"。 @@ -2048,11 +2048,11 @@ struct ContactsTab { AmIcon({ iconName: iconName, iconSize: 12, - iconColor: tone === 'danger' ? Theme.danger : Theme.textMuted + iconColor: tone === 'danger' ? Theme.dangerFor() : Theme.textMuted }) Text(label) .fontSize(11) - .fontColor(tone === 'danger' ? Theme.danger : Theme.textMuted) + .fontColor(tone === 'danger' ? Theme.dangerFor() : Theme.textMuted) .margin({ left: 4 }) } .height(26) @@ -2078,7 +2078,7 @@ struct ContactsTab { .maxLines(2).textOverflow({ overflow: TextOverflow.Ellipsis }) .width('100%') Text('对应 Agent 的 session 将被归档,此列表与邮箱界面同时移除') - .fontSize(11).fontColor(Theme.textSubtle) + .fontSize(11).fontColor(Theme.textSubtleFor()) .margin({ top: 3 }) .width('100%') Row() { @@ -2147,7 +2147,7 @@ struct ContactsTab { * 于是同一个联系人在两种视图里给出的关键信息不一致。 */ Text(c.mail_count + ' 封 · ' + shortTimeOf(c.last_activity)) - .fontSize(11).fontColor(Theme.textSubtle) + .fontSize(11).fontColor(Theme.textSubtleFor()) .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }) } .width('100%').margin({ top: 3 }) diff --git a/client/harmony/entry/src/main/ets/pages/SessionsPage.ets b/client/harmony/entry/src/main/ets/pages/SessionsPage.ets index 81fbf06..210d07b 100644 --- a/client/harmony/entry/src/main/ets/pages/SessionsPage.ets +++ b/client/harmony/entry/src/main/ets/pages/SessionsPage.ets @@ -74,20 +74,20 @@ struct SessionsPage { if (this.loading) { Column() { LoadingProgress().width(40).height(40) - Text('加载中…').fontSize(14).fontColor(Theme.textSubtle).margin({ top: 8 }) + Text('加载中…').fontSize(14).fontColor(Theme.textSubtleFor()).margin({ top: 8 }) } .width('100%').layoutWeight(1) .justifyContent(FlexAlign.Center) } else if (this.error.length > 0) { Column() { - Text(this.error).fontSize(14).fontColor(Theme.danger) + Text(this.error).fontSize(14).fontColor(Theme.dangerFor()) Button('重试').margin({ top: 12 }).onClick(() => { this.loadSessions(); }) } .width('100%').layoutWeight(1) .justifyContent(FlexAlign.Center) } else if (this.sessions.length === 0) { Column() { - Text('暂无会话').fontSize(16).fontColor(Theme.textSubtle) + Text('暂无会话').fontSize(16).fontColor(Theme.textSubtleFor()) } .width('100%').layoutWeight(1) .justifyContent(FlexAlign.Center) @@ -108,7 +108,7 @@ struct SessionsPage { // 底栏 Row() { - Text('共 ' + this.sessions.length + ' 个会话').fontSize(12).fontColor(Theme.textSubtle) + Text('共 ' + this.sessions.length + ' 个会话').fontSize(12).fontColor(Theme.textSubtleFor()) } .width('100%').height(40).padding({ left: 16 }) .backgroundColor(Theme.surface) @@ -130,7 +130,7 @@ struct SessionsPage { // 预算进度 if (s.max_rounds > 0) { Text(s.used_rounds + '/' + s.max_rounds) - .fontSize(11).fontColor(s.used_rounds >= s.max_rounds ? Theme.danger : Theme.textMuted) + .fontSize(11).fontColor(s.used_rounds >= s.max_rounds ? Theme.dangerFor() : Theme.textMuted) .backgroundColor(s.used_rounds >= s.max_rounds ? Theme.dangerBg : Theme.surfaceMuted) .borderRadius(4).padding({ left: 4, right: 4, top: 1, bottom: 1 }) } @@ -155,8 +155,8 @@ struct SessionsPage { .backgroundColor(Theme.border).borderRadius(4) .padding({ left: 4, right: 4, top: 1, bottom: 1 }) Blank() - Text(s.mail_count + ' 封').fontSize(11).fontColor(Theme.textSubtle) - Text(' · ' + s.status).fontSize(11).fontColor(s.status === 'active' ? Theme.approve : Theme.textSubtle) + Text(s.mail_count + ' 封').fontSize(11).fontColor(Theme.textSubtleFor()) + Text(' · ' + s.status).fontSize(11).fontColor(s.status === 'active' ? Theme.approveFor() : Theme.textSubtle) } .width('100%').margin({ top: 4 }) } diff --git a/client/harmony/entry/src/main/ets/pages/SettingsPage.ets b/client/harmony/entry/src/main/ets/pages/SettingsPage.ets index 6081f15..c0b7486 100644 --- a/client/harmony/entry/src/main/ets/pages/SettingsPage.ets +++ b/client/harmony/entry/src/main/ets/pages/SettingsPage.ets @@ -754,7 +754,7 @@ export struct SettingsPane { InfoRow(label: string, value: string) { Row() { Text(label) - .fontSize(12).fontColor(Theme.textSubtle) + .fontSize(12).fontColor(Theme.textSubtleFor()) .width(80).flexShrink(0) Text(value) .fontSize(14).fontColor(Theme.textPrimary) @@ -823,7 +823,7 @@ export struct SettingsPane { Blank() Text(statusLabel(this.appearanceStatus)) .fontSize(11) - .fontColor(this.appearanceStatus === 'synced' ? Theme.textMuted : Theme.warnFg) + .fontColor(this.appearanceStatus === 'synced' ? Theme.textMuted : Theme.warnFgFor()) } .width('100%') @@ -842,7 +842,7 @@ export struct SettingsPane { .width('100%').margin({ top: 10 }) Text('主题由系统按色彩模式给色(深浅两套不靠手写色值);换账号时外观跟着账号走。') - .fontSize(11).fontColor(Theme.textSubtle).margin({ top: 6 }) + .fontSize(11).fontColor(Theme.textSubtleFor()).margin({ top: 6 }) Divider().color(Theme.border).margin({ top: 12, bottom: 12 }) @@ -889,7 +889,7 @@ export struct SettingsPane { ? '服务器未配置推送' : (this.pushServerStatus === 'server-on' ? '服务器已开启推送通道' : '推送通道状态未知')) .fontSize(11) - .fontColor(this.pushServerStatus === 'server-on' ? Theme.textMuted : Theme.warnFg) + .fontColor(this.pushServerStatus === 'server-on' ? Theme.textMuted : Theme.warnFgFor()) .margin({ top: 2 }) } .layoutWeight(1).alignItems(HorizontalAlign.Start) @@ -902,7 +902,7 @@ export struct SettingsPane { .width('100%') Text('新邮件到达时由系统弹通知(点通知直达邮件)。未配置服务端推送时请保持关闭。') - .fontSize(11).fontColor(Theme.textSubtle).margin({ top: 6 }) + .fontSize(11).fontColor(Theme.textSubtleFor()).margin({ top: 6 }) } .width('100%').alignItems(HorizontalAlign.Start) .padding(16).margin({ top: 8 }) @@ -928,11 +928,11 @@ export struct SettingsPane { .width('100%') Text('第三方客户端用该密钥访问自己的邮箱(Authorization: Bearer)。它不能用于注册 Agent。') - .fontSize(11).fontColor(Theme.textSubtle).margin({ top: 6 }) + .fontSize(11).fontColor(Theme.textSubtleFor()).margin({ top: 6 }) if (this.keysError.length > 0) { Text(this.keysError) - .fontSize(12).fontColor(Theme.danger).margin({ top: 8 }) + .fontSize(12).fontColor(Theme.dangerFor()).margin({ top: 8 }) } /* @@ -944,7 +944,7 @@ export struct SettingsPane { if (this.newKeyToken.length > 0) { Column() { Text('密钥已创建。全文仅显示这一次,关闭后无法再次查看。') - .fontSize(11).fontColor(Theme.warnFg) + .fontSize(11).fontColor(Theme.warnFgFor()) .width('100%') Text(this.newKeyToken) .fontSize(11).fontColor(Theme.textPrimary) @@ -1043,7 +1043,7 @@ export struct SettingsPane { .margin({ right: 12 }) Text('吹销') - .fontSize(12).fontColor(Theme.danger) + .fontSize(12).fontColor(Theme.dangerFor()) .onClick(() => { this.revokeKey(k.key_id); }) } .width('100%') @@ -1088,11 +1088,11 @@ export struct SettingsPane { /* 两次不一致就地提示(与 WebUI `mismatch` 同一行为,不等提交才发现) */ if (this.confirmPw.length > 0 && this.newPw !== this.confirmPw) { Text('两次密码不一致') - .fontSize(11).fontColor(Theme.danger).margin({ top: 6 }) + .fontSize(11).fontColor(Theme.dangerFor()).margin({ top: 6 }) } if (this.pwError.length > 0) { Text(this.pwError) - .fontSize(12).fontColor(Theme.danger).margin({ top: 8 }) + .fontSize(12).fontColor(Theme.dangerFor()).margin({ top: 8 }) } if (this.pwMsg.length > 0) { Text(this.pwMsg) @@ -1145,11 +1145,11 @@ export struct SettingsPane { Row() { Text('管理').fontSize(Theme.fontBody).fontColor(Theme.textPrimary) .layoutWeight(1) - Text('›').fontSize(Theme.fontBody).fontColor(Theme.textSubtle) + Text('›').fontSize(Theme.fontBody).fontColor(Theme.textSubtleFor()) } .width('100%') Text('用户管理(新建 / 编辑 / 启停 / 重置密码)') - .fontSize(Theme.fontTiny).fontColor(Theme.textSubtle) + .fontSize(Theme.fontTiny).fontColor(Theme.textSubtleFor()) .width('100%').margin({ top: 4 }) } .width('100%').alignItems(HorizontalAlign.Start) @@ -1197,7 +1197,7 @@ export struct SettingsPane { } Text('删除') - .fontSize(12).fontColor(Theme.danger) + .fontSize(12).fontColor(Theme.dangerFor()) .onClick(() => { this.removeAccount(account.id); }) } .width('100%').height(72)