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