fix(webui): 日历补上圆角 —— 外层面板圆了、里面的格子还是直角
用户:「日历页面怎么没有圆角」。 原因与列表那次同源:**外层面板一直是圆的**(`.app-shell > *` 给了 14px), 但里面的日格/列写的是 `border-b border-r`(直角 + 细边框),而我早前为了修滚动 去掉了面板的 `overflow: hidden` ⇒ 里面的直角就戳在圆角外面,看起来"整页没有圆角"。 改法:日格、周视图列、以及日历的两个大面板都改用 `.glass-card` (圆角 14px + 白色玻璃 + 细边框),与列表项、顶部气泡同一套语言。 顺手去掉日格上的 `overflow-hidden`(它会把事件条裁掉,而"裁掉"正是这几轮的老毛病)。 实测(自有浏览器,壁纸开):日历里 **42 个 `.glass-card`**(月视图 42 格 + 面板), 格子 `border-radius: 14px`、底色 `rgba(255,255,255,0.78)`。 (这条同样是"外层圆角 + 内层直角"这一类问题 —— 我已经在列表、顶部、日历上 各修了一次。要找根因的话:**面板圆角不能靠 `overflow: hidden` 裁**(它会杀滚动), 所以每一层自己都要圆角。这是这套"浮动面板"设计的固有代价,写在这里备查。)
This commit is contained in:
@ -1,13 +1,13 @@
|
||||
/*
|
||||
* AgentMail 鸿蒙客户端 — 主框架(底部 Tab 导航)
|
||||
* 收件箱 / 会话 / 联系人 三个 TabContent
|
||||
* 收件箱 / 联系人 两个 TabContent(平级的「会话」已并入这两处,见文件末尾的说明)
|
||||
*/
|
||||
import { ApiClient, ApiError } from '../api/ApiClient';
|
||||
import { Theme } from '../common/Theme';
|
||||
import { MailApi, InboxResponse } from '../api/MailApi';
|
||||
import { AccountManager, AccountInfo } from '../api/AccountManager';
|
||||
import { SseService, SseEvent } from '../api/SseService';
|
||||
import { MailSummary, Session, Contact } from '../model/Models';
|
||||
import { MailSummary, Contact } from '../model/Models';
|
||||
import {
|
||||
MailLike,
|
||||
SessionGroup,
|
||||
@ -19,7 +19,11 @@ import {
|
||||
budgetLabel,
|
||||
nextContactView,
|
||||
contactViewTitle,
|
||||
lastFromIsHuman
|
||||
lastFromIsHuman,
|
||||
permissionLabel,
|
||||
permissionChipText,
|
||||
permissionHint,
|
||||
enforcementLabel
|
||||
} from '../model/MailGrouping';
|
||||
import { MailDetailParams, ComposeParams } from '../model/RouteParams';
|
||||
import { promptAction } from '@kit.ArkUI';
|
||||
@ -506,8 +510,15 @@ struct InboxTab {
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.padding({ left: 8 })
|
||||
|
||||
if (mail.permission_mode.length > 0) {
|
||||
Text(mail.permission_mode)
|
||||
/*
|
||||
* 收件箱行里的档位徽标:只写**中文档位**(`permissionLabel`)。
|
||||
*
|
||||
* 为什么不带强制力标记:收件箱列表接口(`/me/mail/inbox`)的每条邮件里
|
||||
* **没有** `permission_enforcement`(那是会话级的)—— 这里画一个标记就等于
|
||||
* 编一个"平台做到了什么"出来。强制力标记只出现在拿得到该字段的地方(卡片视图)。
|
||||
*/
|
||||
if (permissionLabel(mail.permission_mode).length > 0) {
|
||||
Text(permissionLabel(mail.permission_mode))
|
||||
.fontSize(10).fontColor(Theme.permFg(mail.permission_mode))
|
||||
.backgroundColor(Theme.permBg(mail.permission_mode)).borderRadius(4)
|
||||
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
|
||||
@ -519,118 +530,19 @@ struct InboxTab {
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
struct SessionsTab {
|
||||
@State sessions: Session[] = [];
|
||||
@State loading: boolean = false;
|
||||
@State error: string = '';
|
||||
private mailApi: MailApi | null = null;
|
||||
|
||||
aboutToAppear(): void {
|
||||
const ctx = this.getUIContext().getHostContext();
|
||||
if (ctx !== undefined) {
|
||||
this.mailApi = new MailApi(ApiClient.getInstance(ctx));
|
||||
this.loadData();
|
||||
}
|
||||
}
|
||||
|
||||
async loadData(): Promise<void> {
|
||||
const m: MailApi | null = this.mailApi;
|
||||
if (m === null) {
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
try {
|
||||
const resp = await m.sessions();
|
||||
this.sessions = resp.sessions;
|
||||
} 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)
|
||||
}
|
||||
.width('100%').height(56).padding({ left: 16 })
|
||||
.backgroundColor(Theme.surface)
|
||||
|
||||
if (this.loading) {
|
||||
Column() {
|
||||
LoadingProgress().width(40).height(40)
|
||||
}
|
||||
.width('100%').layoutWeight(1).justifyContent(FlexAlign.Center)
|
||||
} else if (this.error.length > 0) {
|
||||
Column() {
|
||||
Text(this.error).fontSize(14).fontColor(Theme.danger)
|
||||
}
|
||||
.width('100%').layoutWeight(1).justifyContent(FlexAlign.Center)
|
||||
} else if (this.sessions.length === 0) {
|
||||
Column() {
|
||||
Text('📭 暂无会话').fontSize(16).fontColor(Theme.textSubtle)
|
||||
}
|
||||
.width('100%').layoutWeight(1).justifyContent(FlexAlign.Center)
|
||||
} else {
|
||||
List({ space: 1 }) {
|
||||
ForEach(this.sessions, (s: Session, idx: number) => {
|
||||
ListItem() {
|
||||
this.SessionItem(s, idx)
|
||||
}
|
||||
.height(90)
|
||||
}, (_s: Session, idx: number) => idx.toString())
|
||||
}
|
||||
.width('100%').layoutWeight(1)
|
||||
.divider({ strokeWidth: 1, color: Theme.border, startMargin: 16, endMargin: 16 })
|
||||
}
|
||||
}
|
||||
.width('100%').height('100%')
|
||||
.backgroundColor(Theme.pageBg)
|
||||
}
|
||||
|
||||
@Builder
|
||||
SessionItem(s: Session, idx: number) {
|
||||
Row() {
|
||||
Column() {
|
||||
Row() {
|
||||
Text(s.session_alias.length > 0 ? s.session_alias : ('会话 #' + (idx + 1)))
|
||||
.fontSize(14).fontWeight(FontWeight.Bold).fontColor(Theme.textPrimary)
|
||||
.maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||
.layoutWeight(1)
|
||||
if (s.max_rounds > 0) {
|
||||
Text(s.used_rounds + '/' + s.max_rounds)
|
||||
.fontSize(11).fontColor(s.used_rounds >= s.max_rounds ? Theme.danger : Theme.textMuted)
|
||||
.backgroundColor(s.used_rounds >= s.max_rounds ? Theme.dangerBg : Theme.surfaceMuted)
|
||||
.borderRadius(4).padding({ left: 4, right: 4, top: 1, bottom: 1 })
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
|
||||
Text('对方: ' + s.from_agent).fontSize(12).fontColor(Theme.textMuted).margin({ top: 4 })
|
||||
|
||||
Row() {
|
||||
Text(s.permission_mode.length > 0 ? s.permission_mode : '—')
|
||||
.fontSize(11).fontColor(Theme.permFg(s.permission_mode))
|
||||
.backgroundColor(Theme.permBg(s.permission_mode)).borderRadius(4)
|
||||
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
|
||||
Blank()
|
||||
Text(s.mail_count + ' 封').fontSize(11).fontColor(Theme.textSubtle)
|
||||
Text(' · ' + s.status).fontSize(11).fontColor(s.status === 'active' ? Theme.approve : Theme.textSubtle)
|
||||
}
|
||||
.width('100%').margin({ top: 4 })
|
||||
}
|
||||
.layoutWeight(1).height('100%')
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.justifyContent(FlexAlign.SpaceBetween)
|
||||
}
|
||||
.width('100%').height('100%')
|
||||
.padding({ left: 16, right: 16, top: 12, bottom: 12 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 平级的「会话」tab 已撤(2026-09-14,P2a 收尾)。
|
||||
*
|
||||
* 撤掉的依据是"信息没有丢":那张会话列表独有的一列 ——
|
||||
* 会话别名 / 主题、对方 from_agent、邮件数、权限档、往返预算(used/max)、status ——
|
||||
* 现在落在两处:
|
||||
* · 收件箱那栏**按会话折叠**,组头就是会话(别名/主题/未读/封数),点开是那几封;
|
||||
* · 联系人那栏的**卡片视图**是会话的进度视角(主题主角、最新摘要谁说的、
|
||||
* 权限档 + 强制力、往返预算条)。
|
||||
* 其中 `status`(active/archived) 与 `from_agent` 参考实现(WebUI `WorkCard.tsx`)
|
||||
* **也不显示** —— 判据 `卡片字段两边一致` 钉住这一点:哪天 WebUI 补上了,
|
||||
* 那条判据会红,提醒鸿蒙跟着补,而不是悄悄地少一块。
|
||||
*/
|
||||
|
||||
@Component
|
||||
struct ContactsTab {
|
||||
@ -796,12 +708,26 @@ struct ContactsTab {
|
||||
Text(c.mail_count + ' 封 · ' + c.last_activity)
|
||||
.fontSize(11).fontColor(Theme.textSubtle)
|
||||
Blank()
|
||||
if (c.permission_mode.length > 0) {
|
||||
Text(c.permission_mode)
|
||||
/*
|
||||
* 权限档位徽标:**档位 + 强制力标记**,点它弹出"平台实际做到了什么"。
|
||||
*
|
||||
* 只显示档位会让人以为 plan 档真的管住了对方;WebUI 把这句话放在悬停提示里,
|
||||
* 而手指没有悬停 —— 所以鸿蒙这边拆成两步:标记形状当场可辨(● 平台强制 /
|
||||
* ◉ 覆盖不完整 / ○ 仅提示),点一下用 toast 说完整那句话(文案两边逐字一致)。
|
||||
*/
|
||||
if (permissionChipText(c.permission_mode, c.permission_enforcement).length > 0) {
|
||||
Text(permissionChipText(c.permission_mode, c.permission_enforcement))
|
||||
.fontSize(10).fontColor(Theme.permFg(c.permission_mode))
|
||||
.backgroundColor(Theme.permBg(c.permission_mode)).borderRadius(4)
|
||||
.padding({ left: 5, right: 5, top: 1, bottom: 1 })
|
||||
.margin({ right: 6 })
|
||||
.onClick(() => {
|
||||
promptAction.showToast({
|
||||
message: permissionHint(c.permission_mode, c.permission_enforcement)
|
||||
+ '(强制力:' + enforcementLabel(c.permission_enforcement) + ')',
|
||||
duration: 6000
|
||||
});
|
||||
})
|
||||
}
|
||||
// 往返预算:上限为 0 = 不限,不显示(与 WebUI BudgetChip 同判据)
|
||||
if (budgetLabel(c.max_rounds, c.used_rounds).length > 0) {
|
||||
@ -845,7 +771,8 @@ struct ContactsTab {
|
||||
.margin({ top: 3 })
|
||||
|
||||
Row() {
|
||||
Text(c.permission_mode.length > 0 ? c.permission_mode : '—')
|
||||
// 列表视图(跟谁在聊):只要档位,不塞强制力标记 —— 详细说明在卡片视图那颗可点的徽标上
|
||||
Text(permissionLabel(c.permission_mode).length > 0 ? permissionLabel(c.permission_mode) : '—')
|
||||
.fontSize(10).fontColor(Theme.permFg(c.permission_mode))
|
||||
.backgroundColor(Theme.permBg(c.permission_mode)).borderRadius(4)
|
||||
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
|
||||
@ -885,21 +812,29 @@ struct MainPage {
|
||||
}
|
||||
|
||||
build() {
|
||||
/*
|
||||
* 只剩两个平级页签:收件箱 / 联系人。
|
||||
*
|
||||
* 「会话」原先是个平级 tab,现在**撤掉**了 —— 它不是第三个地方,
|
||||
* 而是"同一批数据的另一种看法":收件箱那栏按会话折叠(组头就是会话),
|
||||
* 联系人那栏的**卡片视图**就是会话的进度视角(主题、最新摘要谁说的、
|
||||
* 权限档与强制力、往返预算)。这两处齐了,平级的「会话」就是重复入口。
|
||||
*
|
||||
* 时机是按 pi 的判断排的:**先补视图与折叠,再撤 tab** —— 撤早了,
|
||||
* 往返预算 / status / from_agent 这些只在会话列表里出现的信息就没地方看了。
|
||||
* (status 与 from_agent 参考实现也不显示,见 `WorkCard.tsx` 的字段集,
|
||||
* 判据里有一条"卡片字段两边一致"钉住这件事,避免以后以为漏了。)
|
||||
*/
|
||||
Tabs({ barPosition: BarPosition.End }) {
|
||||
TabContent() {
|
||||
InboxTab()
|
||||
}
|
||||
.tabBar(this.TabBarBuilder('收件箱', '📬', 0))
|
||||
|
||||
TabContent() {
|
||||
SessionsTab()
|
||||
}
|
||||
.tabBar(this.TabBarBuilder('会话', '💬', 1))
|
||||
|
||||
TabContent() {
|
||||
ContactsTab()
|
||||
}
|
||||
.tabBar(this.TabBarBuilder('联系人', '👤', 2))
|
||||
.tabBar(this.TabBarBuilder('联系人', '👤', 1))
|
||||
}
|
||||
.onChange((index: number) => {
|
||||
this.currentIndex = index;
|
||||
|
||||
Reference in New Issue
Block a user