写在前面

用了element-ui的小伙伴们都应该知道,官方给的案例中,el-table是最简单的,未经过二次封装写起来肯定是如下:

<template>
    <el-table
      :data="tableData"
      style="width: 100%">
      <el-table-column
        prop="date"
        label="日期"
        width="180">
      </el-table-column>
      <el-table-column
        prop="name"
        label="姓名"
        width="180">
      </el-table-column>
      <el-table-column
        prop="address"
        label="地址">
      </el-table-column>
    </el-table>
  </template>

  <script>
    export default {
      data() {
        return {
          tableData: [{
            date: '2016-05-02',
            name: '王小虎',
            address: '上海市普陀区金沙江路 1518 弄'
          }, {
            date: '2016-05-04',
            name: '王小虎',
            address: '上海市普陀区金沙江路 1517 弄'
          }, {
            date: '2016-05-01',
            name: '王小虎',
            address: '上海市普陀区金沙江路 1519 弄'
          }, {
            date: '2016-05-03',
            name: '王小虎',
            address: '上海市普陀区金沙江路 1516 弄'
          }]
        }
      }
    }
  </script>

看起来不美观吧,要是多了的话肯定会写很长很长.废话不多说,现在开始封装:

封装el-table-column

原生的普通列如下:

<el-table-column
        prop="date"
        label="日期"
        width="180">
      </el-table-column>

多级表头列:

<el-table-column
      prop="date"
      label="日期"
      width="150">
    </el-table-column>
    <el-table-column label="配送信息">
      <el-table-column
        prop="name"
        label="姓名"
        width="120">
      </el-table-column>
      <el-table-column label="地址">
        <el-table-column
          prop="province"
          label="省份"
          width="120">
        </el-table-column>
        <el-table-column
          prop="city"
          label="市区"
          width="120">
        </el-table-column>
        <el-table-column
          prop="address"
          label="地址"
          width="300">
        </el-table-column>
        <el-table-column
          prop="zip"
          label="邮编"
          width="120">
        </el-table-column>
      </el-table-column>
    </el-table-column>

不太好看是吧,因为要兼容多级,而且在未知层级的情况下,name就要用到一个方法.递归组件,
现在开始封装如下:

//取名 Column.vue

<template>
    <el-table-column
        :show-overflow-tooltip="col.key !== 'operation' && !col.overflow"
        :prop="col.key"
        :align="col.align || 'center'"
        :width="col.width || 'auto'"
        :label="col.label"
        :fixed="col.fixed"
    >
        <template slot-scope="{row}">
            <slot :row="row" :col="col" />
        </template>
        <template v-if="col[children] && col[children].length">
            <Column v-for="(item,k) in col[children]" :key="k" :col="item">
                <template slot-scope="{row}">
                    <slot :prow="row" :row="row[col.key] || row" :pcol="col" :col="item" />
                </template>
            </Column>
        </template>
    </el-table-column>
</template>
<script>
export default {
    name: 'Column',
    props: {
        col: {
            type: Object,
            required: true
        },
        children: { //child识别字段,用于识别多级表头字段
            type: String,
            required: false,
            default: `children`
        }
    }
};
</script>

到这一步了,表格的列就完成封装了,现在要进行封装表格的容器组件了.如下:

封装el-table

// 取名: AppTable.vue

<template>
    <div class="app-table">
        <!-- 普通表格 -->
        <el-table
            ref="DefaultTable"
            :data="table.data"
            :row-key="table.rowKey"
            :tree-props="table.treeProps"
            :height="table.height"
            :cell-style="table.cellStyle"
            :header-cell-style="table.headerCellStyle"
            :highlight-current-row="table.highlight || false"
            :border="table.border"
            :default-expand-all="table.defaultExpandAll"
            @selection-change="handleCurrentSelectionChange"
            @row-click="handleCurrentRowClick"
        >
            <el-table-column
                v-if="table.indexShow"
                type="index"
                width="70"
                align="center"
                :label="table.indexTitle"
                :index="indexMethod"
            />
            <el-table-column
                v-if="table.selection"
                type="selection"
                align="center"
                :reserve-selection="true"
                width="55"
            />
            <template v-if="table.tableType == 1">
                <column
                    v-for="(c,k) in table.cols"
                    :key="k"
                    :col="c"
                    :children="table.children"
                >
                    <template slot-scope="data">
                        <slot name="column" :data="data" />
                    </template>
                </column>
            </template>
            <template v-else-if="table.tableType == 2">
                <template v-for="(col,k) in table.cols">
                    <el-table-column
                        v-if="col.types && col.types.includes(include)"
                        :key="k"
                        :show-overflow-tooltip="col.key !== 'operation' && !col.overflow"
                        :align="col.align || 'center'"
                        :width="col.width || 'auto'"
                        :label="col.label"
                    >
                        <template slot-scope="{$index,row}">
                            <slot name="column" :data="{row,col,index:$index}" />
                        </template>
                    </el-table-column>
                </template>
            </template>
        </el-table>
        <el-pagination
            v-if="!table.hidePagination"
            background
            class="table-pagination"
            :current-page="table.params.page"
            :page-size="table.pagination.sizes[0]"
            :page-sizes="table.pagination.sizes"
            :layout="table.pagination.layout"
            :total="table.total"
            @current-change="handleCurrentPageChange"
            @size-change="handleCurrentSizeChange"
        />
        <!-- 其它组件 如表格的dialog -->
        <slot name="other" />
    </div>
</template>
<script>
import util from '@/lib/util';
import Column from './Column';
export default {
    components: { Column },
    props: {
        config: {
            required: false,
            type: Object,
            default: () => null
        },
        include: {
            type: [String, Number, Object],
            default() {
                return {
                    include: null
                };
            }
        }
    },
    data() {
        return {
            table: {
                indexShow: true,
                border: true,
                page: 1, //页码索引
                total: 0, //总页码
                tableType: 1, //1 普通表格 2多类型表格
                hidePagination: false, //是否隐藏分页器
                selection: false, //表格的多选
                rowKey: null, //行数据的 Key
                treeProps: undefined, //渲染嵌套数据的配置选项[Object] { hasChildren: 'hasChildren', children: 'children' }
                height: undefined,
                headerCcellStyleellStyle: null,
                defaultExpandAll: false, //是否默认展开所有行,当 Table 包含展开行存在或者为树形表格时有效
                headerCellStyle: null,
                indexTitle: '序号', //  序号显示标题
                data: [], //表格的数据
                cols: [], //表格的列配置
                pagination: {
                    layout: 'total, sizes, prev, pager, next, jumper', //配置
                    sizes: [10, 20, 30, 40, 50] //每页x条 默认大小为第一个
                }
            },

            //导出数据配置
            excel: {
                exportData: [],
                tHeader: [],
                filterVal: [],
                fileName: `导出数据 ${util.getThisTime()}` //表名+ 日期
            }
        };
    },
    watch: {
        config: { //监听配置的变化
            handler(val, oldVal) {
                if (val) { //读取配置
                    for (const key in val) {
                        this.table[key] = val[key];
                    }
                }
            },
            deep: true,
            immediate: true
        }
    },
    methods: {

        /**
         * 翻页
         */
        handleCurrentPageChange(page) {
            this.$emit(`pageChange`, page);
        },

        /**
         * 每页条数
         */
        handleCurrentSizeChange(size) {
            this.$emit(`sizeChange`, size);
        },

        /**
         * 多选回调
         */
        handleCurrentSelectionChange(val) {
            this.$emit(`selection-change`, val);
        },

        /**
         * 行点击回调
         */
        handleCurrentRowClick (row, event, column) {
            this.$emit(`handleRowClick`, row, event, column);
        },

        /**
         * 选中指定的行
         */
        toggleRowSelection(row) {
            console.log(`选中指定的行`, row);
            this.$refs.DefaultTable.toggleRowSelection(row);
        },

        /**
         * 取消多类型表格选中
         */
        clear() {
            this.$refs.DefaultTable.clearSelection();
        },

        /**
         * 获取表格组件实例
         */
        getAppTable() {
            return this.$refs.DefaultTable;
        },

        /**
        * 序号
        */
        indexMethod(index) {
            if (this.table && this.table.params && this.table.params.page && this.table.params.limit) {
                return (index + 1) + (this.table.params.page - 1) * this.table.params.limit;
            } else {
                return (index + 1);
            }
        },

        /**
         * 导出全部
         * @param list 需要导出的数据列表严格按照 label key的格式
         * @formatJson 过滤函数
         */
        async exportExcel ({ data, ignores = [], format, fileName = '导出数据' }) {
            try {
                const cols = this.table.cols.filter((item) => !ignores.includes(item.key)); //过滤忽略的列
                if (!cols.length) return alert('Export Col Error!');
                const tHeader = cols.map(item => item.label); //Excel表头
                const list = this.____formatJson(data, cols.map(item => item.key), format); //格式化list的数据
                if (!list || list && list.length == 0) return alert('无数据导出!');
                if (!tHeader) return alert('Export Error!');
                const excel = await import ('@/assets/scripts/vendor/Export2Excel');
                excel.export_json_to_excel(tHeader, list, `${fileName} - ${util.getThisTime()}`);
            } catch (error) {
                console.error(`导出数据出错`, error);
            }
        },

        /**
         * 导出过滤器
         * @callback 过滤器函数会执行N次,每次回调参数中v,list中的数据,list中的key值
         * @description 私有方法不对外暴露
         */
        ____formatJson (list, filterVal, callback) {
            if (list && list.length > 0 && filterVal && filterVal.length > 0) {
                return list.map(v => filterVal.map(j => {
                    const res = callback ? callback(v, j) : v[j] || String(v[j]) || '-';
                    //数字类型的需要转换为string类型,否则会被excel转为科学计数法显示
                    return typeof res === 'number' ? String(res) : res;
                }));
            }
            return list;
        }
    }
};
</script>
<style>
.table-pagination {
    margin-top: 30px;
}
</style>

