前面几章节完成界面布局和事件响应,还未实现与后台项目交互,本节实现登录页面,与后台项目交互必须引入Axios Ajax组件,并且需要定义Axios拦截器判断当前请求是否已经登录。
定义前端配置参数
比如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代码
通过后台获取菜单列表
定义登录方法
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
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
添加登录页面
<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;
}在这里插入代码片
界面展示
代码版本gfvue_v1.4.zip
链接:https://pan.baidu.com/s/1FsG2ECLGOkXg2HZ4GRKs_A
提取码:hplo