天行健,君子以自强不息;地势坤,君子以厚德载物。
每个人都有惰性,但不断学习是好好生活的根本,共勉!
文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。
长安一片月,万户捣衣声。
——《子夜吴哥·秋歌》
文章目录
- `第二部分:登录相关业务功能的实现`
- 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
以上就是项目功能的第一部分和第二部分实现,主要是项目的搭建和登录功能,后续的功能请继续阅读下一篇
感谢阅读,祝君暴富!