前面几章节完成界面布局和事件响应,还未实现与后台项目交互,本节实现登录页面,与后台项目交互必须引入Axios Ajax组件,并且需要定义Axios拦截器判断当前请求是否已经登录。

定义前端配置参数

elementui布局自适应_elementui布局自适应


比如dev.env.js文件内容

'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')

module.exports = merge(prodEnv, {
  NODE_ENV: '"development"',
  SERVER_URL: '"http://localhost:6060"',
  IS_SSO: false,
  SSO_URL: '"http://sso.test.com/oauth2/authorize?client_id=syscode&redirect_uri=http://localhost:8080/%23/sso&response_type=code&state=123"',
})

定义认证相关方法

新建JS文件/src/util/auth.js,可以从配置文件中获取参数,并且记录登录状态到本地存储中。

import Cookies from 'js-cookie'

const auth = {
    //在会话存储中保持token
    getToken: function(){
        let token = sessionStorage.getItem('token');
        return token;
    },

    //刷新会话中的token
    refreshToken: function(token){
        sessionStorage.setItem('token',token);
    },

    //保存登录状态
    isLogin: function(){
        //return Cookies.get(this.loginKey)
        let ret = false;
        let isLogin = sessionStorage.getItem('isLogin');
        if (isLogin != null && isLogin != ''){
            ret = isLogin;
        }
        return ret;
    },

    //从配置中判断是否是单点登录环境
    isSsoEnv:function(){
        return process.env.IS_SSO;
    },

    //从配置中获取后台URL
    getServerUrl:function()
    {
        return process.env.SERVER_URL;
    },

    //从配置中获取单点登录URL
    getSsoUrl:function(){
        return process.env.SSO_URL;
    }
}

export default auth

引入Axios组件

引入Axios组件,并定义拦截处理逻辑,每当发起Ajax请求时检查当前是否是登录状态

import axios from 'axios'
import Auth from '@/util/auth'
import router from '../router'
import { Message } from 'element-ui'

// 通过Auth获取配置的后台URL
axios.defaults.baseURL=Auth.getServerUrl();
// 设置请求允许携带token ,解决跨域产生的相关问题
axios.defaults.withCredentials=true;
// 判断当前是否存着执行的Ajax请求
let getTokenLock = false;
//axios取消请求需要的对象
let CancelToken = axios.CancelToken;

//定义Axios实例
const service = axios.create({
    // 请求超时时间
    timeout: 3600000
});

function checkToken(cancel, callback){
    console.log("checkToken..."+new Date()+',Auth.hasToken()='+Auth.getToken());
    //检查是否存着Token
    if(Auth.getToken()==null)
    {
        //如果存着正在检查Token的请求,等待500毫秒
        if(getTokenLock){
            setTimeout(function(){
                checkToken(cancel, callback)
            }, 500)
        } else {
            if(router.currentRoute.path != '/login')
            {
                getTokenLock = true
                //终止当前请求,重新登录
                cancel()
                console.log('run to here')
                router.push('/login');
            }
        }
    }
    else//如果本地存着token,跳过检查
    {
        callback()
    }
}

function refreshToken(){
    //刷新Token
    store.dispatch("auth/getNewToken").then((res) => {
        Auth.refreshToken('res.token')
    })
}

//Axios发起请求时,设置请求参数
service.interceptors.request.use(
    config=>{
        //获取终止Request的对象
        let cancelMethod
        config.cancelToken = new CancelToken(function executor(c) {
            cancelMethod = c;
        })
        checkToken(cancelMethod, function(){
            //将token放到request的头信息中
            config.headers.Authorization = Auth.getToken();
        })

        //从本地存储中获取token
        let token = sessionStorage.getItem('token');
        if (!(token == null || token == 'null' || token == '')) {
            config.headers.Authorization = token;
        }
        //从本地存储中获取userId
        let userId = sessionStorage.getItem('userId');
        if (!(userId == null || userId == 'null' || userId == '')) {
            config.headers.userId = userId;
        }
        //在每个请求后面添加时间戳,保证每次请求绕过Nginx缓存
        let d = new Date();
        let timestamp=d.getTime();
        let params=config.params;
        if (!(params == undefined || params == null)){
            params.timestamp=timestamp;
        }else{
            params={};
            params.timestamp=timestamp;
        }
        config.params=params;
        return config
    },
    error=>{
        return Promise.reject(error);
    }
);

