【VUE】demo03-VUE后台管理系统-vuex实现router动态路由+左侧sidebar+顶部breadcrumb

目录

1.8加入vuex

1.9使用vuex实现router动态路由

1.10修改动态面包屑


上两篇文章:

【VUE】demo01-VUE做后台管理系统页面实例-创建基本环境+页面布局

【VUE】demo02-VUE后台管理系统-axios接口测试+router动态路由

工具:Visual Studio Code + Vue + Vue cil2 + Vuex + Vue Router + ElementUI + axios

项目代码地址:macrozheng_mall学习: 学习macrozheng的mall项目,进行学习记录与代码拆解 - Gitee.com


1.8加入vuex

第一步先添加 vuex 的实例 store,在 src 文件夹中创建一个文件 store/index.js ,我们还需要添加 module ,再在 store 文件夹中创建两个文件 modules/permission.js(用来存储动态路由的)、modules/user.js(用来存储用户信息的),最后为了使用方便添加一个 store/getter.js 文件,是将我们 modules 里的所有 state 添加到一个 getter 对象,方便在不同页面获取 state 值,如下:

 代码:

//-----------store/index.js--------------------------
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
import permission from './modules/permission'
import getters from './getters'

Vue.use(Vuex)

//在这里创建 store 对象,并添加模块
const store = new Vuex.Store({
  modules: {
    user, //这里加入 user 的 module 对象,对应的就是 user.js 里的
    permission  //这里加入 permission 的 module 对象,对应的就是 permission.js 里的
  },
  getters //这里加入 getter 对象,对应的就是 getter.js 里的
})

export default store

//-----------store/modules/user.js--------------------------这里我们去掉了头像等不重要的内容,请酌情去除
import { login, logout, getInfo } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'

