Vue3+vite搭建基礎架構(11)--- 菜單欄功能和Tab頁功能實現

Vue3+vite搭建基礎架構(11)--- 菜單欄功能和Tab頁功能實現

  • 說明
  • 刪除項目中不需要的文件
  • userStore全局屬性代碼
  • 菜單欄代碼
  • Tab頁代碼
  • 解決瀏覽器輸入地址時不會打開tab頁問題和切換tab頁時參數丟失問題

說明

這里記錄下自己在Vue3+vite的項目使用less來寫樣式以及使用vite-plugin-vue-setup-extend直接定義組件name,不使用ts語法,方便以后直接使用。這里承接自己的博客Vue3+vite搭建基礎架構(10)— 使用less和vite-plugin-vue-setup-extend這篇博客,在該博客項目的基礎上增加菜單欄功能和Tab頁功能實現。

刪除項目中不需要的文件

刪除掉src文件夾下的style.css和compoments文件夾下的HelloWorld.vue以及assets文件夾下的vue.svg圖片,這三個都是項目創建完成后自帶的,因為用不到所以刪除掉。
在這里插入圖片描述
刪除views下面home文件夾下的index.vue代碼,因為這個里面代碼是以前用來測試依賴的代碼,所以把代碼清空,保留為一個空文件。
代碼如下:

<!--home首頁代碼-->
<template><div>我是首頁</div>
</template><script setup name="home"></script><style lang="less" scoped></style>

在src下面新建styles文件夾用來存放全局樣式。common.less用來存放html標簽樣式。element-plus.less用來存放ElementPlus組件里面的標簽樣式。然后在main.js里面引入2個樣式文件,讓它們全局生效。
在這里插入圖片描述

common.less里面代碼如下:

//body全局樣式設計
body{font-size: 14px;//字體大小margin: 0px;padding: 0px;
}//a標簽全局樣式
a {color: #1B68B6;//字體顏色text-decoration: none;//去掉下劃線cursor: pointer;//鼠標放上去手型//鼠標放上去顏色/*&:hover {color: #1B68B6;}//鼠標點擊時顏色&:active{color: #1B68B6;}//鼠標點擊后獲取焦點樣式&:focus {color: #1B68B6;}*/
}

element-plus.less目前代碼為空。

userStore全局屬性代碼

在store文件夾下的modules文件夾下的userStore.js文件修改代碼為如下:

//使用pinia來管理全局狀態
import { defineStore } from "pinia"/*defineStore 是需要傳參數的,其中第一個參數是id,就是一個唯一的值,
簡單點說就可以理解成是一個命名空間.
第二個參數就是一個對象,里面有三個模塊需要處理,第一個是 state,
第二個是 getters,
第三個是 actions。
*/
//聲明了一個useUserStore方法
const useUserStore = defineStore('user', {//準備state——用于存儲數據state: () => {return {//當前激活菜單的indexactiveMenu: '',//綁定值,選中選項卡的nameeditableTabsValue: '',//tab標簽選項卡內容editableTabs: [],//tab頁路由地址及參數tabRouterList: []}},//使用persist插件對state里面屬性進行緩存persist: {enabled: true,//開啟緩存,默認緩存所有state里面的屬性,默認key為defineStore里面的id值,這里id值為user,所以默認key為user//自定義持久化參數,指定以下state里面的屬性進行緩存,未指定的不進行緩存strategies: [{// 自定義keykey: 'activeMenu',// 自定義存儲方式,默認sessionStoragestorage: sessionStorage,// 指定要持久化的數據paths: ['activeMenu']},{key: 'editableTabsValue',storage: sessionStorage,paths: ['editableTabsValue']},{key: 'editableTabs',storage: sessionStorage,paths: ['editableTabs']},{key: 'tabRouterList',storage: sessionStorage,paths: ['tabRouterList']}]},getters: {},//準備actions——用于響應組件中的動作和用于操作數據(state),pinia中只有state、getter、action,拋棄了Vuex中的Mutationactions: {/*** 修改state中數據的方法* @param name 需要修改的屬性名* @param value 修改值*/updateState([name, value]) {this[name] = value},//動態添加tab標簽,item為當前點擊的菜單項addTab(item) {const newTab = {title: item.meta.title,name: item.url,iconClass: item.meta.icon,}// 判斷當前editableTabs中是否存在該tab標簽if (this.editableTabs.findIndex(item => item.title === newTab.title) === -1) {this.editableTabs.push(newTab);this.editableTabsValue = newTab.name;this.activeMenu = newTab.name;}},//移除tab標簽removeTab(targetName) {let tabs = this.editableTabslet activeName = this.editableTabsValueif (activeName === targetName) {tabs.forEach((tab, index) => {if (tab.name === targetName) {let nextTab = tabs[index + 1] || tabs[index - 1]if (nextTab) {activeName = nextTab.name}}})}this.activeMenu = activeNamethis.editableTabsValue = activeNamethis.editableTabs = tabs.filter(tab => tab.name !== targetName)this.tabRouterList = this.tabRouterList.filter(item => item.path !== targetName)}}
})export default useUserStore

菜單欄代碼

views文件下layout文件夾下的layout.vue布局代碼如下:

<template><div><el-container><!--側邊欄,height: 100vh;設置高度為視口高度--><el-aside style="width: 200px;height: 100vh;"><SliderBar></SliderBar></el-aside><el-container><!--頭部--><el-header><Navbar></Navbar></el-header><!--主體內容--><el-main><!--主體內容--><AppMain></AppMain></el-main></el-container></el-container></div>
</template><script>import { Navbar, SliderBar, AppMain } from './components/index.js'export default {name: "layout",components: {Navbar,SliderBar,AppMain}}
</script><style scoped></style>

views文件下layout文件夾下的components文件夾下sliderBar文件夾下的sliderBar.vue代碼如下:

<!--通用布局側邊欄內容-->
<template><el-row><el-col><div class="header"><!--系統logo,隨便找一個圖片示例用--><SvgIcon iconClass="systemManagement" /><span class="icon-text">后臺管理系統</span></div><!--router表示為啟動路由模式,路由模式下index為你的頁面路由路徑--><!--通過設置default-active屬性點擊tab頁時,自動選中左邊菜單欄選項--><div><el-menuactive-text-color="#1B68B6"background-color="#FFFFFF":default-active="store.activeMenu"text-color="#333333"@select="handleSelect":router="true"class="menu-items"><!--引用菜單樹組件將路由的菜單欄循環顯示出來--><MenuTree :menuList="menuTreeList"/></el-menu></div></el-col></el-row>
</template><script setup name="SliderBar">//引入菜單列表組件import MenuTree from "./menuTree.vue"//引入全局狀態里面的關于菜單欄列表數據和相關方法import useUserStore from "@/store/modules/userStore"//使用useUserStore里面的屬性const store = useUserStore()//菜單激活回調函數,當tab頁已經打開的情況下,再次點擊菜單項,對應的tab頁也跟著切換function handleSelect(key) {store.updateState(["editableTabsValue", key])store.updateState(["activeMenu", key])}//菜單樹列表,這里模擬后端接口請求返回的數據,示例數據如下:const menuTreeList = [{id: 1,url: "/test-management1",//該url要與路由文件里面的path值要一致level: 1,//菜單等級meta: { title: "測試管理1", icon: "systemManagement" },children: [] //子菜單},{id: 2,url: "/system-management",level: 1,meta: { title: "系統管理", icon: "systemManagement" },children: [{id: 3,url: "/user-management",level: 2,meta: { title: "用戶管理", icon: "userManagement" },children: []},{id: 4,url: "/role-management",level: 2,meta: { title: "角色管理", icon: "roleManagement" },children: []},{id: 5,url: "/permission-management",level: 2,meta: { title: "權限管理", icon: "permissionManagement" },children: []},{id: 6,url: "/password-management",level: 2,meta: { title: "密碼管理", icon: "systemManagement" },children: []},],},{id: 7,url: "/test-management2",level: 1,meta: { title: "測試管理2", icon: "systemManagement" },children: []},{id: 8,url: "/test-management3",level: 1,meta: { title: "測試管理3", icon: "systemManagement" },children: [{id: 9,url: "/test-management4",level: 2,meta: { title: "測試管理4", icon: "systemManagement" },children: [{id: 10,url: "/test-management5",level: 3,meta: { title: "測試管理5", icon: "systemManagement" },children: []}]}]}]
</script><style lang="less" scoped>
.header {height: 64px;display: flex;align-items: center; //垂直居中justify-content: left; //水平居左//logo樣式.svg-icon {width: 64px;height: 32px;}.icon-text {font-size: 16px;color: #1b68b6;margin-left: -5px;}
}//普通菜單懸浮樣式
:deep(.el-menu-item:hover) {background-color: #E8EFF7;//背景顏色color: #1B68B6;//字體顏色
}//子菜單懸浮樣式,子菜單的圖標顏色需要修改svg圖片里面的fill值,由fill="#333333"改為fill="currentColor"后,圖標懸浮樣式顏色才會一起變化
:deep(.el-sub-menu__title:hover) {background-color: #E8EFF7;//背景顏色color: #1B68B6;//字體顏色
}//菜單被選中的樣式
:deep(.el-menu .el-menu-item.is-active) {background-color: #E8EFF7; //背景顏色color: #1B68B6; //字體顏色border-right: 3px solid #1B68B6;//右邊框顏色
}//子菜單被選中的樣式
:deep(.el-sub-menu.is-active .el-sub-menu__title){color: #1B68B6; //字體顏色
}//菜單欄樣式
.menu-items {height: 100%; //設置高度為父容器高度border-right: none;//去掉菜單欄右邊框
}
</style>

views文件下layout文件夾下的components文件夾下sliderBar文件夾下的menuTree.vue代碼如下:

<!--菜單樹列表-->
<template><!--將菜單列表循環出來--><template v-for="item in menuList"><!--判斷菜單里面是否有子菜單--><el-sub-menu:key="item.id":index="item.url"v-if="item.children.length"><template #title><el-icon><SvgIcon :iconClass="item.meta.icon"></SvgIcon></el-icon><span>{{ item.meta.title }}</span></template><!--調用自身循環顯示子菜單--><MenuTree :menuList="item.children" /></el-sub-menu><!--菜單節點--><el-menu-itemv-else:key="item.id":index="item.url"@click="store.addTab(item)"><el-icon><SvgIcon :iconClass="item.meta.icon"></SvgIcon></el-icon><span>{{ item.meta.title }}</span></el-menu-item></template>
</template><script setup name="MenuTree">//引入全局狀態里面的關于菜單欄列表數據和相關方法import useUserStore from "@/store/modules/userStore"const store = useUserStore()//定義屬性給組件接收const props = defineProps({//菜單欄屬性menuList: {type: Array,//類型為數組//默認值為空數組default() {return []}}})
</script><style scoped></style>

在views文件夾下新建菜單樹列表里面對應的頁面文件,每個頁面文件加上如下一句代碼,用來表示不同頁面內容。
在這里插入圖片描述
src文件下router文件夾下的index.js代碼如下:

//引入router路由做頁面請求
import { createRouter,createWebHashHistory } from 'vue-router'
/* Layout通用組件 */
import Layout from '../views/layout/layout'const routes = [{path: '/404', component: () => import('@/views/404')},//必須要把組件放在Layout的children里面,才能在側邊欄的右側顯示頁面內容,否則不加載通用架構直接在當前空白頁面渲染內容,如:404頁面{path: '',component: Layout,redirect: '/home',children: [{path: 'home',name: 'home',component: () => import('@/views/home/index'),meta: {title: '首頁', icon: 'home'}},{path: 'test-management1',name: 'test-management1',component: () => import('@/views/test-management1/index'),meta: {title: '測試管理1', icon: 'systemManagement'}},{path: 'user-management',name: 'user-management',component: () => import('@/views/system-management/user-management'),meta: {title: '用戶管理', icon: 'userManagement'}},{path: 'role-management',name: 'role-management',component: () => import('@/views/system-management/role-management'),meta: {title: '角色管理', icon: 'roleManagement'}},{path: 'permission-management',name: 'permission-management',component: () => import('@/views/system-management/permission-management'),meta: {title: '權限管理', icon: 'permissionManagement'}},{path: 'password-management',name: 'password-management',component: () => import('@/views/system-management/password-management'),meta: {title: '密碼管理', icon: 'systemManagement'}},{path: 'test-management2',name: 'test-management2',component: () => import('@/views/test-management2/index'),meta: {title: '測試管理2', icon: 'systemManagement'}},{path: 'test-management5',name: 'test-management5',component: () => import('@/views/test-management5/index'),meta: {title: '測試管理5', icon: 'systemManagement'}}]}
]// 3. 創建路由實例并傳遞 `routes` 配置
const router = createRouter({// 4. 內部提供了 history 模式的實現。為了簡單起見,我們在這里使用 hash 模式。history: createWebHashHistory(),routes, // `routes: routes` 的縮寫
})//路由前置守衛
router.beforeEach((to, from, next) => {//路由發生變化修改頁面titleif (to.meta.title) {document.title = to.meta.title}next()
})//導出路由
export default router

啟動項目后,瀏覽器結果如下:
在這里插入圖片描述

點擊不同的菜單欄選項,頁面內容也會相應的變化,這種算是單頁面,activeMenu也會相應的變化,之所以要把這個寫到session storage里面,是為了防止頁面刷新時,點擊的高亮菜單選項消失問題。
在這里插入圖片描述

Tab頁代碼

views文件下layout文件夾下的components文件夾下的navbar.vue代碼如下:

<!--通用布局頭部內容-->
<template><el-row><el-col :span="20"><el-tabsv-model="store.editableTabsValue"type="border-card"closable@tab-remove="handleTabRemove"@tab-click="handleTabClick"v-if="store.editableTabs.length !== 0"><el-tab-pane v-for="item in store.editableTabs" :key="item.name" :name="item.name" :label="item.title"><!-- 右鍵菜單開始:自定義標簽頁顯示名稱,保證每個標簽頁都能實現右鍵菜單 --><template #label><el-dropdowntrigger="contextmenu":id="item.name"@visible-change="handleChange($event, item.name)"ref="dropdownRef"><span style="font-size: 16px;color: #909399;":class="store.editableTabsValue === item.name ? 'label' : ''"><SvgIcon :iconClass="item.iconClass"></SvgIcon>{{ item.title }}</span><template #dropdown><el-dropdown-menu><el-dropdown-item @click="closeCurrent(item.name)"><el-icon><Close /></el-icon>關閉當前標簽頁</el-dropdown-item><el-dropdown-item @click="closeLeft(item.name)" v-if="show(item.name, 'left')"><el-icon><DArrowLeft /></el-icon>關閉左側標簽頁</el-dropdown-item><el-dropdown-item @click="closeRight(item.name)" v-if="show(item.name, 'right')"><el-icon><DArrowRight /></el-icon>關閉右側標簽頁</el-dropdown-item><el-dropdown-item @click="closeOther(item.name)" v-if="store.editableTabs.length > 1"><el-icon><Operation /></el-icon>關閉其他標簽頁</el-dropdown-item><el-dropdown-item @click="closeAll()"><el-icon><Minus /></el-icon>關閉全部標簽頁</el-dropdown-item></el-dropdown-menu></template></el-dropdown></template><!-- 右鍵菜單結束 --></el-tab-pane></el-tabs></el-col><el-col :span="4"><div class="header"><!-- 用戶信息 --><!--trigger="click"通過點擊下標觸發--><div style="cursor: pointer;"><el-dropdown trigger="click"><span>{{ username }}<SvgIcon iconClass="arrowDown" /></span><template #dropdown><el-dropdown-menu><el-dropdown-item @click="logout">退出登錄</el-dropdown-item></el-dropdown-menu></template></el-dropdown></div></div></el-col></el-row>
</template><script setup name="navbar">//引入全局狀態里面的關于菜單欄列表數據和相關方法import useUserStore from '@/store/modules/userStore'import { useRouter, useRoute } from "vue-router"import { onMounted, ref, computed } from 'vue'import {Close,DArrowLeft,DArrowRight,Operation,Minus} from '@element-plus/icons-vue'//接手全局狀態里面的屬性和方法const store = useUserStore();//使用路由相當于$router,系統路由方法const router = useRouter()//使用路由相當于$route,點擊菜單欄時當前點擊的路由頁面里面的屬性值const route = useRoute()//用戶名const username = '超級管理員'//觸發右鍵菜單標簽頁為第一個時,不展示【關閉左側標簽頁】//觸發右鍵菜單標簽頁為最后一個時,不展示【關閉右側標簽頁】const show = (name, type) => {const index = store.editableTabs.findIndex((item) => name === item.name)return type === 'left' ? index !== 0 : index !== store.editableTabs.length - 1}//右鍵菜單refconst dropdownRef = ref()//在觸發右鍵菜單后,關閉其他tab頁上的右鍵菜單const handleChange = (visible, name) => {if (!visible) returndropdownRef.value.forEach((item) => {if (item.id === name) returnitem.handleClose()})}//關閉當前tab頁const closeCurrent = (targetName) => {handleTabRemove(targetName)}//關閉左側tab頁const closeLeft = (targetName) => {//查找當前點擊的tab頁所在位置let currentIndex = store.editableTabs.findIndex((item) => item.name === targetName)//查找當前激活標簽頁indexconst activeIndex = store.editableTabs.findIndex((item) => item.name === store.editableTabsValue)//關閉左側tab頁store.editableTabs.splice(0, currentIndex)//刪除對應的左側歷史路由store.tabRouterList.splice(0, currentIndex)//如果當前關閉點擊的tab頁包含激活的tab頁,則將激活tab頁重置為當前點擊的tabif (activeIndex < currentIndex) {//將當前激活的tab頁改為當前點擊的store.updateState(['editableTabsValue', targetName])//將激活菜單改為當前點擊的store.updateState(['activeMenu', targetName])//路由跳轉到當前點擊的tab頁//查詢當前點擊的tab頁緩存路由參數let result = store.tabRouterList.find(item => item.path === targetName);//路由跳轉且帶上對應tab頁的參數router.push({ path: targetName, query: result.query })}}//關閉右側tab頁const closeRight = (targetName) => {//查找當前點擊的tab頁所在位置let currentIndex = store.editableTabs.findIndex((item) => item.name === targetName)//查找當前激活標簽頁indexconst activeIndex = store.editableTabs.findIndex((item) => item.name === store.editableTabsValue)//關閉右側tab頁store.editableTabs.splice(currentIndex + 1)//刪除對應的右側歷史路由store.tabRouterList.splice(currentIndex + 1)//如果當前關閉點擊的tab頁包含激活的tab頁,則將激活tab頁重置為當前點擊的tabif (activeIndex > currentIndex) {//將當前激活的tab頁改為當前點擊的store.updateState(['editableTabsValue', targetName])//將激活菜單改為當前點擊的store.updateState(['activeMenu', targetName])//路由跳轉到當前點擊的tab頁//查詢當前點擊的tab頁緩存路由參數let result = store.tabRouterList.find(item => item.path === targetName);//路由跳轉且帶上對應tab頁的參數router.push({ path: targetName, query: result.query })}}//關閉其他tab頁const closeOther = (targetName) => {//查找當前點擊的tab頁所在位置let currentIndex = store.editableTabs.findIndex((item) => item.name === targetName)//關閉其他標簽頁store.editableTabs = [store.editableTabs[currentIndex]]//刪除除當前點擊外的歷史路由store.tabRouterList = [store.tabRouterList[currentIndex]]//如果當前點擊的不等于當前激活的if (targetName !== store.editableTabsValue) {//將當前激活的tab頁改為當前點擊的store.updateState(['editableTabsValue', targetName])//將激活菜單改為當前點擊的store.updateState(['activeMenu', targetName])//路由跳轉到當前點擊的tab頁//查詢當前點擊的tab頁緩存路由參數let result = store.tabRouterList.find(item => item.path === targetName);//路由跳轉且帶上對應tab頁的參數router.push({ path: targetName, query: result.query })}}//關閉全部tab頁const closeAll = () => {//清空tabs數組store.editableTabs.length = 0//清空歷史路由store.tabRouterList.length = 0//當前選中tab頁設置為空store.updateState(['editableTabsValue', ''])//當前激活菜單設置為空store.updateState(['activeMenu', ''])//跳轉到首頁router.push('home')}//處理tab標簽x按鈕的移除function handleTabRemove(targetName) {//如果editableTabs列表不為空數組if (store.editableTabs.length > 0) {//如果當前所在的tab頁路由地址與移除的tab頁名一樣,則移到前面一個tab頁且路由跳轉if (route.path === targetName) {let tabs = store.editableTabstabs.forEach((tab, index) => {if (tab.name === targetName) {//獲取當前tab的后一個或者前一個let nextTab = tabs[index + 1] || tabs[index - 1]//如果有值就移到它上面,沒有就是最后一個跳轉到首頁if (nextTab) {//根據name屬性進行查詢當前tab頁的緩存路由參數let result = store.tabRouterList.find(item => item.path === nextTab.name);//路由跳轉且帶上對應tab頁的參數router.push({ path: nextTab.name, query: result.query })} else {// 更改tab標簽綁定值,選中選項卡的namestore.updateState(['editableTabsValue', ''])// 更改當前激活的菜單store.updateState(['activeMenu', ''])//當刪除的是最后一個tab頁的時候,跳轉到首頁router.push('home')}}})//從editableTabs中移除當前tab標簽store.removeTab(targetName)} else {//從editableTabs中移除當前tab標簽store.removeTab(targetName)}}}//tab標簽被選中時觸發的事件function handleTabClick(tab) {store.updateState(['activeMenu', tab.props.name])store.updateState(['editableTabsValue', tab.props.name])// 判斷當前url地址和即將跳轉的是否一致,不一致進行跳轉,防止跳轉多次if (tab.props.name !== route.path) {// 根據name屬性進行查詢let result = store.tabRouterList.find(item => item.path === tab.props.name);//路由跳轉且帶上對應tab頁的參數router.push({ path: tab.props.name, query: result.query })}}//退出登錄方法function logout() {}
</script><style lang="less" scoped>//設置高度:deep(.el-tabs__nav-scroll) {height: 60px;}//去掉el-tabs的邊框:deep(.el-tabs) {border: none;}.header {height: 62px;position: absolute;right: 30px;top: 0px;z-index: 1; //不設置這個,el-down點擊出不來,被tab標簽頁長度擋住了display: flex;align-items: center; //垂直居中}//tab標簽頁里面字體設置.label {color: #1B68B6 !important; //激活標簽頁高亮font-size: 16px;}:deep(.el-tabs__item) {&:hover {span {color: #1B68B6 !important; //鼠標移到標簽頁高亮}}}
</style>

