系统登录是指用户必须提供满足一定条件的信息后,才可以进入系统。最早系统一般是指用户名和密码,如今,登录方式已多元化,系统一般登录方式有:用户名+密码、二维码扫码登录、第三方授权登录、手机号+短信登录等等。移动端登录方式除以上几种外,还有手机号一键登录、人脸识别登录、指纹登录、语音登录等等。
前面Vue讲解的这些篇幅了解后,可以实现一个简单的登录功能了,这里还是用传统的 用户名+密码 方式实现登录功能。
一、前期准备功能
在之前几篇中,已详细说明了vue的搭建到各种插件的运用方法,这里不在细说。如果还不了解Vue的基本语法或写法,可以看上之前篇幅后,再来阅读此篇文章。地址如下:
地址一:Vue.js快速入门之一:安装和配置_觉醒法师的博客-CSDN博客_vuejs安装及环境配置
地址二:Vue.js快速入门之二:使用状态管理工具Vuex_觉醒法师的博客-CSDN博客_js中如何使用vuex
地址三:Vue.js快速入门之三:Vue-Router路由_觉醒法师的博客-CSDN博客
地址四:Vue.js快速入门之五:Mockjs的使用和语法详解_觉醒法师的博客-CSDN博客
地址五:Vue.js快速入门之六:Set和Map的妙用_觉醒法师的博客-CSDN博客
地址六:Vue.js快速入门之七:系统权限管理_觉醒法师的博客-CSDN博客_js权限管理
现在咱们就来实现一个如下图的,简单的登录功能。
二、store本地状态管理器定义
2.1 state.js文件
const state = {
/**
* 存储用户信息
*/
userInfo: {},
/**
* 存储接口访问令牌
*/
token: "",
/**
* 角色权限
*/
roles: [],
/**
* 菜单列表
*/
menuList: []
}
export default state;
2.2 gettter.js文件
这里除了正常返回userInfo,token, menu_list信息外,还增加了userRoles权限信息和menuList中有授权访问菜单信息;通过userRoles直接获取该用户当下所拥有的权限列表,通过menuList可以控制界面哪些栏目有权限访问并显示在菜单栏目中。
const getters = {
/**
* 用户信息
*/
userInfo(state){
if(state.userInfo){
let { username, company } = state.userInfo;
return { username, company };
}else{
return null;
}
},
/**
* 访问令牌
*/
accessToken(state){
return state.token;
},
/**
* 用户角色
*/
userRoles(state){
return state.userInfo&&Array.isArray(state.userInfo['roles'])?
state.userInfo.roles : [];
},
/**
* 菜单列表
*/
menu_list(state){
return Array.isArray(state.menu_list)?state.menu_list:[];
},
/**
* 过滤权限的菜单列表
*/
menuList(state, {menu_list, userRoles}){
return Array.isArray(menu_list)&&Array.isArray(userRoles)?menu_list.filter(
item => Array.isArray(item['permission'])&&
(
//没有权限信息的,直接通过
item.permission.length==0 ||
//在权限数组中的,可以通过
userRoles.filter(sub => item.permission.includes(sub.roleName)).length>0
)
):[];
}
}
export default getters;
2.3 mutationTyoe.js文件
在该文件中定义常量,作为统一指令进行操作。
/**
* 用户信息
*/
export const USERINFO = "USERINFO";
/**
* 访问令牌
*/
export const TOKEN = "TOKEN";
/**
* 当前栏目列表
*/
export const MENU_LIST = "MENU_LIST";
2.4 mutaions.js文件
vuex中所有变量值修改,都在mutations中处理,代码如下:
import { USERINFO, TOKEN, MENU_LIST } from './mutationsType'
/**
* 裂变器
*/
const mutations = {
/**
* 修改访问令牌信息
*/
[TOKEN](state, param){
state.token = param;
},
/**
* 修改用户信息
*/
[USERINFO](state, param){
state.userInfo = param;
},
/**
* 修改菜单列表
*/
[MENU_LIST](state, param){
state.menu_list = param;
}
}
export default mutations;
2.5 actions.js文件
在业务层添加checkRolesRoutePermission校验函数,判断每次路由跳转时,判断该路由是否有访问权限。其目的是防止某些用户是通过收藏地址,直接通过路由进行访问。所以在路由卫士中添加些函数进行判断,则通过路由或栏目菜单点击时,都可被拦截到。
import Vue from 'vue'
import { MENU_LIST } from './mutationsType'
import { Loading } from 'element-ui'
/**
* 业务层
*/
const actions = {
/**
* 保存登录信息
*/
saveLoginInfo({commit}, param){
if(param['token']) {
commit(TOKEN, param.token);
Vue.ls.set(TOKEN, param.token);
}
if(param['userinfo']) {
commit(USERINFO, param.userinfo);
Vue.ls.set(USERINFO, param.userinfo);
}
},
/**
* 退出登录
*/
exitLogin({commit}, param){
commit(TOKEN, '');
commit(USERINFO, '');
Vue.ls.remove(TOKEN);
Vue.ls.remove(USERINFO);
},
/**
* 检测是否登录
*/
checkIsLogin({ commit, state }){
let _token = Vue.ls.get(TOKEN),
_userinfo = Vue.ls.get(USERINFO);
if(!(state.token&&state.userInfo)&&(_token&&_userinfo)){
commit(TOKEN, _token);
commit(USERINFO, _userinfo);
}
return new Promise((resolve, reject) => {
if(state.token&&state.userInfo){
resolve();
}else{
reject();
}
})
},
/**
* 检查路由访问权限
*/
checkRolesRoutePermission({ commit, getters }, param){
let handle = null,
timeIndex = 0,
timeTotal = 10, //每1秒执行一次,执行10后未校验到权限,进入无权限页面
loading = Loading.service({
text: "权限校验中...",
background: "rgba(0, 0, 0, 0)"
}),
//递归检测子项
DGFilter = (_child, _path) => {
return Array.isArray(_child)
&& _child.length>0
&& _child.filter(
item => item.path == _path
|| DGFilter(item['children'], _path)
).length > 0;
},
//检测回调函数
callback = (resolve, reject) => {
loading.close();
let { menu_list, userRoles } = getters,
//筛选出当前路径对应的一线栏目
filterMenuList = menu_list.filter(
item => item.path == param || DGFilter(item['children'], param)
);
//如果菜单列表中有筛选到数据,进入处理,否则放行
if(filterMenuList.length>0){
let _permission = filterMenuList[0]['permission'];
//如果菜单列表中权限列表长度大于0,表示有权限数据,进入处理, 否则放行
if(Array.isArray(_permission)&&_permission.length>0){
//判断角色列表中,是否存在该该权限,存在则可放行,不存在,无法通行
if( userRoles.filter(item => _permission.includes(item.roleName)).length>0 ){
resolve();
}else{
reject({
code: 404,
message: "当前页面无访问权限"
});
}
}else{
resolve();
}
}else{
resolve();
}
//if end
};
return new Promise((resolve, reject) => {
if(getters.menuList.length>0){
clearInterval(handle);
callback(resolve, reject);
}else{
handle = setInterval(() => {
//检测到菜单数组大于0时
if(getters.menuList.length>0){
clearInterval(handle);
loading.close();
callback(resolve, reject);
return;
}
timeIndex++;
//超时自动停止
if(timeIndex>=timeTotal){
clearInterval(handle);
loading.close();
reject({
code: 404,
message: "当前页面不存在"
});
}
}, 1000);
}
//if end
});
},
/**
* 业务层 - 初始化菜单
*/
initalMenu({commit}, params){
if(Array.isArray(params)){
commit(MENU_LIST, params);
}
//if end
}
}
export default actions;
2.6 store/index.js文件
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import getters from './getters'
import actions from './actions'
import mutations from './mutations'
Vue.use(Vuex);
export default new Vuex.Store({
state,
getters,
actions,
mutations
})
2.7 注入Vue对象中
import Vue from 'vue'
import App from './App'
import store from '@/store/index'
new Vue({
el: '#app',
store,
components: { App },
template: '<App/>'
})
三、路由定义
3.1 路由定义
在pages目录中,创建相应的页面。这次先创建首页(index)、登录页(login)、页面不存在(err404)、无访问权限(err404)。其他页面大家自行创建,这里只演示登录功能。
import Vue from 'vue'
import Router from 'vue-router'
import Error404 from '@/pages/Error/err404'
import Error405 from '@/pages/Error/err405'
import Index from '@/pages/index'
import Login from '@/pages/login'
import store from '@/store'
Vue.use(Router);
let _router = new Router({
routes: [
{
path: '/',
name: 'Index',
component: Index,
},
{
path: '/login',
name: 'Login',
component: Login,
},
{
path: '/no-permission',
name: 'Error405',
component: Error405,
},
{
path: '*',
name: 'Error404',
component: Error404,
},
]
});
_router.beforeEach((toRoute, fromRoute, next) => {
next();
});
3.2 路由卫士
判断登录是否失效,以及路由访问权限进行校验。
刚在vuex中的actions里,已定义了以下鉴权功能函数,直接调用作好对应处理即可。
_router.beforeEach((toRoute, fromRoute, next) => {
//检测是否登录
store.dispatch('checkIsLogin').then(() => {
console.log('login success');
//检测路由是否权限
store.dispatch('checkRolesRoutePermission', toRoute.path).then(res => {
//本页面禁用跳转
if(toRoute.path!=fromRoute.path){
next();
}
}).catch(e => {
//本页面禁用跳转
if(toRoute.path!=fromRoute.path){
next(e.code==405?'/no-permission':'/error');
}
});
}).catch(() => {
console.log('login error...')
if('/login'==toRoute.path){
next();
}else{
next('/login')
}
});
});
四、API实现
这里还是通过mockjs进行本地模拟接口的开发,如果有自己服务器小伙伴和懂后台语言的,如java、php、nodejs、C#等后端语言,可以开发真实系统进行登录操作。
4.1 封装axios请求
在utils目录创建request.js文件,封闭axios请求,预定义header头部信息,拦截request和response请求和响应,作相应数据处理。
import axios from 'axios'
import { Message, Loading } from 'element-ui'
//配置全局数据请求类型
axios.defaults.headers['Content-Type'] = "application/json;charset=utf-8";
//实例新的请求
const Service = axios.create({
baseURL: "",
timeout: 30 * 1000
});
//配置加载参数
let loadingOption = {
text: "正在努力加载中...",
background: "rgba(0, 0, 0, 0)"
}, loading;
//请求拦截
Service.interceptors.request.use(config => {
loading = Loading.service(loadingOption);
//数据转换
config.data = 'object'===typeof config.data?JSON.stringify(config.data):config.data;
return config;
}, error => {
loading.close();
return Promise.reject(error);
})
Service.interceptors.response.use(response => {
loading.close();
if(response.status==200){
return response['data'];
}
return Promise.reject(response);
}, error => {
loading.close();
return Promise.reject(error);
})
export default Service;
4.2 定义mockjs/index.js文件
在mockjs/index.js文件中,定义模拟接口,实现登录功能。
import { mock } from 'mockjs'
import DBData from '@/db'
import { randomStrName } from '@/utils/utils'
/**
* 获取栏目列表信息
*/
mock('/api/category/list', 'get', (request, response) => {
let _code = 200, _result = {}, _msg = 'success';
return {
code: _code,
data: DBData.get('category').map(item => item),
message: _msg
};
});
/**
* 登录功能
*/
mock('/api/login', 'post', (request, response) => {
let _code = 0, _result = {}, _msg = 'success';
if(request.body){
try{
let _data = JSON.parse(request.body);
//判断用户名和密码是否正确
if(_data.username=='admin'&&_data.password=='123456'){
_code = 200;
_result = {
token: randomStrName(30),
users: DBData.get('users')
};
}else{
_result = null;
_msg = '用户名或密码错误';
}
}catch(e){
console.log('error', e);
}
}
return {
code: _code,
data: _result,
message: _msg
};
});
这里把数据存储在db目录中了,也可直接放在mockjs/index.js文件中
import { Random } from 'mockjs'
/**
* 定义数据库容器
*/
const DBData = new Map();
/**
* 栏目信息
*/
DBData.set('category', [{
"name": "首页",
"path": "/",
"permission": ['系统设置_首页'],
"icon": "el-icon-data-line"
},
{
"name": "栏目管理",
"icon": "el-icon-data-board",
"permission": ['系统设置_栏目管理'],
"children": []
},
{
"name": "内容管理",
"path": "/auditing/index",
"permission": ['系统设置_内容管理'],
"icon": "el-icon-pie-chart"
},
{
"name": "系统设置",
"icon": "el-icon-data-analysis",
"permission": ['系统设置_系统设置'],
"children": []
}
].map(item => {
item['id'] = Random.id();
return item;
}));
/**
* 用户信息
*/
DBData.set('users', {
username: "用户名",
company: "公司信息",
roles: [
{
"roleId": Random.id(),
"roleName": "系统设置_首页"
},
{
"roleId": Random.id(),
"roleName": "系统设置_栏目管理"
},
{
"roleId": Random.id(),
"roleName": "系统设置_内容管理"
},
{
"roleId": Random.id(),
"roleName": "系统设置_系统设置"
}
]
})
export default DBData;
4.3 定义api/index.js文件
在api/index.js文件中,定义登录接口和退出登录接口。
import Service from '@/utils/request'
/**
* 获取栏目列表
*/
export const getCategoryList = () => {
return Service.get('/api/category/list', {});
}
/**
* 登录
*/
export const loginInfo = params => {
return Service.get('/api/category/list', params);
}
五、开发登录界面
先实现登录界面的基本样式、输入功能和登录功能。
5.1 基本样式
页面基本架构和Css样式代码如下:
html代码部分如下:
<template>
<div class="container">
<div class="login-box">
<div class="title">
<div class="login-info">
<h3>系统登录</h3>
</div>
</div>
<div class="content">
<el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="ruleForm">
<el-form-item label="用户名" prop="username">
<el-input v-model="ruleForm.username"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input type="password" v-model="ruleForm.password" autocomplete="off" show-password></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" class="btn-submit" @click="submitForm()">提交</el-button>
</el-form-item>
</el-form>
</div>
</div>
</div>
</template>
css部分样式代码:
<style lang="scss" scoped>
.login-box{ text-align: center; padding: 50px 0; width: 600px; margin: 0 auto;
.title{ padding: 15px 0;
h3{ font-size: 24px;color: #666666; }
}
.content{ padding: 20px 0; }
}
.btn-submit{ width: 100%; }
</style>
5.2 输入和登录校验功能实现
js部分变量和登录执行函数定义
<script>
export default {
data(){
return {
rules: {
username: [
{ required: true, message: '请输入姓名' }
],
password: [
{ required: true, message: '请输入密码' }
]
},
ruleForm: {}
}
},
methods: {
submitForm() {
this.$refs['ruleForm'].validate((valid) => {
if (valid) {
console.log('submit!');
} else {
this.$message.info('账号或密码错误');
return false;
}
});
},
//end
}
}
</script>
5.3 引入请求接口函数
引入刚上面所创建的api/index.js,获取登录接口功能函数。
import { loginInfo } from '@/api/index.js'
5.4 实现登录功能
对submitForm函数中,登录信息校验成功后,则可以调用登录接口了,实现登录后并获取相应用户信息,保存本地。
submitForm() {
this.$refs['ruleForm'].validate((valid) => {
if (valid) {
loginInfo(this.ruleForm).then(res => {
if(res.code==200){
this.$store.dispatch('saveLoginInfo', {
userinfo: res.data['users'],
token: res.data['token']
});
setTimeout(() => {
this.$router.push('/');
}, 200);
}else{
this.ruleForm = {};
this.$message.error(res.message);
}
});
} else {
this.$message.info('账号或密码错误');
return false;
}
})
}
以上代码功能实现后,就可以进行登录操作了,登录后vuex状态管理器中,则会显示用户和菜单信息,如下图: