vue2+elementUI table多个字段排序

最近用elementUI的table组件进行多字段排序时发现在设定一个初始默认排序后,点击其他排序字段,会让之前选中的排序字段样式都消失掉,虽然传入接口的数据可以自行处理,但传入接口的数据和用户看到的样式不统一,还是需要优化,下图为前面描述的排序样式图标

最终,经过一系列调整,完善了:可指定默认排序,且指定默认排序后点击其他的排序字段不会清空掉之前的排序样式

实现效果:

最后会贴出完整组件代码

主要排序代码部分

props:

javascript 复制代码
// 默认排序
        defaultSort: {
            type: Object,
            default: () => { }
        },

mounted:

javascript 复制代码
        // 处理默认排序
        if (this.defaultSort && this.defaultSort.prop) {
            this.$nextTick(() => {
                const table = this.$refs[`table_${this.timestamp}`];
                if (table) {
                    const column = table.columns.find(col => col.property === this.defaultSort.prop);
                    if (column) {
                        // 同步两个状态
                        column.order = this.defaultSort.order;
                        column.multiOrder = this.defaultSort.order;
                        // 初始化排序参数
                        this.handleOrderChange(this.defaultSort.prop, this.defaultSort.order);
                    }
                }
            });
        }
    

method:

javascript 复制代码
 // 设置列的排序为我们自定义的排序
        headerCellClassName ({ column }) {
            // 只有当 multiOrder 存在时才覆盖 order
            if (column.multiOrder !== undefined) {
                column.order = column.multiOrder;
            }
        },
        // 列表排序
        sortChange ({ column, prop, order }) {
            // 有些列不需要排序,提前返回
            if (column.sortable !== "custom") {
                return;
            }
            if (!column.multiOrder) {
                column.multiOrder = "descending";
            } else if (column.multiOrder === "descending") {
                column.multiOrder = "ascending";
            } else {
                column.multiOrder = "";
            }
            this.handleOrderChange(column.property, column.multiOrder);
        },
        // 点击排序箭头 
        handleOrderChange (sortName, sortType) {
            if (!sortType) {
                this.sortParams = this.sortParams.filter(
                    (item) => {
                        return item.sortName !== sortName;
                    }
                );
            } else {
                let result = this.sortParams.find(
                    (e) => e.sortName === sortName
                );
                if (result) {
                    result.sortType = sortType == "descending" ? "desc" : "asc";
                } else {
                    this.sortParams.push({
                        sortName: sortName,
                        sortType: sortType == "descending" ? "desc" : "asc",
                    });
                }
            }
            let sortStr = this.buildSortParams()
            this.$nextTick(() => {
                this.$emit("sort-change", sortStr);
                this.$emit("sortChange", sortStr);
                this.$emit("sortchange", sortStr);
            })
        },
        // 构建排序参数字符串
        buildSortParams () {
            if (!this.sortParams.length) return "";
            return this.sortParams
                .map((item, index) =>
                    `&sortList[${index}].sortName=${item.sortName}&sortList[${index}].sortType=${item.sortType}`
                )
                .join('');
        },

这里以整个组件的形式贴出记录(含业务代码可能有些功能不可直接食用)

