elementUI el-table二次封装的详细实例

作者:画意桥边 时间:2024-05-03 15:12:00 

前言

很多中后台业务的系统中,表格是最高频的组件之一,其中一般包括搜索条件、表格展示、表格操作列、分页等。那么我们二次封装的这个表格组件就需要包含以下几个功能点:

1、数据自动获取和刷新

2、自定义列配置

3、分页功能

4、根据搜索条件进行搜索功能

5、加载中状态和空数据状态

一、先上页面最终效果

elementUI el-table二次封装的详细实例

二、创建目录yxt-table如下图

index.vue为父级页面

yxt-table.vue为表格组件

elementUI el-table二次封装的详细实例

二、数据自动获取和刷新

因为表格的数据一般都比较简单,就是根据搜索条件,调用接口请求一个到列表,然后将列表数据一一展示到表格上,再对特定的列进行一些过滤转化等个性化操作。但是万变不离其宗,这个步骤基本每个表格都要进行一遍,所以考虑到通用性(其实是为了偷懒),将请求接口获取数据这一步放在组件里面实现。

created () {
       this.getData()
   },
   methods: {
       getData () {
           const fun = this.apiUrl
           fun().then(res => {
               this.tableData = res[this.otherConfig.list] || []
               this.tableTotal = res.pageInfo?.total || 0
           })
       }
   }

三、自定义列配置

组件接收一个数组作为自定义列

tableColumn: [
   { prop: 'name', label: '名称' },
   { prop: 'code', label: '编码' },
   { prop: 'status', label: '状态' }
]

 index.vue

<!-- index.vue -->
<template>
 <div>
   <yxt-table :apiUrl="yxtTableList"
              :tableColumn="tableColumn"></yxt-table>
 </div>
</template>

<!-- index.vue -->
<script>
import yxtTable from './yxt-table.vue'
import { yxtTableList } from 'https/yxtDemo.js'
export default {
   name: 'yxtDemoTable',
   components: {
       yxtTable
   },
   data () {
       return {
           yxtTableList,
           tableColumn: [
               { prop: 'name', label: '名称' },
               { prop: 'code', label: '编码' },
               { prop: 'status', label: '状态' }
           ]
       }
   }
}
</script>

 yxt-table.vue

<!-- yxt-table.vue -->
<template>
 <div>
   <el-table :data="tableData">
     <el-table-column v-for="item in tableColumn"
                      :key="item.prop"
                      :prop="item.prop"
                      :label="item.label"></el-table-column>
   </el-table>
 </div>
</template>

<!-- yxt-table.vue -->
<script>
export default {
   name: 'yxtTable',
   props: {
       apiUrl: { // 列表接口(必填)
           type: Function,
           required: true
       },
       tableColumn: { // 自定义列配置
           type: Array,
           default: () => []
       },
       otherConfig: { //
           type: Object,
           default: () => {
               return {
                   list: 'list'
               }
           }
       }
   },
   data () {
       return {
           tableData: []
       }
   },
   created () {
       this.getData()
   },
   methods: {
       getData () {
           const fun = this.apiUrl
           fun().then(res => {
               this.tableData = res[this.otherConfig.list] || []
               this.tableTotal = res.pageInfo?.total || 0
           })
       }
   }
}
</script>

至此,一个表格可以实现了

elementUI el-table二次封装的详细实例

1、otherConfig说明

由于我们的接口请求放在组件里面了,但是我们对接的接口可能由于业务的不同项目组的不同开发人员的不同,而导致接口返回的列表的字段名不同,这里通过传参的形式做一下兼容

2、上面这样只能实现简单的展示功能,还有些数据比如 状态1 需要转化为打卡成功,状态0 需要转化为打卡失败进行显示,这类需求可以通过filter进行转化

<!-- yxt-table.vue -->
     <el-table-column v-for="item in tableColumn"
                      :key="item.prop"
                      :prop="item.prop"
                      :label="item.label">
       <template v-slot:default="scope">
         <div v-if="item.dictCode">
           {{ scope.row[item.prop] | filterStatus(dict[item.dictCode]) }}
         </div>
         <div v-else>
           {{ scope.row[item.prop] }}
         </div>
       </template>
     </el-table-column>

props: {
       dict: { // 全部字典
           type: Object,
           default: () => {}
       }
   },
   filters: {
       filterStatus (value, array, code = 'code', name = 'name') {
           if (!value && value !== 0) { // 要把0摘出来,一般0都是正常的数据,所以不能只用  !value
               return ''
           }
           const find = array.find(e => (e[code] === value.toString()) || (e[code] === +value)) // 字符型数值型都得匹配
           if (find) {
               return find[name]
           } else { // 没有匹配的就原样返回
               return value
           }
       }
   },
<!-- index.vue -->
   <yxt-table :apiUrl="yxtTableList"
              :tableColumn="tableColumn"
              :dict="dict"></yxt-table>

data () {
       return {
          tableColumn: [
               { prop: 'name', label: '名称' },
               { prop: 'code', label: '编码' },
               { prop: 'status', label: '状态', dictCode: 'status' }
           ],
           dict: {
               status: [
                   { code: 0, name: '打卡失败' },
                   { code: 1, name: '打卡成功' }
               ]
           }
       }
   }

elementUI el-table二次封装的详细实例

这里dict设置为对象的原因是为了装进更多字典

3、思考一下,如果要在表格中展示这样的自定义图标怎么办? 

