Files
FamilyBook/app/Family.php
T
laoyang 97e963e2ee v59 服务端:标语(个人→家庭→默认)/ 最近纪念日 / 真实头像
配合端上「界面六条」,服务端只补三样数据:

① Family::slogan(fam, pid) —— 三层优先级
   用户:「如果这个用户自己有标语,他可以写一句自己的标语;如果他有自己的标语,
   这个地方就是用户自己自定义的标语。如果他没有标语,就用我们给他的这个标语。」
   优先级:persons.slogan → families.motto → Family::DEFAULT_SLOGAN。
   DEFAULT_SLOGAN = '一家人用岁月写成的一本书'(用户明确指定,别再改)。
   与网页端 templates/home.php 的 heroSlogan 同一套规则,两边永远一致。
   /login 与 /me 都返回 slogan —— 每次启动都会打 /me,所以网页端改了 slogan
   手机下次启动就跟上,不必为此另加接口。

② /home 新增 recent_days —— 首页「最近纪念日」
   用户:「未来最近的三个纪念日,包括每个人的生日,以及手动填写的各个纪念日。」
   = Anniversary::upcoming(fam, pid, 370) 取前 3。
   · 为什么是 370 天而不是网页端概述卡那样的 30 天:只取 30 天时,
     一个"刚过完生日"的家庭会**一个都取不到**,模块直接空着 ——
     而用户要的是一个永远的倒计时。取满一年保证"接下来的三个"一定有。
   · 数据源 all() = 派生生日 + 自建纪念日(正好覆盖用户说的两样,
     不必再去 Birthday:: 那套单独取一遍,否则迟早口径不一)。
   · 每条带 days(倒计天数)、source(custom / birthday_solar / birthday_lunar),
     顺序由服务端排好(最近的在前),端上不再排一遍。

③ 真实头像
   用户:「右上角的头像应该是真实的。」
   · /home 新增 me_avatar(当前登录者的 avatar_url());
   · /login 的 person 增加 avatar_url。
   avatar_url() 负责按线上存储驱动决定走 /uploads 还是 OSS 直链 ——
   端上不自己拼路径(拼错了就是永远破图)。

验证:_fb_home_api.py 60/0(新增 16 条:recent_days 真数据/天数/排序/≤3 条、
me_avatar、slogan 三层优先级实测 —— 给 persons#1 写一句个人 slogan,
必须优先返回他那句);php -l 通过;部署脚本增「第59批形态」9 条。
真机实测:抽屉显示「修身,齐家」= 杨永全自己的 slogan(个人优先生效)。
2026-10-18 20:14:50 +08:00

101 lines
4.0 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);
namespace App;
final class Family
{
/**
* 应用自己的口号(用户 2026-10-18 明确指定,别再改)。
*
* 🔴 用户原话:「我们应用的口号或者标语是:**一家人用岁月写成的一本书**。
* 这也是我们"家书"这个名字的由来。所以你不要再乱换这个标语了,
* 只要是想出现标语或者副标题的地方,就用这样一句话。」
*
* ⚠️ 它与 `families.motto`(用户可在网页端「系统设置」里自己写一句)
* 是**两件事**:这里这句是"谁都没写时的默认值",
* 用户写了自己的就用他自己的 —— 规则见 `slogan()`。
*/
public const DEFAULT_SLOGAN = '一家人用岁月写成的一本书';
/**
* 「我」这一端该显示的那句话(标语 / 副标题)。
*
* 优先级(与网页端 `templates/home.php` 的 `heroSlogan` **同一套规则**):
* ① **我自己的** `persons.slogan`(在「我的资料」里填的那一句);
* ② 家庭的 `families.motto`(网页端「系统设置」里那句,用户可自定义);
* ③ 应用默认口号 `DEFAULT_SLOGAN`。
*
* 🔴 为什么不是"简单取 motto":用户 2026-10-18 说得很清楚 ——
* 「如果这个用户自己有标语,他可以写一句自己的标语;……如果他没有标语,
* 就用我们给他的这个标语。」
* 个人 → 家庭 → 默认,正是这三层;网页端首页的 `heroSlogan` 已经是这个逻辑,
* 端上照抄同一套,两边才不会出现"网页一句、手机另一句"。
*
* @param int $familyId 家庭 id
* @param int|null $personId 当前登录者(null = 不取个人那句,直接走家庭/默认)
* @return string 永远非空(兜底是 DEFAULT_SLOGAN)
*/
public static function slogan(int $familyId, ?int $personId = null): string
{
if ($personId !== null && $personId > 0) {
try {
$st = Database::get()->prepare('SELECT slogan FROM persons WHERE id = ? AND family_id = ?');
$st->execute([$personId, $familyId]);
$mine = trim((string)($st->fetchColumn() ?: ''));
if ($mine !== '') {
return $mine;
}
} catch (\Throwable $ex) {
// 老库还没这一列 / 读不到都不该挡路:往下走家庭那句
}
}
try {
$f = self::find($familyId);
$motto = trim((string)($f['motto'] ?? ''));
if ($motto !== '') {
return $motto;
}
} catch (\Throwable $ex) {
// 同上:读不到就用默认
}
return self::DEFAULT_SLOGAN;
}
public static function find(int $id): ?array
{
$st = Database::get()->prepare('SELECT * FROM families WHERE id = ?');
$st->execute([$id]);
$row = $st->fetch();
return $row ?: null;
}
public static function create(string $name, string $motto = ''): int
{
$st = Database::get()->prepare('INSERT INTO families (name, motto) VALUES (?, ?)');
$st->execute([$name, $motto !== '' ? $motto : null]);
return (int)Database::get()->lastInsertId();
}
public static function update(int $id, string $name, string $motto = ''): void
{
$st = Database::get()->prepare('UPDATE families SET name = ?, motto = ? WHERE id = ?');
$st->execute([$name, $motto !== '' ? $motto : null, $id]);
}
public static function countMembers(int $familyId): int
{
$st = Database::get()->prepare(
"SELECT COUNT(*) FROM persons WHERE family_id = ? AND kind = 'member'"
);
$st->execute([$familyId]);
return (int)$st->fetchColumn();
}
}