# 技术实现方案: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 全同) ```jsx 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 闭包) ```jsx 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 高亮线(结构合规、视觉连续),实现纪要记录最终选型。 ```jsx { 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' }} > {gapLine(i, 'td1')} ≡ ... ``` 间隙线样式(与 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 即存、 拖回原位不重复写库、刷新页面后顺序保持。