elementUI el-table二次封装的详细实例

使用插槽slot,在tableColumn里面设置某行属性的slot为true,改造el-table-column如下:

<!-- yxt-table.vue -->    
    <el-table-column v-for="(item, index) in tableColumn"
                      :key="index"
                      :prop="item.prop"
                      :label="item.label">
       <template v-if="item.slot"
                 v-slot:default="scope">
         <slot :name="item.prop"
               :row="scope.row"
               :index="scope.$index"></slot>
       </template>
       <template v-else v-slot:default="scope">
         <div v-if="item.dictCode">
           {{ scope.row[item.prop] | filterStatus(dict[item.dictCode]) }}
         </div>
         <div v-else>
           {{ scope.row[item.prop] }}
         </div>
       </template>
     </el-table-column>
<!-- index.vue -->    
   <yxt-table :apiUrl="yxtTableList"
              :tableColumn="tableColumn"
              :otherConfig="otherConfig"
              :dict="dict">
     <template v-slot:icon="{row, index}">
       <i :class="row.status ? 'el-icon-circle-check' : 'el-icon-circle-close'"></i>
     </template>
   </yxt-table>

data () {
       return {
           tableColumn: [
               { prop: 'name', label: '名称' },
               { prop: 'code', label: '编码' },
               { prop: 'status', label: '状态', dictCode: 'status' },
               { prop: 'icon', label: '图标', slot: true }
           ]
       }
   }

 4、在实际项目中,除了字典转化,还有一些比较定制化的展示需求,这个可以通过传入一个函数format进行计算,然后在这个方法里面将最后的计算结果return

<!-- yxt-table.vue -->
     <el-table-column v-for="(item, index) in tableColumn"
                      :key="index"
                      :prop="item.prop"
                      :label="item.label">
       <template v-if="item.slot"
                 v-slot:default="scope">
         <slot :name="item.prop"
               :row="scope.row"
               :index="scope.$index"></slot>
       </template>
       <template v-else v-slot:default="scope">
         <div v-if="item.dictCode">
           {{ scope.row[item.prop] | filterStatus(dict[item.dictCode]) }}
         </div>
         <div v-else-if="item.format">
           {{ item.format(scope.row) }}
         </div>
         <div v-else>
           {{ scope.row[item.prop] }}
         </div>
       </template>
     </el-table-column>
<!-- index.vue -->
   data () {
       return {
           tableColumn: [
               { prop: 'name', label: '名称' },
               { prop: 'code', label: '编码' },
               { prop: 'status', label: '状态', dictCode: 'status' },
               { prop: 'icon', label: '图标', slot: true },
               { prop: 'phone',
                   label: '电话号码',
                   format: (row) => {
                       return `${row.name}-${row.code}(${row.phone})`
                   } }
           ]
       }
   }

elementUI el-table二次封装的详细实例

5、表格一般还有批量操作,所以需要多选和单选以及针对特定场景设置禁选

elementUI el-table二次封装的详细实例

 yxt-table.vue

<!-- yxt-table.vue -->
<template>
 <div class="yxt-table">
   <!-- 批量操作按钮,因为每个需求不同,批量操作的功能也不同,所以这里只放一个插槽,不设置默认内容,所有按钮均在父级设置 -->
   <div class="multiple-operation">
     <slot name="multiple-operation"
           :selectionData="selectionData"></slot>
   </div>
   <!-- 页面主表格 -->
   <el-table :data="tableData"
             :row-key="rowKey"
             @selection-change="selectionChange">
     <!-- 可选框(多选) -->
     <el-table-column v-if="selection === 'multiple'"
                      type="selection"
                      align="center"
                      width="55"
                      :reserve-selection="rowKey ? true : false"
                      :selectable="selectable"/>
     <!-- 可选框(单选) -->
     <el-table-column v-else-if="selection === 'single'"
                      align="center"
                      width="30">
       <template v-slot:default="scope">
         <el-radio v-model="selectionRadio"
                   :label="scope.$index"
                   :disabled="selectable ? !selectable(scope.row) : false"
                   @change="selectionChangeSingle(scope.row)">
           {{ '' }}
         </el-radio>
       </template>
     </el-table-column>
     <el-table-column v-for="(item, index) in tableColumn"
                      :key="index"
                      :prop="item.prop"
                      :label="item.label">
       <template v-if="item.slot"
                 v-slot:default="scope">
         <slot :name="item.prop"
               :row="scope.row"
               :index="scope.$index"></slot>
       </template>
       <template v-else v-slot:default="scope">
         <div v-if="item.dictCode">
           {{ scope.row[item.prop] | filterStatus(dict[item.dictCode]) }}
         </div>
         <div v-else-if="item.format">
           {{ item.format(scope.row) }}
         </div>
         <div v-else>
           {{ scope.row[item.prop] }}
         </div>
       </template>
     </el-table-column>
   </el-table>
 </div>
</template>

