v63+v64:影院管理补齐(v63)与观影「记一场」豆瓣/弹框选择(v64)

⚠️ 本提交含**两批**改动(同一批文件里交织,无法按文件拆开):
  · v63(影院管理与搜索/统计/年报拆成平级入口)—— 本会话之外的工作,
    随本轮一并提交以免工作区长期留半成品;
  · v64(本会话,用户 2026-10-19 六条)。

v64 六条:
① 片名可经豆瓣自动获取简介与海报(与网页端同一份服务端输出:
   summary 全文 + 已落盘的 poster_key);简介**只填空白的**,不覆盖用户写过的。
   联想 320ms 防抖(与网页端同值)。
② 开始/结束改成**与日程同一个**日期时间选择控件 —— 新抽 components/FbDateTimeField.ets
   (行 + 弹层 + 一组换算函数),日程那边旧的自绘实现整段删掉改用它
   (用户要的就是"两处一样")。
③ 影院、同看人改成**弹框选择**:
   · FbCinemaPickSheet:顶部搜索框,匹配「地方 + 名称」,「不记录影院」恒在列表;
   · FbPersonPickSheet:顶部搜索框,拼音首字母**子序列**匹配(yyq 打成 yq 也能搜到),
     多选走"暂存 + 确定"两段式(点一个不关窗)。
   两排横向胶囊/头像全删(用户:"这种方式太难找了")。
④ 允许上传**多张**图片:海报 1 张 + 观影照片可一次选 9 张、可多次追加、可逐张删。
   ⚠️ 顺序是**先建记录再传图**(建记录失败就不会留孤儿图);图失败**不挡记录**。
⑤ 删掉「地点」输入口(location 字段仍发,网页端与历史数据不受影响)。
⑥ 观影页搜索框改明显:白底 + 实色描边 + 图标放大到 18 并染主题色 +
   占位文字提到 muted/fontBody + 右侧「筛选 ›」暗示(用户:"根本看不清")。

FbMovieRepo 新增 doubanSuggest / doubanFetch / uploadPoster / uploadPhoto
(multipart 手写,与头像上传同一形态,抽成共用的 uploadOne)。

