4.5 KiB
4.5 KiB
技术实现方案: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 模式)
<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 定位)
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 + build(tsdown 产物,弹层 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 清理防拖出弹层残留。
- 边界维持:固定列不参与、服务端零改动、↑↓ 兜底保留、立即持久化不变。