- 纪念日列表: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 可改尺寸、调顺序、开关
161 lines
8.9 KiB
PHP
161 lines
8.9 KiB
PHP
<?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() 把单引号转成 `'`,
|
||
而浏览器会先解实体再交给 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>
|