//Axios收到响应时完成处理逻辑
service.interceptors.response.use(
    response=>{
        if (response.data.value  === 'token invalid')
            refreshToken();
        return Promise.resolve(response.data)
    },
    error=>{
        if(axios.isCancel(error)){
            console.log(`Axios请求终止:`+JSON.stringify(error));
        } else if (error.response) {
            if (error.response.status == 401){
               router.push('error/401');
            }
            else if (error.response.status == 403){
               router.push('error/403');
            }else{
               Message({
                 message: `服务器错误:`+JSON.stringify(error.response.data),
                 type: 'error'
               })
            }
            return Promise.reject(error.response.data)
        }
    }
);

export default service;

在Main.js中引入Axios

// 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'
import axios from './util/ajax'

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

Vue.config.productionTip = false
Vue.prototype.$axios = axios

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代码

通过后台获取菜单列表

elementui布局自适应_elementui布局自适应_02


定义登录方法

elementui布局自适应_ios_03

import Cookies from 'js-cookie'
import axios from '@/util/ajax'

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) =>{
            axios({
                url: '/menulist',
                methods: 'post',
                data: {}
            }).then((res) => {
                alert(JSON.stringify(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);
                    sessionStorage.setItem('userId', res.uid);
                    sessionStorage.setItem('orgName', res.orgname);
                    sessionStorage.setItem('userName', res.name);
                    sessionStorage.setItem('token', res.token);
                }
                resolve(res)
            })
        });
    },
    logout({commit}) {
        return new Promise((resolve) => {
            sessionStorage.removeItem('isLogin');
            sessionStorage.removeItem('userId'); 
            sessionStorage.removeItem('orgName'); 
            sessionStorage.removeItem('userName'); 
            sessionStorage.removeItem('token');  
            resolve()
        })
    },
}

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

修改路由index.js

elementui布局自适应_Vue_04

