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


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


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


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


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


文章目录

  • `第二部分:登录相关业务功能的实现`
    • 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'// 当前选中的tabconst 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)})// 当前选中的tabconst 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: {},// 当前登录的标识tokentoken: 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);},// 存储tokensetToken: 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 = 60timer = 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.datalet 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-formref="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-inputprefix-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-inputprefix-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-inputprefix-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.dataqrToken = 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 = () => {// TODOapi({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 = 60timer = 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.datalet userInfo = res.datalet 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-formref="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-inputprefix-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-inputprefix-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-inputprefix-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.dataqrToken = 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 = () => {// TODOapi({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: UserLoginredirect: "/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.rhkb.cn/news/401349.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

怎么等比例调整图片尺寸大小?调整图片尺寸的8个方法

在数字时代&#xff0c;图片已成为我们日常生活与工作中不可或缺的一部分。从社交媒体分享到专业设计项目&#xff0c;图片的质量和外观直接影响着信息的传达与接收。因此&#xff0c;在处理图片时&#xff0c;保持其原始的纵横比&#xff0c;即等比例调整图片尺寸&#xff0c;…

梅丽尔·斯特里普表演艺术家中心对外开放并恢复线下活动 体现了她的“卓越”

梅丽尔斯特里普表演艺术家中心对外开放并恢复线下活动 体现了她的“卓越” 2024-08-14 20:38 发布于&#xff1a;河北省 该中心将为美国演员工会和美国电视广播艺人协会的艺术家提供资源和机会&#xff0c;而且全部免费 同时命名的还有汤姆汉克斯和丽塔威尔逊放映室、妮可…

PHP 无参数RCE总结

在这篇文章中&#xff0c;我总结了在参与CTF比赛过程中积累的关于PHP无参数远程代码执行&#xff08;RCE&#xff09;的经验。由于一直以来时间有限&#xff0c;今天终于有机会整理这些知识点。 可能用到的函数&#xff08;PHP的内置函数&#xff09; localeconv() 函数返回一…

安美数字酒店宽带运营系统 weather.php 任意文件读取漏洞复现

0x01 产品简介 HiBOS酒店宽带运营系统是由安美世纪(北京)科技有限公司开发的一套专为酒店设计的宽带管理系统。该系统旨在提升酒店宽带服务的运营效率和安全性&#xff0c;为酒店客人提供稳定、高速、便捷的上网体验。 0x02 漏洞概述 安美数字酒店宽带运营系统 weather.php …

Ansible自动化运维中剧本角色(roles)来完成apache服务操作

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f427;Linux基础知识(初学)&#xff1a;点击&#xff01; &#x1f427;Linux高级管理防护和群集专栏&#xff1a;点击&#xff01; &#x1f510;Linux中firewalld防火墙&#xff1a;点击&#xff01; Ansible…

Kafka服务端日志详解

文章目录 服务端日志Topic消息存储方式主体介绍log文件追加记录消息index和timeindex索引文件 日志文件清理Kafka的文件高效读写机制Kafka的文件结构顺序写磁盘零拷贝 合理配置刷盘频率客户端消费进度管理 服务端日志 Kafka的日志信息是通过conf/server.properties文件中的log…

互联网红利消退,AI 大模型接棒新红利

在科技发展的浪潮中&#xff0c;互联网曾经是推动经济增长和社会变革的强大引擎&#xff0c;为无数企业和个人带来了巨大的红利。然而&#xff0c;随着时间的推移&#xff0c;互联网红利似乎正在逐渐消退&#xff0c;而与此同时&#xff0c;AI 大模型正以其强大的创新能力和广泛…

搜索旋转排序数组

搜索旋转排序数组 没思路。 看了下全网的思路,一个个来o&#xff0c;多做点题就知道了二分不仅仅只能用在有序的数组中。 这道题很关键的一个值就是nums[0]。 法一&#xff1a;先用二分找到旋转点&#xff0c;旋转点两边都是的&#xff0c;判断要搜索的值在哪边&#xff0c;…

玩转haproxy --花十分钟看看,全是干货

Haproxy是一款开源集群软件&#xff08;在上一篇文章中提到过集群的相关知识&#xff0c;往期点击http://t.csdnimg.cn/qWtQG&#xff09;是法国开发者 威利塔罗(Willy Tarreau) 在2000年使用C语言开发的&#xff0c;是一款具备高并发(万级以上)、高性能的TCP和HTTP负载均衡器 …

