spring boot+iview 前后端分离架构之首页菜单栏的实现(二十二)

65 篇文章 5 订阅
42 篇文章 2 订阅

spring boot 与 iview 前后端分离架构之首页菜单栏的实现(二十二)

公众号

在这里插入图片描述
大家可以直接微信扫描上面的二维码关注我的公众号,然后回复【bg22】 里面就会给到源代码的下载地址同时会附上相应的视频教程,并定期在我的公众号上给大家推送相应的技术文章,欢迎大家关注我的公众号。

首页菜单栏

在第二十一章我们实现了首页的面包屑了,那么在这一章节我们将实现首页的菜单栏。

改造路由

由于我们之前的菜单的都是我们在页面上写死的,因此不会涉及到任何关于权限的问题,但是我们现在的菜单需要根据用户所拥有的菜单权限来动态展示,因此我们需要去改造我们原有的路由数据,router.js改造完成以后的代码如下:

import main from '../view/main/main.vue';

export default [
  {
    path: '/login',
    name: 'login',
    meta: {
      icon: 'ios-settings',
      hideInMenu: true,
      title: '登陆页',
      requireAuth: false //表示当前响应的请求是否需要进行登录拦截验证【true:需要;false:不需要】
    },
    component: resolve => {
      require(['../view/login/login.vue'], resolve);
    }
  },
  {
    path: '/main',
    name: 'main',
    meta: {
      icon: 'ios-settings',
      title: '系统首页',
      hideInMenu: true,
      requireAuth: false //表示当前响应的请求是否需要进行登录拦截验证【true:需要;false:不需要】
    },
    component: resolve => {
      require(['../view/main/main.vue'], resolve);
    }
  },
  {
    path: '/sys',
    name: 'sys',
    component: main,
    meta: {
      icon: 'ios-construct',
      title: '系统管理',
      code:'system-manage',
      requireAuth: true //表示当前响应的请求是否需要进行登录拦截验证【true:需要;false:不需要】
    },
    children:[
      {
        path: 'dictList',
        name: 'dictList',
        meta: {
          icon: 'ios-paper',
          title: '字典维护',
          code:'system-manage-dict',
          requireAuth: true //表示当前响应的请求是否需要进行登录拦截验证【true:需要;false:不需要】
        },
        component: resolve => {
          require(['../view/sys/dict/dictList.vue'], resolve);
        }
      },
      {
        path: 'treeList',
        name: 'treeList',
        meta: {
          icon: 'md-git-network',
          title: '菜单管理',
          code:'system-manage-tree',
          requireAuth: true //表示当前响应的请求是否需要进行登录拦截验证【true:需要;false:不需要】
        },
        component: resolve => {
          require(['../view/sys/tree/treeList.vue'], resolve);
        }
      },
      {
        path: 'roleList',
        name: 'roleList',
        meta: {
          icon: 'ios-cog',
          title: '角色管理',
          code:'system-manage-role',
          requireAuth: true //表示当前响应的请求是否需要进行登录拦截验证【true:需要;false:不需要】
        },
        component: resolve => {
          require(['../view/sys/role/roleList.vue'], resolve);
        }
      },
      {
        path: 'orgList',
        name: 'orgList',
        meta: {
          icon: 'ios-people',
          title: '用户组织',
          code:'system-manage-user',
          requireAuth: true //表示当前响应的请求是否需要进行登录拦截验证【true:需要;false:不需要】
        },
        component: resolve => {
          require(['../view/sys/user/orgList.vue'], resolve);
        }
      }
    ]
  }
]

引入用户的存储

在前面的二十一个章节登录的首页是直接调用后台的登录方法进行登录,登录成功以后就直接跳转到我们的首页,因此没有存储任何关于用户的信息,因此我们在store的module底下引入user.js来存储用户数据,代码如下:

import { login,getUserInfo } from '../../api/sys/user/user.api';
import router from '../../router/router';
import {getLoginMenuList} from '../../lib/util'

