vuedraggable Sortable.js 实现拖拽排序功能VUE3
一、页面效果:通过拖拽☰完成拖拽并立即更新顺序 
二、方法:
2-1:引用:
npm install vuedraggable@next
2-2:代码:
<template><div class="main" v-loading="loading"><table class="order_write order_table_top"><thead></thead><tbody><tr><th>{{ t('common.当前顺序') }}</th><td colspan="3">{{ seqArray }}</td></tr><tr><th>{{ t('common.中心管理') }}</th><td colspan="3"></td></tr><tr><th>{{ t('common.顺序') }}</th><td><!-- 拖拽容器 --><div ref="sortableContainer" class="sortable-container"><div v-for="(item, index) in tableList" :key="item.CENTER_SEQ" class="draggable-item"><span class="drag-handle">☰</span><el-input readonly class="iptBox1":value="`${item.CENTER_NM}`" /></div></div></td></tr></tbody></table><div class="btn-box"><el-button type="primary" @click="handleSave" size="small">{{ t('btn.save') }}</el-button><el-button type="primary" plain @click="handleClose">{{ t('btn.close') }}</el-button></div></div>
</template><script setup>
import { ref, onMounted } from 'vue';
import Sortable from 'sortablejs';
import { useI18n } from 'vue-i18n';
import { toast } from '~/composables/util';
import { closePopup } from '~/composables/popupManager';
import { apiService } from '~/api/setting';const { t } = useI18n();const loading = ref(false);
const tableList = ref([]);
const seqArray = ref([]);
const sortableContainer = ref(null);// 创建广播通道
const channel = new BroadcastChannel('vue-apps-channel');// 获取列表数据
const getList = async () => {loading.value = true;try {await apiService.getList().then((res) => {if (res.code === 200) {tableList.value = JSON.parse(JSON.stringify(res?.data));if (tableList.value?.length > 0) {seqArray.value = tableList.value.map((item) => item.CENTER_SEQ);}} else {toast(res.msg || t('common.error'), 'error');}});} finally {loading.value = false;}
};// 初始化 Sortable.js
onMounted(() => {getList();// 初始化拖拽功能new Sortable(sortableContainer.value, {handle: '.drag-handle', // 指定拖拽手柄animation: 150, // 动画效果onEnd: (evt) => {const { oldIndex, newIndex } = evt;// 更新 tableList 的顺序const movedItem = tableList.value.splice(oldIndex, 1)[0];tableList.value.splice(newIndex, 0, movedItem);// 更新 seqArrayseqArray.value = tableList.value.map((item) => item.CENTER_SEQ);console.log('拖拽结束,当前顺序:', seqArray.value);},});
});// 保存排序结果
const handleSave = async () => {try {if (!seqArray.value.length) {toast(t('common.noDataToSave'), 'error');return;}// 更新排序数据const currentData = {CENTER_SEQ: seqArray.value,};await centerStoreService.sortCenterStore(currentData).then((res) => {if (res.code === 200) {toast(t('common.updateSuccess'));} else {toast(res.msg, 'error');}});} catch (error) {toast(t('common.error'), 'error');} finally {// 关闭窗口handleClose();}
};// 关闭窗口
const handleClose = () => {closePopup('centerSort');channel.postMessage({ type: 'popup', message: false });
};
</script><style scoped>
.main {width: 90%;padding: 0 8px;color: #313131;
}.order_write {width: 80%;margin: 20px auto;background-color: #fff;width: 100%;
}.order_write th,
.order_write td {border: 1px solid #e5e5e5;padding: 10px;border-collapse: collapse;font-size: 12px;
}.order_write th {width: 10%;font-size: 12px;color: #444;background-color: #fbfbfbee;
}.order_write td {width: 40%;
}.order_table_top {border-top: 1px solid #e5e5e5;
}.sortable-container {display: flex;flex-direction: column;
}.draggable-item {display: flex;align-items: center;margin-bottom: 5px;
}.drag-handle {margin-right: 10px;font-size: 16px;color: #aaa;cursor: move;
}.iptBox1 {width: 95%;
}.btn-box {display: flex;justify-content: center;margin-bottom: 2vh;
}
</style>