Vue.js结合Element-UI递归实现多级导航菜单动态加载

本文介绍了一个基于Element UI的导航组件实现方案,通过递归的方式构建多级菜单,支持不同层级的菜单项展示,并提供了完整的Vue组件代码示例。

主要参考https://blog.youkuaiyun.com/gnosed/article/details/105178518

Menu.Vue

在需要加载导航的地方调用该组件:

<el-menu >
    <navigation-item v-for="(menu,i) in List" :key="i" :item="menu"/>
</el-menu>

完整代码: 

<template>
  <div>
    <el-aside width="200px">
    <el-menu >
    <navigation-item v-for="(menu,i) in List" :key="i" :item="menu"/>
    </el-menu>
    </el-aside>
  </div>
</template>
<script>
import navigationItem from './navigationItem'

export default {
  name:'navigation',
  components: { navigationItem },
  data() {
    return{
      List: [
        {
          "id": 1,
          "path": "/admin ",
          "name": "Content",
          "nameZh": "物理机能耗预测",
          "iconCls": "el-icon-tickets",
          "component": "AdminIndex",
          "parentId": 0,
          "children": [{
            "id": 9,
            "path": "/admin/content/department",
            "name": "Department",
            "nameZh": "Host1",
            "iconCls": null,
            "component": "content/department",
            "parentId": 3,
            "children": null
          },
            {
              "id": 10,
              "path": "/admin/content/student",
              "name": "Student",
              "nameZh": "Host2",
              "iconCls": null,
              "component": "content/student",
              "parentId": 3,
              "children": null
            },
            {
              "id": 11,
              "path": "/admin/content/enterprise",
              "name": "Enterprise",
              "nameZh": "Host3",
              "iconCls": null,
              "component": "content/enterprise",
              "parentId": 3,
              "children": null
            }
          ]
        },
        {
          "id": 4,
          "path": "/admin",
          "name": "System",
          "nameZh": "物理机能耗分解",
          "iconCls": "el-icon-s-tools",
          "component": "AdminIndex",
          "parentId": 0,
          "children": [{
            "id": 12,
            "path": "/admin/system/run",
            "name": "Run",
            "nameZh": "虚拟机分解",
            "iconCls": null,
            "component": "system/run",
            "parentId": 4,
            "children": [{
              "id": 9,
              "path": "/admin/content/department",
              "name": "Department",
              "nameZh": "Host1",
              "iconCls": null,
              "component": "content/department",
              "parentId": 3,
              "children": null
            },
              {
                "id": 10,
                "path": "/admin/content/student",
                "name": "Student",
                "nameZh": "Host2",
                "iconCls": null,
                "component": "content/student",
                "parentId": 3,
                "children": [{
                  "id": 20,
                  "path": "/admin/content/student/graduateInfo",
                  "name": "graduateInfo",
                  "nameZh": "VM1",
                  "iconCls": null,
                  "component": "content/student/graduateInfo",
                  "parentId": 10,
                  "children": [{
                    "id": 20,
                    "path": "/admin/content/student/graduateInfo",
                    "name": "graduateInfo",
                    "nameZh": "Docker1",
                    "iconCls": null,
                    "component": "content/student/graduateInfo",
                    "parentId": 10,
                    "children": {}
                  }]
                },{
                  "id": 20,
                  "path": "/admin/content/student/graduateInfo",
                  "name": "graduateInfo",
                  "nameZh": "VM2",
                  "iconCls": null,
                  "component": "content/student/graduateInfo",
                  "parentId": 10,
                  "children": null
                }]
              },
              {
                "id": 11,
                "path": "/admin/content/enterprise",
                "name": "Enterprise",
                "nameZh": "Host3",
                "iconCls": null,
                "component": "content/enterprise",
                "parentId": 3,
                "children": [{
                  "id": 11,
                  "path": "/admin/content/enterprise",
                  "name": "Enterprise",
                  "nameZh": "VM4",
                  "iconCls": null,
                  "component": "content/enterprise",
                  "parentId": 3,
                  "children": null
                }]
              }
            ]
          },
            {
              "id": 13,
              "path": "/admin/system/data",
              "name": "Data",
              "nameZh": "Docker分解",
              "iconCls": null,
              "component": "system/data",
              "parentId": 4,
              "children": null
            },
            {
              "id": 14,
              "path": "/admin/system/log",
              "name": "Log",
              "nameZh": "硬件分解",
              "iconCls": null,
              "component": "system/log",
              "parentId": 4,
              "children": null
            }
          ]
        }
      ],
    }
  },
  props:{
    menuList: {
      type: Array,
      required: true
    }
  }
}
</script>

 

