Files
FamilyBook/install.php
T
laoyang 71a7ec35a0 v14: 观影/家谱/日程/旅程/资源/任务六模块统一,首页改 dashboard
用户 14 条需求一次交付(1-13 功能 + 14 工作方式)。

观影(1-4)
- 返回文案全模块统一为「返回观影」(不再有「返回列表」「返回观影记录」三种叫法)
- 观影人一律用彩色人物标签 person_chip:底色由头像主色算出来(上传时存库),
  瀑布流卡片也按人显示;五处观影模板全部改用它
- 人员显示统一走 person_name()(shown → 我给他起的称呼 → 姓名)
- 影院管理页与年度报告页的上下两块用 .card + .card 统一给间距(原来紧贴成一整块)

家谱(5)
- 树根从「我」换成**辈分最高的祖先**:库画的是"树根的祖先链+后代+树根的兄弟姐妹",
  根=我 时伯父/堂兄/嫂子/侄子一个都不画(12 人夹具只画出 8 人)
- focusMap 按**代数**圈定三代(不再按步数),normalizeLinks 把子女对称挂到父母双方
  —— 修掉"母亲登录看不到孩子"
- 新增「看全部 / 回到我」两个按钮,放在画布外(画布整块被库接管了手势)

统一事件原语(6-9)
- 旅程:TripLog 全量迁入(3 趟旅程 / 21 段行程 / 72 张照片 / 5 个人)。
  复用 events(kind='trip') + trip_details + trip_nodes(计划/执行双轨保留)
- 日程:每人可建日历本(自定义色)+ 共享只读/可写;观影与旅行是每人默认两本,
  内容是**从各模块实时派生**的(不复制,所以改了观影日期日历立刻跟着变);
  没有具体时分 = 全天;月视图参照群晖 Calendar
- 资源:挂在某人名下的附属信息(标题+正文+多附件),可共享给多人并设只读/读写,
  默认折叠、可搜标题与正文
- 任务:看板三列 + 两级子任务 + 评论@人 + 共享 + 循环(完成时按原截止日推下一期)
- 统一附件表:四个模块的附件共用一张,下载地址不落库、一律由 media.php 现算

模块化(10-11)
- App\Module 是模块的唯一来源(加一行 = 导航条/主题色/个人设置三处自动出现)
- 个人设置里可勾选要显示哪些模块、拖动排序(关掉的模块**不会**被兜底补回来)
- 每个模块一套主题色(家庭级),只覆盖 --brand 三兄弟,不碰纸墨底色

其他(12-13)
- 新增成员表单可选所属分组(编辑表单没有:分组是"我和他的关系",只归我自己)
- 首页改 dashboard:接下来的日子 / 未来七天 / 手头在做 / 往年今日。
  家人卡片从首页撤走(看人去名册)

验证(全部通过)
- e2e 542 条(新增 4 模块端到端 60+ 条)
- 桌面几何 368 / 移动端几何 520 / 家谱 12 / 交互 97 / 老库升级 123
- SQL 列名静态校验 0 处可疑(对着真实表结构)
- 反向验证 18 条新注入全部抓住

顺带修掉的既有问题
- 老库升级回填 SQL 按实际存在的列动态拼(缺列不再 1054 炸断升级)
- 全新安装时 Schema::ensure 跑在"人还没建"之前 → 补建日历本三处齐备
- 窄屏导航横向溢出(模块从 4 个加到 8 个后顶栏撑破视口)
2026-10-02 09:28:07 +08:00

