先看示例图
我们逐一来分析:
1,既然是在顶部改造topbar,那么我们首先来找到对应的位置来写dom元素。(
在src > layout > index.vue
)
<div :class="classObj" class="app-wrapper">
<!-- 改动的地方 start-->
我是topBar我是topBar我是topBar我是topBar我是topBar我是topBar
<!-- 改动的地方 end-->
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
<sidebar class="sidebar-container" />
<div class="main-container">
<div :class="{'fixed-header':fixedHeader}">
<navbar />
</div>
<app-main />
</div>
</div>
2,写完可以发现,顶部出现了
我是topBar我是topBar我是topBar我是topBar我是topBar我是topBar
的字样,但是左侧没有被顶下来,是因为左侧使用了定位,我们来找到定位的地方,把top注释掉(src > styles > sidebar.scss 大概在第10行可以找到 .sidebar-container 的样式
)
.sidebar-container {
transition: width 0.28s;
width: $sideBarWidth !important;
background-color: $menuBg;
height: 100%;
position: fixed;
font-size: 0px;
//改动的地方start
// top: 0;
//改动的地方end
bottom: 0;
left: 0;
z-index: 1001;
overflow: hidden;
}
3,改完发现没效果,别急,我们来改一下height:90%试一下
.sidebar-container {
transition: width 0.28s;
width: $sideBarWidth !important;
background-color: $menuBg;
//改动的地方start
height: 90%;
//改动的地方end
position: fixed;
font-size: 0px;
//改动的地方start
// top: 0;
//改动的地方end
bottom: 0;
left: 0;
z-index: 1001;
overflow: hidden;
}
4,改完再看下,发现下来了,说明我们改的没毛病,就是这
5,这时候 你会怎么想怎么做?我想到的是左侧区域怎么也能下来呢?怎么和左侧的顶部持平呢?带着疑问,我们继续往下走。
通过上图可以看出,右侧主体区域的最外层的class类名是main-container
,我们尝试着改下min-height
试试看呢
ps:改过以后,发现没有变化,无用,回退
这时候,就要想,可不可以 加一个margin-top
,让他下来呢,那就加上试试。
.main-container {
min-height: 100%;
transition: margin-left .28s;
margin-left: $sideBarWidth;
position: relative;
// 改动的地方start
margin-top: 50px;
// 改动的地方end
}
下图可以看到,确实生效了。
但是margin-top
的值给多少才合适呢?通过分析得知,margin-top
的值 应该是顶部你想要设置的高度的像素,比如我们顶部 想要设置高度56px,那么我们的margin-top
的值就是56px,这时候最好设置一个变量方便我们去管理。在哪设置变量呢,路径(src > styles > variables.scss
)
$subMenuBg:#1f2d3d;
$subMenuHover:#001528;
$sideBarWidth: 210px;
// 改的的地方start
$topBarHeight: 56px;
// 改的的地方end
然后把
margin-top
的值 改成$topBarHeight:
,就是这样了
6,问题随着而来,左侧slidbar 的高度肯定不能是固定的90%
,那应该是多少呢?应该是100vh - 顶部的高度
,所以我们还要设置一个变量来管理这个slidbar的高度,同样在(src > styles > variables.scss
)
$menuBg:#304156;
$menuHover:#263445;
$subMenuBg:#1f2d3d;
$subMenuHover:#001528;
$sideBarWidth: 210px;
// 改的的地方start
$topBarHeight: 56px;
$contentHeight: calc(100vh - 56px);
// 改的的地方end
.sidebar-container {
transition: width 0.28s;
width: $sideBarWidth !important;
background-color: $menuBg;
// 改的的地方start
height: $contentHeight;
// 改的的地方end
position: fixed;
font-size: 0px;
//改动的地方start
// top: 0;
//改动的地方end
bottom: 0;
left: 0;
z-index: 1001;
overflow: hidden;
}
然后把顶部的文字注释掉 完事是这样的!
左边和右边都持平了。达到了我们想要的效果
下面开始改造顶部。
1,在《src > layout > components
》下新建 Topbar.vue
,效仿 Navbar
的引入方式
// 改的地方start
import { Navbar, Sidebar, AppMain, Topbar } from './components'
// 改的地方end
components: {
Navbar,
Sidebar,
AppMain,
// 改的地方start
Topbar
// 改的地方end
},
/*src > layout > components > index.js */
export { default as Navbar } from './Navbar'
export { default as Sidebar } from './Sidebar'
export { default as AppMain } from './AppMain'
/** 改动的地方 start */
export { default as Topbar } from './Topbar'
/** 改动的地方 end */
完事在如图的地方引入
然后就是这样
2,在topbar页面里写入这些代码
<template>
<div class="top-nav">
<div class="log">后台管理系统</div>
<el-menu :active-text-color="variables.menuActiveText" :default-active="activeMenu" mode="horizontal" @select="handleSelect">
<div v-for="item in routes" :key="item.path" class="nav-item">
<app-link :to="resolvePath(item)">
<el-menu-item v-if="!item.hidden" :index="item.path">{{ item.meta ? item.meta.title : item.children[0].meta.title }}</el-menu-item>
</app-link>
</div>
</el-menu>
<div class="right-menu">
<el-dropdown class="avatar-container" trigger="click">
<div class="avatar-wrapper">
<img :src="avatar+'?imageView2/1/w/80/h/80'" class="user-avatar">
<i class="el-icon-caret-bottom" />
</div>
<el-dropdown-menu slot="dropdown" class="user-dropdown">
<router-link to="/">
<el-dropdown-item>Home</el-dropdown-item>
</router-link>
<a href="https://github.com/PanJiaChen/vue-admin-template/" target="_blank">
<el-dropdown-item>Github</el-dropdown-item>
</a>
<a href="https://panjiachen.github.io/vue-element-admin-site/#/" target="_blank">
<el-dropdown-item>Docs</el-dropdown-item>
</a>
<el-dropdown-item divided @click.native="logout">
<span style="display:block;">Log Out</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</template>
<script>
export default {
}
</script>
<style lang="scss" scoped>
</style>
页面肯定会报错,我们跟着报错一步一步来解决
首先来解决"method "variables" is not defined
的问题,variables
是一个计算属性,return出来variables
文件 先把variables
scss文件 引过来
import variables from '@/styles/variables.scss'
然后在计算属性里 这样写
computed:{
variables() {
return variables
},
}
在来解决 method "activeMenu" is not defined
的问题
activeMenu
也是一个计算属性
activeMenu() {
const route = this.$route
const { meta, path } = route
// if set path, the sidebar will highlight the path you set
if (meta.activeMenu) {
return meta.activeMenu
}
// 如果是首页,首页高亮
if (path === '/dashboard') {
return '/'
}
// 如果不是首页,高亮一级菜单
const activeMenu = '/' + path.split('/')[1]
return activeMenu
},
在来解决 method "handleSelect" is not defined
的问题,在methods里定义这个方法
methods: {
handleSelect() {}
},
在解决 method "routes" is not defined
,routes是我们要循环遍历的路由,在data里定义一个变量,把路由引过来赋值给这个变量
import { constantRoutes } from '@/router'
data() {
return {
routes: constantRoutes
}
},
刷新页面在来解决 method "resolvePath" is not defined
的问题,在methods里定义
resolvePath(item) {
// 如果是个完成的url直接返回
if (isExternal(item.path)) {
return item.path
}
// 如果是首页,就返回重定向路由
if (item.path === '/') {
const path = item.redirect
return path
}
// 如果有子项,默认跳转第一个子项路由
let path = ''
/**
* item 路由子项
* parent 路由父项
*/
const getDefaultPath = (item, parent) => {
// 如果path是个外部链接(不建议),直接返回链接,存在个问题:如果是外部链接点击跳转后当前页内容还是上一个路由内容
if (isExternal(item.path)) {
path = item.path
return
}
// 第一次需要父项路由拼接,所以只是第一个传parent
if (parent) {
path += (parent.path + '/' + item.path)
} else {
path += ('/' + item.path)
}
// 如果还有子项,继续递归
if (item.children) {
getDefaultPath(item.children[0])
}
}
if (item.children) {
getDefaultPath(item.children[0], item)
return path
}
return item.path
},
在引入
import { isExternal } from '@/utils/validate'
在把app-link组件引入过来
import AppLink from './Sidebar/Link'
components: {
AppLink
},
完事是这样
3,在来把css样式写一写,在styles文件夹下新建topbar.scss
.top-nav {
// margin-left: $sideBarWidth;
width: 100%;
background-color: #304156;
position: fixed;
top: 0;
left: 0;
z-index: 1001;
overflow: hidden;
.log {
padding: 0 20px;
line-height: 56px;
font-size: 24px;
font-weight: bold;
color: rgb(191, 203, 217);
float: left;
margin-right: 50px;
}
.el-menu {
float: left;
border: none !important;
background-color: #304156;
.nav-item {
display: inline-block;
.el-menu-item {
color: rgb(191, 203, 217);
&:hover {
background-color: $subMenuHover !important;
}
&:focus {
background-color: $subMenuHover !important;
// color: $subMenuActiveText !important;
}
}
}
}
.right-menu {
float: right;
height: 100%;
&:focus {
outline: none;
}
.right-menu-item {
display: inline-block;
padding: 0 8px;
height: 100%;
font-size: 18px;
color: #5a5e66;
vertical-align: text-bottom;
&.hover-effect {
cursor: pointer;
transition: background .3s;
&:hover {
background: rgba(0, 0, 0, .025)
}
}
}
.avatar-container {
margin-right: 30px;
.avatar-wrapper {
margin-top: 5px;
position: relative;
.user-avatar {
cursor: pointer;
width: 40px;
height: 40px;
border-radius: 10px;
}
.el-icon-caret-bottom {
cursor: pointer;
position: absolute;
right: -20px;
top: 25px;
font-size: 12px;
}
}
}
}
}
4,在index.js里引入topbar.scss
@import './variables.scss';
@import './mixin.scss';
@import './transition.scss';
@import './element-ui.scss';
@import './sidebar.scss';
// 修改的地方start
@import './topbar.scss';
// 修改的地方end
这样我们就把一级菜单渲染到了顶部
然后把头像的东西渲染到上面
把代码放这里
下面解决报错,把头像从vuex拿进来
import { mapGetters } from 'vuex'
...mapGetters(['avatar'])
最后把下面的右侧区域删掉就ok了。
5,上方的布局算是实现了,但是左侧我们想要渲染子级菜单,并不想渲染所有的,下面我们就来改造,(思路是这样 :当点击顶部的一级菜单时,拿到点击菜单的子级路由,保存起来,左侧去渲染这个保存起来的子级路由
)
6,找到我们topbar.vue 找到select事件定义的方法handleSelect
我们可以看到该事件会回调选中菜单的index 和path,开始写逻辑
handleSelect(key, keyPath) {
//得到子级路由
const route = this.routes.find(item => item.path === key)
console.log(route);
// 把选中路由的子路由保存store
},
7,下一步就是 把选中路由的子路由保存store,那么我们就要在store里定义方法供这里调用。
现在 《
src > store > modules
》下新建一个permission.js
const state = {
currentRoutes: {}
}
const mutations = {
SET_CURRENT_ROUTES: (state, routes) => {
state.currentRoutes = routes
}
}
export default {
namespaced: true,
state,
mutations
}
在 《
src > store > index.js
》下引入permission.js
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
import app from './modules/app'
import settings from './modules/settings'
import user from './modules/user'
/** 改动的地方*/
import permission from './modules/permission'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
app,
settings,
user,
/** 改动的地方*/
permission
},
getters
})
export default store
好,我们现在调用store的方法,把子级路由存起来,
handleSelect(key, keyPath) {
// 把选中路由的子路由保存store
const route = this.routes.find(item => item.path === key)
this.$store.commit('permission/SET_CURRENT_ROUTES', route)
},
通过调试工具可以看到,确实把子级路由存进去了。
下面把 src > layout > components > Sidebar > index.vue 里的routes计算属性改成下面这样
routes() {
// return this.$router.options.routes
// 改动的地方
return this.$store.state.permission.currentRoutes.children
},
点击一级菜单看页面,已经正确渲染了。但是存在几个问题,现在一个一个来解决。
(1)刷新页面左侧子级路导航没了!
众所周知,vuex里面的数据刷新页面就会丢失,这就是 左侧子级路导航没了 的原因,下面开始解决
在topbar.vue里新建一个方法 initCurrentRoutes ,在页面加载的时候就调用,代码如下
// 通过当前路径找到二级菜单对应项,存到store,用来渲染左侧菜单
initCurrentRoutes() {
const { path } = this.$route
let route = this.routes.find(
item => item.path === '/' + path.split('/')[1]
)
// 如果找不到这个路由,说明是首页
if (!route) {
route = this.routes.find(item => item.path === '/')
}
this.$store.commit('permission/SET_CURRENT_ROUTES', route)
},
mounted() {
this.initCurrentRoutes()
},
在刷新页面就不会出现上面那种情况了。
(2)点击顶部一级菜单左侧没有高亮显示第一个子级路由
在src > layout > components > Sidebar > SidebarItem.vue
里 修改这里
/** 改动的地方 */
const currentRoutes = this.$store.state.permission.currentRoutes
if (currentRoutes && currentRoutes.path) {
return path.resolve(currentRoutes.path, this.basePath, routePath)
}
ok!
(3)点击左侧跳转404
完事你会惊讶的发现,这一个问题竟然也被修复了 哈哈...
至此,我们已经完成了 顶部一级导航,左侧子级导航的布局修改!如果你想精益求精,在左侧没有一个子级的时候,把侧边栏隐藏掉。
在topbar.vue
新建一个方法
// 设置侧边栏的显示和隐藏
setSidebarHide(route) {
if (!route.children || route.children.length === 1) {
this.$store.dispatch('app/toggleSideBarHide', true)
} else {
this.$store.dispatch('app/toggleSideBarHide', false)
}
},
在 handleSelect 、 initCurrentRoutes
方法内调用
this.setSidebarHide(route)
在
store的 app.js
里的actions里新建toggleSideBarHide
方法,在mutations
里新建SET_SIDEBAR_HIDE ,state 里 加一个hide
SET_SIDEBAR_HIDE: (state, status) => {
state.sidebar.hide = status
}
/** 改动的地方*/
toggleSideBarHide({ commit }, status) {
commit('SET_SIDEBAR_HIDE', status)
},
/** 改动的地方*/
const state = {
sidebar: {
opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
withoutAnimation: false,
hide: false
},
device: 'desktop'
}
然后在这里加上 v-if 判断
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
<div :class="{sidebarHide: sidebar.hide}" class="main-container">
完事可以看到,侧边栏消失了,但是左侧还占位,但是可以看到class类名加上了,我们把margin-left 设置成0 就可以了
ok 最后的效果
但是,主体区域好像高出了顶部的高度的距离,
看图理解
在
AppMain.vue
里修改
.app-main {
/** 改动的地方 */
/*107 = navbar 50 + topbar 57 */
min-height: calc(100vh - 107px);
width: 100%;
position: relative;
overflow: hidden;
}
好了 根据截图的代码全局搜一下 就可以找到位置啦
大工告成。累死我了