组件封装01 - vue3 封装仿antd-vue的Table组件基本实现

1,154 阅读2分钟

最近需要在不使用UI框架的情况下封装一个简单的表格,由于对antd-vue比较熟悉,所以仿照了antd-vue的样子写了个简化版的Table组件。

使用技术:VUE3、SCSS

用过antd-vue的都应该知道table组件主要的传参是两个:columns和dataSource,分别对应表头信息和表格的数据信息。

1.表头数据columns

表头的传参数据结构如下:

[
  { label: '姓名', key: 'name' },
  { label: '年龄', key: 'age' }
]
  • key:必须,用来标志当前行
  • label:非必须,当前列的列头显示内容

子组件-表头的遍历:

image.png

2.表格数据dataSource

表格数据传参的数据结构如下:

[
  {name: '张三', age: 24},
  {name: '李四', age: 45}   
]

可以看到dataSource里面的key都是columns里面的key字段对应的值

对应源代码如下:

image.png

主要思路是先遍历每一行的数据,再在每一行的数据中根据columns遍历当前列的数据

做到这里不加66行的代码的话,整个表格显示功能就已经实现了。

 3.添加操作功能

其实整个功能做起来比较简单,现在回想起来,还是自己对vue3的slot使用不太熟悉。

slot的基本使用见官网

这里能实现这个功能主要看的是作用域插槽的部分

代码还是看上面那张图的65、66两行。

但是columns的传参需要有变化:

[  { label: '姓名', key: 'name' },  { label: '年龄', key: 'age' },  { label: '操作', key: 'operate', slots: 'operate'}   ]

 slots参数表示当前列需要使用插槽

父组件使用table组件:

<template #operate="record">
     <a @click="handleOperate(record.rowData)">详情</a>
</template>

注意:

  • #operate是v-slot的简化写法
  • record是子组件内的slot插槽传过来的参数
  • rowData与Table组件的:rowData对应,即当前tr的内容

接下来贴上使用代码:

 可以看到使用基本和antd-vue差别不大了

4.其他功能

目前已经实现了表头固定table可滚动、宽度等基本功能、如果需要添加其他功能的话可以在此基础上添加各种传参即可

5.全部源码:

<template>
  <div class="gw-table-content">
    <!-- 需要表头固定,且表格可以滚动时的表头 -->
    <table class="gw-fixed-table" v-if="tableHeaderFixed">
      <thead
        :style="{
          backgroundColor: headerBackground
            ? 'hsla(200, 79%, 49%, 0.2)'
            : 'transparent',
          fontSize: headerFontSize || '18px'
        }"
      >
        <th style="width:70px;" v-if="serialNumber">序号</th>
        <th
          :class="{ ellipsis: headerEllipsis }"
          :width="h.width"
          v-for="(h, i) in columns"
          :key="i"
        >
          {{ h.label }}
        </th>
      </thead>
    </table>
    <div class="un-fixed-table-box">
      <table class="gw-table">
        <!-- 正常的表头,无固定 -->
        <thead
          v-if="!tableHeaderFixed"
          :style="{
            backgroundColor: headerBackground
              ? 'hsla(200, 79%, 49%, 0.2)'
              : 'transparent',
            fontSize: headerFontSize || '18px'
          }"
        >
          <th style="width:70px;" v-if="serialNumber">序号</th>
          <th
            :class="{ ellipsis: headerEllipsis }"
            :width="h.width"
            v-for="(h, i) in columns"
            :key="i"
          >
            {{ h.label }}
          </th>
        </thead>
        <tbody>
          <tr
            :class="{ dash: trDash }"
            v-for="(tr, ri) in dataSource"
            :key="ri"
          >
            <!-- 是否需要序号 -->
            <td style="width:70px;" v-if="serialNumber">{{ ri + 1 }}</td>
            <td
              :style="{
                ...td.style,
                padding: tdPadding,
                width: td.width
              }"
              v-for="(td, key) in columns"
              :key="key"
            >
              <span :class="{ ellipsis: columnEllipsis }" :title="tr[td.key]">
                <!-- 当前列的插槽, -->
                <slot v-if="!td.slots">{{ tr[td.key] }}</slot>
                <slot v-else :name="td.slots" :rowData="tr"></slot>
                <!-- tr[td.key] 这个东西为什么能获取到当前这个key值的数据没太懂。。 -->
              </span>
            </td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
</template>

<script lang="js">
import { defineComponent } from 'vue'

export default defineComponent({
  props: {
    columns: {
      // 表头
      required: true,
      default: []
    },
    dataSource: {
      // 表数据
      required: true,
      default: []
    },
    serialNumber: {
      // 是否需要第一列的序列号
      required: false,
      default: false
    },
    headerBackground: {
      // 是否需要表头的背景色
      required: false,
      default: false
    },
    headerFontSize: {
      // 表头字体大小
      required: false,
      default: ''
    },
    tdPadding: {
      // td的padding值
      required: false,
      default: ''
    },
    headerEllipsis: {
      // thead是否超过宽度省略
      required: false,
      default: true
    },
    columnEllipsis: {
      // td是否超过宽度省略
      required: false,
      default: true
    },
    trDash: {
      // 每行加下边框
      required: false,
      default: false
    },
    tableHeaderFixed: {
      // 表头是否固定
      required: false,
      default: false
    }
  }
})
</script>

<style lang="scss" scoped>
.gw-table-content {
  padding: 12px;
  height: calc(100% - 21px);
  .un-fixed-table-box {
    max-height: calc(100% - 44px);
    overflow-y: scroll;
  }
  .gw-table,
  .gw-fixed-table {
    width: 100%;
    max-height: 100%;
    position: relative;
    table-layout: fixed;
    thead {
      background-color: #1ba0e1;
      color: #00aaff;
      font-family: 'Adobe Heiti Std R';
      font-size: 17px;
      th {
        padding: 10px 12px;
        font-family: Adobe Heiti Std;
        font-weight: normal;
        color: #00aaff;
      }
      th.ellipsis {
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
      }
    }
    tbody {
      tr:nth-child(odd) {
        background-color: rgba(27, 160, 225, 0.05);
      }
      tr {
        overflow: hidden;
        color: #00aaff;
        td {
          padding: 11px 12px;
          font-size: 14px;
          border-right: 2px solid transparent;

          > span {
            max-width: 100%;
            display: inline-block;
          }
          > span.ellipsis {
            overflow: hidden;
            white-space: nowrap;
            text-overflow: ellipsis;
          }
        }
      }
      tr.dash {
        border-bottom: 1px dashed rgba(#1ba0e1, 0.3);
      }
    }
  }
}
</style>