本文主要是帮助提炼业务封装能力。
ElementUI和i-View应该是前端使用较多的两个UI框架了吧~
然而在使用ElementUI中的table组件的时候很多小伙伴肯定是这样:
<el-table :data="data" stripe border style="width: 100%">
<el-table-column prop="id" label="ID"/>
<el-table-column prop="name" label="名称"/>
<el-table-column prop="price" label="金额"/>
</el-table>
显示字段不多,乍一看好像没什么问题~~
现在产品告诉你,在金额前面加个¥,然后你一顿巴拉巴拉:
<el-table :data="data" stripe border style="width: 100%">
<el-table-column prop="id" label="ID"/>
<el-table-column prop="name" label="名称"/>
<el-table-column label="金额">
<template slot-scope="scope">
¥{{scope.row.price}}
</template>
</el-table-column>
</el-table>
乍一看好像也没什么问题~~
那么现在看这个:
<el-table :data="dataList" stripe border style="width: 100%">
<el-table-column prop="date" label="订单ID"/>
<el-table-column prop="name" label="名称" width="150"/>
<el-table-column label="班级名称" width="150"/>
<el-table-column label="期别">
<template slot-scope="scope">¥{{ scope.row.xxx }}</template>
</el-table-column>
<el-table-column label="老师">
<template slot-scope="scope">¥{{ scope.row.xxx }}</template>
</el-table-column>
<el-table-column label="重复周期">
<template slot-scope="scope">¥{{ scope.row.xxx}}</template>
</el-table-column>
<el-table-column prop="xxx" label="开课时间"/>
<el-table-column prop="xxx" label="操作时间"/>
<el-table-column prop="xxx" label="操作人"/>
......
</el-table>
是不是有超级多的代码冗余?
有的小伙伴肯定会说用v-for循环下不就行了,但是如果需要在字段上加前缀、后缀、或者一个 el-table-column里有多个字段显示呢?每次使用都各种判断?不,这显然不是我们想看到的。
我们首先看i-view是怎么样的实现:
<template>
<Table :columns="columns" :data="data1"></Table>
</template>
<script>
export default {
data () {
return {
columns: [
{
title: 'Name',
key: 'name'
},
{
title: 'Age',
key: 'age'
},
{
title: 'Address',
key: 'address'
}
]
}
}
}
</script>
可以看到是通过一个columns的props数组对象自定义表头~
实现基本配置功能
我们来用Element初步实现下:
首先创建一个CommTable.vue文件。
这个文件有两个props,分别是data(table的数据)columns(配置表头及显示内容)。
// CommTable.vue
<template>
<el-table :data="data">
<template v-for="({label, prop, width, fixed}, index) in columns" :key="index">
<el-table-column
:key="index"
:prop="prop"
:label="label"
:width="width || 'auto'"
:fixed="fixed || false"
/>
</el-table>
</template>
<script>
export default {
props: {
data: {
type: Array,
default: () => []
},
columns: {
type: Array,
default: () => []
}
}
}
</script>
使用:
<template>
<comm-table :data="data" >
</template>
<script>
import CommTable from './CommTable'
export default {
components: {
CommTable
}
data(){
return {
data: [],
columns: [
{ label: '名称', prop: 'name', width: 200, fixed: 'left'}
...
]
}
}
}
</script>
怎么样,是不是非常简单?上文已经配置好了基本的width、fixed属性的使用
实现前缀、后缀功能
接下来实现前缀和后缀,首先上配置:
// 省略部分代码
columns: [
{ label: '金额', prop: 'price', prefix: '¥', suffix: '元'}
]
最常见的就是在金额前面加“¥”符号,后面加单位“元”这种需求了。
代码实现:
// CommTable.vue
<template>
<el-table :data="data">
<template v-for="({label, prop, width, fixed, prefix, suffix}, index) in columns">
<el-table-column
v-if="!prefix && !suffix"
:key="index"
:prop="prop"
:label="label"
:width="width || 'auto'"
:fixed="fixed || false"
/>
<el-table-column
v-else
:key="index"
:label="label"
:width="width || 'auto'"
:fixed="fixed || false"
>
<template slot-scope="{row, index}">
<!-- 当存在前缀或后缀的时候就显示出对应的前缀或后缀 -->
<span>{{ prefix }}{{ row[prop] }}{{ suffix }}</span>
</template>
</el-table-column>
</template>
</el-table>
</template>
<script>
export default {
props: {
data: {
type: Array,
default: () => []
},
columns: {
type: Array,
default: () => []
}
}
}
</script>
使用:
// 省略部分代码
data: [
{ name: '张三', price: 50 },
{ name: '李四', price: 80 }
],
columns: [
{ label: '名称', prop: 'name', width: 200},
{ label: "金额", prop: "price", prefix: "¥", suffix: "元" }
]
效果:
要实现的效果:
// 省略部分代码
data: [
{ name: "小学一年级", price: 50, signup: 6, payment: 5, total: 30 },
{ name: "初中一年级", price: 80, signup: 10, payment: 15, total: 50 },
],
columns: [
{ label: "班级", prop: "name", width: 200 },
{ label: "金额", prop: "price", prefix: "¥", suffix: "元" },
{ label: "已报名/已缴费/总人数", prop: ['signup', 'payment', 'total'] }
]
可以看到在要多字段显示的columns里prop的值变成了一个数组,大家肯定猜到要怎么实现了
实现单列多字段功能
上代码
<template>
<div id="app">
<el-table :data="data">
<template
v-for="({label, prop, width, fixed, prefix, suffix, separator = '/'}, index) in columns"
>
<el-table-column
v-if="!prefix && !suffix && !Array.isArray(prop)"
:key="index"
:prop="prop"
:label="label"
:width="width || 'auto'"
:fixed="fixed || false"
/>
<el-table-column
v-else
:key="index"
:label="label"
:width="width || 'auto'"
:fixed="fixed || false"
>
<template slot-scope="{row}">
<!-- 当存在前缀或后缀的时候就显示出对应的前缀或后缀 -->
<template v-if="Array.isArray(prop)">
<span
v-for="(item, index) in prop"
:key="index"
>{{ prefix }}{{ row[item] }}{{ suffix }}{{ index === prop.length - 1 ? '' : separator }}</span>
</template>
<template v-else>
<span>{{ prefix }}{{ row[prop] }}{{ suffix }}</span>
</template>
</template>
</el-table-column>
</template>
</el-table>
</div>
</template>
// 省略部分代码
新加了一个配置属性 separator 分隔符的配置,默认为 / 。然后对prop判断是否为一个数组,是的话进行遍历显示即可。
好了,以上就是今天的全......不,怎么可能这么简单!下面来看看这个效果:
看配置:
data() {
return {
data: [
{
name: "小学一年级",
price: 50,
signup: 6,
payment: 5,
total: 30,
children: [
{ studentName: '小王', sex: '男', age: 18, phone: '13800000000' },
{ studentName: '小董', sex: '男', age: 18, phone: '13800000000' },
{ studentName: '小朱', sex: '男', age: 18, phone: '13800000000' }
]
}
],
columns: [
{ label: "班级", prop: "name" },
{ label: "金额", prop: "price", prefix: "¥", suffix: "元" },
{ label: "已报名/已缴费/总人数", prop: ['signup', 'payment', 'total'], width: 160 },
[
{ label: "学生", prop: "studentName" },
{ label: "性别", prop: "sex" },
{ label: "年龄", prop: "age" },
{ label: "电话", prop: "phone" },
]
]
};
}
可以看到columns里面的需要子集显示的数据被一个数组包裹起来了
而后端返回的子集数据也是被一个children字段所包裹住的。
实现子列功能
实现:
<template>
<div id="app">
<el-table :data="data">
<template
v-for="({label, prop, width, fixed, prefix, suffix, separator = '/', _child}, index) in columns"
>
<el-table-column
v-if="!prefix && !suffix && !Array.isArray(prop) && !_child"
:key="index"
:prop="prop"
:label="label"
:width="width || 'auto'"
:fixed="fixed || false"
/>
<el-table-column
v-else
:key="index"
:label="label"
:width="width || 'auto'"
:fixed="fixed || false"
>
<template slot-scope="{row}">
<!-- 当是子集的时候 -->
<template v-if="_child">
<!-- 循环当前的children数据 -->
<div v-for="(child, index) in row[childType]" :key="index" class="comm-table-child">
<!-- 判断是否多字段显示,是则遍历否则直接显示 -->
<template v-if="Array.isArray(prop)">
<span
v-for="(item, index) in prop"
:key="index"
>{{ prefix }}{{ child[item] }}{{ suffix }}{{ index === prop.length - 1 ? '' : separator }}</span>
</template>
<span v-else>{{ prefix }}{{child[prop]}}{{ suffix }}</span>
</div>
</template>
<template v-else>
<template v-if="Array.isArray(prop)">
<span
v-for="(item, index) in prop"
:key="index"
>{{ prefix }}{{ row[item] }}{{ suffix }}{{ index === prop.length - 1 ? '' : separator }}</span>
</template>
<span v-else>{{ prefix }}{{ row[prop] }}{{ suffix }}</span>
</template>
</template>
</el-table-column>
</template>
</el-table>
</div>
</template>
<script>
export default {
props: {
data: {
type: Array,
default: () => []
},
columns: {
type: Array,
default: () => []
},
childType: {
type: String,
default: 'children'
}
},
created() {
this.columns.forEach((item, index) => {
if (Array.isArray(item)) {
item.forEach(item => item._child = true)
this.columns.splice(index, 1, ...item)
}
})
}
};
</script>
<style>
.comm-table-child {
position: relative;
}
.comm-table-child:not(:first-child) {
padding-top: 10px;
}
.comm-table-child:not(:first-child)::after {
content: "";
position: absolute;
left: -10px;
top: 5px;
background: #ebeef5;
width: calc(100% + 20px);
height: 1px;
}
</style>
这里就需要CSS的支持了。在初始化阶段先遍历columns这个数组,判断item是否为数组,是的话展开并替换掉原来的数组。
并且(重点)给定一个其为子数组的标识(这里我习惯在自定义添加的属性前面加上_来区分)。
这里props还新增了一个childType属性,并给定其默认值为children对应data数据里的子集字段children。
tips:这里在子组件修改了父组件的数据,但是由于其引用类型不会报错,但还是推荐深拷一份进行操作~
实现render功能
来看看i-view的columns配置里:
// 部分代码省略
columns: [
{
title: 'Name',
key: 'name',
render: (h, params) => {
return h('div', [
h('Icon', {
props: {
type: 'person'
}
}),
h('strong', params.row.name)
]);
}
}
]
可以看到直接把render的写在了配置文件,第一个参数h就是this.$createElement了!知道这样就好实现了。
上配置:
columns: [
{
label: "班级",
prop: "name",
render: (h, row) => {
return h('strong', row.name)
}
},
{ label: "金额", prop: "price", prefix: "¥", suffix: "元" },
{ label: "已报名/已缴费/总人数", prop: ['signup', 'payment', 'total'], width: 160 },
[
{ label: "学生", prop: "studentName" },
{ label: "性别/年龄", prop: ['sex', 'age'] },
{ label: "电话", prop: "phone" },
]
]
这里只实现一个加粗的name字段。
实现代码:
<template>
<div id="app">
<el-table :data="data">
<template
v-for="({label, prop, width, fixed, prefix, suffix, separator = '/', _child, _render}, index) in columns"
>
<el-table-column
v-if="!prefix && !suffix && !Array.isArray(prop) && !_child && !_render"
:key="index"
:prop="prop"
:label="label"
:width="width || 'auto'"
:fixed="fixed || false"
/>
<el-table-column
v-else
:key="index"
:label="label"
:width="width || 'auto'"
:fixed="fixed || false"
>
<template slot-scope="{row}">
<template v-if="_child">
<div v-for="(child, index) in row.children" :key="index" class="comm-table-child">
<template v-if="Array.isArray(prop)">
<span
v-for="(item, index) in prop"
:key="index"
>{{ prefix }}{{ child[item] }}{{ suffix }}{{ index === prop.length - 1 ? '' : separator }}</span>
</template>
<span v-else>{{ prefix }}{{child[prop]}}{{ suffix }}</span>
</div>
</template>
<!-- 当存在前缀或后缀的时候就显示出对应的前缀或后缀 -->
<template v-else>
<template v-if="Array.isArray(prop)">
<span
v-for="(item, index) in prop"
:key="index"
>{{ prefix }}{{ row[item] }}{{ suffix }}{{ index === prop.length - 1 ? '' : separator }}</span>
</template>
<template v-else>
<span v-if="_render">
<!-- 重点!!! 调用一个函数并把render方法处理后的VNODE以参数形式传入,然后在slot里显示出来 -->
{{ createNode('_render_'+index, _render(row)) }}
<slot :name="'_render_'+index"></slot>
</span>
<span v-else>{{ prefix }}{{ row[prop] }}{{ suffix }}</span>
</template>
</template>
</template>
</el-table-column>
</template>
</el-table>
</div>
</template>
// 省略部分代码
created() {
this.columns.forEach((item, index) => {
if (Array.isArray(item)) {
item.forEach(item => item._child = true)
this.columns.splice(index, 1, ...item)
}
if (item.render && typeof item.render === 'function') {
item._render = row => item.render(this.$createElement, row)
}
})
},
methods: {
createNode(key, vnode) {
this.$slots[key] = vnode
}
}
效果:
以上只实现了非多级字段的render函数,还有操作按钮、子列的checkbox拓展等更多功能可以实现,可拓展性非常高~
结语
Element、i-view这类框架只是提供了一些常用的组件功能,项目中不能只单纯的做各种搬运工作!要做到按需封装,解决项目里存在的各类痛点、代码冗余等问题才是一个合格的前端工程师应该做的。
最后,2019~一起加油!