Vue3(递归组件) + 原生Table 实现树结构复杂表格

news/2024/11/15 3:51:35/

一、递归组件

什么是递归,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: stringid: stringparentId: string | nullchildren?: 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: stringid: stringparentId: string | nullchildren?: 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 || 0let 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👈

❤️ 💓 💗 💖 ✨ ⭐️ 🌟 💥 💥


http://www.ppmy.cn/news/33283.html

相关文章

为什么VMware会给我多创建了两个网络呢?Windows和Linux为什么可以彼此ping的通呢

为什么VMware会给我多创建了两个网络呢&#xff1f;Windows和Linux为什么可以彼此ping的通呢 文章目录为什么VMware会给我多创建了两个网络呢&#xff1f;Windows和Linux为什么可以彼此ping的通呢桥接模式ANT模式&#xff08;VMnet8&#xff09;仅主机模式&#xff08;VMnet1&a…

牛客top100 - 自刷打卡day1 - 链表

top100-打卡day1链表BM1反转链表BM2链表内指定区间反转BM3链表中的节点每k个一组翻转BM4合并两个排序的链表BM5合并k个已排序的链表BM6判断链表中是否有环BM7链表中环的入口结点BM8链表中倒数最后k个结点BM9删除链表的倒数第n个节点BM10两个链表的第一个公共结点BM11链表相加(二…

CSDN周赛第37期题解(Python版)

这期周赛题目和测试集还算完整&#xff0c;没有出现往期的bug。1、题目名称&#xff1a;幼稚班作业幼稚园终于又有新的作业了。 老师安排同学用发给同学的4根木棒拼接成一个三角形。 当然按照正常的逻辑&#xff0c;如果不能拼接成三角形。 必然要折断某个木棍来拼接三角形。 可…

【Linux】进程的程序替换

文章目录1. 程序替换1.创建子进程的目的是什么&#xff1f;2.了解程序是如何进行替换的3. 程序替换的基本原理当创建进程的时候&#xff0c;先有进程数据结构&#xff0c;还是先加载代码和数据&#xff1f;程序替换是整体替换&#xff0c;不是局部替换execl 返回值4. 替换函数1…

vue2+高德地图web端开发使用

创建vue2项目我们创建一个vue2项目&#xff0c;创建vue2项目就不用再多说了吧&#xff0c;使用“vue create 项目名 ”创建即可注册高德地图高德地图官网地址&#xff1a;https://lbs.amap.com/如果是第一次使用&#xff0c;点击注册然后进入我们的控制台注册完之后进入控制台&…

ES6新特性--变量声明

可以使用let关键字来声明变量let a;let b,c;//同时声明多个变量let stu = 张三;let name =李四,age = 12;//声明变量的同时赋值 let关键字使用的注意事项(1).变量在声明的时候不可以重复,这也符合其他语言的变量声明规范 let name = 李四; let name = 张三;//这里开始报错,但…

2023年Android现代开发

2023年现代Android开发 下面与大家分享如何构建具有2023年最新趋势的Android应用程序。 Android是什么&#xff1f; Android 是一种基于 Linux 内核并由 Google 开发的开源操作系统。它用于各种设备&#xff0c;包括智能手机、平板电脑、电视和智能手表。 目前&#xff0c…

IntelliJIDEA 常用快捷键

IntelliJIDEA 常用快捷键 Alt Enter 导入包&#xff0c;自动修正&#xff0c;自动创建变量名。 Ctrl Alt O 优化导入的类和包 Ctrl / 单行注释 (//) Ctrl Shift / 多行注释 (/* … */) 方法或类说明注释&#xff08;文档注释&#xff09; 在一个方法或类的开头&#xf…