Files
FamilyBook/templates/home_cards.php
T

146 lines
6.7 KiB
PHP
Raw Normal View History

<?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>