<!-- yxt-table.vue -->
<script>
export default {
   name: 'yxtTable',
   props: {
       apiUrl: { // 列表接口(必填)
           type: Function,
           required: true
       },
       tableColumn: { // 自定义列配置
           type: Array,
           default: () => []
       },
       otherConfig: { // 其他配置
           type: Object,
           default: () => {
               return {
                   list: 'list' // 接口返回数据的列表字段的字段名(因为在组件里面调接口,可能不同业务不同项目组不同一个开发者返回给前端的参数名不一致,这里进行兼容)
               }
           }
       },
       dict: { // 全部字典
           type: [Array, Object],
           default: () => []
       },
       selection: { // 是否显示可选框(多选-multiple 、单选-single )
           type: String
       },
       selectable: { // 当前行是否可选择
           type: Function
       },
       rowKey: { // 表格唯一key(适用于分页多选表格,保留之前的选择,不传则为单页选择)
           type: [Number, String, Function],
           default: ''
       }
   },
   filters: {
       filterStatus (value, array, code = 'code', name = 'name') {
           if (!value && value !== 0) { // 要把0摘出来,一般0都是正常的数据,所以不能只用  !value
               return ''
           }
           const find = array.find(e => (e[code] === value.toString()) || (e[code] === +value)) // 字符型数值型都得匹配
           if (find) {
               return find[name]
           } else { // 没有匹配的就原样返回
               return value
           }
       }
   },
   data () {
       return {
           tableData: [],
           tableTotal: 0,
           selectionRadio: '',
           selectionData: []
       }
   },
   created () {
       this.getData()
   },
   methods: {
       getData () {
           const fun = this.apiUrl
           fun().then(res => {
               this.tableData = res[this.otherConfig.list] || []
               this.tableTotal = res.pageInfo?.total || 0
           })
       },

// 多选,选择行数据change
       selectionChange (selection) {
           this.selectionData = selection
       },

// 单选,选择行数据change
       selectionChangeSingle (selection) {
           this.selectionData = [selection]
       }
   }
}
</script>
<style scoped lang="scss">
.yxt-table {
 margin: 30px;
 .multiple-operation {
   margin-bottom: 10px;
 }
}
</style>

 index.vue

<!-- index.vue -->
<template>
 <div>
   <yxt-table :apiUrl="yxtTableList"
              :tableColumn="tableColumn"
              :otherConfig="otherConfig"
              :dict="dict"
              selection="multiple"
              :selectable="isSelectable">
     <!-- 图标插槽 -->
     <template v-slot:icon="{row, index}">
       <i :class="row.status ? 'el-icon-circle-check' : 'el-icon-circle-close'"></i>
     </template>
     <!-- 批量操作按钮插槽 -->
     <template v-slot:multiple-operation="{selectionData}">
       <el-button type="primary"
                  size="small"
                  @click="handleClick1(selectionData)">批量操作1</el-button>
       <el-button type="success"
                  size="small"
                  @click="handleClick2(selectionData)">批量操作2</el-button>
     </template>
   </yxt-table>
   <yxt-table :apiUrl="yxtTableList"
              :tableColumn="tableColumn"
              :otherConfig="otherConfig"
              :dict="dict"
              selection="single"
              :selectable="isSelectable">
     <!-- 图标插槽 -->
     <template v-slot:icon="{row, index}">
       <i :class="row.status ? 'el-icon-circle-check' : 'el-icon-circle-close'"></i>
     </template>
     <!-- 批量操作按钮插槽 -->
     <template v-slot:multiple-operation="{selectionData}">
       <el-button type="primary"
                  size="small"
                  @click="handleClick1(selectionData)">单选操作</el-button>
     </template>
   </yxt-table>
 </div>
</template>

<!-- index.vue -->
<script>
import yxtTable from './yxt-table.vue'
import { yxtTableList } from 'https/yxtDemo.js'
export default {
   name: 'yxtDemoTable',
   components: {
       yxtTable
   },
   data () {
       return {
           yxtTableList,
           tableColumn: [
               { prop: 'name', label: '名称' },
               { prop: 'code', label: '编码' },
               { prop: 'status', label: '状态', dictCode: 'status' },
               { prop: 'icon', label: '图标', slot: true },
               { prop: 'phone',
                   label: '电话号码',
                   format: (row) => {
                       return `${row.name}-${row.code}(${row.phone})`
                   } }
           ],
           tableConfig: {
               stripe: 'stripe',
               border: 'border',
               height: '200',
               maxHeight: '200',
               showHeader: true
           },
           otherConfig: {
               list: 'tasks'
           },
           dict: {
               status: [
                   { code: 0, name: '打卡失败' },
                   { code: 1, name: '打卡成功' }
               ]
           }
       }
   },
   methods: {
       handleClick1 (selectionData) {
           console.log('1', selectionData)
       },
       handleClick2 (selectionData) {
           console.log('2', selectionData)
       },
       isSelectable (row) {
           return row.selectable !== 0
       }
   }
}
</script>
<style scoped lang="scss">
.el-icon-circle-check {
 font-size: 28px;
 color: #67C23A;
}
.el-icon-circle-close {
 font-size: 28px;
 color: #F00;
}
</style>

6、操作列

根据业务需求,可以在操作列设置几个默认按钮,通过setupConfig设置开关,如果有除了默认按钮之外的操作需求,再通过插槽slot进行插入

<!-- yxt-table.vue -->
     <!-- 操作列 -->
     <el-table-column v-if="setupConfig.width !== 0"
                      :fixed="setupConfig.fixed"
                      :width="setupConfig.width"
                      label="操作">
       <template v-slot:default="scope">
         <slot name="setup"
               :row="scope.row"
               :index="scope.$index"></slot>
         <!-- 查看 -->
         <el-button v-if="setupConfig.view"
                    type="text"
                    @click="setupEvents('view', scope.row)">查看</el-button>
         <!-- 编辑 -->
         <el-button v-if="setupConfig.edit"
                    type="text"
                    @click="setupEvents('edit', scope.row)">编辑</el-button>
         <!-- 删除 -->
         <el-button v-if="setupConfig.del"
                    type="text"
                    @click="setupEvents('del', scope.row)">删除</el-button>
         <!-- 操作日志 -->
         <el-button v-if="setupConfig.log"
                    type="text"
                    @click="setupEvents('log', scope.row)">操作日志</el-button>
       </template>
     </el-table-column>

