|
|
<template>
|
|
|
<component :is="render" />
|
|
|
</template>
|
|
|
<script setup lang="jsx">
|
|
|
import { ElTable } from 'element-plus/es/components/table/index';
|
|
|
import 'element-plus/es/components/table/style/css';
|
|
|
import Sortable from 'sortablejs';
|
|
|
const props = defineProps({
|
|
|
/**
|
|
|
* 是否可拖拽排序
|
|
|
*/
|
|
|
sortable: {
|
|
|
type: Boolean,
|
|
|
default: false,
|
|
|
},
|
|
|
/**
|
|
|
* 拖拽表格唯一标识,用于区分多个拖拽区域互相拖拽时数据来源
|
|
|
*/
|
|
|
code: {
|
|
|
type: String,
|
|
|
default: '',
|
|
|
},
|
|
|
/**
|
|
|
* el-table原生属性,表格数据,代理element原生属性,用于修改数据为排序后的数据
|
|
|
*/
|
|
|
data: {
|
|
|
type: Array,
|
|
|
required: true,
|
|
|
},
|
|
|
/**
|
|
|
* el-table原生属性,唯一标识属性字段名,默认id
|
|
|
*/
|
|
|
rowKey: {
|
|
|
type: String,
|
|
|
default: 'id',
|
|
|
},
|
|
|
/**
|
|
|
* 排序属性字段名,默认sort
|
|
|
*/
|
|
|
sortKey: {
|
|
|
type: String,
|
|
|
default: 'sort',
|
|
|
},
|
|
|
/**
|
|
|
* 控制是否修改表格数据排序,默认修改,如果不修改则只改变表格渲染顺序但是真实数据顺序不变
|
|
|
*/
|
|
|
modifyData: {
|
|
|
type: Boolean,
|
|
|
default: true,
|
|
|
},
|
|
|
/**
|
|
|
* sortablejs原生属性,分组,详细用法见官方文档
|
|
|
* http://www.sortablejs.com/options.html#:~:text=group%EF%BC%9Astring%20or%20object
|
|
|
*/
|
|
|
group: {
|
|
|
type: [String, Object],
|
|
|
default: '',
|
|
|
},
|
|
|
/**
|
|
|
* sortablejs原生属性,是否列表单元
|
|
|
*/
|
|
|
sort: {
|
|
|
type: Boolean,
|
|
|
default: true,
|
|
|
},
|
|
|
/**
|
|
|
* sortablejs原生属性,是否此sortable对象是否可用
|
|
|
*/
|
|
|
disabled: {
|
|
|
type: Boolean,
|
|
|
default: false,
|
|
|
},
|
|
|
/**
|
|
|
* sortablejs原生属性,动画持续时间
|
|
|
*/
|
|
|
animation: {
|
|
|
type: Number,
|
|
|
default: 150,
|
|
|
},
|
|
|
/**
|
|
|
* sortablejs原生属性,可拖拽元素
|
|
|
*/
|
|
|
draggable: {
|
|
|
type: String,
|
|
|
default: 'el-table__row',
|
|
|
},
|
|
|
/**
|
|
|
* sortablejs原生属性,元素可拖拽部分选择器,默认为空表示元素任意部分都可拖拽
|
|
|
*/
|
|
|
handle: {
|
|
|
type: String,
|
|
|
default: '',
|
|
|
},
|
|
|
/**
|
|
|
* sortablejs原生属性,幽灵元素
|
|
|
*/
|
|
|
ghostClass: {
|
|
|
type: String,
|
|
|
default: 'ghost-row',
|
|
|
},
|
|
|
/**
|
|
|
* sortablejs原生属性,选中的元素
|
|
|
*/
|
|
|
chosenClass: {
|
|
|
type: String,
|
|
|
default: 'chosen-row',
|
|
|
},
|
|
|
/**
|
|
|
* sortablejs原生属性,拖拽的元素
|
|
|
*/
|
|
|
dragClass: {
|
|
|
type: String,
|
|
|
default: 'drag-row',
|
|
|
},
|
|
|
/**
|
|
|
* sortablejs原生属性,不允许拖拽元素选择器
|
|
|
*/
|
|
|
filter: {
|
|
|
type: String,
|
|
|
default: '.ignore-drag-sort',
|
|
|
},
|
|
|
});
|
|
|
const attrs = useAttrs();
|
|
|
const slots = useSlots();
|
|
|
const emits = defineEmits(['row-sort', 'row-add', 'row-remove', 'row-clone', 'expand-change']);
|
|
|
|
|
|
// 可拖拽表格唯一标识
|
|
|
const uid = 'sortableTable' + getCurrentInstance().uid;
|
|
|
const { proxy } = getCurrentInstance();
|
|
|
// 表格ref
|
|
|
const sortting = ref(false);
|
|
|
const refsTable = ref(null);
|
|
|
const expandRowKeys = ref([]);
|
|
|
const handleExpandChange = (row, expandRows) => {
|
|
|
expandRowKeys.value = expandRows.map((item) => item[props.rowKey]);
|
|
|
emits('expand-change', row, expandRows);
|
|
|
};
|
|
|
// sortablejs实例
|
|
|
let sortable = ref(null);
|
|
|
// 初始化sortablejs
|
|
|
const handleInit = () => {
|
|
|
// 获取拖拽区域元素
|
|
|
const el = document.querySelector(`[sort-id='${uid}'] :not(.el-table) tbody`);
|
|
|
if (el) {
|
|
|
sortable.value = new Sortable(el, {
|
|
|
group: props.group,
|
|
|
sort: props.sort,
|
|
|
disabled: !props.sortable || props.disabled,
|
|
|
animation: props.animation,
|
|
|
draggable: '.' + props.draggable,
|
|
|
handle: props.handle,
|
|
|
ghostClass: props.ghostClass,
|
|
|
chosenClass: props.chosenClass,
|
|
|
dragClass: props.dragClass,
|
|
|
filter: props.filter,
|
|
|
onUpdate(e) {
|
|
|
if (typeof e.newIndex === 'number') {
|
|
|
if (props.modifyData) {
|
|
|
// 开始对数据进行重新排序
|
|
|
const row = unref(props.data).splice(e.oldIndex, 1)[0];
|
|
|
unref(props.data).splice(e.newIndex, 0, row);
|
|
|
// 刷新排序属性
|
|
|
unref(props.data).forEach((item, index) => {
|
|
|
item[props.sortKey] = index + 1;
|
|
|
});
|
|
|
proxy.$nextTick(() => {
|
|
|
// 排序完成
|
|
|
proxy.$nextTick(() => {
|
|
|
proxy.$forceUpdate();
|
|
|
// 触发排序完成事件
|
|
|
emits('row-sort', e.newIndex, e.oldIndex, e);
|
|
|
});
|
|
|
});
|
|
|
} else {
|
|
|
// 触发排序完成事件
|
|
|
emits('row-sort', e.newIndex, e.oldIndex, e);
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
setData(dataTransfer) {
|
|
|
dataTransfer.setData('code', props.code);
|
|
|
},
|
|
|
onAdd(e) {
|
|
|
// 从sort-id为这个的表格里面拖过来的元素,需要自行根据表格元素判断数据来源
|
|
|
let code = e.originalEvent.dataTransfer.getData('code');
|
|
|
// 触发添加元素事件
|
|
|
emits(
|
|
|
'row-add',
|
|
|
e.newIndex,
|
|
|
e.oldIndex,
|
|
|
code,
|
|
|
(row) => {
|
|
|
if (props.modifyData && row) {
|
|
|
sortting.value = true;
|
|
|
unref(props.data).splice(e.newIndex, 0, row);
|
|
|
// 通过v-if强行重新渲染表格,否则表格可能渲染错误
|
|
|
proxy.$nextTick(() => {
|
|
|
sortting.value = false;
|
|
|
proxy.$nextTick(() => {
|
|
|
handleInit();
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
},
|
|
|
e
|
|
|
);
|
|
|
},
|
|
|
onRemove(e) {
|
|
|
if (typeof e.newIndex === 'number') {
|
|
|
if (props.modifyData && e.pullMode !== 'clone') {
|
|
|
unref(props.data).splice(e.oldIndex, 1);
|
|
|
proxy.$forceUpdate();
|
|
|
}
|
|
|
// 触发添加元素事件
|
|
|
emits('row-remove', e.newIndex, e.oldIndex, e);
|
|
|
}
|
|
|
},
|
|
|
onClone(e) {
|
|
|
if (typeof e.newIndex === 'number') {
|
|
|
// 触发赋值元素事件
|
|
|
emits('row-clone', e.newIndex, e.oldIndex, e);
|
|
|
}
|
|
|
},
|
|
|
});
|
|
|
} else {
|
|
|
console.error('可拖拽表格ID不存在');
|
|
|
}
|
|
|
};
|
|
|
// 元素实例化后初始化sortablejs
|
|
|
onMounted(handleInit);
|
|
|
// 代理原生函数
|
|
|
const handleProxy = (fnName, args) => {
|
|
|
return unref(refsTable)[fnName]?.apply(unref(refsTable), args);
|
|
|
};
|
|
|
const clearSelection = function () {
|
|
|
return handleProxy('clearSelection', arguments);
|
|
|
};
|
|
|
const toggleRowSelection = function () {
|
|
|
return handleProxy('toggleRowSelection', arguments);
|
|
|
};
|
|
|
const toggleAllSelection = function () {
|
|
|
return handleProxy('toggleAllSelection', arguments);
|
|
|
};
|
|
|
const toggleRowExpansion = function () {
|
|
|
return handleProxy('toggleRowExpansion', arguments);
|
|
|
};
|
|
|
const setCurrentRow = function () {
|
|
|
return handleProxy('setCurrentRow', arguments);
|
|
|
};
|
|
|
const clearSort = function () {
|
|
|
return handleProxy('clearSort', arguments);
|
|
|
};
|
|
|
const clearFilter = function () {
|
|
|
return handleProxy('clearFilter', arguments);
|
|
|
};
|
|
|
const doLayout = function () {
|
|
|
return handleProxy('doLayout', arguments);
|
|
|
};
|
|
|
const sort = function () {
|
|
|
return handleProxy('sort', arguments);
|
|
|
};
|
|
|
// 抛出
|
|
|
defineExpose({
|
|
|
refsTable,
|
|
|
clearSelection,
|
|
|
toggleRowSelection,
|
|
|
toggleAllSelection,
|
|
|
toggleRowExpansion,
|
|
|
setCurrentRow,
|
|
|
clearSort,
|
|
|
clearFilter,
|
|
|
doLayout,
|
|
|
sort,
|
|
|
});
|
|
|
const render = () => (
|
|
|
<ElTable
|
|
|
ref={refsTable}
|
|
|
{...props}
|
|
|
{...attrs}
|
|
|
class={{ 'sortable-table': true, [props.group]: true }}
|
|
|
expand-row-keys={unref(expandRowKeys)}
|
|
|
row-key={props.rowKey}
|
|
|
sort-id={unref(uid)}
|
|
|
onExxpandChange={() => handleExpandChange}
|
|
|
v-slots={unref(sortting) ? {} : slots}
|
|
|
/>
|
|
|
);
|
|
|
</script>
|
|
|
|
|
|
<style lang="less" scoped>
|
|
|
:deep(.ghost-row) {
|
|
|
background: #ddd;
|
|
|
}
|
|
|
:deep(.chosen-row) {
|
|
|
background: #eee;
|
|
|
}
|
|
|
:deep(.drag-row) {
|
|
|
background: #ccc;
|
|
|
}
|
|
|
</style>
|