1、创建layout文件夹,在里面创建index.vue
<template>
<div class="layout">
<el-container style="height: 100%;">
<el-header style="padding: 0 !important;">
<!-- 头部 -->
<HeadBar></HeadBar>
</el-header>
<el-container>
<el-aside :width="!sidebar.opened ? '60px' : '200px'">
<!-- 左侧区域 -->
<SlideBar></SlideBar>
</el-aside>
<el-main style="padding: 0;">
<!-- 内容区域 -->
<PageTabs></PageTabs>
<router-view v-slot="{ Component }" class="p-15 box-border" style="height: calc(100% - 41px);">
<transition mode="fade" appear >
<keep-alive :max="10">
<component :is="Component" :key="openTabIndex"></component>
</keep-alive>
</transition>
</router-view>
</el-main>
</el-container>
</el-container>
</div>
</template>
<script>
import { mapState } from 'vuex'
import HeadBar from '@/layout/components/Header.vue'
import SlideBar from '@/layout/components/SlideBar.vue'
import PageTabs from '@/layout/components/pageTabs.vue'
export default {
data() {
return {
}
},
components: {
HeadBar,
SlideBar,
PageTabs
},
computed: {
...mapState([
'sidebar','openTabIndex'
]),
},
onLoad(){
},
methods:{
}
};
</script>
<style>
.layout{
height: 100%;
}
.el-aside{
overflow: hidden !important;
}
/* 加过渡给侧边导航*/
.el-aside {
transition: width 0.25s;
-webkit-transition: width 0.25s;
-moz-transition: width 0.25s;
-webkit-transition: width 0.25s;
-o-transition: width 0.25s;
}
.el-main{background-color: #f0f2f5;}
.fade-enter-from{
opacity: 0;
}
.fade-enter-to{
opacity: 1;
}
.fade-leave-from{
opacity: 1;
}
.fade-leave-to{
opacity: 0;
}
.fade-enter-active,
.fade-leave-active{
transition: all 0.3s;
}
.fade-enter-active{
transition-delay: 0.3s;
}
</style>
2、在layout文件夹里创建components,再创建Header.vue,SlideBar.vue,AppMain.vue,pageTabs.vue
// SlideBar.vue
<template>
<div class="layout-left">
<el-menu
:default-active="$route.path"
router
:unique-opened="true"
class="el-menu-vertical-demo"
@open="handleOpen"
@close="handleClose"
background-color="#191a23"
text-color="#fff"
active-text-color="#fff">
<template v-for="(item,index) in menuList" >
<el-submenu v-if="item.children && item.children.length > 0" :key="index" :index='item.index'>
<template #title>
<el-icon>
<component :is="item.icon"></component>
</el-icon>
<span>{{ item.title }}</span>
</template>
<el-menu-item v-for="item2 in item.children" :key="item2.id" :index="item2.index">
<el-icon>
<component :is="item2.icon"></component>
</el-icon>
<span>{{ item2.title }}</span>
</el-menu-item>
</el-submenu>
<el-menu-item v-else :index="item.index" :key="index">
<el-icon>
<component :is="item.icon"></component>
</el-icon>
<span>{{ item.title }}</span>
</el-menu-item>
</template>
</el-menu>
</div>
</template>
<script>
import { mapState } from 'vuex';
export default {
data(){
return {
menuList:[
{
id:'1',
title: '轮播图管理',
icon:'el-icon-location',
index:'/banner',
children:[
{
index:'/banner/type',
title:'轮播图分类',
icon:''
},
{
index:'/banner/page',
title:'轮播图列表',
icon:''
},
]
},
{
id:'2',
title: '模型管理',
icon:'el-icon-location',
index:'/models',
children:[
{
index:'/models/page',
title:'模型分类',
icon:''
},
{
index:'/models/field',
title:'模型字段',
icon:''
},
]
},
{
id:'3',
title: '栏目管理',
icon:'el-icon-location',
index:'/navs/page',
children:[ ]
},
{
id:'4',
title: '内容管理',
icon:'el-icon-location',
index:'/content/page',
children:[ ]
},
{
id:'5',
title: '系统管理',
icon:'el-icon-location',
index:'/system',
children:[
{
index:'/system/user',
title:'管理员管理',
icon:''
},
{
index:'/system/role',
title:'角色管理',
icon:''
},
{
index:'/system/menu',
title:'菜单管理',
icon:''
},
{
index:'/system/configs',
title:'系统配置',
icon:''
},
]
},
]
}
},
computed: {
...mapState([
'openTab'
]),
},
watch:{
'$route'(to){
console.log("to",to)
//判断路由是否已经打开
//已经打开的 ,将其置为active
//未打开的,将其放入队列里
let flag = false;
for(let item of this.openTab){
if(item.name === to.name){
this.$store.commit('setTabsIndex',to.path)
flag = true;
break;
}
}
if(!flag){
this.$store.commit('addTabs', {route: to.path, name: to.name,title:to.meta.title,matched:to.matched});
this.$store.commit('setTabsIndex', to.path);
}
}
},
mounted () {
// 刷新时以当前路由做为tab加入tabs
// 当前路由不是首页时,添加首页以及另一页到store里,并设置激活状态
// 当当前路由是首页时,添加首页到store,并设置激活状态
if (this.$route.path !== '/' && this.$route.path !== '/') {
console.log('1');
this.$store.commit('addTabs', {route: '/' , name: 'main',title:'主页',matched:[]});
this.$store.commit('addTabs', {route: this.$route.path , name: this.$route.name,title:this.$route.meta.title,matched:this.$route.matched });
this.$store.commit('setTabsIndex', this.$route.path);
} else {
console.log('2');
this.$store.commit('addTabs', {route: '/', name: 'main',title:'主页',matched:[]});
this.$store.commit('setTabsIndex', '/');
this.$router.push('/');
}
},
methods: {
handleOpen(key, keyPath) {
console.log(key, keyPath);
},
handleClose(key, keyPath) {
console.log(key, keyPath);
}
}
}
</script>
<style scoped>
.layout-left{background-color: #191a23;height: 100%;}
.layout-left .is-active{background-color: #1890ff;}
.el-menu{border-right: 0px !important;}
.el-menu-item.is-active { background-color: #1890ff !important;}
</style>
// Header.vue
<template>
<div class="layout-header">
<el-container style="height: 100%;">
<el-aside :width="!sidebar.opened ? '60px' : '200px'">
<!-- 左侧区域 -->
<div class="logo d-flex align-center justify-center">
网站后台
</div>
</el-aside>
<el-main class="d-flex align-center justify-between " style="overflow-y:hidden !important;background-color: #ffffff;">
<!-- 内容区域 -->
<div class="d-flex align-center">
<i class="icon-btn cursor-pointer mr-20" :class="sidebar.opened ? 'el-icon-s-fold' : 'el-icon-s-unfold'" @click="changeCollapse"></i>
<i class="el-icon-refresh-right icon-btn cursor-pointer mr-20" @click="refresh"></i>
<el-breadcrumb separator="/">
<el-breadcrumb-item>主页</el-breadcrumb-item>
<el-breadcrumb-item v-for="(item,index) in breadcrumb" :key="index">{{item.meta.title}}</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="d-flex align-center">
<i class="el-icon-full-screen icon-btn cursor-pointer mr-20" @click="buttoncli"></i>
<div class="relative mr-20">
<i class="el-icon-message icon-btn cursor-pointer"></i>
<div class="absolute right--15 top--10 f-12 bg-pink c-fff border-radius-10 d-flex align-center px-5 h-16r border-white-1">99+</div>
</div>
<el-avatar icon="el-icon-user-solid" :size="28"></el-avatar>
<el-dropdown class="cursor-pointer ml-10 mr-20">
<span class="el-dropdown-link">
Admin<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>
<i class="el-icon-user cursor-pointer"></i>个人中心
</el-dropdown-item>
<el-dropdown-item>
<i class="el-icon-key cursor-pointer"></i>修改密码
</el-dropdown-item>
<el-dropdown-item>
<i class="el-icon-switch-button cursor-pointer"></i>退出登录
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</el-main>
</el-container>
</div>
</template>
<script>
import { mapState } from 'vuex';
import screenfull from 'screenfull'
export default {
data() {
return {
breadcrumb:[],
isFullscreen:false
}
},
watch:{
'$route'(to){
this.breadcrumb = to.matched
}
},
components: {
},
computed: {
...mapState([
'sidebar',
]),
},
mounted(){
},
methods:{
changeCollapse(){
this.$store.commit('setSidebar',!this.sidebar.opened)
},
refresh(){
location.reload()
},
// 全屏
buttoncli() {
screenfull.toggle()
this.checkFull()
},
// 监控屏幕变化
checkFull() {
var isFull = document.fullscreenEnabled || window.fullScreen || document.webkitIsFullScreen || document.msFullscreenEnabled;
// 是否全屏判断
this.isFullscreen = !this.isFullscreen;
if (isFull === undefined) {
isFull = false;
}
return isFull;
},
}
};
</script>
<style scoped>
.layout-header{height: 100%;}
.logo{ background-color: #191a23;height: 100%;color: #fff; }
.icon-btn{ font-size: 20px;}
</style>
// pageTabs.vue
<template>
<div class="page-tabs bc-fff">
<el-tabs v-model="currentTab" type="card" closable v-if="openTab.length" @tab-click='tabClick' @tab-remove='tabRemove'>
<el-tab-pane :key="index" v-for="(item,index) in openTab" :label="item.title" :name="item.route" > </el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import { mapState } from 'vuex';
export default {
data() {
return {
currentTab:''
}
},
components: {
},
computed: {
...mapState([
'openTab','openTabIndex'
]),
},
watch:{
openTabIndex(val){
this.currentTab =val
}
},
onLoad(){
this.currentTab = this.openTabIndex
},
activated(){
this.currentTab = this.openTabIndex
},
methods:{
//tab标签点击时,切换相应的路由
tabClick(tab){
console.log("tab",tab);
console.log('active',this.currentTab);
this.$router.push({path: this.currentTab});
},
//移除tab标签
tabRemove(targetName){
console.log("tabRemove",targetName);
//首页不删
if(targetName == '/'){
return
}
this.$store.commit('deleteTabs', targetName);
if (this.currentTab === targetName) {
// 设置当前激活的路由
if (this.openTab && this.openTab.length >= 1) {
console.log('=============',this.openTab[this.openTab.length-1].route)
this.$store.commit('setTabsIndex', this.openTab[this.openTab.length-1].route);
this.$router.push({path: this.currentTab});
} else {
this.$router.push({path: '/'});
}
}
}
}
};
</script>
<style scoped>
.page-tabs{height: 40px;overflow: hidden;border-top:1px solid #E4E7ED}
.el-tabs__content,.el-tabs--border-card>.el-tabs__content,.el-tabs__header{padding: 0 !important;}
</style>
<style>
.el-tabs--border-card .el-tabs__header,.el-tabs__nav-wrap,.el-tabs__nav-scroll{background-color: #fff !important;}
.el-tabs--card>.el-tabs__header .el-tabs__item{border-left: 0px;position: relative;}
.el-tabs--card>.el-tabs__header .el-tabs__nav{border: 0px}
.el-tabs__item.is-active {
color: #1890ff; background: #e6f7ff;
}
.el-tabs--bottom .el-tabs--left>.el-tabs__header .el-tabs__item, .el-tabs--bottom .el-tabs--right>.el-tabs__header .el-tabs__item, .el-tabs--bottom.el-tabs--border-card>.el-tabs__header .el-tabs__item, .el-tabs--bottom.el-tabs--card>.el-tabs__header .el-tabs__item, .el-tabs--top .el-tabs--left>.el-tabs__header .el-tabs__item, .el-tabs--top .el-tabs--right>.el-tabs__header .el-tabs__item, .el-tabs--top.el-tabs--border-card>.el-tabs__header .el-tabs__item, .el-tabs--top.el-tabs--card>.el-tabs__header .el-tabs__item{padding-left: 15px !important;}
.el-tabs--top .el-tabs__item.is-top, .el-tabs--top .el-tabs__item.is-bottom, .el-tabs--bottom .el-tabs__item.is-top, .el-tabs--bottom .el-tabs__item.is-bottom{padding-right: 15px !important;}
.ele-admin-tabs .el-tabs__item.is-closable{padding-right: 8px !important;}
.el-cascader-menu:last-child .el-cascader-node, .el-tabs--bottom .el-tabs--left>.el-tabs__header .el-tabs__item:last-child, .el-tabs--bottom .el-tabs--right>.el-tabs__header .el-tabs__item:last-child, .el-tabs--bottom.el-tabs--border-card>.el-tabs__header .el-tabs__item:last-child, .el-tabs--bottom.el-tabs--card>.el-tabs__header .el-tabs__item:last-child, .el-tabs--top .el-tabs--left>.el-tabs__header .el-tabs__item:last-child, .el-tabs--top .el-tabs--right>.el-tabs__header .el-tabs__item:last-child, .el-tabs--top.el-tabs--border-card>.el-tabs__header .el-tabs__item:last-child, .el-tabs--top.el-tabs--card>.el-tabs__header .el-tabs__item:last-child{padding-right: 8px !important;}
.el-tabs--top .el-tabs__item.is-top::after, .el-tabs--top .el-tabs__item.is-bottom::after, .el-tabs--bottom .el-tabs__item.is-top::after, .el-tabs--bottom .el-tabs__item.is-bottom::after{
content: "";
width: 0;
height: 2px;
background: #1890ff;
position: absolute;
bottom: 0;
left: 0;
}
.el-tabs--top .el-tabs__item.is-top.is-active::after, .el-tabs--top .el-tabs__item.is-bottom.is-active::after, .el-tabs--bottom .el-tabs__item.is-top.is-active::after, .el-tabs--bottom .el-tabs__item.is-bottom.is-active::after{ width: 100%; }
</style>
3、新建store文件夹,然后新建index.js
/**
* vuex状态管理
*/
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
sliderBar: 200,
sidebar:{
opened: true
},
openTab:[], //tab标签页
openTabIndex:'/' //激活状态
},
mutations: {
setSidebar(state, data) {
state.sidebar.opened = data;
},
// 添加tabs
addTabs (state, data) {
state.openTab.push(data);
console.log('state.openTab',state.openTab)
},
// 删除tabs
deleteTabs (state, route) {
let index = 0;
for (let option of state.openTab) {
if (option.route === route) {
break;
}
index++;
}
state.openTab.splice(index, 1);
},
// 设置当前激活的tab
setTabsIndex (state, index) {
state.openTabIndex = index;
},
},
actions: {
},
getters:{
}
});