views文件下layout文件夾下的components文件夾下的appMain.vue代碼如下:

<!--通用布局頁面主體內容-->
<template><!-- 路由視圖對象 --><router-view v-slot="{ Component }"><!--include主要解決關閉tab頁時,同時銷毀該組件,防止再次重新打開時數據還在的情況。注意:組件name名必須和路由name名一致,否則會導致組件不緩存的情況。--><keep-alive :include="tabsNames"><component :is="Component"></component></keep-alive></router-view>
</template><script setup name="AppMain">import useUserStore from "@/store/modules/userStore"import { computed } from "vue"const store = useUserStore()//將路由里面的name取出來作為一個數組const tabsNames = computed(() => store.tabRouterList.map((item) => item.name))
</script><style scoped></style>

這里之所以不把appMain.vue的路由視圖對象寫到navbar.vue里面的el-tabs里面,是因為寫到el-tabs里面后,當你打開多個tab頁的時候,當你向后端發送請求時,打開了多少個tab頁,就會重復發送多少個后端接口請求,所以這里將它拆開寫,el-tabs里面內容實際是個空的。
通過element-plus里面的樣式讓它內邊距為0,看著內容像是放在了el-tabs里面。如下:

//讓el-tabs__content不顯示內容
.el-tabs--border-card>.el-tabs__content{padding: 0px;
}

styles文件夾下的element-plus.less樣式代碼如下:

//移除頭部間距,讓寬度100%占滿
.el-header{--el-header-padding:0px;min-width: 1000px;
}
//未打開tab頁右邊背景色
.el-tabs__nav-scroll{background: #FFFFFF;
}
//設置內容背景顏色
.el-main{background: #F2F6FB;min-width: 1000px;
}
//讓el-tabs__content不顯示內容
.el-tabs--border-card>.el-tabs__content{padding: 0px;
}
//Tabs標簽頁全局樣式
.el-tabs__item {height: 64px;font-size: 16px;background: #ffffff; //未選中Tabs頁背景顏色
}
//Tabs標簽頁選中樣式
.el-tabs--border-card > .el-tabs__header .el-tabs__item.is-active {color: #1b68b6; //選中Tabs標簽頁后字體顏色background-color: #e3edf7; //選中Tabs標簽頁后背景顏色
}

瀏覽器結果如下,點擊多個菜單欄打開多個tab頁結果:
在這里插入圖片描述
點擊tab頁的同時,菜單欄也來到對應的選項,如下:
在這里插入圖片描述
鼠標放到tab頁上面的字體上面,然后鼠標右鍵菜單,會顯示對應的關閉菜單下拉選項,如下:
在這里插入圖片描述
到這里tab頁相關的代碼就寫完了,但是有幾個問題需要注意,第一個問題就是你在瀏覽器上面直接輸入路由地址,它不會打開或者跳轉到對應tab頁上面。第2個問題就是在實際開發中,如果頁面跳轉需要攜帶參數過去,當你切換點擊tab頁的時候,參數會丟失問題。對于參數會丟失問題,所以才在userStore.js里面寫了一個tabRouterList屬性來存儲歷史路由及參數。

