Files
FamilyBook/templates/home_cards.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

146 lines
6.7 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 第 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>