props: {
       setupConfig: {
           type: Object,
           default: () => {
               return {
                   width: 'auto'
               }
           }
       }
   },
   methods: {
       setupEvents (setupType, row) { // 操作列方法 查看/编辑/删除/操作日志
           this.$emit(setupType, row)
       }
   }

elementUI el-table二次封装的详细实例

 index.vue做相应的处理,这里不贴代码了

7、分页 

pagination控制是否需要分页组件,如果不需要分页则设置为false。根据业务需求,可传入pageSizes控制条数下拉框的条数选项

<!-- yxt-table.vue -->
   <!-- 分页 -->
   <el-pagination v-if="pagination"
                  class="pagination tablePage"
                  :pager-count="5"
                  :page-sizes="pageSizes || [10, 20, 50, 100]"
                  :total="tableTotal || 0"
                  :page-size="pageInfo.pageSize || 10"
                  :current-page="pageInfo.startPage || 1"
                  layout="total, sizes, prev, pager, next, jumper"
                  @size-change="sizeChange"
                  @current-change="pageChange"></el-pagination>

props: {
       pagination: { // 是否需要分页,默认需要
           type: Boolean,
           default: true
       },
       pageSizes: {
           type: Array
       }
   },
   methods: {
       getData () {
           const fun = this.apiUrl
           const pageInfo = { // 分页信息
               pageSize: this.pageInfo.pageSize,
               startPage: this.pageInfo.startPage
           }
           let param = { // 其他的搜素条件

}
           if (this.pagination) { // 如果需要分页,则传分页信息
               param = { ...param, ...pageInfo }
           }
           fun(param).then(res => {
               this.tableData = res[this.otherConfig.list] || []
               this.tableTotal = res.pageInfo?.total || 0
           })
       },

// 条数变化
       sizeChange (size) {
           this.pageInfo.startPage = 1
           this.pageInfo.pageSize = size
           this.getData()
       },

// 页码变化
       pageChange (page) {
           this.pageInfo.startPage = page
           this.getData()
       }
   }

elementUI el-table二次封装的详细实例

 8、el-table还有一个展开行功能expand,根据业务需求,也可以加进组件里

<!-- yxt-table.vue -->
     <!-- 展开行 -->
     <el-table-column v-if="expand"
                      type="expand">
       <template v-slot:default="scope">
         <slot name="expand"
               :row="scope.row"
               :index="scope.$index"></slot>
       </template>
     </el-table-column>

props: {
       expand: { // 是否展开行
           type: Boolean,
           default: false
       }
   }
<!-- index.vue -->
   <yxt-table :apiUrl="yxtTableList"
              :tableColumn="tableColumn"
              :expand="true">
     <template v-slot:expand="{row, index}">
       <div>
         <p>序号:{{index}}</p>
         <p>内容:{{row}}</p>
       </div>
     </template>
   </yxt-table>

elementUI el-table二次封装的详细实例

四、根据搜索条件进行搜索更新表格数据

新增一个yxt-search.vue

elementUI el-table二次封装的详细实例

<!-- yxt-search.vue -->
<template>
 <div class="yxt-search">
   <div v-for="(item,index) in searchConfig"
        :key="index"
        class="yxt-search-item">
     <el-input v-if="item.type==='input'"
               v-model="searchModel[item.key]"
               size="medium"
               :clearable="item.clearable || true"
               :placeholder="item.placeholder || '请输入'"
               :maxlength="item.maxlength"></el-input>
     <el-select v-if="item.type==='select'"
                v-model="searchModel[item.key]"
                size="medium"
                style="width: 100%"
                :clearable="item.clearable || true"
                :filterable="item.filterable || true"
                :disabled="item.disabled || false"
                :multiple="item.multiple || false"
                :allow-create="item.allowCreate"
                :placeholder="item.placeholder || '请选择'">
       <el-option v-for="(selectItem, selectIndex) in item.selectList"
                  :key="selectIndex"
                  :label="selectItem[item.listLabel]"
                  :value="selectItem[item.listValue]"></el-option>
     </el-select>
   </div>
   <div v-if="searchConfig.length" class="yxt-search-button">
     <el-button size="medium" type="primary" @click="search">搜索</el-button>
     <el-button size="medium" type="primary" plain @click="reset">重置</el-button>
     <!-- 其他的按钮需求通过插槽传入 -->
     <slot name="searchBtn" :searchData="searchModel"></slot>
   </div>
 </div>
</template>

