本章節,咱們開發管理系統側邊欄及面包屑功能。
先上一張效果圖

樣式呢,作者前端初審,關於設計上毫無美感可言,大家可根據自己情況設計更好看的哦~
側邊欄
這里我們借助element的aslide側邊欄,直接使用。
在components>commons下新建Aslide.vue,Header.vue組件。分別作為我們的側邊欄和頭部組件。
Aslide內容,我們直接使用el-menu及相關側邊欄組件。不過要仔細閱讀以下官方文檔,否則使用會比較費勁。
collapse 是否水平折疊收起菜單(僅在 mode 為 vertical 時可用) boolean false
default-active default-active string
default-openeds 當前打開的 sub-menu 的 index 的數組 Array
unique-opened 是否只保持一個子菜單的展開 boolean false
router 是否使用 vue-router 的模式,啟用該模式會在激活導航時以 index 作為 path 進行路由跳轉 boolean false
以上就是主要的屬性,我們要仔細閱讀加以理解。
這里的側邊欄的話,因為我們需要注意的是
-
如果當前展開菜單為2級的某一菜單,那么在頁面刷新后和瀏覽器回退后,也依然要展開。
-
不同的角色登錄后,所擁有的權限是不同的。這里我么可以做成較為簡單的,前端處理,控制某些菜單顯示來實現,當然。后期如果有時間,后端也是需要對接口做權限校驗的!
那么,我么開始吧~
首先,我們可以復制elementui的代碼過來,直接放到Aslide.vue文件中,然后引用,都是沒有問題的。
下來我們就要開始改造了。
因為要做權限的管理,我們這里要控制菜單的顯示,所以這里,我們不再頁面中寫死,這里給提供兩種解決方案:
-
在static中配置靜態的menu.json文件,將我們的菜單欄加以不同的角色進行配置,然后在頁面中根據登錄后的權限,進行動態控制顯示對應角色的菜單欄。
-
將菜單欄放到store中管理。getters直接解構取值獲得並使用。(這里之所以放在store中,是因為后面如果后端配合使用權限控制,那么我們就需要后端返回菜單欄信息,並格式化轉換為我們的路由信息。實現動態路由的使用~),當然,因為是自己的管理平台,MD還是懶~
這里,我們先一起采用store的方式來存儲menu.json文件吧
大家先按照如圖所示補全目錄。

我們,將menu文件存儲在store>modules>aslide.js文件中:
/**
* @description 側邊欄狀態庫
* @author chaizhiyang
*/
const aslide = {
state: {
isCollapse: false,
menuList: [
{
"text": "概況",
"path": "",
"icon": "el-icon-c-scale-to-original",
"itemGroup": [
{
"text": "概況數據",
"path": "/index"
}
]
},
{
"text": "菜單",
"path": "menu",
"icon": "el-icon-s-operation",
"itemGroup": [
{
"text": "菜單列表",
"path": "/menu_list"
}
]
},
{
"text": "文章",
"path": "article",
"icon": "el-icon-document",
"itemGroup": [
{
"text": "文章列表",
"path": "/article_list"
},
{
"text": "詳情",
"path": "/article_detail"
}
]
}
]
},
mutations: {
changeCollapse(state) {
state.isCollapse = state.isCollapse == false ? true : false
},
}
}
export default aslide
除了,菜單信息外,后面所涉及的header中控制菜單的展開折疊的方法,我們也一並放置在狀態中進行管理。
const getters = {
isCollapse: state => state.aslide.isCollapse,
menuList: state => state.aslide.menuList,
}
export default getters;
簡單說就是為了后期mapGetters的使用,方便我們去取state中的數據,使用更加方便~
index.js文件:
/**
* @description vuex主入口文件
* @author chaizhiyang
*/
import Vue from 'vue'
import Vuex from 'vuex'
import aslide from './modules/aslide'
import getters from './getters'
Vue.use(Vuex);
const store = new Vuex.Store({
modules: {
aslide,
},
getters
})
export default store;
store文件基本的配置也就算是完成了,下來我們需要在main.js中引入
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router/permission'
import store from './store';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css'
import Utils from './utils';
import './assets/styles/index.css';
Vue.config.productionTip = false
Vue.use(ElementUI);
Vue.use(Utils);
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
側邊欄的配置已經好了,但是還沒有使用。下來我們補全一些其他的頁面信息。