const user = {
  state: {
    token: getToken(),
    name: '',
    roles: []
  },
//同步
  mutations: {
    SET_TOKEN: (state, token) => {
      state.token = token
    },
    SET_NAME: (state, name) => {
      state.name = name
    },
    SET_ROLES: (state, roles) => {
      state.roles = roles
    }
  },
//异步
  actions: {
    // 登录,这里就可以代替我们在 login.vue 中点击登录的逻辑。
    Login({ commit }, userInfo) {
      const username = userInfo.username.trim()
      return new Promise((resolve, reject) => {
        login(username, userInfo.password).then(response => {
          const data = response.data
          const tokenStr = data.tokenHead+data.token
          setToken(tokenStr)
          commit('SET_TOKEN', tokenStr)
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    },

    // 获取用户信息,这里就可以代替我们在 1.7的 permission.js 中的 router 前置过滤器中 getInfo() 获取用户信息的逻辑 
    GetInfo({ commit, state }) {
      return new Promise((resolve, reject) => {
        getInfo().then(response => {
          const data = response.data
          if (data.roles && data.roles.length > 0) { // 验证返回的roles是否是一个非空数组
            commit('SET_ROLES', data.roles) //在 router 前置过滤器中获取并判断是否已添加动态路由
          } else {
            reject('getInfo: roles must be a non-null array !')
          }
          commit('SET_NAME', data.username)
          resolve(response)
        }).catch(error => {
          reject(error)
        })
      })
    },

    // 登出
    LogOut({ commit, state }) {
      return new Promise((resolve, reject) => {
        logout(state.token).then(() => {
          commit('SET_TOKEN', '')
          commit('SET_ROLES', [])
          removeToken()
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    },

    // 前端 登出
    FedLogOut({ commit }) {
      return new Promise(resolve => {
        commit('SET_TOKEN', '')
        removeToken()
        resolve()
      })
    }
  }
}

export default user


//-----------store/modules/permission.js--------------------------
import { asyncRouterMap, constantRouterMap } from '@/router/index';

//判断是否有权限访问该菜单
function hasPermission(menus, route) {
  ...
}

//根据路由名称获取菜单
function getMenu(name, menus) {
  ...
}

//对菜单进行排序
function sortRouters(accessedRouters) {
  ...
}

//降序比较函数
function compare(p){
  ...
}

const permission = {
  state: {
    routers: constantRouterMap, //总路由信息,先默认为静态路由
    addRouters: [] //添加的路由
  },
  mutations: { //同步,安全
    SET_ROUTERS: (state, routers) => {
      state.addRouters = routers;
      state.routers = constantRouterMap.concat(routers);
    }
  },
  actions: {  //异步,不安全
    // 获取动态路由,这里就可以代替我们在 1.7的 permission.js 中的 router 前置过滤器中 getInfo().then 获取动态路由的逻辑 
    GenerateRoutes({ commit }, data) { 
      return new Promise(resolve => {
        const { menus } = data;
        const { username } = data;
        const accessedRouters = asyncRouterMap.filter(v => {
          //admin帐号直接返回所有菜单
          // if(username==='admin') return true;
          if (hasPermission(menus, v)) {
            if (v.children && v.children.length > 0) {
              v.children = v.children.filter(child => {
                if (hasPermission(menus, child)) {
                  return child
                }
                return false;
              });
              return v
            } else {
              return v
            }
          }
          return false;
        });
        //对菜单进行排序
        sortRouters(accessedRouters);
        commit('SET_ROUTERS', accessedRouters); //最终都要调用同步方法设置 vuex 的 data 属性。
        resolve();
      })
    }
  }
};

export default permission;



//-----------store/getter.js--------------------------
//里面的 state 值都是 module 里面的 state 对应的,
const getters = {
  //例如: token 是我们通过 $store.getters.token 使用的, state.user.token 对应的就是 user.js 里面的 token 值
  token: state => state.user.token,  
  name: state => state.user.name,
  roles: state => state.user.roles,
  addRouters: state => state.permission.addRouters,
  routers: state => state.permission.routers
}
export default getters

第二步 ,将 store 添加到 main.js 里面

//---------main.js-------------
...
import store from './store' //添加进去
...
new Vue({
  el: '#app',
  router,store, //添加进去
  components: { App },
  template: '<App/>'
})

之后我们在其余页面就可以通过下面的方式获取存入的 state 数据:

方式一:--------store对象------------
import store from './store' //添加进去
store.getters.***

方式二:--------getter对象-----------
{{routes}}

import { mapGetters } from "vuex";

export default {
  //通过几个数据的变化,来影响一个数据,
  computed: {
    ...mapGetters(["routers"]), //routers是 getter 的变量名 
    routes() {
      return this.routers;
    },

  },

};

如果我们要添加或者设置 state 数据,就通过 store.dispatch('[actions名]').then (异步)来调用方法,然后在这个 action 里面通过commit('[mutations名]') (同步)来修改 state 变量。

 添加完毕,我们实现一下 router 动态路由测试一下吧!

1.9使用vuex实现router动态路由

第一步,先获取到路由信息,也就是先修改router的前置过滤器,打开 src/permission.js 文件,将原来 if(!getHaveAuth()){ ...}这里的内容换成调用 store 的action 的内容,

//----src/permission.js-----------

//去掉 function hasPermission,getMenu,sortRouters,compare方法,这些直接在 store 的 permission 中使用
import store from './store' //使用前先引入

router.beforeEach((to, from, next) => {
... //将原来 21行 - 61行 的内容换成下面的内容
    //这里需要加个判断哈,如果添加了当前用户的路由就不需要再次添加了,否则程序会警告重复路由。
    //getHaveAuth()这个也是保存到了 cookie 中,我直接写在了 auth.js 里面
      if (store.getters.roles.length === 0) {   //roles 是在 store 的 GetInfo action 里面加入到 cookie 的。 
        store.dispatch('GetInfo').then(res => { // 拉取用户信息,将 menu username 等传给 GenerateRoutes action
          let menus = res.data.menus;
          let username = res.data.username;
          store.dispatch('GenerateRoutes', { menus, username }).then(() => { // 生成可访问的路由表
            router.addRoutes(store.getters.addRouters); // 动态添加可访问路由表
            next({ ...to, replace: true })
          })
        }).catch((err) => { //如果上面的失败了,就需要退出操作,清除 token 值,防止登陆成功但没有权限
          store.dispatch('FedLogOut').then(() => {
            Message.error(err || 'Verification failed, please login again')
            next({ path: '/' })
          })
        })
      } else {
        next()
      }
...

}

第二步,修改登录时的 token 保存,在  login.js 文件中的 handleLogin() 方法进行修改

//-----login.js-----------
//这里引入cookie通用方法来保存 username ,passwd 。注意password最好不要保存在前端!!!
import {setSupport,getSupport,setCookie,getCookie} from '@/utils/support';

    //登陆核心
     handleLogin() {
      this.$refs.loginForm.validate((valid) => {
        if (valid) {
          this.loading = true;
          //将 110 - 122 行的代码替换成下面的
          this.$store.dispatch('Login', this.loginForm).then(() => {
            //异步调用成功之后会执行 then
            this.loading = false;
            setCookie("username",this.loginForm.username,15);
            setCookie("password",this.loginForm.password,15);
            this.$router.push({path: '/home'})
          }).catch(() => {
            this.loading = false
          })
        } else {
          console.log("参数验证不合法!");
          return false;
        }
      });

 第三步,修改左侧导航栏 sidebar 的内容,这里需要从 store 中获取数值

//------sidebar/index.vue------------
<template>
  <el-menu
    mode="vertical"
    :show-timeout="200"
    :default-active="$route.path"
    background-color="#304156"
    text-color="#bfcbd9"
    active-text-color="#409EFF"
  >
    <sidebar-item :routes="routes"></sidebar-item>
  </el-menu>
</template>

<script>
import SidebarItem from "./SidebarItem";
import { mapGetters } from "vuex";

export default {
  components: { SidebarItem },
  //通过几个数据的变化,来影响一个数据,如果使用 data ,那store改变时就无法影响这里了
  computed: {
    ...mapGetters(["routers"]),
    routes() {
      // return this.$router.options.routes
      return this.routers;
    },
  },
  //注意不能用下面这种方式!!!
  // data(){
  //   return{
  //     ...mapGetters(["routers"]),
  //     routes:this.routers
  //   }
  // }
};
</script>

最后,我们测试一下,访问 http://localhost:8080/#/home 接口,直接跳转到 login 路由,输入用户名密码,跳转到 /home 页面,动态导航栏添加成功!!!

 

1.10修改动态面包屑

这里我们也添加一下面包屑吧,具体的按照什么格式展示看业务了,这里我们按照 mall 系统提供的导航栏展示,不过哪一个页面最前端都显示首页,之后跟着一级导航栏和二级导航,二级导航这里还显示其余的页面的内容,也就是 router 中一级导航的 child 页面,

例如:首页/权限/用户列表 ,是二级导航用户列表的面包屑,而他的其余页面 新增用户页面 的面包屑就是 首页/权限/添加用户。

第一步,在 src/component 文件夹中添加 Breadcrumb/index.vue 文件,里面封装 elementUI 的面包屑的组件,先获取当前路由的信息,然后将我们需要展示的路由信息拼接在一起:

//-------breadcrumb/index.vue-------------------------
<template>
  <el-breadcrumb class="app-breadcrumb" separator="/">
    <transition-group name="breadcrumb">
      <!-- 当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级,这意味着 v-if 将分别重复运行于每个 v-for 循环中 -->
      <!-- 这里是拿到当前访问的路由信息,然后获取到他的父级路由信息(如果有的话),最后将首页拼到最前面,后面跟着父级和自己的name -->
      <el-breadcrumb-item v-for="(item,index)  in levelList" :key="item.path" v-if="item.meta.title">
        <span v-if="item.redirect==='noredirect'||index==levelList.length-1" class="no-redirect">{{item.meta.title}}</span>
        <router-link v-else :to="item.redirect||item.path">{{item.meta.title}}</router-link>
      </el-breadcrumb-item>
    </transition-group>
  </el-breadcrumb>
</template>

<script>
export default {
  created() {
    this.getBreadcrumb()
  },
  data() {
    return {
      levelList: null
    }
  },
  //1.watch擅长处理的场景:一个数据影响多个数据(修改主数据的时候,方法中影响相应的数据)

// 2.computed擅长处理的场景:一个数据受多个数据影响(修改某数据的时候,若有主数据,会影响主数据的)
  watch: {
    $route() {
      this.getBreadcrumb()
    }
  },
  methods: {
    //拿到当前路由页面的信息,如果不是首页,就将首页的信息添加到第一个
    getBreadcrumb() {
      let matched = this.$route.matched.filter(item => item.name)
      const first = matched[0]
      if (first && first.name !== 'home') {
        matched = [{ path: '/home', meta: { title: '首页' }}].concat(matched)
      }
      this.levelList = matched
    }
  }
}
</script>

<style rel="stylesheet/scss" lang="scss" scoped>
  .app-breadcrumb.el-breadcrumb {
    display: inline-block;
    font-size: 14px;
    line-height: 50px;
    margin-left: 10px;
    .no-redirect {
      color: #97a8be;
      cursor: text;
    }
  }
</style>


第二步,在 Navbar.vue 中引入面包屑组件

 //-------Navbar.vue---------------------
<template>
  <el-menu class="navbar" mode="horizontal">
    <breadcrumb></breadcrumb>
  </el-menu>
</template>
<script>
import Breadcrumb from '@/components/Breadcrumb'
export default {
  components: {
    Breadcrumb,
  }
}
</script>

<style rel="stylesheet/scss" lang="scss" scoped>
.navbar {
  height: 50px;
  line-height: 50px;
  border-radius: 0px !important;
}
</style>

添加完毕,我们直接打开就会发现顶部的面包屑

 

okkkk,到目前为止,所有的大类环境都已经添加完毕,自己对 vue 和相关组件的使用也相对的熟悉了一些,之后就是拿项目练手啦,加油吧少年~~~///(^v^)\\\~~~。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值