在我们实际开发过程中UI组件的二次封装是一种基操。可以节省代码的臃余。让整个页面看上去更加整洁,增强代码的可维护性。
背景
公司是做传统业务开发的,后台管理系统中百分之80的页面都是表格数据展示,为了便于后期维护,便考虑将el-table进行封装,决心做一款灵活,简单,便维护的表格组件。
功能如下:
- 表格自带分页
- 表格自动后台请求数据
- 支持多选、单选
- 兼容el-table一切方法与属性
基础用法
<template>
<zt-table :tableOption="tableOption"
ref="ztTable">
<el-table-column label="操作"
width="160">
<template slot-scope="scope">
<el-button type="text">编辑</el-button>
<el-button type="text">删除</el-button>
<el-button type="text">查看</el-button>
</template>
</el-table-column>
</zt-table>
</template>
<script>
import ztTable from '@/components/ztTable.vue' //这里可以挂在全局
export default {
components: { ztTable },
data() {
return {
/* -----------表格配置------------*/
tableOption: {
api: {
url: '/hisEmergencyResponse/responsePage', //列表api配置
type: 'post', //列表api请求类型
params: { //params里面的属性,发请求的时候会携带上用于数据的条件筛选
name:'' //名称搜索
phone:'' //电话号码
},
},
option: [
//表格参数配置
this.T('姓名', 'source'),
this.T('电话', 'eventTypeCenter'),
this.T('岗位', 'happenLocation'),
this.T('部门', 'happenTime'),
this.T('职责', 'responseLevel')
],
},
}
},
methods:{
//该方法可以单独抽离到一个公共的工具JS文件,或者 minxin 里面
T(label, prop, tag, width, fixed, sortable){ //这些属性的配置可参照el-table
return {
label, //表头
prop, //值
tag, //插槽
width, //单元格宽度
fixed, //是否固定单元格
sortable, //是否可以排序
};
}
}
}
</script>
扩展方法
插槽
场景:比如表格里面有一些字段是字典数据返回0或1,需要根据字典匹配中文
<template>
<zt-table :tableOption="tableOption"
ref="ztTable">
<template #sex='{data}'>
{{data.sex == 0? '男':'女'}}
</template>
</zt-table>
</template>
<script>
import ztTable from '@/components/ztTable.vue' //这里可以挂在全局
export default {
components: { ztTable },
data() {
return {
/* -----------表格配置------------*/
tableOption: {
api: {
url: '/hisEmergencyResponse/responsePage', //列表api配置
type: 'post', //列表api请求类型
params: { //params里面的属性,发请求的时候会携带上用于数据的条件筛选
name:'' //名称搜索
phone:'' //电话号码
},
},
option: [
//表格参数配置
this.T('性别', 'sex','sex'),
],
},
}
},
}
</script>
渲染,分页,单选、多选回调
场景:比如表格数据加载完成,需要做某件事!表格点击分页,需要做某件事!单选多选功能等等业务需求
<template>
<zt-table :tableOption="tableOption"
ref="ztTable"
:isSelect='true' //开启多选,默认关闭false
:isRadio='true' //开启单选,默认关闭false
@onsuccess='onsuccess' //渲染数据成功的回调
@handleCurrentChange='handleCurrentChange' //分页的回调
@handleRadioChange='handleRadioChange' //表格单选
@handleSelectionChange='handleSelectionChange' //表格多选
>
</zt-table>
</template>
<script>
import ztTable from '@/components/ztTable.vue' //这里可以挂在全局
export default {
components: { ztTable },
methods:{
onsuccess(data){
//data是列表数据
},
handleCurrentChange(page){
//page当前页面
},
handleRadioChange(data){
//当前选中的数据集合
},
handleSelectionChange(e){
}
}
}
</script>
渲染拦截
场景:比如表格数据字段需要修改,就必须在渲染之前做一次拦截,修改完成再次渲染
<template>
<zt-table :tableOption="tableOption"
ref="ztTable">
</zt-table>
</template>
<script>
import ztTable from '@/components/ztTable.vue' //这里可以挂在全局
export default {
components: { ztTable },
data() {
return {
/* -----------表格配置------------*/
tableOption: {
api: {
url: '/hisEmergencyResponse/responsePage', //列表api配置
type: 'post', //列表api请求类型
params: {},
callback(data) { //拦截器函数
data.forEach(item=>{
item.userName = item.name //修改一下数据字段
})
return data //这里要记得写return 不然数据没法渲染
}
},
option: [
//表格参数配置
this.T('性别', 'sex','sex'),
],
},
}
},
}
</script>
手动刷新表格数据
场景:有时候不想表格自己发请求加载,需要自己手动加载
<template>
<zt-table :tableOption="tableOption"
ref="ztTable"
:isLoadData='false' //该属性默认开启true
>
</zt-table>
</template>
<script>
import ztTable from '@/components/ztTable.vue' //这里可以挂在全局
export default {
components: { ztTable },
methods:{
onTable(){
this.$refs.ztTable.reload() //调用这个方法,能重新加载数据
}
}
}
</script>
讲到这里组件的大致用法就讲完了。接下来把源码分享给大家。有需求的同学可以copy到自己的项目里面试一下哦。
注意:源码里很多功能,是与当前项目需求产生耦合的。并非开箱即用。需伙伴们自行删减!这里只是给伙伴们一种思路与参照
<template>
<div>
<el-table :data="tableData"
:summary-method="getSummaries"
v-bind="$attrs"
:row-key="getRowKey"
v-on="$listeners"
ref="table"
stripe
border
:header-cell-style="isHeadColor ? headStyle : {}"
:empty-text="emptyText"
:highlight-current-row="true"
@selection-change="handleSelectionChange"
@row-click="handleRadioChange"
:height="height ? height=='auto'?'':height : tableHeight">
<el-table-column type="selection"
width="55"
:reserve-selection="true"
v-if="isSelect" />
<el-table-column align="center"
v-if="isRadio"
width="50"
label="选择">
<template slot-scope="scope">
<el-radio :label="scope.row.id"
v-model="rowRadio"></el-radio>
</template>
</el-table-column>
<el-table-column type="index"
v-if="isOrder"
label="序号"
width="60"
align="center">
<template slot-scope="scope">
<span v-text="getIndexNum(scope.$index)"></span>
</template>
</el-table-column>
<el-table-column v-for="(item, index) in tableOption.option"
:key="index"
:prop="item.prop"
:label="item.label"
:width="item.width ? item.width : null"
:sortable="item.sortable ? item.sortable : false"
:show-overflow-tooltip="item.showTip || false"
:align="item.align || 'left'"
:fixed="item.fixed || false"
:header-align="item.headerAlign || 'left'">
<template v-if="item.children && item.children.length">
<el-table-column v-for="(childrenItem, childrenIndex) in item.children"
:key="childrenIndex"
:prop="childrenItem.prop"
:label="childrenItem.label"
:width="childrenItem.width ? childrenItem.width : null"
:sortable="childrenItem.sortable ? childrenItem.sortable : false"
:align="childrenItem.headerAlign || 'left'"
:fixed="childrenItem.fixed || false"
:header-align="childrenItem.headerAlign || 'left'">
<template slot-scope="scope">
<template v-if="childrenItem.tag">
<slot :name="childrenItem.tag"
:data="scope.row"></slot>
</template>
<span v-else>{{ scope.row[childrenItem.prop] }}</span>
</template>
</el-table-column>
</template>
<template slot-scope="scope">
<template v-if="item.tag">
<slot :name="item.tag"
:data="scope.row"></slot>
</template>
<span v-else>{{ scope.row[item.prop] }}</span>
</template>
</el-table-column>
<slot />
</el-table>
<el-pagination background
v-if="isPage"
small
class="table_pagination"
:current-page.sync="page.pageNo"
:page-sizes="page.list"
:page-size="page.pageSize"
:layout="page.layout"
:total="page.total"
@current-change="handleCurrentChange"
@size-change="handleSizeChange" />
</div>
</template>
<script>
export default {
props: {
emptyText: {
type: String,
default: '暂无数据',
},
tableOption: {
type: Object,
},
// 选中数据
selectData: {
type: Array,
default: () => {
return []
},
},
height: {
type: String,
},
isOrder: {
//是否开启序号
type: Boolean,
default: true,
},
isRadio: {
//是否选择
type: Boolean,
default: false,
},
isSelect: {
//是否选择
type: Boolean,
default: false,
},
RowKey: {
//选中的字段名
type: String,
default: 'id',
},
isLoadData: {
//是否一开始就加载
type: Boolean,
default: true,
},
isPage: {
//是否开启分页
type: Boolean,
default: true,
},
isHeadColor: {
//是否开启表头部样式
type: Boolean,
default: false,
},
},
data() {
return {
rowRadio: '',
headStyle: { color: '#909399', background: '#fafafa' },
tableData: [],
tableHeight: 0,
page: {
list: [5, 10, 15, 20, 25, 50, 100, 200, 400],
total: 0,
pageSize: 10,
pageNo: 1,
layout: 'total,sizes,prev,pager,next,jumper',
},
isShow: true,
}
},
updated() {
this.doLayout()
},
destroyed() {
window.removeEventListener('resize', this.getTableHeight)
window.removeEventListener('resize', this.doLayout)
},
created() {
if (this.isLoadData) {
this.initTable()
}
window.addEventListener('resize', this.getTableHeight)
window.addEventListener('resize', this.doLayout)
},
mounted() {
this.$nextTick(() => {
this.getTableHeight()
this.doLayout()
})
},
methods: {
getRowKey(row) {
return row[this.RowKey]
},
toogleExpand(row) {
let $table = this.$refs.table
this.tableData.map((item) => {
if (row.id != item.id) {
$table.toggleRowExpansion(item, false)
}
})
$table.toggleRowExpansion(row)
},
doLayout() {
setTimeout(() => {
if (this.$refs['table']) {
this.$refs['table'].doLayout()
}
}, 1000)
},
getDataItems(dataItems, data) {
dataItems.push(data)
if (data.list && data.list.length > 0) {
data.list.forEach((d) => {
this.getDataItems(dataItems, d)
})
}
return dataItems
},
getSummaries(param) {
const { columns, data } = param
const sums = []
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = '合计'
return
}
const values = data.map((item) => Number(item[column.property]))
if (!values.every((value) => isNaN(value))) {
sums[index] = values.reduce((prev, curr) => {
const value = Number(curr)
if (!isNaN(value)) {
return prev + curr
} else {
return prev
}
}, 0)
}
})
return sums
},
/* 表格序号计算 */
getIndexNum($index) {
return (this.page.pageNo - 1) * this.page.pageSize + $index + 1
},
// 重新加载数据(重置到第一页)
reload() {
if (this.page.pageNo !== 1) {
this.page.pageNo = 1
}
this.isShow = false
this.initTable()
},
async initTable() {
let { url, type, params } = this.tableOption.api
const new_params = Object.assign(
{
current: this.page.pageNo,
size: this.page.pageSize,
page: this.page.pageNo,
},
params
) // 右值覆盖左值,返回左值
try {
let { data } = await this.getData(url, type || 'post', new_params)
if (typeof data == 'string') {
data = JSON.parse(data)
data.records = data.list || data.content
}
this.tableData = data.records || data.tableData || data
this.doLayout()
this.setRowSelection(this.selectData) //设置选中数据
// this.tableData = createTree(this.tableData);
if (this.tableOption.api.callback) {
this.tableData = this.tableOption.api.callback(this.tableData)
}
this.page.total = Number(data.total || data.totalElements || 0)
this.tableOption.callback && this.tableOption.callback(data)
if (this.isShow) {
this.$emit('onsuccess', this.tableData)
}
console.log('表格数据', this.tableData)
} catch (err) {
console.log('报错了', err)
}
},
getData(url, type, params) {
return new Promise(function (resolve, reject) {
Object.toAjaxJson(url, null, params, true, type, function (res) {
resolve(res)
})
})
},
handleCurrentChange(currentPage) {
this.page.pageNo = currentPage
this.$emit('handleCurrentChange', currentPage)
this.initTable()
},
handleSizeChange(size) {
this.page.pageSize = size
this.page.pageNo = 1
this.$emit('handleSizeChange', size)
this.initTable()
},
handleRadioChange(row, index) {
this.rowRadio = row.id
this.$emit('onRadio', row)
},
handleSelectionChange(e) {
this.$emit('onTab', e)
},
isLastPage() {
// 根据总的数据条数total和一页显示的数据条数,得到总页数
let lastPage = Math.ceil(this.page.total / this.page.pageSize)
// 判断当前页是否是最后一页
if (this.page.pageNo === lastPage) {
if (this.page.pageNo <= 1) {
this.page.pageNo = 1
return
}
this.page.current--
}
},
//设置选中数据
setRowSelection(selectData) {
if (selectData && selectData.length > 0) {
this.$nextTick(() => {
this.tableData.forEach((k1) => {
k1.isSelect = 0
selectData.forEach((k2) => {
if (k2.id === k1.id) {
k1.isSelect = 1
this.$refs.table.toggleRowSelection(k1, true)
}
})
})
})
}
},
selectable(rows) {
if (rows.isSelect == 1) {
return false
} else {
return true
}
},
getTableHeight() {
let parent = this.$parent
let seachHeight = 0
while (parent.$options._componentTag != 'ztPage') {
parent = parent['$parent']
}
parent.$children.forEach((el) => {
if (el.$options._componentTag == 'zt-search') {
seachHeight = el.$el.clientHeight
}
})
this.tableHeight = window.innerHeight - seachHeight - 200
},
},
}
</script>
<style lang="scss" scoped>
.table_pagination {
margin-top: 10px;
width: 100%;
display: flex;
justify-content: center;
}
::v-deep .el-radio__label {
display: none;
}
::v-deep .el-table__expand-icon {
display: none;
}
</style>