锋盈数科-知识库 Logo
首页
软件开发
计算机基础
Hello Halo
新手必读
关于本知识库
登录 →
锋盈数科-知识库 Logo
首页 软件开发 计算机基础 Hello Halo 新手必读 关于本知识库
登录
  1. 首页
  2. 软件开发
  3. 前后端分离项目实战-通用管理系统搭建(前端Vue3+ElementPlus,后端Springboot+Mysql+Redis)第二篇:项目登录功能的实现

前后端分离项目实战-通用管理系统搭建(前端Vue3+ElementPlus,后端Springboot+Mysql+Redis)第二篇:项目登录功能的实现

0
  • 软件开发
  • 发布于 2024-08-19
  • 1 次阅读
黄健
黄健

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


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


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


长安一片月,万户捣衣声。
------《子夜吴哥·秋歌》



文章目录

  • `第二部分:登录相关业务功能的实现`
    • 10. 登录页布局实现
      • 10.1 主组件
      • 10.2 登录窗口和声明的实现
        • 10.2 1 template标签代码
        • 10.2.2 style标签代码
        • 10.2.3 页面效果
      • 10.3 登录页的logo和表单布局实现
        • 10.3.1 template标签代码
        • 10.3.2 style标签代码
        • 10.3.3 页面效果
      • 10.4 登录页表单部分tab实现
        • 10.4.1 script标签代码
        • 10.4.2 template标签代码
        • 10.4.3 style标签代码
        • 10.4.4 页面效果
      • 10.5 三种登录方式初始页面的实现
        • 10.5.1 登录方式vue组件
        • 10.5.2 script标签代码
        • 10.5.3 template标签代码
        • 10.5.4 页面效果
      • 10.6 页面样式参数以变量形式应用
        • 10.6.1 script标签代码
        • 10.6.2 template标签代码
        • 10.6.3 style标签代码
    • 11. 手机验证码登录功能实现
      • 11.1 三种方式登录的表单内容布局设置
      • 11.2 手机验证码登录布局实现
        • 11.2.1 script标签代码
        • 11.2.2 template标签代码
        • 11.2.3 style标签代码
        • 11.2.4 页面效果
      • 11.3 短信和图片验证码获取的布局实现
        • 11.3.1 引入全局css样式
        • 11.3.2 验证码按钮布局代码实现
        • 11.3.3 页面效果展示
      • 11.4 手机验证码登录的逻辑实现
        • 11.4.1 表单验证逻辑实现
        • 11.4.2 获取短信验证码按钮触发的事件实现
        • 11.4.3 记住用户名功能实现
      • 11.5 手机验证码登录相关功能优化
      • 11.6 完整代码
    • 12. 用户密码登录功能的实现
      • 12.1 添加或修改的内容
      • 12.2 完整代码
      • 12.3 页面效果展示
    • 13. 扫码登录功能的实现
      • 13.1 扫码登录组件代码
      • 13.2 扫码登录页面效果
    • 14. 手机验证码登录的接口、状态存储和路由跳转的实现
      • 14.1 后端服务接口创建
        • 14.1.1 路由前缀
        • 14.1.2 生成验证码
        • 14.1.3 获取验证码
      • 14.2 api实例添加路由前缀
      • 14.3 配置全局状态存储store
      • 14.4 工具类中添加页面加载优化的代码
      • 14.5 手机验证码登录组件代码更新
      • 14.6 页面效果展示
    • 15. 账号密码登录的接口、状态存储和路由跳转的实现
      • 15.1 后端接口
      • 15.2 代码实现
      • 15.3 页面效果展示
    • 16. 扫码登录的接口、状态存储和路由跳转的实现
      • 16.1 接口地址
      • 16.2 代码实现
      • 16.3 页面效果展示
    • 17. 登录缓存验证的实现
      • 17.1 三种登录方式代码改写
      • 17.2 页面缓存校验
      • 17.3 主页代码
      • 17.4 路由代码修改
    • 18. 项目的源码下载地址



第二部分:登录相关业务功能的实现

10. 登录页布局实现

10.1 主组件

在主组件App.vue中将多于部分代码剔除,保留路由标签RouterView
App.vue此时完整代码如下

<script setup lang="ts">
</script>

<template>
  <RouterView></RouterView>
</template>

<style scoped>
</style>

10.2 登录窗口和声明的实现

在login.vue中添加以下代码

10.2 1 template标签代码

在template标签中添加如下代码,共两部分,在整个div块中布局两个内容,登录窗口和页脚声明

	<div class="login-page">
        <div class="login-panel">
        </div>
        <div class="login-footer" >
            版权声明:通用管理系统最终解释权归寒山李白所有
        </div>
	</div>
10.2.2 style标签代码

在style scoped标签中添加如下代码,对上面的div块进行样式渲染,页面背景色设为蓝色渐变,登录窗口设为白色背景,声明字体颜色为白色。

