前后端分离项目实战-通用管理系统搭建(前端Vue3+ElementPlus,后端Springboot+Mysql+Redis)第七篇:菜单和路由动态绑定

ops/2024/10/9 0:40:26/

天行健,君子以自强不息;地势坤,君子以厚德载物。


每个人都有惰性,但不断学习是好好生活的根本,共勉!


文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。


绿竹入幽径,青萝拂行衣。
——《下终南山过斛斯山人宿置酒》


文章目录

    • 24. 菜单和路由动态绑定
      • 24.1 后端接口代码修改(MenuController.java)
      • 24.2 接口返回数据
      • 24.3 路由代码修改(src/router/index.ts)
      • 24.4 缓存代码修改(src/store/index.ts)
      • 24.5 主页代码修改(src/views/index/HomeIndex.vue)
      • 24.6 菜单栏组件代码修改(src/views/index/components/MenuBar.vue)
      • 24.7 工具栏组件代码修改(src/views/index/components/ToolBar.vue)
      • 24.8 新增组件-工作台(src/views/index/components/WorkPlat.vue)
      • 24.9 手机验证码登录组件代码修改(src/views/login/components/PhoneCodeForm.vue)
      • 24.10 二维码登录组件代码修改(src/views/login/components/QcodeForm.vue)
      • 24.11 账号密码登录组件代码修改(src/views/login/components/UsernameForm.vue)
      • 24.12 App.vue组件代码修改(src/App.vue)
      • 24.13 页面效果展示


Vue入门学习专栏


24. 菜单和路由动态绑定

将菜单和路由地址动态绑定,实现点击菜单跳转对应路由,且刷新页面依旧保持当前选中菜单界面

24.1 后端接口代码修改(MenuController.java)

这里需要先修改后端接口代码中的数据结构,修改后代码如下
MenuController.java