<!-- yxt-search.vue -->
<script>
export default {
   name: 'yxtSearch',
   props: {
       searchConfig: { // 搜索条件配置项
           type: Array,
           required: true,
           default () {
               return []
           }
       },
       searchModel: { // 搜索条件绑定值
           type: Object,
           required: true,
           default () {
               return {}
           }
       },
       searchReset: { // 搜索条件默认值重置值
           type: Object
       }
   },
   data () {
       return {
       }
   },
   methods: {
       search () {
           this.$emit('search', this.searchModel)
       },
       reset () {
           if (this.searchReset) { // 如果传入有默认值,则重置后为默认值
               Object.keys(this.searchModel).forEach((item) => {
                   this.searchModel[item] = this.searchReset[item]
               })
           } else {
               Object.keys(this.searchModel).forEach((item) => {
                   this.searchModel[item] = ''
               })
           }
       }
   }
}
</script>
<style scoped lang="scss">
.yxt-search {
 display: flex;
 flex-direction: row;
 flex-wrap: wrap;
 justify-content: flex-start;
 .yxt-search-item {
   flex: 1;
   margin: 0 10px 10px 0;
   width: calc((100% - 30px) / 4);  // 这里的30px = (分布个数4-1)*间隙1px, 可以根据实际的分布个数和间隙区调整
   min-width: calc((100% - 30px) / 4);
   max-width: calc((100% - 30px) / 4);
   &:nth-child(4n) { // 去除每行最后一个(第4n个)的margin-right
     margin-right: 0;
   }
 }
 .yxt-search-button {
   margin: 0 0 10px 0;
   width: 100%;
   text-align: right;
 }
}
</style>
<!-- yxt-table.vue -->
   <yxt-search :searchConfig="searchConfig"
               :searchModel="searchModel"
               :searchReset="searchReset"
               @search="getData(1)">
     <template v-slot:searchBtn="{searchData}">
       <!-- 其他的按钮需求通过插槽传入 -->
       <slot name="searchBtn" :searchData="searchData"></slot>
     </template>
   </yxt-search>

props: {
       searchConfig: { // 搜索条件配置项
           type: Array,
           default () {
               return []
           }
       },
       searchReset: { // 搜索条件默认值重置值
           type: Object
       }
   },
   data () {
       return {
           searchModel: this.searchReset ? JSON.parse(JSON.stringify(this.searchReset)) : {}
       }
   },
   methods: {
       getData (startPage) {
           if (startPage) { // 如果传入值,则从改值的页码数开始
               this.pageInfo.startPage = startPage
           }
           let param = { // 其他的搜素条件
               ...this.searchModel
           }
           ...
       }
   }
<!-- index.vue -->
   <yxt-table :apiUrl="yxtTableList"
              :tableColumn="tableColumn"
              :searchConfig="searchConfig"
              :searchReset="searchReset">
     <template v-slot:searchBtn="{searchData}">
       <el-button size="medium" type="success" @click="handleClickExport(searchData)">导出</el-button>
     </template>
   </yxt-table>

data () {
       return {
           searchConfig: [
               { type: 'input', key: 'name' },
               { type: 'input', key: 'code' },
               { type: 'select',
                   key: 'status',
                   selectList: [
                       { code: 0, name: '打卡失败' },
                       { code: 1, name: '打卡成功' }
                   ],
                   listLabel: 'name',
                   listValue: 'code' }
           ],
           searchReset: {
               name: '张三',
               code: '',
               status: 1
           }
       }
   },
   methods: {
       handleClickExport (data) {
           console.log(data)
       }
   }

五、加载中状态和空数据状态

加载中:el-table 添加 v-loading="loading",getData里面,发送请求之前设置为true,获得数据后设置为false

elementUI el-table二次封装的详细实例

空数据:通过插槽empty设置

elementUI el-table二次封装的详细实例

 六、完整代码:

index.vue

<!-- index.vue -->
<template>
 <div>
   <yxt-table :apiUrl="yxtTableList"
              :tableColumn="tableColumn"
              :otherConfig="otherConfig"
              :dict="dict"
              selection="multiple"
              :selectable="isSelectable"
              :setupConfig="setupConfig"
              :searchConfig="searchConfig"
              :searchReset="searchReset"
              @view="view"
              @log="log">
     <!-- 图标插槽 -->
     <template v-slot:icon="{row, index}">
       <i :class="row.status ? 'el-icon-circle-check' : 'el-icon-circle-close'"></i>
     </template>
     <!-- 批量操作按钮插槽 -->
     <template v-slot:multiple-operation="{selectionData}">
       <el-button type="primary"
                  size="small"
                  @click="handleClick1(selectionData)">批量操作1</el-button>
       <el-button type="success"
                  size="small"
                  @click="handleClick2(selectionData)">批量操作2</el-button>
     </template>
     <template v-slot:searchBtn="{searchData}">
       <el-button size="medium" type="success" @click="handleClickExport(searchData)">导出</el-button>
     </template>
   </yxt-table>
   <yxt-table :apiUrl="yxtTableList"
              :tableColumn="tableColumn"
              :otherConfig="otherConfig"
              :dict="dict"
              selection="single"
              :selectable="isSelectable"
              :setupConfig="setupConfig2"
              :pagination="false"
              :expand="true"
              :emptyText="'没有数据的展示文字'">
     <!-- 图标插槽 -->
     <template v-slot:icon="{row, index}">
       <i :class="row.status ? 'el-icon-circle-check' : 'el-icon-circle-close'"></i>
     </template>
     <!-- 批量操作按钮插槽 -->
     <template v-slot:multiple-operation="{selectionData}">
       <el-button type="primary"
                  size="small"
                  @click="handleClick1(selectionData)">单选操作</el-button>
     </template>
     <template v-slot:expand="{row, index}">
       <div>
         <p>序号:{{index}}</p>
         <p>内容:{{row}}</p>
       </div>
     </template>
   </yxt-table>
 </div>
</template>

