vue3 element-plus 实现 table表格合并单元格 和 多级表头

多级表头

数据结构比较复杂的时候,可使用多级表头来展现数据的层次关系。
只需要将el-table-column 放置于el-table-column 中,你可以实现组头。

一般可以直接用官网提供的写法,但是有可能数据会比较多的时候,就需要我们稍微改造一下,方便以后再出现合并的数据,直接可以公用。

合并行或列

多行或多列共用一个数据时,可以合并行或列。
通过给 table 传入span-method方法可以实现合并行或列, 方法的参数是一个对象,里面包含当前行 row、当前列 column、当前行号 rowIndex、当前列号 columnIndex 四个属性。 该函数可以返回一个包含两个元素的数组,第一个元素代表 rowspan,第二个元素代表 colspan。 也可以返回一个键名为 rowspan 和 colspan 的对象。

效果图如下

代码

表格的数据布局 index.vue

<!-- 架型匹配与拆分记录 -->
<template>
  <el-table
    :data="tableData"
    style="width: 100%"
    height="100%"
    class="record-supply"
    :row-key="getRowKeys"
    :header-cell-style="{
      background: '#fafafa',
      fontSize: '14px',
      height: '40px',
      fontWeight: 'normal',
      boxSizing: 'border-box',
      color: '#707070'
    }"
    ref="myTable"
    :span-method="spanMethod"
    @cell-mouse-enter="handleCellMouseEnter"
    @cell-mouse-leave="handleCellMouseLeave"
  >
    <el-table-column
      v-for="item in columns"
      :key="item.id"
      :label="item.label"
      :align="item.align"
      :width="item.width"
      :label-class-name="item?.labelClass"
      :class-name="item?.className"
    >
      <!-- 如果有子列,递归渲染子列 -->
      <template v-if="item.children && item.children.length > 0">
        <template v-for="child in item.children" :key="child.id">
          <el-table-column
            v-if="child?.formatValue"
            :label="child.label"
            :prop="child.prop"
            :width="child.width"
            :align="child.align"
            :label-class-name="child?.labelClass"
            :class-name="child?.className"
            :formatter="child?.formatValue"
          />

          <el-table-column
            v-else
            fixed="right"
            :label="child.label"
            :prop="child.prop"
            :width="child.width"
            :align="child.align"
            :label-class-name="child?.labelClass"
            :class-name="child?.className"
          >
            <template #default="scope">
              <div v-if="child.prop === 'projectName'">
                <div class="link">{{scope.row?.projectName}}</div>
                <div class="grey">{{scope.row?.projectNo ?? scope.row?.projectCode}}</div>
              </div>
              <div v-if="child.prop === 'matchTime'">
                <div style="word-break: break-word; white-space: normal">
                  {{ scope.row.matchTime ?? '--' }}
                </div>
              </div>
              <!-- 录入架型数据 支架型号/类型 -->
              <div v-if="child.prop === 'supplyModel'">
                <div class="link">
                  {{ scope.row.supplyModel ?? '--' }}
                </div>
                <div class="grey" v-if="scope.row.supplyFirstType || scope.row.supplySecondType">
                  {{ GET_SUPPORT_TYPE(scope.row.supplyFirstType) }} > {{ GET_SUPPORT_SECOND_TYPE_NAME(scope.row.supplySecondType) }}
                </div>
              </div>
              <!-- ERP匹配数据 支架型号/类型 --> 
              <div v-if="child.prop === 'model'">
                <div class="link">
                  {{ scope.row.model }}
                </div>
                <!-- ERP 匹配数据 -->
                <div class="grey">
                  {{ GET_SUPPORT_SECOND_TYPE_NAME(scope.row.secondDeviceType) }} 
                </div>
              </div>
            </template>
          </el-table-column>
        </template>
      </template>
    </el-table-column>
  </el-table>
</template>

<script setup lang="ts">
// import dayjs from 'dayjs'
import {
  GET_SUPPORT_TYPE,
  GET_SUPPORT_SECOND_TYPE_NAME
} from '@/views/project/device/components/config'
const props = defineProps({
  columns: { type: Array<any>, default: [] },
  tableData: { type: Array<any>, default: [] }
})
const { columns, tableData } = toRefs(props)
const getRowKeys = (row: any) => {
  return row.id
}
const megre = reactive([
  'projectNo', // 项目编码
  'expectedDeliveryTime', // 排产月
  'contractNo', // 合同号
  'matchTime', // 匹配日期
  'supplyModel', // 支架型号/类型
  'supplySingleWeight', // 单重
  'supplyDeviceCount', // 数量
  'supplyTotalWeight' // 总吨位
])
const spanArr = computed(() => {
  const spanArr: any = {}
  megre.forEach((m) => {
    spanArr[m] = { spanArr: [], pos: 0 }
  })
  tableData.value.forEach((row: any, i: any) => {
    megre.forEach((m: any) => {
      if (i == 0) {
        spanArr[m].spanArr.push(1)
        spanArr[m].pos = 0
      } else {
        // 批次号相同且项目编码相同的
        let flag = row.batchNo === tableData.value[i - 1].batchNo && row.projectNo == tableData.value[i - 1].projectNo
        //根据项目编码 合并单元格 一样则合并  为空||不同的情况不合并
        if (flag && tableData.value[i - 1].projectNo) {
          if (row[m] === tableData.value[i - 1][m]) {
            // 相等的合并+1
            spanArr[m].spanArr[spanArr[m].pos] += 1
            spanArr[m].spanArr.push(0)
          } else {
            // 不相等push 1,并且可修改下标指向
            spanArr[m].spanArr.push(1)
            spanArr[m].pos = i
          }
        } else {
          spanArr[m].spanArr.push(1)
          spanArr[m].pos = i
        }
      }
    })
  })
  return spanArr
})

