vue3 element plus table+Sortable.js 行拖动,表格拖动,表格拖动实现排序 电脑版发表于:2021/3/23 16:23 要先安装好Sortable.js依赖 ``` cnpm install sortable.js ``` tn2>拖动排序思路:直接交换不行哦,因为拖动的目标位置不会跑到拖动前的位置去哦,他还是在原来的位置这里哦,所以拖动后重新生成一下序号在存储,这就要求在所有数据里边去实现拖动排序了,而不是在搜索条件过滤后在拖动排序这种情况要禁用掉不然很难处理。 [TOC] ### 实现拖动的代码如下 可以直接复制运行 ``` <template> <div class="app-container"> <el-card> <el-table :data="state.tableData" style="width: 100%"> <el-table-column prop="date" label="Date" width="180" /> <el-table-column prop="name" label="Name" width="180" /> <el-table-column prop="address" label="Address" /> </el-table> </el-card> </div> </template> <script lang="ts" setup> import { defineAsyncComponent, reactive, toRefs, onMounted, ref } from 'vue' import Sortable from 'sortablejs' const state = reactive({ tableData: [ { date: '2016-05-03', name: 'Tom-1', address: 'No. 189, Grove St, Los Angeles', }, { date: '2016-05-02', name: 'Tom-2', address: 'No. 189, Grove St, Los Angeles', }, { date: '2016-05-04', name: 'Tom-3', address: 'No. 189, Grove St, Los Angeles', }, { date: '2016-05-01', name: 'Tom-4', address: 'No. 189, Grove St, Los Angeles', }, ] }) //行拖拽 const dragSort = () => { let that = this; // 首先获取需要拖拽的dom节点 const el1 = document.querySelectorAll('.el-table__body-wrapper')[0].querySelectorAll('table > tbody')[0]; Sortable.create(el1, { disabled: false, // 是否开启拖拽,false为开启 ghostClass: 'sortable-ghost', //拖拽样式 animation: 150, // 拖拽延时,效果更好看 group: { // 是否开启跨表拖拽 pull: false, put: false }, onEnd: (evt: any) => { //进行数据的处理,拖拽实际并不会改变绑定数据的顺序 console.log("拖动结束了,", evt) // 可以获取拖动前与拖动后的序号 const { newIndex, oldIndex } = evt console.log(oldIndex, newIndex) if (oldIndex == newIndex) { // ElMessage({ // message: "没有变化无需排序!", // type: 'warning' // }) return } // 数据源不会变的哦 console.log(state.tableData) } }); } onMounted(() => { // 实现行拖拽。这个方法一般都要放到数据回来之后,比如数据是通过接口回来的 dragSort() }) </script> <style scoped="scoped" lang="scss"> </style> ``` ### 进行绑定的数据源处理,拖拽实际并不会改变绑定数据的顺序 一般存储改造后的数据就可以了,调用存储方法存储一下就行了。这里要特别注意`el-table的row-id属性这个必需要添加,否则进行数据源操作后数据顺序会错乱哦` ``` <template> <div class="app-container"> <el-card> <el-table :data="state.tableData" row-key="id" style="width: 100%"> <el-table-column prop="date" label="Date" width="180" /> <el-table-column prop="name" label="Name" width="180" /> <el-table-column prop="address" label="Address" /> </el-table> </el-card> </div> </template> <script lang="ts" setup> import { defineAsyncComponent, reactive, toRefs, onMounted, ref } from 'vue' import Sortable from 'sortablejs' const state = reactive({ dictTableKey:1, tableData: [ { id:"1", date: '2016-05-03', name: 'Tom-1', address: 'No. 189, Grove St, Los Angeles', }, { id:"2", date: '2016-05-02', name: 'Tom-2', address: 'No. 189, Grove St, Los Angeles', }, { id:"3", date: '2016-05-04', name: 'Tom-3', address: 'No. 189, Grove St, Los Angeles', }, { id:"4", date: '2016-05-01', name: 'Tom-4', address: 'No. 189, Grove St, Los Angeles', }, ] }) //行拖拽 const dragSort = () => { let that = this; // 首先获取需要拖拽的dom节点 const el1 = document.querySelectorAll('.el-table__body-wrapper')[0].querySelectorAll('table > tbody')[0]; Sortable.create(el1, { disabled: false, // 是否开启拖拽,false为开启 ghostClass: 'sortable-ghost', //拖拽样式 animation: 150, // 拖拽延时,效果更好看 group: { // 是否开启跨表拖拽 pull: false, put: false }, onEnd: (evt: any) => { //进行数据的处理,拖拽实际并不会改变绑定数据的顺序 console.log("拖动结束了,", evt) // 可以获取拖动前与拖动后的序号 const { newIndex, oldIndex } = evt console.log(oldIndex, newIndex) // 数据源不会变的哦 console.log("改造前的数据源", state.tableData) // 这样可以把拖动前的那一行取出来 // const currRow = state.tableData[oldIndex] // 把拖动前的那一行干掉并且取出来 const currRow = state.tableData?.splice(oldIndex, 1)[0] // 把拖动前的那一行放到拖动后的位置 state.tableData?.splice(newIndex, 0, currRow) // 一般存储改造后的数据就可以了,调用存储方法存储一下就行了。 } }); } onMounted(() => { // 实现行拖拽。这个方法一般都要放到数据回来之后,比如数据是通过接口回来的 dragSort() }) </script> <style scoped="scoped" lang="scss"></style> ``` **如果不想改变数据源,可以复制一份数据源出来改造:** 当然这种情况还是比较少的,一般都直接操作数据源了,存储也好存储,不然可能还要维护多份数据 ``` onEnd: (evt: any) => { //进行数据的处理,拖拽实际并不会改变绑定数据的顺序 console.log("拖动结束了,", evt) // 可以获取拖动前与拖动后的序号 const { newIndex, oldIndex } = evt console.log(oldIndex, newIndex) // 数据源不会变的哦 console.log("改造前的数据源",state.tableData) // 这样可以把拖动前的那一行取出来 // const currRow = state.tableData[oldIndex] // 复制一份数据出来改造 let copyData = JSON.parse(JSON.stringify(state.tableData)) // 把拖动前的那一行干掉并且取出来 const currRow = copyData?.splice(oldIndex, 1)[0] // 把拖动前的那一行放到拖动后的位置 copyData?.splice(newIndex, 0, currRow) console.log("改造后的数据源",copyData) // 一般用保存后的数据,调用存储方法存储一下就行了 //state.tableData = copyData } ``` ### dom节点可以通过vue3的ref来获取,特别是封装成组件后每个组件里边的表格都需要拖动的情况 ``` <div ref="tableDataWrappeRef"> <el-card> <el-table :data="state.tableData" style="width: 100%"> <el-table-column prop="date" label="Date" width="180" /> <el-table-column prop="name" label="Name" width="180" /> <el-table-column prop="address" label="Address" /> </el-table> </el-card> </div> ``` ts部分: ``` // dom节点可以通过vue3的ref来获取,特别是封装成组件后每个组件里边的表格都需要拖动的 const el1 = tableDataWrappeRef.value.querySelectorAll('.el-table__body-wrapper')[0].querySelectorAll('table > tbody')[0]; ``` **注意ref要放到dom对象上面比如div,不要放到组件上面,也就是不要放到el-table上面,比如不要这么写:** 这样放到组件上面获取不到dom节点哦 ``` <el-card ref="tableDataWrappeRef"> <el-table :data="state.tableData" style="width: 100%"> <el-table-column prop="date" label="Date" width="180" /> <el-table-column prop="name" label="Name" width="180" /> <el-table-column prop="address" label="Address" /> </el-table> </el-card> ``` ### 成长系统里边的用法,拖动实现排序 ``` //行拖拽 const dragSort = () => { let that = this; // 首先获取需要拖拽的dom节点 // const el1 = document.querySelectorAll('.el-table__body-wrapper')[0].querySelectorAll('table > tbody')[0]; // console.log(taskTableDataRef.value) const el1 = taskTableDataRef.value.querySelectorAll('.el-table__body-wrapper')[0].querySelectorAll('table > tbody')[0]; Sortable.create(el1, { disabled: false, // 是否开启拖拽 ghostClass: 'sortable-ghost', //拖拽样式 animation: 150, // 拖拽延时,效果更好看 group: { // 是否开启跨表拖拽 pull: false, put: false }, onEnd: (evt: any) => { //进行数据的处理,拖拽实际并不会改变绑定数据的顺序 if (state.pageParam.taskCAT != 0) { ElMessage({ message: "请在全部数据下在进行拖动排序,此次拖动不会存储!", type: 'warning' }) return } // 可以获取拖动前与拖动后的序号 const { newIndex, oldIndex } = evt console.log(oldIndex, newIndex) if (oldIndex == newIndex) { // ElMessage({ // message: "没有变化无需排序!", // type: 'warning' // }) return } // 复制一份数据出来改造 let copyData = JSON.parse(JSON.stringify(state.tasksPlanList)) // 把拖动前的那一行干掉并且取出来 const currRow = copyData?.splice(oldIndex, 1)[0] // 把拖动前的那一行放到拖动后的位置 copyData?.splice(newIndex, 0, currRow) console.log("改造后的数据源", copyData) // state.tasksPlanList = copyData // 存储改变后的数据源,相当于每拖动一次都把数据源全部修改了一边顺序,因为成长系统这边是新添加的,所以可以在前端重新排序即可 saveTaskPlan(copyData) } }); } // 存储任务规划 const saveTaskPlan = (_saveData: any) => { if (!props.schoolId || !props.gradeID) { ElMessage({ message: "无专业(阶段)与grade信息", type: 'warning' }) } let dataLen = _saveData.length if (dataLen === 0) { ElMessage({ message: "无数据", type: 'warning' }) } else { let dataList = [] let planMonth = dayjs(state.newPlanMonth) for (let index = 0; index < _saveData.length; index++) { const element = _saveData[index]; let dataItem = { "allotTaskID": element.allotTaskID, "taskID": element.taskID, "schoolID": props.schoolId, "gradeID": props.gradeID, "year": planMonth.year(), "month": planMonth.month() + 1, "termNum": state.levelID, "score": element.score, "orderNum": index } dataList.push(dataItem) } console.log("组装好的数据", dataList) // 请求的api地址,以及返回数据的格式自己根据自己的修改 request.post('/growing/api/Tasks/SaveXX', dataList).then((res: any) => { console.log(res) if (res.success) { ElMessage.success('排序成功!') state.dialoglabedit = false } }) } } ``` ### 师资系统里边的拖动排序实现方法 onMove里边可以限制拖动,我这里是限制有搜索条件的情况下禁止拖动排序 ``` //行拖拽 const dragSort = () => { // alert(state.pageParam.trainingSubProgramName) let that = this // 首先获取需要拖拽的dom节点 const el1 = document.querySelectorAll('.el-table__body-wrapper')[0].querySelectorAll('table > tbody')[0] Sortable.create(el1, { disabled: false, // 是否开启拖拽,false为开启 ghostClass: 'sortable-ghost', //拖拽样式 animation: 150, // 拖拽延时,效果更好看 group: { // 是否开启跨表拖拽 pull: false, put: false }, // onStart: function (evt: any) { // console.log(state.pageParam.trainingSubProgramName) // // 如果有条件搜索就应该把拖动排序禁用掉 // if (state.pageParam.trainingSubProgramName) { // ElMessage.warning('有搜索条件的情况下禁止拖动排序,否者会造成顺序错乱!') // return false // } // }, onMove: function (evt: any) { // 如果有条件搜索就应该把拖动排序禁用掉 if (state.pageParam.trainingSubProgramName) { ElMessage.warning('有搜索条件的情况下禁止拖动排序,否者会造成顺序错乱!') return false } }, onEnd: (evt: any) => { //进行数据的处理,拖拽实际并不会改变绑定数据的顺序 console.log('拖动结束了,', evt) // 可以获取拖动前与拖动后的序号 const { newIndex, oldIndex } = evt console.log(oldIndex, newIndex) // const oldRow = state.trainingSubProgramList[oldIndex] // const currRow = state.trainingSubProgramList[newIndex] // console.log("旧行",oldRow) // console.log("新行",currRow) // 创建数组副本(保持响应性) const newList = [...state.trainingSubProgramList] // 执行数组元素位置交换 const [movedItem] = newList.splice(oldIndex, 1) newList.splice(newIndex, 0, movedItem) // 数据源不会变的哦 // console.log('原始数据源:', state.trainingSubProgramList) // console.log('新版的数据源:', newList) // 获取顺序数据(拿到id和新的排序码,方便回传到后台去更新) const orderedData = newList.map((item, index) => ({ id: item.id, sort: index + 1 // 根据实际排序字段调整 })) // console.log('更新后的顺序数据:', orderedData) updateSort(orderedData) } }) } const updateSort = (orderedData: any) => { request.post('/xxApi/api/TrainingSubProgram/UpdateSort', orderedData).then((res: any) => { if (res.success) { ElMessage.success('存储成功!') } }) } const getTrainingSubProgramDataList = async () => { // ..............请求数据 // 请求数据后在开启行拖动 dragSort() } ``` **后台存储的代码:** ``` /// <summary> /// 更新拖动后的排序 /// </summary> /// <param name="inputs"></param> /// <returns></returns> [HttpPost] public async Task<ObjectResult> UpdateSort([FromBody] List<UpdateSubProgramSortDto> inputs) { if (inputs.Count == 0) { return Failed("无效数据!无法重新排序!"); } bool result = await _trainingSubProgramAppService.BatchUdapteSortAsync(inputs); return Success(result); } ``` 应用层代码: ``` /// <summary> /// 批量更新排序码 /// </summary> /// <param name="trainingSubPrograms"></param> /// <returns></returns> public async Task<bool> BatchUdapteSortAsync(List<UpdateSubProgramSortDto> inputs) { List<TrainingSubProgram> trainingSubPrograms = new List<TrainingSubProgram>(); foreach (var item in inputs) { TrainingSubProgram training = new TrainingSubProgram(); training.Id = item.Id; training.Sort = item.Sort; trainingSubPrograms.Add(training); } // 获取SqlSugar客户端实例(假设仓储已暴露DbContext) var db = _trainingSubProgramRepository.Context; // 执行批量更新(仅更新Sort字段) var affectedRows = await db.Updateable(trainingSubPrograms) .UpdateColumns(it => new { it.Sort }) // 指定更新字段 .WhereColumns(it => new { it.Id }) // 根据主键匹配记录 .ExecuteCommandAsync(); return affectedRows > 0; } ``` dto: ``` public class UpdateSubProgramSortDto { /// <summary> /// 主键,存储雪花id类型 ///</summary> [Newtonsoft.Json.JsonConverter(typeof(ValueToStringConverter))] public long Id { get; set; } /// <summary> /// 顺序 ///</summary> public int Sort { get; set; } } ```