前言
这个系列主要是分享自己在工作中常用到的业务组件,以及如何对这些组件进行有效的封装和封装的思路。注:都是基于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="https://www.atool.online/article/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="https://www.atool.online/article/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,渲染具体的内容可以使用插槽或者动态组件。
如果项目中需要用到的插槽比较多,推荐使用动态组件的方式。
更多关于Vue组件封装Table的资料请关注阿兔在线工具其它相关文章!