laoyang a2b447bee4 v24 第 18 批:选人弹框改「暂存 + 确定」两段式(用户第 18 条)
用户原话:「在弹出框里输入一个人的首字母…筛选出这个人,点击这个人的头像或者是名字,
把这个人的信息**暂存到这个选择框的上半部分**。这时候他可以继续在输入框里面输入另外
一个人的拼音首字母,等所有人都选完之后,**点击确定按钮**完成多选然后再返回原来的界面儿,
把他选中的多个人都放入到之前的页面儿里。」

改动:
- templates/_person_picker.php:多选弹框加**暂存区**(搜索框之下的"上半部分")
  与底部「取消 / 确定」;这两块**只在 `$ppMode='multi'` 时渲染**。
- assets/js/person_picker.js:
  · 点候选人(**click**,不是 change —— 用户点的是"头像或名字")→ 进暂存区、
    弹窗**不关**、输入框清空并重新聚焦("继续输入另一个人的首字母");
  · 再点同一个人 = 从暂存区撤掉;暂存区里每个 chip 也带 ×;
  · 「确定」= 关弹窗(勾选本来就在 input 上,等于已生效)+ 重画外面 chips;
  · 「取消」= 按打开时的快照还原;
  · **没点确定就关**(× / Esc / 点遮罩)→ 问一句"要保留吗",**绝不静默丢**;
  · 单选保持旧行为(点一个即选中并关闭)。
- assets/css/app.css:.pp-stage / .pp-actions 等(含 `[hidden]` 规则 —— 本项目老坑)。

🔴 两个设计取舍(都写在代码注释里):
  ① **暂存状态就用 `input.checked` 表达,不另存一份数组**。字段的提交语义只由
     `input.checked` 决定(表单直接提交它们),另存一份就必然要"确定时写回 input"——
     漏做就是"我选了、保存后没人"(第 11 批栽过同类)。所以确定 = 关弹窗。
  ② **单选完全不动**:它的字段名(person_id / owner_id)在后端是**一个值**,
     多选会直接改坏数据。模板里包在 `if ($ppIsMulti)` 里,e2e 断言钉住"单选不许有"。

🔴 真机探针(新套件 _fb_b18_picker.py)抓到两个 jsdom 测不出的真 bug:
  ① **单选点"已选中的那个人"→ 浏览器不触发 change**(值没变)→ 弹窗一直开着
     → 用户以为"点了没反应"。补一刀 `if (!stagedMode && box.checked) closeModal(true)`,
     并在 jsdom 里加了同口径回归断言(runSingle())。
  ② 人数显示按 id 当 class 查(`pp-stage-n`)→ 取到 null → 永远显示 0(chip 却正常)。

验证:
- e2e 937/0(+17 条本批断言,含单选回归)· 升级 149/0 · 城市解析 32/32 ·
  桌面几何 428/0 · 移动端 688/0 · 交互 92/0 · 窄屏溢出 24/0 ·
  **选人弹框真机 10/10**(新套件,含"暂存区几何上在搜索框之下、清单之上")·
  题图真机 4/4。
- 反向验证 3/3:退回"点一个关一次" / 去掉关闭时的询问 / 给单选也渲染暂存区
  —— 三种坏法全部被断言抓住。
2026-10-05 20:12:18 +08:00
2026-09-29 15:08:42 +00:00

FamilyBook

家书,记录家庭的全部生活,一本用岁月写成的家谱。

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


它是什么

一个自托管的家庭生活系统。它不是又一个待办清单,而是这个家的长期档案 —— 十年之后还能翻出孩子第一次观影的票根、那年暑假走过的路线。

围绕三个圈展开:

  • 人:家庭成员、亲友通讯录,以及他们之间的共享关系
  • 时间:日历、日程、提醒,以及任务的期限
  • 事件:观影、旅行、其他活动 —— 一切都落在同一条时间轴上

它在做什么

阶段 主题 内容
0 地基 名册、账号、登录、权限模型、安装向导
1 通信 通讯录、共享给某个成员、CardDAV 服务端
2 时间 日历本、重复日程、提醒、CalDAV 服务端
3 归一 观影与旅行迁入统一事件模型
4 任务 任务管理,期限自动映射成日历事件
5 回看 统计、年度家书、时间轴

当前进度:阶段 0 起步,仓库里只有图标与脚手架。

技术选型

  • PHP 7.4+(无框架、无 Composer,上传到站点目录即可运行)
  • MySQL 5.7+ / 8.0(utf8mb4)
  • 前端为原生 HTML / CSS / JavaScript,无构建步骤

⚠️ 注意 CalDAV / CardDAV 是双向协议,和单纯的 .ics 文件导出不是一个量级的东西。

部署

  1. 把代码放到站点根目录;
  2. 复制 config.example.php 为 config.php,填写数据库与 app_key;
  3. 访问站点根,按引导页确认 PHP 版本、扩展、配置、数据库连接四项都通过。

config.php 不入库(已在 .gitignore 中忽略),其中包含数据库口令与加密密钥。

⚠️ app_key 一旦开始记录数据就不能再改(换掉即解不开已有数据), 并且它必须单独备份 —— 密钥丢失等同于数据永久丢失。

隐私

身份证号、手机号属于敏感个人信息,一律加密存储。 (就家庭自用而言,身份证号其实没有多少必须存的场景,启用前请三思。)

图标

见 assets/icons/。概念是「这本书就是家」:屋顶即一本摊开的书, 屋顶下纸做的墙上,三行横线是这个家写下的日子。

开源协议

Apache License 2.0

S
Description
家书,记录家庭的全部生活,一个家庭用岁月写成的书。
Readme Apache-2.0
8.5 MiB
Languages
PHP 81.6%
JavaScript 9.3%
CSS 9.1%