fix(harmony): 令牌收尾 —— 裸色值清零、遮罩拆两段式、权限档位配色对齐 WebUI
接手核对时发现:「不许写死颜色」那条判据**只挡得住枚举的 8 个旧值** —— 判据全绿的 同期,pages/ 里还留着 14 处另一套写死的色(Google/Material:#E8F0FE、#E8F5E9、 #FFF3E0、#D93025、#777777×2、#555555、#444444、#cccccc、#aaaaaa、 遮罩 #80000000×2、透明 #00000000×2)。枚举挡不住漂移,只有"类"能挡。 ## 改法 - 14 处全部换成令牌。新增 accentStrong / warnBg / warnFg(取值对齐 WebUI `:root` 的 blue-700 / amber-50 / amber-700)与 `Theme.permBg/permFg` —— 权限档位徽标与 WebUI 的 `PermissionChip.tsx` **同一映射**(plan=蓝 / workspace=绿 / full=琥珀); 原先写成 `full ? 绿 : 橙`(Material 色),与 WebUI **反着来**。 - 遮罩拆成 `overlayColor` + `overlayAlpha`(照 WebUI 的 `--bg-scrim` + `--bg-dim` 两段式):遮罩色要能随主题换向,色与透明度焊死成一个 `#AARRGGBB` 等于把枚举写回 代码。ArkUI 只认单值,故由 `Theme.overlay()` 组装。 - 判据从"枚举旧值"改成"按类挡":pages/ 下**一个裸色值都不许有**(含 8 位 `#AARRGGBB`),页面清单从硬编码 7 个文件名改成**扫目录** —— 旧写法下, 接下来要加的发件箱/授权/日历会自动逃出判据。另加一条判据:权限档位配色与 WebUI `:root` 变量逐一比对,防"看起来差不多"。 ## 验证 - 变异测试:往 `InboxPage.ets` 塞一个 `#E8F0FE` → 判据红;撤回 → 绿。 - cross-client 判据 6 → 8 条全绿;`hvigorw assembleHap` BUILD SUCCESSFUL。 - **视觉未验**(模拟器在本机文件沙箱下起不来,见 `docs/HARMONY-ALIGN-PLAN.md` 5.4), 未写成"已完成"。
This commit is contained in:
@ -49,6 +49,61 @@ export class Theme {
|
||||
static readonly approveBg: string = '#F0FDF4';
|
||||
static readonly approveFg: string = '#15803D';
|
||||
|
||||
/** 品牌蓝的**深**前景(对应 WebUI 的 --c-blue-700: 29 78 216)—— 与 accentSoft 成对用于 chip */
|
||||
static readonly accentStrong: string = '#1D4ED8';
|
||||
/** 警示面/前景(对应 WebUI 的 --c-amber-50 / --c-amber-700)—— 权限 full 档、待决策徽标 */
|
||||
static readonly warnBg: string = '#FFFBEB';
|
||||
static readonly warnFg: string = '#B45309';
|
||||
/**
|
||||
* 遮罩(模态/淡出层):**颜色与透明度分开**,照 WebUI 的 `--bg-scrim` + `--bg-dim`
|
||||
* 两段式(`index.css` 的 `.app-backdrop::after`)。
|
||||
*
|
||||
* 为什么不写成一个 `#80000000`:遮罩色要能**随主题换向** —— 浅色主题用白把图案洗淡,
|
||||
* 深色主题必须换黑,否则浅色照片在深色界面里糊成一块亮斑、正文读不动(WebUI 侧实测踩过)。
|
||||
* 色与透明度焊死成一个 AARRGGBB,换向时只能再写一个常量,于是又变成枚举 ——
|
||||
* 那正是这条判据要防的东西。
|
||||
*
|
||||
* 注意:这是**唯一没有 WebUI 同名令牌**的一组(WebUI 的弹层不压遮罩,
|
||||
* 它那份 scrim 是给壁纸调暗用的),所以不往 WebUI 立同名令牌 —— 立了没人用、
|
||||
* 判据只能验"它存在",是自证。
|
||||
*/
|
||||
static readonly overlayColor: string = '#000000';
|
||||
static readonly overlayAlpha: number = 0.5;
|
||||
|
||||
/** 遮罩色 → ArkUI 只认的 `#AARRGGBB` 单值(鸿蒙没有"色 + 透明度"两参的重载) */
|
||||
static overlay(): string {
|
||||
const a: number = Math.round(Theme.overlayAlpha * 255);
|
||||
const hex: string = a.toString(16).toUpperCase();
|
||||
return '#' + (hex.length < 2 ? '0' + hex : hex) + Theme.overlayColor.substring(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限档位 → 徽标底色 / 文字色。
|
||||
*
|
||||
* 与 WebUI 的 `PermissionChip.tsx` **同一映射**(plan=蓝 / workspace=绿 / full=琥珀),
|
||||
* 认不出的档位与空档位按 workspace 处理 —— 映射只有这一处实现,
|
||||
* 页面不再各自 if-else 挑颜色。
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/** 圆角:卡片 14、控件 8(与 WebUI 的 --radius-card / --radius-control 一致) */
|
||||
static readonly radiusCard: number = 14;
|
||||
static readonly radiusControl: number = 8;
|
||||
|
||||
@ -248,7 +248,7 @@ struct ComposePage {
|
||||
Row() {
|
||||
Text('收件人').fontSize(14).fontColor(Theme.textSubtle).width(60)
|
||||
TextInput({ placeholder: 'name@path.session', text: this.to })
|
||||
.layoutWeight(1).fontSize(14).backgroundColor('#00000000')
|
||||
.layoutWeight(1).fontSize(14).backgroundColor(Color.Transparent)
|
||||
.onChange((v: string) => { this.to = v; })
|
||||
}
|
||||
.width('100%').height(48).padding({ left: 12, right: 12 })
|
||||
@ -260,7 +260,7 @@ struct ComposePage {
|
||||
Row() {
|
||||
Text('主题').fontSize(14).fontColor(Theme.textSubtle).width(60)
|
||||
TextInput({ placeholder: '邮件主题', text: this.subject })
|
||||
.layoutWeight(1).fontSize(14).backgroundColor('#00000000')
|
||||
.layoutWeight(1).fontSize(14).backgroundColor(Color.Transparent)
|
||||
.onChange((v: string) => { this.subject = v; })
|
||||
}
|
||||
.width('100%').height(48).padding({ left: 12, right: 12 })
|
||||
|
||||
@ -165,7 +165,7 @@ struct LoginPage {
|
||||
Row() {
|
||||
Text('账号密码').fontSize(14).fontColor(this.mode === 0 ? Theme.accent : Theme.textSubtle)
|
||||
.onClick(() => { this.mode = 0; })
|
||||
Text(' | ').fontSize(14).fontColor('#cccccc')
|
||||
Text(' | ').fontSize(14).fontColor(Theme.border)
|
||||
Text('用户密钥').fontSize(14).fontColor(this.mode === 1 ? Theme.accent : Theme.textSubtle)
|
||||
.onClick(() => { this.mode = 1; })
|
||||
}
|
||||
@ -197,7 +197,7 @@ struct LoginPage {
|
||||
|
||||
// 模拟器备选地址提示
|
||||
Text('模拟器 NAT 不通时用 ' + EMULATOR_HOST_BASE)
|
||||
.fontSize(11).fontColor('#aaaaaa').margin({ top: 20 })
|
||||
.fontSize(11).fontColor(Theme.textSubtle).margin({ top: 20 })
|
||||
|
||||
if (this.loggedIn) {
|
||||
// 登录成功 → 进入主界面(占位,后续 M2 替换)
|
||||
|
||||
@ -127,8 +127,8 @@ struct MailDetailPage {
|
||||
.fontSize(16).fontWeight(FontWeight.Bold).fontColor(Theme.textPrimary)
|
||||
.layoutWeight(1)
|
||||
Text(this.permissionMode)
|
||||
.fontSize(11).fontColor(Theme.textMuted)
|
||||
.backgroundColor(this.permissionMode === 'full' ? '#E8F5E9' : '#FFF3E0')
|
||||
.fontSize(11).fontColor(Theme.permFg(this.permissionMode))
|
||||
.backgroundColor(Theme.permBg(this.permissionMode))
|
||||
.borderRadius(4).padding({ left: 6, right: 6, top: 2, bottom: 2 })
|
||||
}
|
||||
.width('100%').height(56)
|
||||
@ -210,7 +210,7 @@ struct MailDetailPage {
|
||||
|
||||
Text(this.body)
|
||||
.fontSize(15)
|
||||
.fontColor('#444444')
|
||||
.fontColor(Theme.textPrimary)
|
||||
.width('100%')
|
||||
.padding({ left: 16, right: 16, bottom: 16 })
|
||||
.lineHeight(24)
|
||||
@ -242,7 +242,7 @@ struct MailDetailPage {
|
||||
// 遮罩
|
||||
Column()
|
||||
.width('100%').layoutWeight(1)
|
||||
.backgroundColor('#80000000')
|
||||
.backgroundColor(Theme.overlay())
|
||||
.onClick(() => { this.showReplyBox = false; })
|
||||
|
||||
// 回复框
|
||||
|
||||
@ -344,7 +344,7 @@ struct InboxTab {
|
||||
if (this.accountFilter === 'all' && mail.source_account_name.length > 0) {
|
||||
Text(mail.source_account_name)
|
||||
.fontSize(10).fontColor(Theme.accent)
|
||||
.backgroundColor('#E8F0FE').borderRadius(4)
|
||||
.backgroundColor(Theme.accentSoft).borderRadius(4)
|
||||
.padding({ left: 5, right: 5, top: 1, bottom: 1 })
|
||||
.margin({ left: 6 })
|
||||
}
|
||||
@ -366,8 +366,8 @@ struct InboxTab {
|
||||
|
||||
if (mail.permission_mode.length > 0) {
|
||||
Text(mail.permission_mode)
|
||||
.fontSize(10).fontColor(Theme.textMuted)
|
||||
.backgroundColor(Theme.border).borderRadius(4)
|
||||
.fontSize(10).fontColor(Theme.permFg(mail.permission_mode))
|
||||
.backgroundColor(Theme.permBg(mail.permission_mode)).borderRadius(4)
|
||||
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
|
||||
}
|
||||
}
|
||||
@ -471,8 +471,8 @@ struct SessionsTab {
|
||||
|
||||
Row() {
|
||||
Text(s.permission_mode.length > 0 ? s.permission_mode : '—')
|
||||
.fontSize(11).fontColor(Theme.textMuted)
|
||||
.backgroundColor(Theme.border).borderRadius(4)
|
||||
.fontSize(11).fontColor(Theme.permFg(s.permission_mode))
|
||||
.backgroundColor(Theme.permBg(s.permission_mode)).borderRadius(4)
|
||||
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
|
||||
Blank()
|
||||
Text(s.mail_count + ' 封').fontSize(11).fontColor(Theme.textSubtle)
|
||||
@ -587,8 +587,8 @@ struct ContactsTab {
|
||||
|
||||
Row() {
|
||||
Text(c.permission_mode.length > 0 ? c.permission_mode : '—')
|
||||
.fontSize(10).fontColor(Theme.textMuted)
|
||||
.backgroundColor(Theme.border).borderRadius(4)
|
||||
.fontSize(10).fontColor(Theme.permFg(c.permission_mode))
|
||||
.backgroundColor(Theme.permBg(c.permission_mode)).borderRadius(4)
|
||||
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
|
||||
Blank()
|
||||
Text(c.last_preview.length > 0 ? c.last_preview : '—')
|
||||
|
||||
@ -153,7 +153,7 @@ struct SettingsPage {
|
||||
|
||||
if (this.accounts.length === 0) {
|
||||
Column() {
|
||||
Text('📭 暂无账号').fontSize(16).fontColor('#777777')
|
||||
Text('📭 暂无账号').fontSize(16).fontColor(Theme.textMuted)
|
||||
Button('添加第一个账号')
|
||||
.margin({ top: 16 }).backgroundColor(Theme.accent)
|
||||
.onClick(() => { this.showAddDialog = true; })
|
||||
@ -176,7 +176,7 @@ struct SettingsPage {
|
||||
Column() {
|
||||
Column()
|
||||
.width('100%').layoutWeight(1)
|
||||
.backgroundColor('#80000000')
|
||||
.backgroundColor(Theme.overlay())
|
||||
.onClick(() => { this.showAddDialog = false; })
|
||||
|
||||
Column() {
|
||||
@ -202,7 +202,7 @@ struct SettingsPage {
|
||||
|
||||
Row() {
|
||||
Button('取消')
|
||||
.width(80).height(36).backgroundColor(Theme.surfaceMuted).fontColor('#555555')
|
||||
.width(80).height(36).backgroundColor(Theme.surfaceMuted).fontColor(Theme.textMuted)
|
||||
.onClick(() => { this.showAddDialog = false; })
|
||||
Blank()
|
||||
Button(this.adding ? '验证中…' : '添加')
|
||||
@ -243,7 +243,7 @@ struct SettingsPage {
|
||||
.width('100%')
|
||||
|
||||
Text((account.username.length > 0 ? account.username + ' · ' : '') + account.server)
|
||||
.fontSize(12).fontColor('#777777')
|
||||
.fontSize(12).fontColor(Theme.textMuted)
|
||||
.margin({ top: 3 })
|
||||
.maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||
}
|
||||
@ -259,7 +259,7 @@ struct SettingsPage {
|
||||
}
|
||||
|
||||
Text('删除')
|
||||
.fontSize(12).fontColor('#D93025')
|
||||
.fontSize(12).fontColor(Theme.danger)
|
||||
.onClick(() => { this.removeAccount(account.id); })
|
||||
}
|
||||
.width('100%').height('100%')
|
||||
|
||||
Reference in New Issue
Block a user