/* 主页样式 */
.login-page{
   
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    /* background: v-bind(bgColor); */
    /* 页面背景色的渐变 */
    background: linear-gradient(45deg, #5198d3, #0f9fe2, #61a2d6);
    
}
/* 登录模块样式 */
.login-page .login-panel{
   
    /* text-align: center; */
    width: 800px;
    height: 450px;
    background: #ffff;
    padding: 10px;
    border-radius: 5px;
    /* 水平居中配置,使用margin的auto值 */
    margin: 0 auto;
    /* 垂直居中配置,使用cacl函数,使用100vh减去高度height的值除以2 */
    margin-top: calc((100vh - 450px)/2);

    /* 登录窗口边界阴影效果 */
    box-shadow: 0 0 20px 20px #00000055;

}

/* 页脚声明样式配置 */
.login-page .login-footer{
   
    position: fixed;
    bottom: 0;
    left: 0;
    width: 100%;
    height: 60px;
    text-align: center;
    color: #fff;
    font-size: 14px;
}
10.2.3 页面效果

此时页面是这样的,

10.3 登录页的logo和表单布局实现

在login.vue中添加以下代码

在class为login-panel的div块中添加一个class为login-inner的div块作为logo和登录表单的父级div块
然后再在login-inner的div块中添加三个div块,分别为logo图、分割线、表单

图片的话可以自己随便搞一个logo图,放到src下的assets包中

10.3.1 template标签代码

在template标签中添加以下代码(在class为login-panel的div块中添加)

            <div class="login-inner">
                <div class="logo-panel">
                    <img src="../../assets/logo-h.png">
                    <!-- <img src="../../assets/logo-v.png"> -->
                </div>
                <!-- 分割线,区分开logo图和表单 -->
                <div class="login-inner-split" ></div>
                <!-- 登录的表单部分 -->
                <div class="login-form-panel">
                </div>
            </div>
10.3.2 style标签代码

在style标签中添加以下对新增div样式的配置

/* 登录logo和表单的父类标签布局 */
.login-page .login-panel .login-inner{
   
    /* 自动伸缩均分展示 */
    display: flex;
}

/* logo 图标样式配置 */
.login-page .login-panel .logo-panel{
   
    /* 宽度占比40% */
    width: 40%;
    text-align: center;
}

/* logo图标样式设置 */
.login-page .login-panel .logo-panel img{
   
    width: 300px;
    margin-top: 125px;
}

/* 分割线样式 */
.login-page .login-panel .login-inner-split{
   
    width: 3px;
    background: #f8f8f8;
    height: 450px;
}
10.3.3 页面效果

此时的页面如下图

10.4 登录页表单部分tab实现

在login.vue中添加以下代码

在class为login-form-panel的div块中添加一个class为tabs的div块,在这个div块中添加三个子div块,分别为免密登录、账号登录、扫码登录

10.4.1 script标签代码

在script标签中添加如下代码

    import {
   ref} from 'vue'
    // 当前选中的tab
    const curtab = ref(1);
    // tab切换监听事件
    const changeTab = (tabIndex) => {
   
        curtab.value = tabIndex;
    }
10.4.2 template标签代码

在template标签中添加以下代码(在class为login-form-panel的div块中添加)

                    <!-- 三种不同方式登录的表单实现 -->
                    <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==2}" @click="changeTab(2)" >账号登录</div>
                        <div class="tab-item" :class="{'tab-item-selected':curtab==3}" @click="changeTab(3)" >扫码登录</div>
                    </div>
10.4.3 style标签代码

在style标签中添加以下代码


/* 表单登录部分的布局设置 */
.login-page .login-panel .login-form-panel{
   
    /* 此项下的布局均分 */
    flex: 1;
}

/* tabs对应的块的样式设置 */
.login-page .login-panel .login-form-panel .tabs{
   
    height: 45px;
    line-height: 45px;
    text-align: center;
    display: flex;
}

/* 登录界面的登录方式表单样式配置 */
.login-page .login-panel .login-form-panel .tabs .tab-item{
   
    /* 每个部分均分 */
    flex: 1;

    cursor: pointer;
}

/* 鼠标悬浮变红,以及选中后变红 */
.login-page .login-panel .login-form-panel .tabs .tab-item:hover,
.login-page .login-panel .login-form-panel .tabs .tab-item-selected{
   
    color:red;
}
10.4.4 页面效果

此时页面效果如下

10.5 三种登录方式初始页面的实现

先创建三个登录方式的vue组件
再在App.vue中添加代码,在class为login-form-panel的div块中,添加class为tab-content的div块

10.5.1 登录方式vue组件

在src下的views包中的login包下,创建一个component包,在包中创建三个文件
PhoneCodeForm.vue

<script setup lang="ts">

</script>

<template>
    手机验证码登录
</template>

<style scoped>
</style>

QcodeForm.vue

<script setup lang="ts">

</script>

<template>
    扫码登录
</template>

<style scoped>
</style>

UsernameForm.vue

<script setup lang="ts">

</script>

<template>
    用户名密码登录
</template>

<style scoped>
</style>
10.5.2 script标签代码

