用Spring Boot+Vue做微人事项目第七天

 用Spring Boot+Vue做微人事项目系列目录

 用Spring Boot+Vue做微人事项目第七天

前两天做了微人事登录的前端页面和后端接口,第三天则实现了前后端接口的对接,输入正确的用户名和密码之后,成功的跳转到home页。第四天做了Home页的Title制作和下拉菜单,下拉菜单有三个选项,个人中心、设置和注销登录,还做了注销登录,点击注销登录会出现提示:“此操作将注销登录,是否继续”,点是就重新跳转到登录页面,第五天做的是左边的导航菜单,第六天是做的服务端菜单接口的设计,现在是Vuex的介绍、安装和配置了

菜单数据不仅仅是要在Home.vue,Login.vue里面使用,还需要在很多很多vue页面里面使用,所以要放在一个所有vue文件都可以访问的地方。Home.vue和Login.vue里面的data只能在各自的页面使用 ,这些data都是局部变量,我要做的事是把加载后的数据放到一个公共的地方,不管是Home.vue还是其他组件都能访问到的地方,放到sessionStoranglocalStorage里面是可以访问到的,但是还可以放到vuex

什么时候需要状态管理?

当我们的项目很大的时候,这个vuex肯定是不可避免需要的,因为我的这些.vue组件它不可能是完全独立的,互相之间肯定有互相调用的。

如下面的V部落图片里面的"已发表","草稿箱","回收站"这三个里面的数据其实在数据库里面加载的是同一张表,只不过表里面有一个记录状态码不一样

什么是Vuex?

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。

 

Vuex如何安装?

使用npm install vuex命令进行下载安装

Vuex的配置

①.首先在src目录里面创建一个store目录,然后在store目录里面创建index.js

②.首先要导入vue对象和vuex对象,然后用vue对象.use使用vuex

 

//导入vue对象
import Vue from 'vue'
//导入vuex
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
    //定义变量的
    state:{
        //这是我的路由数组,一会从服务器上加载下来的菜单项都放在这个数组里面去
        routes:[]
    },
    //修改变量的方法  想当于是java中的setter方法
     mutations:{
    //通过方法放   state:要通过state才可以访问到上面的state变量里面的routes数组   data:传进来的参数  等下去调用只需要传data参数即可
        initRoutes(state,data){
            state.routes=data;
        }
    },
    actions:{

    }

})

定义好之后还不能直接使用,还需要在mian.js里面使用导入语句导入进来

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import {postKeyValueRequest} from "./utils/api";
import {postRequest} from "./utils/api";
import {getRequest} from "./utils/api";
import {putRequest} from "./utils/api";
import {deleteRequest} from "./utils/api";
import {initMenu} from "./utils/menus";


Vue.prototype.postRequest=postRequest;
Vue.prototype.postKeyValueRequest=postKeyValueRequest;
Vue.prototype.getRequest=getRequest;
Vue.prototype.putRequest=putRequest;
Vue.prototype.deleteRequest=deleteRequest;
Vue.config.productionTip = false

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

菜单请求工具类封装   

菜单的请求方法包括前端数据的格式化

原因:服务端的菜单数据已经写好了,但是不能直接给前端用,因为服务端数据中的component是一个字符串,而我们在router里面定义的component都是导入的对象。所以需要写一个菜单请求工具类

utils文件夹里面新建一个menu.js文件

①.把getRequest方法导入进来

②.定义一个initMenu菜单初始化的方法,传两个参数进来,一个是router:因为菜单格式化好后要加到router里面来,所以要把router对象    一个是store:这个数据我还要往store保存一份

import {getRequest} from "./api";

//工具类方法
//定义一个菜单初始化的方法  参数router:因为菜单格式化好后要加到router里面来,所以要把router对象给我  store:这个数据我还要往store保存一份
export const initMenu = (router, store) => {
    if (store.state.routes.length > 0) {
        return;
    }
    //url:请求接口   data:请求回来的数据
    getRequest("/system/config/menu").then(data => {
        if (data) {
            //定义一个fmtRoutes 这是格式化之后的
            let fmtRoutes = formatRoutes(data);
            //把router add 到fmtRoutes里面来
            router.addRoutes(fmtRoutes);
            //通过store.commit 第一个参数是方法的名字  第二个是格式化之后的fmtRoutes
            store.commit('initRoutes', fmtRoutes)
        }
    })
}

