laoyang 13f716c936 v73:家谱自绘重做(Canvas 画线 + 原生卡片),去掉列表只留图谱
用户原话:「手机端这个家谱实在是很难看,能有什么库之类的,让它好看点吗」

改法:不再全 Canvas 手绘(只有彩色圆点 + 首字,中文又平又硬、做不出圆角阴影),
改成「Canvas 只画线 + 原生 Column/Text/FbAvatar 绝对定位」。
不能改用 Path 组件:Path.commands 单位是 px,而 .position() 是 vp,混用会缩 1/3;
Canvas 绘制坐标系本身是 vp,与 .position() 自洽。

布局算法抽成纯函数 common/KinLayout.ets(可单独验证,不再和"画"揉在一起):
- 横向单位是「夫妻单元」而不是「人」,夫妻并排紧邻
- 子女挂在夫妻单元中点下方(family-chart 语义)
- 迭代松弛 24 轮容纳冲突约束(家谱不是树:姻亲的"挂父母下方"与"夫妻并排"互相打架)
- 全程确定性(排序带 minId 兜底),避免每次重画卡片抖位置

真机抓到并修掉两个真 bug:
1. 户号 0 被当成"没分户":服务端 unit_of 从 0 编号,端上判 > 0
   ⇒ 我与妻子被拆两户(真机 Δx=680px)。修:布局层 >= 0,解析层缺失给 -1,
   不把任何数值当哨兵。
2. 线没连对(用户原话「主要是线没连对」):buildBloodEdges 用 gen 数值当行号,
   minGen=-1 ⇒ 长辈线从 y=-148 起(屏幕外),整组线串到上一代卡片。
   修:新增 rowIndex(gens, gen),用走标换算而不是数值。

验证:
- 离线布局 29/0(含户号 0、脏数据一户三人、镜像常量与端上源码一致)
- 反向验证 11/0(9 处注入,含"户号 0 当没分户"与"用 gen 数值当行号"两条真 bug 复现)
- 真机几何 15/0、真实数据+屏幕对照 9/0(反解密度 3.125x,残差 0.23/0.32px;
  12 个血缘线端点全部贴在卡片上下沿)
- ArkTS 静态守卫 905/0
2026-10-21 22:18:43 +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
4.9 MiB
Languages
TypeScript 100%