在script标签中添加如下代码,引入vue组件

    // 引入登录界面的组件
    import QcodeForm from './components/QcodeForm.vue'
    import UsernameForm from './components/UsernameForm.vue'
    import PhoneCodeForm from './components/PhoneCodeForm.vue'
10.5.3 template标签代码

在App.vue中的class为login-form-panel的div块中添加如下代码(与class为tabs的div同级)

                    <!-- 三种不同方式登录的对应的vue组件页面 -->
                    <div class="tab-content" >
                        <PhoneCodeForm v-if="curtab==1"></PhoneCodeForm>
                        <UsernameForm v-else-if="curtab==2"></UsernameForm>
                        <QcodeForm v-else></QcodeForm>
                    </div>
10.5.4 页面效果

此时页面如下,分别点击不同登录方式会展示不同方式对应的组件页面



10.6 页面样式参数以变量形式应用

在style标签中的样式配置,有很多参数值,这些值如果用的很多,可以将其在ts中声明变量,在样式中使用变量,这样对以后得修改会方便很多
整合后的代码如下(这里也是目前项目的完整代码内容)
login.vue

10.6.1 script标签代码

完整的ts代码如下

    import api from '../../api/api'
    import {
   ref} from 'vue'
    // 引入登录界面的组件
    import QcodeForm from './components/QcodeForm.vue'
    import UsernameForm from './components/UsernameForm.vue'
    import PhoneCodeForm from './components/PhoneCodeForm.vue'

    // 调用的是接口而非网址
    // api.get('127.0.0.1:8089/test/libai').then(resp=>{
   
    api.get('https://www.baidu.com').then(resp=>{
   
        console.log(resp.data)
    })
    // 当前选中的tab
    const curtab = ref(1);
    // tab切换监听事件
    const changeTab = (tabIndex) => {
   
        curtab.value = tabIndex;
    }
    // 样式变量
    const loginPagePanelWidth = '800px';
    const loginPagePanelHeight = '450px';
10.6.2 template标签代码

完整的template代码

登录界面
    <div class="login-page">
        <div class="login-panel">
            <div class="login-inner">
                <div class="logo-panel">
                    <img src="../../assets/logo-h.png">
                    <!-- <img src="../../assets/logo-v.png"> -->
                </div>
                <!-- 分割线,区分开logo图和表单 -->
                <div class="login-inner-split" ></div>
                <!-- 登录的表单部分 -->
                <div class="login-form-panel">
                    <!-- 三种不同方式登录的表单实现 -->
                    <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==2}" @click="changeTab(2)" >账号登录</div>
                        <div class="tab-item" :class="{'tab-item-selected':curtab==3}" @click="changeTab(3)" >扫码登录</div>
                    </div>
                    <!-- 三种不同方式登录的对应的vue组件页面 -->
                    <div class="tab-content" >
                        <PhoneCodeForm v-if="curtab==1"></PhoneCodeForm>
                        <UsernameForm v-else-if="curtab==2"></UsernameForm>
                        <QcodeForm v-else></QcodeForm>
                    </div>
                </div>
            </div>
        </div>
        <div class="login-footer" >
            版权声明:通用管理系统最终解释权归寒山李白所有
        </div>
    </div>
10.6.3 style标签代码

完整的style代码如下


