vue2+table封装二级展开合并表格

本文详细描述了一个Vue组件,展示了如何创建一个可动态调整列宽的表格,包含序号、多选框以及通过插槽进行复杂数据展示。组件还支持表格数据的初始化和处理全选/单选操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

示例:

 组件代码:

<template>
  <div id="table-box" :style="'height:' + height + ';'">
    <table cellspacing="0" style="text-align: center;" cellpadding="0" width="100%" align="center">
      <thead>
        <tr>
          <th v-if="isIndex" width="60">序号</th>
          <th v-if="isCheck" width="60">
            <el-checkbox v-model="checkAll" :indeterminate="isIndeterminate" @change="handleCheckAllChange" />
          </th>
          <th v-for="(header, index) in headers" :key="index" :width="header.width">{{ header.label }}</th>
        </tr>
      </thead>
      <tbody v-if="tableData.length">
        <template v-for="(order, index) in tableData">
          <!-- 序号 -->
          <!-- eslint-disable-next-line vue/require-v-for-key -->
          <tr class="nodeRow">
            <td id="isShowId">
              <i :class="order.isShow ? 'el-icon-minus' : 'el-icon-plus'" @click="order.isShow = !order.isShow" />
            </td>
            <td :colspan="getColspan()">
              <slot name="node" :row="order" :$index="index" />
            </td>
          </tr>
          <!-- 每一行 -->
          <!-- eslint-disable-next-line vue/no-lone-template -->
          <template v-if="order.isShow">
            <tr v-for="(row, ind) in order.list" :key="index + '-' + ind">
              <td v-if="isIndex">{{ row.$index }}</td>
              <td v-if="isCheck">
                <el-checkbox v-model="row.checked" @change="handleCheckedCitiesChange" />
              </td>
              <td v-for="(item,x) in headers" :key="x">
                <slot
                  v-if="item.slotName"
                  :name="item.slotName"
                  :row="row"
                  :$index="ind"
                  :parent="order"
                  :$parentIndex="index"
                />
                <div v-else-if="item.isParentVal">{{ order[item.prop] }}</div>
                <div v-else>{{ row[item.prop] }}</div>
              </td>
            </tr>
          </template>
        </template>

      </tbody>
      <tbody v-else>
        <tr>
          <td style="border: none;" :colspan="getColspan() + 1">暂无数据</td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script>
