代码
<div style="height: auto; overflow: auto">
<el-table ref="dataTableRef" v-loading="loading" :data="pageData" highlight-current-row border
@selection-change="handleSelectionChange" :span-method="objectSpanMethod" :row-style="tableRowStyle">
<el-table-column type="selection" width="55" align="center" />
<!-- <el-table-column key="id" label="id" prop="id" align="center" /> -->
<el-table-column key="roomName" label="房间名称" prop="roomName" align="center" />
<el-table-column key="categoryName" label="所属分类" prop="categoryName" align="center" />
<el-table-column key="deviceName" label="设备名称" prop="deviceName" align="center" />
</el-table>
<pagination v-if="total > 0" v-model:total="total" v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize" @pagination="handleQuery()" style="float: right;" />
</div>
js
interface User {
id: string
roomName: string
deviceName: string
categoryName: string
}
interface SpanMethodProps {
row: User
column: TableColumnCtx<User>
rowIndex: number
columnIndex: number
}
const objectSpanMethod = ({ row, column, rowIndex }) => {
if (column.property === 'roomName') {
const currentRoom = row.roomName;
let prevRoom = rowIndex === 0 ? null : pageData.value[rowIndex - 1].roomName;
if (rowIndex === 0 || currentRoom !== prevRoom) {
let count = 1;
for (let i = rowIndex + 1; i < pageData.value.length; i++) {
if (pageData.value[i].roomName === currentRoom) {
count++;
} else {
break;
}
}
return { rowspan: count, colspan: 1 };
} else {
return { rowspan: 0, colspan: 0 };
}
}
if (column.property === 'categoryName') {
const currentCategory = row.categoryName;
let prevCategory = rowIndex === 0 ? null : pageData.value[rowIndex - 1].categoryName;
if (rowIndex === 0 || currentCategory !== prevCategory) {
let count = 1;
for (let i = rowIndex + 1; i < pageData.value.length; i++) {
if (pageData.value[i].categoryName === currentCategory) {
count++;
} else {
break;
}
}
return { rowspan: count, colspan: 1 };
} else {
return { rowspan: 0, colspan: 0 };
}
}
return { rowspan: 1, colspan: 1 };
}
// 定义合并信息的类型
type SpanInfo = {
rowspan: number
colspan: number
}
// 用于存储 roomName 和 categoryName 的合并信息
const roomNameSpanMap: Map<number, SpanInfo> = new Map() // key 是 rowIndex
const categoryNameSpanMap: Map<number, SpanInfo> = new Map()
// 计算合并信息的方法
const calculateSpans = (data: User[]) => {
let roomNameCount = 1
let categoryNameCount = 1
let prevRoomName = data[0]?.roomName
let prevCategoryName = data[0]?.categoryName
// 第一行默认 rowspan 为后续相同项的数量
roomNameSpanMap.set(0, { rowspan: 1, colspan: 1 })
categoryNameSpanMap.set(0, { rowspan: 1, colspan: 1 })
for (let i = 1; i < data.length; i++) {
const current = data[i]
const prev = data[i - 1]
// 处理 roomName
if (current.roomName === prev.roomName) {
roomNameCount++
// 当前行不显示,rowspan 设为 0
roomNameSpanMap.set(i, { rowspan: 0, colspan: 0 })
} else {
// 新的 roomName,设置 rowspan 为累计的数量
roomNameSpanMap.set(i - 1, { rowspan: roomNameCount, colspan: 1 })
roomNameCount = 1 // 重置计数
roomNameSpanMap.set(i, { rowspan: 1, colspan: 1 }) // 当前行可能是新的开始
}
// 处理 categoryName
if (current.categoryName === prev.categoryName) {
categoryNameCount++
categoryNameSpanMap.set(i, { rowspan: 0, colspan: 0 })
} else {
categoryNameSpanMap.set(i - 1, { rowspan: categoryNameCount, colspan: 1 })
categoryNameCount = 1
categoryNameSpanMap.set(i, { rowspan: 1, colspan: 1 })
}
prevRoomName = current.roomName
prevCategoryName = current.categoryName
}
// 处理最后一行的 rowspan
roomNameSpanMap.set(data.length - 1, { rowspan: roomNameCount, colspan: 1 })
categoryNameSpanMap.set(data.length - 1, { rowspan: categoryNameCount, colspan: 1 })
}
const tableRowStyle = ({ row, rowIndex }) => {
return {
height: '40px',
'vertical-align': 'middle'
}
}
style
// 表格
/* 确保表格行高一致,避免合并后行高错乱 */
:deep(.el-table .el-table__row) {
/* 不要设置height/min-height/line-height,自动撑开 */
vertical-align: middle !important;
}
:deep(.el-table__cell) {
vertical-align: middle !important;
}
/* 确保表格容器有足够的空间 */
:deep(.table-container) {
height: auto;
/* 自动高度 */
overflow: auto;
/* 如果内容超出,显示滚动条 */
}