diff --git a/client/electron/test/cross-client-theme.test.mjs b/client/electron/test/cross-client-theme.test.mjs index 9ec30bf..a8abec9 100644 --- a/client/electron/test/cross-client-theme.test.mjs +++ b/client/electron/test/cross-client-theme.test.mjs @@ -237,7 +237,7 @@ const SELF_OWNED_COLORS = [ * * 三者的取值理由都已写在 `Theme.ets` 各自的注释里(本判据的要求)。 */ - 'navActiveBg', 'navBrandFg', 'badgePlain', + 'navActiveBg', 'navBrandFg', 'badgePlain', 'accentSoftDark', /* * SSE 连接指示器的四个状态色(`WideSidebar.ets` 的 `sseColorOf`)。 * 逐档对齐 WebUI 的 Tailwind 类(`ConnectionIndicator.tsx:22-25`): @@ -820,3 +820,61 @@ test('★ 断点:两端各是多少、含义是什么、差异被登记(不 + '否则这条登记会变成没人在核的化石(登记也该跟着事实走)'); } }); + +test('★ 品牌浅底必须有深色变体(深色下白底卡片 = 刺眼的 bug)', () => { + /* + * ★★ 2026-09-19 设备实测撞出来的真 bug: + * 深色主题下「多账号」里**选中**的那张卡片仍是接近纯白的浅蓝 + * (`Theme.accentSoft = #EFF6FF` 是写死的),在深色页面上刺眼得像渲染错误。 + * + * 根因:WebUI 靠 **CSS 变量在 `.dark` 段反转发**解决 + * (`index.css:113` 的 `--c-blue-50: 239 246 255` → `:475` 的 `28 37 54`), + * 而 ArkTS 的 `static readonly` **没有那层机制** —— 一个常量一个值。 + * + * 修法:显式提供深色取值 + 一个按当前主题选值的入口 + * (`Theme.accentSoftFor(dark)`),**不让页面各自 `isDark ? a : b`** + * —— 那样每处都会各写一遍,迟早漏一处。 + * + * 判据断三件事: + * ① 深色变体存在,且**取值来自 WebUI 的 `.dark` 段**(不是随手挑一个深色); + * ② 有按主题选值的入口(页面不该自己写三元); + * ③ 用到它的地方**真的走那个入口**(定义了不接 = 那处深色下照样刺眼)。 + */ + const themeSrc = prose(join(HARMONY_ETS, 'common/Theme.ets')); + const webCss = prose(join(ROOT, 'client/electron/src/index.css')); + + /* ① WebUI 深色下的 blue-50 是权威值 */ + const darkBlue50 = /\.dark\s*\{[\s\S]*?--c-blue-50:\s*(\d+)\s+(\d+)\s+(\d+)/.exec(webCss); + assert.ok(darkBlue50, 'WebUI `.dark` 段要有 `--c-blue-50`(品牌浅底的深色取值)'); + const [r, g, b] = [darkBlue50[1], darkBlue50[2], darkBlue50[3]].map(Number); + const hex = '#' + [r, g, b].map((v) => v.toString(16).padStart(2, '0').toUpperCase()).join(''); + + const darkVar = new RegExp(`static readonly accentSoftDark: string = '${hex}'`, 'i'); + assert.match(themeSrc, darkVar, + '★ 鸿蒙要有 accentSoftDark = ' + hex + '(对齐 WebUI 的 .dark --c-blue-50)—— ' + + '深色下用写死的浅底会让选中卡片在深色页上刺眼'); + + /* ② 有按主题选值的入口 */ + assert.match(themeSrc, /static accentSoftFor\(dark: boolean\): string/, + '★ 要有 `accentSoftFor(dark)` 入口 —— 页面各自写 `isDark ? a : b` 迟早漏一处'); + + /* ③ 用到它的地方真的走入口(不是还在直接用浅色那个) */ + const pages = readdirSync(HARMONY_ETS + '/pages').filter((f) => f.endsWith('.ets')); + const offenders = []; + for (const f of pages) { + const src = code(join(HARMONY_ETS, 'pages', f)); + /* + * 找"把 accentSoft 当背景色用"的地方。用 accentSoftFor 的**不算**。 + * 只看 backgroundColor(...),因为 accentSoft 也可以当文字色 + * (那种场景下深浅色差异不刺眼,不在本条范围)。 + */ + for (const m of src.matchAll(/backgroundColor\(([^)]*Theme\.accentSoft\b[^)]*)\)/g)) { + const line = src.slice(0, m.index).split('\n').length; + offenders.push(`${f}:${line}`); + } + } + assert.deepEqual(offenders, [], + '★ 这些地方还在直接把浅色 accentSoft 当背景(深色下会刺眼):\n ' + + offenders.join('\n ') + + '\n改成 Theme.accentSoftFor(this.isDarkNow)(页面要有一个 isDarkNow 状态)'); +}); diff --git a/client/electron/test/run-all.mjs b/client/electron/test/run-all.mjs index 55bc407..2974173 100644 --- a/client/electron/test/run-all.mjs +++ b/client/electron/test/run-all.mjs @@ -68,7 +68,7 @@ const SUITE = [ // 「只有通信页深色正常」的回归锁 —— 38 条里后 8 条是这次新增)。 ['test/theme.test.mjs', [], 38], ['test/background.test.mjs', [], 44], - ['test/cross-client-theme.test.mjs', [], 16], + ['test/cross-client-theme.test.mjs', [], 17], // 左右滑动翻页的**语义契约**(两端逐项相同、数值各自定)—— 这是 // `docs/DEBTS.json` 的 `gesture-semantics` 那条债:它写着「鸿蒙侧出现滑动 // 手势代码时**立即建**(此前建 = 只有一端存在的假判据)」。 diff --git a/client/harmony/entry/src/main/ets/common/Theme.ets b/client/harmony/entry/src/main/ets/common/Theme.ets index 6f00853..0bcb331 100644 --- a/client/harmony/entry/src/main/ets/common/Theme.ets +++ b/client/harmony/entry/src/main/ets/common/Theme.ets @@ -137,7 +137,11 @@ export class Theme { * 品牌(跨客户端身份,系统给不了): * · accent #2563EB 与 WebUI 的 --c-blue-600 逐字一致 * · accentFg #FFFFFF 品牌底上的文字 - * · accentSoft #EFF6FF 品牌浅底(选中态背景) + * · accentSoft #EFF6FF 品牌浅底(选中态背景,浅色主题用) + * · accentSoftDark #1C2536 品牌浅底在**深色**下的取值 —— 对齐 WebUI + * `index.css:475` 的 `.dark --c-blue-50`(`28 37 54`)。 + * ★ 不登记会怎样:深色下选中卡片是接近纯白的浅蓝, + * 在深色页面上刺眼得像 bug(2026-09-19 设备实测到) * · accentStrong #1D4ED8 品牌深色变体(选中文字/边框) * * 业务语义(系统只有 list/warning/alert 这一档,没有"同意/拒绝"): @@ -191,6 +195,31 @@ export class Theme { static readonly accentFg: string = '#FFFFFF'; /** 品牌蓝的浅底 / 深前景(与 WebUI 的 blue-50 / blue-700 成对) */ static readonly accentSoft: string = '#EFF6FF'; + /** + * 品牌浅底在**深色**下的取值 —— WebUI `index.css:475` 的 `.dark --c-blue-50` + * 是 `28 37 54`(= `#1C2536`)。 + * + * ★ 为什么需要它(2026-09-19 设备实测):`accentSoft` 是**写死的浅蓝**, + * 而它被用作「选中态背景」(多账号卡片、导航选中项…)。深色主题下 + * 那几处仍是**接近纯白的浅蓝** —— 在深色页面上刺眼得像个 bug。 + * WebUI 靠 CSS 变量反转发解决(`.dark` 段把 `--c-blue-50` 换成深蓝黑), + * 静态常量没有那层机制 ⇒ 必须显式提供深色取值。 + * + * ★ 配套的 `accentSoftFor(isDark)` 才是使用入口 —— **不要在页面里 + * 自己 `isDark ? a : b`**:那样每处都会各写一遍,迟早漏一处 + * (WebUI 那条"由 test/theme.test.mjs 逐档断言"就是为防这个)。 + */ + static readonly accentSoftDark: string = '#1C2536'; + + /** + * 按当前主题给品牌浅底。 + * + * @param dark 当前是否深色(页面的 `isDarkMode(...)` 算出来传进来 —— + * `Theme` 是静态类,拿不到 Context,所以深浅色由调用方给) + */ + static accentSoftFor(dark: boolean): string { + return dark ? Theme.accentSoftDark : Theme.accentSoft; + } static readonly accentStrong: string = '#1D4ED8'; /** 导航文字:未选中 / 选中(对应 --nav-fg-muted / --nav-active-fg) */ diff --git a/client/harmony/entry/src/main/ets/pages/CalendarPage.ets b/client/harmony/entry/src/main/ets/pages/CalendarPage.ets index 655ef62..8c30b24 100644 --- a/client/harmony/entry/src/main/ets/pages/CalendarPage.ets +++ b/client/harmony/entry/src/main/ets/pages/CalendarPage.ets @@ -113,6 +113,16 @@ const WEEK_START: number = 1; @Component export struct CalendarPage { + /* + * 当前是否深色(`MainPage` 经 `AppStorage` 发布)—— 用来选品牌浅底的深浅变体。 + * + * ★ 2026-09-19:深色主题下这些"浅蓝底"(未读行 / 选中项 / 分段选中) + * 在深色页面上刺眼。根因是 `Theme.accentSoft` 写死浅色 + * (WebUI 靠 CSS 的 `.dark` 段反转发解决,ArkTS 静态常量没有那层机制)。 + * 窗格自己算不出深浅色 ⇒ 从 AppStorage 读。 + */ + @StorageProp('agentmail.appearance.isDark') isDarkNow: boolean = false; + /** 背景开着时要让出页面底(与其它 pane 同一约定) */ @Prop bgActive: boolean = false; /** @@ -1169,7 +1179,7 @@ export struct CalendarPage { Row() { Button('今天') .fontSize(Theme.fontSmall) - .backgroundColor(Theme.accentSoft) + .backgroundColor(Theme.accentSoftFor(this.isDarkNow)) .fontColor(Theme.accentStrong) .onClick(() => this.goToday()) Blank() @@ -1181,7 +1191,7 @@ export struct CalendarPage { Text(scaleLabel(s)) .fontSize(Theme.fontSmall) .fontColor(this.calScale === s ? Theme.accentFg : Theme.textPrimary) - .backgroundColor(this.calScale === s ? Theme.accent : Theme.accentSoft) + .backgroundColor(this.calScale === s ? Theme.accent : Theme.accentSoftFor(this.isDarkNow)) .textAlign(TextAlign.Center) .width(34) .height(26) diff --git a/client/harmony/entry/src/main/ets/pages/ComposePage.ets b/client/harmony/entry/src/main/ets/pages/ComposePage.ets index a534ceb..2e2bf1c 100644 --- a/client/harmony/entry/src/main/ets/pages/ComposePage.ets +++ b/client/harmony/entry/src/main/ets/pages/ComposePage.ets @@ -17,6 +17,16 @@ import { Insets, KEY_WINDOW_INSETS, topInset } from '../model/WindowInsets'; @Entry @Component struct ComposePage { + /* + * 当前是否深色(`MainPage` 经 `AppStorage` 发布)—— 用来选品牌浅底的深浅变体。 + * + * ★ 2026-09-19:深色主题下这些"浅蓝底"(未读行 / 选中项 / 分段选中) + * 在深色页面上刺眼。根因是 `Theme.accentSoft` 写死浅色 + * (WebUI 靠 CSS 的 `.dark` 段反转发解决,ArkTS 静态常量没有那层机制)。 + * 窗格自己算不出深浅色 ⇒ 从 AppStorage 读。 + */ + @StorageProp('agentmail.appearance.isDark') isDarkNow: boolean = false; + @State to: string = ''; /** * 窗口避让区 —— 由 `EntryAbility.setupFullScreenWindow` 写进 AppStorage。 @@ -252,7 +262,7 @@ struct ComposePage { } } .width('100%').height(40).padding({ left: 40, right: 12 }) - .backgroundColor(this.selectedAccountId === acct.id ? Theme.accentSoft : Theme.surface) + .backgroundColor(this.selectedAccountId === acct.id ? Theme.accentSoftFor(this.isDarkNow) : Theme.surface) .onClick(() => { this.selectedAccountId = acct.id; this.showAccountPicker = false; diff --git a/client/harmony/entry/src/main/ets/pages/InboxPage.ets b/client/harmony/entry/src/main/ets/pages/InboxPage.ets index 9f41622..15d9b7a 100644 --- a/client/harmony/entry/src/main/ets/pages/InboxPage.ets +++ b/client/harmony/entry/src/main/ets/pages/InboxPage.ets @@ -15,6 +15,16 @@ import { Insets, KEY_WINDOW_INSETS, topInset } from '../model/WindowInsets'; @Entry @Component struct InboxPage { + /* + * 当前是否深色(`MainPage` 经 `AppStorage` 发布)—— 用来选品牌浅底的深浅变体。 + * + * ★ 2026-09-19:深色主题下这些"浅蓝底"(未读行 / 选中项 / 分段选中) + * 在深色页面上刺眼。根因是 `Theme.accentSoft` 写死浅色 + * (WebUI 靠 CSS 的 `.dark` 段反转发解决,ArkTS 静态常量没有那层机制)。 + * 窗格自己算不出深浅色 ⇒ 从 AppStorage 读。 + */ + @StorageProp('agentmail.appearance.isDark') isDarkNow: boolean = false; + @State mails: MailSummary[] = []; /** * 窗口避让区 —— 由 `EntryAbility.setupFullScreenWindow` 写进 AppStorage。 @@ -124,7 +134,7 @@ struct InboxPage { this.MailItem(mail) } .height(80) - .backgroundColor(mail.status === 'unread' ? Theme.accentSoft : Theme.surface) + .backgroundColor(mail.status === 'unread' ? Theme.accentSoftFor(this.isDarkNow) : Theme.surface) .onClick(() => { this.getUIContext().getRouter().pushUrl({ url: 'pages/MailDetailPage', @@ -149,7 +159,7 @@ struct InboxPage { } .layoutWeight(1).height('100%') .justifyContent(FlexAlign.Center) - .backgroundColor(Theme.accentSoft) + .backgroundColor(Theme.accentSoftFor(this.isDarkNow)) Column() { AmIcon({ iconName: 'chatBubble', iconSize: 20, iconColor: Theme.textMuted }) diff --git a/client/harmony/entry/src/main/ets/pages/LoginPage.ets b/client/harmony/entry/src/main/ets/pages/LoginPage.ets index abb007a..c74a467 100644 --- a/client/harmony/entry/src/main/ets/pages/LoginPage.ets +++ b/client/harmony/entry/src/main/ets/pages/LoginPage.ets @@ -19,6 +19,16 @@ import { AmIcon } from '../common/Icons'; @Entry @Component struct LoginPage { + /* + * 当前是否深色(`MainPage` 经 `AppStorage` 发布)—— 用来选品牌浅底的深浅变体。 + * + * ★ 2026-09-19:深色主题下这些"浅蓝底"(未读行 / 选中项 / 分段选中) + * 在深色页面上刺眼。根因是 `Theme.accentSoft` 写死浅色 + * (WebUI 靠 CSS 的 `.dark` 段反转发解决,ArkTS 静态常量没有那层机制)。 + * 窗格自己算不出深浅色 ⇒ 从 AppStorage 读。 + */ + @StorageProp('agentmail.appearance.isDark') isDarkNow: boolean = false; + @State serverAddr: string = DEFAULT_API_BASE; /** 地址合法但值得提醒(例如公网明文 http)—— 直接渲染在输入框下面,不靠一次性 toast */ @State addrWarning: string = ''; @@ -273,7 +283,7 @@ struct LoginPage { .width(48) .height(48) .borderRadius(12) - .backgroundColor(Theme.accentSoft) + .backgroundColor(Theme.accentSoftFor(this.isDarkNow)) Text('AgentMail') .fontSize(16) @@ -324,7 +334,7 @@ struct LoginPage { .fontSize(12) .fontWeight(this.mode === 0 ? FontWeight.Medium : FontWeight.Regular) .fontColor(this.mode === 0 ? Theme.accentStrong : Theme.textMuted) - .backgroundColor(this.mode === 0 ? Theme.accentSoft : Color.Transparent) + .backgroundColor(this.mode === 0 ? Theme.accentSoftFor(this.isDarkNow) : Color.Transparent) .borderRadius(Theme.radiusControl) .onClick(() => { this.mode = 0; }) Button('用户密钥') @@ -334,7 +344,7 @@ struct LoginPage { .fontSize(12) .fontWeight(this.mode === 1 ? FontWeight.Medium : FontWeight.Regular) .fontColor(this.mode === 1 ? Theme.accentStrong : Theme.textMuted) - .backgroundColor(this.mode === 1 ? Theme.accentSoft : Color.Transparent) + .backgroundColor(this.mode === 1 ? Theme.accentSoftFor(this.isDarkNow) : Color.Transparent) .borderRadius(Theme.radiusControl) .onClick(() => { this.mode = 1; }) } diff --git a/client/harmony/entry/src/main/ets/pages/MailDetailPage.ets b/client/harmony/entry/src/main/ets/pages/MailDetailPage.ets index e832fa6..632aee0 100644 --- a/client/harmony/entry/src/main/ets/pages/MailDetailPage.ets +++ b/client/harmony/entry/src/main/ets/pages/MailDetailPage.ets @@ -46,6 +46,16 @@ function localDateTime(iso: string): string { @Component export struct MailDetailView { + /* + * 当前是否深色(`MainPage` 经 `AppStorage` 发布)—— 用来选品牌浅底的深浅变体。 + * + * ★ 2026-09-19:深色主题下这些"浅蓝底"(未读行 / 选中项 / 分段选中) + * 在深色页面上刺眼。根因是 `Theme.accentSoft` 写死浅色 + * (WebUI 靠 CSS 的 `.dark` 段反转发解决,ArkTS 静态常量没有那层机制)。 + * 窗格自己算不出深浅色 ⇒ 从 AppStorage 读。 + */ + @StorageProp('agentmail.appearance.isDark') isDarkNow: boolean = false; + /** * 既支持旧 router 页面,也支持 Navigation 的 NavDestination 内嵌复用。 * 内嵌时由父级传入 mailId/accountId;独立页面仍从 router params 读取。 @@ -336,7 +346,7 @@ export struct MailDetailView { if (this.status === 'unread') { Text('未读') .fontSize(10).fontColor(Theme.accent) - .backgroundColor(Theme.accentSoft).borderRadius(4) + .backgroundColor(Theme.accentSoftFor(this.isDarkNow)).borderRadius(4) .padding({ left: 6, right: 6, top: 2, bottom: 2 }) .margin({ left: 6 }) } diff --git a/client/harmony/entry/src/main/ets/pages/MainPage.ets b/client/harmony/entry/src/main/ets/pages/MainPage.ets index 806c248..e57755d 100644 --- a/client/harmony/entry/src/main/ets/pages/MainPage.ets +++ b/client/harmony/entry/src/main/ets/pages/MainPage.ets @@ -164,6 +164,14 @@ struct InboxTab { @Prop navReserve: number = 0; /** 选中邮件回调:宽屏内联到 Navigation 右栏,窄屏 push 到目标页 */ onOpenMail: (mailId: string, accountId: string) => void = (): void => {}; + /* + * 当前是否深色(`MainPage` 发布)—— 用来选品牌浅底的深浅变体。 + * + * ★ 2026-09-19:深色主题下「未读」行/选中账号的**浅蓝底**在深色页面上刺眼, + * 根因是 `Theme.accentSoft` 是写死的浅色(WebUI 靠 `.dark` 段反转发解决, + * ArkTS 的静态常量没有那层机制)。窗格自己算不出深浅色 ⇒ 走 AppStorage 读。 + */ + @StorageProp('agentmail.appearance.isDark') isDarkNow: boolean = false; @State mails: MailSummary[] = []; /** 按会话折叠后的列表(单封的组平铺渲染) */ @State groups: SessionGroup[] = []; @@ -433,7 +441,7 @@ struct InboxTab { .fontSize(14).fontColor(this.accountFilter === 'all' ? Theme.accent : Theme.textPrimary) .fontWeight(this.accountFilter === 'all' ? FontWeight.Bold : FontWeight.Normal) .width('100%').height(40).padding({ left: 16 }) - .backgroundColor(this.accountFilter === 'all' ? Theme.accentSoft : Theme.surface) + .backgroundColor(this.accountFilter === 'all' ? Theme.accentSoftFor(this.isDarkNow) : Theme.surface) .onClick(() => { this.accountFilter = 'all'; this.accountName = '全部邮箱'; @@ -454,7 +462,7 @@ struct InboxTab { .layoutWeight(1) } .width('100%').height(48).padding({ left: 16 }) - .backgroundColor(this.accountFilter === acct.id ? Theme.accentSoft : Theme.surface) + .backgroundColor(this.accountFilter === acct.id ? Theme.accentSoftFor(this.isDarkNow) : Theme.surface) .onClick(() => { this.accountFilter = acct.id; this.accountName = acct.displayName; @@ -535,7 +543,7 @@ struct InboxTab { this.MailItem(mail) } .width('100%').height(64) - .backgroundColor(mail.status === 'unread' ? Theme.accentSoft : Theme.surface) + .backgroundColor(mail.status === 'unread' ? Theme.accentSoftFor(this.isDarkNow) : Theme.surface) .borderRadius(Theme.radiusCard) .border({ width: 1, color: Theme.border }) .clip(true) @@ -626,7 +634,7 @@ struct InboxTab { if (this.accountFilter === 'all' && mail.source_account_name.length > 0) { Text(mail.source_account_name) .fontSize(10).fontColor(Theme.accent) - .backgroundColor(Theme.accentSoft).borderRadius(4) + .backgroundColor(Theme.accentSoftFor(this.isDarkNow)).borderRadius(4) .padding({ left: 5, right: 5, top: 1, bottom: 1 }) .margin({ left: 6 }) } @@ -2170,6 +2178,14 @@ struct MainPage { @State currentIndex: number = 0; /** 宽屏模式(≥768vp):复刻 WebUI 的三栏布局(Sidebar | 内容,无底部导航条) */ @State isWide: boolean = false; + /** + * "当前是否深色"的发布键(`AppStorage`)。 + * + * 各窗格里有若干"品牌浅底"(`Theme.accentSoft`),深色下必须换深色变体。 + * 窗格自己算不出这件事(`Theme` 是静态类、拿不到 Context)⇒ + * 由 MainPage 算好发布、窗格 `@StorageProp` 读(与徽标同一套机制)。 + */ + private readonly KEY_IS_DARK: string = 'agentmail.appearance.isDark'; /* * 壁纸层(背景的**画法**在 `model/Wallpaper.ts` 里算好,这里只负责画)。 * @@ -2377,6 +2393,8 @@ struct MainPage { store.applyTheme(ctx, next); store.saveLocal(ctx, snap); this.isDarkNow = next === 'dark'; + /* 侧栏的主题快捷键也要把新状态发布出去(否则窗格里的浅底不跟着换) */ + AppStorage.setOrCreate(this.KEY_IS_DARK, this.isDarkNow); const client: ApiClient = ApiClient.getInstance(ctx); new AppearanceApi(client).put(snap, store.wallpaper !== null).catch(() => {}); } @@ -2479,6 +2497,14 @@ struct MainPage { const dark: boolean = isDarkMode(snap.theme, systemMode); /* 侧栏底部那个主题按钮的图标跟着它走(WebUI `ThemeToggleButton` 同口径) */ this.isDarkNow = dark; + /* + * 把"当前是不是深色"**发布出去** —— 各窗格(通信/日历/联系/我的)里 + * 有若干"品牌浅底"(`Theme.accentSoft`),深色下必须换成深色变体。 + * 那些窗格自己算不出这件事(`Theme` 是静态类、拿不到 Context), + * 所以走 `AppStorage` 单向发布(与徽标、windowInsets 同一套机制): + * MainPage 算 → 写 → 窗格 `@StorageProp` 读。 + */ + AppStorage.setOrCreate(this.KEY_IS_DARK, dark); /* * 侧栏头像的前两字:当前**活跃账号**的展示名(不是 `accountName`, * 那是收件箱筛选器选的)。拿不到就让它空着 —— 侧栏会显示 `?`。 diff --git a/client/harmony/entry/src/main/ets/pages/SettingsPage.ets b/client/harmony/entry/src/main/ets/pages/SettingsPage.ets index 1637623..0901ff7 100644 --- a/client/harmony/entry/src/main/ets/pages/SettingsPage.ets +++ b/client/harmony/entry/src/main/ets/pages/SettingsPage.ets @@ -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 }) } }