3.2 KiB
3.2 KiB
技术实现方案:21-Tab设置排序间隙高亮统一
总体思路
把 SettingsSection.jsx 的 TabSettings 组件落点指示从「整行高亮(overId → 行背景 color-mix 12%)」 改为「行间间隙高亮线(overPos { idx, half } → 目标行上缘/下缘 3px 主色线)」,与 ColumnSettingsPopover.jsx (R-024)逐点对齐;其余交互(≡ 手柄、开关、立即持久化、徽标、saving 禁用)不动。
改动点(单文件:src/client/views/SettingsSection.jsx,仅 TabSettings 组件)
1. 状态
overId(整行高亮锚点)→overPos { idx, half }(间隙线锚点行下标 + 上/下缘);dragId保留。
2. 悬停判定(与 R-024 全同)
const handleOver = (e, index) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
const rect = e.currentTarget.getBoundingClientRect();
const half = e.clientY - rect.top < rect.height / 2 ? 'top' : 'bottom';
setOverPos({ idx: index, half });
};
3. drop(事件内按坐标重算,不依赖 state 闭包)
const handleDrop = async (e, index) => {
e.preventDefault();
if (!tabs || !dragId) { setDragId(null); setOverPos(null); return; }
const rect = e.currentTarget.getBoundingClientRect();
const before = e.clientY - rect.top < rect.height / 2;
let to = before ? index : index + 1;
const from = tabs.findIndex((x) => x.id === dragId);
if (from < 0 || to < 0 || to > tabs.length) { setDragId(null); setOverPos(null); return; }
if (to === from || to === from + 1) { setDragId(null); setOverPos(null); return; } // 原地跳过写库
const next = tabs.slice();
const [moved] = next.splice(from, 1);
const insertAt = to > from ? to - 1 : to;
next.splice(insertAt, 0, moved);
const ordered = next.map((x, i) => ({ ...x, order: i }));
setDragId(null);
setOverPos(null);
await persist(ordered, '「' + moved.name + '」已移动');
};
4. 行渲染(tr 上挂间隙线样式)
tr 为表格行,直接内嵌 div 会破坏表格结构(tr 只允许 td/th)——间隙线以 td 为定位锚点绘制: 首选在每个 td 内各画一段 3px 高亮线(结构合规、视觉连续),实现纪要记录最终选型。
<tr
key={t.id}
draggable={!saving}
onDragStart={(e) => { setDragId(t.id); setOverPos(null); e.dataTransfer.effectAllowed = 'move'; }}
onDragOver={(e) => handleOver(e, i)}
onDrop={(e) => handleDrop(e, i)}
onDragEnd={() => { setDragId(null); setOverPos(null); }}
style={{ cursor: saving ? 'default' : 'grab' }}
>
<td style={{ position: 'relative', padding: '8px 6px' }}> {gapLine(i, 'td1')} ≡</td>
...
</tr>
间隙线样式(与 R-024 全同):absolute、left/right 内缩 6、height 3、borderRadius 2、 背景 primary token;top:0(插前)/ bottom:0(插后)。
边界与不做
- 服务端/API/表格列结构/持久化逻辑零改动;
- 不动「策略分组」子 tab(该处无排序交互);
- 不引入第三方 DnD 库。
回归
- pnpm typecheck + build(SettingsSection 变更需重编 web bundle);
- 页面人工验收:Tab 设置拖动 → 间隙线位置正确(上=前/下=后)、整行高亮消失、drop 即存、 拖回原位不重复写库、刷新页面后顺序保持。