[SortableJS] 拖拽功能

187 阅读1分钟

背景

常见需求:通过拖拽来改变顺序。

像 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>