diff --git a/entry/src/main/ets/pages/home/FbHomePage.ets b/entry/src/main/ets/pages/home/FbHomePage.ets index 7acb557..25d22a8 100644 --- a/entry/src/main/ets/pages/home/FbHomePage.ets +++ b/entry/src/main/ets/pages/home/FbHomePage.ets @@ -71,6 +71,7 @@ import { FbPersonEditSheet } from '../../components/FbPersonEditSheet'; import { FbMovieRepo, FbMovieListItem, FbCinemaItem, FbMovieDetail } from '../../data/FbMovieRepo'; import { FbMovieDetailSheet } from '../../components/FbMovieDetailSheet'; import { FbPersonRepo, FbRosterPerson } from '../../data/FbPersonRepo'; +import { fbImageSrc } from '../../data/FbAttachQueue'; import { FB_DOCK_RESERVE } from '../../components/FloatDock'; import { fbOnScroll, fbOnTouch } from '../../common/FbScroll'; import { fbMaskTransition, fbSheetTransition } from '../../common/FbAnim'; @@ -309,6 +310,18 @@ export struct FbHomePage { @Local mvCreating: boolean = false; /** 观影卡要显示的最近几条(本地库) */ @Local mvRecent: FbMovieListItem[] = []; + /** + * 海报加载失败的电影 id。 + * + * 🔴 用户 2026-11-05:「首页最近观影没有图片呢,左侧都是占位符。」 + * v86 当初为了"省网络请求"刻意用 🎬 色块占位,网页端 `_card_movies.php` + * 一直是真海报 ⇒ 端上补回真图。 + * + * ⚠️ 海报是网络图,取不到是常态(没网、令牌失效、图被删)⇒ 必须记下失败, + * 那张卡退回 🎬 色块(与"本来就没海报"同观感),否则会一直挂半张白图。 + * ⚠️ 与电影页同一个手法:`@Local` 只代理第一层,**整体换新数组**才刷新。 + */ + @Local mvPosterFailIds: number[] = []; /** 影院列表(`FbMovieNewSheet` 的必填项之一,懒拉) */ @Local mvCinemas: FbCinemaItem[] = []; @Local mvLoading: boolean = false; @@ -2465,11 +2478,17 @@ export struct FbHomePage { } /** - * 「最近观影」里的一行(v86)。 + * 「最近观影」里的一行(v86,v88 补海报)。 * - * 形态照网页端 `_card_movies.php`:**片名**(主)+ 日期 · 影院(次)。 - * ⚠️ 不放海报:卡片上放图要么挤成小方块看不清、要么整行高度被拉起来, - * 而且每行拉一张图 = 首页多几个请求。看海报请进电影页。 + * 形态照网页端 `_card_movies.php`:**海报**(左)+ 片名(主)+ 日期 · 影院(次)。 + * + * 🔴 用户 2026-11-05:「首页最近观影没有图片呢,左侧都是占位符。」 + * v86 当初为了"首页少打几个请求"把海报砍成 🎬 色块,网页端一直是真图 + * ⇒ 这里补回真海报。三条约束: + * ① 卡片上只放 **5 条**(`loadSideCards` 的 limit),5 张缩略图可接受; + * ② 走 `fbImageSrc` —— 本地库里已缓存的直接用本地明文,命中缓存时零请求; + * ③ **不加 `?w=` 压缩档位**:`media.php` 只留 1600 一档(#56), + * 对 34×46 的缩略图没意义,白付一次 OSS 处理费。 * ⚠️ 又是**两参数 @Builder**(`isLast` 只为画分隔线)—— * 与 `memoryRow` / `recentDayRow` 同一个安全前提:整块由 ForEach 按 key 重建。 */ @@ -2477,19 +2496,37 @@ export struct FbHomePage { movieRow(m: FbMovieListItem, isLast: boolean) { Column({ space: 0 }) { Row({ space: 10 }) { - /* 小圆角色块代替海报:用影院色(`cinemaColor`) - —— 既区分了影院、又不吃网络请求。 - ⚠️ 影院色可能是空串(没设影院的老数据)⇒ 兜底一个中性灰。 */ - Column() { - Text('🎬') - .fontSize(15) + /* 左侧海报位(2:3 竖版,34×46 —— 与网页端 `.hc-mv-poster` 同尺寸口径)。 + 有海报放真图,没有 / 加载失败退回 🎬 色块。 */ + Stack({ alignContent: Alignment.Center }) { + if (m.posterKey !== '' && !this.mvPosterFailed(m.id)) { + Image(fbImageSrc(m.posterKey, FB_TOKEN_STORE.get())) + .width('100%') + .height('100%') + .objectFit(ImageFit.Cover) + // 底下垫一张图标:图还没回来时不会闪白块 + .alt($r('app.media.fb_ic_movies')) + // 海报是大图,同步加载会卡住首页首屏 + .onError((): void => { + this.markMvPosterFailed(m.id); + }) + } else { + /* 没有海报 / 取不到:🎬 + 影院色底(影院色空串由 `movieTint` 兜底)。 + 这一层同时是上面 Image 的"底"—— 真图铺满后看不见。 */ + Column() { + Text('🎬') + .fontSize(15) + } + .width('100%') + .height('100%') + .backgroundColor(this.movieTint(m.cinemaColor)) + } } .width(34) - .height(34) - .borderRadius(9) - .backgroundColor(this.movieTint(m.cinemaColor)) - .justifyContent(FlexAlign.Center) - .alignItems(HorizontalAlign.Center) + .height(46) + .borderRadius(4) + .clip(true) + .backgroundColor(FbColors.inputBg) Column({ space: 3 }) { Text(m.title !== '' ? m.title : '(未命名)') @@ -2529,6 +2566,25 @@ export struct FbHomePage { }) } + /** 这张海报是不是已经确认取不到(取不到就退回 🎬 色块) */ + private mvPosterFailed(id: number): boolean { + return this.mvPosterFailIds.indexOf(id) >= 0; + } + + /** + * 记下"这张海报取不到"。 + * + * ⚠️ 必须**换新数组**赋值(`concat`)—— `@Local` 不代理数组内部变化, + * `push` 进去界面不会刷新(现象:图还是白的,占位也没出现)。 + * ⚠️ 已记过就不再重复加:`onError` 可能多次触发,重复加会让数组无限增长。 + */ + private markMvPosterFailed(id: number): void { + if (this.mvPosterFailed(id)) { + return; + } + this.mvPosterFailIds = this.mvPosterFailIds.concat([id]); + } + /** 观影行的副标题:`2026-10-20 · 万达影城`(影院名空则只留日期) */ private movieSub(m: FbMovieListItem): string { const d: string = this.memoryDate(m.startsAt);