//格式化
export const formatRoutes = (routes) => {
    //这是我一会要返回去的数据
    let fmRoutes = [];
    routes.forEach(router => {
        //这是一个批量的变量定义
        let {
            path,
            component,
            name,
            meta,
            iconCls,
            children
        } = router;
        //判断是不是一个的children
        if (children && children instanceof Array) {
            //递归调用给它格式化一下
            children = formatRoutes(children);
        }
        let fmRouter = {
            path: path,
            name: name,
            iconCls:iconCls,
            meta: meta,
            children: children,
            //要动态的去加载component  当成一个方法来处理,里面有一个参数叫resolve
            component(resolve) {
                if (component.startWith("Emp")) {
                    require(['../views/emp/' + component + '.vue'], resolve);
                } else if (component.startWith("Per")) {
                    require(['../views/per/' + component + '.vue'], resolve);
                } else if (component.startWith("Sal")) {
                    require(['../views/sal/' + component + '.vue'], resolve);
                } else if (component.startWith("Sta")) {
                    require(['../views/sta/' + component + '.vue'], resolve);
                } else if (component.startWith("Sys")) {
                    require(['../views/sys/' + component + '.vue'], resolve);
                }
                //这是ES5里面的写法  []:请求组件的地址
            }
        }
        fmRoutes.push(fmRouter);
    })
    return fmRoutes;
}

根据数据库的component把其他的vue页面也创建出来

 左边导航菜单加载

工具类的方法已经写好了,那么什么时候去调用它?有人说登录之后调用 ,这个是没问题的,但是用户按了F5或者是浏览器上的刷新按钮,所有的菜单都没了,这个是不行的。

解决方法:可以使用vue的路由导航守卫

全局前置守卫

你可以使用router.beforeEach注册一个全局前置守卫

const router = new VueRouter({...})

router.beforeEach((to,from,next) = > ){
    //...
}

当一个导航触时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫resolve完之前一直处于等待中

每个守卫方法接收三个参数:

  • to: Route:即将要进入的目标 路由对象
  • from:Route:当前导航正要离开的路由
  • next:Function:一定要调用该方法resolve这个钩子,执行效果依赖next方法的调用参数

因为这个是全局前置守卫,所以要写在main.js文件里面,写在Vue.use(ElementUI)后面

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import {postKeyValueRequest} from "./utils/api";
import {postRequest} from "./utils/api";
import {getRequest} from "./utils/api";
import {putRequest} from "./utils/api";
import {deleteRequest} from "./utils/api";
import {initMenu} from "./utils/menus";
import 'font-awesome/css/font-awesome.min.css'


Vue.prototype.postRequest=postRequest;
Vue.prototype.postKeyValueRequest=postKeyValueRequest;
Vue.prototype.getRequest=getRequest;
Vue.prototype.putRequest=putRequest;
Vue.prototype.deleteRequest=deleteRequest;
Vue.config.productionTip = false

Vue.use(ElementUI)

router.beforeEach((to,from,next)=>{
  //如果你要去的页面是login页,直接next
  if (to.path=='/'){
    next();
  }else {
    //如果你要去的是其他页面呢,就是用initMenu初始化菜单的方法
    initMenu(router,store);
    next();
  }
})
new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

如果要去的是login页面,直接next,如果要去的是其他页面,就要使用inintMenu方法进行初始化,我们之前就在menus.js里面进行判断了,数据判断的之后,我都会把数据保存在store里面,所以一上来我就先判断store里面有没有数据,如果store里面有数据的话,那就说明这是一次正常的跳转,不是用户按了F5的刷新,正常的跳转后面的getRequest请求就不用执行了

这个initMenu方法执行了之后,这个数据现在就保存在store的routes里面去了,现在就可以在Home.vue里面使用了

<el-submenu :index="index+''" v-for="(item,index) in this.$router.options.routes" v-if="!item.hidden" :key="index">   <!--这个遍历拿到的是index.js里面的routers地址数组 -->
                            <template slot="title">
                                <i style="color: #409eff;margin-right: 10px" :class="item.iconCls"></i>
                                <span>{{item.name}}</span>
                            </template>
                            <el-menu-item-group>
                                <el-menu-item :index="child.path" v-for="(child,index) in item.children" :key="index">{{child.name}}</el-menu-item>
                            </el-menu-item-group>
</el-submenu>

我们现在用的是this.$router.options.routes,要换成我们在store里面存的数据

在store里面,我们习惯使用computed计算属性来列出store里面的数据

