说明

该文章是属于OverallAuth2.0系列文章,每周更新一篇该系列文章(从0到1完成系统开发)。

该系统文章,我会尽量说的非常详细,做到不管新手、老手都能看懂。

说明:OverallAuth2.0 是一个简单、易懂、功能强大的权限+可视化流程管理系统。

友情提醒:本篇文章是属于系列文章,看该文章前,建议先看之前文章,可以更好理解项目结构。

qq群:801913255

有兴趣的朋友,请关注我吧(*^▽^*)。

关注我,学不会你来打我

上篇回顾

在上一篇:(系列九)使用Vue3+Element Plus创建前端框架(附源码) 博客中,我们说道,使用vue3+element plus 创建项目,成功实现了布局组件container+菜单组件Menu搭建框架。

布局样式如下:

然而我们只是实现了界面的搭建,并没有实现任何交互。

也因此有很多人在询问,如何做动态切换菜单。

我想说,不要慌,一切需求都会安排到位。

接下来我们就要实现菜单和路由的结合使用,做到动态切换菜单。

安装路由

命令:npm install vue-router

安装成功后,手动创建以下目录及文件

base-routes.ts 内容

import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
import Panel from '../../views/panel/index.vue'; export const routes: RouteRecordRaw[] = []; routes.push(
{
path: '/panel',
component: Panel,
name: "面板",
},
{
path: '/menu',
redirect: '/menu/index',
meta: { title: '菜单管理' },
name: "菜单管理",
children: [
{
path: '/menu',
name: '菜单',
component: () => import('../../views/menu/index.vue'),
meta: { title: '菜单', requireAuth: true, affix: true, closable: false },
}
]
},
{
path: '/user',
meta: { title: '用户管理' },
name: "用户管理",
children: [
{
path: '/user',
name: '用户',
component: () => import('../../views/user/index.vue'),
meta: { title: '用户' },
}]
},
) //创建路由,并且暴露出去
const router = createRouter({
history: createWebHashHistory(), //开发环境
//history:createWebHistory(), //正式环境
routes
})
export default router

该文件主要是配置菜单的json文件,及暴露路由。里面的属性应该不必多说,很容易看懂。

至于views文件夹中的vue文件内容,大家随便填写什么都可以,只要三个页面的内容不一样即可。

然后在main.ts中配置路由,全局变量。

如下图:

使用路由

做完以上步骤,接下来的工作就很简单了,我们只需要,在HelloWorld.vue(接上一篇文章代码),中修改代码如下

el-main中的内容替换为 <router-view></router-view>
el-menu中添加 router属性
然后导入base-routes.ts 文件,并添加如下代码
  const menu = routes;
return {
menu,
};

完整的HelloWorld.vue代码如下

