基于elementUI封装table组件

4,113 阅读2分钟

「这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战

一、效果

此组件使用起来非常简单,只需配置如下:

/**
* 表格列项数据 (必传)
* label 表格列项名称
* prop: 表格数据的字段名
* width 列项宽度
* align       内容对齐方式 (默认居中)
* headerAlign 表头对齐方式 (默认居中)
* columnType  是否开启插槽
* soltName    插槽名(自定义)
*/
    tableColumnOptions: [
    { label: "账号", prop: "id" },
    { label: "姓名", prop: "name" },
    { label: "tag", prop: "type", columnType: true, soltName: "tag" },
    { label: "特殊样式", columnType: true, soltName: "link" },
    { label: "操作", width: "300", columnType: true, soltName: "operation"},
    ]

即可显示:

image.png

二、技术点

1. 组件传值

通过 Prop 向子组件传递数据

Prop 是你可以在组件上注册的一些自定义 attribute。当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property,使用起来也很简单:

  • 在父组件的子组件调用标签上:绑定自定义属性名及属性值
// partent.vue
<template>
  <div>
      //在父组件的子组件调用标签上:绑定自定义属性名及属性值
      <childTemp :title="partentTitle"></childTemp>
  </div>
</template>
<script>
import childTemp from "./child.vue"
export default {
    compontent:{childTemp},
    data(){
        return{
            partentTitle:"xxxxxxxx"
        }
    }
}
</script>


  • 在子组件中:使用props接收传过来的值,接收到的数据使用方法与data中的数据一致
// child.vue
<template>
    <div>
        <h2>{{title}}</h2>
    </div> 
</template>
<script>
    export default {
        props:['title'] //在子组件中使用props接收在父组件中绑定的title
    }
</script>
监听子组件事件
  • 在子组件的点击事件中绑定一个函数,再通过this.$emit("自定义一个父组件的事件名",参数) 传递数据
//child.vue
<template>
    <div>
        <button @click="childClick">按钮</button>
    </div> 
</template>
<script>
    export default {
        props:['title'],
        data(){
            return{
                childData:"来自子组件的数据"

            }
        },
        methods:{
            childClick(){
                this.$emit("fun",this.childData)
            }
        }
    }
</script>
  • 在父组件中:在调用标签上使用v-bind(简写为@)绑定刚才在子组件中定义的事件名fun,在赋予其一个自定义函数, 刚才在子组件中通过this.$emit传递的参数会自动注入至此自定义函数的参数中
<template>
  <div>
      <childTemp @fun="getData"></childTemp>
  </div>
</template>

<script>
import childTemp from "../co/PropsChild.vue"
export default {
    components:{childTemp},
    data(){
        return{
            partentTitle:"xxxxxxxx"
        }
    },
    methods:{
        getData(e){//
            console.log(e); //打印出 "来自子组件的数据"
        }
    }
}
</script>

2.具名插槽

具名插槽可以出现在不同的地方,不限制出现的次数。只要匹配了 name 那么这些内容就会被插入到这个 name 的插槽中去。

跟 v-on 和 v-bind 一样,v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被重写为 #header

  • 父组件中 <template #header> xxxxxx </template> 标签会自动取找子组件中那个叫header的slot标签,将template标签内包裹的所有东西替换掉那个slot标签
<template>
  <div>
    <childTemp>
        <template #header>我这里写的东西,他会自动去找子组件中叫header的slot标签,替换掉他</template>
        <!-- <template #main>我这里写的东西,他会自动去找子组件中叫main的slot标签,替换掉他</template> -->
        <template #footer>我这里写的东西,他会自动去找子组件中叫footer的slot标签,替换掉他</template>
    </childTemp>
  </div>
</template>

<script>
import childTemp from "./childTemp.vue";
export default {
  components: { childTemp }
};
</script>

</script>

  • 子组件中
