說明
這里記錄下自己在Vue3+vite的項(xiàng)目使用less來寫樣式以及使用vite-plugin-vue-setup-extend直接定義組件name,不使用ts語法,方便以后直接使用。這里承接自己的博客Vue3+vite搭建基礎(chǔ)架構(gòu)(10)— 使用less和vite-plugin-vue-setup-extend這篇博客,在該博客項(xiàng)目的基礎(chǔ)上增加菜單欄功能和Tab頁功能實(shí)現(xiàn)。
刪除項(xiàng)目中不需要的文件
刪除掉src文件夾下的style.css和compoments文件夾下的HelloWorld.vue以及assets文件夾下的vue.svg圖片,這三個(gè)都是項(xiàng)目創(chuàng)建完成后自帶的,因?yàn)橛貌坏剿詣h除掉。
刪除views下面home文件夾下的index.vue代碼,因?yàn)檫@個(gè)里面代碼是以前用來測(cè)試依賴的代碼,所以把代碼清空,保留為一個(gè)空文件。
代碼如下:
<!--home首頁代碼-->
<template>
<div>我是首頁</div>
</template>
<script setup name="home">
</script>
<style lang="less" scoped>
</style>
在src下面新建styles文件夾用來存放全局樣式。common.less用來存放html標(biāo)簽樣式。element-plus.less用來存放ElementPlus組件里面的標(biāo)簽樣式。然后在main.js里面引入2個(gè)樣式文件,讓它們?nèi)稚А?br>
common.less里面代碼如下:
//body全局樣式設(shè)計(jì)
body{
font-size: 14px;//字體大小
margin: 0px;
padding: 0px;
}
//a標(biāo)簽全局樣式
a {
color: #1B68B6;//字體顏色
text-decoration: none;//去掉下劃線
cursor: pointer;//鼠標(biāo)放上去手型
//鼠標(biāo)放上去顏色
/*&:hover {
color: #1B68B6;
}
//鼠標(biāo)點(diǎn)擊時(shí)顏色
&:active{
color: #1B68B6;
}
//鼠標(biāo)點(diǎn)擊后獲取焦點(diǎn)樣式
&:focus {
color: #1B68B6;
}*/
}
element-plus.less目前代碼為空。
userStore全局屬性代碼
在store文件夾下的modules文件夾下的userStore.js文件修改代碼為如下:
//使用pinia來管理全局狀態(tài)
import { defineStore } from "pinia"
/*defineStore 是需要傳參數(shù)的,其中第一個(gè)參數(shù)是id,就是一個(gè)唯一的值,
簡(jiǎn)單點(diǎn)說就可以理解成是一個(gè)命名空間.
第二個(gè)參數(shù)就是一個(gè)對(duì)象,里面有三個(gè)模塊需要處理,第一個(gè)是 state,
第二個(gè)是 getters,
第三個(gè)是 actions。
*/
//聲明了一個(gè)useUserStore方法
const useUserStore = defineStore('user', {
//準(zhǔn)備state——用于存儲(chǔ)數(shù)據(jù)
state: () => {
return {
//當(dāng)前激活菜單的index
activeMenu: '',
//綁定值,選中選項(xiàng)卡的name
editableTabsValue: '',
//tab標(biāo)簽選項(xiàng)卡內(nèi)容
editableTabs: [],
//tab頁路由地址及參數(shù)
tabRouterList: []
}
},
//使用persist插件對(duì)state里面屬性進(jìn)行緩存
persist: {
enabled: true,//開啟緩存,默認(rèn)緩存所有state里面的屬性,默認(rèn)key為defineStore里面的id值,這里id值為user,所以默認(rèn)key為user
//自定義持久化參數(shù),指定以下state里面的屬性進(jìn)行緩存,未指定的不進(jìn)行緩存
strategies: [
{
// 自定義key
key: 'activeMenu',
// 自定義存儲(chǔ)方式,默認(rèn)sessionStorage
storage: sessionStorage,
// 指定要持久化的數(shù)據(jù)
paths: ['activeMenu']
},
{
key: 'editableTabsValue',
storage: sessionStorage,
paths: ['editableTabsValue']
},
{
key: 'editableTabs',
storage: sessionStorage,
paths: ['editableTabs']
},
{
key: 'tabRouterList',
storage: sessionStorage,
paths: ['tabRouterList']
}
]
},
getters: {
},
//準(zhǔn)備actions——用于響應(yīng)組件中的動(dòng)作和用于操作數(shù)據(jù)(state),pinia中只有state、getter、action,拋棄了Vuex中的Mutation
actions: {
/**
* 修改state中數(shù)據(jù)的方法
* @param name 需要修改的屬性名
* @param value 修改值
*/
updateState([name, value]) {
this[name] = value
},
//動(dòng)態(tài)添加tab標(biāo)簽,item為當(dāng)前點(diǎn)擊的菜單項(xiàng)
addTab(item) {
const newTab = {
title: item.meta.title,
name: item.url,
iconClass: item.meta.icon,
}
// 判斷當(dāng)前editableTabs中是否存在該tab標(biāo)簽
if (this.editableTabs.findIndex(item => item.title === newTab.title) === -1) {
this.editableTabs.push(newTab);
this.editableTabsValue = newTab.name;
this.activeMenu = newTab.name;
}
},
//移除tab標(biāo)簽
removeTab(targetName) {
let tabs = this.editableTabs
let activeName = this.editableTabsValue
if (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 = activeName
this.editableTabsValue = activeName
this.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>
<!--側(cè)邊欄,height: 100vh;設(shè)置高度為視口高度-->
<el-aside style="width: 200px;height: 100vh;">
<SliderBar></SliderBar>
</el-aside>
<el-container>
<!--頭部-->
<el-header>
<Navbar></Navbar>
</el-header>
<!--主體內(nèi)容-->
<el-main>
<!--主體內(nèi)容-->
<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代碼如下:
<!--通用布局側(cè)邊欄內(nèi)容-->
<template>
<el-row>
<el-col>
<div class="header">
<!--系統(tǒng)logo,隨便找一個(gè)圖片示例用-->
<SvgIcon iconClass="systemManagement" />
<span class="icon-text">后臺(tái)管理系統(tǒng)</span>
</div>
<!--router表示為啟動(dòng)路由模式,路由模式下index為你的頁面路由路徑-->
<!--通過設(shè)置default-active屬性點(diǎn)擊tab頁時(shí),自動(dòng)選中左邊菜單欄選項(xiàng)-->
<div>
<el-menu
active-text-color="#1B68B6"
background-color="#FFFFFF"
:default-active="store.activeMenu"
text-color="#333333"
@select="handleSelect"
:router="true"
class="menu-items"
>
<!--引用菜單樹組件將路由的菜單欄循環(huán)顯示出來-->
<MenuTree :menuList="menuTreeList"/>
</el-menu>
</div>
</el-col>
</el-row>
</template>
<script setup name="SliderBar">
//引入菜單列表組件
import MenuTree from "./menuTree.vue"
//引入全局狀態(tài)里面的關(guān)于菜單欄列表數(shù)據(jù)和相關(guān)方法
import useUserStore from "@/store/modules/userStore"
//使用useUserStore里面的屬性
const store = useUserStore()
//菜單激活回調(diào)函數(shù),當(dāng)tab頁已經(jīng)打開的情況下,再次點(diǎn)擊菜單項(xiàng),對(duì)應(yīng)的tab頁也跟著切換
function handleSelect(key) {
store.updateState(["editableTabsValue", key])
store.updateState(["activeMenu", key])
}
//菜單樹列表,這里模擬后端接口請(qǐng)求返回的數(shù)據(jù),示例數(shù)據(jù)如下:
const menuTreeList = [
{
id: 1,
url: "/test-management1",//該url要與路由文件里面的path值要一致
level: 1,//菜單等級(jí)
meta: { title: "測(cè)試管理1", icon: "systemManagement" },
children: [] //子菜單
},
{
id: 2,
url: "/system-management",
level: 1,
meta: { title: "系統(tǒng)管理", 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: "權(quán)限管理", icon: "permissionManagement" },
children: []
},
{
id: 6,
url: "/password-management",
level: 2,
meta: { title: "密碼管理", icon: "systemManagement" },
children: []
},
],
},
{
id: 7,
url: "/test-management2",
level: 1,
meta: { title: "測(cè)試管理2", icon: "systemManagement" },
children: []
},
{
id: 8,
url: "/test-management3",
level: 1,
meta: { title: "測(cè)試管理3", icon: "systemManagement" },
children: [
{
id: 9,
url: "/test-management4",
level: 2,
meta: { title: "測(cè)試管理4", icon: "systemManagement" },
children: [
{
id: 10,
url: "/test-management5",
level: 3,
meta: { title: "測(cè)試管理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;//字體顏色
}
//子菜單懸浮樣式,子菜單的圖標(biāo)顏色需要修改svg圖片里面的fill值,由fill="#333333"改為fill="currentColor"后,圖標(biāo)懸浮樣式顏色才會(huì)一起變化
: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%; //設(shè)置高度為父容器高度
border-right: none;//去掉菜單欄右邊框
}
</style>
views文件下layout文件夾下的components文件夾下sliderBar文件夾下的menuTree.vue代碼如下:
<!--菜單樹列表-->
<template>
<!--將菜單列表循環(huán)出來-->
<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>
<!--調(diào)用自身循環(huán)顯示子菜單-->
<MenuTree :menuList="item.children" />
</el-sub-menu>
<!--菜單節(jié)點(diǎn)-->
<el-menu-item
v-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">
//引入全局狀態(tài)里面的關(guān)于菜單欄列表數(shù)據(jù)和相關(guān)方法
import useUserStore from "@/store/modules/userStore"
const store = useUserStore()
//定義屬性給組件接收
const props = defineProps({
//菜單欄屬性
menuList: {
type: Array,//類型為數(shù)組
//默認(rèn)值為空數(shù)組
default() {
return []
}
}
})
</script>
<style scoped>
</style>
在views文件夾下新建菜單樹列表里面對(duì)應(yīng)的頁面文件,每個(gè)頁面文件加上如下一句代碼,用來表示不同頁面內(nèi)容。
src文件下router文件夾下的index.js代碼如下:
//引入router路由做頁面請(qǐng)求
import { createRouter,createWebHashHistory } from 'vue-router'
/* Layout通用組件 */
import Layout from '../views/layout/layout'
const routes = [
{path: '/404', component: () => import('@/views/404')},
//必須要把組件放在Layout的children里面,才能在側(cè)邊欄的右側(cè)顯示頁面內(nèi)容,否則不加載通用架構(gòu)直接在當(dāng)前空白頁面渲染內(nèi)容,如: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: '測(cè)試管理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: '權(quán)限管理', 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: '測(cè)試管理2', icon: 'systemManagement'}
},
{
path: 'test-management5',
name: 'test-management5',
component: () => import('@/views/test-management5/index'),
meta: {title: '測(cè)試管理5', icon: 'systemManagement'}
}
]
}
]
// 3. 創(chuàng)建路由實(shí)例并傳遞 `routes` 配置
const router = createRouter({
// 4. 內(nèi)部提供了 history 模式的實(shí)現(xiàn)。為了簡(jiǎn)單起見,我們?cè)谶@里使用 hash 模式。
history: createWebHashHistory(),
routes, // `routes: routes` 的縮寫
})
//路由前置守衛(wèi)
router.beforeEach((to, from, next) => {
//路由發(fā)生變化修改頁面title
if (to.meta.title) {
document.title = to.meta.title
}
next()
})
//導(dǎo)出路由
export default router
啟動(dòng)項(xiàng)目后,瀏覽器結(jié)果如下:
點(diǎn)擊不同的菜單欄選項(xiàng),頁面內(nèi)容也會(huì)相應(yīng)的變化,這種算是單頁面,activeMenu也會(huì)相應(yīng)的變化,之所以要把這個(gè)寫到session storage里面,是為了防止頁面刷新時(shí),點(diǎn)擊的高亮菜單選項(xiàng)消失問題。
Tab頁代碼
views文件下layout文件夾下的components文件夾下的navbar.vue代碼如下:
<!--通用布局頭部?jī)?nèi)容-->
<template>
<el-row>
<el-col :span="20">
<el-tabs
v-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">
<!-- 右鍵菜單開始:自定義標(biāo)簽頁顯示名稱,保證每個(gè)標(biāo)簽頁都能實(shí)現(xiàn)右鍵菜單 -->
<template #label>
<el-dropdown
trigger="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>關(guān)閉當(dāng)前標(biāo)簽頁
</el-dropdown-item>
<el-dropdown-item @click="closeLeft(item.name)" v-if="show(item.name, 'left')">
<el-icon>
<DArrowLeft />
</el-icon>關(guān)閉左側(cè)標(biāo)簽頁
</el-dropdown-item>
<el-dropdown-item @click="closeRight(item.name)" v-if="show(item.name, 'right')">
<el-icon>
<DArrowRight />
</el-icon>關(guān)閉右側(cè)標(biāo)簽頁
</el-dropdown-item>
<el-dropdown-item @click="closeOther(item.name)" v-if="store.editableTabs.length > 1">
<el-icon>
<Operation />
</el-icon>關(guān)閉其他標(biāo)簽頁
</el-dropdown-item>
<el-dropdown-item @click="closeAll()">
<el-icon>
<Minus />
</el-icon>關(guān)閉全部標(biāo)簽頁
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<!-- 右鍵菜單結(jié)束 -->
</el-tab-pane>
</el-tabs>
</el-col>
<el-col :span="4">
<div class="header">
<!-- 用戶信息 -->
<!--trigger="click"通過點(diǎn)擊下標(biāo)觸發(fā)-->
<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">
//引入全局狀態(tài)里面的關(guān)于菜單欄列表數(shù)據(jù)和相關(guān)方法
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'
//接手全局狀態(tài)里面的屬性和方法
const store = useUserStore();
//使用路由相當(dāng)于$router,系統(tǒng)路由方法
const router = useRouter()
//使用路由相當(dāng)于$route,點(diǎn)擊菜單欄時(shí)當(dāng)前點(diǎn)擊的路由頁面里面的屬性值
const route = useRoute()
//用戶名
const username = '超級(jí)管理員'
//觸發(fā)右鍵菜單標(biāo)簽頁為第一個(gè)時(shí),不展示【關(guān)閉左側(cè)標(biāo)簽頁】
//觸發(fā)右鍵菜單標(biāo)簽頁為最后一個(gè)時(shí),不展示【關(guān)閉右側(cè)標(biāo)簽頁】
const show = (name, type) => {
const index = store.editableTabs.findIndex((item) => name === item.name)
return type === 'left' ? index !== 0 : index !== store.editableTabs.length - 1
}
//右鍵菜單ref
const dropdownRef = ref()
//在觸發(fā)右鍵菜單后,關(guān)閉其他tab頁上的右鍵菜單
const handleChange = (visible, name) => {
if (!visible) return
dropdownRef.value.forEach((item) => {
if (item.id === name) return
item.handleClose()
})
}
//關(guān)閉當(dāng)前tab頁
const closeCurrent = (targetName) => {
handleTabRemove(targetName)
}
//關(guān)閉左側(cè)tab頁
const closeLeft = (targetName) => {
//查找當(dāng)前點(diǎn)擊的tab頁所在位置
let currentIndex = store.editableTabs.findIndex(
(item) => item.name === targetName
)
//查找當(dāng)前激活標(biāo)簽頁index
const activeIndex = store.editableTabs.findIndex((item) => item.name === store.editableTabsValue)
//關(guān)閉左側(cè)tab頁
store.editableTabs.splice(0, currentIndex)
//刪除對(duì)應(yīng)的左側(cè)歷史路由
store.tabRouterList.splice(0, currentIndex)
//如果當(dāng)前關(guān)閉點(diǎn)擊的tab頁包含激活的tab頁,則將激活tab頁重置為當(dāng)前點(diǎn)擊的tab
if (activeIndex < currentIndex) {
//將當(dāng)前激活的tab頁改為當(dāng)前點(diǎn)擊的
store.updateState(['editableTabsValue', targetName])
//將激活菜單改為當(dāng)前點(diǎn)擊的
store.updateState(['activeMenu', targetName])
//路由跳轉(zhuǎn)到當(dāng)前點(diǎn)擊的tab頁
//查詢當(dāng)前點(diǎn)擊的tab頁緩存路由參數(shù)
let result = store.tabRouterList.find(item => item.path === targetName);
//路由跳轉(zhuǎn)且?guī)蠈?duì)應(yīng)tab頁的參數(shù)
router.push({ path: targetName, query: result.query })
}
}
//關(guān)閉右側(cè)tab頁
const closeRight = (targetName) => {
//查找當(dāng)前點(diǎn)擊的tab頁所在位置
let currentIndex = store.editableTabs.findIndex(
(item) => item.name === targetName
)
//查找當(dāng)前激活標(biāo)簽頁index
const activeIndex = store.editableTabs.findIndex((item) => item.name === store.editableTabsValue)
//關(guān)閉右側(cè)tab頁
store.editableTabs.splice(currentIndex + 1)
//刪除對(duì)應(yīng)的右側(cè)歷史路由
store.tabRouterList.splice(currentIndex + 1)
//如果當(dāng)前關(guān)閉點(diǎn)擊的tab頁包含激活的tab頁,則將激活tab頁重置為當(dāng)前點(diǎn)擊的tab
if (activeIndex > currentIndex) {
//將當(dāng)前激活的tab頁改為當(dāng)前點(diǎn)擊的
store.updateState(['editableTabsValue', targetName])
//將激活菜單改為當(dāng)前點(diǎn)擊的
store.updateState(['activeMenu', targetName])
//路由跳轉(zhuǎn)到當(dāng)前點(diǎn)擊的tab頁
//查詢當(dāng)前點(diǎn)擊的tab頁緩存路由參數(shù)
let result = store.tabRouterList.find(item => item.path === targetName);
//路由跳轉(zhuǎn)且?guī)蠈?duì)應(yīng)tab頁的參數(shù)
router.push({ path: targetName, query: result.query })
}
}
//關(guān)閉其他tab頁
const closeOther = (targetName) => {
//查找當(dāng)前點(diǎn)擊的tab頁所在位置
let currentIndex = store.editableTabs.findIndex(
(item) => item.name === targetName
)
//關(guān)閉其他標(biāo)簽頁
store.editableTabs = [store.editableTabs[currentIndex]]
//刪除除當(dāng)前點(diǎn)擊外的歷史路由
store.tabRouterList = [store.tabRouterList[currentIndex]]
//如果當(dāng)前點(diǎn)擊的不等于當(dāng)前激活的
if (targetName !== store.editableTabsValue) {
//將當(dāng)前激活的tab頁改為當(dāng)前點(diǎn)擊的
store.updateState(['editableTabsValue', targetName])
//將激活菜單改為當(dāng)前點(diǎn)擊的
store.updateState(['activeMenu', targetName])
//路由跳轉(zhuǎn)到當(dāng)前點(diǎn)擊的tab頁
//查詢當(dāng)前點(diǎn)擊的tab頁緩存路由參數(shù)
let result = store.tabRouterList.find(item => item.path === targetName);
//路由跳轉(zhuǎn)且?guī)蠈?duì)應(yīng)tab頁的參數(shù)
router.push({ path: targetName, query: result.query })
}
}
//關(guān)閉全部tab頁
const closeAll = () => {
//清空tabs數(shù)組
store.editableTabs.length = 0
//清空歷史路由
store.tabRouterList.length = 0
//當(dāng)前選中tab頁設(shè)置為空
store.updateState(['editableTabsValue', ''])
//當(dāng)前激活菜單設(shè)置為空
store.updateState(['activeMenu', ''])
//跳轉(zhuǎn)到首頁
router.push('home')
}
//處理tab標(biāo)簽x按鈕的移除
function handleTabRemove(targetName) {
//如果editableTabs列表不為空數(shù)組
if (store.editableTabs.length > 0) {
//如果當(dāng)前所在的tab頁路由地址與移除的tab頁名一樣,則移到前面一個(gè)tab頁且路由跳轉(zhuǎn)
if (route.path === targetName) {
let tabs = store.editableTabs
tabs.forEach((tab, index) => {
if (tab.name === targetName) {
//獲取當(dāng)前tab的后一個(gè)或者前一個(gè)
let nextTab = tabs[index + 1] || tabs[index - 1]
//如果有值就移到它上面,沒有就是最后一個(gè)跳轉(zhuǎn)到首頁
if (nextTab) {
//根據(jù)name屬性進(jìn)行查詢當(dāng)前tab頁的緩存路由參數(shù)
let result = store.tabRouterList.find(item => item.path === nextTab.name);
//路由跳轉(zhuǎn)且?guī)蠈?duì)應(yīng)tab頁的參數(shù)
router.push({ path: nextTab.name, query: result.query })
} else {
// 更改tab標(biāo)簽綁定值,選中選項(xiàng)卡的name
store.updateState(['editableTabsValue', ''])
// 更改當(dāng)前激活的菜單
store.updateState(['activeMenu', ''])
//當(dāng)刪除的是最后一個(gè)tab頁的時(shí)候,跳轉(zhuǎn)到首頁
router.push('home')
}
}
})
//從editableTabs中移除當(dāng)前tab標(biāo)簽
store.removeTab(targetName)
} else {
//從editableTabs中移除當(dāng)前tab標(biāo)簽
store.removeTab(targetName)
}
}
}
//tab標(biāo)簽被選中時(shí)觸發(fā)的事件
function handleTabClick(tab) {
store.updateState(['activeMenu', tab.props.name])
store.updateState(['editableTabsValue', tab.props.name])
// 判斷當(dāng)前url地址和即將跳轉(zhuǎn)的是否一致,不一致進(jìn)行跳轉(zhuǎn),防止跳轉(zhuǎn)多次
if (tab.props.name !== route.path) {
// 根據(jù)name屬性進(jìn)行查詢
let result = store.tabRouterList.find(item => item.path === tab.props.name);
//路由跳轉(zhuǎn)且?guī)蠈?duì)應(yīng)tab頁的參數(shù)
router.push({ path: tab.props.name, query: result.query })
}
}
//退出登錄方法
function logout() {
}
</script>
<style lang="less" scoped>
//設(shè)置高度
: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; //不設(shè)置這個(gè),el-down點(diǎn)擊出不來,被tab標(biāo)簽頁長(zhǎng)度擋住了
display: flex;
align-items: center; //垂直居中
}
//tab標(biāo)簽頁里面字體設(shè)置
.label {
color: #1B68B6 !important; //激活標(biāo)簽頁高亮
font-size: 16px;
}
:deep(.el-tabs__item) {
&:hover {
span {
color: #1B68B6 !important; //鼠標(biāo)移到標(biāo)簽頁高亮
}
}
}
</style>
views文件下layout文件夾下的components文件夾下的appMain.vue代碼如下:
<!--通用布局頁面主體內(nèi)容-->
<template>
<!-- 路由視圖對(duì)象 -->
<router-view v-slot="{ Component }">
<!--include主要解決關(guān)閉tab頁時(shí),同時(shí)銷毀該組件,防止再次重新打開時(shí)數(shù)據(jù)還在的情況。注意:組件name名必須和路由name名一致,否則會(huì)導(dǎo)致組件不緩存的情況。-->
<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取出來作為一個(gè)數(shù)組
const tabsNames = computed(() => store.tabRouterList.map((item) => item.name))
</script>
<style scoped>
</style>
這里之所以不把a(bǔ)ppMain.vue的路由視圖對(duì)象寫到navbar.vue里面的el-tabs里面,是因?yàn)閷懙絜l-tabs里面后,當(dāng)你打開多個(gè)tab頁的時(shí)候,當(dāng)你向后端發(fā)送請(qǐng)求時(shí),打開了多少個(gè)tab頁,就會(huì)重復(fù)發(fā)送多少個(gè)后端接口請(qǐng)求,所以這里將它拆開寫,el-tabs里面內(nèi)容實(shí)際是個(gè)空的。
通過element-plus里面的樣式讓它內(nèi)邊距為0,看著內(nèi)容像是放在了el-tabs里面。如下:
//讓el-tabs__content不顯示內(nèi)容
.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;
}
//設(shè)置內(nèi)容背景顏色
.el-main{
background: #F2F6FB;
min-width: 1000px;
}
//讓el-tabs__content不顯示內(nèi)容
.el-tabs--border-card>.el-tabs__content{
padding: 0px;
}
//Tabs標(biāo)簽頁全局樣式
.el-tabs__item {
height: 64px;
font-size: 16px;
background: #ffffff; //未選中Tabs頁背景顏色
}
//Tabs標(biāo)簽頁選中樣式
.el-tabs--border-card > .el-tabs__header .el-tabs__item.is-active {
color: #1b68b6; //選中Tabs標(biāo)簽頁后字體顏色
background-color: #e3edf7; //選中Tabs標(biāo)簽頁后背景顏色
}
瀏覽器結(jié)果如下,點(diǎn)擊多個(gè)菜單欄打開多個(gè)tab頁結(jié)果:
點(diǎn)擊tab頁的同時(shí),菜單欄也來到對(duì)應(yīng)的選項(xiàng),如下:
鼠標(biāo)放到tab頁上面的字體上面,然后鼠標(biāo)右鍵菜單,會(huì)顯示對(duì)應(yīng)的關(guān)閉菜單下拉選項(xiàng),如下:
到這里tab頁相關(guān)的代碼就寫完了,但是有幾個(gè)問題需要注意,第一個(gè)問題就是你在瀏覽器上面直接輸入路由地址,它不會(huì)打開或者跳轉(zhuǎn)到對(duì)應(yīng)tab頁上面。第2個(gè)問題就是在實(shí)際開發(fā)中,如果頁面跳轉(zhuǎn)需要攜帶參數(shù)過去,當(dāng)你切換點(diǎn)擊tab頁的時(shí)候,參數(shù)會(huì)丟失問題。對(duì)于參數(shù)會(huì)丟失問題,所以才在userStore.js里面寫了一個(gè)tabRouterList屬性來存儲(chǔ)歷史路由及參數(shù)。
解決瀏覽器輸入地址時(shí)不會(huì)打開tab頁問題和切換tab頁時(shí)參數(shù)丟失問題
在router文件夾下面的index.js文件,將代碼修改為如下:
//引入router路由做頁面請(qǐng)求
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里面,才能在側(cè)邊欄的右側(cè)顯示頁面內(nèi)容,否則不加載通用架構(gòu)直接在當(dāng)前空白頁面渲染內(nèi)容,如: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: '測(cè)試管理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: '權(quán)限管理', 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: '測(cè)試管理2', icon: 'systemManagement'}
},
{
path: 'test-management5',
name: 'test-management5',
component: () => import('@/views/test-management5/index'),
meta: {title: '測(cè)試管理5', icon: 'systemManagement'}
}
]
}
]
// 3. 創(chuàng)建路由實(shí)例并傳遞 `routes` 配置
const router = createRouter({
// 4. 內(nèi)部提供了 history 模式的實(shí)現(xiàn)。為了簡(jiǎn)單起見,我們?cè)谶@里使用 hash 模式。
history: createWebHashHistory(),
routes, // `routes: routes` 的縮寫
})
//黑名單,在該黑名單里面的路由將不會(huì)動(dòng)態(tài)加載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) {
//判斷當(dāng)前路由的標(biāo)題是否已經(jīng)在editableTabs里,如果不在則動(dòng)態(tài)添加tab頁
const indexInEditableTabs = store.editableTabs.findIndex(
(item) => item.title === to.meta.title
)
//當(dāng)前路由的標(biāo)題已經(jīng)在editableTabs里
if (indexInEditableTabs !== -1) {
//判斷tabRouterList是否已經(jīng)存在相同的路由
const indexInTabRouterList = store.tabRouterList.findIndex(
(item) => item.name === to.name
)
//當(dāng)前路由的name已經(jīng)在tabRouterList里面
if (indexInTabRouterList !== -1) {
//根據(jù)當(dāng)前路由名稱找到對(duì)應(yīng)的歷史路由
let result = store.tabRouterList.find(item => item.name === to.name)
//在name相同但是路由的query參數(shù)不一樣,則替換為這個(gè)最新的(將對(duì)象轉(zhuǎn)為string字符串比較,即可判斷2個(gè)對(duì)象屬性與值是否完全一樣)
let queryMatched=JSON.stringify(result.query) === JSON.stringify(to.query)
//如果為false,則替換當(dāng)前路由參數(shù)
if (!queryMatched) {
//若存在,則從原始數(shù)組中移除該對(duì)象
store.tabRouterList = store.tabRouterList.filter(
(item) => item.name !== to.name
)
//重新添加這個(gè)新路由
store.tabRouterList.push(handleToParams(to))
}
} else {
//點(diǎn)擊菜單欄時(shí),如果不在則添加該路由
store.tabRouterList.push(handleToParams(to))
}
} else {
//判斷該路由是否在黑名單里面,不在則動(dòng)態(tài)添加tab頁
if (!blackList.includes(to.path)) {
//如果不在editableTabs里面,那么就在editableTabs里面添加這個(gè)tab頁
store.editableTabs.push({
title: to.meta.title,
name: to.path,
iconClass: to.meta.icon,
})
//點(diǎn)擊頁面中的某個(gè)按鈕進(jìn)行頁面跳轉(zhuǎn)的時(shí)候,如果不在則添加該路由里面部分字段
store.tabRouterList.push(handleToParams(to))
}
}
}
//路由前置守衛(wèi)
router.beforeEach((to, from, next) => {
//如果沒有匹配到路由,則跳轉(zhuǎn)到404頁面
if (to.matched.length === 0) {
next("/404")
} else {
//路由發(fā)生變化修改頁面title
document.title = to.meta.title
//使用pinia里面的全局狀態(tài)屬性
const store = useUserStore()
//更改tab標(biāo)簽綁定值,選中選項(xiàng)卡的name
store.updateState(["editableTabsValue", to.path])
//更改當(dāng)前激活的菜單
store.updateState(["activeMenu", to.path])
//動(dòng)態(tài)添加tab頁及tab頁切換時(shí)參數(shù)也跟著切換
handleRouteInEditableTabs(to,store)
next()
}
})
//導(dǎo)出路由
export default router
瀏覽器結(jié)果如下,在瀏覽器直接輸入相應(yīng)路由,會(huì)自動(dòng)跳轉(zhuǎn)到對(duì)應(yīng)的tab,如下:
輸入不存在的路由會(huì)直接跳轉(zhuǎn)到404頁面,如下:
從用戶管理攜帶參數(shù)跳轉(zhuǎn)到角色管理,測(cè)試如下:
views文件夾下面的system-management文件夾下的user-management.vue代碼如下:
<template>
<div>
用戶管理頁面
<el-button type="primary"
@click="toRoleManagement(1)">
跳轉(zhuǎn)到角色管理
</el-button>
</div>
</template>
<script setup name="user-management">
import { useRouter } from "vue-router"
//使用router跳轉(zhuǎn)路由
const router=useRouter()
const toRoleManagement = (id) => {
//跳轉(zhuǎn)到郵單詳情里面
router.push({ path: 'role-management', query: { id: id } })
}
</script>
<style scoped>
</style>
views文件夾下面的system-management文件夾下的role-management.vue代碼如下:文章來源:http://www.zghlxwxcb.cn/news/detail-831541.html
<template>
<div>角色管理頁面</div>
</template>
<script setup name="role-management">
import {onActivated} from 'vue'
import { useRoute } from "vue-router"
//使用route接受路由傳過來的參數(shù)
const route=useRoute()
//每次頁面初始化時(shí)或者在郵件管理頁面點(diǎn)擊郵件詳情時(shí)執(zhí)行該方法
onActivated(()=>{
const id=route.query.id
console.info("接受到的id=",id)
})
</script>
<style scoped>
</style>
瀏覽器結(jié)果如下:
點(diǎn)擊跳轉(zhuǎn)到角色管理按鈕結(jié)果如下:
然后再次點(diǎn)擊用戶管理tab頁,如下:
再次點(diǎn)擊角色管理tab頁,發(fā)現(xiàn)參數(shù)依舊在沒有消失。問題解決。
到這里Vue3+vite搭建基礎(chǔ)架構(gòu)的所有代碼就結(jié)束了。只需要根據(jù)實(shí)際需求添加對(duì)應(yīng)頁面即可。這里附上該示例項(xiàng)目的所有代碼地址,如果有需要,自行下載即可。
Vue3+vite搭建基礎(chǔ)架構(gòu)代碼鏈接:https://download.csdn.net/download/weixin_48040732/88855369文章來源地址http://www.zghlxwxcb.cn/news/detail-831541.html
到了這里,關(guān)于Vue3+vite搭建基礎(chǔ)架構(gòu)(11)--- 菜單欄功能和Tab頁功能實(shí)現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!