05-登录-表单校验
文档:https://vee-validate.logaretm.com/v4/ 支持vue3.0
第一步:安装
- 执行命令
npm i vee-validate@4.0.3
第二步:导入
- 修改文件
src/views/login/index.vue
import { Form, Field } from 'vee-validate'
第三步:定义校验规则
- 新建文件
src/utils/vee-validate-schema.js
// 定义校验规则提供给vee-validate组件使用
export default {
// 校验account
account (value) {
// value是将来使用该规则的表单元素的值
// 1. 必填
// 2. 6-20个字符,需要以字母开头
// 如何反馈校验成功还是失败,返回true才是成功,其他情况失败,返回失败原因。
if (!value) return '请输入用户名'
if (!/^[a-zA-Z]\w{5,19}$/.test(value)) return '字母开头且6-20个字符'
return true
},
password (value) {
if (!value) return '请输入密码'
if (!/^\w{6,24}$/.test(value)) return '密码是6-24个字符'
return true
},
mobile (value) {
if (!value) return '请输入手机号'
if (!/^1[3-9]\d{9}$/.test(value)) return '手机号格式错误'
return true
},
code (value) {
if (!value) return '请输入验证码'
if (!/^\d{6}$/.test(value)) return '验证码是6个数字'
return true
},
isAgree (value) {
if (!value) return '请勾选同意用户协议'
return true
}
}
- 提取目的
这些校验规则将来在其他表单验证时候可复用
第三步:使用 Form
组件,使用 vee-validate-schema
校验规则
- 修改文件
src/views/login/index.vue
-<div class="form">...</div>
+<Form class="form" :validation-schema="schema" autocomplete="off">...</Form>
import veeSchema from '@/utils/vee-validate-schema'
setup () {
// 控制短信登录切换的
const isMsgLogin = ref(false)
// 表单对象数据
const form = reactive({
isAgree: true,
account: null,
password: null,
mobile: null,
code: null
})
// 校验规则对象
const mySchema = {
account: schema.account,
password: schema.password,
mobile: schema.mobile,
code: schema.code,
isAgree: schema.isAgree
}
return { isMsgLogin, form, scheam:mySchema, submit }
}
第四步:使用 Field
组件,添加表单项目校验
- 修改文件
src/views/login/index.vue
<div class="form-item">
<div class="input">
<i class="iconfont icon-user"></i>
+ <Field :class="{error:errors.mobile}" v-model="form.mobile" name="mobile" type="text" placeholder="请输入手机号" />
</div>
+ <div class="error" v-if="errors.mobile"><i class="iconfont icon-warning" />{{errors.mobile}}</div>
</div>
<div class="form-item" v-if="!isMsgLogin">
<div class="input">
<i class="iconfont icon-lock"></i>
+ <Field :class="{error:errors.password}" v-model="form.password" name="password" type="password" placeholder="请输入密码" />
</div>
+ <div class="error" v-if="errors.password"><i class="iconfont icon-warning" />{{errors.password}}</div>
</div>
<div class="form-item" v-else>
<div class="input">
<i class="iconfont icon-code"></i>
+ <Field :class="{error:errors.code}" v-model="form.code" name="code" type="password" placeholder="请输入验证码" />
<span class="code">发送验证码</span>
</div>
+ <div class="error" v-if="errors.code"><i class="iconfont icon-warning" />{{errors.code}}</div>
</div>
<Form class="form" :validation-schema="schema" v-slot="{errors}"
- 其实就是把input改成
Field
组件,默认解析成input Field
添加name属性,作用是指定使用schema中哪个校验规则Form
添加v-slot="{errors}"
使用作用域插槽暴露errors
错误对象- 通过
errors['校验规则名称']
取出错误信息,有则显示,无即隐藏
- 通过
第五步:如何校验 自定义组件 XtxCheckbox
- 修改文件
src/views/login/index.vue
-<XtxCheckbox v-model="form.isAgree" />
+<Field as="XtxCheckbox" name="isAgree" v-model="form.isAgree"/>
Field
的as
属性可以指定为其他标签,也可指定为组件。- 但是组件需要支持 v-model 否则校验不会触发。
第六步:如何在切换 短信 与 账户 登录时候清空表单和校验结果
- 修改文件
src/views/login/index.vue
<Form ref="formCom" class="form" :validation-schema="schema" v-slot="{errors}"
// 切换表单元素,还原数据和清除校验效果
const formCom = ref(null)
watch(isMsgLogin, () => {
// 还原数据
form.isAgree = true
form.account = null
form.password = null
form.mobile = null
form.code = null
// 补充校验效果清除,Form组件提供resetForm()
formCom.value.resetForm()
})
- 首先需要自己手动清除数据,然后使用Form 组件提供 resetForm 方法对表单进行清除校验结果
第七步:如何整体表单校验
- 修改文件
src/views/login/index.vue
<a @click="login()" href="javascript:;" class="btn">登 录</a>
// 需要在点击登录的时候对整体表单进行校验
const login = async () => {
// Form组件提供了一个 validate 函数作为整体表单校验,当是返回的是一个promise
const valid = await formCom.value.validate()
console.log(valid)
}
return { isMsgLogin, form, schema: mySchema, formCom, login }
Form
组件提供 validate 方法对表单进行整体校验
06-登录-消息提示组件封装
目的:在接口请求报错的时候给用户进行提示
组件功能分析:
- 固定顶部显示,有三种类型:成功,错误,警告。
- 显示消息提示时需要动画从上滑入且淡出。
- 组件使用的方式不够便利,封装成工具函数方式。
大致实现步骤:
-
先把布局,和三种情况的显示,完成。
- 定义组件:
src/components/library/xtx-message.vue
- 定义组件:
<template>
<div class="xtx-message" :style="style[type]">
<!-- 上面绑定的是样式 -->
<!-- 不同提示图标会变 -->
<i class="iconfont" :class="[style[type].icon]"></i>
<span class="text">{{text}}</span>
</div>
</template>
<script>
export default {
name: 'XtxMessage',
props: {
text: {
type: String,
default: ''
},
type: {
type: String,
// warn 警告 error 错误 success 成功
default: 'warn'
}
},
setup () {
// 定义一个对象,包含三种情况的样式,对象key就是类型字符串
const style = {
warn: {
icon: 'icon-warning',
color: '#E6A23C',
backgroundColor: 'rgb(253, 246, 236)',
borderColor: 'rgb(250, 236, 216)'
},
error: {
icon: 'icon-shanchu',
color: '#F56C6C',
backgroundColor: 'rgb(254, 240, 240)',
borderColor: 'rgb(253, 226, 226)'
},
success: {
icon: 'icon-queren2',
color: '#67C23A',
backgroundColor: 'rgb(240, 249, 235)',
borderColor: 'rgb(225, 243, 216)'
}
}
return { style }
}
}
</script>
<style scoped lang="less">
.xtx-message {
width: 300px;
height: 50px;
position: fixed;
z-index: 9999;
left: 50%;
margin-left: -150px;
top: 25px;
line-height: 50px;
padding: 0 25px;
border: 1px solid #e4e4e4;
background: #f5f5f5;
color: #999;
border-radius: 4px;
i {
margin-right: 4px;
vertical-align: middle;
}
.text {
vertical-align: middle;
}
}
</style>
- 使用组件
<XtxMessage text="手机号或密码错误" type="error" />
- 实现显示的时候动画效果
<template>
+ <Transition name="down">
+ <div class='xtx-message' :style="style" v-show="show">
<!-- 上面绑定的是样式 -->
<!-- 不同提示图标会变 -->
<i class="iconfont" :class="[style[type].icon]"></i>
<span class="text">{{text}}</span>
</div>
+ </Transition>
</template>
<script>
+import { onMounted, ref } from 'vue'
export default {
name: 'XtxMessage',
props: {
text: {
type: String,
default: ''
},
type: {
type: String,
// warn 警告 error 错误 success 成功
default: 'warn'
}
},
setup () {
// 定义一个对象,包含三种情况的样式,对象key就是类型字符串
const style = {
warn: {
icon: 'icon-warning',
color: '#E6A23C',
backgroundColor: 'rgb(253, 246, 236)',
borderColor: 'rgb(250, 236, 216)'
},
error: {
icon: 'icon-shanchu',
color: '#F56C6C',
backgroundColor: 'rgb(254, 240, 240)',
borderColor: 'rgb(253, 226, 226)'
},
success: {
icon: 'icon-queren2',
color: '#67C23A',
backgroundColor: 'rgb(240, 249, 235)',
borderColor: 'rgb(225, 243, 216)'
}
}
+ // 定义一个数据控制显示隐藏,默认是隐藏,组件挂载完毕显示
+ const visible = ref(false)
+ onMounted(() => {
+ visible.value = true
+ })
+ return { style, visible }
}
}
</script>
<style scoped lang='less'>
+.down {
+ &-enter {
+ &-from {
+ transform: translate3d(0,-75px,0);
+ opacity: 0;
+ }
+ &-active {
+ transition: all 0.5s;
+ }
+ &-to {
+ transform: none;
+ opacity: 1;
+ }
+ }
+}
// 。。。 省略
- 封装成vue实例函数式调用
- vue3.0使用app.config.globalProperties挂载原型方法
- 也支持直接导入函数使用
src/components/library/Message.js
// 实现使用函数调用xtx-message组件的逻辑
import { createVNode, render } from 'vue'
import XtxMessage from './xtx-message.vue'
// 准备dom容器
const div = document.createElement('div')
div.setAttribute('class', 'xtx-message-container')
document.body.appendChild(div)
// 定时器标识
let timer = null
export default ({ type, text }) => {
// 实现:根据xtx-message.vue渲染消息提示
// 1. 导入组件
// 2. 根据组件创建虚拟节点
const vnode = createVNode(XtxMessage, { type, text })
// 3. 准备一个DOM容器
// 4. 把虚拟节点渲染DOM容器中
render(vnode, div)
// 5. 开启定时,移出DOM容器内容
clearTimeout(timer)
timer = setTimeout(() => {
render(null, div)
}, 3000)
}
src/components/library/index.js
import Message from './Message'
// 定义指令
defineDirective(app)
+ // 如果你想挂载全局的属性,能够通过组件实例调用的属性 this.$message
+ app.config.globalProperties.$message = Message// 原型函数
}
- 在登录逻辑中使用
src/views/login/index.vue
import Message from '@/components/library/Message'
// 帐号密码登录
userAccountLogin(form).then(data => {
// 成功
}).catch(e => {
// 失败
+ Message({ type: 'error', text: '登录失败' })
})
07-登录-账户登录
目的:完成以账户进行登录
定义API src/api/user.js
import request from '@/utils/request'
/**
* 帐号登录
* @param {String} account - 用户名
* @param {String} password - 密码
* @returns Promise
*/
export const userAccountLogin = ({ account, password }) => {
return request('/login', 'post', { account, password })
}
定义修改用户信息的 mutations src/store/module/user.js
// 用户状态
export default {
namespaced: true,
state: () => ({
id: '',
+ account:'',
nickname: '',
avatar: '',
token: '',
mobile: ''
})
}
进行登录 src/views/login/index.vue
import { userAccountLogin } from '@/api/user'
import Message from '@/components/library/Message'
import { useStore } from 'vuex'
import { useRoute, useRouter } from 'vue-router'
// 使用store
const store = useStore()
// 使用router
const router = useRouter()
// 使用route
const route = useRoute()
// 登录提交
const submit = async () => {
// 整体校验
const valid = await target.value.validate()
console.log(valid)
if (valid) {
// 发送请求
if (!isMsgLogin.value) {
// 帐号密码登录
userAccountLogin(form).then(data => {
// 成功
// 1. 存储信息
const { id, account, nickname, avatar, token, mobile } = data.result
store.commit('user/setUser', { id, account, nickname, avatar, token, mobile })
// 2. 提示
Message({ type: 'success', text: '登录成功' })
// 3. 跳转
router.push(route.query.redirectUrl || '/')
}).catch(e => {
// 失败
Message({ type: 'error', text: e.response.data.message || '登录失败' })
})
} else {
// 短信登录
}
}
}
08-登录-手机号登录
src/api/user.js
实现代码
封装好发短信和进行短信登录的接口API
/**
* 短信登录
* @param {String} mobile - 手机号
* @param {String} code - 验证码
* @returns Promise
*/
export const userMobileLogin = ({ mobile, code }) => {
return request('/login/code', 'post', { mobile, code })
}
/**
* 获取短信登录验证码
* @param {String} mobile - 手机号
* @returns Promise
*/
export const userMobileLoginMsg= (mobile) => {
return request('/login/code', 'get', { mobile })
}
src/views/login/index.vue
实现代码发送短信
根据数据渲染按钮文字,绑定点击事件发送验证码:
<span @click="send()" class="code">
{{time===0?'发送验证码':`${time}秒后发送`}}
</span>
使用 schma 函数来校验mobile,如果成功继续执行,不成功使用Form
组件错误 setFieldError
发送验证码,需要校验手机号,和判断是否60秒内,方可发送。组件销毁时候清除定时器。
import { useIntervalFn } from '@vueuse/core'
// pause 暂停 resume 开始
// useIntervalFn(回调函数,执行间隔,是否立即开启)
const time = ref(0)
const { pause, resume } = useIntervalFn(() => {
time.value--
if (time.value <= 0) {
pause()
}
}, 1000, false)
onUnmounted(() => {
pause()
})
// 发送短信
const send = async () => {
const valid = mySchema.mobile(form.mobile)
if (valid === true) {
// 通过
if (time.value === 0) {
// 没有倒计时才可以发送
await userMobileLoginMsg(form.mobile)
Message({ type: 'success', text: '发送成功' })
time.value = 60
resume()
}
} else {
// 失败,使用vee的错误函数显示错误信息 setFieldError(字段,错误信息)
formCom.value.setFieldError('mobile', valid)
}
}
return { isMsgLogin, form, schema: mySchema, formCom, login, send, time }
src/views/login/index.vue
实现代码手机号登录
// 使用store
const store = useStore()
// 使用router
const router = useRouter()
// 使用route
const route = useRoute()
// 登录提交
const submit = async () => {
// 整体校验
const valid = await target.value.validate()
if (valid) {
// 发送请求
let data = null
try {
if (!isMsgLogin.value) {
// 帐号登录
data = await userAccountLogin(form)
} else {
// 短信登录
// 1. 定义两个API 短信登录,获取短信验证码
// 2. 实现发送短信验证码发送功能
// 3. 完成手机号短信验证码登录逻辑
data = await userMobileLogin(form)
}
} catch (e) {
Message({ type: 'error', text: e.response.data.message || '登录失败' })
}
// 成功
// 1. 存储信息
const { id, account, nickname, avatar, token, mobile } = data.result
store.commit('user/setUser', { id, account, nickname, avatar, token, mobile })
// 2. 提示
Message({ type: 'success', text: '登录成功' })
// 3. 跳转
router.push(route.query.redirectUrl || '/')
}
}
09-退出登录
目的:完成退出
src/components/app-topnav.vue
<template v-if="profile.token">
<li>
<a href="javascript:;"><i class="iconfont icon-user"></i>
{{profile.account}}
</a>
</li>
<li><a @click="logout()" href="javascript:;">退出登录</a></li>
</template>
import { computed } from 'vue'
import { useStore } from 'vuex'
import { useRouter } from 'vue-router'
export default {
name: 'AppTopnav',
setup () {
// 获取用户的登录信息才能控制切换导航菜单
const store = useStore()
// 使用vuex中的state需要设置计算属性,否则不是响应式
const profile = computed(() => {
return store.state.user.profile
})
+ const router = userRouter()
+ const logout = () => {
+ store.commit('user/setUser',{})
+ router.push('/login')
+ }
+ return { profile, logout}
}
}