<!-- index.vue -->
<script>
import yxtTable from './yxt-table.vue'
import { yxtTableList } from 'https/yxtDemo.js'
export default {
   name: 'yxtDemoTable',
   components: {
       yxtTable
   },
   data () {
       return {
           yxtTableList,
           tableColumn: [
               { prop: 'name', label: '名称' },
               { prop: 'code', label: '编码' },
               { prop: 'status', label: '状态', dictCode: 'status' },
               { prop: 'icon', label: '图标', slot: true },
               { prop: 'phone',
                   label: '电话号码',
                   format: (row) => {
                       return `${row.name}-${row.code}(${row.phone})`
                   } }
           ],
           tableConfig: {
               stripe: 'stripe',
               border: 'border',
               height: '200',
               maxHeight: '200',
               showHeader: true
           },
           otherConfig: {
               list: 'tasks'
           },
           setupConfig: {
               width: 100,
               view: true,
               log: true
           },
           setupConfig2: {
               edit: true,
               del: true,
               log: true
           },
           dict: {
               status: [
                   { code: 0, name: '打卡失败' },
                   { code: 1, name: '打卡成功' }
               ]
           },
           searchConfig: [
               { type: 'input', key: 'name' },
               { type: 'input', key: 'code' },
               { type: 'select',
                   key: 'status',
                   selectList: [
                       { code: 0, name: '打卡失败' },
                       { code: 1, name: '打卡成功' }
                   ],
                   listLabel: 'name',
                   listValue: 'code' }
           ],
           searchReset: {
               name: '张三',
               code: '',
               status: 1
           }
       }
   },
   methods: {
       handleClick1 (selectionData) {
           console.log('1', selectionData)
       },
       handleClick2 (selectionData) {
           console.log('2', selectionData)
       },
       handleClickExport (data) {
           console.log(data)
       },
       isSelectable (row) {
           return row.selectable !== 0
       },
       view (row) {
           console.log('view', row)
       },
       log (row) {
           console.log('log', row)
       }
   }
}
</script>
<style scoped lang="scss">
.el-icon-circle-check {
 font-size: 28px;
 color: #67C23A;
}
.el-icon-circle-close {
 font-size: 28px;
 color: #F00;
}
</style>

yxt-table.vue

<!-- yxt-table.vue -->
<template>
 <div class="yxt-table">
   <yxt-search :searchConfig="searchConfig"
               :searchModel="searchModel"
               :searchReset="searchReset"
               @search="getData(1)">
     <template v-slot:searchBtn="{searchData}">
       <!-- 其他的按钮需求通过插槽传入 -->
       <slot name="searchBtn" :searchData="searchData"></slot>
     </template>
   </yxt-search>
   <!-- 批量操作按钮,因为每个需求不同,批量操作的功能也不同,所以这里只放一个插槽,不设置默认内容,所有按钮均在父级设置 -->
   <div class="multiple-operation">
     <slot name="multiple-operation"
           :selectionData="selectionData"></slot>
   </div>
   <!-- 页面主表格 -->
   <el-table :data="tableData"
             :row-key="rowKey"
             v-loading="loading"
             @selection-change="selectionChange">
     <!-- 可选框(多选) -->
     <el-table-column v-if="selection === 'multiple'"
                      type="selection"
                      align="center"
                      width="55"
                      :reserve-selection="rowKey ? true : false"
                      :selectable="selectable"/>
     <!-- 可选框(单选) -->
     <el-table-column v-else-if="selection === 'single'"
                      align="center"
                      width="30">
       <template v-slot:default="scope">
         <el-radio v-model="selectionRadio"
                   :label="scope.$index"
                   :disabled="selectable ? !selectable(scope.row) : false"
                   @change="selectionChangeSingle(scope.row)">
           {{ '' }}
         </el-radio>
       </template>
     </el-table-column>
     <!-- 展开行 -->
     <el-table-column v-if="expand"
                      type="expand">
       <template v-slot:default="scope">
         <slot name="expand"
               :row="scope.row"
               :index="scope.$index"></slot>
       </template>
     </el-table-column>
     <el-table-column v-for="(item, index) in tableColumn"
                      :key="index"
                      :prop="item.prop"
                      :label="item.label">
       <template v-if="item.slot"
                 v-slot:default="scope">
         <slot :name="item.prop"
               :row="scope.row"
               :index="scope.$index"></slot>
       </template>
       <template v-else v-slot:default="scope">
         <div v-if="item.dictCode">
           {{ scope.row[item.prop] | filterStatus(dict[item.dictCode]) }}
         </div>
         <div v-else-if="item.format">
           {{ item.format(scope.row) }}
         </div>
         <div v-else>
           {{ scope.row[item.prop] }}
         </div>
       </template>
     </el-table-column>
     <!-- 操作列 -->
     <el-table-column v-if="setupConfig.width !== 0"
                      :fixed="setupConfig.fixed"
                      :width="setupConfig.width"
                      label="操作">
       <template v-slot:default="scope">
         <slot name="setup"
               :row="scope.row"
               :index="scope.$index"></slot>
         <!-- 查看 -->
         <el-button v-if="setupConfig.view"
                    type="text"
                    @click="setupEvents('view', scope.row)">查看</el-button>
         <!-- 编辑 -->
         <el-button v-if="setupConfig.edit"
                    type="text"
                    @click="setupEvents('edit', scope.row)">编辑</el-button>
         <!-- 删除 -->
         <el-button v-if="setupConfig.del"
                    type="text"
                    @click="setupEvents('del', scope.row)">删除</el-button>
         <!-- 操作日志 -->
         <el-button v-if="setupConfig.log"
                    type="text"
                    @click="setupEvents('log', scope.row)">操作日志</el-button>
       </template>
     </el-table-column>
     <!-- 空状态 -->
     <template slot="empty">
       <p>{{ emptyText }}</p>
     </template>
   </el-table>
   <!-- 分页 -->
   <el-pagination v-if="pagination"
                  class="pagination tablePage"
                  :pager-count="5"
                  :page-sizes="pageSizes || [10, 20, 50, 100]"
                  :total="tableTotal || 0"
                  :page-size="pageInfo.pageSize || 10"
                  :current-page="pageInfo.startPage || 1"
                  layout="total, sizes, prev, pager, next, jumper"
                  @size-change="sizeChange"
                  @current-change="pageChange"></el-pagination>
 </div>
