写在前面
用了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>
到了这一步,我们的为了方便层级关系,目录与组件设计如下:
表格的使用方法
现在封装好了,先全局注册一下.
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
还需要导入
由于不能上传附件,请参考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]();
}
}
}
};
};