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: 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;
  /* 如果内容超出,显示滚动条 */
}
相关推荐
Highcharts.js2 小时前
React 中原生支持使用 JSX 组件配置自定义表格 Highcharts Grid
前端·javascript·react.js·前端框架·jsx·highcharts·自定义表格
Java后端的Ai之路2 小时前
【AIAgent开发】-Agent工具封装成CLI(纯干货!!!)
java·javascript·人工智能·agent·cli
薛定谔的猫-菜鸟程序员2 小时前
Electron开发超详细教程(从入门到放弃)
前端·javascript·electron
最强小杰2 小时前
DeepSeek V4 Flash + Codex CLI 接入教程:原生 Responses API 适配,从配置到跑通收藏这篇就够了(2026)
java·前端·python·ai
静水leo3 小时前
使用 Whistle 拦截接口并模拟后端响应
前端
掘金者阿豪3 小时前
SQL Server数据迁移完成后,凌晨的数据库告警该怎么查?
前端·后端
JudithHuang3 小时前
React 嵌套路由与布局
前端·react.js·arcgis·react·react router
htzyl2063 小时前
前端阶梯——第四十三章、实战二:待办清单To-Do
前端·javascript·html
程序员大阳3 小时前
HTML+CSS+JS对话式实战(1)-开篇:认识我们要做的社团活动发布平台
javascript·css·html·实战·系统·平台·社团活动发布