<template>
<div style="height: calc(100vh); overflow: hidden">
<el-container style="height: 100%; overflow: hidden">
<el-aside width="auto">
<el-menu
class="el-menu-vertical-demo"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b"
style="height: 100%"
router
>
<div class="el-menu-box">
<div
class="logo-image"
style="width: 18px; height: 18px; background-size: 18px 18px"
></div>
<div style="padding-left: 5px; padding-top: 7px">
OverallAuth2.0
</div>
</div>
<div v-for="menuItem in menu" :key="menuItem.path">
<el-sub-menu
v-if="menuItem.children && menuItem.children.length"
:index="menuItem.path"
:key="menuItem.name"
>
<template #title>
<el-icon><location /></el-icon>{{ menuItem.name }}</template
>
<el-menu-item
v-for="subMenuItem in menuItem.children"
:index="subMenuItem.path"
:route="{ name: subMenuItem.name }"
:key="subMenuItem.name"
style="cursor: pointer"
>
{{ subMenuItem.name }}
</el-menu-item>
</el-sub-menu> <el-menu-item
v-else
:index="menuItem.path"
:key="menuItem.path"
:route="{ name: menuItem.name }"
style="cursor: pointer"
>
{{ menuItem.name }}
</el-menu-item>
</div>
</el-menu>
</el-aside> <el-container>
<el-header class="headerCss">
<div style="display: flex; height: 100%; align-items: center">
<div
style="
text-align: left;
width: 50%;
font-size: 18px;
display: flex;
"
>
<div class="logo-image" style="width: 32px; height: 32px"></div>
<div style="padding-left: 10px; padding-top: 7px">
OverallAuth2.0 权限管理系统
</div>
</div>
<div
style="
text-align: right;
width: 50%;
display: flex;
justify-content: right;
cursor: pointer;
"
>
<div
class="user-image"
style="width: 22px; height: 22px; background-size: 22px 22px"
></div>
<div style="padding-left: 5px; padding-top: 3px">
微信公众号:不只是码农
</div>
</div>
</div>
</el-header> <el-main>
<router-view></router-view>
</el-main>
</el-container>
</el-container>
</div>
</template> <script lang="ts">
import { defineComponent, ref } from "vue";
import { routes } from "../router/module/base-routes"; export default defineComponent({
setup() {
const menu = routes;
return {
menu,
};
},
components: {},
});
</script> <style scoped>
.el-menu-vertical-demo:not(.el-menu--collapse) {
width: 200px;
min-height: 400px;
}
.el-menu-box {
display: flex;
padding-left: 25px;
align-items: center;
height: 57px;
box-shadow: 0 1px 4px #00152914;
border: 1px solid #00152914;
color: white;
}
.el-main {
padding-top: 0px;
padding-left: 1px;
padding-right: 1px;
margin: 0;
}
.headerCss {
font-size: 12px;
border: 1px solid #00152914;
box-shadow: 0 1px 4px #00152914;
justify-content: right;
align-items: center;
/* display: flex; */
}
.logo-image {
background-image: url("../components/权限分配.png");
}
.user-image {
background-image: url("../components/用户.png");
}
.demo-tabs /deep/ .el-tabs__header {
color: #333; /* 标签页头部字体颜色 */
margin: 0 0 5px !important;
}
.demo-tabs /deep/ .el-tabs__nav-wrap {
padding-left: 10px;
}
</style>

做好这些后,我们就能够动态切换菜单。

效果如下图

是不是很完美。

不,我想说,还没有完,还差的远。

接着看往下看

加入tab标签

可能大家也发现了,在我们点击左侧菜单时,访问过的菜单,在系统中没有历史访问标签。

现在我们做以下操作,把访问过的菜单记录到tab标签中,以防止系统重新对接口进行请求。

同样修改HelloWorld.vue文件。

把el-main标签中的内容换成

 <el-tabs
v-if="tabsList.length > 0"
v-model="defaultActive"
class="demo-tabs"
@click="tabsClick(defaultActive)"
@tab-remove="tabRemoveClick"
>
<el-tab-pane
v-for="item in tabsList"
:label="item.name"
:name="item.path"
:key="item.path"
:closable="item.path == '/panel' ? false : true"
style="font-size: 16px;"
>
<router-view></router-view>
</el-tab-pane>
</el-tabs>
在el-menu-item标签中,加入菜单切换事件menuItemClick()
setup()方法中的内容替换成
 setup() {
const defaultActive = ref("/panel");
const menu = routes;
const tabsList = ref<RouteRecordRaw[]>([]);
onMounted(() => {
tabsList.value.push(routes[0]);
router.push(routes[0]);
});
//菜单项点击事件
function menuItemClick(subMenuItem: RouteRecordRaw) {
// tabList中不存在则追加
if (!tabsList.value.some((sub) => sub.path == subMenuItem.path)) {
tabsList.value.push(subMenuItem);
}
defaultActive.value = subMenuItem.path;
} //菜单标签点击事件
const tabsClick = (item: string) => {
defaultActive.value = item;
router.push({ path: item });
}; //菜单标签移除事件
const tabRemoveClick = (path: any) => {
tabsList.value.map((item: { path: string }, index: any) => {
if (item.path == path) tabsList.value.splice(index, 1); //index 当前元素索引;1:需要删除的元素个数
});
defaultActive.value = "/panel";
router.push({ path: "/panel" });
};
return {
menu,
tabsList,
defaultActive,
tabsClick,
tabRemoveClick,
menuItemClick,
};
},

