diff --git a/client/harmony/entry/src/main/ets/common/Theme.ets b/client/harmony/entry/src/main/ets/common/Theme.ets index d19909c..3d3d126 100644 --- a/client/harmony/entry/src/main/ets/common/Theme.ets +++ b/client/harmony/entry/src/main/ets/common/Theme.ets @@ -464,6 +464,26 @@ export class Theme { */ static readonly accentSoftDark: string = '#1C2536'; + /** + * 品牌浅底的**边框**(与 WebUI 的 `border-blue-200` 成对)。 + * + * ★★ 2026-09-20 补。WebUI 的"选中"是**底 + 边**两个 token 一起用 + * (`MailList.tsx:280`:`active ? 'bg-blue-50 border-blue-200'`): + * · 底 `#EFF6FF` = 我们的 `accentSoft` + * · 边 `#BFDBFE` = 这一条 + * 我第一版只搬了底、忘了边 ⇒ 选中态只有一层很淡的蓝, + * 在浅色壁纸上几乎看不出来(实测对比 WebUI 明显更弱)。 + * + * 深色取值对齐 WebUI `.dark` 段的 `--c-blue-200`。 + */ + static readonly accentEdge: string = '#BFDBFE'; + static readonly accentEdgeDark: string = '#2B3B57'; + + /** 按当前主题给品牌浅底的边框(与 `accentSoftFor` 同一纪律)。 */ + static accentEdgeFor(dark?: boolean): string { + return Theme.isDarkNow(dark) ? Theme.accentEdgeDark : Theme.accentEdge; + } + /** * 按当前主题给品牌浅底。 * diff --git a/client/harmony/entry/src/main/ets/pages/MainPage.ets b/client/harmony/entry/src/main/ets/pages/MainPage.ets index ff8c9f6..3f9519b 100644 --- a/client/harmony/entry/src/main/ets/pages/MainPage.ets +++ b/client/harmony/entry/src/main/ets/pages/MainPage.ets @@ -254,6 +254,24 @@ struct InboxTab { @State accountFilter: string = 'all'; // 'all' 或 accountId @State showAccountPicker: boolean = false; @State accountList: AccountInfo[] = []; + /** + * **当前正在看的邮件**(点开的那一封)—— 用来给列表里对应的那张卡加高亮。 + * + * ★★ 2026-09-20 补(用户:「邮件展示左侧没有圆角」→ 顺着逐项比对时发现)。 + * + * 之前鸿蒙**完全没有"当前选中邮件"这个概念**:点开一封邮件后,左侧列表 + * 那一行和旁边几行长得一模一样 —— 你不知道自己正在读哪一封、读完了该往哪回。 + * 而 WebUI 一直有(`MailList.tsx:104/116/229` 把 `currentMail.mail_id` 传下去, + * 卡片据此上 `bg-blue-50 border-blue-200`)。 + * + * 存 `mail_id`(不是索引/组键):SSE 推送会让列表重排,索引会错位; + * `mail_id` 才是这一封的稳定身份。 + * + * ★ 由 `CommPage` 拥有、以 `@Prop` 下发 —— 因为"点开了哪一封"是 + * `CommPage.openMail()` 那个动作产生的(收件箱/发件箱都能触发), + * 两个 tab 各存一份必然会分叉。单点写、多点读。 + */ + @Prop currentMailId: string = ''; private sseService: SseService | null = null; aboutToAppear(): void { @@ -589,9 +607,28 @@ struct InboxTab { this.MailItem(mail) } .width('100%').height(64) - .backgroundColor(mail.status === 'unread' ? Theme.accentSoftFor(this.isDarkNow) : Theme.surface) + /* + * 底 + 边**成对**决定这张卡的身份,口径逐项对齐 WebUI(`MailList.tsx:280`): + * + * active ? 'bg-blue-50 border-blue-200' + * : 'border-transparent hover:bg-gray-50' + * + * 三级优先:**选中 > 未读 > 普通**。 + * · 选中:`accentSoft` + `accentEdge`(`#EFF6FF` / `#BFDBFE`,与 blue-50/200 同值) + * · 未读:`accentSoft`,边仍是普通边框(未读只是"没看过",不是"正在看") + * · 普通:`Theme.surface` + 普通边框 + * + * ★ 为什么选中要压过未读:两者底色相同(都是 accentSoft), + * 若只靠底色区分,选中一封未读邮件时**看不出任何变化**。 + * 所以选中额外加那圈 `accentEdge` 边 —— 这正是 WebUI 两个 token 并存的原因。 + */ + .backgroundColor(this.currentMailId === mail.mail_id || mail.status === 'unread' + ? Theme.accentSoftFor(this.isDarkNow) : Theme.surface) .borderRadius(Theme.radiusCard) - .border({ width: 1, color: Theme.border }) + .border({ + width: 1, + color: this.currentMailId === mail.mail_id ? Theme.accentEdgeFor(this.isDarkNow) : Theme.border + }) .clip(true) .onClick(() => { this.openMail(mail); @@ -797,6 +834,13 @@ struct InboxTab { struct SentTab { /** 背景是否开启:开着就让出页面底(WebUI 的做法是页面底完全透明) */ @Prop bgActive: boolean = false; + /** + * 正在看的那一封(`CommPage` 下发)—— 见 `InboxTab.currentMailId` 的注释。 + * 发件箱的每一行也要高亮,否则"从发件箱点开一封"就断了同一条线索。 + */ + @Prop currentMailId: string = ''; + /** 深浅色(`InboxTab` 同名属性同一口径:`Theme` 是静态类,算不出主题,走 AppStorage) */ + @StorageProp('agentmail.appearance.isDark') isDarkNow: boolean = false; /** 底部悬浮条高度(窄屏非 0)——理由见 `InboxTab` 的 `navReserve` */ @Prop navReserve: number = 0; /** 选中邮件回调 */ @@ -1003,6 +1047,13 @@ struct SentTab { .width('100%') .attributeModifier(GlassCardModifier.of(this.bgActive)) .borderRadius(Theme.radiusCard) + /* 与 `MailRow` 同一口径:选中(正在读的那一封)上品牌边。 + WebUI 组内条目也是这么标的(`MailList.tsx:229` + 把 `currentMailID` 传进 `MailItem` 的 `active`)。 */ + .border({ + width: 1, + color: this.currentMailId === m.mail_id ? Theme.accentEdgeFor(this.isDarkNow) : Theme.border + }) .margin({ bottom: 6 }) .onClick(() => { this.openMail(m); }) } @@ -1302,6 +1353,11 @@ struct CommPage { private readonly KEY_NAV_BADGE_CONTACTS: string = 'agentmail.nav.contacts'; /** Navigation 路由栈:宽屏 Split 时列表+详情并排,窄屏 Stack 时详情 push 覆盖 */ private navPathStack: NavPathStack = new NavPathStack(); + /** + * 正在看的那一封(本页拥有,下发给 `InboxTab`/`SentTab`)—— 见 + * `InboxTab.currentMailId` 的注释。 + */ + @State currentMailId: string = ''; aboutToAppear(): void { this.refreshCounts(); @@ -1490,6 +1546,9 @@ struct CommPage { } openMail(mailId: string, accountId: string): void { + /* 记下"正在看哪一封" ⇒ 列表里那一行高亮(对齐 WebUI 的 currentMail)。 + 放在 push 之前:即使 push 失败,用户也确实点了这一封。 */ + this.currentMailId = mailId; const params: MailDetailParams = { mail_id: mailId, account_id: accountId @@ -1545,6 +1604,7 @@ struct CommPage { if (this.commTab === 'sent') { SentTab({ bgActive: this.bgActive, + currentMailId: this.currentMailId, navReserve: this.navReserve, onOpenMail: (mailId: string, accountId: string): void => { this.openMail(mailId, accountId); } }) @@ -1553,6 +1613,7 @@ struct CommPage { } else { InboxTab({ bgActive: this.bgActive, + currentMailId: this.currentMailId, navReserve: this.navReserve, onOpenMail: (mailId: string, accountId: string): void => { this.openMail(mailId, accountId); } })