v63:卡片配色 / 日历本靠右 / 地图题图 / 设置页清理 / 公共标签颜色 / 令牌下线
- 卡片右上角按钮改用「来自哪个模块」的主题色:HomeCards 登记表新增 mod 字段 与 module(),首页从登记表取色(原来是模板里硬编码 in_array(['agenda','tasks']) 白名单,新加带按钮的卡片会默默变灰) - 首页日程把日历本放到色块右侧:.ag-ev 改弹性行,「时间+标题」包进 .ag-ev-main(flex:1 1 auto; min-width:0),日历本作右侧兄弟。 原来 display:block 会把日历本顶到第二行,色块一矮就完全看不见 - 旅程地图显示题图:服务端把 media_url(cover_key) 塞进地图数据, 地图卡片左侧 40x40 题图(object-fit:cover),没有题图不留灰框 - 系统设置第一块改叫「系统标语」;删掉「首页上那一行小字」提示、 「不是全家的统一口号」说明、「上次结构升级有步骤失败了」整框; 运行环境删「排错时看这一块」与「亲属连线」那一行(功能与数据未动) - 公共标签:说明文字全删;列表与编辑合并成一行(色块+名字+N人+箭头+编辑); person_groups 加 color 列(Schema addColumn,老库可升);点「编辑」弹框 改名字与颜色、点「完成」提交;下面那个独立编辑列表撤掉。删除挪进弹框 - 个人设置删掉「手动签发接口令牌」整块;settings/api_token 路由保留但挡下提交 (只删界面会留下知道地址就能用的后门);令牌只由登录后自动签发, 设备管理卡保留。apiTokens/apiPlain/apiLabel/apiBase 四个变量一并撤掉
This commit is contained in:
+27
-3
@@ -43,47 +43,71 @@ final class HomeCards
|
||||
public const HEIGHTS = [2, 4, 6, 8];
|
||||
|
||||
/**
|
||||
* 卡片注册表:key => [名字, 说明, 默认宽, 默认高, 详情页路由, 按钮文字, 是否有"显示几条"]
|
||||
* 卡片注册表:key => [名字, 说明, 默认宽, 默认高, 详情页路由, 按钮文字, 是否有"显示几条", 来自哪个模块]
|
||||
*
|
||||
* ⚠️ key 就是模板名(`_card_<key>.php`)与 `data-hc` 的值,三处必须一致。
|
||||
* ⚠️ 新加一张卡片只改这里 + 写一个 `_card_xxx.php`:布局设置页、默认布局、
|
||||
* 首页渲染全部自动出现(`HomeMods` 那条"新增模块的唯一入口"的同一条经验)。
|
||||
*
|
||||
* 🔴 v63(用户 2026-10-19 第 1 条):「卡片右上角的按钮,应该跟这个卡片来自于
|
||||
* 什么模块的主题色一样才对。」⇒ 每张卡片登记它**来自哪个模块**(`mod`),
|
||||
* 右上角按钮的底色就取那个模块的主题色(家庭级、可在设置里改,见 `Module::color`)。
|
||||
* ⚠️ 之前只有日程/任务两张硬编码写死(`in_array($hcKey, ['agenda','tasks'])`),
|
||||
* 那张白名单放在模板里 —— 新加一张带按钮的卡片就忘了加,按钮默默变灰。
|
||||
* 现在登记在这里:**有 route 就该有 mod**,两件事一次改完。
|
||||
* ⚠️ `mod` 只影响按钮配色,不参与权限/路由;认不出的 key 走 `Module::color` 的兜底。
|
||||
*/
|
||||
public const TYPES = [
|
||||
'brief' => [
|
||||
'name' => '概述', 'desc' => '问候、日期、农历、天气,以及今天的数字',
|
||||
'w' => 8, 'h' => 2, 'route' => '', 'more' => '', 'n' => 0,
|
||||
'w' => 8, 'h' => 2, 'route' => '', 'more' => '', 'n' => 0, 'mod' => '',
|
||||
],
|
||||
'agenda' => [
|
||||
'name' => '今天的日程', 'desc' => '按日视图那样排的今天',
|
||||
'w' => 4, 'h' => 8, 'route' => 'calendar', 'more' => '更多日程', 'n' => 0,
|
||||
'mod' => 'calendar',
|
||||
],
|
||||
'tasks' => [
|
||||
'name' => '近期任务', 'desc' => '进行中与待办(含看板与状态)',
|
||||
'w' => 4, 'h' => 8, 'route' => 'tasks', 'more' => '全部任务', 'n' => 0,
|
||||
'mod' => 'tasks',
|
||||
],
|
||||
'memories' => [
|
||||
'name' => '往年的今天', 'desc' => '往年同一天的观影与旅程',
|
||||
'w' => 4, 'h' => 4, 'route' => '', 'more' => '', 'n' => 0,
|
||||
'w' => 4, 'h' => 4, 'route' => '', 'more' => '', 'n' => 0, 'mod' => '',
|
||||
],
|
||||
'days' => [
|
||||
'name' => '纪念日', 'desc' => '接下来 60 天的纪念日与生日',
|
||||
'w' => 4, 'h' => 4, 'route' => 'days', 'more' => '全部纪念日', 'n' => 0,
|
||||
'mod' => 'days',
|
||||
],
|
||||
'movies' => [
|
||||
'name' => '观影', 'desc' => '最近看过的片子',
|
||||
'w' => 4, 'h' => 4, 'route' => 'movies', 'more' => '观影记录', 'n' => 0,
|
||||
'mod' => 'movies',
|
||||
],
|
||||
'books' => [
|
||||
'name' => '读书', 'desc' => '在读与最近加进书架的书',
|
||||
'w' => 4, 'h' => 4, 'route' => 'books', 'more' => '书架', 'n' => 0,
|
||||
'mod' => 'books',
|
||||
],
|
||||
'contacts' => [
|
||||
'name' => '联系人', 'desc' => '名册里的人,可以选显示几个',
|
||||
'w' => 4, 'h' => 4, 'route' => 'people', 'more' => '名册', 'n' => 8,
|
||||
'mod' => 'people',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* 这张卡片来自哪个模块(没有归属的返回空串 —— 模板据此决定按钮用不用主题色)。
|
||||
*
|
||||
* ⚠️ 别在模板里再写一份白名单:`mod` 是唯一来源。
|
||||
*/
|
||||
public static function module(string $key): string
|
||||
{
|
||||
return (string)(self::TYPES[$key]['mod'] ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认布局(第一次打开首页的人看到的样子)。
|
||||
*
|
||||
|
||||
+69
-10
@@ -24,6 +24,44 @@ final class PersonGroup
|
||||
/** 公共分组(管理员维护,全家共用)的 owner_id */
|
||||
public const PUBLIC_OWNER = 0;
|
||||
|
||||
/**
|
||||
* 🔴 v63(用户 2026-10-19 第 5 条):「每个标签增加一个颜色。」
|
||||
*
|
||||
* 色板 = 标签可以挑的颜色。与「模块主题色」(`Module::PRESET_COLORS`)**同一条
|
||||
* 审美规矩**:中低饱和、中低明度 —— 标签是纸面上的一行字,颜色太艳会盖过内容。
|
||||
*
|
||||
* ⚠️ 挑色是"选"不是"填":界面上只给这些色块(外加一个系统取色器兜底),
|
||||
* 服务端一律按白名单校验(`sanitizeColor`),别让手改表单塞进 CSS。
|
||||
*/
|
||||
public const SWATCHES = [
|
||||
'#A84A28', '#8A6B4B', '#4A7C82', '#7A4E8C', '#3F7D5A', '#B0763A',
|
||||
'#5566A8', '#A8574F', '#5C8A48', '#6B5B95', '#9A5C7A', '#3D6B8E',
|
||||
];
|
||||
|
||||
/** 颜色白名单:只接受 #rrggbb,其余回退到 $fallback(同 `Nav::safeColor` 的做法) */
|
||||
public static function sanitizeColor(string $c, string $fallback): string
|
||||
{
|
||||
return preg_match('/^#[0-9a-fA-F]{6}$/', trim($c)) ? strtolower(trim($c)) : $fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* 这个标签"实际显示成什么颜色"。
|
||||
*
|
||||
* ⚠️ 没选过颜色(`color` 为 NULL/空)时**按 id 稳定地**从色板里挑一个 ——
|
||||
* 不能用"列表里的第几个"来挑:那样改一次顺序,满屏标签颜色全跳一遍,
|
||||
* 用户会以为自己点错了。用 id 取模则"这个标签一辈子是这个色",直到用户改它。
|
||||
*/
|
||||
public static function swatch(array $g): string
|
||||
{
|
||||
$raw = trim((string)($g['color'] ?? ''));
|
||||
if ($raw !== '') {
|
||||
return self::sanitizeColor($raw, self::SWATCHES[0]);
|
||||
}
|
||||
$id = (int)($g['id'] ?? 0);
|
||||
$n = count(self::SWATCHES);
|
||||
return self::SWATCHES[$id % $n];
|
||||
}
|
||||
|
||||
/** 预置分组的来源在 Schema::DEFAULT_GROUPS(安装与升级共用一份,避免两处漂移) */
|
||||
|
||||
/**
|
||||
@@ -185,7 +223,8 @@ final class PersonGroup
|
||||
* @param int $ownerId 0=公共(只有管理员能建,调用方把关);否则=建给自己的
|
||||
* @return array{ok:bool,id?:int,error?:string}
|
||||
*/
|
||||
public static function add(int $familyId, string $name, int $ownerId = self::PUBLIC_OWNER): array
|
||||
public static function add(int $familyId, string $name, int $ownerId = self::PUBLIC_OWNER,
|
||||
string $color = ''): array
|
||||
{
|
||||
$ownerId = max(0, $ownerId);
|
||||
$name = trim($name);
|
||||
@@ -208,19 +247,28 @@ final class PersonGroup
|
||||
);
|
||||
$st2->execute([$familyId, $ownerId]);
|
||||
$max = (int)$st2->fetchColumn();
|
||||
// 🔴 v63:颜色可空 —— 空串存 NULL(= "还没选过",见 swatch() 的说明)。
|
||||
// 传了颜色就按白名单归一(非法值退回空 = 也当没选过,不写脏数据进库)。
|
||||
$col = trim($color) === '' ? null : self::sanitizeColor($color, '');
|
||||
$db->prepare(
|
||||
'INSERT INTO person_groups (family_id, owner_id, name, position, is_builtin)
|
||||
VALUES (?, ?, ?, ?, 0)'
|
||||
)->execute([$familyId, $ownerId, $name, max($max + 10, 80)]);
|
||||
'INSERT INTO person_groups (family_id, owner_id, name, color, position, is_builtin)
|
||||
VALUES (?, ?, ?, ?, ?, 0)'
|
||||
)->execute([$familyId, $ownerId, $name, $col, max($max + 10, 80)]);
|
||||
return ['ok' => true, 'id' => (int)$db->lastInsertId()];
|
||||
}
|
||||
|
||||
/**
|
||||
* 改名。$ownerId 用来确认"你改的是你自己那一套里的分组"——
|
||||
* 公共分组只有管理员能改(调用方把关),别人的私自分组连管理员也不该动。
|
||||
*
|
||||
* 🔴 v63:顺手支持改颜色(`$color === null` = **不动颜色**)。
|
||||
* ⚠️ 用 null 而不是空串表示"不改":空串是"清掉颜色"这个合法意图
|
||||
* (退回按 id 取默认色),两者必须能区分 —— 这是"部分更新"接口
|
||||
* 最容易写错的一处(传不传那个字段,语义完全不同)。
|
||||
* @return array{ok:bool,error?:string}
|
||||
*/
|
||||
public static function rename(int $familyId, int $id, string $name, int $ownerId): array
|
||||
public static function rename(int $familyId, int $id, string $name, int $ownerId,
|
||||
?string $color = null): array
|
||||
{
|
||||
$ownerId = max(0, $ownerId);
|
||||
$name = trim($name);
|
||||
@@ -243,12 +291,23 @@ final class PersonGroup
|
||||
if ((int)$st->fetchColumn() > 0) {
|
||||
return ['ok' => false, 'error' => '已经有叫「' . $name . '」的分组了。'];
|
||||
}
|
||||
$up = $db->prepare(
|
||||
'UPDATE person_groups SET name = ? WHERE family_id = ? AND owner_id = ? AND id = ?'
|
||||
);
|
||||
$up->execute([$name, $familyId, $ownerId, $id]);
|
||||
if ($color === null) {
|
||||
$up = $db->prepare(
|
||||
'UPDATE person_groups SET name = ? WHERE family_id = ? AND owner_id = ? AND id = ?'
|
||||
);
|
||||
$up->execute([$name, $familyId, $ownerId, $id]);
|
||||
} else {
|
||||
$col = trim($color) === '' ? null : self::sanitizeColor($color, '');
|
||||
$up = $db->prepare(
|
||||
'UPDATE person_groups SET name = ?, color = ? WHERE family_id = ? AND owner_id = ? AND id = ?'
|
||||
);
|
||||
$up->execute([$name, $col, $familyId, $ownerId, $id]);
|
||||
}
|
||||
if ($up->rowCount() === 0) {
|
||||
return ['ok' => false, 'error' => '找不到这个分组。'];
|
||||
// ⚠️ rowCount()===0 有两种可能:找不到(不可能,上面刚查过)与
|
||||
// **改的值和原来一模一样**(MySQL 对无变化的 UPDATE 不计数)。
|
||||
// 后者不是错误 —— 用户点了「完成」但什么都没改,不该报"找不到"。
|
||||
return ['ok' => true];
|
||||
}
|
||||
return ['ok' => true];
|
||||
}
|
||||
|
||||
@@ -375,6 +375,7 @@ CREATE TABLE IF NOT EXISTS `person_groups` (
|
||||
`family_id` INT UNSIGNED NOT NULL,
|
||||
`owner_id` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '0=全家的公共分组(管理员在系统设置里维护);否则=这个人的私自分组,只有他自己看得到',
|
||||
`name` VARCHAR(20) NOT NULL COMMENT '分组名,如「家人」「同事」',
|
||||
`color` VARCHAR(7) DEFAULT NULL COMMENT '标签颜色 #rrggbb(v63;NULL=还没选过,界面按顺序取默认色)',
|
||||
`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,
|
||||
@@ -2390,6 +2391,18 @@ SQL);
|
||||
}
|
||||
});
|
||||
|
||||
/* 🔴 v63(用户 2026-10-19 第 5 条):「公共标签这个板块…每个标签增加一个颜色。」
|
||||
⇒ `person_groups` 加 `color`。
|
||||
⚠️ 老库没有这一列,**必须**用 addColumn 显式补(CREATE TABLE IF NOT EXISTS
|
||||
对已存在的表什么都不做 —— 这是本文件开头那段的全部意义)。
|
||||
⚠️ 允许 NULL(不是 NOT NULL DEFAULT):NULL = "从来没选过颜色",
|
||||
界面按标签顺序从预设色板里挑一个(`PersonGroup::swatch()`)。
|
||||
给了 DEFAULT 就分不清"用户故意选了那个色"和"系统默认给的"。
|
||||
⚠️ 位置:`ADD COLUMN ... AFTER \`name\`` —— 列顺序影响不了程序,
|
||||
但影响"人看 information_schema 时的心智",放在名字后面最自然。 */
|
||||
self::addColumn($db, 'person_groups', 'color',
|
||||
"ADD COLUMN `color` VARCHAR(7) DEFAULT NULL COMMENT '标签颜色 #rrggbb(v63;NULL=还没选过,界面按顺序取默认色)' AFTER `name`");
|
||||
|
||||
// 被容错跳过的数据步骤记进设置,系统设置页会把它显出来 ——
|
||||
// 不然「升级没跑完」这件事只活在日志里,跟没发生一样。
|
||||
if (self::$stepErrors) {
|
||||
|
||||
Reference in New Issue
Block a user