import Vue from 'vue'
import Router from 'vue-router'
import staticRoute from './staticRoutes'
import store from '../store'
import NProgress from 'nprogress'
import Auth from '@/util/auth'

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 (Auth.isLogin()) {
    console.log('登录成功 from='+from.path+',to='+to.path);
    if (to.path === '/login') {
      //next({path: "/gf/home", replace: true})
      next()
    } 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

修改静态路由表

const Layout = () => import('../gf/layout/main')

const staticRoute = [
    {
        path: '/',
        redirect: '/gf/home'
    },
    {
        path: '/login',
        component: () => import('../gf/login/login')
    },
    {
        path: '/logout',
        component: () => import('../gf/login/logout')
    },
    {
        path: '/gf',
        component: Layout,
        children: [
            {
                path: 'home',
                component: () => import('../gf/home'),
            },
            {
                path: 'prdtlist',
                component: () => import( '../gf/product/prdtlist')
            },
        ]
    }
]

export default staticRoute

添加登录页面

elementui布局自适应_elementui布局自适应_05

<template>
    <div class="sys-login">
        <div class="login-area">
            <div class="logo">
                <img src="~static/images/logo.png" alt="">
            </div>
            <div class="form-group">
                <el-form :model="loginForm" :rules="loginRules" ref="loginForm" label-position="left" label-width="0px">
                    <el-form-item prop="name">
                        <el-input v-model="loginForm.name" type="text"></el-input>
                    </el-form-item>
                    <el-form-item prop="password">
                        <el-input v-model="loginForm.password" type="password"></el-input>
                    </el-form-item>
                    <el-form-item prop="captcha" v-if="captcha.show" class="captcha">
                        <img :src="captcha.src" alt="">
                        <el-input v-model="loginForm.captcha" type="text"></el-input>
                    </el-form-item>
                    <p class="textR"></p>
                    <a class="btn-login" type="primary" @click="submitForm()">Login</a>
                </el-form>
                <div v-if="sysMsg" class="err-msg">{{sysMsg}}</div>
            </div>
            <div class="tip">
                <p>{{sysMsg}}</p>
            </div>
        </div>
        <div style="height:300px"></div>
    </div>
</template>

<script>
import { mapState, mapActions } from 'vuex'

export default {
    data() {
        return {
            loginForm: {
                name: '',
                password: '',
                captcha: ''
            },
            loginRules: {
                name: [
                    {required: true, message: '', trigger: 'blur'}
                ],
                password :[
                    {required: true, message: '', trigger: 'blur'}
                ],
                captcha: [
                    {required: false, message: '', trigger: 'blur'}
                ]
            },
            captcha: {
                show: false,
                src: ''
            },
            sysMsg: ''
        }
    },
    computed: {
        ...mapState({
            lang: state => state.lang,
            theme: state => state.theme
        })
    },
    watch: {
        "captcha.show"(val){
            this.loginRules.captcha[0].required = val
        }
    },
    beforeMount(){

    },
    methods: {
        ...mapActions({
            login: 'auth/login',
            loadLang: 'loadLang'
        }),
        submitForm(){
            this.$refs.loginForm.validate((valid) => {
                if (valid) {
                    this.login({
                        name: this.loginForm.name,
                        password: this.loginForm.password
                    }).then(res => {
                        if(res.login)
                        {
                            alert(JSON.stringify(res.navList))
                            this.$store.commit('auth/setNavList',res.navList);
                            this.$store.commit('auth/setPermissionList',res.permissionList);
                            var userId = res.uid;
                            if(userId== null || userId == ''){
                                this.$router.push('/prompt');
                            }
                            this.$router.push('/gf/home');
                        }
                        else
                        {
                            this.sysMsg = res.message;
                        }
                    }).catch(err=>{
                        this.sysMsg = JSON.stringify(err);
                    })
                } else {
                    return false
                }
            });
        },
        changeLang(val){
            if(val == this.lang) return
            // 切换语言后重新加载语言包,并对重置登陆表单
            this.loadLang(val).then(() => {
                this.$refs.loginForm.resetFields()
            })
        },
        changeTheme(val){

        },
        setErrMsg(msg){
            this.sysMsg = msg;
        }
    }
}
</script>

后台Controller

/**
	 * 本地登录
	 * @param m
	 * @return
	 */
	@RequestMapping("/login")
	public Map login(HttpServletRequest req,@RequestBody Map m)
	{
		Map map = new HashMap();
		try
		{
			String name = (String)m.get("name");
			String password = (String)m.get("password");
			password = Util.getMD5(password);
			int pos = name.indexOf(":");
			if(pos<=0)
			{
				map.put("login", false);
				map.put("message", "输入登录名:"+name+" 错误,请检查");
				return map;
			}
			String flag = name.substring(0,pos);
			String loginId = name.substring(pos+1);
			map = orgServ.login(flag,loginId,password);
			
			Boolean login = (Boolean)map.get("login");
			if(login)
			{
				String userId = (String)map.get("uid");
				List<FunctionInfo> rtn = funcServ.getNavFuncTree(userId);
				map.put("navList", rtn);
				List<FunctionInfo> permissionList = funcServ.getPermissionFuncList(userId);
				map.put("permissionList", permissionList);
			}
			System.out.println("name="+name+",password="+password+",map="+map);
		}
		catch(Exception e)
		{
			map.put("message", e.getMessage());
		}
		return map;
	}	

	@RequestMapping("/menulist")
	public Map<String,List<FunctionInfo>> menulist(HttpServletRequest req)
	{
		Map<String,List<FunctionInfo>> map = new HashMap<String,List<FunctionInfo>>();
		FunctionInfo sysFi = new FunctionInfo("sys","系统管理","",1,true,"",null,"1");
		FunctionInfo userFi = new FunctionInfo("user","用户管理","/gf/home",1,true,"/gf/home",null,"sys");
		sysFi.getChild().add(userFi);
		FunctionInfo prdtFi = new FunctionInfo("prdt","产品管理","",1,true,"",null,"1");
		FunctionInfo addFi = new FunctionInfo("add","产品新建","/gf/prdtlist",1,true,"/gf/prdtlist",null,"prdt");
		prdtFi.getChild().add(addFi);
		
		
		List<FunctionInfo> navList = new ArrayList<FunctionInfo>();
		navList.add(sysFi);
		navList.add(prdtFi);
		
		List<FunctionInfo> permissionList = new ArrayList<FunctionInfo>();
		permissionList.add(userFi);
		permissionList.add(addFi);
		
		map.put("navList", navList);
		map.put("permissionList", permissionList);
		return map;
	}在这里插入代码片

界面展示

elementui布局自适应_elementui布局自适应_06


elementui布局自适应_ios_07

代码版本gfvue_v1.4.zip
链接:https://pan.baidu.com/s/1FsG2ECLGOkXg2HZ4GRKs_A
提取码:hplo