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

news2025/1/9 15:41:09

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


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


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


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


文章目录

  • `第二部分:登录相关业务功能的实现`
    • 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

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


感谢阅读,祝君暴富!


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2042531.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Element UI左侧导航栏写法(递归组件实现)

1、左侧导航栏组件使用的是Element Ui的导航栏组件&#xff0c;思路&#xff1a;首先判断导航栏数据是否存在children&#xff0c;以此来实现一级菜单与多级菜单的渲染&#xff0c;然后使用递归组件实现多级菜单的子菜单渲染&#xff0c;注意使用递归组件需将菜单栏数据在父组件…

SQL— DDL语句学习【后端 10】

SQL— DDL语句学习 在数据管理的广阔领域中&#xff0c;SQL&#xff08;Structured Query Language&#xff09;作为操作关系型数据库的编程语言&#xff0c;扮演着举足轻重的角色。它不仅定义了操作所有关系型数据库的统一标准&#xff0c;还为我们提供了强大的工具来管理、查…

TypeScript教程(四)type、interface、类型断言、非空断言

上一章我们讲解了Typescript的基础类型&#xff0c;接下来我们就来看看Typescript的、type、interface、类型断言、非空断言 type关键字(类型别名) type 关键字可以帮助我们为现有的类型创建别名&#xff0c;提高代码的可读性和可维护性 在前面&#xff0c;我们通过在类型注解…

一切计算皆AI,开放计算再破局

“OpenAI最差的决定就是没有开放它的大模型。”著名科技预言家凯文凯利在2024年最新演讲中强调开源对于AI发展的重要性。 的确&#xff0c;开源大模型近年来迸发出巨大的产业活力&#xff0c;推动了AI的协作与创新&#xff1a;2023年全球新发布的基础模型中有三分之二为开源模…

随笔二、OV5695摄像头测试

摘要&#xff1a;泰山派开发板SDK使用了GStreamer媒体框架作为音视频的编解码器&#xff0c;支持硬件编解码。开发板提供MIPI_CSI摄像头接口连接摄像头。 1. 摄像头信息 开发板MIPI-CPI接口是30pin、4lane&#xff1b;测试用的OV5696是2lane&#xff0c;像素500万&#xff0c;…

GPU云服务器有什么优点?

云服务器是一种通过互联网提供计算资源和服务的灵活、高性能的解决方案。而随着人工智能及数据密集型应用的迅猛发展&#xff0c;GPU&#xff08;图形处理器&#xff09;云服务器逐渐成为许多企业和个人的优选。下面将介绍GPU云服务器的几个优势。 1、GPU云服务器具备强大的计算…

线程的退出

线程退出方式总结&#xff1a; 1.pthread_exit; void pthread_exit(void *retval); 传的是退出状态值对应的地址 2.执行函数中return 3.pthread_cancel // int pthread_cancel(pthread_t thread); 4.在任何一个线程中调用了exit #include <stdio.h> #include <…

软件测试之Linux系统性能调优技巧

​ 大家好&#xff0c;我是程序员小羊&#xff01; 前言 Linux系统性能调优是一项复杂且系统性的任务&#xff0c;它涉及操作系统内核、硬件资源、应用程序、文件系统、网络设置等多个方面。通过合理的调优策略&#xff0c;可以有效提高Linux系统的性能&#xff0c;满足生产环境…

qt-10基本对话框(文件--颜色--字体)

基本对话框--输入对话框 DialogDialog.hDialog.cpp exdialog.hexdialog.hexdialog.cpp 运行图文件对话框颜色对话框字体对话框输入对话框-字符串输入对话框-选择 Item输入对话框-数字 Dialog Dialog.h #ifndef DIALOG_H #define DIALOG_H#include "exdialog.h"#inc…

数字文创产业园怎么凭借智慧园区建设出圈

在数字化浪潮席卷全球的当下&#xff0c;数字文创产业正以前所未有的速度崛起。而数字文创产业园若想在众多竞争对手中脱颖而出&#xff0c;凭借智慧园区建设出圈无疑是一条明智之路。 首先得明白什么是智慧园区建设&#xff1f; 智慧园区建设指的是通过现代信息技术和智能化手…