navigationItem.Vue

<template>
  <div>
    <!--叶子级菜单-->
    <template v-if="item.children && item.children.length === 0">
      <el-menu-item :key="item.id" :index="item.path">
        {{item.nameZh}}
      </el-menu-item>
    </template>
    <!--父级菜单-->
    <el-submenu v-else :index="item.path" style="text-align: left">
      <span slot="title">
        <i :class="item.iconCls"></i>
        {{item.nameZh}}
      </span>
      <template v-for="child in item.children">
        <navigation-item v-if="child.children && child.children.length>0" :key="child.id" :item="child"/>
        <el-menu-item v-else :key="child.id" :index="child.path">
          <i :class="child.icon"></i>
          {{child.nameZh}}
        </el-menu-item>
      </template>
    </el-submenu>
  </div>
</template>

<script>
export default {
  name: 'navigationItem',
  props: {
    item: {
      type: Object,
      required: true
    }
  }
}
</script>

效果

 

Vue3 + Element Plus(Element-UIVue3 版本)中实现**动态多级表头表格的导出功能**,需要结合 `el-table` 的多级表头能力与前端 Excel 导出库(如 [SheetJS / xlsx](https://github.com/SheetJS/sheetjs))来完成。由于 Element Plus 表格支持嵌套 `el-table-column` 实现多级表头,但原生不支持导出,因此需手动解析表头结构并生成对应的 Excel 文件。 下面是一个完整的解决方案: --- ### ✅ 功能目标: 1. 支持 **动态配置的多级表头**(任意层级) 2. 使用 `Element Plus` 的 `<el-table>` 展示数据 3. 点击按钮将表格(含多级表头)导出为 `.xlsx` 文件 --- ### ✅ 所需依赖 ```bash npm install xlsx ``` --- ### ✅ 示例代码(Vue3 + Composition API + Element Plus) ```vue <template> <div> <el-button @click="exportToExcel">导出为 Excel</el-button> <el-table :data="tableData" border style="width: 100%; margin-top: 20px" ref="tableRef" > <!-- 动态渲染多级表头 --> <template v-for="col in tableColumns" :key="col.prop"> <el-table-column v-if="!col.children" :prop="col.prop" :label="col.label" :width="col.width" /> <el-table-column v-else :label="col.label" :width="col.width" > <template v-for="child in col.children" :key="child.prop"> <el-table-column :prop="child.prop" :label="child.label" :width="child.width" /> </template> </el-table-column> </template> </el-table> </div> </template> <script setup> import { ref, reactive } from 'vue' import * as XLSX from 'xlsx' // 模拟数据 const tableData = ref([ { name: '张三', info_age: 25, info_city: '北京', score_chinese: 88, score_math: 92 }, { name: '李四', info_age: 27, info_city: '上海', score_chinese: 76, score_math: 98 }, { name: '王五', info_age: 24, info_city: '深圳', score_chinese: 90, score_math: 85 } ]) // 多级表头定义(动态可变) const tableColumns = reactive([ { prop: 'name', label: '姓名', width: 100 }, { label: '个人信息', children: [ { prop: 'info_age', label: '年龄', width: 80 }, { prop: 'info_city', label: '城市', width: 100 } ] }, { label: '成绩信息', children: [ { prop: 'score_chinese', label: '语文', width: 80 }, { prop: 'score_math', label: '数学', width: 80 } ] } ]) // 将多级表头扁平化,并记录每列的完整路径用于映射数据 function flattenHeaders(columns, parentPath = '', parentLabel = '') { let result = [] columns.forEach(col => { if (col.children && col.children.length > 0) { const groupLabel = parentLabel ? `${parentLabel} - ${col.label}` : col.label col.children.forEach(child => { result.push({ prop: child.prop, header: [groupLabel, child.label], // 用于生成两层表头 valuePath: `${parentPath}${child.prop}` }) }) } else { result.push({ prop: col.prop, header: [col.label], valuePath: col.prop }) } }) return result } // 导出为 Excel const exportToExcel = () => { const flatHeaders = flattenHeaders(tableColumns) // 构建表头行(二维数组,支持多级) const headerRows = [[], []] // 第一行:主分组;第二行:具体字段名 const data = [] flatHeaders.forEach(item => { // 填充第一行(分组名) if (item.header.length === 2) { headerRows[0].push(item.header[0]) headerRows[1].push(item.header[1]) } else { headerRows[0].push(item.header[0]) headerRows[1].push('') // 第二行为空 } }) // 合并单元格逻辑(仅对第一行分组合并) const merges = [] let lastGroup = null let groupStartCol = 0 headerRows[0].forEach((groupLabel, index) => { if (groupLabel !== lastGroup) { if (lastGroup !== null) { if (index - groupStartCol > 1) { merges.push({ s: { r: 0, c: groupStartCol }, // start e: { r: 0, c: index - 1 } // end }) } } lastGroup = groupLabel groupStartCol = index } }) // 添加最后一个组的合并 if (headerRows[0].length - groupStartCol > 1 && lastGroup) { merges.push({ s: { r: 0, c: groupStartCol }, e: { r: 0, c: headerRows[0].length - 1 } }) } // 填充数据行 tableData.value.forEach(row => { const rowData = [] flatHeaders.forEach(item => { rowData.push(row[item.prop] || '') }) data.push(rowData) }) // 使用 SheetJS 创建工作簿和工作表 const worksheet = XLSX.utils.aoa_to_sheet([]) // 写入第一行(分组) XLSX.utils.sheet_add_aoa(worksheet, [headerRows[0]], { origin: 'A1' }) // 写入第二行(字段) XLSX.utils.sheet_add_aoa(worksheet, [headerRows[1]], { origin: 'A2' }) // 写入数据行(从第3行开始) XLSX.utils.sheet_add_aoa(worksheet, data, { origin: 'A3' }) // 设置列宽(可选) const colWidths = flatHeaders.map(item => ({ wch: 12 })) worksheet['!cols'] = colWidths // 设置合并单元格 worksheet['!merges'] = merges // 创建工作簿并导出 const workbook = XLSX.utils.book_new() XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1') // 触发下载 XLSX.writeFile(workbook, '动态多级表头导出.xlsx') } </script> <style scoped> .el-table { margin-top: 20px; } </style> ``` --- ### 🔍 代码解释 | 部分 | 说明 | |------|------| | `tableColumns` | 定义了支持嵌套 `children` 的多级结构,是动态可配置的核心 | | `flattenHeaders()` | 将嵌套列结构展平,提取每个叶子节点的路径、标签和数据属性 | | `headerRows` | 构造两行表头:第一行为“个人信息”、“成绩信息”等大类,第二行为具体字段 | | `merges` | 记录需要合并的单元格范围(第一行中相同分组的连续列) | | `XLSX.utils.aoa_to_sheet` | 使用数组 of 数组方式构建表格 | | `sheet_add_aoa` | 分别写入不同行的数据 | | `worksheet['!merges']` | 设置 Excel 中的合并单元格 | --- ### ✅ 输出效果(Excel) | 个人信息 | 成绩信息 | |----------------|--------------| | 年龄 | 城市 | 语文 | 数学 | | 25 | 北京 | 88 | 92 | | 27 | 上海 | 76 | 98 | 其中“个人信息”跨两列合并,“成绩信息”也跨两列。 --- ### ✅ 注意事项 - 如果表头超过两层(比如三级),需要递归处理并生成更多行。 - 当前方案适用于中小型数据量导出(千行以内)。大数据建议使用流式导出或后端服务。 - 可扩展支持样式、冻结窗格、数字格式等高级特性。 --- ### ✅ 相关优化方向 - 抽离成通用组件 `DynamicTableExporter` - 支持自定义导出字段过滤 - 支持时间格式、金额格式自动识别 - 添加 loading 和错误提示 ---
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值