sortablejs操作文档可具体参照:Sortable.js中文网 (sortablejs.com)
一、html代码
<template>
<el-dialog
v-model="props.columnsBol"
title="设置列"
width="70%"
@close="cameraClose"
>
<div class="tips">
<el-icon color="#4290f7" :size="12">
<BellFilled />
</el-icon>
<span class="tips-info">可以通过拖拽行直接修改行的位置与顺序;对列表进行勾选可控制列的显示与隐藏</span>
</div>
<div class="box-wrapper">
<div class="single-table-container left-table">
<div class="search-form-wrapper">
<div class="title">
左侧固定列
</div>
</div>
<div ref="table_ref" class="single-table">
<el-table
ref="tables"
:data="leftFixedColumn"
class="draggable"
row-key="prop"
border
fit
highlight-current-row
style="width: 100%"
height="500px"
@select="handleChageLeft"
@select-all="handleChageLeft"
>
<el-table-column type="selection" width="50" />
<el-table-column align="center" label="列" prop="label" width="150">
<template #default="scope">
<div style="display: flex; align-items: center">
<el-icon><Rank /></el-icon>
<span style="margin-left: 10px">{{ scope.row.label }}</span>
</div>
</template>
</el-table-column>
<el-table-column align="center" label="操作" >
<template #default="scope">
<el-button
link
type="primary"
@click="onMoveLeftOrRight(scope.row, 'right')"
>
移动至【右侧滚动列】
</el-button>
<el-button
link
type="primary"
:disabled="scope.$index == 0"
@click="onMoveTopOrBottom(scope.row, scope.$index, 'left', 'top')"
>
上移
</el-button>
<el-button
link
type="primary"
:disabled="scope.$index == leftFixedColumn.length - 1"
@click="onMoveTopOrBottom(scope.row, scope.$index, 'left', 'bottom')"
>
下移
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
<div class="single-table-container right-table">
<div class="search-form-wrapper">
<div class="title">
右侧滚动列
</div>
</div>
<div ref="table_refB" class="single-table">
<el-table
ref="tablesB"
height="500px"
:data="originalColumn"
class="draggable"
row-key="prop"
border
fit
highlight-current-row
style="width: 100%"
@select="handleChageRight"
@select-all="handleChageRight"
>
<el-table-column type="selection" width="50" />
<el-table-column align="center" label="列" prop="label" width="150">
<template #default="scope">
<div style="display: flex; align-items: center">
<el-icon><Rank /></el-icon>
<span style="margin-left: 10px">{{ scope.row.label }}</span>
</div>
</template>
</el-table-column>
<el-table-column align="center" label="操作" >
<template #default="scope">
<el-button
link
type="primary"
@click="onMoveLeftOrRight(scope.row, 'left')"
>
移动至【左侧固定列】
</el-button>
<el-button
link
type="primary"
:disabled="scope.$index == 0"
@click="onMoveTopOrBottom(scope.row, scope.$index, 'right', 'top')"
>
上移
</el-button>
<el-button
link
type="primary"
:disabled="scope.$index == originalColumn.length - 1"
@click="onMoveTopOrBottom(scope.row, scope.$index, 'right', 'bottom')"
>
下移
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<!-- <el-button @click="onReset()">重置</el-button> -->
<el-button @click="cameraClose()">取消</el-button>
<el-button type="primary" @click="onSubmit()" >确认</el-button>
</span>
</template>
</el-dialog>
</template>
二、typeScript 代码
<script setup lang='ts'>
import _ from 'lodash';
import type { SortableEvent } from 'sortablejs';
import Sortable from 'sortablejs';
import { setColumnList, updateColumnList } from '~/api/system/dynamicColumn';
import { useColumnStore } from '~/store/modules/columns';
interface User {
label: string;
prop: string;
fixed: string | boolean;
checked: boolean;
width: number;
showOverflowTooltip?: boolean;
}
const props = defineProps({
columnsBol: {
type: Boolean,
default: false,
},
clonColumnList: {
type: Array,
},
// 表格id
tableId: {
type: Number,
require: true,
},
columnCode: {
type: String,
},
columnId: {
type: Number,
},
});
const emits = defineEmits<{ (e: 'update:columnsBol', value: boolean): void; (e: 'setColumnList', value: any): void; }>();
const cameraClose = () => {
emits('update:columnsBol', false);
emits('setColumnList', props.clonColumnList);
tables.value!.clearSelection();
tablesB.value!.clearSelection();
};
const leftFixedColumn = ref<[]>([]);
const leftSelectList = ref<[]>([]);
const originalColumn = ref<[]>([]);
const originalSelectList = ref<[]>([]);
// @ts-ignore
leftFixedColumn.value = _.filter(props.clonColumnList, (item) => item.fixed == 'left');
// @ts-ignore
originalColumn.value = _.filter(props.clonColumnList, (item) => !item.fixed);
// 监听移动的 表格数据 重新赋值
watch(
() => props.clonColumnList,
(val) => {
onReset();
},
{ deep: true },
);
// 获取el-table ref
const tables: any = ref<HTMLElement | null>(null);
// 获取t-table ref
const table_ref: any = ref<HTMLElement | null>(null);
// 获取el-tableB ref
const tablesB: any = ref<HTMLElement | null>(null);
// 获取t-tableB ref
const table_refB: any = ref<HTMLElement | null>(null);
// 行拖拽
const initSort = () => {
nextTick(() => {
const el = table_ref.value.querySelector('.el-table__body-wrapper tbody');
Sortable.create(el, {
group: 'shared', // 添加才能左右表格互相拖拽,添加以后才有onAdd、onRemove事件
animation: 150, // 动画
// disabled: false, // 拖拽不可用? false 启用(刚刚渲染表格的时候起作用,后面不起作用)
// handle: '.move', // 指定拖拽目标,点击此目标才可拖拽元素(此例中设置操作按钮拖拽)
// filter: '.disabled', // 指定不可拖动的类名(el-table中可通过row-class-name设置行的class)
dragClass: 'dragClass', // 设置拖拽样式类名
ghostClass: 'ghostClass', // 设置拖拽停靠样式类名
chosenClass: 'chosenClass', // 设置选中样式类名
onAdd: (event: SortableEvent) => {
if (event && event.oldIndex !== undefined && event.newIndex !== undefined) {
const curRow = originalColumn.value.splice(event.oldIndex, 1)[0];
console.log('===curRow111', curRow);
// @ts-ignore
curRow.fixed = 'left';
leftFixedColumn.value.splice(event.newIndex, 0, curRow);
}
},
onUpdate: (event: SortableEvent) => {
if (event && event.oldIndex !== undefined && event.newIndex !== undefined) {
const curRow = leftFixedColumn.value.splice(event.oldIndex, 1)[0];
leftFixedColumn.value.splice(event.newIndex, 0, curRow);
}
},
onEnd: () => {
toggleSelection();
},
});
const elB = table_refB.value.querySelector('.el-table__body-wrapper tbody');
Sortable.create(elB, {
group: 'shared', //
animation: 150, // 动画
// disabled: false, // 拖拽不可用? false 启用(刚刚渲染表格的时候起作用,后面不起作用)
// handle: '.move', // 指定拖拽目标,点击此目标才可拖拽元素(此例中设置操作按钮拖拽)
// filter: '.disabled', // 指定不可拖动的类名(el-table中可通过row-class-name设置行的class)
dragClass: 'dragClass', // 设置拖拽样式类名
ghostClass: 'ghostClass', // 设置拖拽停靠样式类名
chosenClass: 'chosenClass', // 设置选中样式类名
onAdd: (event: SortableEvent) => {
if (event && event.oldIndex !== undefined && event.newIndex !== undefined) {
const curRow = leftFixedColumn.value.splice(event.oldIndex, 1)[0];
// @ts-ignore
curRow.fixed = false;
originalColumn.value.splice(event.newIndex, 0, curRow);
}
// console.log('===leftFixedColumn.value', leftFixedColumn.value);
// console.log('===originalColumn.value', originalColumn.value);
},
onUpdate: (event: SortableEvent) => {
if (event && event.oldIndex !== undefined && event.newIndex !== undefined) {
const curRow = originalColumn.value.splice(event.oldIndex, 1)[0];
originalColumn.value.splice(event.newIndex, 0, curRow);
}
},
onEnd: () => {
toggleSelection();
},
});
});
};
// 默认选中
const toggleSelection = () => {
nextTick(() => {
// @ts-ignore
_.forEach(leftFixedColumn.value, (row) => {
// @ts-ignore
if (row && tables.value) {
tables.value.toggleRowSelection(
row,
// @ts-ignore
row.checked,
);
}
});
// @ts-ignore
_.forEach(originalColumn.value, (row) => {
// @ts-ignore
if (row && tablesB.value) {
tablesB.value.toggleRowSelection(
row,
// @ts-ignore
row.checked,
);
}
});
});
};
// 左右移动
const onMoveLeftOrRight = (e, move) => {
if (move === 'right') {
e.fixed = false;
// @ts-ignore
originalColumn.value.unshift(e);
// @ts-ignore
leftFixedColumn.value = _.filter(leftFixedColumn.value, (item) => item.prop != e.prop);
}
if (move === 'left') {
e.fixed = 'left';
// @ts-ignore
leftFixedColumn.value.unshift(e);
// @ts-ignore
originalColumn.value = _.filter(originalColumn.value, (item) => item.prop != e.prop);
}
toggleSelection();
};
// 上下移动
const onMoveTopOrBottom = (e, index, position, move) => {
if (position === 'left') {
if (move === 'top' && index == 0) {
return;
}
if (move === 'bottom' && index == leftFixedColumn.value.length - 1) {
return;
}
const curRow = leftFixedColumn.value.splice(index, 1)[0];
leftFixedColumn.value.splice(move === 'top' ? index - 1 : index + 1, 0, curRow);
}
if (position === 'right') {
if (move === 'top' && index == 0) {
return;
}
if (move === 'bottom' && index == originalColumn.value.length - 1) {
return;
}
const curRow = originalColumn.value.splice(index, 1)[0];
originalColumn.value.splice(move === 'top' ? index - 1 : index + 1, 0, curRow);
}
};
// 保存
const onSubmit = () => {
const allList = _.concat(leftFixedColumn.value, originalColumn.value);
emits('setColumnList', allList);
emits('update:columnsBol', false);
const code = ref<string>('');
const id = ref<number>();
const { tableId, columnCode, columnId } = props;
// @ts-ignore
const columnInfo = useColumnStore().getColumnCodeAndIdStore(props.tableId);
code.value = columnCode || (columnInfo.code ? columnInfo.code : '');
id.value = columnId || (columnInfo.id ? columnInfo.id : 0);
// console.log('===columnInfo.value', columnInfo);
if (code.value) {
updateColumnList({
tableId,
code: code.value,
id: id.value,
tableColumns: JSON.stringify(allList),
}).then((res) => {
// @ts-ignore
useColumnStore().setColumnStore(props.tableId, allList);
});
}
else {
setColumnList({
tableId,
tableColumns: JSON.stringify(allList),
}).then((res) => {
// console.log('==res新增', res);
// @ts-ignore
useColumnStore().setColumnStore(props.tableId, allList);
});
}
tables.value!.clearSelection();
tablesB.value!.clearSelection();
};
const onReset = () => {
// @ts-ignore
leftFixedColumn.value = _.filter(_.cloneDeep(props.clonColumnList), (item) => item.fixed == 'left');
// @ts-ignore
originalColumn.value = _.filter(_.cloneDeep(props.clonColumnList), (item) => !item.fixed);
toggleSelection();
};
const handleChageRight = (selection) => {
const data = _.map(selection, 'prop');
_.forEach(originalColumn.value, (item, index) => {
// @ts-ignore
if (_.includes(data, item.prop)) {
// @ts-ignore
originalColumn.value[index].checked = true;
}
else {
// @ts-ignore
originalColumn.value[index].checked = false;
}
});
};
const handleChageLeft = (selection) => {
const data = _.map(selection, 'prop');
_.forEach(leftFixedColumn.value, (item, index) => {
// @ts-ignore
if (_.includes(data, item.prop)) {
// @ts-ignore
leftFixedColumn.value[index].checked = true;
}
else {
// @ts-ignore
leftFixedColumn.value[index].checked = false;
}
});
};
defineExpose({
initSort,
toggleSelection,
});
</script>
三、style 代码
<style lang="scss" scoped>
.tips {
line-height: 35px;
border: 1px solid #4290f7;
border-radius: 10px;
padding: 0 20px;
margin-bottom: 10px;
background: #B4D5FF;
color: #333;
&-info {
margin-left: 5px;
}
}
.box-wrapper {
font-size: 14px;
display: flex;
justify-content: space-between;
.left-table {
width: 51%;
padding: 0;
}
.right-table {
width: 48%;
padding: 0;
.el-input {
width: 100%;
}
.el-form-item {
margin: 0;
}
.el-radio-group {
:deep(.el-radio) {
margin-right: 10px;
.el-radio__label {
font-size: 12px;
padding-left: 10px;
}
}
}
}
}
//单页表格
.single-table-container {
width: 100%;
height: 100%;
padding: 10px;
overflow: auto;
.search-form-wrapper {
height: 40px;
display: flex;
align-items: center;
border: 1px solid #e6eaef;
border-bottom: none;
padding: 0 10px;
.title {
font-size: 14px;
font-weight: 700;
color: #303133;
}
}
.single-table {
height: calc(100% - 80px);
.inner_table {
padding: 10px;
// background: rgba(25, 137, 254, 0.1);
margin-top: -4px;
margin-bottom: -4px;
}
}
.table-pagination {
text-align: right;
height: 40px;
background: #fff;
border: 1px solid #e6eaef;
border-top: unset;
.el-pagination {
padding: 6px 10px;
}
}
}
</style>
四、核心实现(***添加group才能左右拖拽***)
// 行拖拽
const initSort = () => {
nextTick(() => {
const el = table_ref.value.querySelector('.el-table__body-wrapper tbody');
Sortable.create(el, {
group: 'shared', // 添加才能左右表格互相拖拽,添加以后才有onAdd、onRemove事件
animation: 150, // 动画
// disabled: false, // 拖拽不可用? false 启用(刚刚渲染表格的时候起作用,后面不起作用)
// handle: '.move', // 指定拖拽目标,点击此目标才可拖拽元素(此例中设置操作按钮拖拽)
// filter: '.disabled', // 指定不可拖动的类名(el-table中可通过row-class-name设置行的class)
dragClass: 'dragClass', // 设置拖拽样式类名
ghostClass: 'ghostClass', // 设置拖拽停靠样式类名
chosenClass: 'chosenClass', // 设置选中样式类名
onAdd: (event: SortableEvent) => {
if (event && event.oldIndex !== undefined && event.newIndex !== undefined) {
const curRow = originalColumn.value.splice(event.oldIndex, 1)[0];
console.log('===curRow111', curRow);
// @ts-ignore
curRow.fixed = 'left';
leftFixedColumn.value.splice(event.newIndex, 0, curRow);
}
},
onUpdate: (event: SortableEvent) => {
if (event && event.oldIndex !== undefined && event.newIndex !== undefined) {
const curRow = leftFixedColumn.value.splice(event.oldIndex, 1)[0];
leftFixedColumn.value.splice(event.newIndex, 0, curRow);
}
},
onEnd: () => {
toggleSelection();
},
});
const elB = table_refB.value.querySelector('.el-table__body-wrapper tbody');
Sortable.create(elB, {
group: 'shared', //
animation: 150, // 动画
// disabled: false, // 拖拽不可用? false 启用(刚刚渲染表格的时候起作用,后面不起作用)
// handle: '.move', // 指定拖拽目标,点击此目标才可拖拽元素(此例中设置操作按钮拖拽)
// filter: '.disabled', // 指定不可拖动的类名(el-table中可通过row-class-name设置行的class)
dragClass: 'dragClass', // 设置拖拽样式类名
ghostClass: 'ghostClass', // 设置拖拽停靠样式类名
chosenClass: 'chosenClass', // 设置选中样式类名
onAdd: (event: SortableEvent) => {
if (event && event.oldIndex !== undefined && event.newIndex !== undefined) {
const curRow = leftFixedColumn.value.splice(event.oldIndex, 1)[0];
// @ts-ignore
curRow.fixed = false;
originalColumn.value.splice(event.newIndex, 0, curRow);
}
// console.log('===leftFixedColumn.value', leftFixedColumn.value);
// console.log('===originalColumn.value', originalColumn.value);
},
onUpdate: (event: SortableEvent) => {
if (event && event.oldIndex !== undefined && event.newIndex !== undefined) {
const curRow = originalColumn.value.splice(event.oldIndex, 1)[0];
originalColumn.value.splice(event.newIndex, 0, curRow);
}
},
onEnd: () => {
toggleSelection();
},
});
});
};