/* * AgentMail 鸿蒙客户端 — 邮件详情页 * GET /mail/{id} 展示单封完整内容 + 回复按钮 * 路由参数:mail_id */ import { ApiClient, ApiError } from '../api/ApiClient'; import { MailApi } from '../api/MailApi'; import { AccountManager, AccountInfo } from '../api/AccountManager'; import { MailDetail, SendMailRequest } from '../model/Models'; import { MailDetailParams } from '../model/RouteParams'; import { promptAction } from '@kit.ArkUI'; @Entry @Component struct MailDetailPage { @State mailId: string = ''; @State accountId: string = ''; @State subject: string = ''; @State fromName: string = ''; @State toName: string = ''; @State body: string = ''; @State createdAt: string = ''; @State permissionMode: string = ''; @State sessionAlias: string = ''; @State loading: boolean = true; @State error: string = ''; @State showReplyBox: boolean = false; @State replyBody: string = ''; @State sending: boolean = false; @State sessionId: string = ''; @State switchingPerm: boolean = false; private mailApi: MailApi | null = null; aboutToAppear(): void { const ctx = this.getUIContext().getHostContext(); if (ctx === undefined) { this.loading = false; this.error = '无法获取应用上下文'; return; } 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); if (account === null) { account = accountManager.getActiveAccount(); } if (account === null) { this.loading = false; this.error = '找不到邮件所属账号'; return; } this.accountId = account.id; const accountClient: ApiClient = new ApiClient(ctx); accountClient.setBase(account.server); accountClient.setToken(account.token); this.mailApi = new MailApi(accountClient); if (this.mailId.length === 0) { this.loading = false; this.error = '缺少邮件 ID'; return; } this.loadMail(this.mailId); }); } async loadMail(mailId: string): Promise { const m: MailApi | null = this.mailApi; if (m === null) { return; } this.loading = true; this.error = ''; try { const mail: MailDetail = await m.mailDetail(mailId); this.subject = mail.subject; this.fromName = mail.from_name; this.toName = mail.to_name; this.body = mail.body; this.createdAt = mail.created_at; this.permissionMode = mail.permission_mode; this.sessionAlias = mail.session_alias; this.sessionId = mail.session_id; } catch (e) { const ae = e as ApiError; this.error = ae.code === 0 ? ae.message : '加载失败'; } finally { this.loading = false; } } async switchPermission(mode: string): Promise { const m: MailApi | null = this.mailApi; const sid: string = this.sessionId; if (m === null || sid.length === 0 || this.switchingPerm) { return; } this.switchingPerm = true; try { await m.setPermissionMode(sid, mode); this.permissionMode = mode; promptAction.showToast({ message: '权限已切换为 ' + mode }); } catch (e) { const ae = e as ApiError; promptAction.showToast({ message: '切换失败: ' + ae.message }); } finally { this.switchingPerm = false; } } build() { Column() { // 顶栏 Row() { Text('‹') .fontSize(24).fontColor('#1A73E8') .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') .layoutWeight(1) Text(this.permissionMode) .fontSize(11).fontColor('#666666') .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') if (this.loading) { Column() { LoadingProgress().width(40).height(40) Text('加载中…').fontSize(14).fontColor('#999999').margin({ top: 8 }) } .width('100%').layoutWeight(1) .justifyContent(FlexAlign.Center) } else if (this.error.length > 0) { Column() { Text(this.error).fontSize(14).fontColor('#FF4444') Button('重试').margin({ top: 12 }).onClick(() => { this.loadMail(this.mailId); }) } .width('100%').layoutWeight(1) .justifyContent(FlexAlign.Center) } else { Scroll() { Column() { // 元信息卡片 Column() { Row() { Text('发件人').fontSize(12).fontColor('#999999').width(60) Text(this.fromName).fontSize(14).fontColor('#333333') }.width('100%').margin({ bottom: 6 }) Row() { Text('收件人').fontSize(12).fontColor('#999999').width(60) Text(this.toName).fontSize(14).fontColor('#333333') }.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') }.width('100%').margin({ bottom: 6 }) } Row() { Text('时间').fontSize(12).fontColor('#999999').width(60) Text(this.createdAt).fontSize(13).fontColor('#666666') }.width('100%').margin({ bottom: 6 }) Row() { Text('权限').fontSize(12).fontColor('#999999').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') .borderRadius(4) .padding({ left: 8, right: 8, top: 3, bottom: 3 }) .margin({ right: 6 }) .onClick(() => { this.switchPermission(mode); }) }, (mode: string) => mode) } }.width('100%') } .width('100%') .padding(16) .backgroundColor('#F8F9FA') .borderRadius(8) .margin({ left: 12, right: 12, top: 8 }) // 邮件正文 Text(this.subject) .fontSize(20) .fontWeight(FontWeight.Bold) .fontColor('#333333') .width('100%') .padding({ left: 16, right: 16, top: 16, bottom: 8 }) Text(this.body) .fontSize(15) .fontColor('#444444') .width('100%') .padding({ left: 16, right: 16, bottom: 16 }) .lineHeight(24) Blank().height(60) } .width('100%') } .layoutWeight(1) .scrollBar(BarState.Off) // 底部回复按钮 Row() { Button('↩ 回复') .width('90%').height(44) .backgroundColor('#1A73E8') .fontSize(15) .onClick(() => { this.showReplyBox = true; }) } .width('100%').height(56) .justifyContent(FlexAlign.Center) .backgroundColor('#FFFFFF') // 回复弹层 if (this.showReplyBox) { Column() { // 遮罩 Column() .width('100%').layoutWeight(1) .backgroundColor('#80000000') .onClick(() => { this.showReplyBox = false; }) // 回复框 Column() { Text('回复给 ' + this.fromName) .fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333') .margin({ bottom: 12 }) TextArea({ placeholder: '输入回复内容…' }) .layoutWeight(1).width('100%') .fontSize(14) .onChange((v: string) => { this.replyBody = v; }) Row() { Button('取消') .width(80).height(36) .backgroundColor('#F5F5F5') .fontColor('#666666') .fontSize(13) .onClick(() => { this.showReplyBox = false; }) Blank() Button(this.sending ? '发送中…' : '发送') .width(80).height(36) .backgroundColor('#1A73E8') .fontSize(13) .enabled(!this.sending && this.replyBody.length > 0) .onClick(() => { this.doReply(); }) } .width('100%') .margin({ top: 12 }) } .width('100%') .height('60%') .padding(16) .backgroundColor('#FFFFFF') .borderRadius({ topLeft: 12, topRight: 12 }) } .width('100%').height('100%') .position({ x: 0, y: 0 }) } } } .width('100%').height('100%') .backgroundColor('#F5F7FA') } async doReply(): Promise { const m: MailApi | null = this.mailApi; if (m === null || this.replyBody.length === 0) { return; } this.sending = true; try { const req: SendMailRequest = new SendMailRequest(); req.to = this.fromName + '@'; req.subject = 'Re: ' + this.subject; req.body = this.replyBody; req.reply_to = this.mailId; await m.send(req); this.showReplyBox = false; this.replyBody = ''; promptAction.showToast({ message: '回复已发送' }); } catch (e) { const ae = e as ApiError; promptAction.showToast({ message: '发送失败: ' + ae.message }); } finally { this.sending = false; } } }