291 lines
8.1 KiB
Vue
291 lines
8.1 KiB
Vue
|
|
<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" />
|
||
|
|
|
||
|
|
<!-- 动态列 -->
|
||
|
|
<template v-for="column in columns" :key="column.prop || column.key">
|
||
|
|
<!-- 自定义插槽列 -->
|
||
|
|
<el-table-column v-if="column.slot && column.visible" :prop="column.prop" :label="column.label"
|
||
|
|
:width="column.width" :min-width="column.minWidth" :fixed="column.fixed" :align="column.align || 'left'"
|
||
|
|
:sortable="column.sortable" :show-overflow-tooltip="column.showOverflowTooltip !== false">
|
||
|
|
<template #default="scope">
|
||
|
|
<slot :name="column.slot" :row="scope.row" :column="scope.column" :$index="scope.$index" />
|
||
|
|
</template>
|
||
|
|
</el-table-column>
|
||
|
|
|
||
|
|
<!-- 普通列 -->
|
||
|
|
<el-table-column v-if="!column.slot && column.visible" :prop="column.prop" :label="column.label"
|
||
|
|
:width="column.width" :min-width="column.minWidth" :fixed="column.fixed" :align="column.align || 'left'"
|
||
|
|
:sortable="column.sortable" :show-overflow-tooltip="column.showOverflowTooltip !== false"
|
||
|
|
:formatter="column.formatter">
|
||
|
|
<!-- 表头插槽 -->
|
||
|
|
<template v-if="column.headerSlot" #header="scope">
|
||
|
|
<slot :name="column.headerSlot" :column="scope.column" :$index="scope.$index" />
|
||
|
|
</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;
|
||
|
|
key?: string;
|
||
|
|
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>
|