我们需要实现的功能
- 展示数据(带边框,紧凑型/松散型,斑马条纹)
- 选中数据(单选/全选)
- 展示排序
- 固定表头/列
- 可展开
组件的基本结构
- table.vue
<template>
<div>
<table>
<thead>
<tr>
<th>#</th>
<th v-for="column in columns">
{{column.text}}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(item,index) in dataSource">
<td>{{index}}</td>
<template v-for="column in columns">
<!--显示dataSource中对应表头字段里的内容-->
<td>{{item[column.field]}}</td>
</template>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
name: "LiFaTable",
props: {
columns: {
type: Array,
required: true
},
dataSource: {
type: Array,
required: true
}
}
}
</script>
- demo.vue
<lf-table :columns="columns" :data-source="dataSource"></lf-table>
data() {
return {
columns: [
//表头每一列显示的文本和字段
{text: '姓名', field: 'name'},
{text: '分数', field: 'score'}
],
dataSource: [
{id: 1, name: '发发', score: 100},
{id: 2, name: '琳琳', score: 99}
]
}
},
实现checkbox全选和单选
思路: 通过单向数据流,外界好传入一个selectedItem,table接受这个selectedItem,默认为空数组
单选:对表单内容的checkbox添加change事件onChangeItem把每一列行的index,item还有原生event,如果是选中状态就把传入的所有数据的数组dataSource作为参数通过refs.a.indeterminate = true,其他情况都等于false
- demo.vue
<div style="margin: 28px">
<lf-table :columns="columns" :data-source="dataSource"
bordered :selected-item.sync="selectedItem"
></lf-table>
</div>
- table.vue
<template>
<div class="lifa-table-wrapper">
<table class="lifa-table" :class="{bordered,compact,striped}">
<thead>
<tr>
<th>
<input type="checkbox" @change="onChangeItemAll($event)" ref="a">
</th>
<th v-if="numberVisible">#</th>
<th v-for="column in columns">
{{column.text}}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(item,index) in dataSource">
<th>
<input type="checkbox" @change="onChangeItem(item, index, $event)"
:checked="onChecked(item)"
>
</th>
<td v-if="numberVisible">{{index+1}}</td>
<template v-for="column in columns">
<!--显示dataSource中对应表头字段里的内容-->
<td>{{item[column.field]}}</td>
</template>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
name: "LiFaTable",
props: {
columns: {
type: Array,
required: true
},
dataSource: {
type: Array,
required: true
},
selectedItem: {
type: Array,
default: ()=>[]
},
striped: {
type: Boolean,
default: true
},
//是否显示索引
numberVisible: {
type: Boolean,
default: false
},
//是否带边框
bordered: {
type: Boolean,
default: false
},
//是否是紧凑型
compact: {
type: Boolean,
default: false
}
},
methods: {
onChangeItem(item, index, e){
let copy = JSON.parse(JSON.stringify(this.selectedItem))
if(e.target.checked){
copy.push(item)
}else{
copy.splice(copy.indexOf(item),1)
}
this.$emit('update:selectedItem',copy)
},
onChangeItemAll(e){
if(e.target.checked){
this.$emit('update:selectedItem',this.dataSource)
}else{
this.$emit('update:selectedItem',[])
}
},
onChecked(item){
return this.selectedItem.filter(n=>n.id === item.id).length > 0 ? true : false
}
},
watch: {
selectedItem(){
if(this.selectedItem.length === this.dataSource.length){
this.$refs.a.indeterminate = false
this.$refs.a.checked = true
}else if(this.selectedItem.length === 0){
this.$refs.a.indeterminate = false
}else{
this.$refs.a.indeterminate = true
}
}
}
}
</script>
<style scoped lang="scss">
@import 'var';
.lifa-table{
border-collapse: collapse;
border-spacing: 0;
border-bottom: 1px solid $gray;
width: 100%;
&.bordered{
border: 1px solid $gray;
td,th{
border: 1px solid $gray;
}
}
&.compact{
td,th{
padding: 4px;
}
}
&.striped{
tbody{
> tr{
&:nth-child(odd){
background: white;
}
&:nth-child(even){
background: #fafafa;
}
}
}
}
th,td{
border-bottom: 1px solid $gray;
text-align: left;
padding: 8px;
}
th{
color: #909399;
}
td{
color: #606266;
}
}
</style>
解决取消选中异常的bug
当我们全选的时候,比如我点击第二个取消选中可最后一个也跟着取消选中了,原因就是因为深拷贝的原因,因为深拷贝后数组里的对象和原先的不是同一个索引,所以不能通过indexOf查找,而要通过取消选中的时候,当前取消选中项的id,找到深拷贝后数组里的id不等于当前选中项的id的元素,然后传给父组件
let copy = JSON.parse(JSON.stringify(this.selectedItem))
if(e.target.checked){
copy.push(item)
}else{
//取消选中状态:点击当前的checkbox保留数组中id不等于当前id的项
copy= copy.filter(i=>i.id !== item.id)
}
this.$emit('update:selectedItem',copy)
},
watch: {
selectedItem(){
if(this.selectedItem.length === this.dataSource.length){
this.$refs.a.indeterminate = false
}else if(this.selectedItem.length === 0){
this.$refs.a.indeterminate = false
}else{
this.$refs.a.indeterminate = true
}
}
}
实现选中所有单选的checkbox后,全选的checkbox也跟着选中
错误写法:
直接判断selectedItem.length和dataSource.length的长度是否相等,这样虽然可以实现我们要的功能,但是是错误的写法
<input type="checkbox" @change="onChangeItemAll($event)" ref="a" :checked="areAllItemChecked">
computed: {
areAllItemChecked(){
return this.selectedItem.length === this.dataSource
}
}
正确写法:
我们需要判断这两个数组里的所有项的id是否都相等来判断是否全选。
那么现在的问题是我们如何判断两个数组是一样的。比如this.dataSource=[{id:1},{id:2}]
和this.selectedItem = [{id:2},{id:1}]
我们该如何判断两个数组是一样的哪?
我们无法通过遍历判断第一个dataSource的第一个是否等于selectedItem的第一个来判断是否相等,因为上面的顺序不一样,但也是相等的。
所以我们需要
- 先对这两个数组里的id进行排序
this.dataSource.sort((a,b)=>a.id - b.id)
但是sort会改变原来的数组,所以我们需要先用map生成一个新的数组,然后在排序
areAllItemChecked(){
const a = this.dataSource.map(n=>n.id).sort()
const b = this.selectedItem.map(n=>n.id).sort()
if(a.length === b.length){
for(let i = 0;i<a.length;i++){
if(a[i] !== b[i]){
return false
}else{
return true
}
}
}
}
表格排序的实现
补充知识:在vue里,你不可能根据一个属性检查另一个属性的合法性,因为你在validate中拿不到实例(this)
export default{
props: {
name: 'lifa'
},
orderBy: {
type: Object,
default: ()=>({}),
validator(object){
console.log(this)//undefined
}
}
}
定义排序规则:
通过外界传入一个ordreBy对象来定义
orderBy: {//true:开启排序,但是不确定asc desc
name: true,
score: 'desc'
},
如果想要某个列按升序排列就在当前字段后加一个'asc',降序就加'score',默认开启排序,但是不是升序也不是降序就用true,然后在组件中接受这个orderBy
<th v-for="column in columns" :key="column.field">
<div class="lifa-table-header">
{{column.text}}
<!--如果对应的key在orderBy这个对象里,就显示-->
<span class="lifa-table-sorter" v-if="column.field in orderBy" @click="changeOrderBy(column.field)">
<lf-icon name="asc" :class="{active:orderBy[column.field] === 'asc'}"></lf-icon>
<lf-icon name="desc" :class="{active: orderBy[column.field] === 'desc'}"></lf-icon>
</span>
</div>
</th>
props: {
//通过什么排序
orderBy: {
type: Object,
default: ()=>({})
}
},
methods: {
changeOrderBy(key){
const copy = JSON.parse(JSON.stringify(this.orderBy))
if(copy[key] === 'asc'){
copy[key] = 'desc'
}else if(copy[key] === 'desc'){
copy[key] = true
}else{
copy[key] = 'asc'
}
this.$emit('update:orderBy',copy)
}
}
上面的代码可以实现状态的切换,当点击的时候如果是升序就会变成降序,如果是降序就会变成默认状态,否则就会变成升序,然后需要在外界.sync一下
<lf-table :order-by.sync="orderBy"></lf-table>
然后我们只需要监听这个update:orderBy事件,当事件触发的时候执行一个方法,来修改我们dataSource里的数据
<lf-table :order-by.sync="orderBy"
@update:orderBy="changeOrder"
></lf-table>
methods: {
changeOrder(data){
this.$nextTick(()=>{
let type
let arr = this.dataSource.map(item=>{
type = typeof item[this.key]
return item[this.key]
})
if( data[this.key]=== 'asc'){
if(type === 'number'){
arr.sort((a,b)=>a-b)
}else{
arr.sort((a, b) => b.localeCompare(a, 'zh-Hans-CN', {sensitivity: 'accent'}))
}
}else if(data[this.key] === 'desc'){
if(type === 'number'){
arr.sort((a,b)=>b-a)
}else{
arr.sort((a, b) => a.localeCompare(b, 'zh-Hans-CN', {sensitivity: 'accent'}))
}
}
arr.map((item,index)=>{
this.dataSource[index][this.key]=item
})
})
},
},
watch: {
//监听orderBy的变化,如果新值里的属性值不等于旧值的属性值说明当前属性变了,拿到这个key,比如我一开始是{name: '发发',score:100}现在是{name: '发发',score:90},那么我们就可以拿到score这个key
orderBy(val,oldVal){
for(let key in this.orderBy){
if(val[key] !== oldVal[key]){
this.key = key
}
}
}
},
实现表头固定
思路:
- 对table拷贝一份,然后把拷贝后的table中的tbody删除,只留一个thead固定在顶部(这里因为thead必须在table中,所以我们不能像div一样把它单独拿出来)
- 复制后的table删除tbody后,宽度会和之前的不一致,这时候你需要获取拷贝前的table里每一个th的宽度,然后设置给拷贝后的th
- table.vue
<template>
<div class="lifa-table-wrapper" ref="warpper">
<div :style="{height,overflow:'auto'}">
<table class="lifa-table" :class="{bordered,compact,striped}" ref="table">
<thead>
<tr>
<th>
<input type="checkbox" @change="onChangeItemAll($event)" ref="a" :checked="areAllItemChecked">
</th>
<th v-if="numberVisible">#</th>
<th v-for="column in columns" :key="column.field">
<div class="lifa-table-header">
{{column.text}}
<!--如果对应的key在orderBy这个对象里,就显示-->
<span class="lifa-table-sorter" v-if="column.field in orderBy"
@click="changeOrderBy(column.field)">
<lf-icon name="asc" :class="{active:orderBy[column.field] === 'asc'}"></lf-icon>
<lf-icon name="desc" :class="{active: orderBy[column.field] === 'desc'}"></lf-icon>
</span>
</div>
</th>
</tr>
</thead>
<tbody>
<tr v-for="(item,index) in dataSource" :key="item.id">
<th>
<input type="checkbox" @change="onChangeItem(item, index, $event)"
:checked="onChecked(item)" class="checkbox"
>
</th>
<td v-if="numberVisible">{{index+1}}</td>
<template v-for="column in columns">
<!--显示dataSource中对应表头字段里的内容-->
<td :key="column.field">{{item[column.field]}}</td>
</template>
</tr>
</tbody>
</table>
</div>
<div class="lifa-table-loading" v-if="loading">
<lf-icon name="loading"></lf-icon>
</div>
</div>
</template>
props: {
height: {
type: [Number, String],
}
},
mounted(){
let oldTable = this.$refs.table
let newTable = oldTable.cloneNode(true)
this.newTable = newTable
this.updateHeadersWidth()
//窗口改变时重新获取一下各个th的宽度,重新赋值
window.addEventListener('resize', this.onWindowResize)
newTable.classList.add('lifa-table-copy')
this.$refs.warpper.appendChild(newTable)
},
beforeDestroy() {
window.removeEventListener('resize', this.onWindowResize)
this.newTable.remove()
},
methods: {
onWindowResize() {
this.updateHeadersWidth()
},
updateHeadersWidth() {
let tableHeader = Array.from(this.$refs.table.children).filter(node => node.nodeName.toLocaleLowerCase() === 'thead')[0]
let tableHeader2
Array.from(this.newTable.children).map((node) => {
if (node.nodeName.toLocaleLowerCase() === 'tbody') {
node.remove()
} else {
//也就是tableHeader=<thead>
tableHeader2 = node
}
})
Array.from(tableHeader.children[0].children).map((node, index) => {
let {width} = node.getBoundingClientRect()
console.log(width);
tableHeader2.children[0].children[index].style.width = `${width}px`
})
},
}
上面的代码虽然实现了顶部表头固定,但是因为你是复制了一个,所以表头的点击事件并不会起作用。
解决方法:
我们在拷贝一个新的table的时候不全部拷贝只拷贝table不拷贝table里面的子元素,然后我们把原有的thead添加到拷贝后的table中,这样就还是原来的thead,而且可以单独拿出来,然后我们通过让用户给每一列传一个宽度,通过这个宽度来设置原来的table里的宽度,并且拷贝后的table因为会覆盖表格内容的第一行,所以还要通过margin-top移下来thead的高度的距离
data(){
return {
columns: [
//表头每一列显示的文本和字段
{text: '姓名', field: 'name', width: 100},
{text: '分数', field: 'score',width: 100},
{text: '年龄', field: 'age'}
],
orderBy: {//true:开启排序,但是不确定asc desc
name: true,
score: 'desc'
},
dataSource: [
{id: 1, name: '发发', score: 100, age:18},
{id: 2, name: '琳琳', score: 99, age: 16},
{id: 3, name: '西西', score: 99, age: 20},
{id: 4, name: '泳儿', score: 99, age: 21},
{id: 5, name: '美美', score: 99, age: 22},
{id: 6, name: '阿宇', score: 99, age: 26},
{id: 7, name: '发发', score: 100, age:18},
{id: 8, name: '琳琳', score: 99, age: 16},
{id: 9, name: '西西', score: 99, age: 20},
{id: 10, name: '泳儿', score: 99, age: 21},
{id: 11, name: '美美', score: 99, age:18},
{id: 12, name: '阿宇', score: 99, age: 16}
],
}
}
- table.vue
<template>
<div class="lifa-table-wrapper" ref="wrapper">
<div :style="{height: `${height}px`,overflow:'auto'}" ref="tableContent">
<table class="lifa-table" :class="{bordered,compact,striped}" ref="table">
<thead>
<tr>
<th :style="{width: '50px'}">
<input type="checkbox" @change="onChangeItemAll($event)" ref="a" :checked="areAllItemChecked">
</th>
<th v-if="numberVisible" :style="{width: '50px'}">#</th>
<th v-for="column in columns" :key="column.field" :style="{width: `${column.width}px`}">
<div class="lifa-table-header">
{{column.text}}
<!--如果对应的key在orderBy这个对象里,就显示-->
<span class="lifa-table-sorter" v-if="column.field in orderBy"
@click="changeOrderBy(column.field)">
<lf-icon name="asc" :class="{active:orderBy[column.field] === 'asc'}"></lf-icon>
<lf-icon name="desc" :class="{active: orderBy[column.field] === 'desc'}"></lf-icon>
</span>
</div>
</th>
</tr>
</thead>
<tbody>
<tr v-for="(item,index) in dataSource" :key="item.id">
<th :style="{width: '50px'}">
<input type="checkbox" @change="onChangeItem(item, index, $event)"
:checked="onChecked(item)" class="checkbox"
>
</th>
<td v-if="numberVisible" :style="{width: '50px'}">{{index+1}}</td>
<template v-for="column in columns">
<!--显示dataSource中对应表头字段里的内容-->
<td :key="column.field" :style="{width: `${column.width}px`}">{{item[column.field]}}</td>
</template>
</tr>
</tbody>
</table>
</div>
<div class="lifa-table-loading" v-if="loading">
<lf-icon name="loading"></lf-icon>
</div>
</div>
</template>
mounted() {
let oldTable = this.$refs.table
let newTable = oldTable.cloneNode()
let {height} = oldTable.children[0].getBoundingClientRect()
newTable.appendChild(oldTable.children[0])
this.$refs.tableContent.style.marginTop = height + 'px'
this.$refs.wrapper.style.height = this.height - height + 'px'
newTable.classList.add('lifa-table-copy')
this.$refs.wrapper.appendChild(newTable)
},
实现展开行功能
实现思路:通过用户在dataSource里传入description指定展开要显示的字段,然后通过一个expend-field传入我们的description字段,也就是expend-field="description"
。因为我们要在每一个tr下面再加一个tr,但是你不能再单独v-for那就不能同步了,所以要想同时遍历多个外层就要用template,然后给每一列前面加一个展开的按钮,点击这个按钮的时候把当前的id传给一个数组,然后通过判断数组里是否有这个id,没有就加到数组里,有就删除,之后再给展开的这一栏通过判断数组里是否有当前id来让它是否展示
- table.vue
<template v-for="(item,index) in dataSource">
<tr :key="item.id">
<td :style="{width: '50px'}" @click="expendItem(item.id)"
class="lifa-table-center" :class="{expend: expendVisible(item.id)}"
>
<lf-icon name="right"></lf-icon>
</td>
<td :style="{width: '50px'}" class="lifa-table-center">
<input type="checkbox" @change="onChangeItem(item, index, $event)"
:checked="onChecked(item)" class="checkbox"
>
</td>
<td v-if="numberVisible" :style="{width: '50px'}">{{index+1}}</td>
<template v-for="column in columns">
<!--显示dataSource中对应表头字段里的内容-->
<td :key="column.field" :style="{width: `${column.width}px`}">{{item[column.field]}}</td>
</template>
</tr>
<tr v-if="expendVisible(item.id)">
<td :key="`${item.id}-1`" :colspan="columns.length+2">
{{item[expendField] || '空'}}
</td>
</tr>
</template>
methods: {
expendItem(id){
if(this.expendIds.indexOf(id) >= 0){
this.expendIds.splice(this.expendIds.indexOf(id),1)
}else{
this.expendIds.push(id)
}
},
expendVisible(id){
return this.expendIds.indexOf(id) >= 0
},
}
<lf-table :columns="columns" :data-source="dataSource"
bordered :selected-item.sync="selectedItem" :order-by.sync="orderBy"
@update:orderBy="changeOrder" :loading="loading" :height="400"
expend-field="description"
></lf-table>
dataSource: [
{id: 1, name: '发发', score: 100, age:18, description: '你最帅'},
{id: 2, name: '琳琳', score: 99, age: 16, description: '为啥不做我媳妇'},
{id: 3, name: '西西', score: 99, age: 20, description: '好累啊'},
{id: 4, name: '泳儿', score: 99, age: 21},
{id: 5, name: '美美', score: 99, age: 22},
{id: 6, name: '阿宇', score: 99, age: 26},
{id: 7, name: '发发', score: 100, age:18},
{id: 8, name: '琳琳', score: 99, age: 16},
{id: 9, name: '西西', score: 99, age: 20},
{id: 10, name: '泳儿', score: 99, age: 21},
{id: 11, name: '美美', score: 99, age:18},
{id: 12, name: '阿宇', score: 99, age: 16}
],
上面的展开行里的列数是写死的,而我们有可能没有展开按钮或者没有选择框,那么这个列数就不对了,所以我们需要通过计算属性来计算
<tr v-if="expendVisible(item.id)">
<td :key="`${item.id}-1`" :colspan="columns.length+ expendedCellColSpan">
{{item[expendField] || '空'}}
</td>
</tr>
computed: {
expendedCellColSpan(){
let result = 0
if(this.checkable){
result += 1
}
if(this.expendField){
result += 1
}
return result
}
},
props: {
//是否显示选择框
checkable: {
type: Boolean,
default: false
}
}
table里面可以有按钮
思路通过用户传入一个template,然后在table中通过slot来把你template里的按钮放到指定位置
<lf-table :columns="columns" :data-source="dataSource"
bordered :selected-item.sync="selectedItem" :order-by.sync="orderBy"
@update:orderBy="changeOrder" :loading="loading" :height="400"
expend-field="description" checkable
>
<template slot-scope="xxx">
<button @click="edit(xxx.item.id)">编辑</button>
<button @click="view(xxx.item.id)">查看</button>
</template>
</lf-table>
- table.vue
<td>
<slot :item="item"></slot>
</td>
methods: {
edit(id){
alert(`正在编辑第${id}个`)
},
view(id){
alert(`正在查看第${id}个`)
}
}
升级table组件,支持自定义
问题:我们table里只能传入data而不能传入a标签,我们现在想给每一列添加一个a标签,使用vue就没法实现
思路:使用jsx
- 在vue中使用JSX
(1).lang="jsx"
(2).render返回一个标签
(3).使用css直接还是用class
<script lang="jsx">
export default {
data() {
return {
n: 13,
items: [2, 3, 4, 5, 6]
}
},
name: 'demo2',
render(h) {
return (
<div class="xxx">
{this.n > 10 ? <span>大</span> : <span>小</span>}
<ul>
{this.items.map(i =>
<li>{i}</li>
)}
</ul>
</div>
)
}
}
</script>
<style scoped lang="scss">
.xxx {
color: red;
}
</style>
问题:我们必须得将之前的代码都改成jsx,而且使用的人也必须使用jsx格式,限制性太多
- 使用slot插槽自定义
相关补充:我们可以在组件里写任何组件标签,这些组件标签会默认转为插槽,获取他们的方式有两种,一:在mounted中通过this.children拿到他们
将原来的columns数组去掉,使用组件table-columns来代替
- table-columns.vue
<template>
<div></div>
</template>
<script>
export default {
name: "table-column.vue",
props: {
text: {
type: String,
required: true
},
field: {
type: String,
required: true
},
width: {
type: Number
}
}
}
</script>
- demo.vue
<lf-table :data-source="dataSource"
bordered :selected-item.sync="selectedItem" :height="400"
expend-field="description" checkable>
<!-- <template slot-scope="xxx">-->
<!-- <lf-button @click="edit(xxx.item.id)">编辑</lf-button>-->
<!-- <lf-button @click="view(xxx.item.id)">查看</lf-button>-->
<!-- </template>-->
<lf-table-column text="姓名" field="name" :width="100">
<template slot-scope="scope">
<a href="#">{{scope.value}}</a>
</template>
</lf-table-column>
<lf-table-column text="分数" field="score"></lf-table-column>
</lf-table>
import LfTable from './table'
import LfTableColumn from './table-column'
export default {
name: "demo",
components: {
LfTable,
LfTableColumn
},
data() {
return {
dataSource: [
{id: 1, name: '发发', score: 100, age:18, description: '你最帅,将来一定会成为一个了不起的演员'},
{id: 2, name: '琳琳', score: 99, age: 16, description: '为啥不做我媳妇'},
{id: 3, name: '西西', score: 99, age: 20, description: '好累啊'},
{id: 4, name: '泳儿', score: 99, age: 21},
{id: 5, name: '美美', score: 99, age: 22},
{id: 6, name: '阿宇', score: 99, age: 26},
{id: 7, name: '泳儿', score: 99, age: 21},
{id: 8, name: '美美', score: 99, age: 22},
{id: 9, name: '阿宇', score: 99, age: 26}
],
selectedItem: [],
}
},
- table.vue
<template v-for="column in columns">
<!--显示dataSource中对应表头字段里的内容-->
<td :key="column.field" :style="{width: `${column.width}px`}">
// 在原来遍历td内容的地方直接加判断
{{column.render ? column.render({value: item[column.field]}) : item[column.field]}}
</td>
</template>
data() {
+ columns: []
},
mounted() {
// this.$slots遍历拿到每一个table-columns组件
this.columns = this.$slots.default.map(node => {
// node.componentOptions.propsData拿到组件中外界传进来的props的值
let { text, field, width } = node.componentOptions.propsData
// 如果组件里面使用了插槽那么就可以拿到插槽里对应的render函数也就是
// <template slot-scope="scope">
// <a href="#">{{scope.value}}</a>
// </template>
let render = node.data.scopedSlots && node.data.scopedSlots.default
return {
text, field, width, render
}
})
// 这里之所以要render里面传一个对象key是value,是因为我们前面写的scope.value,
// scope就是我们的形参也就是下面的{value:'立发'}
let result = this.columns[0].render({value: '立发'})
console.log(result)
}
问题: 上面的代码如果直接在原来的td渲染的时候修改会报错
单独将this.columns[0].render({value: '立发'})打印我们发现,他是一个Vnode
问题:那么我们该如何将vnode展示在template里哪
方法:
(1).声明一个vnodes组件如下
components: {
LfIcon,
vnodes: {
function: true,
render: (h, ctx) => ctx.props.vnodes
}
},
(2).对vnodes组件传入一个vnodes,绑定的数据就是你要传入的value
<template v-for="column in columns">
<!--显示dataSource中对应表头字段里的内容-->
<td :key="column.field" :style="{width: `${column.width}px`}">
<template v-if="column.render">
<vnodes :vnodes="column.render({value: item[column.field]})"></vnodes>
</template>
<template v-else>
{{item[column.field]}}
</template>
</td>
</template>