解決瀏覽器輸入地址時不會打開tab頁問題和切換tab頁時參數丟失問題

在router文件夾下面的index.js文件,將代碼修改為如下:

//引入router路由做頁面請求
import { createRouter,createWebHashHistory } from 'vue-router'
/* Layout通用組件 */
import Layout from '../views/layout/layout'
//引入pinia里面的state屬性和方法
import useUserStore from "@/store/modules/userStore"const routes = [{path: '/404', component: () => import('@/views/404')},//必須要把組件放在Layout的children里面,才能在側邊欄的右側顯示頁面內容,否則不加載通用架構直接在當前空白頁面渲染內容,如:404頁面{path: '',component: Layout,redirect: '/home',children: [{path: 'home',name: 'home',component: () => import('@/views/home/index'),meta: {title: '首頁', icon: 'home'}},{path: 'test-management1',name: 'test-management1',component: () => import('@/views/test-management1/index'),meta: {title: '測試管理1', icon: 'systemManagement'}},{path: 'user-management',name: 'user-management',component: () => import('@/views/system-management/user-management'),meta: {title: '用戶管理', icon: 'userManagement'}},{path: 'role-management',name: 'role-management',component: () => import('@/views/system-management/role-management'),meta: {title: '角色管理', icon: 'roleManagement'}},{path: 'permission-management',name: 'permission-management',component: () => import('@/views/system-management/permission-management'),meta: {title: '權限管理', icon: 'permissionManagement'}},{path: 'password-management',name: 'password-management',component: () => import('@/views/system-management/password-management'),meta: {title: '密碼管理', icon: 'systemManagement'}},{path: 'test-management2',name: 'test-management2',component: () => import('@/views/test-management2/index'),meta: {title: '測試管理2', icon: 'systemManagement'}},{path: 'test-management5',name: 'test-management5',component: () => import('@/views/test-management5/index'),meta: {title: '測試管理5', icon: 'systemManagement'}}]}
]// 3. 創建路由實例并傳遞 `routes` 配置
const router = createRouter({// 4. 內部提供了 history 模式的實現。為了簡單起見,我們在這里使用 hash 模式。history: createWebHashHistory(),routes, // `routes: routes` 的縮寫
})//黑名單,在該黑名單里面的路由將不會動態加載tab頁
const blackList=['/404','/home']const handleToParams = (to) => {const route = {fullPath: to.fullPath,meta: to.meta,name: to.name,params: to.params,path: to.path,query: to.query,}return route
}function  handleRouteInEditableTabs(to,store) {//判斷當前路由的標題是否已經在editableTabs里,如果不在則動態添加tab頁const indexInEditableTabs = store.editableTabs.findIndex((item) => item.title === to.meta.title)//當前路由的標題已經在editableTabs里if (indexInEditableTabs !== -1) {//判斷tabRouterList是否已經存在相同的路由const indexInTabRouterList = store.tabRouterList.findIndex((item) => item.name === to.name)//當前路由的name已經在tabRouterList里面if (indexInTabRouterList !== -1) {//根據當前路由名稱找到對應的歷史路由let result = store.tabRouterList.find(item => item.name === to.name)//在name相同但是路由的query參數不一樣,則替換為這個最新的(將對象轉為string字符串比較,即可判斷2個對象屬性與值是否完全一樣)let queryMatched=JSON.stringify(result.query) === JSON.stringify(to.query)//如果為false,則替換當前路由參數if (!queryMatched) {//若存在,則從原始數組中移除該對象store.tabRouterList = store.tabRouterList.filter((item) => item.name !== to.name)//重新添加這個新路由store.tabRouterList.push(handleToParams(to))}} else {//點擊菜單欄時,如果不在則添加該路由store.tabRouterList.push(handleToParams(to))}} else {//判斷該路由是否在黑名單里面,不在則動態添加tab頁if (!blackList.includes(to.path)) {//如果不在editableTabs里面,那么就在editableTabs里面添加這個tab頁store.editableTabs.push({title: to.meta.title,name: to.path,iconClass: to.meta.icon,})//點擊頁面中的某個按鈕進行頁面跳轉的時候,如果不在則添加該路由里面部分字段store.tabRouterList.push(handleToParams(to))}}
}//路由前置守衛
router.beforeEach((to, from, next) => {//如果沒有匹配到路由,則跳轉到404頁面if (to.matched.length === 0) {next("/404")} else {//路由發生變化修改頁面titledocument.title = to.meta.title//使用pinia里面的全局狀態屬性const store = useUserStore()//更改tab標簽綁定值,選中選項卡的namestore.updateState(["editableTabsValue", to.path])//更改當前激活的菜單store.updateState(["activeMenu", to.path])//動態添加tab頁及tab頁切換時參數也跟著切換handleRouteInEditableTabs(to,store)next()}
})//導出路由
export default router

瀏覽器結果如下,在瀏覽器直接輸入相應路由,會自動跳轉到對應的tab,如下:
在這里插入圖片描述
輸入不存在的路由會直接跳轉到404頁面,如下:
在這里插入圖片描述
在這里插入圖片描述
從用戶管理攜帶參數跳轉到角色管理,測試如下:
views文件夾下面的system-management文件夾下的user-management.vue代碼如下:

<template><div>用戶管理頁面<el-button type="primary"@click="toRoleManagement(1)">跳轉到角色管理</el-button></div>
</template><script setup name="user-management">import { useRouter } from "vue-router"//使用router跳轉路由const router=useRouter()const toRoleManagement = (id) => {//跳轉到郵單詳情里面router.push({ path: 'role-management', query: { id: id } })}
</script><style scoped></style>

views文件夾下面的system-management文件夾下的role-management.vue代碼如下:

<template><div>角色管理頁面</div>
</template><script setup name="role-management">import {onActivated} from 'vue'import { useRoute } from "vue-router"//使用route接受路由傳過來的參數const route=useRoute()//每次頁面初始化時或者在郵件管理頁面點擊郵件詳情時執行該方法onActivated(()=>{const id=route.query.idconsole.info("接受到的id=",id)})
</script><style scoped></style>

瀏覽器結果如下:
在這里插入圖片描述
點擊跳轉到角色管理按鈕結果如下:
在這里插入圖片描述
然后再次點擊用戶管理tab頁,如下:
在這里插入圖片描述
再次點擊角色管理tab頁,發現參數依舊在沒有消失。問題解決。
在這里插入圖片描述
到這里Vue3+vite搭建基礎架構的所有代碼就結束了。只需要根據實際需求添加對應頁面即可。這里附上該示例項目的所有代碼地址,如果有需要,自行下載即可。
Vue3+vite搭建基礎架構代碼鏈接:https://download.csdn.net/download/weixin_48040732/88855369

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/696552.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/696552.shtml
英文地址,請注明出處:http://en.pswp.cn/news/696552.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

低代碼開發——企業轉型的萬金油

在數字化時代&#xff0c;企業面臨著日新月異的市場環境和激烈的競爭壓力。為了在這場變革中脫穎而出&#xff0c;企業需要不斷優化業務流程、提升創新能力&#xff0c;以及實現敏捷響應。在這個過程中&#xff0c;低代碼開發作為一種創新性的技術手段&#xff0c;正成為企業轉…

統信UOS_麒麟KYLINOS上監控網絡:探索Smokeping的強大功能

原文鏈接&#xff1a;統信UOS|麒麟KYLINOS上監控網絡&#xff1a;探索Smokeping的強大功能 在當今的網絡環境中&#xff0c;無論是個人用戶還是企業用戶&#xff0c;都非常重視網絡的穩定性和連通性。特別是在進行遠程工作、在線會議、云計算等活動時&#xff0c;網絡質量直接影…

程序員必備技能----刪庫跑路大總結

刪庫跑路大總結&#xff0c;各個都是大殺器&#xff0c;破壞性太大&#xff0c;輕易不要嘗試。 刪除linux根目錄&#xff0c;用戶目錄&#xff0c;其實還可以增加一個刪除/etc。刪除&#xff08;清除&#xff09;數據庫。刪除redis緩存和持久化文件。刪除mongodb庫。git push …

說一說Eclipse的項目類型和常用項目的區別

Eclipse在新建項目的時候有很多類型&#xff0c;包括Java project、Web project等等&#xff0c;如下&#xff1a; 那么這些項目類型有什么區別呢&#xff1f;我們在創建項目的時候應該如何選擇&#xff0c;了解清楚這一點還是非常重要的&#xff0c;但記住一個出發點&#xff…

2.22 day3、4 QT

完善對話框&#xff0c;點擊登錄對話框&#xff0c;如果賬號和密碼匹配&#xff0c;則彈出信息對話框&#xff0c;給出提示"登錄成功”&#xff0c;提供一個Ok按鈕&#xff0c;用戶點擊Ok后&#xff0c;關閉登錄界面&#xff0c;跳轉到其他界面 如果賬號和密碼不匹配&…

讀書筆記:《看電影學金融》

大空頭 禁止做空可以延緩資產價格下降的過程&#xff0c;但是人為保護的高股價最終還是不能持續的。做空引入的空頭買家&#xff0c;可以增加市場的流動性。住房價格指數期貨使房地產的投資屬性與住宅屬性分離&#xff0c;降低因炒房而空置的房屋&#xff0c;降低房價。收益互…

【論文解讀】Uncertainty Quantification of Collaborative Detection for Self-Driving

Uncertainty Quantification of Collaborative Detection for Self-Driving 摘要引言方法問題定義方法概覽Double-M 實驗結論 摘要 在聯網和自動駕駛汽車(CAVs)之間共享信息從根本上提高了自動駕駛協同目標檢測的性能。然而&#xff0c;由于實際挑戰&#xff0c;CAV 在目標檢測…

十九、圖像的放縮和插值

項目功能實現&#xff1a;對一張圖像進行放大和縮小操作 按照之前的博文結構來&#xff0c;這里就不在贅述了 一、頭文件 resizing.h #pragma once#include<opencv2/opencv.hpp>using namespace cv;class RESIZING { public:void resizing(Mat& image); };#pragma…

深度解析:用 Python 爬蟲逆向破解 dappradar 的請求頭加密 X-Api-Sk

