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(个人优先生效)。
This commit is contained in:
2026-10-18 20:14:50 +08:00
parent 9348dd3796
commit 97e963e2ee
2 changed files with 138 additions and 4 deletions
+59
View File
@@ -9,6 +9,65 @@ 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 = ?');