import type { AxiosInstance, AxiosResponse } from 'axios';
|
import type { TableInstance } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
import Sortable from 'sortablejs';
|
import type { Ref } from 'vue';
|
import { nextTick, ref, unref } from 'vue';
|
import { convertListToTree, flatten } from '/@/utils/util';
|
import { deepClone } from '/@/utils/other';
|
import request from '/@/utils/request';
|
|
const changeItemPosition = (arr: any[], oldPosition: number, newPosition: number) => {
|
const oldData = arr.splice(oldPosition, 1)[0];
|
arr.splice(newPosition, 0, oldData);
|
return oldData;
|
};
|
|
const checkHaveChild = (arr) => {
|
if (arr.Children && arr.Children.length !== 0) {
|
return true;
|
} else {
|
return false;
|
}
|
};
|
|
// 重新渲染表格,保持页面跟数据一致
|
const reRender = (reactiveData: Ref<any[]>, data, needNextTick = true) => {
|
// 可通过主动控制 expandedKeys,维持重新渲染时的折叠展开状态
|
if (needNextTick) {
|
reactiveData.value = [];
|
nextTick(() => {
|
reactiveData.value = data;
|
});
|
} else {
|
reactiveData.value = data;
|
}
|
};
|
|
/**
|
* @param reactiveData 响应式数据
|
* @param targetFlattenedData 拖拽后的数据(树形数据需要先扁平化再传入)
|
* @param originalData 拖拽前数据(方便拖拽失败后回滚)
|
* @param getTableData 获取数据函数,拖拽结束后重新获取数据
|
* @param UpdateSorter 更新 sorter 的接口函数
|
* @param errorHandler 更新失败后,可能会有一些额外的处理
|
* @param extraParams UpdateSorter,可能除了 ID,SortCode 外需要传入额外的参数
|
*/
|
export const updateSort = async (
|
reactiveData: Ref<any[]>,
|
targetFlattenedData,
|
originalData,
|
getTableData: () => Promise<void> | void,
|
UpdateSorter: (params: any, req?: AxiosInstance) => Promise<AxiosResponse<any, any>>,
|
errorHandler?: () => void,
|
extraParams: Record<string, any> = {},
|
req: any = request
|
) => {
|
const rawExtraParams = {};
|
for (const key in extraParams) {
|
rawExtraParams[key] = unref(extraParams[key]);
|
}
|
|
const sortParams = targetFlattenedData.map((item, index) => ({ ID: item.ID, SortCode: index + 1, ...rawExtraParams }));
|
|
const res = await UpdateSorter(sortParams, req);
|
|
if (res?.Code === 0) {
|
if (res.Data) {
|
getTableData();
|
ElMessage.success('更新排序成功');
|
} else {
|
reRender(reactiveData, originalData, false);
|
errorHandler && errorHandler();
|
ElMessage.error('更新排序失败');
|
}
|
} else {
|
reRender(reactiveData, originalData, false);
|
errorHandler && errorHandler();
|
ElMessage.error('更新排序失败' + (res?.Message ? `,${JSON.stringify(res.Message)}` : ''));
|
}
|
};
|
|
/**
|
* 拖拽排序
|
* @param tableData 表格响应式变量(如果是树,使用扁平化数据)
|
* @param UpdateSorter 更新排序接口(已支持自定义 req,默认为 request)
|
* @param getTableData 获取表格数据函数
|
* @param extraParams 更新排序接口的额外参数
|
* @param isTree 为 children 构建的表格,isTree 设置为 true
|
* @param req 当前请求使用的 req,默认为 request
|
* @returns handleDragStatus、draggableTableRef
|
*/
|
export const useTableSort = (
|
tableData: Ref<any[]>,
|
UpdateSorter: (params: any, req?: AxiosInstance) => Promise<AxiosResponse<any, any>>,
|
getTableData: () => Promise<void> | void,
|
extraParams: Record<string, any> = {},
|
isTree?,
|
req: any = request
|
) => {
|
const draggableTableRef = ref<TableInstance>(null);
|
|
// 拖拽树结构,拖拽带子级菜单的行,需要手动连带子级一起改变位置
|
const handleDragTree = (oldIndex, newIndex) => {
|
const flatTableData = flatten(deepClone(tableData.value));
|
const targetRow = flatTableData[newIndex];
|
const dragRow = flatTableData[oldIndex];
|
const cloneTableData = deepClone(tableData.value);
|
|
// 同级情况,父级相同
|
if (targetRow.ParentID === dragRow.ParentID) {
|
// 都无Children
|
if (!checkHaveChild(dragRow) && !checkHaveChild(targetRow)) {
|
changeItemPosition(flatTableData, oldIndex, newIndex);
|
}
|
|
// drag有, relate无
|
if (checkHaveChild(dragRow) && !checkHaveChild(targetRow)) {
|
const oldData = changeItemPosition(flatTableData, oldIndex, newIndex);
|
|
// 往前拖
|
if (newIndex < oldIndex) {
|
// 有子元素的,子元素需要同样跟上来
|
const flatChildren = flatten(deepClone(oldData.Children));
|
|
for (let i = 1, len = flatChildren.length; i <= len; i++) {
|
changeItemPosition(flatTableData, oldIndex + i, newIndex + i);
|
}
|
|
// 往后拖
|
} else {
|
// 有子元素的,子元素需要同样跟下来
|
const flatChildren = flatten(deepClone(oldData.Children));
|
|
for (let i = 1, len = flatChildren.length; i <= len; i++) {
|
changeItemPosition(flatTableData, oldIndex, newIndex);
|
}
|
}
|
}
|
|
// drag无, relate有
|
if (!checkHaveChild(dragRow) && checkHaveChild(targetRow)) {
|
changeItemPosition(flatTableData, oldIndex, newIndex);
|
|
// 往后拖
|
if (newIndex > oldIndex) {
|
// 有子元素的,子元素需要同样跟上来
|
const flatChildren = flatten(deepClone(targetRow.Children));
|
|
for (let i = 1, len = flatChildren.length; i <= len; i++) {
|
changeItemPosition(flatTableData, newIndex + i, newIndex + i - 1);
|
}
|
}
|
}
|
|
// drag有, relate有
|
if (checkHaveChild(dragRow) && checkHaveChild(targetRow)) {
|
if (newIndex < oldIndex) {
|
const oldData = changeItemPosition(flatTableData, oldIndex, newIndex);
|
|
// 有子元素的,子元素需要同样跟上来
|
const flatChildren = flatten(deepClone(oldData.Children));
|
|
for (let i = 1, len = flatChildren.length; i <= len; i++) {
|
changeItemPosition(flatTableData, oldIndex + i, newIndex + i);
|
}
|
} else {
|
const relateFlatChildren = flatten(deepClone(targetRow.Children));
|
|
const oldData = changeItemPosition(flatTableData, oldIndex, newIndex + relateFlatChildren.length);
|
|
// 有子元素的,子元素需要同样跟下来
|
const flatChildren = flatten(deepClone(oldData.Children));
|
|
for (let i = 1, len = flatChildren.length; i <= len; i++) {
|
changeItemPosition(flatTableData, oldIndex, newIndex + relateFlatChildren.length);
|
}
|
}
|
}
|
for (const item of flatTableData) {
|
Reflect.deleteProperty(item, 'Children');
|
}
|
const treeData = convertListToTree(flatTableData);
|
|
reRender(tableData, treeData, true);
|
updateSort(tableData, flatTableData, cloneTableData, getTableData, UpdateSorter, undefined, extraParams, req);
|
} else {
|
ElMessage.warning('禁止跨级拖动排序');
|
reRender(tableData, cloneTableData, true);
|
}
|
};
|
|
//监听表格拖拽开关
|
const handleDragStatus = (value) => {
|
if (tableData.value.length === 0) return;
|
if (value) {
|
setTableDraggable();
|
} else {
|
destroySortObj();
|
}
|
};
|
let tableSortObj = null;
|
|
// 初始化表格拖动
|
const setTableDraggable = () => {
|
const el = draggableTableRef.value.$el.querySelector('.el-table__body tbody');
|
tableSortObj = Sortable.create(el, {
|
handle: '.el-table__row',
|
sort: true,
|
onEnd(evt) {
|
const { newIndex, oldIndex } = evt;
|
if (oldIndex == newIndex) return;
|
if (isTree) {
|
handleDragTree(oldIndex, newIndex);
|
} else {
|
const cloneTableData = deepClone(tableData.value);
|
changeItemPosition(tableData.value, evt.oldIndex, evt.newIndex);
|
updateSort(tableData, tableData.value, cloneTableData, getTableData, UpdateSorter, undefined, extraParams, req);
|
}
|
},
|
});
|
};
|
|
//关闭拖拽
|
const destroySortObj = () => {
|
// 销毁拖动对象
|
tableSortObj && tableSortObj.destroy();
|
};
|
|
return {
|
handleDragStatus,
|
draggableTableRef,
|
};
|
};
|