针对表格进行封装,在列表页面直接传入字段数组就可以展示数据表:
javascript
<template>
<div class="table-container" :class="{ 'show-vertical-lines': showVerticalLines }">
<!-- 数据表格 -->
<el-table
ref="tableRef"
:data="tableData"
border
size="mini"
style="width: 100%"
:max-height="tableHeight"
row-key="id"
:row-class-name="rowClassName"
highlight-current-row
>
<!-- 单选框列 -->
<el-table-column v-if="showRadio" width="55" align="center">
<template slot-scope="scope">
<el-radio v-model="radioSelection" :label="scope.row.id" @change="handleRadioChange(scope.row)">
</el-radio>
</template>
</el-table-column>
<!-- 多选框列 -->
<el-table-column
v-if="showSelection"
type="selection"
width="55"
align="center"
></el-table-column>
<!-- 序号列 -->
<el-table-column
v-if="showIndex"
type="index"
label="序号"
width="60"
align="center"
>
<template slot-scope="scope">
{{ scope.$index + 1 }}
</template>
</el-table-column>
<!-- 动态列 -->
<el-table-column
v-for="(column, index) in columns"
:key="column.prop || index"
:prop="column.prop"
:label="column.label"
:width="column.width"
:min-width="column.minWidth || 80"
:header-align="column.headerAlign || 'left'"
:align="column.align || 'left'"
:fixed="column.fixed"
>
<template slot-scope="scope">
<!-- 作用域插槽:允许父组件自定义内容 -->
<slot :name="column.prop" :row="scope.row" :editable="isRowEditable(scope.row)">
<!-- 默认渲染逻辑 -->
<template v-if="isRowEditable(scope.row) && column.editable === true">
<el-input
v-model="scope.row[column.prop]"
size="small"
@blur="handleInputBlur(scope.row, column.prop)"
@click.native.stop
></el-input>
</template>
<template v-else>
<!-- 超长文本省略 -->
<el-tooltip
v-if="scope.row[column.prop] && scope.row[column.prop].toString().length > 100"
:content="scope.row[column.prop]"
placement="top"
effect="dark"
>
<div class="ellipsis-text">{{ scope.row[column.prop] }}</div>
</el-tooltip>
<span v-else>{{ scope.row[column.prop] }}</span>
</template>
</slot>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-fixed">
<el-pagination
@size-change="$emit('size-change', $event)"
@current-change="$emit('current-change', $event)"
:current-page="currentPage"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
background
style="text-align:right;"
></el-pagination>
</div>
</div>
</template>
<script>
export default {
name: "TableContainer",
props: {
tableData: { type: Array, default: () => [] },
columns: { type: Array, default: () => [] },
currentPage: { type: Number, default: 1 },
pageSize: { type: Number, default: 10 },
total: { type: Number, default: 0 },
showSelection: { type: Boolean, default: false },
showRadio: { type: Boolean, default: false },
showIndex: { type: Boolean, default: false },
showVerticalLines: { type: Boolean, default: false },
// 新增:允许父组件传入表格距离顶部的偏移量,默认 260px
offsetTop: { type: Number, default: 260 }
},
data() {
return {
radioSelection: null,
tempBackup: null, // 新增:用于存储当前正在编辑行的快照
tableHeight: 400
};
},
mounted() {
this.$nextTick(() => {
this.calculateTableHeight();
});
window.addEventListener('resize', this.calculateTableHeight);
},
beforeDestroy() {
window.removeEventListener('resize', this.calculateTableHeight);
},
methods: {
rowClassName({ row }) {
return this.radioSelection === row.id ? 'radio-selected' : '';
},
isRowEditable(row) {
return this.radioSelection === row.id;
},
// 核心逻辑:处理单选框切换
handleRadioChange(currentRow) {
// 1. 恢复上一行的数据 (如果存在上一行且备份存在)
if (this.tempBackup) {
// 找到表格数据中对应的那一行对象
const prevRowData = this.tableData.find(item => item.id === this.tempBackup.id);
if (prevRowData) {
// 用备份的数据覆盖当前表格中的数据(撤销修改)
Object.assign(prevRowData, JSON.parse(JSON.stringify(this.tempBackup)));
}
}
// 2. 为当前点击的这一行创建新的备份
// 注意:这里必须深拷贝,否则 tempBackup 会跟着表格数据一起变
this.tempBackup = JSON.parse(JSON.stringify(currentRow));
// 3. 触发外部事件
this.$emit('radio-change', currentRow);
},
handleInputBlur(row, prop) {
this.$emit('input-blur', row, prop);
},
calculateTableHeight() {
// 方式一:基于窗口高度减去固定偏移量(简单粗暴)
// this.tableHeight = window.innerHeight - this.offsetTop;
// 方式二:更精准的计算(推荐)
// 获取表格容器相对于视口的位置
const rect = this.$el.getBoundingClientRect();
// 视口高度 - 表格顶部距离 - 分页高度(约50) - 底部留白(20)
this.tableHeight = window.innerHeight - rect.top - 70;
// 设置最小高度防止过小
if (this.tableHeight < 200) this.tableHeight = 200;
}
},
emits: ['size-change', 'current-change', 'radio-change', 'input-blur']
};
</script>
<style scoped>
.table-container {
width: 100%;
position: relative;
background-color: #fff;
/* border-radius: 4px; */
/* box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08); */
overflow: hidden;
}
.el-table {
width: 100%;
margin: 0;
}
/* 核心调整:行高缩小至 24px,与文字大小一致 */
.el-table__row {
height: 24px !important;
}
.el-table__cell {
padding: 0 12px; /* 上下padding设为0,仅保留左右 */
line-height: 1.2;
vertical-align: middle; /* 确保垂直居中 */
}
/* 确保表头高度也同步缩小 */
::v-deep .el-table th {
height: 24px !important;
line-height: 24px !important;
background-color: #f0f2f5 !important;
font-weight: 600 !important;
user-select: text !important;
}
/* 消除单元格底部多余边框间距 */
::v-deep .el-table--border::after,
::v-deep .el-table--group::after,
::v-deep .el-table::before {
height: 0;
}
.pagination-fixed {
margin-top: 0;
display: flex;
justify-content: flex-end;
align-items: center;
padding: 6px 0;
background-color: #fff;
}
.banner-image {
max-width: 100px;
max-height: 50px;
}
.radio-selected {
background-color: #f0f9eb !important;
}
.ellipsis-text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
display: inline-block;
}
/* 隐藏表格竖线 */
.table-container:not(.show-vertical-lines) ::v-deep .el-table th,
.table-container:not(.show-vertical-lines) ::v-deep .el-table td {
border-right: none !important;
border-left: none !important;
}
.table-container:not(.show-vertical-lines) ::v-deep .el-table--border,
.table-container:not(.show-vertical-lines) ::v-deep .el-table--group {
border-right: none !important;
border-left: none !important;
}
</style>
传入字段数组格式:
javascript
data() {
return {
tableData: [],
columns: [
{ prop: 'title', label: '标题', minWidth: 80 },
{ prop: 'introduction', label: '内容简介', minWidth: 200 },
{ prop: 'publishTime', label: '发布时间', minWidth: 80 },
{ prop: 'status', label: '状态', minWidth: 50 },
{ prop: 'createTime', label: '创建时间', minWidth: 80 },
{ prop: 'createBy', label: '创建人', minWidth: 80 },
{ prop: 'action', label: '操作', minWidth: 80, headerAlign: 'center', align: 'center' }
],
currentPage: 1,
pageSize: 10,
total: 0,
};
},
调用方式:
javascript
<table-container
:table-data="tableData"
:columns="columns"
:current-page="currentPage"
:page-size="pageSize"
:total="total"
:base-url="baseUrl"
:show-selection="false"
:show-index="true"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<template #status="{ row }">
<span v-if="row.status === 1" style="color:#13ce66">已发布</span>
<span v-else style="color:#ff4949">未发布</span>
</template>
<template #createBy="{ row }">
<span v-if="row.createBy">
{{ getCreateUser(row) }}
</span>
</template>
<template #action="{ row }">
<el-button v-if="row.status === 0" type="text" @click="handleEdit(row)">编辑</el-button>
<span v-if="row.status === 0" style="margin:0 8px;color:#ddd">|</span>
<el-button v-if="row.status === 0" type="text" @click="handlePublish(row)">发布</el-button>
<span v-if="row.status === 0" style="margin:0 8px;color:#ddd">|</span>
<el-button v-if="row.status !== 0" type="text" @click="handlePreview(row)">预览</el-button>
<span v-if="row.status !== 0" style="margin:0 8px;color:#ddd">|</span>
<el-button type="text" @click="handleDelete(row.id)">删除</el-button>
</template>
</table-container>
</div>