超时控制+第三方调用控制

文章目录 第三方调用控制背景介绍一致性抽象同步转异步自动替换第三方压测支持 超时控制目标确定超时时间根据用户体验根据响应时间压力测试根据代码计算 监控超时时间 第三方调用控制 背景介绍 我的系统对可用性要求非常高&#xff0c;为此我综合使用了熔断、限流、降级、超…

mac 链接数据库报错 - Public Key Retrieval is not allowed

使用 DBeaver 链接 MySQL 时&#xff0c;连接失败&#xff0c;提示 Public Key Retrieval is not allowed 发现是需要修改一个链接属性字段&#xff0c;方式如下&#xff1a; 1、选中服务器&#xff0c;点击 编辑连接 2、连接设置 – 驱动属性 – allowPublicKeyRetrieval 改为…

暨南大学2024年硕士研究生报考录取情况统计表

今天暨南大学2024年硕士研究生报考录取情况统计表出来了&#xff0c;在之前的笔记中&#xff0c;我对比过2022年与2023年的硕士研究生报考录取情况统计表中暨南大学智科院人工智能专硕报考情况 2024硕士研究生报考录取情况统计表中智科院的电子信息对应的是人工智能专硕(01方向…

科研项目验收管理系统

TOC springboot245科研项目验收管理系统 绪论 1.1 研究背景 现在大家正处于互联网加的时代&#xff0c;这个时代它就是一个信息内容无比丰富&#xff0c;信息处理与管理变得越加高效的网络化的时代&#xff0c;这个时代让大家的生活不仅变得更加地便利化&#xff0c;也让时…

面向对象编程-继承

目录 一、为什么需要继承 二、继承的基本介绍 1、继承的基本介绍 2、继承示意图 3、继承的基本语法 三、快速入门 四、继承的使用细节 五、练习 一、为什么需要继承 1、一个小问题&#xff0c;还是看程序&#xff0c;提出代码复用问题 1&#xff09;我们编写两个类&a…

【AI 绘画】Q版人物定制生成

AI 绘画-PulID手办定制 1. 效果展示 本次测试主要结果展示如下: 牛仔风 古风 2. 基本原理 PuLID是一种类似于 ip-adapter 的恢复面部特征的方法。它同时使用 insightface 嵌入和 CLIP 嵌入,类似于 ip-adapter faceid plus 模型所做的。但是,在将图像传递给 CLIP 之前,还…

8.看门狗(WDG)

理论 防止系统跑飞 喂狗&#xff1a;让值为0 分类&#xff1a;独立看门狗(IWDG,0到100喂狗)、窗口看门狗(60到100喂狗)&#xff0c;值(60)&#xff0c;顶值(100) 代码编写 按键喂狗 独立看门狗配置 超过10s&#xff0c;复位 注意时钟&#xff1a; 按键、LED灯配置参考&#x…

sql注入实战——thinkPHP

sql注入实战——thinkPHP sql注入实战——thinkPHPthinkPHP前期环境搭建创建数据库开始寻找漏洞点输入SQL注入语句漏洞分析 实验错误 sql注入实战——thinkPHP thinkPHP前期环境搭建 下载thinkPHP文件 解压&#xff0c;将framework关键文件放到think-5.0.15中&#xff0c;改…

Containerd详解

一.Containerd概述 1.什么是Containerd Containerd (container Daemon)是一个开源的容器运行时&#xff0c;它提供了一种标准化的方式来管理容器的生命周期。该项目最初是由Docker开发团队创建的&#xff0c;并在后来成为一个独立的项目&#xff0c;被纳入了Cloud Native Comp…

播放视频时黑屏无报错或报错“播放失败,请检查设备及客户端网络”

播放视频时黑屏无报错或报错“播放失败&#xff0c;请检查设备及客户端网络”或该如何解决&#xff1f; 请先使用排障工具排查&#xff0c;获得具体错误码&#xff0c;排障工具操作文档详见&#xff1a; 播放失败自助排障及常见错误码解决方案 - 播放问题 - 萤石社区 - Powered…