內容可以隨便寫成標志性的內容,這都不是重點。
重點是Aslide.vue文件中的引用:
<template>
<div class="menu">
<el-menu
class="el-menu-admin"
:default-active="active"
:default-openeds="openeds"
:unique-opened="true"
:router="true"
:collapse="isCollapse"
ref="menuchild">
<!-- 菜單欄包含單個選項 -->
<el-menu-item
v-for="(item, pindex) in menuList"
:key="+new Date() + pindex"
:index="item.path"
v-if="!item.itemGroup">
<i :class="item.icon"></i>
<span slot="title">{{item.text}}</span>
</el-menu-item>
<!-- 菜單欄包含多個選項 -->
<el-submenu
v-for="(item, pindex) in menuList"
:key="pindex"
:index="item.path">
<template slot="title">
<i :class="item.icon"></i>
<span>{{item.text}}</span>
</template>
<!-- 菜單欄只有二級菜單 -->
<el-menu-item
v-for="(subitem, subindex) in item.itemGroup"
:key="subindex"
:route="subitem.path"
:index="subitem.path"
v-if="!subitem.items"
>{{subitem.text}}</el-menu-item>
<!-- 菜單欄有三級菜單 -->
<el-submenu
v-for="(subitem, subindex) in item.itemGroup"
:key="subindex"
:index="subitem.path"
v-if="subitem.items">
<!-- 第三項分組標題 -->
<template slot="title">{{subitem.text}}</template>
<!-- 第三項分組的items -->
<el-menu-item
v-for="(s_subitem, s_subindex) in subitem.items"
:key="s_subindex"
:route="s_subitem.path"
:index="s_subitem.path"
>{{s_subitem.text}}</el-menu-item>
</el-submenu>
</el-submenu>
</el-menu>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
data() {
return {
openeds: [],
}
},
watch: {
// 監聽路由變化
$route(to, from) {
this.setMenulist(to);
}
},
computed: {
...mapGetters([
'menuList','isCollapse'
])
},
created() {
this.setMenulist(this.$route);
},
methods: {
// 設置菜單欄
setMenulist(route) {
let _this = this;
if (route.matched[0].path != "") {
// 多頁面菜單欄
this.openeds = [route.matched[0].path];
this.active = route.fullPath.split("?")[0]; //攜帶參數時,只匹配"?"前的路徑
} else if (route.matched[1].path != "") {
// 單頁面菜單欄
this.openeds = [route.matched[0].path];
this.active = route.fullPath.split("?")[0]; //攜帶參數時,只匹配"?"前的路徑
} else {
this.$nextTick(() => {
_this.active = "";
_this.openeds = [""];
_this.$refs.menuchild.close(_this.active);
});
}
}
}
}
</script>
<style lang="less" scoped>
.menu {
height: 100%;
.el-menu {
height: 100%;
border: 0;
}
.el-menu-vertical-demo {
color: #303133;
}
.el-menu-item {
box-sizing: border-box;
border-left: 5px solid transparent;
}
.el-menu-item.is-active {
border-left: 5px solid #409EFF;
}
.el-menu-admin:not(.el-menu--collapse) {
width: 145px;
max-height: 400px;
}
}
</style>
-
之所以要寫watch監聽,是因為上面我們說到過的頁面刷新后,也依然要保持菜單欄的點擊和展開狀態。當然也可以使用本地緩存區實現,不過就有點小題大做了。
-
另外,這里之所以不惜消耗性能的去循環的時候去判斷,是因為我們可能有單個的一級菜單。這個時候他是不需要展開的,所以種種狀態我們都需要去做判斷。
-
具體的實現思路:
active要求為字符串,且:router="true"這個屬性的開關直接控制了是否將index作為路由進行跳轉。
第一種:我們可以給給個菜單配置單獨的下標,我們可以寫死,比如:'1','1-1','1-2','2','2-1','2-2',采用這種方式去標記,去區別。(這種方式的使用,我們需要將router設置為false,否則話跳轉到1-1.。。根本不是我們想要的。)。
第二種::router="true"。設置為true后,下標就會作為路由進行跳轉。我們就需要將下標設置為路由的路徑。
當然兩種方法的區別就是,一個是寫死的下標。一個是路徑作為下標。都要求我們在配置json文件的時候主要需要的參數。
Next,下來我們就要去Layout布局組件中引入我們的側邊欄啦
Layout:
<template>
<el-container>
<el-header>
<adminHeader />
</el-header>
<el-container>
<el-aside>
<adminAslide />
</el-aside>
<el-container class="loading-area">
<el-main>
<adminCrumbs />
<keep-alive>
<router-view v-if="this.$route.meta.isAlive"></router-view>
</keep-alive>
<router-view v-if="!this.$route.meta.isAlive"></router-view>
</el-main>
<el-footer>Footer</el-footer>
</el-container>
</el-container>
</el-container>
</template>
<script>
import adminHeader from './Header.vue';
import adminAslide from './Aslide.vue';
import adminCrumbs from './Crumbs.vue';
export default {
components: {
adminHeader,
adminAslide,
adminCrumbs
},
}
</script>
<style lang="less" scoped>
.el-container {
width: 100%;
height: 100%;
}
.el-header, .el-footer {
background-color: #B3C0D1;
color: #333;
line-height: 60px;
}
.el-header {
padding: 0!important;
}
.el-aside {
// background-color: #D3DCE6;
width: auto!important;
color: #333;
text-align: left;
overflow: hidden;
// line-height: 200px;
}
.el-main {
background-color: #E9EEF3;
color: #333;
text-align: center;
line-height: 160px;
}
</style>
需要注意的是:這里引入的時候沒有用Header代表頭部組件和Aslide代表側邊欄組件,是因為這些組件在原生的h5中含有相同的標簽,難免造成混淆。作者曾經在使用MpVue開發小程序的過程中,就因為沒有區別,所以報了一個error,讓我頭疼了好久~
大家可以暫時先把上面的面包屑和header引入先關掉,這里不是還沒配置嘛。不關閉的話,會報錯哦。
Next,非常重要的一個環節。側邊欄我們已經配置好了,我們要對路由進行配置。不過這里。我們需要先將我們原來設置的登錄攔截給管理。
在路由中設置auth為false
meta: {
auth: false,
isAlive: true,
title: '文章列表'
}
接着,我們只需要按照剛才創建的文件的目錄去補全路由:
router/index.js
import Vue from 'vue'
import Router from 'vue-router'
// import HelloWorld from '@/components/HelloWorld'
Vue.use(Router)
const _import = file => () => import('@/pages/' + file + '.vue');
const _import_ = file => () => import('@/components/' + file + '.vue');
const asyncRouterMap = [];
const constantRouterMap = [
{
path: '/login',
name: 'login',
component: _import('login/index'),
},
{
path: '/',
name: '概況',
component: _import_('commons/Layout'),
redirect: '/index',
children: [
{
path: '/index',
name: '總覽',
component: _import('home/index'),
meta: {
isAlive: true,
auth: false,
title: '概況數據'
}
}
]
},
{
path: 'menu',
name: "菜單",
component: _import_('commons/Layout'),
redirect: '/menu_list',
children: [
{
path: '/menu_list',
name: '列表',
component: _import('menu/index'),
meta: {
auth: false,
isAlive: true,
title: '菜單列表'
}
},
]
},
{
path: 'article',
name: '文章',
component: _import_('commons/Layout'),
redirect: '/article_list',
children: [
{
path: '/article_list',
name: '列表',
component: _import('article/index'),
meta: {
auth: false,
isAlive: true,
title: '文章列表'
}
},
{
path: '/article_detail',
name: '詳情',
component: _import('article/detail'),
meta: {
auth: false,
isAlive: true,
title: '文章詳情'
},
}
]
},
{
path: '/404',
name: '404',
component: _import('error/index'),
meta: {
title: "請求頁面未找到",
auth: false
},
},
{
path: '*',
meta: {
title: "請求頁面未找到",
auth: false
},
redirect: '/404'
}
];
const router = new Router({
mode: 'history',
routes: constantRouterMap,
linkActiveClass: "router-link-active",
});
export default router
這里,我們新增了404路由和通配符。在匹配不到路由時,就會跳轉到404頁面,當然我們也需要在pages中創建error文件 pages>error>index.vue
細心的同學會發現路由我也都配置了name。這個name就是配置面包屑而准備噠。需要值得注意的是,路由中name的配置,不能有相同項,雖然不影響使用不會報錯,但是控制台會出現一個warn告訴我們避免相同的name。
嘿嘿嘿~下來我們就可以配置我們的BreadCrumbs了。
BreadCrumbs配置
BreadCrumbs面包屑導航 什么事面包屑導航呢?
可以理解為當前路由信息的導航提示,並隨着路由的改變而改變。
elemnt-ui面包屑組件的使用:
一個數組,里頭有很多對象,對象為路由的信息。如果有路徑就是可以跳轉,如果沒有就不能通過面包屑挑戰。
eg:
[{ path:'/',name:"主頁"
},{ name:"標簽"
}]
這里的最終顯示效果就為: 主頁 > 標簽
主頁是可以點擊的。標簽頁則不可以點擊。
知道了組件需要什么我們就好整理數據啦。
這里我們實現的思路為:
使用路由的this.$route.matched來實現
matched可以返回一個數組,該數組匯總含有當前路由的所有parent信息。
我們定義的name和path也都有。我們就只需要在路由變化的時候去改變傳給面包屑的數組即可。
在components>commons>Crumbs.vue文件
上菜Crumbs.vue:
<template>
<div class="crumbs">
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item
v-for="(item,index) in crumbsList"
:key="+new Date() + index"
:to="item.redirect?item.redirect:item.path">
{{item.name}}
</el-breadcrumb-item>
</el-breadcrumb>
</div>
</template>
<script>
export default {
data() {
return {
crumbsList: []
}
},
watch: {
$route() {
this.getCrumbs();
}
},
methods: {
getCrumbs() {
this.crumbsList = this.$route.matched;
}
},
created() {
this.getCrumbs();
}
}
</script>
<style lang="less" scoped>
</style>
這面包屑配置就ok啦。當然,menu,router和面包屑三者有一個有問題都會造成問題。所以還是挺復雜的。面包屑組件寫好了。我們就在layout中將面包屑打開即可。
Header,菜單欄的收縮
我們因為已經在store中配置好了collapse所以下來要實現按鈕控制收縮,我們就需要調用store方法即可.
直接上Header.vue碼:
<template>
<div class="header df">
<div class="logo df">
<i class="el-icon-menu" @click="handleChangeCollapse"></i>logo</div>
</div>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
handleChangeCollapse() {
this.$store.commit('changeCollapse');
}
},
created() {
},
}
</script>
<style lang="less" scoped>
.header {
height: 100%;
.logo {
width: 145px;
height: 100%;
cursor: pointer;
font-size: 30px;
}
}
</style>
第二章的內容就完成了,在開發完成后記得推送到倉庫哦!
git add . git commit -m "菜單欄,面包屑" git push origin master
總結
信心的你,或許發現了。我在元素便利的時候key給的是這樣的+new Date() + index;
學到了么?這樣寫的話,不會造成index重復造成的error。
另外以上所有內容中用到的圖標都是element-ui自帶的圖標。
下一章
-
-
頁面開發~
