后台排序改造:按板块标签页 + 拖拽排序 + 顺序完全固定
- 后台网站列表按板块分标签页,每个板块单独调顺序;用 URL hash (#sgroup-<gid>) 定位,上移/下移提交后回跳到原来的板块 - 支持鼠标拖拽排序(原生 HTML5 拖拽,零库依赖):拖动时实时重排预览, 松手后整组落库(Ajax 动作 site_reorder,带 CSRF 校验与板块归属校验, 防止跨板块搬条目) - 稀疏排序:新增链接的顺序号 = 同板块 MAX + SORT_STEP(1000),天然排最后; 拖拽落库、历史数据迁移时整组归一化为 index * SORT_STEP - 修复真根因:旧机制靠「交换两条 sort_order」上移/下移,一旦历史数据里 出现重复值,交换即无效、位置退化为按 id 排序 —— 这正是「排了顺序又被 打乱」的来源。新增一次性归一化迁移,用 settings.mig_sort_sparse 作哨兵, 避免每次请求重复覆盖用户设置 - 新增/修改链接不再用 MAX+1(MAX+1 会落进已有间隙、插到用户排好的中间) - 改变所属板块时,该条目自动排到新板块的最后 - 后台页面 + 拖拽手柄做了配套样式,触屏设备降级为上下移按钮
This commit is contained in:
+51
-5
@@ -102,11 +102,28 @@ try {
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
if ($id > 0) {
|
||||
$st = $pdo->prepare('UPDATE sites SET group_id=?, url=?, name=?, short_name=?, icon=?, icon_custom=?, is_visible=?, updated_at=? WHERE id=?');
|
||||
$st->execute([$gid, $url, $name, $short, $iconFile, $custom, $visible, $now, $id]);
|
||||
// 若这次改了「所属板块」,条目要落到**新板块的最后**(同组 MAX + SORT_STEP)。
|
||||
// ⚠️ 否则它会带着旧板块的 sort_order 混进新板块的中间 ——
|
||||
// 这正是用户抱怨的「新链接混入其中、打乱原有排序」的另一种形态:
|
||||
// 旧板块用稀疏号(如 12000),挪进一个只有 1000/2000 的板块后,
|
||||
// 它会按 sort_order 插到那些条目之间,而不是排到最后。
|
||||
$old = $pdo->prepare('SELECT group_id FROM sites WHERE id=?');
|
||||
$old->execute([$id]);
|
||||
$oldGid = (int)$old->fetchColumn();
|
||||
if ($oldGid !== $gid) {
|
||||
$max = next_sort_order('sites', $gid);
|
||||
$st = $pdo->prepare('UPDATE sites SET group_id=?, url=?, name=?, short_name=?, icon=?, icon_custom=?, sort_order=?, is_visible=?, updated_at=? WHERE id=?');
|
||||
$st->execute([$gid, $url, $name, $short, $iconFile, $custom, $max, $visible, $now, $id]);
|
||||
} else {
|
||||
$st = $pdo->prepare('UPDATE sites SET group_id=?, url=?, name=?, short_name=?, icon=?, icon_custom=?, is_visible=?, updated_at=? WHERE id=?');
|
||||
$st->execute([$gid, $url, $name, $short, $iconFile, $custom, $visible, $now, $id]);
|
||||
}
|
||||
flash($custom ? '已保存(图标已锁定,不再自动更新)' : '已保存网站');
|
||||
} else {
|
||||
$max = (int)$pdo->query('SELECT COALESCE(MAX(sort_order),0)+1 FROM sites WHERE group_id=' . $gid)->fetchColumn();
|
||||
// 新增条目:一律排在该板块**最后**(同组 MAX + SORT_STEP)。
|
||||
// ⚠️ 不要改回 `MAX+1`:拖拽后 sort_order 之间必然有间隙,
|
||||
// MAX+1 会落进既有间隙里、跑到用户排好的中间去(详见 next_sort_order 注释)。
|
||||
$max = next_sort_order('sites', $gid);
|
||||
$st = $pdo->prepare('INSERT INTO sites (group_id,url,name,short_name,icon,icon_custom,sort_order,is_visible,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?,?)');
|
||||
$st->execute([$gid, $url, $name, $short, $iconFile, $custom, $max, $visible, $now, $now]);
|
||||
flash($custom ? '已添加网站(图标已锁定,不再自动更新)' : '已添加网站');
|
||||
@@ -127,6 +144,33 @@ try {
|
||||
}
|
||||
case 'site_move': {
|
||||
move_sort('sites', post_id(), (string)($_POST['dir'] ?? 'up'), 'group_id');
|
||||
// 非 AJAX 提交后回到「该板块」那个标签页,而不是跳回第一个标签。
|
||||
// 表单里带了 gid;回退用 `#sgroup-<gid>` 锚点,由 admin.js 在载入时激活对应标签。
|
||||
$gid = (int)($_POST['gid'] ?? 0);
|
||||
if ($gid > 0) {
|
||||
$back = 'admin/?p=sites#sgroup-' . $gid;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'site_reorder': {
|
||||
// 拖拽排序落库:一次拖动后把**该板块**的完整新顺序提交上来。
|
||||
// 表单字段 `order` = 逗号分隔的 site id(按新顺序)。
|
||||
// 归属校验在 apply_sort_order() 里做 —— 不属于该板块的 id 会被忽略,
|
||||
// 防止被篡改的请求把别的板块的条目混进来。
|
||||
$gid = (int)($_POST['group_id'] ?? 0);
|
||||
if ($gid <= 0) {
|
||||
if ($isAjax) { json_out(['ok' => false, 'msg' => '缺少板块']); }
|
||||
flash('排序失败:缺少板块', 'err');
|
||||
break;
|
||||
}
|
||||
$ids = [];
|
||||
foreach (explode(',', (string)($_POST['order'] ?? '')) as $one) {
|
||||
$one = (int)trim($one);
|
||||
if ($one > 0) { $ids[] = $one; }
|
||||
}
|
||||
$n = apply_sort_order('sites', $ids, $gid);
|
||||
$ajax = ['count' => $n];
|
||||
if (!$isAjax) { flash('已保存排序(' . $n . ' 项)'); }
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -142,7 +186,8 @@ try {
|
||||
->execute([$name, $display, $visible, $id]);
|
||||
flash('已保存板块');
|
||||
} else {
|
||||
$max = (int)$pdo->query('SELECT COALESCE(MAX(sort_order),0)+1 FROM groups')->fetchColumn();
|
||||
// 新增板块:排最后(稀疏步长,理由同 next_sort_order 注释)
|
||||
$max = next_sort_order('groups');
|
||||
$pdo->prepare('INSERT INTO groups (name,slug,display,sort_order,is_visible) VALUES (?,?,?,?,?)')
|
||||
->execute([$name, slugify($name), $display, $max, $visible]);
|
||||
flash('已添加板块');
|
||||
@@ -183,7 +228,8 @@ try {
|
||||
->execute([$name, $slug, $search, $param, $sugUrl, $sugType, $color, $active, $id]);
|
||||
flash('已保存搜索引擎');
|
||||
} else {
|
||||
$max = (int)$pdo->query('SELECT COALESCE(MAX(sort_order),0)+1 FROM engines')->fetchColumn();
|
||||
// 新增引擎:排最后(稀疏步长,理由同 next_sort_order 注释)
|
||||
$max = next_sort_order('engines');
|
||||
$pdo->prepare('INSERT INTO engines (name,slug,search_url,param,suggest_url,suggest_type,color,is_active,sort_order) VALUES (?,?,?,?,?,?,?,?,?)')
|
||||
->execute([$name, $slug, $search, $param, $sugUrl, $sugType, $color, $active, $max]);
|
||||
flash('已添加搜索引擎');
|
||||
|
||||
Reference in New Issue
Block a user