一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情。
前言
这个系列主要是分享自己在工作中常用到的业务组件,以及如何对这些组件进行有效的封装和封装的思路。注:都是基于element ui进行二次封装。
封装组件的基本方法就是通过props和emit进行父子组件的传值和通信。利用插槽、组件等去增加组件的可扩展性和复用性。
Table组件介绍
用于展示多条结构类似的数据,可对数据进行排序、筛选、对比或其他自定义操作。table组件常用于后台管理系统,基本上是后台管理系统中使用频率最高的组件了,一个table组件具有最基本的功能就是crud。
基本的table
Table组件封装思路
了解element Table组件代码
这里以最基本的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>
table样式
根据基本的Table代码,我们可以知道:
- Table渲染的数据是一个数组,数组的每一项包含了每一列的信息,然后绑定到el-table的data属性里面。
- el-table-column为Table一行中每列的内容,一行有多少列,就写多少个el-table-column。
- 每个el-table-column中绑定了prop、label属性,他们分别对应data数据里所渲染出来的内容和每列的表头文字,注意prop的值一定要与tableData里的对象属性相同。
Table组件如何去封装
通过分析Table代码我们可以把el-table-column里面绑定的属性抽离出一个配置文件,通过对配置文件的遍历得到所有el-table-column。
配置文件代码实现
新建LTable组件
我们在components文件夹下新建一个LTable表示我们封装的Table组件。基于Table组件的基本代码,我们写下LTable下代码内容:
<template>
<div>
<el-table :data="tableData" border style="width: 100%">
<el-table-column type="index" align="center" width="50" v-if="showIndexColumn">
</el-table-column>
<el-table-column type="selection" align="center" width="55" v-if="showSelectColumn">
</el-table-column>
<template v-for="item in columnList">
<el-table-column :key="item.prop" align="center" v-bind="item">
</el-table-column>
</template>
</el-table>
</div>
</template>
<script>
export default {
props: {
columnList: {
type: Array,
default: () => []
},
showIndexColumn: {
type: Boolean,
default: false
},
showSelectColumn: {
type: Boolean,
default: false
},
requestUrl: {
type: String,
require: true
}
},
created() {
this.getData();
},
data() {
return {
tableData: [],
// defaultSlotList: ["action"]
};
},
computed: {
isDefaultSlot() {
return function(slotName) {
return this.defaultSlotList.includes(slotName);
};
}
},
methods: {
getData() {
this.api.get(this.requestUrl).then(res => {
this.tableData = res.dataList
})
},
// handleEdit(row) {
// console.log(row);
// },
// handleDelete(row) {
// console.log(row);
// }
}
};
</script>
在组件中我们需要父组件传入一个columnList,也就是前面我们说的配置文件,table的数据我们在LTable里获取,不用父组件传递过来。这样做的好处是:将通过接口获取table数据统一在LTable管理,减少父组件获取接口数据然后传递给LTable的重复代码逻辑。相应我们需要传递请求数据的接口地址。
配置文件
新建一个配置文件tableConfig.js,导出文件内容,然后再需要用到的页面引入。
export const tableConfig = {
columnList: [
{
label: "日期",
prop: "date",
sortable: true //对表格进行排序
},
{
label: "图片", //文字
prop: "imgSrc", //渲染数据对应的属性
width: "180" //每列对应宽度
},
{
label: "姓名",
prop: "name"
},
{
label: "地址",
prop: "address"
}
],
showIndexColumn: true, //是否显示table索引
showSelectColumn: true, //是否显示选择多行
requestUrl: "api/getData" //接口请求地址
};
页面内容:
<template>
<div class="app-container">
<l-table v-bind="tableConfig"></l-table>
</div>
</template>
<script>
import { tableConfig } from "./config/tableConfig.js";
export default {
components: { LTable: () => import("@/components/LTable") },
data() {
return {
tableConfig
};
},
mounted() {},
methods: {}
};
</script>
table效果
这样,一个基本的Table封装就完成了,可以看到我们在页面中的代码是非常少的,只有引入了组件,然后将配置文件绑定到组件上就可以了。
不过目前写的配置都是非常简单的,如果遇到复杂的表格内容,我们怎么办?
配置插槽
刚刚完成的小案例我们发现图片是直接显示了地址,那我们想展示图片怎么办,一般表格还会有操作列,我们怎么展示出来呢?
这种我们不能直接渲染数据,而是需要转换,比较灵活得到我们想要的的内容就需要用到插槽了。
首先我们需要在配置文件增加插槽的选项,我们这里取名slotName:
columnList: [
{
label: "日期",
prop: "date",
Sortable: true
},
{
label: "图片",
prop: "imgSrc",
width: "180",
slotName: "img"
},
{
label: "姓名",
prop: "name"
},
{
label: "地址",
prop: "address"
},
{
label: "操作",
prop: "action",
slotName: "action"
}
],
对应的LTable组件里面的渲染逻辑我们也需要调整,改为有插槽和没有插槽两种情况去渲染。表格里的插槽有些在所有表格里面都会用到比如操作,这种我们就当做默认插槽,写在LTabl里面。不是每个表格都需要用到插槽就是动态插槽了,放在对应的页面里。LTable增加插槽的代码:
<template>
<div>
<el-table :data="tableData" border style="width: 100%">
// 带有索引列
<el-table-column type="index" align="center" width="50" v-if="showIndexColumn">
</el-table-column>
// 多选列
<el-table-column type="selection" align="center" width="55" v-if="showSelectColumn">
</el-table-column>
<template v-for="item in columnList">
<el-table-column :key="item.prop" v-if="item.slotName" v-bind='item' align="center">
<template slot-scope="scope">
<!-- 动态插槽 -->
<slot v-if="!isDefaultSlot(item.slotName)" :name="item.slotName"
:row="scope.row">
</slot>
<!-- 默认插槽 -->
<slot v-else name="action">
<el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button>
<el-button size="mini" type="danger" @click="handleDelete(scope.row)">删除</el-button>
</slot>
</template>
</el-table-column>
// 直接渲染列
<el-table-column v-else :key="item.prop" align="center" v-bind='item'>
</el-table-column>
</template>
</el-table>
</div>
</template>
页面里面动态插槽内容:
<template>
<div class="app-container">
<l-table v-bind="tableConfig">
<template #img="scope">
<el-image
style="width: 100px; height: 100px"
:src="scope.row.imgSrc"
:preview-src-list="[scope.row.imgSrc]"
fit="cover"
preview-teleported
></el-image>
</template>
</l-table>
</div>
</template>
渲染出来的表格:
带有插槽表格
这样,我们就通过配置文件搭配插槽完成了Table组件的封装,我们可以自由灵活的显示我们表格里的内容。但同时,我们也发现,当我们LTable组件里涉及到的插槽越来越多的时候,里面的代码也会越来越多。随着我们的项目越来越复杂,需要的插槽可能有几十个甚至上百个,使用插槽的方式进行封装开始显示出了很多问题。我们该如何对这种情况进行优化呢?
动态组件
解决插槽存在的问题
针对上面提出的问题,我从自动化的角度进行了思考,放弃了大量使用插槽,改用组件代替插槽的形式进行优化。将插槽的类型进行汇总,分别定义不同类型的组件去替换插槽,大大减少了由插槽产生的代码。
代码实现
首先在配置文件里面添加了type属性,表示它对应哪个动态组件。还添加了cb回调函数,主要处理对数据的格式进行转化。
export const tableConfig = {
columnList: [
{
type: "text",
label: "日期",
prop: "date",
width: "180"
},
{
type: "image",
label: "图片",
prop: "imgSrc",
width: "180"
},
{
type: "text",
label: "姓名",
prop: "name"
},
{
type: "function",
label: "性别",
prop: "sex",
sortable: true,
cb: data => {
return data == 1 ? "男" : "女";
}
},
{
type: "input",
label: "地址",
prop: "address"
},
{
type: "slot",
label: "操作",
prop: "action",
slotName: "actions"
}
],
showIndexColumn: true,
showSelectColumn: true,
requestUrl: "api/getData"
};
然后在LTable组件里面做自动化处理,这里需要用到Node里面的require.context方法,他会递归获取相应文件夹下的对应类型文件。
所以我们还需要在相应文件夹下面创建相应的组件,这里我是在components文件夹下创建了control文件夹,control文件夹下放我们对应需要渲染的组件,这里我创建了四个,function、image、input、text,分别对应内容需要转化、图片展示、最基本文字、输入框。根据具体的业务创建不同的组件即可。
control文件夹
我们需要用require.context方法在LTable组件里拿到control文件夹下的所有组件。
const modules = {};
const files = require.context("../control", true, /\index.vue$/);
files.keys().forEach(item => {
const name = item.split("/")[1];
modules[`com-${name}`] = files(item).default;
});
console.log(modules, 'modules');
打印modules
注册组件:
components: {
...modules
},
我们通过动态组件的方式,根据我们要渲染的表格内容去加载不同的组件:
<el-table-column
v-for="item in columnList"
:key="item.prop"
v-bind="item"
align="center"
>
<template slot-scope="scope">
<slot
v-if="item.type === 'slot'"
:name="item.slotName"
:row="scope.row"
></slot>
<components
v-else
:row="scope.row"
:prop="item.prop"
:config="item"
:is="`com-${item.type}`"
></components>
</template>
</el-table-column>
页面里面的内容:
<template>
<div class="app-container">
<automation-table v-bind="tableConfig">
<template #actions="scope">
<el-button @click="handleClick(scope.row)" type="text" size="small"
>查看</el-button
>
<el-button type="text" size="small">编辑</el-button>
</template>
</automation-table>
</div>
</template>
最后,我们在control文件夹下,给定义的组件写上渲染逻辑:
function组件:
<template>
<div>
{{ config.cb && config.cb(row[prop]) }}
</div>
</template>
<script>
export default {
props: {
row: {
type: Object,
require: true
},
prop: {
type: String,
require: true
},
config: {
type: Object,
require: true
}
},
data() {
return {};
}
};
</script>
<style lang="scss" scoped></style>
主要调用配置文件里的cb,传入表格内容作为参数,进行我们想要的内容转化。
image组件:
<template>
<div>
<el-image
style="width: 70px; height: 70px"
:src="row[prop]"
:preview-src-list="[row[prop]]"
fit="cover"
preview-teleported
></el-image>
</div>
</template>
<script>
export default {
props: {
row: {
type: Object,
require: true
},
prop: {
type: String,
require: true
},
config: {
type: Object,
require: true
}
},
data() {
return {};
}
};
</script>
<style lang="scss" scoped></style>
主要将我们传入的文件地址,转化为图片展示出来。
input组件:
<template>
<div>
<el-input v-model="row[prop]" clearable></el-input>
</div>
</template>
<script>
export default {
props: {
row: {
type: Object,
require: true
},
prop: {
type: String,
require: true
},
config: {
type: Object,
require: true
}
},
data() {
return {};
}
};
</script>
<style lang="scss" scoped></style>
主要将我们传入的内容绑定到input输入框里面,一般进行编辑操作。
text组件:
<template>
<div>
{{ row[prop] }}
</div>
</template>
<script>
export default {
props: {
row: {
type: Object,
require: true
},
prop: {
type: String,
require: true
},
config: {
type: Object,
require: true
}
},
data() {
return {};
}
};
</script>
<style lang="scss" scoped></style>
直接展示表格内容。
最终得到的表格效果:
表格图片
总结
以上就是介绍的两种封装Table组件的方式。核心都是基于配置文件去进行渲染Table,渲染具体的内容可以使用插槽或者动态组件。如果项目中需要用到的插槽比较多,推荐使用动态组件的方式。
点个关注,互相监督学习进步!
微信公众号:CodeLee前端开发 关注可了解更多的技术文章。问题或建议,请公众号留言; 如果你觉得文章对你有帮助,点个「在看」