114 lines
4.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 安装向导:建表 → 建家庭 → 建第一个人(同时是他/她的管理员账号)。
*
* 「是否已安装」以**数据库里的四张表**为准,不以某个文件是否存在为准 ——
* 文件可能被删、可能被恢复,表在就是装过了。
*/
declare(strict_types=1);
require __DIR__ . '/app/bootstrap.php';
use App\Account;
use App\Crypto;
use App\Database;
use App\Family;
use App\Person;
use App\Schema;
use App\View;
if (Database::hasTables()) {
// 装过之后这个入口就该消失:给 404,别让它在公网一直挂着、也别让爬虫收录
http_response_code(404);
View::renderBare('error', [
'title' => '已经装好了',
'message' => '「家书」已经安装过。如果要重新安装,请先清空数据库里的 families / persons / accounts / settings 四张表。',
]);
exit;
}
$dbName = $GLOBALS['config']['db']['name'];
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
csrf_check();
$familyName = trim((string)($_POST['family_name'] ?? ''));
$motto = trim((string)($_POST['motto'] ?? ''));
$name = trim((string)($_POST['name'] ?? ''));
$phone = trim((string)($_POST['phone'] ?? ''));
$password = (string)($_POST['password'] ?? '');
$errors = [];
if ($familyName === '') { $errors['_'] = '家庭名称不能为空'; }
if ($name === '') { $errors['name'] = '请填写你的姓名'; }
if (!preg_match('/^\+?[0-9\-]{6,20}$/', $phone)) {
$errors['phone'] = '手机号格式看起来不对';
}
$pwErr = Account::validatePassword($password);
if ($pwErr) { $errors['password'] = implode(';', $pwErr); }
if (!$errors) {
try {
// ensure() 而不是逐条 exec(statements()):它还会补齐后加的列与枚举值,
// 所以老库重新跑一次就能自动升级,不必手工改表。
Schema::ensure();
$familyId = Family::create($familyName, $motto);
// 关系分组的预置值:在这里建好,免得「装完第一眼进名册发现一个分组都没有」
// (ensure() 里也会补,但那要等下一次请求)
\App\PersonGroup::seedDefaults((int)$familyId);
// 装完就把结构版本盖上:不盖的话,第一次进首页发现"版本对不上",
// 会再跑一次 ensure()(幂等,不出错,但白跑一遍 ALTER 查询)。
\App\Setting::set($familyId, 'schema_version', Schema::VERSION);
$personId = Person::create($familyId, [
'name' => $name,
'gender' => '',
'avatar' => '',
'phone' => $phone,
'id_card' => '',
'email' => '',
'address' => '',
'note' => '',
'slogan' => '',
'kind' => 'member',
]);
Account::create($personId, $password, 'admin');
// 名册是「每人一份」(v5 起)。创始人先把自己放进自己的名册 ——
// 之后他每加一个人,那个人就自动进他的名册(见 index.php 的 people/create)。
// ⚠️ 这里**不能**把全家成员都塞给他:新装时除他之外还没有别人,
// 而"别人"要靠他自己加或复用,这正是"每人一份"的意思。
\App\PersonRoster::ensure((int)$familyId, (int)$personId, (int)$personId, null, 'self');
// 默认日历本(观影 / 旅行):**必须在这里补一遍**,因为上面那次
// Schema::ensure() 跑的时候这个人还没建出来(ensure 里的补建遍历的是
// "已开通账号的人")—— 只在 ensure 里做的话,装完的这个人一本日历都没有,
// 而且版本号已被盖上,升级路径也不会再来补。
// (登录处还有第二道兜底,见 Auth::login。)
\App\Calendar::seedDefaults((int)$familyId, (int)$personId);
// 装完就删掉自己 —— 避免把安装入口长期暴露在公网
@unlink(__FILE__);
header('Location: index.php?r=auth/login');
exit;
} catch (\Throwable $ex) {
$errors['_'] = '安装失败:' . $ex->getMessage();
}
}
set_old($_POST);
View::renderBare('install', [
'title' => '安装家书',
'dbName' => $dbName,
'errors' => $errors,
]);
exit;
}
clear_old();
View::renderBare('install', [
'title' => '安装家书',
'dbName' => $dbName,
'errors' => [],
]);