computed:{
     routes(){
     return this.$store.state.routes;
}

然后在上面渲染的时候就可以直接使用v-for="(index,item) in routes"

在这说一下我做的时候粗心大意遇到的两个bug,希望大家可以避免这个错误。

抱歉,上面截图有个笔误,是看api.js里面封装请求的方法

第一个小bug是把api.js里面封装的请求方法的url属性写错了,url属性包裹的是` `:顿号,不是' ':单引号

export const getRequest = (url, params) => {
    return axios({
        method: 'get',
        url: `${base}${url}`,
        data: params
    });
}

第二个小bug的截图如下:

 在动态渲染左侧导航栏菜单的时候,一级菜单出来了,但是二级菜单和一级菜单是一样的。控制台也没有报错

后来用postman测试一下菜单接口,才发现是数据格式有问题,改了一下MenuMapper.xml里面的代码就没问题了。

动态渲染的菜单导航栏效果如下图:

细心的小伙伴可以发现了我菜单前面的title变了,这个其实也很好实现

①:在Terminal里面输入npm install font-asesome命令即可下载成功

② :在<el-menu>标签里面的修改<i>标签里面内容,并且加了颜色和外边距

<el-menu router unique-opened="">
                        <el-submenu :index="index+''" v-for="(item,index) in routes" v-if="!item.hidden" :key="index">   <!--这个遍历拿到的是index.js里面的routers地址数组 -->
                            <template slot="title">
                                <i style="color: #409eff;margin-right: 10px" :class="item.iconCls"></i>
                                <span>{{item.name}}</span>
                            </template>
                            <el-menu-item-group>
                                <el-menu-item :index="child.path" v-for="(child,index) in item.children" :key="index">{{child.name}}</el-menu-item>
                            </el-menu-item-group>
                        </el-submenu>
</el-menu>

③:但是加了也不会有效果是因为还没有把font-awesome样式导入进来,在main.js里面加上下面一行代码即可

import 'font-awesome/css/font-awesome.min.css'

 

动态渲染了菜单导航栏之后,点击子菜单进不去里面的页面,这是因为没有在menus.js里面进行判断,判断一下就可以点进其他的页面了

component(resolve) {
                if (component.startsWith("Home")){
                    require(['../views/'+component+'.vue'],resolve);
                } else if (component.startsWith("Emp")) {
                    require(['../views/emp/' + component + '.vue'], resolve);
                } else if (component.startsWith("Per")) {
                    require(['../views/per/' + component + '.vue'], resolve);
                } else if (component.startsWith("Sal")) {
                    require(['../views/sal/' + component + '.vue'], resolve);
                } else if (component.startsWith("Sta")) {
                    require(['../views/sta/' + component + '.vue'], resolve);
                } else if (component.startsWith("Sys")) {
                    require(['../views/sys/' + component + '.vue'], resolve);
                }
                //这是ES5里面的写法  []:请求组件的地址
            }

在Home也上加个面包屑,方便我们去使用,ElementUI里面为我们提供了面包屑

把面包屑的代码复制到<router-view/>的前面,效果如下图:

 这个面包屑应该是点了哪个菜单项,显示的就是哪个,所以要把面包屑的源代码改一下:

<el-breadcrumb separator-class="el-icon-arrow-right">
                        <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
                        <!--通过this.$router.currentRoute.name:获取当前路由对象的name属性-->
                        <el-breadcrumb-item>{{this.$router.currentRoute.name}}</el-breadcrumb-item>
</el-breadcrumb>

修改后的效果如下图:

写完之后发现点击首页显示Home字体,不太合适,就不显示Home字体了,进行判断一下

<el-breadcrumb separator-class="el-icon-arrow-right" v-if="this.$router.currentRoute.path!='/home'">
                        <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
                        <!--通过this.$router.currentRoute.name:获取当前路由对象的name属性-->
                        <el-breadcrumb-item>{{this.$router.currentRoute.name}}</el-breadcrumb-item>
</el-breadcrumb>

判断之后点击首页,就是空白的页面,空白页也不是很合适,我们可以在<router-view/>标签上面添加一个<div>并添加“欢迎来到微人事”的字并设置它的大小,居中,样式等。写好之后所有页面都会显示“欢迎来到微人事”,这样是不行的,我们可以在div里面使用v-if判断一下,是点击首页的时候再显示“欢迎来到微人事”

<div class="homeWelcome" v-if="this.$router.currentRoute.path=='/home'">欢迎来到微人事!</div>

显示效果如下图:

很多人喜欢在登录的时候按回车进行登录,这个也是可以实现的,在Login.vue页面里面加上如下代码:

@keydown.enter.native="submitLogin"

<el-form-item prop="password">
                <el-input type="password" v-model="loginForm.password" auto-complete="off" placeholder="请输入密码" @keydown.enter.native="submitLogin"></el-input>
</el-form-item>

                                                        觉得文章对自己有用,想要继续学下去的可以

                                                                         可以长按下方二维码        

                                                                

                                                                           关注【javalingfeng】哦

                                                          公众号正在更新vue.js和springboot的详细教程

                                                          还有超多免费Java,Python,Android等系列教程

                                                                                    等你来领

 

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值