源码地址:https://github.com/18291907191/gwc_manage

Vue实现狗尾草博客后台管理系统第三章

本章节,咱们开发管理系统侧边栏及面包屑功能。

先上一张效果图

样式呢,作者前端初审,关于设计上毫无美感可言,大家可根据自己情况设计更好看的哦~

侧边栏

这里我们借助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

以上就是主要的属性,我们要仔细阅读加以理解。

这里的侧边栏的话,因为我们需要注意的是

  1. 如果当前展开菜单为2级的某一菜单,那么在页面刷新后和浏览器回退后,也依然要展开。

  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中控制菜单的展开折叠的方法,我们也一并放置在状态中进行管理。

getters.js文件如下

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>

  

  1. 之所以要写watch监听,是因为上面我们说到过的页面刷新后,也依然要保持菜单栏的点击和展开状态。当然也可以使用本地缓存区实现,不过就有点小题大做了。

  2. 另外,这里之所以不惜消耗性能的去循环的时候去判断,是因为我们可能有单个的一级菜单。这个时候他是不需要展开的,所以种种状态我们都需要去做判断。

  3. 具体的实现思路:

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自带的图标。

下一章

  1. iconfont的仓库配置和引入

  2. 页面开发~

Vue实战狗尾草博客后台管理系统第三章的更多相关文章

  1. Vue实战狗尾草博客后台管理系统第七章

    Vue实战狗尾草博客后台管理平台第七章 本章内容为借助模块化来阐述Vuex的进阶使用. 在复杂项目的架构中,对于数据的处理是一个非常头疼的问题.处理不当,不仅对维护增加相当的工作负担,也给开发增加巨大 ...

  2. Vue实战狗尾草博客管理平台第六章

    Vue实现狗尾草博客后台管理系统第六章 本章节内容 文章列表 文章详情 草稿箱 文章发布. 本章节内容呢,开发的很是随意哈,因为多数就是element-ui的使用,熟悉的童鞋,是可以很快完成本章节的内 ...

  3. Vue实战狗尾草博客管理平台第四章

    本章主要内容如下: 填补上期的坑. iconfont仓库的关联,引入. 开发登录页面 填坑 上期中我们功能都已正常使用.但不知道有没有小伙伴测试过error页面,当访问地址不存在时,路由是否能正常挑战 ...

  4. Vue实战狗尾草博客管理平台第五章

    本章主要内容如下: 静态资源服务器的配置.学会如何使用静态资源服务器引入静态资源.并给大家推荐一个免费可使用的oss服务器~ 页面的开发由于近期做出的更改较大.就放在下一篇中. 静态资源服务器 静态资 ...

  5. Vue实战狗尾草博客管理系统第一章

    Vue实战狗尾草博客后台管理系统第一章 这里准备采用的技术栈为:vue全家桶+element-ui 这里因为是后台管理系统,没有做SSR的必要.所以这里就采用前后端分离来昨晚这个项目~ 项目搭建 vu ...

  6. Vue实战狗尾草博客管理系统第二章

    伙伴们出来啦,探讨各问题,关于项目中大量的表单,大家是怎么处理的? 本章主要内容如下:底层布局,路由配置,github仓库推送关联. 关联GitHub仓库 关联建立在github已创建账号的基础上 登 ...

  7. 使用react全家桶制作博客后台管理系统

    前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基于react全家桶(React.React-r ...

  8. 使用react全家桶制作博客后台管理系统 网站PWA升级 移动端常见问题处理 循序渐进学.Net Core Web Api开发系列【4】:前端访问WebApi [Abp 源码分析]四、模块配置 [Abp 源码分析]三、依赖注入

    使用react全家桶制作博客后台管理系统   前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基 ...

  9. 基于Laravel开发博客应用系列 —— 构建博客后台管理系统

    一个完整的博客应用不能没有后台管理系统.所以在本节中我们将继续完善博客应用 —— 开发后台管理系统. 1.创建路由 在上一节十分钟创建博客项目中,已经设置过了 app/Http/routes.php, ...

随机推荐

  1. HttpClientExtensions去了哪里

    使用HttpClient实现http请求是非常常见的方式,有一个HttpClient的拓展类HttpClientExtensions提供了更多的拓展方法,包括但不限于 PostAsJsonAsync ...

  2. CAD简易口诀,保你一天就记住!零基础也能轻松学!CAD制图宝典!

    如何才能快速的学习CAD制图呢?不仅仅需要多练习,CAD口诀也是不能错过的哦!实用干货这一个就够了快点收藏起来! 1.创建直线的快捷方式是L+空格 2.创建圆的快捷方式是C+空格 3.创建圆弧的快捷方 ...

  3. 总结在ssm整合中,Mybatis出现Mapped Statements collection already contains value for xxxxx的解决方案

    先贴一段报错信息: 前面的都不是很重要,看最后灰色标注的那段.... 严重: 异常将上下文初始化事件发送到类的侦听器实例.[org.springframework.web.context.Contex ...

  4. PHP strstr 字符串函数

    定义和用法 strstr - 查找字符串的首次出现 版本支持 PHP4 PHP5 PHP7 支持 支持 支持 V5.3.0 新增可选的 before_needle 参数. V4.3.0 strstr( ...

  5. GPS NMEA-0183协议常用报文数据格式

    点击上方↑↑↑蓝字[协议分析与还原]关注我们 " 整理的GPS有关的协议分析资料." 之前分析一些车载设备的流量时,有部分经验,在这里和大家分享. 产生这些流量的设备通常是实体终端 ...

  6. 推荐四个phpstorm酷炫实用插件 让你写代码的时候不在孤单!

    程序员写代码很孤独,每天只能和电脑屏幕交流,想要一个程序员鼓励师妹子,老板又不给配,如何让自己写代码的时候不再孤单呢?今天给大家分享的这四个插件,既实用又好玩,还能提高开发效率,这四个插件主要用到ph ...

  7. BIM工程信息管理新系统- 系统管理模块

    系统管理模块 1.实体类 public partial class T_Role { public string RoleId { get; set; } public string RoleName ...

  8. PHP获取二维数组指定字段值的和

    array_sum(array_column($arr, 'num')); //获取二维数组 num字段的和 $arr = [ [ 'device_uid' => '123456', 'num' ...

  9. Linux:RPM安装工具的使用

    RPM安装工具的使用 RPM包管理工具介绍 RedHat 软件包管理工具(RedHat Package Manager,RPM) RPM 软件包工具常用于软件包的安装.查询.更新升级.校验.卸载以及生 ...

  10. MATLAB实例:将批量的图片保存为.mat文件

    MATLAB实例:将批量的图片保存为.mat文件 作者:凯鲁嘎吉 - 博客园 http://www.cnblogs.com/kailugaji/ 一.彩色图片 图片数据:horse.rar 1. MA ...