最终效果
实现过程
组件封装
Table.vue组件
<template>
<el-table :data="list">
<Column v-for="column in columns" :key="column.prop" :column="column" :sortParams="sortParams" @sortChange="handleSortChange">
<template v-if="column.slotName" #[column.slotName]>
<slot :name="column.slotName"></slot>
</template>
<template v-for="item in column.deepSlotNames" #[item]="scope">
<slot :name="item" v-bind="scope"></slot>
</template>
</Column>
</el-table>
</template>
<script lang="ts">
import Column from './Column.vue';
export default {
props: {
columns: {
type: Array,
default: () => [
{ prop: 'name', label: '姓名', sortable: true },
{ prop: 'age', label: '年龄', sortable: true },
{ prop: 'info', label: '个人信息', deepSlotNames: ['father', 'mother', 'brother', 'xiaox'], children: [
{ prop: 'parent', label: '父母', deepSlotNames: ['father', 'mother'], children: [
{ prop: 'father', label: '父亲', slotName: 'father' },
{ prop: 'mother',label: '母亲', slotName: 'mother', children: [
] }
]},
{ prop: 'brother', label: '兄弟', slotName: 'brother' },
{ prop: 'school', label: '学校', deepSlotNames: ['xiaox'], children: [
{ prop: 'yiwu', label: '义务教育阶段', deepSlotNames: ['xiaox'], children: [
{ prop: 'xiaox', label: '小学', slotName: 'xiaox' }
] }
] }
]
},
{ prop: 'operation', label: '操作', slotName: 'operation' }
]
}
},
components: {
Column,
},
data() {
return {
list: [
{ id: 1, name: '小明', age: 12, father: '小明爸爸1', mother: '小明妈妈' },
{ id: 2, name: '小红', age: 22, father: '小红爸爸2', mother: '小红妈妈' },
{ id: 3, name: '小白', age: 32, father: '小白爸爸3', mother: '小白妈妈' },
],
sortParams: {}
}
},
methods: {
handleSortChange() {
console.log('sss22', this.sortParams)
}
}
}
</script>
Column 表头组件
<template>
<el-table-column :label="column.label" :prop="column.prop">
<Column v-for="columnItem in column.children" :column="columnItem" :key="columnItem.prop">
<template v-for="item in column.deepSlotNames" #[item]="scope">
<slot :name="item" v-bind="scope"></slot>
</template>
<template #[column.slotName]="scope">
<slot :name="column.slotName" v-bind="scope"></slot>
</template>
</Column>
<template v-if="column.sortable" #header>
<div class="cust-header">
<span>{{ column.label }}</span>
<SortItem :sort.sync="sortParams[column.prop]" @sortChange="handleSortChange"></SortItem>
</div>
</template>
<!-- 递归出口 -->
<template v-if="column.slotName" #default="scope">
<slot :name="column.slotName" v-bind="scope"></slot>
</template>
</el-table-column>
</template>
<script lang="ts">
import { PropType } from 'vue'
import SortItem from './SortItem.vue'
type TColumn = {
label: string
prop: string
deepSlotNames?: string[]
children?: TColumn
[key: string]: any
}
export default {
name: 'Column',
components: {
SortItem
},
props: {
column: {
type: Object as PropType<TColumn>,
default: () => ({})
},
sortParams: {
type: Object,
default: () => ({})
}
},
methods: {
handleSortChange() {
this.$emit('sortChange')
}
}
}
</script>
<style lang="css">
.cust-header {
display: flex;
justify-content: space-between;
}
</style>
SortItem组件
<template>
<div class="table-sort">
<div class="table-sort-up" @click="handleSortMode('up')" :class="{
active: sort === 'asc'
}">^</div>
<div class="table-sort-down" @click="handleSortMode('down')" :class="{
active: sort === 'desc'
}">v</div>
</div>
</template>
<script lang="ts">
import { PropType } from 'vue'
export default {
props: {
sort: {
type: String as PropType<'asc' | 'desc'>
}
},
methods: {
handleSortMode(type: string) {
if (type === 'up') {
const newValue = this.sort === 'asc' ? '' : 'asc'
this.$emit('update:sort', newValue)
} else {
const newValue = this.sort === 'desc' ? '' : 'desc'
this.$emit('update:sort', newValue)
}
this.$emit('sortChange')
}
}
}
</script>
<style lang="css">
.table-sort {
display: flex;
flex-direction: column;
}
.table-sort-up, .table-sort-down {
width: 16px;
height: 16px;
text-align: center;
line-height: 16px;
cursor: pointer;
}
.table-sort-up.active, .table-sort-down.active {
color: red;
}
.table-sort-up {
font-size: 16px;
}
.table-sort-down {
font-size: 14px;
}
</style>
封装表格调用slot透传
<Table>
<template #operation>操作-slot</template>
<template #brother>brother</template>
<template #father="{ row }">
<div @click="handleGetRow(row)">{{ row.id }}: father-slot</div>
</template>
<template #mother="{ row }">{{ row.id }}: mother-slot</template>
<template #xiaox>xiaox-slot</template>
</Table>
特殊说明,由于递归层级不确定,slot透传主要靠 deepSlotNames 这个字段接收slot并传递下去,还不确定性能影响,如果有更好的方式欢迎留言讨论