跨端对齐:授权栏 navigator_only + 组件按页拆分 + 服务器补 permission_options
用户两项裁定落地(均为 ask_user 明确选择):
① 授权栏口径 = navigator_only(照 WebUI 架构)
· 新建 pages/PermissionPanel.ets —— 详情页的决策面板,
对应 MailView.tsx:693 的 PermissionPanel(审批型 / 主动提问 / 已处理 三态)
· 决策入口从授权栏移到 MailDetailPage;MailDetailPage 原来只显示一个
「权限请求」小标签、根本没有决策入口(比 WebUI 少一整块,且反了:
栏里能决策、点进详情反而不能)
· PermissionTab 删掉内联「同意/拒绝」+ 备注框 + decide():
整卡可点 → onOpenMail(对齐 WebUI PermissionList.tsx:81 的 pick())
· PermissionRequest 补 source_account_id(客户端侧记来源,跳详情要定位网关)
② 服务器补 permission_options —— 修一条真实的、跨端共有的缺口
· mails.permission_options 从 INSERT 起就写进去,但**从来没有任何读路径
选过它** ⇒ 详情端点永远返回空。WebUI 的决策面板读 mail.permission_options,
所以提问型的预设选项**两端全部落空**(审批型靠 ['同意','拒绝'] 兜底蒙混)
· GetMailByID 补选该列 + JSON 反序列化(与 cc_list 同款)
③ 组件按页封装(用户要求「以便与 WebUI 一一对应」)
MainPage.ets 4592 → 3192 行
· pages/PermissionTab.ets 720 行 ↔ PermissionList.tsx
· pages/ContactsTab.ets 796 行 ↔ ContactPanel.tsx
· pages/NavDestinations.ets 181 行 ↔ Navigation 壳
· pages/NavShared.ets 65 行 ↔ 跨栏共用件
④ 判据跟着组件搬家(否则静默失效,不是红)
harmony-logic 的 pageCode / harmony-nav 的 navSrc 改为显式文件名单;
harmony-appearance 的 PANE_SOURCES 补 ContactsTab;harmony-contacts 三个
test 并入 ContactsTab;harmony-logic 的决策断言改指 PermissionPanel,
并新增「授权栏不许再有内联决策」两条(navigator_only 的正形状)。
animation-audit:共享元素转场判据从「同文件共址」改为「按 id 找驱动」。
旧形状把 in/out 端必须在同一文件当成代理,而两端**天然在两处**;
抽出写信页(NavDestinations 持有 in 端)后误报。新判据仍要求每个 id
都有 Motion.morph 驱动 —— 变异实测:把驱动换成裸 animateTo 仍判红。
判据:files=34 checks=556 red=1(仅 build-stamp,产物待重构建)
This commit is contained in:
@ -522,6 +522,47 @@ export struct ComposeView {
|
||||
return account !== null ? account.displayName : '选择账号';
|
||||
}
|
||||
|
||||
/**
|
||||
* 账号选择菜单(`bindMenu` 的内容)—— 原来内联在 `build()` 里的那块列表。
|
||||
*
|
||||
* ★★ 2026-09-21 新增:从内联 `if/ForEach` 改为官方菜单内容。
|
||||
*
|
||||
* ★ 逐项不再挂 `Theme.menuIn()`:那个过渡原本是为了掩盖"列表推进布局流
|
||||
* 导致下方内容被顶下去"。现在菜单是系统浮层(不占布局),
|
||||
* 出入场由系统菜单自己负责 —— 再挂一层会与系统动画叠在一起。
|
||||
*
|
||||
* ★ 保留逐项的高亮与勾选(选中态的语言不变);
|
||||
* 只是把"怎么弹出来"交给了系统。
|
||||
*/
|
||||
@Builder
|
||||
AccountMenu() {
|
||||
Column() {
|
||||
ForEach(this.accountList, (acct: AccountInfo) => {
|
||||
Row() {
|
||||
Text(acct.displayName)
|
||||
.fontSize(13)
|
||||
.fontColor(this.selectedAccountId === acct.id ? Theme.accentFor() : Theme.textPrimary)
|
||||
.fontWeight(this.selectedAccountId === acct.id ? FontWeight.Bold : FontWeight.Normal)
|
||||
.layoutWeight(1)
|
||||
if (this.selectedAccountId === acct.id) {
|
||||
AmIcon({ iconName: 'check', iconSize: 14, iconColor: Theme.accentFor() })
|
||||
}
|
||||
}
|
||||
.width(220).height(40).padding({ left: 12, right: 12 })
|
||||
.backgroundColor(this.selectedAccountId === acct.id
|
||||
? Theme.accentSoftFor(this.isDarkNow) : Color.Transparent)
|
||||
.attributeModifier(PressEffectModifier.of())
|
||||
.onClick(() => {
|
||||
this.selectedAccountId = acct.id;
|
||||
this.showAccountPicker = false;
|
||||
})
|
||||
}, (acct: AccountInfo) => acct.id)
|
||||
}
|
||||
.width(220)
|
||||
.padding({ top: 4, bottom: 4 })
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
}
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
// 顶栏
|
||||
@ -591,46 +632,25 @@ export struct ComposeView {
|
||||
.rotate({ angle: this.showAccountPicker ? 90 : 0 })
|
||||
/* 展开/收起箭头平滑旋转(WebUI `transition-transform` 的对应物) */
|
||||
.animation({ duration: Motion.dur(Theme.durFast), curve: Theme.easeOutSoft })
|
||||
.onClick(() => { this.showAccountPicker = !this.showAccountPicker; })
|
||||
}
|
||||
.width('100%').height(40).padding({ left: 12, right: 12 })
|
||||
.backgroundColor(Theme.surface)
|
||||
|
||||
// 账号选择下拉
|
||||
if (this.showAccountPicker) {
|
||||
ForEach(this.accountList, (acct: AccountInfo) => {
|
||||
Row() {
|
||||
Text(acct.displayName)
|
||||
.fontSize(13)
|
||||
.fontColor(this.selectedAccountId === acct.id ? Theme.accentFor() : Theme.textPrimary)
|
||||
.fontWeight(this.selectedAccountId === acct.id ? FontWeight.Bold : FontWeight.Normal)
|
||||
.layoutWeight(1)
|
||||
if (this.selectedAccountId === acct.id) {
|
||||
AmIcon({ iconName: 'check', iconSize: 14, iconColor: Theme.accentFor() })
|
||||
}
|
||||
}
|
||||
.width('100%').height(40).padding({ left: 40, right: 12 })
|
||||
.backgroundColor(this.selectedAccountId === acct.id ? Theme.accentSoftFor(this.isDarkNow) : Theme.surface)
|
||||
.attributeModifier(PressEffectModifier.of())
|
||||
.onClick(() => {
|
||||
this.selectedAccountId = acct.id;
|
||||
this.showAccountPicker = false;
|
||||
})
|
||||
/*
|
||||
* 弹层入场(对齐 WebUI `@keyframes menu-in`:下移 4vp + 缩到 0.985 + 淡入)。
|
||||
*
|
||||
* ★ 只给**弹层**挂 —— WebUI 那条 `.animate-menu-in` 的注释把适用范围
|
||||
* 钉得很窄("只给真正是弹层的东西"),因为它曾经挂着 `glass-control`,
|
||||
* 于是每次切视图页面上**所有**按钮与输入框一起淡入位移(几十个元素同时动),
|
||||
* 2026-09-15 被摘掉。别把这个放到常驻控件上。
|
||||
*
|
||||
* 挂在 `Row` 上(即 `ForEach` 的每一项):整张候选列表逐项轻落,
|
||||
* 与 WebUI 里"整块列表一起动"略有差别 —— 但 ArkUI 的 `ForEach` 每项
|
||||
* 是独立节点,逐项入场反而更接近"列表展开"的观感,且不会让整层不可交互。
|
||||
*/
|
||||
.transition(Theme.menuIn())
|
||||
}, (acct: AccountInfo) => acct.id)
|
||||
}
|
||||
/*
|
||||
* ★★ 2026-09-21 改用 `bindMenu(isShow, content)`(**API 11** 重载)。
|
||||
*
|
||||
* 第一版写的是 `bindMenu(... ? this.AccountMenu() : undefined, {onDisappear})`
|
||||
* —— 那是 API 7 那个靠"内容为 undefined 就不弹"的重载。
|
||||
* 改成 `isShow` 版后:
|
||||
* · 显隐是**显式参数**(不再靠内容是不是 undefined 隐式表达);
|
||||
* · `$$` 两向绑定让"用户点外部关闭"自动回写状态(不再靠 onDisappear 补)。
|
||||
*
|
||||
* ★ 与 `bindSheet($$this.showAddDialog, …)` 同一套写法 ——
|
||||
* 两处弹层用一种形状,而不是两种。
|
||||
*
|
||||
* ★ 仍然与 `MainPage` 的账号筛选器一致(同一交互,不该两种形状)。
|
||||
*/
|
||||
.bindMenu($$this.showAccountPicker, this.AccountMenu())
|
||||
.onClick(() => { this.showAccountPicker = !this.showAccountPicker; })
|
||||
|
||||
Divider().color(Theme.border)
|
||||
}
|
||||
@ -757,6 +777,31 @@ export struct ComposeView {
|
||||
.width('100%')
|
||||
.backgroundColor(Theme.surface)
|
||||
.borderRadius(Theme.glassRadius)
|
||||
/*
|
||||
* ★★ 2026-09-21 补:**候选列表的入场过渡**(原来一个都没有 ⇒ 硬弹)。
|
||||
*
|
||||
* 这是 `Theme.menuIn()` 的注释里点名要覆盖的那一类("账号选择器、**下拉候选**"),
|
||||
* 但它从来没有真正挂到候选列表上 —— 只有账号选择器用了。
|
||||
*
|
||||
* ── 为什么现在才暴露 ──
|
||||
* 三个原本挂 `menuIn()` 的地方里,两个账号选择器今天换成了官方
|
||||
* `bindMenu`(系统自带转场),`menuIn()` 于是只剩定义、没有调用点。
|
||||
* 我去查"要不要删掉这个死方法"时,顺手拿它的**注释**对了一遍实际用法:
|
||||
* 它写的是给"账号选择器、下拉候选",**候选那一半从来没兑现**。
|
||||
*
|
||||
* ── 为什么必须补(不是纯观感)──
|
||||
* WebUI 的同一处在 `AddressInput.tsx:261` 挂着 `animate-menu-in`
|
||||
* (`.animate-menu-in` 就是 `@keyframes menu-in`,与 `menuIn()` 逐值对齐)。
|
||||
* 也就是说:**WebUI 的候选是动的、鸿蒙的是硬弹** —— 这是真实的跨端不一致,
|
||||
* 不是"我们少做了个美化"。
|
||||
*
|
||||
* ★ 挂在这一整层 `Column` 上(整块列表一起动),与 WebUI 的
|
||||
* `<div class="animate-menu-in">` 包整块一致。
|
||||
* (历史上一度逐项挂 `menuIn()` 的是**账号选择器**,那时是为了掩盖
|
||||
* "下拉推进布局流把列表顶下去";现在的候选是浮在内容之上的,
|
||||
* 整块入场才是对的,别再逐项。)
|
||||
*/
|
||||
.transition(Theme.menuIn())
|
||||
}
|
||||
|
||||
Divider().color(Theme.border)
|
||||
|
||||
Reference in New Issue
Block a user