说明

该文章是属于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. manim边学边做--圆弧形

    圆弧形可以算是一种特殊的圆形,它是圆形的一部分.manim中,单独为圆弧形状封装了几个模块: Arc:通用的圆弧,根据半径和角度来绘制圆弧 ArcBetweenPoints:根据两个点和角度来绘制圆弧 ...

  2. CMake构建学习笔记6-giflib库的构建

    前面构建的zlib.libpng.libjpeg和libtiff都提供了CMakeList.txt文件,因此都可以通过CMake进行构建.不过有的依赖库是并没有CMakeList.txt文件,也就是官 ...

  3. autorun.inf 配置

    autorun.inf 文件是一个配置文件,通常用于可移动磁盘(如 USB 驱动器和 CD/DVD)来自动执行某些操作或配置一些设置.当插入可移动磁盘时,Windows 会读取 autorun.inf ...

  4. 效率跃升16倍!火山引擎ByteHouse助力销售数据平台复杂查询效率大幅提高

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群.   销售数据,是反映市场趋势.消费者行为以及产品表现的重要指标,也是企业做出精准决策的关键依据.因此,对销售数据 ...

  5. AD LDAP

    Ref: Windows 域之 LDAP与AD [安全科普]AD域安全协议(三)LDAP AD 域服务简介(一)- 基于 LDAP 的 AD 域服务器搭建及其使用 https://milestone- ...

  6. 6.13API接口服务类漏洞探针

    ip地址解析:www.x.x.x.com, 对应网站目录为d:/wwwroot/xiaodi/ 而127.x.x.x,对应网站目录为d:/wwwroot/,可能存在网站备份文件zip,所以ip网址端口 ...

  7. AT_agc057_e 题解

    AT_agc057_e [0] 约定 \(r_i = \sum\limits_{j = 1}^{m}[A_{i,j}\le k]\) \(r^{'}_i = \sum\limits_{j = 1}^{ ...

  8. Locust 进行分布式负载测试

    什么是 Locust Locust 是一个开源的负载测试工具,用于测试网站和其他应用程序的性能.它通过编写 Python 脚本来定义虚拟用户的行为,模拟这些用户对目标系统发起请求.Locust 提供了 ...

  9. SimpleAIAgent:使用免费的glm-4-flash即可开始构建简单的AI Agent应用

    SimpleAIAgent是基于C# Semantic Kernel 与 WPF构建的一款AI Agent探索应用.主要用于使用国产大语言模型或开源大语言模型构建AI Agent应用的探索学习,希望能 ...

  10. [OI] 数学与推论证明 3(高中数学篇)

    1 \[\color{#40865d}(2) \] \(f(x)=x^{2}-a(x+a\ln x)(a\neq0)\),若 \(f(1)+f'(1)=0\) 且 \(a\gt 0\),问可以得到什么 ...