开门见山,我直接阐述我的问题的由来:
根据Element官方的Demo,使用其渲染一个表格需要以下代码:
<el-table
:data="data"
stripe border>
<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>
在此,我们不讨论多级表头及各种骚操作,假设项目经理提出了一个新的需求。”不同的用户表格需要展示的列不同,不同用户可以根据自己的使用偏好自定义表格列的顺序(序号列不参与)。“
我们开始着手实现这个需求,我们提出两个实现方案:1、v-if;2、v-for;
本文主要阐述以v-for来实现这一需求,因此对于方案1的实现仅仅阐述思路。v-if仅仅可以根据权限的不同实现表格展示特定的列,单表格的顺序是没法控制的,在编码的时候就被局限了。通过保留一个JSON,为表格的每个字段分配一个布尔变量,对于每个<el-table-column /> 使用v-if,编码繁琐,使用及其不便还不能完全实现需求。
下面开始阐述v-for的实现细节:
我们首先分析<el-table-column />有几种形态。1、checkbox列;2 、序号列;3、标准列(标准列很简单,就是行数据是什么样,就展示成什么样,如上述代码所示);4、自定义显示内容列(划重点);自定义列有两类,4.1是当前列的内容由行数据的多个字段确定;4.2 当前列渲染出来的html片段需要能触发事件;4.2是整个需求里最复杂的点,因为我们在v-for的时候是没法像x写寻常的时候写如下代码:
<el-table-column label="操作">
<template slot-scope="scope">
<span @click="someHandler(scope.row)">Click Me!</span>
</template>
</el-table-column>
此时,我们需要用到动态组件,先直接贴代码,随后进行解释:
html部分:
<template>
<el-table-column
:label="column.label"
:prop="column.prop"
:width="column.width"
:min-width="column.minWidth"
:header-align="column.align"
:sortable="column.sortable"
:fixed="column.fixed"
:sort-method="column.sortMethod"
show-overflow-tooltip
>
<template v-if="!column.columns" #default="{ row }">
<a
class="f-csp"
v-if="column.routeHandler"
@click="column.routeHandler(row)"
>{{ row[column.prop] }}</a>
<div v-else-if="column.html" v-html="column.html(row)"></div>
<component v-else-if="column.component" :is="column.component.name" :row="row"></component>
<div
v-else
:class="$ui.renderColumnClass(row, column)"
v-html="$ui.renderColumn(row, column)"
></div>
</template>
<template v-if="column.columns">
<base-table-column
v-for="subColumn in column.columns"
:key="subColumn.prop"
:column="subColumn"
></base-table-column>
</template>
</el-table-column>
</template>
JS部分(基于TypeScript):
import { Vue, Component, Prop } from 'vue-property-decorator';
import { CustomColumn } from '@/types';
@Component({
name: 'BaseTableColumn',
})
export default class BaseTableColumn extends Vue {
@Prop({ type: Object, required: true }) column!: CustomColumn;
created() {
if (this.column.component) {
Vue.component(this.column.component.name, {
props: {
row: {
type: Object,
required: true,
},
},
render: this.column.component.render,
});
}
}
}
在HTML部分有一个关键的标签是<component />,传送门
于是,我们在基础组件创建的时候,全局注册一个名为this.column.component.name的组件。Vue的组件html部分有2种形态,一种是写在单文件里面的html代码的形式。另一种是使用render函数的形式。其实最终都是转换为render形式,只不过前者需要经过vue的compile变成render函数的形式,使得我们开放上书写更简洁。render函数请参看:Here
基础组件编写完成,假设我们需要往我们的基础组件传递columns的数组,当我们在外部使用的时候对于这类列只需要编写代码如下:
[{
label: '评分次数',
prop: 'scoreCounter',
width: '80',
render: (col: any) => {
return typeof col == 'undefined' ? '暂无评分' : col;
},
component: {
name: 'RateEntry',
render(h) {
let row = this.$props.row;
let empty = typeof row.scoreCounter == 'undefined' || row.scoreCounter <= 0;
return h(
'span',
{
class: {
'f-csp': !empty,
},
style: {
color: empty ? 'black' : '#337ab7',
textDecoration: empty ? 'none' : 'underline',
},
on: {
click: (e: Event) => {
// do something that you want to do
},
},
},
empty ? '暂无评分' : row.scoreCounter,
);
},
},
},]
注意,在render函数里面的this并不是vm。
我在之前使用过iview,iview是通过配置实现表格列的展示,个人观点是它把这一过程于内部进行了隐藏。
写在最后:slot-scope 或者 scope 已经不再推荐使用,详情请看Here