板块管理页支持拖拽排序(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);
|
||||
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': {
|
||||
|
||||
+13
-5
@@ -191,7 +191,7 @@ if ($p === 'sites'):
|
||||
data-group-id="<?= $gid ?>">
|
||||
<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-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']); ?>
|
||||
<tr draggable="true" data-id="<?= (int)$r['id'] ?>">
|
||||
<td class="td-drag"><?= drag_handle() ?></td>
|
||||
@@ -251,11 +251,18 @@ elseif ($p === 'groups'): ?>
|
||||
</form>
|
||||
</section>
|
||||
<section class="card">
|
||||
<div class="tbl-wrap"><table class="tbl">
|
||||
<thead><tr><th>名称</th><th>展示方式</th><th>网站数</th><th>显示</th><th>排序</th><th>操作</th></tr></thead>
|
||||
<tbody>
|
||||
<div class="card-head"><h2>板块列表(<?= count($groups) ?>)</h2></div>
|
||||
<p class="hint" style="margin-top:-6px">
|
||||
板块的先后顺序就是它们在<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(); ?>
|
||||
<tr>
|
||||
<tr draggable="true" data-id="<?= (int)$g['id'] ?>">
|
||||
<td class="td-drag"><?= drag_handle() ?></td>
|
||||
<td><?= e($g['name']) ?></td>
|
||||
<td><?= $g['display'] === 'tabs' ? '合集标签页' : '普通网格' ?></td>
|
||||
<td><?= $cnt ?></td>
|
||||
@@ -272,6 +279,7 @@ elseif ($p === 'groups'): ?>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table></div>
|
||||
<p class="hint sort-status"></p>
|
||||
</section>
|
||||
<?php
|
||||
|
||||
|
||||
+17
-11
@@ -244,24 +244,30 @@
|
||||
|
||||
/**
|
||||
* 拖拽排序:按住某一行拖动即可改顺序,松手即保存。
|
||||
* 用于「网站列表」(每板块一个 tbody)与「板块列表」(整表一个 tbody)。
|
||||
*
|
||||
* 实现要点:
|
||||
* · 用**原生 HTML5 拖拽**(draggable=true + dragstart/dragover/drop),
|
||||
* 不引任何第三方库(本项目零依赖);
|
||||
* · 拖动过程中实时把被拖行**移动到光标位置**(真·所见即所得),
|
||||
* 而不是只画一条插入线 —— 个人导航站的列表不长,实时重排体验更直观;
|
||||
* · **只允许在本板块的 tbody 内拖动**:跨板块拖会被忽略(拖到别的板块前先改「板块」再排);
|
||||
* · 松手后收集该 tbody 的 id 顺序 → POST 给 site_reorder → 服务端整组归一化。
|
||||
* · 网站列表**只允许在本板块的 tbody 内拖动**:跨板块拖会被忽略(拖到别的板块前先改「板块」再排);
|
||||
* · 松手后收集该 tbody 的 id 顺序 → POST 给对应 action(tbody 上的 data-sort-action)
|
||||
* → 服务端整组归一化。
|
||||
*/
|
||||
function initDragSort() {
|
||||
var bodies = $$('tbody[data-group-id]');
|
||||
var bodies = $$('tbody[data-sortable]');
|
||||
if (!bodies.length) return;
|
||||
var wrap = $('#sites-tabs');
|
||||
var url = wrap ? wrap.getAttribute('data-reorder-url') : '';
|
||||
var csrf = (document.querySelector('input[name="csrf"]') || {}).value || '';
|
||||
|
||||
bodies.forEach(function (body) {
|
||||
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) {
|
||||
var row = ev.target.closest ? ev.target.closest('tr') : null;
|
||||
@@ -293,7 +299,7 @@
|
||||
if (!dragRow) return;
|
||||
dragRow.classList.remove('dragging');
|
||||
dragRow = null;
|
||||
saveOrder(body);
|
||||
saveOrder(body, action, gid, url);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -309,16 +315,16 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 收集顺序并提交;成功后更新状态提示 */
|
||||
function saveOrder(body) {
|
||||
/** 收集顺序并提交;成功后更新状态提示(action/gid/url 来自所属 tbody 的 data-*) */
|
||||
function saveOrder(body, action, gid, url) {
|
||||
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 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();
|
||||
fd.append('action', 'site_reorder');
|
||||
fd.append('group_id', gid);
|
||||
fd.append('action', action);
|
||||
if (gid) { fd.append('group_id', gid); }
|
||||
fd.append('order', ids.join(','));
|
||||
fd.append('ajax', '1');
|
||||
fd.append('csrf', csrf);
|
||||
|
||||
Reference in New Issue
Block a user