javascript 复制代码
<template>
    <div class="custom-table" ref="tableContainer">
        <el-table :max-height="maxHeight" :data="tableData" border :height="computedHeight" v-loading="tableLoading"
            @sort-change="sortChange" @selection-change="handleSelectionChange" :ref="`table_${timestamp}`"
            :default-sort="defaultSort" :stripe="stripe" :size="size" :show-summary="showSummary"
            :summary-method="summaryMethod" @row-click="rowClick" @header-dragend="headerDragend"
            :header-cell-class-name="headerCellClassName">
            <!-- 选择列 -->
            <el-table-column fixed="left" v-if="showSelection" type="selection" width="55" align="center" />

            <!-- 序号列 -->
            <el-table-column fixed="left" v-if="showIndex" align="center" type="index" label="序号" width="50"
                :index="indexMethod" />

            <!-- 数据列 -->
            <template v-for="item in tableColumns">
                <el-table-column :fixed="item.fixed" align="center" :prop="item.prop" :label="item.label"
                    :min-width="item.minWidth" :width="item.width" :sortable="item.sortable" :formatter="item.formatter"
                    :show-overflow-tooltip="item.showOverflowTooltip !== false">
                    <template #default="scope">
                        <slot v-if="$scopedSlots[`col-${item.prop}`]" :name="`col-${item.prop}`" :row="scope.row"
                            :index="scope.$index" />
                        <!-- 文件ID显示 -->
                        <el-button v-else-if="item.isImage && scope.row[item.prop]" type="text"
                            @click="viewImages(scope.row, item.prop)">查看图片</el-button>
                        <!-- 其他普通字段显示 -->
                        <span v-else>{{ scope.row[item.prop] || "--" }}</span>
                    </template>
                </el-table-column>
            </template>

            <!-- 操作列 -->
            <el-table-column v-if="showOperate" align="center" label="操作" :width="operateWidth" fixed="right">
                <template slot-scope="scope">
                    <slot name="operate" :row="scope.row" :index="scope.$index" />
                </template>
            </el-table-column>
        </el-table>

        <!-- 分页 -->
        <div v-if="showPagination" class="pagination" ref="pagination">
            <el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange"
                :current-page="currentPage" :page-sizes="pageSizes" :page-size="pageSize" :layout="paginationLayout"
                :total="total" />
        </div>
        <!-- 图片预览弹窗 -->
        <CustomPreview v-model="imageDialogVisible" :images="previewImageUrl"></CustomPreview>
    </div>
</template>

<script>
/**
 * @description 自定义表格组件--支持多个排序高亮
 * 
 * @example
 * <!-- 基础用法 -->
 * <custom-table
 *   :table-data="tableData"
 *   :table-columns="tableColumns"
 *   :show-pagination="true"
 *   :show-selection="true"
 *   :show-summary="true"
 *   :summary-method="getSummaries"
 *   :default-sort="{ prop: 'createTime', order: 'descending' }"
 *   @selection-change="handleSelection"
 *   @sort-change="handleSort"
 * >
 *   <!-- 自定义状态列 -->
 *   <template #col-status="{ row }">
 *     <el-tag :type="row.status ? 'success' : 'danger'">
 *       {{ row.status ? '启用' : '禁用' }}
 *     </el-tag>
 *   </template>
 * 
 *   <!-- 自定义操作列 -->
 *   <template #operate="{ row }">
 *     <el-button type="text" @click="handleEdit(row)">编辑</el-button>
 *     <el-button type="text" @click="handleDelete(row)">删除</el-button>
 *   </template>
 * </custom-table>
 * 
 * @example
 * // 数据配置示例
 * data() {
 *   return {
 *     tableData: [
 *       {
 *         id: 1,
 *         name: '张三',
 *         status: 1,
 *         createTime: '2023-01-01',
 *         fileIds: 'file1,file2'  // 支持文件预览
 *       }
 *     ],
 *     tableColumns: [
 *       {
 *         prop: 'name',
 *         label: '姓名',
 *         sortable: 'custom',  // 支持自定义排序
 *         minWidth: 120
 *       },
 *       {
 *         prop: 'status',
 *         label: '状态',
 *         width: 100
 *       },
 *       {
 *         prop: 'createTime',
 *         label: '创建时间',
 *         sortable: 'custom',
 *         formatter: (row) => formatDate(row.createTime)
 *       },
 *       {
 *         prop: 'fileIds',
 *         label: '附件',
 *         width: 100
 *       }
 *     ],
 *     // 合计行方法
 *     getSummaries(param) {
 *       const { columns, data } = param;
 *       const sums = [];
 *       columns.forEach((column, index) => {
 *         if (index === 0) {
 *           sums[index] = '合计';
 *           return;
 *         }
 *         const values = data.map(item => Number(item[column.property]));
 *         if (!values.every(value => isNaN(value))) {
 *           sums[index] = values.reduce((prev, curr) => {
 *             const value = Number(curr);
 *             if (!isNaN(value)) {
 *               return prev + curr;
 *             } else {
 *               return prev;
 *             }
 *           }, 0).toFixed(2);
 *         } else {
 *           sums[index] = '';
 *         }
 *       });
 *       return sums;
 *     }
 *   }
 * },
 * methods: {
 *   // 处理选择变化
 *   handleSelection(selection) {
 *     console.log('选中的行:', selection);
 *   },
 *   // 处理排序变化
 *   handleSort(sortStr) {
 *     console.log('排序参数:', sortStr);
 *     // 重新加载数据
 *     this.getTableData();
 *   }
 * }
 */



import CustomPreview from "@/components/Custom/CustomPreview.vue"
export default {
    name: "CustomTable",
    props: {
        // 最大高度
        maxHeight: {
            type: [String, Number],
            default: null
        },

        // 表格数据
        tableData: {
            type: Array,
            default: () => []
        },
        // 表格列配置
        tableColumns: {
            type: Array,
            default: () => []
        },
        // 表格高度
        tableHeight: {
            type: [String, Number],
            default: "100%"
        },
        // 加载状态
        tableLoading: {
            type: Boolean,
            default: false
        },
        // 操作列宽度
        operateWidth: {
            type: [Number, String],
            default: 100
        },
        // 默认排序
        defaultSort: {
            type: Object,
            default: () => { }
        },
        // 是否显示斑马纹
        stripe: {
            type: Boolean,
            default: true
        },
        // 表格尺寸
        size: {
            type: String,
            default: 'medium'
        },
        // 是否显示序号
        showIndex: {
            type: Boolean,
            default: true
        },
        // 是否显示选择框
        showSelection: {
            type: Boolean,
            default: false
        },
        // 是否显示操作列
        showOperate: {
            type: Boolean,
            default: true
        },
        // 是否显示分页
        showPagination: {
            type: Boolean,
            default: false
        },
        // 是否显示合计行
        showSummary: {
            type: Boolean,
            default: false
        },
        // 合计行计算方法
        summaryMethod: {
            type: Function,
            default: null
        },
        // 当前页码
        currentPage: {
            type: Number,
            default: 1
        },
        // 每页显示条数
        pageSize: {
            type: Number,
            default: 10
        },
        // 可选的每页显示条数
        pageSizes: {
            type: Array,
            default: () => [10, 20, 50, 100]
        },
        // 总条数
        total: {
            type: Number,
            default: 0
        },
        // 分页布局
        paginationLayout: {
            type: String,
            default: 'total, sizes, prev, pager, next, jumper'
        }
    },
    components: {
        CustomPreview,
    },
    data () {
        return {
            sortParams: [],
            imageDialogVisible: false,
            computedHeight: null,
            previewImageUrl: [],
            timestamp: Date.now() // 添加时间戳

        }
    },
    mounted () {
        this.calculateHeight();
        window.addEventListener('resize', this.calculateHeight);
        // 处理默认排序
        if (this.defaultSort && this.defaultSort.prop) {
            this.$nextTick(() => {
                const table = this.$refs[`table_${this.timestamp}`];
                if (table) {
                    const column = table.columns.find(col => col.property === this.defaultSort.prop);
                    if (column) {
                        // 同步两个状态
                        column.order = this.defaultSort.order;
                        column.multiOrder = this.defaultSort.order;
                        // 初始化排序参数
                        this.handleOrderChange(this.defaultSort.prop, this.defaultSort.order);
                    }
                }
            });
        }
    },
    beforeDestroy () {
        window.removeEventListener('resize', this.calculateHeight);
    },
    methods: {
        //刷新表格事件
        refreshTable () {
            if (this.$refs[`table_${this.timestamp}`]) {
                console.log('刷新了表格');
                this.$refs[`table_${this.timestamp}`].doLayout();
            }
        },
        headerDragend (newWidth, oldWidth, column, event) {
            this.$refs[`table_${this.timestamp}`].doLayout();
            this.$emit('header-dragend', newWidth, oldWidth, column, event);
        },
        // 查看图片
        async viewImages (row, prop) {
            this.previewImageUrl = row[prop]
            this.imageDialogVisible = true;
        },
        // 计算表格高度
        calculateHeight () {
            if (this.maxHeight) {
                console.log('使用最大高度');
            }
            else if (this.tableHeight === '100%') {
                this.$nextTick(() => {
                    const containerHeight = this.$refs.tableContainer?.clientHeight;
                    const paginationHeight = this.showPagination ? this.$refs.pagination?.clientHeight || 40 : 0;
                    if (containerHeight) {
                        this.computedHeight = containerHeight - paginationHeight;
                    }
                });
            } else {
                this.computedHeight = this.tableHeight;
            }
        },
        // 设置列的排序为我们自定义的排序
        headerCellClassName ({ column }) {
            // 只有当 multiOrder 存在时才覆盖 order
            if (column.multiOrder !== undefined) {
                column.order = column.multiOrder;
            }
        },
        // 列表排序
        sortChange ({ column, prop, order }) {
            // 有些列不需要排序,提前返回
            if (column.sortable !== "custom") {
                return;
            }
            if (!column.multiOrder) {
                column.multiOrder = "descending";
            } else if (column.multiOrder === "descending") {
                column.multiOrder = "ascending";
            } else {
                column.multiOrder = "";
            }
            this.handleOrderChange(column.property, column.multiOrder);
        },
        // 点击排序箭头 
        handleOrderChange (sortName, sortType) {
            if (!sortType) {
                this.sortParams = this.sortParams.filter(
                    (item) => {
                        return item.sortName !== sortName;
                    }
                );
            } else {
                let result = this.sortParams.find(
                    (e) => e.sortName === sortName
                );
                if (result) {
                    result.sortType = sortType == "descending" ? "desc" : "asc";
                } else {
                    this.sortParams.push({
                        sortName: sortName,
                        sortType: sortType == "descending" ? "desc" : "asc",
                    });
                }
            }
            let sortStr = this.buildSortParams()
            this.$nextTick(() => {
                this.$emit("sort-change", sortStr);
                this.$emit("sortChange", sortStr);
                this.$emit("sortchange", sortStr);
            })
        },
        // 构建排序参数字符串
        buildSortParams () {
            if (!this.sortParams.length) return "";
            return this.sortParams
                .map((item, index) =>
                    `&sortList[${index}].sortName=${item.sortName}&sortList[${index}].sortType=${item.sortType}`
                )
                .join('');
        },
        // 行点击
        rowClick (row, column, event) {
            this.$emit("row-click", row, column, event);
        },
        // 选择变化
        handleSelectionChange (selection) {
            this.$emit("selection-change", selection);
        },
        // 每页条数变化
        handleSizeChange (val) {
            this.$emit("size-change", val);
        },
        // 当前页变化
        handleCurrentChange (val) {
            this.$emit("current-change", val);
        },
        // 自定义序号
        indexMethod (index) {
            return (this.currentPage - 1) * this.pageSize + index + 1;
        },
        // 清空选择
        clearSelection () {
            this.$refs[`table_${this.timestamp}`].clearSelection();
        },
        // 切换行选择状态
        toggleRowSelection (row, selected) {
            this.$refs[`table_${this.timestamp}`].toggleRowSelection(row, selected);
        }
    },
    watch: {
        tableHeight: {
            handler () {
                this.calculateHeight();
            },
            immediate: true
        },
        showPagination () {
            this.calculateHeight();
        },
        // 新增监听 tableData
        tableData: {
            handler (newVal) {
                if (newVal && newVal.length > 0) {
                    this.$nextTick(() => {
                        if (this.$refs[`table_${this.timestamp}`]) {
                            this.$refs[`table_${this.timestamp}`].doLayout();
                        }
                    });
                }
            },
            immediate: true
        }
    }
};
</script>

<style lang="scss" scoped>
.custom-table {
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.custom-table :deep(.el-table) {
    flex: 1;
}

.pagination {
    margin-top: 20px;
    text-align: center;
    flex-shrink: 0;
}

.delete-btn {
    color: #F56C6C;
}

.delete-btn:hover {
    color: #f78989;
}

.flex {
    display: flex;
    justify-content: center;
    padding: 10px 0;
}

::v-deep {
    .el-table__cell {
        background-color: #fff;
    }

    .el-table th {
        background-color: #f5f7fa;
    }

    .el-table__body-wrapper {
        z-index: 2;
    }

}
</style>
相关推荐
H@Z*rTE|i16 小时前
elementUi 当有弹窗的时候提示语被覆盖的问题
前端·javascript·elementui
hellokatewj16 小时前
React Hooks 全解:原理、API 与应用场景
前端·javascript·react.js
袋鱼不重16 小时前
保姆级教程:让 Cursor 编辑器突破地区限制,正常调用大模型(附配置 + 截图)
前端·后端·cursor
bieao16 小时前
Vite+Antd+Micro-app中iframe模式下样式闪烁的问题
前端
zhouzhouya16 小时前
码上星辰,人间烟火:我的2025
前端·程序员·代码规范
江湖yi山人16 小时前
生产环境的log,上传到开发者的本地服务器
javascript·python
彭涛36116 小时前
什么是MessageChannel
前端
嘉琪00117 小时前
provide 和 inject的理解?
前端·javascript·vue.js
匆叔17 小时前
ESLint,前端项目CTRL+S,自动保存格式化文档,超细
前端