const spanMethod = ({ row, column, rowIndex, columnIndex }: any) => {
  // 合并单元格 indexOf
  const spanArr1: any = spanArr.value
  switch (columnIndex) {
    case 0:
      return {
        rowspan: spanArr1.projectNo.spanArr[rowIndex],
        colspan: spanArr1.projectNo.spanArr[rowIndex] == 0 ? 0 : 1
      }
    case 1:
      return {
        rowspan: spanArr1.projectNo.spanArr[rowIndex],
        colspan: spanArr1.projectNo.spanArr[rowIndex] == 0 ? 0 : 1
      }
    case 2:
      return {
        rowspan: spanArr1.contractNo.spanArr[rowIndex],
        colspan: spanArr1.contractNo.spanArr[rowIndex] == 0 ? 0 : 1
      }
    case 3:
      return {
        rowspan: spanArr1.matchTime.spanArr[rowIndex],
        colspan: spanArr1.matchTime.spanArr[rowIndex] == 0 ? 0 : 1
      }
    case 4:
      return {
        rowspan: spanArr1.supplyModel.spanArr[rowIndex],
        colspan: spanArr1.supplyModel.spanArr[rowIndex] == 0 ? 0 : 1
      }
    case 5:
      return {
        rowspan: spanArr1.supplySingleWeight.spanArr[rowIndex],
        colspan: spanArr1.supplySingleWeight.spanArr[rowIndex] == 0 ? 0 : 1
      }
    case 6:
      return {
        rowspan: spanArr1.supplyDeviceCount.spanArr[rowIndex],
        colspan: spanArr1.supplyDeviceCount.spanArr[rowIndex] == 0 ? 0 : 1
      }
    case 7:
      return {
        rowspan: spanArr1.supplyTotalWeight.spanArr[rowIndex],
        colspan: spanArr1.supplyTotalWeight.spanArr[rowIndex] == 0 ? 0 : 1
      }

    default:
      return {
        rowspan: 1,
        colspan: 1
      }
  }
}

let currentIndex = ref('')
let currentColumnIndex = ref('')
const handleCellMouseEnter = (row: any, column: any) => {
  //鼠标移入后赋值
  currentIndex.value = row.projectNo //row.productCode是行相同的标志
  currentColumnIndex.value = column.property //获取列的标题名
}
const handleCellMouseLeave = () => {
  //鼠标移走后置空
  currentIndex.value = ''
  currentColumnIndex.value = ''
}

// // 行的颜色设置 :row-class-name="tableRowClassName" :cell-style="cellStyle"
// const tableRowClassName = ({ row }: any) => {
//   let flag = row.projectNo == currentIndex.value && megre.includes(currentColumnIndex.value)
//   return flag ? 'quotatemplate-my-hover-row' : ''
// }

// // 鼠标移入除合并单元格的其它单元格,这时候需要单独一行展示。
// const cellStyle = ({ row, column, rowIndex, columnIndex }: any) => {
//   let flag =
//     row.projectNo == currentIndex.value &&
//     currentColumnIndex.value &&
//     megre.includes(currentColumnIndex.value)
//   return flag ? { background: '#f4f6fa' } : { '': '' }
// }
</script>

<style scoped lang="scss">
.header-nav {
  display: flex;
  align-items: center;
  height: 40px;
  border: 1px solid #ebeef5;
  background: #fafafa;
  border-bottom: none;
  border-right: none;
  & > div {
    flex: 1;
    border-right: 1px solid #ebeef5;
    text-align: center;
  }
}
:deep(.record-supply) {
  .el-table__border-left-patch {
    width: 0;
  }
  .column-border {
    border-left: 1px solid #ebeef5;
  }
  .border-none {
    border-right: none;
  }
  .border-right {
    border-right: 1px solid #ebeef5;
  }
  .column-height {
    height: 70px;
  }
  .el-table__cell {
    .cell {
      padding-left: 8px;
    }
  }
}
.edit-style {
  color: #3076fe;
  & > div {
    cursor: pointer;
  }
}
:deep(.el-table--border::before),
:deep(.el-table--border::after) {
  width: 0px;
}
.link {
  // cursor: pointer;
  color: #262626;
  overflow: hidden;
  text-overflow: ellipsis;
}
.grey {
  color: #a3a3a3;
}
</style>
<style>
/* 移除划过时表格的背景色 || 自行设置划过的背景色 || 不写下方样式将默认颜色 */
.record-supply.el-table .quotatemplate-my-hover-row {
  background: #f4f6fa !important;
}
</style>