到了这一步,我们的为了方便层级关系,目录与组件设计如下:

element中的table 将表头第五列和第六列合并_vue

表格的使用方法

现在封装好了,先全局注册一下.

import Vue from 'vue';
import AppTable from './AppTable'; //(新表)
Vue.component('AppTable',AppTable);

在组件模板中使用,普通类型的表格:

<template>
    <app-table ref="AppTable" :config="table" @pageChange="handleCurrentChange" @sizeChange="handleSizeChange">
        <template slot="column" slot-scope="{data}">
            <template v-if="data.col.key === 'operation'">
                <el-button-group>
                    <el-button title="更新章节" type="primary" icon="el-icon-refresh" @click="handleUpdateBook(data.row)" />
                    <el-button title="删除" type="danger" icon="el-icon-delete" @click="handleDel(data.row)" />
                </el-button-group>
            </template>
            <template v-else>
                {{ data.row[data.col.key] || '-' }}
            </template>
        </template>
    </app-table>
</template>
<script>
import pager from '@/mixins/pager';
export default {
    name: 'Books',
    mixins: [pager()],
    data () {
        return {
            table: {
                params: { //查询参数
                    title: null,
                    author: null,
                    page: 1, //获取第几页的数据,默认为1
                    limit: 10//每页数据条数,默认为10
                },
                data: [], //表格数据
                total: 0, //总页数
                cols: [ //表格列配置
                    {
                        key: 'title',
                        label: '书名'
                    },
                    {
                        key: 'author',
                        label: '作者'
                    },
                    {
                        key: 'image',
                        label: '图片'
                    },
                    {
                        key: 'chapterCount',
                        label: '章节数'
                    },
                    {
                        key: 'readCount',
                        label: '阅读数'
                    },
                    {
                        key: 'type',
                        label: '分类'
                    },
                    {
                        key: 'sourceName',
                        label: '来源名称'
                    },
                    {
                        key: 'sourceUrl',
                        label: '来源地址'
                    },
                    {
                        key: 'status',
                        label: '状态'
                    },
                    {
                        key: 'createdTime',
                        label: '创建时间'
                    },
                    {
                        key: 'updatedTime',
                        label: '修改时间'
                    },
                    {
                        key: 'operation',
                        width: '180px',
                        label: '操作'
                    }
                ]
            }
        };
    },
    created() {
        this.init();
    },

    methods: {

        async init () {
            try {
            //   const { data: { total, list } } = http
                this.table.data = [];
                this.table.total = 0;
            } catch (error) {
                console.error(error);
            }
        },

        /**
         * 删除
         */
        handleDel ({ stid, ids }) {
            this.$confirm(`此操作将会删除此数据,是否继续?`, '提示', {
                cancelButtonText: '取消',
                confirmButtonText: '确定',
                type: 'warning',
                center: true,
                customClass: 'bg-warning'
            }).then(async () => {
            //todo...
            }).catch(() => {});
        },

        /**
         * 更新章节
         */
        handleUpdateBook ({ bookId }) {
            this.$confirm(`此操作将会更新此书籍的章节,是否继续?`, '提示', {
                cancelButtonText: '取消',
                confirmButtonText: '确定',
                type: 'warning',
                center: true,
                customClass: 'bg-warning'
            }).then(async () => {
            //todo
            }).catch(() => {});
        },

        /**
         * 批量删除
         */
        handleSelectionChange(list) {
            this.deleteData.ids = list.map(item => item.stid);
        },

        /**
         * 导出表格的数据
         */
        exportExcel() {
            this.$refs.AppTable.exportExcel({
                data: this.table.data,
                ignores: ['operation'],
                fileName: '书籍数据',
                format: (data, key) => {
                // if (key == 'roleList') {
                //     const roleList = data[key].map((item) => {
                //         return item.roleName;
                //     });
                //     return roleList;
                // } else {
                //     return data[key];
                // }
                    return data[key];
                }
            });
        }
    }
};
</script>

组件模板中使用多级表头的表格:

<template>
    <app-table ref="AppTable" :config="table" @pageChange="handleCurrentChange" @sizeChange="handleSizeChange">
        <template slot="column" slot-scope="{data}">
            <template v-if="data.col.key === 'operation'">
                <el-button-group>
                    <el-button title="更新章节" type="primary" icon="el-icon-refresh" @click="handleUpdateBook(data.row)" />
                    <el-button title="删除" type="danger" icon="el-icon-delete" @click="handleDel(data.row)" />
                </el-button-group>
            </template>
            <template v-else>
                {{ data.row[data.col.key] || '-' }}
            </template>
        </template>
    </app-table>
</template>
<script>
import pager from '@/mixins/pager';
export default {
    name: 'Books',
    mixins: [pager()],
    data () {
        return {
            table: {
                params: { //查询参数
                    title: null,
                    author: null,
                    page: 1, //获取第几页的数据,默认为1
                    limit: 10//每页数据条数,默认为10
                },
                data: [], //表格数据
                total: 0, //总页数
                cols: [ //表格列配置
                    {
                        key: 'title',
                        label: '书名',
                        children: [ //多级表头只需要把这里配置一下即可
                            {
                                key: 'author',
                                label: '作者',
                                width: '150px',
                                fixed: true //冻结列
                            },
                            {
                                key: 'image',
                                label: '图片',
                                width: '100px',
                                fixed: true
                            },
                            {
                                key: 'chapterCount',
                                label: '章节数',
                                width: '100px',
                                fixed: true
                            },
                            {
                                key: 'readCount',
                                label: '阅读数'
                            }
                        ]
                    },
                    {
                        key: 'type',
                        label: '分类'
                    },
                    {
                        key: 'sourceName',
                        label: '来源名称'
                    },
                    {
                        key: 'sourceUrl',
                        label: '来源地址'
                    },
                    {
                        key: 'status',
                        label: '状态'
                    },
                    {
                        key: 'createdTime',
                        label: '创建时间'
                    },
                    {
                        key: 'updatedTime',
                        label: '修改时间'
                    },
                    {
                        key: 'operation',
                        width: '180px',
                        label: '操作'
                    }
                ]
            }
        };
    },
    created() {
        this.init();
    },

    methods: {

        async init () {
            try {
            //   const { data: { total, list } } = http
                this.table.data = [];
                this.table.total = 0;
            } catch (error) {
                console.error(error);
            }
        },

        /**
         * 删除
         */
        handleDel ({ stid, ids }) {
            this.$confirm(`此操作将会删除此数据,是否继续?`, '提示', {
                cancelButtonText: '取消',
                confirmButtonText: '确定',
                type: 'warning',
                center: true,
                customClass: 'bg-warning'
            }).then(async () => {
            //todo...
            }).catch(() => {});
        },

        /**
         * 更新章节
         */
        handleUpdateBook ({ bookId }) {
            this.$confirm(`此操作将会更新此书籍的章节,是否继续?`, '提示', {
                cancelButtonText: '取消',
                confirmButtonText: '确定',
                type: 'warning',
                center: true,
                customClass: 'bg-warning'
            }).then(async () => {
            //todo
            }).catch(() => {});
        },

        /**
         * 批量删除
         */
        handleSelectionChange(list) {
            this.deleteData.ids = list.map(item => item.stid);
        },

        /**
         * 导出表格的数据
         */
        exportExcel() {
            this.$refs.AppTable.exportExcel({
                data: this.table.data,
                ignores: ['operation'],
                fileName: '书籍数据',
                format: (data, key) => {
                // if (key == 'roleList') {
                //     const roleList = data[key].map((item) => {
                //         return item.roleName;
                //     });
                //     return roleList;
                // } else {
                //     return data[key];
                // }
                    return data[key];
                }
            });
        }
    }
};
</script>
写在后面

到这里的话文章已经差不多了,有可能在copy的小伙伴发现,有部分依赖项为安装,因为缺少了xlsx.js的表格导出组件.需要安装

npm i xlsx --save

还需要导入

element中的table 将表头第五列和第六列合并_封装_02


由于不能上传附件,请参考GitHub上的代码部分,地址如下:

https://github.com/langyuxiansheng/biuxs/tree/master/service.biuxs.com/client/components

分页器组件:
//pager.js

/**
 * 分页器回调
 * @param callbackName 分页回调函数名称
 */
export default (callbackName = 'init') => {
    return {
        methods: {
            /**
             * 分页翻页
             */
            handleCurrentChange(page) {
                if (this.table && this.table.params && this.table.params.page) {
                    this.table.params.page = page;
                    this[callbackName]();
                };
            },

            /**
             * 分页大小
             */
            handleSizeChange(size) {
                if (this.table && this.table.params && this.table.params.limit) {
                    this.table.params.limit = size;
                    this[callbackName]();
                }
            }
        }
    };
};