观影/联系人详情卡补内置蒙版 (#78)

用户:电影这个框弹出来的时候,应该在下面盖一层蒙版吧?跟近期任务类似。

首页各浮层里只有这两张卡没有遮罩:FbTaskPanel / FbDayEditSheet 都是组件内部
根 Stack 自带遮罩+卡片,而 FbMovieDetailSheet / FbPersonSheet 两处漏了,
卡片直接浮在正文上,底下的片名、影院、联系人名字透出来。

改法(按项目既有约定:谁铺满全屏,谁负责画遮罩):
- FbMovieDetailSheet.build 根节点 Column -> Stack,前面插全屏 FB_MASK 遮罩,
  onClick 关闭;卡片容器保持铺满+居中且 HitTestMode.Transparent;
- FbPersonSheet.build 同样套一层 Stack + 遮罩(embedded 分支跳过,
  那种形态宿主是 bindSheet,系统已有遮罩,再叠就是双暗);
- 宿主 FbHomePage 不再包遮罩,只留注释说明。

原因:组件根节点自己铺满全屏,会在外面先吃掉点击 —— 宿主包遮罩会
蒙版看着有、点它关不掉;包两层还会越点越黑(本轮真机实测踩到)。

验证(真机 5NC0225B27004718):
- 弹卡后卡片外像素 (247,243,234) -> (147,146,141),反推 alpha=0.40 = 0x66/255;
- 卡片内海报 (230,212,188) 不受影响;
- 点遮罩后恢复 (247,243,234),卡片关闭;
- 联系人卡同样三条全过;hvigorw assembleHap BUILD SUCCESSFUL。
This commit is contained in:
2026-11-06 07:35:47 +08:00
parent 4e3038a0aa
commit a7947f31be
3 changed files with 119 additions and 63 deletions
@@ -15,7 +15,7 @@
* 加载中 / 成功 / 失败。别只画成功态 —— 断网时用户会看到"空白浮层"。 * 加载中 / 成功 / 失败。别只画成功态 —— 断网时用户会看到"空白浮层"。
*/ */
import { FbColors, FbMetric, fbAlpha } from '../common/Theme'; import { FbColors, FbMetric, fbAlpha, FB_MASK } from '../common/Theme';
import { FbModuleDef, fbModuleOf, fbThemeVars } from '../model/Modules'; import { FbModuleDef, fbModuleOf, fbThemeVars } from '../model/Modules';
import { FbMovieAttendeeRow, FbMovieDetail } from '../data/FbMovieRepo'; import { FbMovieAttendeeRow, FbMovieDetail } from '../data/FbMovieRepo';
import { FB_TOKEN_STORE } from '../data/FbTokenStore'; import { FB_TOKEN_STORE } from '../data/FbTokenStore';
@@ -139,67 +139,94 @@ export struct FbMovieDetailSheet {
} }
build() { build() {
// FB_WRAP_CENTER:把卡片**垂直居中**(用户 2026-10-16 要求:浮在屏幕中间)。 /* 🔴 用户 2026-11-06:「电影这个框弹出来的时候,应该在下面盖一层蒙版吧?
// ⚠️ 居中放在**组件内部**做,而不是改宿主的 Stack 对齐 —— 跟近期任务类似。」
// 宿主 Stack 里还有别的子元素(日程页的悬浮 +),改对齐会连带挪走它们。
// ⚠️ 外层铺满 + Center;内层仍是那张 92% 宽的圆角卡。 ⇒ 根节点是 **`Stack`**,里面「全屏遮罩 + 卡片」是兄弟节点、叠在一起 ——
Column() { 与 `FbTaskPanel`(其文件头第 ③ 条)、`FbDayEditSheet` 完全同一做法。
// 🔴 用 Stack 把"浮层内容"和"全屏查看器"叠起来:
// 查看器要盖住整个浮层(含它自己的圆角/把手),所以必须是 ⚠️ 为什么蒙版做在**组件内部**、而不是宿主外面包一层:
// **同一个 Stack 里的后声明节点**,不能放进浮层内部的 Column · 组件根节点自己铺满全屏(居中卡片需要),会把"点遮罩关闭"的点击
// (那会被浮层的圆角裁剪、也会被内层 Scroll 的滚动带走)。 吃在手里 ⇒ 蒙版看着有、点它关不掉;
Stack({ alignContent: Alignment.Center }) { · 宿主再包一层遮罩就成了两层暗色叠在一起(越点越黑)。
/* ⚠️ 2026-10-19:外面这一层只给宽度(92%)—— ⇒ 谁铺满全屏,谁负责画遮罩。这是本项目浮层的一致约定。
以前卡片是 `width('100%')`(贴屏幕两边)+ 内部 Scroll 92%,
看上去是"一整块通栏白",加环也没处可加(环会跑出屏幕)。 ⚠️ 遮罩色统一用 `FB_MASK`(`#66000000`,与全站其它浮层一致;
现在:卡片 92% + 主题色环,内部滚动区 100%(等价于原来的内容宽度)。 */ 实测压暗后底噪 247→147,反推 alpha≈0.40 = 0x66/255)。 */
Column() { Stack({ alignContent: Alignment.Center }) {
FbSheetRing({ radius: FbMetric.radius, ringColor: this.accentColor() }) { /* 全屏遮罩:点它就关(与任务面板同一交互) */
Column({ space: 0 }) { Column()
this.sheetHeader() .width('100%')
this.sheetBody() .height('100%')
.backgroundColor(FB_MASK)
.onClick((): void => {
this.onClose();
})
/* 卡片容器:铺满 + 居中。
⚠️ 居中放在**组件内部**做,而不是改宿主的 Stack 对齐 ——
宿主 Stack 里还有别的子元素(日程页的悬浮 +),改对齐会连带挪走它们。
⚠️ 必须 `HitTestMode.Transparent`:让点击穿透到上层遮罩,
同时卡片自身的子节点仍可点(`Block` 会让关闭按钮点不动)。 */
Column() {
// 🔴 用 Stack 把"浮层内容"和"全屏查看器"叠起来:
// 查看器要盖住整个浮层(含它自己的圆角),所以必须是
// **同一个 Stack 里的后声明节点**,不能放进浮层内部的 Column
// (那会被浮层的圆角裁剪、也会被内层 Scroll 的滚动带走)。
Stack({ alignContent: Alignment.Center }) {
/* ⚠️ 2026-10-19:外面这一层只给宽度(92%)——
以前卡片是 `width('100%')`(贴屏幕两边)+ 内部 Scroll 92%,
看上去是"一整块通栏白",加环也没处可加(环会跑出屏幕)。
现在:卡片 92% + 主题色环,内部滚动区 100%。 */
Column() {
FbSheetRing({ radius: FbMetric.radius, ringColor: this.accentColor() }) {
Column({ space: 0 }) {
this.sheetHeader()
this.sheetBody()
}
.width('100%')
} }
.width('100%') }
.width('92%')
// ⚠️ 同样用 `Default`(不写)。`Block` 会挡住浮层内部的子节点
// (照片缩略图、关闭按钮都点不动)。
// ---- 全屏大图查看器 ----
if (this.viewerOpen) {
this.viewer()
}
// ---- 影评 + 评论浮层(工单 #27)----
if (this.reviewsOpen && this.detail !== null) {
FbMovieReviewSheet({
movieId: this.detail.id,
movieTitle: this.detail.title,
accent: this.accentColor(),
onClose: (): void => {
this.reviewsOpen = false;
},
onChanged: (): void => {
// 评分/条数可能变了,但详情接口不返回,不在这里强刷
}
})
} }
} }
.width('92%') .width('100%')
// ⚠️ 同样用 `Default`(不写)。`Block` 会挡住浮层内部的子节点 // ⚠️ 高度随内容自适应(浮层是"底部抽屉"形态)。
// (照片缩略图、关闭按钮都点不动)。 // 🔴 但**查看器打开时必须撑满**:查看器要盖住整屏,
// `Default` 已足够:浮层在遮罩之后声明 ⇒ 是兄弟节点、会被优先命中。 // 如果这里还是内容高度,查看器会被裁在一小块里,
// 而且**它没盖住的地方点击会穿透到外层** ——
// ---- 全屏大图查看器 ---- // 表现就是"点查看器的 ✕,结果把详情浮层也一起关了"
if (this.viewerOpen) { // ( 2026-10-12 实测到的 bug)。
this.viewer() .height(this.viewerOpen ? '100%' : undefined)
}
// ---- 影评 + 评论浮层(工单 #27)----
if (this.reviewsOpen && this.detail !== null) {
FbMovieReviewSheet({
movieId: this.detail.id,
movieTitle: this.detail.title,
accent: this.accentColor(),
onClose: (): void => {
this.reviewsOpen = false;
},
onChanged: (): void => {
// 评分/条数可能变了,但详情接口不返回,不在这里强刷
}
})
}
} }
.width('100%') .width('100%')
// ⚠️ 高度随内容自适应(浮层是"底部抽屉"形态)。 .height('100%')
// 🔴 但**查看器打开时必须撑满**:查看器要盖住整屏, .justifyContent(FlexAlign.Center)
// 如果这里还是内容高度,查看器会被裁在一小块里, .hitTestBehavior(HitTestMode.Transparent)
// 而且**它没盖住的地方点击会穿透到外层的遮罩** ——
// 表现就是"点查看器的 ✕,结果把详情浮层也一起关了"
// ( 2026-10-12 实测到的 bug)。
.height(this.viewerOpen ? '100%' : undefined)
} }
.width('100%') .width('100%')
.height('100%') .height('100%')
.justifyContent(FlexAlign.Center)
.hitTestBehavior(HitTestMode.Transparent)
} }
/** 浮层顶部:把手 + 关闭 */ /** 浮层顶部:把手 + 关闭 */
@@ -24,7 +24,7 @@
* 只是缺那几项详情专属字段,界面不会空。 * 只是缺那几项详情专属字段,界面不会空。
*/ */
import { FbColors, FbMetric, fbTint, FbTint, fbAlpha } from '../common/Theme'; import { FbColors, FbMetric, fbTint, FbTint, fbAlpha, FB_MASK } from '../common/Theme';
import { FbModuleDef, fbModuleOf, fbThemeVars } from '../model/Modules'; import { FbModuleDef, fbModuleOf, fbThemeVars } from '../model/Modules';
import { FbRosterPerson, FbJobRow, FbContactRow } from '../data/FbPersonRepo'; import { FbRosterPerson, FbJobRow, FbContactRow } from '../data/FbPersonRepo';
import { FbAvatar, FbTag } from './Widgets'; import { FbAvatar, FbTag } from './Widgets';
@@ -364,11 +364,27 @@ export struct FbPersonSheet {
} }
build() { build() {
// FB_WRAP_CENTER:把卡片**垂直居中**(用户 2026-10-16 要求:浮在屏幕中间)。 /* 🔴 用户 2026-11-06:「电影这个框弹出来的时候,应该在下面盖一层蒙版吧?
// ⚠️ 居中放在**组件内部**做,而不是改宿主的 Stack 对齐 —— 跟近期任务类似。」
// 宿主 Stack 里还有别的子元素(日程页的悬浮 +),改对齐会连带挪走它们。 ⇒ 同一条要求同步到这里:首页「联系人」点开的这张卡原来也没有遮罩。
// ⚠️ 外层铺满 + Center;内层仍是那张 92% 宽的圆角卡。
Column() { ⚠️ 蒙版做在**组件内部**(谁铺满全屏谁负责画遮罩),宿主不再包一层:
包两层会越点越黑,而且外层遮罩会被组件自己的全屏根节点吃掉点击。
⚠️ `embedded` 时不画遮罩:那种情况宿主是 `bindSheet`,
系统已经给了自己的遮罩,再叠一层就是双暗。 */
Stack({ alignContent: Alignment.Center }) {
if (!this.embedded) {
Column()
.width('100%')
.height('100%')
.backgroundColor(FB_MASK)
.onClick((): void => {
this.onClose();
})
}
/* ↓↓↓ 以下为原来的根 Column(内容一字未改,只是套进了 Stack) ↓↓↓ */
Column() {
// ⚠️ 2026-10-19:**卡片外面那一圈主题色环**由共用组件 `FbSheetRing` 给 // ⚠️ 2026-10-19:**卡片外面那一圈主题色环**由共用组件 `FbSheetRing` 给
// (用户要求"所有弹框都加线,保持一致")。 // (用户要求"所有弹框都加线,保持一致")。
// 这一层只负责**尺寸**(92% 宽 / 72% 高上限)—— 尺寸与外观分开, // 这一层只负责**尺寸**(92% 宽 / 72% 高上限)—— 尺寸与外观分开,
@@ -630,6 +646,9 @@ export struct FbPersonSheet {
.width(this.embedded ? 'auto' : '100%') .width(this.embedded ? 'auto' : '100%')
.height(this.embedded ? 'auto' : '100%') .height(this.embedded ? 'auto' : '100%')
.justifyContent(this.embedded ? FlexAlign.Start : FlexAlign.Center) .justifyContent(this.embedded ? FlexAlign.Start : FlexAlign.Center)
.hitTestBehavior(HitTestMode.Transparent) .hitTestBehavior(HitTestMode.Transparent)
}
.width('100%')
.height('100%')
} }
} }
+12 -2
View File
@@ -2125,7 +2125,14 @@ export struct FbHomePage {
/* 🔴 用户:「"最近观影"点了之后没反应…点了之后应该有反应, /* 🔴 用户:「"最近观影"点了之后没反应…点了之后应该有反应,
要么弹出来详细信息。」 要么弹出来详细信息。」
⚠️ 复用电影页的同一张卡(`FbMovieDetailSheet`), ⚠️ 复用电影页的同一张卡(`FbMovieDetailSheet`),
不为首页另写一套 —— 否则改一处要改两处。 */ 不为首页另写一套 —— 否则改一处要改两处。
🔴 用户 2026-11-06:「电影这个框弹出来的时候,应该在下面盖一层蒙版吧?
跟近期任务类似。」
⇒ 蒙版由**组件自己**提供(`FbMovieDetailSheet` 内部根 Stack:
遮罩 + 卡片叠着),与 `FbTaskPanel` / `FbDayEditSheet` 同一做法。
**宿主这里不要再包一层** —— 包了就是两层暗色叠在一起(越点越黑),
而且点遮罩时会被组件自己的全屏根节点拦掉,关不掉。 */
if (this.mvDetail !== null || this.mvDetailLoading if (this.mvDetail !== null || this.mvDetailLoading
|| this.mvDetailError !== '') { || this.mvDetailError !== '') {
FbMovieDetailSheet({ FbMovieDetailSheet({
@@ -2145,7 +2152,10 @@ export struct FbHomePage {
// ==================== #64:联系人详情(可打电话)==================== // ==================== #64:联系人详情(可打电话)====================
/* 🔴 用户:「点击之后弹出来详细信息,就跟名册里面那个人的列表 /* 🔴 用户:「点击之后弹出来详细信息,就跟名册里面那个人的列表
点击完之后的效果是一样的,这样可以直接打电话。」 点击完之后的效果是一样的,这样可以直接打电话。」
⚠️ 复用名册那张卡(`FbPersonSheet`),它自带手机号拨号入口。 */ ⚠️ 复用名册那张卡(`FbPersonSheet`),它自带手机号拨号入口。
🔴 2026-11-06 同一条:蒙版同样由组件自己提供(`FbPersonSheet` 内部根
Stack:遮罩 + 卡片)。宿主不包 —— 理由同上一段。 */
if (this.pfDetail !== null) { if (this.pfDetail !== null) {
FbPersonSheet({ FbPersonSheet({
person: this.pfDetail, person: this.pfDetail,