element-plus table表格实现可拖拽

1,954 阅读3分钟

1 最终效果

element-plus的原生table没有支持拖拽的能力,需要引用 SortableJS 来实现拖拽的功能。

最终效果如下: 表格拖拽效果图

体验地址 zhonghuitech.github.io/vfg/#/st

2 解决方案

2.1 安装 SortableJS

安装 SortableJS

pnpm install sortablejs

2.2 表格拖拽

所有代码如下: 源码地址: github.com/zhonghuitec…

<template>
    <div>
        <el-row style="margin-top:50px" align="middle" group="componentsGroup" animation="340" :gutter="24">
            <el-col :span="12">
                <div style="display: flex;align-items: center;justify-content: center;margin-bottom: 10px;">
                    <span style="color: orange;">实际效果</span>
                </div>

                <el-table ref="tabA" class="elTable" :data="dList">
                    <el-table-column label="排序号" width="150px" align="center" fixed prop="num">
                        <template #default="scope">
                            <div style="display: flex; align-items: center" :attrabc="scope.row.num" class="attrabcccc">
                                <span style="margin-left: 10px">{{ scope.row.num }}</span>
                            </div>
                        </template>
                    </el-table-column>
                    <el-table-column label="姓名" fixed align="center" prop="name" />
                    <el-table-column label="操作" align="center" fixed="right" width="150px"
                        class-name="small-padding fixed-width">
                        <template #default="scope">
                            <el-button type="primary" link icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
                            <el-button type="danger" link icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
                        </template>
                    </el-table-column>
                </el-table>
            </el-col>
            <el-col :span="12" style="border-left: 1px solid #409eff;">
                <div style="display: flex;align-items: center;justify-content: center;margin-bottom: 10px;">
                    <span style="color: green;">期望效果</span>
                </div>

                <el-table ref="tabB" :data="newList">
                    <el-table-column label="排序号" width="150px" align="center" fixed prop="num" />
                    <el-table-column label="姓名" fixed align="center" prop="name" />
                    <el-table-column label="操作" align="center" fixed="right" width="150px"
                        class-name="small-padding fixed-width">
                        <template #default="scope">
                            <el-button type="primary" link icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
                            <el-button type="danger" link icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
                        </template>
                    </el-table-column>
                </el-table>
            </el-col>
        </el-row>

        <div style="display: flex;align-items: center;justify-content: center;margin-top: 10px;">
            <el-button style="margin-left: 50px;margin-top: 20px;" type="warning" icon="WarningFilled"
                @click="savAction(false)">提交排序</el-button>
            <el-button style="margin-left: 20px;margin-top: 20px;" type="success" icon="SuccessFilled"
                @click="savAction(true)">提交排序(修复后)</el-button>
            <el-button style="margin-left: 20px;margin-top: 20px;" icon="RefreshLeft" @click="iniData" circle />
        </div>
    </div>

</template>

<script setup>
import Sortable from 'sortablejs';
import { onMounted, ref, toRaw, watch } from "vue"

const initArr = [
    { num: 1, name: "N1" },
    { num: 2, name: "N2" },
    { num: 3, name: "N3" },
    { num: 4, name: "N4" },
    { num: 5, name: "N5" },
]
const newList = ref(initArr)  // newList 永远保持最新拖拽完后的排序
const dList = ref(initArr.map(i => i))

const sortInstance = ref(null)
const tabA = ref(null)
const tabB = ref(null)

const iniData = () => {
    newList.value = initArr
    dList.value = initArr.map(i => i)
    forceReoderChild()
}

const initSort = () => {
    console.log('init sort.........')
    const table = document.querySelector(".elTable .el-table__body-wrapper tbody");
    console.log(table)
    sortInstance.value = new Sortable(table, {
        group: 'shared',
        animation: 150,
        ghostClass: 'sortable-ghost', //拖拽样式
        easing: 'cubic-bezier(1, 0, 0, 1)',
        store: null,
        draggable: '.el-table__row',
        onStart: (item) => {
            console.log('onstart...');
            console.log(sortInstance.value.toArray())
        },
        onSort: function (/**Event*/evt) {
            console.log('onsort...')
            console.log(sortInstance.value.toArray())
        },
        // 结束拖动事件
        onEnd: (item) => {
            console.log(item);
            console.log('oldIdx=' + item.oldIndex + ', newIdx=' + item.newIndex)
            onEnd(item)
        },
    })
}

const handleUpdate = (row) => {
}

const handleDelete = (row) => {
}

