vue3+ts+elementui-表格根据相同值合并
代码
<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: stringroomName: stringdeviceName: stringcategoryName: string
}
interface SpanMethodProps {row: Usercolumn: TableColumnCtx<User>rowIndex: numbercolumnIndex: 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: numbercolspan: 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 = 1let categoryNameCount = 1let prevRoomName = data[0]?.roomNamelet 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]// 处理 roomNameif (current.roomName === prev.roomName) {roomNameCount++// 当前行不显示,rowspan 设为 0roomNameSpanMap.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 }) // 当前行可能是新的开始}// 处理 categoryNameif (current.categoryName === prev.categoryName) {categoryNameCount++categoryNameSpanMap.set(i, { rowspan: 0, colspan: 0 })} else {categoryNameSpanMap.set(i - 1, { rowspan: categoryNameCount, colspan: 1 })categoryNameCount = 1categoryNameSpanMap.set(i, { rowspan: 1, colspan: 1 })}prevRoomName = current.roomNameprevCategoryName = current.categoryName}// 处理最后一行的 rowspanroomNameSpanMap.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;/* 如果内容超出,显示滚动条 */
}