系统登录是指用户必须提供满足一定条件的信息后,才可以进入系统。最早系统一般是指用户名和密码,如今,登录方式已多元化,系统一般登录方式有:用户名+密码、二维码扫码登录、第三方授权登录、手机号+短信登录等等。移动端登录方式除以上几种外,还有手机号一键登录、人脸识别登录、指纹登录、语音登录等等。
前面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权限管理
现在咱们就来实现一个如下图的,简单的登录功能。

- const state = {
- /**
- * 存储用户信息
- */
- userInfo: {},
- /**
- * 存储接口访问令牌
- */
- token: "",
- /**
- * 角色权限
- */
- roles: [],
- /**
- * 菜单列表
- */
- menuList: []
- }
-
- export default state;
这里除了正常返回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;
在该文件中定义常量,作为统一指令进行操作。
- /**
- * 用户信息
- */
- export const USERINFO = "USERINFO";
-
- /**
- * 访问令牌
- */
- export const TOKEN = "TOKEN";
-
- /**
- * 当前栏目列表
- */
- export const MENU_LIST = "MENU_LIST";
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;
在业务层添加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;
- 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
- })
- import Vue from 'vue'
- import App from './App'
- import store from '@/store/index'
-
- new Vue({
- el: '#app',
- store,
- components: { App },
- template: '
' - })
在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();
- });
判断登录是否失效,以及路由访问权限进行校验。
刚在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')
- }
- });
- });
这里还是通过mockjs进行本地模拟接口的开发,如果有自己服务器小伙伴和懂后台语言的,如java、php、nodejs、C#等后端语言,可以开发真实系统进行登录操作。
在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;
在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;
在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);
- }
先实现登录界面的基本样式、输入功能和登录功能。
页面基本架构和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部分样式代码:
-
- .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%; }
js部分变量和登录执行函数定义
- 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
- }
- }
引入刚上面所创建的api/index.js,获取登录接口功能函数。
import { loginInfo } from '@/api/index.js'
对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状态管理器中,则会显示用户和菜单信息,如下图:
