Files

89 lines
4.5 KiB
Markdown
Raw Permalink 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.
# 技术实现方案:20-策略列设置拖拽排序
## 总体思路
在 ColumnSettingsPopover.jsx 内复用 Tab 设置(SettingsSection.jsx TabSettings)已验证的
**原生 HTML5 Drag & Drop** 模式:行 draggable + onDragStart/onDragOver/onDrop + 落点高亮 + drop 重排,
零第三方依赖。持久化沿用现有 strategy-columns/update 整表覆盖 RPC,把「保存」语义从手动按钮改为
每次变更(drop / 勾选 / ↑↓)立即提交。
## 改动点(单文件:src/client/views/ColumnSettingsPopover.jsx
### 1. 状态
- 已有 `dragKey`(未被使用,正好用于 DnD):当前被拖行的 column.key
- 新增 `overKey`:当前悬停落点的 column.key(用于高亮);
- `saving` 保持:正在写库时禁止再次拖动/勾选。
### 2. 持久化函数(替换原 save)
- 新增 `persist(nextList, msg)`:提交整表 `{key, visible}` → strategy-columns/update
成功 Toast 提示 + onSaved() 刷新;失败 Toast 错误 + 本地回滚(还原为服务端最新列配置)。
- 勾选显隐:切完立即 persist(不再等「保存」按钮);
- ↑↓ 箭头:移动完立即 persist;
- drop 重排:重排完立即 persist(对齐 Tab 设置)。
### 3. 拖拽事件(照搬 TabSettings 模式)
```jsx
<div key={c.key} draggable={!saving}
onDragStart={(e) => { setDragKey(c.key); e.dataTransfer.effectAllowed = 'move'; }}
onDragOver={(e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; setOverKey(c.key); }}
onDragLeave={() => { if (overKey === c.key) setOverKey(null); }}
onDrop={(e) => { e.preventDefault(); handleDrop(c.key); }}
style={{ cursor: saving ? 'default' : 'grab',
background: overKey === c.key ? 'color-mix(in srgb, var(--dsw-alias-state-business-primary, #1565c0) 12%, transparent)' : 'transparent' }}>
<span></span>
<button></button><button></button>
<input type="checkbox" ... />
...
</div>
```
### 4. handleDrop(照搬 TabSettings 逻辑,key 定位)
```js
const handleDrop = (targetKey) => {
if (!dragKey || dragKey === targetKey) { setDragKey(null); setOverKey(null); return; }
const from = list.findIndex((c) => c.key === dragKey);
const to = list.findIndex((c) => c.key === targetKey);
if (from < 0 || to < 0) { setDragKey(null); setOverKey(null); return; }
const next = list.slice();
const [moved] = next.splice(from, 1);
next.splice(to, 0, moved);
setDragKey(null); setOverKey(null);
persist(next, `${moved.label}」已移动`, next);
};
```
### 5. UI 文案
- 标题下提示改为:「勾选显示列,按住 ≡ 拖动调整顺序;代码/名称/操作固定」;
- 底部按钮区:去掉「保存」,保留「关闭」;
- 每行最前加 ≡ 手柄(配色沿用 Tab 设置 `#999`/label-tertiary)。
## 边界与不做
- 服务端、API、表格渲染零改动(strategy-columns/update 已是整表覆盖语义);
- 固定列不参与排序,不进列表(现状维持);
- 不做方案 C(表格表头直接拖列头),不引入第三方 DnD 库;
- `saving` 期间禁用拖拽与勾选,防并发写。
## 回归
- scripts/test-r013-columns.mjs(列配置归一化/读写的服务端逻辑,未改动应全绿);
- npm run typecheck + buildtsdown 产物,弹层 JSX 变更需重编 web bundle);
- 页面人工验收:拖拽重排 / 落点高亮 / drop 即存 / ↑↓ 微调 / 刷新保持。
## 实现纪要(2026-09-10,老师验收反馈后修订)
> 老师反馈:初版按原方案做的是「整行高亮」(overKey → 目标行淡蓝底),拖动时无法判断会插入目标列的**前面还是后面**,
> 观感不符合习惯。定稿修订为 **间隙高亮线**:
- 落点判定:拖动悬停时读鼠标在目标行内的 **Y 坐标**getBoundingClientRect),上半 → 插入该行**之前**(线画行上缘),
下半 → 插入该行**之后**(线画行下缘);两行之间显示 3px 主色圆角高亮线(absolute 定位在行自身 padding 内,left/right 6
无负偏移无裁剪风险),指示精确插入位置。
- drop 判定:drop 事件内直接按坐标重算 before(不依赖 state 闭包,避免滞后);无操作原地(insertAt 等于原槽位)
跳过写库;移除后目标下标左移(to > from 时 to-1)。
- 状态:overKey(整行)→ overPos { idx, half }(间隙线锚点行 + 上下缘);保留 onDragEnd 清理防拖出弹层残留。
- 边界维持:固定列不参与、服务端零改动、↑↓ 兜底保留、立即持久化不变。