diff --git a/client/harmony/entry/src/main/ets/common/BackgroundPicker.ets b/client/harmony/entry/src/main/ets/common/BackgroundPicker.ets index 6aff946..561e4d1 100644 --- a/client/harmony/entry/src/main/ets/common/BackgroundPicker.ets +++ b/client/harmony/entry/src/main/ets/common/BackgroundPicker.ets @@ -309,7 +309,7 @@ export struct BackgroundPicker { Text(this.uploadError) .fontSize(Theme.fontTiny).fontColor(Theme.dangerFor()) .width('100%').padding(8).margin({ top: 6 }) - .backgroundColor(Theme.dangerBg).borderRadius(Theme.radiusControl) + .backgroundColor(Theme.dangerBgFor()).borderRadius(Theme.radiusControl) } Text('上传前会先压到最长边 ' + MAX_EDGE + ' 像素(手机直出照片 4–8MB,服务端上限 4MB)。') .fontSize(Theme.fontTiny).fontColor(Theme.textSubtleFor()) diff --git a/client/harmony/entry/src/main/ets/common/Theme.ets b/client/harmony/entry/src/main/ets/common/Theme.ets index 34c056c..dbaafed 100644 --- a/client/harmony/entry/src/main/ets/common/Theme.ets +++ b/client/harmony/entry/src/main/ets/common/Theme.ets @@ -772,6 +772,31 @@ export class Theme { /** 警示面/前景(对应 WebUI 的 --c-amber-50 / --c-amber-700)—— 权限 full 档、待决策徽标 */ static readonly warnBg: string = '#FFFBEB'; static readonly warnFg: string = '#B45309'; + /* + * ★★ 2026-09-21 **语义面的深色档**(与 `warnFgDark` 同一批漏掉的另一半)。 + * + * 背景.test/`cross-client-theme` 之前只盯了**前景**(`*For()` 那一族), + * 语义**面**(`dangerBg` / `approveBg` / `warnBg` / `chipSpentBg` / `chipWarnBg`) + * 全部只有浅色值。实测(邮件详情页深色)「权限」「预算」两行: + * `#F3F4F6` 白胶囊上压浅色字 ⇒ 白底白字,完全读不出。 + * + * 取值照 WebUI `.dark` 段逐字对齐(`index.css:486-520`): + * --c-red-50 `46 31 37` ⇒ #2E1F25 dangerBgDark + * --c-red-100 `58 34 39` ⇒ #3A2227 chipSpentBgDark + * --c-green-50 `25 44 39` ⇒ #192C27 approveBgDark + * --c-amber-50 `46 40 31` ⇒ #2E281F warnBgDark + * --c-amber-100 `59 48 29` ⇒ #3B301D + * --c-orange-100 `60 41 31` ⇒ #3C291F chipWarnBgDark + * + * ★ 为什么不是一个通用的 "bgDark":这六个在浅色下**本来就不同** + * (红/绿/琥珀/橙各自一档),压成一个等于把语义丢了 —— + * “预算用尽”与“预算将尽”就分不出来了。 + */ + static readonly dangerBgDark: string = '#2E1F25'; + static readonly approveBgDark: string = '#192C27'; + static readonly warnBgDark: string = '#2E281F'; + static readonly chipSpentBgDark: string = '#3A2227'; + static readonly chipWarnBgDark: string = '#3C291F'; /* * ──── 语义色的**深色档**(2026-09-19 加)──── @@ -846,12 +871,69 @@ export class Theme { /** 中性 chip(对应 WebUI 的 --c-gray-100 / --c-gray-500)—— 预算条"还宽裕"档 */ static readonly chipNeutralBg: string = '#F3F4F6'; static readonly chipNeutralFg: string = '#5A6270'; + /* + * ★★ 2026-09-21 **加深色变体**(用户:「你看看这可读性」)。 + * + * 实测(深色主题,邮件详情页的「权限」「预算」两行): + * 浅灰底 `#F3F4F6` 上压 `Theme.textMuted` ⇒ 两个色都是浅色档, + * 深色下变成**白胶囊 + 白字**(截图里就是两个看不见字的白块)。 + * + * `#F3F4F6` 就是 WebUI 浅色的 `--c-gray-100`(`243 244 246`, + * `index.css:53`);它`.dark` 段的同一个是 `32 36 44`(`index.css:374`)。 + * 所以深色取值 = `#20242C`(与 `DARK_GRAY_100` 同值)。 + * + * 字色 `#5A6270` 对齐 WebUI 的 `--c-gray-600`;深色用 `#9BA3B0` + *(WebUI `.dark --c-gray-600` 一族的量级)—— 压在 `#20242C` 上约 7:1。 + * + * ★ 为什么这是个**早就该修**的:同文件 `permissionMode` 那几行的注释里 + * 就写着「`surface` 是会跟随主题翻转的面色 …… 深色下变成近黑 ⇒ 字看不见。 + * 这是本仓同一个错法的第 N 次出现(同批一共修了 12 处)」。 + * 那批修的是**前景**(`fontColor`),而 `chipNeutralBg` 这个**背景** + * 当时漏了 —— 于是同一行里前景换了、背景没换,反而更糟。 + */ + static readonly chipNeutralBgDark: string = '#20242C'; + static readonly chipNeutralFgDark: string = '#9BA3B0'; + /** + * 语义**面**的颜色访问器(主题感知)—— `dangerFor` 那一族的背景版。 + * + * 与 `chipBgFor` 同一范式。为什么需要:`dangerBg` 这些只有浅色值, + * 深色下把浅色调的底留在深色页面上(实测邮件详情页的「权限」「预算」行)。 + */ + static dangerBgFor(dark?: boolean): string { + return Theme.isDarkNow(dark) ? Theme.dangerBgDark : Theme.dangerBg; + } + + static approveBgFor(dark?: boolean): string { + return Theme.isDarkNow(dark) ? Theme.approveBgDark : Theme.approveBg; + } + + static warnBgFor(dark?: boolean): string { + return Theme.isDarkNow(dark) ? Theme.warnBgDark : Theme.warnBg; + } + + static chipSpentBgFor(dark?: boolean): string { + return Theme.isDarkNow(dark) ? Theme.chipSpentBgDark : Theme.chipSpentBg; + } + + static chipWarnBgFor(dark?: boolean): string { + return Theme.isDarkNow(dark) ? Theme.chipWarnBgDark : Theme.chipWarnBg; + } + /** 预算用尽(对应 WebUI 的 --c-red-100 / --c-red-700) */ static readonly chipSpentBg: string = '#FEE2E2'; + /* + * 预算 chip 的**前景**深色档 —— 与背景同一批漏的。 + * + * `#B91C1C` 是 WebUI 浅色的 `--c-red-700`(`185 28 28`); + * 深色段同一个变量是 `248 164 164`(`index.css:493`)⇒ `#F8A4A4`。 + * 橙色同理:`--c-orange-700` 深色 `251 168 111` ⇒ `#FBA86F`。 + */ + static readonly chipSpentFgDark: string = '#F8A4A4'; static readonly chipSpentFg: string = '#B91C1C'; /** 预算将尽(对应 WebUI 的 --c-orange-100 / --c-orange-700) */ static readonly chipWarnBg: string = '#FFEDD5'; static readonly chipWarnFg: string = '#C2410C'; + static readonly chipWarnFgDark: string = '#FBA86F'; /** * 往返预算档位 → chip 底色 / 字色。 @@ -862,24 +944,39 @@ export class Theme { * 这里只管"哪个档用什么颜色"。这三个色**不进跨端"取值相同"判据**(业务局部), * 但必须进枚举完整性判据(三档齐全、认不出的归一到中性档)。 */ - static budgetBg(state: string): string { - if (state === 'spent') { - return Theme.chipSpentBg; - } - if (state === 'warn') { - return Theme.chipWarnBg; - } - return Theme.chipNeutralBg; + /** + * 权限/预算 chip 的底色(**主题感知**)。 + * + * 与 `glassCardFor` 同一范式:深浅从 `Theme.isDarkNow()` 读,不靠调用方传。 + * 实测漏修后果:深色下 `#F3F4F6` 白胶囊 + 浅色字 = 完全读不出。 + */ + static chipBgFor(dark?: boolean): string { + return Theme.isDarkNow(dark) ? Theme.chipNeutralBgDark : Theme.chipNeutralBg; } - static budgetFg(state: string): string { + /** 权限/预算 chip 的字色(主题感知)。`chipBgFor` 的配对。 */ + static chipFgFor(dark?: boolean): string { + return Theme.isDarkNow(dark) ? Theme.chipNeutralFgDark : Theme.chipNeutralFg; + } + + static budgetBg(state: string, dark?: boolean): string { if (state === 'spent') { - return Theme.chipSpentFg; + return Theme.chipSpentBgFor(dark); } if (state === 'warn') { - return Theme.chipWarnFg; + return Theme.chipWarnBgFor(dark); } - return Theme.chipNeutralFg; + return Theme.chipBgFor(dark); + } + + static budgetFg(state: string, dark?: boolean): string { + if (state === 'spent') { + return Theme.isDarkNow(dark) ? Theme.chipSpentFgDark : Theme.chipSpentFg; + } + if (state === 'warn') { + return Theme.isDarkNow(dark) ? Theme.chipWarnFgDark : Theme.chipWarnFg; + } + return Theme.chipFgFor(dark); } /** 字体大小(与 WebUI 的 text-2xs/xs/sm 对齐) */ diff --git a/client/harmony/entry/src/main/ets/pages/AdminUsersPage.ets b/client/harmony/entry/src/main/ets/pages/AdminUsersPage.ets index e8696d3..06ff9cf 100644 --- a/client/harmony/entry/src/main/ets/pages/AdminUsersPage.ets +++ b/client/harmony/entry/src/main/ets/pages/AdminUsersPage.ets @@ -325,7 +325,7 @@ struct AdminUsersPage { Text(this.errorText) .fontSize(Theme.fontSmall).fontColor(Theme.dangerFor()) .width('100%').padding(10).margin({ left: 12, right: 12, top: 8 }) - .backgroundColor(Theme.dangerBg) + .backgroundColor(Theme.dangerBgFor()) .borderRadius(Theme.radiusControl) } @@ -442,12 +442,12 @@ struct AdminUsersPage { .fontSize(Theme.fontBody).fontColor(Theme.textPrimary).layoutWeight(1) .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }) this.Chip(user.role === 'admin' ? '管理员' : '用户', - user.role === 'admin' ? Theme.chipNeutralBg : Theme.surfaceMuted, + user.role === 'admin' ? Theme.chipBgFor() : Theme.surfaceMuted, /* ★ 2026-09-19 修:三元里的裸 `Theme.accent`(同批 2 处,见 SettingsPage 那条)。 */ user.role === 'admin' ? Theme.accentFor() : Theme.textMuted) Text(' ').width(4) this.Chip(user.status === 'active' ? '启用' : '禁用', - user.status === 'active' ? Theme.approveBg : Theme.surfaceMuted, + user.status === 'active' ? Theme.approveBgFor() : Theme.surfaceMuted, user.status === 'active' ? Theme.approveFg : Theme.textMuted) /* * 「受限」徽标:**只在普通用户且白名单非空时**显示 —— 与 WebUI 逐字一致 @@ -488,7 +488,7 @@ struct AdminUsersPage { * 参数类型是 `ResourceColor` 而不是 `string`:底色/文字色可能来自**系统语义色** * (`Theme.surfaceMuted` / `Theme.textSubtle` 是 `$r('sys.color.*')` ⇒ `Resource`), * 也可能来自自写色值(`Theme.chipNeutralBg` ⇒ `string`)。写成 `string` 时 - * `user.role === 'admin' ? Theme.chipNeutralBg : Theme.surfaceMuted` 这种三目 + * `user.role === 'admin' ? Theme.chipBgFor() : Theme.surfaceMuted` 这种三目 * 就变成 `string | Resource` 而**编译不过**(`ArkTS Compiler Error`,不是警告)。 * `fontColor`/`backgroundColor` 本来就收 `ResourceColor`,所以放宽参数类型即可, * 不需要把系统色抄成字符串——那正是这个文件要避免的事。 diff --git a/client/harmony/entry/src/main/ets/pages/CalendarPage.ets b/client/harmony/entry/src/main/ets/pages/CalendarPage.ets index cf5d87b..ad51643 100644 --- a/client/harmony/entry/src/main/ets/pages/CalendarPage.ets +++ b/client/harmony/entry/src/main/ets/pages/CalendarPage.ets @@ -721,7 +721,25 @@ export struct CalendarPage { private cellFg(cell: DayCell): ResourceColor { if (cell.iso === this.selectedIso) { - return Theme.accentFg; + /* + * ★★ 2026-09-21 修:**周/日档下不能用 `accentFg`(白)**。 + * + * 实测(宽屏 3184、周视图第 1 列「21」): + * 字 `rgb(254,254,254)` 压底 `rgb(254,254,254)` ⇒ 对比度 **1.00:1**(完全看不见)。 + * + * 原因:两档的"选中底色"**不是同一个东西**: + * · 月视图:格子铺**实心** `Theme.accent`(蓝)⇒ 白字对(`accentFg`); + * · 周/日视图:底色改由日期头承担,用的是**淡蓝** + * `Theme.accentSoftFor()`(`Color.Transparent` 让上面那层铺) + * ⇒ 白字压淡蓝 = 看不到。 + * + * 而本方法原来**不看档位**,一律返回 `accentFg` —— + * 于是从月视图切到周视图,那个"今天"的数字就消失了。 + * + * 修法:按档位分流。周/日档的选中/今天用 `Theme.accent`(品牌蓝作**前景**)—— + * 它在淡蓝底上是 4.5:1 以上,且与“选中”的语义一致。 + */ + return this.cellShowsChips() ? Theme.accent : Theme.accentFg; } if (cell.iso === this.todayIso) { return Theme.accent; @@ -953,7 +971,7 @@ export struct CalendarPage { .width('100%') .padding({ left: 4, right: 4, top: 1, bottom: 1 }) .borderRadius(4) - .backgroundColor(ev.status === 'active' ? Theme.accentSoftFor(this.isDarkNow) : Theme.chipNeutralBg) + .backgroundColor(ev.status === 'active' ? Theme.accentSoftFor(this.isDarkNow) : Theme.chipBgFor(this.isDarkNow)) .attributeModifier(PressEffectModifier.of()) .onClick(() => { /* 与 WebUI 同构:先把选中日挪到那天,再开编辑面板 */ @@ -1001,7 +1019,10 @@ export struct CalendarPage { * 系统三级色没有这层适配 ⇒ 深色下当小字会掉到 2.5:1。 * 改用二级(`textMuted`),与 WebUI 的 gray-500 档位一致。 */ - .fontColor(cell.iso === this.selectedIso ? Theme.accentFg : Theme.textMuted) + .fontColor(cell.iso === this.selectedIso + /* 与 `cellFg` 同一理由:周/日档的选中底色是**淡蓝**,不能用白字 */ + ? (this.cellShowsChips() ? Theme.accent : Theme.accentFg) + : Theme.textMuted) .maxLines(1) } } @@ -1191,7 +1212,7 @@ export struct CalendarPage { .layoutWeight(1) Button('取消') .fontSize(Theme.fontSmall) - .backgroundColor(Theme.chipNeutralBg) + .backgroundColor(Theme.chipBgFor()) .fontColor(Theme.chipNeutralFg) .onClick(() => { this.editorOpen = false; @@ -1228,7 +1249,7 @@ export struct CalendarPage { } .width('100%') .padding(10) - .backgroundColor(Theme.chipNeutralBg) + .backgroundColor(Theme.chipBgFor()) .borderRadius(Theme.radiusControl) .attributeModifier(PressEffectModifier.of()) .onClick(() => { @@ -1266,7 +1287,7 @@ export struct CalendarPage { ForEach(RECURRENCE_KEYS, (key: string, i: number) => { Button(RECURRENCE_LABELS[i]) .fontSize(Theme.fontTiny) - .backgroundColor(this.fRecurrence === key ? Theme.accent : Theme.chipNeutralBg) + .backgroundColor(this.fRecurrence === key ? Theme.accent : Theme.chipBgFor()) .fontColor(this.fRecurrence === key ? Theme.accentFg : Theme.chipNeutralFg) .margin({ right: 6 }) .onClick(() => { @@ -1286,7 +1307,7 @@ export struct CalendarPage { ForEach(REMIND_OPTIONS, (mins: number, i: number) => { Button(REMIND_LABELS[i]) .fontSize(Theme.fontTiny) - .backgroundColor(this.fRemindBefore === mins ? Theme.accent : Theme.chipNeutralBg) + .backgroundColor(this.fRemindBefore === mins ? Theme.accent : Theme.chipBgFor()) .fontColor(this.fRemindBefore === mins ? Theme.accentFg : Theme.chipNeutralFg) .margin({ right: 6 }) .onClick(() => { @@ -1320,7 +1341,7 @@ export struct CalendarPage { if (this.editingId.length > 0) { Button(this.fStatus === 'active' ? '暂停' : '恢复') .fontSize(Theme.fontSmall) - .backgroundColor(Theme.chipWarnBg) + .backgroundColor(Theme.chipWarnBgFor()) .fontColor(Theme.chipWarnFg) .onClick(() => { this.toggleStatus(); @@ -1328,7 +1349,7 @@ export struct CalendarPage { .attributeModifier(PressEffectModifier.of()) Button(this.confirmDelete ? '确认删除' : '删除') .fontSize(Theme.fontSmall) - .backgroundColor(Theme.dangerBg) + .backgroundColor(Theme.dangerBgFor()) .fontColor(Theme.dangerFor()) .margin({ left: 6 }) .onClick(() => { @@ -1615,18 +1636,39 @@ export struct CalendarPage { .width('100%') .padding({ left: 10, right: 10, top: 6, bottom: 4 }) - /* 周表头:顺序**必须**来自 weekdayLabels(WEEK_START),不许在这里硬编码 */ - Row() { - ForEach(weekdayLabels(WEEK_START), (w: string) => { - Text(w) - .fontSize(Theme.fontTiny) - .fontColor(Theme.textMuted) - .layoutWeight(1) - .textAlign(TextAlign.Center) - .height(WEEKDAY_HEIGHT) - }, (w: string) => `w${w}`) + /* + * 周表头:顺序**必须**来自 `weekdayLabels(WEEK_START)`,不许在这里硬编码。 + * + * ★★ 2026-09-21 修:**只给月/周档,日视图不画**。 + * + * 实测(宽屏 3184、日视图):小时时间轴上方挂着一条毫无意义的 + * 「一 二 三 四 五 六 日」—— 一天只有一个日子,七个格子标签全落空。 + * + * WebUI 根本没有这么一条独立表头: + * · `MonthGrid`(`CalendarView.tsx:536`)的表头在**自己内部** + * (`grid grid-cols-7` 的第一行); + * · `WeekGrid`(`:604`)的星期名在**每一列内部**(sticky 表头); + * · `DayGrid`(`:669`)**没有任何星期行**,顶上是日期+农历那一条。 + * + * ⇒ 所以两边差异不只是"多一行": + * 我们的表头原本是**独立于网格**的 `Row`(每列 `layoutWeight(1)` 均分**可用宽**), + * 而网格列在 `minWidth(322)` 的行里(均分**更大宽**)⇒ + * 两者参考宽度不同,表头与列**对不上**。 + * 日档直接不画就同时治了两个问题。 + */ + if (this.calScale !== 'day') { + Row() { + ForEach(weekdayLabels(WEEK_START), (w: string) => { + Text(w) + .fontSize(Theme.fontTiny) + .fontColor(Theme.textMuted) + .layoutWeight(1) + .textAlign(TextAlign.Center) + .height(WEEKDAY_HEIGHT) + }, (w: string) => `w${w}`) + } + .width('100%') } - .width('100%') /* * ★★ 2026-09-21:**日视图走 24 小时时间轴,不再复用七列网格** diff --git a/client/harmony/entry/src/main/ets/pages/MailDetailPage.ets b/client/harmony/entry/src/main/ets/pages/MailDetailPage.ets index bf00e98..ff15e92 100644 --- a/client/harmony/entry/src/main/ets/pages/MailDetailPage.ets +++ b/client/harmony/entry/src/main/ets/pages/MailDetailPage.ets @@ -6,7 +6,7 @@ import { ApiClient, ApiError } from '../api/ApiClient'; import { Theme } from '../common/Theme'; /* Markdown 渲染(第三方库,鸿蒙原生 ArkTS 引擎,不依赖 WebView)—— 正文用它,不再吐原始 Markdown */ -import { Markdown } from '@luvi/lv-markdown-in'; +import { Markdown, MarkdownController } from '@luvi/lv-markdown-in'; import { MailApi, SessionBudget, ThreadApiResponse } from '../api/MailApi'; import { ThreadNode } from '../model/Models'; import { SessionApi } from '../api/SessionApi'; @@ -518,6 +518,80 @@ export struct MailDetailView { ); } + /** + * Markdown 正文的样式控制器 ——**必须按主题给色,否则库用自己的浅色默认值**。 + * + * ══ 实测到的真 bug(用户:「你看看这可读性」)══ + * + * 深色主题下邮件正文实测: + * 文字 `rgb(35,33,52)` 压底色 `rgb(0,0,0)` ⇒ **对比度 1.34:1**(完全不可读) + * 同一屏里只有行内代码(红色 spans)看得见:5.02:1。 + * 头部信息区更差:`rgb(34,35,36)` 压 `rgb(32,33,35)` = **1.02:1**(根本没画出来)。 + * + * ── 根因 ── + * + * `Markdown({ text: this.body })` ——**只传了文本,没传 `controller`**。 + * 于是 `@luvi/lv-markdown-in` 用它自己的默认样式:那套默认值是给**浅色**配的 + * (实测 `rgb(35,33,52)` ≈ WebUI 浅色的 `--c-gray-800` = `31 41 55`)。 + * 而我们的页面底在深色下是近黑 ⇒ 深色字压深底。 + * + * 这是**第三方组件不继承宿主主题**这一类错:它不读 `$r('sys.color.*')`, + * 也不读 `AppStorage`,必须显式把色注入进去。 + * + * ── 取值照 WebUI 的 `.markdown` 那一层(`index.css:592-628`)逐条对齐 ── + * + * .markdown → color: rgb(var(--c-gray-800)) ⇒ Theme.textPrimary + * .markdown a → color: rgb(var(--c-blue-600)) ⇒ Theme.accentFor() + * .markdown blockquote → color: rgb(var(--c-gray-600)) ⇒ Theme.textMuted + * border-left: 3px solid gray-300 ⇒ Theme.border + * .markdown code → background: rgb(var(--c-gray-100)) ⇒ Theme.surfaceMuted + * .markdown pre / th → background: rgb(var(--c-gray-100)) ⇒ Theme.surfaceMuted + * border: 1px solid gray-200 ⇒ Theme.border + * + * `--c-gray-800` 在两端**都是跟随主题翻转的**(浅色 `31 41 55` / 深色 `231 235 240`), + * 而 `Theme.textPrimary` = `sys.color.ohos_id_color_text_primary` 同样会翻 —— + * 所以映射到它是对的,不需要再造一个令牌。 + * + * ★ 为什么每次 build 都新建一个 controller(而不是做个 `@State` 存一个): + * `Theme.isDarkNow()` 读的是 `AppStorage` 那个发布键,换主题时会变。 + * 控制器是**普通对象、不是 `@State`**,ArkUI 不会因它内部变化而重渲染; + * 而这里是在 `build()` 里现调,所以主题一变就会拿到新色。 + * 若改成 `@State` 存一份并靠 `aboutToAppear` 初始化,切主题后**不会刷新** —— + * 那正是这个 bug 的同一个形状(一次性取值)。 + * + * ★ 只管**色**,不管字号:库的默认字号体系与 WebUI 的 rem 档位不同, + * 这里硬改字号会引入两边不一致的新偏差。先把“读不动”修掉。 + */ + private mdController(): MarkdownController { + const c: MarkdownController = new MarkdownController(); + /* 正文(`.markdown` 的 color)*/ + c.setTextColor(Theme.textPrimary); + /* 标题:与正文同色,字号由库自己的档位给(WebUI 靠 font-size 区分级别)*/ + c.setTitleColor(Theme.textPrimary); + /* 行内代码 / 代码块底:`--c-gray-100` → `Theme.surfaceMuted` */ + c.setInlineCodeBackgroundColor(Theme.surfaceMuted); + c.setInlineCodeColor(Theme.textPrimary); + c.setTableBackgroundColor(Theme.surface); + c.setTableTitleBackgroundColor(Theme.surfaceMuted); + c.setTableTitleTextColor(Theme.textPrimary); + c.setTableNormalTextColor(Theme.textPrimary); + c.setTableInterleaveTextColor(Theme.textPrimary); + c.setTableInterleaveBackgroundColor(Theme.surfaceMuted); + /* 引用块:WebUI 是 gray-600 文字 + gray-300 左边框 */ + c.setQuoteTextColor(Theme.textMuted); + c.setQuoteBorderColor(Theme.border); + c.setQuoteBackgroundColor(Theme.surfaceMuted); + /* 链接:`--c-blue-600` → `Theme.accentFor()`(**前景**用的那个访问器)*/ + c.setHyperlinkTextColor(Theme.accentFor()); + /* 无序列表的点 */ + c.setUlPointColor(Theme.textPrimary); + /* 分隔线 */ + c.setLineColor(Theme.border); + /* 脚注 */ + c.setFootnoteTextColor(Theme.textMuted); + return c; + } + /** 详情里的一行「标签 + 值」(对应 WebUI `Header` 的 ``) */ @Builder MetaRow(label: string, value: string) { @@ -561,7 +635,7 @@ export struct MailDetailView { * 这是本仓同一个错法的第 N 次出现(同批一共修了 12 处)。 */ .fontColor(this.permissionMode === mode ? Theme.accentFg : Theme.textMuted) - .backgroundColor(this.permissionMode === mode ? Theme.accent : Theme.chipNeutralBg) + .backgroundColor(this.permissionMode === mode ? Theme.accent : Theme.chipBgFor()) .borderRadius(4) .padding({ left: 8, right: 8, top: 3, bottom: 3 }) .margin({ right: 6 }) @@ -626,7 +700,7 @@ export struct MailDetailView { Text(this.budgetLabel()) .fontSize(11) .fontColor(this.budgetExhausted() ? Theme.dangerFor() : Theme.textMuted) - .backgroundColor(this.budgetExhausted() ? Theme.dangerBg : Theme.chipNeutralBg) + .backgroundColor(this.budgetExhausted() ? Theme.dangerBgFor() : Theme.chipBgFor()) .borderRadius(4) .padding({ left: 8, right: 8, top: 3, bottom: 3 }) .onClick(() => { @@ -779,7 +853,7 @@ export struct MailDetailView { if (this.mailType === 'permission_request') { Text('权限请求') .fontSize(10).fontColor(Theme.warnFgFor()) - .backgroundColor(Theme.warnBg).borderRadius(4) + .backgroundColor(Theme.warnBgFor()).borderRadius(4) .padding({ left: 6, right: 6, top: 2, bottom: 2 }) .margin({ left: 6 }) } @@ -1000,7 +1074,7 @@ export struct MailDetailView { * 用第三方库 `@luvi/lv-markdown-in`(鸿蒙原生 ArkTS 渲染引擎, * **不依赖 WebView**):解析与渲染全在原生层,`ohpm install` 一行接入。 */ - Markdown({ text: this.body }) + Markdown({ text: this.body, controller: this.mdController() }) .width('100%') .padding({ left: 16, right: 16, bottom: 16 }) @@ -1208,10 +1282,30 @@ export struct MailDetailView { */ if (this.showReplyBox) { Column() { + /* + * 「回复给 …」靠左。 + * + * ★★ 2026-09-21 修(用户:「还有那邮件还是居中对齐」)。 + * + * 根因不在这个 `Text`,而在**ArkUI `Column` 的默认交叉轴对齐是 + * `HorizontalAlign.Center`**(官方《线性布局 (Row/Column)》: + * 「HorizontalAlign.Center(**默认值**):子元素在水平方向居中对齐」)。 + * + * 子元素没显式给宽时,它就跟着内容宽居中 —— 而这行 `Text` + * 只有一行短文本(`width` 未设)⇒ 整行浮在面板中间。 + * WebUI 那边是块级流,天然左对齐,所以“同样的代码”看起来不一样。 + * + * 这里是**本仓反复出现的形状**:容器默认值不等于块级流语义。 + * 修法两种:给子元素 `width('100%')`,或给容器 `alignItems(Start)`。 + * 这里选后者 —— 一次解决本条内所有子元素(含将来的), + * 而不是逐个补宽度(补漏一个就又不齐)。 + */ Text('回复给 ' + this.replyTargetAddress()) .fontSize(12).fontColor(Theme.textSubtleFor()) .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }) .margin({ bottom: 8 }) + .width('100%') + .textAlign(TextAlign.Start) TextArea({ placeholder: '输入回复内容…' }) .width('100%').height(96).fontSize(14) @@ -1240,6 +1334,15 @@ export struct MailDetailView { .margin({ top: 8 }) } .width('100%') + /* + * ★★ 2026-09-21 必须显式 `alignItems(HorizontalAlign.Start)`。 + * ArkUI `Column` 的默认交叉轴对齐是 **Center** + * (官方《线性布局 (Row/Column)》:「HorizontalAlign.Center(**默认值**): + * 子元素在水平方向居中对齐」),而 WebUI 的块级流天然左对齐 ⇒ + * 不给这句,子元素(「回复给 …」/「转发「…」」那行)就会浮在面板中间 —— + * 这正是用户报的「邮件还是居中对齐」。 + */ + .alignItems(HorizontalAlign.Start) /* `shrink-0` 的对应物:不给 `layoutWeight` ⇒ 高度由内容决定 */ .padding({ left: 16, right: 16, top: 10, bottom: 10 }) .backgroundColor(Theme.surface) @@ -1345,6 +1448,15 @@ export struct MailDetailView { .margin({ top: 8 }) } .width('100%') + /* + * ★★ 2026-09-21 必须显式 `alignItems(HorizontalAlign.Start)`。 + * ArkUI `Column` 的默认交叉轴对齐是 **Center** + * (官方《线性布局 (Row/Column)》:「HorizontalAlign.Center(**默认值**): + * 子元素在水平方向居中对齐」),而 WebUI 的块级流天然左对齐 ⇒ + * 不给这句,子元素(「回复给 …」/「转发「…」」那行)就会浮在面板中间 —— + * 这正是用户报的「邮件还是居中对齐」。 + */ + .alignItems(HorizontalAlign.Start) /* `shrink-0` 的对应物:不给 `layoutWeight` ⇒ 高度由内容决定 */ .padding({ left: 16, right: 16, top: 10, bottom: 10 }) .backgroundColor(Theme.surface) diff --git a/client/harmony/entry/src/main/ets/pages/MainPage.ets b/client/harmony/entry/src/main/ets/pages/MainPage.ets index a445ec8..a7a50dc 100644 --- a/client/harmony/entry/src/main/ets/pages/MainPage.ets +++ b/client/harmony/entry/src/main/ets/pages/MainPage.ets @@ -665,7 +665,7 @@ struct InboxTab { .fontSize(11).fontColor(Theme.warnFgDark) .width('100%') .padding({ left: 8, right: 8, top: 6, bottom: 6 }) - .backgroundColor(Theme.warnBg) + .backgroundColor(Theme.warnBgFor()) .borderRadius(6) .margin({ top: 6 }) } @@ -1631,7 +1631,7 @@ struct PermissionTab { Text('拒绝') .fontSize(13).height(36).layoutWeight(1) .textAlign(TextAlign.Center) - .backgroundColor(Theme.dangerBg).fontColor(Theme.dangerFor()) + .backgroundColor(Theme.dangerBgFor()).fontColor(Theme.dangerFor()) .borderRadius(Theme.radiusControl) // 拒绝先展开备注框,而不是直接拒:拒绝往往要说明理由,而理由是给模型看的 .onClick(() => { @@ -2947,7 +2947,7 @@ struct ContactsTab { .width('100%') .padding(12) .borderRadius(Theme.radiusControl) - .backgroundColor(Theme.dangerBg) + .backgroundColor(Theme.dangerBgFor()) .border({ width: 1, color: Theme.danger }) } diff --git a/client/harmony/entry/src/main/ets/pages/SessionsPage.ets b/client/harmony/entry/src/main/ets/pages/SessionsPage.ets index 210d07b..9733d75 100644 --- a/client/harmony/entry/src/main/ets/pages/SessionsPage.ets +++ b/client/harmony/entry/src/main/ets/pages/SessionsPage.ets @@ -131,7 +131,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.dangerFor() : Theme.textMuted) - .backgroundColor(s.used_rounds >= s.max_rounds ? Theme.dangerBg : Theme.surfaceMuted) + .backgroundColor(s.used_rounds >= s.max_rounds ? Theme.dangerBgFor() : Theme.surfaceMuted) .borderRadius(4).padding({ left: 4, right: 4, top: 1, bottom: 1 }) } diff --git a/client/harmony/entry/src/main/ets/pages/SettingsPage.ets b/client/harmony/entry/src/main/ets/pages/SettingsPage.ets index c110000..c542f33 100644 --- a/client/harmony/entry/src/main/ets/pages/SettingsPage.ets +++ b/client/harmony/entry/src/main/ets/pages/SettingsPage.ets @@ -1011,7 +1011,7 @@ export struct SettingsPane { .onClick(() => { this.newKeyToken = ''; }) } .width('100%').margin({ top: 10 }).padding(10) - .backgroundColor(Theme.warnBg) + .backgroundColor(Theme.warnBgFor()) .borderRadius(Theme.radiusControl) }