先上效果圖
達成這樣的效果,其實根本在於,通過開啟el-menu的route屬性,點擊menu菜單后會進行路徑的跳轉,但是你跳轉的路由地址必須寫為根組件的子路由地址,否則會直接進行整個頁面的跳轉,失去了這樣的效果。
然后看一下el-tabs,這里放一下elementUI的官方文檔
子元素都是可以通過for遍歷循環出來的,所以到了這里,基本的思路應該就是通過watch來監聽$route,根據跳轉的路徑,在遍歷tabs的數組里面進行判斷,來動態的增加或者進行路由跳轉。
同時,這里面有多個組件之間的通信,我們可以使用vuex,在這里做個最基本的應用。
下面開始上代碼。
首先,我的頁面結構布局是 左側的菜單,右側的頭部和主體內容,直接使用element的Container布局容器
<el-container>
<el-aside width="200px">Aside</el-aside>
<el-container>
<el-header>Header</el-header>
<el-main>Main</el-main>
</el-container>
</el-container>
先來看看左側的Menu的代碼:
<template>
<div class="menu">
<el-menu
:default-active="String(activeNav)"
class="el-menu-vertical-demo"
@open="handleOpen"
@close="handleClose"
:collapse="getToggle"
:collapse-transition="false"
unique-opened
router
>
<div class="menu_logo">
<img :src="logoImg" alt="" />
<span v-show="!getToggle">{{ menuTitle }}</span>
</div>
<fragment v-for="item in menuList" :key="item.key">
<!-- 一級菜單 -->
<el-submenu
v-if="item.children && item.children.length"
:index="item.path"
:key="item.key"
>
<template slot="title"
><i class="icon iconfont" v-html="item.icon"></i
><span>{{ item.title }}</span></template
>
<!-- 二級菜單 -->
<template v-for="itemChild in item.children">
<el-submenu
v-if="
itemChild.children && itemChild.children.length
"
:index="itemChild.path"
:key="itemChild.key"
>
<template slot="title"
><i
class="icon iconfont"
v-html="itemChild.icon"
></i
><span>{{ itemChild.title }}</span></template
>
<!-- 三級菜單 -->
<el-menu-item
v-for="itemChild_Child in itemChild.children"
:index="itemChild_Child.path"
:key="itemChild_Child.key"
>
<i
class="icon iconfont"
v-html="itemChild_Child.icon"
></i
><span slot="title">{{
itemChild_Child.title
}}</span></el-menu-item
>
</el-submenu>
<el-menu-item
v-else
:index="itemChild.path"
:key="itemChild.key"
><i
class="icon iconfont"
v-html="itemChild.icon"
></i
><span slot="title">{{
itemChild.title
}}</span></el-menu-item
>
</template>
</el-submenu>
<el-menu-item v-else :index="item.path" :key="item.key"
><i class="icon iconfont" v-html="item.icon"></i
><span slot="title">{{ item.title }}</span></el-menu-item
>
</fragment>
</el-menu>
</div>
</template>
<script>
import sunLogo from '@/assets/image/sun-logo.png';
import { mapGetters, mapState } from 'vuex';
export default {
name: 'Menu',
props: {
menuTitle: {
type: String,
default: () => {
return '';
}
}
},
data() {
return {
logoImg: sunLogo
};
},
created() {},
mounted() {},
computed: {
activeNav() {
// 獲取當前激活的導航
return this.$route.path;
},
...mapGetters(['getToggle']),
...mapState(['menuList'])
},
methods: {
handleOpen(key, keyPath) {},
handleClose(key, keyPath) {}
}
};
</script>
<style lang="less" scoped>
@import '~@/assets/css/common.less';
.menu {
::v-deep .el-menu {
border-right: none;
background-color: transparent;
.menu_logo {
text-align: center;
padding-top: 1.5rem;
margin-bottom: 1.5rem;
img {
width: 2.83rem;
height: 2.83rem;
vertical-align: middle;
margin-right: 0.5rem;
}
span {
font-size: 1.5rem;
font-family: PingFangSC-Semibold, PingFang SC;
font-weight: 600;
color: #ffffff;
height: 2.08rem;
line-height: 2.08rem;
}
}
.el-submenu__title,
.el-menu-item {
font-size: 1.17rem;
height: 4rem;
line-height: 4rem;
color: @theme-white;
transition: none;
.icon {
margin-right: 0.33rem;
color: @theme-white;
}
}
.el-submenu__title i {
color: @theme-white;
}
.el-submenu .el-menu-item {
height: 40px;
line-height: 40px;
}
.el-menu-item.is-active {
background-color: @theme-white;
color: @theme-text-color-primary;
border-top-left-radius: 25px;
border-bottom-left-radius: 25px;
position: relative;
.icon {
color: @theme-text-color-primary !important;
}
&::before {
content: '';
width: 0;
height: 0;
border: 4px solid transparent;
border-bottom-color: @theme-white;
border-right-color: @theme-white;
position: absolute;
top: -8px;
right: 0;
}
&::after {
content: '';
width: 0;
height: 0;
border: 4px solid transparent;
border-top-color: @theme-white;
border-right-color: @theme-white;
position: absolute;
bottom: -8px;
right: 0;
}
}
.el-menu--inline {
padding-left: 1.27rem;
}
.el-menu-item:focus,
.el-menu-item:hover,
.el-submenu__title:hover {
background-color: @theme-white;
border-top-left-radius: 25px;
border-bottom-left-radius: 25px;
color: @theme-text-color-primary;
position: relative;
.icon {
color: @theme-text-color-primary !important;
}
i {
color: @theme-text-color-primary !important;
}
&::before {
content: '';
width: 0;
height: 0;
border: 4px solid transparent;
border-bottom-color: @theme-white;
border-right-color: @theme-white;
position: absolute;
top: -8px;
right: 0;
}
&::after {
content: '';
width: 0;
height: 0;
border: 4px solid transparent;
border-top-color: @theme-white;
border-right-color: @theme-white;
position: absolute;
bottom: -8px;
right: 0;
}
}
}
}
</style>
其中,fragment標簽是用來替代div使折疊菜單生效的,可以使用 npm i vue-fragment -D 下載包后,在main.js里面 import Fragment from 'vue-fragment' 來使用。
可以看到,是啟用了route屬性的,而 :default-active="String(activeNav)" 則是來根據跳轉的菜單來進行當前選中的激活。
這個 getToggle 則是用來收縮菜單的, 也是放在了 vuex 里面,可以先暫時不考慮這個。
這個 menuList 我則是放在了vuex里面,里面的數據為:
menuList: [
{
key: 0,
title: '首頁',
icon: '',
path: '/index'
},
{
key: 1,
title: '人員管理',
icon: '',
path: '/person_manage'
},
{
key: 2,
title: '事物管理',
icon: '',
path: 'drug_case_manage',
children: [
{
key: '2-1',
title: '物件管理',
icon: '',
path: '/case_manage'
},
{
key: '2-2',
title: '物件研判',
icon: '',
path: '/person_involved'
}
]
},
{
key: 3,
title: '一鍵搜',
icon: '',
path: '/search'
}
]
其中的icon是我本地使用的,這個可以不用管,接下來看下router.js里面的路由配置。
這是我router文件夾下面的路由配置
{
path: '/',
redirect: '/home'
},
{
path: '/home',
name: 'Home',
component: () => import(/* webpackChunkName: "home" */ '@/views/Home'),
redirect: '/guide',
children: [
{
// 引導頁
path: '/guide',
name: 'Guide',
meta: {
name: 'guide',
title: '歡迎使用',
comp: 'guide'
},
component: () => import('@/views/Guide/guide')
},
{
// 首頁
path: '/index',
name: 'Index',
meta: {
name: 'index',
title: '首頁',
comp: 'Index'
},
component: () => import('@/views/index/Index')
},
{
path: '/search',
name: 'Search',
component: () => import('@/views/OneClickSearch/GoSearch.vue')
},
{
// 人員管理
path: '/person_manage',
name: 'PersonManage',
meta: {
name: 'PersonManage',
title: '人員管理',
comp: 'manage'
},
component: () => import('@/views/PersonManage/manage')
},
{
path: '/case_manage',
name: 'CaseManage',
meta: {
name: 'CaseManage',
title: '物品管理',
comp: 'CaseManage'
},
component: () => import('@/views/CaseManage/manage')
},
{
path: '/person_involved',
name: 'involved',
meta: {
name: 'involved',
title: '物件研判',
comp: 'involved'
},
component: () => import('@/views/CaseManage/involved')
},
{
path: '/search',
name: '一鍵搜',
meta: {
name: '一鍵搜',
title: '一鍵搜',
comp: 'Search'
},
component: () => import('@/views/OneClickSearch/GoSearch')
}
]
}
其中meta是自定義的屬性值,可以在監聽 $route 時,監聽到定義的屬性值。
將這些頁面設為home根路徑下面的子路徑,並將home頁面重定向到歡迎頁,這樣打開時直接顯示的就是歡迎頁面。
接下來就是tabs組件了,主要的邏輯功能都在這一部分。
先上代碼:
<template>
<div>
<el-tabs
v-model="activeIndex"
type="card"
@tab-remove="removeTab"
@tab-click="clickTab"
>
<el-tab-pane
:key="item.name"
v-for="item in openTab"
:label="item.title"
:name="item.name"
:closable="item.closable"
>
<router-view></router-view>
</el-tab-pane>
</el-tabs>
</div>
</template>
其中的 activeIndex 就是tabs當前選中的,我們默認為歡迎頁面,然后通過循環 openTab 這個數組來進行動態的添加或刪除,而這個 closable 屬性則是可以手動設置為是否可以關掉tabs的選項卡,我們在里面放一個 router-view 來進行子頁面的跳轉。
data 里面的則是:
data() {
return {
activeIndex: '/guide',
openTab: [
{
title: '引導頁',
name: '/guide',
closable: false
}
]
};
}
這是初始數據,默認設置歡迎頁,然后重點就是通過watch來監聽 $route ,代碼如下:
watch: {
$route(to, form) {
var flag = false;
// 當前頁面菜單已打開
for (let i = 0; i < this.openTab.length; i++) {
if (to.path == this.openTab[i].name) {
this.activeIndex = this.openTab[i].name;
flag = true;
break;
}
}
// 打開新的頁面
if (!flag) {
let obj = {
title: to.meta.title,
name: to.path,
closable: true
};
this.activeIndex = to.path;
this.openTab.push(obj);
}
}
}
我們加個自定義的 flag 屬性來進行往 openTab 這個數組里面push數據的判斷依據,同時,通過methods里面的 clickTab 方法來進行tabs頁面的切換
clickTab(tab) {
this.activeIndex = tab.paneName;
this.$router.push({ path: this.activeIndex });
}
接下來是 removeTab 方法,關閉打開的tabs標簽頁
removeTab(target) {
// 刪除的是當前選中的頁面
if (this.activeIndex === target) {
this.openTab.forEach((item, index) => {
if (item.name == target) {
let nextTab = item[index + 1] || item[index - 1];
if (nextTab) {
this.activeIndex = nextTab.name;
}
}
});
}
var i = 0;
this.openTab.forEach((item, index) => {
if (item.name == target) {
// eslint-disable-next-line no-return-assign
return (i = index);
}
});
this.openTab.splice(i, 1);
// 更新路由
this.$router.push({ path: this.openTab[this.openTab.length - 1].name });
}
到這里,基本的頁面跳轉以及切換就差不多了,但是我們還需要再補充一下,當我們打開幾個頁面時,進行刷新,就會發現重置了,這時需要在 mounted 里面添加如下代碼:
mounted() {
// 監聽頁面加載前
window.addEventListener('beforeunload', e => {
sessionStorage.setItem(
'openTab',
JSON.stringify({
openTab: this.openTab.filter(
item => item.name != '/guide'
),
openTabPath: this.openTab.filter(
item => item.name !== '/guide'
),
currActiveTabs: this.activeIndex
})
);
});
}
同時,在created里面進行讀取本地 sessionStorage 來進行判斷:
created() {
const sessionTab = JSON.parse(window.sessionStorage.getItem('openTab')) || '';
if (sessionTab) {
if (sessionTab.openTab.length != 0 && sessionTab.openTabPath.length != 0) {
for (let i = 0; i < sessionTab.openTab.length; i++) {
this.openTab.push({
title: sessionTab.openTab[i].title,
name: sessionTab.openTab[i].name,
closable: true
});
}
this.activeIndex = sessionTab.currActiveTabs;
this.$router.push({ path: this.activeIndex });
}
}
}
這樣,在打開幾個頁面后,刷新頁面之前,保留已打開的頁面,刷新之后,還是顯示之前打開過的頁面,至此,基本的流程已經結束。
我把這些功能都單獨拿出去了,所以就貼上了基本的代碼,主要的邏輯流程就是這些,其中可能會有些問題,我們還是需要自己來一一解決。
框架完整代碼可以在碼雲上面去看
https://gitee.com/chenkai777/vue_demo