背景
常见需求:通过拖拽来改变顺序。
像 el-table 组件的行拖拽,自定义列表的拖拽等,可以用 SortableJS 实现。
另外:SortableJS 使用 Drag and Drop 实现拖放,而不是鼠标事件,无法限制拖拽区域
实现
<template>
<div ref="tableWrapperRef">
<!-- 注意:el-table必须设置row-key属性,字段必须唯一,否则拖拽后顺序不对 -->
<el-table :data="tableData" row-key="id">
<el-table-column type="index" width="30" />
<el-table-column width="40">
<template #default="scope">
<div style="display: flex; align-items: center">
<SvgIcon
v-if="!scope.row.disabledRowMove"
name="drag"
:class="['move-icon', { 'move-disabled': scope.row.disabledRowMove }]"
/>
</div>
</template>
</el-table-column>
<el-table-column label="Date" prop="date"></el-table-column>
<el-table-column label="Name" prop="name"></el-table-column>
<el-table-column label="Address" prop="address"></el-table-column>
</el-table>
<div class="pre-wrapper">
<pre v-for="item in tableData">{{ item }}</pre>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import Sortable from 'sortablejs';
const tableData = ref([]);
tableData.value = [
{
id: 0,
date: '2023-11-03',
name: '小明',
address: '浙江温州',
disabledRowMove: true,
},
{
id: 1,
date: '2023-11-02',
name: '小张',
address: '浙江杭州',
},
{
id: 2,
date: '2023-11-04',
name: '小李',
address: '四川成都',
},
{
id: 3,
date: '2023-11-01',
name: '小王',
address: '广东深圳',
disabledRowMove: true,
},
];
const tableWrapperRef = ref();
const initSortable = () => {
// vue3的setup语法没办法直接拿到组件的DOM元素,所以对el-table做了一层包装,先获取包装元素,再通过querySelector拿到el-table的tbody元素
const tbody = tableWrapperRef.value.querySelector('.el-table__body-wrapper tbody');
Sortable.create(tbody, {
// 指定拖拽手柄的选择器
handle: '.move-icon',
// 指定不可拖动的选择器
filter: '.move-disabled',
// 控制动画速度
animation: 300,
// 拖拽结束事件
onEnd: (evt) => {
const { newIndex, oldIndex } = evt;
if (newIndex && !tableData.value[newIndex].disabledRowMove) {
// 获取并删除移动行
const moveRow = tableData.value.splice(oldIndex, 1)[0];
// 插入移动行
tableData.value.splice(newIndex, 0, moveRow);
} else {
// 移动被禁用,恢复原始顺序
const temp = JSON.parse(JSON.stringify(tableData.value));
tableData.value = [];
nextTick(() => {
tableData.value = temp;
});
}
},
});
const preWrapper = tableWrapperRef.value.querySelector('.pre-wrapper');
Sortable.create(preWrapper, {
handle: 'pre',
// 所选项
chosenClass: 'chosen-class',
// 拖动项
dragClass: 'drag-class',
// 背景项
ghostClass: 'ghost-class',
onEnd: (evt) => {
const { newIndex, oldIndex } = evt;
const moveRow = tableData.value.splice(oldIndex, 1)[0];
tableData.value.splice(newIndex, 0, moveRow);
},
});
};
onMounted(() => {
// 初始化拖拽
initSortable();
});
</script>
<style lang="scss" scoped>
.move-icon {
cursor: move;
}
</style>
<style lang="scss">
// 注意顺序!
.chosen-class {
background-color: red !important;
}
.ghost-class {
background-color: green !important;
}
.drag-class {
background-color: blue !important;
}
</style>