完善登录
登录表单校验
对于element-plus中想要对表单进行表单校验需要关注以下3点:
- 为e-form绑定model属性
- 为el-form绑定rules属性
- 为el-form-item绑定prop属性
新建views/login/rules.js,用来存放验证规则,如果是大厂项目,它的验证规则比较复杂,适合用单独文件进行存储;
views/login/rules.js
export const validatePassword = () => {
return (rule, value, cb) => {
if (value.length < 6) {
cb(new Error('密码不能少于6位!'))
} else {
cb()
}
}
}
密码框状态通用处理
密码框默认是密文状态,如果点击小眼睛图标,则会显示出明文状态;这里我们是动态修改input的type类型即可,type为password是密文显示,type为text则为明文显示;
此时views/login/index.vue代码为:
views/login/index.vue
<template>
<div class="login-container">
<el-form class="login-form" :model="loginForm" :rules="loginRules">
<div class="title-container">
<h3 class="title">用户登录</h3>
</div>
<el-form-item prop="username">
<span class="svg-container">
<SvgIcon icon="user"></SvgIcon>
</span>
<el-input
placeholder="username"
name="username"
type="text"
v-model="loginForm.username"
></el-input>
</el-form-item>
<el-form-item prop="password">
<span class="svg-container">
<SvgIcon icon="password"></SvgIcon>
</span>
<el-input
placeholder="password"
name="password"
:type="passwordType"
v-model="loginForm.password"
></el-input>
<span class="show-pwd">
<span class="svg-container" @click="changeType">
<SvgIcon
:icon="passwordType === 'password' ? 'eye' : 'eye-open'"
></SvgIcon>
</span>
</span>
</el-form-item>
<el-button type="primary" style="width: 100%; margin-bottom: 30px"
>登录</el-button
>
</el-form>
</div>
</template>
<script setup>
import { ref } from 'vue'
import SvgIcon from '@/components/SvgIcon'
import { validatePassword } from './rules'
// 数据源
const loginForm = ref({
username: 'super-admin',
password: '123456'
})
// 验证规则
const loginRules = ref({
username: [
{
required: true,
trigger: 'blur',
message: '用户名为必填项'
}
],
password: [
{
required: true,
trigger: 'blur',
validator: validatePassword()
}
]
})
// 处理密码框文本显示
const passwordType = ref('password')
const changeType = () => {
// 当passwordType的值为password改成text
if (passwordType.value === 'password') {
passwordType.value = 'text'
} else {
passwordType.value = 'password'
}
}
</script>
通用后台登录方案解析
1.axios模块封装;
2.接口请求模块;
3.登录请求动作;
4.Token缓存;
5.登录鉴权;
1.配置环境变量封装axios模块
我们希望封装出来的axios模块,必须能根据当前模式的不同,设定不同的BaseUrl,因为企业级项目在开发状态和生产状态的下的BaseUrl是不同的;
对于@vue/cli来说,它具备3种模式:
1.development
2.test
3.production
我们这里使用@vue/cli提供的环境变量实现,详情🔎
我们可以在项目中创建两个文件:
- 开发状态:
.env.development
.env.development
ENV = 'development'
VUE_APP_BASE_API = '/api'
- 生产状态:
.env.production
.env.production
ENV = 'production'
VUE_APP_BASE_API = '/prod-api'
js
// 安装axios
npm i axios --save
新建utils/request.js
utils/request.js
import axios from 'axios'
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 5000
})
export default service
2.封装请求动作
1.封装接口请求模块
2.封装登录请求动作
封装接口请求模块:
创建api/sys.js
api/sys.js
import request from '@/utils/request'
// 登录
export const login = (data) => {
return request({
url: 'sys/login',
methods: 'POST',
data
})
}
封装登录请求动作:
我们期望把它封装到vuex的action中; 创建store/modules/user.js,用于处理所有和用户相关的内容(此处用第三方包md5)
js
// 安装md5
npm i md5 --save
store/modules/user.js
import { login } from '@/api/sys'
import md5 from 'md5'
export default {
namespaced: true,
state: () => ({}),
mutations: {},
actions: {
/**
* 登录请求action
*/
login: (userInfo) => {
const { username, password } = userInfo
return new Promise((resolve, reject) => {
login({
username,
password: md5(password)
})
.then((data) => {
resolve()
})
.catch((err) => {
reject(err)
})
})
}
}
}
在store/index.js完成模块注册:
store/index.js
import { createStore } from 'vuex'
import user from './modules/user'
export default createStore({
modules: {
user
}
})
3.登录触发动作
在vue.config.js中配置devServer;老师给的api地址不好用,我这里是用egg起了一个服务,所以target是我本地的地址,这里大家也可以freestyle;
vue.config.js
// webpack devServer提供了代理的功能,该代理可以把所有请求到当前服务中的请求,转发(代理)到另外的服务器上
devServer: {
proxy: {
// 当地址中包含/api时,触发此代理
// changeOrigin跨域设置
'/project': {
target: 'http://localhost:7001/',
changeOrigin: true
}
}
},
views/login/index.vue
// 按钮添加点击事件
// form上加一个ref
<template>
<el-form
ref="loginFormRef"
>
<el-button
type="primary"
:loading="loading"
@click="handleLogin"
style="width: 100%; margin-bottom: 30px"
>登录</el-button
>
</el-form>
</template>
<script>
import { useStore } from 'vuex'
// 处理登录
const loading = ref(false)
const store = useStore()
const loginFormRef = ref(null)
const handleLogin = () => {
// 1.进行表单校验
loginFormRef.value.validate((valid) => {
if (!valid) return
// 2.触发登录动作
loading.value = true
store
.dispatch('user/login', loginForm.value)
.then(() => {
loading.value = false
// 3.进行登录后处理
})
.catch((err) => {
loading.value = false
console.log('err--->', err)
})
})
}
</script>
4.本地缓存处理方案
在获取到token之后,我们会把token进行缓存,而缓存的方式将会分为两种:
1.本地缓存(LocalStorage): 为了方便实现自动登录;
2.全局状态管理(Vuex): 为了后面在其他位置使用;
LocalStorage
创建utils/storage.js文件,封装3个对应方法:
utils/storage.js
/**
* 存储数据
*/
export const setItem = (key, value) => {
// value分为复杂数据和基本数据
if (typeof value === 'object') {
value = JSON.stringify(value)
}
window.localStorage.setItem(key, value)
}
/**
* 获取数据
*/
export const getItem = (key) => {
const data = window.localStorage.getItem(key)
try {
return JSON.parse(data)
} catch {
return data
}
}
/**
* 删除指定数据
*/
export const removeItem = (key) => {
window.localStorage.removeItem(key)
}
/**
* 删除所有数据
*/
export const removeAll = () => {
window.localStorage.clear()
}
修改modules/user.js
js
import { login } from '@/api/sys'
import md5 from 'md5'
import { setItem, getItem } from '@/utils/storage'
import { TOKEN } from '@/constant'
export default {
namespaced: true,
state: () => ({
token: getItem(TOKEN) || ''
}),
mutations: {
setToken(state, token) {
state.token = token
setItem(TOKEN, token)
}
},
actions: {
/**
* 登录请求action
*/
login(context, userInfo) {
const { username, password } = userInfo
return new Promise((resolve, reject) => {
login({
username,
password: md5(password)
})
.then((res) => {
this.commit('user/setToken', res.data.data.token)
resolve()
})
.catch((err) => {
reject(err)
})
})
}
}
}
创建constants/index.js,用来定义常量;
constants/index.js
export const TOKEN = 'token'
5.响应数据统一处理
上面通过res.data.data.token获取token是非常不方便的,这里用axios响应拦截器进行优化;
修改utils/request.js
utils/request.js
import axios from 'axios'
import { ElMessage } from 'element-plus'
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 5000
})
// 响应拦截器
service.interceptors.response.use(
(response) => {
const { success, message } = response.data
if (success) {
return response.data
} else {
// 失败时,消息提示
ElMessage.error(message)
return Promise.reject(new Error(message))
}
},
(err) => {
ElMessage.error(err)
return Promise.reject(new Error(err))
}
)
export default service
6.登录后操作
我们距离登录操作还差最后一个登录鉴权;
什么是登录鉴权?
当用户未登陆时,不允许进入除login之外的其他页面;
用户登录后,token未过期之前,不允许进入login页面;
想要实现这个功能,最好的方式是通过路由守卫来进行实现;
创建layout/index.vue;
layout/index.vue
<template>
<div>layout</div>
</template>
<script>
export default {}
</script>
<style></style>
router/index.js添加一个路由;
router/index.js
{
path: '/',
component: () => import('@/layout/index')
}
创建一个permission.js,和main.js同级;
permission.js
import router from '@/router'
import store from '@/store'
// 白名单
const whiteList = ['/login']
/**
* 处理前置守卫
* to: 要到哪里去
* from: 从哪里来
* next: 是否要去
*/
router.beforeEach((to, from, next) => {
// 1. 用户已登录,则不允许进入login
// 2. 用户未登录,则不允许出login
// 用token判断是否登录
if (store.getters.token) {
if (to.path === '/login') {
next('/')
} else {
next()
}
} else {
if (whiteList.includes(to.path)) {
next()
} else {
next('/login')
}
}
return false
})
在main.js中导入permisssion.js
main.js
// 导入路由鉴权
import './permission'
给store加一个getters;
/store/getters.js
// 快捷访问
const getters = {
token: (state) => state.user.token
}
export default getters