Files
one_divine_lot/docs/04-迭代记录/21-Tab设置排序间隙高亮统一/技术实现方案.md
T

85 lines
3.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 技术实现方案: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
<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 tokentop:0(插前)/ bottom:0(插后)。
## 边界与不做
- 服务端/API/表格列结构/持久化逻辑零改动;
- 不动「策略分组」子 tab(该处无排序交互);
- 不引入第三方 DnD 库。
## 回归
- pnpm typecheck + buildSettingsSection 变更需重编 web bundle);
- 页面人工验收:Tab 设置拖动 → 间隙线位置正确(上=前/下=后)、整行高亮消失、drop 即存、
拖回原位不重复写库、刷新页面后顺序保持。