以上是HelloWorld.vue文件中变动的代码。

我们来看下完整的HelloWorld.vue文件代码

<template>
<div style="height: calc(100vh); overflow: hidden">
<el-container style="height: 100%; overflow: hidden">
<el-aside width="auto">
<el-menu
:default-active="defaultActive"
class="el-menu-vertical-demo"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b"
style="height: 100%"
router
>
<div class="el-menu-box">
<div
class="logo-image"
style="width: 18px; height: 18px; background-size: 18px 18px"
></div>
<div style="padding-left: 5px; padding-top: 7px">
OverallAuth2.0
</div>
</div>
<div v-for="menuItem in menu" :key="menuItem.path">
<el-sub-menu
v-if="menuItem.children && menuItem.children.length"
:index="menuItem.path"
:key="menuItem.name"
>
<template #title>
<el-icon><location /></el-icon>{{ menuItem.name }}</template
>
<el-menu-item
v-for="subMenuItem in menuItem.children"
:index="subMenuItem.path"
:route="{ name: subMenuItem.name }"
:key="subMenuItem.name"
@click="menuItemClick(subMenuItem)"
style="cursor: pointer"
>
{{ subMenuItem.name }}
</el-menu-item>
</el-sub-menu> <el-menu-item
v-else
:index="menuItem.path"
:key="menuItem.path"
:route="{ name: menuItem.name }"
@click="menuItemClick(menuItem)"
style="cursor: pointer"
>
{{ menuItem.name }}
</el-menu-item>
</div>
</el-menu>
</el-aside> <el-container>
<el-header class="headerCss">
<div style="display: flex; height: 100%; align-items: center">
<div
style="
text-align: left;
width: 50%;
font-size: 18px;
display: flex;
"
>
<div class="logo-image" style="width: 32px; height: 32px"></div>
<div style="padding-left: 10px; padding-top: 7px">
OverallAuth2.0 权限管理系统
</div>
</div>
<div
style="
text-align: right;
width: 50%;
display: flex;
justify-content: right;
cursor: pointer;
"
>
<div
class="user-image"
style="width: 22px; height: 22px; background-size: 22px 22px"
></div>
<div style="padding-left: 5px; padding-top: 3px">
微信公众号:不只是码农
</div>
</div>
</div>
</el-header> <el-main>
<el-tabs
v-if="tabsList.length > 0"
v-model="defaultActive"
class="demo-tabs"
@click="tabsClick(defaultActive)"
@tab-remove="tabRemoveClick"
>
<el-tab-pane
v-for="item in tabsList"
:label="item.name"
:name="item.path"
:key="item.path"
:closable="item.path == '/panel' ? false : true"
style="font-size: 16px"
>
<router-view></router-view>
</el-tab-pane>
</el-tabs>
</el-main>
</el-container>
</el-container>
</div>
</template> <script lang="ts">
import { defineComponent, onMounted, ref } from "vue";
import router, { routes } from "../router/module/base-routes";
import { RouteRecordRaw } from "vue-router"; export default defineComponent({
setup() {
const defaultActive = ref("/panel");
const menu = routes;
const tabsList = ref<RouteRecordRaw[]>([]); //初始加载dom
onMounted(() => {
tabsList.value.push(routes[0]); //默认打开第一个标签
router.push(routes[0]);
});
//菜单项点击事件
function menuItemClick(subMenuItem: RouteRecordRaw) {
// tabList中不存在则追加
if (!tabsList.value.some((sub) => sub.path == subMenuItem.path)) {
tabsList.value.push(subMenuItem);
}
defaultActive.value = subMenuItem.path;
} //菜单标签点击事件
const tabsClick = (item: string) => {
defaultActive.value = item;
router.push({ path: item });
}; //菜单标签移除事件
const tabRemoveClick = (path: any) => {
tabsList.value.map((item: { path: string }, index: any) => {
if (item.path == path) tabsList.value.splice(index, 1); //index 当前元素索引;1:需要删除的元素个数
});
defaultActive.value = "/panel";
router.push({ path: "/panel" });
};
return {
menu,
tabsList,
defaultActive,
tabsClick,
tabRemoveClick,
menuItemClick,
};
},
components: {},
});
</script> <style scoped>
.el-menu-vertical-demo:not(.el-menu--collapse) {
width: 200px;
min-height: 400px;
}
.el-menu-box {
display: flex;
padding-left: 25px;
align-items: center;
height: 57px;
box-shadow: 0 1px 4px #00152914;
border: 1px solid #00152914;
color: white;
}
.el-main {
padding-top: 0px;
padding-left: 1px;
padding-right: 1px;
margin: 0;
}
.headerCss {
font-size: 12px;
border: 1px solid #00152914;
box-shadow: 0 1px 4px #00152914;
justify-content: right;
align-items: center;
/* display: flex; */
}
.logo-image {
background-image: url("../components/权限分配.png");
}
.user-image {
background-image: url("../components/用户.png");
}
.demo-tabs /deep/ .el-tabs__header {
color: #333; /* 标签页头部字体颜色 */
margin: 0 0 5px !important;
}
.demo-tabs /deep/ .el-tabs__nav-wrap {
padding-left: 10px;
}
</style>

