组件/SortableList 拖拽排序

SortableList 拖拽排序

支持拖拽与键盘重排的可排序列表

已验证自 0.3.4

用法

基础

每项需要稳定的字符串 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 参考

属性

属性名类型默认值说明
modelValueSortableListItem[]必填有序列表数据,每项需有稳定的字符串 id。
disabledbooleanfalse禁用拖拽、键盘重排与排序事件。
handlebooleanfalse要求从 [data-tx-sort-handle="true"] 开始拖拽。
dragMode'pointer' | 'native''pointer'pointer 行跟手、其余行让位;native 走 HTML5 拖放,可拖出本列表。
ariaLabelstring-列表的可访问名称。
itemLabel(item) => string-播报中的条目名称,缺省为 id。
labelsSortableListLabels-grabbed / moved / dropped / cancelled 与内置 handle 的播报模板,支持 {item}、{position}、{size}。

事件

事件名参数说明
update:modelValueSortableListItem[]指针拖拽松手时触发一次;原生拖拽与键盘每移动一步触发一次。
reorder{ from: number, to: number, items: SortableListItem[] }交互结束时触发一次,带起止索引。

插槽

插槽名Props说明
item{ item, dragging, grabbed, index, handleAttrs }自定义每一项,缺省显示 id;handleAttrs 展开到发起拖拽的元素上。

SortableListItem

字段类型说明
idstringkey 与拖拽状态使用的稳定身份;其它字段保留。

概述

  • 根节点为 role="list",每项为 role="listitem"。
  • 预览由组件持有,行的移动不依赖宿主写回 modelValue;重排发出浅拷贝数组,条目对象保持引用。
  • 指针拖拽移动 4px 后才开始,结束时的 click 被吞掉;输入框与可编辑区域不发起拖拽;Escape 或 pointercancel 取消且不发事件。
  • 原生拖拽在 dragover 经过行时即重排;拖到列表外释放同样生效。
  • 键盘:整个列表一个 Tab 停靠点;空格或回车拿起、方向键移动、再按放下,Escape 恢复原顺序,失焦自动放下;每一步经 role="status" 区域播报。
  • 减少动态效果时不抬升、弹簧时长为 0;被拖的行仍跟随指针。

技术实现

  • 位移与抬升分别写在 translate 与 scale 属性上,各自计时;弹簧由 resolveTransition 编译为 CSS linear() 曲线。
  • 源码:packages/tuffex/packages/components/src/sortable-list/。
查看源码
packages/tuffex/packages/components/src/sortable-list/index.ts