当前位置: 首页 > article >正文

通用后台管理系统实战演示(Vue3 + element-plus)汇总篇二


天行健,君子以自强不息;地势坤,君子以厚德载物。


每个人都有惰性,但不断学习是好好生活的根本,共勉!


文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。


浮云游子意,落日故人情。
——《送友人》


文章目录

    • 19. 登录优化(记住用户名和密码)
      • 19.1 api/api.ts
      • 19.2 store/index.ts
      • 19.3 index/HomeIndex.vue
      • 19.4 PhoneCodeForm.vue
      • 19.5 QcodeForm.vue
      • 19.6 UsernameForm.vue
      • 19.7 App.vue
      • 19.8 页面效果展示
        • 19.8.1 使用短信验证码登录的展示
        • 19.8.2 使用账号密码登录的展示
        • 19.8.3 使用二维码登录的展示
    • 20. 找回密码功能实现
      • 20.1 找回密码的布局开发
        • 20.1.1 ResetPwd.vue
        • 20.1.2 router/index.ts
        • 20.1.3 UsernameForm.vue
        • 20.1.4 页面效果
      • 20.2 短信验证码找回密码
        • 20.2.1 PhoneCodeSetPwdForm.vue
        • 20.2.2 ResetPwd.vue密码重置页面中引入短信验证页
        • 20.2.3 获取短信验证码页面的效果
        • 20.2.4 ResetPwdForm.vue重置密码页面的实现
        • 20.2.5 设置密码的页面效果展示
        • 20.2.6 设置密码后的上一步和下一步配置
        • 20.2.7 ResetSuccess.vue
        • 20.2.8 密码设置成功后的自动跳转效果
      • 20.3 邮箱找回密码
        • 20.3.1 引入邮箱找回密码的页面(ResetPwd.vue)
        • 20.3.2 邮箱找回密码代码创建(EmailCodeSetPwdForm.vue)
        • 20.3.3 密码设置成功页的页面优化(ResetSuccess.vue)
        • 20.3.4 邮箱找回密码功能页面效果演示
      • 20.4 项目下载地址
    • 21. 找回密码页面优化(ResetPwd.vue)


Vue入门学习专栏


接着上一篇继续
先进行登录的优化
然后再进行找回密码功能的实现

先进行上一篇最后一部分登录问题进行优化
优化内容如下:
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>

页面效果展示
在这里插入图片描述


感谢阅读,祝君暴富!



http://www.kler.cn/a/287687.html

相关文章:

  • 【windows】05-windows系统级深度隐藏文件方法
  • 文心一言编写小球反弹程序并优化
  • 使用 TensorFlow 实现 ZFNet 进行 MNIST 图像分类
  • AI驱动的桌面笔记应用Reor
  • 【Oracle篇】掌握SQL Tuning Advisor优化工具:从工具使用到SQL优化的全方位指南(第六篇,总共七篇)
  • Python爬虫项目 | 一、网易云音乐热歌榜歌曲
  • 设计模式之生成器方法
  • css揭秘 7 结构与布局
  • Swin Transformer: Hierarchical Vision Transformer using Shifted Windows
  • 使用API有效率地管理Dynadot域名,添加账户中的联系人信息
  • Java中Object的常用方法
  • 专利复现_基于ngboost和SHAP值可解释预测方法
  • 【html】新建一个html并且在浏览器运行
  • 零域(微隔离)详述
  • docker4
  • ios 企业签名证书购买_iOS苹果企业签名须知
  • Spring源码浅析の循环依赖
  • 泰山派的小手机后续(2)
  • upload-labs通关攻略
  • Clickhouse集群化(四)使用clickhouse-operator部署clickhouse集群
  • vs中在工具箱添加自定义控件numberTextBox
  • 链表题总结
  • 使用MySql
  • [YM]课设-C#-WebApi-Vue-员工管理系统 (六)前后端交互
  • 二手电脑配置给你不一样的成就感之四
  • SQLite3 数据类型深入全面讲解