说明

该文章是属于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. BOM 相关知识总结

    一:介绍BOM 1 1.什么是BOM? 2 DOM就是一套操作HTML标签的API(接口/方法/属性) 3 BOM就是一套操作浏览器的API(接口/方法/属性) 4 5 2.BOM中常见的对象 6 w ...

  2. SPI协议,MCP2515裸机驱动详解

    SPI概述 Serial Peripheral interface 通用串行外围设备接口 是Motorola首先在其MC68HCXX系列处理器上定义的.SPI接口主要应用在 EEPROM,FLASH, ...

  3. 【CMake系列】06-项目结构与输出路径管理

    为了对大型项目实现更好的管理[模块化协作开发等等],cmake 提供了很多指令,可以对项目的结构进行调整.管理,便于项目的合理规划.本文我们要学习的就是 项目结构的设置,以及 构建程序等 输出路径的设 ...

  4. Lambert cos 定律再积分无穷级数求和

    设有能量为 \(I\) 的一束光射向表面 \(s\),发生理想的漫反射.设反射率为 \(a\),则 \(s\) 向在 \(\phi\) 方向反射的能量 \(R\) 可由 Lambert cos 定律给 ...

  5. JavaScript设计模式样例十 —— 组合模式

    组合模式(Composite Pattern) 定义:又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象.目的:将对象组合成树形结构以表示"部分-整体"的层次结构.场景:您 ...

  6. zabbix 4.0监控web页面

    web监控 web监测是用来监控web程序的,可以监控web程序的平均下载速度.响应时间.HTTP状态码. 环境 至少有两台被监控主机 静态网页检测 选择监控主机 ​ 配置场景 web监测->创 ...

  7. docker with GPU support

    自己总结的: nvidia-docker, 不支持windows,2019.10, nvidia-docker过时了,从docker 1903开始,安装一个nvidia-container-runti ...

  8. 万字长文全面详解现代C++智能指针:原理、应用和陷阱

    现代C++智能指针详解:原理.应用和陷阱 智能指针是C++11引入的新特性.本篇文章详细介绍了C++智能指针的原理.应用与陷阱,通过丰富的代码实例介绍了三种智能指针:std::unique_ptr.s ...

  9. Mmdetection dataset pipline

    数据的加载顺序是上图(来自mmdetection官网)中的顺序进行,上图中只有一次padding,但是其实dataloader一共有两次padding,一次是pad,另外一次就是collect后,给模 ...

  10. 音视频处理三剑客之 AEC:回声产生原因及回声消除原理

    在上一期课程<音视频开发者进阶 -- 音频要素>中,我们从声音三要素.音频模拟信号的数字化和音频数字信号特征等方面,重新认识了"声音"这个老朋友.今天,我们会进一步聊聊 ...