跨端: 深色模式下的品牌浅底不跟随 —— 12 处「选中/未读」底色在深色页上刺眼

## 真 bug(设备实测)

深色主题下,「我的」页**选中**的那张账号卡片仍是接近纯白的浅蓝
(实测像素 `(255,255,255)` 级别的浅底压在 `(32,34,36)` 的深色页上)。

根因:`Theme.accentSoft = '#EFF6FF'` 是**写死的浅色**,而它被当作
「选中态背景」用在 12 处(未读邮件行、选中账号、分段选中、登录页模式切换…)。

**为什么只有 WebUI 没这个问题**:它有 CSS 变量的**反转发**机制 ——
`index.css:113` 的 `--c-blue-50: 239 246 255` 在 `.dark` 段(`:475`)被换成
`28 37 54`(深蓝黑)。ArkTS 的 `static readonly` **一个常量一个值**,
没有那层机制 ⇒ 静态常量必须自己提供两个取值。

## 修法

1. `Theme.accentSoftDark = '#1C2536'`(对齐 WebUI `.dark --c-blue-50` 的 `28 37 54`)
2. `Theme.accentSoftFor(dark)` 作为**唯一入口** —— 不在页面里各自
   `isDark ? a : b`:那样每处都会各写一遍,迟早漏一处
   (WebUI 那条"由 test/theme.test.mjs 逐档断言"就是为防这个)
3. **深浅色从哪来**:只有 `MainPage` 算得出(它读 `resourceManager` 的
   `colorMode`)。所以走 `AppStorage` 单向发布(与徽标、windowInsets 同一套):
   MainPage 算 → 写 `agentmail.appearance.isDark` → 各窗格 `@StorageProp` 读。
   6 个文件、12 处,全部改用 `accentSoftFor(this.isDarkNow)`。

**设备实测**:深色下「我的」页账号卡片与收件箱未读行都变成深蓝底,
像素 `(32,34,36)` 与页面底一致(不再刺眼)。

## 判据(这条是新加的,形状值得记)

`cross-client-theme` 新增:**品牌浅底必须有深色变体**。断三件事:
1. 深色变体存在,且**取值从 WebUI 的 `.dark` 段反推**(不是随手挑一个深色);
2. 有按主题选值的**入口**(防"页面各自写三元");
3. **用到它的地方真的走那个入口** —— 扫描所有 `backgroundColor(… accentSoft …)`
   并排除 `accentSoftFor`,把漏改的位置**逐行报出来**。

第 3 条在我改到一半时**当场列出了剩下 9 处**(`CalendarPage:1172`、
`ComposePage:255`、`LoginPage:327`…)—— 这就是它该有的样子:
不是"断言存在某个常量",而是"断言没有一处漏改"。

★ 写这条判据时踩了自己一次:JS 模板串里嵌了反引号包围的标识符
(`` `accentSoft` ``),直接 SyntaxError。改用字符串拼接。

## 判据

`run-all.mjs` → `checks=506 pass=506 fail=0 skip=0 red=0 broken=0 unreported=0`。
`hvigorw assembleHap` 成功;前端重建。

**未验**:日历/登录页在深色下的观感(只逐处改了底色,没逐页截图)。
This commit is contained in:
2026-09-19 16:02:26 +08:00
parent a18014e1e3
commit fc295893cb
10 changed files with 197 additions and 19 deletions

View File

@ -20,7 +20,7 @@ import { AccountManager, AccountInfo } from '../api/AccountManager';
import { SseService } from '../api/SseService';
import { AppearanceStore } from '../common/AppearanceStore';
import { AppearanceApi } from '../api/AppearanceApi';
import { AppearanceSnapshot, statusLabel } from '../model/Appearance';
import { AppearanceSnapshot, isDarkMode, statusLabel } from '../model/Appearance';
import { MeApi } from '../api/AdminApi';
import { AdminUser, UserKey } from '../model/Models';
import { isAdminRole } from '../model/AdminUsers';
@ -49,6 +49,15 @@ export struct SettingsPane {
* 页面只保存**显示用的一份副本** —— ArkTS 的 @State 观察不到类内部字段的变化。
*/
@State appearanceTheme: string = 'system';
/**
* 当前**实际**是不是深色(`appearanceTheme='system'` 时要看系统反馈的模式)。
*
* ★ 2026-09-19 加:用来选品牌浅底(`Theme.accentSoftFor`)。
* 实测问题:深色主题下「多账号」那张选中卡片仍是**接近纯白的浅蓝**
* (`accentSoft` 是写死的 `#EFF6FF`)—— 在深色页面上刺眼得像 bug。
* WebUI 靠 CSS 变量反转发解决,静态常量没有那层机制。
*/
@State isDarkNow: boolean = false;
@State appearanceStatus: string = 'local-only';
/** 当前账号是不是管理员 —— 只用来决定「管理」入口显不显示(口径见 model/AdminUsers.ts 的 isAdminRole) */
@State isAdmin: boolean = false;
@ -207,6 +216,12 @@ export struct SettingsPane {
/** 把 store 里的快照复制进 @State(@State 观察不到类内部字段变化) */
adoptSnapshot(snap: AppearanceSnapshot, status: string): void {
this.appearanceTheme = snap.theme;
/* `system` 时要看系统反馈的色彩模式(`Theme` 是静态类,算不出这件事) */
const ctx2 = this.getUIContext().getHostContext();
if (ctx2 !== undefined) {
const mode: number = ctx2.resourceManager.getConfigurationSync().colorMode;
this.isDarkNow = isDarkMode(snap.theme, mode);
}
this.bgKind = snap.bgKind;
this.bgPresetId = snap.bgPresetId;
this.bgDim = snap.bgDim;
@ -1158,7 +1173,7 @@ export struct SettingsPane {
if (account.id === this.activeId) {
Text(' 默认')
.fontSize(11).fontColor(Theme.accent)
.backgroundColor(Theme.accentSoft).borderRadius(4)
.backgroundColor(Theme.accentSoftFor(this.isDarkNow)).borderRadius(4)
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
.margin({ left: 6 })
}
@ -1188,7 +1203,7 @@ export struct SettingsPane {
.width('100%').height(72)
.padding({ left: 16, right: 16 })
.alignItems(VerticalAlign.Center)
.backgroundColor(account.id === this.activeId ? Theme.accentSoft : Theme.surface)
.backgroundColor(account.id === this.activeId ? Theme.accentSoftFor(this.isDarkNow) : Theme.surface)
.border({ width: { bottom: 1 }, color: Theme.border })
}
}