harmony(ui): 登录页 1:1 对齐 WebUI 卡片规格 + 通信页改用系统 Navigation Auto
登录页(LoginPage.ets):对照 WebUI LoginPage.tsx 同规格重写 build() - maxWidth 380vp 居中卡片(与 WebUI 380px 同档) - 14vp 圆角(Theme.glassRadius) - 48vp 品牌图标(Envelope 浅蓝底 accentSoft) - 显式字段标签(服务器/用户名/密码/用户密钥) - 40vp 输入框与登录按钮(WebUI 控件同高) - 分段式登录方式切换(账号密码 / 用户密钥) - 窄屏安全边距 16vp + Scroll 可滚 通信页(MainPage.ets CommPage):改用鸿蒙系统 Navigation 组件 - Navigation(navPathStack) 包住列表,navDestination 注册邮件详情目标页 - mode(NavigationMode.Auto):phone 自动 Stack、tablet/2in1 自动 Split - navBarWidth(320) 对齐 WebUI MailList 320px;navBarWidthRange([280,360]) - minContentWidth(360) 参与 Auto 断点计算 - Inbox/Sent 选中邮件改走 navPathStack.pushPath,不再 router.pushUrl - 切收件箱/发件箱/授权时清空详情栈避免跨栏残留 详情页(MailDetailPage.ets):抽出可复用 MailDetailView - 既有详情实现导出为 @Component struct MailDetailView - 新增 initialMailId/initialAccountId/embedded/onBack 输入 - 无 initialMailId 时才读路由参数(兼容旧 router 入口) - embedded=true 时返回走 onBack 回调(NavPathStack.pop) - 薄壳 @Entry MailDetailPage 用 Column 包裹(@Entry 根节点须容器) module.json5:deviceTypes 扩展为 phone/tablet/2in1 三形态 判据:harmony-widescreen.test.mjs 新增 ⑥ 钉住 Navigation Auto 不许退回手搓分栏 验证:ArkTS 编译通过;harmony-arkts/widescreen/nav/cross-client-theme 35/35 通过
This commit is contained in:
@ -89,6 +89,33 @@ test('④ MainPage 接线:宽屏才挂侧栏、宽屏藏底部条、断点 768
|
||||
assert.match(code_, /bottom:\s*this\.isWide \? 0 : NAV_CONTENT_RESERVE/, '宽屏内容 padding 要归零(没有底部条了)');
|
||||
});
|
||||
|
||||
test('⑥ 邮件列表→详情使用系统 Navigation Auto,不再手搓 Row 分栏', () => {
|
||||
const main = read('pages/MainPage.ets');
|
||||
const code_ = stripComments(main);
|
||||
/*
|
||||
* phone / tablet / 2in1 的列表→详情行为交给系统:
|
||||
* - 窄宽度自动 Stack(详情覆盖列表,可返回);
|
||||
* - 宽窗口自动 Split(列表 + 详情并排)。
|
||||
* 断点由 navBarWidthRange + minContentWidth 共同决定,不复制一套 onAreaChange。
|
||||
*/
|
||||
assert.match(code_, /private navPathStack: NavPathStack = new NavPathStack\(\)/,
|
||||
'通信页必须持有 NavPathStack');
|
||||
assert.match(code_, /Navigation\(this\.navPathStack\)/,
|
||||
'通信页列表/详情必须由原生 Navigation 承载');
|
||||
assert.match(code_, /\.navDestination\(this\.DestinationBuilder\)/,
|
||||
'Navigation 必须注册邮件详情目标页 builder');
|
||||
assert.match(code_, /\.mode\(NavigationMode\.Auto\)/,
|
||||
'必须用 NavigationMode.Auto,让系统自动选择 Stack / Split');
|
||||
assert.match(code_, /\.navBarWidth\(320\)/,
|
||||
'宽屏列表栏应与 WebUI MailList 的 320px 同档');
|
||||
assert.match(code_, /\.navBarWidthRange\(\[280, 360\]\)/,
|
||||
'列表栏应允许系统在 280–360vp 内适配,而不是固定死布局');
|
||||
assert.match(code_, /\.minContentWidth\(360\)/,
|
||||
'详情栏最小宽度参与 Auto 断点计算');
|
||||
assert.match(code_, /this\.navPathStack\.pushPath\(\{ name: MAIL_DETAIL_ROUTE, param: params \}\)/,
|
||||
'选中邮件必须进入 NavPathStack,而不是绕回 router.pushUrl');
|
||||
});
|
||||
|
||||
test('⑤ 宽屏 app-shell 几何:padding/gap/radius 与 WebUI 同值(一比一复刻的骨架)', () => {
|
||||
const main = read('pages/MainPage.ets');
|
||||
const code_ = stripComments(main);
|
||||
|
||||
@ -161,87 +161,163 @@ struct LoginPage {
|
||||
}
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
// 标题区
|
||||
Scroll() {
|
||||
Column() {
|
||||
Text('AgentMail')
|
||||
.fontSize(28)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
.fontColor(Theme.accent)
|
||||
Text('邮件驱动 · 多智能体协作平台')
|
||||
.fontSize(14)
|
||||
Column() {
|
||||
// 与 WebUI 登录页一致:品牌标识不是巨幅文字,而是一枚紧凑的浅蓝图标卡。
|
||||
Stack() {
|
||||
Text('✉')
|
||||
.fontSize(22)
|
||||
.fontColor(Theme.accent)
|
||||
}
|
||||
.width(48)
|
||||
.height(48)
|
||||
.borderRadius(12)
|
||||
.backgroundColor(Theme.accentSoft)
|
||||
|
||||
Text('AgentMail')
|
||||
.fontSize(16)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(Theme.textPrimary)
|
||||
.margin({ top: 12 })
|
||||
Text('邮件驱动的多智能体协作平台')
|
||||
.fontSize(12)
|
||||
.fontColor(Theme.textMuted)
|
||||
.margin({ top: 4 })
|
||||
}
|
||||
.width('100%')
|
||||
.alignItems(HorizontalAlign.Center)
|
||||
.margin({ bottom: 24 })
|
||||
|
||||
Text('服务器地址')
|
||||
.width('100%')
|
||||
.fontSize(11)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(Theme.textMuted)
|
||||
.margin({ top: 6 })
|
||||
}
|
||||
.margin({ top: 100, bottom: 48 })
|
||||
|
||||
// 服务器地址
|
||||
TextInput({ placeholder: '服务器地址(含 /api/v1)', text: this.serverAddr })
|
||||
.width('85%')
|
||||
.height(48)
|
||||
.margin({ bottom: 12 })
|
||||
.onChange((v: string) => {
|
||||
this.serverAddr = v;
|
||||
// 边输边算:合规性提示在按下"登录"之前就看得见
|
||||
this.addrWarning = validateApiBase(v).warning;
|
||||
})
|
||||
|
||||
if (this.addrWarning.length > 0) {
|
||||
Text(this.addrWarning)
|
||||
.fontSize(11).fontColor(Theme.warnFg)
|
||||
.width('85%').margin({ bottom: 12 })
|
||||
}
|
||||
|
||||
// 切换登录方式
|
||||
Row() {
|
||||
Text('账号密码').fontSize(14).fontColor(this.mode === 0 ? Theme.accent : Theme.textSubtle)
|
||||
.onClick(() => { this.mode = 0; })
|
||||
Text(' | ').fontSize(14).fontColor(Theme.border)
|
||||
Text('用户密钥').fontSize(14).fontColor(this.mode === 1 ? Theme.accent : Theme.textSubtle)
|
||||
.onClick(() => { this.mode = 1; })
|
||||
}
|
||||
.margin({ bottom: 16 })
|
||||
|
||||
if (this.mode === 0) {
|
||||
TextInput({ placeholder: '用户名', text: this.username })
|
||||
.width('85%').height(48).margin({ bottom: 12 })
|
||||
.onChange((v: string) => { this.username = v; })
|
||||
TextInput({ placeholder: '密码', text: this.password })
|
||||
.width('85%').height(48).margin({ bottom: 24 })
|
||||
.type(InputType.Password)
|
||||
.onChange((v: string) => { this.password = v; })
|
||||
} else {
|
||||
TextInput({ placeholder: '粘贴用户密钥 (Bearer token)', text: this.userKey })
|
||||
.width('85%').height(48).margin({ bottom: 24 })
|
||||
.onChange((v: string) => { this.userKey = v; })
|
||||
}
|
||||
|
||||
Button(this.loading ? '登录中…' : '登 录')
|
||||
.width('85%').height(48)
|
||||
.backgroundColor(Theme.accent)
|
||||
.fontSize(16)
|
||||
.enabled(!this.loading)
|
||||
.onClick(() => {
|
||||
hilog.info(0x0001, 'LoginPage', 'onClick fired, loading=%{public}s', this.loading.toString());
|
||||
this.doLogin();
|
||||
})
|
||||
|
||||
// 地址提示:默认值就是推荐值;末尾 /api/v1 必须带(少了会 404,而 404 以前只显示"无法连接")
|
||||
Text('地址要写到 /api/v1 为止,例如 ' + DEFAULT_API_BASE)
|
||||
.fontSize(11).fontColor(Theme.textSubtle).margin({ top: 20 })
|
||||
Text('模拟器 NAT 不通时用 ' + EMULATOR_HOST_BASE)
|
||||
.fontSize(11).fontColor(Theme.textSubtle).margin({ top: 4 })
|
||||
|
||||
if (this.loggedIn) {
|
||||
// 登录成功 → 进入主界面(占位,后续 M2 替换)
|
||||
Blank()
|
||||
Text('登录成功:' + this.me.username)
|
||||
.fontSize(16).fontColor(Theme.accent).margin({ bottom: 60 })
|
||||
.margin({ bottom: 4 })
|
||||
TextInput({ placeholder: 'https://example.com/api/v1', text: this.serverAddr })
|
||||
.width('100%')
|
||||
.height(40)
|
||||
.fontSize(14)
|
||||
.border({ width: 1, color: Theme.border })
|
||||
.borderRadius(Theme.radiusControl)
|
||||
.backgroundColor(Theme.surface)
|
||||
.padding({ left: 12, right: 12 })
|
||||
.onChange((v: string) => {
|
||||
this.serverAddr = v;
|
||||
this.addrWarning = validateApiBase(v).warning;
|
||||
})
|
||||
|
||||
if (this.addrWarning.length > 0) {
|
||||
Text(this.addrWarning)
|
||||
.fontSize(11)
|
||||
.fontColor(Theme.warnFg)
|
||||
.width('100%')
|
||||
.margin({ top: 6 })
|
||||
}
|
||||
|
||||
Row() {
|
||||
Button('账号密码')
|
||||
.type(ButtonType.Normal)
|
||||
.height(32)
|
||||
.layoutWeight(1)
|
||||
.fontSize(12)
|
||||
.fontWeight(this.mode === 0 ? FontWeight.Medium : FontWeight.Regular)
|
||||
.fontColor(this.mode === 0 ? Theme.accentStrong : Theme.textMuted)
|
||||
.backgroundColor(this.mode === 0 ? Theme.accentSoft : Color.Transparent)
|
||||
.borderRadius(Theme.radiusControl)
|
||||
.onClick(() => { this.mode = 0; })
|
||||
Button('用户密钥')
|
||||
.type(ButtonType.Normal)
|
||||
.height(32)
|
||||
.layoutWeight(1)
|
||||
.fontSize(12)
|
||||
.fontWeight(this.mode === 1 ? FontWeight.Medium : FontWeight.Regular)
|
||||
.fontColor(this.mode === 1 ? Theme.accentStrong : Theme.textMuted)
|
||||
.backgroundColor(this.mode === 1 ? Theme.accentSoft : Color.Transparent)
|
||||
.borderRadius(Theme.radiusControl)
|
||||
.onClick(() => { this.mode = 1; })
|
||||
}
|
||||
.width('100%')
|
||||
.padding(3)
|
||||
.margin({ top: 12, bottom: 12 })
|
||||
.borderRadius(Theme.radiusControl)
|
||||
.backgroundColor(Theme.surfaceMuted)
|
||||
|
||||
if (this.mode === 0) {
|
||||
Text('用户名')
|
||||
.width('100%').fontSize(11).fontWeight(FontWeight.Medium)
|
||||
.fontColor(Theme.textMuted).margin({ bottom: 4 })
|
||||
TextInput({ placeholder: '用户名', text: this.username })
|
||||
.width('100%').height(40).fontSize(14)
|
||||
.border({ width: 1, color: Theme.border }).borderRadius(Theme.radiusControl)
|
||||
.backgroundColor(Theme.surface).padding({ left: 12, right: 12 })
|
||||
.onChange((v: string) => { this.username = v; })
|
||||
Text('密码')
|
||||
.width('100%').fontSize(11).fontWeight(FontWeight.Medium)
|
||||
.fontColor(Theme.textMuted).margin({ top: 12, bottom: 4 })
|
||||
TextInput({ placeholder: '密码', text: this.password })
|
||||
.width('100%').height(40).fontSize(14)
|
||||
.border({ width: 1, color: Theme.border }).borderRadius(Theme.radiusControl)
|
||||
.backgroundColor(Theme.surface).padding({ left: 12, right: 12 })
|
||||
.type(InputType.Password)
|
||||
.onChange((v: string) => { this.password = v; })
|
||||
} else {
|
||||
Text('用户密钥')
|
||||
.width('100%').fontSize(11).fontWeight(FontWeight.Medium)
|
||||
.fontColor(Theme.textMuted).margin({ bottom: 4 })
|
||||
TextInput({ placeholder: '在网页版「账号」页创建', text: this.userKey })
|
||||
.width('100%').height(40).fontSize(14)
|
||||
.border({ width: 1, color: Theme.border }).borderRadius(Theme.radiusControl)
|
||||
.backgroundColor(Theme.surface).padding({ left: 12, right: 12 })
|
||||
.type(InputType.Password)
|
||||
.onChange((v: string) => { this.userKey = v; })
|
||||
Text('密钥仅保存在本机,用于客户端访问你的邮箱。')
|
||||
.width('100%').fontSize(10).fontColor(Theme.textSubtle)
|
||||
.lineHeight(15).margin({ top: 5 })
|
||||
}
|
||||
|
||||
Button(this.loading ? '登录中…' : '登录')
|
||||
.width('100%')
|
||||
.height(40)
|
||||
.margin({ top: 16 })
|
||||
.borderRadius(Theme.radiusControl)
|
||||
.backgroundColor(Theme.accent)
|
||||
.fontColor(Theme.accentFg)
|
||||
.fontSize(14)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.enabled(!this.loading)
|
||||
.onClick(() => {
|
||||
hilog.info(0x0001, 'LoginPage', 'onClick fired, loading=%{public}s', this.loading.toString());
|
||||
this.doLogin();
|
||||
})
|
||||
|
||||
Text('地址须以 /api/v1 结尾;模拟器 NAT 不通时可用 ' + EMULATOR_HOST_BASE)
|
||||
.width('100%')
|
||||
.fontSize(10)
|
||||
.fontColor(Theme.textSubtle)
|
||||
.textAlign(TextAlign.Center)
|
||||
.lineHeight(15)
|
||||
.margin({ top: 12 })
|
||||
|
||||
if (this.loggedIn) {
|
||||
Text('登录成功:' + this.me.username)
|
||||
.fontSize(12).fontColor(Theme.accent).margin({ top: 12 })
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.constraintSize({ maxWidth: 380 })
|
||||
.padding(24)
|
||||
.borderRadius(Theme.glassRadius)
|
||||
.backgroundColor(Theme.surface)
|
||||
.shadow({ radius: 24, color: Theme.overlay, offsetX: 0, offsetY: 8 })
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.margin({ top: 24, bottom: 24 })
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(Theme.pageBg)
|
||||
.alignItems(HorizontalAlign.Center)
|
||||
.padding({ left: 16, right: 16 })
|
||||
.backgroundColor(Theme.surfaceMuted)
|
||||
.align(Alignment.Center)
|
||||
}
|
||||
}
|
||||
@ -13,9 +13,16 @@ import { MailDetail, SendMailRequest } from '../model/Models';
|
||||
import { MailDetailParams } from '../model/RouteParams';
|
||||
import { AmIcon } from '../common/Icons';
|
||||
|
||||
@Entry
|
||||
@Component
|
||||
struct MailDetailPage {
|
||||
export struct MailDetailView {
|
||||
/**
|
||||
* 既支持旧 router 页面,也支持 Navigation 的 NavDestination 内嵌复用。
|
||||
* 内嵌时由父级传入 mailId/accountId;独立页面仍从 router params 读取。
|
||||
*/
|
||||
@Prop initialMailId: string = '';
|
||||
@Prop initialAccountId: string = '';
|
||||
@Prop embedded: boolean = false;
|
||||
onBack: () => void = (): void => {};
|
||||
@State mailId: string = '';
|
||||
@State accountId: string = '';
|
||||
@State subject: string = '';
|
||||
@ -42,9 +49,14 @@ struct MailDetailPage {
|
||||
this.error = '无法获取应用上下文';
|
||||
return;
|
||||
}
|
||||
const params = this.getUIContext().getRouter().getParams() as MailDetailParams;
|
||||
this.mailId = params?.mail_id ?? '';
|
||||
this.accountId = params?.account_id ?? '';
|
||||
if (this.initialMailId.length > 0) {
|
||||
this.mailId = this.initialMailId;
|
||||
this.accountId = this.initialAccountId;
|
||||
} else {
|
||||
const params = this.getUIContext().getRouter().getParams() as MailDetailParams;
|
||||
this.mailId = params?.mail_id ?? '';
|
||||
this.accountId = params?.account_id ?? '';
|
||||
}
|
||||
const accountManager: AccountManager = AccountManager.getInstance(ctx);
|
||||
accountManager.load().then(() => {
|
||||
let account: AccountInfo | null = accountManager.getAccount(this.accountId);
|
||||
@ -114,6 +126,14 @@ struct MailDetailPage {
|
||||
}
|
||||
}
|
||||
|
||||
goBack(): void {
|
||||
if (this.embedded) {
|
||||
this.onBack();
|
||||
return;
|
||||
}
|
||||
this.getUIContext().getRouter().back();
|
||||
}
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
// 顶栏
|
||||
@ -123,7 +143,7 @@ struct MailDetailPage {
|
||||
.width(40).height(40)
|
||||
.textAlign(TextAlign.Center)
|
||||
.onClick(() => {
|
||||
this.getUIContext().getRouter().back();
|
||||
this.goBack();
|
||||
})
|
||||
Text(this.subject.length > 20 ? this.subject.substring(0, 20) + '…' : this.subject)
|
||||
.fontSize(16).fontWeight(FontWeight.Bold).fontColor(Theme.textPrimary)
|
||||
@ -332,4 +352,16 @@ struct MailDetailPage {
|
||||
this.sending = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 旧 router 页面入口:与 Navigation 目标页共用同一份 MailDetailView。 */
|
||||
@Entry
|
||||
@Component
|
||||
struct MailDetailPage {
|
||||
build() {
|
||||
Column() {
|
||||
MailDetailView()
|
||||
}
|
||||
.width('100%').height('100%')
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ import { ApiClient, ApiError } from '../api/ApiClient';
|
||||
import { Theme } from '../common/Theme';
|
||||
import { AmIcon } from '../common/Icons';
|
||||
import { WideSidebar } from './WideSidebar';
|
||||
import { MailDetailView } from './MailDetailPage';
|
||||
import { MailApi, InboxResponse } from '../api/MailApi';
|
||||
import { AccountManager, AccountInfo } from '../api/AccountManager';
|
||||
import { SseService, SseEvent } from '../api/SseService';
|
||||
@ -81,11 +82,52 @@ import {
|
||||
|
||||
/** 一页取多少封。取满了就要如实提示"可能还有更多"(服务端 total 是未读数,不是总封数)。 */
|
||||
const INBOX_PAGE_SIZE: number = 50;
|
||||
const MAIL_DETAIL_ROUTE: string = 'mail-detail';
|
||||
|
||||
/**
|
||||
* Navigation 目标页:按官方示例在 NavDestination.onReady 读取路径参数。
|
||||
* 系统 Navigation 自动决定 Stack / Split;这里不读窗口宽度、不手搓分栏。
|
||||
*/
|
||||
@Component
|
||||
struct MailDetailDestination {
|
||||
@State mailId: string = '';
|
||||
@State accountId: string = '';
|
||||
private pathStack: NavPathStack = new NavPathStack();
|
||||
|
||||
handleReady(ctx: NavDestinationContext): void {
|
||||
const params: MailDetailParams = ctx.pathInfo.param as MailDetailParams;
|
||||
this.mailId = params?.mail_id ?? '';
|
||||
this.accountId = params?.account_id ?? '';
|
||||
this.pathStack = ctx.pathStack;
|
||||
}
|
||||
|
||||
build() {
|
||||
NavDestination() {
|
||||
if (this.mailId.length > 0) {
|
||||
MailDetailView({
|
||||
initialMailId: this.mailId,
|
||||
initialAccountId: this.accountId,
|
||||
embedded: true,
|
||||
onBack: (): void => { this.pathStack.pop(); }
|
||||
})
|
||||
} else {
|
||||
Column() {
|
||||
LoadingProgress().width(32).height(32)
|
||||
}
|
||||
.width('100%').height('100%').justifyContent(FlexAlign.Center)
|
||||
}
|
||||
}
|
||||
.hideTitleBar(true)
|
||||
.onReady((ctx: NavDestinationContext) => { this.handleReady(ctx); })
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
struct InboxTab {
|
||||
/** 背景是否开启:开着就让出页面底(WebUI 的做法是页面底完全透明) */
|
||||
@Prop bgActive: boolean = false;
|
||||
/** 选中邮件回调:宽屏内联到 Navigation 右栏,窄屏 push 到目标页 */
|
||||
onOpenMail: (mailId: string, accountId: string) => void = (): void => {};
|
||||
@State mails: MailSummary[] = [];
|
||||
/** 按会话折叠后的列表(单封的组平铺渲染) */
|
||||
@State groups: SessionGroup[] = [];
|
||||
@ -273,14 +315,7 @@ struct InboxTab {
|
||||
}
|
||||
|
||||
openMail(mail: MailLike): void {
|
||||
const params: MailDetailParams = {
|
||||
mail_id: mail.mail_id,
|
||||
account_id: mail.source_account_id
|
||||
};
|
||||
this.getUIContext().getRouter().pushUrl({
|
||||
url: 'pages/MailDetailPage',
|
||||
params: params
|
||||
});
|
||||
this.onOpenMail(mail.mail_id, mail.source_account_id);
|
||||
}
|
||||
|
||||
openCompose(): void {
|
||||
@ -611,6 +646,8 @@ struct InboxTab {
|
||||
struct SentTab {
|
||||
/** 背景是否开启:开着就让出页面底(WebUI 的做法是页面底完全透明) */
|
||||
@Prop bgActive: boolean = false;
|
||||
/** 选中邮件回调 */
|
||||
onOpenMail: (mailId: string, accountId: string) => void = (): void => {};
|
||||
@State groups: SessionGroup[] = [];
|
||||
@State expandedKeys: string[] = [];
|
||||
@State loading: boolean = false;
|
||||
@ -681,11 +718,7 @@ struct SentTab {
|
||||
}
|
||||
|
||||
openMail(mail: MailLike): void {
|
||||
const params: MailDetailParams = {
|
||||
mail_id: mail.mail_id,
|
||||
account_id: mail.source_account_id
|
||||
};
|
||||
this.getUIContext().getRouter().pushUrl({ url: 'pages/MailDetailPage', params: params });
|
||||
this.onOpenMail(mail.mail_id, mail.source_account_id);
|
||||
}
|
||||
|
||||
@Builder
|
||||
@ -1074,6 +1107,8 @@ struct CommPage {
|
||||
@State commTab: string = 'inbox';
|
||||
@State unreadCount: number = 0;
|
||||
@State pendingCount: number = 0;
|
||||
/** Navigation 路由栈:宽屏 Split 时列表+详情并排,窄屏 Stack 时详情 push 覆盖 */
|
||||
private navPathStack: NavPathStack = new NavPathStack();
|
||||
|
||||
aboutToAppear(): void {
|
||||
this.refreshCounts();
|
||||
@ -1177,6 +1212,7 @@ struct CommPage {
|
||||
.border({ width: { bottom: this.commTab === key ? 2 : 0 }, color: Theme.accent })
|
||||
.onClick(() => {
|
||||
this.commTab = normalizeCommTab(key);
|
||||
this.navPathStack.clear();
|
||||
this.refreshCounts();
|
||||
})
|
||||
}, (key: string) => key)
|
||||
@ -1185,37 +1221,68 @@ struct CommPage {
|
||||
.backgroundColor(Theme.surface)
|
||||
}
|
||||
|
||||
openMail(mailId: string, accountId: string): void {
|
||||
const params: MailDetailParams = {
|
||||
mail_id: mailId,
|
||||
account_id: accountId
|
||||
};
|
||||
this.navPathStack.pushPath({ name: MAIL_DETAIL_ROUTE, param: params });
|
||||
}
|
||||
|
||||
@Builder
|
||||
DestinationBuilder(name: string, param: Object) {
|
||||
if (name === MAIL_DETAIL_ROUTE) {
|
||||
MailDetailDestination()
|
||||
}
|
||||
}
|
||||
|
||||
build() {
|
||||
Stack({ alignContent: Alignment.BottomEnd }) {
|
||||
Column() {
|
||||
Row() {
|
||||
Text('通信').fontSize(20).fontWeight(FontWeight.Bold).fontColor(Theme.textPrimary)
|
||||
Blank()
|
||||
/*
|
||||
* 设置入口。
|
||||
* 图标用 `person`:与 WebUI 的「我的」(`NarrowNav.tsx` 的 `PersonIcon`)同一枚,
|
||||
* 而设置页里正是账号 / 外观 / 密钥这些“属于这个人的东西”。
|
||||
* 2026-09-15 之前这里是 emoji `⚙`(理由与替换过程见 `model/NavItems.ts`)。
|
||||
*/
|
||||
AmIcon({ iconName: 'person', iconSize: 20, iconColor: Theme.textMuted })
|
||||
.width(36).height(36)
|
||||
.onClick(() => {
|
||||
this.getUIContext().getRouter().pushUrl({ url: 'pages/SettingsPage' });
|
||||
})
|
||||
}
|
||||
.width('100%').height(52).padding({ left: 16, right: 8 })
|
||||
.backgroundColor(Theme.surface)
|
||||
Navigation(this.navPathStack) {
|
||||
Column() {
|
||||
Row() {
|
||||
Text('通信').fontSize(20).fontWeight(FontWeight.Bold).fontColor(Theme.textPrimary)
|
||||
Blank()
|
||||
/*
|
||||
* 设置入口。
|
||||
* 图标用 `person`:与 WebUI 的「我的」(`NarrowNav.tsx` 的 `PersonIcon`)同一枚,
|
||||
* 而设置页里正是账号 / 外观 / 密钥这些“属于这个人的东西”。
|
||||
* 2026-09-15 之前这里是 emoji `⚙`(理由与替换过程见 `model/NavItems.ts`)。
|
||||
*/
|
||||
AmIcon({ iconName: 'person', iconSize: 20, iconColor: Theme.textMuted })
|
||||
.width(36).height(36)
|
||||
.onClick(() => {
|
||||
this.getUIContext().getRouter().pushUrl({ url: 'pages/SettingsPage' });
|
||||
})
|
||||
}
|
||||
.width('100%').height(52).padding({ left: 16, right: 8 })
|
||||
.backgroundColor(Theme.surface)
|
||||
|
||||
this.CommTabBar()
|
||||
this.CommTabBar()
|
||||
|
||||
if (this.commTab === 'sent') {
|
||||
SentTab({ bgActive: this.bgActive })
|
||||
} else if (this.commTab === 'permissions') {
|
||||
PermissionTab({ bgActive: this.bgActive })
|
||||
} else {
|
||||
InboxTab({ bgActive: this.bgActive })
|
||||
if (this.commTab === 'sent') {
|
||||
SentTab({
|
||||
bgActive: this.bgActive,
|
||||
onOpenMail: (mailId: string, accountId: string): void => { this.openMail(mailId, accountId); }
|
||||
})
|
||||
} else if (this.commTab === 'permissions') {
|
||||
PermissionTab({ bgActive: this.bgActive })
|
||||
} else {
|
||||
InboxTab({
|
||||
bgActive: this.bgActive,
|
||||
onOpenMail: (mailId: string, accountId: string): void => { this.openMail(mailId, accountId); }
|
||||
})
|
||||
}
|
||||
}
|
||||
.width('100%').height('100%')
|
||||
}
|
||||
.navDestination(this.DestinationBuilder)
|
||||
.mode(NavigationMode.Auto)
|
||||
/* WebUI 邮件列表栏固定约 320px;Auto 的断点 = 列表最小宽 + 详情最小宽。 */
|
||||
.navBarWidth(320)
|
||||
.navBarWidthRange([280, 360])
|
||||
.minContentWidth(360)
|
||||
.hideTitleBar(true)
|
||||
.width('100%').height('100%')
|
||||
|
||||
/*
|
||||
|
||||
@ -5,11 +5,19 @@
|
||||
"description": "$string:module_desc",
|
||||
"mainElement": "EntryAbility",
|
||||
"deviceTypes": [
|
||||
"phone"
|
||||
"phone",
|
||||
"tablet",
|
||||
"2in1"
|
||||
],
|
||||
"deliveryWithInstall": true,
|
||||
"installationFree": false,
|
||||
"pages": "$profile:main_pages",
|
||||
"metadata": [
|
||||
{
|
||||
"name": "client_id",
|
||||
"value": "2039846327155747840"
|
||||
}
|
||||
],
|
||||
"abilities": [
|
||||
{
|
||||
"name": "EntryAbility",
|
||||
|
||||
Reference in New Issue
Block a user