chore: directory migration - gateway→server, web→client/electron
This commit is contained in:
321
client/harmony/entry/src/main/ets/pages/ComposePage.ets
Normal file
321
client/harmony/entry/src/main/ets/pages/ComposePage.ets
Normal file
@ -0,0 +1,321 @@
|
||||
/*
|
||||
* AgentMail 鸿蒙客户端 — 写邮件页
|
||||
* 收件人 / 主题 / 正文 / 附件选择 / 发送
|
||||
* 路由参数可选: to, reply_to, session_alias
|
||||
*/
|
||||
import { ApiClient, ApiError } from '../api/ApiClient';
|
||||
import { MailApi } from '../api/MailApi';
|
||||
import { AccountManager, AccountInfo } from '../api/AccountManager';
|
||||
import { SendMailRequest } from '../model/Models';
|
||||
import { ComposeParams } from '../model/RouteParams';
|
||||
import { promptAction } from '@kit.ArkUI';
|
||||
import { hilog } from '@kit.PerformanceAnalysisKit';
|
||||
import { picker } from '@kit.CoreFileKit';
|
||||
|
||||
@Entry
|
||||
@Component
|
||||
struct ComposePage {
|
||||
@State to: string = '';
|
||||
@State subject: string = '';
|
||||
@State body: string = '';
|
||||
@State replyTo: string = '';
|
||||
@State sessionAlias: string = '';
|
||||
@State attachmentIds: string[] = [];
|
||||
@State attachmentNames: string[] = [];
|
||||
@State sending: boolean = false;
|
||||
@State uploading: boolean = false;
|
||||
@State status: string = '';
|
||||
// 发信账号选择
|
||||
@State accountList: AccountInfo[] = [];
|
||||
@State selectedAccountId: string = '';
|
||||
@State showAccountPicker: boolean = false;
|
||||
|
||||
aboutToAppear(): void {
|
||||
const ctx = this.getUIContext().getHostContext();
|
||||
if (ctx === undefined) {
|
||||
return;
|
||||
}
|
||||
const params = this.getUIContext().getRouter().getParams() as ComposeParams;
|
||||
const routeAccountId: string = params?.account_id ?? '';
|
||||
this.to = params?.to ?? '';
|
||||
this.replyTo = params?.reply_to ?? '';
|
||||
this.sessionAlias = params?.session_alias ?? '';
|
||||
|
||||
const accountManager: AccountManager = AccountManager.getInstance(ctx);
|
||||
accountManager.load().then(() => {
|
||||
this.accountList = accountManager.getAccounts();
|
||||
let selectedId: string = routeAccountId;
|
||||
if (accountManager.getAccount(selectedId) === null) {
|
||||
const active: AccountInfo | null = accountManager.getActiveAccount();
|
||||
selectedId = active !== null ? active.id : '';
|
||||
}
|
||||
this.selectedAccountId = selectedId;
|
||||
});
|
||||
}
|
||||
|
||||
getSelectedAccount(): AccountInfo | null {
|
||||
for (let i = 0; i < this.accountList.length; i++) {
|
||||
if (this.accountList[i].id === this.selectedAccountId) {
|
||||
return this.accountList[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
createSelectedMailApi(ctx: Context): MailApi | null {
|
||||
const account: AccountInfo | null = this.getSelectedAccount();
|
||||
if (account === null) {
|
||||
return null;
|
||||
}
|
||||
const accountClient: ApiClient = new ApiClient(ctx);
|
||||
accountClient.setBase(account.server);
|
||||
accountClient.setToken(account.token);
|
||||
return new MailApi(accountClient);
|
||||
}
|
||||
|
||||
/** 选择附件(通过文件选择器) */
|
||||
async pickAttachment(): Promise<void> {
|
||||
const ctx: Context | undefined = this.getUIContext().getHostContext();
|
||||
if (ctx === undefined || this.uploading) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const documentPicker = new picker.DocumentViewPicker(ctx);
|
||||
const result: string[] = await documentPicker.select({
|
||||
maxSelectNumber: 5,
|
||||
fileSuffixFilters: ['*']
|
||||
});
|
||||
if (result.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mailApi: MailApi | null = this.createSelectedMailApi(ctx);
|
||||
if (mailApi === null) {
|
||||
promptAction.showToast({ message: '请先选择发信账号' });
|
||||
return;
|
||||
}
|
||||
this.uploading = true;
|
||||
this.status = '上传中…';
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
const fileUri: string = result[i];
|
||||
const nameStart: number = fileUri.lastIndexOf('/');
|
||||
const name: string = nameStart >= 0 ? fileUri.substring(nameStart + 1) : fileUri;
|
||||
try {
|
||||
const attachmentId: string = await mailApi.uploadAttachment(fileUri, name);
|
||||
this.attachmentIds.push(attachmentId);
|
||||
this.attachmentNames.push(name);
|
||||
hilog.info(0x0001, 'Compose', 'uploaded: %{public}s → %{public}s', name, attachmentId);
|
||||
} catch (e) {
|
||||
const apiError = e as ApiError;
|
||||
promptAction.showToast({ message: '上传失败: ' + name + ' - ' + apiError.message });
|
||||
}
|
||||
}
|
||||
this.status = this.attachmentIds.length + ' 个附件已上传';
|
||||
} catch (e) {
|
||||
this.status = '';
|
||||
} finally {
|
||||
this.uploading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 移除附件 */
|
||||
removeAttachment(index: number): void {
|
||||
if (index >= 0 && index < this.attachmentIds.length) {
|
||||
this.attachmentIds.splice(index, 1);
|
||||
this.attachmentNames.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/** 发送邮件 */
|
||||
async doSend(): Promise<void> {
|
||||
if (this.sending) {
|
||||
return;
|
||||
}
|
||||
if (this.to.length === 0) {
|
||||
promptAction.showToast({ message: '请填写收件人' });
|
||||
return;
|
||||
}
|
||||
if (this.subject.length === 0) {
|
||||
promptAction.showToast({ message: '请填写主题' });
|
||||
return;
|
||||
}
|
||||
const ctx: Context | undefined = this.getUIContext().getHostContext();
|
||||
if (ctx === undefined) {
|
||||
return;
|
||||
}
|
||||
const mailApi: MailApi | null = this.createSelectedMailApi(ctx);
|
||||
if (mailApi === null) {
|
||||
promptAction.showToast({ message: '请先选择发信账号' });
|
||||
return;
|
||||
}
|
||||
|
||||
this.sending = true;
|
||||
try {
|
||||
const request: SendMailRequest = new SendMailRequest();
|
||||
request.to = this.to;
|
||||
request.subject = this.subject;
|
||||
request.body = this.body;
|
||||
if (this.replyTo.length > 0) {
|
||||
request.reply_to = this.replyTo;
|
||||
}
|
||||
if (this.sessionAlias.length > 0) {
|
||||
request.session_alias = this.sessionAlias;
|
||||
}
|
||||
if (this.attachmentIds.length > 0) {
|
||||
request.attachment_ids = this.attachmentIds;
|
||||
}
|
||||
await mailApi.send(request);
|
||||
promptAction.showToast({ message: '✅ 邮件已发送' });
|
||||
this.getUIContext().getRouter().back();
|
||||
} catch (e) {
|
||||
const apiError = e as ApiError;
|
||||
promptAction.showToast({ message: '发送失败: ' + apiError.message });
|
||||
} finally {
|
||||
this.sending = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取选中的账号显示名 */
|
||||
getSelectedAccountName(): string {
|
||||
const account: AccountInfo | null = this.getSelectedAccount();
|
||||
return account !== null ? account.displayName : '选择账号';
|
||||
}
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
// 顶栏
|
||||
Row() {
|
||||
Text('取消')
|
||||
.fontSize(15).fontColor('#FF4444')
|
||||
.onClick(() => { this.getUIContext().getRouter().back(); })
|
||||
Blank()
|
||||
Text('写邮件').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333')
|
||||
Blank()
|
||||
Button(this.sending ? '发送中…' : '发送')
|
||||
.fontSize(13).height(32)
|
||||
.backgroundColor('#1A73E8')
|
||||
.enabled(!this.sending && !this.uploading)
|
||||
.onClick(() => { this.doSend(); })
|
||||
}
|
||||
.width('100%').height(56)
|
||||
.padding({ left: 12, right: 12 })
|
||||
.backgroundColor('#FFFFFF')
|
||||
|
||||
Divider().color('#EEEEEE')
|
||||
|
||||
// 发信账号选择(多账号时显示)
|
||||
if (this.accountList.length > 1) {
|
||||
Row() {
|
||||
Text('发信账号').fontSize(13).fontColor('#999999').width(72)
|
||||
// 显示当前选中的账号名
|
||||
Text(this.getSelectedAccountName())
|
||||
.fontSize(13).fontColor('#1A73E8')
|
||||
.layoutWeight(1)
|
||||
.onClick(() => { this.showAccountPicker = !this.showAccountPicker; })
|
||||
Text(' ▾').fontSize(12).fontColor('#999999')
|
||||
.onClick(() => { this.showAccountPicker = !this.showAccountPicker; })
|
||||
}
|
||||
.width('100%').height(40).padding({ left: 12, right: 12 })
|
||||
.backgroundColor('#FFFFFF')
|
||||
|
||||
// 账号选择下拉
|
||||
if (this.showAccountPicker) {
|
||||
ForEach(this.accountList, (acct: AccountInfo) => {
|
||||
Row() {
|
||||
Text(acct.displayName)
|
||||
.fontSize(13)
|
||||
.fontColor(this.selectedAccountId === acct.id ? '#1A73E8' : '#333333')
|
||||
.fontWeight(this.selectedAccountId === acct.id ? FontWeight.Bold : FontWeight.Normal)
|
||||
.layoutWeight(1)
|
||||
if (this.selectedAccountId === acct.id) {
|
||||
Text('✓').fontSize(14).fontColor('#1A73E8')
|
||||
}
|
||||
}
|
||||
.width('100%').height(40).padding({ left: 40, right: 12 })
|
||||
.backgroundColor(this.selectedAccountId === acct.id ? '#F0F7FF' : '#FFFFFF')
|
||||
.onClick(() => {
|
||||
this.selectedAccountId = acct.id;
|
||||
this.showAccountPicker = false;
|
||||
})
|
||||
}, (acct: AccountInfo) => acct.id)
|
||||
}
|
||||
|
||||
Divider().color('#F0F0F0')
|
||||
}
|
||||
|
||||
// 收件人
|
||||
Row() {
|
||||
Text('收件人').fontSize(14).fontColor('#999999').width(60)
|
||||
TextInput({ placeholder: 'name@path.session', text: this.to })
|
||||
.layoutWeight(1).fontSize(14).backgroundColor('#00000000')
|
||||
.onChange((v: string) => { this.to = v; })
|
||||
}
|
||||
.width('100%').height(48).padding({ left: 12, right: 12 })
|
||||
.backgroundColor('#FFFFFF')
|
||||
|
||||
Divider().color('#F0F0F0')
|
||||
|
||||
// 主题
|
||||
Row() {
|
||||
Text('主题').fontSize(14).fontColor('#999999').width(60)
|
||||
TextInput({ placeholder: '邮件主题', text: this.subject })
|
||||
.layoutWeight(1).fontSize(14).backgroundColor('#00000000')
|
||||
.onChange((v: string) => { this.subject = v; })
|
||||
}
|
||||
.width('100%').height(48).padding({ left: 12, right: 12 })
|
||||
.backgroundColor('#FFFFFF')
|
||||
|
||||
Divider().color('#F0F0F0')
|
||||
|
||||
// 正文
|
||||
TextArea({ placeholder: '输入邮件正文…' })
|
||||
.layoutWeight(1).width('100%')
|
||||
.fontSize(14)
|
||||
.backgroundColor('#FFFFFF')
|
||||
.onChange((v: string) => { this.body = v; })
|
||||
|
||||
// 附件区
|
||||
if (this.attachmentNames.length > 0) {
|
||||
Column() {
|
||||
ForEach(this.attachmentNames, (name: string, idx: number) => {
|
||||
Row() {
|
||||
Text('📎 ' + name)
|
||||
.fontSize(12).fontColor('#333333')
|
||||
.layoutWeight(1)
|
||||
.maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||
Text('✕')
|
||||
.fontSize(14).fontColor('#FF4444')
|
||||
.onClick(() => { this.removeAttachment(idx); })
|
||||
}
|
||||
.width('100%').height(32)
|
||||
.padding({ left: 12, right: 12 })
|
||||
.backgroundColor('#F8F9FA')
|
||||
}, (_name: string, idx: number) => idx.toString())
|
||||
}
|
||||
.width('100%')
|
||||
}
|
||||
|
||||
// 底部工具栏
|
||||
Row() {
|
||||
Text('📎 添加附件')
|
||||
.fontSize(13).fontColor('#1A73E8')
|
||||
.onClick(() => { this.pickAttachment(); })
|
||||
|
||||
if (this.uploading) {
|
||||
Text(this.status)
|
||||
.fontSize(11).fontColor('#666666')
|
||||
.margin({ left: 12 })
|
||||
} else if (this.status.length > 0) {
|
||||
Text(this.status)
|
||||
.fontSize(11).fontColor('#4CAF50')
|
||||
.margin({ left: 12 })
|
||||
}
|
||||
}
|
||||
.width('100%').height(44)
|
||||
.padding({ left: 12, right: 12 })
|
||||
.backgroundColor('#FFFFFF')
|
||||
}
|
||||
.width('100%').height('100%')
|
||||
.backgroundColor('#F5F7FA')
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user