/** * 家书 FamilyBook · 影院列表浮层(鸿蒙端) * * 观影模块的"影院管理"入口(Web 端在观影页的设置里)。 * * 🔴 为什么影院**不占抽屉里的位置**: * 它不是独立模块,是"观影"的一部分 —— 一家影院只有"在哪、 * 叫什么、在那看过几部"这三个属性,独立成一页太空。 * ⇒ 做成观影页里的浮层(与详情、设置同一套交互)。 * * ⚠️ 数据来自**本地库**(观影同步时一起落库的 `cinemas` 表), * 不是现拉接口 —— 见 `FbMovieRepo.cinemaList()` 的说明。 */ import { FbColors, FbMetric } from '../common/Theme'; import { FbCinemaItem } from '../data/FbMovieRepo'; /** * 贴底卡片的手势区缓冲(vp)。 * * 🔴 应用开了全屏布局且**隐藏了系统手势条**(见 EntryAbility)—— * 手势区不再有任何视觉提示,贴底的卡片必须自己让出这一段, * 否则最后一排按钮会被"从下往上滑"误触。 */ const FB_SHEET_BOTTOM_GAP: number = 16; @ComponentV2 export struct FbCinemaSheet { /** 影院列表 */ @Param cinemas: FbCinemaItem[] = []; /** 关闭回调 */ @Event onClose: () => void = (): void => { }; build() { // FB_WRAP_CENTER:把卡片**垂直居中**(用户 2026-10-16 要求:浮在屏幕中间)。 // ⚠️ 居中放在**组件内部**做,而不是改宿主的 Stack 对齐 —— // 宿主 Stack 里还有别的子元素(日程页的悬浮 +),改对齐会连带挪走它们。 // ⚠️ 外层铺满 + Center;内层仍是那张 92% 宽的圆角卡。 Column() { Column({ space: 0 }) { // ---- 标题 ---- Row() { Column({ space: 2 }) { Text('影院') .fontSize(FbMetric.fontTitle) .fontWeight(FontWeight.Medium) .fontColor(FbColors.ink) Text(this.cinemas.length.toString() + ' 家 · 按名字排序') .fontSize(FbMetric.fontTiny) .fontColor(FbColors.faint) } .alignItems(HorizontalAlign.Start) .layoutWeight(1) Text('关闭') .fontSize(FbMetric.fontSmall) .fontColor(FbColors.muted) .padding({ left: 12, right: 12, top: 6, bottom: 6 }) .backgroundColor(FbColors.inputBg) .borderRadius(FbMetric.radiusS) .onClick((): void => { this.onClose(); }) } .width('100%') .alignItems(VerticalAlign.Center) Divider() .color(FbColors.line) .margin({ top: 12, bottom: 4 }) // ---- 列表 ---- // ⚠️ 用 List 而不是 ForEach 堆在 Column 里:影院可能几十家, // List 才会**按需渲染**(进视口才建),Column 会一次全建。 if (this.cinemas.length === 0) { Text('还没有影院。同步观影记录后,去过的地方会自动出现在这里。') .fontSize(FbMetric.fontSmall) .fontColor(FbColors.faint) .width('100%') .padding({ top: 32, bottom: 32, left: 12, right: 12 }) .textAlign(TextAlign.Center) } else { List({ space: 0 }) { ForEach(this.cinemas, (c: FbCinemaItem, idx: number) => { ListItem() { Column({ space: 0 }) { Row({ space: 12 }) { // 色点:每家影院一个色(与 Web 端一致,便于在别处一眼认出来) Column() .width(10) .height(10) .backgroundColor(c.color) .borderRadius(5) Column({ space: 3 }) { Row({ space: 8 }) { Text(c.name) .fontSize(FbMetric.fontBody) .fontColor(FbColors.ink) .maxLines(1) if (c.movieCount > 0) { Text(c.movieCount.toString() + ' 部') .fontSize(10) .fontColor(FbColors.faint) } } .alignItems(VerticalAlign.Center) // 场所 / 地址(有才显示) if (c.place !== '' || c.address !== '') { Text(c.place !== '' ? c.place : c.address) .fontSize(FbMetric.fontTiny) .fontColor(FbColors.muted) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) } } .alignItems(HorizontalAlign.Start) .layoutWeight(1) } .width('100%') .padding({ left: 4, right: 4, top: 11, bottom: 11 }) .alignItems(VerticalAlign.Center) if (idx < this.cinemas.length - 1) { Divider() .strokeWidth(0.5) .color(FbColors.line) } } .width('100%') } }, (c: FbCinemaItem): string => 'cin_' + c.id.toString()) } .width('100%') .layoutWeight(1) .scrollBar(BarState.Off) .edgeEffect(EdgeEffect.Spring) } } // 🔴 用户 2026-10-16:「它应该弹出一个**圆角的、浮在上面的卡片**…… // **不要让它左右充满整个屏幕,下面也不要挨着屏幕**, // 而是放在**屏幕中间**,采用**圆角浮窗**式的形式。」 // ⇒ 不再贴底满宽:左右留边、四角圆角、垂直居中(由宿主的 Stack 对齐)。 .width('92%') .constraintSize({ maxHeight: '72%' }) .padding({ left: 18, right: 18, top: 18, bottom: 20 + FB_SHEET_BOTTOM_GAP }) .backgroundColor(FbColors.surface) .borderRadius(FbMetric.radius) .alignItems(HorizontalAlign.Start) } .width('100%') .height('100%') .justifyContent(FlexAlign.Center) .hitTestBehavior(HitTestMode.Transparent) } }