Files
FamilyBook_harmonyos/entry/src/main/ets/pages/card/FbMovieCard6x4.ets
T

228 lines
7.3 KiB
Plaintext
Raw Normal View History

/**
* 家书 FamilyBook · 6×4 服务卡片(观影 · 上概述,下**两列海报墙**)—— v92 / v95
*
* 用户 2026-10-25:「观影应该显示图片……实际上应该用大图的形式显示,
* 就做成和观影模式一样,不行吗?」
* ⇒ 与 4×4 同一套海报墙。
*
* 🔴 v95(用户 2026-10-26):「4x6,宽度都是一样的,为啥要弄成三列?」
* ⇒ 列数从 3 改成 **2**(与 4×4 一致),并补上列间距 `gutter`。
* ⚠️ 文件头原来写的是"这块卡更宽、一排放三张"——那句已经**过时**,
* 别再照着它改回 `.lanes(3)`。
*
* 🔴 卡片里的图不能写 http 地址,只能 `Image('memory://' + img)`
* (图由 `FbCardMediaData` 下载+缩图、以 `formImages` 的 fd 交过来)。
* ⚠️ 卡片不支持 `Scroll`,只支持 `List` ⇒ 多列用 `List().lanes(n, gutter)`,别换成 Grid。
* ⚠️ ForEach key 带 version 前缀(v89 教训)。
*/
let storageMvP6 = new LocalStorage();
/** 海报墙一格 */
class FbMovieCell9b {
key: string = '';
title: string = '';
dateText: string = '';
cinema: string = '';
rating: string = '';
/** `memory://` 的内存键;空串 = 没图,画占位 */
img: string = '';
}
@Entry(storageMvP6)
@Component
struct FbMovieCard6x4 {
@LocalStorageProp('state') state: string = 'loading';
@LocalStorageProp('hint') hint: string = '';
@LocalStorageProp('updatedAt') updatedAt: string = '';
@LocalStorageProp('yearCount') yearCount: number = 0;
@LocalStorageProp('totalCount') totalCount: number = 0;
@LocalStorageProp('listJson') listJson: string = '[]';
@LocalStorageProp('version') version: string = '';
private rows(): FbMovieCell9b[] {
try {
const all: FbMovieCell9b[] = JSON.parse(this.listJson) as FbMovieCell9b[];
return all;
} catch (err) {
return [];
}
}
private openApp(): void {
postCardAction(this, {
action: 'router',
abilityName: 'EntryAbility',
params: { fbModule: 'movies' }
});
}
/** 「+ 新建」→ 进 app 直开「记一场」浮层(EntryAbility → modCreate 通道) */
private addOne(): void {
postCardAction(this, {
action: 'router',
abilityName: 'EntryAbility',
params: { fbModule: 'movies', widgetAction: 'add' }
});
}
/** 图下那行小字:`10月24日 · 万象城`(都空就不画) */
private subText(item: FbMovieCell9b): string {
let s: string = item.dateText;
if (item.cinema !== '') {
s = s !== '' ? s + ' · ' + item.cinema : item.cinema;
}
return s;
}
/** 一格格:大图 + 底部黑条上的片名/日期 */
@Builder
posterCell(item: FbMovieCell9b) {
Stack({ alignContent: Alignment.Bottom }) {
if (item.img !== '') {
// ⚠️ 只能 memory://(见文件头):写 http 地址在卡片里是一张空白
Image('memory://' + item.img)
.width('100%')
.height('100%')
.objectFit(ImageFit.Cover)
.borderRadius(10)
} else {
Column() {
Text(item.title)
.fontSize(10)
.fontColor('#7A4E8C')
.maxLines(3)
.textAlign(TextAlign.Center)
.padding({ left: 4, right: 4 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.backgroundColor('#EDE6F1')
.borderRadius(10)
}
Column({ space: 1 }) {
Text(item.title)
.fontSize(10)
.fontWeight(FontWeight.Medium)
.fontColor('#FFFFFF')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.width('100%')
if (this.subText(item) !== '') {
Text(this.subText(item))
.fontSize(8)
.fontColor('#FFFFFF')
.opacity(0.8)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.width('100%')
}
}
.width('100%')
.padding({ left: 6, right: 6, top: 3, bottom: 3 })
.backgroundColor('#66000000')
}
.width('100%')
.aspectRatio(0.68)
.borderRadius(10)
.clip(true)
}
/** 空态 */
@Builder
emptyHint() {
Column() {
Text(this.hint !== '' ? this.hint : '还没有观影记录')
.fontSize(12)
.fontColor(this.hint !== '' ? '#C0533F' : '#A8988A')
.maxLines(3)
.textAlign(TextAlign.Center)
}
.width('100%')
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
}
build() {
Column({ space: 6 }) {
// ===== 顶栏:标题 + 更新时间 + + 新建 =====
Row({ space: 6 }) {
Text('观影')
.fontSize(15)
.fontWeight(FontWeight.Bold)
.fontColor('#2C231D')
.maxLines(1)
.layoutWeight(1)
Text(this.updatedAt)
.fontSize(9)
.fontColor('#A8988A')
.maxLines(1)
Button('+')
.fontSize(13)
.fontColor('#FFFFFF')
.width(26)
.height(26)
.borderRadius(13)
.padding(0)
.backgroundColor('#7A4E8C')
.onClick((): void => this.addOne())
}
.width('100%')
// ===== 概述行 =====
Row({ space: 8 }) {
Text(`今年 ${this.yearCount} 场`)
.fontSize(11)
.fontColor('#7A4E8C')
.fontWeight(FontWeight.Medium)
Text(`共 ${this.totalCount} 条`)
.fontSize(10)
.fontColor(this.hint !== '' ? '#C0533F' : '#A8988A')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.layoutWeight(1)
}
.width('100%')
// ===== 海报墙(三列,可滚动)=====
if (this.rows().length === 0) {
this.emptyHint()
} else {
List({ space: 8 }) {
ForEach(this.rows(), (item: FbMovieCell9b) => {
ListItem() {
this.posterCell(item)
}
}, (item: FbMovieCell9b): string => `mp_${this.version}_${item.key}`)
}
/* 🔴🔴 v95(用户 2026-10-26):「4x6,宽度都是一样的,为啥要弄成三列?」
—— 原来是 `.lanes(3)`(文件头那句"这块卡更宽、一排放三张")。
⇒ 改成**两列**,与 4×4 一致:
· 用户明确要求,且三列时每张海报明显更小、更挤,观感差;
· 两列 + 更高的卡高 ⇒ 一屏能看到的行数更多,反而比三列看得多。
⚠️ 同时补 `gutter`:`List({ space: 8 })` 的 `space` 在 lanes 多列
模式下只管**行间距**,列缝必须靠 `lanes(count, gutter)` 给,
否则两列又会贴死(与 4×4 同一个坑,见 FbMovieCard4x4 里的详细说明)。
`gutter` 是**位置参数**(`Dimension`),直接传数字,**不是** `{ gutter: 8 }`。
⚠️ 别再改回 `.lanes(3)`:列数一多,同一块卡里的海报就被压到
看不清片名,而用户要的恰恰是"大图"。 */
.lanes(2, 8)
.alignListItem(ListItemAlign.Start)
.width('100%')
.layoutWeight(1)
.scrollBar(BarState.Off)
}
}
.width('100%')
.height('100%')
.padding(12)
.backgroundColor('#FFFFFF')
.borderRadius(16)
.onClick((): void => this.openApp())
}
}