0.为什么要对Element-Plus组件封装
在进行表格类数据展示时,使用原始的组件库,需要写大量的重复代码,不易于维护。可以考虑将Element-Plus的表格类组件进行一个二次封装,我们仅仅需要往table组件内传入多个参数, 就可以实现表格数据的内容展示。在封装好组件后,对于类似的业务场景,可以直接迁移使用,仅仅需要对参数进行修改即可。
1.如何对表格原始数据进行处理
对Element-Plus组件库进行二次封装,编写一个可扩展的table组件。在很多业务场景中,对于表格内容展示,有时不仅仅要展示文本。可能还需要对原始数据进行二次处理,例如:
- 对时间字符串进行格式化
- 将原始数据的0或1转换成禁用或启用
除此之外,还有很多应用场景。因此考虑将表格组件进行封装。以下列代码片段为例:截取了应用插槽的代码块。propItem是父组件调用table组件时传入的参数,
具体的数据构成如图所示
我们在中定义了插槽slot,并且传入名字name属性。:name="propItem.slotName"
-
- 当前项不存在slotName,变为默认插槽,那么就显示原本的文本内容{{ scope.row[propItem.prop] }}
- 当前项存在slotName,变为具名作用域插槽,作用域插槽可以为父组件传递row属性。这样的话可以在父组件处来针对具体的slotName编写要填入的内容。
以下图为例,对slotName是createAt的插槽,往里填写了内容,主要做了一个时间的格式化工作。
涉及数据二次处理部分的组件代码
<el-table
:data="listData"
border
style="width: 100%"
@selection-change="handleSelectionChange"
v-bind="childrenProps"
>
<template v-for="propItem in propList" :key="propItem.prop">
<el-table-column v-bind="propItem" align="center" show-overflow-tooltip>
<!-- scope.row会存放表格中的所有行的数据,scope.row[propItem.prop]则取出某一行 -->
<template #default="scope">
<!-- 作用域插槽,向父组件传递name与row属性 -->
<!-- 插槽名也是动态的,根据父组件传入的参数slotName决定,
所以若slotName不存在,则没有name属性,则应该就认作默认插槽
-->
<slot :name="propItem.slotName" :row="scope.row">
{{ scope.row[propItem.prop] }}
</slot>
</template>
</el-table-column>
</template>
</el-table>
2.添加header与footer
2.1 header部分
table组件不仅仅需要展示基本的数据,还需要提供一些功能性的按钮,因此增加头部区域和底部区域footer,设置了插槽。定义了默认内容,并支持父组件传入新内容。
对于顶部页面header。展示表格名称,以及一个插槽。
<!-- 1.页面顶部header -->
<div class="header">
<slot name="header">
<div class="title">{{ title }}</div>
<div class="handler">
<!-- header部分的插槽 -->
<slot name="headerHandler"></slot>
</div>
</slot>
</div>
举例说明如何对header部分添加内容。下列是父组件调用table组件时为插槽填入的具体内容,实现了新建数据功能。
<!-- 1.header中的插槽 -->
<template #headerHandler>
<el-button
v-if="isCreate"
type="primary"
size="medium"
@click="handleNewClick"
>新建数据</el-button
>
</template>
2.2 footer部分
在自定义table组件中,为footer部分的插槽编写了默认内容,提供了一个分页器,需要传入page参数。并绑定了handleSizeChange和handleCurrentChange函数,更改每页数据个数,以及当前页时触发调用。会将对应的数据信息传递给父组件使用。
<!-- 3.页面底部footer -->
<div class="footer" v-if="showFooter">
<slot name="footer">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="page.currentPage"
:page-size="page.pageSize"
:page-sizes="[10, 20, 30]"
layout="total, sizes, prev, pager, next, jumper"
:total="listCount"
>
</el-pagination>
</slot>
</div>
3.组件代码
<template>
<div class="diy-table">
<!-- 1.页面顶部header -->
<div class="header">
<slot name="header">
<div class="title">{{ title }}</div>
<div class="handler">
<!-- header部分的插槽 -->
<slot name="headerHandler"></slot>
</div>
</slot>
</div>
<!-- v-bind="childrenProps" 绑定父级组件传来的contentTableConfig中的配置信息中的一部分,用于实现分级列表 -->
<!-- 2.表格内容展示 -->
<el-table
:data="listData"
border
style="width: 100%"
@selection-change="handleSelectionChange"
v-bind="childrenProps"
>
<!-- 勾选列 和 序号列 根据 showSelectColumn\showIndexColumn 来决定是否显示-->
<!-- el-table-column来显示表头 -->
<el-table-column
v-if="showSelectColumn"
type="selection"
align="center"
width="60"
></el-table-column>
<!-- label表示表头某一列具体名称 -->
<el-table-column
v-if="showIndexColumn"
type="index"
label="序号"
align="center"
width="80"
></el-table-column>
<template v-for="propItem in propList" :key="propItem.prop">
<el-table-column v-bind="propItem" align="center" show-overflow-tooltip>
<!-- scope.row会存放表格中的所有行的数据,scope.row[propItem.prop]则取出某一行 -->
<template #default="scope">
<!-- 作用域插槽,向父组件传递name与row属性 -->
<!-- 插槽名也是动态的,根据父组件传入的参数slotName决定,
所以若slotName不存在,则没有name属性,则应该就认作默认插槽
-->
<slot :name="propItem.slotName" :row="scope.row">
{{ scope.row[propItem.prop] }}
</slot>
</template>
</el-table-column>
</template>
</el-table>
<!-- 3.页面底部footer -->
<div class="footer" v-if="showFooter">
<slot name="footer">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="page.currentPage"
:page-size="page.pageSize"
:page-sizes="[10, 20, 30]"
layout="total, sizes, prev, pager, next, jumper"
:total="listCount"
>
</el-pagination>
</slot>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
props: {
title: {
type: String,
default: ''
},
listData: {
type: Array,
required: true
},
listCount: {
type: Number,
default: 0
},
propList: {
type: Array,
required: true
},
showIndexColumn: {
type: Boolean,
default: false
},
showSelectColumn: {
type: Boolean,
default: false
},
page: {
type: Object,
default: () => ({ currentPage: 0, pageSize: 10 })
},
childrenProps: {
type: Object,
default: () => ({})
},
showFooter: {
type: Boolean,
default: true
}
},
emits: ['selectionChange', 'update:page'],
setup(props, { emit }) {
const handleSelectionChange = (value: any) => {
emit('selectionChange', value)
}
const handleCurrentChange = (currentPage: number) => {
emit('update:page', { ...props.page, currentPage })
}
const handleSizeChange = (pageSize: number) => {
emit('update:page', { ...props.page, pageSize })
}
return {
handleSelectionChange,
handleCurrentChange,
handleSizeChange
}
}
})
</script>
<style scoped lang="less">
.header {
display: flex;
height: 45px;
padding: 0 5px;
justify-content: space-between;
align-items: center;
.title {
font-size: 20px;
font-weight: 700;
}
.handler {
align-items: center;
}
}
.footer {
margin-top: 15px;
.el-pagination {
text-align: right;
}
}
</style>