最近做了一个后台管理系统,用的是vue+elementui,里面包含了很多表格 ,为了节省开发时间,将el-table进行了二次封装,特此记录下,有需要的也可以参考
表格封装
<template>
<div class="base-table-container">
<!-- 表格过滤 -->
<div class="filter-box">
<table-filter v-if="filterList" ref="tableFilter" :filterList="filterList" @filterChange="filterChange"></table-filter>
</div>
<!-- 添加插槽 -->
<slot name="table-top" />
<!-- 表格 -->
<div class="table-wrapper">
<el-table v-on="$listeners" v-bind="$attrs" :data="tableList||list" v-loading="loading">
<template v-for="(item, i) in columns">
<!-- 根据角色权限判断当前列是否显示 -->
<template v-if="item.condition===undefined||item.condition(item)">
<!-- 操作按钮 直接在生成操作列时 获取item.btns-->
<el-table-column :key="i" v-bind="item.attrs" v-if="item.btns">
<template slot-scope="{row}">
<template v-for="(btn,key) in item.btns(row)">
<el-button class="table-button" type="text" v-bind="btn.attrs" v-if="btn.condition===undefined||btn.condition(row)" v-on="btn.listeners" :key="key">{{btn.name}}
</el-button>
</template>
</template>
</el-table-column>
<!-- 渲染render -->
<el-table-column :key="i" v-bind="item.attrs" v-else-if="item.render">
<template slot-scope="{row}">
<expand-dom :column="item.attrs" :row="row" :render="item.render" :index="i"></expand-dom>
</template>
</el-table-column>
<!-- 状态数据 -->
<el-table-column :key="i" v-bind="item.attrs" v-else-if="item.statusList">
<template slot-scope="{row}">
<template v-for="(subItem,subI) in item.statusList">
<span v-if="subItem.value==row[item.attrs.prop]" :key="subI" :style="{...subItem.style}">{{subItem.label}}</span>
</template>
</template>
</el-table-column>
<!-- 基础数据 -->
<el-table-column :key="i" v-bind="item.attrs" v-else></el-table-column>
</template>
</template>
</el-table>
<!-- 分页 -->
<table-pagination v-if="pagination&&page&&page.total" style="margin-top:20px;" @size-change="handleSizeChange" @current-change="handleCurrentChange" v-bind="{...page,}"></table-pagination>
</div>
</div>
</template>
<script>
import tableFilter from './tableFilter'
import tablePagination from './tablePagination'
export default {
name: "BaseTable",
components: {
expandDom: {
functional: true,
props: {
row: Object, render: Function, index: Number,
column: { type: Object, default: null }
},
render: (h, ctx) => {
const params = {
row: ctx.props.row,
index: ctx.props.index
}
if (ctx.props.column) params.column = ctx.props.column
return ctx.props.render(h, params)
}
},
tableFilter,
tablePagination
},
data() {
return {
/* 分页参数 */
page: {
total: undefined,
currentPage: 1,
pageSize: 10
},
// 表格加载状态
loading: false,
// 筛选参数
form: {},
// 列表数据
list: [],
}
},
props: {
/* 表格头 */
columns: {
type: Array,
},
// 表格数据 若有接收该数据 则用该数据
tableList: {
type: Array
},
// filter配置项列表(配置过滤条件)
filterList: {
type: Array
},
// 获取列表的方法
http: {
type: Function
},
// 获取列表时的固定参数
defaultParme: {
type: Object,
default: null
},
// 后台列表数据版本是否为老版本(与老版本后台返回的列表数据格式做兼容)
isOld: {
type: Boolean,
default: false
},
// 是否展示分页组件
pagination: {
type: Boolean,
default: true
}
},
created() {
this.getList()
},
methods: {
//每页条数改变*
handleSizeChange(val) {
this.page.currentPage = 1
this.page.pageSize = val;
this.getList();
},
//当前页改变*
handleCurrentChange(val) {
this.page.currentPage = val;
this.getList();
},
/* filter条件发生改变 */
filterChange(form) {
this.form = form
this.page.currentPage = 1
this.$emit('queryForm', form)
this.getList();
},
/* filter条件发生改变 */
search() {
this.$refs.tableFilter.search()
},
/* 重置表格过滤参数 */
reset() {
this.$refs.tableFilter.reset()
},
/* 获取表格数据 */
getList() {
if (!this.http||this.loading) return
let parme = {
...this.defaultParme,
[this.isOld ? 'pageSize' : 'size']: this.page.pageSize,//每页显示的数据数
[this.isOld ? 'pageNo' : 'current']: this.page.currentPage,//当前所在页
...this.form
}
// 获取列表前做点什么
this.$emit('beforeHttp', parme)
this.loading = true
this.http(parme).then(res => {
if (res.code != 200) {
this.$message.error(res.message || '数据加载错误!')
return
}
const d = res.data
// 后端接口框架更改 导致总数用total字段接收 之前框架用的count
this.page.total = this.isOld ? d.count : d.total
let list = this.isOld ? d.list : d.records
this.list = list
// 数据获取成功
this.$emit('currentData',res.data)
}).finally(_ => this.loading = false)
},
},
};
</script>
<style scoped lang="scss">
.table-wrapper {
padding: 20px;
background-color: #fff;
}
.base-table-container {
margin: 20px;
}
.filter-box {
margin-bottom: 20px;
}
.table-button{
margin-left: 0;
&:not(:last-child){
margin-right: 10px;
}
}
</style>
筛选项组件 tableFilter 封装
<template>
<div class="filter-wrapper" v-if="formList.length">
<el-form ref="form" :inline="true" v-bind="$attrs">
<template v-for="(item, key) in formList">
<template v-if="item.condition===undefined||item.condition(item)">
<!-- 文本输入框 -->
<el-form-item :label="item.name" :key="key" v-if="item.type == 'input'">
<el-input v-model="item.value" v-on="item.listeners" v-bind="item.attrs" clearable @change="sendForm">
</el-input>
</el-form-item>
<!-- 自动搜索 -->
<el-form-item :label="item.name" :key="key" v-if="item.type == 'autocomplete'">
<el-autocomplete v-model="item.value" v-on="item.listeners" v-bind="item.attrs" clearable @select="sendForm">
</el-autocomplete>
</el-form-item>
<!-- select选择器 -->
<el-form-item :label="item.name" :key="key" v-if="item.type == 'select'">
<el-select v-model="item.value" v-on="item.listeners" v-bind="item.attrs" clearable @change="sendForm">
<el-option label="全部" value="">
</el-option>
<el-option v-for="sub in item.options" :key="sub[item.props?item.props.value:'value']" :label="sub[item.props?item.props.label:'label']" :value="sub[item.props?item.props.value:'value']">
</el-option>
</el-select>
</el-form-item>
<!-- 日期选择器 -->
<el-form-item :label="item.name" :key="key" v-if="item.type == 'date'">
<el-date-picker clearable v-model="item.value" v-on="item.listeners" v-bind="item.attrs" :picker-options="pickerOptions" @change="sendForm" start-placeholder="开始日期" end-placeholder="结束日期" range-separator="-">
</el-date-picker>
</el-form-item>
<!-- 级联选择器 -->
<el-form-item :label="item.name" :key="key" v-if="item.type == 'cascader'">
<el-cascader clearable v-model="item.value" v-on="item.listeners" v-bind="item.attrs" @change="sendForm">
</el-cascader>
</el-form-item>
<!-- 区间输入 -->
<el-form-item :label="item.name" :key="key" v-if="item.type == 'range'">
<div style="width:300px;display:flex;">
<el-input v-model="item.value[0]" :placeholder="item.attrs.startPlaceholder" v-on="item.listeners" v-bind="item.attrs" @change="sendForm" clearable></el-input>
<span style="margin:0 10px;">-</span>
<el-input v-model="item.value[1]" :placeholder="item.attrs.endPlaceholder" v-on="item.listeners" v-bind="item.attrs" @change="sendForm" clearable></el-input>
</div>
</el-form-item>
<!-- 按钮 -->
<el-form-item :key="key" v-if="item.type == 'button'">
<el-button v-bind="item.attrs" v-on="item.listeners">{{ item.name }}</el-button>
</el-form-item>
</template>
</template>
</el-form>
</div>
</template>
<script>
export default {
name: "TableFilter",
data() {
return {
formList: [],//表单数据
pickerOptions: {//日期选择器参数
shortcuts: [{
text: '最近一周',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
picker.$emit('pick', [start, end]);
}
}, {
text: '最近一个月',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
picker.$emit('pick', [start, end]);
}
}, {
text: '最近三个月',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
picker.$emit('pick', [start, end]);
}
}]
},
};
},
props: {
filterList: {
type: Array,
default() {
return [];
},
},
},
created() {
/* 获取筛选项列表 用于数据的动态绑定 */
this.init()
},
methods: {
/* 重置 */
reset() {
this.init()
this.sendForm()
},
// 搜索 查询
search() {
this.sendForm()
},
/* 初始化数据 */
init() {
this.$set(this, 'formList', this.filterList)
this.formList.forEach((item, i) => {
if (item.type === 'range') {
this.$set(this.formList[i], 'value', ['', ''])
return
}
this.$set(this.formList[i], 'value', '')
})
},
/* 筛选条件发生变化 input输入不会触发该事件 但是input后点击enter键会触发 */
sendForm() {
let form = {}
this.formList.forEach(item => {
if (item.value !== '') {
if (item.type == 'date'&&item.props) {
if (item.value[0] !== '') {
form[item.props.start] = item.value[0] + ' 00:00:00'
}
if (item.value[1] !== '') {
form[item.props.end] = item.value[1] + ' 23:59:59'
}
return
}
if (item.type == 'date'&&item.prop) {
if (item.value[0] !== '') {
form[item.prop] = item.value + ' 23:59:59'
}
return
}
if (item.type === 'range') {
if (item.value[0] !== '') {
form[item.props.start] = item.value[0]
}
if (item.value[1] !== '') {
form[item.props.end] = item.value[1]
}
return
}
// 返回的数据是数组 但是后台需要用多个字段接收 因此遍历配置的 prop 分别接收value(数组)中的值
if (item.type == "cascader") {
if (item.props) {
item.props.forEach((lable, index) => form[lable] = item.value[index] || '')
return
}
}
form[item.prop] = item.value
}
})
// 筛选条件发生变化 暴露filterChange方法,并返回参数form(当前的筛选条件)
this.$emit('filterChange', form)
}
},
};
</script>
<style scoped lang="scss">
.filter-wrapper {
padding: 20px;
padding-bottom: 0;
background-color: #fff;
}
</style>
分页组件 tablePagination
<template>
<div class="container">
<!-- 分页 -->
<el-pagination
v-on="$listeners"
v-bind="$attrs"
:pageSizes="[10, 20, 50, 100, 300, 500]"
:layout="'total, sizes, prev, pager, next, jumper'"
></el-pagination>
</div>
</template>
<script>
export default {
data() {
return {
};
},
};
</script>
<style scoped lang="scss">
</style>
使用效果,超级简单,原本一个页面几百行代码,现在几十行解决,而且还方便维护
<template>
<my-table ref="myTable" :isOld="true" :filterList="filterList" :columns="columns" :http="findPurchasePage"></my-table>
</template>
<script>
import { findPurchasePage } from '@/api/supplier/purchase'
import { receivGoods } from '@/api/orderManage/list'
import ShopItem from "@/components/Base/shopItem";
import MyTable from "@/components/MyTable";
import PreSaleOrderDetail from "./components/preSaleOrderDetail";
import {
orderTypeList,//单据类别
payStateList,//支付状态
billStateList//单据状态
} from '@/utils/statusManage'
export default {
name: "purchaseOrderList",
components: {
ShopItem,
MyTable,
},
data() {
return {
findPurchasePage,
form: {},
filterList: [
{ name: '单据号', type: 'input', prop: 'conditions', attrs: { placeholder: '单据号', style: { width: '380px' } } },
{ name: '单据类别', type: 'select', prop: 'orderType', options: orderTypeList },
{ name: '单据状态', type: 'select', prop: 'orderStatus', options: billStateList },
{ name: '创建日期', type: 'date', props: { start: 'beginTime', end: 'endTime' }, attrs: { type: "daterange", valueFormat: "yyyy-MM-dd" } },
{ name: '支付状态', type: 'select', prop: 'payState', options: payStateList },
{ name: '查询', type: 'button', attrs: { type: 'primary' }, listeners: { click: _ => this.$refs.myTable.search() } },
{ name: '重置', type: 'button', listeners: { click: _ => this.$refs.myTable.reset() } },
],
/* 表头 */
columns: [
{ attrs: { prop: 'id', label: '单据号', minWidth: '140', align: 'center' } },
{ attrs: { prop: 'orderId', label: '商品', minWidth: '300', align: 'center' }, render: (h, { row }) => h(ShopItem, { props: { list: row.orderItemList } }) },
{ attrs: { prop: 'supplierName', label: '供应商信息', width: '140', align: 'center' } },
{ attrs: { prop: 'orderType', label: '单据类别', width: '140', align: 'center' }, statusList: orderTypeList },
{ attrs: { prop: 'payState', label: '支付状态', width: '140', align: 'center' }, statusList: payStateList },
{ attrs: { prop: 'orderStatus', label: '单据状态', width: '140', align: 'center' }, statusList: billStateList },
{ attrs: { prop: 'totalMoney', label: '总金额', width: '160', align: 'center' } },
{ attrs: { prop: 'orderTime', label: '创建时间', minWidth: '100', align: 'center' } },
{
attrs: { label: '操作', minWidth: '150', fixed: 'right', align: 'center' }, btns: row => [
{ name: '立即支付', condition: row => row.payState == 0 && row.orderStatus == 1, listeners: { click: _ => this.toPay(row) } },
{ name: '确认收货', condition: row => row.orderStatus == 3, listeners: { click: _ => this.confirmReceipt(row) } },
]
},
]
};
}
};
</script>