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

一、递归组件

什么是递归,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。如果文章对你有所帮助,能否帮我点个免费的赞和收藏😍。

👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇

  • 6
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
Vue3语法糖中使用 ElementUI 实现表格分页的方法与 Vue2 差别不大,主要是在引入 ElementUI 和定义组件的方式上有些不同。 1. 在 setup 中引入 ElementUI 组件: ``` import { ref } from 'vue'; import { ElTable, ElTableColumn, ElPagination } from 'element-plus'; export default { setup() { const currentPage = ref(1); // 当前页码 const pageSize = ref(10); // 每页显示条目数 const total = ref(0); // 总条目数 const tableData = ref([]); // 表格数据 const handleCurrentChange = (val) => { currentPage.value = val; getData(); } const getData = async () => { const res = await axios.get('/api/data', { params: { page: currentPage.value, size: pageSize.value } }); tableData.value = res.data.list; total.value = res.data.total; } getData(); return { currentPage, pageSize, total, tableData, handleCurrentChange } }, components: { ElTable, ElTableColumn, ElPagination } } ``` 2. 在 template 中使用 ElementUI 组件: ``` <template> <div> <el-pagination :current-page="currentPage" :page-size="pageSize" :total="total" @current-change="handleCurrentChange"> </el-pagination> <el-table :data="tableData" border> <el-table-column prop="name" label="姓名"></el-table-column> <el-table-column prop="age" label="年龄"></el-table-column> <el-table-column prop="address" label="地址"></el-table-column> </el-table> </div> </template> ``` 需要注意的是,Vue3 中使用 ElementUI 组件时需要在组件选项中声明组件,而不是在 template 中通过 import 引入。另外,由于 Vue3 中没有了 this,需要使用 ref 来定义响应式数据。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Etc.End

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值