我们看下效果

后端WebApi 预览地址:http://139.155.137.144:8880/swagger/index.html

前端vue 预览地址:http://139.155.137.144:8881

关注公众号:发送【权限】,获取前后端代码

有兴趣的朋友,请关注我微信公众号吧(*^▽^*)。

关注我:一个全栈多端的宝藏博主,定时分享技术文章,不定时分享开源项目。关注我,带你认识不一样的程序世界

(系列十)Vue3中菜单和路由的结合使用,实现菜单的动态切换(附源码)的更多相关文章

  1. C#进阶系列——一步一步封装自己的HtmlHelper组件:BootstrapHelper(三:附源码)

    前言:之前的两篇封装了一些基础的表单组件,这篇继续来封装几个基于bootstrap的其他组件.和上篇不同的是,这篇的有几个组件需要某些js文件的支持. 本文原创地址:http://www.cnblog ...

  2. shell脚本中字符串的常见操作及"command not found"报错处理(附源码)

    简介 昨天在通过shell脚本实现一个功能的时候,由于对shell处理字符串的方法有些不熟悉导致花了不少时间也犯了很多错误,因此将昨日的一些错误记录下来,避免以后再犯. 字符串的定义与赋值 # 定义S ...

  3. 零基础自学Python十天的时候,写的一款猜数字小游戏,附源码和软件下载链接!

    自学一门语言最重要的是要及时给自己反馈,那么经常写一些小程序培养语感很重要,写完可以总结一下程序中运用到了哪些零散的知识点. 本程序中运用到的知识点有: 1.输入输出函数 (input.print) ...

  4. Vue路由实现之通过URL中的hash(#号)来实现不同页面之间的切换(图表展示、案例分析、附源码详解)

    前言 本篇随笔主要写了Vue框架中路由的基本概念.路由对象属性.vue-router插件的基本使用效果展示.案例分析.原理图解.附源码地址获取. 作为自己对Vue路由进行页面跳转效果知识的总结与笔记. ...

  5. MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)

    前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...

  6. android弹力效果菜单、组件化项目、电影票选座控件的源码

    Android精选源码 android启动扫一扫和收付款的小部件源码 android弹力效果的抽屉菜单源码 对RecyclerView Item做动画 源码 android类似QQ空间,微信朋友圈,微 ...

  7. arcgis api 3.x for js 入门开发系列批量叠加 zip 压缩 SHP 图层优化篇(附源码下载)

    前言 关于本篇功能实现用到的 api 涉及类看不懂的,请参照 esri 官网的 arcgis api 3.x for js:esri 官网 api,里面详细的介绍 arcgis api 3.x 各个类 ...

  8. openlayers4 入门开发系列之地图导航控件篇(附源码下载)

    前言 openlayers4 官网的 api 文档介绍地址 openlayers4 api,里面详细的介绍 openlayers4 各个类的介绍,还有就是在线例子:openlayers4 官网在线例子 ...

  9. openlayers4 入门开发系列结合 echarts4 实现散点图(附源码下载)

    前言 openlayers4 官网的 api 文档介绍地址 openlayers4 api,里面详细的介绍 openlayers4 各个类的介绍,还有就是在线例子:openlayers4 官网在线例子 ...

  10. openlayers5-webpack 入门开发系列结合 echarts4 实现散点图(附源码下载)

    前言 openlayers5-webpack 入门开发系列环境知识点了解: node 安装包下载webpack 打包管理工具需要依赖 node 环境,所以 node 安装包必须安装,上面链接是官网下载 ...

