chore: directory migration - gateway→server, web→client/electron
This commit is contained in:
316
client/harmony/entry/src/main/ets/pages/MailDetailPage.ets
Normal file
316
client/harmony/entry/src/main/ets/pages/MailDetailPage.ets
Normal file
@ -0,0 +1,316 @@
|
||||
/*
|
||||
* 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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user