</template>

<!-- yxt-table.vue -->
<script>
import yxtSearch from './yxt-search'
export default {
   name: 'yxtTable',
   components: {
       yxtSearch
   },
   props: {
       apiUrl: { // 列表接口(必填)
           type: Function,
           required: true
       },
       tableColumn: { // 自定义列配置
           type: Array,
           default: () => []
       },
       otherConfig: { // 其他配置
           type: Object,
           default: () => {
               return {
                   list: 'list' // 接口返回数据的列表字段的字段名(因为在组件里面调接口,可能不同业务不同项目组不同一个开发者返回给前端的参数名不一致,这里进行兼容)
               }
           }
       },
       dict: { // 全部字典
           type: [Array, Object],
           default: () => []
       },
       selection: { // 是否显示可选框(多选-multiple 、单选-single )
           type: String
       },
       selectable: { // 当前行是否可选择
           type: Function
       },
       rowKey: { // 表格唯一key(适用于分页多选表格,保留之前的选择,不传则为单页选择)
           type: [Number, String, Function],
           default: ''
       },
       setupConfig: {
           type: Object,
           default: () => {
               return {
                   width: 'auto'
               }
           }
       },
       pagination: { // 是否需要分页,默认需要
           type: Boolean,
           default: true
       },
       pageSizes: { // 分页的下拉框选项
           type: Array
       },
       expand: { // 是否展开行
           type: Boolean,
           default: false
       },
       searchConfig: { // 搜索条件配置项
           type: Array,
           default () {
               return []
           }
       },
       searchReset: { // 搜索条件默认值重置值
           type: Object
       },
       emptyText: {
           type: String
       }
   },
   filters: {
       filterStatus (value, array, code = 'code', name = 'name') {
           if (!value && value !== 0) { // 要把0摘出来,一般0都是正常的数据,所以不能只用  !value
               return ''
           }
           const find = array.find(e => (e[code] === value.toString()) || (e[code] === +value)) // 字符型数值型都得匹配
           if (find) {
               return find[name]
           } else { // 没有匹配的就原样返回
               return value
           }
       }
   },
   data () {
       return {
           loading: true,
           tableData: [],
           tableTotal: 0,
           pageInfo: {
               pageSize: 10,
               startPage: 1
           },
           selectionRadio: '',
           selectionData: [],
           searchModel: this.searchReset ? JSON.parse(JSON.stringify(this.searchReset)) : {}
       }
   },
   created () {
       this.getData()
   },
   methods: {
       getData (startPage) {
           if (startPage) { // 如果传入值,则从改值的页码数开始
               this.pageInfo.startPage = startPage
           }
           this.loading = true
           const fun = this.apiUrl
           const pageInfo = { // 分页信息
               pageSize: this.pageInfo.pageSize,
               startPage: this.pageInfo.startPage
           }
           let param = { // 其他的搜素条件
               ...this.searchModel
           }
           if (this.pagination) { // 如果需要分页,则传分页信息
               param = { ...param, ...pageInfo }
           }
           fun(param).then(res => {
               setTimeout(() => {
                   this.tableData = res[this.otherConfig.list] || []
                   this.tableTotal = res.pageInfo?.total || 0
                   this.loading = false
               }, 2000)
           })
       },

// 多选,选择行数据change
       selectionChange (selection) {
           this.selectionData = selection
       },

// 单选,选择行数据change
       selectionChangeSingle (selection) {
           this.selectionData = [selection]
       },

// 操作列方法 查看/编辑/删除/操作日志
       setupEvents (setupType, row) {
           this.$emit(setupType, row)
       },

// 条数变化
       sizeChange (size) {
           this.pageInfo.startPage = 1
           this.pageInfo.pageSize = size
           this.getData()
       },

// 页码变化
       pageChange (page) {
           this.pageInfo.startPage = page
           this.getData()
       }
   }
}
</script>
<style scoped lang="scss">
.yxt-table {
 margin: 30px;
 .multiple-operation {
   margin-bottom: 10px;
 }
}
</style>

yxt-search.vue

