编辑观影记录:修豆瓣联想乱弹与多字段回填,标题主题色、删副标题 (#83)
用户:编辑观影记录后片名下面列出好几个条目(豆瓣的?输入框变更后才该出现);
影院、同看的人、观影照片、简介、备注都没带进来;
编辑观影记录要用主题色;副标题那句删掉。
五个问题三种根因:
A. TextInput/TextArea 把回填当成用户输入
- 片名:prefill 改完 this.title,下次渲染的 TextInput 把新值当 onChange 回调
⇒ scheduleSuggest 弹出一排豆瓣候选。改为 onChange 里先比较 v !== this.title,
值没真变就不调度;prefill 末尾兜底清联想态。
(先试过 @Local prefilling 标记,没用——prefill 同步跑完标记立刻复位,
而 onChange 是下一帧才回调的。)
- 简介/备注:areaField 是**值参数** Builder,TextArea({text:value}) 只拿首次
调用的字面量。拆成 infoField/noteField 两个 Builder,内部直接读 this.info/note。
B. Row { 标签.width(100%); 内层Row } 把内容挤出可视区
- cinemaField/personField/photoField 三个字段都是这个结构,内层 Row 塌成 0 高、
完全不渲染 ⇒ 看起来像没带进来,其实没画。全部改两排版式。
(这是 #80 修 posterField 时漏掉的同一个坑,同文件另外三处没扫全。)
- 观影照片:照片区只渲染 photoUris(新选图),已有图在 editingDetail.photos 里
从未渲染。新增 @Local existingPhotos,显示「已有 N 张(保存后保持不变)」。
C. 字段压根没从接口接上
- FbMovieDetail / FbApiMovieDetail 都没有 cinemaId/cinema_id,但服务端
Movie::find() 的 SELECT 明确有 d.cinema_id。所以只能按影院名反查,
重名会选错、改名会失败。补齐字段并解析,cinemaIdOf 改为优先用 id。
另外:FbSheetTitleBar 加 @Param titleColor(默认空=正文色,老调用点零影响),
观影编辑传 accentColor;编辑模式 subtitle 传空串,新建模式保留必填提示。
验证(真机):豆瓣候选无;影院「黄岛城市传媒广场 · 青岛东方影都艺术影城」;
同看人「已选 2 人:杨永全、红围脖」;简介输入框深色像素 2181(916 字全文);
备注 0(该记录 note 本来就是 NULL,SQL 已核实);照片「已有 4 张」;
标题 RGB(99,69,112)=观影模块色 #7A4E8C 派生;标题下方 50px 内无文本。
hvigorw assembleHap BUILD SUCCESSFUL。
This commit is contained in:
@@ -136,7 +136,7 @@ export struct FbMovieDetailSheet {
|
||||
return {
|
||||
ok: false, error: '', id: 0, title: '', startsAt: '', endsAt: '', note: '',
|
||||
info: '', posterKey: '', ticketKey: '', cinemaName: '', cinemaPlace: '',
|
||||
cinemaAddress: '', cinemaColor: '', myRating: 0, myReview: '',
|
||||
cinemaAddress: '', cinemaColor: '', cinemaId: 0, myRating: 0, myReview: '',
|
||||
reviewCount: 0, reviewAvg: 0, attendees: [], photos: []
|
||||
};
|
||||
}
|
||||
|
||||
@@ -129,6 +129,19 @@ export struct FbMovieNewSheet {
|
||||
* 保存后海报变成了另一张"。
|
||||
*/
|
||||
@Local existingPosterKey: string = '';
|
||||
/**
|
||||
* 编辑模式下**这条记录已有的观影照片**(相对键;只展示,不提交)。
|
||||
*
|
||||
* 🔴 用户 2026-11-06:「观影照片…没带进来。」
|
||||
* 原来照片区只渲染 `photoUris`(本次新选的本地图),已有照片在
|
||||
* `editingDetail.photos` 里**完全没画** ⇒ 用户以为照片丢了。
|
||||
*
|
||||
* ⚠️ 用 `@Local` 而不是在 build 里读 `this.editingDetail.photos`:
|
||||
* `if` 条件里调方法/取对象深层字段都不建依赖(本项目反复踩过)。
|
||||
* ⚠️ 与 `photoUris` 分开存:那个是"待上传的本地 uri",语义不同,
|
||||
* 混在一起会让"保存时传哪些"变得说不清。
|
||||
*/
|
||||
@Local existingPhotos: string[] = [];
|
||||
/** 豆瓣抓到的元数据 JSON(存进库,详情页另有展示) */
|
||||
@Local doubanMeta: string = '';
|
||||
|
||||
@@ -205,13 +218,15 @@ export struct FbMovieNewSheet {
|
||||
const en: string = d.endsAt;
|
||||
this.endDate = en.length >= 10 ? en.substring(0, 10) : this.startDate;
|
||||
this.endTime = en.length >= 16 ? en.substring(11, 16) : '';
|
||||
this.cinemaId = this.cinemaIdOf(d.cinemaName);
|
||||
this.cinemaId = this.cinemaIdOf(d);
|
||||
this.info = d.info;
|
||||
this.note = d.note;
|
||||
/* 已有海报:只展示,不提交(见 existingPosterKey 的说明)。
|
||||
⚠️ 别塞进 doubanPosterKey —— 那个会被 PATCH 带上去,
|
||||
虽然值相同不出错,但语义就乱了,后人容易改错。 */
|
||||
this.existingPosterKey = d.posterKey;
|
||||
/* 已有观影照片(票根另算,它是 movie_details.ticket_key,不在这里) */
|
||||
this.existingPhotos = d.photos.slice();
|
||||
const ids: number[] = [];
|
||||
/* ⚠️ 必须显式标类型(`arkts-no-any-unknown`):
|
||||
直接 `for (const a of d.attendees)` 会让 `a` 被推断成 any 而编译失败。 */
|
||||
@@ -220,24 +235,38 @@ export struct FbMovieNewSheet {
|
||||
ids.push(a.id);
|
||||
}
|
||||
this.picked = ids;
|
||||
/* 🔴 2026-11-06:回填完把豆瓣联想的任何残留态清干净。
|
||||
用户报的「片名下面列出来好几个条目(来自豆瓣)」就是联想列表 ——
|
||||
拦它的地方在 `titleField` 的 `onChange`(值没真变就不调度),
|
||||
这里负责兜底清一次,双保险。 */
|
||||
this.clearSuggestTimer();
|
||||
this.suggests = [];
|
||||
this.suggestOpen = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 影院名 → 影院 id。
|
||||
* 影院 id 的回填。
|
||||
*
|
||||
* ⚠️ 详情接口只给 `cinemaName`(不给 id)—— 查一下本地库那份影院列表。
|
||||
* 找不到(影院被删/改名)就返回 0,表单显示"未选影院",不会硬塞一个错的。
|
||||
* 🔴 2026-11-06:原来只能按**影院名**反查(因为 `FbMovieDetail` 里没有 id),
|
||||
* 用户报的「影院没带进来」就是这个 ——
|
||||
* · 影院重名(很多城市都有「万达影城」)⇒ 反查选中了错的那家;
|
||||
* · 影院改过名 ⇒ 旧记录的 `cinema_name` 与影院表对不上 ⇒ 直接回填失败。
|
||||
* 现在 `FbMovieDetail.cinemaId` 已经从接口接上了,**优先用 id**,
|
||||
* 只有 id 缺失(老数据 / 接口没给)时才退回按名字模糊匹配。
|
||||
*/
|
||||
private cinemaIdOf(name: string): number {
|
||||
if (name === '') {
|
||||
private cinemaIdOf(d: FbMovieDetail): number {
|
||||
if (d.cinemaId > 0) {
|
||||
// 确认这家影院还在本地列表里(可能已被删)
|
||||
const hit: FbCinemaItem | undefined =
|
||||
this.cinemas.find((c: FbCinemaItem): boolean => c.id === d.cinemaId);
|
||||
return hit !== undefined ? d.cinemaId : 0;
|
||||
}
|
||||
if (d.cinemaName === '') {
|
||||
return 0;
|
||||
}
|
||||
for (const c of this.cinemas) {
|
||||
if (c.name === name) {
|
||||
return c.id;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
const byName: FbCinemaItem | undefined =
|
||||
this.cinemas.find((c: FbCinemaItem): boolean => c.name === d.cinemaName);
|
||||
return byName !== undefined ? byName.id : 0;
|
||||
}
|
||||
|
||||
aboutToDisappear(): void {
|
||||
@@ -276,6 +305,34 @@ export struct FbMovieNewSheet {
|
||||
return out.join('、');
|
||||
}
|
||||
|
||||
/**
|
||||
* 「同看的人」那一行的文案。
|
||||
*
|
||||
* 🔴 2026-11-06:用户报「同看的人没带进来」。
|
||||
* 实际是**名字还没查出来时显示成 `已选 2 人:`(冒号后面空白)**,
|
||||
* 看起来像"什么都没带进来"。
|
||||
*
|
||||
* 根因是两条时序叠在一起:
|
||||
* ① `prefill()` 只填了 `picked`(id 数组),名字要靠 `roster` 反查;
|
||||
* ② `loadRoster()` 是**异步**的 —— 表单刚打开那一两帧里 `roster` 还是空,
|
||||
* 于是 `pickedNames()` 返回空串。
|
||||
*
|
||||
* ⇒ 分三种情况说清楚,不让用户看到"半句话":
|
||||
* · 没人选 → 「还没有选人」
|
||||
* · 有人但名字还没到 → 「已选 N 人(正在读取名字…)」
|
||||
* · 都有 → 「已选 N 人:张三、李四」
|
||||
*/
|
||||
private pickedLabel(): string {
|
||||
if (this.picked.length === 0) {
|
||||
return '还没有选人';
|
||||
}
|
||||
const names: string = this.pickedNames();
|
||||
if (names === '') {
|
||||
return '已选 ' + this.picked.length.toString() + ' 人(正在读取名字…)';
|
||||
}
|
||||
return '已选 ' + this.picked.length.toString() + ' 人:' + names;
|
||||
}
|
||||
|
||||
private cinemaLabel(): string {
|
||||
if (this.cinemaId <= 0) {
|
||||
return '不记录影院';
|
||||
@@ -579,10 +636,14 @@ export struct FbMovieNewSheet {
|
||||
FbSheetTitleBar({
|
||||
/* 编辑模式换个标题,用户一眼知道自己在改而不是新建 */
|
||||
title: this.editingId > 0 ? '编辑观影记录' : '记一场电影',
|
||||
subtitle: this.editingId > 0
|
||||
? '改完点下面保存;照片可以再补几张'
|
||||
: '片名和时间必填,其余可以留空',
|
||||
/* 🔴 用户 2026-11-06:「编辑观影记录这几个字,也应该是主题颜色才对,
|
||||
下面的一行小字(改完点下面保存,照片可以再补几张。),删除不要了。」
|
||||
⇒ ① 标题用主题色(见下面 `titleColor` 参数的来源);
|
||||
② **编辑模式不再传 subtitle** —— 那句说明是他明确要删的。
|
||||
新建模式保留原有的提示(那边还需要"片名和时间必填"这句引导)。 */
|
||||
subtitle: this.editingId > 0 ? '' : '片名和时间必填,其余可以留空',
|
||||
accent: this.accentColor(),
|
||||
titleColor: this.accentColor(),
|
||||
onClose: (): void => {
|
||||
this.onClose();
|
||||
}
|
||||
@@ -626,14 +687,9 @@ export struct FbMovieNewSheet {
|
||||
this.posterField()
|
||||
this.photoField()
|
||||
|
||||
this.areaField('简介', '剧情简介(可空,点「自动获取」从豆瓣填)', this.info,
|
||||
(v: string): void => {
|
||||
this.info = v;
|
||||
})
|
||||
this.areaField('备注', '想记的话(可空)', this.note,
|
||||
(v: string): void => {
|
||||
this.note = v;
|
||||
})
|
||||
this.infoField()
|
||||
|
||||
this.noteField()
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ top: 6, bottom: 4 })
|
||||
@@ -785,7 +841,19 @@ export struct FbMovieNewSheet {
|
||||
.layoutWeight(1)
|
||||
.padding({ left: 10, right: 10 })
|
||||
.onChange((v: string): void => {
|
||||
/* 🔴 2026-11-06:编辑模式回填时,`TextInput` 会把"刚被赋值的新内容"
|
||||
当成一次 onChange 回调(用户报的"片名下面列出来好几个条目")。
|
||||
⇒ 值没有真正变化时,不调度联想 ——
|
||||
回填是"值本来就是这个",不是"用户改了"。
|
||||
⚠️ 这里用"值比较"而不是 `prefilling` 标记:
|
||||
`prefill()` 是同步执行完的,标记会立刻复位;
|
||||
而 `onChange` 是在**下一帧**渲染时才回调的,
|
||||
那时标记早就 false 了,拦不住(我第一版就是这么写的,没用)。 */
|
||||
const changed: boolean = v !== this.title;
|
||||
this.title = v;
|
||||
if (!changed) {
|
||||
return;
|
||||
}
|
||||
this.scheduleSuggest();
|
||||
})
|
||||
|
||||
@@ -885,7 +953,13 @@ export struct FbMovieNewSheet {
|
||||
*/
|
||||
@Builder
|
||||
cinemaField() {
|
||||
Row({ space: 8 }) {
|
||||
/* 🔴 2026-11-06 结构性修正(与 `posterField` 同一条病):
|
||||
原来外层是 `Row { Text('影院').width('100%'); Row { 值…选择 } }`
|
||||
—— 标签 `width('100%')` 吃满整行,把内层 `Row` 挤到行外。
|
||||
真机症状:编辑表单里「影院」只剩一个标签,**下面的值整个不见了**
|
||||
(用户报「影院没带进来」,其实是根本没渲染)。
|
||||
⇒ 按《全局表单规范》改成上下两排:标签第一排、控件第二排整宽。 */
|
||||
Column({ space: 6 }) {
|
||||
/* GLOBAL-FORM-SPEC:标签独占第一排(主题色 Bold,必填红无星号) */
|
||||
Text('影院')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
@@ -893,6 +967,7 @@ export struct FbMovieNewSheet {
|
||||
.fontColor(this.accentColor())
|
||||
.width('100%')
|
||||
|
||||
/* 第二排:色点 + 当前选中 + 「选择」 */
|
||||
Row({ space: 8 }) {
|
||||
if (this.cinemaColor() !== '') {
|
||||
Text('')
|
||||
@@ -911,7 +986,7 @@ export struct FbMovieNewSheet {
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(this.accentColor())
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.width('100%')
|
||||
.height(42)
|
||||
.padding({ left: 12, right: 12 })
|
||||
.backgroundColor(FbColors.inputBg)
|
||||
@@ -922,7 +997,7 @@ export struct FbMovieNewSheet {
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.alignItems(VerticalAlign.Center)
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -934,7 +1009,12 @@ export struct FbMovieNewSheet {
|
||||
*/
|
||||
@Builder
|
||||
personField() {
|
||||
Row({ space: 8 }) {
|
||||
/* 🔴 2026-11-06 结构性修正(与 `cinemaField` / `posterField` 同一条病):
|
||||
原来外层 `Row` 里标签 `width('100%')` 把内层 `Row` 挤出可视区,
|
||||
真机症状是「同看的人」只剩标签、**下面的值整个不渲染**
|
||||
(用户报「同看的人没带进来」其实是根本没画)。
|
||||
⇒ 改成上下两排。 */
|
||||
Column({ space: 6 }) {
|
||||
/* GLOBAL-FORM-SPEC:标签独占第一排(主题色 Bold,必填红无星号) */
|
||||
Text('同看的人')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
@@ -942,10 +1022,9 @@ export struct FbMovieNewSheet {
|
||||
.fontColor(this.accentColor())
|
||||
.width('100%')
|
||||
|
||||
/* 第二排:已选人 + 「选择」 */
|
||||
Row({ space: 8 }) {
|
||||
Text(this.picked.length > 0
|
||||
? ('已选 ' + this.picked.length.toString() + ' 人:' + this.pickedNames())
|
||||
: '还没有选人')
|
||||
Text(this.pickedLabel())
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(this.picked.length > 0 ? FbColors.ink : FbColors.muted)
|
||||
.maxLines(1)
|
||||
@@ -956,7 +1035,7 @@ export struct FbMovieNewSheet {
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(this.accentColor())
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.width('100%')
|
||||
.height(42)
|
||||
.padding({ left: 12, right: 12 })
|
||||
.backgroundColor(FbColors.inputBg)
|
||||
@@ -972,7 +1051,7 @@ export struct FbMovieNewSheet {
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.alignItems(VerticalAlign.Center)
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1099,17 +1178,20 @@ export struct FbMovieNewSheet {
|
||||
@Builder
|
||||
photoField() {
|
||||
Column({ space: 6 }) {
|
||||
Row({ space: 8 }) {
|
||||
/* GLOBAL-FORM-SPEC:标签独占第一排(主题色 Bold,必填红无星号) */
|
||||
/* GLOBAL-FORM-SPEC:标签独占第一排(主题色 Bold,必填红无星号)
|
||||
🔴 2026-11-06:原来标签和内层 `Row` 挤在同一个外层 `Row` 里,
|
||||
标签 `width('100%')` 把内层挤出可视区 ⇒ 「选照片」按钮整个不渲染。
|
||||
改成上下两排(同 `cinemaField` / `personField` / `posterField`)。 */
|
||||
Text('观影照片')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
.fontColor(this.accentColor())
|
||||
.width('100%')
|
||||
|
||||
/* 第二排:说明/已选数 + 「选照片」 */
|
||||
Row({ space: 8 }) {
|
||||
Text(this.photoUris.length > 0
|
||||
? ('已选 ' + this.photoUris.length.toString() + ' 张')
|
||||
? ('已新选 ' + this.photoUris.length.toString() + ' 张')
|
||||
: '票根、合影、随手拍都可以放这儿')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(this.photoUris.length > 0 ? FbColors.ink : FbColors.muted)
|
||||
@@ -1120,7 +1202,7 @@ export struct FbMovieNewSheet {
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(this.accentColor())
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.width('100%')
|
||||
.height(42)
|
||||
.padding({ left: 12, right: 12 })
|
||||
.backgroundColor(FbColors.inputBg)
|
||||
@@ -1129,9 +1211,6 @@ export struct FbMovieNewSheet {
|
||||
.onClick((): void => {
|
||||
this.pickPhotos();
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
if (this.photoUris.length > 0) {
|
||||
Flex({ wrap: FlexWrap.Wrap }) {
|
||||
@@ -1163,12 +1242,53 @@ export struct FbMovieNewSheet {
|
||||
.width('100%')
|
||||
.padding({ left: 56 })
|
||||
}
|
||||
|
||||
/* 🔴 2026-11-06:**已有照片**(编辑模式)。
|
||||
用户报「观影照片没有带进来」—— 原来这里只渲染 `photoUris`
|
||||
(= 本次新选的本地图),而编辑时已有的图在 `existingPhotos` 里,
|
||||
完全没画出来 ⇒ 用户以为照片丢了,会重新选一遍。
|
||||
⚠️ 只**展示 + 说明**,不做删除:删照片是另一件事(要调服务端删附件),
|
||||
用户这轮只要求"带进来能看到"。
|
||||
⚠️ 条件读 `@Local existingPhotos`(不是调方法),
|
||||
否则 ArkUI 不建立依赖(同 `posterField` 那条教训)。 */
|
||||
if (this.existingPhotos.length > 0) {
|
||||
Column({ space: 6 }) {
|
||||
Text('已有 ' + this.existingPhotos.length.toString() + ' 张(保存后保持不变)')
|
||||
.fontSize(10)
|
||||
.fontColor(FbColors.faint)
|
||||
.width('100%')
|
||||
|
||||
Flex({ wrap: FlexWrap.Wrap }) {
|
||||
ForEach(this.existingPhotos, (k: string) => {
|
||||
Image(fbImageSrc(k, this.token()))
|
||||
.width(60)
|
||||
.height(60)
|
||||
.borderRadius(4)
|
||||
.objectFit(ImageFit.Cover)
|
||||
.alt($r('app.media.fb_ic_movies'))
|
||||
.margin({ right: 8, bottom: 8 })
|
||||
}, (k: string): string => 'exph_' + k)
|
||||
}
|
||||
.width('100%')
|
||||
}
|
||||
.width('100%')
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
}
|
||||
|
||||
/** 一行多行文本输入(简介 / 备注) */
|
||||
/**
|
||||
* 一行多行文本输入。
|
||||
*
|
||||
* ⚠️ **值参数 Builder 有坑**(本项目 `pitfalls.md` 记过):
|
||||
* `Builder(value)` 的值参数**不参与状态观察**,
|
||||
* `TextArea({ text: value })` 只拿得到首次调用时的字面量。
|
||||
* ⇒ 简介 / 备注**已改成独立 Builder**(`infoField` / `noteField`),
|
||||
* 直接在内部读 `this.info` / `this.note`,编辑回填才能显示出来。
|
||||
* ⇒ 这个通用版留着给"值不会变、或调用方自己保证重传"的场景用。
|
||||
*/
|
||||
@Builder
|
||||
areaField(label: string, placeholder: string, value: string, onChange: (v: string) => void) {
|
||||
/* GLOBAL-FORM-SPEC:标签独占第一排(原来是 .width(48) 硬凑) */
|
||||
@@ -1196,4 +1316,74 @@ export struct FbMovieNewSheet {
|
||||
.width('100%')
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
}
|
||||
|
||||
/**
|
||||
* 简介(横跨整个表单宽度)。
|
||||
*
|
||||
* 🔴 2026-11-06:**不再走 `areaField(...)`**,因为它是**值参数** Builder ——
|
||||
* `TextArea({ text: value })` 拿到的是**首次调用时**的字面量,
|
||||
* `prefill()` 在 `aboutToAppear` 里改了 `this.info` 之后,这里收不到新值
|
||||
* ⇒ 编辑表单里「简介」永远空白(用户报的"简介没带进来"就是这个)。
|
||||
*
|
||||
* 这与 `pitfalls.md` 的「@Builder 值参数不参与状态观察」是**同一条规则**:
|
||||
* ❌ `Builder(value)` —— 值参数,建不了依赖
|
||||
* ✅ Builder 内部直接读 `this.xxx`
|
||||
*
|
||||
* ⚠️ `areaField` 保留:它还被别的地方用(备注已改成独立 Builder,
|
||||
* 见下面 `noteField`)。真要彻底删得先确认没有其它调用点。
|
||||
*/
|
||||
@Builder
|
||||
infoField() {
|
||||
Column({ space: 6 }) {
|
||||
Text('简介')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
.fontColor(this.accentColor())
|
||||
.width('100%')
|
||||
|
||||
TextArea({ text: this.info, placeholder: '剧情简介(可空,点「自动获取」从豆瓣填)' })
|
||||
.placeholderColor(FbColors.faint)
|
||||
.placeholderFont({ size: FbMetric.fontTiny })
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.ink)
|
||||
.backgroundColor(FbColors.inputBg)
|
||||
.borderRadius(FbMetric.radiusS)
|
||||
.height(110)
|
||||
.width('100%')
|
||||
.padding({ left: 10, right: 10, top: 8, bottom: 8 })
|
||||
.onChange((v: string): void => {
|
||||
this.info = v;
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
}
|
||||
|
||||
/** 备注(同上,直接读 `this.note`) */
|
||||
@Builder
|
||||
noteField() {
|
||||
Column({ space: 6 }) {
|
||||
Text('备注')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
.fontColor(this.accentColor())
|
||||
.width('100%')
|
||||
|
||||
TextArea({ text: this.note, placeholder: '想记的话(可空)' })
|
||||
.placeholderColor(FbColors.faint)
|
||||
.placeholderFont({ size: FbMetric.fontTiny })
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.ink)
|
||||
.backgroundColor(FbColors.inputBg)
|
||||
.borderRadius(FbMetric.radiusS)
|
||||
.height(72)
|
||||
.width('100%')
|
||||
.padding({ left: 10, right: 10, top: 8, bottom: 8 })
|
||||
.onChange((v: string): void => {
|
||||
this.note = v;
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
}
|
||||
}
|
||||
@@ -96,6 +96,17 @@ export struct FbSheetTitleBar {
|
||||
@Param confirmText: string = '';
|
||||
/** 主题色(标题下的副标题、确认文字用它) */
|
||||
@Param accent: string = '';
|
||||
/**
|
||||
* 主标题的颜色(空 = 用正文色 `FbColors.ink`)。
|
||||
*
|
||||
* 🔴 用户 2026-11-06:「编辑观影记录这几个字,也应该是主题颜色才对。」
|
||||
*
|
||||
* ⚠️ 为什么单独开一个参数,而不是直接让 `title` 用 `accent`:
|
||||
* 本组件被**全站浮层**引用(设置类、影院管理、任务面板…),
|
||||
* 标题默认必须是正文色(深墨),只有明确要求的浮层才上主题色。
|
||||
* `@Param` 带默认空串 ⇒ 老调用点一行都不用改。
|
||||
*/
|
||||
@Param titleColor: string = '';
|
||||
/** 同上:回调统一 `@Event`(`@Param` 实测也能用,但语义不对) */
|
||||
@Event onClose: () => void = (): void => {};
|
||||
|
||||
@@ -106,7 +117,7 @@ export struct FbSheetTitleBar {
|
||||
Text(this.title)
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
.fontColor(this.titleColor !== '' ? this.titleColor : FbColors.ink)
|
||||
.maxLines(1)
|
||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||
}
|
||||
|
||||
@@ -278,6 +278,19 @@ export interface FbMovieDetail {
|
||||
cinemaPlace: string;
|
||||
cinemaAddress: string;
|
||||
cinemaColor: string;
|
||||
/**
|
||||
* 影院 id(0 = 没选)。
|
||||
*
|
||||
* 🔴 2026-11-06:**这个字段原来漏了**,`parseMovieDetail` 没解析 `cinema_id`。
|
||||
* 后果:编辑观影记录时「影院」永远是「不记录影院」—— 用户报的
|
||||
* 「影院没带进来」就是这个。
|
||||
*
|
||||
* 为什么必须有 id 而不能只靠 `cinemaName` 反查:
|
||||
* · 影院可能**重名**(「万达影城」很多城市都有)⇒ 按名字 find 会选错;
|
||||
* · 影院被改名后,旧记录里的 `cinema_name` 与影院表对不上 ⇒ 反查失败;
|
||||
* · 服务端 `Movie::find()` 本来就 `SELECT ... d.cinema_id`,只是端上没接。
|
||||
*/
|
||||
cinemaId: number;
|
||||
/** 我的评分(0 = 没评)。⚠️ 本版恒为 0:详情接口不返回"我的评价" */
|
||||
myRating: number;
|
||||
/** 我的评价正文。⚠️ 同上 */
|
||||
@@ -1506,6 +1519,15 @@ interface FbApiMovieDetail {
|
||||
cinema_place?: string | null;
|
||||
cinema_address?: string | null;
|
||||
cinema_color?: string | null;
|
||||
/**
|
||||
* 影院 id。
|
||||
*
|
||||
* 🔴 2026-11-06:**这个字段原来漏了**(接口真实返回有它,端上没声明也没解析)。
|
||||
* 服务端 `Movie::find()` 的 SELECT 里明确有 `d.cinema_id`;
|
||||
* 少声明它的后果是编辑表单里「影院」永远回填不了(只能靠名字反查,
|
||||
* 而名字会重名/被改名)。
|
||||
*/
|
||||
cinema_id?: number | null;
|
||||
attendees?: FbApiAttendee[];
|
||||
/**
|
||||
* ⚠️ 照片是**对象数组**(`[{id, key}]`),不是字符串数组 ——
|
||||
@@ -1526,7 +1548,7 @@ function emptyDetail(id: number, error: string): FbMovieDetail {
|
||||
return {
|
||||
ok: false, error: error, id: id, title: '', startsAt: '', endsAt: '', note: '',
|
||||
info: '', posterKey: '', ticketKey: '', cinemaName: '', cinemaPlace: '',
|
||||
cinemaAddress: '', cinemaColor: '', myRating: 0, myReview: '',
|
||||
cinemaAddress: '', cinemaColor: '', cinemaId: 0, myRating: 0, myReview: '',
|
||||
reviewCount: 0, reviewAvg: 0, attendees: [], photos: []
|
||||
};
|
||||
}
|
||||
@@ -1579,6 +1601,8 @@ function parseDetail(id: number, json: string): FbMovieDetail {
|
||||
cinemaPlace: strOf(o.cinema_place),
|
||||
cinemaAddress: strOf(o.cinema_address),
|
||||
cinemaColor: strOf(o.cinema_color),
|
||||
/* 🔴 2026-11-06:补上 `cinema_id`(原来漏解析,导致编辑表单里影院回填不了)。 */
|
||||
cinemaId: numOf(o.cinema_id),
|
||||
// ⚠️ 「我的评价」与「评分汇总」详情接口都**不返回**(实测确认)——
|
||||
// 前者要拉子资源,后者由调用方从列表项带过来。
|
||||
myRating: 0,
|
||||
|
||||
Reference in New Issue
Block a user