2026-10-25 09:29:41 +08:00
|
|
|
|
/**
|
2026-10-26 07:28:06 +08:00
|
|
|
|
* 家书 FamilyBook · 6×4 服务卡片(观影 · 上概述,下**两列海报墙**)—— v92 / v95
|
2026-10-25 09:29:41 +08:00
|
|
|
|
*
|
2026-10-26 07:28:06 +08:00
|
|
|
|
* 用户 2026-10-25:「观影应该显示图片……实际上应该用大图的形式显示,
|
|
|
|
|
|
* 就做成和观影模式一样,不行吗?」
|
|
|
|
|
|
* ⇒ 与 4×4 同一套海报墙。
|
2026-10-25 09:29:41 +08:00
|
|
|
|
*
|
2026-10-26 07:28:06 +08:00
|
|
|
|
* 🔴 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 教训)。
|
2026-10-25 09:29:41 +08:00
|
|
|
|
*/
|
|
|
|
|
|
|
2026-10-26 07:28:06 +08:00
|
|
|
|
let storageMvP6 = new LocalStorage();
|
2026-10-25 09:29:41 +08:00
|
|
|
|
|
2026-10-26 07:28:06 +08:00
|
|
|
|
/** 海报墙一格 */
|
|
|
|
|
|
class FbMovieCell9b {
|
2026-10-25 09:29:41 +08:00
|
|
|
|
key: string = '';
|
|
|
|
|
|
title: string = '';
|
|
|
|
|
|
dateText: string = '';
|
|
|
|
|
|
cinema: string = '';
|
|
|
|
|
|
rating: string = '';
|
2026-10-26 07:28:06 +08:00
|
|
|
|
/** `memory://` 的内存键;空串 = 没图,画占位 */
|
|
|
|
|
|
img: string = '';
|
2026-10-25 09:29:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-26 07:28:06 +08:00
|
|
|
|
@Entry(storageMvP6)
|
2026-10-25 09:29:41 +08:00
|
|
|
|
@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 = '';
|
|
|
|
|
|
|
2026-10-26 07:28:06 +08:00
|
|
|
|
private rows(): FbMovieCell9b[] {
|
2026-10-25 09:29:41 +08:00
|
|
|
|
try {
|
2026-10-26 07:28:06 +08:00
|
|
|
|
const all: FbMovieCell9b[] = JSON.parse(this.listJson) as FbMovieCell9b[];
|
2026-10-25 09:29:41 +08:00
|
|
|
|
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' }
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-26 07:28:06 +08:00
|
|
|
|
/** 图下那行小字:`10月24日 · 万象城`(都空就不画) */
|
|
|
|
|
|
private subText(item: FbMovieCell9b): string {
|
|
|
|
|
|
let s: string = item.dateText;
|
|
|
|
|
|
if (item.cinema !== '') {
|
|
|
|
|
|
s = s !== '' ? s + ' · ' + item.cinema : item.cinema;
|
|
|
|
|
|
}
|
|
|
|
|
|
return s;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 一格格:大图 + 底部黑条上的片名/日期 */
|
2026-10-25 09:29:41 +08:00
|
|
|
|
@Builder
|
2026-10-26 07:28:06 +08:00
|
|
|
|
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 }) {
|
2026-10-25 09:29:41 +08:00
|
|
|
|
Text(item.title)
|
2026-10-26 07:28:06 +08:00
|
|
|
|
.fontSize(10)
|
|
|
|
|
|
.fontWeight(FontWeight.Medium)
|
|
|
|
|
|
.fontColor('#FFFFFF')
|
2026-10-25 09:29:41 +08:00
|
|
|
|
.maxLines(1)
|
|
|
|
|
|
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
2026-10-26 07:28:06 +08:00
|
|
|
|
.width('100%')
|
|
|
|
|
|
if (this.subText(item) !== '') {
|
|
|
|
|
|
Text(this.subText(item))
|
|
|
|
|
|
.fontSize(8)
|
|
|
|
|
|
.fontColor('#FFFFFF')
|
|
|
|
|
|
.opacity(0.8)
|
2026-10-25 09:29:41 +08:00
|
|
|
|
.maxLines(1)
|
|
|
|
|
|
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
2026-10-26 07:28:06 +08:00
|
|
|
|
.width('100%')
|
2026-10-25 09:29:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-10-26 07:28:06 +08:00
|
|
|
|
.width('100%')
|
|
|
|
|
|
.padding({ left: 6, right: 6, top: 3, bottom: 3 })
|
|
|
|
|
|
.backgroundColor('#66000000')
|
2026-10-25 09:29:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
.width('100%')
|
2026-10-26 07:28:06 +08:00
|
|
|
|
.aspectRatio(0.68)
|
|
|
|
|
|
.borderRadius(10)
|
|
|
|
|
|
.clip(true)
|
2026-10-25 09:29:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 空态 */
|
|
|
|
|
|
@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%')
|
|
|
|
|
|
|
2026-10-26 07:28:06 +08:00
|
|
|
|
// ===== 海报墙(三列,可滚动)=====
|
2026-10-25 09:29:41 +08:00
|
|
|
|
if (this.rows().length === 0) {
|
|
|
|
|
|
this.emptyHint()
|
|
|
|
|
|
} else {
|
2026-10-26 07:28:06 +08:00
|
|
|
|
List({ space: 8 }) {
|
|
|
|
|
|
ForEach(this.rows(), (item: FbMovieCell9b) => {
|
2026-10-25 09:29:41 +08:00
|
|
|
|
ListItem() {
|
2026-10-26 07:28:06 +08:00
|
|
|
|
this.posterCell(item)
|
2026-10-25 09:29:41 +08:00
|
|
|
|
}
|
2026-10-26 07:28:06 +08:00
|
|
|
|
}, (item: FbMovieCell9b): string => `mp_${this.version}_${item.key}`)
|
2026-10-25 09:29:41 +08:00
|
|
|
|
}
|
2026-10-26 07:28:06 +08:00
|
|
|
|
/* 🔴🔴 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)
|
2026-10-25 09:29:41 +08:00
|
|
|
|
.width('100%')
|
|
|
|
|
|
.layoutWeight(1)
|
|
|
|
|
|
.scrollBar(BarState.Off)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
.width('100%')
|
|
|
|
|
|
.height('100%')
|
|
|
|
|
|
.padding(12)
|
|
|
|
|
|
.backgroundColor('#FFFFFF')
|
|
|
|
|
|
.borderRadius(16)
|
|
|
|
|
|
.onClick((): void => this.openApp())
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|