chore: directory migration - gateway→server, web→client/electron

This commit is contained in:
2026-09-08 19:16:35 +08:00
parent fd9f99a3f9
commit f9d757b5e5
243 changed files with 5095 additions and 228 deletions

View 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;
}
}
}