240 lines
9.0 KiB
Plaintext
240 lines
9.0 KiB
Plaintext
/**
|
||||
|
|
* 家书 FamilyBook · 「选择影院」弹框(鸿蒙端)
|
|||
|
|
*
|
|||
|
|
* 🔴 v64(用户 2026-10-19)原话:
|
|||
|
|
* 「然后另外**影院都应该是弹框选择**…这种选择方式太难找了。」
|
|||
|
|
* 「这个弹框选择要跟**网页端的一样**,在上面有个筛选框。」
|
|||
|
|
*
|
|||
|
|
* ⇒ 观影「记一场」原来把影院做成一排**横向胶囊**(要左右划着找,影院一多就找不到)。
|
|||
|
|
* 网页端(`templates/movies/form.php` 的 `#cinemaPicker`)是:
|
|||
|
|
* **一个按钮 → 弹出列表 → 列表顶部有搜索框 → 点一项选中**。
|
|||
|
|
* 这里按同一形态做,并保留网页端已有的两条细节:
|
|||
|
|
* · 「不记录影院」这一项**恒在列表最上方**(它是"清空选择"的出口,
|
|||
|
|
* 被搜没了用户就取消不掉了 —— 网页端注释里点明过);
|
|||
|
|
* · 搜索匹配「地方 + 名称」(网页端 `data-text` 就是这两段拼的),
|
|||
|
|
* 并且**不区分大小写、按子串**(与网页 `indexOf` 同一口径)。
|
|||
|
|
*
|
|||
|
|
* ⚠️ 为什么与人员选择器**不合并成一个组件**:
|
|||
|
|
* 两者的候选源完全不同(影院有颜色/地方;人有意/头像/拼音首字母),
|
|||
|
|
* 筛选规则也不同(影院按名称子串,人要按拼音**子序列**)。
|
|||
|
|
* 硬合并会变成一个"什么都做、什么都做不专"的组件,且参数会膨胀到十几个。
|
|||
|
|
* 共用的部分已经抽在 `FbSheetRing` / `FbSheetClose` 里了 —— 那才是真正重复的。
|
|||
|
|
*
|
|||
|
|
* ⚠️ 选中即关闭(单选语义):
|
|||
|
|
* 影院是单选(一条观影记录只可能在一家影院看),没有"暂存 + 确定"的必要 ——
|
|||
|
|
* 那一步只会让用户白点一次。多选的是「同看的人」,那边才有暂存区。
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
import { FbColors, FbMetric, FB_MASK, fbAlpha, fbTint, FbTint } from '../common/Theme';
|
|||
|
|
import { FbModuleDef, fbModuleOf, fbThemeVars } from '../model/Modules';
|
|||
|
|
import { FbCinemaItem } from '../data/FbMovieRepo';
|
|||
|
|
import { FbSheetRing } from './FbSheetRing';
|
|||
|
|
import { FbSheetClose } from './FbSheetClose';
|
|||
|
|
|
|||
|
|
@ComponentV2
|
|||
|
|
export struct FbCinemaPickSheet {
|
|||
|
|
/** 候选影院(本地库,通常几十家) */
|
|||
|
|
@Param cinemas: FbCinemaItem[] = [];
|
|||
|
|
/** 当前选中的影院 id(0 = 不记录影院) */
|
|||
|
|
@Param selected: number = 0;
|
|||
|
|
/** 环色(hex,'' = 观影模块色) */
|
|||
|
|
@Param accent: string = '';
|
|||
|
|
|
|||
|
|
/** 选中一家(0 = 不记录影院) */
|
|||
|
|
@Event onPick: (id: number) => void = (): void => {
|
|||
|
|
};
|
|||
|
|
/** 关闭(不改变选择) */
|
|||
|
|
@Event onClose: () => void = (): void => {
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
@Local keyword: string = '';
|
|||
|
|
|
|||
|
|
private accentColor(): string {
|
|||
|
|
if (this.accent !== '') {
|
|||
|
|
return this.accent;
|
|||
|
|
}
|
|||
|
|
const m: FbModuleDef | undefined = fbModuleOf('movies');
|
|||
|
|
return fbThemeVars(m !== undefined ? m.color : '#A84A28').brand;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 影院的显示名(「地方 · 名称」;没有地方就只是名称 —— 与网页端同口径) */
|
|||
|
|
private labelOf(c: FbCinemaItem): string {
|
|||
|
|
return c.place !== '' ? (c.place + ' · ' + c.name) : c.name;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 按关键词过滤。
|
|||
|
|
*
|
|||
|
|
* ⚠️ 与网页端 `filter()` **同一口径**:`toLowerCase().indexOf(kw) !== -1`。
|
|||
|
|
* 用子串而不是前缀 —— 用户常常只记得"万象城"却记不全影院名。
|
|||
|
|
* ⚠️ 匹配的是「地方 + 名称」拼起来的串(网页端 `data-text` 就是这个):
|
|||
|
|
* 只匹配名称的话,按"青岛"就搜不出青岛那几家。
|
|||
|
|
*/
|
|||
|
|
private filtered(): FbCinemaItem[] {
|
|||
|
|
const kw: string = this.keyword.trim().toLowerCase();
|
|||
|
|
if (kw === '') {
|
|||
|
|
return this.cinemas;
|
|||
|
|
}
|
|||
|
|
return this.cinemas.filter((c: FbCinemaItem): boolean =>
|
|||
|
|
this.labelOf(c).toLowerCase().indexOf(kw) >= 0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
build() {
|
|||
|
|
// 整屏遮罩 + 居中卡片(与全应用浮层同一形态)
|
|||
|
|
Column() {
|
|||
|
|
Column()
|
|||
|
|
.width('100%')
|
|||
|
|
.height('100%')
|
|||
|
|
.backgroundColor(FB_MASK)
|
|||
|
|
.onClick((): void => {
|
|||
|
|
this.onClose();
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
Column() {
|
|||
|
|
FbSheetRing({ radius: FbMetric.radius, ringColor: this.accentColor() }) {
|
|||
|
|
Column({ space: 10 }) {
|
|||
|
|
// ---- 标题栏(统一版式:左标题 + 右 ✕)----
|
|||
|
|
Row({ space: 8 }) {
|
|||
|
|
Column({ space: 2 }) {
|
|||
|
|
Text('选择影院')
|
|||
|
|
.fontSize(FbMetric.fontTitle)
|
|||
|
|
.fontWeight(FontWeight.Medium)
|
|||
|
|
.fontColor(FbColors.ink)
|
|||
|
|
.maxLines(1)
|
|||
|
|
Text('共 ' + this.cinemas.length.toString() + ' 家')
|
|||
|
|
.fontSize(FbMetric.fontTiny)
|
|||
|
|
.fontColor(this.accentColor())
|
|||
|
|
.maxLines(1)
|
|||
|
|
}
|
|||
|
|
.alignItems(HorizontalAlign.Start)
|
|||
|
|
.layoutWeight(1)
|
|||
|
|
.flexShrink(1)
|
|||
|
|
|
|||
|
|
FbSheetClose({
|
|||
|
|
onClose: (): void => {
|
|||
|
|
this.onClose();
|
|||
|
|
},
|
|||
|
|
color: this.accentColor()
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
.width('100%')
|
|||
|
|
.alignItems(VerticalAlign.Center)
|
|||
|
|
|
|||
|
|
// ---- 搜索框(用户点名要"跟网页端一样,上面有个筛选框")----
|
|||
|
|
Row({ space: 6 }) {
|
|||
|
|
Image($r('app.media.fb_ic_search'))
|
|||
|
|
.width(15)
|
|||
|
|
.height(15)
|
|||
|
|
.renderMode(ImageRenderMode.Template)
|
|||
|
|
.colorFilter(fbTint(FbTint.faint))
|
|||
|
|
TextInput({ text: this.keyword, placeholder: '搜影院(地方或名称)' })
|
|||
|
|
.placeholderColor(FbColors.faint)
|
|||
|
|
.placeholderFont({ size: FbMetric.fontTiny })
|
|||
|
|
.fontSize(FbMetric.fontSmall)
|
|||
|
|
.fontColor(FbColors.ink)
|
|||
|
|
.backgroundColor(Color.Transparent)
|
|||
|
|
.height(38)
|
|||
|
|
.layoutWeight(1)
|
|||
|
|
.padding(0)
|
|||
|
|
.onChange((v: string): void => {
|
|||
|
|
this.keyword = v;
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
.width('100%')
|
|||
|
|
.padding({ left: 10, right: 10 })
|
|||
|
|
.backgroundColor(FbColors.inputBg)
|
|||
|
|
.borderRadius(FbMetric.radiusS)
|
|||
|
|
.alignItems(VerticalAlign.Center)
|
|||
|
|
|
|||
|
|
// ---- 列表 ----
|
|||
|
|
Scroll() {
|
|||
|
|
Column({ space: 6 }) {
|
|||
|
|
// 「不记录影院」**恒在**(哪怕正在搜索)—— 它是取消选择的唯一出口
|
|||
|
|
this.pickRow(0, '不记录影院', '', true)
|
|||
|
|
|
|||
|
|
ForEach(this.filtered(), (c: FbCinemaItem) => {
|
|||
|
|
this.pickRow(c.id, this.labelOf(c), c.color, false)
|
|||
|
|
}, (c: FbCinemaItem): string => 'cp_' + c.id.toString())
|
|||
|
|
|
|||
|
|
if (this.filtered().length === 0) {
|
|||
|
|
Text('没有匹配的影院')
|
|||
|
|
.fontSize(FbMetric.fontTiny)
|
|||
|
|
.fontColor(FbColors.faint)
|
|||
|
|
.width('100%')
|
|||
|
|
.textAlign(TextAlign.Center)
|
|||
|
|
.padding({ top: 18, bottom: 18 })
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
.width('100%')
|
|||
|
|
}
|
|||
|
|
.constraintSize({ maxHeight: 320 })
|
|||
|
|
.width('100%')
|
|||
|
|
.scrollBar(BarState.Auto)
|
|||
|
|
.align(Alignment.TopStart)
|
|||
|
|
}
|
|||
|
|
.width('100%')
|
|||
|
|
.padding({ left: 18, right: 18, top: 18, bottom: 18 })
|
|||
|
|
.alignItems(HorizontalAlign.Start)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
.width('92%')
|
|||
|
|
}
|
|||
|
|
.width('100%')
|
|||
|
|
.height('100%')
|
|||
|
|
.justifyContent(FlexAlign.Center)
|
|||
|
|
.hitTestBehavior(HitTestMode.Transparent)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 一行影院。
|
|||
|
|
*
|
|||
|
|
* @param id 影院 id(0 = 不记录影院)
|
|||
|
|
* @param text 显示名
|
|||
|
|
* @param color 影院主题色(空 = 画一个虚线空心点,与网页端 `is-none` 一致)
|
|||
|
|
* @param isNone 是不是"不记录影院"那一项
|
|||
|
|
*/
|
|||
|
|
@Builder
|
|||
|
|
pickRow(id: number, text: string, color: string, isNone: boolean) {
|
|||
|
|
Row({ space: 10 }) {
|
|||
|
|
// 色点:影院色;「不记录影院」画空心圈
|
|||
|
|
if (isNone || color === '') {
|
|||
|
|
Text('')
|
|||
|
|
.width(12)
|
|||
|
|
.height(12)
|
|||
|
|
.borderRadius(6)
|
|||
|
|
.border({ width: 1.5, color: FbColors.faint })
|
|||
|
|
} else {
|
|||
|
|
Text('')
|
|||
|
|
.width(12)
|
|||
|
|
.height(12)
|
|||
|
|
.borderRadius(6)
|
|||
|
|
.backgroundColor(color)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Text(text)
|
|||
|
|
.fontSize(FbMetric.fontSmall)
|
|||
|
|
.fontColor(FbColors.ink)
|
|||
|
|
.maxLines(1)
|
|||
|
|
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
|||
|
|
.layoutWeight(1)
|
|||
|
|
|
|||
|
|
// 选中态:右侧一个对勾(比"整行变色"更明确 —— 列表底色本来就浅)
|
|||
|
|
if (id === this.selected) {
|
|||
|
|
Text('✓')
|
|||
|
|
.fontSize(FbMetric.fontSmall)
|
|||
|
|
.fontColor(this.accentColor())
|
|||
|
|
.fontWeight(FontWeight.Medium)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
.width('100%')
|
|||
|
|
.height(46)
|
|||
|
|
.padding({ left: 12, right: 12 })
|
|||
|
|
.backgroundColor(id === this.selected
|
|||
|
|
? fbAlpha(this.accentColor(), 0.10) : FbColors.inputBg)
|
|||
|
|
.borderRadius(FbMetric.radiusS)
|
|||
|
|
.alignItems(VerticalAlign.Center)
|
|||
|
|
.onClick((): void => {
|
|||
|
|
this.onPick(id);
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|