数据的映射config.ts 页面

import dayjs from 'dayjs'
// 架型匹配与拆分记录
export const splitRecordColumns = (): any[] => [
  {
    id: 1,
    label: '录入架型数据',
    width: '',
    labelClass: 'column-border',
    className: '',
    align: 'center',
    children: [
      {
        id: 11,
        label: '项目名称/编码',
        prop: 'projectName',
        width: '172',
        align: ''
      },
      {
        id: 12,
        label: '排产月',
        prop: 'expectedDeliveryTime',
        width: '80',
        align: 'center',
        formatValue: (row: any) =>
          row.expectedDeliveryTime ? dayjs(row.expectedDeliveryTime).format('YYYY-MM') : '--'
      },
      {
        id: 13,
        label: '合同号',
        prop: 'contractNo',
        width: '160',
        labelClass: 'column-border',

        align: 'center',
        formatValue: (row: any) => row.contractNo || '--'
      },
      {
        id: 14,
        label: '匹配时间',
        prop: 'matchTime',
        width: '100',
        align: ''
      },
      {
        id: 15,
        label: '支架型号/类型',
        prop: 'supplyModel',
        width: '170',
        align: ''
      },
      {
        id: 16,
        label: '单重',
        prop: 'supplySingleWeight',
        width: '70',
        align: 'center',
        formatValue: (row: any) => (row.supplySingleWeight ? row.supplySingleWeight + '吨' : '--')
      },
      {
        id: 17,
        label: '数量',
        prop: 'supplyDeviceCount',
        width: '70',
        align: 'center',
        formatValue: (row: any) => (row.supplyDeviceCount ? row.supplyDeviceCount + '架' : '--')
      },
      {
        id: 18,
        label: '总吨位',
        prop: 'supplyTotalWeight',
        width: '90',
        align: 'center',
        formatValue: (row: any) => (row.supplyTotalWeight ? row.supplyTotalWeight + '吨' : '--')
      }
    ]
  },
  {
    id: 2,
    label: 'ERP匹配数据',
    width: '',
    labelClass: '',
    className: '',
    align: 'center',
    children: [
      {
        id: 21,
        label: '排产月',
        prop: 'planProduceMonth',
        width: '80',
        align: '',
        formatValue: (row: any) =>
          row.planProduceMonth ? dayjs(row.planProduceMonth).format('YYYY-MM') : '--'
      },
      {
        id: 22,
        label: '施工号',
        prop: 'constructionNo',
        width: '110',
        align: '',
        formatValue: (row: any) => row.constructionNo || '--'
      },
      {
        id: 23,
        label: '支架型号/类型',
        prop: 'model',
        width: '170',
        align: ''
      },
      {
        id: 24,
        label: '单重',
        prop: 'singleWeight',
        width: '70',
        align: '',
        formatValue: (row: any) => (row.singleWeight ? row.singleWeight + '吨' : '--')
      },
      {
        id: 25,
        label: '数量',
        prop: 'deviceCount',
        width: '70',
        align: '',
        formatValue: (row: any) => (row.deviceCount ? row.deviceCount + '架' : '--')
      },
      {
        id: 26,
        label: '总吨位',
        prop: 'totalWeight',
        width: '90',
        align: '',
        formatValue: (row: any) => (row.totalWeight ? row.totalWeight + '吨' : '--')
      },
      {
        id: 27,
        label: '交审日期',
        prop: 'transferDate',
        width: '90',
        align: '',
        formatValue: (row: any) =>
          row.transferDate ? dayjs(row.transferDate).format('MM月DD日') : '--'
      },
      {
        id: 28,
        label: '备注',
        prop: 'remark',
        labelClass: 'border-right',
        className: 'border-none column-height',
        width: '',
        align: '',
        formatValue: (row: any) => row.remark || '--'
      }
    ]
  }
]

注意的是,可能需求不同,但是方法是一样的。只需要把方法中的值替换即可实现公用。

如下图:

注意的项是 要和并的字段不一样,我们需要单独去设置;spanMethod 方法会根据 设定的 merge 值去合并;

spanArr 方法执行计算属性,添加我们的判断条件。需要满足需求条件才会更新数据。

这样就结束了。

展示最后的效果

满足条件的进行合并,不满足的则是单独一行展示

相关推荐
活宝小娜44 分钟前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点1 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow1 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
我开心就好o1 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app
刚刚好ā2 小时前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
yqcoder3 小时前
reactflow 中 useNodesState 模块作用
开发语言·前端·javascript
会发光的猪。4 小时前
css使用弹性盒,让每个子元素平均等分父元素的4/1大小
前端·javascript·vue.js
天下代码客4 小时前
【vue】vue中.sync修饰符如何使用--详细代码对比
前端·javascript·vue.js
周全全5 小时前
Spring Boot + Vue 基于 RSA 的用户身份认证加密机制实现
java·vue.js·spring boot·安全·php
Domain-zhuo5 小时前
什么是JavaScript原型链?
开发语言·前端·javascript·jvm·ecmascript·原型模式