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

目录
  • 前言
  • 一、先上页面最终效果
  • 二、创建目录yxt-table如下图
  • 二、数据自动获取和刷新
  • 三、自定义列配置
  • 四、根据搜索条件进行搜索更新表格数据
  • 五、加载中状态和空数据状态
  • 六、完整代码:
  • 总结

前言

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

1、数据自动获取和刷新

2、自定义列配置

3、分页功能

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

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

一、先上页面最终效果

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

index.vue为父级页面

yxt-table.vue为表格组件

二、数据自动获取和刷新

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

    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>

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

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: '打卡成功' }
                ]
            }
        }
    }

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

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

使用插槽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})`
                    } }
            ]
        }
    }

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

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)
        }
    }

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()
        }
    }

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>

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

新增一个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>
<!-- 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

空数据:通过插槽empty设置

六、完整代码:

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二次封装的文章就介绍到这了,更多相关el-table二次封装内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • vue2.x el-table二次封装实现编辑修改

    目录 computed中 接收以下参数 colData 控制列数的数组 表格可编辑 select绑定相关 最近开发新业务,看到有些功能一样的表格,想着封装一个组件,记录一下: 最终实现效果 大概实现是: 封装一个通用的表格 接收两个数组, 一个控制行数,一个控制列数 表格可进行编辑操作 官方文档 图中我们可以看到: :data="tableData"中 传入的tableData用来控制表格行数 el-table-column用来控制列数,有几个el-table-column, 表格就有

  • Element使用el-table组件二次封装

    目录 前言 一.安装引入 二.封装功能 三.样式覆盖 四.使用组件 前言 在vue开发中使用element-ui的el-table时一般都需要进行封装以便于复用,提高开发效率,减少重复代码,这篇博客对el-table进行简单的二次封装: 一.安装引入 Element官方文档 npm安装element-ui: npm i element-ui -S 可以看文档按需引入,这里为了方便直接全局引入了: import Vue from 'vue' import App from './App.vue'

  • vue elementui二次封装el-table带插槽问题

    目录 elementui二次封装el-table带插槽 element-ui table组件的二次封装(插槽的形式) 1.外层table组件封装 2.内层table组件代码 elementui二次封装el-table带插槽 子组件table封装 html部分 <template>   <div     v-loading="loading">     <el-table       ref="tableData"       :stri

  • element el-table表格的二次封装实现(附表格高度自适应)

    前言 在公司实习使用vue+element-ui框架进行前端开发,使用表格el-table较为多,有些业务逻辑比较相似,有些地方使用的重复性高,如果多个页面使用相同的功能,就要多次重复写逻辑上差不多的代码,所以打算对表格这个组件进行封装,将相同的代码和逻辑封装在一起,把不同的业务逻辑抽离出来.话不多说,下面就来实现一下吧. 一.原生el-tbale代码--简单の封装 这里直接引用官方的基础使用模板,直接抄过来(✪ω✪),下面代码中主要是抽离html部分,可以看出每个el-table-column

  • 基于Vue+element-ui 的Table二次封装的实现

    本人第一次写这个 写的不好还望指出来 作为一个由于公司产品的升级然促使我从一个后端人员自学变成前端的开发人员 ! 公司做的数据管理系统所以离不开表格了 然后表格样式统一啥的就想到封装一个element-ui 里面的table+Pagination了 效果图 表格组件的引入与使用 <com-table title="监测数据" v-model="tableData4" @selection-change="handleSelectionChange&q

  • vue-cli对element-ui组件进行二次封装的实战记录

    目录 为什么要element对进行二次封装? 如何对element对进行二次封装? 总结 为什么要element对进行二次封装? 1.element-ui组件的部分样式不满足当前项目的需求. element-ui组件的样式是固定的,比如我们常用的那些组件,table,button,icon,tab等等.当我们需要的样式和element组件有偏差的时候,我们可以通过针对element组件进行二次封装,然后通过Vue.component()方法,定义到全局,来解决我们当前的项目需求. 2.eleme

  • 基于element-ui表格的二次封装实现

    在项目中经常会使用到element的表格,如果每次都cv,也确实有点麻烦,基于这个情况我对表格进行了二次封装 写一个Table组件 首先先写表格样式 <el-table :data="tableData" :header-cell-style="headerStyle" :height="height" :border="border" @selection-change="handleSelectionCha

  • Vue技巧Element Table二次封装实战示例

    目录 前言 思考 实践 filterPane.vue 明确目标 传入数据结构整理 timeSelect elinput elselect 开始封装 tablePane.vue 明确目标 传入数据结构整理 tool cols pageData operation tablePane.vue配置项Cols详解 开始封装 实战 结尾 前言 由于重构后台管理项目中有好多表格页面, 举个栗子 这表格看着还挺好看,写起来叫人直呼XX,多动脑子少掉发,少走弯路多省鞋. 写了一个后感觉太麻烦了,于是我奋笔疾书,

  • vue axios二次封装的详细解析

    axios的二次封装 视频讲解 npm i axios //下载axios 首先创建两个文件夹在src目录下:api和config 先在config文件夹下建立一个index.js:具体代码如下: export default{ baseUrl:{ dev: "http://localhost:8082/mhy", //开发环境 pro: "http://xxx.xx.xx.xx:8082/mhy", //上线环境 } } 当然我这里是因为我主要写后端springb

  • elementUi 中table表尾插入行的实例

    目录 elementUi table表尾插入行 element table 自定义表尾 先看最终实现的效果 实现过程 elementUi table表尾插入行 <template> <div> <el-table stripe class="jx-table" :data="tableData" border ref="table" style="width: 100%"> <el-t

  • elementui实现预览图片组件二次封装

    起因 在elementui组件库中,el-image组件有个预览图片功能,比较简洁,但是和图片绑定在一起,我们只想要一个单纯的预览组件,传递一个图片,通过事件方式,显示大图 分析 通过查看elementui的代码,发现在image(el-image组件)目录中里面有一个image-viewer组件, el-image组件的预览功能也是通过这一个组件实现的,只是官方没有把这个组件单独暴露出来 这里就比较简单了,我们可以使用手动导入组件的方式,引入image-viewer组件 image-viewe

  • Python如何实现Paramiko的二次封装

    Paramiko是一个用于执行SSH命令的Python第三方库,使用该库可实现自动化运维的所有任务,如下是一些常用代码的封装方式,多数代码为半成品,只是敲代码时的备份副本防止丢失,仅供参考. 目前本人巡检百台设备完全无压力,如果要巡检过千台则需要多线程的支持,过万台则需要加入智能判断等. 实现命令执行: 直接使用过程化封装,执行CMD命令. import paramiko ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(par

  • 详解Flutter中网络框架dio的二次封装

    其实dio框架已经封装的很好了,但是在实战项目中,为了项目可以统一管理,还是需要对dio框架进行二次封装. 整体思路:一般情况下,后台返回的数据我们可以分为两部分 1.状态数据 2.渲染数据 状态数据就是接口有没有正常返回数据相关的数据,这部分数据跟业务无关,我们可以封装起来统一管理,渲染数据就是我们渲染页面所需要的数据,这块的数据需要我们自己处理. 接下来我们就主要处理渲染数据这块的内容,我定义了两个函数,渲染数据可能为一个对象或者一个数组,我做了分别处理,定义两个函数来接受渲染数据. //

随机推荐