# 技术实现方案: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