SortableList 拖拽排序
支持拖拽与键盘重排的可排序列表
用法
基础
每项需要稳定的字符串 id;item 插槽的 dragging 标记被拖的行。
示例加载中...
拖拽模式
默认的 pointer 让行跟手、其余行弹性让位;条目要拖出本列表(如看板跨列)时用 dragMode="native"。
<template>
<TxSortableList v-model="column.cards" drag-mode="native" />
</template>
拖拽手柄模式
handle 要求拖拽从 [data-tx-sort-handle="true"] 内开始;默认行自带手柄,自定义插槽把 handleAttrs 展开到手柄元素上。
<template>
<TxSortableList v-model="steps" handle @reorder="saveOrder">
<template #item="{ item, handleAttrs }">
<div>
<button type="button" v-bind="handleAttrs" aria-label="拖拽项目">☰</button>
<span>{{ item.title }}</span>
</div>
</template>
</TxSortableList>
</template>
持久化排序
<script setup lang="ts">
const tasks = ref([{ id: 'draft' }, { id: 'review' }, { id: 'ship' }])
async function persistOrder({ items }: { items: Array<{ id: string }> }) {
tasks.value = items
await api.saveTaskOrder(items.map(item => item.id))
}
</script>
<template>
<TxSortableList v-model="tasks" @reorder="persistOrder" />
</template>
最佳实践
- 使用稳定的持久化 id,不要用数组索引作 id。
- 用
v-model更新本地数组;服务端需要顺序时在reorder中持久化。 - 行内有按钮、链接、输入框或可选中文本时开启
handle;触屏上的长列表也开启,避免整行接管滑动。 - id 不可读时传
itemLabel,否则读屏会念出plugin-a1b3这样的 id。 - 列表规模保持适中;超长列表改用其它拖拽策略并配合虚拟化。
API 参考
属性
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
modelValue | SortableListItem[] | 必填 | 有序列表数据,每项需有稳定的字符串 id。 |
disabled | boolean | false | 禁用拖拽、键盘重排与排序事件。 |
handle | boolean | false | 要求从 [data-tx-sort-handle="true"] 开始拖拽。 |
dragMode | 'pointer' | 'native' | 'pointer' | pointer 行跟手、其余行让位;native 走 HTML5 拖放,可拖出本列表。 |
ariaLabel | string | - | 列表的可访问名称。 |
itemLabel | (item) => string | - | 播报中的条目名称,缺省为 id。 |
labels | SortableListLabels | - | grabbed / moved / dropped / cancelled 与内置 handle 的播报模板,支持 {item}、{position}、{size}。 |
事件
| 事件名 | 参数 | 说明 |
|---|---|---|
update:modelValue | SortableListItem[] | 指针拖拽松手时触发一次;原生拖拽与键盘每移动一步触发一次。 |
reorder | { from: number, to: number, items: SortableListItem[] } | 交互结束时触发一次,带起止索引。 |
插槽
| 插槽名 | Props | 说明 |
|---|---|---|
item | { item, dragging, grabbed, index, handleAttrs } | 自定义每一项,缺省显示 id;handleAttrs 展开到发起拖拽的元素上。 |
SortableListItem
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | key 与拖拽状态使用的稳定身份;其它字段保留。 |
概述
- 根节点为
role="list",每项为role="listitem"。 - 预览由组件持有,行的移动不依赖宿主写回
modelValue;重排发出浅拷贝数组,条目对象保持引用。 - 指针拖拽移动 4px 后才开始,结束时的 click 被吞掉;输入框与可编辑区域不发起拖拽;Escape 或
pointercancel取消且不发事件。 - 原生拖拽在
dragover经过行时即重排;拖到列表外释放同样生效。 - 键盘:整个列表一个 Tab 停靠点;空格或回车拿起、方向键移动、再按放下,Escape 恢复原顺序,失焦自动放下;每一步经
role="status"区域播报。 - 减少动态效果时不抬升、弹簧时长为 0;被拖的行仍跟随指针。
技术实现
- 位移与抬升分别写在
translate与scale属性上,各自计时;弹簧由resolveTransition编译为 CSSlinear()曲线。 - 源码:
packages/tuffex/packages/components/src/sortable-list/。
查看源码
packages/tuffex/packages/components/src/sortable-list/index.ts