vue-项目增加登录模块 从0到1
思路:
1.路由增加登录页面
2.编写登录页面及校验、记住我、重定向跳转、登录接口
3.vuex中登录方法,存储用户名、token、登出方法、清除token
4.菜单登出方法
5.request拦截器设置头部token
6.路由守卫,白名单,无token定向登录页,无用户名重新请求接口
const TokenKey = 'admin_token' export function getToken() { return sessionStorage.getItem(TokenKey) } export function setToken(token) { return sessionStorage.setItem(TokenKey, token) } export function removeToken() { return sessionStorage.removeItem(TokenKey) }
<template> <div class="login-container"> <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on" label-position="left" > <div class="title-container"> <h3 class="title">登录页</h3> </div> <el-form-item prop="username"> <span class="svg-container"> <svg-icon icon-class="user" /> </span> <el-input ref="username" v-model="loginForm.username" placeholder="Username" name="username" type="text" tabindex="1" auto-complete="on" /> </el-form-item> <el-form-item prop="password"> <span class="svg-container"> <svg-icon icon-class="password" /> </span> <el-input :key="passwordType" ref="password" v-model="loginForm.password" :type="passwordType" placeholder="Password" name="password" tabindex="2" auto-complete="on" @keyup.enter.native="handleLogin" /> <span class="show-pwd" @click="showPwd"> <svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" /> </span> </el-form-item> <el-checkbox v-model="loginForm.rememberMe" style="position: absolute; right: 40px" >记住我</el-checkbox > <el-button :loading="loading" type="primary" style="width: 100%; margin-bottom: 30px; margin-top: 40px" @click.native.prevent="handleLogin" >Login</el-button > <!-- <div class="tips">--> <!-- <span style="margin-right:20px;">username: admin</span>--> <!-- <span> password: any</span>--> <!-- </div>--> </el-form> </div> </template> <script> import { validUsername } from "@/utils/validate"; import Cookies from "js-cookie"; export default { name: "Login", data() { const validateUsername = (rule, value, callback) => { if (!validUsername(value)) { callback(new Error("Please enter the correct user name")); } else { callback(); } }; const validatePassword = (rule, value, callback) => { if (value.length < 6) { callback(new Error("The password can not be less than 6 digits")); } else { callback(); } }; return { loginForm: { username: "admin", password: "123456", rememberMe: true, }, loginRules: { username: [ { required: true, trigger: "blur", validator: validateUsername }, ], password: [ { required: true, trigger: "blur", validator: validatePassword }, ], }, loading: false, passwordType: "password", redirect: undefined, }; }, watch: { $route: { handler: function (route) { this.redirect = route.query && route.query.redirect; }, immediate: true, }, }, methods: { showPwd() { if (this.passwordType === "password") { this.passwordType = ""; } else { this.passwordType = "password"; } this.$nextTick(() => { this.$refs.password.focus(); }); }, handleLogin() { // console.log("登陆界面"); this.$refs.loginForm.validate((valid) => { // console.log(this.loginForm); if (valid) { this.loading = true; if (this.loginForm.rememberMe) { Cookies.set("username", this.loginForm.username, { expires: 30, }); Cookies.set("password", btoa(this.loginForm.password), { expires: 30, }); Cookies.set("rememberMe", this.loginForm.rememberMe, { expires: 30, }); } else { Cookies.remove("username"); Cookies.remove("password"); Cookies.remove("rememberMe"); } this.$store .dispatch("user/login", this.loginForm) .then(() => { this.$router.push({ path: this.redirect || "/" }); this.loading = false; }) .catch(() => { this.loading = false; }); } else { console.log("error submit!!"); return false; } }); }, }, created() { if (Cookies.get("username")) { this.loginForm.username = Cookies.get("username"); } if (Cookies.get("password")) { this.loginForm.password = window.atob(Cookies.get("password")); } }, }; </script> <style lang="scss"> /* 修复input 背景不协调 和光标变色 */ /* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */ $bg: #283443; $light_gray: #fff; $cursor: #fff; @supports (-webkit-mask: none) and (not (cater-color: $cursor)) { .login-container .el-input input { color: $cursor; } } /* reset element-ui css */ .login-container { .el-input { display: inline-block; height: 47px; width: 85%; input { background: transparent; border: 0px; -webkit-appearance: none; border-radius: 0px; padding: 12px 5px 12px 15px; color: $light_gray; height: 47px; caret-color: $cursor; &:-webkit-autofill { box-shadow: 0 0 0px 1000px $bg inset !important; -webkit-text-fill-color: $cursor !important; } } } .el-form-item { border: 1px solid rgba(255, 255, 255, 0.1); background: rgba(0, 0, 0, 0.1); border-radius: 5px; color: #454545; } } </style> <style lang="scss" scoped> $bg: #2d3a4b; $dark_gray: #889aa4; $light_gray: #eee; .login-container { min-height: 100%; width: 100%; background-color: $bg; overflow: hidden; .login-form { position: relative; width: 520px; max-width: 100%; padding: 160px 35px 0; margin: 0 auto; overflow: hidden; } .tips { font-size: 14px; color: #fff; margin-bottom: 10px; span { &:first-of-type { margin-right: 16px; } } } .svg-container { padding: 6px 5px 6px 15px; color: $dark_gray; vertical-align: middle; width: 30px; display: inline-block; } .title-container { position: relative; .title { font-size: 26px; color: $light_gray; margin: 0px auto 40px auto; text-align: center; font-weight: bold; } } .show-pwd { position: absolute; right: 10px; top: 7px; font-size: 16px; color: $dark_gray; cursor: pointer; user-select: none; } } </style>
<template> <div class="navbar"> <hamburger :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" /> <breadcrumb class="breadcrumb-container" /> <div class="right-menu"> <el-dropdown class="avatar-container" trigger="click"> <div class="avatar-wrapper"> <img :src="circleUrl" class="user-avatar" /> </div> <el-dropdown-menu slot="dropdown" class="user-dropdown"> <router-link to="/"> <el-dropdown-item> Home </el-dropdown-item> </router-link> <el-dropdown-item divided @click.native="logout"> <span style="display: block">Log Out</span> </el-dropdown-item> </el-dropdown-menu> </el-dropdown> </div> </div> </template> <script> import { mapGetters } from "vuex"; import Breadcrumb from "@/components/Breadcrumb"; import Hamburger from "@/components/Hamburger"; export default { components: { Breadcrumb, Hamburger, }, computed: { ...mapGetters(["sidebar", "avatar"]), }, data() { return { circleUrl: "https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png", }; }, methods: { toggleSideBar() { this.$store.dispatch("app/toggleSideBar"); }, logout() { this.$confirm("确定要退出系统吗?", "提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning", }).then(() => { this.$store.dispatch("user/logout").then(() => { this.$message({ type: "success", message: "退出成功!", }); this.$router.push("/login"); }); // setTimeout(() => { // this.$router.push(`/login?redirect=${this.$route.fullPath}`); // }, 1000); }); }, }, }; </script> <style lang="scss" scoped> .navbar { height: 50px; overflow: hidden; position: relative; background: #fff; box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08); .hamburger-container { line-height: 46px; height: 100%; float: left; cursor: pointer; transition: background 0.3s; -webkit-tap-highlight-color: transparent; &:hover { background: rgba(0, 0, 0, 0.025); } } .breadcrumb-container { float: left; } .right-menu { float: right; height: 100%; line-height: 50px; &:focus { outline: none; } .right-menu-item { display: inline-block; padding: 0 8px; height: 100%; font-size: 18px; color: #5a5e66; vertical-align: text-bottom; &.hover-effect { cursor: pointer; transition: background 0.3s; &:hover { background: rgba(0, 0, 0, 0.025); } } } .avatar-container { margin-right: 30px; .avatar-wrapper { margin-top: 5px; position: relative; .user-avatar { cursor: pointer; width: 40px; height: 40px; border-radius: 10px; } .el-icon-caret-bottom { cursor: pointer; position: absolute; right: -20px; top: 25px; font-size: 12px; } } } } } </style>
import axios from 'axios'; import store from '../store' import { getToken } from './auth' import errorCode from './errorCode'; import router from '../router' import { removeToken } from '@/utils/auth' import { MessageBox, Message } from 'element-ui' export let isRelogin = { show: false }; axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'; // 创建axios实例 const service = axios.create({ // axios中请求配置有baseURL选项,表示请求URL公共部分 baseURL: process.env.VUE_APP_BASE_API, // 超时 timeout: 600000, }); // request拦截器 service.interceptors.request.use( (config) => { // 是否需要设置 token const isToken = (config.headers || {}).isToken === false; if (getToken() && !isToken) { config.headers['AccessToken'] = getToken() } config.headers['Access-Control-Max-Age'] = 86400 //,一天内,不用发出另一条预检请求 // get请求映射params参数 if (config.method === 'get' && config.params) { let url = config.url + '?'; for (const propName of Object.keys(config.params)) { const value = config.params[propName]; var part = encodeURIComponent(propName) + '='; if (value !== null && typeof value !== 'undefined') { if (typeof value === 'object') { for (const key of Object.keys(value)) { let params = propName + '[' + key + ']'; var subPart = encodeURIComponent(params) + '='; url += subPart + encodeURIComponent(value[key]) + '&'; } } else { url += part + encodeURIComponent(value) + '&'; } } } url = url.slice(0, -1); config.params = {}; config.url = url; } return config; }, (error) => { Promise.reject(error); } ); // 响应拦截器 service.interceptors.response.use( (res) => { // 未设置状态码则默认成功状态 const code = res.data.code || 200; // 获取错误信息 const msg = errorCode[code] || res.data.msg || errorCode['default']; // if (code == 200) { // return res.data; // } else if (code == 1) { // toLogin() // } else { // let statusCode = res.status; // if (statusCode === 401 || statusCode === 302) { // MessageBox.confirm('登录状态已过期', '系统提示', { // confirmButtonText: '重新登录', // cancelButtonText: '取消', // showCancelButton: false, // }).then(() => { // toLogin(); // }) // } else { // Message.error(msg) // toLogin(); // return Promise.reject(msg, 'error'); // } // } if (code === 401) { if (!isRelogin.show) { isRelogin.show = true; MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => { toLogin(); // isRelogin.show = false; }).catch(() => { isRelogin.show = false; }); } return Promise.reject('无效的会话,或者会话已过期,请重新登录。') } else if (code === 500) { Message({ message: msg, type: 'error' }) return Promise.reject(new Error(msg)) } else if (code !== 200) { Message.error(msg) return Promise.reject('error') } else { return Promise.resolve(res.data) } }, (error) => { if (error && error.response) { let { message } = error; let code = error.response.status; if (code === 401 || code === 302) { MessageBox.confirm('登录状态已过期', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', showCancelButton: false, }).then(() => { toLogin(); }) } else { if (message == 'Network Error') { message = '后端接口连接异常'; } else if (message.includes('timeout')) { message = '系统接口请求超时'; } else if (message.includes('Request failed with status code')) { message = '系统接口' + message.substr(message.length - 3) + '异常'; } Message.error(message) } } else { toLogin(); } return Promise.reject(error); } ); function toLogin() { store.dispatch("user/logout").then(() => { router.push("/login"); }).catch(() => { removeToken() router.push("/login"); }) } export default service;
import { login, logout, getInfo } from '@/api/user' import { getToken, setToken, removeToken } from '@/utils/auth' import { resetRouter } from '@/router' const getDefaultState = () => { return { token: getToken(), name: '', avatar: '' } } const state = getDefaultState() const mutations = { RESET_STATE: (state) => { Object.assign(state, getDefaultState()) }, SET_TOKEN: (state, token) => { state.token = token }, SET_NAME: (state, name) => { state.name = name }, SET_AVATAR: (state, avatar) => { state.avatar = avatar } } const actions = { // user login login({ commit }, userInfo) { const { username, password, rememberMe } = userInfo return new Promise((resolve, reject) => { login({ userName: username.trim(), passWord: password, rememberMe }).then(response => { // console.log(response, "response") const { data } = response commit('SET_TOKEN', data.token) // commit('SET_NAME', data.info.userName) setToken(data.token) resolve() }).catch(error => { reject(error) }) }) }, // get user info getInfo({ commit, state }) { return new Promise((resolve, reject) => { getInfo(state.token).then(response => { // console.log(response,"response") const { data } = response if (!data) { return reject('Verification failed, please Login again.') } // const { // name, // avatar // } = data commit('SET_NAME', data.userName) // commit('SET_AVATAR', avatar) resolve(data) }).catch(error => { reject(error) }) }) }, // user logout logout({ commit, state }) { return new Promise((resolve, reject) => { // console.log(state.token) logout(state.token).then(() => { removeToken() // must remove token first // resetRouter() commit('RESET_STATE') resolve() }).catch(error => { reject(error) }) }) }, // remove token resetToken({ commit }) { return new Promise(resolve => { removeToken() // must remove token first commit('RESET_STATE') resolve() }) } } export default { namespaced: true, state, mutations, actions }
import router from './router' import store from './store' import { Message } from 'element-ui' import NProgress from 'nprogress' // progress bar import 'nprogress/nprogress.css' // progress bar style import { getToken } from '@/utils/auth' // get token from cookie import getPageTitle from '@/utils/get-page-title' NProgress.configure({ showSpinner: false }) // NProgress Configuration const whiteList = ['/login', ] // no redirect whitelist router.beforeEach(async (to, from, next) => { NProgress.start() // set page title // document.title = getPageTitle(to.meta.title) // determine whether the user has logged in const hasToken = getToken() // console.log(hasToken) if (hasToken) { if (to.path === '/login') { // if is logged in, redirect to the home page // next({ // path: '/' // }) store.dispatch('user/logout').then(() => { next() NProgress.done() }) } else { const hasGetUserInfo = store.getters.name if (hasGetUserInfo) { next() } else { try { // get user info*********** await store.dispatch('user/getInfo') next() } catch (error) { // remove token and go to login page to re-login await store.dispatch('user/resetToken') Message.error(error || 'Has Error') next(`/login?redirect=${to.path}`) NProgress.done() } } } } else { /* has no token*/ if (whiteList.indexOf(to.path) !== -1) { // in the free login whitelist, go directly next() } else { // other pages that do not have permission to access are redirected to the login page. next(`/login?redirect=${to.path}`) NProgress.done() } } }) router.afterEach(() => { // finish progress bar NProgress.done() })
import request from '@/utils/request' export function login(data) { return request({ url: '/xhszhmgr/manage/login', method: 'post', data }) } export function logout(token) { return request({ url: '/xhszhmgr/manage/outLogin', method: 'get', headers:{ token } }) } export function getInfo(token) { return request({ url: '/xhszhmgr/manage/tokenToUser', method: 'get', headers:{ token } }) }
本文仅提供参考,是本人闲时所写笔记,如有错误,还请赐教,作者:阿蒙不萌,大家可以随意转载