<!-- yxt-search.vue -->
<template>
 <div class="yxt-search">
   <div v-for="(item,index) in searchConfig"
        :key="index"
        class="yxt-search-item">
     <el-input v-if="item.type==='input'"
               v-model="searchModel[item.key]"
               size="medium"
               :clearable="item.clearable || true"
               :placeholder="item.placeholder || '请输入'"
               :maxlength="item.maxlength"></el-input>
     <el-select v-if="item.type==='select'"
                v-model="searchModel[item.key]"
                size="medium"
                style="width: 100%"
                :clearable="item.clearable || true"
                :filterable="item.filterable || true"
                :disabled="item.disabled || false"
                :multiple="item.multiple || false"
                :allow-create="item.allowCreate"
                :placeholder="item.placeholder || '请选择'">
       <el-option v-for="(selectItem, selectIndex) in item.selectList"
                  :key="selectIndex"
                  :label="selectItem[item.listLabel]"
                  :value="selectItem[item.listValue]"></el-option>
     </el-select>
   </div>
   <div v-if="searchConfig.length" class="yxt-search-button">
     <el-button size="medium" type="primary" @click="search">搜索</el-button>
     <el-button size="medium" type="primary" plain @click="reset">重置</el-button>
     <!-- 其他的按钮需求通过插槽传入 -->
     <slot name="searchBtn" :searchData="searchModel"></slot>
   </div>
 </div>
</template>

<!-- yxt-search.vue -->
<script>
export default {
   name: 'yxtSearch',
   props: {
       searchConfig: { // 搜索条件配置项
           type: Array,
           required: true,
           default () {
               return []
           }
       },
       searchModel: { // 搜索条件绑定值
           type: Object,
           required: true,
           default () {
               return {}
           }
       },
       searchReset: { // 搜索条件默认值重置值
           type: Object
       }
   },
   data () {
       return {
       }
   },
   methods: {
       search () {
           this.$emit('search', this.searchModel)
       },
       reset () {
           if (this.searchReset) { // 如果传入有默认值,则重置后为默认值
               Object.keys(this.searchModel).forEach((item) => {
                   this.searchModel[item] = this.searchReset[item]
               })
           } else {
               Object.keys(this.searchModel).forEach((item) => {
                   this.searchModel[item] = ''
               })
           }
       }
   }
}
</script>
<style scoped lang="scss">
.yxt-search {
 display: flex;
 flex-direction: row;
 flex-wrap: wrap;
 justify-content: flex-start;
 .yxt-search-item {
   flex: 1;
   margin: 0 10px 10px 0;
   width: calc((100% - 30px) / 4);  // 这里的30px = (分布个数4-1)*间隙1px, 可以根据实际的分布个数和间隙区调整
   min-width: calc((100% - 30px) / 4);
   max-width: calc((100% - 30px) / 4);
   &:nth-child(4n) { // 去除每行最后一个(第4n个)的margin-right
     margin-right: 0;
   }
 }
 .yxt-search-button {
   margin: 0 0 10px 0;
   width: 100%;
   text-align: right;
 }
}
</style>

yxtTable.json

{
 "retCode": "0",
 "retMsg": "success",
 "pageInfo": {
   "total": 300
 },
 "tasks": [
   { "name": "张三",
     "code": "zhangSan",
     "status": 1,
     "icon": true,
     "phone":  "17801010101",
     "selectable": 1
   },
   { "name": "李四",
     "code": "liSi",
     "status": 0,
     "icon": false,
     "phone": "17802020202",
     "selectable": 2
   },
   { "name": "王五",
     "code": "wangWu",
     "status": 2,
     "icon": true,
     "phone": "17803030303",
     "selectable": 0
   },
   { "name": "马六",
     "code": "maLiu",
     "status": 1,
     "icon": false,
     "phone": "17804040404",
     "selectable": 2
   }
 ]
}

最后效果

elementUI el-table二次封装的详细实例

来源:https://blog.csdn.net/yerongtao/article/details/126998230

标签:el-table,二次封装
0
投稿

猜你喜欢

  • python利用JMeter测试Tornado的多线程

    2022-10-15 12:03:26
  • Pycharm学生免费专业版安装教程的方法步骤

    2022-01-11 14:39:30
  • 如何安装控制器JavaScript生成插件详解

    2024-04-10 10:51:51
  • pytorch交叉熵损失函数的weight参数的使用

    2021-02-27 15:52:31
  • 完全卸载VSCode--解决卸载重新安装后还有原来配置的问题(图解)

    2022-06-15 05:10:12
  • 在python中bool函数的取值方法

    2021-10-06 00:47:37
  • Sharding-JDBC自动实现MySQL读写分离的示例代码

    2024-01-18 00:02:35
  • 如何使用PHP中的字符串函数

    2024-05-11 10:01:59
  • python实现本地图片转存并重命名的示例代码

    2021-08-01 16:33:46
  • linux下python中文乱码解决方案详解

    2023-09-01 03:53:04
  • JavaScript简单获取页面图片原始尺寸的方法

    2024-02-23 14:23:33
  • GO语言中的方法值和方法表达式的使用方法详解

    2024-05-09 14:53:04
  • Python图像运算之图像点运算与灰度化处理详解

    2021-06-15 23:43:14
  • plsql和tsql常用函数比对

    2009-09-13 17:50:00
  • WEB2.0网页制作标准教程(1)选择什么样的DOCTYPE

    2007-11-13 12:57:00
  • Python httplib,smtplib使用方法

    2021-12-23 04:34:29
  • golang三元表达式的使用方法

    2023-08-28 14:34:09
  • 使用SQL语句去掉重复的记录【两种方法】

    2024-01-18 16:55:59
  • 不支持RSS,如何跟踪网站的内容更新?

    2008-09-08 12:38:00
  • mysql创建外键报错的原因及解决(can't not create table)

    2024-01-15 11:57:44
  • asp之家 网络编程 m.aspxhome.com