跨端: 闭合 harmony-permission-history —— 授权栏补上「已决策的历史」
这是 `docs/DEBTS.json` 里登记的一条,它的到期条件原文是
「做『授权栏与 WebUI 对齐』时」—— 就是现在这一轮。
## 原缺口
鸿蒙的 `PermissionTab` 只调 `GET /permission/pending`(服务端
`ListPendingPermissionsFor`,SQL 带 `WHERE pr.result IS NULL`)
⇒ **只拿得到待决的**,于是"这条会话批过哪些事"完全看不到;
而 WebUI 有(`PermissionList.tsx:182` 的「历史 {n}」)。
## 关键判断:**不照抄 WebUI 的 inbox 分组**
我先把 `PermissionTab.load` 整个改成读 inbox + `groupPermissions`,
**改到一半发现行不通**(编译报 `question`/`context`/`agent_name` 找不到):
· WebUI 从 inbox 分组,但它的 `PermissionRow` **只渲染**
`subject`/`created_at`/`permission_result`/`permission_expires_at`
(逐字段 grep 过,全文件没有 `question`/`options`/`context`);
· 而**待决**那一段我们要显示 `question`/`options`/`context`/`kind`
—— 那四个字段在 `permission_requests` **表**里,
inbox 回包(`models.Mail`)**没有它们**(模型逐条核过,只有 `permission_result`
与 `permission_options`)。
⇒ 两条来源各有各的信息量,不是二选一:
· **待决**继续走专用端点(信息更全、能直接决策);
· **历史**走 inbox 补上。
代价是每账号多一次请求 —— 这是**有意的取舍**,写在代码注释里。
(半成品已 `git checkout` 撤掉,没有把它留在提交里。
撤掉的原因如实记在注释里,免得下一个人以为"照着 WebUI 改"就行。)
## 落地
· `model/MailGrouping.ts`:加 `groupPermissions` + `PermissionGroup` +
`isPendingPermission`,逐条对齐 WebUI 的 `groupPermissions`,
含它那**三步排序**(有待决的先来 → 待决多的更靠前 → 最新一封倒序)。
· `PermissionTab`:从 inbox 取 `mail_type=permission_request &&
permission_result != ''` 的,分组后渲染「历史 n 条」(只读、不可操作)。
· 空态判据从 `requests.length === 0` 改成**两者都空**才显示 ——
否则"有待决的历史"会被误报成"没有待决策的请求"。
## 判据自己抓到了我
`cross-client-logic.test.mjs` 的「缺口只减不增」在我补上 `groupPermissions`
之后立刻变红,并给出准确指引:
减少(harmony 补上了功能)→ 请把 gaps 里对应的名字删掉
⇒ 已清空 `gaps`。**这条判据在这轮里三次发挥作用**:
第一次报出这个缺口(09-20),第二次在我半成品时红了,
第三次确认闭合。`pass=7 fail=0`。
`docs/DEBTS.json` 的 `count` 已改 0、`due` 记完成、`note` 写明修法与取舍。
## 设备验证(如实)
✓ 授权页正常渲染,进程存活(23343),无新 jscrash
✓ 空态文案正确(本机确实没有权限邮件)
✗ **"历史 n 条"真的显示出来**这条路径没能实测:
本机没有已决策的权限请求(服务端实测 `permission_request` 0 封)。
逻辑逐条对齐 WebUI、编译通过,但我不声称已看到它渲染。
This commit is contained in:
@ -29,6 +29,8 @@ import { BackgroundPlan, PresetLayer, TRANSPARENT, resolveBackground } from '../
|
||||
import { MailSummary, Contact, PermissionRequest, DecideResponse, SentResponse, PendingResponse } from '../model/Models';
|
||||
import {
|
||||
MailLike,
|
||||
PermissionGroup,
|
||||
groupPermissions,
|
||||
MailSplit,
|
||||
SessionGroup,
|
||||
groupMailsBySession,
|
||||
@ -1409,6 +1411,14 @@ struct PermissionTab {
|
||||
/** 底部悬浮条高度(窄屏非 0)——理由见 `InboxTab` 的 `navReserve` */
|
||||
@Prop navReserve: number = 0;
|
||||
@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 = '';
|
||||
/** 正在填备注的那条(空串 = 没有) */
|
||||
@ -1432,6 +1442,8 @@ struct PermissionTab {
|
||||
await acctMgr.load();
|
||||
const accounts: AccountInfo[] = acctMgr.getAccounts();
|
||||
const all: PermissionRequest[] = [];
|
||||
/* 已决策的历史(从 inbox 取,见下面 `settled` 的注释) */
|
||||
const settled: MailLike[] = [];
|
||||
for (let i = 0; i < accounts.length; i++) {
|
||||
const acct: AccountInfo = accounts[i];
|
||||
try {
|
||||
@ -1442,11 +1454,43 @@ struct PermissionTab {
|
||||
for (let j = 0; j < resp.requests.length; j++) {
|
||||
all.push(resp.requests[j]);
|
||||
}
|
||||
/*
|
||||
* ★★ 2026-09-21 补**已决策的历史**。
|
||||
*
|
||||
* 用户可见差异(登记在 `docs/DEBTS.json` 的 `harmony-permission-history`,
|
||||
* 其到期条件正是「做『授权栏与 WebUI 对齐』时」):
|
||||
* `GET /permission/pending` 的 SQL 带 `WHERE pr.result IS NULL`
|
||||
* ⇒ **只拿得到待决的**,于是"这条会话批过哪些事"在鸿蒙上完全看不到,
|
||||
* 而 WebUI 能看到(`PermissionList.tsx:182` 的「历史 {n}」)。
|
||||
*
|
||||
* ── 为什么不改走 WebUI 的 inbox 分组 ──
|
||||
*
|
||||
* 核实过:WebUI 从 inbox 分组(`groupPermissions`),但它的
|
||||
* `PermissionRow` **只渲染** `subject` / `created_at` / `permission_result`
|
||||
* / `permission_expires_at`(逐字段 grep 过)。
|
||||
* 而**待决**那一段我们要显示 `question`/`options`/`context`/`kind` ——
|
||||
* 那四个字段在 `permission_requests` **表**里,
|
||||
* inbox 回包(`models.Mail`)**没有它们**(模型里逐条核过)。
|
||||
*
|
||||
* ⇒ 待决继续走专用端点(信息更全、能直接决策),
|
||||
* 历史走 inbox 补上。两条来源合起来,与 WebUI 的可见信息量一致。
|
||||
* 代价:多一次请求/账号。这是**有意的取舍**,不是漏了优化。
|
||||
*/
|
||||
const inbox: InboxResponse = await new MailApi(c).inbox('all', INBOX_PAGE_SIZE);
|
||||
for (let j = 0; j < inbox.mails.length; j++) {
|
||||
const m: MailSummary = inbox.mails[j];
|
||||
/* 只要**已决策**的权限请求(待决的已由上面那个端点给了) */
|
||||
if (m.mail_type === 'permission_request' && m.permission_result.length > 0) {
|
||||
m.source_account_id = acct.id;
|
||||
settled.push(m);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// 单账号失败不空整栏
|
||||
}
|
||||
}
|
||||
this.requests = all;
|
||||
this.permGroups = groupPermissions(settled);
|
||||
} catch (e) {
|
||||
const ae = e as ApiError;
|
||||
this.error = ae.message.length > 0 ? ae.message : '加载失败';
|
||||
@ -1624,7 +1668,7 @@ struct PermissionTab {
|
||||
} else if (this.error.length > 0) {
|
||||
Column() { Text(this.error).fontSize(13).fontColor(Theme.dangerFor()) }
|
||||
.width('100%').layoutWeight(1).justifyContent(FlexAlign.Center)
|
||||
} else if (this.requests.length === 0) {
|
||||
} else if (this.requests.length === 0 && this.permGroups.length === 0) {
|
||||
Column() {
|
||||
AmIcon({ iconName: 'shield', iconSize: 36, iconColor: Theme.textSubtleFor() }).margin({ bottom: 8 })
|
||||
Text(emptyTitle('permissions')).fontSize(15).fontColor(Theme.textMuted)
|
||||
@ -1633,12 +1677,49 @@ struct PermissionTab {
|
||||
.width('100%').layoutWeight(1).justifyContent(FlexAlign.Center)
|
||||
} else {
|
||||
List({ space: 8 }) {
|
||||
/* 待决(可决策) */
|
||||
ForEach(this.requests, (req: PermissionRequest) => {
|
||||
ListItem() {
|
||||
this.RequestCard(req)
|
||||
}
|
||||
.width('100%')
|
||||
}, (req: PermissionRequest) => req.request_id)
|
||||
|
||||
/*
|
||||
* ── 已决策的历史(对齐 WebUI `PermissionList.tsx:182` 的「历史 {n}」)──
|
||||
*
|
||||
* ★★ 2026-09-21 新增。这一段在鸿蒙侧**此前完全看不到**
|
||||
* (`harmony-permission-history`,其到期条件就是"做授权栏对齐")。
|
||||
*
|
||||
* 只读、不可操作(决策已经发生,重放没有意义)—— 与 WebUI 一致:
|
||||
* 它把 `settled` 归到折叠的分组里,默认收起。
|
||||
* 这里是**平铺一行一行**的历史摘要(会话别名 + 决策 + 时间),
|
||||
* 不逐条列邮件正文:那是详情页的事。
|
||||
*/
|
||||
ForEach(this.permGroups, (g: PermissionGroup) => {
|
||||
ListItem() {
|
||||
Row() {
|
||||
Column() {
|
||||
Text(g.alias.length > 0 ? g.alias : '(未命名会话)')
|
||||
.fontSize(13)
|
||||
.fontFamily('monospace')
|
||||
.fontColor(Theme.textMuted)
|
||||
.maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||
.width('100%')
|
||||
Text('历史 ' + g.settled.length.toString() + ' 条')
|
||||
.fontSize(11).fontColor(Theme.textSubtleFor())
|
||||
.margin({ top: 2 })
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
}
|
||||
.width('100%')
|
||||
.padding(12)
|
||||
.attributeModifier(GlassCardModifier.of(this.bgActive, false))
|
||||
.borderRadius(Theme.radiusCard)
|
||||
}
|
||||
.width('100%')
|
||||
}, (g: PermissionGroup) => 'hist-' + g.key)
|
||||
}
|
||||
.width('100%').layoutWeight(1)
|
||||
.fadingEdge(true, { fadingEdgeLength: LengthMetrics.vp(LIST_FADE_LENGTH) })
|
||||
|
||||
Reference in New Issue
Block a user