elementplus结合sortablejs做出表格可拖拽效果的组件
2026/8/4 14:06:52 网站建设 项目流程
  1. 1.安装
npm install sortablejs
  1. 2.在src下新建可复用的组合式函数文件夹composables,src/composables/useSortableDrag.js
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue' import Sortable from 'sortablejs' /** * 拖拽排序配置选项 * @typedef {Object} SortableOptions * @property {string} handle - 拖拽手柄选择器,默认为 '.drag-handle' * @property {number} animation - 动画持续时间(ms),默认为 150 * @property {string} easing - 缓动函数,默认为 'cubic-bezier(0.25, 0.46, 0.45, 0.94)' * @property {number} scrollThreshold - 触发滚动的边缘距离(px),默认为 80 * @property {number} scrollSpeed - 滚动速度,默认为 15 * @property {boolean} autoScroll - 是否启用自动滚动,默认为 true * @property {Function} onSort - 排序完成后的回调函数 * @property {Function} onStart - 拖拽开始时的回调函数 * @property {Function} onEnd - 拖拽结束时的回调函数 * @property {Function} onMove - 拖拽移动时的回调函数 */ /** * 拖拽排序组合式函数 * @param {string|Ref} tableRef - 表格引用 * @param {Ref<Array>} dataList - 数据列表引用 * @param {SortableOptions} options - 配置选项 * @returns {Object} 拖拽控制方法 */ export function useSortableDrag(tableRef, dataList, options = {}) { const { handle = '.drag-handle', animation = 150, easing = 'cubic-bezier(0.25, 0.46, 0.45, 0.94)', scrollThreshold = 80, scrollSpeed = 15, autoScroll = true, onSort = null, onStart = null, onEnd = null, onMove = null } = options let sortableInstance = null let autoScrollTimer = null let autoScrollDirection = 0 // 0: 停止, 1: 向下, -1: 向上 const isDragging = ref(false) // 停止自动滚动 const stopAutoScroll = () => { if (autoScrollTimer) { clearInterval(autoScrollTimer) autoScrollTimer = null } autoScrollDirection = 0 } // 启动自动滚动 const startAutoScroll = (tableWrapper) => { if (!autoScroll || autoScrollTimer) return autoScrollTimer = setInterval(() => { if (autoScrollDirection === 0 || !tableWrapper) return const scrollTop = tableWrapper.scrollTop const maxScrollTop = tableWrapper.scrollHeight - tableWrapper.clientHeight let newScrollTop = scrollTop + autoScrollDirection * scrollSpeed // 限制滚动范围 if (newScrollTop < 0) { newScrollTop = 0 autoScrollDirection = 0 stopAutoScroll() } else if (newScrollTop > maxScrollTop) { newScrollTop = maxScrollTop autoScrollDirection = 0 stopAutoScroll() } tableWrapper.scrollTop = newScrollTop }, 16) // 约 60fps } // 获取表格包装器 const getTableWrapper = () => { if (!tableRef.value) return null // 兼容不同的表格组件 if (tableRef.value.$el) { return tableRef.value.$el.querySelector('.el-table__body-wrapper') } return tableRef.value.querySelector?.('.el-table__body-wrapper') || null } // 获取表格主体 const getTableBody = () => { const wrapper = getTableWrapper() return wrapper?.querySelector('tbody') || null } // 初始化拖拽 const initSortable = () => { if (!tableRef.value) { console.warn('useSortableDrag: tableRef is not ready') return } const tableBody = getTableBody() const tableWrapper = getTableWrapper() if (!tableBody || !tableWrapper) { console.warn('useSortableDrag: table body or wrapper not found') return } // 如果已经存在实例,先销毁 destroySortable() sortableInstance = Sortable.create(tableBody, { handle, animation, easing, scroll: false, // 禁用默认滚动,使用自定义滚动 onStart: (evt) => { isDragging.value = true document.body.style.cursor = 'grabbing' if (onStart) onStart(evt) }, onMove: (evt) => { if (!autoScroll) { if (onMove) onMove(evt) return } const { originalEvent } = evt const clientY = originalEvent.clientY if (!tableWrapper) return const rect = tableWrapper.getBoundingClientRect() const relativeY = clientY - rect.top // 检查是否在边缘区域 if (relativeY < scrollThreshold) { // 向上滚动 autoScrollDirection = -1 startAutoScroll(tableWrapper) } else if (relativeY > rect.height - scrollThreshold) { // 向下滚动 autoScrollDirection = 1 startAutoScroll(tableWrapper) } else { // 停止滚动 autoScrollDirection = 0 stopAutoScroll() } if (onMove) onMove(evt) }, onEnd: (evt) => { // 拖拽结束时停止自动滚动 stopAutoScroll() document.body.style.cursor = '' isDragging.value = false const { oldIndex, newIndex } = evt if (oldIndex === newIndex) { if (onEnd) onEnd(evt) return } // 移动数据 const [movedItem] = dataList.value.splice(oldIndex, 1) dataList.value.splice(newIndex, 0, movedItem) if (onSort) onSort(evt, movedItem, oldIndex, newIndex) if (onEnd) onEnd(evt) } }) } // 销毁拖拽实例 const destroySortable = () => { if (sortableInstance) { sortableInstance.destroy() sortableInstance = null } stopAutoScroll() } // 更新拖拽实例(当数据变化时重新初始化) const refreshSortable = () => { nextTick(() => { initSortable() }) } // 禁用拖拽 const disableSortable = () => { if (sortableInstance) { sortableInstance.option('disabled', true) } } // 启用拖拽 const enableSortable = () => { if (sortableInstance) { sortableInstance.option('disabled', false) } } // 设置拖拽手柄选择器 const setHandle = (selector) => { if (sortableInstance) { sortableInstance.option('handle', selector) } } // 生命周期钩子 onMounted(() => { // 延迟初始化,确保 DOM 已渲染 setTimeout(initSortable, 100) }) onBeforeUnmount(() => { destroySortable() }) return { initSortable, destroySortable, refreshSortable, disableSortable, enableSortable, setHandle, isDragging, stopAutoScroll } }
  1. 3.在需要用到的地方添加
    <el-table-column align="center" fixed width="40" label=" "> <template #default> <el-icon class="drag-handle" style="cursor: grab; color: #909399"> <Operation /> </el-icon> </template> </el-table-column> // 使用拖拽排序功能 const { refreshSortable } = useSortableDrag(tableRef, chainTableList, { handle: '.drag-handle', animation: 150, autoScroll: true, scrollThreshold: 80, scrollSpeed: 15, disabled: false }) async function getData() { …… refreshSortable() }

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询