/* 主页背样式 */
.login-page{
   
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    /* background: v-bind(bgColor); */
    /* 页面背景色的渐变 45度渐变 */
    background: linear-gradient(45deg, #5198d3, #0f9fe2, #61a2d6);
    /* 上下渐变色 */
    /* background: linear-gradient(to bottom, #5198d3, #0f9fe2, #61a2d6); */
    
}

/* 登录模块样式 */
.login-page .login-panel{
   
    /* text-align: center; */
    /* width: 800px; */
    /* height: 450px; */
    width: v-bind(loginPagePanelWidth);
    height: v-bind(loginPagePanelHeight);
    background: #ffff;
    padding: 10px;
    border-radius: 5px;
    /* 水平居中配置,使用margin的auto值 */
    margin: 0 auto;
    /* 垂直居中配置,使用cacl函数,使用100vh减去高度height的值除以2 */
    margin-top: calc((100vh - 450px)/2);
    /* margin-top: calc((100vh - v-bind(loginPagePanelHeight)) / 2); */

    /* 登录窗口边界阴影效果 */
    box-shadow: 0 0 20px 20px #00000055;

}

/* 登录logo和表单的父类标签布局 */
.login-page .login-panel .login-inner{
   
    /* 自动伸缩均分展示 */
    display: flex;
}

/* logo 图标样式配置 */
.login-page .login-panel .logo-panel{
   
    /* 宽度占比40% */
    width: 40%;
    text-align: center;
}

/* logo图标样式设置 */
.login-page .login-panel .logo-panel img{
   
    /* width: 300px;
    margin-top: 125px; */
    width: 80%;
    margin-top: calc(v-bind(loginPagePanelHeight)*0.4);
}

/* 分割线样式 */
.login-page .login-panel .login-inner-split{
   
    width: 3px;
    /* height: 450px; */
    height: v-bind(loginPagePanelHeight);
    /* 分割线左右间隔 */
    margin: 0 10px;
    /* 分割线背景色,灰色 */
    background: #f8f8f8;
}

/* 表单登录部分的布局设置 */
.login-page .login-panel .login-form-panel{
   
    /* 此项下的布局均分 */
    flex: 1;
}

/* tabs对应的块的样式设置 */
.login-page .login-panel .login-form-panel .tabs{
   
    height: 45px;
    line-height: 45px;
    /* 增加tabs上边框的距离 */
    margin-top: 20px;
    text-align: center;
    display: flex;
}

/* 登录界面的登录方式表单样式配置 */
.login-page .login-panel .login-form-panel .tabs .tab-item{
   
    /* 每个部分均分 */
    flex: 1;

    cursor: pointer;
}

/* 鼠标悬浮变红,以及选中后变红 */
.login-page .login-panel .login-form-panel .tabs .tab-item:hover,
.login-page .login-panel .login-form-panel .tabs .tab-item-selected{
   
    color:red;
}

/* 页脚声明样式配置 */
.login-page .login-footer{
   
    position: fixed;
    bottom: 0;
    left: 0;
    width: 100%;
    height: 60px;
    text-align: center;
    color: #fff;
    font-size: 14px;
}

11. 手机验证码登录功能实现

基于以上代码实现手机验证码登录功能

11.1 三种方式登录的表单内容布局设置

在login.vue中的style样式中添加以下对表单内容配置样式的代码

/* 登录表单内容的样式配置  */
.login-page .login-panel .login-form-panel .tab-content{
   
    /* 边框距离设置 */
    padding-top: 20px;
    padding-left: 45px;
    padding-right: 45px;
}

11.2 手机验证码登录布局实现

11.2.1 script标签代码

登录表单实例和数据的创建

import {
    ref,reactive } from 'vue'


    // 登录表单的实例
    const loginFormRef = ref(null);
    // 登录表单的数据
    const loginForm = reactive({
   
        // 用户名
        username: '',
        // 手机验证码
        smscode: '',
        // 图片验证
        imgcode: '',
        // 记住用户名,默认否
        saveUsername: false
    });
    // 登录验证规则
    const rules = ({
   

    });
11.2.2 template标签代码

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">
                <el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' />
            </el-form-item>
            <!-- 图片验证 -->
            <el-form-item prop="imgcode">
                <el-input prefix-icon="Picture" v-model="loginForm.imgcode" placeholder="请输入图片验证码" size='large' />
            </el-form-item>
            <!-- 记住用户名 -->
            <el-form-item prop="saveUsername">
                <el-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox>
            </el-form-item>
            <!-- 登录按钮 -->
            <el-form-item prop="saveUsername">
                <el-button class="loginBtn" type="danger" size='large' @click="onSubmit">登录</el-button>
            </el-form-item>

        </el-form>
    </div>
11.2.3 style标签代码

style标签的代码如下

	/* 按钮宽度设为最大 */
    .loginBtn{
   
        width: 100%;
    }
11.2.4 页面效果

此时页面展示效果如下

11.3 短信和图片验证码获取的布局实现

在输入验证码和输入图片验证码后面加上获取验证码的按钮

11.3.1 引入全局css样式

src下的styles包下有个default.css文件,内容如下

@charset 'utf-8';
html,
body {
   
	margin: 0;
	padding: 0;
}

.flex {
   
	display: flex;
}

.flexItem {
   
	flex: 1;
}

在src下的main.ts中引入该文件

// 引入公共样式
import './styles/default.css'
11.3.2 验证码按钮布局代码实现

在原来的基础上修改并添加内容以下是完整的PhoneCodeForm.vue组件代码

<script setup lang="ts">

import {
    ref,reactive } from 'vue'


    // 登录表单的实例
    const loginFormRef = ref(null);
    // 登录表单的数据
    const loginForm = reactive({
   
        // 用户名
        username: '',
        // 手机验证码
        smscode: '',
        // 图片验证
        imgcode: '',
        // 记住用户名,默认否
        saveUsername: false
    });
    // 登录验证规则
    const rules = ({
   

    });

    // 图片验证码图片
    const imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;
    // const imgCodeSrc = '../../../assets/code.png';

</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" class="" >获取验证码</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" ></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 prop="saveUsername">
                <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;

    }
    /* 验证码图片高度 */
    .codeBtn:deep(img){
   
        height: 40px;
    }

    /* 这一行宽度占满 */
    .loginLine{
   
        width: 100%
    }

</style>

验证码图片自己截图保存到src下的assets包中即可

11.3.3 页面效果展示

此时页面如下

11.4 手机验证码登录的逻辑实现

表单验证的实现(参考element-plus表单组件中的表单验证代码)

输入框未输入时点击登录提示实现

刷新验证码点击事件的实现

11.4.1 表单验证逻辑实现

