开发总是领导们的玩具,领导们的需求就是朝令夕改。这不,领导上网随便一搜一个时髦的后台框架,一眼就相中了列展示这个骚功能(注意,下面的实现都是Vue2)。具体如下:
由于用的是Element框架,本身就不提供这样的方法,没办法,这只能自己去造了。
实现模板表格封装
要实现这样的功能,基本上是需要多选框与表格相互配合好才行,由于用过像Ant Design Vue这样的框架,所以我第一时间就想到的是写对象数组,参考el-table-column的属性,我就可以得出下面的数据接口。
const tableData = [
{
title: '测试',
unit_name: '这是单位',
unit_nature_name: '这是单位性质',
nature_name: '这是职位性质'
}
]
const tableColumn = [
{
prop: 'title',
label: '职位名称',
},
{
prop: 'unit_name',
label: '单位名称',
},
{
prop: 'unit_nature_name',
label: '单位性质',
},
{
prop: 'nature_name',
label: '职位性质',
},
]
接着我只需要设计一个组件,如下:
<template>
<el-table
v-bind="$attrs"
:cell-style="{
height: '76px',
}"
:header-cell-style="{
height: '56px',
backgroundColor: '#FCF2F1',
color: '#573E41',
textAlign: 'center',
}"
>
<template v-for="(column, index) in tableColumn">
<el-table-column
:key="index"
:prop="column.prop"
:label="column.label"
:width="column.width"
align="center"
>
</el-table-column>
</template>
</el-table>
</template>
<script>
export default {
name: "HelloWorld",
props: {
tableColumn: {
type: Array,
default() {
return [];
},
},
},
};
</script>
<style scoped>
</style>
那么我就可以实现一个最基础的渲染了表格。如下:
当然,表格的需求往往没这么简单,剩下的需求实现,我就斗胆献丑了,给大家参考一下我的最终代码:
<template>
<el-table
ref="caitlynTable"
v-loading="loading"
border
:row-key="rowKey"
max-height="500px"
:cell-style="{
height: '76px',
}"
:header-cell-style="{
height: '56px',
backgroundColor: '#FCF2F1',
color: '#573E41',
textAlign: 'center',
}"
v-bind="$attrs"
@selection-change="selectionChange"
>
<el-table-column
v-if="needIndex"
:key="caitlynKey"
type="selection"
:selectable="selectable"
width="55"
align="center"
></el-table-column>
<template v-for="(column, index) in tableColumn">
<el-table-column
v-if="!column.show"
:key="index"
:prop="column.prop"
:label="column.label"
:width="column.width"
:fixed="column.fixed"
:align="column.align ? column.align : 'center'"
:formatter="
(row, column, cellValue, index) => {
if (!cellValue && cellValue !== 0) {
return '--';
} else {
return cellValue;
}
}
"
>
<template v-if="column.slotHeader" #header="scope">
<slot :name="column.slotHeader" v-bind="scope"></slot>
</template>
<template v-if="column.slotName" #default="scope">
<slot :name="column.slotName" v-bind="scope"></slot>
</template>
</el-table-column>
</template>
</el-table>
</template>
<script>
export default {
props: {
loading: {
type: Boolean,
default: false,
},
tableColumn: {
type: Array,
default() {
return [];
},
},
needIndex: {
type: Boolean,
default: false,
},
rowKey: {
type: String,
default: 'id',
},
selectionChange: {
type: Function,
default() {
return {};
},
},
selectable: {
type: Function,
default() {
return {};
},
},
},
data() {
return {
caitlynKey: 'caitlynKey',
};
},
watch: {
tableColumn: {
handler() {
this.$refs.caitlynTable.doLayout();
},
deep: true,
},
},
};
</script>
<style scoped lang="scss"></style>
这样的封装已经能实现我项目的所有需求了。由于我需要自己手动改的表格非常的多,就单单这个修改需求,我就整整做了有一个星期有余,我就开始发现了不少缺点。(列展示组件的如何实现不是本文的重点,所以就不展示相关代码)
1、tableCloumn数据格式属性过多
因为原来的表格都是用Element上面的例子做的,所以就会出现很多el-table-column的情况,由于需要写一个tableColumn的数组,我需要写很多个数据,非常的麻烦,譬如:
{
prop: '',
label: '操作',
width: '150',
fixed: 'right',
slotName: 'operation',
slotHeader: 'operateHeader'
}
记的属性过多,插槽名虽然是自定义的,但是如果这个表格非常多的插槽的时候,会不会变成插槽灾难区呢?
像上面这种没注释的,你根本不知道那个插槽是哪个,还不如el-table-column那种形式查看的实在。
2、需要兼容多个el-tabel-column的属性
我们都知道,封装兼容东西的大部分做法是,直接将他所有的功能都做了,你需要什么就自己配什么。比如我在封装这个组件的时候,基本是遇到一个问题,发现组件没有兼容,然后就需要在里面加,遇到一个加一个。其实这个里面就有心智成本,说到底这样的通用组件,避免不了有些功能没兼容的情况,你自己封装的人会很清楚,哪个属性干什么,但是对用第一次使用的人来说,他根本不知道你这个组件的功能实现需要哪个属性!然后就是去看一遍你的代码,更有甚至缺失的功能要他自己手动添加!
函数式组件的使用
基于上面的问题,我就在想,能不能不修改原来那么多el-table-column,直接就渲染他们出来,我只需要知道,哪个需要渲染而哪个不需要渲染就行了,基于这个原因,我想到了Vue提供的渲染函数:函数式组件。 可以参考这两个地址:Vue2函数式组件 函数式组件语法。那么下面展示代码:
const Table = {
functional: true,
name: 'wRenderTable',
props: {
selectionChange: {
type: Function,
default() {
return {};
},
},
tableColumn: {
type: Array,
default() {
return [];
},
},
},
render(h, context) {
const {
props,
data: { attrs },
} = context;
const cellStyle = { height: '76px' };
const headerCellStyle = {
height: '56px',
backgroundColor: '#FCF2F1',
color: '#573E41',
textAlign: 'center',
};
const column = props.tableColumn.map(item => item.label);
return (
<el-table
vLoading={attrs.loading}
{...{ attrs }}
cell-style={cellStyle}
header-cell-style={headerCellStyle}
border
row-key="id"
vOn:selection-change={props.selectionChange}
>
{context.children.map(child => {
const { propsData } = child.componentOptions;
if (!propsData.type) {
// 无数据格式化
propsData.formatter = (row, column, cellValue) => {
if (!cellValue && cellValue !== 0) {
return '--';
} else {
return cellValue;
}
};
// 默认居中
!propsData.align && (propsData.align = 'center');
}
// propsData.type是选择框时才有。
if (column.includes(propsData.label) || propsData.type) {
return child;
} else {
return null;
}
})}
</el-table>
);
},
};
export default Table;
context.children能获取到函数式组件下面所有的node节点,如下图:
那么我如何控制,到底那个列不需要渲染呢。没错,我只需要通过遍历时,从传进来的tableColumn中判断子节点是否存在,来判断是否渲染,不渲染的时候时候,返回null即可。
优点
- 最大的优点就是,我不需要删除原来一大堆的el-table-column,又去写一个繁杂麻烦的对象数组。
- 使用者没有多少心智负担,比如列展示的组件会处理最终展示的列,只需要将这个列数组传给表格组件,通过判断即可处理渲染不渲染的逻辑。(为什么需要判断呢,因为获取的节点是全部的,只能内部判断那个节点需要渲染,可以看上面的判断逻辑,很简单)
- 最重要的是,由于可以使用原来el-talbel-column,就不需要配置在组件内部配置一大堆属性,而且插槽也不需要自己去定义名字,也不会出现一大堆插槽,不知道谁是谁的局面。
- 还有一个必须需要注意的点就是,函数式组件是无状态的,也没有实例的(this),这个意味着什么,我一开始也不知道,直到我碰到那个问题——我怎么知道进来tableColumn是不是最新的。我找了很久都不知道这个怎么去解决,函数式组件也没有watch方法。无状态,所以非常依赖外部状态,当props里面的属性,更新的时候,这个函数式组件也会重新渲染,那么不就意味着,我根本不需要管tableColumn的值,因为它自动会重新熏染。
最后的吹牛
感觉组件封装就是这样的,先是找到一个可以实现功能的方案,然后就是实现功能的过程中,发现这个方案非常的麻烦,然后就开始思考能不能最小化干扰原来的东西,也能修改。这大概就是我最终选用函数式组件的原因。觉得有用的兄弟够我点个赞或者是收藏,呆在3级好久了,谢谢兄弟们了(砰砰砰)