package com.hslb.management.controller;import com.alibaba.fastjson2.JSONObject;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;/*** @ClassDescription: 菜单相关接口* @JdkVersion: 1.8* @Author: 李白* @Created: 2024/8/19 14:19*/@RestController
@CrossOrigin
@RequestMapping(value = "/menu")
public class MenuController {@GetMapping(value = "/getMenu")public JSONObject getMenu(){//menuList<JSONObject> menuList = new ArrayList<>();//工作台-----------------------------------------------------------------------JSONObject workPlat = new JSONObject();workPlat.put("name","工作台");workPlat.put("path","/HomeIndex/WorkPlat");workPlat.put("icon","Platform");workPlat.put("component","WorkPlat");//是否需要鉴权
//        JSONObject meta = new JSONObject();
//        metaList<JSONObject> workPlatList = new ArrayList<>();
//        JSONObject workPlatChildrenJs = new JSONObject();
//        workPlatChildrenJs.put("name","列表实例");
//        workPlatChildrenJs.put("path","/index/workPlat/List");
//        workPlatChildrenJs.put("icon","ScaleToOriginal");
//        workPlatChildrenJs.put("components","WorkPlat");
//        workPlatList.add(workPlatChildrenJs);workPlat.put("children",workPlatList);menuList.add(workPlat);//业务菜单-----------------------------------------------------------------------JSONObject businessMenu = new JSONObject();businessMenu.put("name","业务菜单");businessMenu.put("path","/HomeIndex/businessMenu");businessMenu.put("icon","Menu");businessMenu.put("component","BusinessMenu");List<JSONObject>  businessMenuList = new ArrayList<>();//列表示例JSONObject businessMenuListExam = new JSONObject();businessMenuListExam.put("name","列表示例");businessMenuListExam.put("path","/HomeIndex/businessMenu/list");businessMenuListExam.put("icon","Tickets");businessMenuListExam.put("component","BusinessMenu");businessMenuList.add(businessMenuListExam);//详情示例JSONObject businessMenuDetailExam = new JSONObject();businessMenuDetailExam.put("name","详情示例");businessMenuDetailExam.put("path","/HomeIndex/businessMenu/detail");businessMenuDetailExam.put("icon","DocumentRemove");businessMenuDetailExam.put("component","BusinessMenu");businessMenuList.add(businessMenuDetailExam);//图表示例JSONObject businessMenuChartExam = new JSONObject();businessMenuChartExam.put("name","图表示例");businessMenuChartExam.put("path","/HomeIndex/businessMenu/chart");businessMenuChartExam.put("icon","Postcard");businessMenuChartExam.put("component","BusinessMenu");businessMenuList.add(businessMenuChartExam);//文件上传JSONObject businessMenuFileUpload = new JSONObject();businessMenuFileUpload.put("name","文件上传");businessMenuFileUpload.put("path","/HomeIndex/businessMenu/fileUpload");businessMenuFileUpload.put("icon","Files");businessMenuFileUpload.put("component","BusinessMenu");businessMenuList.add(businessMenuFileUpload);//富文本示例JSONObject businessMenuRichTextExam = new JSONObject();businessMenuRichTextExam.put("name","富文本示例");businessMenuRichTextExam.put("path","/HomeIndex/businessMenu/richText");businessMenuRichTextExam.put("icon","Document");businessMenuRichTextExam.put("component","BusinessMenu");businessMenuList.add(businessMenuRichTextExam);businessMenu.put("children",businessMenuList);menuList.add(businessMenu);//基础数据-----------------------------------------------------------------------JSONObject baseData = new JSONObject();baseData.put("name","基础数据");baseData.put("path","/HomeIndex/baseData");baseData.put("icon","TrendCharts");baseData.put("component","BaseData");List<JSONObject>  baseDataList = new ArrayList<>();//基础数据-消息数据JSONObject baseDataMsgData = new JSONObject();baseDataMsgData.put("name","消息数据");baseDataMsgData.put("path","/HomeIndex/baseData/msgData");baseDataMsgData.put("icon","Message");baseDataMsgData.put("component","BaseData");baseDataList.add(baseDataMsgData);//基础数据-实体配置JSONObject baseDataEntitySet = new JSONObject();baseDataEntitySet.put("name","实体配置");baseDataEntitySet.put("path","/HomeIndex/baseData/entitySet");baseDataEntitySet.put("icon","Operation");baseDataEntitySet.put("component","BaseData");baseDataList.add(baseDataEntitySet);//基础数据-验证码数据JSONObject baseDataValidationCode = new JSONObject();baseDataValidationCode.put("name","验证码数据");baseDataValidationCode.put("path","/HomeIndex/baseData/validationCode");baseDataValidationCode.put("icon","DocumentChecked");baseDataValidationCode.put("component","BaseData");baseDataList.add(baseDataValidationCode);baseData.put("children",baseDataList);menuList.add(baseData);//系统管理-----------------------------------------------------------------------JSONObject systemManagement = new JSONObject();systemManagement.put("name","系统管理");systemManagement.put("path","/HomeIndex/SystemManagement");systemManagement.put("icon","Tools");systemManagement.put("component","System");List<JSONObject>  systemManagementList = new ArrayList<JSONObject>();//系统管理-用户管理JSONObject sysMngUser = new JSONObject();sysMngUser.put("name","用户管理");sysMngUser.put("path","/HomeIndex/SystemManagement/user");sysMngUser.put("icon","User");sysMngUser.put("component","System");systemManagementList.add(sysMngUser);//系统管理-角色管理JSONObject sysMngRole = new JSONObject();sysMngRole.put("name","角色管理");sysMngRole.put("path","/HomeIndex/SystemManagement/role");sysMngRole.put("icon","Van");sysMngRole.put("component","System");systemManagementList.add(sysMngRole);//系统管理-菜单管理JSONObject sysMngMenu = new JSONObject();sysMngMenu.put("name","菜单管理");sysMngMenu.put("path","/HomeIndex/SystemManagement/menu");sysMngMenu.put("icon","Reading");sysMngMenu.put("component","System");systemManagementList.add(sysMngMenu);//系统管理-日志管理JSONObject sysMngLog = new JSONObject();sysMngLog.put("name","日志管理");sysMngLog.put("path","/HomeIndex/SystemManagement/log");sysMngLog.put("icon","Memo");sysMngLog.put("component","System");systemManagementList.add(sysMngLog);//系统管理-系统配置JSONObject sysMngSet = new JSONObject();sysMngSet.put("name","系统配置");sysMngSet.put("path","/HomeIndex/SystemManagement/set");sysMngSet.put("icon","DataLine");sysMngSet.put("component","System");systemManagementList.add(sysMngSet);systemManagement.put("children",systemManagementList);menuList.add(systemManagement);JSONObject resultJson = new JSONObject();resultJson.put("result", 200);resultJson.put("data", menuList);resultJson.put("msg", "左侧栏菜单数据获取");System.out.println(resultJson);return resultJson;}}

24.2 接口返回数据

接口返回的数据如下

{"result": 200,"data": [{"name": "工作台","path": "/HomeIndex/WorkPlat","icon": "Platform","component": "WorkPlat","children": []},{"name": "业务菜单","path": "/HomeIndex/businessMenu","icon": "Menu","component": "BusinessMenu","children": [{"name": "列表示例","path": "/HomeIndex/businessMenu/list","icon": "Tickets","component": "BusinessMenu"},{"name": "详情示例","path": "/HomeIndex/businessMenu/detail","icon": "DocumentRemove","component": "BusinessMenu"},{"name": "图表示例","path": "/HomeIndex/businessMenu/chart","icon": "Postcard","component": "BusinessMenu"},{"name": "文件上传","path": "/HomeIndex/businessMenu/fileUpload","icon": "Files","component": "BusinessMenu"},{"name": "富文本示例","path": "/HomeIndex/businessMenu/richText","icon": "Document","component": "BusinessMenu"}]},{"name": "基础数据","path": "/HomeIndex/baseData","icon": "TrendCharts","component": "BaseData","children": [{"name": "消息数据","path": "/HomeIndex/baseData/msgData","icon": "Message","component": "BaseData"},{"name": "实体配置","path": "/HomeIndex/baseData/entitySet","icon": "Operation","component": "BaseData"},{"name": "验证码数据","path": "/HomeIndex/baseData/validationCode","icon": "DocumentChecked","component": "BaseData"}]},{"name": "系统管理","path": "/HomeIndex/SystemManagement","icon": "Tools","component": "System","children": [{"name": "用户管理","path": "/HomeIndex/SystemManagement/user","icon": "User","component": "System"},{"name": "角色管理","path": "/HomeIndex/SystemManagement/role","icon": "Van","component": "System"},{"name": "菜单管理","path": "/HomeIndex/SystemManagement/menu","icon": "Reading","component": "System"},{"name": "日志管理","path": "/HomeIndex/SystemManagement/log","icon": "Memo","component": "System"},{"name": "系统配置","path": "/HomeIndex/SystemManagement/set","icon": "DataLine","component": "System"}]}],"msg": "左侧栏菜单数据获取"
}

24.3 路由代码修改(src/router/index.ts)

修改路由代码新增meta参数,其中的requireAuth参数会在后续使用导航守卫时根据此参数进行路由跳转时的校验
应用了router的路由匹配机制使用:path+来匹配一级到多级的路由
src/router/index.ts


import { createRouter, createWebHistory } from 'vue-router'// 自定义路由组件或从其他文件导入,这里选择从其他文件导入
import UserLogin from "../views/login/UserLogin.vue";
// import UserLogin from '@/views/login/UserLogin.vue';
import HomeIndex from '@/views/index/HomeIndex.vue';
import WorkPlat from '@/views/index/components/WorkPlat.vue';
import ResetPwd from '@/views/resetPassword/ResetPwd.vue';
// import ResetPwd from '../views/resetPassword/ResetPwd.vue';// 定义一些路由,每个路由都需要映射到一个组件,
const routes = [{path: '/',// component: HomeIndex,// component: WorkPlat,// redirect: "/HomeIndex",redirect: "/HomeIndex/WorkPlat",meta: {requireAuth: true// requireAuth: false}},{path: '/UserLogin',component: UserLogin,meta: {requireAuth: false}},{// 匹配具有多个部分的路由 如/111/222/333// :path+表示匹配一级或多级// path: '/:HomeIndex+',path: '/HomeIndex/:path+',// path: '/HomeIndex',component: HomeIndex,meta: {requireAuth: true// requireAuth: false}},{path: '/ResetPwd',component: ResetPwd,meta: {requireAuth: false}}
]// 创建路由实例并传递‘routes’配置 你可以在这里输入更多的配置
const router = createRouter({history: createWebHistory(),// routes:routes可以简写成routes,不会报错// routes:[]routes,// : [//   {//     path:"/HomeIndex",//     component: HomeIndex//   }// ]
})export default router

24.4 缓存代码修改(src/store/index.ts)

加了一些注释,并未有大的改动
src/store/index.ts

// 引入, 用于存储全局的状态数据,可供其他地方调用
import { createStore } from "vuex";
// 引入工具方法
import utils from "@/utils/utils";// 创建一个新的store实例
const store = createStore({state() {return{// count: 0// 当前登录的用户信息userInfo: {},// 当前登录的标识tokentoken: null,}},getters: {// 获取当前用户信息getUserInfo(state:any){return state.userInfo;},// 获取当前tokengetToken(state:any){return state.token;},// 判断当前是否登录// isLogin(state:any){//     console.log("---",state.token, "===",state.userInfo)//     return (state.token && state.userInfo) ? true : false;// }},mutations: {// 登出,清除缓存中的数据logout: function(state:any){console.log("---111---")state.userInfo = null;utils.removeData("userInfo");utils.removeData("token");// utils.removeData("username");// utils.saveData("username","");// utils.removeData("saveUsername");// utils.removeData("password");// utils.removeData("savePassword");},// 存储用户信息setUserInfo: function(state:any, userInfo:any){state.userInfo = userInfo;utils.saveData('userInfo', userInfo);},// 存储tokensetToken: function(state:any, token:any){state.token = token;utils.saveData('token', token);}}})export default store;

24.5 主页代码修改(src/views/index/HomeIndex.vue)

无大修改,简单优化
src/views/index/HomeIndex.vue

<script setup lang="ts">import { ref, } from 'vue'// import {reactive, onMounted, onUnmounted } from 'vue'// import utils from '@/utils/utils';// import { useRoute, useRouter } from 'vue-router';import MenuBar from './components/MenuBar.vue';import ToolBar from './components/ToolBar.vue';// 左侧菜单栏宽度const slideWidth = ref('250px');// 从MenuBar组件中传过来的值const menuCollapse = (value:boolean)=>{if(value){// 如果值为true则为收起状态,宽度设为60pxslideWidth.value = '60px';}else{// 如果为false则是展开状态,宽度设为250pxslideWidth.value = '250px'}}</script><template><!-- 后台主页 --><div class="index-layout"><el-container><!-- 宽度以变量形式传入,打开关闭侧边菜单栏 --><el-aside class="layout-aside" :width="slideWidth"><MenuBar @menuCollapse="menuCollapse"></MenuBar></el-aside><el-container><el-main class="layout-main"><!-- tab标签页 --><!-- <el-tabs v-model="activeName" class="main-tabs" @tab-click="handleClick"> --><el-tabs  class="main-tabs" ><!-- <el-tab-pane label="User" name="first">主界面</el-tab-pane> --><el-tab-pane class="tabs-pane">主界面<RouterView></RouterView><template #label><span class="pane-label"><!-- <el-icon class="label-icon"><calendar /></el-icon> --><el-icon class="label-icon"><Menu /></el-icon><span class="label-span">工作台</span></span></template></el-tab-pane></el-tabs></el-main><!-- ToolBar 头部工具栏 --><el-header class="layout-header"><ToolBar></ToolBar></el-header></el-container></el-container></div></template><style scoped>.index-layout{/* height: 100%; *//* width: 100%; */font-size: 20px;}/* 侧边菜单栏样式 */.layout-aside{/* height: 100%; */height: 100vh;box-shadow: var(--el-box-shadow);/* 左右侧栏之间的边框线 */border-right: var(--el-border);}/* 菜单栏与右侧界面的边距设为0 */.layout-main{padding: 0;margin: 0;background: var(--el-bg-color-page);}/* header头工具栏相对于主界面的样式设置 */.layout-main:deep(.el-tabs__header){/* 让主界面与header头工具栏的距离归0 */margin: 0;/* 头部栏背景色设为白色 */background-color: #fff;/* 头部栏左侧边框距离 */padding-left: 10px;/* 头部栏右侧边框距离 */padding-right: 10px;}/* 图标的位置调整,与文字上下和左右距离 */.layout-main:deep(.pane-label .label-icon){/* 图标右侧边距 */margin-right: 4px;/* 位置 *//* position: relative; *//* 图标上方距离 */top: 2px;}.layout-header{position: fixed;top: 0;right: 0;width: 300px;/* height: 60px; */line-height: 35px;}</style>

24.6 菜单栏组件代码修改(src/views/index/components/MenuBar.vue)

新增菜单栏数据获取,菜单栏路由及组件的遍历,事件的监听,选中菜单栏的事件触发等
src/views/index/components/MenuBar.vue

<script setup lang="ts">import { onMounted, reactive, ref, } from 'vue'// import {reactive, onMounted, onUnmounted } from 'vue'import utils from '@/utils/utils';import api from '@/api/api';import { useRoute, useRouter } from 'vue-router';// import MenuBar from './components/MenuBar.vue';// import ToolBar from './components/ToolBar.vue';import HomeIndex from '../HomeIndex.vue';const router = useRouter();// 左侧菜单栏展开收起的标识// const isCollapse = ref(true)// 默认false,左侧栏展开const isCollapse = ref(false)const collapseController = (value:boolean)=> {// isCollapse.value = !isCollapse.value;isCollapse.value = value;// 将值传到事件中emits('menuCollapse', value);}// 定义事件,传值,并在主页监听const emits = defineEmits(['menuCollapse', 'select'])// const handleOpen = (key: string, keyPath: string[]) => {// console.log(key, keyPath)// }// const handleClose = (key: string, keyPath: string[]) => {// console.log(key, keyPath)// }// 菜单数据const menuData = reactive([]);// let menuData:any = null;// 这里定义一个默认展示的路由地址,展示对应的菜单页面const curMenu = ref("");onMounted (()=>{loadMenuData();});// 加载菜单数据const loadMenuData = () => {utils.showLoadding("加载中");api.get("/menu/getMenu").then((res)=>{utils.showLoadding("加载中");if(!res||res.status!=200){if(res.data){utils.showError("问题");return;}// utils.showError("加载失败");return;}if(res.data.result==200){// utils.showSuccess("请求成功")menuData.values = res.data;// menuData = res.data;console.log("111",res.data);console.log("222",menuData.values);// menuData.splice(0, menuData.length);// menuData.push(res.data.path);// 将菜单信息注册到路由中let indexChildrens:any = [];menuData.values.data.forEach((item:any)=>{console.log("item: ",item)let routerItem:any = {path: item.path,// 注意:这里为了能正常使用还未创建的vue组件,故意将component写成component,不然报错component: item.components,meta:{requireAuth: item.requireAuth},children: []};if(item.children && item.children.length>0){item.children.forEach((subItem:any)=>{console.log("subItem: ",subItem)let subRouterItem:any = {path: subItem.path,component: subItem.components,meta:{requireAuth: subItem.requireAuth},};routerItem.children.push(subRouterItem);});}indexChildrens.push(routerItem);console.log("indexChildrens: ",indexChildrens)})router.addRoute({// path: '/:HomeIndex+',// path: '/HomeIndex/:path+',path: '/HomeIndex',component: HomeIndex,meta: {requireAuth: true// requireAuth: false},children: indexChildrens});}// 根据url中的路由信息自动选中对应的菜单// curMenu.value = router.currentRoute.value.path;// 选中菜单de事件触发 传入的值为当前组件的路由地址如,/HomeIndex/businessMenu/detailselectMenu(router.currentRoute.value.path);}).catch((error)=>{console.log("error,",error)utils.hideLoadding();utils.showError("加载失败");}).finally(()=>{utils.hideLoadding();});}// 选择当前菜事件触发的方法const selectMenu = (value:any)=>{if(value){curMenu.value = value;// 当前菜单路由console.log("selectMenu-value: ",value);}let curMenuData = null;// 遍历菜单所有路由列表menuData.values.data.forEach((item:any)=>{console.log("selectMenu-item: ",item);// 如果获取的菜单路由地址和当前地址一致if(item.path == curMenu.value){// 将数据获取curMenuData = item;console.log("selectMenu-curMenuData: ",curMenuData);}// 如果该菜单项的子菜单不为空且子菜单数量大于0,即该项为二级菜单if(item.children && item.children.length>0){// 遍历子菜单item.children.forEach((subItem:any)=>{console.log("selectMenu-subItem: ",subItem);// 如果子菜单路由和子项的值一致if(subItem.path==curMenu.value){// 获取子项数据curMenuData = subItem;console.log("selectMenu-sub-curMenuData: ",curMenuData);}});}})emits('select', curMenuData);};// 暴露选中菜单方法,可让外部调用该方法选中对应菜单defineExpose({selectMenu})// 13-8.5=4.5// 36-4.5=31.5// 12+10=22// 9.5</script><template><div class="logo" ><div class="logo-name" v-if="!isCollapse">寒山李白通用系统</div><!-- 动态绑定侧边栏展开收起的图标按钮,当收起时即isCollapse为真,将class值转为logo-collapse-ef并设置图标居中 --><div class="logo-collapse" :class="{'logo-collapse-ef': isCollapse}"><!-- 展开按钮 如果isCollapse是真则展示按钮,触发事件传值为false --><el-icon v-if="isCollapse" @click="collapseController(false)"><Expand /></el-icon><!-- 收起按钮 如果isCollapse是假则展示按钮,触发事件,传值为true --><el-icon v-else @click="collapseController(true)"><Fold /></el-icon></div></div><!-- <el-radio-group v-model="isCollapse" style="margin-bottom: 20px"><el-radio-button :value="false">expand</el-radio-button><el-radio-button :value="true">collapse</el-radio-button></el-radio-group> --><!-- default-active="4" 设置加载时的激活项,此为4 --><!-- :collapse-transition="false" 取消收起展开时的动画,展开收起更快 --><!-- router 启用vue-router模式 激活导航时 以index作为path进行路由跳转 使用 --><el-menu:default-active="curMenu"class="el-menu-vertical-collapse":collapse="isCollapse":collapse-transition="false"router@select="selectMenu"><!-- @open="handleOpen"@close="handleClose" --><!-- 请求接口返回数据-获取其中的菜单数据data,遍历菜单数据中的每一项 --><template v-for="item in menuData.values.data"><!-- 如果该项中有子项,则为二级菜单,继续进行遍历 --><el-sub-menu class="menu" v-if="item.children && item.children.length>0" :index="item.path"><!-- 该项的一级菜单图标和名称 --><template #title><!-- 该项的一级菜单图标 --><component class="menu-icon" :is="item.icon"></component><!-- 该项的一级菜单名称 --><span>{{ item.name }}</span></template><!-- 该项的二级菜单遍历 --><template v-for="subItem in item.children"><el-menu-item class="menu" :index="subItem.path"><component class="menu-icon" :is="subItem.icon"></component><span>{{ subItem.name }}</span></el-menu-item></template></el-sub-menu><!-- 如果该项中没有子项,则为一级菜单,直接展示即可 --><el-menu-item class="menu" v-else :index="item.path"><component class="menu-icon" :is="item.icon"></component><span>{{ item.name }}</span></el-menu-item> </template><!-- <el-sub-menu index="1"><template #title><el-icon><location /></el-icon><span>Navigator One</span></template><el-menu-item-group><template #title><span>Group One1</span></template><el-menu-item index="1-1">item one</el-menu-item><el-menu-item index="1-2">item two</el-menu-item></el-menu-item-group><el-menu-item-group title="Group Two1"><el-menu-item index="1-3">item three</el-menu-item></el-menu-item-group><el-sub-menu index="1-4"><template #title><span>item four</span></template><el-menu-item index="1-4-1">item one</el-menu-item></el-sub-menu></el-sub-menu> --><!-- <el-menu-item index="2"><el-icon><icon-menu /></el-icon><template #title>Navigator Two</template></el-menu-item><el-menu-item index="3" disabled><el-icon><document /></el-icon><template #title>Navigator Three</template></el-menu-item><el-menu-item index="4"><el-icon><setting /></el-icon><template #title>Navigator Four</template></el-menu-item> --><!-- <el-sub-menu index="1"><template #title><el-icon><location /></el-icon><span>Navigator Five</span></template><el-menu-item-group><template #title><span>Group One1</span></template><el-menu-item index="1-1">item one</el-menu-item><el-menu-item index="1-2">item two</el-menu-item></el-menu-item-group><el-menu-item-group title="Group Two1"><el-menu-item index="1-3">item three</el-menu-item></el-menu-item-group><el-sub-menu index="1-4"><template #title><span>item four</span></template><el-menu-item index="1-4-1">item one</el-menu-item></el-sub-menu></el-sub-menu><el-sub-menu index="1"><template #title><el-icon><location /></el-icon><span>Navigator Six</span></template><el-menu-item-group><template #title><span>Group One1</span></template><el-menu-item index="1-1">item one</el-menu-item><el-menu-item index="1-2">item two</el-menu-item></el-menu-item-group><el-menu-item-group title="Group Two1"><el-menu-item index="1-3">item three</el-menu-item></el-menu-item-group><el-sub-menu index="1-4"><template #title><span>item four</span></template><el-menu-item index="1-4-1">item one</el-menu-item></el-sub-menu></el-sub-menu> --></el-menu></template><style scoped>/* .el-menu-vertical-demo:not(.el-menu--collapse) {width: 200px;min-height: 400px;} */.logo{display: flex;background-color: var(--el-color-info-light-7)/* height: 60px; */} .logo-name{/* position: fixed; *//* top: 0; *//* left: 0; */flex: 1;text-align: center;font-size: 20px;font-weight: bold;letter-spacing: 2px;padding: 2%;background-image: -webkit-linear-gradient(right, rgba(78, 224, 33, 0.795), #22fc2d, rgb(236, 126, 36));/* background-image: -webkit-background-clip(bottom, red, #fd8403, yellow); *//* -webkit-background-clip: text; */background-clip: text;-webkit-text-fill-color: transparent;}.logo-collapse{width: 20px;/* margin-top: 10px; */padding-right: 10%;padding-top: 1%;/* height: 30px; */text-align: center;cursor: pointer;font-size: 30px;}.logo-collapse:hover{color: var(--el-color-primary)}/* 动态绑定侧边栏收起展开图标的样式 */.logo-collapse-ef{/* 图标宽度居中 */width: 100%}.el-menu-vertical-collapse{/* 剔除侧边栏菜单边框,收起时无边框 */border: none;height: calc(100% - 60px);overflow-y: auto;}/* 设置滚动条样式 */.el-menu-vertical-collapse::-webkit-scrollbar{width: 10px;}/* 滚动槽 */.el-menu-vertical-collapse::-webkit-scrollbar-track{-webkit-box-shadow: inset 0 0 6px var(--el-border-color-dark);border-radius: 8px;}/* 滚动条滑块 */.el-menu-vertical-collapse::-webkit-scrollbar-thumb{border-radius: 8px;background: var(--el-border-color-darker);/* -webkit-box-shadow: inset 0 0 6px var(--el-border-color-dark); */}/* 滚动条上下设置 *//* .el-menu-vertical-collapse::-webkit-scrollbar-thumb{background: var(--el-border-color-darker);} */.el-menu-vertical-collapse:deep(.menu-icon){width: 20px;margin: 10px;color: var(--el-color-primary);}.el-menu-vertical-collapse .menu:hover{color: var(--el-color-primary);}</style>

24.7 工具栏组件代码修改(src/views/index/components/ToolBar.vue)

新增了一个消息图标和未定义的函数
src/views/index/components/ToolBar.vue

<script setup lang="ts">const openMsg = ()=>{}</script><template><div class="toolbar"><div class="toolbar-icon"><el-icon><Search /></el-icon></div><div class="toolbar-icon"><el-icon><FullScreen /></el-icon></div><div class="toolbar-icon"><el-icon><TurnOff /></el-icon></div><div class="toolbar-icon" @click="openMsg"><el-icon><BellFilled /></el-icon></div><div class="toolbar-icon"><el-dropdown><span class="icon-dropdown-link"><el-icon class="link-icon--left" style="top: 2px;"><UserFilled /></el-icon><span>管理员</span><el-icon class="link-icon--right"><arrow-down /></el-icon></span><template #dropdown><el-dropdown-menu><el-dropdown-item>个人信息</el-dropdown-item><el-dropdown-item>修改密码</el-dropdown-item><el-dropdown-item>退出系统</el-dropdown-item></el-dropdown-menu></template></el-dropdown></div></div>
</template><style scoped>.toolbar{display: flex;}.toolbar-icon{flex: 1;text-align: center;cursor: pointer;/* height: 20px; */}.toolbar-icon:hover{color: var(--el-color-primary);}.toolbar-icon:deep(.icon-dropdown-link){/* width: 100%; */line-height: 30px;width: 80px;}.icon-dropdown-link:hover{color: var(--el-color-primary);}</style>

24.8 新增组件-工作台(src/views/index/components/WorkPlat.vue)

src/views/index/components/WorkPlat.vue


<script setup lang="ts"></script><template>工作台界面
</template><style scoped></style>

24.9 手机验证码登录组件代码修改(src/views/login/components/PhoneCodeForm.vue)

仅修改了登录成功后跳转的路由地址
src/views/login/components/PhoneCodeForm.vue

<script setup lang="ts">
import { ref, reactive, onMounted, onUnmounted } from 'vue'// 引入状态存储工具store
import {useStore} from 'vuex'// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'
// 路由引入
import { useRoute, useRouter } from 'vue-router';// 登录表单的实例
// let loginFormRef = ref(null);
let loginFormRef = ref()
// 登录表单的数据
const loginForm = reactive({// 用户名username: '',// 手机验证码smscode: '',// 图片验证imgcode: '',// 记住用户名,默认否saveUsername: false
})// 登录验证规则
const rules = {username: [{required: true,message: '请输入用户名',trigger: 'blur'}],smscode: [{required: true,message: '请输入短信验证码',trigger: 'blur'}],imgcode: [{required: true,message: '请输入图片验证码',trigger: 'blur'}]
}const formSize = "";// 图片验证码路径
let imgCodeSrc = new URL('../../../assets/code.png', import.meta.url).href
// const imgCodeSrc = '../../../assets/code.png';// 刷新图片验证码
const getImgCode = () => {// 后续改为从服务器上获取动态图片imgCodeSrc = new URL('../../../assets/code.png', import.meta.url).href
}// 定时器
let timer: any = null
// 获取短信验证码的间隔时间
let curTime = 0
// 获取短信验证码按钮的文本显示内容
let smsCodeBtnText = ref('获取验证码')// 获取短信验证码
const getSmsCode = () => {// 当点击获取短信验证码时,如果其他信息没填则提示输入if (!loginForm.username) {utils.showError('请输入用户名')return}// if(!loginForm.smscode){//     utils.showError('请输入短信验证码');//     return;// }// TODO 从后台获取短信验证码// 调用接口生成短信验证码// 1 直接使用axios请求后端完整地址请求
//   axios({
//     method: 'post',
//     url: 'http://127.0.0.1:8888/login/redis/setMessageCode',
//     // url: 'login/redis/setMessageCode',
//     // 这里需要注意,不管请求方式是什么,这里是根据后端传参方式来定的,如果后端使用@RequestParam则这里使用params作为key
//     params: {
//       username: loginForm.username
//     }
//   });// 2 使用axios实例传参请求后端接口地址的用法  api({method: 'post',url: '/login/redis/setMessageCode',params: {username: loginForm.username}})curTime = 60timer = setInterval(() => {curTime--;smsCodeBtnText.value = curTime + '秒后重新获取';if (curTime <= 0) {smsCodeBtnText.value = '获取验证码'clearInterval(timer)// 清除时,值为空,防止重复点击触发多次timer = null}}, 1000)
}// 状态存储的store
const store = useStore();
// 路由,转到指定页面,使用push
// const route = useRoute();
const router = useRouter();// 登录提交事件
const onSubmit = () => {// form表单中的值,校验,loginFormRef.value.validate((valid: string, fileds: any) => {// 如果valid值为假,则遍历输出报错if (!valid) {for (let key in fileds) {// 获取报错信息中的字段对应的key的索引为0的信息utils.showError(fileds[key][0].message)return;}return}// 登录表单的记住用户名如果被勾选if (loginForm.saveUsername==true) {console.log("短信验证登录1:",loginForm.saveUsername);// 保存输入的用户名utils.saveData('username', loginForm.username)// 保存被勾选的操作utils.saveData('saveUsername', loginForm.saveUsername)} else {console.log("短信验证登录2:",loginForm.saveUsername);// 如果记住用户名的勾选取消,则移除这两个存储的内容utils.removeData('username')utils.removeData('saveUsername')}// TODO 调用接口登录// 因为太快了,所以可能看不到效果,可以将下方的hideLoading方法注掉,可以看到效果utils.showLoadding('正在加载中')api({method: 'get',url: '/login/redis/getMessageCode',params: {username: loginForm.username,smscode: loginForm.smscode// imgcode: loginForm.imgcode}}).then((res) => {utils.hideLoadding()console.log(res)// console.log(res.status)// if (!res || res.status != 200 || !res.data || res.data.result != 200 || !res.data.data) {if (res.status != 200 || res.data.result != 200 || !res.data.msgCode) {utils.showError('登录失败-请求数据返回有误');return;}// console.log(res.data.data, loginForm.smscode);if(res.data.msgCode == loginForm.smscode){utils.showSuccess('登陆成功')// 存储用户token信息并转到主页// let userInfo = res.data.datalet userInfo = res.datalet token = res.data.token// 状态数据存储store.commit('setUserInfo', userInfo);store.commit('setToken', token);// 登录成功后将页面转到主页// router.push('/HomeIndex')router.push('/')}else if(res.data.msgCode != loginForm.smscode){utils.showError('登录失败-验证码错误');return;}// utils.showError('登录失败')}).catch((error) => {// utils.hideLoadding();console.log(error);utils.showError('登录失败-出现异常')})// api.post("/api/login/code",{//     username: loginForm.username,//     smscode: loginForm.smscode,//     imgcode: loginForm.imgcode// }).then((res)=>{//     utils.hideLoadding();//     console.log(res);//     console.log(res.status);//     if(!res || res.status != 200 || !res.data || res.data.code != 8888 || !res.data.data){//         if(res.data.message){//             utils.showError(res.data.message);//             return;//         }//         utils.showError('登录失败');//         return;//     }//     // 存储用户token信息并转到主页//     let userInfo = res.data.data;//     let token = res.data.token;//     utils.showSuccess('登陆成功');// }).catch((error)=>{//     // utils.hideLoadding();//     utils.showError('登录失败');// });// 登录成功信息提示// utils.showSuccess("登录成功");})
}// 挂载
onMounted(() => {// 获取记住用户名的值loginForm.saveUsername = utils.getData('saveUsername')// 如果记住用户名被勾选,则获取用户名显示(saveUsername可能会是undefined,当为undefined时也是真,故这里不能直接使用saveUsername,而是要判断是否为true)if (loginForm.saveUsername==true) {loginForm.username = utils.getData('username')}
})// 清空定时器
onUnmounted(() => {timer && clearInterval(timer)
})
</script><template><!-- 手机验证码登录 --><div class="phoneCodeLoginBox"><el-formref="loginFormRef"style="max-width: 600px":model="loginForm":rules="rules"label-width="0"class="loginFrom":size="formSize"status-icon><!-- 用户名 --><el-form-item prop="username"><!-- 图标设置,动态绑定username,提示信息,设置输入框大小 --><el-inputprefix-icon="UserFilled"v-model="loginForm.username"placeholder="请输入用户名"size="large"/></el-form-item><!-- 短信验证 --><el-form-item prop="smscode"><!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 --><div class="flex loginLine"><div class="flexItem"><el-inputprefix-icon="Iphone"v-model="loginForm.smscode"placeholder="请输入验证码"size="large"/></div><div class="codeBtn"><el-button type="primary" size="large" @click="getSmsCode" :disabled="curTime > 0">{{smsCodeBtnText}}</el-button></div></div></el-form-item><!-- 图片验证 --><el-form-item prop="imgcode"><!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 --><div class="flex loginLine"><div class="flexItem"><el-inputprefix-icon="Picture"v-model="loginForm.imgcode"placeholder="请输入图片验证码"size="large"/><!-- <el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' /> --></div><div class="codeBtn"><el-image :src="imgCodeSrc" size="large" @click="getImgCode"></el-image><!-- <el-button type="primary" size="large" @click="getSmsCode" class="" >获取验证码</el-button> --></div></div></el-form-item><!-- 记住用户名 --><el-form-item prop="saveUsername"><el-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox></el-form-item><!-- 登录按钮 --><el-form-item><el-button class="loginBtn" type="danger" size="large" @click="onSubmit">登录</el-button></el-form-item></el-form></div>
</template><style scoped>
/* 按钮宽度设为最大 */
.loginBtn {width: 100%;/* 登录按钮圆角边框 */border-radius: 20px;
}/* 验证码按钮样式配置 */
.codeBtn {width: 100px;margin-left: 10px;
}
/* 按钮和图片宽度100px */
.codeBtn:deep(.el-button),
.codeBtn:deep(img) {width: 100px;/* height: 40px; */
}
/* 验证码图片高度 */
.codeBtn:deep(img) {height: 40px;/* 鼠标移上去会变成手型 */cursor: pointer;
}/* 这一行宽度占满 */
.loginLine {width: 100%;
}
</style>

24.10 二维码登录组件代码修改(src/views/login/components/QcodeForm.vue)

仅修改了登录成功后跳转的路由地址
src/views/login/components/QcodeForm.vue

<script setup lang="ts">import { ref,reactive, onMounted, onUnmounted } from 'vue'// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'// 引入store
import {useStore} from 'vuex'
// 引入router
import {useRouter} from 'vue-router'const store = useStore();const router = useRouter();// 二维码// let qcodePath:any = null;// 二维码对应的token, 用于判断当前二维码是否已经被扫码登录let qrToken:string = "";// 第一次获取验证码// api({//     method: 'post',//     url: 'login/qr/generateQrCodeAsFile'// }).then((res)=>{// if(res.data.result != 200){// utils.showError("登录失败");// }// utils.showSuccess("登录成功");//     qcodePath = res.data.data//     qrToken = res.data.token// });// qcodePath = 'E:\\WORKPROJECTS\\MySelfPro\\hslb-general-management-system\\src\\main\resources\\login_qr_pngs\\QRCode.png';// 二维码let qcodeSrc = new URL("../../../assets/hslb-qcode.png", import.meta.url).href;// let qcodeSrc = new URL(qcodePath, import.meta.url).href;// let qcodeSrc = qcodePath;const qcodeToken = ref('');// 当前定时器事件const curTime = ref(0);let timer:any = null;let username:string = utils.getData("username");const qrString = "100100100222";// 后台更新获取二维码const loadQcode = () => {// 后续改为从服务器上获取动态图片// const qrString = "100100100222";console.log("9999999====== "+qrString);// let username:string = utils.getData("username");api({method: 'post',url: 'login/qr/generateQrCodeAsFile',params: {username: username,qrContent: qrString}}).then((res)=>{// if(res.data.result != 200){// utils.showError("登录失败");// }// utils.showSuccess("登录成功");// qcodePath = res.data.dataqrToken = res.data.token});qcodeSrc = new URL("../../../assets/hslb-qcode.png", import.meta.url).href;// qcodeSrc = new URL(qcodePath, import.meta.url).href;// qcodeSrc = qcodePath;// 初始化token的值qcodeToken.value = qrToken;// 设定定时时间// curTime.value = 60;// 为了让二维码失效的效果及时,这里暂时设置10秒,后续改回60秒即可curTime.value = 10;// 定义定时器,倒计时timer = setInterval(() => {curTime.value--;// 这里获取toekn,校验是否已经被登陆过checkLogin();if(curTime.value<=0){// 事件为0则清空定时器clearInterval(timer);timer = null;}}, 1000);};// 登录提交事件// const onSubmit = () => {// };// 挂载onMounted(() => {// 获取二维码loadQcode();});// 清空计时器onUnmounted(()=>{timer && clearInterval(timer);});// 使用qcodeToken判断当前二维码是否已经被扫码登录const checkLogin = () => {// TODOapi({method: 'post',url: 'login/qr/generateQrCodeAsFile',params: {username: username,qrContent: qrString}}).then((res)=>{if(res.data.token){utils.showSuccess("登录成功");store.commit('setUserInfo',res.data);store.commit('setToken',res.data.token);// router.push('/HomeIndex');router.push('/');}// res.data.token;}).catch((error)=>{console.log(error);// utils.showError("登录失败")});}</script><template><!-- 扫码登录 --><div class="qcodeLoginBox"><div class="qcodeBox" ><img class="qcodeImg" :class="{'endImg':curTime<=0}" :src="qcodeSrc" alt="无法获取二维码,请联系客服解决"><div v-if="curTime<=0" class="endBox" @click="loadQcode" >当前二维码失效,点击重新加载{{ curTime }}</div></div><div class="tipInfo" >使用微信或移动端扫码登录 此二维码将在{{ curTime }}秒后刷新</div></div></template><style scoped>/* 二维码窗口样式 */.qcodeBox{width: 80%;height: 80%;position: relative;/* 边框自动 */margin: 0 auto;   }/* 二维码图片样式 */.qcodeBox .qcodeImg{width: 100%;height: 100%;}.qcodeBox .endBox{width: 100%;height: 100%;/* 悬浮显示 */position: absolute;/* 靠左 *//* left: 0%; *//* 靠上 */top: 0;/* 居中 *//* text-align: center; *//* 字体大小 */font-size: 14px;/* 字体颜色 */color: red;display: flex;/* 上下居中 */align-items: center;/* justify-items: center; *//* 左右居中 */justify-content: center;/* 背景色为灰色 */background-color: #00000055;}/* .endImg{filter: brightness(10%);} *//* 提示信息样式 */.tipInfo{/* 行高 */line-height: 30px;/* 字体大小 */font-size: 14px;/* 居中 */text-align: center;/* 颜色 */color: var(--el-text-color-placeholder);}</style>

24.11 账号密码登录组件代码修改(src/views/login/components/UsernameForm.vue)

仅修改了登录成功后跳转的路由地址
src/views/login/components/UsernameForm.vue

<script setup lang="ts">import { ref,reactive, onMounted } from 'vue'
// 引入状态存储store
import { useStore } from 'vuex'
// 引入路由工具router
import { useRouter } from 'vue-router'// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'// 登录表单的实例// let loginFormRef = ref(null);let loginFormRef = ref();// 登录表单的数据const loginForm = reactive({// 用户名username: '',// 密码password: '',// 图片验证imgcode: '',// 记住用户名,默认否saveUsername: false,// 记住用户名,默认否savePassword: false});// 登录验证规则const rules = ({username:[{required: true,message: '请输入用户名',trigger: 'blur'}],password:[{required: true,message: '请输入密码',trigger: 'blur'}],imgcode:[{required: true,message: '请输入图片验证码',trigger: 'blur'}]});const formSize = "";// 图片验证码路径let imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;// const imgCodeSrc = '../../../assets/code.png';// 刷新图片验证码const getImgCode = () => {// 后续改为从服务器上获取动态图片imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;};// 全局状态存储const store = useStore();// 路由调用const router = useRouter();// 登录提交事件const onSubmit = () => {// form表单中的值,校验loginFormRef.value.validate((valid:string, fileds:any)=>{// 如果valid值为假,则遍历输出报错if(!valid){for(let key in fileds){// 获取报错信息中的字段对应的key的索引为0的信息utils.showError(fileds[key][0].message);}return;}// 登录表单的记住用户名如果被勾选if(loginForm.saveUsername){// 保存输入的用户名utils.saveData('username', loginForm.username);// 保存被勾选的操作utils.saveData('saveUsername', loginForm.saveUsername);}else{// 如果记住用户名的勾选取消,则移除这两个存储的内容utils.removeData('username');utils.removeData('saveUsername');}// 登录表单的记住用户名如果被勾选if(loginForm.savePassword){// 保存输入的用户名utils.saveData('password', loginForm.password);// 保存被勾选的操作utils.saveData('savePassword', loginForm.savePassword);}else{// 如果记住用户名的勾选取消,则移除这两个存储的内容utils.removeData('password');utils.removeData('savePassword');}// TODO 调用接口登录utils.showLoadding("正在加载中");api({method: 'get',url: '/login/login',params: {username: loginForm.username,password: loginForm.password}}).then((res)=>{utils.hideLoadding();if(res.status != 200 || res.data.result != 200){utils.showError("登录失败-请求数据返回有误");return;}if(res.data.login == 1){utils.showSuccess("登录成功");// 存储用户信息// let userInfoLogin = res.data.login;let userInfoLogin = res.data;let token = res.data.token;console.log("usernamelogin:", token);store.commit('setUserInfo', userInfoLogin);store.commit('setToken', token);console.log("----------------token: ", token);// 登录成功后跳转主页// router.push('/HomeIndex');router.push('/');}else if(res.data.login == 0){utils.showError("登录失败-用户不存在");return;}else if(res.data.login == 2){utils.showError("登录失败-密码错误");return;}// utils.showError("登录失败-返回数据错误")}).catch((error)=>{console.log(error);utils.showError("登录失败-发生异常");});// 登录成功提示// utils.showSuccess("登录成功");});};// 挂载onMounted(() => {// 获取记住用户名的值loginForm.saveUsername = utils.getData('saveUsername');// 如果记住用户名被勾选,则获取用户名显示if(loginForm.saveUsername==true){loginForm.username = utils.getData('username');}// 获取记住密码的值loginForm.savePassword = utils.getData('savePassword');// 如果记住密码被勾选,则获取密码if(loginForm.savePassword==true){loginForm.password = utils.getData('password');}});</script><template><!-- 用户密码登录 --><div class="usernameLoginBox"><el-form ref="loginFormRef"style="max-width: 600px":model="loginForm":rules="rules"label-width="0"class="loginFrom":size="formSize" status-icon><!-- 用户名 --><el-form-item prop="username"><!-- 图标设置,动态绑定username,提示信息,设置输入框大小 --><el-input prefix-icon="UserFilled" v-model="loginForm.username" placeholder="请输入用户名" size='large' /></el-form-item><!-- 密码 --><el-form-item prop="password"><!-- 密码 --><div class="flexItem" ><!-- show-password 属性表示是否显示切换显示密码的图标,true为显示 --><!-- <el-input prefix-icon="Lock" show-password="off" type="password" v-model="loginForm.password" placeholder="请输入密码" size='large' /> --><el-input prefix-icon="Lock" show-password type="password" v-model="loginForm.password" placeholder="请输入密码" size='large' /></div></el-form-item><!-- 图片验证 --><el-form-item prop="imgcode"><!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 --><div class="flex loginLine"><div class="flexItem" ><el-input prefix-icon="Picture" v-model="loginForm.imgcode" placeholder="请输入图片验证码" size='large' /><!-- <el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' /> --></div><div class="codeBtn" ><el-image  :src="imgCodeSrc" size='large' @click="getImgCode" ></el-image><!-- <el-button type="primary" size="large" @click="getSmsCode" class="" >获取验证码</el-button> --></div></div></el-form-item><!-- <el-form-item prop="saveUsername"> --><el-form-item ><!-- 记住账号密码的勾选 --><div class="flex loginLine" ><!-- 记住用户名 --><div class="flexItem" ><el-form-item prop="saveUsername"><el-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox></el-form-item></div><!-- 记住密码 --><div class="flexItem" ><el-form-item prop="savePassword"><el-checkbox v-model="loginForm.savePassword">记住密码</el-checkbox></el-form-item></div><div class="flexItem" ><!-- <el-form-item prop="savePassword"> --><router-link to="/ResetPwd">忘记密码</router-link><!-- </el-form-item> --></div></div></el-form-item><!-- <el-form-item prop="savePassword"> --><!-- </el-form-item> --><!-- 登录按钮 --><el-form-item><el-button class="loginBtn" type="danger" size='large' @click="onSubmit">登录</el-button></el-form-item></el-form></div></template><style scoped>/* 按钮宽度设为最大 */.loginBtn{width: 100%;/* 登录按钮圆角边框 */border-radius: 20px;}/* 验证码按钮样式配置 */.codeBtn{width: 100px;margin-left: 10px;}/* 按钮和图片宽度100px */.codeBtn:deep(.el-button),.codeBtn:deep(img){width: 100px;/* height: 40px; */}/* 验证码图片高度 */.codeBtn:deep(img){height: 40px;/* 鼠标移上去会变成手型 */cursor: pointer;}/* 这一行宽度占满 */.loginLine{width: 100%}</style>

24.12 App.vue组件代码修改(src/App.vue)

新增路由守卫,以及注释了部分多于代码
src/App.vue

<script setup lang="ts">
import { onMounted } from 'vue';
// import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
import utils from './utils/utils';
import api from './api/api';// // // 引入暗黑主题的动态切换
// import { useDark, useToggle } from '@vueuse/core'// const isDark = useDark()
// // // 切换主题函数
// const toggleDark = useToggle(isDark)// 状态存储
// let store = useStore();// 路由使用
const router = useRouter();// 路由守卫
router.beforeEach((to)=>{console.log("to: ",to)// 鉴权 在router.ts中设置的requireAuth参数,true则需要鉴权,false则不需要if(to.meta.requireAuth){console.log("开始鉴权,===>>>")// 进入鉴权,通过缓存中的token与接口中的token进行校验let token = utils.getData("token");// let userInfo = utils.getData("userInfo");// 当前不是登录状态console.log("20240021 token",token);let username = utils.getData("username");if(username==undefined){username = "";}let newToken = "";api.get('/login/tokenCheck',{params:{username}}).then((res)=>{console.log("data: ",res.data.token);newToken = res.data.token;if(token!=newToken){console.log("鉴权失败,返回登录界面===>>>")// 路由跳转登录router.push("/UserLogin")// 以下方法跳转失败// return { //   path:"/UserLogin",//   query: {//     redirect: router.currentRoute.value.path//   }// }}console.log("---->",newToken)});// console.log("---->",newToken)// 如果token不一致,则进行路由跳转,进行重新登陆// if(token!=newToken&&to.name!=="UserLogin"){// if(token!=newToken){// console.log("===>>>")// 路由跳转登录// router.push("/UserLogin")// router.push({//   path: "/UserLogin",//   query: {//     redirect: router.currentRoute.value.path//   }// })// return {path: "/UserLogin"}// }console.log("鉴权成功,====》》》》")}
});onMounted(()=>{// let tt = localStorage.getItem("token");// console.log("tt: ",tt);// console.log("=== ===");let token = "";// 由于token可能返回undefined报错,需要进行报错处理try {token = utils.getData("token");} catch (error) {error;}// console.log("store-token",token);let userInfo = utils.getData('userInfo');// console.log("userInfoL: "+userInfo)// console.log("userInfoL: "+token&&userInfo)if(token && userInfo){// console.log("token userInfo :",token," -- ", userInfo);// 登录成功,验证utils.showLoadding("正在加载")const username = utils.getData('username');if(!username){// 登录失败,跳转到登录页if(username===undefined){utils.saveData("username","");}// token验证失败utils.showError("用户名过期-请重新登录");router.push('/UserLogin');utils.hideLoadding();}else{// console.log("username-", username);api.get('/login/tokenCheck',{params:{username}}).then((res)=>{// console.log("res.data.token",res.data);// newToken = res.data.token;utils.hideLoadding();if(res.data.token==token){// 登陆成功// store.commit('setUserInfo', userInfo);// store.commit('setToken', token);// router.push('/');// 验证成功后保持当前页面,即刷新页面时不再跳转// router.push(router.currentRoute.value.path);// 也可注释掉跳转功能,此处不做跳转处理,使用MenuBar.vue中的selectMenu方法进行保持当前选中菜单路由utils.showSuccess("登录成功");}else{// if(username===undefined){//   utils.saveData("username","");// }// 登录失败utils.showError("Token已过期,请重新登录");// 登录失败,跳转到登录页router.push('/UserLogin');}});utils.hideLoadding();}}else{// 登录失败,跳转到登录页utils.showError("用户登录缓存过期,请重新登录");router.push('/UserLogin');utils.hideLoadding();}});</script><template><!-- 暗黑主题动态切换按钮实现 --><!-- <button @click="toggleDark()"><i inline-block align-middle i="dark:carbon-moon carbon-sun"/><span class="ml-2">{{ isDark ? 'Dark' : 'Light' }}</span></button> --><RouterView></RouterView></template><style scoped>/* @import url(./styles/default.css);@import url(./styles/theme/default-theme.css); *//* html,body{margin: 0;} *//* #app{width: 100%;height: 100%;} */
</style>

24.13 页面效果展示

登录成功后的界面
在这里插入图片描述
选择菜单后的路由地址界面(详情示例)
在这里插入图片描述
刷新页面后依旧是这个菜单被选中,路由地址不变


感谢阅读,祝君暴富!



http://www.ppmy.cn/ops/105309.html

相关文章

浅析JVM invokedynamic指令和Java Lambda语法|得物技术

一、导语 尽管近年来JDK的版本发布愈发敏捷&#xff0c;当前最新版本号已经20&#xff0c;但是日常使用中&#xff0c;JDK8还是占据了统治地位。 你发任你发&#xff0c;我用Java8&#xff1a;【Jetbrains】2023 开发者生态系统现状 - https://www.jetbrains.com/zh-cn/lp/dev…

【数论 状态机dp】2572. 无平方子集计数

本文涉及知识点 C动态规划 数论 质数、最大公约数、菲蜀定理 LeetCode 2572. 无平方子集计数 给你一个正整数数组 nums 。 如果数组 nums 的子集中的元素乘积是一个 无平方因子数 &#xff0c;则认为该子集是一个 无平方 子集。 无平方因子数 是无法被除 1 之外任何平方数整…

Docker通信全视角:原理、实践与技术洞察

一、引言 在云计算和微服务架构日益成熟的今天&#xff0c;Docker作为一种轻量级的容器化技术&#xff0c;已成为现代软件开发和部署的关键组件。Docker容器通过为应用程序提供隔离的运行环境&#xff0c;不仅显著提升了部署效率&#xff0c;而且增强了系统的可移植性和安全性。…

git中的分支是什么?分支有哪些好处?如何建立分支?

git中的分支是什么&#xff1f; 在Git中&#xff0c;分支是版本库中记录版本位置&#xff08;支线&#xff09;的一种方式。分支可以被视为一条时间线&#xff0c;每次提交都会在这条时间线上形成一个新的版本。通过分支&#xff0c;开发者可以在不影响主线&#xff08;通常是…

TS 学习(一)

如果我们在 ts 中写 不用运行就能在文件中报错 ts 是一种静态类型的检查 能将运行时出现的错误前置 一般不用 命令行编译 ts 转换成 js 将中文转码 tsc index&#xff08;.ts&#xff09; 输入命令生成 配置文件 能在中间进行 配置转换成 js 的哪个规范 es5 还是 6 和其它转…

Java12 Excel和Json文件解析

Excel文件解析&#xff1a; Excel文件解析(EasyExcel框架解析) Excel文件解析(Apache POl框架解析) &#xff08;1&#xff09;Excel文件对象创建&#xff1a;POI 《1》创建工作簿对象: XSSFWorkbook workbooknew XSSFWorkbook&#xff08;&#xff09;&#xff1b; 《2》创…

Swift 可选类型

Swift 可选类型 Swift 是一种强类型编程语言,它在类型安全方面做了很多工作,以确保代码的稳定性和可靠性。在 Swift 中,可选类型(Optional)是一种特殊的类型,用于处理值可能缺失的情况。本文将详细介绍 Swift 中的可选类型,包括其定义、使用场景、语法以及如何正确地处…

跨vue、react、angular框架渲染

应用场景 A 框架项目渐进式迁移到 B 框架一码多框架&#xff0c;开发一套组件&#xff0c;各个框架的应用复用&#xff1b;微前端实现【无沙箱能力】 思路 在 A 框架中渲染 B 框架的组件&#xff0c;将 B 框架的组件渲染成真实 dom 再挂载到对应位置。 实现demo 待补充&am…