前后端分离项目实战-通用管理系统搭建(前端Vue3+ElementPlus,后端Springboot+Mysql+Redis)第八篇:Tab标签页的实现


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


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


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


黄鹤楼中吹玉笛,江城五月落梅花。
——《与史郎中钦听黄鹤楼上吹笛》


文章目录

  • 前后端分离项目实战-通用管理系统搭建(前端Vue3+ElementPlus,后端Springboot+Mysql+Redis)第八篇:Tab标签页的实现
    • 25. Tab标签页的实现
      • 25.1 本地缓存代码更新(store/index.ts)
      • 25.2 菜单数据的后端接口代码
      • 25.3 后端接口返回的菜单数据
      • 25.4 创建菜单组件
      • 25.5 菜单栏代码更新(MenuBar.vue)
      • 25.6 主页代码更新(HomeIndex.vue)
      • 25.7 App.vue代码更新
      • 25.8 页面效果展示
      • 25.9 代码下载地址


Vue入门学习专栏


前后端分离项目实战-通用管理系统搭建(前端Vue3+ElementPlus,后端Springboot+Mysql+Redis)第八篇:Tab标签页的实现

25. Tab标签页的实现

本小节将实现一下几点功能:

  • 左侧菜单栏点击后在tab栏显示对应的标签页
  • tab标签页与菜单动态绑定
  • tab标签页与路由地址动态绑定
  • tab标签页关闭按钮

