290 lines
8.0 KiB
Vue
Raw Normal View History

2025-08-22 19:48:04 +08:00
<template>
<div class="custom-table-wrapper">
<!-- 表格主体 -->
<el-table ref="tableRef" :data="tableData" :loading="loading" :stripe="config.stripe || false"
:border="config.border" :fit="config.fit !== true" :show-header="config.showHeader !== false"
:highlight-current-row="config.highlightCurrentRow || false" :row-class-name="config.rowClassName"
:header-cell-style="config.headerCellStyle" :cell-style="config.cellStyle" :max-height="config.maxHeight"
:height="config.height" :size="config.size || 'default'" :empty-text="config.emptyText || '暂无数据'"
@selection-change="handleSelectionChange" @current-change="handleCurrentChange" @row-click="handleRowClick"
@row-dblclick="handleRowDblclick" @sort-change="handleSortChange" v-bind="$attrs">
<!-- 多选列 -->
<el-table-column v-if="config.selection" type="selection" :width="config.selectionWidth || 55"
:fixed="config.selectionFixed" align="center" />
<!-- 序号列 -->
<el-table-column v-if="config.index" type="index" :label="config.indexLabel || '序号'"
:width="config.indexWidth || 60" :fixed="config.indexFixed" align="center" :index="getIndex" />
<!-- 动态列 -->
2025-08-26 17:31:31 +08:00
<template v-for="item in columns" :key="item.prop || item.key">
2025-08-22 19:48:04 +08:00
<!-- 自定义插槽列 -->
2025-08-26 17:31:31 +08:00
<el-table-column v-if="item.slot && item.visible" :prop="item.prop" :label="item.label" :width="item.width"
:min-width="item.minWidth" :fixed="item.fixed" :align="item.align || 'left'" :sortable="item.sortable"
:show-overflow-tooltip="item.showOverflowTooltip !== false">
2025-08-22 19:48:04 +08:00
<template #default="scope">
2025-08-26 17:31:31 +08:00
<slot :name="item.slot" :row="scope.row" :column="scope.column" :$index="scope.$index" />
2025-08-22 19:48:04 +08:00
</template>
</el-table-column>
<!-- 普通列 -->
2025-08-26 17:31:31 +08:00
<el-table-column v-if="!item.slot && item.visible" :prop="item.prop" :label="item.label" :width="item.width"
:min-width="item.minWidth" :fixed="item.fixed" :align="item.align || 'left'" :sortable="item.sortable"
:show-overflow-tooltip="item.showOverflowTooltip !== false" :formatter="item.formatter">
2025-08-22 19:48:04 +08:00
<!-- 表头插槽 -->
2025-08-26 17:31:31 +08:00
<template v-if="item.headerSlot" #header="scope">
<slot :name="item.headerSlot" :column="scope.column" :$index="scope.$index" />
2025-08-22 19:48:04 +08:00
</template>
</el-table-column>
</template>
<!-- 操作列 -->
<el-table-column v-if="$slots.action" :label="config.actionLabel || '操作'" :width="config.actionWidth"
:min-width="config.actionMinWidth || 120" :fixed="config.actionFixed || 'right'"
:align="config.actionAlign || 'center'">
<template #default="scope">
<slot name="action" :row="scope.row" :column="scope.column" :$index="scope.$index" />
</template>
</el-table-column>
<!-- 空数据插槽 -->
<template v-if="$slots.empty" #empty>
<slot name="empty" />
</template>
</el-table>
<!-- 分页组件 -->
<div v-if="pagination.show" class="pagination-wrapper">
<el-pagination v-model:current-page="currentPage" v-model:page-size="currentPageSize" :total="pagination.total"
:page-sizes="pagination.pageSizes || [10, 20, 50, 100]"
:layout="pagination.layout || 'total, sizes, prev, pager, next, jumper'"
:background="pagination.background !== false" :small="pagination.small || false"
:disabled="pagination.disabled || false" @size-change="handleSizeChange"
@current-change="handleCurrentPageChange" />
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref, watch, PropType } from "vue";
import { TableColumnCtx } from 'element-plus';
interface TableColumn {
prop?: string;
2025-08-26 17:31:31 +08:00
key?: string | number;
2025-08-22 19:48:04 +08:00
label?: string;
width?: string | number;
minWidth?: string | number;
fixed?: boolean | string;
align?: string;
sortable?: boolean;
showOverflowTooltip?: boolean;
formatter?: Function;
slot?: string;
headerSlot?: string;
visible?: boolean; // 是否显示列
}
// Props 定义
const props = defineProps({
// 表格数据
data: {
type: Array,
default: () => [],
},
// 表格列配置
columns: {
type: Array as PropType<TableColumn[]>,
default: () => [],
},
// 表格配置
config: {
type: Object,
default: () => ({}),
},
// 加载状态
loading: {
type: Boolean,
default: false,
},
// 分页配置
pagination: {
type: Object,
default: () => ({
show: false,
total: 0,
pageSize: 10,
currentPage: 1,
}),
},
});
// Emits 定义
const emit = defineEmits([
"selection-change",
"current-change",
"row-click",
"row-dblclick",
"sort-change",
"page-change",
"size-change",
]);
// 响应式数据
const tableRef = ref();
const currentPage = ref(props.pagination.currentPage || 1); // 第几页
const currentPageSize = ref(props.pagination.pageSize || 10); // 一页显示多少条
// 计算属性
const tableData = computed(() => {
if (props.pagination.show && props.pagination.clientPaging) {
// 客户端分页
const start = (currentPage.value - 1) * currentPageSize.value;
const end = start + currentPageSize.value;
return props.data.slice(start, end);
}
return props.data;
});
// 序号计算
const getIndex = (index: number) => {
if (props.pagination.show && !props.pagination.clientPaging) {
return (currentPage.value - 1) * currentPageSize.value + index + 1;
}
return index + 1;
};
// 事件处理
const handleSelectionChange = (selection: any[]) => {
emit("selection-change", selection);
};
const handleCurrentChange = (currentRow: any, oldCurrentRow: any) => {
emit("current-change", currentRow, oldCurrentRow);
};
const handleRowClick = (row: any, column: TableColumnCtx<any>, event: Event) => {
emit("row-click", row, column, event);
};
const handleRowDblclick = (row: any, column: TableColumnCtx<any>, event: Event) => {
emit("row-dblclick", row, column, event);
};
const handleSortChange = (sortInfo: Object) => {
emit("sort-change", sortInfo);
};
const handleSizeChange = (size: number) => {
currentPageSize.value = size;
currentPage.value = 1;
emit("size-change", {
pageSize: size,
currentPage: 1,
});
};
const handleCurrentPageChange = (page: number) => {
currentPage.value = page;
emit("page-change", {
currentPage: page,
pageSize: currentPageSize.value,
});
};
// 监听分页配置变化
watch(
() => props.pagination.currentPage,
(newVal) => {
if (newVal) {
currentPage.value = newVal;
}
},
);
watch(
() => props.pagination.pageSize,
(newVal) => {
if (newVal) {
currentPageSize.value = newVal;
}
},
);
// 暴露方法
const clearSelection = () => {
tableRef.value?.clearSelection();
};
const toggleRowSelection = (row: Object, selected: boolean) => {
tableRef.value?.toggleRowSelection(row, selected);
};
const toggleAllSelection = () => {
tableRef.value?.toggleAllSelection();
};
const setCurrentRow = (row: Object) => {
tableRef.value?.setCurrentRow(row);
};
const clearSort = () => {
tableRef.value?.clearSort();
};
const doLayout = () => {
tableRef.value?.doLayout();
};
const sort = (prop: string, order: any) => {
tableRef.value?.sort(prop, order);
};
// 导出方法
defineExpose({
clearSelection,
toggleRowSelection,
toggleAllSelection,
setCurrentRow,
clearSort,
doLayout,
sort,
tableRef,
});
</script>
<style scoped lang="scss">
.custom-table-wrapper {
width: 100%;
}
.pagination-wrapper {
display: flex;
align-items: center;
justify-content: flex-end;
padding: 12px 0;
margin-top: 16px;
margin-right: 20px;
}
/* 修改选中行背景色 */
::v-deep .el-table__body tr.current-row>td {
background-color: #add7f7 !important;
}
/* 可选:修改悬停颜色 */
::v-deep .el-table__body tr.hover-row>td {
background-color: #326ca5 !important;
}
/* 响应式设计 */
@media (max-width: 768px) {
.pagination-wrapper {
justify-content: center;
}
.pagination-wrapper :deep(.el-pagination) {
flex-wrap: wrap;
}
}
</style>