laoyang 41e3354fc8 纪念日:生日改成只读卡、自建纪念日才可编辑 (#87)
用户 2026-11-05 四条:
  1.「如果这个纪念日来自生日,我们是不能编辑的。」
  2.「点击之后,像日程那样弹出一个框来显示就行:显示这个人的头像、姓名,
     以及生日的『日』和日期就可以了。」
  3.「如果是我们自己创建的纪念日,那么这里面就是编辑纪念日。」
  4.「这个框和刚才我们编辑任务的框是一样的做法。样式、格式全部都一样。」

🔴 先说一个**会改坏用户数据的 bug**(这次一并修掉):
   原来派生生日也走 FbDayEditSheet,而且传的是
     editingMonth: 1, editingDay: 1   ← 写死的 1 月 1 日
   结果点开任何一个生日都显示「1 月 1 日」,点保存还能把
   **成员档案里的真实生日覆盖成 1 月 1 日**。
   这不是样式问题,是数据损坏。

改动:

① 新增 components/FbDayBirthdaySheet.ets(派生生日的只读卡)
   · 外壳用 FbSheetRing + 遮罩点击关闭 + 右上角 FbSheetClose
     —— 与 FbTaskPanel / FbDayEditSheet 同一套版式(用户第 4 条)
   · 左上角「生日 / 农历生日」主题色粗体 + 一行小字
     「来自成员资料,不能在这里修改」——把"不能编辑"这条边界写明白,
     否则用户会以为卡片坏了(用户第 1 条)
   · 主体:FbAvatar(56) + 姓名(主题色粗体) + 完整日期
   · 「日」做成视觉焦点:84×84 浅主题色块里放 34 号大字数字 + 「11 月」
   · 右侧:还有几天 / 满 N 周岁 / 农历说明
   · 底部:「要修改生日,请到「家人」里打开这位成员的资料」
     —— 只读也要给出口,不然用户不知道去哪儿改

② FbHomePage 按 source 分发
   birthday_solar / birthday_lunar → FbDayBirthdaySheet(只读)
   custom                          → FbDayEditSheet(可编辑,原行为)
   ⚠️ 判据用 source 前缀而不是 id === 0:两者目前等价,但 source 是语义、
      id 是实现细节 —— 万一以后派生条目也带 id,用 id 判会静默错。

③ 服务端 Anniversary::occurrences() 补输出
   · person_name —— `birthdays()` 算出来了,但 occurrences 逐个字段挑着抄时漏了
   · avatar / avatar_color —— 只读卡要画头像(key,端上拼 URL)
   · day / month —— 用户要的「生日的『日』」
     ⚠️ 用 $a['day'] 而不是从 $date 截:农历生日的 day 是农历日
        (初五 = 5),date 是换算后的公历日 —— 两者不同,用户要看"生日那天"
   · birthdays() 同步补 avatar / avatar_color(它原来也没有)

④ Types / FbParse 补五个字段
   personName / avatar / avatarColor / day / month

编译通过,已装真机。
2026-11-05 20:32:02 +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%