板块管理页支持拖拽排序(group_reorder)
- 板块列表与网站列表同款的拖拽交互:按住行首手柄拖动,实时重排预览, 松手即保存(Ajax 动作 group_reorder,整组归一化为 index * SORT_STEP) - 板块没有分组维度,group_reorder 不带 group_id、不做归属校验 (apply_sort_order 不传 $groupId 时按给定 id 顺序整组重写) - admin.js 的拖拽排序从「只认网站列表」泛化为「所有带 data-sortable 的 tbody」:落库 action / group_id / 落库地址都改由 tbody 的 data-* 提供, 网站列表行为不变(仍从 #sites-tabs 读地址、带 group_id) - 新增板块仍排最后(MAX + SORT_STEP,上一轮已实现);板块上移/下移按钮 保留作无 JS 回退;页面顶部补说明文案 - 验证:HTTP e2e 新增 S12 系列 18 条(结构 / 拖拽落库 / 前台同步 / 新增排最后 / 上移回退),72/72;jsdom 新增板块页 19 项,26+19 全绿; 反向验证:函数层 4 变体 + HTTP 层 7 变体(新增 G:group_reorder 失效) + jsdom 4 变体全部精确报红、基准全绿;全量既有回归 193/193
This commit is contained in:
@@ -210,6 +210,20 @@ try {
|
|||||||
move_sort('groups', post_id(), (string)($_POST['dir'] ?? 'up'), null);
|
move_sort('groups', post_id(), (string)($_POST['dir'] ?? 'up'), null);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'group_reorder': {
|
||||||
|
// 拖拽排序落库:板块没有分组维度(全表一套顺序),无需归属校验 ——
|
||||||
|
// apply_sort_order() 不带 $groupId 时按给定 id 顺序整组重写。
|
||||||
|
// 表单字段 `order` = 逗号分隔的 group id(按新顺序)。
|
||||||
|
$ids = [];
|
||||||
|
foreach (explode(',', (string)($_POST['order'] ?? '')) as $one) {
|
||||||
|
$one = (int)trim($one);
|
||||||
|
if ($one > 0) { $ids[] = $one; }
|
||||||
|
}
|
||||||
|
$n = apply_sort_order('groups', $ids);
|
||||||
|
$ajax = ['count' => $n];
|
||||||
|
if (!$isAjax) { flash('已保存排序(' . $n . ' 项)'); }
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------- 搜索引擎 ---------- */
|
/* ---------- 搜索引擎 ---------- */
|
||||||
case 'engine_save': {
|
case 'engine_save': {
|
||||||
|
|||||||
+13
-5
@@ -191,7 +191,7 @@ if ($p === 'sites'):
|
|||||||
data-group-id="<?= $gid ?>">
|
data-group-id="<?= $gid ?>">
|
||||||
<div class="tbl-wrap"><table class="tbl tbl-sortable">
|
<div class="tbl-wrap"><table class="tbl tbl-sortable">
|
||||||
<thead><tr><th class="th-drag"></th><th>简称(展示)</th><th>名称</th><th>网址</th><th>显示</th><th>微调</th><th>操作</th></tr></thead>
|
<thead><tr><th class="th-drag"></th><th>简称(展示)</th><th>名称</th><th>网址</th><th>显示</th><th>微调</th><th>操作</th></tr></thead>
|
||||||
<tbody data-group-id="<?= $gid ?>">
|
<tbody data-group-id="<?= $gid ?>" data-sortable data-sort-action="site_reorder">
|
||||||
<?php foreach ($t['items'] as $r): $label = $r['short_name'] !== '' ? $r['short_name'] : ($r['name'] !== '' ? $r['name'] : domain_of($r['url'])); $src = icon_src($r['icon']); ?>
|
<?php foreach ($t['items'] as $r): $label = $r['short_name'] !== '' ? $r['short_name'] : ($r['name'] !== '' ? $r['name'] : domain_of($r['url'])); $src = icon_src($r['icon']); ?>
|
||||||
<tr draggable="true" data-id="<?= (int)$r['id'] ?>">
|
<tr draggable="true" data-id="<?= (int)$r['id'] ?>">
|
||||||
<td class="td-drag"><?= drag_handle() ?></td>
|
<td class="td-drag"><?= drag_handle() ?></td>
|
||||||
@@ -251,11 +251,18 @@ elseif ($p === 'groups'): ?>
|
|||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
<section class="card">
|
<section class="card">
|
||||||
<div class="tbl-wrap"><table class="tbl">
|
<div class="card-head"><h2>板块列表(<?= count($groups) ?>)</h2></div>
|
||||||
<thead><tr><th>名称</th><th>展示方式</th><th>网站数</th><th>显示</th><th>排序</th><th>操作</th></tr></thead>
|
<p class="hint" style="margin-top:-6px">
|
||||||
<tbody>
|
板块的先后顺序就是它们在<b>前台出现的顺序</b>:<b>按住某一行拖动</b>即可调整,松手即保存;
|
||||||
|
也可以用每行右侧的 ↑ ↓ 微调(不支持拖拽的环境用)。
|
||||||
|
<b>新添加的板块一律排在最后</b>,直到你手动把它拖到别处。
|
||||||
|
</p>
|
||||||
|
<div class="tbl-wrap"><table class="tbl tbl-sortable">
|
||||||
|
<thead><tr><th class="th-drag"></th><th>名称</th><th>展示方式</th><th>网站数</th><th>显示</th><th>微调</th><th>操作</th></tr></thead>
|
||||||
|
<tbody data-sortable data-sort-action="group_reorder" data-reorder-url="<?= e(url('admin/actions.php')) ?>">
|
||||||
<?php foreach ($groups as $g): $cnt = (int)$pdo->query('SELECT COUNT(*) FROM sites WHERE group_id=' . (int)$g['id'])->fetchColumn(); ?>
|
<?php foreach ($groups as $g): $cnt = (int)$pdo->query('SELECT COUNT(*) FROM sites WHERE group_id=' . (int)$g['id'])->fetchColumn(); ?>
|
||||||
<tr>
|
<tr draggable="true" data-id="<?= (int)$g['id'] ?>">
|
||||||
|
<td class="td-drag"><?= drag_handle() ?></td>
|
||||||
<td><?= e($g['name']) ?></td>
|
<td><?= e($g['name']) ?></td>
|
||||||
<td><?= $g['display'] === 'tabs' ? '合集标签页' : '普通网格' ?></td>
|
<td><?= $g['display'] === 'tabs' ? '合集标签页' : '普通网格' ?></td>
|
||||||
<td><?= $cnt ?></td>
|
<td><?= $cnt ?></td>
|
||||||
@@ -272,6 +279,7 @@ elseif ($p === 'groups'): ?>
|
|||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table></div>
|
</table></div>
|
||||||
|
<p class="hint sort-status"></p>
|
||||||
</section>
|
</section>
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
|||||||
+17
-11
@@ -244,24 +244,30 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 拖拽排序:按住某一行拖动即可改顺序,松手即保存。
|
* 拖拽排序:按住某一行拖动即可改顺序,松手即保存。
|
||||||
|
* 用于「网站列表」(每板块一个 tbody)与「板块列表」(整表一个 tbody)。
|
||||||
*
|
*
|
||||||
* 实现要点:
|
* 实现要点:
|
||||||
* · 用**原生 HTML5 拖拽**(draggable=true + dragstart/dragover/drop),
|
* · 用**原生 HTML5 拖拽**(draggable=true + dragstart/dragover/drop),
|
||||||
* 不引任何第三方库(本项目零依赖);
|
* 不引任何第三方库(本项目零依赖);
|
||||||
* · 拖动过程中实时把被拖行**移动到光标位置**(真·所见即所得),
|
* · 拖动过程中实时把被拖行**移动到光标位置**(真·所见即所得),
|
||||||
* 而不是只画一条插入线 —— 个人导航站的列表不长,实时重排体验更直观;
|
* 而不是只画一条插入线 —— 个人导航站的列表不长,实时重排体验更直观;
|
||||||
* · **只允许在本板块的 tbody 内拖动**:跨板块拖会被忽略(拖到别的板块前先改「板块」再排);
|
* · 网站列表**只允许在本板块的 tbody 内拖动**:跨板块拖会被忽略(拖到别的板块前先改「板块」再排);
|
||||||
* · 松手后收集该 tbody 的 id 顺序 → POST 给 site_reorder → 服务端整组归一化。
|
* · 松手后收集该 tbody 的 id 顺序 → POST 给对应 action(tbody 上的 data-sort-action)
|
||||||
|
* → 服务端整组归一化。
|
||||||
*/
|
*/
|
||||||
function initDragSort() {
|
function initDragSort() {
|
||||||
var bodies = $$('tbody[data-group-id]');
|
var bodies = $$('tbody[data-sortable]');
|
||||||
if (!bodies.length) return;
|
if (!bodies.length) return;
|
||||||
var wrap = $('#sites-tabs');
|
var wrap = $('#sites-tabs');
|
||||||
var url = wrap ? wrap.getAttribute('data-reorder-url') : '';
|
|
||||||
var csrf = (document.querySelector('input[name="csrf"]') || {}).value || '';
|
var csrf = (document.querySelector('input[name="csrf"]') || {}).value || '';
|
||||||
|
|
||||||
bodies.forEach(function (body) {
|
bodies.forEach(function (body) {
|
||||||
var dragRow = null;
|
var dragRow = null;
|
||||||
|
var action = body.getAttribute('data-sort-action') || 'site_reorder';
|
||||||
|
var gid = body.getAttribute('data-group-id') || '';
|
||||||
|
// 落库地址:优先 tbody 自带;网站列表没有时回退到标签容器上的
|
||||||
|
var url = body.getAttribute('data-reorder-url')
|
||||||
|
|| (wrap ? wrap.getAttribute('data-reorder-url') : '');
|
||||||
|
|
||||||
body.addEventListener('dragstart', function (ev) {
|
body.addEventListener('dragstart', function (ev) {
|
||||||
var row = ev.target.closest ? ev.target.closest('tr') : null;
|
var row = ev.target.closest ? ev.target.closest('tr') : null;
|
||||||
@@ -293,7 +299,7 @@
|
|||||||
if (!dragRow) return;
|
if (!dragRow) return;
|
||||||
dragRow.classList.remove('dragging');
|
dragRow.classList.remove('dragging');
|
||||||
dragRow = null;
|
dragRow = null;
|
||||||
saveOrder(body);
|
saveOrder(body, action, gid, url);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -309,16 +315,16 @@
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 收集顺序并提交;成功后更新状态提示 */
|
/** 收集顺序并提交;成功后更新状态提示(action/gid/url 来自所属 tbody 的 data-*) */
|
||||||
function saveOrder(body) {
|
function saveOrder(body, action, gid, url) {
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
var gid = body.getAttribute('data-group-id');
|
|
||||||
var ids = $$('tr', body).map(function (r) { return r.getAttribute('data-id'); }).filter(Boolean);
|
var ids = $$('tr', body).map(function (r) { return r.getAttribute('data-id'); }).filter(Boolean);
|
||||||
var status = document.querySelector('.sort-status[data-group-id="' + gid + '"]');
|
var scope = body.closest('.sites-panel') || body.closest('.card') || document;
|
||||||
|
var status = $('.sort-status', scope);
|
||||||
|
|
||||||
var fd = new FormData();
|
var fd = new FormData();
|
||||||
fd.append('action', 'site_reorder');
|
fd.append('action', action);
|
||||||
fd.append('group_id', gid);
|
if (gid) { fd.append('group_id', gid); }
|
||||||
fd.append('order', ids.join(','));
|
fd.append('order', ids.join(','));
|
||||||
fd.append('ajax', '1');
|
fd.append('ajax', '1');
|
||||||
fd.append('csrf', csrf);
|
fd.append('csrf', csrf);
|
||||||
|
|||||||
Reference in New Issue
Block a user