25.1 本地缓存代码更新(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;

25.2 菜单数据的后端接口代码

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","/src/views/workPlat/WorkPlat.vue");workPlat.put("requireAuth",true);//是否需要鉴权
//        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");
//        businessMenu.put("requireAuth",true);List<JSONObject>  businessMenuList = new ArrayList<>();//列表示例JSONObject businessMenuListExam = new JSONObject();businessMenuListExam.put("name","列表示例");businessMenuListExam.put("path","/HomeIndex/businessMenu/listExam");businessMenuListExam.put("icon","Tickets");businessMenuListExam.put("component","/src/views/businessMenu/ListExam.vue");businessMenuListExam.put("requireAuth",true);businessMenuList.add(businessMenuListExam);//详情示例JSONObject businessMenuDetailExam = new JSONObject();businessMenuDetailExam.put("name","详情示例");businessMenuDetailExam.put("path","/HomeIndex/businessMenu/detailExam");businessMenuDetailExam.put("icon","DocumentRemove");businessMenuDetailExam.put("component","/src/views/businessMenu/DetailExam.vue");businessMenuDetailExam.put("requireAuth",true);businessMenuList.add(businessMenuDetailExam);//图表示例JSONObject businessMenuChartExam = new JSONObject();businessMenuChartExam.put("name","图表示例");businessMenuChartExam.put("path","/HomeIndex/businessMenu/chartExam");businessMenuChartExam.put("icon","Postcard");businessMenuChartExam.put("component","/src/views/businessMenu/ChartExam.vue");businessMenuChartExam.put("requireAuth",true);businessMenuList.add(businessMenuChartExam);//文件上传JSONObject businessMenuFileUpload = new JSONObject();businessMenuFileUpload.put("name","文件上传");businessMenuFileUpload.put("path","/HomeIndex/businessMenu/fileUpload");businessMenuFileUpload.put("icon","Files");businessMenuFileUpload.put("component","/src/views/businessMenu/FileUpload.vue");businessMenuFileUpload.put("requireAuth",true);businessMenuList.add(businessMenuFileUpload);//富文本示例JSONObject businessMenuRichTextExam = new JSONObject();businessMenuRichTextExam.put("name","富文本示例");businessMenuRichTextExam.put("path","/HomeIndex/businessMenu/richTextExam");businessMenuRichTextExam.put("icon","Document");businessMenuRichTextExam.put("component","/src/views/businessMenu/RichTextExam.vue");businessMenuRichTextExam.put("requireAuth",true);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");
//        baseData.put("requireAuth",true);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","/src/views/baseData/MsgData.vue");baseDataMsgData.put("requireAuth",true);baseDataList.add(baseDataMsgData);//基础数据-实体配置JSONObject baseDataEntitySet = new JSONObject();baseDataEntitySet.put("name","实体配置");baseDataEntitySet.put("path","/HomeIndex/baseData/entityConfig");baseDataEntitySet.put("icon","Operation");baseDataEntitySet.put("component","/src/views/baseData/EntityConfig.vue");baseDataEntitySet.put("requireAuth",true);baseDataList.add(baseDataEntitySet);//基础数据-验证码数据JSONObject baseDataValidationCode = new JSONObject();baseDataValidationCode.put("name","验证码数据");baseDataValidationCode.put("path","/HomeIndex/baseData/validationCode");baseDataValidationCode.put("icon","DocumentChecked");baseDataValidationCode.put("component","/src/views/baseData/ValidationCode.vue");baseDataValidationCode.put("requireAuth",true);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");
//        systemManagement.put("requireAuth",true);List<JSONObject>  systemManagementList = new ArrayList<JSONObject>();//系统管理-用户管理JSONObject sysMngUser = new JSONObject();sysMngUser.put("name","用户管理");sysMngUser.put("path","/HomeIndex/systemManagement/userManagement");sysMngUser.put("icon","User");sysMngUser.put("component","/src/views/systemManagement/UserManagement.vue");sysMngUser.put("requireAuth",true);systemManagementList.add(sysMngUser);//系统管理-角色管理JSONObject sysMngRole = new JSONObject();sysMngRole.put("name","角色管理");sysMngRole.put("path","/HomeIndex/systemManagement/roleManagement");sysMngRole.put("icon","Van");sysMngRole.put("component","/src/views/systemManagement/RoleManagement.vue");sysMngRole.put("requireAuth",true);systemManagementList.add(sysMngRole);//系统管理-菜单管理JSONObject sysMngMenu = new JSONObject();sysMngMenu.put("name","菜单管理");sysMngMenu.put("path","/HomeIndex/systemManagement/menuManagement");sysMngMenu.put("icon","Reading");sysMngMenu.put("component","/src/views/systemManagement/MenuManagement.vue");sysMngMenu.put("requireAuth",true);systemManagementList.add(sysMngMenu);//系统管理-日志管理JSONObject sysMngLog = new JSONObject();sysMngLog.put("name","日志管理");sysMngLog.put("path","/HomeIndex/systemManagement/logManagement");sysMngLog.put("icon","Memo");sysMngLog.put("component","/src/views/systemManagement/LogManagement.vue");sysMngLog.put("requireAuth",true);systemManagementList.add(sysMngLog);//系统管理-系统配置JSONObject sysMngSet = new JSONObject();sysMngSet.put("name","系统配置");sysMngSet.put("path","/HomeIndex/systemManagement/systemConfig");sysMngSet.put("icon","DataLine");sysMngSet.put("component","/src/views/systemManagement/SystemConfig.vue");sysMngSet.put("requireAuth",true);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;}}

25.3 后端接口返回的菜单数据

menuData.json

// 引入, 用于存储全局的状态数据,可供其他地方调用
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;

25.4 创建菜单组件

根据后端接口定义的菜单数据,创建所有菜单组件,当然,此时只是组件,内容并未实现
组件模板如下

<script setup lang="ts"></script><template>组件名称
</template><script setup></script>

在src/views/包下创建主菜单及子菜单组件如下
在这里插入图片描述
在这里插入图片描述
这里的组件名称需与接口返回数据中的路径一致

25.5 菜单栏代码更新(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("");// 外部参数,这里是从HomeIndex组件中传过来的const option = defineProps({fixedTab:{type: String}})onMounted (()=>{loadMenuData();});const checkToken = ref(1);checkToken.value = utils.getData("token");// 加载菜单数据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 = [];// 固定tab页对象let fixedTabItem = null;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: []};// 如果传过来的参数与当前的item路径path一致,则将当前item赋值fixedTabItemif(option && option.fixedTab && option.fixedTab == item.path){fixedTabItem = item;}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},};if(option && option.fixedTab && option.fixedTab == subItem.path){fixedTabItem = subItem;}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});// router.addRoute('/HomeIndex/:path+', indexChildrens);// router.addRoute('/HomeIndex', indexChildrens);// router.addRoute('/', indexChildrens);// 这里判断固定的标签页是否有值,有值则先保持该标签页不关闭if(option && option.fixedTab && fixedTabItem){selectMenu(fixedTabItem.path);}// 根据url中的路由信息自动选中对应的菜单// curMenu.value = router.currentRoute.value.path;// 选中菜单de事件触发 传入的值为当前组件的路由地址如,/HomeIndex/businessMenu/detailselectMenu(router.currentRoute.value.path);console.log("router.currentRoute.value.path: ",router.currentRoute.value.path)}}).catch((error)=>{console.log("error: ",error)utils.hideLoadding();utils.showError("加载失败");}).finally(()=>{utils.hideLoadding();});}// 选择当前菜事件触发的方法const selectMenu = (value:any)=>{const curMenuData = selectMenuByPath(value);emits('select', curMenuData);};// 根据路径选中对应菜单const selectMenuByPath = (value:any)=>{// if(value == curMenu.value){//     return;// }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);}});}});console.log("--------->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>---: ", curMenuData.path);if(curMenuData){console.log("--------->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>---: ", curMenuData.path);// router.push(curMenuData.path);}return curMenuData;}// 暴露选中菜单方法,可让外部调用该方法选中对应菜单defineExpose({selectMenuByPath})// 13-8.5=4.5// 36-4.5=31.5// 12.5+7.5=20// 11.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>

25.6 主页代码更新(HomeIndex.vue)

src/views/index/HomeIndex.vue

<script setup lang="ts">import { defineAsyncComponent, reactive, 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'}}// 当前选中的tabconst activeName = ref('');// 当前打开的所有tabconst tabDatas = reactive([]);// 通过路径动态加载对应的组件const getComponentByPath = (path:any) => {// 异步组件// return defineAsyncComponent(()=> import(//     new URL(path,import.meta.url).href// ));return defineAsyncComponent(()=> {return import(new URL(path,import.meta.url).href);});// return defineAsyncComponent(()=> {return import(path)});}// 选中菜单的事件处理const selectMenu = (value:any)=>{let tabExsisted = false;// 遍历tabDatas(打开的tab页)中的每一项tabDatas.forEach(item => {console.log("item.path: ",item);// 如果包含当前选中菜单相同的项if(item.path == value.path){// 将tabExsisted值改为truetabExsisted = true;}});// 如果tabDatas(打开的tab页)中不包含当前选中菜单if(!tabExsisted){// 则将当前选中的菜单添加到tab页中tabDatas.push(value);}activeName.value = value.path;}// 固定不可关闭的tab页const fixedTab = ref("/HomeIndex/WorkPlat");// menuBar组件实例const menuBar = ref(null);// tab标签改变时触发的事件const tabChange = (value:any) => {menuBar.value.selectMenuByPath(value);}// tab标签移出时触发事件const tabRemove = () => {}</script><template><!-- 后台主页 --><div class="index-layout"><el-container><!-- 宽度以变量形式传入,打开关闭侧边菜单栏 --><el-aside class="layout-aside" :width="slideWidth"><MenuBar ref="menuBar" @menuCollapse="menuCollapse" @select="selectMenu" :fixedTab="fixedTab"></MenuBar><!-- <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" v-model="activeName" @tab-change="tabChange" @tab-remove="tabRemove"><!-- 单个tab --><!-- <el-tab-pane label="User" name="first">主界面</el-tab-pane> --><!-- 这里需要注意:tabDatas不需要使用.values获取值,直接遍历即可 --><!-- <el-tab-pane class="tabs-pane" v-for="item in tabDatas" :name="item.path" closable> --><el-tab-pane class="tabs-pane" v-for="item in tabDatas" :name="item.path" :closable="fixedTab != item.path"><!-- <el-tab-pane class="tabs-pane" v-for="item in tabDatas.values"> --><!-- <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> --><component class="label-icon" :is="item.icon"></component><!-- <span class="label-span">工作台</span> --><span class="label-span">{{ item.name }}</span></span></template><component class="label-icon" :is="item.component?getComponentByPath(item.component):null"></component></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;}/* 标签样式设置 */.main-tabs:deep(.label-icon ){width: 15px;/* height: 15px; *//* 位置-图标和文本的位置持平 */position: relative;}/* tab关闭按钮样式设置 */.main-tabs:deep(.is-icon-close ){/* width: 15px; *//* height: 15px; *//* 位置-图标和文本的位置持平 *//* position: relative; */top: 1px;}.layout-header{position: fixed;top: 0;right: 0;width: 300px;/* height: 60px; */line-height: 35px;}</style>

25.7 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, from)=>{console.log("to: ",to)// 鉴权 在router.ts中设置的requireAuth参数,true则需要鉴权,false则不需要if(to.meta.requireAuth){console.log("开始鉴权,===>>>")// 进入鉴权,通过缓存中的token与接口中的token进行校验// let token = utils.getData("token");let routerToken = utils.getData("token");let userInfo = utils.getData("userInfo");// 当前不是登录状态// console.log("20240021 token ",token);console.log("store.getters.isLogin: ",(routerToken&&userInfo)?true:false);// if(!store.getters.isLogin){// 是否登录过,校验缓存中token和userinfo数据是否存在const loginCheck = (routerToken&&userInfo)?true:false;const userLogin = router.currentRoute.value.path;console.log("!loginCheck: ", !loginCheck);console.log("userLogin==UserLogin: ", userLogin=="/UserLogin");// if(!loginCheck&&userLogin!="/UserLogin"){if(!loginCheck){// router.push({//   path: "/UserLogin",//   query: {//     redirect: router.currentRoute.value.fullPath//   }// })router.push("/UserLogin");return false;}// if(router.currentRoute.value.path=="/UserLogin"){//   console.log("鉴权成功,====》》》》",router.currentRoute.value.path)//   // router.push("/");//   // return true;// }console.log("鉴权成功,====》》》》")return true;}
});onMounted(()=>{let token = "";// let nToken = getToken();// 由于token可能返回undefined报错,需要进行报错处理try {console.log("=== 1 ===");token = utils.getData("token");const userinfo = utils.getData("userInfo");console.log("token: ",token);console.log("userinfo: ",userinfo);} catch (error) {console.log("=== 2 ===");console.log("error: ",error)error;}let userInfo = utils.getData('userInfo');if(token && userInfo){console.log("=== 3 ===");// 登录成功,验证utils.showLoadding("正在加载")const username = utils.getData('username');console.log("username-1-1-1-1-",username);if(!username){console.log("=== 4 ===");// 登录失败,跳转到登录页if(username===undefined){console.log("=== 5 ===");utils.saveData("username","");}// token验证失败utils.showError("用户名过期-请重新登录");router.push('/UserLogin');utils.hideLoadding();}else{console.log("=== 6 ===");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方法进行保持当前选中菜单路由// 如果当前在UserLogin界面则进行跳转操作console.log("currentRoutePath: ",router.currentRoute.value.fullPath)// if(router.currentRoute.value.path=="/UserLogin"){// router.push("/");// }utils.showSuccess("登录状态验证成功app.vue");}else{// if(username===undefined){//   utils.saveData("username","");// }// 登录失败utils.showError("Token已过期,请重新登录");// 登录失败,跳转到登录页router.push('/UserLogin');return;}});// utils.showError("未知错误!!!!!!");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>

25.8 页面效果展示

控制台一致保持不关闭
标签页与路由地址及左侧菜单栏绑定
在这里插入图片描述
刷新页面后工作台和选中的菜单对应的标签页保留
在这里插入图片描述

25.9 代码下载地址

此阶段代码已上传到CSDN
前段项目下载地址:前端 vue 前后端分离项目hslb-management-system 菜单栏功能实现0827
后端项目下载地址:java springboot 前后端分离项目hslb-management-system 后端接口 菜单数据的更新优化0827


感谢阅读,祝君暴富!


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/411138.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用C++封装顺序表

作业&#xff1a;使用C手动封装一个顺序表&#xff0c;包含成员数组一个&#xff0c;成员变量N个 #include <iostream>using namespace std;using datatypeint; #define MAX 20struct SeqList { private: //私有datatype *data;int size0; …

【Java】数据类型与变量(二)

目录 3.变量 3.1什么是变量&#xff08;变量的概念&#xff09; 3.2语法格式 ​编辑​编辑3.3整型变量 3.3.1整型变量如何定义 ​编辑 3.3.2长整型变量 3.3.3短整型变量 3.3.4字节型变量 3.4浮点型变量 3.4.1双精度浮点型 3.4.2单精度浮点型 3.4.3单精度浮点型与双…

Google Search Console:完整教程

Google 提供了各种工具来收集和分析网站数据&#xff0c;其中最有价值的工具之一是 Google Search Console &#xff08;GSC&#xff09;。前身为 Google Webmaster Tools&#xff0c;它为 SEO 提供了对网站性能的宝贵见解。自 2015 年推出以来&#xff0c;该平台取得了长足的发…

关机软件项目规划

一、概述 1.1 编写目的 此项目开发规划书的编写主要是为《UPS SNMP卡网络监控系统》中配套使用的关机软件做主要的规划和整合&#xff0c;在开发过程中起到引导作用&#xff0c;以及给使用者提供简要的说明。 1.2 项目背景 关机软件是UPS网络监控适配器项目监控层的组成部分…

黑神化爆火,悟空的八十一难究竟用到了什么数据库?

九九八十一难&#xff0c;第一难。猿神&#xff0c;启动…然后发现先解压缩&#xff0c;后着色编译。就这姿势&#xff0c;这就是爆火的 《黑神话&#xff1a;悟空》单机游戏&#xff0c;哪怕是在工作日&#xff0c;大家仍纷纷涌入这个游戏世界。8月20日&#xff0c;万众瞩目的…

Excel表格合并后同步修改行号,删除重复项,按合并后的列进行排序

Excel合并单元格后每个合并后的行占据多列&#xff0c;如何进行排序 1、全选后选择合并选项中的取消合并单元格 2、选择删除重复项&#xff08;可以直接选定唯一行&#xff09; 3、可以发现合并后的每行占Excel的一行 4、然后制定排序规则 5、序号列下拉重排&#xff08;鼠标放…

智谱开源 CogVideoX-5B 视频生成模型,RTX 3060 显卡可运行;曝 OpenAI 模型「草莓」今秋推出

开发者朋友们大家好&#xff1a; 这里是 「RTE 开发者日报」 &#xff0c;每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE&#xff08;Real-Time Engagement&#xff09; 领域内「有话题的 新闻 」、「有态度的 观点 」、「有意思的 数据 」、「有思考的 文…

Android Studio Koala下载并安装,测试helloworld.

1、下载&#xff1a; 下载 Android Studio 和应用工具 - Android 开发者 | Android Developers 2、滚动条拉到近最后&#xff0c;各个系统的下载地址&#xff1a; 3、下载完成以后&#xff0c;我们双击运行安装&#xff1a; 如果有路径要修改&#xff0c;则修改下就可以了&a…

【大模型系列篇】预训练模型:BERT GPT

2018 年&#xff0c;Google 首次推出 BERT&#xff08;Bidirectional Encoder Representations from Transformers&#xff09;。该模型是在大量文本语料库上结合无监督和监督学习进行训练的。 BERT 的目标是创建一种语言模型&#xff0c;可以理解句子中单词的上下文和含义&…

新华三H3C HCL配置IS-IS基本配置

实验目标 完成本实验,应该能够达到以下目标。 ●掌握如何在路由器进行单区域IS-IS的基本配置 ●掌握如何在路由器上查看IS-IS路由表、邻居信息 ●掌握如何在路由器上查看IS-IS的LSDB信息 实验拓扑 IP地址表 实验任务 单区域配置&#xff1a; 在本实验任务中,需要在路由器上…

Dockerfile+私有仓库

使用Dockerfile创建应用镜像 在Docker file中定义所需要执⾏的指令&#xff0c;使⽤ docker build创建镜 像&#xff0c;过程中会按照dockerfile所定义的内容进⾏打开临时性容器&#xff0c;把docker file中命令全部执⾏完成&#xff0c;就得到了⼀个容器应⽤镜像&#xff0c;每…

排序算法刷题【leetcode88题目:合并两个有序数组、leetcode21:合并两个有序链表】

一、合并两个有序数组 题目比较简单&#xff0c;使用归并排序里面的同样的操作就可以&#xff0c;代码如下所示 #include <iostream> #include <vector> using namespace std;/* leetcode88题&#xff1a;合并两个有序数组 */ class Solution { public:void merge…

代码随想录训练营 Day41打卡 动态规划 part08 121. 买卖股票的最佳时机 122. 买卖股票的最佳时机II 123. 买卖股票的最佳时机III

代码随想录训练营 Day41打卡 动态规划 part08 一、力扣121. 买卖股票的最佳时机 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股票。设计…

网络安全总结②

上一篇&#xff1a;网络安全总结① 下一篇&#xff1a; 传统防火墙 传统防火墙 技术&#xff1a;访问控制、代理技术、会话机制 工作层次&#xff1a;应用层一下 防御模式&#xff1a;通过防御设备划分边界&#xff0c;基于IP/端口和特征进行判断&#xff1b;以隔离为基础&am…

java Boss直聘爬虫数据分析

摘要 本报告利用Java和Selenium爬虫技术获取数据&#xff0c;并使用ECharts库对薪资数据进行可视化分析&#xff0c;旨在探究不同经验和学历的薪资分布情况。 数据来源 数据来源于Boss直聘&#xff0c;使用Java结合Selenium库进行数据抓取。 数据总数&#xff1a;约2000家企…

LeetCode --- 411周赛

题目列表 3258. 统计满足 K 约束的子字符串数量 I 3259. 超级饮料的最大强化能量 3260. 找出最大的 N 位 K 回文数 3261. 统计满足 K 约束的子字符串数量 II 一、统计满足K约束的子字符串数量I 这种要求满足区间内某种性质的题&#xff0c;一般都可以用滑动窗口来做。这题…

黄河:曾月入十几万,被裁后做独立开发,我每天必须要做的事就是写代码

这是《开发者说》的第16期&#xff0c;本期我们邀请的开发者是黄河&#xff0c;来自西北城市银川&#xff0c;半路转行为程序员&#xff0c;靠着自己对编程的热爱&#xff0c;一路坚持下来&#xff0c;虽地处偏远&#xff0c;正是得益于互联网的好处&#xff0c;让全球每一个角…

畅捷通CRM newleadset.php SQL注入漏洞复现

0x01 产品简介 用友畅捷通CRM是面向小企业全力打造的简单、实用的客户关系管理应用。帮助企业用好自己的客户资源、管好商机跟进过程、引导好业务员跟单行为,促进团队销售能力的提升;通过查询和分析,识别企业的价值客户,融合电话、短信、邮件等工具,实现精准营销;帮助企…

网络安全之渗透测试实战-DC-3-靶机入侵

一、下载靶机DC-3&#xff0c;解压后导入Vmware Workstation https://pan.baidu.com/s/17BcSH6RqC7wuyB7PRNqOow?pwdkc12启动DC-3靶机&#xff0c;由于不知道密码&#xff0c;无需登录 二、靶机的网卡采用的是NAT模式自动获取IP地址&#xff0c;此时我们需要先获取其MAC地址…

Qt:鼠标事件

虽然Qt是跨平台的c开发框架&#xff0c;但是Qt的很多能力是系统提供的&#xff0c;只是其封装了系统的API&#xff0c;例如在Linux环境下的Qt就封装了Linux的一堆API 系统API 事件&#xff1a;图形化界面中&#xff0c;用户操作和程序之间交互的机制&#xff08;封装了系统的事…