登录模块
登录页面基本布局
**目标**完成登录页面的基础布局
- 基本布局
<!-- 放置标题图片 @是设置的src目录别名-->
<div class="title-container">
<h3 class="title">
<img src="@/assets/common/login-logo.png" alt="">
</h3>
</div>
- 样式处理
/* reset element-ui css */
.login-container {
// 设置背景图片
background-image: url('~@/assets/common/login.jpg');
// 将图片位置设置为充满整个屏幕
background-position: center;
}
**注意**: 如需要在样式表中使用**@**别名的时候,需要在@前面加上一个**~**符号,否则不识别
- 设置手机号和密码的字体颜色
// 修改输入框字体颜色
$light_gray: #407ffe;
- 设置输入表单整体背景色
.el-form-item {
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(255, 255, 255, 0.9); // 输入登录表单的背景色
border-radius: 5px;
color: #454545;
}
- 设置错误信息的颜色
.el-form-item__error {
color: #fff;
font-size: 14px;
}
- 设置登录按钮的样式
.login-btn {
background: #407ffe;
height: 64px;
line-height: 32px;
font-size: 24px;
}
需要给el-button 增加一个login-btn的class样式
- 修改显示的提示文本和登录文本
<div class="tips">
<span style="margin-right:20px;">账号: 13800000002</span>
<span> 密码: 123456</span>
</div>
总结:实现登录页面的布局样式
注意:定义element组件样式,需要找到对应元素的类名,进行覆盖
登录表单的校验
**目标**对登录表单进行规则校验基础模板已经有了基础校验的代码,我们只需在此基础上修改即可
- el-form表单校验的必要条件
-
el-form标签上面绑定model和rules熟悉
-
model指的是绑定到输入域上的表单数据
-
rules表示验证的规则配置
-
-
el-form-item标签上绑定prop属性,值是要验证的表单的输入域的name
-
el-input上面要双向绑定输入域的name
-
- 登录表单验证
- 手机号必填,并且进行格式校验
- 密码必填,长度6-16位之间
// @/utils/validate.js
// 校验手机号
export function validMobile(str) {
// 正则的方法test用于判断str是否符合正则的规则
return /^1[3-9]\d{9}$/.test(str)
}
// -------------------------------------
import { validMobile } from '@/utils/validate'
data() {
// 自定义校验函数
const validateMobile = (rule, value, callback) => {
if (!validMobile(value)) {
callback(new Error('手机格式有误'))
} else {
callback()
}
}
return {
loginForm: {
mobile: '13800000002',
password: '123456'
},
loginRules: {
mobile: [
{ required: true, message: '请输入手机号', trigger: ['blur', 'change'] },
{ validator: validateMobile, trigger: ['blur', 'change'] }
],
password: [
{ required: true, message: '请输入密码', trigger: ['blur', 'change'] },
{ min: 6, max: 16, message: '密码必须是6-16位', trigger: ['blur', 'change'] }
]
},
loading: false,
passwordType: 'password',
redirect: undefined
}
},
总结:遵循ElementUI的表单验证规则;自定义验证规则要熟悉
- 内置表单验证规则
- 自定义表单验证规则
注意:1、el-form绑定model和rules;2、el-form-item绑定prop;3、表单输入域el-input需要v-model绑定name属性
- 手动进行表单验证
handleLogin () {
// 点击登录按钮时,触发表单验证
// validate函数属于谁?el-form组件的实例对象
this.$refs.loginForm.validate(valid => {
if (valid) {
console.log('表单验证通过')
// 调用接口实现登录
} else {
console.log('表单验证失败')
}
})
}
总结:
- 手动表单验证
- ref直接操作组件实例
- 通过ref属性绑定组件
- 通过this.$refs.名称,访问组件实例
登录基本功能
**目标**实现基本的登录功能
handleLogin () {
// 点击登录按钮时,触发表单验证
// validate函数属于谁?el-form组件的实例对象
this.$refs.loginForm.validate(async valid => {
if (valid) {
// 调用接口实现登录
try {
const ret = await login({
mobile: this.loginForm.username,
password: this.loginForm.password
})
if (ret.data.code === 10000) {
// 登录成功,跳转到主页面,缓存token
sessionStorage.setItem('mytoken', ret.data.data)
this.$router.push('/')
} else {
console.log('登录失败')
}
} catch {
console.log('登录失败')
}
} else {
console.log('表单验证失败')
}
})
}
总结:调用接口;获取返回的状态,跳转到主页面
注意:需要缓存token
封装单独的登录接口
**目标**在单独请求模块中,单独封装登录接口完成登录模块之后,我们需要对登录接口进行封装, 登录请求应该单独封装到一个模块中, 便于维护和管理
- 封装基本的接口模块
api/user.js
import request from '@/utils/request'
// 登录功能
export function login (data) {
return request({
method: 'post',
url: '/sys/login',
data: data
})
}
- 封装一个Action方法实现登录
// 用户模块
import { login } from '@/api/user.js'
export default {
namespaced: true,
state: {},
mutations: {},
actions: {
// 实现登录的Action
async login (context, payload) {
try {
const ret = await login(payload)
if (ret.data.code === 10000) {
// 登录成功,缓存token
sessionStorage.setItem('mytoken', ret.data.data)
return true
} else {
// 登录失败
return false
}
} catch {
// 登录失败(网络错误)
return false
}
}
}
}
- 重构登录功能
handleLogin () {
this.$refs.loginForm.validate(async valid => {
if (valid) {
// 表单验证通过,触发action
this.$store.dispatch('user/login', {
mobile: this.loginForm.username,
password: this.loginForm.password
}).then(ret => {
if (ret) {
// 登录成功,跳转到主页面
this.$router.push('/')
} else {
// 登录失败
alert('用户名或者密码错误')
}
})
}
})
}
总结
- 封装登录接口api
- 封装调用接口的Action方法
- 组件中把Action映射为函数并且调用方法实现登录
- 基于async函数和mapActions优化代码
// 把Action映射为函数
...mapActions('user', ['login']),
handleLogin () {
this.$refs.loginForm.validate(async valid => {
if (valid) {
// 表单验证通过,触发action
const ret = await this.login({
mobile: this.loginForm.username,
password: this.loginForm.password
})
if (ret) {
// 登录成功,跳转到主页面
return this.$router.push('/')
}
// 基于ElementUI提供的提示功能
this.$message.error('用户名或者密码错误')
}
})
}
总结:
- Promise的方式可以基于Async函数优化
- 原始的dispatch方法触发Action可以基于映射函数进行优化
基于vuex管理token
**目标**封装获取token的登录action, 在vuex中存储token
- 准备状态
store/modules/user.js
state: {
token: null
},
- 封装 action 和 对应的 mutation
store/modules/user.js
mutations: {
updateToken (state, payload) {
state.token = payload
}
},
actions: {
// 实现登录功能
async login (context, payload) {
try {
const ret = await reqLogin(payload)
if (ret.data.code === 10000) {
// 缓存用户信息
context.commit('updateToken', ret.data.data)
// 登录成功
return true
} else {
return false
}
} catch {
return false
}
}
}
总结:token的值不是标准的JWT,并且没有refresh_token,单纯存储一个token即可
JWT json web token 是一种加密token一种规范
- 局部模块中触发全局mutation
context.commit('showInfo', ret.data.data, { root: true })
总结:
- 局部模块内,默认触发的是当前模块的mutation
- 如果希望触发全局模块的mutation,需要添加配置作为第三个参数 { root: true }
- 总结
- 基于token的登录流程
- vuex的基本使用
- 异步编程相关知识:回调/Promise/Async函数
- 前端路由:编程式导航
- 组件化开发
- 第三方组件表单验证
- Vue基础指令
token缓存封装
**目标**封装通用的token操作模块本地存储:Cookie/sessionStorage/localStorage
Cookie的问题:
- 本地存储的数据量有限制 4K 1K = 1024B(Byte) 1Byte = 8bit
- 不安全(存储在客户端)
特点:发送请求时会自动携带Cookie;没有浏览器的兼容问题;
Cookie的类型:会话Cookie(浏览器一旦关闭就失效了);持久Cookie(可以设置有效期)
- 模块封装
**utils/auth.js**
import Cookies from 'js-cookie'
const TokenKey = 'vue_admin_template_token_128'
// 获取指定的cookie
export function getToken () {
return Cookies.get(TokenKey)
}
// 设置指定的cookie
export function setToken (token) {
return Cookies.set(TokenKey, token)
}
// 删除指定的cookie
export function removeToken () {
return Cookies.remove(TokenKey)
}
总结:
- 原生js可以操作Cookie,但是很麻烦
- 所以可以基于第三方包 js-cookie操作cookie,比较简单
- 初始化获取缓存信息
const state = {
// 一进来优先从缓存中取
token: getToken() || null // token字符串
}
- vuex中存token时, 也同步存到cookie中
mutations: {
updateToken (state, payload) {
// 修改状态
state.token = payload
// 把token一块存储到缓存中
setToken(payload)
}
},
总结:封装token的基本操作方法,本地缓存采用cookie
- 首次登陆时,缓存token
- 页面打开时,从缓存读取token并初始化state中的token
axios响应拦截器
目标: 处理响应拦截器axios返回的数据中默认增加了一层
**data的包裹**, 每次我们都要 res.data.data, 太麻烦, 所以我们需要在这里处理下。
// 响应拦截器
instance.interceptors.response.use(function(response) {
// 对响应数据做点什么
return response.data
}, function(error) {
// 对响应错误做点什么
return Promise.reject(error)
})
总结:通过响应拦截器优化数据的获取
导航守卫处理
**目标**:根据token处理路由组件的访问权限问题 (没有token的用户, 不让你进系统的)
- 权限拦截的流程图
- 功能代码实现
**src/permission.js**
import router from './router'
import store from './store'
// 白名单
const whiteList = ['/login', '/404', '/abc']
router.beforeEach((to, from, next) => {
// 判断token的存在性
// 从vuex中获取token信息
const token = store.getters.token
if (token) {
// 存在:判断是否为登录页面
if (to.path === '/login') {
// 是登录页面,跳转到主页面
next('/')
} else {
// 不是登录页面,直接放行通过
next()
}
} else {
// 不存在,判断是否请求的路径在白名单里面
if (whiteList.includes(to.path)) {
// 在白名单里面,放行通过即可
next()
} else {
// 不在白名单里面,跳转到登录页面
router.push('/login')
}
}
})
总结:导航守卫要拦截所有请求的路由链接
- 判断是否登录(token的存在性判断)
- 判断是否为白名单(根据需求规定一个白名单)
关于基准路径的配置
注意:
.env.development和.env.production文件用于配置开发环境和生产环境的基准url地址
- .env.development 开发环境
- .env.production 生产环境
# base api
VUE_APP_BASE_API = 'http://ihrm-java.itheima.net/api/'
// 通过process.env.环境变量名称(名称自定义)
// process.env是Node.js的API
export const baseURL = process.env.VUE_APP_BASE_API
总结:为了灵活配置接口的基准路径,可以基于配置文件的方式,在开发和生产环境分别切换到不同的地址。这种方式好处就是代码在开发和上线时修改比较容易