验证:静态守卫 636/0 · 端上反向验证 87/0 · 接口 e2e 57/0 · 服务端反向验证 18/0 ·
本地全量 e2e 1521/0 · hvigor 编译成功并已装机。
⚠️ 真机验证脚本 _fb_v64_verify.py 已写好(六条全覆盖),因设备锁屏未能执行。
This commit is contained in:
2026-10-20 07:35:37 +08:00
parent 4a063114c5
commit 8146aab1a7
10 changed files with 3519 additions and 1040 deletions
+148 -32
View File
@@ -31,6 +31,8 @@ import { FbDb } from '../../data/FbDb';
import { FbMovieDetail, FbMovieListItem, FbMovieRepo, FbSyncResult } from '../../data/FbMovieRepo';
import { FbMovieDetailSheet } from '../../components/FbMovieDetailSheet';
import { FbCinemaSheet } from '../../components/FbCinemaSheet';
import { FbMovieNewSheet } from '../../components/FbMovieNewSheet';
import { FbMovieInsightSheet } from '../../components/FbMovieInsightSheet';
import { FbModuleSettingsSheet } from '../../components/FbModuleSettingsSheet';
import { FB_BACK } from '../../common/FbBack';
import { FB_SET_MOVIES } from '../../model/FbSettingsMenus';
@@ -131,6 +133,25 @@ export struct FbMoviesPage {
@Local settingsOpen: boolean = false;
/** 影院浮层是否打开 */
@Local cinemaSheetOpen: boolean = false;
/**
* 「记一场」新建浮层是否打开。
*
* 🔴 v63(用户 2026-10-19):「电影新增一场、新增一个电影,也就是说
* "记一场"这个功能现在可以开始做了。」
* ⇒ 那颗按钮原来**没有回调**(纯摆设),点了什么都不发生。
*/
@Local newOpen: boolean = false;
/**
* 高级搜索 / 统计 / 年度报告浮层是否打开。
*
* 🔴 v63:这三项**从「影院管理」里搬出来**,成为设置菜单里的独立入口
* (用户:「影院管理就是管理影院,不要把统计搜索和年报放在这里面」)。
* ⚠️ 用**一个** open 位 + 一个 mode 串(三者共用一个组件,
* 见 `FbMovieInsightSheet` 的说明),不开三个 boolean。
*/
@Local insightOpen: boolean = false;
/** `search` / `stats` / `report`(配合 `insightOpen`) */
@Local insightMode: string = 'stats';
/**
* 🔴 浮层开合时**让底部悬浮栏让位**(与名册页同一处 bug,见 `FbPeoplePage` 的说明)。
@@ -139,10 +160,11 @@ export struct FbMoviesPage {
* ⇒ 悬浮栏层级更高 ⇒ 浮层底部的按钮会与它重叠、点击被抢走。
* ⇒ 用 `@Monitor` 一处声明、自动跟随派生(避免在多个赋值点手写、漏一处就出事)。
*/
@Monitor('sheetOpen', 'cinemaSheetOpen', 'settingsOpen')
@Monitor('sheetOpen', 'cinemaSheetOpen', 'settingsOpen', 'newOpen', 'insightOpen')
onSheetChanged(): void {
const app: FbAppState = fbAppState();
app.sheetCount = (this.sheetOpen || this.cinemaSheetOpen || this.settingsOpen) ? 1 : 0;
app.sheetCount = (this.sheetOpen || this.cinemaSheetOpen || this.settingsOpen
|| this.newOpen || this.insightOpen) ? 1 : 0;
}
/** 模块主题色(观影 = 紫) */
private accent: string = '#7A4E8C';
@@ -165,6 +187,12 @@ export struct FbMoviesPage {
}
private backOnce(): boolean {
if (this.newOpen) {
this.newOpen = false;
}
if (this.insightOpen) {
this.insightOpen = false;
}
if (this.settingsOpen) {
this.settingsOpen = false;
return true;
@@ -426,6 +454,58 @@ export struct FbMoviesPage {
.transition(fbSheetTransition())
}
// ---- 「记一场」新建浮层(用户 v63:「记一场这个功能现在可以开始做了」)----
if (this.newOpen) {
Column()
.width('100%')
.height('100%')
.backgroundColor(FB_MASK)
.transition(fbMaskTransition())
.onClick((): void => {
this.newOpen = false;
})
FbMovieNewSheet({
cinemas: this.cinemas,
accent: this.accent,
/* 🔴 v64:「同看的人」默认要带上**我**(与网页端 v38 同口径:
「自己不用选择就应该已经选上了」)。登录者 id 从全局会话取,
取不到(0)时浮层就不预置任何人 —— 不会硬塞一个错的 id 进 attendees。 */
meId: this.app.session.me.id,
onClose: (): void => {
this.newOpen = false;
},
/* 新建成功 ⇒ 重读本地库(`createMovie` 里已经重拉过接口并落库,
这里只是把页面的列表刷出来)。 */
onCreated: (): void => {
this.reloadFromLocal();
}
})
.transition(fbSheetTransition())
}
// ---- 高级搜索 / 统计 / 年度报告(三个入口共用这一个浮层)----
if (this.insightOpen) {
Column()
.width('100%')
.height('100%')
.backgroundColor(FB_MASK)
.transition(fbMaskTransition())
.onClick((): void => {
this.insightOpen = false;
})
FbMovieInsightSheet({
mode: this.insightMode,
cinemas: this.cinemas,
accent: this.accent,
onClose: (): void => {
this.insightOpen = false;
}
})
.transition(fbSheetTransition())
}
// ---- 模块设置浮层(三条横线点开的那个)----
if (this.settingsOpen) {
Column()
@@ -446,10 +526,22 @@ export struct FbMoviesPage {
this.settingsOpen = false;
},
onPick: (action: string): void => {
// 目前只有「影院管理」是已实现的;其余在菜单里显示「暂未开放」
/* 🔴 v63:四条**全部接上**。
⚠️ 三条"洞察类"(搜索/统计/年报)走**同一个浮层 + 不同 mode**,
不是三个浮层(见 `FbMovieInsightSheet` 的说明)。
⚠️ 先关设置再开目标浮层 —— 两者不叠(叠起来会看到两层遮罩)。 */
this.settingsOpen = false;
if (action === 'cinema') {
this.settingsOpen = false;
this.cinemaSheetOpen = true;
} else if (action === 'moviesearch') {
this.insightMode = 'search';
this.insightOpen = true;
} else if (action === 'moviestats') {
this.insightMode = 'stats';
this.insightOpen = true;
} else if (action === 'moviereport') {
this.insightMode = 'report';
this.insightOpen = true;
}
}
})
@@ -481,7 +573,12 @@ export struct FbMoviesPage {
FbPrimaryButton({
label: '记一场',
bg: this.accent,
icon: $r('app.media.fb_ic_plus')
icon: $r('app.media.fb_ic_plus'),
/* 🔴 v63:**接上回调**(原来这颗按钮没有 `onTap`,
点了什么都不发生 —— 用户:「记一场这个功能现在可以开始做了」)。 */
onTap: (): void => {
this.newOpen = true;
}
})
// 🔴 设置按钮 = 网页版那种**三条横线**(☰);「影院」也不单独占抽屉位置,
// 它是观影模块的一部分,从设置菜单里进(与网页版一致)。
@@ -499,42 +596,61 @@ export struct FbMoviesPage {
.alignItems(VerticalAlign.Center)
// ---- 搜索条 ----
Row({ space: 6 }) {
//
// 🔴 v64(用户 2026-10-19):「**电影的这个搜索框,非常不明显,根本看不清。**」
//
// 原来那版的三个毛病(改前):
// · 底色是 `FbColors.inputBg`(`#F2EDE2`)—— 与页面底色 `paper` 几乎同色,
// 整条**看不出是个框**,像一行普通文字飘在那儿;
// · 图标 15vp + `faint`(`#A8988A`)—— 对比度太低,真机上几乎看不见;
// · 文字也是 `faint` 且只有 `fontSmall`,与背景糊成一片。
//
// 现在的做法(四件事一起做才"看得清",只做一件仍然发飘):
// ① 底色给**白**(`surface`)—— 与 paper 底形成面与面的区分;
// ② 加一圈**实色描边**(`line`)—— 边界是硬边,不依赖光照假设;
// ③ 图标放大到 18 且改用主题色 —— 它同时也是"可点"的视觉暗示;
// ④ 占位文字从 `faint` 提到 `muted`、字号从 `small` 提到 `body`。
Row({ space: 8 }) {
Image($r('app.media.fb_ic_search'))
.width(15)
.height(15)
.width(18)
.height(18)
.renderMode(ImageRenderMode.Template)
.colorFilter(fbTint(FbTint.faint))
.colorFilter(fbTint(this.accent))
Text('搜索片名、影院、观影人…')
.fontSize(FbMetric.fontSmall)
.fontColor(FbColors.faint)
.fontSize(FbMetric.fontBody)
.fontColor(FbColors.muted)
.layoutWeight(1)
.maxLines(1)
}
.height(36)
.padding({ left: 12, right: 12 })
.backgroundColor(FbColors.inputBg)
.borderRadius(FbMetric.radiusS)
.margin({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 4 })
.alignItems(VerticalAlign.Center)
// ---- 影院数(可点:看影院列表)----
// 🔴 之前这是个**纯文字**(只能看数字);现在点它能看到名单 ——
// Web 端「影院管理」就在观影模块里,这里给它一个入口。
Row({ space: 6 }) {
Blank()
Text('影院 ' + this.cinemas.length.toString() + ' 家')
// 右侧一枚"进入"提示 —— 让这一条**看着就是可以点的**(原来完全没有暗示)
Text('筛选 ›')
.fontSize(FbMetric.fontTiny)
.fontColor(FbColors.faint)
Text('›')
.fontSize(FbMetric.fontSmall)
.fontColor(FbColors.faint)
.fontColor(this.accent)
}
.width('100%')
.height(42)
.padding({ left: 12, right: 12 })
.backgroundColor(FbColors.surface)
.borderRadius(FbMetric.radiusS)
.border({ width: 1, color: FbColors.line })
.margin({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 8 })
.alignItems(VerticalAlign.Center)
/* 🔴 v63(用户 2026-10-19):「"搜索"这个功能也可以开始做了,
**现在点搜索还没有反应**。」
⇒ 这原来是个**纯展示的假输入框**(一个 Row + 一行占位文字,
连点击回调都没有 —— 所以点了没有任何反应)。
现改成**点它打开「高级搜索」** —— 与设置菜单里那一条是同一个浮层,
这样"看到搜索框 → 点它 → 就能搜"符合直觉。
⚠️ 不在这里做"就地输入过滤":观影搜索是**组合条件 + 要跨影院/日期**,
就地过滤只能按片名,做出来会让用户以为"搜不到就是没有"。 */
.onClick((): void => {
this.cinemaSheetOpen = true;
this.insightMode = 'search';
this.insightOpen = true;
})
.padding({ left: FbMetric.pagePad + 4, right: FbMetric.pagePad + 4, top: 6, bottom: 6 })
/* 🔴 v63(用户 2026-10-19):「**观影列表里面也有个"影院26家",
也不知道是什么意思,没有用,要把它删掉。**都在影院管理、都在设置里面
就可以了。」
⇒ 整行删除。影院列表现在只从**设置 ☰ → 影院管理**进
(以前这一行也是"影院管理"的入口,与设置里的那条重复)。 */
// ---- 同步状态提示(只在有话说时显示,不占位)----