在PhoneCodeForm.vue中添加以下代码即可实现输入框的报错提示,必须输入后方可登录
在script标签中添加以下代码

    // 登录验证规则
    const rules = ({
   
        username:[{
   
            required: true,
            message: '请输入用户名',
            trigger: 'blur'
        }],
        smscode:[{
   
            required: true,
            message: '请输入短信验证码',
            trigger: 'blur'
        }],
        imgcode:[{
   
            required: true,
            message: '请输入图片验证码',
            trigger: 'blur'
        }]
    });

页面效果如下,点击输入框后不输入内容会提示

11.4.2 获取短信验证码按钮触发的事件实现

点击获取验证码,开始计时,倒计时60秒后重新获取,同时实现如果没有填写用户名,无法获取验证码
此时代码的修改如下
script中代码中引入以下内容

// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'
    // 定时器
    let timer = null;
    // 获取短信验证码的间隔时间
    let curTime = 0;
    // 获取短信验证码按钮的文本显示内容
    let smsCodeBtnText = ref('获取验证码');

    // 获取短信验证码
    const getSmsCode = () => {
   
        // 当点击获取短信验证码时,如果其他信息没填则提示输入
        if(!loginForm.username){
   
            utils.showError('请输入用户名');
            return;
        }
        // if(!loginForm.smscode){
   
        //     utils.showError('请输入短信验证码');
        //     return;
        // }
        

        // TODO 从后台获取短信验证码

        curTime = 60;
        timer = setInterval(() => {
   
            curTime--;
            smsCodeBtnText.value = curTime+'秒后重新获取';
            if(curTime<=0){
   
                smsCodeBtnText.value = '获取验证码';
                clearInterval(timer);
            }
        },1000);

    };

template标签中的代码修改短信验证部分,添加对应事件绑定

            <!-- 短信验证 -->
            <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>

此时页面效果如下,不输入用户名点击获取验证码,顶部提示输入信息

输入用户名后再点击获取验证码,开始进行读秒,读完后方可重新获取

11.4.3 记住用户名功能实现

当填入用户信息后,勾选记住用户名,刷新页面,用户名还存在
在script标签的代码中添加以下逻辑

    // 登录提交事件
    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');
            }
        });
    };

    // 挂载
    onMounted(() => {
   
        // 获取记住用户名的值
        loginForm.saveUsername = utils.getData('saveUsername');
        // 如果记住用户名被勾选,则获取用户名显示
        if(loginForm.saveUsername){
   
            loginForm.username = utils.getData('username');
        }
    })

在template标签中修改记住用户名和登录按钮对应的内容,在按钮中添加事件

            <!-- 记住用户名 -->
            <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>

此时访问浏览器页面,输入数据,登录

刷新页面,用户名还在,记住用户名也还被勾选着

11.5 手机验证码登录相关功能优化

优化了定时器,在点击登录后给一个登录成功的提示(如不知代码添加位置,可参考11.6 完整代码)
定时器相关代码

        curTime = 60;
        timer = setInterval(() => {
   
            curTime--;
            smsCodeBtnText.value = curTime+'秒后重新获取';
            if(curTime<=0){
   
                smsCodeBtnText.value = '获取验证码';
                clearInterval(timer);
                // 清除时,值为空,防止重复点击触发多次
                timer = null;
            }
        },1000);
    // 清空定时器
    onMounted(() => {
   
        timer && clearInterval(timer);
    });

登录成功提示

            // 登录成功信息提示
            utils.showSuccess("登录成功");

11.6 完整代码

手机验证码登录的完整vue组件代码如下
PhoneCodeForm.vue

<script setup lang="ts">

import {
    ref,reactive, onMounted } from 'vue'

// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'


    // 登录表单的实例
    let loginFormRef = ref(null);
    // 登录表单的数据
    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 从后台获取短信验证码

        curTime = 60;
        timer = setInterval(() => {
   
            curTime--;
            smsCodeBtnText.value = curTime+'秒后重新获取';
            if(curTime<=0){
   
                smsCodeBtnText.value = '获取验证码';
                clearInterval(timer);
                // 清除时,值为空,防止重复点击触发多次
                timer = null;
            }
        },1000);

    };

    // 登录提交事件
    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');
            }

            // TODO 调用接口登录

            // 登录成功信息提示
            utils.showSuccess("登录成功");

        });
    };

    // 挂载
    onMounted(() => {
   
        // 获取记住用户名的值
        loginForm.saveUsername = utils.getData('saveUsername');
        // 如果记住用户名被勾选,则获取用户名显示
        if(loginForm.saveUsername){
   
            loginForm.username = utils.getData('username');
        }
    });

    // 清空定时器
    onMounted(() => {
   
        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>

12. 用户密码登录功能的实现

参考手机验证码登录界面实现,复制代码进行修改

12.1 添加或修改的内容

密码输入的图标和是否可见密码的按钮实现
记住密码的实现
登录成功的提示

12.2 完整代码

完整的用户密码登录代码如下
UsernameForm.vue

<script setup lang="ts">

import {
    ref,reactive, onMounted } from 'vue'

// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'


    // 登录表单的实例
    let loginFormRef = ref(null);
    // 登录表单的数据
    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 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.showSuccess("登录成功");

        });
    };

    // 挂载
    onMounted(() => {
   
        // 获取记住用户名的值
        loginForm.saveUsername = utils.getData('saveUsername');
        // 如果记住用户名被勾选,则获取用户名显示
        if(loginForm.saveUsername){
   
            loginForm.username = utils.getData('username');
        }

        // 获取记住密码的值
        loginForm.savePassword = utils.getData('savePassword');
        // 如果记住密码被勾选,则获取密码
        if(loginForm.saveUsername){
   
            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' />
                </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-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox>
                    </div>
                    <!-- 记住密码 -->
                    <div class="flexItem" >
                        <el-checkbox v-model="loginForm.savePassword">记住密码</el-checkbox>
                    </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>

12.3 页面效果展示

页面效果如下

13. 扫码登录功能的实现

13.1 扫码登录组件代码

QcodeForm.vue

<script setup lang="ts">

import {
    ref,reactive, onMounted, onUnmounted } from 'vue'

// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'


    // 二维码
    let qcodeSrc = new URL("../../../assets/hslb-qcode.png", import.meta.url).href;
    // const imgCodeSrc = '../../../assets/code.png';

    
    // 二维码对应的token, 用于判断当前二维码是否已经被扫码登录
    const qcodeToken = ref('');

    // 当前定时器事件
    const curTime = ref(0);
    let timer:any = null;
    
    // 后台更新获取二维码
    const loadQcode = () => {
   
        // 后续改为从服务器上获取动态图片
        qcodeSrc = new URL("../../../assets/hslb-qcode.png", import.meta.url).href;

        // 初始化token的值
        qcodeToken.value = "";
        // 设定定时时间
        // 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
    }

</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>

13.2 扫码登录页面效果

刷新页面,选择扫码登录,可以看到二维码和倒计时时间

时间到了之后,二维码变黑色,出现红色提示,点击红色提示文本进行重新获取二维码

14. 手机验证码登录的接口、状态存储和路由跳转的实现

手机验证码登录方式
接口有:生成验证码、获取验证码
状态存储:用于存储状态,方便在全局调用
路由跳转:登录成功后跳转到主页页面

14.1 后端服务接口创建

使用你掌握的后端语言编写服务,我是java,使用springboot框架集成mysql和redis来存储数据
这里你可以不用管这个后端的项目,只需要知道我们需要一个后端的接口地址,并且知道如何在前端的代码中使用即可

14.1.1 路由前缀

后端的接口地址如下:
后端服务的地址为本机地址,所以正常访问接口前面都是

http://127.0.0.1:8888/
14.1.2 生成验证码

点击获取验证码按钮即调用生成验证码接口
生成验证码:

http://127.0.0.1:8888/login/redis/setMessageCode

请求方式为post
请求参数为username

14.1.3 获取验证码

这里只能去redis中查看,模拟手机收到验证码
获取验证码接口:

http://127.0.0.1:8888/login/redis/getMessageCode

请求方式为get
请求参数为username

14.2 api实例添加路由前缀

在src的api包下,将api.ts的代码中baseURL的值修改为后端服务访问的地址前缀
修改后的内容如下
api.ts

import axios from 'axios'

// 数据请求自定义配置(实例)
// 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'
});

export default api;

14.3 配置全局状态存储store

为了记录登录状态并且方便全局调用,使用store进行存储
在src包下的store包中,修改index.ts代码,修改后如下
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: {
   
        // increment(state) {
   
        //     state.count++
        // }
        // 存储用户信息
        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;

14.4 工具类中添加页面加载优化的代码

src包下的utils包中的utils.ts代码修改如下
utils/utils.ts

import {
    ElLoading, ElMessage } from "element-plus";


const utils = {
   
    // 加载动画
    loading: null,
    // loadingInstance:ref(),
    // loading: String,
    // 展示加载动画
    showLoadding(msg:string){
   
        if(utils.loading){
   
            return;
        }
        utils.loading = ElLoading.service({
   
        // const loadingInstance = ElLoading.service({
   
        // loadingInstance.value = ElLoading.service({
   
            // lock: true,
            body: true,
            fullscreen: true,
            text: msg?msg:'Loading',
            background: 'rgba(0,0,0,0.7)',
        });
        // return loadingInstance;

    },
    // 隐藏加载动画
    hideLoadding(){
   
        // showLoadding()
        // loadingInstance.value.close();
        utils.loading && utils.loading.close();
        utils.loading = null;
    },
    // 消息提示
    showError(msg:string){
   
        return ElMessage({
   
            message: msg,
            grouping: true,
            type: 'error'
        })
    } ,
    showSuccess(msg:string){
   
        return ElMessage({
   
            message: msg,
            grouping: true,
            type: 'success'
        })
    } ,
    showWarning(msg:string){
   
        return ElMessage({
   
            message: msg,
            grouping: true,
            type: 'warning'
        })
    } ,
    showDefault(msg:string){
   
        return ElMessage({
   
            message: msg,
            grouping: true,
            type: 'info'
        })
    } ,
    closeMessage(){
   
        ElMessage.closeAll()
    } ,

    // 数据操作相关的方法
    // 本地存储数据
    saveData(key:any, data:any){
   
        localStorage.setItem(key, JSON.stringify(data));
    },
    // 移除数据
    removeData(key:any){
   
        localStorage.removeItem(key);
    },
    // 获取数据
    getData(key:any){
   
        const data = localStorage.getItem(key);
        if(data){
   
            return JSON.parse(data);
        }
        return null;
    }
}

export default utils;

14.5 手机验证码登录组件代码更新

应用store和路由进行登录接口访问,获取验证码后进行判断,正确则登陆成功跳转到主页,这里主页是一个假的地址,所以会跳转到空白,后续在添加主页组件
(关于图片验证码暂时未做实现,所以随便输入,后续进行完善)
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) {
   
      // 保存输入的用户名
      utils.saveData('username', loginForm.username)
      // 保存被勾选的操作
      utils.saveData('saveUsername', loginForm.saveUsername)
    } else {
   
      // 如果记住用户名的勾选取消,则移除这两个存储的内容
      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.data) {
   
          utils.showError('登录失败-请求数据返回有误');
          return;
        }
        // console.log(res.data.data, loginForm.smscode);
        if(res.data.data == loginForm.smscode){
   
          utils.showSuccess('登陆成功')
          // 存储用户token信息并转到主页
          let userInfo = res.data.data
          let token = res.data.token
          // 状态数据存储
          store.commit('setUserInfo', userInfo);
          store.commit('setToken', token);
          // 登录成功后将页面转到主页
          router.push('/index')

        }else if(res.data.data != 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')
  // 如果记住用户名被勾选,则获取用户名显示
  if (loginForm.saveUsername) {
   
    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>

14.6 页面效果展示

访问浏览器页面

点击获取验证码

到redis中查看验证码

输入验证码,验证码60秒后过期
(图片验证码随便输入)

登录成功,完成跳转,此时未实现主页组件,此为假地址,故为空白页

15. 账号密码登录的接口、状态存储和路由跳转的实现

根据手机验证码登录的实现,对账号密码登录的代码进行功能实现

15.1 后端接口

账号密码登录使用的接口如下

login/login

请求方式为get
请求参数为username和password

15.2 代码实现

如下
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 token = res.data.token;
                    store.commit('setUserInfo',userInfoLogin);
                    // 登录成功后跳转主页
                    router.push('/index');
                }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){
   
            loginForm.username = utils.getData('username');
        }

        // 获取记住密码的值
        loginForm.savePassword = utils.getData('savePassword');
        // 如果记住密码被勾选,则获取密码
        if(loginForm.saveUsername){
   
            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' />
                </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-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox>
                    </div>
                    <!-- 记住密码 -->
                    <div class="flexItem" >
                        <el-checkbox v-model="loginForm.savePassword">记住密码</el-checkbox>
                    </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>

15.3 页面效果展示

浏览器页面展示账号密码登录
填写数据,账号密码是提前定好的,图片验证码随便输入

登录,跳转到指定页面

16. 扫码登录的接口、状态存储和路由跳转的实现

这里应该需要有二维码获取的接口,同时存储token用于记录二维码
这里简单实现一下

16.1 接口地址

生成二维码的接口

login/qr/generateQrCodeAsFile

参数 无
请求方法 post

16.2 代码实现

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;
    
    // 后台更新获取二维码
    const loadQcode = () => {
   
        // 后续改为从服务器上获取动态图片

        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
        });

        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: {
   

            }
        }).then((res)=>{
   
            if(res.data.token){
   
                utils.showSuccess("登录成功");
                store.commit('setUserInfo',res.data.token);
                router.push('/index1');
            }

            // 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>

16.3 页面效果展示

现在自动校验通过,后续实现逻辑

成功后跳转到新的页面

17. 登录缓存验证的实现

在每次登录后,本地缓存存储token,在线存储临时token,在线的保存时间暂定60秒,使用的是redis存储,在前端代码中调用后端接口获取在线的redis中token与本地存储的token进行对比,一致则继续保持登录并刷新到主页,否则跳转到登录页面重新登陆

17.1 三种登录方式代码改写

在校验之前需要先在登录的时候将token存储到本地的LocalStorage缓存中,以下为改写后的三种登录方式的代码
PhoneForm.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) {
   
      // 保存输入的用户名
      utils.saveData('username', loginForm.username)
      // 保存被勾选的操作
      utils.saveData('saveUsername', loginForm.saveUsername)
    } else {
   
      // 如果记住用户名的勾选取消,则移除这两个存储的内容
      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')
  // 如果记住用户名被勾选,则获取用户名显示
  if (loginForm.saveUsername) {
   
    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>

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>

UserLogin.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){
   
            loginForm.username = utils.getData('username');
        }

        // 获取记住密码的值
        loginForm.savePassword = utils.getData('savePassword');
        // 如果记住密码被勾选,则获取密码
        if(loginForm.saveUsername){
   
            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' />
                </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-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox>
                    </div>
                    <!-- 记住密码 -->
                    <div class="flexItem" >
                        <el-checkbox v-model="loginForm.savePassword">记住密码</el-checkbox>
                    </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>

17.2 页面缓存校验

在App.vue中编写代码,进行token的校验
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');
  if(token && userInfo){
   

    console.log("token userInfo :",token," -- ", userInfo);
    // 登录成功,验证
    utils.showLoadding("正在加载")
    const username = utils.getData('username');
    
    if(!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{
   
          // 登录失败
          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>

17.3 主页代码

编写主页代码,暂时只有四个字显示,后续再做开发

<script setup lang="ts">

import utils from '@/utils/utils';
import {
    onMounted } from 'vue';



</script>

<template>
    后台主页
</template>

<style scoped>
</style>

17.4 路由代码修改

在路由router/index.ts中添加路由组件,将默认登录跳转到主页,此时会在App.vue中进行校验,校验不通过则会跳转到登录页面


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';

// 定义一些路由,每个路由都需要映射到一个组件,
const routes = [
  {
   
    path: '/',
    // component: UserLogin
    redirect: "/HomeIndex"
  },
  {
   
    path: '/UserLogin',
    component: UserLogin
  },
  {
   
    path: '/HomeIndex',
    component: HomeIndex
  }
]

// 创建路由实例并传递'routes'配置 你可以在这里输入更多的配置
const router = createRouter({
   
  history: createWebHistory(),
  // routes:routes可以简写成routes,不会报错
  // routes:[]
  routes
})

export default router

18. 项目的源码下载地址

以上操作实现了项目的登录功能,有些粗糙,但基本的功能操作也还可以,源码下载地址如下

前端项目下载:hslb-vue3-elementplus-admin.zip
后端项目下载:java hslb-general-management-system.zip

以上就是项目功能的第一部分和第二部分实现,主要是项目的搭建和登录功能,后续的功能请继续阅读下一篇


感谢阅读,祝君暴富!


原文链接: https://hanshan.blog.csdn.net//article/details/140978163

标签: #VUE 61 #Spring Boot 173 #Mybatis 37 #redis 48 #ElementPlus 4
相关文章

万字:支付“核心系统”详解 2024-11-02 15:33

专栏作者:隐墨星辰 \| 主编:陈天宇宙 这篇文章也尝试化繁为简,探寻支付系统的本质,讲清楚在线支付系统最核心的一些概念和设计理念。 虽然支付行业已经过了风头最劲的时光,但跨境支付仍然在蓬勃发展,每年依然有很多新人进入这个行业,这篇文章尝试为这些刚入行的新人提供一点帮助。 文章只介绍一些支付行业十几

资深支付架构师视角:实战从问题定义到代码落地的完整套路 2024-11-02 15:33

前言 今天从一个实际案例入手,介绍站在架构师的角度,如何识别并定义问题,提炼需求,技术方案选型,再到详细设计,最后利用AI的能力协助写出核心的代码,验证与调优。 解决问题存在一定的模式,也可以称之为框架,总结出自己的思考和解题框架,以后再碰到同类型的问题就可以如庖丁解牛一样容易。 很多年前,我写代码

Spring 实现 3 种异步接口 2024-10-18 09:07

大家好,我是苏三~ 如何处理比较耗时的接口? 这题我熟,直接上异步接口,使用 Callable、WebAsyncTask 和 DeferredResult、CompletableFuture等均可实现。 但这些方法有局限性,处理结果仅返回单个值。在某些场景下,如果需要接口异步处理的同时,还持续不断地

重学SpringBoot3-集成Redis(五)之布隆过滤器 2024-10-08 11:24

更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 重学SpringBoot3-集成Redis(五)之布隆过滤器 1. 什么是布隆过滤器? * 基本概念 适用场景 2. 使用 Redis 实现布隆过滤器 * 项目依赖 Redis 配置

设计模式第16讲——迭代器模式(Iterator) 2024-10-08 11:24

一、什么是迭代器模式 迭代器模式是一种行为型设计模式,它提供了一种统一的方式来访问集合对象中的元素,而不是暴露集合内部的表示方式。简单地说,就是将遍历集合的责任封装到一个单独的对象中,我们可以按照特定的方式访问集合中的元素。 二、角色组成 抽象迭代器(Iterator):定义了遍历聚合对象所需的方法

vue2路由和vue3路由区别及原理 2024-10-08 11:24

一、Vue2 与 Vue3 路由的区别 1. 创建路由实例方式的不同 Vue 2 中,通过 Vue.use() 注册路由插件,并通过 new VueRouter() 来创建路由实例。 import Vue from 'vue';import VueRouter from 'vue-router';i

目录

IT 外包服务商

  • 意见投递
  • zyf6619

软件开发应用

主菜单

  • 首页
  • 软件开发
  • 计算机基础
  • Hello Halo
  • 新手必读
  • 关于本知识库
Copyright © 2024 your company All Rights Reserved. Powered by Halo.