feat(harmony): 对齐第一阶段 + 对齐计划文档(差距/分期/验收纪律)

用户:「安排对齐」。

## 先量差距(不靠感觉)

鸿蒙侧的调色板与 WebUI **根本不同**:`#1A73E8`(Google 蓝)vs 品牌 `#2563EB`、
`#333333` vs slate-900 `#0F172A`、`#F5F7FA` vs `#F8FAFC`、`#FF4444` vs red-600…
共 217 处硬编码色值散在 7 个页面里。

功能面:鸿蒙是 收件箱/会话/联系人 三个 tab,**缺 发件箱 / 授权 / 日历 / 管理**,
也没有玻璃悬浮底栏、主题壁纸同步、Composer 共用组件。

## 第一阶段(已完成并可验收)

- `common/Theme.ets` 补齐文字/浅底/语义令牌,页面里的旧调色板**全量替换为令牌**
  (共 203 处),`hvigorw assembleHap` **BUILD SUCCESSFUL**。
- 判据:`cross-client-theme.test.mjs` 新增"鸿蒙页面里不得再出现旧调色板色值"
  (6 条全绿,含扰动自检)。这条防的是**新页面又随手写个"差不多"的颜色** ——
  漂移就是这么开始的,而此前没有任何判据会红。

## 计划文档:docs/HARMONY-ALIGN-PLAN.md

写清两件事,免得每轮重新猜"还差什么":

- **差距表**(逐项,标出"缺页面/交互不同/观感不同"的性质);
- **分期**:P2 发件箱(与收件箱同构,风险最低)→ P3 授权页(备注必须随决策送达模型
  —— WebUI 侧踩过这个坑)→ P4 主题/壁纸同步(服务端"无记录"时以本地为准)
  → P5 玻璃悬浮导航 → P6 日历(最大,单独排)。

## 如实说明

鸿蒙**视觉未验证**:本机 `hdc list targets` 为空、HAP 未签名 ⇒ 只能保证编译通过 +
令牌一致,观感需要设备或签名后由人眼确认。文档里也把这条写进"验收纪律"。
This commit is contained in:
2026-09-14 12:49:40 +08:00
parent cefd96a6a3
commit 5434bc9e4e
10 changed files with 275 additions and 182 deletions

View File

