Skip to content

Draggable 拖拽

基于 SortableJS 封装的列表拖拽排序能力,适合任务排序、跨列表流转、物料选择和轻量级编排场景。

何时使用

  • 需要让用户调整一组同级数据的显示顺序。
  • 需要在多个列表之间移动或复制条目。
  • 需要组合 SortableJS 的高级能力,同时希望数据由 Vue 响应式状态驱动。

基础用法

需求评审确认组件 API 和边界状态
交互设计补齐拖拽反馈和禁用态
组件实现接入 SortableJS 适配层
回归验证构建和文档预览检查
vue
<template>
  <x-draggable v-model="list" animation="150" ghost-class="ghost">
    <div v-for="item in list" :key="item.id" class="item">
      {{ item.title }}
    </div>
  </x-draggable>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const list = ref([
  { id: 1, title: '需求评审' },
  { id: 2, title: '交互设计' },
  { id: 3, title: '组件实现' },
]);
</script>

拖拽手柄

表单校验
弹窗反馈
列表排序
vue
<template>
  <x-draggable v-model="list" handle=".drag-handle" animation="150">
    <div v-for="item in list" :key="item.id">
      <button class="drag-handle" type="button" aria-label="拖拽排序">⋮⋮</button>
      <span>{{ item.title }}</span>
    </div>
  </x-draggable>
</template>

禁用状态

可拖拽项 A
可拖拽项 B
可拖拽项 C
vue
<template>
  <x-button @click="disabled = !disabled">
    {{ disabled ? '恢复拖拽' : '禁用拖拽' }}
  </x-button>
  <x-draggable v-model="list" :disabled="disabled">
    <div v-for="item in list" :key="item.id">{{ item.title }}</div>
  </x-draggable>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const disabled = ref(false);
const list = ref([{ id: 1, title: '可拖拽项' }]);
</script>

跨列表拖拽

待处理

补 Props 表
补 Events 表
补 Methods 表
拖到这里

已完成

基础示例
拖到这里
vue
<template>
  <x-draggable
    v-model="todo"
    class="drop-list"
    :group="{ name: 'tasks' }"
    ghost-class="ghost"
    chosen-class="chosen"
    :empty-insert-threshold="48"
  >
    <div v-for="item in todo" :key="item.id">{{ item.title }}</div>
  </x-draggable>
  <x-draggable
    v-model="done"
    class="drop-list"
    :group="{ name: 'tasks' }"
    ghost-class="ghost"
    chosen-class="chosen"
    :empty-insert-threshold="48"
  >
    <div v-for="item in done" :key="item.id">{{ item.title }}</div>
  </x-draggable>
</template>

克隆模式

组件物料

输入框
选择器
按钮

画布

表单区块
拖到这里
vue
<template>
  <x-draggable
    v-model="source"
    :group="{ name: 'builder', pull: 'clone', put: false }"
    :sort="false"
    :clone="cloneItem"
  >
    <div v-for="item in source" :key="item.id">{{ item.title }}</div>
  </x-draggable>
  <x-draggable
    v-model="target"
    class="drop-list"
    :group="{ name: 'builder' }"
    ghost-class="ghost"
    :empty-insert-threshold="48"
  >
    <div v-for="item in target" :key="item.id">{{ item.title }}</div>
  </x-draggable>
</template>

事件与移动限制

固定在顶部固定项
可排序任务 A可排序
可排序任务 B可排序
可排序任务 C可排序
vue
<template>
  <div class="fixed-item">固定在顶部</div>
  <x-draggable v-model="sortableList" :allow-move="allowMove" @move="handleMove" @end="handleEnd">
    <div v-for="item in sortableList" :key="item.id">
      {{ item.title }}
    </div>
  </x-draggable>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const sortableList = ref([
  { id: 1, title: '可排序任务 A' },
  { id: 2, title: '可排序任务 B' },
  { id: 3, title: '可排序任务 C' },
]);
const allowMove = () => true;
const handleMove = (event) => {
  console.log('move', event.oldDraggableIndex, event.newDraggableIndex);
};
const handleEnd = (event) => {
  console.log(event.oldDraggableIndex, event.newDraggableIndex);
};
</script>

组合式 API

组合式 API
手动暂停
读取顺序
等待操作
vue
<template>
  <div ref="root">
    <div v-for="item in list" :key="item.id">{{ item.title }}</div>
  </div>
  <button @click="api.pause()">暂停</button>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { useDraggable } from 'x-next';

const root = ref<HTMLElement | null>(null);
const list = ref([{ id: 1, title: '组合式 API' }]);
const api = useDraggable(root, list, { animation: 150 });
</script>

指令用法

指令挂载
自动销毁
按需使用
vue
<template>
  <div v-draggable="[list, { animation: 150 }]">
    <div v-for="item in list" :key="item.id">{{ item.title }}</div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const list = ref([{ id: 1, title: '指令挂载' }]);
</script>

按需导入

ts
import { Draggable, useDraggable, vDraggable } from 'x-next';
import type { DraggableEvent, UseDraggableOptions, UseDraggableReturn } from 'x-next';

Props

参数说明类型默认值
modelValue拖拽列表数据any[]必填
tag外层渲染标签string'div'
target指定拖拽目标元素选择器或元素string-
customUpdate自定义同列表排序后的数据更新逻辑(event: DraggableEvent) => void-
animation拖拽动画时长number | string-
groupSortable 分组配置string | object-
disabled是否禁用拖拽booleanfalse
sort是否允许当前列表内部排序booleantrue
handle拖拽手柄选择器string-
draggable可拖拽元素选择器string-
cloneclone 模式下复制数据的方法(item: any) => anyJSON 深拷贝
allowMove移动控制回调,返回 false 可阻止移动(event: DraggableEvent, originalEvent: Event) => boolean | void-
moveValidatorallowMove 的别名,适合表达校验语义(event: DraggableEvent, originalEvent: Event) => boolean | void-
immediateuseDraggable 是否在挂载后立即创建实例booleantrue

其他 SortableJS 选项会透传给内部实例。

Events

事件名说明回调参数
update:modelValue列表顺序变化时触发value: any[]
update同列表排序完成event: DraggableEvent
start拖拽开始event: DraggableEvent
add元素被添加到当前列表event: DraggableEvent
remove元素从当前列表移出event: DraggableEvent
choose选中拖拽元素event: DraggableEvent
unchoose取消选中拖拽元素event: DraggableEvent
end拖拽结束event: DraggableEvent
sort排序发生变化event: DraggableEvent
filter命中过滤元素event: DraggableEvent
clone创建 DOM 克隆event: DraggableEvent
move移动过程监听事件;需要阻止移动时使用 allowMove / moveValidatorevent: DraggableEvent, originalEvent: Event
changeSortable change 事件event: DraggableEvent

Slots

插槽名说明参数
default拖拽内容UseDraggableReturn

Methods

组件实例和 useDraggable 都会暴露以下方法。

方法名说明参数
start创建或重建 Sortable 实例target?: HTMLElement
pause暂停拖拽-
resume恢复拖拽-
destroy销毁 Sortable 实例-
option读取或设置 Sortable 选项name, value?
toArray返回 Sortable 的 data-id 顺序-
save调用 Sortable store 保存-
closest查找最近的 Sortable 匹配元素...args

已知限制

  • 当前键盘排序和屏幕阅读器提示仍需业务侧自行补充。
  • @move 是 Vue 事件监听,若要通过返回 false 阻止移动,请使用 :allow-move:move-validator
  • 使用 draggable / filter 时,列表数据应只对应可拖拽项。