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:
@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user