1. 背景
当处理好后端传过来的权限路由表的时候,需要通过这份路由表进行前端的菜单注册。本文使用vue2技术栈,以 element ui的navmenu组件为例,记录一下生成菜单的处理思路。
2. 处理思路
处理步骤大概分为如下三步\
- 二级路由出口
- 通过navmenu渲染后端权限路由表
- 前端路由注册
2.1 二级路由出口
MainView组件部分代码如下:
<template>
<div class="main-view">
<!-- navmenu菜单 -->
<sidebar class="sidebar-container" />
<!-- 二级路由占位 -->
<router-view />
</div>
</template>
<script>
import { Sidebar } from './layout/components/index'
export default {
name: 'MainView',
components: {
Sidebar
},
}
</script>
<style scoped>
.main-view {
display: flex;
}
</style>
2.2 navmenu菜单渲染
最小单元是
el-menu-item
; 如果有children
存在,则使用el-submenu
包裹
sidebar部分代码如下:
<template>
<div>
<el-menu ref="menuRef" unique-opened :collapse-transition="false" mode="vertical">
<sidebar-item v-for="route in routes" :key="route.id" :item="route" />
</el-menu>
</div>
</template>
<script>
import SidebarItem from './SidebarItem'
export default {
components: { SidebarItem },
computed: {
routes() {
return this.$store.state.menu.menus; // 后端权限路由表(详细可见相关文档)
},
},
}
</script>
<style scoped></style>
SidebarItem部分代码如下:
<template>
<div class="menu-wrapper">
<!-- 如果没有children,或者children为空数组的 => 直接渲染为el-menu-item -->
<template v-if="!item.children?.length">
<!--app-link => 如果是前端路由,则渲染为router-link,如果是url,则新开一个页签 -->
<app-link v-if="item.meta" :to="item.path">
<el-menu-item :index="item.path">
<item :icon="item.meta?.icon" :title="item.meta.title" />
</el-menu-item>
</app-link>
</template>
<el-submenu v-else ref="subMenu" :index="item.meta && item.meta.title" popper-append-to-body>
<template slot="title">
<item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
</template>
<!-- 递归渲染 -->
<sidebar-item
v-for="child in item.children"
:key="child.id"
:item="child"
/>
</el-submenu>
</div>
</template>
<script>
import Item from './Item'
import AppLink from './Link'
import FixiOSBug from './FixiOSBug'
export default {
name: 'SidebarItem',
components: { Item, AppLink },
props: {
// route object
item: {
type: Object,
required: true
},
},
}
</script>
Item(函数组件,用于展示icon和title) 部分代码如下:
<script>
export default {
name: 'MenuItem',
functional: true,
props: {
icon: {
type: String,
default: ''
},
title: {
type: String,
default: ''
}
},
render(h, context) {
const { icon, title } = context.props
const vnodes = []
if (icon) {
vnodes.push(<svg-icon icon-class={icon} />)
}
if (title) {
vnodes.push(<span slot="title">{title}</span>)
}
return vnodes
}
}
</script>
AppLink 组件(动态组件,如果是路由则渲染为router-link
,否则渲染为a
标签)部分代码:
<template>
<!-- eslint-disable vue/require-component-is -->
<component v-bind="linkProps(to)">
<slot />
</component>
</template>
<script>
import { isExternal } from '@/utils/validate'; // 判断是否为url
export default {
props: {
to: {
type: String,
required: true
}
},
methods: {
linkProps(url) {
if (isExternal(url)) {
return {
is: 'a',
href: url,
target: '_blank',
rel: 'noopener'
}
}
return {
is: 'router-link',
to: url
}
}
}
}
</script>
2.3 前端路由注册
router.js 部分代码如下:
import Vue from "vue";
import Router from "vue-router";
import MainView from "@/components/mainView"; // 二级路由出口和navmenu组件
import LoginView from "@/components/Login"; // 登录页
import operationData from "./operationData"; // 前端各个模块的路由
Vue.use(Router);
export default new Router({
routes: [
...operationData,
{
path: "/login",
name: "Login",
component: LoginView,
},
{
path: "/",
component: MainView,
redirect: "/dashboard", // 一级路由只有dashboard
meta: { title: "首页", icon: "dashboard" },
children: [
{
path: "dashboard",
name: "Dashboard",
component: () => import("@/views/dashboard/dashboard.vue"),
hidden: true,
alwaysShow: true,
meta: { title: "首页", icon: "dashboard", affix: true },
},
],
},
],
});
operationData (前端各模块的路由表)部分记录如下:
import MainView from "@/components/mainView"; // 二级路由出口和navmenu组件
export default [
{
path: '/operationData',
component: MainView,
name: 'OperationData',
redirect: 'noRedirect',
meta: { title: '运营数据', icon: 'analysis' },
children: [
{
path: 'disease-type-analysis',
name: 'DiseaseTypeAnalysis',
component: () =>
import('@/views/operationData/disease-and-prescription/disease-type-analysis.vue'),
meta: { title: '诊所病种分析' }
},
...
]
}
]
函数组件
vue 在 render 函数的第二个参数中提供了
context
,用于访问props
、slots
等属性:
props: 组件 props 对象。\
- data: 组件的数据对象,即 h 的第二个参数。
- listeners: 组件上监听的事件对象,在组件上监听
event-name
,listeners 对象就有event-name
属性,值为函数,数据可通过该函数的参数抛到父组件。listeners 是data.on
的别名。 - slots: 函数,返回了包含所有插槽的对象。
- scopedSlots: 对象,每个属性为返回插槽的 VNode 的函数,可传递参数。
- children:子节点数组,可直接传入
h
函数的第三个参数。 - parent: 父组件,可通过它修改父组件的 data 或者调用父组件的方法。
- injection:注入对象
相关记录和参考资料
最后
仅作为记录