<template>
  <div class="container">
    我没在slot标签里,所以还是可以看见我
    <header style="background:blue">
      <slot name="header">写了又怎样,只要人家调用我,我就被替换掉了</slot>
    </header>
    <main style="background:red">
      <slot name="main">不调用我的话我就还在</slot>
    </main>
    <footer style="background:pink">
      <slot name="footer"></slot>
    </footer>
  </div>
</template>
<script>
</script>

提示,

v-slot 指令自 Vue 2.6.0 起被引入,提供更好的支持 slot 和 slot-scope attribute 的 API 替代方案。在接下来所有的 2.x 版本中 slot 和 slot-scope attribute 仍会被支持,但已经被官方废弃且不会出现在 Vue 3 中。

三、回归正题,封装组件

封装的table组件代码如下

BaseTable.vue

<template>
  <div id="Wrap">
    <!-- 表格 -->
    <el-table height="100px" @selection-change="selectionChange" class="table" :data="tableData" :border="hasBorder" :row-class-name="tableRowClassName">
     <el-table-column type="selection" width="50" v-if="selectionShow"> </el-table-column>
      <!-- 序号 -->
      <el-table-column v-if="hasIndex" type="index" label="序号" header-align="center" align="center" width="80" > </el-table-column>
      <!-- 其他 -->
      <template v-for="item in tableColumnOptions">
        <!-- 插槽列 -->
        <el-table-column
          v-if="item.columnType"
          :key="item.label"
          :prop="item.prop"
          :label="item.label"
          :width="item.width"
          :header-align="item.headerAlign || 'center'"
          :align="item.align || 'center'"
        >
          <template slot-scope="{ row }">
            <slot :name="item.soltName" :data="row"></slot>
          </template>
        </el-table-column>
        <!-- 非插槽列 -->
        <el-table-column
          v-else
          :key="item.label"
          :prop="item.prop"
          :label="item.label"
          :width="item.width"
          :header-align="item.headerAlign || 'center'"
          :align="item.align || 'center'"
        >
        </el-table-column>
      </template>
    </el-table>
    <!-- 分页器 -->
    <div class="pagination" v-if="paginationShow">
      <el-pagination
        :hide-on-single-page="isShowPagination"
        :page-sizes="[10, 20, 50, 100]"
        :current-page="currentPage"
        :page-size="pageSize"
        :total="tableDataTotal"
        layout="total, sizes, prev, pager, next, jumper"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
      >
      </el-pagination>
    </div>
  </div>
</template>
<script type="text/ecmascript-6">
export default {
  name: "BaseTable",
  props: {
    // 表格数据
    tableData: {
      type: Array,
      default() {
        return [];
      },
    },
    // 表格列项
    tableColumnOptions: {
      type: Array,
      require: true,
      default() {
        return [];
      },
    },
    // 表格数据总量
    tableDataTotal: {
      type: Number,
      default() {
        return 0;
      },
    },
    // 是否具有索引
    hasIndex: {
      type: Boolean,
      default() {
        return true;
      },
    },
    // 是否显示边框
    hasBorder: {
      type: Boolean,
      default() {
        return false;
      },
    },
    // 是否显示选择框
    selectionShow: {
      type: Boolean,
      default() {
        return true;
      },
    },
    // 是否显示选分页器
    paginationShow: {
      type: Boolean,
      default() {
        return true;
      },
    },
  },
  data() {
    return {
      // 表格当前页数
      currentPage: 1,
      // 表格每页数量
      pageSize: 10,
    };
  },
  computed: {
    // 计算是否显示分页器
    isShowPagination() {
      const isShow = this.tableDataTotal === 0;
      return isShow;
    },
  },
  methods: {
    // 表格变色
    tableRowClassName({ row, rowIndex }) {
      if (rowIndex % 2 == 1) {
        return "color-row";
      }
    },
    // 修改当前页
    handleCurrentChange(val) {
      const params = {
        currentPage: val,
        pageSize: this.pageSize,
      };
      this.$emit("tableUpdate", params);
    },
    // 修改每页数量
    handleSizeChange(val) {
      const params = {
        currentPage: this.currentPage,
        pageSize: val,
      };
      this.$emit("tableUpdate", params);
    },
    // 选择框改变
    selectionChange(e){
      this.$emit('selectionChange',e)
    }
  },
};
</script>
<style  scoped>
#Wrap {
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  background-color: #fff;
  display: flex;  
  flex-direction: column;  
}
>>>.el-table{
  /* overflow-y:scroll!important; */
}
#Wrap .pagination{
  margin-top: auto;
  margin-left: auto;
}
>>>.cell{
    text-align: center;
}
>>> .cell #link{
  font-family: MicrosoftYaHei;
  color: #445af7;
  cursor: pointer;
}
>>> .cell #delete {
  color: #ff5b5b;
  margin-left: 35px;
    cursor: pointer;
}
>>>.color-row{
  background-color: rgb(243, 243, 243);
}
>>>.el-table--border::after, .el-table--group::after, .el-table::before {
    content: '';
    position: absolute;
    background-color: transparent!important;
    z-index: 1;
}
</style>

