3.使用ElementUI搭建前端系统框架三

本节内容是参数化布局管理器的菜单和响应事件,需要用到新技术Vuex,Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化,Vuex细节参考https://vuex.vuejs.org/zh/,我们在此只做简单介绍,重点是如何在前端项目中应用Vuex。

使用VUE设计前端项目,各组件的数据是彼此分离的,无法很好地共享数据,使用Vuex可以在不同vue组件间共享数据。

vuex包括state,mutation,action,getter,module。
state
用于存储数据,可以存储扁平和树状结构对象,各组件间使用全局变量可以共享state数据,同时支持mapState映射所有的数据变量

const state = {
    token: '',
    navList: [],
    permissionList:[],
    openedList:[]
}

mutation
用于修改state数据,vue组件必须使用mutation才可以修改vuex中的数据。mutation相对于计算属性,它的第一个参数必须是state,第二个参数是赋值对象

const mutations = {
    setToken: (state, data) => {
        state.token = data
    },
    setNavList: (state, data) => {
        state.navList = data
        sessionStorage.setItem('navList', data);
    },
    setPermissionList: (state, data) => {
        state.permissionList = data
        sessionStorage.setItem('permissionList', data);
    },
    addPage(state, data){
        if (state.openedList.some(v => v.path === data.path)) 
            return
        data.visiable=true
        data.url = data.path
        state.openedList.push(data)
    },
    removePage(state, data){
        if(data){
            for(let [i, v] of state.openedList.entries()){
                if(v.path === data.path){
                    state.openedList.splice(i, 1)
                }
            }
        } else{
            state.openedList = []        
        }
    }
}

在vue组件中调用mutation必须使用commit方法

this.$store.commit("auth/removePage", item)

action
Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。
    Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。
    也可以第一个参数定义为commit,同时支持mapAction映射。
