/* * AgentMail 鸿蒙客户端 — 授权(通信页第三栏) * * ★★ 2026-09-23 **从 `pages/MainPage.ets` 抽出来**(那个文件已 4592 行、28 个内联 * `@Builder`)。抽出的动因不是"文件太长不好看",而是**对齐需要**: * * 用户要求「两端系统性对齐」。而 WebUI 那边每一块都是一个组件文件 * (`components/PermissionList.tsx` / `PermissionChip.tsx` / `MailList.tsx` …), * 鸿蒙这边全是一个大 struct 里的内联 Builder ⇒ **没有一个可对比的单元**。 * 于是"对齐"只能退化成逐行比渲染字段 —— 而那正是用户批评的修修补补。 * * ⇒ 把授权栏抽成独立组件,让它与 `PermissionList.tsx` 形成**一一对应**: * 以后对齐看这一处,不用在 4592 行里找。 * * ★ 为什么放在 `pages/` 而不是 `common/`: * `common/` 里是可被任意页面复用的**通用**件(`Surface` 的玻璃卡/按压、 * `BackgroundPicker`、`Icons`…)。授权栏是**一个功能面**,只服务通信页第三栏, * 与 `WideSidebar.ets` 同类 —— 那个也是放在 `pages/` 下的独立组件文件。 */ import { ApiClient, ApiError } from '../api/ApiClient'; import { Theme } from '../common/Theme'; import { AppHeader, GlassCardModifier, PaneModifier } from '../common/Surface'; import { AmIcon } from '../common/Icons'; import { MailApi, InboxResponse } from '../api/MailApi'; import { AccountManager, AccountInfo } from '../api/AccountManager'; import { PermissionRequest, PendingResponse, MailSummary } from '../model/Models'; import { LIST_FADE_LENGTH } from '../model/NavItems'; import { emptyTitle, emptyHint } from '../model/CommTabs'; import { LengthMetrics } from '@kit.ArkUI'; import { MailLike, PermissionGroup, groupPermissions, shortTimeOf } from '../model/MailGrouping'; import { INBOX_PAGE_SIZE } from '../common/MailStore'; import { Insets, KEY_WINDOW_INSETS, topInset } from '../model/WindowInsets'; /** WebUI 邮件列表统一使用 MM/DD HH:mm(与 `MainPage` 的同名函数一致)。 */ function compactMailTime(iso: string): string { const value: Date = new Date(iso); if (Number.isNaN(value.getTime())) { return ''; } const month: string = (value.getMonth() + 1).toString().padStart(2, '0'); const day: string = value.getDate().toString().padStart(2, '0'); const hour: string = value.getHours().toString().padStart(2, '0'); const minute: string = value.getMinutes().toString().padStart(2, '0'); return month + '/' + day + ' ' + hour + ':' + minute; } /* * ─────────────────────── 授权(通信页第三栏) ─────────────────────── * * 只放**待人点头**的事:`GET /permission/pending`(不从收件箱筛,理由见 `MailApi`)。 * 决策走 `POST /permission/decide`,`note` 会随决策送达模型 —— * 所以拒绝时**能填备注**,而且填了必须真的发出去。 * * 两件必须如实说的事(WebUI 侧踩过坑,见 `decidePermission` 的返回类型): * ① 请求**已过期**时服务端会带 `expired`:这时决策落到了一个没人在等的请求上, * 得当场告诉人(否则会以为"批了,Agent 继续干活了"); * ② `warning` 同理,服务端让显示什么就显示什么。 */ @Component export struct PermissionTab { /** 背景是否开启:开着就让出页面底(WebUI 的做法是页面底完全透明) */ @Prop bgActive: boolean = false; /** 底部悬浮条高度(窄屏非 0)——理由见 `InboxTab` 的 `navReserve` */ @Prop navReserve: number = 0; /* * ★★ 2026-09-23:授权栏走 `navigator_only`(用户裁定)。 * * 点一条 → 推详情页(与 InboxTab / ContactsTab 同一条 `openMail`)。 * 决策 UI 在详情页的 `PermissionPanel`,**不在**这里 —— * 所以这里不再需要「同意/拒绝」按钮、备注框、`decide()` 那一整套。 * 对齐 WebUI `PermissionList.tsx:81` 的 `pick()` = `selectMail + showDetail`。 */ onOpenMail: (mailId: string, accountId: string) => void = (): void => {}; /* * 状态栏避让(窗口级事实,见 `model/WindowInsets.ts`)。 * ★ 原先 `AppHeader({ topInsetPx: 0 })` 硬编码 0 ⇒ 授权页顶栏压进系统状态栏 * (真机 MatePad Pro 实测 statusBar=38.58 读到了却被丢掉)。 */ @StorageLink(KEY_WINDOW_INSETS) windowInsets: Insets = new Insets(); @State requests: PermissionRequest[] = []; /** * 已决策的**历史**,按会话分组(对齐 WebUI `PermissionList.tsx:182` 的「历史 {n}」)。 * * ★ 来源是 **inbox**(不是 `/permission/pending`)—— 后者 SQL 带 * `WHERE pr.result IS NULL`,永远拿不到已决策的。 * 详见 `load()` 里那段取舍说明。 */ @State permGroups: PermissionGroup[] = []; @State loading: boolean = false; @State error: string = ''; /* * ★★ 2026-09-23:`noteFor` / `noteText` / `busyId` 已删 —— * `navigator_only` 后决策 UI 搬到详情页,授权栏不再做决策。 */ /** * 已展开的会话分组(键 = `PermissionGroup.key`)。 * * ★★ 2026-09-23 新增 —— 对齐 WebUI `PermissionList.tsx` 的**展开层**。 * * ── 为什么需要它(之前那版把这一整层漏了)── * 上一版鸿蒙只给每个会话一行「历史 n 条」,**不可展开**。 * 而 WebUI 那行是个 `