/* * AgentMail 鸿蒙客户端 — 设计令牌 * * ## 这个文件在 2026-09-14 换了一次"来源"(jianf:「鸿蒙要求用系统方案」) * * 原先这里的每一格都是**手抄 WebUI 的色值**(`#F8FAFC`、`#0F172A`…)。现在分成两类, * 分界线是"**系统有没有这份语义**": * * - **表面 / 文字 / 分隔 / 遮罩 / 圆角 / 材质 —— 交给系统**(`$r('sys.color.*')`、 * `$r('sys.float.*')`、`BlurStyle.*`)。理由不是"看起来更原生",而是三条实际的: * ① 它们会**跟随深色模式**(手抄的值不会,这正是 WebUI 那边"半深不浅"的病根); * ② 跟随系统动效/无障碍设置; * ③ 少一处会漂移的副本 —— 这些格子的值不再由我们决定,也就不会再和系统打架。 * - **品牌色与业务语义色 —— 仍然自己写**:`accent = #2563EB` 是**跨客户端身份** * (两个客户端是同一个产品),不能退化成随主题/厂商皮肤变的系统强调色; * 权限三档(蓝/绿/琥珀)与预算三档(灰/橙/红)在系统里**没有对应物** * (系统只有 warning/alert 两个"情绪色"),硬套会把语义丢掉。 * * ## 名字不是猜的 * * `sys.*` 的名字一旦写错,**编译期不报**、只有真机运行到那一行才炸 —— 而本机没有设备。 * 所以名字全部对着 SDK 自带的系统资源名表核过: * `sdk/default/openharmony/toolchains/id_defined.json`(本机 API 26,7826 条)。 * 而且有一条判据(`client/electron/test/harmony-system-api.test.mjs`)持续盯着这件事: * 源码里每个 `$r('sys..')` 都必须在表里存在且类型相符。 * * ## 保留的取舍 * * - WebUI 的"正文面"是 0.88 不透明的玻璃(字要读得清);鸿蒙用的系统卡片底色也是不透明的, * 结论一致:**正文面不透,只有浮在内容之上的那一层用材质**("玻璃只出现在一层")。 * - 字体大小仍与 WebUI 的 text-2xs/xs/sm 对齐(字号不是"系统方案"要解决的问题, * 两边的版式意图是同一套)。 */ export class Theme { // ─────────────── 表面:系统语义色 ─────────────── /** 页面底色(系统 `ohos_id_color_background`:浅色白/深色深灰,自动跟随主题) */ static readonly pageBg: Resource = $r('sys.color.ohos_id_color_background'); /** 承载文字的面 = **列表卡片底色**(对应"每项一张卡/气泡",而不是通栏底色) */ static readonly surface: Resource = $r('sys.color.ohos_id_color_list_card_bg'); /** 次级面(分组底、列表行 hover) */ static readonly surfaceMuted: Resource = $r('sys.color.ohos_id_color_sub_background'); /** 分隔线 */ static readonly border: Resource = $r('sys.color.ohos_id_color_list_separator'); // ─────────────── 文字:系统语义色(三级) ─────────────── static readonly textPrimary: Resource = $r('sys.color.ohos_id_color_text_primary'); static readonly textMuted: Resource = $r('sys.color.ohos_id_color_text_secondary'); static readonly textSubtle: Resource = $r('sys.color.ohos_id_color_text_tertiary'); // ─────────────── 遮罩与材质:交给系统 ─────────────── /** * 遮罩(模态/淡出层):系统遮罩色。 * * 这里原来存着 `overlayColor` + `overlayAlpha` 两个常量,再用 `overlay()` 拼成 * `#AARRGGBB` —— 之所以要"色与透明度分开",是因为**遮罩色必须随主题换向** * (浅色主题用白把图案洗淡、深色主题必须换黑,否则浅色照片在深色界面里糊成一块亮斑)。 * 那件事现在由系统做:一个语义色就够,且换向不会再漏。 */ static readonly overlay: Resource = $r('sys.color.ohos_id_color_mask_regular'); /** * 导航/浮层的材质档次。**不再有 `#B8FFFFFF` / `#B80F172A` 这种手写玻璃 alpha** —— * 那两个值等于"我们替系统猜了深色该怎么做",与"用系统方案"直接冲突; * 而材质档次是系统给的,深浅两套颜色由系统按主题挑。 * * 用 `COMPONENT_THICK`:贴在界面组件上的一层材质(导航条正属于这一类)。 */ static readonly navMaterial: BlurStyle = BlurStyle.COMPONENT_THICK; // ─────────────── 圆角:系统尺寸 ─────────────── /** 卡片圆角:系统"卡片"圆角(不再与 WebUI 的 14vp 绑死 —— 允许各自跟随系统) */ static readonly radiusCard: Resource = $r('sys.float.ohos_id_corner_radius_card'); /** 控件圆角:系统"按钮"圆角 */ static readonly radiusControl: Resource = $r('sys.float.ohos_id_corner_radius_button'); /* * ─────────────── 手写色**登记表**(改这里要一起改判据) ─────────────── * * 为什么需要这张表:本文件里"自己写的色值"是有理由的(品牌身份 + 系统没有对应物的 * 业务语义色),但**理由不能靠记忆**。判据(`cross-client-theme.test.mjs` 的 * A2 条)会枚举本文件里所有 `static readonly X: string = '#……'`, * 发现**未登记的名字就判红** —— 于是"新写死一个色"必须显式过一道: * 要么登记在这里并写清理由,要么它本来就该走 `$r('sys.*')`。 * * 这条是 pi 读出来的真缺口:只枚举 11 个"必须是系统资源"的名字,挡不住 * 第 12 个**新加的手写色**(它不在名单里,于是 A/B/裸色值三条都碰不到它)。 * 「枚举挡实例,类才挡漂移」—— 这次枚举的是**名字**,所以要有名单。 * * 登记项(17 个,名字与判据里的 SELF_OWNED_COLORS 逐字一致 —— 逐个列出而不是缩写, * 这样 grep 一个名字就能找到它的理由): * * 品牌(跨客户端身份,系统给不了): * · accent #2563EB 与 WebUI 的 --c-blue-600 逐字一致 * · accentFg #FFFFFF 品牌底上的文字 * · accentSoft #EFF6FF 品牌浅底(选中态背景) * · accentStrong #1D4ED8 品牌深色变体(选中文字/边框) * * 业务语义(系统只有 list/warning/alert 这一档,没有"同意/拒绝"): * · approve #15803D 同意(绿) * · approveBg #F0FDF4 同意底 * · approveFg #15803D 同意文字 * · danger #B91C1C 拒绝/危险(红) * · dangerBg #FEF2F2 拒绝底 * · warnBg #FFFBEB 警示底(对应 WebUI --c-amber-50) * · warnFg #B45309 警示文字(对应 WebUI --c-amber-700) * * 档位胶囊(档位是产品语义:系统不认识 plan / workspace / full): * · chipNeutralBg #F3F4F6 plan 档底(最宽档但不报警) * · chipNeutralFg #5A6270 plan 档文字 * · chipSpentBg #FEE2E2 预算耗尽底 * · chipSpentFg #B91C1C 预算耗尽文字 * · chipWarnBg #FFEDD5 预算紧张底 * · chipWarnFg #C2410C 预算紧张文字 * * 不在这里的手写色只有一种合法去处:`$r('sys.*')`(跟随系统/深色模式)。 */ // ─────────────── 品牌色:跨客户端身份,必须自己写 ─────────────── /** * 品牌蓝(按钮、链接、焦点环)。 * * **不要**为了"用系统方案"把它换成系统的强调色:系统强调色会随主题/厂商皮肤变, * 一旦换过去,"两个客户端是同一个产品"这件事就靠不住了。 * 这是整个文件里**唯一必须与 WebUI 逐字一致**的取值(判据钉住)。 */ static readonly accent: string = '#2563EB'; static readonly accentFg: string = '#FFFFFF'; /** 品牌蓝的浅底 / 深前景(与 WebUI 的 blue-50 / blue-700 成对) */ static readonly accentSoft: string = '#EFF6FF'; static readonly accentStrong: string = '#1D4ED8'; /** 导航文字:未选中 / 选中(对应 --nav-fg-muted / --nav-active-fg) */ static readonly navFg: Resource = $r('sys.color.ohos_id_color_text_secondary'); static readonly navFgActive: string = Theme.accentStrong; // ─────────────── 业务语义色:系统没有对应物,继续自己写 ─────────────── /** 语义色:同意 / 拒绝(对应 WebUI 的 approve/danger) */ static readonly approve: string = '#15803D'; static readonly danger: string = '#B91C1C'; static readonly dangerBg: string = '#FEF2F2'; static readonly approveBg: string = '#F0FDF4'; static readonly approveFg: string = '#15803D'; /** 警示面/前景(对应 WebUI 的 --c-amber-50 / --c-amber-700)—— 权限 full 档、待决策徽标 */ static readonly warnBg: string = '#FFFBEB'; static readonly warnFg: string = '#B45309'; /** * 权限档位 → 徽标底色 / 文字色。 * * 与 WebUI 的 `PermissionChip.tsx` **同一映射**(plan=蓝 / workspace=绿 / full=琥珀), * 认不出的档位与空档位按 workspace 处理 —— 映射只有这一处实现, * 页面不再各自 if-else 挑颜色。 * * 为什么不用系统情绪色:系统只有 warning/alert 两个,凑不出三档; * 而档位是**可被文档与判据按名字引用的枚举标识**(plan/workspace/full), * 用情绪色顶替它,界面对了语义丢了。 */ static permBg(mode: string): string { if (mode === 'plan') { return Theme.accentSoft; } if (mode === 'full') { return Theme.warnBg; } return Theme.approveBg; } static permFg(mode: string): string { if (mode === 'plan') { return Theme.accentStrong; } if (mode === 'full') { return Theme.warnFg; } return Theme.approveFg; } /** 中性 chip(对应 WebUI 的 --c-gray-100 / --c-gray-500)—— 预算条"还宽裕"档 */ static readonly chipNeutralBg: string = '#F3F4F6'; static readonly chipNeutralFg: string = '#5A6270'; /** 预算用尽(对应 WebUI 的 --c-red-100 / --c-red-700) */ static readonly chipSpentBg: string = '#FEE2E2'; static readonly chipSpentFg: string = '#B91C1C'; /** 预算将尽(对应 WebUI 的 --c-orange-100 / --c-orange-700) */ static readonly chipWarnBg: string = '#FFEDD5'; static readonly chipWarnFg: string = '#C2410C'; /** * 往返预算档位 → chip 底色 / 字色。 * * 与 WebUI 的 `BudgetChip` **同一映射**(`WorkCard.tsx`): * 剩 0 = 红(用尽)、剩 ≤1 = 橙(将尽,任务需要人介入)、其余 = 中性灰。 * 档位本身由 `MailGrouping.ts` 的 `budgetState()` 算(那是可被判据执行的一层), * 这里只管"哪个档用什么颜色"。这三个色**不进跨端"取值相同"判据**(业务局部), * 但必须进枚举完整性判据(三档齐全、认不出的归一到中性档)。 */ static budgetBg(state: string): string { if (state === 'spent') { return Theme.chipSpentBg; } if (state === 'warn') { return Theme.chipWarnBg; } return Theme.chipNeutralBg; } static budgetFg(state: string): string { if (state === 'spent') { return Theme.chipSpentFg; } if (state === 'warn') { return Theme.chipWarnFg; } return Theme.chipNeutralFg; } /** 字体大小(与 WebUI 的 text-2xs/xs/sm 对齐) */ static readonly fontTiny: number = 11; static readonly fontSmall: number = 12; static readonly fontBody: number = 14; }