Linux Day1 系统编程和文件操作

系统编程内容 文件I/O (输入/输出): 1&#xff09;使用标准库函数如fopen, fclose, fread, fwrite, fgetc, fputc, fgets, fprintf, fscanf等进行文件操作。 2&#xff09;使用open, close, read, write等系统调用来实现底层文件操作。 进程管理: 1&#xff09;使用fork, e…

安卓用户专属福利:OfficeSuite中文高级版,让你的工作更轻松!

OfficeSuite – 世界顶级移动办公软件&#xff01;Google Play商店下载最多的办公软件应用&#xff0c;迄今为止&#xff0c;智能手机平台上&#xff0c;功能最强大、兼容性最好的移动Office办公套件。创建&#xff0c;查看和编辑Word&#xff0c;Excel和PowerPoint文档&#x…

ThinkPHP5漏洞分析之代码执行

漏洞概要 本次漏洞存在于 ThinkPHP 的缓存类中。该类会将缓存数据通过序列化的方式&#xff0c;直接存储在 .php 文件中&#xff0c;攻击者通过精心构造的 payload &#xff0c;即可将 webshell 写入缓存文件。缓存文件的名字和目录均可预测出来&#xff0c;一旦缓存目录可访问…

python自动化笔记:os模块和异常处理

目录 一、os模块1.1、常用方法1.2、其他方法&#xff08;了解即可&#xff09; 二、异常处理 try except2.1、语法格式1&#xff1a;2.2、语法格式2&#xff1a;指定异常类别&#xff0c;捕获异常2.3、语法格式3&#xff1a;try-finally 语句无论是否发生异常都将执行最后的代码…

SQL每日一练-0814

今日SQL题难度&#xff1a;&#x1f31f;☆☆☆☆☆☆☆☆☆ 1、题目要求 找出每个部门中薪资最高的员工显示部门ID、部门名称、员工ID、员工姓名以及对应的薪资 2、表和虚拟数据 现有两个表&#xff1a;Employees 和 Departments&#xff0c;记录了员工和部门信息。…

【机器学习】ImageNet的基本概念以及如何使用ImageNet数据集

引言 ImageNet是一个大型的图像数据库&#xff0c;它根据WordNet的层级结构&#xff08;目前仅限于名词&#xff09;组织&#xff0c;其中每个层级节点都由成百上千张图像来描绘。这个项目对计算机视觉和深度学习研究的发展起到了重要作用 文章目录 引言一、ImageNet的基本概念…

一次sql请求,返回分页数据和总条数

日常搬砖&#xff0c;总少不了需要获取分页数据和总行数。 一直以来的实践是编码两次sql请求&#xff0c;分别拉分页数据和totalCount。 最近我在思考&#xff1a; 常规实践为什么不是 在一次sql请求中中执行多次sql查询或多次更新&#xff0c;显而易见的优势&#xff1a; ① 能…

Halcon 算子汇总

gen_tuple_const(1000,1.5) 生成一个长度为1000&#xff0c;里面每一个数组元素都为1.5的数组 gen_tuple_const(100,chr(ord(a) 1)) 生成一个长度为100&#xff0c;里面每一个数组元素都为b的数组 ord函数是库函数&#xff0c;用于获取字符的ASCII值 chr(ord(a) 1) 结…

8.13-LVS的nat模式+DR模式

LVS 一、nat模式 1.角色 主机名ip地址功能web01192.168.2.101rsweb02192.168.2.102realserveenat内网:192.168.2.103 外网:192.168.2.120directorserver,ntpdns192.168.2.105dns 2..web服务器 [rootweb01 ~]# yum -y install nginx ​ [rootweb01 ~]# echo "web01&qu…

【14】二叉树的Morris等

目录 一.树形dp套路 二.派对的最大快乐值 三.Morris遍历 morris先序遍历 morris中序遍历 moris后序遍历 判断是不是搜索二叉树 四.额外习题 一.树形dp套路 情况1&#xff1a;最大距离&#xff0c;节点X不参与。 > 左树最大距离 or 右树最大距离 情况2&#xff1a;最…

html编写贪吃蛇页面小游戏(可以玩)

<!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>贪吃蛇小游戏</title><style>body {…