export default {
  props: {
    // 表格高度
    height: {
      type: String,
      default: 'auto'
    },
    // 是否显示序号
    isIndex: {
      default: true,
      type: Boolean
    },
    // 是否显示多选
    isCheck: {
      default: true,
      type: Boolean
    },
    // 表头数据
    headers: {
      type: Array,
      default: () => {
        return []
      }
    },
    // 表格数据
    data: {
      type: Array,
      default: () => {
        return []
      }
    },
    // 子集字段名称
    children: {
      type: String,
      default: 'list'
    }
  },
  data() {
    return {
      checkAll: false, // 全选框值
      isIndeterminate: false, // 全选框状态
      tableData: [] // 数据
    }
  },
  watch: {
    data: {
      handler(val) {
        this.init(val)
      },
      deep: true, // 深度监听
      immediate: true // 初次监听即执行
    }
  },
  mounted() {

  },
  methods: {
    // 初始化数据
    init(data) {
      let i = 0
      const arr = JSON.parse(JSON.stringify(data))
      arr.forEach(row => {
        const list = JSON.parse(JSON.stringify(row[this.children]))
        row.list = list
        delete row[this.children]
        this.$set(row, 'isShow', true)
        row.list.forEach(item => {
          this.$set(item, 'checked', false)
          i++
          this.$set(item, '$index', i)
        })
      })
      this.tableData = arr
    },
    // 多选框---全选
    handleCheckAllChange(val) {
      this.tableData.forEach(item => {
        item.list.forEach(v => {
          v.checked = val
        })
      })
      this.isIndeterminate = false
      this.$emit('changeChecked', this.getCheckedList())
    },
    // 多选框---单个选择
    handleCheckedCitiesChange(value) {
      let isAll = true
      let isCheck = false
      for (let index = 0; index < this.tableData.length; index++) {
        const item = this.tableData[index]
        for (let index = 0; index < item.list.length; index++) {
          const v = item.list[index]
          if (!v.checked) {
            isAll = false
          } else {
            isCheck = true
          }
        }
      }
      this.checkAll = isAll
      this.isIndeterminate = isCheck && !isAll
      this.$emit('changeChecked', this.getCheckedList())
    },
    // 获取选中的数据
    getCheckedList() {
      const arr = []
      this.tableData.forEach(item => {
        // 深拷贝数据
        const obj = JSON.parse(JSON.stringify(item))
        delete obj.isShow
        const newArr = []
        obj.list.forEach(v => {
          if (v.checked) {
            delete v.checked
            delete v.$index
            newArr.push(v)
          }
        })
        obj[this.children] = newArr
        delete obj.list
        if (newArr.length > 0) {
          arr.push(obj)
        }
      })
      return arr
    },
    // 获取合并单元格数量
    getColspan() {
      let i = 0
      if (this.isIndex) i++
      if (this.isCheck) i++
      return this.headers.length + i - 1
    }
  }
}
</script>
<style scoped lang="scss">
#table-box {
  overflow: auto;
  border: 1px solid #d6d6d6;

  table {
    overflow: auto;
  }

  th,
  td {
    border: 1px solid #d6d6d6;
    padding: 10px 10px;
  }

  th {
    border-right: none;
    border-top: none;
    border-top: none;
    background-color: #edf4fa !important;
    position: sticky;
    top: 0;
    /* 首行永远固定在头部  */
    z-index: 9;
    white-space: nowrap;
  }

  td:first-child,
  th:first-child {
    position: sticky;
    left: 0;
    /* 首列永远固定在左侧 */
    z-index: 9;
    border-left: none;
    background-color: #fff;
    border-right: 1px solid #d6d6d6;
  }

  th:first-child {
    z-index: 10;
    /*表头的首列要在上面*/
  }

  // td th第二列
  td:nth-child(2),
  th:nth-child(2) {
    border-left: none;
  }

  th>div {
    width: 100%;
    white-space: normal;
    word-wrap: break-word;
    word-break: break-all;
  }

  td {
    border-right: none;
    border-top: none;
  }

  .nodeRow {
    background-color: #eaf3ff;
    text-align: left;
    border-top: #d6d6d6 1px solid;
    border-bottom: #d6d6d6 1px solid;

    td {
      background-color: #eaf3ff;
    }
  }

  #isShowId {
    padding-left: 20px;

    i {
      border: 1px solid #d6d6d6;
      cursor: pointer;
    }
  }
}
</style>

使用:

<nodeTable v-loading="loading" children="orderBarcodeVoList" height="500px" :headers="headers" :data="tableData" @changeChecked="changeChecked">
      <!-- 展开节点行插槽  row本行数据 $index 本行下标  -->
      <template #node="data">
        {{ data.row.orderNo }}
        <el-button class="btn-qbdy" type="text">全部打印</el-button>
      </template>
      <!-- 单元格插槽 row本行数据  $index 本行下标  parent父级数据  $parentIndex父级下标  -->
      <template #subtotal="data">
        {{ Number(data.parent.settlPrice)*Number(data.row.ticketCount) }}
      </template>
      <template #caozuo="data">
        <el-button type="text">票重打</el-button>
      </template>
    </nodeTable>

headers数据参数:

headers: [{
        prop: 'barcodeId',
        label: '票号'
      }, {
        prop: 'ticketName',
        label: '票种名称',
        isParentVal: true // 是否从父级取值
      }, {
        prop: 'ticketFlag',
        label: '出票方式',
        slotName: 'ticketFlag'
      }]

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

另一个自己IT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值