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 | - |
group | Sortable 分组配置 | string | object | - |
disabled | 是否禁用拖拽 | boolean | false |
sort | 是否允许当前列表内部排序 | boolean | true |
handle | 拖拽手柄选择器 | string | - |
draggable | 可拖拽元素选择器 | string | - |
clone | clone 模式下复制数据的方法 | (item: any) => any | JSON 深拷贝 |
allowMove | 移动控制回调,返回 false 可阻止移动 | (event: DraggableEvent, originalEvent: Event) => boolean | void | - |
moveValidator | allowMove 的别名,适合表达校验语义 | (event: DraggableEvent, originalEvent: Event) => boolean | void | - |
immediate | useDraggable 是否在挂载后立即创建实例 | boolean | true |
其他 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 / moveValidator | event: DraggableEvent, originalEvent: Event |
change | Sortable 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时,列表数据应只对应可拖拽项。