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 可改尺寸、调顺序、开关
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
/**
|
||||
* 首页卡片布局(v62 用户 2026-10-18 第 3 条)。
|
||||
*
|
||||
* 用户原话:「今天,我们正式引入卡片的概念……卡片的规格,我们可以定义一下,
|
||||
* 例如,把宽度定义为 8,高度可以是 2、4、6、8……如果是 2x2,那么就可以
|
||||
* 一排放 4 个,如果是 2x8,那就是一行放一个,类似于这样的定义。
|
||||
* 你可以先规划一些,实现出来,我们再讨论。」
|
||||
*
|
||||
* ⇒ 这一页是**摆卡片**的地方:勾上要哪几张、各自多宽多高、什么顺序。
|
||||
* 尺寸口径(与 templates/home.php / app.css 的 `.hc-*` 完全一致):
|
||||
* · 一排 **8 格**;宽 = 占几格 → 2:一排 4 张 / 4:一排 2 张 / 8:独占一排;
|
||||
* · 高 = 占几行 → 2 / 4 / 6 / 8(一行 76px + 10px 间距)。
|
||||
* ⚠️ 卡片宽度**允许 6**(不是只有 2/4/8):6 宽 = 一排"一大一小",
|
||||
* 这是 8 格制里很自然的一种排法,实现上零成本,就没必要禁掉。
|
||||
*
|
||||
* 交互:▲▼ 与拖动都能排序(拖动的状态只存在于 DOM 里,提交时按 DOM 顺序读 order[])。
|
||||
*
|
||||
* @param array $rows HomeCards::orderedForPicker() 的结果(含 on/w/h/n)
|
||||
*/
|
||||
$hcRows = (array)($rows ?? []);
|
||||
$hcN = count($hcRows);
|
||||
$hcWs = \App\HomeCards::WIDTHS;
|
||||
$hcHs = \App\HomeCards::HEIGHTS;
|
||||
?>
|
||||
<div class="page-head">
|
||||
<h1>首页卡片</h1>
|
||||
<span class="sub">一排 8 格 · 宽 2 就是一排 4 张,宽 8 就是独占一排</span>
|
||||
<div class="actions">
|
||||
<a class="btn btn-ghost" href="<?= e(url('home')) ?>">返回首页</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post" action="<?= e(url('home/cards_save')) ?>" class="card card-pad">
|
||||
<?= csrf_field() ?>
|
||||
<h2 class="cinema-h2">
|
||||
摆哪几张、多大
|
||||
<span class="hint">只影响你自己看到的首页</span>
|
||||
</h2>
|
||||
<p class="small muted" style="margin:0 0 16px">
|
||||
<b>宽</b>:2 = 一排 4 张,4 = 一排 2 张,6 = 一大一小,8 = 独占一排。
|
||||
<b>高</b>:2 / 4 / 6 / 8 行(一行 76px),卡片内容多的时候内部会自己滚。
|
||||
<br>关掉的卡片<b>不会丢数据</b>,只是不摆在首页上。
|
||||
</p>
|
||||
|
||||
<ol class="hc-set-list" id="hcSetList">
|
||||
<?php foreach ($hcRows as $i => $r): $k = (string)$r['k']; $def = \App\HomeCards::TYPES[$k]; ?>
|
||||
<li class="hc-set-row" draggable="true" data-hc-key="<?= e($k) ?>">
|
||||
<?php /* 顺序藏在 DOM 里:每行自带 order[],拖动搬整行 ⇒ 提交上来的顺序
|
||||
就是屏幕上的顺序(与「模块设置」那张表同一手法)。 */ ?>
|
||||
<input type="hidden" name="order[]" value="<?= e($k) ?>">
|
||||
<span class="hc-set-order">
|
||||
<span class="drag-handle" title="拖我排序" aria-hidden="true">⋮⋮</span>
|
||||
<button class="btn btn-ghost btn-sm" type="submit" name="move" value="up:<?= e($k) ?>"
|
||||
title="上移" <?= $i === 0 ? 'disabled' : '' ?>>▲</button>
|
||||
<button class="btn btn-ghost btn-sm" type="submit" name="move" value="down:<?= e($k) ?>"
|
||||
title="下移" <?= $i === $hcN - 1 ? 'disabled' : '' ?>>▼</button>
|
||||
</span>
|
||||
<label class="hc-set-on">
|
||||
<input type="checkbox" name="on[<?= e($k) ?>]" value="1" <?= !empty($r['on']) ? 'checked' : '' ?>>
|
||||
<span class="hc-set-name">
|
||||
<b><?= e((string)$def['name']) ?></b>
|
||||
<span class="small muted"><?= e((string)$def['desc']) ?></span>
|
||||
</span>
|
||||
</label>
|
||||
<label class="hc-set-dim">
|
||||
<span class="small muted">宽</span>
|
||||
<select class="select" name="w[<?= e($k) ?>]">
|
||||
<?php foreach ($hcWs as $w): ?>
|
||||
<option value="<?= (int)$w ?>" <?= (int)$r['w'] === (int)$w ? 'selected' : '' ?>><?= (int)$w ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
<label class="hc-set-dim">
|
||||
<span class="small muted">高</span>
|
||||
<select class="select" name="h[<?= e($k) ?>]">
|
||||
<?php foreach ($hcHs as $h): ?>
|
||||
<option value="<?= (int)$h ?>" <?= (int)$r['h'] === (int)$h ? 'selected' : '' ?>><?= (int)$h ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
<?php if ((int)$def['n'] > 0): ?>
|
||||
<?php /* 用户原话:「联系人,可以有卡片,让用户选择显示几个联系人在首页上。」 */ ?>
|
||||
<label class="hc-set-dim">
|
||||
<span class="small muted">显示</span>
|
||||
<input class="input hc-set-n" type="number" name="n[<?= e($k) ?>]"
|
||||
min="1" max="<?= (int)\App\HomeCards::N_MAX ?>"
|
||||
value="<?= (int)$r['n'] ?>" aria-label="显示几个联系人">
|
||||
<span class="small muted">个</span>
|
||||
</label>
|
||||
<?php else: ?>
|
||||
<span class="hc-set-dim hc-set-null"></span>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ol>
|
||||
|
||||
<div class="btn-row" style="margin-top:16px">
|
||||
<button class="btn" type="submit" name="save" value="1">保存布局</button>
|
||||
<span class="small muted">改完记得保存 —— 这一页不会边改边生效。</span>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
/* 拖动排序(▲▼ 是没有 JS 时的兜底,两条路都能用)。
|
||||
⚠️ 搬的是整行 <li>:它自带的 order[] 隐藏域跟着走,
|
||||
提交时按 DOM 顺序读,不用再同步一份顺序数组。 */
|
||||
var list = document.getElementById('hcSetList');
|
||||
if (!list) { return; }
|
||||
var dragEl = null;
|
||||
Array.prototype.forEach.call(list.querySelectorAll('.hc-set-row'), function (li) {
|
||||
li.addEventListener('dragstart', function (ev) {
|
||||
dragEl = li;
|
||||
li.classList.add('is-dragged');
|
||||
if (ev.dataTransfer) { ev.dataTransfer.effectAllowed = 'move'; }
|
||||
});
|
||||
li.addEventListener('dragend', function () {
|
||||
li.classList.remove('is-dragged');
|
||||
dragEl = null;
|
||||
Array.prototype.forEach.call(list.querySelectorAll('.hc-set-row'), function (x) {
|
||||
x.classList.remove('is-drop-before');
|
||||
});
|
||||
});
|
||||
li.addEventListener('dragover', function (ev) {
|
||||
if (!dragEl || dragEl === li) { return; }
|
||||
ev.preventDefault();
|
||||
var box = li.getBoundingClientRect();
|
||||
li.classList.toggle('is-drop-before', ev.clientY < box.top + box.height / 2);
|
||||
});
|
||||
li.addEventListener('dragleave', function () { li.classList.remove('is-drop-before'); });
|
||||
li.addEventListener('drop', function (ev) {
|
||||
if (!dragEl || dragEl === li) { return; }
|
||||
ev.preventDefault();
|
||||
var box = li.getBoundingClientRect();
|
||||
if (ev.clientY < box.top + box.height / 2) {
|
||||
list.insertBefore(dragEl, li);
|
||||
} else {
|
||||
list.insertBefore(dragEl, li.nextSibling);
|
||||
}
|
||||
li.classList.remove('is-drop-before');
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
Reference in New Issue
Block a user