/* * AgentMail 鸿蒙客户端 — 收件箱页 * GET /me/inbox 分页,列表展示邮件摘要 */ import { ApiClient, ApiError } from '../api/ApiClient'; import { Theme } from '../common/Theme'; import { MailApi } from '../api/MailApi'; import { MailSummary, Me } from '../model/Models'; import { router } from '@kit.ArkUI'; import { AmIcon } from '../common/Icons'; import { LIST_FADE_LENGTH } from '../model/NavItems'; import { LengthMetrics } from '@kit.ArkUI'; import { Insets, KEY_WINDOW_INSETS, topInset } from '../model/WindowInsets'; @Entry @Component struct InboxPage { @State mails: MailSummary[] = []; /** * 窗口避让区 —— 由 `EntryAbility.setupFullScreenWindow` 写进 AppStorage。 * * ★ 推上来的页**也要**消费它:`setWindowLayoutFullScreen(true)` 是**窗口级**的, * 一旦设上,这个窗口里**所有**用 `router.pushUrl` 推上来的页都从 y=0 开始画。 * 本页顶栏原来贴 y=0 ⇒ 全屏后「取消/发送」会与时钟、wifi/电量图标重叠 * (2026-09-18 实测截图硬证)。只给 `MainPage` 加避让就是把"黑边"换成 * "顶栏被状态栏压住",同属只做一半。 */ @StorageLink(KEY_WINDOW_INSETS) windowInsets: Insets = new Insets(); @State loading: boolean = false; @State limit: number = 20; @State total: number = 0; @State unread: number = 0; @State me: Me = new Me(); @State error: string = ''; private client: ApiClient | null = null; private mailApi: MailApi | null = null; aboutToAppear(): void { const ctx = this.getUIContext().getHostContext(); if (ctx !== undefined) { const client: ApiClient = ApiClient.getInstance(ctx); this.client = client; this.mailApi = new MailApi(client); this.loadInbox(); } } async loadInbox(): Promise { const c: ApiClient | null = this.client; const m: MailApi | null = this.mailApi; if (c === null || m === null) { return; } this.loading = true; this.error = ''; try { const resp = await m.inbox('all', this.limit); this.mails = resp.mails; this.total = resp.total; // 客户端统计未读(API 未提供顶层 unread) let unreadCount: number = 0; for (let i = 0; i < this.mails.length; i++) { if (this.mails[i].status === 'unread') { unreadCount++; } } this.unread = unreadCount; } catch (e) { const ae = e as ApiError; this.error = ae.message.length > 0 ? ae.message : '加载失败'; } finally { this.loading = false; } } build() { Column() { // 顶栏 Row() { Text('收件箱') .fontSize(20).fontWeight(FontWeight.Bold).fontColor(Theme.textPrimary) Blank() if (this.unread > 0) { Text(this.unread + ' 未读') .fontSize(13).fontColor(Theme.surface) .backgroundColor(Theme.danger) .borderRadius(10) .padding({ left: 8, right: 8, top: 2, bottom: 2 }) } } /* 高度与 padding-top 一起加避让 —— 理由见 `ComposePage` 顶栏(同形状) */ .width('100%') .height(56 + topInset(this.windowInsets)) .padding({ left: 16, right: 16, top: topInset(this.windowInsets) }) .backgroundColor(Theme.surface) // 列表 if (this.loading) { Column() { LoadingProgress().width(40).height(40) Text('加载中…').fontSize(14).fontColor(Theme.textSubtle).margin({ top: 8 }) } .width('100%').height('80%') .justifyContent(FlexAlign.Center) } else if (this.error.length > 0) { Column() { Text(this.error).fontSize(14).fontColor(Theme.danger) Button('重试').margin({ top: 12 }).onClick(() => { this.loadInbox(); }) } .width('100%').height('80%') .justifyContent(FlexAlign.Center) } else if (this.mails.length === 0) { Column() { Text('收件箱为空').fontSize(16).fontColor(Theme.textSubtle) } .width('100%').height('80%') .justifyContent(FlexAlign.Center) } else { List({ space: 1 }) { ForEach(this.mails, (mail: MailSummary) => { ListItem() { this.MailItem(mail) } .height(80) .backgroundColor(mail.status === 'unread' ? Theme.accentSoft : Theme.surface) .onClick(() => { this.getUIContext().getRouter().pushUrl({ url: 'pages/MailDetailPage', params: { mail_id: mail.mail_id } }); }) }, (mail: MailSummary) => mail.mail_id) } .width('100%') .layoutWeight(1) /* 上下边缘渐隐(2026-09-17 补:原先漏了,同一次滚动里只有部分是渐隐) */ .fadingEdge(true, { fadingEdgeLength: LengthMetrics.vp(LIST_FADE_LENGTH) }) .divider({ strokeWidth: 1, color: Theme.border, startMargin: 16, endMargin: 16 }) } // 底部 Tab 栏(预留系统导航栏空间) Column() { Row() { Column() { AmIcon({ iconName: 'inbox', iconSize: 20, iconColor: Theme.accent }) Text('收件箱').fontSize(11).fontColor(Theme.accent) } .layoutWeight(1).height('100%') .justifyContent(FlexAlign.Center) .backgroundColor(Theme.accentSoft) Column() { AmIcon({ iconName: 'chatBubble', iconSize: 20, iconColor: Theme.textMuted }) Text('会话').fontSize(11).fontColor(Theme.textMuted) } .layoutWeight(1).height('100%') .justifyContent(FlexAlign.Center) .onClick(() => { this.getUIContext().getRouter().pushUrl({ url: 'pages/SessionsPage' }); }) Column() { AmIcon({ iconName: 'contacts', iconSize: 20, iconColor: Theme.textMuted }) Text('联系人').fontSize(11).fontColor(Theme.textMuted) } .layoutWeight(1).height('100%') .justifyContent(FlexAlign.Center) } .width('100%').height(56) .backgroundColor(Theme.surface) } .width('100%') } .width('100%').height('100%') .backgroundColor(Theme.pageBg) } @Builder MailItem(mail: MailSummary) { Row() { // 未读圆点 Column() { if (mail.status === 'unread') { Circle({ width: 8, height: 8 }).fill(Theme.accent) } } .width(20).height('100%') .justifyContent(FlexAlign.Center) // 正文 Column() { Row() { Text(mail.from_name).fontSize(14).fontWeight(mail.is_read ? FontWeight.Normal : FontWeight.Bold).fontColor(Theme.textPrimary) Blank() Text(mail.created_at).fontSize(11).fontColor(Theme.textSubtle) } .width('100%') Text(mail.subject).fontSize(13).fontColor(Theme.textPrimary) .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }) .margin({ top: 2 }) Text(mail.body_preview).fontSize(12).fontColor(Theme.textSubtle) .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }) .margin({ top: 2 }) } .layoutWeight(1).height('100%') .alignItems(HorizontalAlign.Start) .padding({ left: 8 }) // 附件图标 if (mail.has_attachments) { AmIcon({ iconName: 'paperclip', iconSize: 14, iconColor: Theme.textMuted }).margin({ right: 8 }) } // 权限档位徽标 if (mail.permission_mode.length > 0) { Text(mail.permission_mode) .fontSize(10).fontColor(Theme.textMuted) .backgroundColor(Theme.border).borderRadius(4) .padding({ left: 4, right: 4, top: 1, bottom: 1 }) } } .width('100%').height('100%') .padding({ left: 12, right: 12 }) .alignItems(VerticalAlign.Center) } }