Files
FamilyBook/templates/days/settings.php
T
laoyang 9a66068c33 v62:纪念日列表修正 / 分类图标与设置菜单 / 首页卡片体系
- 纪念日列表:display:flex 从 td 移到内层 div(写在 td 上会破坏表格列算法,
  表现为关联人列与其它列高度不一致);行内列序按表头对齐;
  改完再点「新建」不再带出上次修改的数据;文案改为「与我相关」「来自名册」;
  别人记的、把我挂成关联人的条目一并显示(split() 返回 hiddenIds,
  路由层据此过滤「接下来的日子」,与两张表同一套可见性)
- 分类图标:新增 app/AnnivIcon.php(17 个行内 SVG 图标 + 同 key 的 emoji
  供日历纯文本处使用,内网无 CDN 不用 FontAwesome);去掉「看日历」按钮;
  设置按钮改为菜单,第一项进 days/settings 独立页;分类可自选图标,
  列表徽标与日历条目都按分类显示
- 首页卡片:新增 app/HomeCards.php 与 templates/home_cards.php;
  8 格宽 × 高 2/4/6/8;首页拆成 templates/home/_card_*.php 共 8 个片段;
  home/cards 可改尺寸、调顺序、开关
2026-10-19 12:51:17 +08:00

161 lines
8.9 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
/**
* 纪念日「分类设置」页(v62 用户 2026-10-18 第 2 条)。
*
* 用户原话:「设置按钮,点击后弹菜单,第一个菜单是分类设置,进去之后可以设置分类,
* 每一类纪念日都有不同的图标,现在的分类,你自己把图标补全。如果是用的
* fontawesome,那么如果用户添加了自己的分类,就让用户自己选。
* 在列表中、日历中,都要根据分类,显示不同的图标。」
*
* ⇒ 这一页做三件事(都在**一张表单**里,改完点一次保存):
* ① 改名(已用它的纪念日**不会**跟着改名 —— 页面底下有一句提示,别让用户以为丢了);
* ② 换图标(下拉里是 `AnnivIcon` 那一套,选完左边立刻能预览);
* ③ 调顺序 / 增 / 删。
*
* ⚠️ 三个动作(保存 / 删除 / 上移下移 / 新增)**共用同一个表单**,靠 submit 按钮的
* `name` 区分(`del` / `move` / `add`)。为什么不用"每行一个小表单":
* 一行一个小表单的话,用户改了 A 行的图标、又点了 B 行的「删」,
* A 行那次修改会被**静默丢弃**(浏览器只提交被点的那张表单)。
* 一张表单 = 一次提交 = 屏幕上看到的改动全部生效,没有"改了一半"的中间态。
*
* ⚠️ 分类是**家庭级**的(settings.anniv_kinds):全家共用一套叫法。
* 所以这一页对**所有能写的成员**开放(不是只有管理员),但改的是同一份。
*
* @param array $kindsFull array<int,array{name:string,icon:string}>
* @param array $kindCounts 分类名 => 用了几条
*/
$kindList = (array)($kindsFull ?? \App\Anniversary::kindsFull((int)\App\Auth::familyId()));
$kindCount = (array)($kindCounts ?? []);
$canEdit = \App\Auth::canWrite();
$n = count($kindList);
?>
<div class="page-head">
<h1>纪念日分类</h1>
<span class="sub">每个分类一个图标 · 全家共用一套</span>
<div class="actions">
<a class="btn btn-ghost" href="<?= e(url('days')) ?>">返回纪念日</a>
</div>
</div>
<div class="card card-pad">
<h2 class="cinema-h2">
分类与图标
<span class="hint">列表、日历、卡片上都按这里的图标显示</span>
</h2>
<?php if (!$canEdit): ?>
<p class="empty-hint">你没有编辑权限,这里只显示现有分类。</p>
<ul class="kind-list">
<?php foreach ($kindList as $k): ?>
<li class="kind-row is-ro">
<span class="kind-prev"><?= \App\AnnivIcon::svg((string)$k['icon'], 16) ?></span>
<span class="kind-name-ro"><?= e((string)$k['name']) ?></span>
<span class="kind-use"><?= (int)($kindCount[(string)$k['name']] ?? 0) ?> 条</span>
</li>
<?php endforeach; ?>
</ul>
<?php else: ?>
<form method="post" action="<?= e(url('days/settings')) ?>" id="kindForm">
<?= csrf_field() ?>
<ol class="kind-list" id="kindList">
<?php foreach ($kindList as $i => $k): ?>
<li class="kind-row">
<span class="kind-move">
<button class="btn btn-ghost btn-sm" type="submit" name="move" value="<?= (int)$i ?>:up"
title="上移" <?= $i === 0 ? 'disabled' : '' ?>>▲</button>
<button class="btn btn-ghost btn-sm" type="submit" name="move" value="<?= (int)$i ?>:down"
title="下移" <?= $i === $n - 1 ? 'disabled' : '' ?>>▼</button>
</span>
<?php /* 左边这个 svg 是"当前图标"的预览;右边下拉一动,JS 立刻换成新的
(图标库见页面底部的 .kind-icon-bank,不在 JS 里手抄一遍 path)。 */ ?>
<span class="kind-prev" data-kind-prev="<?= (int)$i ?>"><?= \App\AnnivIcon::svg((string)$k['icon'], 16) ?></span>
<select class="select kind-sel" name="k_icon[<?= (int)$i ?>]"
aria-label="<?= e((string)$k['name']) ?>的图标" data-kind-sel="<?= (int)$i ?>">
<?php foreach (\App\AnnivIcon::choices() as $icKey => $icLabel): ?>
<option value="<?= e($icKey) ?>" <?= (string)$k['icon'] === $icKey ? 'selected' : '' ?>><?= e($icLabel) ?></option>
<?php endforeach; ?>
</select>
<input class="input kind-name-in" name="k_name[<?= (int)$i ?>]" maxlength="20"
value="<?= e((string)$k['name']) ?>" aria-label="分类名">
<span class="kind-use"><?= (int)($kindCount[(string)$k['name']] ?? 0) ?> 条</span>
<?php /* ⚠️ confirm 里**不要**拼分类名:e() 把单引号转成 `&#039;`,
而浏览器会先解实体再交给 JS ⇒ 名字里带引号时这段脚本直接语法错,
「删除」按钮点了没反应(本项目踩过同型的坑)。用固定文案最稳。 */ ?>
<button class="btn btn-warn btn-sm" type="submit" name="del" value="<?= (int)$i ?>"
onclick="return confirm('删掉这个分类?已用它的纪念日不变,只是新建时不再出现。')">删除</button>
</li>
<?php endforeach; ?>
</ol>
<?php if (!$kindList): ?>
<p class="empty-hint" style="margin:8px 0 14px">还没有分类。下面加一个。</p>
<?php endif; ?>
<div class="kind-add">
<span class="kind-add-lb">加一个分类</span>
<select class="select" name="new_icon" aria-label="新分类的图标">
<?php foreach (\App\AnnivIcon::choices() as $icKey => $icLabel): ?>
<option value="<?= e($icKey) ?>" <?= $icKey === 'star' ? 'selected' : '' ?>><?= e($icLabel) ?></option>
<?php endforeach; ?>
</select>
<input class="input" name="new_name" maxlength="20" placeholder="如:相识纪念日">
<button class="btn btn-sm" type="submit" name="add" value="1">加分类</button>
</div>
<div class="btn-row" style="margin-top:16px">
<button class="btn" type="submit" name="save" value="1">保存分类</button>
<?php /* 动过图标/名字但还没保存时 JS 会把这句显出来(见页面底部脚本):
这一页**不自动保存**,不提示的话用户挑完图标直接返回,改动就静默丢了。 */ ?>
<span class="small kind-dirty" id="kindDirty" hidden>改过了,记得点左边的「保存分类」</span>
</div>
</form>
<p class="small muted" style="margin:14px 0 0">
改名<b>不会</b>改掉已经用过旧名字的那些纪念日(它们仍记着旧分类);
删掉一个分类也只是"新建时不再出现",历史记录照旧。
<br>分类加得越多,新建时的下拉越长 —— 建议按"这件事是什么性质"来分,不要按人来分(人有关联人那一列)。
</p>
<?php /* 图标库:全部图标在这里渲染一次,JS 只做"挑一个塞进预览",
避免在 JS 里再抄一份 SVG path(两份迟早不一致)。 */ ?>
<div class="kind-icon-bank" hidden aria-hidden="true">
<?php foreach (array_keys(\App\AnnivIcon::LIST) as $icKey): ?>
<span data-ic="<?= e($icKey) ?>"><?= \App\AnnivIcon::svg($icKey, 16) ?></span>
<?php endforeach; ?>
</div>
<script>
(function () {
var bank = document.querySelector('.kind-icon-bank');
if (!bank) { return; }
/* 改完图标/名字之后**一定要点保存**(这一页不自动存)。
不提示的话,用户挑完图标顺手点「返回纪念日」,改动就静默丢了 ——
所以只要动过,就把保存按钮标脏,并挂一句小字。 */
var saveBtn = document.querySelector('button[name=save]');
var dirty = document.getElementById('kindDirty');
function markDirty() {
if (saveBtn) { saveBtn.classList.add('is-dirty'); }
if (dirty) { dirty.hidden = false; }
}
Array.prototype.forEach.call(document.querySelectorAll('[data-kind-sel]'), function (sel) {
sel.addEventListener('change', function () {
var i = sel.getAttribute('data-kind-sel');
var prev = document.querySelector('[data-kind-prev="' + i + '"]');
var src = bank.querySelector('[data-ic="' + sel.value + '"]');
if (prev && src) { prev.innerHTML = src.innerHTML; }
markDirty();
});
});
Array.prototype.forEach.call(document.querySelectorAll('#kindList input.kind-name-in'),
function (inp) { inp.addEventListener('input', markDirty); });
/* 任何一次提交(保存 / 加分类 / 删除 / 上移下移)都算"处理过了",
标志就地清掉 —— 免得提交完回来还挂着红点。 */
var f = document.getElementById('kindForm');
if (f) { f.addEventListener('submit', function () {
if (saveBtn) { saveBtn.classList.remove('is-dirty'); }
if (dirty) { dirty.hidden = true; }
}); }
})();
</script>
<?php endif; ?>
</div>