(系列十)Vue3中菜单和路由的结合使用,实现菜单的动态切换(附源码)
说明
该文章是属于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(接上一篇文章代码),中修改代码如下
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>
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中菜单和路由的结合使用,实现菜单的动态切换(附源码)的更多相关文章
- C#进阶系列——一步一步封装自己的HtmlHelper组件:BootstrapHelper(三:附源码)
前言:之前的两篇封装了一些基础的表单组件,这篇继续来封装几个基于bootstrap的其他组件.和上篇不同的是,这篇的有几个组件需要某些js文件的支持. 本文原创地址:http://www.cnblog ...
- shell脚本中字符串的常见操作及"command not found"报错处理(附源码)
简介 昨天在通过shell脚本实现一个功能的时候,由于对shell处理字符串的方法有些不熟悉导致花了不少时间也犯了很多错误,因此将昨日的一些错误记录下来,避免以后再犯. 字符串的定义与赋值 # 定义S ...
- 零基础自学Python十天的时候,写的一款猜数字小游戏,附源码和软件下载链接!
自学一门语言最重要的是要及时给自己反馈,那么经常写一些小程序培养语感很重要,写完可以总结一下程序中运用到了哪些零散的知识点. 本程序中运用到的知识点有: 1.输入输出函数 (input.print) ...
- Vue路由实现之通过URL中的hash(#号)来实现不同页面之间的切换(图表展示、案例分析、附源码详解)
前言 本篇随笔主要写了Vue框架中路由的基本概念.路由对象属性.vue-router插件的基本使用效果展示.案例分析.原理图解.附源码地址获取. 作为自己对Vue路由进行页面跳转效果知识的总结与笔记. ...
- MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)
前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...
- android弹力效果菜单、组件化项目、电影票选座控件的源码
Android精选源码 android启动扫一扫和收付款的小部件源码 android弹力效果的抽屉菜单源码 对RecyclerView Item做动画 源码 android类似QQ空间,微信朋友圈,微 ...
- arcgis api 3.x for js 入门开发系列批量叠加 zip 压缩 SHP 图层优化篇(附源码下载)
前言 关于本篇功能实现用到的 api 涉及类看不懂的,请参照 esri 官网的 arcgis api 3.x for js:esri 官网 api,里面详细的介绍 arcgis api 3.x 各个类 ...
- openlayers4 入门开发系列之地图导航控件篇(附源码下载)
前言 openlayers4 官网的 api 文档介绍地址 openlayers4 api,里面详细的介绍 openlayers4 各个类的介绍,还有就是在线例子:openlayers4 官网在线例子 ...
- openlayers4 入门开发系列结合 echarts4 实现散点图(附源码下载)
前言 openlayers4 官网的 api 文档介绍地址 openlayers4 api,里面详细的介绍 openlayers4 各个类的介绍,还有就是在线例子:openlayers4 官网在线例子 ...
- openlayers5-webpack 入门开发系列结合 echarts4 实现散点图(附源码下载)
前言 openlayers5-webpack 入门开发系列环境知识点了解: node 安装包下载webpack 打包管理工具需要依赖 node 环境,所以 node 安装包必须安装,上面链接是官网下载 ...
随机推荐
- vue(element)中使用monaco实现代码高亮
vue(element)中使用monaco实现代码高亮 使用的是vue语言,用element的组件,要做一个在线编辑代码,要求输入代码内容,可以进行高亮展示,可以切换各不同语言,而且支持关键字补全,还 ...
- 关于mysql配置文件中jdbc url 的记录
版本不同 url不同 大同小异 基本就是不同参数配置的区别 maven 仓库地址 https://mvnrepository.com/artifact/mysql/mysql-connector-ja ...
- Java微信授权登录小程序接口
1.微信授权登录小程序的流程是什么 微信授权登录小程序的流程是一个涉及前端和后端交互的过程,主要目的是让用户能够使用微信账号快速登录小程序,避免重复输入用户名和密码.以下是该流程的详细步骤: 1.1前 ...
- 关于捣鼓Gentoo的一些见解
现在很少有人使用gentoo,大家对它的印象都是一个很难用的系统,我想给大家讲讲折腾Gentoo一年的心得,仅供参考 使用archlinux安装盘,genfstab生成fstab 使用gentoo-k ...
- WinForm DevExpress 添加行内按钮
1.在设计器里面添加一列,设置单元格不可编辑.只读属性 2. 在所在GridView属性里面添加CustomDrawCell事件与RowCellClick事件 private void gvMain_ ...
- 推荐一款开源一站式SQL审核查询平台!功能强大、安全可靠!
1.前言 在当今这个数据驱动的时代,数据库作为企业核心信息资产的载体,其重要性不言而喻.随着企业业务规模的不断扩大,数据库的数量和种类也日益增多,这对数据库的管理与运维工作提出了前所未有的挑战.在这样 ...
- C语言中的数据类型及其转换
目录 计算机中的数据类型 整型数据之间的转换 相同字长之间的转换 小字长转大字长 大字长转小字长 int.float.double之间的转换 float->double double->f ...
- git 批量删除本地分支及远程分支
git 批量删除本地分支及远程分支 一.批量删除本地分支 git branch |grep 'name' |xargs git branch -D 备注: name 为需要匹配的分支名称 二.批量删除 ...
- 鸿蒙应用开发:环境搭建(Mac)与项目构建
2021年6月2日,华为发布了鸿蒙操作系统-HarmonyOS.它是一款"面向未来".面向全场景(移动办公.运动健康.社交通信.媒体娱乐等)的分布式操作系统.我们要基于Harm ...
- C#/.NET/.NET Core技术前沿周刊 | 第 8 期(2024年10.01-10.06)
前言 C#/.NET/.NET Core技术前沿周刊,你的每周技术指南针!记录.追踪C#/.NET/.NET Core领域.生态的每周最新.最实用.最有价值的技术文章.社区动态.优质项目和学习资源等. ...