vue 多维数组循环合并单元格

在之前的文章中介绍了php导出 excel 时进行单元格合并,
在这篇文章中,介绍一下 vue 数据循环时如何进行单元格合并,
这里主要介绍 二维三维 数组循环时数组的合并。

看一下合并后的效果: 冯奎博客

不说太多,直接上例子:

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>Vue-Table-Demo</title>
  <style type="text/css">
    * {
      margin: 0;
      padding: 0;
    }

    body {
      padding: 20px;
    }

    table {
      width: 100%;
      border-collapse: collapse;
      text-align: center;
      margin-bottom: 20px;
    }

    table th,
    td {
      border: 1px solid #999;
    }

    thead th {
      height: 40px;
      background-color: #f4f4f4;
    }
  </style>
  <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.min.js"></script>
</head>

<body>
  <div id="app" class="div-table" style="width: 1200px">
    <data-table1 :data="table1"></data-table1>
    <data-table2 :data="table2"></data-table2>
    <data-table3 :data="table3"></data-table3>
  </div>

  <script type="text/x-template" id="table1-template">
    <table>
      <thead>
        <tr>
          <th>派工单号</th>
          <th>派工日期</th>
          <th>本单费用小计</th>
          <th>路径</th>
          <th>公里数</th>
          <th>费用</th>
        </tr>
      </thead>
      <tbody>
        <template v-for="(item, index) in data" :key="index">
          <template v-for="(subItem, subIndex) in item.table2" :key="subIndex">
            <tr>
              <!-- 合并单元格:只在第一行显示 -->
              <template v-if="subIndex === 0">
                <td :rowspan="item.table2.length">{{ item.order }}</td>
                <td :rowspan="item.table2.length">{{ item.date }}</td>
                <td :rowspan="item.table2.length">{{ item.total }}</td>
              </template>
              <!-- 其余的单元格正常显示 -->
              <td>{{ subItem.path }}</td>
              <td>{{ subItem.mileage }}</td>
              <td>{{ subItem.fee }}</td>
            </tr>
          </template>
        </template>
      </tbody>

      <thead>
        <tr>
          <th>路径</th>
          <th>订单号</th>
          <th>日期</th>
          <th>总价</th>
          <th>里程</th>
          <th>费用</th>
        </tr>
      </thead>
      <tbody>
        <template v-for="(item, index) in data" :key="index">
          <template v-for="(subItem, subIndex) in item.table2" :key="subIndex">
            <tr>
              <!-- 路径始终显示在第一列 -->
              <td>{{ subItem.path }}</td>

              <!-- 其余列只在第一行合并显示 -->
              <template v-if="subIndex === 0">
                <td :rowspan="item.table2.length">{{ item.order }}</td>
                <td :rowspan="item.table2.length">{{ item.date }}</td>
                <td :rowspan="item.table2.length">{{ item.total }}</td>
              </template>

              <!-- 空列用于占位,防止重复显示 -->
              <template v-else>
                <td v-if="false"></td>
                <td v-if="false"></td>
                <td v-if="false"></td>
              </template>

              <!-- 显示里程和费用 -->
              <td>{{ subItem.mileage }}</td>
              <td>{{ subItem.fee }}</td>
            </tr>
          </template>
        </template>
      </tbody>
    </table>
  </script>

  <script type="text/x-template" id="table2-template">
    <table>
      <thead>
        <tr>
          <th>派工单号</th>
          <th>派工日期</th>
          <th>本单费用小计</th>
          <th>路径</th>
          <th>公里数</th>
          <th>费用</th>
          <th>费用名称</th>
          <th>金额</th>
          <th>备注</th>
        </tr>
      </thead>
      <tbody>
        <template v-for="item in data">
          <template v-for="(route, routeIdx) in item.table2">
            <tr v-for="(fee, feeIdx) in route.table3.length ? route.table3 : [{}]">
              <template v-if="feeIdx === 0">
                <td v-if="routeIdx === 0" :rowspan="totalFees(item)">{{ item.order }}</td>
                <td v-if="routeIdx === 0" :rowspan="totalFees(item)">{{ item.date }}</td>
                <td v-if="routeIdx === 0" :rowspan="totalFees(item)">{{ item.total }}</td>
                <td :rowspan="route.table3.length || 1">{{ route.path }}</td>
                <td :rowspan="route.table3.length || 1">{{ route.mileage }}</td>
                <td :rowspan="route.table3.length || 1">{{ route.fee }}</td>
              </template>
              <td>{{ fee.feeType || '' }}</td>
              <td>{{ fee.monye || '' }}</td>
              <td>{{ fee.remarks || '' }}</td>
            </tr>
          </template>
        </template>
      </tbody>
      <thead>
        <tr>
          <th>路径</th>
          <th>订单号</th>
          <th>日期</th>
          <th>总价</th>
          <th>里程</th>
          <th>费用</th>
          <th>费用类型</th>
          <th>金额</th>
          <th>备注</th>
        </tr>
      </thead>
      <tbody>
        <template v-for="(order, orderIndex) in data" :key="orderIndex">
          <template v-for="(route, routeIndex) in order.table2" :key="routeIndex">
            <template v-for="(fee, feeIndex) in (route.table3.length ? route.table3 : [null])" :key="feeIndex">
              <tr>
                <!-- 路径列:每组route下仅显示一次 -->
                <template v-if="feeIndex === 0">
                  <td :rowspan="route.table3.length || 1">{{ route.path }}</td>
                </template>

                <!-- 订单号、日期、总价:只在第一条route的第一条fee中显示 -->
                <template v-if="routeIndex === 0 && feeIndex === 0">
                  <td :rowspan="order.table2.reduce((sum, r) => sum + Math.max(r.table3.length || 1, 1), 0)">
                    {{ order.order }}
                  </td>
                  <td :rowspan="order.table2.reduce((sum, r) => sum + Math.max(r.table3.length || 1, 1), 0)">
                    {{ order.date }}
                  </td>
                  <td :rowspan="order.table2.reduce((sum, r) => sum + Math.max(r.table3.length || 1, 1), 0)">
                    {{ order.total }}
                  </td>
                </template>

                <!-- 路径下的里程和费用:每组route仅第一fee行展示 -->
                <template v-if="feeIndex === 0">
                  <td :rowspan="route.table3.length || 1">{{ route.mileage }}</td>
                  <td :rowspan="route.table3.length || 1">{{ route.fee }}</td>
                </template>

                <!-- 费用明细 -->
                <td>{{ fee?.feeType || '' }}</td>
                <td>{{ fee?.monye || '' }}</td>
                <td>{{ fee?.remarks || '' }}</td>
              </tr>
            </template>
          </template>
        </template>
      </tbody>
    </table>
  </script>

  <script type="text/x-template" id="table3-template">
    <table id="table3">
      <thead>
        <tr>
          <th>派工单号</th>
          <th>派工日期</th>
          <th>本单费用小计</th>
          <th>路径</th>
          <th>公里数</th>
          <th>费用</th>
          <th>费用名称</th>
          <th>金额</th>
          <th>备注</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="item in data">
          <td>{{ item.order }}</td>
          <td>{{ item.date }}</td>
          <td>{{ item.total }}</td>
          <td>{{ item.path }}</td>
          <td>{{ item.mileage }}</td>
          <td>{{ item.fee }}</td>
          <td>{{ item.feeType }}</td>
          <td>{{ item.monye }}</td>
          <td>{{ item.remarks }}</td>
        </tr>
      </tbody>
    </table>
  </script>

  <script>
    Vue.component('data-table1', {
      props: ['data'],
      template: '#table1-template'
    });

    Vue.component('data-table2', {
      props: ['data'],
      template: '#table2-template',
      methods: {
        totalFees(item) {
          return item.table2.reduce((sum, route) => sum + (route.table3.length || 1), 0);
        }
      }
    });

    Vue.component('data-table3', {
      props: ['data'],
      template: '#table3-template'
    });

    new Vue({
      el: '#app',
      data: {
        table1: [
          {
            order: "ASPG20190427", date: "2019-04-27", total: "1200", table2: [
              { path: '苏州—北京', mileage: '7777km', fee: '1000' },
              { path: '上海—北京', mileage: '1000km', fee: '1000' },
              { path: '杭州—苏州', mileage: '1000km', fee: '1000' },
              { path: '成都—内蒙', mileage: '200km', fee: '200' },
              { path: '武汉—太原', mileage: '200km', fee: '200' }
            ]
          },
          {
            order: "ASPG20190428", date: "2019-04-28", total: "1200", table2: [
              { path: '上海—北京', mileage: '1000km', fee: '1000' },
              { path: '北京—天津', mileage: '200km', fee: '200' }
            ]
          }
        ],
        table2: [
          {
            order: "ASPG20190427", date: "2019-04-27", total: "1200", table2: [
              {
                path: '苏州—北京', mileage: '7777km', fee: '1000', table3: [
                  // { feeType: '油费', monye: '300', remarks:'备注', },
                  // { feeType: '路桥费', monye: '500', remarks:'备注', }
                ]
              },
              {
                path: '上海—北京', mileage: '1000km', fee: '1000', table3: [
                  { feeType: '油费', monye: '300', remarks: '备注', },
                  { feeType: '住宿费', monye: '200', remarks: '备注', },
                  { feeType: '路桥费', monye: '500', remarks: '备注', }
                ]
              },
              {
                path: '杭州—苏州', mileage: '1000km', fee: '900', table3: [
                  { feeType: '油费', monye: '300', remarks: '备注', },
                ]
              },
              {
                path: '成都—内蒙', mileage: '200km', fee: '200', table3: [
                  // { feeType: '油费', monye: '100', remarks:'备注', },
                  // { feeType: '路桥费', monye: '100', remarks:'备注', }
                ]
              },
              {
                path: '武汉—太原', mileage: '200km', fee: '200', table3: [
                  { feeType: '油费', monye: '100', remarks: '备注', },
                  { feeType: '路桥费', monye: '100', remarks: '备注', }
                ]
              }
            ]
          },
          {
            order: "ASPG20190428", date: "2019-04-28", total: "1200", table2: [
              {
                path: '上海—北京', mileage: '1000km', fee: '1000', table3: [
                  { feeType: '油费', monye: '300', remarks: '备注', },
                  { feeType: '住宿费', monye: '200', remarks: '备注', },
                  { feeType: '路桥费', monye: '500', remarks: '备注', }
                ]
              },
              {
                path: '北京—天津', mileage: '200km', fee: '200', table3: [
                  { feeType: '油费', monye: '100', remarks: '备注', },
                  { feeType: '路桥费', monye: '100', remarks: '备注', }
                ]
              }
            ]
          }
        ],
        table3: [
          { order: "ASPG20190427", date: "2019-04-27", total: "1200", path: '苏州—北京', mileage: '7777km', fee: '1000', feeType: '油费', monye: '300', remarks: '备注' },
          { order: "ASPG20190427", date: "2019-04-27", total: "1200", path: '苏州—北京', mileage: '7777km', fee: '1000', feeType: '路桥费', monye: '500', remarks: '备注' },
          { order: "ASPG20190427", date: "2019-04-27", total: "1200", path: '上海—北京', mileage: '1000km', fee: '1000', feeType: '油费', monye: '300', remarks: '备注' },
          { order: "ASPG20190427", date: "2019-04-27", total: "1200", path: '上海—北京', mileage: '1000km', fee: '1000', feeType: '住宿费', monye: '200', remarks: '备注' },
          { order: "ASPG20190427", date: "2019-04-27", total: "1200", path: '上海—北京', mileage: '1000km', fee: '1000', feeType: '路桥费', monye: '500', remarks: '备注' },
          { order: "ASPG20190427", date: "2019-04-27", total: "1200", path: '杭州—苏州', mileage: '1000km', fee: '900', feeType: '油费', monye: '300', remarks: '备注' },
          { order: "ASPG20190427", date: "2019-04-27", total: "1200", path: '成都—内蒙', mileage: '200km', fee: '200', feeType: '油费', monye: '100', remarks: '备注' },
          { order: "ASPG20190427", date: "2019-04-27", total: "1200", path: '成都—内蒙', mileage: '200km', fee: '200', feeType: '路桥费', monye: '100', remarks: '备注' },
          { order: "ASPG20190428", date: "2019-04-28", total: "1200", path: '上海—北京', mileage: '1000km', fee: '1000', feeType: '油费', monye: '300', remarks: '备注' },
          { order: "ASPG20190428", date: "2019-04-28", total: "1200", path: '上海—北京', mileage: '1000km', fee: '1000', feeType: '住宿费', monye: '200', remarks: '备注' },
          { order: "ASPG20190428", date: "2019-04-28", total: "1200", path: '上海—北京', mileage: '1000km', fee: '1000', feeType: '路桥费', monye: '500', remarks: '备注' },
          { order: "ASPG20190428", date: "2019-04-28", total: "1200", path: '北京—天津', mileage: '200km', fee: '200', feeType: '油费', monye: '100', remarks: '备注', },
          { order: "ASPG20190428", date: "2019-04-28", total: "1200", path: '北京—天津', mileage: '200km', fee: '200', feeType: '路桥费', monye: '100', remarks: '备注' },
        ]
      },
      mounted: function () {
        mergeSameCell(document.getElementById('table3'), 1, 0, [0, 1, 2, 3, 4, 5]);
      }
    });

    /**
     * [mergeSameCell 单元格合并]
     * @param  {[type]} tbl      [table对应的dom元素]
     * @param  {[type]} beginRow [从第几行开始合并(从0开始)]
     * @param  {[type]} endRow   [合并到哪一行,负数表示从底下数几行不合并]
     * @param  {[type]} colIdxes [合并的列下标的数组,如[0,1]表示合并前两列,[0]表示只合并第一列]
     * @return {[type]}          [description]
     */
    function mergeSameCell(tbl, beginRow, endRow, colIdxes) {
      var colIdx = colIdxes[0];
      var newColIdxes = colIdxes.concat();
      newColIdxes.splice(0, 1)
      var delRows = new Array();
      var rs = tbl.rows;
      //endRow为0的时候合并到最后一行,小于0时表示最后有-endRow行不合并
      if (endRow === 0) {
        endRow = rs.length - 1;
      } else if (endRow < 0) {
        endRow = rs.length - 1 + endRow;
      }
      var rowSpan = 1; // 要设置的rowSpan的值
      var rowIdx = beginRow; // 要设置rowSpan的cell行下标
      var cellValue; // 存储单元格里面的内容
      for (var i = beginRow; i <= endRow + 1; i++) {
        if (i === endRow + 1) { //过了最后一行的时候合并前面的单元格
          if (newColIdxes.length > 0) {
            mergeSameCell(tbl, rowIdx, endRow, newColIdxes);
          }
          rs[rowIdx].cells[colIdx].rowSpan = rowSpan;
        } else {
          var cell = rs[i].cells[colIdx];
          if (i === beginRow) { // 第一行的时候初始化各个参数
            cellValue = cell.innerHTML;
            rowSpan = 1;
            rowIdx = i;
          } else if (cellValue != cell.innerHTML) { // 数据改变合并前面的单元格
            cellValue = cell.innerHTML;
            if (newColIdxes.length > 0) {
              mergeSameCell(tbl, rowIdx, i - 1, newColIdxes);
            }
            rs[rowIdx].cells[colIdx].rowSpan = rowSpan;
            rowSpan = 1;
            rowIdx = i;
          } else if (cellValue === cell.innerHTML) { // 数据和前面的数据重复的时候删除单元格
            rowSpan++;
            delRows.push(i);
          }
        }
      }
      for (var j = 0; j < delRows.length; j++) {
        rs[delRows[j]].deleteCell(colIdx);
      }
    }
  </script>
</body>

</html>

参考文章:
1、vue中table跨行合并demo1demo2
2、VUE中嵌套多层table
3、js动态合并单元格

冯奎博客
请先登录后发表评论
  • latest comments
  • 总共0条评论