五、使用

在要使用的页面中

<template>
  <div id="baseButton">
    <BaseTable
      :has-index="true"
      :has-border="false"
      :table-data="tableData"
      :table-data-total="tableData.length"
      :table-column-options="tableColumnOptions"
      @tableUpdate="tableUpdate"
      @selectionChange="selectionChange"
    >
      <!-- 类型插槽 -->
      <template #tag="{ data }">
        <div class="tag">
          <el-tag type="success" v-if="data.type == 1">tag1</el-tag>
          <el-tag type="danger" v-if="data.type == 0">tag2</el-tag>
        </div>
      </template>
      <!-- 操作插槽 -->
      <template #operation="{ data }">
        <span @click="tableRowEdit(data)">编辑</span>
        <span @click="tableRowDelete(data)" id="delete">删除</span>
      </template>
      <!-- 链接插槽 -->
      <template #link="{ data }">
        <span id="link" @click="check(data)">查看</span>
      </template>
    </BaseTable>
  </div>
</template>

<script>
import BaseTable from "../BaseTable.vue";
export default {
  components: {
    BaseTable,
  },
  methods: {
    selectionChange(e) {
      console.log("选择框改变时:",e);
    },
    tableUpdate(e) {
      console.log("表格发生变化时",e);
    },
    tableRowEdit(e){
      console.log("拿到此行的数据",e);
    }
  },
  data() {
    return {
      //表格组件--模拟数据
      tableData: [
        { id: 201791074073, name: "张三", type: 1 },
        { id: 201791074071, name: "李四", type: 0 },
        { id: 201791074074, name: "张二", type: 0 },
        { id: 201791074074, name: "张二", type: 0 },
        { id: 201791074074, name: "张二", type: 1 },
      ],
      /**
       * 表格列项数据 (必传)
       * label 表格列项名称
       * prop: ''
       * width 列项宽度
       * align       内容对齐方式 (默认居中)
       * headerAlign 表头对齐方式 (默认居中)
       * columnType  是否开启插槽
       * soltName    插槽名
       */
      tableColumnOptions: [
        { label: "账号", prop: "id" },
        { label: "姓名", prop: "name" },
        { label: "tag", prop: "type", columnType: true, soltName: "tag" },
        { label: "特殊样式", columnType: true, soltName: "link" },
        { label: "操作", width: "300", columnType: true, soltName: "operation"},
      ],
    };
  },
};
</script>

原理很简单,使用起来也很方便,注释写的很清楚了,普通数据就直接配置一个lableprop,特殊样式或功能的配置columnType将插槽开启及soltName插槽名。在baseTable标签里里使用具名插槽去编写你的特殊样式。

这样,我们在不同页面调用此组件时,既保证了不同页面的表格风格一致,也保证不同页面的差异化,使用此组件最大的好处就是,表格的样式统一设置在封装的表格组件内,一处更改,其他地方随之更改,可以快速全局改变风格。

over