2025-05-30 17:31:05 +08:00
|
|
|
|
<template>
|
|
|
|
|
<div class="custom-table-container">
|
|
|
|
|
<div class="custom-table-tool">
|
|
|
|
|
<el-form :inline="true" class="demo-form-inline" :label-width="'auto'">
|
2025-06-03 17:35:22 +08:00
|
|
|
|
<el-form-item label="每页显示" style="margin-bottom: 10px">
|
2025-05-30 17:31:05 +08:00
|
|
|
|
<el-select
|
2025-06-03 17:35:22 +08:00
|
|
|
|
v-model="computedPageSize"
|
2025-05-30 17:31:05 +08:00
|
|
|
|
placeholder="请选择"
|
|
|
|
|
style="width: 100px"
|
|
|
|
|
@change="pageSizeChange($event)"
|
|
|
|
|
>
|
|
|
|
|
<el-option
|
|
|
|
|
v-for="item in pageSizes"
|
|
|
|
|
:key="item"
|
|
|
|
|
:label="item + '条'"
|
|
|
|
|
:value="item"
|
|
|
|
|
/>
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
</div>
|
|
|
|
|
<el-table
|
2025-06-03 17:35:22 +08:00
|
|
|
|
style="flex: 1; display: flex; flex-direction: column; text-align: center"
|
2025-05-30 17:31:05 +08:00
|
|
|
|
:max-height="tableMaxHeight"
|
2025-06-03 17:35:22 +08:00
|
|
|
|
v-loading="loading"
|
|
|
|
|
:data="tableData"
|
2025-05-30 17:31:05 +08:00
|
|
|
|
:border="showBorder"
|
|
|
|
|
:stripe="showStripe"
|
2025-06-03 17:35:22 +08:00
|
|
|
|
:fit="true"
|
2025-05-30 17:31:05 +08:00
|
|
|
|
v-bind="$attrs"
|
|
|
|
|
:header-cell-class-name="headerCellClassName"
|
|
|
|
|
:cell-class-name="cellClassName"
|
|
|
|
|
@selection-change="handleSelectionChange"
|
|
|
|
|
>
|
|
|
|
|
<!-- 首列多选框 -->
|
|
|
|
|
<el-table-column
|
|
|
|
|
v-if="showSelection"
|
|
|
|
|
type="selection"
|
|
|
|
|
width="55"
|
|
|
|
|
align="center"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<template v-for="column in columns" :key="column.prop">
|
|
|
|
|
<el-table-column
|
|
|
|
|
:prop="column.prop"
|
|
|
|
|
:label="column.label"
|
|
|
|
|
:width="column.width"
|
|
|
|
|
:align="column.align || 'left'"
|
|
|
|
|
:sortable="column.sortable"
|
|
|
|
|
:header-class-name="column.headerClassName"
|
|
|
|
|
>
|
|
|
|
|
<!-- 支持插槽 -->
|
|
|
|
|
<template v-if="column.slotName" #default="scope">
|
|
|
|
|
<slot :name="column.slotName" :row="scope.row"></slot>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table>
|
|
|
|
|
|
|
|
|
|
<div v-if="showPagination" class="pagination-container">
|
|
|
|
|
<span class="custom-pagination-text">
|
2025-06-03 17:35:22 +08:00
|
|
|
|
第{{ currentPage }}页,共{{
|
|
|
|
|
Math.ceil(total / computedPageSize)
|
|
|
|
|
}}页,共{{ total }}条
|
2025-05-30 17:31:05 +08:00
|
|
|
|
</span>
|
|
|
|
|
<div
|
2025-06-03 17:35:22 +08:00
|
|
|
|
style="
|
|
|
|
|
flex: 1;
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: end;
|
|
|
|
|
text-align: right;
|
|
|
|
|
padding-right: 120px;
|
|
|
|
|
"
|
2025-05-30 17:31:05 +08:00
|
|
|
|
>
|
|
|
|
|
<el-pagination
|
2025-06-03 17:35:22 +08:00
|
|
|
|
v-model:current-page="computedCurrentPage"
|
|
|
|
|
v-model:page-size="computedPageSize"
|
2025-05-30 17:31:05 +08:00
|
|
|
|
:page-sizes="pageSizes"
|
|
|
|
|
:small="small"
|
|
|
|
|
:disabled="disabled"
|
|
|
|
|
:background="background"
|
2025-06-03 17:35:22 +08:00
|
|
|
|
layout="prev, pager, next, jumper"
|
|
|
|
|
:total="total"
|
2025-05-30 17:31:05 +08:00
|
|
|
|
@current-change="handleCurrentChange"
|
|
|
|
|
/>
|
|
|
|
|
<span class="custom-pagination-size">
|
2025-06-03 17:35:22 +08:00
|
|
|
|
{{ computedPageSize }}条/页
|
2025-05-30 17:31:05 +08:00
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import { ref, watch, computed, onMounted, onBeforeUnmount } from "vue";
|
|
|
|
|
|
|
|
|
|
const props = defineProps({
|
2025-06-03 17:35:22 +08:00
|
|
|
|
loading: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: false,
|
|
|
|
|
},
|
2025-05-30 17:31:05 +08:00
|
|
|
|
// 表格数据
|
|
|
|
|
tableData: {
|
|
|
|
|
type: Array,
|
|
|
|
|
default: () => [],
|
|
|
|
|
},
|
|
|
|
|
// 列配置
|
|
|
|
|
columns: {
|
|
|
|
|
type: Array,
|
|
|
|
|
default: () => [],
|
|
|
|
|
},
|
2025-06-03 17:35:22 +08:00
|
|
|
|
// 当前页码(由外部传入)
|
|
|
|
|
currentPage: {
|
|
|
|
|
type: Number,
|
|
|
|
|
default: 1,
|
|
|
|
|
},
|
|
|
|
|
// 每页数量(由外部传入)
|
|
|
|
|
pageSize: {
|
|
|
|
|
type: Number,
|
|
|
|
|
default: 10,
|
|
|
|
|
},
|
|
|
|
|
// 总条数
|
|
|
|
|
total: {
|
|
|
|
|
type: Number,
|
|
|
|
|
default: 0,
|
|
|
|
|
},
|
2025-05-30 17:31:05 +08:00
|
|
|
|
// 是否显示分页
|
|
|
|
|
showPagination: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: true,
|
|
|
|
|
},
|
|
|
|
|
// 是否显示边框
|
|
|
|
|
showBorder: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: false,
|
|
|
|
|
},
|
|
|
|
|
// 是否显示斑马纹
|
|
|
|
|
showStripe: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: false,
|
|
|
|
|
},
|
|
|
|
|
// 是否显示首列多选框
|
|
|
|
|
showSelection: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: false,
|
|
|
|
|
},
|
|
|
|
|
// 每页显示条数选项
|
|
|
|
|
pageSizes: {
|
|
|
|
|
type: Array,
|
|
|
|
|
default: () => [10, 20, 30, 50],
|
|
|
|
|
},
|
|
|
|
|
// 是否使用小型分页样式
|
|
|
|
|
small: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: false,
|
|
|
|
|
},
|
|
|
|
|
// 是否禁用
|
|
|
|
|
disabled: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: false,
|
|
|
|
|
},
|
|
|
|
|
// 是否为分页按钮添加背景色
|
|
|
|
|
background: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: false,
|
|
|
|
|
},
|
|
|
|
|
// 自定义表头类名函数
|
|
|
|
|
headerCellClassName: {
|
|
|
|
|
type: Function,
|
|
|
|
|
default: () => "",
|
|
|
|
|
},
|
|
|
|
|
// 自定义单元格类名函数
|
|
|
|
|
cellClassName: {
|
|
|
|
|
type: Function,
|
|
|
|
|
default: () => "",
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2025-06-03 17:35:22 +08:00
|
|
|
|
const emit = defineEmits([
|
|
|
|
|
"update:currentPage",
|
|
|
|
|
"update:pageSize",
|
|
|
|
|
"page-change",
|
|
|
|
|
"selection-change",
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// 每页数量(双向绑定)
|
|
|
|
|
const computedPageSize = computed({
|
|
|
|
|
get: () => props.pageSize,
|
|
|
|
|
set: (val) => emit('update:pageSize', val)
|
|
|
|
|
});
|
|
|
|
|
// 当前页码(双向绑定)
|
|
|
|
|
const computedCurrentPage = computed({
|
|
|
|
|
get: () => props.currentPage,
|
|
|
|
|
set: (val) => emit('update:currentPage', val)
|
2025-05-30 17:31:05 +08:00
|
|
|
|
});
|
|
|
|
|
|
2025-06-03 17:35:22 +08:00
|
|
|
|
// 监听外部传入的pageSize变化
|
|
|
|
|
// watch(
|
|
|
|
|
// () => props.pageSize,
|
|
|
|
|
// (newVal) => {
|
|
|
|
|
// internalPageSize.value = newVal;
|
|
|
|
|
// }
|
|
|
|
|
// );
|
2025-05-30 17:31:05 +08:00
|
|
|
|
|
|
|
|
|
// 分页大小改变
|
|
|
|
|
const pageSizeChange = (val) => {
|
|
|
|
|
console.log(`每页 ${val} 条`);
|
|
|
|
|
internalPageSize.value = val;
|
2025-06-03 17:35:22 +08:00
|
|
|
|
console.log(props.currentPage, internalPageSize.value);
|
|
|
|
|
emit("update:pageSize", val);
|
|
|
|
|
emit("page-change", {
|
|
|
|
|
page: props.currentPage,
|
|
|
|
|
pageSize: val,
|
|
|
|
|
});
|
2025-05-30 17:31:05 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 当前页改变
|
|
|
|
|
const handleCurrentChange = (val) => {
|
|
|
|
|
console.log(`当前页改变 ${val} 页`);
|
|
|
|
|
emit("page-change", {
|
2025-06-03 17:35:22 +08:00
|
|
|
|
page: val,
|
2025-05-30 17:31:05 +08:00
|
|
|
|
pageSize: internalPageSize.value,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 多选框变化
|
|
|
|
|
const handleSelectionChange = (selection) => {
|
2025-06-03 17:35:22 +08:00
|
|
|
|
const selectedIds = selection.map((row) => row.id);
|
|
|
|
|
emit("selection-change", selection, selectedIds);
|
2025-05-30 17:31:05 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const tableRef = ref(null);
|
|
|
|
|
const tableMaxHeight = ref(null); // 使用max-height而不是height
|
|
|
|
|
|
|
|
|
|
// 自动计算最大高度(预留分页器空间)
|
|
|
|
|
const calculateMaxHeight = () => {
|
|
|
|
|
const paginationHeight = 60; // 分页器固定高度
|
|
|
|
|
const container = tableRef.value?.$el?.parentElement;
|
|
|
|
|
if (container) {
|
|
|
|
|
const containerHeight = container.clientHeight;
|
|
|
|
|
tableMaxHeight.value = containerHeight - paginationHeight;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
calculateMaxHeight();
|
|
|
|
|
window.addEventListener("resize", calculateMaxHeight);
|
|
|
|
|
|
|
|
|
|
// 添加MutationObserver监听父容器尺寸变化
|
|
|
|
|
const observer = new MutationObserver(calculateMaxHeight);
|
|
|
|
|
if (tableRef.value?.$el?.parentElement) {
|
|
|
|
|
observer.observe(tableRef.value.$el.parentElement, {
|
|
|
|
|
attributes: true,
|
|
|
|
|
attributeFilter: ["style", "class"],
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
|
window.removeEventListener("resize", calculateMaxHeight);
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
.custom-table-container {
|
|
|
|
|
// position: relative;
|
|
|
|
|
padding: 10px 0;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
height: 100%; /* 关键:继承父容器高度 */
|
|
|
|
|
overflow: hidden; /* 防止内容溢出 */
|
|
|
|
|
|
2025-06-03 17:35:22 +08:00
|
|
|
|
/* 表格弹性布局 */
|
|
|
|
|
:deep(.el-table) {
|
2025-05-30 17:31:05 +08:00
|
|
|
|
flex: 1;
|
2025-06-03 17:35:22 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
|
|
|
|
/* 表头固定 */
|
|
|
|
|
.el-table__header-wrapper {
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 表体可滚动 */
|
|
|
|
|
.el-table__body-wrapper {
|
|
|
|
|
flex: 1;
|
|
|
|
|
overflow: auto;
|
|
|
|
|
}
|
2025-05-30 17:31:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-06-03 17:35:22 +08:00
|
|
|
|
.demo-form-inline {
|
|
|
|
|
text-align: left;
|
|
|
|
|
padding-left: 20px;
|
|
|
|
|
padding-top: 4px;
|
|
|
|
|
}
|
2025-05-30 17:31:05 +08:00
|
|
|
|
|
2025-06-03 17:35:22 +08:00
|
|
|
|
.pagination-container {
|
|
|
|
|
margin-top: 10px;
|
|
|
|
|
padding: 0 30px 0 20px;
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
color: #999;
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
}
|
|
|
|
|
.custom-pagination-text {
|
|
|
|
|
flex: 1;
|
|
|
|
|
text-align: left;
|
|
|
|
|
line-height: 32px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
2025-05-30 17:31:05 +08:00
|
|
|
|
|
2025-06-03 17:35:22 +08:00
|
|
|
|
.custom-pagination-size {
|
|
|
|
|
text-align: right;
|
|
|
|
|
line-height: 32px;
|
|
|
|
|
margin-left: 20px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
2025-05-30 17:31:05 +08:00
|
|
|
|
|
2025-06-03 17:35:22 +08:00
|
|
|
|
/* 去除表格边框 */
|
|
|
|
|
:deep(.el-table) {
|
|
|
|
|
--el-table-border-color: transparent;
|
|
|
|
|
}
|
2025-05-30 17:31:05 +08:00
|
|
|
|
|
2025-06-03 17:35:22 +08:00
|
|
|
|
/* 自定义鼠标悬停颜色 */
|
|
|
|
|
:deep(.el-table__body tr:hover > td) {
|
|
|
|
|
background-color: rgba(37, 191, 130, 0.1) !important;
|
|
|
|
|
}
|
2025-05-30 17:31:05 +08:00
|
|
|
|
|
2025-06-03 17:35:22 +08:00
|
|
|
|
/* 自定义表头样式 */
|
|
|
|
|
:deep(.custom-header) {
|
|
|
|
|
background-color: #fff !important;
|
|
|
|
|
color: #999;
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
}
|
2025-05-30 17:31:05 +08:00
|
|
|
|
|
2025-06-03 17:35:22 +08:00
|
|
|
|
:deep(.el-pagination) {
|
|
|
|
|
/* 整体分页样式 */
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
font-weight: normal;
|
|
|
|
|
|
|
|
|
|
/* 页码按钮容器 */
|
|
|
|
|
.el-pager {
|
|
|
|
|
/* 所有页码项 */
|
|
|
|
|
li {
|
|
|
|
|
font-weight: 400; /* 普通页码字体不加粗 */
|
|
|
|
|
color: #606266; /* 普通页码颜色 */
|
|
|
|
|
background: transparent;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
|
|
|
|
/* 当前选中页码 */
|
|
|
|
|
&.active,
|
|
|
|
|
&.is-active {
|
|
|
|
|
font-weight: 400 !important; /* 当前页不加粗 */
|
|
|
|
|
color: #25bf82 !important; /* 自定义当前页颜色 - 橙色示例 */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 悬停状态 */
|
|
|
|
|
&:hover {
|
|
|
|
|
color: #409eff;
|
|
|
|
|
}
|
2025-05-30 17:31:05 +08:00
|
|
|
|
}
|
2025-06-03 17:35:22 +08:00
|
|
|
|
}
|
2025-05-30 17:31:05 +08:00
|
|
|
|
|
2025-06-03 17:35:22 +08:00
|
|
|
|
/* 上一页/下一页按钮 */
|
|
|
|
|
.btn-prev,
|
|
|
|
|
.btn-next {
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
|
|
|
|
|
&:disabled {
|
|
|
|
|
color: #c0c4cc;
|
2025-05-30 17:31:05 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-03 17:35:22 +08:00
|
|
|
|
/* 每页条数选择器 */
|
|
|
|
|
.el-pagination__sizes {
|
|
|
|
|
.el-input__inner {
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
}
|
2025-05-30 17:31:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-06-03 17:35:22 +08:00
|
|
|
|
/* 跳页输入框 */
|
|
|
|
|
.el-pagination__jump {
|
2025-05-30 17:31:05 +08:00
|
|
|
|
font-weight: 400;
|
2025-06-03 17:35:22 +08:00
|
|
|
|
position: absolute;
|
|
|
|
|
right: 16px;
|
|
|
|
|
bottom: 12px;
|
|
|
|
|
color: #999;
|
2025-05-30 17:31:05 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|