说明

该文章是属于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. vue(element)中使用monaco实现代码高亮

    vue(element)中使用monaco实现代码高亮 使用的是vue语言,用element的组件,要做一个在线编辑代码,要求输入代码内容,可以进行高亮展示,可以切换各不同语言,而且支持关键字补全,还 ...

  2. 关于mysql配置文件中jdbc url 的记录

    版本不同 url不同 大同小异 基本就是不同参数配置的区别 maven 仓库地址 https://mvnrepository.com/artifact/mysql/mysql-connector-ja ...

  3. Java微信授权登录小程序接口

    1.微信授权登录小程序的流程是什么 微信授权登录小程序的流程是一个涉及前端和后端交互的过程,主要目的是让用户能够使用微信账号快速登录小程序,避免重复输入用户名和密码.以下是该流程的详细步骤: 1.1前 ...

  4. 关于捣鼓Gentoo的一些见解

    现在很少有人使用gentoo,大家对它的印象都是一个很难用的系统,我想给大家讲讲折腾Gentoo一年的心得,仅供参考 使用archlinux安装盘,genfstab生成fstab 使用gentoo-k ...

  5. WinForm DevExpress 添加行内按钮

    1.在设计器里面添加一列,设置单元格不可编辑.只读属性 2. 在所在GridView属性里面添加CustomDrawCell事件与RowCellClick事件 private void gvMain_ ...

  6. 推荐一款开源一站式SQL审核查询平台!功能强大、安全可靠!

    1.前言 在当今这个数据驱动的时代,数据库作为企业核心信息资产的载体,其重要性不言而喻.随着企业业务规模的不断扩大,数据库的数量和种类也日益增多,这对数据库的管理与运维工作提出了前所未有的挑战.在这样 ...

  7. C语言中的数据类型及其转换

    目录 计算机中的数据类型 整型数据之间的转换 相同字长之间的转换 小字长转大字长 大字长转小字长 int.float.double之间的转换 float->double double->f ...

  8. git 批量删除本地分支及远程分支

    git 批量删除本地分支及远程分支 一.批量删除本地分支 git branch |grep 'name' |xargs git branch -D 备注: name 为需要匹配的分支名称 二.批量删除 ...

  9. 鸿蒙应用开发:环境搭建(Mac)与项目构建

    ​ 2021年6月2日,华为发布了鸿蒙操作系统-HarmonyOS.它是一款"面向未来".面向全场景(移动办公.运动健康.社交通信.媒体娱乐等)的分布式操作系统.我们要基于Harm ...

  10. C#/.NET/.NET Core技术前沿周刊 | 第 8 期(2024年10.01-10.06)

    前言 C#/.NET/.NET Core技术前沿周刊,你的每周技术指南针!记录.追踪C#/.NET/.NET Core领域.生态的每周最新.最实用.最有价值的技术文章.社区动态.优质项目和学习资源等. ...