基于Vue2+Vuex+vue-admin-template实现简单的用户登录
本文所有的功能实现仅展示关键代码片段,标签中的placeholder、tabindex和样式等非主干部分均略去。
表单校验(以手机号-密码为例)
基于el-form表单进行校验数据与属性配置
el-form
el-form:负责绑定 model(表单数据对象) 和 rules(校验规则对象),key必须得统一
el-form-item: 负责绑定prop (校验规则的key名)
el-input: 负责双向绑定具体的表单数据 (表单数据的key名)
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
<el-form-item prop="mobile">
<el-input ref="mobile" v-model="loginForm.mobile"
name="mobile" type="text"
/>
</el-form-item>
<el-form-item prop="password">
<el-input :key="passwordType" ref="password"
v-model="loginForm.password"
name="password" :type="passwordType"
@keyup.enter.native="handleLogin"
/>
</el-form-item>
</el-form>
导入自定义校验规则函数
export function validMobile(phone) {
// 根据具体校验规则编写正则表达式,并返回结果
return /^1[3-9]\d{9}$/.test(phone)
}
表单数据对象与校验规则对象
import { validMobile } from '@/utils/validate' // 需要复杂数据校验时从外部导入
data() {
return {
// 定义校验手机号的箭头函数
const validateMobile = (rule, value, callback) => {
if (!validMobile(value)) { //
callback(new Error('请输入正确的手机号'))
} else {
callback()
}
}
// 表单数据对象 和 校验规则对象
loginForm: {
mobile: '13400000001',
password: '123456'
},
loginRules: {
mobile: [{ required: true, trigger: 'blur', validator: validateMobile }], // 调用校验
password: [{ required: true, trigger: 'blur', min: 6, max: 16, message: '密码长度必须在6-16位之间' }]
}
}
}
登录方法处理与登录调用
methods: {
showPwd() { // 功能:点击切换密码的显示模式:数字还是远点
if (this.passwordType === 'password') {
this.passwordType = ''
} else {
this.passwordType = 'password'
}
this.$nextTick(() => { // 使用Element自带的输入框focus事件,使input框获取焦点
this.$refs.password.focus()
})
},
handleLogin() {
this.$refs.loginForm.validate(async valid => { // 表单校验
if (valid) {
try {
// 避免单组件功能堆积,使用Vuex,通过dispatch调用user模块中actions中的登录API
this.$store.dispatch('user/loginActions', this.loginForm)
} catch (err) { // 错误对象用dir打印
console.dir(err)
}
}
})
},
}
Vuex
state-初始化Token状态
const state = () => ({
token: '' // 用户 Token,默认为 ''
})
mutations-提供操作Token的方法
const mutations = {
// ...其他
// 设置token
SET_TOKEN(state, newToken) {
state.token = newToken
},
// 删除token
REMOVE_TOKEN(state) {
state.token = ''
}
}
actions-调用登录请求接口等异步方法
const actions = {
async loginActions({ commit }, data) {
const res = await loginAPI(data)
commit('SET_TOKEN', res.data) // 在Vuex和浏览器存储Token
}
}
接口调用
export function loginAPI(data) {
return request({
url: '/sys/login', // 以自己对接的后端接口为准
method: 'POST',
// axios内部会默认携带请求头Content-Type: 'application/json'
// Content-Type: 'application/json' -> 请求体中的参数名和值会变成JSON字符串格式给后台
data
})
}
Token处理
axios请求拦截器-统一注入
- 如果当前 vuex中有 token,就在请求头中设置上
- (如果后端有规定)因为后端返回的 token 没有添加上 Bearer 字符串,因此需要手动添加,Authorization 和 Bearer 是本文后端接口要求的写法。
import store from '@/store'
service.interceptors.request.use(
config => {
// js文件中不能使用this.$store,因为这个this关键字不是Vue组件对象, 无法查找原型链上$store
// 但是this.$store为了拿到的是store/index.js导出store对象
// 解决: 直接把store对象导入过来使用, 是同一个store对象
const token = store.getters.token
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
error => {
return Promise.reject(error)
}
)
Token的持久化
现在 token 虽然保存在 vuex 中,但页面刷新之后,vuex 的内容丢失了,将会导致接口访问异常。所以我们需要对 token 做持久化处理,让页面刷新之后 token 不丢失。
- 在设置 token 的时候除了在 vuex 中存一份,在本地也同步存一份
- 在对 token进行初始化的时候先从本地取一下,优先使用本地取到的值
- 在删除 token 的时候除了把 vuex 中的删除掉,把本地的也一并删除
const mutations = {
RESET_STATE: (state) => {
Object.assign(state, getDefaultState())
},
// 设置token
SET_TOKEN(state, newToken) {
state.token = newToken
setToken(newToken)
},
// 删除token
REMOVE_TOKEN(state) {
state.token = ''
removeToken()
}
}
一些关于Vuex、Token和网络请求的问题
1.Token的作用是什么?
(1)判断用户是否登录:如果有token,说明登录过(即便已经过期),能看到登录才能看到的页面
否则,只会放行登录和注册页面,其他页面会被强制next切换到登录注册页面
(2)前端如果携带过期token给后端,后端验证过期后返回401,进入响应拦截器在响应拦截器里写判断代码,如果401了就退出登录
(3)axios请求拦截器将token值添加在请求头中,任意一次请求都会执行
2.为什么不直接使用本地的token, 还要使用vuex里的呢?
- 其实可以直接使用本地的token, 不使用vuex的
- 浏览器本地的token, 是要进行磁盘数据的读取和写入, 速度没有vuex快
- 但是vuex是在内存里, 虽然快, 但是刷新就消失了, 所以要在本地持久存储同步一份
- 如果面试的时候, 遇到抬杠的, 我们就直接都从本地存取吧, 反正也不差那点时间(很快)
3.Vuex各部分的作用是什么?
store只能是唯一的,state里定义全局状态变量
mutations里唯一同步修改state里的值
- 用
$store.commit('要触发的mutations函数名')
出来mutations里的函数执行
actions里定义异步代码(一般调用后端接口,并把值commit给mutations)
- 用
$store.dispatch('要触发的actions函数名')
触发actions里的函数执行