const actions = {
    getNavList({commit}){
        return new Promise((resolve) =>{
            axios({
                url: '/acllist',
                methods: 'post',
                data: {}
            }).then((res) => {
                commit("setNavList", res.navList)
                commit("setPermissionList", res.permissionList)
                resolve(res)
            })
        })
    },
    login({ commit }, userInfo) {
        return new Promise((resolve) => {
            axios({
                url: '/login',
                method: 'post',
                data: {
                    ...userInfo
                }
            }).then(res => {
                if(res && res.login){
                    commit('setToken', res.token)
                    sessionStorage.setItem('isLogin', res.login);
                    localStorage.setItem('userId', res.uid);
                    localStorage.setItem('orgName', res.orgname);
                    localStorage.setItem('userName', res.name);
                    localStorage.setItem('token', res.token);
                }
                resolve(res)
            })
        });
    },

getter
访问vuex 状态数据的一种方式,可以对数据进行过滤处理。支持mapGetter操作

const getters = {
    getNavList: state => {
        return sessionStorage.getItem('navList');
    },
    getPermissionList: state => {
        return sessionStorage.getItem('permissionList');
    }
}

module
当数据结构比较复杂是,vuex支持使用module分组存储

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态

使用Vuex步骤

1、新建目录/src/store
2、分module存储vuex状态数据,新建/src/store/modules/auth存储认证相关数据
3、在/src/store/modules/auth目录新建index.js文件,定义vuex中规定的组件state,mutation,action等,这个文件比较大,后面介绍。
4、在/src/store/modules/目录下新建index.js,引入模块auth中定义组件

import auth from './auth'

export default {
    auth: auth
}

5、在/src/store目录下新建index.js,声明vuex

import Vue from 'vue'
import Vuex from 'vuex'
import vuexModules from './modules'

Vue.use(Vuex)

export default new Vuex.Store({
    modules: vuexModules
})

6、在/src/main.js中引入vuex全局变量store
import store from ‘./store’

扫描二维码关注公众号,回复: 9324481 查看本文章
// 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'
import ElementUI from 'element-ui'
import store from './store'

// 引入CSS
import 'static/css/gf-default.scss'

Vue.config.productionTip = false

Vue.use(ElementUI)

/* eslint-disable no-new */
//new Vue({
//  el: '#app',
//  router,
//  components: { App },
//  template: '<App/>'
//})

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

第三步中定义的vuex组件

import Cookies from 'js-cookie'

const state = {
    token: '',//登录成功后获取的令牌
    navList: [],//菜单对象集合
    permissionList:[],//授权访问菜单对象集合
    openedList:[]//在TabPane中打开的页面集合
}

const getters = {
    getNavList: state => {
        return sessionStorage.getItem('navList');
    },
    getPermissionList: state => {
        return sessionStorage.getItem('permissionList');
    }
}

const mutations = {
    //登录成功后设置token,第一个参数为state对象,第二个是赋值对象
    setToken: (state, data) => {
        state.token = data
    },
    //登录成功后,从后台获取菜单列表
    setNavList: (state, data) => {
        state.navList = data
        sessionStorage.setItem('navList', data);
    },
    //登录成功后,从后台获取授权访问菜单列表
    setPermissionList: (state, data) => {
        state.permissionList = data
        sessionStorage.setItem('permissionList', data);
    },
    //当点击左侧导航菜单时,向openedList中添加页面,TabPane组件上会相应显示
    addPage(state, data){
        if (state.openedList.some(v => v.path === data.path)) 
            return
        data.visiable=true
        data.url = data.path
        state.openedList.push(data)
    },
    //当点击TabPane组件上页面的关闭图标,从openedList中删除页面,TabPane组件上会相应显示
    removePage(state, data){
        if(data){
            for(let [i, v] of state.openedList.entries()){
                if(v.path === data.path){
                    state.openedList.splice(i, 1)
                }
            }
        } else{
            state.openedList = []        
        }
    }
}

const actions = {
    //支持异步操作,通过Ajax从后台获取菜单数据
    getNavList({commit}){
        return new Promise((resolve) =>{

        })
    },
    //登录操作
    login({ commit }, userInfo) {
        return new Promise((resolve) => {

        });
    },
    logout({commit}) {
        return new Promise((resolve) => {
            sessionStorage.removeItem('isLogin');
            localStorage.removeItem('userId'); 
            localStorage.removeItem('orgName'); 
            localStorage.removeItem('userName'); 
            localStorage.removeItem('token');  
            resolve()
        })
    },
}

export default {
    namespaced: true,
    state,
    mutations,
    actions
}

在这里插入图片描述
在/src/main.js中定义vuex全局访问变量store
修改LeftPane.vue组件
在这里插入图片描述

<template>
    <div class="sidediv">
      <div @mouseenter="fn" class="floatdiv">
        <div style="margin-left:0.3rem;margin-top:1.875rem">
          <img src="~static/images/demo.png" alt style="width:25px;" />
        </div>
        <div style="margin-left:0.3rem;margin-top:1.875rem">
          <img src="~static/images/demo2.png" alt style="width:25px;" />
        </div>
        <div style="margin-left:0.3rem;margin-top:1.875rem">
          <img src="~static/images/demo3.png" alt style="width:25px;" />
        </div>
        <div style="margin-left:0.3rem;margin-top:1.875rem">
          <img src="~static/images/demo4.png" alt style="width:25px;" />
        </div>
        <div style="margin-left:0.3rem;margin-top:1.875rem">
          <img src="~static/images/demo5.png" alt style="width:25px;" />
        </div>
        <div style="margin-left:0.3rem;margin-top:1.875rem">
          <img src="~static/images/demo6.png" alt style="width:25px;" />
        </div>
      </div>
      <div class="fly" v-show="hidde" @mouseleave="leave()">
        <div style="height:25px"></div>
        <ul class="uls">
          <li v-for="(item,i) in arr" @click="fun(i)">{{item.name}}</li>
        </ul>
      </div>

      <div class="menudiv">
        <el-menu
          router
          ref="navbar"
          :default-active="defActive"
          :mode="navMode"
          menu-trigger="click"
          @select="selectMenu"
          @open="openMenu"
          @close="closeMenu"
          unique-opened>
          <div class="nav_css">
            <left-menu  v-for="(item, n) in navList" :item="item" :navIndex="String(n)" :key="n"></left-menu>
          </div>
        </el-menu>
      </div>
    </div>
</template>
<style scoped>
.sidediv{
  
}
.menudiv {
  z-index: -1;
  margin-left: 2.4rem;
  width: 7rem;
  height: 100%;
  overflow-x: hidden;
  overflow-y: auto;
}
.nav_css{
  max-height:33rem;
  overflow: hidden;
  width: 9rem;
  overflow-y: scroll;
  text-align:left;
  line-height:33px;
  border:0;
}
.floatdiv {
  position: absolute;
  width: 2.4rem;
  height: 100%;
  background: #001529;
  float: left;
}
.fly {
  width: 7rem;
  height: 100%;
  float: left;
  background: #001529;
  position: absolute;
  left: 2.4rem;
  z-index: 100;
}
.uls li {
  width: 6rem;
  height: 40px;
  line-height: 40px;
  padding-left: 5px;
  margin-bottom: 14.5px;
  color: white;
  cursor:pointer;
}
</style>
<script>
import { mapState } from "vuex";
import LeftMenu from "./LeftMenu";

export default {
  data() {
    return {
      navBgShow: false,
      hidde: false,
      tagNavList: [],
      hometag: {},
      currenttag: {},
      arr: [
        { name: "待办工作" },
        { name: "关闭页签" },
        { name: "导航切换" }
      ],
    };
  },
  props: ["layout"],
  computed: {
    ...mapState({
      navList: state => state.auth.navList
    }),
    defActive() {
      return this.$route.path;
    },
    navMode() {
      if (this.layout == "left") {
        return "vertical";
      }
      if (this.layout == "top") {
        return "horizontal";
      }
    },
    isDark() {
      return true;
    }
  },
  watch: {
    $route() {

    }
  },
  methods: {
    fn() {
      this.hidde = !this.hidde;
    },
    leave() {
      this.hidde = false;
    },
    fun(i) {
      if (i == 0) {
        this.$router.push("/home");
        this.hidde = false;
      }else if (i == 1) {
         this.closeAllTag();
      }else if (i == 2) {
         this.changeMenu();
      }

      // this.$router.push('/'+this.arr.url[i])
    },
    selectMenu(index, indexPath) {
      let openedMenus = this.$refs.navbar.openedMenus;
      let openMenuList;
      if (indexPath.length > 1) {
        let parentPath = indexPath[indexPath.length - 2];
        openMenuList = openedMenus.slice(openedMenus.indexOf(parentPath) + 1);
      } else {
        openMenuList = openedMenus;
      }
      openMenuList = openMenuList.reverse();
      openMenuList.forEach(ele => {
        this.$refs.navbar.closeMenu(ele);
      });
      if (this.navMode == "horizontal") {
        this.navBgShow = false;
      }
    },
    openMenu() {
      if (this.navMode == "horizontal") {
        this.navBgShow = true;
      }
    },

    closeAllTag() {
      this.hometag={};
      this.currenttag={};
      this.tagNavList = this.$store.state.tagNav.openedPageList;

      this.tagNavList.forEach(item =>{
          if (item.path == '/home'){
            this.hometag = item;
          }

          if (item.path == this.$route.path){
            this.currenttag = item;
          }
      })

      this.$store.commit("tagNav/removeTagNav", null);
      this.$store.commit("tagNav/addTagNav", this.hometag);
      this.$store.commit("tagNav/addTagNav", this.currenttag);
    },

    changeMenu() {
      if (this.isBig) {
        this.isBig= false;
      }else{
        this.isBig= true;
      }

      if (this.isSmall) {
        this.isSmall= false;
      }else{
        this.isSmall= true;
      }
    },

    closeMenu() {
      if (this.navMode == "horizontal") {
        this.navBgShow = false;
      }
    },
    closeAll() {
      let openMenu = this.$refs.navbar.openedMenus.concat([]);
      openMenu = openMenu.reverse();
      openMenu.forEach(ele => {
        this.$refs.navbar.closeMenu(ele);
      });
      if (this.navMode == "horizontal") {
        this.navBgShow = false;
      }
    }
  },
  components: { LeftMenu }
};
</script>

修改TabPane.vue组件
在这里插入图片描述

<template>
    <div class="tag-nav">
        <tab-scroll-bar ref="scrollBar">
            <router-link ref="tag" class="tag-nav-item"
                :class="isActive(item) ? 'cur' : ''" v-for="(item, index) in openedList" 
                :to="item.path" :key="index">
                <span class='ivu-tag-dot-inner'></span>
                    {{item.title}}
                <span class='el-icon-close' @click.prevent.stop="closePage(item, index)"></span>
            </router-link>
        </tab-scroll-bar>
    </div>
</template>

<script>
import TabScrollBar from './TabScrollBar'

export default {
    data(){
        return {
            defaultPage: '/gf/home'
        }
    },
    computed: {
        openedList(){
            return this.$store.state.auth.openedList;
        }
    },
    mounted(){
        //this.openPage()
    },
    watch: {
        $route(){
            this.openPage()
            this.scrollToCurPage()
        }
    },
    methods: {
        openPage(){
            if(this.$router.getMatchedComponents()[1])
            {
                this.$store.commit("auth/addPage", {
                    name: this.$router.getMatchedComponents()[1].name,
                    path: this.$route.path,
                    title: this.$route.meta.name
                })
            }
        },
        isActive(item){
            return item.path === this.$route.path
        },
        closePage(item, index){
            this.$store.commit("auth/removePage", item)
            if(this.$route.path == item.path){
                if(index){
                    this.$router.push(this.openedList[index-1].path)
                } else {
                    this.$router.push(this.defaultPage)
                    if(this.$route.path == "/gf/home"){
                        this.openPage()
                    }
                }
            } 
        },
        scrollToCurPage(){
            this.$nextTick(() =>{
                for (let item of this.$refs.tag) {
                    if (item.to === this.$route.path) {
                        this.$refs.scrollBar.scrollToCurPage(item.$el)
                        break
                    }
                }
            })
        }
    },
    components: {TabScrollBar}
}
</script>

修改/src/store/modules/auth/index.js
在这里插入图片描述
路由需要定义beforeEach事件,处理页面添加逻辑
在这里插入图片描述

import Vue from 'vue'
import Router from 'vue-router'
import staticRoute from './staticRoutes'
import store from '../store'
import NProgress from 'nprogress'

Vue.use(Router)

const router = new Router({
  mode: 'hash',
  routes: staticRoute
})


const whiteList = [
  '/login',
  '/sso',
  '/logout',
]

let permissionList = []

function initRoute(router){
  return new Promise((resolve) => {
    store.dispatch('auth/getNavList').then((res) => {
      permissionList = res.permissionList;
      permissionList.forEach(function(v)
      {
        if(v.url)
        {
          let routeItem = router.match(v.url)
          if(routeItem){
            routeItem.meta.permission = v.permission ? v.permission : []
            routeItem.meta.name = v.name
          }
        }
        resolve()
      })
    })
  })
}

router.beforeEach((to, from, next) => {
  NProgress.start();
  if (true) {
    console.log('登录成功 from='+from.path+',to='+to.path);
    if (to.path === '/login') {
      next({path: "/gf/home", replace: true})
    } else if(to.path.indexOf("/error") >= 0){
        next()
    } else {
      initRoute(router).then(() => {
        let isPermission = false
        permissionList.forEach((v) => {
          if(v.url == to.fullPath){
            isPermission = true
          }
        })
        isPermission = true;
        if(!isPermission){
          next({path: "/error/401", replace: true})
        } else {
          next()
        }
      })
    }
  } else {
    console.log('未登录 from='+from.path+',to='+to.path);
    if (whiteList.indexOf(to.path) >= 0) {
      next()
    } else {
      console.warn('当前未处于登录状态,请登录')
      next({path: "/login", replace: true})
      NProgress.done()
    }
  }
})

router.afterEach(() => {
  NProgress.done(); // 结束Progress
})

export default router

刷新网页
在这里插入图片描述
新建产品管理组件,/src/product/prdtlist.vue
在这里插入图片描述
添加路由
在这里插入图片描述
修改/src/store/modules/auth/index.js
定义菜单集合,此时还没有从后台获取
在这里插入图片描述
刷新页面可以演示效果

在这里插入图片描述
gfvue_v1.4.zip 代码下载

链接:https://pan.baidu.com/s/14hO3ZZv6KIcp1FmKEb0Dzg
提取码:o445

发布了189 篇原创文章 · 获赞 34 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/qixiang_chen/article/details/104428828