图片可裁剪 · 名册按关系分组 · 个人设置独立 · 顶栏下拉菜单
① 上传图片一律可裁剪,比例由图位决定
新增 app/ImageSlot.php 统一配置(头像 1:1/512×512;题图 16:9 只留注释,
将来启用只加一行)。前端只挑区域、写回归一化矩形 "x,y,w,h",真正裁剪在服务端
Upload::fitRegion():夹回图内 → 在框内居中收缩到目标比例(不外扩)→ 钉住 sx+sw ≤ w。
比例解析单独写 parseRatioAttr():parseFloat('16:9') 得 16,会把题图裁成细长条且不报错。
② 名册按关系分组,顺序每人可自定
分类全家共用(person_groups,另一个人可以自己加分类);「自己的」只体现在顺序上
(user_settings.group_order,每人一份)。persons.group_id 故意不加外键(老库只能
ADD COLUMN),完整性由 PersonGroup 保证:删分类把人置回未分类,人不会从名册消失。
③ 设置分两级:系统设置 / 个人设置
个人设置拆成 templates/settings/me.php + _profile.php + _nicknames.php,
取代 templates/people/me.php(旧路由与旧表单仍兼容)。
④ 右上角「姓名/头像 + 下拉菜单」
原来是两个裸链接,窄屏会被 CSS 藏掉导致改密码/退出不可达;改成 <details> 零 JS 菜单
(app.js 只补「点外面 / Esc」)。菜单项:个人设置 / 系统设置(管理员)/ 修改密码 / 退出登录。
Schema 版本 3 → 4;安装与老库升级共用 PersonGroup::backfillMissing() 一条回填路径。
验证:php -l 全量;e2e 202/0(新增「关系分组」与「图片裁剪」两节);老库升级 49/0;
桌面几何 108/0;移动端几何 192/0(360/375/390/414);交互 76/0;
反向验证 31/31(逐条把修复改回旧写法,指定的目标断言必须变红);
部署后本地↔线上 72 个文件 sha256 逐字节一致。
This commit is contained in:
+53
-1
@@ -24,8 +24,10 @@ final class Schema
|
||||
* 1 → 初始(families/persons/accounts/settings/relations)
|
||||
* 2 → birth_rank、is_placeholder、accounts.role 加 guest
|
||||
* 3 → 三个生日(birthday 升 DATETIME + id_card/lunar/leap)、slogan、nicknames 表
|
||||
* 4 → person_groups(关系分组,全家统一但可自定义)、persons.group_id、
|
||||
* user_settings(「我的」维度设置:分组顺序等)
|
||||
*/
|
||||
public const VERSION = '3';
|
||||
public const VERSION = '4';
|
||||
|
||||
/** @return string[] */
|
||||
public static function statements(): array
|
||||
@@ -58,12 +60,14 @@ CREATE TABLE IF NOT EXISTS `persons` (
|
||||
`address` VARCHAR(200) DEFAULT NULL,
|
||||
`note` TEXT DEFAULT NULL COMMENT '备注',
|
||||
`is_placeholder` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '虚拟人物:还没弄清真实身份的占位(如「我的父亲」)',
|
||||
`group_id` INT UNSIGNED DEFAULT NULL COMMENT '关系分组(人属于哪一组:家人/同事/朋友…),全家人共用一套分类',
|
||||
`sort_order` INT NOT NULL DEFAULT 1000 COMMENT '名册排序,小的在前',
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_phone_hash` (`phone_hash`),
|
||||
KEY `idx_family_kind` (`family_id`, `kind`, `sort_order`),
|
||||
KEY `idx_person_group` (`group_id`),
|
||||
CONSTRAINT `fk_persons_family` FOREIGN KEY (`family_id`) REFERENCES `families` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='家庭名册'
|
||||
SQL,
|
||||
@@ -123,10 +127,45 @@ CREATE TABLE IF NOT EXISTS `nicknames` (
|
||||
CONSTRAINT `fk_nick_viewer` FOREIGN KEY (`viewer_id`) REFERENCES `persons` (`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_nick_target` FOREIGN KEY (`target_id`) REFERENCES `persons` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='称呼是视角属性:每个人各自决定怎么称呼别人,不挂在被称呼者身上'
|
||||
SQL,
|
||||
<<<'SQL'
|
||||
CREATE TABLE IF NOT EXISTS `person_groups` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`family_id` INT UNSIGNED NOT NULL,
|
||||
`name` VARCHAR(20) NOT NULL COMMENT '分组名,如「家人」「同事」',
|
||||
`position` INT NOT NULL DEFAULT 100 COMMENT '默认顺序(用户没自定义顺序时按它排)',
|
||||
`is_builtin` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '1=安装时预置的分组',
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_group_name` (`family_id`, `name`),
|
||||
KEY `idx_group_family` (`family_id`, `position`),
|
||||
CONSTRAINT `fk_group_family` FOREIGN KEY (`family_id`) REFERENCES `families` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='关系分组:全家人共用一套(一个人只属于一个分组),但每个人可以各自决定分组的先后'
|
||||
SQL,
|
||||
<<<'SQL'
|
||||
CREATE TABLE IF NOT EXISTS `user_settings` (
|
||||
`person_id` INT UNSIGNED NOT NULL COMMENT '这条设置属于谁(登录者本人)',
|
||||
`k` VARCHAR(64) NOT NULL,
|
||||
`v` TEXT DEFAULT NULL,
|
||||
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`person_id`, `k`),
|
||||
CONSTRAINT `fk_uset_person` FOREIGN KEY (`person_id`) REFERENCES `persons` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='「我自己的」设置:只影响我自己看到的样子'
|
||||
SQL,
|
||||
];
|
||||
}
|
||||
|
||||
/** 安装时预置的关系分组(用户可自行增删改名,这里只是初始值) */
|
||||
public const DEFAULT_GROUPS = [
|
||||
'家人' => 10,
|
||||
'亲戚' => 20,
|
||||
'同事' => 30,
|
||||
'朋友' => 40,
|
||||
'合作伙伴' => 50,
|
||||
'认识' => 60,
|
||||
'不熟' => 70,
|
||||
];
|
||||
|
||||
/**
|
||||
* 幂等结构升级:建表 + 补齐后加的列/枚举值。
|
||||
*
|
||||
@@ -175,6 +214,19 @@ SQL,
|
||||
foreach ($db->query('SELECT id FROM families')->fetchAll() as $fam) {
|
||||
\App\Nickname::migrateLegacy((int)$fam['id']);
|
||||
}
|
||||
|
||||
// ---- 关系分组(版本 4)----
|
||||
// persons 新增 group_id。这里**不加外键**:老库只能 ADD COLUMN,加不上约束,
|
||||
// 新装的加了就会两边结构不一致;统一由 PersonGroup 保证不留悬空 id。
|
||||
self::addColumn($db, 'persons', 'group_id',
|
||||
"ADD COLUMN `group_id` INT UNSIGNED DEFAULT NULL COMMENT '关系分组(人属于哪一组:家人/同事/朋友…),全家人共用一套分类' AFTER `is_placeholder`");
|
||||
// 每个家庭都要有一套预置分组(新装的由 install.php 建,老的在这里补)
|
||||
foreach ($db->query('SELECT id FROM families')->fetchAll() as $fam) {
|
||||
\App\PersonGroup::seedDefaults((int)$fam['id']);
|
||||
}
|
||||
// 老库的人还没有分组 → 按名册类型补:家庭成员=家人、外部联系人=认识。
|
||||
// 只补空的,用户自己改过的一律不动。
|
||||
\App\PersonGroup::backfillMissing($db);
|
||||
}
|
||||
|
||||
/** 老库已有阳历生日但没有农历生日 → 补算一次。用「为空」作条件,天然幂等。 */
|
||||
|
||||
Reference in New Issue
Block a user