随机推荐

  1. 如何快速在 Apache DolphinScheduler 新扩展一个任务插件?

    作者 | 代立冬 编辑 | Debra Chen Apache DolphinScheduler 是现代数据工作流编排平台,具有非常强大的可视化能力,DolphinScheduler 致力于使数据工程 ...

  2. 在LCD上的任意位置显示一张任意大小的jpg图片

    /************************************************* * * file name:lcdshowjpg.c * author :momolyl@126. ...

  3. 关于没使用Mybatis 分页,分页sql默认执行count(0) 的问题

    之前的Impl 的方法 :selectFromList(String uid, Integer pageNum, Integer pageSize) 之后的Impl 的方法 :selectFromLi ...

  4. LaTeX 几种中文字体的比较

    根据自己的喜好给常见的几个中文字体的打分: 字体选项 字体名 得分 adobe Adobe 宋体 Std 5 fandol FandolSong 0 founder 方正书宋_GBK 10 hanyi ...

  5. c++学习笔记(四):面向对象

    目录 类 & 对象 封装 访问权限 类的构造函数&析构函数 构造函数的分类及调用 拷贝构造函数的调用时机 构造函数调用规则 深拷贝与浅拷贝 初始化列表 类对象作为类成员 静态成员 C+ ...

  6. Docker 优化NUXT镜像体积

    FROM node:xxx-alpine # 环境变量赋值 ENV 参数名 参数值 RUN mkdir -p /app COPY ./app/package.json /app/package.jso ...

  7. 总结篇4:redis 核心数据存储结构及核心业务模型实现应用场景

    总结篇4:redis 核心数据存储结构及核心业务模型实现应用场景 redis 和memcached 有什么区别?为什么在高并发下,单线程的redis 比多线程的效率高? mc 可以缓存图片和视频,re ...

  8. Angular Material 18+ 高级教程 – CDK Accessibility の Focus

    介绍 CDK Focus 是对原生 DOM focus 的上层封装和扩展. Focus Origin 原生 DOM focus 我们只能知道 element 被 focus 了,但是无法知道它是怎么被 ...

  9. CSS – 实战 Font

    前言 这篇想整理一下在网页开发中, 字体是如果被处理的. 先看完: 平面设计 – 字体 CSS – Font / Text 属性 CSS – Font Family CSS – word-break, ...

  10. 全面掌握 Jest:从零开始的测试指南(下篇)

    在上一篇测试指南中,我们介绍了Jest 的背景.如何初始化项目.常用的匹配器语法以及钩子函数的使用.这一篇篇将继续深入探讨 Jest 的高级特性,包括 Mock 函数.异步请求的处理.Mock 请求的 ...