页面登录功能的思路
一般用模块化的思想来单独封装请求,发送后获取token的数据,除了登录页面如果别的页面想实现数据请求一般都要带上token,所以将token存放在vuex中的state里,通过mutations来修改state中的数据。因为veux中存贮的数据是存在内存里的,刷新后就没有了,所以要把token存在本地,可以用localStorage或者cookie存储,在把本地存储的token赋值到vuex中。用请求拦截器给每次请求都加上请求头和token。但是token不是永久的,会有失效的问题,所以可以根据后端返回的状态码进行判断,如果不存在了,用Vue-Router中提供的钩子函数,实现拦截;如果存在,就正常放行,最后$router.push()实现跳转登录功能。
一 登录功能实现的大致步骤:
1.前端用户验证用户输入是否规范。
2.前端调用后端的接口,向后端发出登录请求。
3.后端收到请求,验查数据库中是否有相应的账号和密码是否正确。
4.验证通过后,将成功信息连同token一起发给前端。
5.前端将token存储起来,每次用户进入登录才能访问的页面时或者每次进网站时向后端发送那个token。
6.如果token过期了,则清除用户的信息,回到未登录的状态。
二 vue-admin-template框架配置登录接口
1.使用axios--配置基础路径
开发阶段基础路径--项目开发阶段使用的服务器:
env.development='http://xx.xxx.xxx.xx:xxxx'
产品环境路径--上线阶段后使用的服务器:
env.production='http://xx.xxx.xxx.xx:xxxx'
重启项目使配置生效
npm run dev
2.真实对接后台登录接口
(api/user.js中)将修改url
修改前:
/vue-admin-template/user/login
修改后:
/user/login
3.修改真实对接用户数据
(login/index.vue中)
修改用户:
修改前:
loginForm:{
username:"admin",
password:111111
}
修改后:
loginForm:{
username:"admin1",
password:123321
}
修改mock校验器限定输入得用户名 ,取消用户名验证
修改前:
loginRules: {
username: [{ required: true, trigger: 'blur', validator: validateUsername }],
password: [{ required: true, trigger: 'blur', validator: validatePassword }]
},
修改后:
loginRules: {
username: [{ required: true, trigger: 'blur', }],
password: [{ required: true, trigger: 'blur', validator: validatePassword }]
},
4.登录时除了发送login 还发送info(getInfo)
给每一个请求都携带token:
(utils/request.js中)
修改请求拦截器:
修改前:
axios.interceptors.request.user(config=>{
config.headers['x-Token']=getToken()
})
修改后:
axios.interceptors.request.user(config=>{
config.headers['Authorization']=getToken()
})
修改响应拦截器 响应拦截器--当响应status不为200时候执行错误回调:
修改前:
axios.interceptors.response.user(config=>{
if(res.code!==20000){
callback(error)
}
})
修改后:
axios.interceptors.response.user(config=>{
if(res.status!==200){
callback(error)
}
})
可以自己封装get post 方法:
//封装get方法
export function get(url,params){
return service.get(url,{params})
}
//封装数据格式为表单的post方法
export function post(url,data){
return service.post(url,qs.stringify(data))
}
//封装装数据格式为json的post方法
export function postJSON(url,data){
return service.post(url,data)
}
5.如何获取个人信息
(store/modules/user.js中)
用户登录和获取个人信息getInfo维护在store中
const { username, userFace } = data //--将后台真实字段从数据中解构出来
commit('SET_NAME', username)
commit('SET_AVATAR', userFace); //将用户名和头像提交到突变函数中
6.使用getter映射状态机数据
(store/modules/getters.js中)
username:state=>state.use.username
userFace:state=>state.use.userFace
三 登录的请求是怎么发出的:
1.表单验证(
在login文件下的index.vue部分代码)
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: 'admin1',
password: '123321'
},
loginRules: {
username: [{ required: true, trigger: 'blur' }],
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() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true
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
}
})
}
}
}
(utils/validate.js里) 设置校验规则:
@param {string} path
* @returns {Boolean}
*/
export function isExternal(path) {
return /^(https?:|mailto:|tel:)/.test(path)
}
/**
* @param {string} str
* @returns {Boolean}
*/
export function validUsername(str) {
const valid_map = ['admin', 'editor','admin1'];
return valid_map.indexOf(str.trim()) >= 0
}
2.调用api/user.js中封装好的api
(utils/request.js部分代码) 创建axios实例,获取axios实例。 设置基地址, baseURL
// create an axios instance
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
// withCredentials: true, // send cookies when cross-domain requests
timeout: 5000 // request timeout
})
//导出实例 service
export default service
3.收集用户的参数,上传给api。(页面上收集页面的数据和接口中的一致)
(login/index.vue中部分代码)
<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">Login Form</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-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @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>
4. 经过请求拦截器,添加请求头(添加token)
(utils/request.js部分代码)
// request interceptor
service.interceptors.request.use(
config => {
// do something before request is sent
if (store.getters.token) {
// let each request carry token
config.headers['Authorization'] = getToken()
}
return config
},
error => {
// do something with request error
console.log(error) // for debug
return Promise.reject(error)
}
)
5.经过响应拦截器的处理
简化获取有效的数据写法,(response.data),判断当前操作是否成功, 决定是否axios报错Promise.reject()
(utils/request.js部分代码)
// response interceptor
service.interceptors.response.use(
response => {
const res = response.data
if (res.status!==200 ) {
Message({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
})
if (res.status==401 || res.status === 500) {
// to re-login
MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
confirmButtonText: 'Re-Login',
cancelButtonText: 'Cancel',
type: 'warning'
}).then(() => {
store.dispatch('user/resetToken').then(() => {
location.reload()
})
})
}
return Promise.reject(new Error(res.message || 'Error'))
} else {
return res
}
},
error => {
console.log('err' + error) // for debug
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
6.得到api调用的结果
(login/index.vue中部分代码)
<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">Login Form</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-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @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>
7.保存token到vuex
(store/modules/user.js部分代码)
const getDefaultState = () => {
return {
token: getToken(),
username: '',
userFace: ''
}
}
const state = getDefaultState()
const mutations = {
RESET_STATE: (state) => {
Object.assign(state, getDefaultState())
},
SET_TOKEN: (state, token) => {
state.token = token
},
SET_NAME: (state, username) => {
state.username = username
},
SET_AVATAR: (state, userFace) => {
state.userFace = userFace
}
}
const actions = {
// user login
login({ commit }, userInfo) {
const { username, password } = userInfo
return new Promise((resolve, reject) => {
login({ username: username.trim(), password: password }).then(response => {
const { data } = response
commit('SET_TOKEN', data.token)
setToken(data.token)
resolve()
}).catch(error => {
reject(error)
})
})
},
}
8.token做到持久化。
(utils/auth.js) 本文使用的是cookie存
import Cookies from 'js-cookie'
const TokenKey = 'vue_admin_template_token'
export function getToken() {
return Cookies.get(TokenKey)
}
export function setToken(token) {
return Cookies.set(TokenKey, token)
}
export function removeToken() {
return Cookies.remove(TokenKey)
}