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>