laoyang 05b86262ff 观影照片支持视频+转码;同看的人改头像标签 (#84, #83)
用户:① 同看的人,按照头像加名字组成标签的形式进行展示。
      ② 确认观影照片是否允许上传视频,不允许则需要改,要求能上传视频,
         视频需要转码,参考旅程中的行程做法。

① 同看的人改头像胶囊(#83 追加)
- 原来一行纯文字「已选 2 人:杨永全、红围脖」,人多了被 maxLines(1) 截断;
- 改成每人一颗胶囊:头像(FbAvatar,有图显示图/没图回落色块字) + 名字 + 小叉,
  Flex(wrap) 流式换行(Row 不换行会横向溢出,本项目 v76 同款事故);
- ⚠️ FbAvatar 必须传 imageUrl,否则有头像的人也退化成色块字
  (首页观影卡 2026-10-19 踩过同一个坑)。

② 观影照片支持视频(#84)
事实核查:原来不允许。POST /movies/<id>/photos 走 Upload::image(),
只收 JPG/PNG/GIF/WebP,传视频直接拒。旅程走 Attachment::save(),
内部对 video/* 调 Video::toH264() —— 观影要支持就得接上这条路。

服务端:
- Schema VERSION 35→36,event_photos 加 kind/thumb_key/mime 三列;
  kind 默认 image(老数据全是照片,默认值即正确,不需要回填 UPDATE);
  建表语句同步改,新库直接是新结构。
- 上传按 MIME 分流:视频走 toH264 + 新增的 Video::poster() 抽首帧,
  图片仍走 Upload::image()(裁剪压缩行为完全不变)。
- Video::poster() 取第 1 秒(手机视频开头常是黑场),失败不阻断上传。
- deletePhoto / destroy 补删 thumb_key,否则删视频留孤儿封面。
- addPhotos 兼容裸键数组与结构化数组,只有一条 INSERT 路径。

端上:
- 选择器 IMAGE_TYPE → IMAGE_VIDEO_TYPE(与旅程 FbTripNodeSheet 对齐);
  海报选择器保持 IMAGE_TYPE(海报必须是图)。
- FbMovieDetail.photos 从 string[] 升到 FbMovieMedia[](带 kind/thumb/mime),
  靠类型检查确保所有读取点都改到。
- 网格里视频显示首帧+播放角标(不解码,一屏 9 个视频各挂解码器必卡死);
  全屏查看器用 Video 播放。⚠️ VideoOptions 只有 src/currentProgressRate/
  previewUri/controller,controls/autoPlay 是属性方法不是构造参数。

验证:造 H.265 视频实测 needsTranscode=true、toH264 成功转成 h264/yuv420p、
poster 抽出 13KB 封面;完整上传后接口返回 5 条(4 image + 1 video 带 thumb);
端上网格出现 ▶ 角标、点击进全屏查看器且视频在播。
迁移已跑(35→36),4 个 PHP 文件 md5 逐个核对一致。测试数据已清理。
hvigorw assembleHap BUILD SUCCESSFUL。
2026-11-06 10:52:42 +08:00

FamilyBook_harmonyos · 家书鸿蒙客户端

家书(FamilyBook)的 HarmonyOS 客户端。 服务端是那套「自托管的家庭生活系统」,这个仓库是它的手机 / 平板端。

把一个家的「人」「日子」「去过的地方、看过的电影」收进同一本书里。


当前状态

界面框架阶段(v0.1.0) —— 骨架与全部模块页面已就位,数据全部是演示数据。 服务端还没有客户端用的接口,所以这一轮只做「长什么样」,不做任何读写。

已完成 说明
工程骨架 Stage 模型 + ArkTS / ArkUI,targetSdk 26.0.0 / compatibleSdk 6.1.1(24)
沉浸式 照本机 SyncCalendar 的方案:系统栏全透明 + 根容器 expandSafeArea,通知栏与页面完全同色
左侧抽屉 宽屏常驻侧栏 / 窄屏滑出层,两套形态同一组件
12 个模块页面 首页、名册、观影、旅程、日程、资源、任务、纪念日、财务、读书、笔记(+ 家谱)
附加页面 通知消息、个人设置
主题体系 宣纸米 / 砖红配色,深浅色两套资源,深浅自动跟随系统
模块主题色 与 Web 端 Module::PRESET_COLORS 同一套 12 色预设
编译验证 hvigorw assembleHap 0 error / 0 warning
真机验证 装机成功;15 个页面逐个点开、断言渲染内容,15/15 通过
待办(下一轮) 说明
接口对接 服务端需要提供客户端用的 JSON 接口(登录、各模块列表与详情)
登录态 会话持久化、令牌刷新、启动时的登录门禁
交互实现 新建 / 编辑 / 删除、搜索、筛选、拖拽排序
日程周/天视图 按半小时定位的时间轴 + 手势(本轮只做了月视图)
附件能力 上传、图片/视频预览、裁剪
财务折线图 净资产与各账目的盘点变化曲线

与 Web 端的取舍

数据看 Web 端,排版按手机重做 —— 不照搬电脑端版面。

模块

不做「导航」模块(Web 端有)。用户原话:「导航是专门为电脑端开发的,都是网站, 主要用在浏览器的主页,所以不需要」。手机上每个网站都有 App,收藏夹没有使用场景。 ⇒ 本工程的模块清单与 Web 端 Module::LIST 有意不等,别"顺手补回去"。

「家谱」不在抽屉里(与 Web 端一致):它默认不上导航条(用户说过 「不需要每天都查看家谱」),走「名册 → 家谱」的分段切换进入。

版面

模块 Web 端 鸿蒙端 为什么
首页 hero 招牌 + 信息面板 + 日程/任务并排 + 我的家人 + 未来七天 + 模块卡 去掉 hero;信息面板作无卡片的页面头部;其余保持顺序上下堆叠 hero 只重复信息面板已有的信息(家庭名 × 问候),手机首屏高度宝贵
观影 瀑布流海报墙 两列定尺寸网格 瀑布流列宽随图高浮动,手机上很难扫
任务 多列看板(拖动改状态) 按状态分组的三段列表 手机宽度放不下多列;用户关心的是"做到哪一步"
笔记 定尺寸卡片网格 两列定高卡片网格 与 Web 端一致(用户要求过"块大小要确定")
家谱 HTML 折线树 「以我为中心的圈层」(直系 / 其他亲属) 折线树在窄屏排不下
财务 概览 + 折线图 + 账目表 概览(图占位)+ 按类别分组的账目 折线图需要图表能力,留到第二轮

目录结构

entry/src/main/ets/
  entryability/EntryAbility.ets       应用入口:主题跟随、系统栏配色
  entrybackupability/                 备份扩展
  common/
    Theme.ets                         设计令牌(颜色 / 间距 / 字号)+ 模块调色板
    Store.ets                         全局状态(V2:@ObservedV2 + @Trace)
  model/
    Modules.ets                       模块注册中心(对应 Web 端 App\Module)
    Types.ets                         全部数据类型(对应各模板渲染的字段)
    DemoData.ets                      演示数据(接上接口后可整个删除)
  components/
    Widgets.ets                       共享展示件(头像 / 标签 / 空态 / 圆点…)
    Drawer.ets                        左侧抽屉 + 骨架(并排 / 浮层两形态)
    TopBar.ets                        顶栏
  pages/
    Index.ets                         主界面:模块分发
    home/ movies/ trips/ calendar/ tasks/ people/
    resources/ days/ ledger/ books/ misc/ settings/

设计说明

沉浸式(三件套,缺一不可)

参考本机另一个应用 SyncCalendar(用户指定)的做法:

  1. 系统栏涂成全透明 —— statusBarColor: '#00000000'、navigationBarColor: '#00000000';
  2. 窗口底色 = 页面底色 —— win.setWindowBackgroundColor(pageBg),供透明系统栏透出;
  3. 紧邻系统栏的元素不设自己的背景色 —— 顶栏不能有 backgroundColor, 让它透出根容器底色;根容器再 expandSafeArea([SYSTEM], [TOP, BOTTOM]) 把背景铺到状态栏区域。

原理:透明系统栏透出的是窗口底色,而顶栏也透出同一个底色 ⇒ 天然同色、 「通知栏与上方完全融为一体」。内容仍在安全区内,不需要手算状态栏高度。

⚠️ 常见的两个坑(都踩过):

  • 调 setWindowLayoutFullScreen(true) 后子组件会钻到状态栏底下,页面得自己让位, 极易算错(曾出现顶部重复避让 156px);
  • 给状态栏或顶栏写死颜色必然与相邻层割裂(曾出现「米色状态栏 + 白色顶栏」的拼接感)。

验证方式:逐行像素采样(_tooling/familybook_immersion_check.py)。 判据是「状态栏区颜色 == 顶栏区颜色」,用数字裁定而不是目测 —— 最终实测 y=0..271 全为 #F7F3EA(纸色),达标。

Web 端顶栏是一排横向模块入口,最多放 12 个模块 —— 手机宽度下必然溢出。 改成左侧抽屉后,竖向排列不受数量限制,还能顺带放下「模块说明 + 主题色」 这层信息,一眼看清每个模块是干什么的。

抽屉有两套形态,由窗口短边决定(≥ 600vp 为宽屏):

  • 宽屏(平板 / 折叠屏展开 / 横屏手机):抽屉常驻,与内容并排;
  • 窄屏:内容铺满,抽屉从左侧滑出,点遮罩或选中模块即收起。

判据用「短边」而不是「宽度」—— 短边在旋转时不变,所以比例接近 1:1 的设备 横竖屏布局保持一致。

为什么是抽屉

Web 端顶栏是一排横向模块入口,最多放 12 个模块 —— 手机宽度下必然溢出。 改成左侧抽屉后,竖向排列不受数量限制,还能顺带放下「模块说明 + 主题色」 这层信息,一眼看清每个模块是干什么的。

抽屉有两套形态,由窗口短边决定(≥ 600vp 为宽屏):

  • 宽屏(平板 / 折叠屏展开 / 横屏手机):抽屉常驻,与内容并排;
  • 窄屏:内容铺满,抽屉从左侧滑出,点遮罩或选中模块即收起。

判据用「短边」而不是「宽度」—— 短边在旋转时不变,所以比例接近 1:1 的设备 横竖屏布局保持一致。

模块注册中心

模块清单只有一个来源:model/Modules.ets 的 FB_MODULES, 与 Web 端 app/Module.php 的 Module::LIST 一一对应(key 必须等于路由名)。

加一个模块只改那一处 —— 抽屉、设置页的显隐开关、主题色选择全部自动出现。 这是 Web 端踩过的坑(漏改一处就「有页面但导航里找不到入口」),端上照搬这套结构。

主题色

每个模块一个主题色,三档(按钮底 / hover / 文字色)由主色按 HSL 压明度算出来, 保证三档永远同色系。算法与 Web 端 Module::cssVars() 共用同一套公式。

预设色板 FB_PRESET_COLORS 也与 Web 端 Module::PRESET_COLORS 逐值一致 —— 否则同一个人在手机和电脑上选出的「观影紫」会是两种紫。

编译期约束(踩过的坑)

坑 处理
Circle().fill() 是 API 26 才有的重载 compatibleSdk 是 24,改用「方块 + 圆角」画圆(components/Widgets.ets::FbDot),零版本风险
setWindowBackgroundColor 对 phone/tablet 不可用 去掉,页面自身已用同色铺满整屏
window.getLastWindow() 只有异步版 由 EntryAbility 用 getMainWindowSync() 取一次存进 AppStorage,页面侧同步读
跨文件顶层名冲突(arkts-no-decl-merging) 所有导出名统一加 Fb 前缀
Button('label') { ... } 报 10905202 一律用 Button() { Row() { ... } }
@Param 类型写成 string 导致传 Resource 报错 颜色类入参统一声明 ResourceColor
嵌套目录下 import 相对路径层级 页面在 pages/<模块>/,公共件在 components/、model/,相对路径要多退一级

构建

需要 HarmonyOS SDK(DevEco Studio 自带)。本机开发时:

export NODE_HOME="<DevEco>/tools/node"
export DEVECO_SDK_HOME="<DevEco>/sdk"
<DevEco>/tools/ohpm/bin/ohpm install --all
<DevEco>/tools/hvigor/bin/hvigorw assembleHap --mode module -p product=default

产物在 entry/build/default/outputs/default/entry-default-unsigned.hap。

local.properties 里写本机 SDK 路径,不入库(见 .gitignore)。


开源协议

Apache License 2.0

S
Description
家书(FamilyBook)的华为鸿蒙操作系统客户端。
Readme Apache-2.0
5.8 MiB
Languages
TypeScript 100%