onMounted(() => {
    initSort()
})

// 强制原生排序
const forceReoderChild = () => {
    // 找到所有 children
    const childCols = document.querySelectorAll('.attrabcccc')
    let sortArr = []
    // 找到 list 的排序值,按这个值重新排序下这个 children
    // 举例: 2 3 1 4 7
    for (let i = 0; i < childCols.length; i++) {
        sortArr[i] = parseInt(childCols[i].attributes['attrabc'].value)
    }
    const sortedArr = sortArr.map(i => i).sort((a, b) => {
        return a < b ? -1 : (a > b ? 1 : 0)
    })
    // 重新排序后找到真正的下标
    let sortedArrMap = {}
    sortedArr.forEach((item, idx) => {
        sortedArrMap[item] = idx
    })

    // sortedArray 为最后排序好的数组
    let sortedArray = []
    const rootEl = sortInstance.value.el;
    sortArr.forEach((item, idx) => {
        const idxOri = sortedArrMap[item]
        sortedArray[idxOri] = rootEl.children[idx]
    })

    // 删除原数组数据
    for (let i = 0; i < rootEl.children.length; i++) {
        rootEl.removeChild(rootEl.children[i])
    }

    // 将排序好的数组 append 到 rootEl
    sortedArray.forEach((item, idx) => {
        rootEl.appendChild(item)
    })
}

const savAction = (forceReorder) => {
    console.log('save action')

    const reOrList = toRaw(newList.value).map((item, idx) => {
        return {
            num: idx + 1,
            name: item.name
        }
    })
    newList.value = reOrList
    dList.value = reOrList

    if (forceReorder) {
        forceReoderChild()
    }
}

const onEnd = (item) => {
    const newIdx = item.newIndex
    const oldIdx = item.oldIndex
    // 生成新的排序后列表
    const newOrderList = toRaw(newList.value).map(item => item)
    const currentRow = newOrderList.splice(oldIdx, 1)[0]
    newOrderList.splice(newIdx, 0, currentRow)

    newList.value = newOrderList
    // dList.value = newOrderList
}
</script>

<style></style>

2.3 遇到问题

正常实现后,如果没有后端交互,一般不会有问题,一旦有交互,后端返回了修改后的数据后会遇到一个 Bug。如下: zhonghuitech.github.io/vfg/#/st 我们把 N1 拖拽到第 3 个位置,得到如下: image.png 接着我们提交排序,BUG 体现: image.png 这里,我们提交到后端(示例里只是做了一个模拟)后,后端重新修改了排序号,导致左边的表排序混乱。

2.4 问题原因

这个问题尝试了很多方法,都没有解决。 网上有人尝试过这个方法: github.com/SortableJS/…

问题原因image.png

其实前端显示了后端返回的数据,只不过顺序是错的。从上面的图,我们可以看到,其实SortableJS接管了结果列表的顺序(在第 5 步里,将结果 list 作了像第 1 步那个的排序)。

2.5 问题解决

找到问题原因了,就很容易解决了。只需要强制重新按后端返回的结果排序下就行。

// 强制原生排序
const forceReoderChild = () => {
    // 找到所有 children
    const childCols = document.querySelectorAll('.attrabcccc')
    let sortArr = []
    // 找到 list 的排序值,按这个值重新排序下这个 children
    // 举例: 2 3 1 4 7
    for (let i = 0; i < childCols.length; i++) {
        sortArr[i] = parseInt(childCols[i].attributes['attrabc'].value)
    }
    const sortedArr = sortArr.map(i => i).sort((a, b) => {
        return a < b ? -1 : (a > b ? 1 : 0)
    })
    // 重新排序后找到真正的下标
    let sortedArrMap = {}
    sortedArr.forEach((item, idx) => {
        sortedArrMap[item] = idx
    })

    // sortedArray 为最后排序好的数组
    let sortedArray = []
    const rootEl = sortInstance.value.el;
    sortArr.forEach((item, idx) => {
        const idxOri = sortedArrMap[item]
        sortedArray[idxOri] = rootEl.children[idx]
    })

    // 删除原数组数据
    for (let i = 0; i < rootEl.children.length; i++) {
        rootEl.removeChild(rootEl.children[i])
    }

    // 将排序好的数组 append 到 rootEl
    sortedArray.forEach((item, idx) => {
        rootEl.appendChild(item)
    })
}

注意这里: image.png

修复后的效果如下: image.png

3 源码和体验

源码地址:github.com/zhonghuitec…

体验地址:zhonghuitech.github.io/vfg/#/st