Files
FamilyBook_harmonyos/entry/src/main/ets/components/FbCinemaSheet.ets
T
laoyang 776c95ba0b v0.7.0 接上真实服务器:登录 / 设备会话 / 全量同步与各模块真实数据
从「演示数据壳子」走到「真的能连家里的服务器用」。

登录(用户 2026-10-17「完整的登录功能」)
  · 首次启动先填服务器地址 → /api/v1/ping 自报门户(认得出"这地址上真是家书")
  · 手机号或用户名 + 密码 → 拿令牌;失败提示统一,不泄露账号是否存在
  · FbDeviceStore:首次启动生成并持久化设备 UUID(三方应用拿不到 UDID),
    设备名取机型(marketName),并上报 platform / app_version

设备会话(v57,用户 2026-10-17)
  · 一把令牌 = 一台设备的一条会话;每次启动打一次 /me(有网就验)
  · 两种锁分开:need_login(401/被踢 → 清令牌)与 offline_expired
    (离线超 24h → 只锁界面、不清数据、联网自动恢复)——
    混成一种就会违背用户要的"一直联网就不用每天输密码"
  · 运行期任何请求 401 → 网络层全局回调立刻锁定跳登录页(FbHttp 的 authProbe
    让登录/校验类请求跳过,避免"两个地方同时跳登录页")
  · 退出登录:先清本地令牌(主防线),再尽力通知服务端
  · 设置页显示版本号与「这台设备」

数据层
  · FbDb / FbSchema / FbParse / 各 Repo(日程 / 名册 / 观影 / 模块)+ FbSyncAll
  · 首页改为服务端 /home(农历/节气/纪念日/生日/天气/统计/往年的今天)
  · 日程页用 FbDayEventPanel,与网页端显示方式/红线/并排三处一致

验证:ArkTS 静态守卫 231/0;hvigor 编译 BUILD SUCCESSFUL(entry-default-signed.hap)。
2026-10-18 03:36:04 +08:00

158 lines
6.2 KiB
Plaintext

/**
* 家书 FamilyBook · 影院列表浮层(鸿蒙端)
*
* 观影模块的"影院管理"入口(Web 端在观影页的设置里)。
*
* 🔴 为什么影院**不占抽屉里的位置**:
* 它不是独立模块,是"观影"的一部分 —— 一家影院只有"在哪、
* 叫什么、在那看过几部"这三个属性,独立成一页太空。
* ⇒ 做成观影页里的浮层(与详情、设置同一套交互)。
*
* ⚠️ 数据来自**本地库**(观影同步时一起落库的 `cinemas` 表),
* 不是现拉接口 —— 见 `FbMovieRepo.cinemaList()` 的说明。
*/
import { FbColors, FbMetric } from '../common/Theme';
import { FbCinemaItem } from '../data/FbMovieRepo';
/**
* 贴底卡片的手势区缓冲(vp)。
*
* 🔴 应用开了全屏布局且**隐藏了系统手势条**(见 EntryAbility)——
* 手势区不再有任何视觉提示,贴底的卡片必须自己让出这一段,
* 否则最后一排按钮会被"从下往上滑"误触。
*/
const FB_SHEET_BOTTOM_GAP: number = 16;
@ComponentV2
export struct FbCinemaSheet {
/** 影院列表 */
@Param cinemas: FbCinemaItem[] = [];
/** 关闭回调 */
@Event onClose: () => void = (): void => {
};
build() {
// FB_WRAP_CENTER:把卡片**垂直居中**(用户 2026-10-16 要求:浮在屏幕中间)。
// ⚠️ 居中放在**组件内部**做,而不是改宿主的 Stack 对齐 ——
// 宿主 Stack 里还有别的子元素(日程页的悬浮 +),改对齐会连带挪走它们。
// ⚠️ 外层铺满 + Center;内层仍是那张 92% 宽的圆角卡。
Column() {
Column({ space: 0 }) {
// ---- 标题 ----
Row() {
Column({ space: 2 }) {
Text('影院')
.fontSize(FbMetric.fontTitle)
.fontWeight(FontWeight.Medium)
.fontColor(FbColors.ink)
Text(this.cinemas.length.toString() + ' 家 · 按名字排序')
.fontSize(FbMetric.fontTiny)
.fontColor(FbColors.faint)
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Text('关闭')
.fontSize(FbMetric.fontSmall)
.fontColor(FbColors.muted)
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.backgroundColor(FbColors.inputBg)
.borderRadius(FbMetric.radiusS)
.onClick((): void => {
this.onClose();
})
}
.width('100%')
.alignItems(VerticalAlign.Center)
Divider()
.color(FbColors.line)
.margin({ top: 12, bottom: 4 })
// ---- 列表 ----
// ⚠️ 用 List 而不是 ForEach 堆在 Column 里:影院可能几十家,
// List 才会**按需渲染**(进视口才建),Column 会一次全建。
if (this.cinemas.length === 0) {
Text('还没有影院。同步观影记录后,去过的地方会自动出现在这里。')
.fontSize(FbMetric.fontSmall)
.fontColor(FbColors.faint)
.width('100%')
.padding({ top: 32, bottom: 32, left: 12, right: 12 })
.textAlign(TextAlign.Center)
} else {
List({ space: 0 }) {
ForEach(this.cinemas, (c: FbCinemaItem, idx: number) => {
ListItem() {
Column({ space: 0 }) {
Row({ space: 12 }) {
// 色点:每家影院一个色(与 Web 端一致,便于在别处一眼认出来)
Column()
.width(10)
.height(10)
.backgroundColor(c.color)
.borderRadius(5)
Column({ space: 3 }) {
Row({ space: 8 }) {
Text(c.name)
.fontSize(FbMetric.fontBody)
.fontColor(FbColors.ink)
.maxLines(1)
if (c.movieCount > 0) {
Text(c.movieCount.toString() + ' 部')
.fontSize(10)
.fontColor(FbColors.faint)
}
}
.alignItems(VerticalAlign.Center)
// 场所 / 地址(有才显示)
if (c.place !== '' || c.address !== '') {
Text(c.place !== '' ? c.place : c.address)
.fontSize(FbMetric.fontTiny)
.fontColor(FbColors.muted)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
}
.width('100%')
.padding({ left: 4, right: 4, top: 11, bottom: 11 })
.alignItems(VerticalAlign.Center)
if (idx < this.cinemas.length - 1) {
Divider()
.strokeWidth(0.5)
.color(FbColors.line)
}
}
.width('100%')
}
}, (c: FbCinemaItem): string => 'cin_' + c.id.toString())
}
.width('100%')
.layoutWeight(1)
.scrollBar(BarState.Off)
.edgeEffect(EdgeEffect.Spring)
}
}
// 🔴 用户 2026-10-16:「它应该弹出一个**圆角的、浮在上面的卡片**……
// **不要让它左右充满整个屏幕,下面也不要挨着屏幕**,
// 而是放在**屏幕中间**,采用**圆角浮窗**式的形式。」
// ⇒ 不再贴底满宽:左右留边、四角圆角、垂直居中(由宿主的 Stack 对齐)。
.width('92%')
.constraintSize({ maxHeight: '72%' })
.padding({ left: 18, right: 18, top: 18, bottom: 20 + FB_SHEET_BOTTOM_GAP })
.backgroundColor(FbColors.surface)
.borderRadius(FbMetric.radius)
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.hitTestBehavior(HitTestMode.Transparent)
}
}