一、递归组件
什么是递归,Javascript中经常能接触到递归函数。也就是函数自己调用自己。那对于组件来说也是一样的逻辑。平时工作中见得最多应该就是菜单组件,大部分系统里面的都是递归组件。文章中我做了按需引入的配置,所以看不到我引用组件、Vue3的相关API等等。需要了解的小伙伴可以看我的另一篇文章 Vite4+Pinia2+vue-router4+ElmentPlus搭建Vue3项目(组件、图标等按需引入)
二、Table的合并
复杂的表格无非就是行或者列的合并。主要涉及到colspan和rowspan。colspan属性规定单元格可横跨的列数。rowspan属性规定单元格可横跨的行数。比如下面的列子。第一行为标题。表格最多为5列。所以第一行的列需要全部合并,colspan的值就为5。第三行和第四行都是统一的食品分类,需要合并,所以第三行的第一列就需要往下合并。往下合并两行,所以rouspan就为2。但是这里要注意,列合并不用管。行的话被合并的列就需要删除。是不是很简单。两个设置就能实现下面的列子。
<template>
<table class="table">
<tr>
<td colspan="5">某某小卖部</td>
</tr>
<tr>
<td>商品分类</td>
<td>商品</td>
<td>价格</td>
<td>库存</td>
<td>描述</td>
</tr>
<tr>
<td rowspan="2">食品</td>
<td>瓜子</td>
<td>5元</td>
<td>20</td>
<td>可以吃的瓜子</td>
</tr>
<tr>
<td>花生</td>
<td>6元</td>
<td>30</td>
<td>可以吃的花生</td>
</tr>
</table>
</template>
<style lang="scss">
.table {
width: 100%;
margin-left: 0;
text-align: center;
font-size: 12px;
}
.table th,
.table td {
border: 1px solid #070707 !important;
padding: 0.35rem !important;
font-size: 16px;
vertical-align: middle !important;
}
.ts-table-bold {
td {
font-weight: bold;
font-size: 18px;
}
}
</style>
三、核心方法
核心的方法主要就是行和列的合并规则,不管是我现在这个表格还是其他复杂的,只要你细心的观察。总会发现规则。然后就能利用js去实现。
因为需要知道树结构总共拥有多少节点,树结构有多少层级。我在列子中也用到了递归函数。比如下面的树结构转平行结构。参数tree的话表示树节点。第二个list表示我需要存储对象,第三个参数表示父节点的id。
// 获取整个树数据的长度 用于行的合并
const treeToList = (tree: TreeType[], list: TreeType[], parentId: string | null) => {
for (let i in tree) {
const nodeData = tree[i];
list.push({
id: nodeData.id,
title: nodeData.title,
parentId: parentId
});
if (nodeData.children && nodeData.children.length !== 0) {
treeToList(nodeData.children, list, nodeData.id)
}
}
}
三、组件的封装完整代码
上面的列子是写死的,所以实现起来比较的简单,接下来就需要获取动态的数据,动态进行行或者列的合并实现复杂的表格展示。组件中props里面的参数:data就是数据源,level表示整个数据的层级,currentLevel表示当前递归到第几层。
<template>
<tr v-if="!data.children || data.children.length === 0">
<td :colspan="(level - currentLevel + 1) / 2">{{ data.title }}</td>
</tr>
<tr v-else>
<td :rowspan="getTreeToArr(data.children) + 1">
{{ data.title }}
</td>
</tr>
<template v-for="it in data.children" :key="it.id">
<ts-recursion-table :data="it" :level="level" :currentLevel="currentLevel + 1" />
</template>
</template>
<script lang="ts">
type TreeType = {
title: string
id: string
parentId: string | null
children?: Array<TreeType>
}
export default defineComponent({
name: 'TsRecursionTable',
props: ['data', 'level', 'currentLevel'],
setup() {
// 获取整个树数据的长度 用于行的合并
const treeToList = (tree: TreeType[], list: TreeType[], parentId: string | null) => {
for (let i in tree) {
const nodeData = tree[i];
list.push({
id: nodeData.id,
title: nodeData.title,
parentId: parentId
});
if (nodeData.children && nodeData.children.length !== 0) {
treeToList(nodeData.children, list, nodeData.id)
}
}
}
const getTreeToArr = (data: any) => {
let result:TreeType[] = []
if (!data) {
return 0
}
treeToList(data, result,null)
return result.length
}
return {
getTreeToArr
}
}
})
</script>
四、组件的使用完整代码
<template>
<div style="width: 1000px;margin: 200px auto auto auto;">
<table class="table">
<tr>
<td :colspan="level">某某区人数统计</td>
</tr>
<ts-recursion-table v-for="(item, index) in tableData" :key="item.id" :data="item" :level="level" :currentLevel="1" />
</table>
</div>
</template>
<script lang="ts">
type TreeType = {
title: string
id: string
parentId: string | null
children?: Array<TreeType>
}
export default defineComponent({
setup() {
const rowLength = ref<number>(0)
const level = ref<number>(0)
const state = reactive({
tableData: [
{
title: '社区一',
id: '1',
parentId: null,
children: [
{
title: '街道一',
id: '1-1',
parentId: '1',
children: [
{
id: '1-1-1',
parentId: '1-1',
title: '小区1',
children: [
{
id: '1-1-1-1',
parentId: '1-1-1',
title: '单元1',
children: [
{
id: '1-1-1-1-1',
parentId: '1-1-1-1',
title: '住户1'
},
{
id: '1-1-1-1-2',
parentId: '1-1-1-1',
title: '住户2'
}
]
},
{
id: '1-1-1-2',
parentId: '1-1-1',
title: '单元2'
},
]
},
{
id: '1-1-2',
parentId: '1-1',
title: '小区2'
},
]
},
{
title: '街道二',
id: '1-2',
parentId: '1',
children: [
{
id: '1-2-1',
parentId: '1-2',
title: '小区1'
},
{
id: '1-2-2',
parentId: '1-2',
title: '小区2'
}
]
}
]
},
{
title: '社区二',
id: '2',
parentId: null,
children: [
{
title: '街道一',
id: '2-1',
parentId: '2',
children: [
{
id: '2-1-1',
parentId: '2-1',
title: '小区1'
},
{
id: '2-1-2',
parentId: '2-1',
title: '小区2'
},
{
id: '2-1-3',
parentId: '2-1',
title: '小区3'
},
]
}
]
}
] as TreeType[]
})
// 获取整个树数据的长度 用于行的合并
const treeToList = (tree: TreeType[], list: TreeType[], parentId: string | null) => {
for (let i in tree) {
const nodeData = tree[i];
list.push({
id: nodeData.id,
title: nodeData.title,
parentId: parentId
});
if (nodeData.children && nodeData.children.length !== 0) {
treeToList(nodeData.children, list, nodeData.id)
}
}
}
// 获取整个树数据的层级 用于列的合并
const getTreeLevel = (arr: TreeType[]) => {
let maxLevel = 0;
(function callBack(arr, level) {
++level;
maxLevel = Math.max(level, maxLevel);
for (let i = 0; i < arr.length; i++) {
let item = arr[i];
if (item.children && item.children.length > 0) {
callBack(item.children, level);
} else {
delete item.children;
}
}
})(arr, 0);
return maxLevel;
}
onMounted(() => {
const list: TreeType[] = []
treeToList(state.tableData, list, null)
rowLength.value = list.length || 0
let length = getTreeLevel(JSON.parse(JSON.stringify(state.tableData)))
if (length > 2) {
level.value = length * 2
} else {
level.value = 2
}
})
return {
...toRefs(state),
rowLength,
level
}
}
})
</script>
<style lang="scss">
.table {
width: 100%;
margin-left: 0;
text-align: center;
font-size: 12px;
}
.table th,
.table td {
border: 1px solid #070707 !important;
padding: 0.35rem !important;
font-size: 16px;
vertical-align: middle !important;
}
.ts-table-bold {
td {
font-weight: bold;
font-size: 18px;
}
}
</style>
五、最终效果
因为这里我只是为了做个demo,里面的Type还有公共的方法以及样式都是可以提取出来放到一个公共的文件里面。这个的话自己去完成。其实表格也不算复杂。
我是Etc.End。如果文章对你有所帮助,能否帮我点个免费的赞和收藏😍。同时欢迎各位小伙伴一起学习,一起成长WX:👉SH--TS👈
❤️ 💓 💗 💖 ✨ ⭐️ 🌟 💥 💥