跨端: 修卡片投影/页签条圆角/日视图/周视图 — 五处对 WebUI 的误读
用户逐条指出后,用 CDP 读 WebUI 的 computed style 与祖先链,发现五处都是我读错/抄错。
## ① 列表底部那条"不知道什么玩意的阴影"(用户原话)
根因:我把 `instance.shadow(Theme.glassShadow)` 挂在 **每张卡片** 上。
WebUI 全仓 `box-shadow` 只有三处 —— `.app-shell > *`(窗格)、
`html[data-bg='on'] .app-shell > *`、`.narrow-nav`(底部导航条)。
**卡片 `.glass-card` 一次都没有**(整个规则块只有 radius/border/bg/transition)。
一屏十几张卡各投一次 ⇒ 叠加成灰雾。设备实测(x=600,卡片底边 y≈1847):
有投影 y=1846→231、y=1852→230(向下衰减的暗带)
去掉后 y=1846→255、y=1852→251(消失)
⇒ 投影移到 `PaneModifier`(窗格层),与 WebUI 一致。
★ 判定实验还排除了一个嫌疑:`List` 默认 `edgeEffect(EdgeEffect.Spring)`
(官方文档「支持弹簧效果和**阴影效果**」)。设 `EdgeEffect.None` 后暗带照样在
⇒ 不是它。**弹簧是滚到边缘的正常反馈,不该为遮一个 bug 把它关掉。**
## ② 页签条右上角"那个圆角"(用户原话)
两道弧叠在一起。WebUI 实测(CDP 读祖先链):
.comm-pane x=80 w=320 radius=14px overflow=hidden ← 窗格
tabstrip x=80 w=320 radius=0px ← 页签条
**两者横向完全齐平**,页签条自己直角、靠窗格裁圆 ⇒ 只有一道弧。
我们内缩 16vp + 自己带角 ⇒ 两道弧,中间一弯月牙。
⇒ 改成与窗格齐平、只留左上角圆角。
★ 期间我犯过两个错,都记在注释里:先把半径设成 `HEADER_HEIGHT/2`=22(条高也是
44,那就是一个完整胶囊),后来自作主张把整条改成"通栏+下边框+无玻璃"——
那是重做而不是用户要的小改,已还原。
## ③ 日视图与周视图一样(用户原话)
`DayTimeline()` 我写好了却**从未调用**。日视图一直走 `rows()`(周视图那套七列)。
⇒ 按 scale 分流:日视图 → 24 小时时间轴(对齐 WebUI `DayGrid` 的 `HOURS` 逐行、
当前小时淡蓝底);周/日格子改用 `WEEK_CELL_HEIGHT`(240) 并画日程标题条
(对齐 `EventChip`:`HH:mm` + 标题,停用的加删除线 + 灰底)。
## ④ 邮件正文垂直居中(用户原话)
官方 FAQ `faqs-arkui-725` 原文:内容比 `Scroll` 矮时「**默认居中排布**」,
要 `.align(Alignment.Top)` 才是顶部排布。
实测内容块在 `Scroll` 里偏移 731px = (1944−481)/2 —— 正好居中。
⇒ 4 处 Scroll/List 补 `.align(Alignment.Top)`(详情页 y 从 1018 → 287)。
## ⑤ 窄屏圆角/留白全丢(用户:「你的邮件的圆角呢?日历的圆角呢?」)
`borderRadius(this.isWide ? glassRadius : 0)` —— 窄屏恒 0;
`left/right: isWide ? paneGap : 0` —— 把 WebUI「窄屏**底边**留白为 0」
错推广成「四边都为 0」。
WebUI 两条 shell 规则**都给圆角**,差的只是底边留白。
⇒ 圆角两端都开;左右留白两端都给;只有底边分宽窄。
## 连带修的两个真问题
· **底部导航避让**(用户:「为什么不避让底部导航栏」):根是 `NavBar` 与内容
是 `Stack` 里的**并列兄弟**(互相重叠),所以每个滚动容器都得手写
`contentEndOffset` —— 我漏了 8 处。照 WebUI 改成 `Column{内容, NavBar}`
(WebUI 实测 `scroller.bottom=861` / `nav.top=871`,内容停在导航上方)。
· **不透明的那一张卡**(用户:「有一个完全不透明的邮箱项」):`MailRow` 手写
`Theme.surface`(系统卡片色 α=1),而 `GroupHeader` 用的是 `GlassCardModifier`。
⇒ 改用同一件基础件 + 新增 `TintModifier` 合成状态色(`.attributeModifier`
是单一插槽,色必须参与合成)。实测 `(255,255,255)` → `(195,218,204)`。
判据随之反转 3 条(`harmony-widescreen` 两处把"窄屏 0"改成"两端都要"、
`cross-client-theme` 抓到我新写的 `Theme.border` 当 `fontColor`)。
This commit is contained in:
@ -9,7 +9,8 @@
|
||||
import { hilog } from '@kit.PerformanceAnalysisKit';
|
||||
import { ApiClient, ApiError } from '../api/ApiClient';
|
||||
import { Theme } from '../common/Theme';
|
||||
import { AppHeader, CompositeModifier, GlassCardModifier, PaneModifier, PressFeedbackModifier } from '../common/Surface';
|
||||
import { AppHeader, CompositeModifier, GlassCardModifier, PaneModifier, PressEffectModifier, TintModifier } from '../common/Surface';
|
||||
import { Motion } from '../common/Motion';
|
||||
import { AmIcon } from '../common/Icons';
|
||||
import { WideSidebar } from './WideSidebar';
|
||||
import { MailDetailView } from './MailDetailPage';
|
||||
@ -96,8 +97,7 @@ import {
|
||||
navBadgeText,
|
||||
navBadgeTone,
|
||||
NavItem,
|
||||
normalizeNavIndex
|
||||
} from '../model/NavItems';
|
||||
normalizeNavIndex, TAB_BAR_HEIGHT } from '../model/NavItems';
|
||||
|
||||
/** 一页取多少封。取满了就要如实提示"可能还有更多"(服务端 total 是未读数,不是总封数)。 */
|
||||
const MAIL_DETAIL_ROUTE: string = 'mail-detail';
|
||||
@ -160,6 +160,25 @@ function DetailPlaceholder() {
|
||||
}
|
||||
.width('100%').height('100%')
|
||||
.justifyContent(FlexAlign.Center)
|
||||
/*
|
||||
* ★★ 2026-09-21 补:**宽屏右栏默认页也要透明**(用户:「邮件看着没有玻璃效果」)。
|
||||
*
|
||||
* `splitPlaceholder` 那块内容由 `Navigation` 直接渲染
|
||||
* (布局树里的 `SplitPlaceholderContentNode`,实测 x=1152..3154)。
|
||||
* 不设背景时它用**系统默认底**(近白不透明),于是整片右栏把壁纸挡死 ——
|
||||
* 实测宽屏 3184px 下:右栏中心 `(242,244,244)`,而屏幕边缘的壁纸是
|
||||
* `(192,194,192)`。差这么远,说明中间隔着的不透明层就是它。
|
||||
*
|
||||
* 这与 2026-09-19 修的 `CommPage` 那个 `Navigation` 是**同一个形状**
|
||||
* (见那段注释里"那条缝是决定性的证据")—— 系统容器不设背景就不会透。
|
||||
*
|
||||
* ★ 全局 `@Builder` 里**拿不到 `this`**(`splitPlaceholder` 只接受全局 Builder),
|
||||
* 所以不能用 `bgActive ? Transparent : surface`。
|
||||
* 这里改成**恒透明**:右栏默认页本来就没有"实体面"的语义
|
||||
* (它是一块空态提示),壁纸关着时底下是 `WallpaperLayer` 之外的
|
||||
* `pageBg`,观感仍然是浅色底 —— 不会因为透明变成异色。
|
||||
*/
|
||||
.backgroundColor(Color.Transparent)
|
||||
}
|
||||
|
||||
@Component
|
||||
@ -521,9 +540,11 @@ struct InboxTab {
|
||||
.fontSize(11).fontColor(Theme.textMuted)
|
||||
AmIcon({ iconName: 'chevronRight', iconSize: 12, iconColor: Theme.textSubtleFor() })
|
||||
.rotate({ angle: 90 })
|
||||
.animation({ duration: Motion.dur(Theme.durFast), curve: Theme.easeOutSoft })
|
||||
.margin({ left: 3 })
|
||||
}
|
||||
.height(32)
|
||||
.attributeModifier(PressEffectModifier.of())
|
||||
.onClick(() => { this.showAccountPicker = !this.showAccountPicker; })
|
||||
}
|
||||
if (this.unread > 0) {
|
||||
@ -573,6 +594,7 @@ struct InboxTab {
|
||||
}
|
||||
.width('100%').height(48).padding({ left: 16 })
|
||||
.backgroundColor(this.accountFilter === acct.id ? Theme.accentSoftFor(this.isDarkNow) : Theme.surface)
|
||||
.attributeModifier(PressEffectModifier.of())
|
||||
.onClick(() => {
|
||||
this.accountFilter = acct.id;
|
||||
this.accountName = acct.displayName;
|
||||
@ -626,15 +648,50 @@ struct InboxTab {
|
||||
// 单封不成组:套一个可折叠的组头只是多一次点击(与 WebUI isFlatGroup 同结论)
|
||||
this.MailRow(g.mails[0])
|
||||
} else {
|
||||
Column() {
|
||||
/*
|
||||
* ── 会话组容器 ──
|
||||
*
|
||||
* ★★ 2026-09-21 修「多个邮件为什么没有堆叠效果」(用户)。
|
||||
*
|
||||
* 原来组头与子邮件是**平铺的兄弟**、各自一张完整的卡、间距一样 ——
|
||||
* 于是一组三封看起来就是"三张互不相干的卡",看不出
|
||||
* "这是同一条会话里的三封"。
|
||||
*
|
||||
* WebUI 的形状(`MailList.tsx:173-232`):
|
||||
* <div className="rounded-lg border"> ← **组容器**(一层外框)
|
||||
* <button className="glass-card …"> ← 组头
|
||||
* <div className="pl-5 pr-1 pb-1.5 space-y-0.5"> ← **缩进** + 紧凑间距
|
||||
* {g.mails.map(m => <MailItem compact />)}
|
||||
*
|
||||
* 三个要点逐条对齐:
|
||||
* ① **缩进** `pl-5`(20px):子邮件明确"在组头之下";
|
||||
* ② **紧凑间距** `space-y-0.5`(2px):一组之内挤在一起,
|
||||
* 而**组与组之间**是列表的 6vp —— 疏密本身就是分组信息;
|
||||
* ③ 组内含选中邮件时给外框上色(`hasActive`),
|
||||
* 否则展开一个组再滚下去会找不到自己在看哪封。
|
||||
*/
|
||||
Column({ space: 2 }) {
|
||||
this.GroupHeader(g)
|
||||
if (this.isExpanded(g.key)) {
|
||||
ForEach(g.mails, (m: MailLike) => {
|
||||
this.MailRow(m)
|
||||
}, (m: MailLike) => m.source_account_id + ':' + m.mail_id)
|
||||
/* 子邮件整体缩进 20vp(WebUI `pl-5`)—— 缩进是分组的视觉主语 */
|
||||
Column({ space: 2 }) {
|
||||
ForEach(g.mails, (m: MailLike) => {
|
||||
this.MailRow(m)
|
||||
}, (m: MailLike) => m.source_account_id + ':' + m.mail_id)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: 20, right: 1, bottom: 4 })
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.borderRadius(Theme.radiusCard)
|
||||
/* 组内含正在读的那封:给外框上色(WebUI 的 `hasActive`) */
|
||||
.border({
|
||||
width: 1,
|
||||
color: this.groupHasActive(g) ? Theme.accentEdgeFor(this.isDarkNow) : Color.Transparent
|
||||
})
|
||||
.backgroundColor(this.groupHasActive(g)
|
||||
? Theme.accentSoftFor(this.isDarkNow) : Color.Transparent)
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
@ -663,6 +720,26 @@ struct InboxTab {
|
||||
* 参数类型是 `MailLike`(`MailGrouping.ts` 的接口)而不是 `MailSummary`:
|
||||
* 折叠后的组里装的是接口类型,ArkTS 不做结构类型匹配,写死具体类就传不进来。
|
||||
*/
|
||||
/**
|
||||
* 这一组里有没有**正在读**的那封。
|
||||
*
|
||||
* 对齐 WebUI `MailList.tsx:171`:
|
||||
* const hasActive = currentMailID ? g.mails.some(m => m.mail_id === currentMailID) : false;
|
||||
* 它的用途写在那边注释里:「组内含选中邮件时给个边框,否则展开一个组再滚下去
|
||||
* 会找不到自己在看哪封」—— 组头离得远,光靠子项自己的高亮不够。
|
||||
*/
|
||||
private groupHasActive(g: SessionGroup): boolean {
|
||||
if (this.currentMailId.length === 0) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < g.mails.length; i++) {
|
||||
if (g.mails[i].mail_id === this.currentMailId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Builder
|
||||
MailRow(mail: MailLike) {
|
||||
Row() {
|
||||
@ -684,8 +761,40 @@ struct InboxTab {
|
||||
* 若只靠底色区分,选中一封未读邮件时**看不出任何变化**。
|
||||
* 所以选中额外加那圈 `accentEdge` 边 —— 这正是 WebUI 两个 token 并存的原因。
|
||||
*/
|
||||
.backgroundColor(this.currentMailId === mail.mail_id || mail.status === 'unread'
|
||||
? Theme.accentSoftFor(this.isDarkNow) : Theme.surface)
|
||||
/*
|
||||
* ★★ 2026-09-21 修「有一个完全不透明的邮件项」(用户原话)。
|
||||
*
|
||||
* 原来这里写的是:
|
||||
* `.backgroundColor(... ? accentSoft : Theme.surface)`
|
||||
* 而 `Theme.surface` = `$r('sys.color.ohos_id_color_list_card_bg')`
|
||||
* —— **系统卡片色,α = 1**。于是壁纸开着时,**已读**的邮件行是一块纯白实心板
|
||||
* (实测像素 `(255,255,255)`,壁纸完全透不出来),
|
||||
* 而未读/选中那些(`accentSoft`)有半透明感 ——
|
||||
* 一排里就"单出一张不透明的",正是用户看到的那样。
|
||||
*
|
||||
* ── 为什么 `GroupHeader` 没这个毛病 ──
|
||||
* 它挂的是 `GlassCardModifier.of(this.bgActive)`(玻璃卡基础件),
|
||||
* 走 `backgroundEffect({radius, saturation})`,**不写实心色**。
|
||||
* `MailRow` 当时漏了这一步 —— 我手写了颜色,没用那件基础件。
|
||||
*
|
||||
* ── 修法 ──
|
||||
* 与 `GroupHeader` 同一件 `GlassCardModifier`(对齐 WebUI:两者的行都是
|
||||
* `.glass-card`,`MailList.tsx:280` 与 `:185`)。
|
||||
*
|
||||
* ★ 但「未读/选中」的淡蓝底**不能丢** —— WebUI 是
|
||||
* `glass-card` + `bg-blue-50` 两层叠着(CSS 里后者只改 background-color,
|
||||
* 玻璃的 border/阴影仍在)。
|
||||
* ArkUI 的 `.attributeModifier` 是**单一插槽**(`.backgroundColor` 链在 modifier
|
||||
* 之外时会被 modifier 覆盖),所以这里用 `CompositeModifier` 显式合成:
|
||||
* ① 有态色(未读/选中)时铺 `accentSoft` —— 它本身就是带 alpha 的令牌;
|
||||
* ② 无态色时走玻璃卡(透明 + backgroundEffect)。
|
||||
*/
|
||||
.attributeModifier(this.currentMailId === mail.mail_id || mail.status === 'unread'
|
||||
? CompositeModifier.of([
|
||||
GlassCardModifier.of(this.bgActive, false),
|
||||
TintModifier.of(Theme.accentSoftFor(this.isDarkNow))
|
||||
])
|
||||
: GlassCardModifier.of(this.bgActive))
|
||||
.borderRadius(Theme.radiusCard)
|
||||
.border({
|
||||
width: 1,
|
||||
@ -715,6 +824,23 @@ struct InboxTab {
|
||||
Stack({ alignContent: Alignment.Center }) {
|
||||
AmIcon({ iconName: 'chevronRight', iconSize: 12, iconColor: Theme.textSubtleFor() })
|
||||
.rotate({ angle: this.isExpanded(g.key) ? 90 : 0 })
|
||||
/*
|
||||
* ★★ 2026-09-21 补:**属性级动画**(用户:「其他界面涉及运动的页面
|
||||
* webui 也是有类似效果的」)。
|
||||
*
|
||||
* 原来这里只写了 `.rotate(...)` —— 角度是**瞬变**的:
|
||||
* 展开/收起时箭头"啪"地跳 90°,感觉是硬切。
|
||||
* WebUI 那边是 `transition-transform`(`MailList.tsx:186` 的
|
||||
* `className="… transition-transform ${open ? 'rotate-90' : ''}"`),
|
||||
* 角度变化会**平滑补间**。
|
||||
*
|
||||
* ArkUI 对应能力就是属性级 `.animation()`:它盯住**绑在它之上**的可动画
|
||||
* 属性,值一变就自动补间(文档 `arkts-attribute-animation-apis.md`)。
|
||||
* 用它而不是 `animateTo`:这里角度由 `isExpanded()` 驱动,
|
||||
* 触发点在 `toggleExpanded()` 里、离得很远,包 `animateTo` 要改三处;
|
||||
* 属性级动画不需要调用方配合。
|
||||
*/
|
||||
.animation({ duration: Motion.dur(Theme.durFast), curve: Theme.easeOutSoft })
|
||||
}
|
||||
.width(20)
|
||||
Column() {
|
||||
@ -766,6 +892,7 @@ struct InboxTab {
|
||||
.borderRadius(Theme.radiusCard)
|
||||
.border({ width: 1, color: Theme.border })
|
||||
.clip(true)
|
||||
.attributeModifier(PressEffectModifier.of())
|
||||
.onClick(() => {
|
||||
this.toggleExpanded(g.key);
|
||||
})
|
||||
@ -1068,6 +1195,7 @@ struct SentTab {
|
||||
.borderRadius(Theme.radiusCard)
|
||||
.margin({ bottom: 6 })
|
||||
.clip(true)
|
||||
.attributeModifier(PressEffectModifier.of())
|
||||
.onClick(() => { this.toggleExpanded(g.key); })
|
||||
}
|
||||
|
||||
@ -1146,6 +1274,7 @@ struct SentTab {
|
||||
color: this.currentMailId === m.mail_id ? Theme.accentEdgeFor(this.isDarkNow) : Theme.border
|
||||
})
|
||||
.margin({ bottom: 6 })
|
||||
.attributeModifier(PressEffectModifier.of())
|
||||
.onClick(() => { this.openMail(m); })
|
||||
}
|
||||
.width('100%')
|
||||
@ -1638,9 +1767,15 @@ struct CommPage {
|
||||
}
|
||||
.justifyContent(FlexAlign.Center)
|
||||
.layoutWeight(1)
|
||||
.height(44)
|
||||
/*
|
||||
* 用 `TAB_BAR_HEIGHT` 而不是字面量 44 —— 它同时决定 `TAB_BAR_RADIUS`
|
||||
* (半径 = 高/2)。写成两个字面量的话,哪天改高忘了改半径,
|
||||
* 半径又会超过一半、圆角重新变成怪形(正是 2026-09-21 修的那个 bug)。
|
||||
*/
|
||||
.height(TAB_BAR_HEIGHT)
|
||||
// 选中态用**下划线**(与 WebUI 的 `border-b-2` 同一观感),不用浮动白胶囊
|
||||
.border({ width: { bottom: this.commTab === key ? 2 : 0 }, color: Theme.accent })
|
||||
.attributeModifier(PressEffectModifier.of())
|
||||
.onClick(() => {
|
||||
this.commTab = normalizeCommTab(key);
|
||||
this.navPathStack.clear();
|
||||
@ -1648,16 +1783,6 @@ struct CommPage {
|
||||
})
|
||||
}, (key: string) => key)
|
||||
}
|
||||
/*
|
||||
* ★ 宽度用 `calc(100% - 2×TAB_BAR_SIDE)`,**不是 `width('100%')` + `margin`**。
|
||||
*
|
||||
* ArkUI 里 `width('100%')` 是按父容器算的,再叠 `margin` 会**向右溢出**
|
||||
* 而不是把条挤窄 —— 实测(模拟器 3184px、密度 2.875):
|
||||
* 页签条实际占 x=231..1151,与面板同宽;TAB_BAR_SIDE(16vp=46px) 完全没生效
|
||||
* ⇒ 看起来仍是"通栏",只是多了圆角,与底部悬浮条(左右各留 16vp)不齐。
|
||||
* 把留白算进宽度里,左右才真的各让出 16vp。
|
||||
*/
|
||||
.width(`calc(100% - ${TAB_BAR_SIDE * 2}vp)`)
|
||||
/*
|
||||
* ★★ 2026-09-20(用户:「顶部的收件箱发件箱和授权为什么不做成悬浮玻璃?」)
|
||||
*
|
||||
@ -1669,11 +1794,27 @@ struct CommPage {
|
||||
* 三种做法各自的取舍见 `model/NavItems.ts` 的 `TAB_BAR_*` 常量注释
|
||||
* (为什么选悬浮而不是 WebUI 的"通栏无底色")。
|
||||
*/
|
||||
/*
|
||||
* 宽度:**与窗格齐平**(`100%`)。
|
||||
*
|
||||
* 原来这里是 `calc(100% - 2×TAB_BAR_SIDE)`(左右各内缩 16vp)。
|
||||
* 实测 WebUI:`.comm-pane` 与 `tabstrip` 的 `x`/`w` **完全相同**(都是 80/320)
|
||||
* —— 它靠**窗格的 `overflow:hidden`** 把顶角裁圆,所以页签条自己直角、通栏。
|
||||
* 我们内缩 + 自己带角 ⇒ 右端出现两道弧(用户:「你又在内部套了一个胶囊」)。
|
||||
* ⇒ 改成与窗格齐平,右端只剩窗格那一道边。
|
||||
*/
|
||||
.width('100%')
|
||||
.backgroundColor(Color.Transparent)
|
||||
.backgroundBlurStyle(this.bgActive ? Theme.navMaterial : BlurStyle.NONE)
|
||||
.borderRadius(TAB_BAR_RADIUS)
|
||||
.clip(true)
|
||||
.margin({ left: TAB_BAR_SIDE, right: TAB_BAR_SIDE, top: TAB_BAR_TOP })
|
||||
/*
|
||||
* ★ 只给**左上角**圆角,右上角不要(用户:「鸿蒙布局还略有不同的,
|
||||
* 你直接改成右边没圆角就行了」)。
|
||||
*
|
||||
* 右上角的弧与窗格右上角的弧**重叠成两道**(页签条一道、窗格一道),
|
||||
* 中间那弯月牙形的空玻璃就是"奇怪"的来源。
|
||||
* 去掉右边那一角,右端就只剩窗格自己那一道边。
|
||||
*/
|
||||
.borderRadius({ topLeft: Theme.glassRadius, topRight: 0 })
|
||||
}
|
||||
|
||||
openMail(mailId: string, accountId: string): void {
|
||||
@ -1841,7 +1982,27 @@ struct CommPage {
|
||||
.navBarWidth(320)
|
||||
.navBarWidthRange([280, 360])
|
||||
.minContentWidth(360)
|
||||
.hideTitleBar(true)
|
||||
.hideTitleBar(true)
|
||||
/*
|
||||
* ★★ 2026-09-21 补:**联系人页的 `Navigation` 缺背景声明**
|
||||
* (用户:「邮件看着没有玻璃效果」→ 实测发现卡内像素 (253,253,253),
|
||||
* 与"白 0.78 叠在壁纸上应得 (240,242,244)"差得太远 ⇒ 下面另有不透明层)。
|
||||
*
|
||||
* `Navigation` 不设背景时用**系统默认底**(近白不透明)。
|
||||
* 实测纵深采样(窄屏 1008px,y=500):
|
||||
* x=2 (191,199,209) ← 壁纸
|
||||
* x=20 (184,192,204) ← 壁纸
|
||||
* x=32 (243,244,248) ← **突然变近白** ⇒ 这一层就是 Navigation 的壳
|
||||
* x=100 (251,253,253) ← 卡片(白纱叠在这个近白壳上,当然看不见壁纸)
|
||||
*
|
||||
* ⇒ 整条链路(壁纸 → pane → 卡片)里,pane 与卡片都已经是半透明了,
|
||||
* **只剩这层壳把壁纸挡在外面** ⇒ 玻璃感整体失效。
|
||||
*
|
||||
* ★ `CommPage` 那个 `Navigation` 在 2026-09-19 已经补过同一处
|
||||
* (见那段注释里记的"那条缝是决定性的证据"),**联系人页漏了** ——
|
||||
* 同一个形状在两个 struct 里各写一遍,就是会漏一个。
|
||||
*/
|
||||
.attributeModifier(PaneModifier.of(this.bgActive))
|
||||
.width('100%').height('100%')
|
||||
/* 右栏占位:栈空时显示引导(否则宽屏两栏并排时右栏是一大片空白)。
|
||||
用系统入口 `splitPlaceholder`,**不是** NavDestination 的 else —— 后者栈空时不挂载。 */
|
||||
@ -2083,6 +2244,7 @@ struct ContactsTab {
|
||||
})
|
||||
}
|
||||
.width(40).height(40)
|
||||
.attributeModifier(PressEffectModifier.of())
|
||||
.onClick(() => { this.switchView(); })
|
||||
}
|
||||
.width('100%').height(56).padding({ left: 16, right: 8 })
|
||||
@ -2149,6 +2311,7 @@ struct ContactsTab {
|
||||
}
|
||||
.width('100%')
|
||||
/* 点卡片 = 打开这条会话(WebUI `ContactPanel` 的 onOpen) */
|
||||
.attributeModifier(PressEffectModifier.of())
|
||||
.onClick(() => {
|
||||
// 确认态下卡片的点击不打开会话:那块区域已经是"确认框"了
|
||||
if (this.pendingArchiveId !== c.session_id) {
|
||||
@ -2201,6 +2364,7 @@ struct ContactsTab {
|
||||
.borderRadius(Theme.radiusCard)
|
||||
.clip(true)
|
||||
/* 点卡片 = 打开这条会话(WebUI `ContactPanel` 的 onOpen) */
|
||||
.attributeModifier(PressEffectModifier.of())
|
||||
.onClick(() => {
|
||||
// 确认态下卡片的点击不打开会话:那块区域已经是"确认框"了
|
||||
if (this.pendingArchiveId !== c.session_id) {
|
||||
@ -2265,6 +2429,7 @@ struct ContactsTab {
|
||||
AmIcon({ iconName: 'chevronLeft', iconSize: 20, iconColor: Theme.accentFor() })
|
||||
}
|
||||
.width(HEADER_BACK_HIT).height(HEADER_BACK_HIT)
|
||||
.attributeModifier(PressEffectModifier.of())
|
||||
.onClick(() => { this.closeSession(); })
|
||||
Text(this.openSessionTitle)
|
||||
.fontSize(14).fontWeight(FontWeight.Bold).fontColor(Theme.textPrimary)
|
||||
@ -2316,6 +2481,7 @@ struct ContactsTab {
|
||||
.border({ width: 1, color: Theme.border })
|
||||
}
|
||||
.width('100%')
|
||||
.attributeModifier(PressEffectModifier.of())
|
||||
.onClick(() => { this.openMail(m.mail_id, m.source_account_id); })
|
||||
}, (m: MailSummary) => m.mail_id)
|
||||
}
|
||||
@ -2470,6 +2636,7 @@ struct ContactsTab {
|
||||
.borderRadius(Theme.radiusControl)
|
||||
.border({ width: 1, color: Theme.border })
|
||||
.backgroundColor(Theme.surface)
|
||||
.attributeModifier(PressEffectModifier.of())
|
||||
.onClick(onTap)
|
||||
}
|
||||
|
||||
@ -2500,6 +2667,7 @@ struct ContactsTab {
|
||||
.borderRadius(Theme.radiusControl)
|
||||
.backgroundColor(Theme.danger)
|
||||
.opacity(this.archiving ? 0.5 : 1)
|
||||
.attributeModifier(PressEffectModifier.of())
|
||||
.onClick(() => { this.confirmArchive(c); })
|
||||
|
||||
Row() {
|
||||
@ -2511,6 +2679,7 @@ struct ContactsTab {
|
||||
.border({ width: 1, color: Theme.border })
|
||||
.backgroundColor(Theme.surface)
|
||||
.margin({ left: 8 })
|
||||
.attributeModifier(PressEffectModifier.of())
|
||||
.onClick(() => { this.cancelArchive(); })
|
||||
}
|
||||
.width('100%').margin({ top: 10 })
|
||||
@ -3194,6 +3363,8 @@ struct MainPage {
|
||||
* 时长用令牌(120ms, `--dur-fast`),不另拍一个数。
|
||||
*/
|
||||
.animation({ duration: Theme.durFast, curve: Theme.easeOutSoft })
|
||||
/* 按压反馈:导航项是最高频的可点元素,按下要有明确的"吃到了" */
|
||||
.attributeModifier(PressEffectModifier.of())
|
||||
.onClick(() => {
|
||||
/*
|
||||
* ★ 四项**都是内容窗格**(用户 2026-09-17:「我的页面完全没有遵守 nav 的导航规则」)。
|
||||
@ -3355,9 +3526,45 @@ struct MainPage {
|
||||
* (status 与 from_agent 参考实现也不显示,见 `WorkCard.tsx` 的字段集,
|
||||
* 判据里有一条"卡片字段两边一致"钉住这件事,避免以后以为漏了。)
|
||||
*/
|
||||
Stack({ alignContent: Alignment.Bottom }) {
|
||||
// 背景在最底下:内容面(系统色)盖在它上面
|
||||
/*
|
||||
* ★★ 2026-09-21 **结构性改**:根从「重叠的 Stack」改成
|
||||
* 「壁纸 + `Column{内容, 导航条}`」
|
||||
* (用户:「你看看 webui 是怎么安排的,它的避让方案是让内容框停在导航栏上方」
|
||||
* 「包括邮箱正文等等,都是这个避让方案」)。
|
||||
*
|
||||
* ── WebUI 的做法(用 CDP 实测它的真实几何,430×932)──
|
||||
*
|
||||
* .narrow-shell { display: flex; flex-direction: column }
|
||||
* <children/> ← 内容(flex 子项,**占位**)
|
||||
* <NarrowNav/> ← `position: static`、`flexShrink: 0`,**真的占 51px**
|
||||
*
|
||||
* 实测:`scroller.bottom = 861`、`nav.top = 871`
|
||||
* ⇒ **内容盒就停在导航栏上方**,两者谁也不用"让"谁。
|
||||
*
|
||||
* ── 鸿蒙原来错在哪 ──
|
||||
*
|
||||
* 根是 `Stack({ alignContent: Alignment.Bottom })`:`NavBar()` 与内容 `Row`
|
||||
* 是**并列兄弟**、都被摆到底部 ⇒ **互相重叠**(导航条浮在内容之上)。
|
||||
* 于是"避让"变成每个滚动容器各自手写 `contentEndOffset(navReserve)` 的手工活。
|
||||
*
|
||||
* 代价已经付过:我**漏了 8 处** —— 邮件详情正文、日视图列表、对话树、
|
||||
* 用户管理页……每处的最后一行都被底栏压住
|
||||
* (实测 `Scroll` 延伸到 y=2231,而底栏占 1957..2232)。
|
||||
*
|
||||
* ★ 这是**同一类错误的第二次**:把该由**布局**承担的事,交给 N 个调用点逐个手写。
|
||||
* 第一次是按压反馈(112 处 `onClick` 只挂上 2 处)。
|
||||
* 判据:**凡"每加一个页面就得记得做一次"的事,迟早会漏一半。**
|
||||
*
|
||||
* ── 改法 ──
|
||||
*
|
||||
* 照 WebUI:竖排 `Column`;内容 `layoutWeight(1)`,`NavBar` 自己占位。
|
||||
* 壁纸层仍绝对铺满(留在 `Stack` 里,不受这根 Column 影响)。
|
||||
* 各处的 `contentEndOffset` **保留**:宽屏没有底栏时它本就是 0,
|
||||
* 而它另外表达"内容末尾别贴死"(例如详情页要躲开回复球)。
|
||||
*/
|
||||
Stack() {
|
||||
this.WallpaperLayer()
|
||||
Column() {
|
||||
/*
|
||||
* 内容按 `currentIndex` 挂载(原来这里是系统 `Tabs` 的 TabContent)。
|
||||
* 底部**必须让出** NAV_CONTENT_RESERVE 的高度:条是浮在内容之上的,
|
||||
@ -3540,7 +3747,22 @@ struct MainPage {
|
||||
* `background-clip: padding-box` 处理,代价是溢出内容在圆角处可能露一点,
|
||||
* 比"完全不能滚动"好得多(原文)。这里照做:**去掉 clip,保留圆角**。
|
||||
*/
|
||||
.borderRadius(this.isWide ? Theme.glassRadius : 0)
|
||||
/*
|
||||
* ★★ 2026-09-21 修「窄屏圆角全没了」(用户:「你的邮件的圆角呢?日历的圆角呢?」)。
|
||||
*
|
||||
* 原来写的是 `this.isWide ? Theme.glassRadius : 0` —— **窄屏恒 0**,
|
||||
* 于是窄屏上「通信 / 日历 / 联系人 / 我的」四个窗格全是**直角**,
|
||||
* 与底部那条圆角玻璃导航条根本不是一套语汇。
|
||||
*
|
||||
* WebUI 那边两条分支**都给圆角**(`index.css`):
|
||||
* .app-shell > * { border-radius: var(--radius-card); } ← 宽屏
|
||||
* .narrow-shell > * { border-radius: var(--radius-card); } ← 窄屏
|
||||
* 差别只在**留白**:宽屏四边都留 `--pane-gap`,窄屏只留左右上
|
||||
* (下面那条 padding 里 `bottom: 0` 就是它),因为底栏自己带 margin。
|
||||
*
|
||||
* 我当初把"窄屏不留白"顺手写成了"窄屏不圆角",两件事被并成了一个三元。
|
||||
*/
|
||||
.borderRadius(Theme.glassRadius)
|
||||
/*
|
||||
* ★ 这里**不再**让位(原来写的是 `padding({ bottom: NAV_CONTENT_RESERVE })`)。
|
||||
*
|
||||
@ -3578,8 +3800,18 @@ struct MainPage {
|
||||
* 否则两层内容同时半透明地叠在屏幕上,看起来像"闪一下"。
|
||||
*/
|
||||
.padding({
|
||||
left: this.isWide ? Theme.paneGap : 0,
|
||||
right: this.isWide ? Theme.paneGap : 0,
|
||||
/*
|
||||
* ★★ 2026-09-21:窄屏**也要**左右留白(原来是 `isWide ? paneGap : 0`)。
|
||||
*
|
||||
* WebUI `.narrow-shell` 的 padding 是 `var(--pane-gap) var(--pane-gap) 0`
|
||||
* ——左右上和宽屏一样留 10px,只有底边为 0(底栏自己带 margin)。
|
||||
* 我们用 `isWide ? paneGap : 0` 把"底边为 0"错推广成了"四边都为 0",
|
||||
* 于是窄屏面板**贴死屏幕两侧**:既没有圆角的余地,也没有投影的余地,
|
||||
* 看起来是一块被屏幕裁掉的白板,而不是一张浮起来的卡。
|
||||
* (这正是用户说"邮箱页面丑死了、跟 WebUI 没法比"的一块。)
|
||||
*/
|
||||
left: Theme.paneGap,
|
||||
right: Theme.paneGap,
|
||||
/*
|
||||
* ★ 避让区就在这里:状态栏高度当 padding-top。
|
||||
*
|
||||
@ -3611,14 +3843,39 @@ struct MainPage {
|
||||
* "宽屏那一支"里。宽屏只是**额外多留** paneGap(面板投影的余量),
|
||||
* 不是**代替**避让。
|
||||
*/
|
||||
top: this.windowInsets.statusBar + (this.isWide ? Theme.paneGap : 0),
|
||||
/*
|
||||
* 避让区 + 面板留白。避让是**窗口级事实**(全屏后内容从 y=0 起画),
|
||||
* 与宽窄无关;留白则两端都有(WebUI 两条 shell 规则的 padding-top 都是 gap)。
|
||||
*/
|
||||
top: this.windowInsets.statusBar + Theme.paneGap,
|
||||
/*
|
||||
* 底边 = 0:窄屏的底栏是**悬浮**的、自己带 `NAV_BAR_BOTTOM` 的 margin,
|
||||
* 内容要能从它底下穿过(玻璃才有东西可糊,见 `navReserve` 那段)。
|
||||
* 宽屏没有底栏,才由这里补上留白。
|
||||
*/
|
||||
bottom: this.isWide ? Theme.paneGap : 0
|
||||
/*
|
||||
* 内容吃 **Column 里 NavBar 之外的剩余高度**。
|
||||
* 原来这里是 `height('100%')`(占满 Stack)—— 加了 NavBar 占位后
|
||||
* 那句会把导航条挤出可视区(实测:底栏整个消失)。
|
||||
*/
|
||||
})
|
||||
.layoutWeight(1)
|
||||
|
||||
// 浮动玻璃条浮在最上层(自绘,见 NavBar 的说明)。宽屏没有底部导航。
|
||||
/*
|
||||
* 底部导航条 —— **Column 的第二个子项**(不是覆盖层)。
|
||||
*
|
||||
* 它**占位**,所以内容盒自然停在它上方,不必再逐处 `contentEndOffset`。
|
||||
* `NavBar` 内部仍保留自己的圆角/外边距/玻璃材质 ——
|
||||
* 那是"悬浮**观感**",由圆角+阴影表达,不再靠 `position` 浮起来。
|
||||
* 宽屏没有底栏,这支只有一个子项,行为与原来一致。
|
||||
*/
|
||||
if (!this.isWide) {
|
||||
this.NavBar()
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.layoutWeight(1)
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
|
||||
Reference in New Issue
Block a user