天行健,君子以自强不息;地势坤,君子以厚德载物。
每个人都有惰性,但不断学习是好好生活的根本,共勉!
文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。
浮云游子意,落日故人情。
——《送友人》
文章目录
接着上一篇继续
先进行登录的优化
然后再进行找回密码功能的实现
先进行上一篇最后一部分登录问题进行优化
优化内容如下:
1.部分警告,虽然不影响项目正常使用,但控制台输出的警告看着还是比较难受的
2.取消勾选记住用户名和密码操作,用户名密码依旧显示,该问题是因为当记住用户名密码对应的值移除后,其值变为undefined,而代码中的判断在其为undefined时依旧满足条件执行,需要将其值与ture进行比较
19. 登录优化(记住用户名和密码)
部分其他代码也进行了优化,以下代码基于上一篇最后下载的项目进行改动
改动如下
19.1 api/api.ts
src/api/api.ts 注掉了一部分无用代码
// 引入axios
import axios from 'axios'
// 引入store
import store from '@/store';
import { useRouter } from 'vue-router';
// 数据请求自定义配置(实例)
// console.log(import.meta.url,"------------");
const api = axios.create({
// baseURL: 'https://mo_sss.blog.csdn.net.cn',
// baseURL: import.meta.BaseURL,
// baseURL: 'https://hanshanlibai.gms.com',
baseURL: 'http://127.0.0.1:8888/',
timeout: 1000,
headers: {
// 'X-Custom-Header': 'foobar'
'Content-Type': 'application/json;charset=UTF-8'
}
// withCredentials 表示跨域请求时是否需要使用凭证,默认是true
// withCredentials: true,
// responseType 表示浏览器将要响应的数据类型,包括arraybuffer、document、json、text、stream
// 浏览器专属类型: blob
// 默认值就是json
// responseType: 'json',
// responseEncoding 表示用于解码响应的编码(Node.js专属),注意,忽略responseType值为stream或者客户端请求
// 默认值为utf-8
// responseEncoding: 'utf-8'
});
// const router = useRouter();
// 添加请求拦截器
api.interceptors.request.use(function (config) {
console.log("request------------------->")
// 在发送请求之前做些什么
// 获取缓存中的token
const token = store.getters.getToken;
if(token){
// 如果token为真值,则将其赋值赋给请求头
config.headers.token = token;
}
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
api.interceptors.response.use(function (response) {
console.log("response-------------------> code",response.status)
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
// store.commit("logout");
// router.push('/UserLogin')
return response;
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
if(error.response){
console.log(error.response.status)
switch(error.response.status){
case 401:
// case 200:
store.commit("logout");
// router.replace({
// path: '/UserLogin',
// query: {
// redirect: router.currentRoute.value.fullPath
// }
// })
break;
default:
store.commit("logout");
// router.replace({
// path: "/UserLogin",
// query: {
// redirect: router.currentRoute.value.fullPath
// }
// })
}
}
return Promise.reject(error);
});
export default api;
19.2 store/index.ts
src/store/index.ts 新增了登出时清除缓存数据的代码,此处为缓存校验时使用
// 引入, 用于存储全局的状态数据,可供其他地方调用
import { createStore } from "vuex";
// 引入工具方法
import utils from "@/utils/utils";
// 创建一个新的store实例
const store = createStore({
state() {
return{
// count: 0
// 当前登录的用户信息
userInfo: {},
// 当前登录的标识token
token: null,
}
},
getters: {
getUserInfo(state:any){
return state.userInfo;
},
getToken(state:any){
return state.token;
}
},
mutations: {
// 登出,清除缓存中的数据
logout: function(state:any){
console.log("---111---")
state.userInfo = null;
utils.removeData("userInfo");
utils.removeData("token");
// utils.removeData("username");
// utils.saveData("username","");
// utils.removeData("saveUsername");
// utils.removeData("password");
// utils.removeData("savePassword");
},
// 存储用户信息
setUserInfo: function(state:any, userInfo:any){
state.userInfo = userInfo;
utils.saveData('userInfo', userInfo);
},
// 存储token
setToken: function(state:any, token:any){
state.toekn = token;
utils.saveData('token', token);
}
}
})
export default store;
19.3 index/HomeIndex.vue
src/views/index/HomeIndex.vue 未做改变,但也列出来看一下
<script setup lang="ts">
import utils from '@/utils/utils';
import { onMounted } from 'vue';
</script>
<template>
后台主页
</template>
<style scoped>
</style>
19.4 PhoneCodeForm.vue
src/views/login/components/PhoneCodeForm.vue 对登录缓存校验进行了优化
<script setup lang="ts">
import { ref, reactive, onMounted, onUnmounted } from 'vue'
// 引入状态存储工具store
import {useStore} from 'vuex'
// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'
// 路由引入
import { useRoute, useRouter } from 'vue-router';
// 登录表单的实例
// let loginFormRef = ref(null);
let loginFormRef = ref()
// 登录表单的数据
const loginForm = reactive({
// 用户名
username: '',
// 手机验证码
smscode: '',
// 图片验证
imgcode: '',
// 记住用户名,默认否
saveUsername: false
})
// 登录验证规则
const rules = {
username: [
{
required: true,
message: '请输入用户名',
trigger: 'blur'
}
],
smscode: [
{
required: true,
message: '请输入短信验证码',
trigger: 'blur'
}
],
imgcode: [
{
required: true,
message: '请输入图片验证码',
trigger: 'blur'
}
]
}
const formSize = "";
// 图片验证码路径
let imgCodeSrc = new URL('../../../assets/code.png', import.meta.url).href
// const imgCodeSrc = '../../../assets/code.png';
// 刷新图片验证码
const getImgCode = () => {
// 后续改为从服务器上获取动态图片
imgCodeSrc = new URL('../../../assets/code.png', import.meta.url).href
}
// 定时器
let timer: any = null
// 获取短信验证码的间隔时间
let curTime = 0
// 获取短信验证码按钮的文本显示内容
let smsCodeBtnText = ref('获取验证码')
// 获取短信验证码
const getSmsCode = () => {
// 当点击获取短信验证码时,如果其他信息没填则提示输入
if (!loginForm.username) {
utils.showError('请输入用户名')
return
}
// if(!loginForm.smscode){
// utils.showError('请输入短信验证码');
// return;
// }
// TODO 从后台获取短信验证码
// 调用接口生成短信验证码
// 1 直接使用axios请求后端完整地址请求
// axios({
// method: 'post',
// url: 'http://127.0.0.1:8888/login/redis/setMessageCode',
// // url: 'login/redis/setMessageCode',
// // 这里需要注意,不管请求方式是什么,这里是根据后端传参方式来定的,如果后端使用@RequestParam则这里使用params作为key
// params: {
// username: loginForm.username
// }
// });
// 2 使用axios实例传参请求后端接口地址的用法
api({
method: 'post',
url: '/login/redis/setMessageCode',
params: {
username: loginForm.username
}
})
curTime = 60
timer = setInterval(() => {
curTime--;
smsCodeBtnText.value = curTime + '秒后重新获取';
if (curTime <= 0) {
smsCodeBtnText.value = '获取验证码'
clearInterval(timer)
// 清除时,值为空,防止重复点击触发多次
timer = null
}
}, 1000)
}
// 状态存储的store
const store = useStore();
// 路由,转到指定页面,使用push
// const route = useRoute();
const router = useRouter();
// 登录提交事件
const onSubmit = () => {
// form表单中的值,校验,
loginFormRef.value.validate((valid: string, fileds: any) => {
// 如果valid值为假,则遍历输出报错
if (!valid) {
for (let key in fileds) {
// 获取报错信息中的字段对应的key的索引为0的信息
utils.showError(fileds[key][0].message)
}
return
}
// 登录表单的记住用户名如果被勾选
if (loginForm.saveUsername==true) {
console.log("短信验证登录1:",loginForm.saveUsername);
// 保存输入的用户名
utils.saveData('username', loginForm.username)
// 保存被勾选的操作
utils.saveData('saveUsername', loginForm.saveUsername)
} else {
console.log("短信验证登录2:",loginForm.saveUsername);
// 如果记住用户名的勾选取消,则移除这两个存储的内容
utils.removeData('username')
utils.removeData('saveUsername')
}
// TODO 调用接口登录
// 因为太快了,所以可能看不到效果,可以将下方的hideLoading方法注掉,可以看到效果
utils.showLoadding('正在加载中')
api({
method: 'get',
url: '/login/redis/getMessageCode',
params: {
username: loginForm.username,
smscode: loginForm.smscode
// imgcode: loginForm.imgcode
}
})
.then((res) => {
utils.hideLoadding()
console.log(res)
// console.log(res.status)
// if (!res || res.status != 200 || !res.data || res.data.result != 200 || !res.data.data) {
if (res.status != 200 || res.data.result != 200 || !res.data.msgCode) {
utils.showError('登录失败-请求数据返回有误');
return;
}
// console.log(res.data.data, loginForm.smscode);
if(res.data.msgCode == loginForm.smscode){
utils.showSuccess('登陆成功')
// 存储用户token信息并转到主页
// let userInfo = res.data.data
let userInfo = res.data
let token = res.data.token
// 状态数据存储
store.commit('setUserInfo', userInfo);
store.commit('setToken', token);
// 登录成功后将页面转到主页
router.push('/HomeIndex')
}else if(res.data.msgCode != loginForm.smscode){
utils.showError('登录失败-验证码错误');
return;
}
// utils.showError('登录失败')
})
.catch((error) => {
// utils.hideLoadding();
console.log(error);
utils.showError('登录失败-出现异常')
})
// api.post("/api/login/code",{
// username: loginForm.username,
// smscode: loginForm.smscode,
// imgcode: loginForm.imgcode
// }).then((res)=>{
// utils.hideLoadding();
// console.log(res);
// console.log(res.status);
// if(!res || res.status != 200 || !res.data || res.data.code != 8888 || !res.data.data){
// if(res.data.message){
// utils.showError(res.data.message);
// return;
// }
// utils.showError('登录失败');
// return;
// }
// // 存储用户token信息并转到主页
// let userInfo = res.data.data;
// let token = res.data.token;
// utils.showSuccess('登陆成功');
// }).catch((error)=>{
// // utils.hideLoadding();
// utils.showError('登录失败');
// });
// 登录成功信息提示
// utils.showSuccess("登录成功");
})
}
// 挂载
onMounted(() => {
// 获取记住用户名的值
loginForm.saveUsername = utils.getData('saveUsername')
// 如果记住用户名被勾选,则获取用户名显示(saveUsername可能会是undefined,当为undefined时也是真,故这里不能直接使用saveUsername,而是要判断是否为true)
if (loginForm.saveUsername==true) {
loginForm.username = utils.getData('username')
}
})
// 清空定时器
onUnmounted(() => {
timer && clearInterval(timer)
})
</script>
<template>
<!-- 手机验证码登录 -->
<div class="phoneCodeLoginBox">
<el-form
ref="loginFormRef"
style="max-width: 600px"
:model="loginForm"
:rules="rules"
label-width="0"
class="loginFrom"
:size="formSize"
status-icon
>
<!-- 用户名 -->
<el-form-item prop="username">
<!-- 图标设置,动态绑定username,提示信息,设置输入框大小 -->
<el-input
prefix-icon="UserFilled"
v-model="loginForm.username"
placeholder="请输入用户名"
size="large"
/>
</el-form-item>
<!-- 短信验证 -->
<el-form-item prop="smscode">
<!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 -->
<div class="flex loginLine">
<div class="flexItem">
<el-input
prefix-icon="Iphone"
v-model="loginForm.smscode"
placeholder="请输入验证码"
size="large"
/>
</div>
<div class="codeBtn">
<el-button type="primary" size="large" @click="getSmsCode" :disabled="curTime > 0">{{
smsCodeBtnText
}}</el-button>
</div>
</div>
</el-form-item>
<!-- 图片验证 -->
<el-form-item prop="imgcode">
<!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 -->
<div class="flex loginLine">
<div class="flexItem">
<el-input
prefix-icon="Picture"
v-model="loginForm.imgcode"
placeholder="请输入图片验证码"
size="large"
/>
<!-- <el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' /> -->
</div>
<div class="codeBtn">
<el-image :src="imgCodeSrc" size="large" @click="getImgCode"></el-image>
<!-- <el-button type="primary" size="large" @click="getSmsCode" class="" >获取验证码</el-button> -->
</div>
</div>
</el-form-item>
<!-- 记住用户名 -->
<el-form-item prop="saveUsername">
<el-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox>
</el-form-item>
<!-- 登录按钮 -->
<el-form-item>
<el-button class="loginBtn" type="danger" size="large" @click="onSubmit">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
<style scoped>
/* 按钮宽度设为最大 */
.loginBtn {
width: 100%;
/* 登录按钮圆角边框 */
border-radius: 20px;
}
/* 验证码按钮样式配置 */
.codeBtn {
width: 100px;
margin-left: 10px;
}
/* 按钮和图片宽度100px */
.codeBtn:deep(.el-button),
.codeBtn:deep(img) {
width: 100px;
/* height: 40px; */
}
/* 验证码图片高度 */
.codeBtn:deep(img) {
height: 40px;
/* 鼠标移上去会变成手型 */
cursor: pointer;
}
/* 这一行宽度占满 */
.loginLine {
width: 100%;
}
</style>
19.5 QcodeForm.vue
src/views/login/components/QcodeForm.vue 对二维码生成部分进行了优化
<script setup lang="ts">
import { ref,reactive, onMounted, onUnmounted } from 'vue'
// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'
// 引入store
import {useStore} from 'vuex'
// 引入router
import {useRouter} from 'vue-router'
const store = useStore();
const router = useRouter();
// 二维码
// let qcodePath:any = null;
// 二维码对应的token, 用于判断当前二维码是否已经被扫码登录
let qrToken:string = "";
// 第一次获取验证码
// api({
// method: 'post',
// url: 'login/qr/generateQrCodeAsFile'
// }).then((res)=>{
// if(res.data.result != 200){
// utils.showError("登录失败");
// }
// utils.showSuccess("登录成功");
// qcodePath = res.data.data
// qrToken = res.data.token
// });
// qcodePath = 'E:\\WORKPROJECTS\\MySelfPro\\hslb-general-management-system\\src\\main\resources\\login_qr_pngs\\QRCode.png';
// 二维码
let qcodeSrc = new URL("../../../assets/hslb-qcode.png", import.meta.url).href;
// let qcodeSrc = new URL(qcodePath, import.meta.url).href;
// let qcodeSrc = qcodePath;
const qcodeToken = ref('');
// 当前定时器事件
const curTime = ref(0);
let timer:any = null;
let username:string = utils.getData("username");
const qrString = "100100100222";
// 后台更新获取二维码
const loadQcode = () => {
// 后续改为从服务器上获取动态图片
// const qrString = "100100100222";
console.log("9999999====== "+qrString);
// let username:string = utils.getData("username");
api({
method: 'post',
url: 'login/qr/generateQrCodeAsFile',
params: {
username: username,
qrContent: qrString
}
}).then((res)=>{
// if(res.data.result != 200){
// utils.showError("登录失败");
// }
// utils.showSuccess("登录成功");
// qcodePath = res.data.data
qrToken = res.data.token
});
qcodeSrc = new URL("../../../assets/hslb-qcode.png", import.meta.url).href;
// qcodeSrc = new URL(qcodePath, import.meta.url).href;
// qcodeSrc = qcodePath;
// 初始化token的值
qcodeToken.value = qrToken;
// 设定定时时间
// curTime.value = 60;
// 为了让二维码失效的效果及时,这里暂时设置10秒,后续改回60秒即可
curTime.value = 10;
// 定义定时器,倒计时
timer = setInterval(() => {
curTime.value--;
// 这里获取toekn,校验是否已经被登陆过
checkLogin();
if(curTime.value<=0){
// 事件为0则清空定时器
clearInterval(timer);
timer = null;
}
}, 1000);
};
// 登录提交事件
// const onSubmit = () => {
// };
// 挂载
onMounted(() => {
// 获取二维码
loadQcode();
});
// 清空计时器
onUnmounted(()=>{
timer && clearInterval(timer);
});
// 使用qcodeToken判断当前二维码是否已经被扫码登录
const checkLogin = () => {
// TODO
api({
method: 'post',
url: 'login/qr/generateQrCodeAsFile',
params: {
username: username,
qrContent: qrString
}
}).then((res)=>{
if(res.data.token){
utils.showSuccess("登录成功");
store.commit('setUserInfo',res.data);
store.commit('setToken',res.data.token);
router.push('/HomeIndex');
}
// res.data.token;
}).catch((error)=>{
console.log(error);
// utils.showError("登录失败")
});
}
</script>
<template>
<!-- 扫码登录 -->
<div class="qcodeLoginBox">
<div class="qcodeBox" >
<img class="qcodeImg" :class="{'endImg':curTime<=0}" :src="qcodeSrc" alt="无法获取二维码,请联系客服解决">
<div v-if="curTime<=0" class="endBox" @click="loadQcode" >
当前二维码失效,点击重新加载{{ curTime }}秒
</div>
</div>
<div class="tipInfo" >
使用微信或移动端扫码登录 此二维码将在{{ curTime }}秒后刷新
</div>
</div>
</template>
<style scoped>
/* 二维码窗口样式 */
.qcodeBox{
width: 80%;
height: 80%;
position: relative;
/* 边框自动 */
margin: 0 auto;
}
/* 二维码图片样式 */
.qcodeBox .qcodeImg{
width: 100%;
height: 100%;
}
.qcodeBox .endBox{
width: 100%;
height: 100%;
/* 悬浮显示 */
position: absolute;
/* 靠左 */
/* left: 0%; */
/* 靠上 */
top: 0;
/* 居中 */
/* text-align: center; */
/* 字体大小 */
font-size: 14px;
/* 字体颜色 */
color: red;
display: flex;
/* 上下居中 */
align-items: center;
/* justify-items: center; */
/* 左右居中 */
justify-content: center;
/* 背景色为灰色 */
background-color: #00000055;
}
/* .endImg{
filter: brightness(10%);
} */
/* 提示信息样式 */
.tipInfo{
/* 行高 */
line-height: 30px;
/* 字体大小 */
font-size: 14px;
/* 居中 */
text-align: center;
/* 颜色 */
color: var(--el-text-color-placeholder);
}
</style>
19.6 UsernameForm.vue
src/views/login/components/UsernameForm.vue 对登录缓存校验进行了优化
<script setup lang="ts">
import { ref,reactive, onMounted } from 'vue'
// 引入状态存储store
import { useStore } from 'vuex'
// 引入路由工具router
import { useRouter } from 'vue-router'
// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'
// 登录表单的实例
// let loginFormRef = ref(null);
let loginFormRef = ref();
// 登录表单的数据
const loginForm = reactive({
// 用户名
username: '',
// 密码
password: '',
// 图片验证
imgcode: '',
// 记住用户名,默认否
saveUsername: false,
// 记住用户名,默认否
savePassword: false
});
// 登录验证规则
const rules = ({
username:[{
required: true,
message: '请输入用户名',
trigger: 'blur'
}],
password:[{
required: true,
message: '请输入密码',
trigger: 'blur'
}],
imgcode:[{
required: true,
message: '请输入图片验证码',
trigger: 'blur'
}]
});
const formSize = "";
// 图片验证码路径
let imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;
// const imgCodeSrc = '../../../assets/code.png';
// 刷新图片验证码
const getImgCode = () => {
// 后续改为从服务器上获取动态图片
imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;
};
// 全局状态存储
const store = useStore();
// 路由调用
const router = useRouter();
// 登录提交事件
const onSubmit = () => {
// form表单中的值,校验,
loginFormRef.value.validate((valid:string, fileds:any)=>{
// 如果valid值为假,则遍历输出报错
if(!valid){
for(let key in fileds){
// 获取报错信息中的字段对应的key的索引为0的信息
utils.showError(fileds[key][0].message);
}
return;
}
// 登录表单的记住用户名如果被勾选
if(loginForm.saveUsername){
// 保存输入的用户名
utils.saveData('username', loginForm.username);
// 保存被勾选的操作
utils.saveData('saveUsername', loginForm.saveUsername);
}else{
// 如果记住用户名的勾选取消,则移除这两个存储的内容
utils.removeData('username');
utils.removeData('saveUsername');
}
// 登录表单的记住用户名如果被勾选
if(loginForm.savePassword){
// 保存输入的用户名
utils.saveData('password', loginForm.password);
// 保存被勾选的操作
utils.saveData('savePassword', loginForm.savePassword);
}else{
// 如果记住用户名的勾选取消,则移除这两个存储的内容
utils.removeData('password');
utils.removeData('savePassword');
}
// TODO 调用接口登录
utils.showLoadding("正在加载中");
api({
method: 'get',
url: '/login/login',
params: {
username: loginForm.username,
password: loginForm.password
}
}).then((res)=>{
utils.hideLoadding();
if(res.status != 200 || res.data.result != 200){
utils.showError("登录失败-请求数据返回有误");
return;
}
if(res.data.login == 1){
utils.showSuccess("登录成功");
// 存储用户信息
// let userInfoLogin = res.data.login;
let userInfoLogin = res.data;
let token = res.data.token;
console.log("usernamelogin:", token);
store.commit('setUserInfo', userInfoLogin);
store.commit('setToken', token);
console.log("----------------token: ", token);
// 登录成功后跳转主页
router.push('/HomeIndex');
}else if(res.data.login == 0){
utils.showError("登录失败-用户不存在");
return;
}else if(res.data.login == 2){
utils.showError("登录失败-密码错误");
return;
}
// utils.showError("登录失败-返回数据错误")
}).catch((error)=>{
console.log(error);
utils.showError("登录失败-发生异常");
});
// 登录成功提示
// utils.showSuccess("登录成功");
});
};
// 挂载
onMounted(() => {
// 获取记住用户名的值
loginForm.saveUsername = utils.getData('saveUsername');
// 如果记住用户名被勾选,则获取用户名显示
if(loginForm.saveUsername==true){
loginForm.username = utils.getData('username');
}
// 获取记住密码的值
loginForm.savePassword = utils.getData('savePassword');
// 如果记住密码被勾选,则获取密码
if(loginForm.savePassword==true){
loginForm.password = utils.getData('password');
}
});
</script>
<template>
<!-- 用户密码登录 -->
<div class="usernameLoginBox">
<el-form
ref="loginFormRef"
style="max-width: 600px"
:model="loginForm"
:rules="rules"
label-width="0"
class="loginFrom"
:size="formSize" status-icon>
<!-- 用户名 -->
<el-form-item prop="username">
<!-- 图标设置,动态绑定username,提示信息,设置输入框大小 -->
<el-input prefix-icon="UserFilled" v-model="loginForm.username" placeholder="请输入用户名" size='large' />
</el-form-item>
<!-- 密码 -->
<el-form-item prop="password">
<!-- 密码 -->
<div class="flexItem" >
<!-- show-password 属性表示是否显示切换显示密码的图标,true为显示 -->
<!-- <el-input prefix-icon="Lock" show-password="off" type="password" v-model="loginForm.password" placeholder="请输入密码" size='large' /> -->
<el-input prefix-icon="Lock" show-password type="password" v-model="loginForm.password" placeholder="请输入密码" size='large' />
</div>
</el-form-item>
<!-- 图片验证 -->
<el-form-item prop="imgcode">
<!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 -->
<div class="flex loginLine">
<div class="flexItem" >
<el-input prefix-icon="Picture" v-model="loginForm.imgcode" placeholder="请输入图片验证码" size='large' />
<!-- <el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' /> -->
</div>
<div class="codeBtn" >
<el-image :src="imgCodeSrc" size='large' @click="getImgCode" ></el-image>
<!-- <el-button type="primary" size="large" @click="getSmsCode" class="" >获取验证码</el-button> -->
</div>
</div>
</el-form-item>
<!-- <el-form-item prop="saveUsername"> -->
<el-form-item >
<!-- 记住账号密码的勾选 -->
<div class="flex loginLine" >
<!-- 记住用户名 -->
<div class="flexItem" >
<el-form-item prop="saveUsername">
<el-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox>
</el-form-item>
</div>
<!-- 记住密码 -->
<div class="flexItem" >
<el-form-item prop="savePassword">
<el-checkbox v-model="loginForm.savePassword">记住密码</el-checkbox>
</el-form-item>
</div>
</div>
</el-form-item>
<!-- <el-form-item prop="savePassword"> -->
<!-- </el-form-item> -->
<!-- 登录按钮 -->
<el-form-item>
<el-button class="loginBtn" type="danger" size='large' @click="onSubmit">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
<style scoped>
/* 按钮宽度设为最大 */
.loginBtn{
width: 100%;
/* 登录按钮圆角边框 */
border-radius: 20px;
}
/* 验证码按钮样式配置 */
.codeBtn{
width: 100px;
margin-left: 10px;
}
/* 按钮和图片宽度100px */
.codeBtn:deep(.el-button),
.codeBtn:deep(img){
width: 100px;
/* height: 40px; */
}
/* 验证码图片高度 */
.codeBtn:deep(img){
height: 40px;
/* 鼠标移上去会变成手型 */
cursor: pointer;
}
/* 这一行宽度占满 */
.loginLine{
width: 100%
}
</style>
19.7 App.vue
src/App.vue 对登录缓存校验进行了优化
<script setup lang="ts">
import { onMounted } from 'vue';
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
import utils from './utils/utils';
import api from './api/api';
// // // 引入暗黑主题的动态切换
// import { useDark, useToggle } from '@vueuse/core'
// const isDark = useDark()
// // // 切换主题函数
// const toggleDark = useToggle(isDark)
// 状态存储
let store = useStore();
// 路由使用
const router = useRouter();
onMounted(()=>{
// let tt = localStorage.getItem("token");
// console.log("tt: ",tt);
console.log("=== ===");
let token = "";
// 由于token可能返回undefined报错,需要进行报错处理
try {
token = utils.getData("token");
} catch (error) {
error;
}
console.log("store-token",token);
let userInfo = utils.getData('userInfo');
console.log("userInfoL: "+userInfo)
console.log("userInfoL: "+token&&userInfo)
if(token && userInfo){
console.log("token userInfo :",token," -- ", userInfo);
// 登录成功,验证
utils.showLoadding("正在加载")
const username = utils.getData('username');
if(!username){
// 登录失败,跳转到登录页
if(username===undefined){
utils.saveData("username","");
}
// token验证失败
utils.showError("用户名过期-请重新登录");
router.push('/UserLogin');
utils.hideLoadding();
}else{
console.log("username-", username);
api.get('/login/tokenCheck',{
params:{username}
}).then((res)=>{
console.log("res.data.token",res.data);
utils.hideLoadding();
if(res.data.token==token){
// 登陆成功
// store.commit('setUserInfo', userInfo);
// store.commit('setToken', token);
router.push('/HomeIndex');
utils.showSuccess("登录成功");
}else{
// if(username===undefined){
// utils.saveData("username","");
// }
// 登录失败
utils.showError("Token已过期,请重新登录");
// 登录失败,跳转到登录页
router.push('/UserLogin');
}
});
utils.hideLoadding();
}
}else{
// 登录失败,跳转到登录页
utils.showError("用户登录缓存过期,请重新登录");
router.push('/UserLogin');
utils.hideLoadding();
}
});
</script>
<template>
<!-- 暗黑主题动态切换按钮实现 -->
<!-- <button @click="toggleDark()">
<i inline-block align-middle i="dark:carbon-moon carbon-sun"/>
<span class="ml-2">{{ isDark ? 'Dark' : 'Light' }}</span>
</button> -->
<RouterView></RouterView>
</template>
<style scoped>
</style>
19.8 页面效果展示
19.8.1 使用短信验证码登录的展示
登录前
不勾选记住用户名登录
输入用户名,点击获取验证码按钮,获取验证码后填入(redis中查看),并填写图片验证码(此项随机填写即可)不勾选记住用户名
登录成功界面
60秒内刷新页面如下
等待60秒后刷新界面(token设定的是60秒过期,故过期后需要重新登录)
勾选记住用户名登录
输入用户名,点击获取验证码按钮,获取验证码后填入(redis中查看),并填写图片验证码(此项随机填写即可),勾选记住用户名
登录成功界面
60秒内刷新页面如下
60秒后刷新界面
19.8.2 使用账号密码登录的展示
登录前
不勾选记住用户名密码登录
登录成功后
60秒后刷新页面
勾选记住用户名密码登录
登录成功
60秒后刷新页面
19.8.3 使用二维码登录的展示
这里没有做任何逻辑进行用户的登录,只是生成了一个token,并且当前用户名是另外两种登录方式存储的缓存,此时直接验证通过
登录成功
60秒后刷新页面会跳转到短信登录
20. 找回密码功能实现
实现找回密码功能,此功能只在使用账号密码登录时可用,故在账号密码登录的页面中添加一个忘记密码
的按钮即可
20.1 找回密码的布局开发
先对找回密码功能的页面进行布局
在src/views包中创建resetPassword包,在该包中创建ResetPwd.vue组件以及component包
以下为找回密码页面ResetPwd.vue的代码
20.1.1 ResetPwd.vue
重置密码页面
src/views/resetPassword/
ResetPwd.vue
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import EmailCodeSetPwdForm from './components/EmailCodeSetPwdForm.vue';
import PhoneCodeSetPwdForm from './components/PhoneCodeSetPwdForm.vue';
const curTab = ref(1);
const changeTab = (tab:any)=>{
curTab.value = tab;
}
// const bgColor = "linear-gradient(45deg, #5198d3, #0f9fe2, #61a2d6);";
// 样式变量
const setPwdPagePanelWidth = '40%';
const setPwdPagePanelHeight = '30%';
</script>
<template>
重置密码
<div class="resetpwd-page">
<div class="resetpwd-box">
<div class="tabs">
<div class="tab-item" :class="{'tab-item-selected':curTab==1}" @click="changeTab(1)">手机验证码找回密码</div>
<div class="tab-item" :class="{'tab-item-selected':curTab!=1}" @click="changeTab(2)">邮箱验证码找回密码</div>
</div>
<div class="tab-content">
<!-- <PhoneCodeSetPwdForm v-if="curTab == 1"></PhoneCodeSetPwdForm> -->
<!-- <EmailCodeSetPwdForm v-else></EmailCodeSetPwdForm> -->
</div>
</div>
<div class="resetpwd-footer">
密码重置页面
</div>
</div>
</template>
<style scoped>
.resetpwd-page{
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
/* background: v-bind(bgColor); */
background: linear-gradient(45deg, #5198d3, #0f9fe2, #61a2d6);
display: flex;
/* text-align: center; */
/* justify-items: center; */
/* justify-content: center; */
align-items: center;
}
.resetpwd-page .resetpwd-box{
background-color: #fff;
height: 50%;
/* height: v-bind(setPwdPagePanelWidth); */
width: 60%;
/* width: v-bind(setPwdPagePanelHeight); */
/* min-height: 800px; */
margin: 0 auto;
text-align: center;
border-radius: 10px;
/* box-shadow: var(--el-box-shadow); */
box-shadow: 0 0 10px 10px #00000055;
padding: 40px;
}
.resetpwd-page .resetpwd-box .tabs{
/* height:20px; */
height: 5%;
/* width: 400px; */
width: 40%;
line-height: 40px;
display: flex;
margin: 0 auto;
}
.resetpwd-page .resetpwd-box .tabs .tab-item{
/* width: 200px; */
width: 50%;
height: 40px;
/* height: 180%; */
text-align: center;
background-color: #00000055;
font-size: 16px;
cursor: pointer;
/* border-radius: 5px; */
}
.resetpwd-page .resetpwd-box .tabs .tab-item:first-child{
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
.resetpwd-page .resetpwd-box .tabs .tab-item:last-child{
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
.resetpwd-page .resetpwd-box .tabs .tab-item:hover,
.resetpwd-page .resetpwd-box .tabs .tab-item-selected{
color: white;
background-color: #0ba4eb;
}
.resetpwd-page .resetpwd-box .tab-content{
/* padding-top: 60px; */
padding-top: 5%;
/* display: flex=1; */
/* justify-content: center; */
/* padding-left: 10%; */
/* padding: 10%; */
/* padding: 50px; */
/* align-items: center; */
/* text-align: center; */
/* height: 400px; */
height: 100%;
/* width: 600px; */
width: 100%;
}
.resetpwd-page .resetpwd-footer{
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 10%;
text-align: center;
color: white;
font-size: 15px;
}
</style>
20.1.2 router/index.ts
在路由中引入新建的页面,然后才能全局跳转
{
path: '/ResetPwd',
component: ResetPwd
}
完整代码如下
src/router/
index.ts
import { createRouter, createWebHistory } from 'vue-router'
// 自定义路由组件或从其他文件导入,这里选择从其他文件导入
import UserLogin from "../views/login/UserLogin.vue";
// import UserLogin from '@/views/login/UserLogin.vue';
import HomeIndex from '@/views/index/HomeIndex.vue';
import ResetPwd from '@/views/resetPassword/ResetPwd.vue';
// import ResetPwd from '../views/resetPassword/ResetPwd.vue';
// 定义一些路由,每个路由都需要映射到一个组件,
const routes = [
{
path: '/',
// component: UserLogin
redirect: "/HomeIndex"
},
{
path: '/UserLogin',
component: UserLogin
},
{
path: '/HomeIndex',
component: HomeIndex
},
{
path: '/ResetPwd',
component: ResetPwd
}
]
// 创建路由实例并传递‘routes’配置 你可以在这里输入更多的配置
const router = createRouter({
history: createWebHistory(),
// routes:routes可以简写成routes,不会报错
// routes:[]
routes
})
export default router
20.1.3 UsernameForm.vue
在账号密码登录的页面中添加忘记密码的按钮,点击可跳转到重置密码页面
<div class="flexItem" >
<!-- <el-form-item prop="savePassword"> -->
<router-link to="/ResetPwd">忘记密码</router-link>
<!-- </el-form-item> -->
</div>
完整代码如下
src/views/login/components/
UsernameForm.vue
<script setup lang="ts">
import { ref,reactive, onMounted } from 'vue'
// 引入状态存储store
import { useStore } from 'vuex'
// 引入路由工具router
import { useRouter } from 'vue-router'
// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'
// 登录表单的实例
// let loginFormRef = ref(null);
let loginFormRef = ref();
// 登录表单的数据
const loginForm = reactive({
// 用户名
username: '',
// 密码
password: '',
// 图片验证
imgcode: '',
// 记住用户名,默认否
saveUsername: false,
// 记住用户名,默认否
savePassword: false
});
// 登录验证规则
const rules = ({
username:[{
required: true,
message: '请输入用户名',
trigger: 'blur'
}],
password:[{
required: true,
message: '请输入密码',
trigger: 'blur'
}],
imgcode:[{
required: true,
message: '请输入图片验证码',
trigger: 'blur'
}]
});
const formSize = "";
// 图片验证码路径
let imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;
// const imgCodeSrc = '../../../assets/code.png';
// 刷新图片验证码
const getImgCode = () => {
// 后续改为从服务器上获取动态图片
imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;
};
// 全局状态存储
const store = useStore();
// 路由调用
const router = useRouter();
// 登录提交事件
const onSubmit = () => {
// form表单中的值,校验
loginFormRef.value.validate((valid:string, fileds:any)=>{
// 如果valid值为假,则遍历输出报错
if(!valid){
for(let key in fileds){
// 获取报错信息中的字段对应的key的索引为0的信息
utils.showError(fileds[key][0].message);
}
return;
}
// 登录表单的记住用户名如果被勾选
if(loginForm.saveUsername){
// 保存输入的用户名
utils.saveData('username', loginForm.username);
// 保存被勾选的操作
utils.saveData('saveUsername', loginForm.saveUsername);
}else{
// 如果记住用户名的勾选取消,则移除这两个存储的内容
utils.removeData('username');
utils.removeData('saveUsername');
}
// 登录表单的记住用户名如果被勾选
if(loginForm.savePassword){
// 保存输入的用户名
utils.saveData('password', loginForm.password);
// 保存被勾选的操作
utils.saveData('savePassword', loginForm.savePassword);
}else{
// 如果记住用户名的勾选取消,则移除这两个存储的内容
utils.removeData('password');
utils.removeData('savePassword');
}
// TODO 调用接口登录
utils.showLoadding("正在加载中");
api({
method: 'get',
url: '/login/login',
params: {
username: loginForm.username,
password: loginForm.password
}
}).then((res)=>{
utils.hideLoadding();
if(res.status != 200 || res.data.result != 200){
utils.showError("登录失败-请求数据返回有误");
return;
}
if(res.data.login == 1){
utils.showSuccess("登录成功");
// 存储用户信息
// let userInfoLogin = res.data.login;
let userInfoLogin = res.data;
let token = res.data.token;
console.log("usernamelogin:", token);
store.commit('setUserInfo', userInfoLogin);
store.commit('setToken', token);
console.log("----------------token: ", token);
// 登录成功后跳转主页
router.push('/HomeIndex');
}else if(res.data.login == 0){
utils.showError("登录失败-用户不存在");
return;
}else if(res.data.login == 2){
utils.showError("登录失败-密码错误");
return;
}
// utils.showError("登录失败-返回数据错误")
}).catch((error)=>{
console.log(error);
utils.showError("登录失败-发生异常");
});
// 登录成功提示
// utils.showSuccess("登录成功");
});
};
// 挂载
onMounted(() => {
// 获取记住用户名的值
loginForm.saveUsername = utils.getData('saveUsername');
// 如果记住用户名被勾选,则获取用户名显示
if(loginForm.saveUsername==true){
loginForm.username = utils.getData('username');
}
// 获取记住密码的值
loginForm.savePassword = utils.getData('savePassword');
// 如果记住密码被勾选,则获取密码
if(loginForm.savePassword==true){
loginForm.password = utils.getData('password');
}
});
</script>
<template>
<!-- 用户密码登录 -->
<div class="usernameLoginBox">
<el-form
ref="loginFormRef"
style="max-width: 600px"
:model="loginForm"
:rules="rules"
label-width="0"
class="loginFrom"
:size="formSize" status-icon>
<!-- 用户名 -->
<el-form-item prop="username">
<!-- 图标设置,动态绑定username,提示信息,设置输入框大小 -->
<el-input prefix-icon="UserFilled" v-model="loginForm.username" placeholder="请输入用户名" size='large' />
</el-form-item>
<!-- 密码 -->
<el-form-item prop="password">
<!-- 密码 -->
<div class="flexItem" >
<!-- show-password 属性表示是否显示切换显示密码的图标,true为显示 -->
<!-- <el-input prefix-icon="Lock" show-password="off" type="password" v-model="loginForm.password" placeholder="请输入密码" size='large' /> -->
<el-input prefix-icon="Lock" show-password type="password" v-model="loginForm.password" placeholder="请输入密码" size='large' />
</div>
</el-form-item>
<!-- 图片验证 -->
<el-form-item prop="imgcode">
<!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 -->
<div class="flex loginLine">
<div class="flexItem" >
<el-input prefix-icon="Picture" v-model="loginForm.imgcode" placeholder="请输入图片验证码" size='large' />
<!-- <el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' /> -->
</div>
<div class="codeBtn" >
<el-image :src="imgCodeSrc" size='large' @click="getImgCode" ></el-image>
<!-- <el-button type="primary" size="large" @click="getSmsCode" class="" >获取验证码</el-button> -->
</div>
</div>
</el-form-item>
<!-- <el-form-item prop="saveUsername"> -->
<el-form-item >
<!-- 记住账号密码的勾选 -->
<div class="flex loginLine" >
<!-- 记住用户名 -->
<div class="flexItem" >
<el-form-item prop="saveUsername">
<el-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox>
</el-form-item>
</div>
<!-- 记住密码 -->
<div class="flexItem" >
<el-form-item prop="savePassword">
<el-checkbox v-model="loginForm.savePassword">记住密码</el-checkbox>
</el-form-item>
</div>
<div class="flexItem" >
<!-- <el-form-item prop="savePassword"> -->
<router-link to="/ResetPwd">忘记密码</router-link>
<!-- </el-form-item> -->
</div>
</div>
</el-form-item>
<!-- <el-form-item prop="savePassword"> -->
<!-- </el-form-item> -->
<!-- 登录按钮 -->
<el-form-item>
<el-button class="loginBtn" type="danger" size='large' @click="onSubmit">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
<style scoped>
/* 按钮宽度设为最大 */
.loginBtn{
width: 100%;
/* 登录按钮圆角边框 */
border-radius: 20px;
}
/* 验证码按钮样式配置 */
.codeBtn{
width: 100px;
margin-left: 10px;
}
/* 按钮和图片宽度100px */
.codeBtn:deep(.el-button),
.codeBtn:deep(img){
width: 100px;
/* height: 40px; */
}
/* 验证码图片高度 */
.codeBtn:deep(img){
height: 40px;
/* 鼠标移上去会变成手型 */
cursor: pointer;
}
/* 这一行宽度占满 */
.loginLine{
width: 100%
}
</style>
20.1.4 页面效果
查看账号密码登录界面,出现忘记密码
链接
点击忘记密码,跳转到密码重置界面
20.2 短信验证码找回密码
布局配置好后,进行短信验证功能的实现
20.2.1 PhoneCodeSetPwdForm.vue
使用短信验证码设置密码的页面
完整代码如下
src/views/resetPassword/components/
PhoneCodeSetPwdForm.vue
<script setup lang="ts">
// vue基础模块引入
import { ref, reactive, onMounted, onUnmounted } from 'vue'
// 引入状态存储工具store
import {useStore} from 'vuex'
// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'
// 路由引入
import { useRoute, useRouter } from 'vue-router';
// 引入密码重置页面组件
import ResetPwdForm from './ResetPwdForm.vue';
//import ResetSuccess from './ResetSuccess.vue';
const formSize = ""
// 步骤选择
const setStep = ref(1);
// 表单数据对象
const resetPwdForm = reactive({
// 用户名
username: '',
// 短信验证码
smscode: '',
// 图片验证码
imgcode: '',
});
// 表单验证规则
const rules = ref({
username: [{
required: true,
message: '请输入用户名',
trigger: 'blur'
}],
smscode: [{
required: true,
message: '请输入短信验证码',
trigger: 'blur'
}],
imgcode: [{
required: true,
message: '请输入图片验证码',
trigger: 'blur'
}]
});
// 获取短信验证码的按钮文本值
let smsCodeBtnText = ref("获取验证码")
// 定时器
let timer:any = null;
// 短信验证码获取的间隔事件
let curTime = 0;
// 获取验证码按钮
const getSmsCode = ()=>{
if(!resetPwdForm.username){
utils.showError("请先输入用户名");
return;
}
// 发送请求生成短信验证码
api({
method: 'post',
url: '/login/redis/setMessageCode',
params: {
username: resetPwdForm.username
}
});
// 同时进行倒计时,读秒60,结束后可重新获取
curTime = 60
timer = setInterval(() => {
curTime--;
// 值重新赋值
smsCodeBtnText.value = curTime+'秒后重新获取';
// 当及时归零时,可重新获取,并将计时器重置
if(curTime<=0){
smsCodeBtnText.value = '获取验证码'
clearInterval(timer);
timer = null;
}
},1000);
}
// 图片验证码路径
let imageCodeSrc = new URL('../../../assets/code.png',import.meta.url).href
// 刷新图片验证码
const getImgCode = () => {
// 从服务器动态获取图片验证码
imageCodeSrc = new URL('../../../assets/code.png',import.meta.url).href
}
const resetPwdFormRef = ref();
// 下一步按钮
const nextSet = () => {
// 表单校验,校验所填表单中是否有值,无值则报错
resetPwdFormRef.value.validate((valid:string, fileds:any)=>{
// 如果valid值为假,则输出报错内容
if(!valid){
for(let key in fileds){
utils.showError(fileds[key][0].message);
return;
}
return;
}
// 加载效果
utils.showLoadding("正在加载中");
// if(resetPwdForm.smscode){
// utils.showError("验证码错误,请重新输入");
// }
// 校验填写的验证码是否一致
api({
method: 'get',
url: 'login/redis/getMessageCode',
params: {
username: resetPwdForm.username
}
}).then((res)=>{
utils.hideLoadding();
// 如果数据返回状态码不是200或请求结果返回不是200或者请求结果返回的短信验证码为空,验证失败
if(res.status!=200||res.data.result!=200||!res.data.msgCode){
utils.showError("验证失败-请求数据返回有误");
return;
}
// 如果填入的短信验证码和收到的短信验证码一致,则验证成功,跳转到修改密码界面
if(res.data.msgCode == resetPwdForm.smscode){
utils.showSuccess("验证成功");
setStep.value = 2;
}else{
utils.showError("验证失败-验证码错误");
}
}).catch((error)=>{
setStep.value = 2;
utils.hideLoadding();
console.log(error);
utils.showError("验证失败-出现异常");
});
});
}
// const systemToken = ref('');
// const toPhoneCodeSetPwdForm = ()=>{
// setStep.value = 1;
// }
// const toResetSuccess = ()=>{
// setStep.value = 3;
// }
</script>
<template>
<!-- <div class="stepLine"> -->
<el-steps style="max-width: 2000px" :active="setStep" align-center>
<el-step title="身份验证" description="请输入账号和验证码进行身份确认" />
<el-step title="密码重置" description="填写新密码并确认" />
<el-step title="重置成功" description="密码修改成功" />
</el-steps>
<!-- </div> -->
<div v-if="setStep == 1" class="phone-reset-password-form">
<el-form
ref="resetPwdFormRef"
style="max-width: 600px;"
:model="resetPwdForm"
:rules="rules"
label-width="0"
class="resetPwdForm"
:size="formSize"
status-icon
>
<!-- 用户名 -->
<el-form-item prop="username">
<el-input
prefix-icon="UserFilled"
v-model="resetPwdForm.username"
placeholder="用户名"
size="large"
/>
</el-form-item>
<!-- 获取验证码 -->
<el-form-item prop="smscode">
<div class="flex resetLine">
<div class="flexItem">
<el-input
prefix-icon="Iphone"
v-model="resetPwdForm.smscode"
placeholder="短信验证码"
size="large"
/>
</div>
<div class="codeBtn">
<el-button type="primary" size="large" @click="getSmsCode" :disabled="curTime>0">
{{ smsCodeBtnText }}
</el-button>
</div>
</div>
</el-form-item>
<!-- 图片验证码 -->
<el-form-item prop="imgcode">
<div class="flex resetLine">
<div class="flexItem">
<el-input
prefix-icon="Picture"
v-model="resetPwdForm.imgcode"
placeholder="图片验证码"
size="large"
/>
</div>
<div class="codeBtn">
<el-image :src="imageCodeSrc" size="large" @click="getImgCode"></el-image>
</div>
</div>
</el-form-item>
<!-- 下一步按钮 -->
<el-form-item>
<!-- <div class="nextBtn"> -->
<el-button class="nextBtn" type="primary" size="large" @click="nextSet">下一步</el-button>
<!-- </div> -->
</el-form-item>
</el-form>
</div>
<!-- <ResetPwdForm v-else-if="setStep == 2"
:systemToken="systemToken"
@pre="toPhoneCodeSetPwdForm"
@next="toResetSuccess"
></ResetPwdForm> -->
<ResetPwdForm v-else-if="setStep == 2" ></ResetPwdForm>
<!-- <ResetSuccess v-else></ResetSuccess> -->
</template>
<style scoped>
.phone-reset-password-form{
padding: 5%;
/* margin-top: 2%; */
margin: 2% auto;
width: 60%;
height: 40%;
/* text-align: center; */
background-color: rgb(204, 240, 210);
display: flex;
justify-content: center;
/* justify-items: center; */
}
.codeBtn{
/* width: 100px; */
margin-left: 10px;
/* margin-bottom: 10px; */
}
/* .codeBtn:deep(.el-button),
.codeBtn:deep(img){
width: 100px;
} */
.codeBtn:deep(img){
height: 40px;
cursor: pointer;
}
.resetLine{
width: 100%;
}
.nextBtn{
width: 100%;
/* display: flex; */
/* justify-content: flex-end; */
/* justify-items: right; */
/* text-align: right; */
/* float: right; */
}
</style>
20.2.2 ResetPwd.vue密码重置页面中引入短信验证页
在ResetPwd.vue中引入PhoneCodeSetPwdForm.vue,在选择手机验证码重置密码时显示短信验证页面
完整代码
src/views/resetPassword/
ResetPwd.vue
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import utils from '@/utils/utils';
// 表单对象
const resetPwdForm = reactive({
newPassword: '',
reNewPassword: '',
});
// 检测两次密码是否一致
// const validatePassword = (rule, value, callback)=>{
const validatePassword = (rule:any, value:any, callback:any)=>{
if(value != resetPwdForm.newPassword){
callback(new Error('两次密码不一致'));
}else{
callback();
}
};
// 表单输入校验
const rules = reactive({
newPassword: [
{
required: true,
message: '请输入新密码',
trigger: 'blur'
},
{
// 定义输入内容长度
min: 6,
max: 12,
message: '密码长度6-12位',
trigger: 'blur'
}
],
reNewPassword: [
{
required: true,
message: '请再次输入新密码',
trigger: 'blur'
},
{
// 调用校验函数进行校验
validator: validatePassword,
trigger: 'blur'
}
]
});
// 表单校验对象
const resetPwdFormRef = ref();
// 当前页
let setStep = ref(2);
// 自定义事件
// const emits = defineEmits([
// 'pre',
// 'next'
// ]);
// 上一步
// const preSet = () =>{
// emits('pre');
// };
// 外部参数获取,传递上一步获取的用于账号验证的token信息
// const option = defineProps({
// systemToken: {
// type: String,
// required: true
// }
// });
// 下一步
const nextSet = () =>{
resetPwdFormRef.value.validate((valid:string, fields:any)=>{
if(!valid){
for(let key in fields){
utils.showError(fields[key][0].message);
}
return;
}
// 调用接口验证密码和用户,进行密码修改
// emits('next');
// if(resetPwdForm.newPassword == resetPwdForm.reNewPassword){
// // 调用接口进行修改
// utils.showSuccess("密码设置成功");
// setStep.value = 3
// }else{
// utils.showError("密码不一致,请重新输入");
// }
});
};
</script>
<template>
<div v-if="setStep == 2" class="resetPwdForm">
<el-form ref="resetPwdFormRef"
style="max-width: 600px; "
:model="resetPwdForm"
:rules="rules"
label-width="auto"
class="resetPwdForm"
status-icon
>
<!-- 新密码 -->
<el-form-item label="新的密码:" prop="newPassword">
<el-input
prefix-icon="Key"
type="password"
show-password
v-model="resetPwdForm.newPassword"
placeholder="请输入新密码"
size="large"
/>
</el-form-item>
<!-- 确认新密码 -->
<el-form-item label="确认密码:" prop="reNewPassword">
<el-input
prefix-icon="Key"
type="password"
show-password
v-model="resetPwdForm.reNewPassword"
placeholder="请再次输入新密码"
size="large"
/>
</el-form-item>
<el-form-item>
<div class="flex preNextLine">
<div class="flexItem">
<!-- 上一步按钮 -->
<el-button class="resetBtn" type="primary" size="large" @click="preSet">上一步</el-button>
</div>
<div class="middleBtn">
</div>
<div class="flexItem">
<!-- 下一步按钮 -->
<el-button class="resetBtn" type="primary" size="large" @click="nextSet">下一步</el-button>
</div>
</div>
</el-form-item>
</el-form>
</div>
<!-- <PhoneCodeSetPwdForm v-else-if="setStep == 1"></PhoneCodeSetPwdForm> -->
<!-- <ResetSuccess v-else></ResetSuccess> -->
</template>
<style scoped>
.resetPwdForm{
height: 50%;
width: 60%;
padding-top: 1%;
margin: 2% auto;
justify-content: center;
background-color: rgb(178, 189, 185)
}
.resetPwdForm .preNextLine{
width: 100%;
margin-top: 10%;
}
.resetPwdForm .preNextLine .resetBtn{
width: 80%;
}
.resetPwdForm .preNextLine .middleBtn{
width: 30%;
}
</style>
20.2.3 获取短信验证码页面的效果
此时页面如下,共两部分,进度提示和下方的信息验证
使用用户名和验证码验证身份,用户名是后台数据库中存储的账号名,我这里是libai,短信验证是通过点击按钮调用后台接口生成到redis中的临时六位数字,图片验证码可填任意字符,然后点击下一步
20.2.4 ResetPwdForm.vue重置密码页面的实现
该页面是在身份校验通过后跳转到的设置密码界面,输入密码后调用接口进行修改表中账号对应的密码
src/views/resetPassword/components/
ResetPwdForm.vue
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import utils from '@/utils/utils';
// 表单对象
const resetPwdForm = reactive({
newPassword: '',
reNewPassword: '',
});
// 检测两次密码是否一致
// const validatePassword = (rule, value, callback)=>{
const validatePassword = (rule:any, value:any, callback:any)=>{
if(value != resetPwdForm.newPassword){
callback(new Error('两次密码不一致'));
}else{
callback();
}
};
// 表单输入校验
const rules = reactive({
newPassword: [
{
required: true,
message: '请输入新密码',
trigger: 'blur'
},
{
// 定义输入内容长度
min: 6,
max: 12,
message: '密码长度6-12位',
trigger: 'blur'
}
],
reNewPassword: [
{
required: true,
message: '请再次输入新密码',
trigger: 'blur'
},
{
// 调用校验函数进行校验
validator: validatePassword,
trigger: 'blur'
}
]
});
// 表单校验对象
const resetPwdFormRef = ref();
// 当前页
let setStep = ref(2);
// 自定义事件
// const emits = defineEmits([
// 'pre',
// 'next'
// ]);
// 上一步
// const preSet = () =>{
// emits('pre');
// };
// 外部参数获取,传递上一步获取的用于账号验证的token信息
// const option = defineProps({
// systemToken: {
// type: String,
// required: true
// }
// });
// 下一步
const nextSet = () =>{
resetPwdFormRef.value.validate((valid:string, fields:any)=>{
if(!valid){
for(let key in fields){
utils.showError(fields[key][0].message);
}
return;
}
// 调用接口验证密码和用户,进行密码修改
// emits('next');
// if(resetPwdForm.newPassword == resetPwdForm.reNewPassword){
// // 调用接口进行修改
// utils.showSuccess("密码设置成功");
// setStep.value = 3
// }else{
// utils.showError("密码不一致,请重新输入");
// }
});
};
</script>
<template>
<div v-if="setStep == 2" class="resetPwdForm">
<el-form ref="resetPwdFormRef"
style="max-width: 600px; "
:model="resetPwdForm"
:rules="rules"
label-width="auto"
class="resetPwdForm"
status-icon
>
<!-- 新密码 -->
<el-form-item label="新的密码:" prop="newPassword">
<el-input
prefix-icon="Key"
type="password"
show-password
v-model="resetPwdForm.newPassword"
placeholder="请输入新密码"
size="large"
/>
</el-form-item>
<!-- 确认新密码 -->
<el-form-item label="确认密码:" prop="reNewPassword">
<el-input
prefix-icon="Key"
type="password"
show-password
v-model="resetPwdForm.reNewPassword"
placeholder="请再次输入新密码"
size="large"
/>
</el-form-item>
<el-form-item>
<div class="flex preNextLine">
<div class="flexItem">
<!-- 上一步按钮 -->
<el-button class="resetBtn" type="primary" size="large" @click="preSet">上一步</el-button>
</div>
<div class="middleBtn">
</div>
<div class="flexItem">
<!-- 下一步按钮 -->
<el-button class="resetBtn" type="primary" size="large" @click="nextSet">下一步</el-button>
</div>
</div>
</el-form-item>
</el-form>
</div>
<!-- <PhoneCodeSetPwdForm v-else-if="setStep == 1"></PhoneCodeSetPwdForm> -->
<!-- <ResetSuccess v-else></ResetSuccess> -->
</template>
<style scoped>
.resetPwdForm{
height: 50%;
width: 60%;
padding-top: 1%;
margin: 2% auto;
justify-content: center;
background-color: rgb(178, 189, 185)
}
.resetPwdForm .preNextLine{
width: 100%;
margin-top: 10%;
}
.resetPwdForm .preNextLine .resetBtn{
width: 80%;
}
.resetPwdForm .preNextLine .middleBtn{
width: 30%;
}
</style>
此刻同时先把设置成功后的界面也创建出来,
src/views/resetPassword/components/
ResetSuccess.vue
<script setup lang="ts">
</script>
<template>
重置密码成功
</template>
<style scoped>
</style>
20.2.5 设置密码的页面效果展示
身份验证通过后进入输入新密码的页面,如下
此时该页面的上一步和下一步还没生效,需要将上一步、下一步对应的页面和当前页面绑定
20.2.6 设置密码后的上一步和下一步配置
上一步和下一步需要在代码中使用defineEmits()进行事件传递,还有如果使用了token进行上一步和下一步的参数传递,会用到defineProps()
设置密码页面的代码新增如下
src/views/resetPassword/components/
ResetPwdForm.vue
<script setup lang="ts">
import { ref, reactive } from 'vue'
import utils from '@/utils/utils';
import api from '@/api/api';
// 表单对象
const resetPwdForm = reactive({
newPassword: '',
reNewPassword: '',
});
// 检测两次密码是否一致
// const validatePassword = (rule, value, callback)=>{
const validatePassword = (rule:any, value:any, callback:any)=>{
if(value != resetPwdForm.newPassword){
callback(new Error('两次密码不一致'));
}else{
callback();
}
};
// 表单输入校验
const rules = reactive({
newPassword: [
{
required: true,
message: '请输入新密码',
trigger: 'blur'
},
{
// 定义输入内容长度
min: 6,
max: 12,
message: '密码长度6-12位',
trigger: 'blur'
}
],
reNewPassword: [
{
required: true,
message: '请再次输入新密码',
trigger: 'blur'
},
{
// 调用校验函数进行校验
validator: validatePassword,
trigger: 'blur'
}
]
});
// 表单校验对象
const resetPwdFormRef = ref();
// 当前页
//let setStep = ref(2);
// 自定义事件
const emits = defineEmits([
'pre',
'next'
]);
// 上一步
const preSet = () =>{
emits('pre');
};
// 外部参数获取,传递上一步获取的用于账号验证的token信息
const option = defineProps({
// 从上一步的组件中获取参数
usernames: String,
systemToken: {
type: String,
required: true
}
});
const username = option.usernames;
// 下一步
const nextSet = () =>{
resetPwdFormRef.value.validate((valid:string, fields:any)=>{
if(!valid){
for(let key in fields){
utils.showError(fields[key][0].message);
}
return;
}
utils.showLoadding("正在加载");
// 调用接口验证密码和用户,进行密码修改
api({
method: 'post',
url: '/user/changePassword',
params: {
username: username,
password: resetPwdForm.newPassword
}
}).then((res)=>{
utils.hideLoadding();
if( res.status!=200){
utils.showError("请求失败-请求返回有误");
return;
}
if(res.data.result==200){
utils.showSuccess("密码修改成功");
emits('next');
}
}).catch((error)=>{
utils.hideLoadding();
console.log(error);
utils.showError("密码修改失败-出现异常");
});
// emits('next');
});
};
</script>
<template>
<div class="resetPwdForm">
<el-form ref="resetPwdFormRef"
style="max-width: 600px; "
:model="resetPwdForm"
:rules="rules"
label-width="auto"
class="resetPwdForm"
status-icon
>
<!-- 新密码 -->
<el-form-item label="新的密码:" prop="newPassword">
<el-input
prefix-icon="Key"
type="password"
show-password
v-model="resetPwdForm.newPassword"
placeholder="请输入新密码"
size="large"
/>
</el-form-item>
<!-- 确认新密码 -->
<el-form-item label="确认密码:" prop="reNewPassword">
<el-input
prefix-icon="Key"
type="password"
show-password
v-model="resetPwdForm.reNewPassword"
placeholder="请再次输入新密码"
size="large"
/>
</el-form-item>
<el-form-item>
<div class="flex preNextLine">
<div class="flexItem">
<!-- 上一步按钮 -->
<el-button class="resetBtn" type="primary" size="large" @click="preSet">上一步</el-button>
</div>
<div class="middleBtn">
</div>
<div class="flexItem">
<!-- 下一步按钮 -->
<el-button class="resetBtn" type="primary" size="large" @click="nextSet">下一步</el-button>
</div>
</div>
</el-form-item>
</el-form>
</div>
</template>
<style scoped>
.resetPwdForm{
height: 50%;
width: 60%;
padding-top: 1%;
margin: 2% auto;
justify-content: center;
background-color: rgb(178, 189, 185)
}
.resetPwdForm .preNextLine{
width: 100%;
margin-top: 10%;
}
.resetPwdForm .preNextLine .resetBtn{
width: 80%;
}
.resetPwdForm .preNextLine .middleBtn{
width: 30%;
}
</style>
手机验证码修改密码的页面的代码新增如下
src/views/resetPassword/components/
PhoneCodeSetPwdForm.vue
<script setup lang="ts">
// vue基础模块引入
import { ref, reactive, onMounted, onUnmounted } from 'vue'
// 引入状态存储工具store
// import {useStore} from 'vuex'
// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'
// 路由引入
// import { useRoute, useRouter } from 'vue-router';
// 引入密码重置页面组件
import ResetPwdForm from './ResetPwdForm.vue';
import ResetSuccess from './ResetSuccess.vue';
const formSize = ""
// 步骤选择
const setStep = ref(1);
// 表单数据对象
const resetPwdForm = reactive({
// 用户名
username: '',
// 短信验证码
smscode: '',
// 图片验证码
imgcode: '',
});
// 表单验证规则
const rules = ref({
username: [{
required: true,
message: '请输入用户名',
trigger: 'blur'
}],
smscode: [{
required: true,
message: '请输入短信验证码',
trigger: 'blur'
}],
imgcode: [{
required: true,
message: '请输入图片验证码',
trigger: 'blur'
}]
});
// 获取短信验证码的按钮文本值
let smsCodeBtnText = ref("获取验证码")
// 定时器
let timer:any = null;
// 短信验证码获取的间隔事件
let curTime = 0;
// 获取验证码按钮
const getSmsCode = ()=>{
if(!resetPwdForm.username){
utils.showError("请先输入用户名");
return;
}
// 发送请求生成短信验证码
api({
method: 'post',
url: '/login/redis/setMessageCode',
params: {
username: resetPwdForm.username
}
});
// 同时进行倒计时,读秒60,结束后可重新获取
curTime = 60
timer = setInterval(() => {
curTime--;
// 值重新赋值
smsCodeBtnText.value = curTime+'秒后重新获取';
// 当及时归零时,可重新获取,并将计时器重置
if(curTime<=0){
smsCodeBtnText.value = '获取验证码'
clearInterval(timer);
timer = null;
}
},1000);
}
// 图片验证码路径
let imageCodeSrc = new URL('../../../assets/code.png',import.meta.url).href
// 刷新图片验证码
const getImgCode = () => {
// 从服务器动态获取图片验证码
imageCodeSrc = new URL('../../../assets/code.png',import.meta.url).href
}
const resetPwdFormRef = ref();
// 下一步按钮
const nextSet = () => {
// 表单校验,校验所填表单中是否有值,无值则报错
resetPwdFormRef.value.validate((valid:string, fileds:any)=>{
// 如果valid值为假,则输出报错内容
if(!valid){
for(let key in fileds){
utils.showError(fileds[key][0].message);
return;
}
return;
}
// 加载效果
utils.showLoadding("正在加载中");
// if(resetPwdForm.smscode){
// utils.showError("验证码错误,请重新输入");
// }
// 校验填写的验证码是否一致
api({
method: 'get',
url: 'login/redis/getMessageCode',
params: {
username: resetPwdForm.username
}
}).then((res)=>{
utils.hideLoadding();
// 如果数据返回状态码不是200或请求结果返回不是200或者请求结果返回的短信验证码为空,验证失败
if(res.status!=200||res.data.result!=200||!res.data.msgCode){
utils.showError("验证失败-请求数据返回有误");
return;
}
// 如果填入的短信验证码和收到的短信验证码一致,则验证成功,跳转到修改密码界面
if(res.data.msgCode == resetPwdForm.smscode){
utils.showSuccess("验证成功");
setStep.value = 2;
}else{
utils.showError("验证失败-验证码错误");
}
}).catch((error)=>{
setStep.value = 2;
utils.hideLoadding();
console.log(error);
utils.showError("验证失败-出现异常");
});
});
}
const systemToken = ref('');
// const susername = resetPwdForm.username;
// systemToken.value = resetPwdForm.username;
const toPhoneCodeSetPwdForm = ()=>{
setStep.value = 1;
}
const toResetSuccess = ()=>{
setStep.value = 3;
}
</script>
<template>
<!-- <div class="stepLine"> -->
<el-steps style="max-width: 2000px" :active="setStep" align-center>
<el-step title="身份验证" description="请输入账号和验证码进行身份确认" />
<el-step title="密码重置" description="填写新密码并确认" />
<el-step title="重置成功" description="密码修改成功" />
</el-steps>
<!-- </div> -->
<div v-if="setStep == 1" class="phone-reset-password-form">
<el-form
ref="resetPwdFormRef"
style="max-width: 600px;"
:model="resetPwdForm"
:rules="rules"
label-width="0"
class="resetPwdForm"
:size="formSize"
status-icon
>
<!-- 用户名 -->
<el-form-item prop="username">
<el-input
prefix-icon="UserFilled"
v-model="resetPwdForm.username"
placeholder="用户名"
size="large"
/>
</el-form-item>
<!-- 获取验证码 -->
<el-form-item prop="smscode">
<div class="flex resetLine">
<div class="flexItem">
<el-input
prefix-icon="Iphone"
v-model="resetPwdForm.smscode"
placeholder="短信验证码"
size="large"
/>
</div>
<div class="codeBtn">
<el-button type="primary" size="large" @click="getSmsCode" :disabled="curTime>0">
{{ smsCodeBtnText }}
</el-button>
</div>
</div>
</el-form-item>
<!-- 图片验证码 -->
<el-form-item prop="imgcode">
<div class="flex resetLine">
<div class="flexItem">
<el-input
prefix-icon="Picture"
v-model="resetPwdForm.imgcode"
placeholder="图片验证码"
size="large"
/>
</div>
<div class="codeBtn">
<el-image :src="imageCodeSrc" size="large" @click="getImgCode"></el-image>
</div>
</div>
</el-form-item>
<!-- 下一步按钮 -->
<el-form-item>
<!-- <div class="nextBtn"> -->
<el-button class="nextBtn" type="primary" size="large" @click="nextSet">下一步</el-button>
<!-- </div> -->
</el-form-item>
</el-form>
</div>
<!-- usernames表示将当前数据的只绑定到usernames中,通过在别的页面中使用defineProps获取 -->
<ResetPwdForm v-else-if="setStep == 2"
:systemToken="systemToken"
@pre="toPhoneCodeSetPwdForm"
@next="toResetSuccess"
:usernames = resetPwdForm.username
></ResetPwdForm>
<!-- <ResetPwdForm v-else-if="setStep == 2" ></ResetPwdForm> -->
<ResetSuccess v-else></ResetSuccess>
</template>
<style scoped>
.phone-reset-password-form{
padding: 5%;
/* margin-top: 2%; */
margin: 2% auto;
width: 60%;
height: 40%;
background-color: rgb(204, 240, 210);
display: flex;
justify-content: center;
}
.codeBtn{
margin-left: 10px;
}
.codeBtn:deep(img){
height: 40px;
cursor: pointer;
}
.resetLine{
width: 100%;
}
.nextBtn{
width: 100%;
}
</style>
以上实现后,在设置密码页面点击上一步会跳转到身份验证界面,点击下一步会跳转到密码设置成功界面,密码设置成功的界面下面接着完善
20.2.7 ResetSuccess.vue
在设置密码后,点击下一步,跳转到密码设置成功界面
在当前成功页面中,提示五秒后自动跳转到登录页面,密码设置成功的页面代码如下
src/views/resetPassword/components/
ResetSuccess.vue
<script setup lang="ts">
import { onMounted } from 'vue';
import {useRouter} from 'vue-router'
const router = useRouter()
let timer:any = null;
let curTime = 5;
const curTimeFun = ()=>{
timer = setInterval(()=>{
curTime--;
if(curTime<=0){
router.push("/UserLogin")
clearInterval(timer);
timer = null;
}
},1000);
}
onMounted(()=>{
curTimeFun();
})
</script>
<template>
<div class="tip">
重置密码成功
</div>
<div class="success">
<!-- <router-link to="/UserLogin" >点击跳转账号密码登录界面</router-link> -->
<router-link to="/UserLogin" :curtime="curTimeFun">点击跳转账号密码登录界面,{{curTime}} 后自动跳转</router-link>
</div>
</template>
<style scoped>
.tip{
width: 20%;
height: 10%;
margin: auto;
padding: 3%;
font-size: 20px;
/* background-color: aqua; */
}
.success{
width: 60%;
height: 20%;
margin: auto;
padding: 4%;
font-size: 15px;
font-style: italic;
/* display: flex; */
/* justify-content: center; */
background-color: bisque;
}
</style>
20.2.8 密码设置成功后的自动跳转效果
当密码设置好后,下一步,密码设置成功界面,提示5秒后自动跳转到登录界面
注:这里暂时还没有实现如何将5秒倒计时实时显示,后面再想怎么解决
20.3 邮箱找回密码
根据前面手机验证码找回密码的实现,一样的方式实现邮箱找回密码的功能
这里暂时使用相同的方式模拟邮箱验证码,即使用redis,在点击获取验证码时,发送请求,生成一个六位数字,并以用户名为键,数字为值的形式存储在redis中
随后输入正确的数字,填写完整信息,点击下一步,即可通过验证
20.3.1 引入邮箱找回密码的页面(ResetPwd.vue)
在找回密码对应的页面组件中引入邮箱找回密码的组件
即在src/views/resetPassword/ResetPwd.vue中引入src/views/resetPassword/components/EmailCodeSetPwdForm.vue
此时,完整的ResetPwd.vue代码如下
<script setup lang="ts">
import { ref } from 'vue'
import EmailCodeSetPwdForm from './components/EmailCodeSetPwdForm.vue';
import PhoneCodeSetPwdForm from './components/PhoneCodeSetPwdForm.vue';
const curTab = ref(1);
const changeTab = (tab:any)=>{
curTab.value = tab;
}
// const bgColor = "linear-gradient(45deg, #5198d3, #0f9fe2, #61a2d6);";
// 样式变量
// const setPwdPagePanelWidth = '40%';
// const setPwdPagePanelHeight = '30%';
</script>
<template>
重置密码
<div class="resetpwd-page">
<div class="resetpwd-box">
<div class="tabs">
<div class="tab-item" :class="{'tab-item-selected':curTab==1}" @click="changeTab(1)">手机验证码找回密码</div>
<div class="tab-item" :class="{'tab-item-selected':curTab!=1}" @click="changeTab(2)">邮箱验证码找回密码</div>
</div>
<div class="tab-content">
<PhoneCodeSetPwdForm v-if="curTab == 1"></PhoneCodeSetPwdForm>
<EmailCodeSetPwdForm v-else></EmailCodeSetPwdForm>
</div>
</div>
<div class="resetpwd-footer">
密码重置页面
</div>
</div>
</template>
<style scoped>
.resetpwd-page{
min-width: 1024px;
min-height: 900px;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
/* background: v-bind(bgColor); */
background: linear-gradient(45deg, #5198d3, #0f9fe2, #61a2d6);
display: flex;
/* text-align: center; */
/* justify-items: center; */
/* justify-content: center; */
align-items: center;
}
.resetpwd-page .resetpwd-box{
background-color: #fff;
height: 50%;
/* height: v-bind(setPwdPagePanelWidth); */
width: 60%;
/* width: v-bind(setPwdPagePanelHeight); */
/* min-height: 800px; */
margin: 0 auto;
text-align: center;
border-radius: 10px;
/* box-shadow: var(--el-box-shadow); */
box-shadow: 0 0 10px 10px #00000055;
padding: 40px;
}
.resetpwd-page .resetpwd-box .tabs{
/* height:20px; */
height: 5%;
/* width: 400px; */
width: 40%;
line-height: 40px;
display: flex;
margin: 0 auto;
}
.resetpwd-page .resetpwd-box .tabs .tab-item{
/* width: 200px; */
width: 50%;
height: 40px;
/* height: 180%; */
text-align: center;
background-color: #00000055;
font-size: 16px;
cursor: pointer;
/* border-radius: 5px; */
}
.resetpwd-page .resetpwd-box .tabs .tab-item:first-child{
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
.resetpwd-page .resetpwd-box .tabs .tab-item:last-child{
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
.resetpwd-page .resetpwd-box .tabs .tab-item:hover,
.resetpwd-page .resetpwd-box .tabs .tab-item-selected{
color: white;
background-color: #0ba4eb;
}
.resetpwd-page .resetpwd-box .tab-content{
/* padding-top: 60px; */
padding-top: 5%;
/* display: flex=1; */
/* justify-content: center; */
/* padding-left: 10%; */
/* padding: 10%; */
/* padding: 50px; */
/* align-items: center; */
/* text-align: center; */
/* height: 400px; */
height: 100%;
/* width: 600px; */
width: 100%;
}
.resetpwd-page .resetpwd-footer{
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 10%;
text-align: center;
color: white;
font-size: 15px;
}
</style>
20.3.2 邮箱找回密码代码创建(EmailCodeSetPwdForm.vue)
将手机验证码找回密码的代码PhoneCodeSetPwdForm.vue完整复制过来即可
然后修改一下函数和变量的名字,将短信该位邮箱就行了
完整代码如下
src/views/resetPassword/components/
EmailCodeSetPwdForm.vue
<script setup lang="ts">
// vue基础模块引入
import { ref, reactive } from 'vue'
// 引入状态存储工具store
// import {useStore} from 'vuex'
// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'
// 路由引入
// import { useRoute, useRouter } from 'vue-router';
// 引入密码重置页面组件
import ResetPwdForm from './ResetPwdForm.vue';
import ResetSuccess from './ResetSuccess.vue';
const formSize = ""
// 步骤选择
const setStep = ref(1);
// 表单数据对象
const resetPwdForm = reactive({
// 用户名
username: '',
// 邮箱验证码
emailcode: '',
// 图片验证码
imgcode: '',
});
// 表单验证规则
const rules = ref({
username: [{
required: true,
message: '请输入用户名',
trigger: 'blur'
}],
emailcode: [{
required: true,
message: '请输入邮箱验证码',
trigger: 'blur'
}],
imgcode: [{
required: true,
message: '请输入图片验证码',
trigger: 'blur'
}]
});
// 获取短信验证码的按钮文本值
let emailcodeBtnText = ref("获取验证码")
// 定时器
let timer:any = null;
// 邮箱验证码获取的间隔事件
let curTime = 0;
// 获取验证码按钮
const getEmailCode = ()=>{
if(!resetPwdForm.username){
utils.showError("请先输入用户名");
return;
}
// 发送请求生成邮箱验证码
api({
method: 'post',
url: '/login/redis/setMessageCode',
params: {
username: resetPwdForm.username
}
});
// 同时进行倒计时,读秒60,结束后可重新获取
curTime = 60
timer = setInterval(() => {
curTime--;
// 值重新赋值
emailcodeBtnText.value = curTime+'秒后重新获取';
// 当及时归零时,可重新获取,并将计时器重置
if(curTime<=0){
emailcodeBtnText.value = '获取验证码'
clearInterval(timer);
timer = null;
}
},1000);
}
// 图片验证码路径
let imageCodeSrc = new URL('../../../assets/code.png',import.meta.url).href
// 刷新图片验证码
const getImgCode = () => {
// 从服务器动态获取图片验证码
imageCodeSrc = new URL('../../../assets/code.png',import.meta.url).href
}
const resetPwdFormRef = ref();
// 下一步按钮
const nextSet = () => {
// 表单校验,校验所填表单中是否有值,无值则报错
resetPwdFormRef.value.validate((valid:string, fileds:any)=>{
// 如果valid值为假,则输出报错内容
if(!valid){
for(let key in fileds){
utils.showError(fileds[key][0].message);
return;
}
return;
}
// 加载效果
utils.showLoadding("正在加载中");
// if(resetPwdForm.emailcode){
// utils.showError("验证码错误,请重新输入");
// }
// 校验填写的验证码是否一致
api({
method: 'get',
url: 'login/redis/getMessageCode',
params: {
username: resetPwdForm.username
}
}).then((res)=>{
utils.hideLoadding();
// 如果数据返回状态码不是200或请求结果返回不是200或者请求结果返回的短信验证码为空,验证失败
if(res.status!=200||res.data.result!=200||!res.data.msgCode){
utils.showError("验证失败-请求数据返回有误");
return;
}
// 如果填入的邮箱验证码和收到的短信验证码一致,则验证成功,跳转到修改密码界面
if(res.data.msgCode == resetPwdForm.emailcode){
utils.showSuccess("验证成功");
setStep.value = 2;
}else{
utils.showError("验证失败-验证码错误");
}
}).catch((error)=>{
setStep.value = 2;
utils.hideLoadding();
console.log(error);
utils.showError("验证失败-出现异常");
});
});
}
const systemToken = ref('');
// const susername = resetPwdForm.username;
// systemToken.value = resetPwdForm.username;
const toPhoneCodeSetPwdForm = ()=>{
setStep.value = 1;
}
const toResetSuccess = ()=>{
setStep.value = 3;
}
</script>
<template>
<!-- <div class="stepLine"> -->
<el-steps style="max-width: 2000px" :active="setStep" align-center>
<el-step title="身份验证" description="请输入账号和验证码进行身份确认" />
<el-step title="密码重置" description="填写新密码并确认" />
<el-step title="重置成功" description="密码修改成功" />
</el-steps>
<!-- </div> -->
<div v-if="setStep == 1" class="phone-reset-password-form">
<el-form
ref="resetPwdFormRef"
style="max-width: 600px;"
:model="resetPwdForm"
:rules="rules"
label-width="0"
class="resetPwdForm"
:size="formSize"
status-icon
>
<!-- 用户名 -->
<el-form-item prop="username">
<el-input
prefix-icon="UserFilled"
v-model="resetPwdForm.username"
placeholder="用户名"
size="large"
/>
</el-form-item>
<!-- 获取验证码 -->
<el-form-item prop="emailcode">
<div class="flex resetLine">
<div class="flexItem">
<el-input
prefix-icon="Iphone"
v-model="resetPwdForm.emailcode"
placeholder="邮箱验证码"
size="large"
/>
</div>
<div class="codeBtn">
<el-button type="primary" size="large" @click="getEmailCode" :disabled="curTime>0">
{{ emailcodeBtnText }}
</el-button>
</div>
</div>
</el-form-item>
<!-- 图片验证码 -->
<el-form-item prop="imgcode">
<div class="flex resetLine">
<div class="flexItem">
<el-input
prefix-icon="Picture"
v-model="resetPwdForm.imgcode"
placeholder="图片验证码"
size="large"
/>
</div>
<div class="codeBtn">
<el-image :src="imageCodeSrc" size="large" @click="getImgCode"></el-image>
</div>
</div>
</el-form-item>
<!-- 下一步按钮 -->
<el-form-item>
<!-- <div class="nextBtn"> -->
<el-button class="nextBtn" type="primary" size="large" @click="nextSet">下一步</el-button>
<!-- </div> -->
</el-form-item>
</el-form>
</div>
<!-- usernames表示将当前数据的只绑定到usernames中,通过在别的页面中使用defineProps获取 -->
<ResetPwdForm v-else-if="setStep == 2"
:systemToken="systemToken"
@pre="toPhoneCodeSetPwdForm"
@next="toResetSuccess"
:usernames = resetPwdForm.username
></ResetPwdForm>
<!-- <ResetPwdForm v-else-if="setStep == 2" ></ResetPwdForm> -->
<ResetSuccess v-else></ResetSuccess>
</template>
<style scoped>
.phone-reset-password-form{
padding: 5%;
/* margin-top: 2%; */
margin: 2% auto;
width: 60%;
height: 40%;
background-color: rgb(204, 240, 210);
display: flex;
justify-content: center;
}
.codeBtn{
margin-left: 10px;
}
.codeBtn:deep(img){
height: 40px;
cursor: pointer;
}
.resetLine{
width: 100%;
}
.nextBtn{
width: 100%;
}
</style>
20.3.3 密码设置成功页的页面优化(ResetSuccess.vue)
对密码设置成功的页面进行优化,做了一点修改
先不自动跳转,注掉了自动跳转的代码,添加了一个图标,将点击的链接下划线剔除了
完整代码如下
src/views/resetPassword/components/
ResetSuccess.vue
<script setup lang="ts">
import { onMounted } from 'vue';
import {useRouter} from 'vue-router'
const router = useRouter()
let timer:any = null;
let curTime = 5;
const curTimeFun = ()=>{
// timer = setInterval(()=>{
// curTime--;
// if(curTime<=0){
// router.push("/UserLogin")
// clearInterval(timer);
// timer = null;
// }
// },1000);
}
onMounted(()=>{
curTimeFun();
})
</script>
<template>
<div class="success-box">
<div class="success-box-icon">
<el-icon><CircleCheck /></el-icon>
</div>
<div class="success-box-link">
<!-- <router-link to="/UserLogin" >点击跳转登录界面</router-link> -->
<router-link to="/UserLogin" class="success-box-link-btn" :curtime="curTimeFun">点击跳转登录界面,{{curTime}} 后自动跳转</router-link>
</div>
</div>
</template>
<style scoped>
/* .success-box{
text-align: center;
} */
.success-box-icon{
width: 20%;
height: 10%;
margin: auto;
padding: 6%;
font-size: 60px;
color: #409eff;
}
/* .success-box-link{ */
/* width: 60%; */
/* height: 20%; */
/* margin: auto; */
/* padding: 1%; */
/* font-size: 20px; */
/* font-style: italic; */
/* background-color: rgb(184, 230, 190); */
/* } */
.success-box-link-btn{
/* width: 60%; */
/* margin: auto; */
padding: 1%;
font-size: 20px;
text-decoration: none;
background-color: rgb(184, 230, 190);
}
</style>
20.3.4 邮箱找回密码功能页面效果演示
忘记密码
选择邮箱找回密码
输入用户名、获取验证码,输入验证码,随便输入图片验证码,下一步
验证通过,输入新密码,下一步
密码修改成功,点击文字链接可跳转到登录界面
20.4 项目下载地址
此时的项目代码已上传,实现了目前文章中所实现的代码功能
前端项目下载地址:hslb-vue3-elementplus-admin 登录和找回密码功能实现
后端接口项目下载地址:java hslb-general-management-system 后端接口服务 登录和密码修改
注:后端接口项目需要配合mysql和数据库,可以根据需要自行实践,如已经有现成的接口可忽略这里的后端项目
21. 找回密码页面优化(ResetPwd.vue)
新增左上角系统的名称,以及右上角的返回登录页面按钮
完整代码
src/views/resetPassword/
ResetPwd.vue
<script setup lang="ts">
import { ref } from 'vue'
import EmailCodeSetPwdForm from './components/EmailCodeSetPwdForm.vue';
import PhoneCodeSetPwdForm from './components/PhoneCodeSetPwdForm.vue';
const curTab = ref(1);
const changeTab = (tab:any)=>{
curTab.value = tab;
}
// const bgColor = "linear-gradient(45deg, #5198d3, #0f9fe2, #61a2d6);";
// 样式变量
// const setPwdPagePanelWidth = '40%';
// const setPwdPagePanelHeight = '30%';
</script>
<template>
<div class="resetpwd-page">
<div class="resetpwd-page-header">
<div class="resetpwd-page-header-name">寒山李白通用系统</div>
<!-- <div class="resetpwd-page-header-return">返回登录页面</div> -->
<router-link class="resetpwd-page-header-return" to="/UserLogin">返回登录页面</router-link>
</div>
<div class="resetpwd-box">
<div class="tabs">
<div class="tab-item" :class="{'tab-item-selected':curTab==1}" @click="changeTab(1)">手机验证码找回密码</div>
<div class="tab-item" :class="{'tab-item-selected':curTab!=1}" @click="changeTab(2)">邮箱验证码找回密码</div>
</div>
<div class="tab-content">
<PhoneCodeSetPwdForm v-if="curTab == 1"></PhoneCodeSetPwdForm>
<EmailCodeSetPwdForm v-else></EmailCodeSetPwdForm>
</div>
</div>
<div class="resetpwd-footer">
密码重置页面
</div>
</div>
</template>
<style scoped>
.resetpwd-page{
min-width: 1024px;
min-height: 900px;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
/* background: v-bind(bgColor); */
background: linear-gradient(45deg, #5198d3, #0f9fe2, #61a2d6);
display: flex;
/* text-align: center; */
/* justify-items: center; */
/* justify-content: center; */
align-items: center;
}
.resetpwd-page-header-name{
position: fixed;
top: 0;
left: 0;
font-size: 30px;
font-weight: bold;
letter-spacing: 3px;
padding: 1%;
background-image: -webkit-linear-gradient(right, rgba(78, 224, 33, 0.795), #22fc2d, rgb(236, 126, 36));
/* background-image: -webkit-background-clip(bottom, red, #fd8403, yellow); */
/* -webkit-background-clip: text; */
background-clip: text;
-webkit-text-fill-color: transparent;
}
.resetpwd-page-header-return{
position: fixed;
top: 0;
right: 0;
border-radius: 3px;
font-size: 20px;
text-decoration: none;
padding: 2%;
color: rgb(11, 75, 211);
}
.resetpwd-page-header-return:hover{
/* background-color: rgb(74, 206, 22); */
color: rgb(241, 228, 33);
}
.resetpwd-page .resetpwd-box{
background-color: #fff;
height: 50%;
/* height: v-bind(setPwdPagePanelWidth); */
width: 60%;
/* width: v-bind(setPwdPagePanelHeight); */
/* min-height: 800px; */
margin: 0 auto;
text-align: center;
border-radius: 10px;
/* box-shadow: var(--el-box-shadow); */
box-shadow: 0 0 10px 10px #00000055;
padding: 40px;
}
.resetpwd-page .resetpwd-box .tabs{
/* height:20px; */
height: 5%;
/* width: 400px; */
width: 40%;
line-height: 40px;
display: flex;
margin: 0 auto;
}
.resetpwd-page .resetpwd-box .tabs .tab-item{
/* width: 200px; */
width: 50%;
height: 40px;
/* height: 180%; */
text-align: center;
background-color: #00000055;
font-size: 16px;
cursor: pointer;
/* border-radius: 5px; */
}
.resetpwd-page .resetpwd-box .tabs .tab-item:first-child{
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
.resetpwd-page .resetpwd-box .tabs .tab-item:last-child{
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
.resetpwd-page .resetpwd-box .tabs .tab-item:hover,
.resetpwd-page .resetpwd-box .tabs .tab-item-selected{
color: white;
background-color: #0ba4eb;
}
.resetpwd-page .resetpwd-box .tab-content{
/* padding-top: 60px; */
padding-top: 5%;
/* display: flex=1; */
/* justify-content: center; */
/* padding-left: 10%; */
/* padding: 10%; */
/* padding: 50px; */
/* align-items: center; */
/* text-align: center; */
/* height: 400px; */
height: 100%;
/* width: 600px; */
width: 100%;
}
.resetpwd-page .resetpwd-footer{
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 10%;
text-align: center;
color: white;
font-size: 15px;
}
</style>
页面效果展示
感谢阅读,祝君暴富!