Vue實戰狗尾草博客後臺管理系統第三章

Vue實現狗尾草博客後臺管理系統第三章

本章節,我們開發管理系統側邊欄及麪包屑功能。javascript

先上一張效果圖css

 

樣式呢,做者前端初審,關於設計上毫無美感可言,你們可根據本身狀況設計更好看的哦~html

側邊欄

這裏咱們藉助element的aslide側邊欄,直接使用。前端

在components>commons下新建Aslide.vue,Header.vue組件。分別做爲咱們的側邊欄和頭部組件。vue

Aslide內容,咱們直接使用el-menu及相關側邊欄組件。不過要仔細閱讀如下官方文檔,不然使用會比較費勁。java

collapse 是否水平摺疊收起菜單(僅在 mode 爲 vertical 時可用) boolean falsewebpack

default-active default-active stringgit

default-openeds 當前打開的 sub-menu 的 index 的數組 Arrayweb

unique-opened 是否只保持一個子菜單的展開 boolean falsevue-router

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. 頁面開發~

相關文章
相關標籤/搜索