#1 用户提四项(2026-10-26): 「日程的右上角那个今天的今,特别不好点,一不小心就到日程里面去了…… 是不是可以把按钮弄的大一点。」 「观影的卡片,2x4,右侧观影的列表,不能上下滑动,第三个被截断,很难看, 如果显示多个,就要能滑动,如果不能滑动,就显示两个。」 「任务的 2x4 卡片,也是一样的。」 「观影的 4x4 卡片,和 4x6 卡片,两列图片挤在一起,中间没有空隙,应该缩小一点, 这个大小,我感觉应该根据屏幕的大小来。4x6,宽度都是一样的,为啥要弄成三列?」 ① 日程卡「今」按钮 22x22 → 32x32 病根:22 的圆比最小建议点击区小一半,手指偏一点就落到卡片本体的 `.onClick(openApp)` 上 ⇒ 「想回今天」变成「进了日程详情」。 面积 484 → 1024(2.1 倍);同一份 sideBtn() 里的「+」一并放大, 翻页键 ‹ › 本来 32,现在四个按钮手感一致。 为什么是 32 不是 40:4x4 卡高度紧,顶栏与内容区竖向堆叠 (内容区 layoutWeight(1)),顶栏长高多少内容区就少多少。 必须改 width/height 本身 —— 卡片里这是 Button,可点区就是自己的宽高。 ②③ 观影/任务 2x4 右半 Column → List(可滚动) 病根不在条数而在容器:Column 根本不滚动 + 硬截 3 条, 3x(38+10=48) + 2x6 = 156vp 而可用仅 ≈130vp ⇒ 第三条被压扁/裁掉。 条数上限 3 → MR_2X4_MAX=12 / TK_2X4_MAX=30;滚动条 BarState.Auto (条目少时不露空滚动条)。数据侧 FbCardMediaData 两处 `rows.length < 4` 同步放到 30,否则卡上拿到的还是 4 条,改了容器也滑不出东西。 ④ 观影 4x4 / 6x4 补列间距 + 6x4 三列改两列 病根:`List({ space: 8 })` 的 space 在 lanes 多列模式下只管行间距, 列与列之间不会自动留缝;海报格 .width('100%') + .aspectRatio(0.68) 又贴着 lane 边排 ⇒ 两列严丝合缝贴死。 改法:.lanes(2) → .lanes(2, 8),第二个位置参数才是列间距 gutter。 ⚠️ gutter 是位置参数(SDK list.d.ts: lanes(value, gutter?: Dimension), Dimension = PX|VP|FP|LPX|Percentage|Resource)⇒ 传数字, 写成 { gutter: 8 } 是类型错误(中途踩到,编译期才炸)。 6x4 同时从 .lanes(3) 改成两列,与 4x4 一致;文件头那句 「这块卡更宽、一排放三张」已过时,已注明别再照着它改回去。 同批带上 v92 未提交的观影海报墙实现(此前只在工作树里,从未提交过): - 新增 data/FbCardImages.ets(下载 + desiredSize 缩图 + fd 管理) - FbCardMediaData / FbFormAbility / FbCardForms 里的 formImages 管道 - string.json 卡片名带尺寸(「家书 · 日程 4×4」/「家书 · 观影 4×4」) ⚠️ FbCardImages.ets 已被 FbCardMediaData import,不一起提交就编译不过。 验证: - 静态守卫 _fb_arkts_guard.js:1303 通过 / 0 失败 (新增 groupV95() 共 28 条,钉住按钮尺寸、List 容器、条数上限、 两列 + gutter>0、且不许写成 { gutter: N } 对象) - 反向验证 _fb_v59d_falsify.py:289 通过 / 0 失败 (新增 9 条注入,逐条证明上述断言真的会红) - 真编译 hvigorw assembleHap:BUILD SUCCESSFUL,HAP 产物已生成 ⚠️ 真机验收未做:hdc list targets 为空(设备未连/未解锁)。 这四项全是观感与手感,静态断言证明不了「点得着、滑得动、看着舒服」, 待设备连上后补真机探针(重点:真的点「今」、真的滑列表、量两列实际间距)。
228 lines
7.3 KiB
Plaintext
228 lines
7.3 KiB
Plaintext
/**
|
||
* 家书 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())
|
||
}
|
||
}
|