@ -4,6 +4,7 @@
* 路由参数:mail_id
*/
import { ApiClient, ApiError } from '../api/ApiClient';
import { Theme } from '../common/Theme';
import { MailApi } from '../api/MailApi';
import { AccountManager, AccountInfo } from '../api/AccountManager';
import { MailDetail, SendMailRequest } from '../model/Models';
@ -116,34 +117,34 @@ struct MailDetailPage {
// 顶栏
Row() {
Text('‹')
.fontSize(24).fontColor('#1A73E8')
.fontSize(24).fontColor(Theme.accent)
.width(40).height(40)
.textAlign(TextAlign.Center)
.onClick(() => {
this.getUIContext().getRouter().back();
})
Text(this.subject.length > 20 ? this.subject.substring(0, 20) + '…' : this.subject)
.fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333')
.fontSize(16).fontWeight(FontWeight.Bold).fontColor(Theme.textPrimary)
.layoutWeight(1)
Text(this.permissionMode)
.fontSize(11).fontColor('#666666')
.fontSize(11).fontColor(Theme.textMuted)
.backgroundColor(this.permissionMode === 'full' ? '#E8F5E9' : '#FFF3E0')
.borderRadius(4).padding({ left: 6, right: 6, top: 2, bottom: 2 })
}
.width('100%').height(56)
.padding({ left: 8, right: 12 })
.backgroundColor('#FFFFFF')
.backgroundColor(Theme.surface)
if (this.loading) {
Column() {
LoadingProgress().width(40).height(40)
Text('加载中…').fontSize(14).fontColor('#999999').margin({ top: 8 })
Text('加载中…').fontSize(14).fontColor(Theme.textSubtle).margin({ top: 8 })
}
.width('100%').layoutWeight(1)
.justifyContent(FlexAlign.Center)
} else if (this.error.length > 0) {
Column() {
Text(this.error).fontSize(14).fontColor('#FF4444')
Text(this.error).fontSize(14).fontColor(Theme.danger)
Button('重试').margin({ top: 12 }).onClick(() => { this.loadMail(this.mailId); })
}
.width('100%').layoutWeight(1)
@ -154,37 +155,37 @@ struct MailDetailPage {
// 元信息卡片
Column() {
Row() {
Text('发件人').fontSize(12).fontColor('#999999').width(60)
Text(this.fromName).fontSize(14).fontColor('#333333')
Text('发件人').fontSize(12).fontColor(Theme.textSubtle).width(60)
Text(this.fromName).fontSize(14).fontColor(Theme.textPrimary)
}.width('100%').margin({ bottom: 6 })
Row() {
Text('收件人').fontSize(12).fontColor('#999999').width(60)
Text(this.toName).fontSize(14).fontColor('#333333')
Text('收件人').fontSize(12).fontColor(Theme.textSubtle).width(60)
Text(this.toName).fontSize(14).fontColor(Theme.textPrimary)
}.width('100%').margin({ bottom: 6 })
if (this.sessionAlias.length > 0) {
Row() {
Text('会话').fontSize(12).fontColor('#999999').width(60)
Text(this.sessionAlias).fontSize(13).fontColor('#1A73E8')
Text('会话').fontSize(12).fontColor(Theme.textSubtle).width(60)
Text(this.sessionAlias).fontSize(13).fontColor(Theme.accent)
}.width('100%').margin({ bottom: 6 })
}
Row() {
Text('时间').fontSize(12).fontColor('#999999').width(60)
Text(this.createdAt).fontSize(13).fontColor('#666666')
Text('时间').fontSize(12).fontColor(Theme.textSubtle).width(60)
Text(this.createdAt).fontSize(13).fontColor(Theme.textMuted)
}.width('100%').margin({ bottom: 6 })
Row() {
Text('权限').fontSize(12).fontColor('#999999').width(60)
Text('权限').fontSize(12).fontColor(Theme.textSubtle).width(60)
if (this.switchingPerm) {
LoadingProgress().width(16).height(16)
} else {
ForEach(['plan', 'workspace', 'full'], (mode: string) => {
Text(mode)
.fontSize(11)
.fontColor(this.permissionMode === mode ? '#FFFFFF' : '#666666')
.backgroundColor(this.permissionMode === mode ? '#1A73E8' : '#F0F0F0')
.fontColor(this.permissionMode === mode ? Theme.surface : Theme.textMuted)
.backgroundColor(this.permissionMode === mode ? Theme.accent : Theme.border)
.borderRadius(4)
.padding({ left: 8, right: 8, top: 3, bottom: 3 })
.margin({ right: 6 })
@ -195,7 +196,7 @@ struct MailDetailPage {
}
.width('100%')
.padding(16)
.backgroundColor('#F8F9FA')
.backgroundColor(Theme.pageBg)
.borderRadius(8)
.margin({ left: 12, right: 12, top: 8 })
@ -203,7 +204,7 @@ struct MailDetailPage {
Text(this.subject)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
.fontColor(Theme.textPrimary)
.width('100%')
.padding({ left: 16, right: 16, top: 16, bottom: 8 })
@ -225,7 +226,7 @@ struct MailDetailPage {
Row() {
Button('↩ 回复')
.width('90%').height(44)
.backgroundColor('#1A73E8')
.backgroundColor(Theme.accent)
.fontSize(15)
.onClick(() => {
this.showReplyBox = true;
@ -233,7 +234,7 @@ struct MailDetailPage {
}
.width('100%').height(56)
.justifyContent(FlexAlign.Center)
.backgroundColor('#FFFFFF')
.backgroundColor(Theme.surface)
// 回复弹层
if (this.showReplyBox) {
@ -247,7 +248,7 @@ struct MailDetailPage {
// 回复框
Column() {
Text('回复给 ' + this.fromName)
.fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333')
.fontSize(16).fontWeight(FontWeight.Bold).fontColor(Theme.textPrimary)
.margin({ bottom: 12 })
TextArea({ placeholder: '输入回复内容…' })
@ -258,8 +259,8 @@ struct MailDetailPage {
Row() {
Button('取消')
.width(80).height(36)
.backgroundColor('#F5F5F5')
.fontColor('#666666')
.backgroundColor(Theme.surfaceMuted)
.fontColor(Theme.textMuted)
.fontSize(13)
.onClick(() => { this.showReplyBox = false; })
@ -267,7 +268,7 @@ struct MailDetailPage {
Button(this.sending ? '发送中…' : '发送')
.width(80).height(36)
.backgroundColor('#1A73E8')
.backgroundColor(Theme.accent)
.fontSize(13)
.enabled(!this.sending && this.replyBody.length > 0)
.onClick(() => { this.doReply(); })
@ -278,7 +279,7 @@ struct MailDetailPage {
.width('100%')
.height('60%')
.padding(16)
.backgroundColor('#FFFFFF')
.backgroundColor(Theme.surface)
.borderRadius({ topLeft: 12, topRight: 12 })
}
.width('100%').height('100%')
@ -287,7 +288,7 @@ struct MailDetailPage {
}
}
.width('100%').height('100%')
.backgroundColor('#F5F7FA')
.backgroundColor(Theme.pageBg)
}
async doReply(): Promise<void> {