export default {
  state: {
    loginAccount: localStorage.getItem('loginAccount') ? localStorage.getItem('loginAccount') : '',
    nickName: localStorage.getItem('nickName') ? localStorage.getItem('nickName') : '',
    token: localStorage.getItem('token') ? localStorage.getItem('token') : '',
    headImg: localStorage.getItem('headImg') ? localStorage.getItem('headImg') : '',
    userId: localStorage.getItem('userId') ? localStorage.getItem('userId') : '',
    menuList: [],// 右侧的显示的菜单栏的数据
    access:localStorage.getItem('access') ? localStorage.getItem('access') : ''// 登录成功以后,当前用户拥有的权限的数据
  },
  getters: {
    token(state, getters, rootState) {
      return rootState.user.token;
    },
    menuList(state, getters, rootState){
      return getLoginMenuList(router, rootState.user.access);
    },
    nickName(state, getters, rootState){
      return rootState.user.nickName;
    }
  },
  mutations: {
    setLoginAccount(state, loginAccount) {
      state.loginAccount = loginAccount;
    },
    setHeadImg(state, headImg) {
      state.headImg = headImg;
    },
    setNickName(state, nickName) {
      state.nickName = nickName;
    },
    setToken(state, token) {
      state.token = token;
    },
    setUserId(state, userId){
      state.userId = userId;
    },
    setAccess(state,access){
      state.access = access;
    }
  },
  actions: {
    handleLogOut ({  commit }) {
      return new Promise((resolve, reject) => {
        localStorage.setItem('token',  '');
        commit('setToken', '');
        localStorage.setItem('headImg',  '');
        commit('setHeadImg', '');
        localStorage.setItem('nickName',  '');
        commit('setNickName',  '');
        localStorage.setItem('userId', '');
        commit('setUserId',  '');
        localStorage.setItem('loginAccount',  '');
        commit('setLoginAccount',  '');
        localStorage.setItem('access', []);
        commit('setAccess',  []);
      })
    },
    handleLogin ({ commit }, {loginAccount, loginPassword}) {
      loginAccount = loginAccount.trim();
      return new Promise((resolve, reject) => {
        login({
          loginAccount,
          loginPassword
        }).then(res => {
          if(res.code!=200){
            commit('setMsg', res.msg);
          }else{
            localStorage.setItem('token',  res.obj.token);
            commit('setToken', res.obj.token);
          }
          resolve(res);
        }).catch(err => {
          reject(err);
        });
      })
    },
    getUserInfo ({ state, commit }) {
      return new Promise((resolve, reject) => {
        getUserInfo({token:state.token}).then(res => {
          if(res.code==200){
            localStorage.setItem('headImg',   res.obj.headImg);
            commit('setHeadImg', res.obj.headImg);
            localStorage.setItem('nickName',   res.obj.nickName);
            commit('setNickName',  res.obj.nickName);
            localStorage.setItem('userId', res.obj.userId);
            commit('setUserId',  res.obj.userId);
            localStorage.setItem('loginAccount',  res.obj.loginAccount);
            commit('setLoginAccount',  res.obj.loginAccount);
            localStorage.setItem('access',  res.obj.access);
            commit('setAccess',  res.obj.access);
          }
          resolve(res);
        }).catch(err => {
          reject(err);
        });
      });
    }
  }
}

接着改造我们的store目录底下的index.js,改造完成以后代码如下:

import Vue from 'vue';
import Vuex from 'vuex';

import app from './module/app';
import user from './module/user';

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    //
  },
  mutations: {
    //
  },
  actions: {
    //
  },
  modules: {
    app,
    user
  }
});

登录页面改造

最后我们改造login.vue页面,由原来的直接登录改为如下的方式处理,改造完成以后代码如下:

<template>
  <div class="layout">
    <Layout>
      <Header class="layout-header" id="layout-header-scroll">
        <Menu mode="horizontal" theme="dark" active-name="1">
          <div class="layout-logo">
            [外链图片转存失败(img-2dt2NhIO-1562205026385)(https://mp.csdn.net/assets/logo.png)]
          </div>
          <div class="layout-nav">
            <language @on-lang-change="setLanguage" style="margin-right: 10px;" :lang="local"/>
          </div>
          <div class="layout-nav">
            <MenuItem name="1" style="font-size: 30px;">
              欢迎使用xxx系统
            </MenuItem>
          </div>
        </Menu>
      </Header>
      <Content :style="{ background: '#fff', minHeight: '500px'}">
        <div style="float: right;margin: 100px 100px 0">
          <Card title="欢迎登录XX系统">
            <Form ref="loginForm" :model="loginForm" :rules="loginFormRule">
              <div>
                <FormItem prop="loginAccount">
                  账号:<Input v-model="loginForm.loginAccount" prefix="ios-contact"
                            :placeholder="$t('login.loginAccount')"
                            style="width: 200px;"/>
                </FormItem>
                <FormItem prop="loginPassword">
                  密码:<Input v-model="loginForm.loginPassword" prefix="ios-compass" type="password"
                            :placeholder="$t('login.loginPassword')" style="width: 200px;"/>
                </FormItem>
              </div>
            </Form>
            <div style="margin-top: 20px;">
              <Button type="primary" @click="loginSystem" :long=true>登录</Button>
            </div>
          </Card>
        </div>
      </Content>
    </Layout>
  </div>
</template>
<script>
  import Language from '../../components/language';
  import {mapActions} from 'vuex';

  export default {
    components: {
      Language
    },
    data() {
      return {
        local: localStorage.getItem("lang"),
        loginForm: {
          loginAccount: '',
          loginPassword: ''
        },
        loginFormRule: {
          loginAccount: [
            {required: true, message: '请输入账号', trigger: 'blur'},
            {type: 'string', max: 30, message: '账号允许输入最大长度为30个字符', trigger: 'blur'}
          ],
          loginPassword: [
            {required: true, message: '请输入密码', trigger: 'blur'},
            {type: 'string', max: 50, message: '密码允许输入最大长度为50个字符', trigger: 'blur'}
          ]
        }
      }
    },
    methods: {
      ...mapActions([
        'handleLogin',
        'getUserInfo'
      ]),
      loginSystem() {
        this.$refs['loginForm'].validate((valid) => {
          // 输出加密结果
          if (valid) {
            this.handleLogin({
              loginAccount: this.loginForm.loginAccount,
              loginPassword: this.loginForm.loginPassword
            }).then(res => {
              if (this.token != '' && res.code == 200) {
                this.getUserInfo().then(res => {
                  if (res.code = 200) {
                    this.$router.push({
                      name: 'main'
                    })
                  }
                })
              } else {
                this.$Message.error('账号密码错误!');
              }
            })
          }
        })
      },
      setLanguage(lang) {
        this.local = lang
        localStorage.setItem('lang', lang)
      }
    },
    mounted() {
    }
  }
</script>
<style scoped>

  .layout-header {
    position: relative;
    z-index: 999;
    height: 60px;
  }

  .layout {
    border: 1px solid #d7dde4;
    background: #f5f7f9;
    position: relative;
    border-radius: 4px;
    overflow: hidden;
  }

  .layout-nav {
    width: auto;
    float: right;
    margin: 0 auto;
    margin-right: 20px;
  }

  .layout-logo {
    width: 100px;
    height: 30px;
    border-radius: 10px;
    float: left;
    position: relative;
    left: 20px;
    top: 5px;
  }
</style>

改造首页完成菜单的动态加载

最后我们队首页main.vue进行改造,改造完成以后代码如下:

<template>
  <div class="layout">
    <Layout>
      <!--  Header 表示头部的位置-->
      <Header id="layout-header-scroll">
        <Menu mode="horizontal" theme="dark" active-name="1" >
          <div class="layout-logo">
            [外链图片转存失败(img-6a2KNOej-1562205065166)(https://mp.csdn.net/assets/logo.png)]
          </div>
          <div class="layout-nav">
            <language @on-lang-change="setLanguage" style="margin-right: 10px;" :lang="local"/>
          </div>
          <div class="layout-nav">
            <template v-for="item in menuList">
              <Submenu :name="item.meta.code" v-if="item.children.length>0">
                <template slot="title">
                  <Icon :type="item.meta.icon"/>
                  {{item.meta.title}}
                </template>
                <template v-for="childrenItem in item.children">
                  <MenuItem :name=childrenItem.meta.code :to="item.path+'/'+childrenItem.path">
                    <Icon :type=childrenItem.meta.icon>
                    </Icon>
                    {{childrenItem.meta.title}}
                  </MenuItem>
                </template>
              </Submenu>
              <MenuItem :name="item.meta.code" v-else>
                <Icon :type="item.meta.icon"/>
                {{item.meta.title}}
              </MenuItem>
            </template>
          </div>
        </Menu>
      </Header>
      <!-- 此处表示的是左侧的菜单栏的布局 -->
      <Layout>
        <Sider hide-trigger :style="{background: '#fff'}">
          <Menu active-name="1-2" theme="light" width="auto" :open-names="['system-manage']">
            <template v-for="item in menuList">
              <Submenu :name=item.meta.code>
                <template slot="title">
                  <Icon :type=item.meta.icon></Icon>
                  {{item.meta.title}}
                </template>
                <template v-for="childrenItem in item.children">
                  <MenuItem :name=childrenItem.meta.code :to="item.path+'/'+childrenItem.path">
                    <Icon :type=childrenItem.meta.icon>
                    </Icon>
                    {{childrenItem.meta.title}}
                  </MenuItem>
                </template>
              </Submenu>
            </template>
          </Menu>
        </Sider>
        <Layout :style="{padding: '0 24px 24px'}">
          <!--  此处是面包屑导航条 -->
          <Breadcrumb :style="{margin: '24px 0'}">
            <BreadcrumbItem>
              <Icon type="ios-home-outline"></Icon>
              首页
            </BreadcrumbItem>
            <BreadcrumbItem v-for="item in breadCrumbList" v-bind:key="item.name" v-if="item.meta && item.meta.title">
              <Icon :type="item.icon"></Icon>
              {{showBreadcrumbItem(item)}}
            </BreadcrumbItem>
          </Breadcrumb>
          <!-- 此处存放的是文本内容的区域 -->
          <Content :style="{padding: '24px', minHeight: '280px', background: '#fff'}">
            <router-view/>
          </Content>
        </Layout>
      </Layout>
    </Layout>
  </div>
</template>
<script>
  import Language from '../../components/language';
  import {mapMutations} from 'vuex';

  export default {
    components:{
      Language
    },
    data() {
      return {
        local:localStorage.getItem("lang")
      }
    },
    methods:{
      ...mapMutations([
        'setBreadCrumb'
      ]),
      /**
       * 顶部跟随着滚动条的变化而滚动
       */
      handleScroll() {
        let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
        if (scrollTop >= 60) {
          document.querySelector('#layout-header-scroll').style.top = scrollTop + 'px';
        } else {
          document.querySelector('#layout-header-scroll').style.top = '0px';
        }
      },
      setLanguage(lang) {
        this.local = lang
        localStorage.setItem('lang',lang)
      },
      showBreadcrumbItem(item) {
        return (item.meta && item.meta.title) || item.name
      }
    },
    watch: {
      '$route'(newRoute) {
        this.setBreadCrumb(newRoute.matched)
      }
    },
    computed: {
      breadCrumbList() {
        return this.$store.state.app.breadCrumbList
      },
      menuList() {
        return this.$store.getters.menuList;
      }
    },
    mounted() {
      /**
       * 监听滚动条的滚动事件
       */
      window.addEventListener('scroll', this.handleScroll)
    }
  }
</script>
<style scoped>
  .layout-header {
    position: relative;
    z-index: 999;
    height: 60px;
  }

  .layout {
    border: 1px solid #d7dde4;
    background: #f5f7f9;
    position: relative;
    border-radius: 4px;
    overflow: hidden;
  }

  .layout-logo {
    width: 100px;
    height: 30px;
    border-radius: 10px;
    float: left;
    position: relative;
    left: 20px;
    top: 5px;
  }

  .layout-nav {
    width: auto;
    float: right;
    margin: 0 auto;
    margin-right: 20px;
  }
</style>

最后我们启动我们的程序,然后重新登录访问页面,大家会看到如下的页面,则说明我们的首页的菜单栏的动态加载就完成了。
在这里插入图片描述
到此为止我们就完成了首页面包屑的实现了。
上一篇文章地址:spring boot+iview 前后端分离架构之首页面包屑的实现(二十一)
下一篇文章地址:spring boot 与 iview 前后端分离架构之首页退出和修改密码的实现(二十三)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笨_鸟_不_会_飞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值