gerson
2025-03-25 982732e3aea8e429a9bbecc9e9927caa1d51a2fb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
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,
    };
};