大家好!我是愛摸魚的小鴻,關注我,收看每期的編程干貨。 逆向是爬蟲工程師進階必備技能,當我們遇到一個問題時可能會有多種解決途徑,而如何做出最高效的抉擇又需要經驗的積累。本期文章將以實戰的方式,帶你詳細地逆向分析 dappradar 網站請求頭加密字段 X-Api-SK 的構造邏…

解決Edge瀏覽器,微博無法查看大圖(Edge Image Viewer)

使用Edge瀏覽器瀏覽微博或其它帶校驗的圖片時&#xff0c;會導致無法查看。 主要原因為Edge自帶了一個Edge Image Viewer, 但是該圖片查看器無法查看帶校驗數據的圖片&#xff0c;所以導致查看時一片空白。 解決方法 地址欄輸入 edge://flags/搜索 Edge Image Viewer選擇 Disa…

HTML5 Canvas 限定文本區域大小,文字自動換行,自動縮放

<!DOCTYPE html> <html> <body><h1>HTML5 Canvas 限定文本展示范圍、自動計算縮放字體大小</h1><div id"tips">0</div> <div id"content">良田千頃不過一日三餐廣廈萬間只睡臥榻三尺良田千頃不過一日三餐…

【Day53】代碼隨想錄之動態規劃_買賣股票ⅠⅡ

文章目錄 動態規劃理論基礎動規五部曲&#xff1a;出現結果不正確&#xff1a; 1. 買賣股票的最佳時機2. 買賣股票的最佳時機Ⅱ 動態規劃理論基礎 動規五部曲&#xff1a; 確定dp數組 下標及dp[i] 的含義。遞推公式&#xff1a;比如斐波那契數列 dp[i] dp[i-1] dp[i-2]。初…

學習git分支

學習git分支 [網頁練習](Learn Git Branching) 基礎篇 git commit Git 倉庫中的提交記錄保存的是你的目錄下所有文件的快照&#xff0c;就像是把整個目錄復制&#xff0c;然后再粘貼一樣&#xff0c;但比復制粘貼優雅許多&#xff01;Git 希望提交記錄盡可能地輕量&#xf…

【GStreamer】GstElement詳解:GStreamer 中最重要的對象

1、什么是元素GstElement? 每個解碼器、編碼器、解復用器、視頻或音頻輸出實際上都是一個GstElement。GstElement可以視為一個黑盒子:例如,對于解碼器元素,輸入為已編碼數據,輸出為解碼后的數據,解碼過程已由GstElement封裝好。 2、都有哪些元素GstElement? 2.1 源點…

概率基礎——幾何分布

概率基礎——幾何分布 介紹 在統計學中&#xff0c;幾何分布是描述了在一系列獨立同分布的伯努利試驗中&#xff0c;第一次成功所需的試驗次數的概率分布。在連續拋擲硬幣的試驗中&#xff0c;每次拋擲結果為正面向上的概率為 p p p&#xff0c;反面向上的概率為 1 ? p 1-p …

ZCC3221 輸入高耐壓 1A 線性鋰電池充電管理芯片(替代CE3221)

特性 :W 內置支持高壓輸入電流可調節的線性充電器&#xff1a; ■ 最高輸入 24V 耐壓&#xff0c;可承受高達 30V 的浪涌電壓 ■ 恒流下最大充電電流可達 1A&#xff0c;支持外部電阻實時配置充電電流 ■ 兼容 5VUSB 功率源和 AC 適配器&#xff0c;并提供熱插拔保護 ■…

GB/T 43565-2023 中小學合成材料面層籃球場地檢測

合成材料面層是指鋪裝在瀝青混凝土或水泥混凝土等基礎層上的高分子合成材料層&#xff0c;按照使用功能分為田徑產地&#xff0c;球類場地和其他活動場地&#xff0c;按照材料形態分為現澆型面層、預制型面層和人造草面層。 GB/T 43565-2023中小學合成材料面層籃球場地檢測項目…

python 驗證RSA密鑰生成加解密簽名驗簽算法實現

目錄 一、RSA加密、解密、簽名、驗簽(驗證簽名)&RSA算法原理 1、RSA加密、簽名區別: 2、對簽名和驗簽過程詳細理解: 2.1 簽名過程: 2.2 驗簽過程: 二、1024bit RSA Key生成 三、python 實現Public_key加密,Private_key解密 四、python 實現Private_Key簽名,使…

RM電控講義【HAL庫篇】

這段代碼中do while的作用&#xff1a; 宏定義中的語句塊&#xff1a;do { ... } while (0) 允許你在宏定義中創建一個語句塊&#xff0c;從而可以包含多條語句。這在宏定義中特別有用&#xff0c;因為宏只是簡單的文本替換&#xff0c;不像函數那樣有作用域和返回類型。因此&…

JBOSS EPA 7.X 接入Oracle數據源

獲取Oracle JDBC驅動程序&#xff1a; 訪問Oracle官方網站&#xff0c;下載適用于您的操作系統和Oracle數據庫版本的JDBC驅動程序文件&#xff08;通常為一個JAR文件&#xff09;。您可能需要一個Oracle賬戶來訪問這些文件。將下載的JAR文件保存到您的計算機上。 將驅動程序文件…