天行健,君子以自强不息;地势坤,君子以厚德载物。
每个人都有惰性,但不断学习是好好生活的根本,共勉!
文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。
云想衣裳花想容,春风拂槛露华浓。
——《清平调》
文章目录
- 通用后台管理系统
- `第一部分:项目的构建以及基础组件的安装配置`
- 1. 后台管理系统模块划分
- 2. 项目的创建
- 2.1 创建Vue3项目
- `Vue Official插件安装`
- 2.2 初始化项目结构
- 2.2.1 删除不需要的文件
- 2.2.2 删除不需要的代码
- 2.2.3 修改文件内容
- 2.2.4 创建需要的包和文件
- 2.3 运行项目
- 2.4 访问项目
- 3. 安装和使用element-plus
- 3.1 安装element-plus
- 3.2 element-plus组件使用实例
- 3.2.1 引入并注册element相关组件
- 3.2.2 element-plus图标的使用
- 4. Vue Router路由的配置使用
- 4.1 创建登录页面组件(login.vue)
- 4.2 路由配置(index.ts)
- 4.3 路由应用(App.vue)
- 4.4 查看结果
- 5. 数据请求配置(axios)
- 5.1 安装axios
- 5.2 配置axios实例
- 5.3 引用axios实例
- 6. 安装和配置mock
- 6.1 安装mock
- 6.2 配置mock
- 7. 安装和配置Vuex
- 7.1 安装Vuex
- 7.2 配置Vuex
- 8. 工具方法实现
- 8.1 加载方法的实现
- 8.2 消息提示的实现
- 8.3 完整代码
- 9. 界面主题配置
- 9.1 引入组件
- 9.2 应用组件
- 9.3 暗黑功能演示
- `第二部分:登录相关业务功能的实现`
- 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. 项目的源码下载地址
Vue入门学习专栏
通用后台管理系统
第一部分:项目的构建以及基础组件的安装配置
1. 后台管理系统模块划分
通用后台管理系统:登录模块、系统主页、用户管理、其他功能、找回密码、权限管理、系统配置
后台管理系统的模块和功能表:
模块 | 功能列表 |
---|---|
登录模块 | 1. 账号密码登录 2. 短信验证码登录 3. 二维码扫码登录 |
系统主页 | 1.动态菜单 2.tab页 3.主题切换 4.个人信息 5.修改密码 |
用户管理 | 1.用户列表 2.重置密码 3.启用停用 |
其他功能 | 1.消息管理 2.验证码管理 3.已读消息 4.数据可视化 |
找回密码 | 1.邮箱找回密码 2.短信验证码找回密码 |
权限管理 | 1.角色管理 2.菜单管理 |
系统配置 | 1.数据字典 2.操作日志 3.系统配置 |
2. 项目的创建
2.1 创建Vue3项目
可参考:Vue3入门之创建vue3的单页应用(vite+vue)
找到一个文件夹位置,项目文件夹会创建在该文件夹下,在此位置目录中输入cmd回车打开窗口
使用命令创建
npm create vue@latest
创建完成后使用vscode打开项目,对项目包结构进行整理
Vue Official插件安装
在Vscode中安装插件Vue - Official,Vue官方发布的那个,如下图
2.2 初始化项目结构
2.2.1 删除不需要的文件
删除src下component包中的文件
删除src下assets包中的文件
2.2.2 删除不需要的代码
删除src包下的App.vue文件中的内容,保留以下标签即可,如下
<script setup>
</script>
<template>
</template>
<style scoped>
</style>
删除main.ts中的main.css引入,保留如下代码即可
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')
删除index.ts中的HomeView引入以及不需要的内容,保留以下内容即可
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: []
})
export default router
2.2.3 修改文件内容
修改index.html
将title默认的Vite App修改为寒山李白通用后台管理系统,内容如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>寒山李白通用后台管理系统</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
2.2.4 创建需要的包和文件
在src包下创建styles包,mock包、store包,utils包,api包,
在styles包下创建一个theme主题包和一个default.css样式文件,然后再在theme包下创建一个default-theme.css文件
编写default.css文件和default-theme.css文件
default.css
@charset 'utf-8';
html,
body {
margin: 0;
padding: 0;
}
default-theme.css
@charset 'utf-8';
.default-theme {}
2.3 运行项目
下载依赖
npm install
启动项目
npm run dev
2.4 访问项目
根据输出提示,浏览器输入地址进行访问
http://localhost:5173/
浏览器界面如下
一个空白页
3. 安装和使用element-plus
3.1 安装element-plus
在terminal终端中执行以下命令安装element-plus
npm install element-plus --save
--save
表示将依赖添加到package.json文件中
3.2 element-plus组件使用实例
3.2.1 引入并注册element相关组件
在main.ts中引入需要的element-plus组件并全局注册
此时main.ts完整代码如下
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
// 引入element-plus
import ElementPlus from 'element-plus'
// 引入element-plus的css组件
import 'element-plus/dist/index.css'
// 引入element-plus的国际化
import zhCn from 'element-plus/es/locale/lang/zh-cn'
// 引入element-plus字体图标相关组件
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
const app = createApp(App)
// 全局注册路由
app.use(router)
// 全局注册element-plus、国际化
app.use(ElementPlus, {
locale: zhCn
})
// 全局注册element-plus图标相关组件
for (const [key, component] of Object.entries(ElementPlusIconsVue)){
app.component(key, component)
}
app.mount('#app')
3.2.2 element-plus图标的使用
element-plus图标组件官网地址:https://element-plus.org/zh-CN/component/icon.html
在element-plus组件界面往下滑找到图标,复制图标,鼠标左键单击即可复制
在App.vue文件的template标签中粘贴图标的代码
<script setup lang="ts">
</script>
<template>
<el-icon><House /></el-icon>
</template>
<style scoped>
</style>
浏览器刷新界面可以看到House图标
4. Vue Router路由的配置使用
Vue-Router路由官网地址:https://router.vuejs.org/zh/guide/
本篇创建项目时已经同时集成了路由Vue Router,无需再次安装
4.1 创建登录页面组件(login.vue)
在views包下创建login包,在login包下创建login.vue组件,组件内容如下
login.vue
<script setup lang="ts">
</script>
<template>
登录界面
</template>
<style scoped>
</style>
注意:组件中的script标签必须加上lang='ts'参数,不然会报错
4.2 路由配置(index.ts)
根据上面的登录组件,在index.ts中引入登录组件login.vue
配置后的index.ts完整代码如下
import { createRouter, createWebHistory } from 'vue-router'
// 自定义路由组件或从其他文件导入,这里选择从其他文件导入
import login from "../views/login/login.vue";
// 定义一些路由,每个路由都需要映射到一个组件,
const routes = [
{
path: '/',
component: login
}
]
// 创建路由实例并传递‘routes’配置 你可以在这里输入更多的配置
const router = createRouter({
history: createWebHistory(),
// routes:routes可以简写成routes,不会报错
// routes:[]
routes
})
export default router
4.3 路由应用(App.vue)
配置后,在App.vue中使用RouterView标签进行渲染页面
App.vue完整代码如下
<script setup lang="ts">
</script>
<template>
<RouterView></RouterView>
</template>
<style scoped>
</style>
4.4 查看结果
完成以上配置后,启动项目,访问,浏览器页面,如下,首页已经路由到了登录页面组件的内容
5. 数据请求配置(axios)
使用axios进行数据请求,可参考axios官网:https://www.axios-http.cn/
5.1 安装axios
在终端输入命令进行安装
npm install axios --save
5.2 配置axios实例
在代码中可直接使用axios进行请求,但有时会需要配置请求的参数,这个时候可以自定义axios实例
在src下的api包下创建文件api.ts,在文件中配置axios
import axios from 'axios'
// 数据请求自定义配置(实例)
const api = axios.create({
// baseURL: 'https://mo_sss.blog.csdn.net.cn',
baseURL: import.meta.url,
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;
5.3 引用axios实例
在login.vue中引用实例
<script setup lang="ts">
import api from '../../api/api'
// 这里的地址必须是一个有返回值的接口,这里暂时没有,先用百度地址
api.get('https://www.baidu.com').then(resp=>{
console.log(resp.data)
})
</script>
<template>
登录界面
</template>
<style scoped>
</style>
6. 安装和配置mock
mock用来模拟数据
官网地址:http://mockjs.com/
6.1 安装mock
使用命令安装mockjs库
npm install mockjs --save
6.2 配置mock
在src下的mock包中新建index.ts文件,文件内容如下
import Mock from 'mockjs'
在main.ts文件中添加引入mock
添加以下代码
// 引入mockjs
import './mock/index'
7. 安装和配置Vuex
vuex用来数据状态的管理
官网地址:https://vuex.vuejs.org/zh/
7.1 安装Vuex
参考官网安装命令如下
npm install vuex@next --save
7.2 配置Vuex
在src下的store包中创建index.ts文件,文件内容如下
// 引入
import { createStore } from "vuex"
// 创建一个新的store实例
const store = createStore({
state() {
return{
count: 0
}
},
mutations: {
increment(state) {
state.count++
}
}
})
export default store;
在main.ts文件中新增以下代码(引入和注册vuex)
// 引入Vuex
import store from './store/index'
// 全局注册vuex状态管理
app.use(store)
8. 工具方法实现
在src包下的utils包中新增不同方法的实现
添加的方法代码参考element-plus官网组件
在src包下的utils包中新建文件utils.ts
8.1 加载方法的实现
加载组件直达链接
在utils包中创建utils.ts
在文件中添加以下代码
import { ElLoading, ElMessage } from "element-plus";
const utils = {
// 加载动画
loading: null,
// loading: String,
// 展示加载动画
showLoadding(msg:string){
utils.loading = ElLoading.service({
lock: true,
text: msg?msg:'Loading',
// background: 'rgba(0,0,0,0.7)',
})
},
// 隐藏加载动画
hideLoadding(){
utils.loading && utils.loading.close()
},
}
export default utils;
8.2 消息提示的实现
参考组件中消息的提示代码,将一下代码填入变量utils
// 消息提示
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()
} ,
8.3 完整代码
完整的utils.ts文件代码如下
import { ElLoading, ElMessage } from "element-plus";
const utils = {
// 加载动画
loading: null,
// loading: String,
// 展示加载动画
showLoadding(msg:string){
utils.loading = ElLoading.service({
lock: true,
text: msg?msg:'Loading',
// background: 'rgba(0,0,0,0.7)',
})
},
// 隐藏加载动画
hideLoadding(){
utils.loading && utils.loading.close()
},
// 消息提示
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()
} ,
}
export default utils;
9. 界面主题配置
为界面配置暗黑主题的切换
参考暗黑组件地址:https://element-plus.org/zh-CN/guide/dark-mode.html
9.1 引入组件
在main.ts中引入暗黑主题的css变量
// 引入element-plus的暗黑模式主题的css变量
import 'element-plus/theme-chalk/dark/css-vars.css'
9.2 应用组件
在App.vue中实现暗黑主题的按钮
在App.vue中的script中添加以下引入暗黑主题组件
// 引入暗黑主题的动态切换
import { useDark, useToggle } from '@vueuse/core'
在template中添加以下代码实现按钮
<!-- 暗黑主题动态切换按钮实现 -->
<button @click="toggleDark()">
<i inline-block align-middle i="dark:carbon-moon carbon-sun"/>
<span class="ml-2">{{ isDark ? 'Dark' : 'Light' }}</span>
</button>
完整的App.vue代码如下
<script setup lang="ts">
// 引入暗黑主题的动态切换
import { useDark, useToggle } from '@vueuse/core'
const isDark = useDark()
// 切换主题函数
const toggleDark = useToggle(isDark)
</script>
<template>
<RouterView></RouterView>
<!-- 暗黑主题动态切换按钮实现 -->
<button @click="toggleDark()">
<i inline-block align-middle i="dark:carbon-moon carbon-sun"/>
<span class="ml-2">{{ isDark ? 'Dark' : 'Light' }}</span>
</button>
<!-- <el-icon><House /></el-icon> -->
<!-- <el-icon><Plus /></el-icon> -->
</template>
<style scoped>
</style>
9.3 暗黑功能演示
运行项目,访问浏览器页面
点击按钮进行界面主题切换
第二部分:登录相关业务功能的实现
10. 登录页布局实现
10.1 主组件
在主组件App.vue中将多于部分代码剔除,保留路由标签RouterView
App.vue此时完整代码如下
<script setup lang="ts">
</script>
<template>
<RouterView></RouterView>
</template>
<style scoped>
</style>
10.2 登录窗口和声明的实现
在login.vue中添加以下代码
10.2 1 template标签代码
在template标签中添加如下代码,共两部分,在整个div块中布局两个内容,登录窗口和页脚声明
<div class="login-page">
<div class="login-panel">
</div>
<div class="login-footer" >
版权声明:通用管理系统最终解释权归寒山李白所有
</div>
</div>
10.2.2 style标签代码
在style scoped标签中添加如下代码,对上面的div块进行样式渲染,页面背景色设为蓝色渐变,登录窗口设为白色背景,声明字体颜色为白色。
/* 主页样式 */
.login-page{
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
/* background: v-bind(bgColor); */
/* 页面背景色的渐变 */
background: linear-gradient(45deg, #5198d3, #0f9fe2, #61a2d6);
}
/* 登录模块样式 */
.login-page .login-panel{
/* text-align: center; */
width: 800px;
height: 450px;
background: #ffff;
padding: 10px;
border-radius: 5px;
/* 水平居中配置,使用margin的auto值 */
margin: 0 auto;
/* 垂直居中配置,使用cacl函数,使用100vh减去高度height的值除以2 */
margin-top: calc((100vh - 450px)/2);
/* 登录窗口边界阴影效果 */
box-shadow: 0 0 20px 20px #00000055;
}
/* 页脚声明样式配置 */
.login-page .login-footer{
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 60px;
text-align: center;
color: #fff;
font-size: 14px;
}
10.2.3 页面效果
此时页面是这样的,
10.3 登录页的logo和表单布局实现
在login.vue中添加以下代码
在class为login-panel的div块中添加一个class为login-inner的div块作为logo和登录表单的父级div块
然后再在login-inner的div块中添加三个div块,分别为logo图、分割线、表单
图片的话可以自己随便搞一个logo图,放到src下的assets包中
10.3.1 template标签代码
在template标签中添加以下代码(在class为login-panel的div块中添加)
<div class="login-inner">
<div class="logo-panel">
<img src="../../assets/logo-h.png">
<!-- <img src="../../assets/logo-v.png"> -->
</div>
<!-- 分割线,区分开logo图和表单 -->
<div class="login-inner-split" ></div>
<!-- 登录的表单部分 -->
<div class="login-form-panel">
</div>
</div>
10.3.2 style标签代码
在style标签中添加以下对新增div样式的配置
/* 登录logo和表单的父类标签布局 */
.login-page .login-panel .login-inner{
/* 自动伸缩均分展示 */
display: flex;
}
/* logo 图标样式配置 */
.login-page .login-panel .logo-panel{
/* 宽度占比40% */
width: 40%;
text-align: center;
}
/* logo图标样式设置 */
.login-page .login-panel .logo-panel img{
width: 300px;
margin-top: 125px;
}
/* 分割线样式 */
.login-page .login-panel .login-inner-split{
width: 3px;
background: #f8f8f8;
height: 450px;
}
10.3.3 页面效果
此时的页面如下图
10.4 登录页表单部分tab实现
在login.vue中添加以下代码
在class为login-form-panel的div块中添加一个class为tabs的div块,在这个div块中添加三个子div块,分别为免密登录、账号登录、扫码登录
10.4.1 script标签代码
在script标签中添加如下代码
import {ref} from 'vue'
// 当前选中的tab
const curtab = ref(1);
// tab切换监听事件
const changeTab = (tabIndex) => {
curtab.value = tabIndex;
}
10.4.2 template标签代码
在template标签中添加以下代码(在class为login-form-panel的div块中添加)
<!-- 三种不同方式登录的表单实现 -->
<div class="tabs">
<div class="tab-item" :class="{'tab-item-selected':curtab==1}" @click="changeTab(1)" >免密登录</div>
<div class="tab-item" :class="{'tab-item-selected':curtab==2}" @click="changeTab(2)" >账号登录</div>
<div class="tab-item" :class="{'tab-item-selected':curtab==3}" @click="changeTab(3)" >扫码登录</div>
</div>
10.4.3 style标签代码
在style标签中添加以下代码
/* 表单登录部分的布局设置 */
.login-page .login-panel .login-form-panel{
/* 此项下的布局均分 */
flex: 1;
}
/* tabs对应的块的样式设置 */
.login-page .login-panel .login-form-panel .tabs{
height: 45px;
line-height: 45px;
text-align: center;
display: flex;
}
/* 登录界面的登录方式表单样式配置 */
.login-page .login-panel .login-form-panel .tabs .tab-item{
/* 每个部分均分 */
flex: 1;
cursor: pointer;
}
/* 鼠标悬浮变红,以及选中后变红 */
.login-page .login-panel .login-form-panel .tabs .tab-item:hover,
.login-page .login-panel .login-form-panel .tabs .tab-item-selected{
color:red;
}
10.4.4 页面效果
此时页面效果如下
10.5 三种登录方式初始页面的实现
先创建三个登录方式的vue组件
再在App.vue中添加代码,在class为login-form-panel的div块中,添加class为tab-content的div块
10.5.1 登录方式vue组件
在src下的views包中的login包下,创建一个component包,在包中创建三个文件
PhoneCodeForm.vue
<script setup lang="ts">
</script>
<template>
手机验证码登录
</template>
<style scoped>
</style>
QcodeForm.vue
<script setup lang="ts">
</script>
<template>
扫码登录
</template>
<style scoped>
</style>
UsernameForm.vue
<script setup lang="ts">
</script>
<template>
用户名密码登录
</template>
<style scoped>
</style>
10.5.2 script标签代码
在script标签中添加如下代码,引入vue组件
// 引入登录界面的组件
import QcodeForm from './components/QcodeForm.vue'
import UsernameForm from './components/UsernameForm.vue'
import PhoneCodeForm from './components/PhoneCodeForm.vue'
10.5.3 template标签代码
在App.vue中的class为login-form-panel的div块中添加如下代码(与class为tabs的div同级)
<!-- 三种不同方式登录的对应的vue组件页面 -->
<div class="tab-content" >
<PhoneCodeForm v-if="curtab==1"></PhoneCodeForm>
<UsernameForm v-else-if="curtab==2"></UsernameForm>
<QcodeForm v-else></QcodeForm>
</div>
10.5.4 页面效果
此时页面如下,分别点击不同登录方式会展示不同方式对应的组件页面
10.6 页面样式参数以变量形式应用
在style标签中的样式配置,有很多参数值,这些值如果用的很多,可以将其在ts中声明变量,在样式中使用变量,这样对以后得修改会方便很多
整合后的代码如下(这里也是目前项目的完整代码内容)
login.vue
10.6.1 script标签代码
完整的ts代码如下
import api from '../../api/api'
import {ref} from 'vue'
// 引入登录界面的组件
import QcodeForm from './components/QcodeForm.vue'
import UsernameForm from './components/UsernameForm.vue'
import PhoneCodeForm from './components/PhoneCodeForm.vue'
// 调用的是接口而非网址
// api.get('127.0.0.1:8089/test/libai').then(resp=>{
api.get('https://www.baidu.com').then(resp=>{
console.log(resp.data)
})
// 当前选中的tab
const curtab = ref(1);
// tab切换监听事件
const changeTab = (tabIndex) => {
curtab.value = tabIndex;
}
// 样式变量
const loginPagePanelWidth = '800px';
const loginPagePanelHeight = '450px';
10.6.2 template标签代码
完整的template代码
登录界面
<div class="login-page">
<div class="login-panel">
<div class="login-inner">
<div class="logo-panel">
<img src="../../assets/logo-h.png">
<!-- <img src="../../assets/logo-v.png"> -->
</div>
<!-- 分割线,区分开logo图和表单 -->
<div class="login-inner-split" ></div>
<!-- 登录的表单部分 -->
<div class="login-form-panel">
<!-- 三种不同方式登录的表单实现 -->
<div class="tabs">
<div class="tab-item" :class="{'tab-item-selected':curtab==1}" @click="changeTab(1)" >免密登录</div>
<div class="tab-item" :class="{'tab-item-selected':curtab==2}" @click="changeTab(2)" >账号登录</div>
<div class="tab-item" :class="{'tab-item-selected':curtab==3}" @click="changeTab(3)" >扫码登录</div>
</div>
<!-- 三种不同方式登录的对应的vue组件页面 -->
<div class="tab-content" >
<PhoneCodeForm v-if="curtab==1"></PhoneCodeForm>
<UsernameForm v-else-if="curtab==2"></UsernameForm>
<QcodeForm v-else></QcodeForm>
</div>
</div>
</div>
</div>
<div class="login-footer" >
版权声明:通用管理系统最终解释权归寒山李白所有
</div>
</div>
10.6.3 style标签代码
完整的style代码如下
/* 主页背样式 */
.login-page{
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
/* background: v-bind(bgColor); */
/* 页面背景色的渐变 45度渐变 */
background: linear-gradient(45deg, #5198d3, #0f9fe2, #61a2d6);
/* 上下渐变色 */
/* background: linear-gradient(to bottom, #5198d3, #0f9fe2, #61a2d6); */
}
/* 登录模块样式 */
.login-page .login-panel{
/* text-align: center; */
/* width: 800px; */
/* height: 450px; */
width: v-bind(loginPagePanelWidth);
height: v-bind(loginPagePanelHeight);
background: #ffff;
padding: 10px;
border-radius: 5px;
/* 水平居中配置,使用margin的auto值 */
margin: 0 auto;
/* 垂直居中配置,使用cacl函数,使用100vh减去高度height的值除以2 */
margin-top: calc((100vh - 450px)/2);
/* margin-top: calc((100vh - v-bind(loginPagePanelHeight)) / 2); */
/* 登录窗口边界阴影效果 */
box-shadow: 0 0 20px 20px #00000055;
}
/* 登录logo和表单的父类标签布局 */
.login-page .login-panel .login-inner{
/* 自动伸缩均分展示 */
display: flex;
}
/* logo 图标样式配置 */
.login-page .login-panel .logo-panel{
/* 宽度占比40% */
width: 40%;
text-align: center;
}
/* logo图标样式设置 */
.login-page .login-panel .logo-panel img{
/* width: 300px;
margin-top: 125px; */
width: 80%;
margin-top: calc(v-bind(loginPagePanelHeight)*0.4);
}
/* 分割线样式 */
.login-page .login-panel .login-inner-split{
width: 3px;
/* height: 450px; */
height: v-bind(loginPagePanelHeight);
/* 分割线左右间隔 */
margin: 0 10px;
/* 分割线背景色,灰色 */
background: #f8f8f8;
}
/* 表单登录部分的布局设置 */
.login-page .login-panel .login-form-panel{
/* 此项下的布局均分 */
flex: 1;
}
/* tabs对应的块的样式设置 */
.login-page .login-panel .login-form-panel .tabs{
height: 45px;
line-height: 45px;
/* 增加tabs上边框的距离 */
margin-top: 20px;
text-align: center;
display: flex;
}
/* 登录界面的登录方式表单样式配置 */
.login-page .login-panel .login-form-panel .tabs .tab-item{
/* 每个部分均分 */
flex: 1;
cursor: pointer;
}
/* 鼠标悬浮变红,以及选中后变红 */
.login-page .login-panel .login-form-panel .tabs .tab-item:hover,
.login-page .login-panel .login-form-panel .tabs .tab-item-selected{
color:red;
}
/* 页脚声明样式配置 */
.login-page .login-footer{
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 60px;
text-align: center;
color: #fff;
font-size: 14px;
}
11. 手机验证码登录功能实现
基于以上代码实现手机验证码登录功能
11.1 三种方式登录的表单内容布局设置
在login.vue中的style样式中添加以下对表单内容配置样式的代码
/* 登录表单内容的样式配置 */
.login-page .login-panel .login-form-panel .tab-content{
/* 边框距离设置 */
padding-top: 20px;
padding-left: 45px;
padding-right: 45px;
}
11.2 手机验证码登录布局实现
11.2.1 script标签代码
登录表单实例和数据的创建
import { ref,reactive } from 'vue'
// 登录表单的实例
const loginFormRef = ref(null);
// 登录表单的数据
const loginForm = reactive({
// 用户名
username: '',
// 手机验证码
smscode: '',
// 图片验证
imgcode: '',
// 记住用户名,默认否
saveUsername: false
});
// 登录验证规则
const rules = ({
});
11.2.2 template标签代码
template中的代码如下
<!-- 手机验证码登录 -->
<div class="phoneCodeLoginBox">
<el-form
ref="loginFormRef"
style="max-width: 600px"
:model="loginForm"
:rules="rules"
label-width="0"
class="loginFrom"
:size="formSize" status-icon>
<!-- 用户名 -->
<el-form-item prop="username">
<!-- 图标设置,动态绑定username,提示信息,设置输入框大小 -->
<el-input prefix-icon="UserFilled" v-model="loginForm.username" placeholder="请输入用户名" size='large' />
</el-form-item>
<!-- 短信验证 -->
<el-form-item prop="smscode">
<el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' />
</el-form-item>
<!-- 图片验证 -->
<el-form-item prop="imgcode">
<el-input prefix-icon="Picture" v-model="loginForm.imgcode" placeholder="请输入图片验证码" size='large' />
</el-form-item>
<!-- 记住用户名 -->
<el-form-item prop="saveUsername">
<el-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox>
</el-form-item>
<!-- 登录按钮 -->
<el-form-item prop="saveUsername">
<el-button class="loginBtn" type="danger" size='large' @click="onSubmit">登录</el-button>
</el-form-item>
</el-form>
</div>
11.2.3 style标签代码
style标签的代码如下
/* 按钮宽度设为最大 */
.loginBtn{
width: 100%;
}
11.2.4 页面效果
此时页面展示效果如下
11.3 短信和图片验证码获取的布局实现
在输入验证码和输入图片验证码后面加上获取验证码的按钮
11.3.1 引入全局css样式
src下的styles包下有个default.css文件,内容如下
@charset 'utf-8';
html,
body {
margin: 0;
padding: 0;
}
.flex {
display: flex;
}
.flexItem {
flex: 1;
}
在src下的main.ts中引入该文件
// 引入公共样式
import './styles/default.css'
11.3.2 验证码按钮布局代码实现
在原来的基础上修改并添加内容以下是完整的PhoneCodeForm.vue组件代码
<script setup lang="ts">
import { ref,reactive } from 'vue'
// 登录表单的实例
const loginFormRef = ref(null);
// 登录表单的数据
const loginForm = reactive({
// 用户名
username: '',
// 手机验证码
smscode: '',
// 图片验证
imgcode: '',
// 记住用户名,默认否
saveUsername: false
});
// 登录验证规则
const rules = ({
});
// 图片验证码图片
const imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;
// const imgCodeSrc = '../../../assets/code.png';
</script>
<template>
<!-- 手机验证码登录 -->
<div class="phoneCodeLoginBox">
<el-form
ref="loginFormRef"
style="max-width: 600px"
:model="loginForm"
:rules="rules"
label-width="0"
class="loginFrom"
:size="formSize" status-icon>
<!-- 用户名 -->
<el-form-item prop="username">
<!-- 图标设置,动态绑定username,提示信息,设置输入框大小 -->
<el-input prefix-icon="UserFilled" v-model="loginForm.username" placeholder="请输入用户名" size='large' />
</el-form-item>
<!-- 短信验证 -->
<el-form-item prop="smscode">
<!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 -->
<div class="flex loginLine">
<div class="flexItem" >
<el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' />
</div>
<div class="codeBtn" >
<el-button type="primary" size="large" @click="getSmsCode" class="" >获取验证码</el-button>
</div>
</div>
</el-form-item>
<!-- 图片验证 -->
<el-form-item prop="imgcode">
<!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 -->
<div class="flex loginLine">
<div class="flexItem" >
<el-input prefix-icon="Picture" v-model="loginForm.imgcode" placeholder="请输入图片验证码" size='large' />
<!-- <el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' /> -->
</div>
<div class="codeBtn" >
<el-image :src="imgCodeSrc" ></el-image>
<!-- <el-button type="primary" size="large" @click="getSmsCode" class="" >获取验证码</el-button> -->
</div>
</div>
</el-form-item>
<!-- 记住用户名 -->
<el-form-item prop="saveUsername">
<el-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox>
</el-form-item>
<!-- 登录按钮 -->
<el-form-item prop="saveUsername">
<el-button class="loginBtn" type="danger" size='large' @click="onSubmit">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
<style scoped>
/* 按钮宽度设为最大 */
.loginBtn{
width: 100%;
/* 登录按钮圆角边框 */
border-radius: 20px;
}
/* 验证码按钮样式配置 */
.codeBtn{
width: 100px;
margin-left: 10px;
}
/* 按钮和图片宽度100px */
.codeBtn:deep(.el-button),
.codeBtn:deep(img){
width: 100px;
}
/* 验证码图片高度 */
.codeBtn:deep(img){
height: 40px;
}
/* 这一行宽度占满 */
.loginLine{
width: 100%
}
</style>
验证码图片自己截图保存到src下的assets包中即可
11.3.3 页面效果展示
此时页面如下
11.4 手机验证码登录的逻辑实现
表单验证的实现(参考element-plus表单组件中的表单验证代码)
输入框未输入时点击登录提示实现
刷新验证码点击事件的实现
11.4.1 表单验证逻辑实现
在PhoneCodeForm.vue中添加以下代码即可实现输入框的报错提示,必须输入后方可登录
在script标签中添加以下代码
// 登录验证规则
const rules = ({
username:[{
required: true,
message: '请输入用户名',
trigger: 'blur'
}],
smscode:[{
required: true,
message: '请输入短信验证码',
trigger: 'blur'
}],
imgcode:[{
required: true,
message: '请输入图片验证码',
trigger: 'blur'
}]
});
页面效果如下,点击输入框后不输入内容会提示
11.4.2 获取短信验证码按钮触发的事件实现
点击获取验证码,开始计时,倒计时60秒后重新获取,同时实现如果没有填写用户名,无法获取验证码
此时代码的修改如下
script中代码中引入以下内容
// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'
// 定时器
let timer = null;
// 获取短信验证码的间隔时间
let curTime = 0;
// 获取短信验证码按钮的文本显示内容
let smsCodeBtnText = ref('获取验证码');
// 获取短信验证码
const getSmsCode = () => {
// 当点击获取短信验证码时,如果其他信息没填则提示输入
if(!loginForm.username){
utils.showError('请输入用户名');
return;
}
// if(!loginForm.smscode){
// utils.showError('请输入短信验证码');
// return;
// }
// TODO 从后台获取短信验证码
curTime = 60;
timer = setInterval(() => {
curTime--;
smsCodeBtnText.value = curTime+'秒后重新获取';
if(curTime<=0){
smsCodeBtnText.value = '获取验证码';
clearInterval(timer);
}
},1000);
};
template标签中的代码修改短信验证部分,添加对应事件绑定
<!-- 短信验证 -->
<el-form-item prop="smscode">
<!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 -->
<div class="flex loginLine">
<div class="flexItem" >
<el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' />
</div>
<div class="codeBtn" >
<el-button type="primary" size="large" @click="getSmsCode" :disabled="curTime>0">{{ smsCodeBtnText }}</el-button>
</div>
</div>
</el-form-item>
此时页面效果如下,不输入用户名点击获取验证码,顶部提示输入信息
输入用户名后再点击获取验证码,开始进行读秒,读完后方可重新获取
11.4.3 记住用户名功能实现
当填入用户信息后,勾选记住用户名,刷新页面,用户名还存在
在script标签的代码中添加以下逻辑
// 登录提交事件
const onSubmit = () => {
// form表单中的值,校验,
loginFormRef.value.validate((valid:string, fileds:any)=>{
// 如果valid值为假,则遍历输出报错
if(!valid){
for(let key in fileds){
// 获取报错信息中的字段对应的key的索引为0的信息
utils.showError(fileds[key][0].message);
}
return;
}
// 登录表单的记住用户名如果被勾选
if(loginForm.saveUsername){
// 保存输入的用户名
utils.saveData('username', loginForm.username);
// 保存被勾选的操作
utils.saveData('saveUsername', loginForm.saveUsername);
}else{
// 如果记住用户名的勾选取消,则移除这两个存储的内容
utils.removeData('username');
utils.removeData('saveUsername');
}
});
};
// 挂载
onMounted(() => {
// 获取记住用户名的值
loginForm.saveUsername = utils.getData('saveUsername');
// 如果记住用户名被勾选,则获取用户名显示
if(loginForm.saveUsername){
loginForm.username = utils.getData('username');
}
})
在template标签中修改记住用户名和登录按钮对应的内容,在按钮中添加事件
<!-- 记住用户名 -->
<el-form-item prop="saveUsername">
<el-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox>
</el-form-item>
<!-- 登录按钮 -->
<el-form-item>
<el-button class="loginBtn" type="danger" size='large' @click="onSubmit">登录</el-button>
</el-form-item>
此时访问浏览器页面,输入数据,登录
刷新页面,用户名还在,记住用户名也还被勾选着
11.5 手机验证码登录相关功能优化
优化了定时器,在点击登录后给一个登录成功的提示(如不知代码添加位置,可参考11.6 完整代码
)
定时器相关代码
curTime = 60;
timer = setInterval(() => {
curTime--;
smsCodeBtnText.value = curTime+'秒后重新获取';
if(curTime<=0){
smsCodeBtnText.value = '获取验证码';
clearInterval(timer);
// 清除时,值为空,防止重复点击触发多次
timer = null;
}
},1000);
// 清空定时器
onMounted(() => {
timer && clearInterval(timer);
});
登录成功提示
// 登录成功信息提示
utils.showSuccess("登录成功");
11.6 完整代码
手机验证码登录的完整vue组件代码如下
PhoneCodeForm.vue
<script setup lang="ts">
import { ref,reactive, onMounted } from 'vue'
// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'
// 登录表单的实例
let loginFormRef = ref(null);
// 登录表单的数据
const loginForm = reactive({
// 用户名
username: '',
// 手机验证码
smscode: '',
// 图片验证
imgcode: '',
// 记住用户名,默认否
saveUsername: false
});
// 登录验证规则
const rules = ({
username:[{
required: true,
message: '请输入用户名',
trigger: 'blur'
}],
smscode:[{
required: true,
message: '请输入短信验证码',
trigger: 'blur'
}],
imgcode:[{
required: true,
message: '请输入图片验证码',
trigger: 'blur'
}]
});
const formSize = ({});
// 图片验证码路径
let imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;
// const imgCodeSrc = '../../../assets/code.png';
// 刷新图片验证码
const getImgCode = () => {
// 后续改为从服务器上获取动态图片
imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;
};
// 定时器
let timer:any = null;
// 获取短信验证码的间隔时间
let curTime = 0;
// 获取短信验证码按钮的文本显示内容
let smsCodeBtnText = ref('获取验证码');
// 获取短信验证码
const getSmsCode = () => {
// 当点击获取短信验证码时,如果其他信息没填则提示输入
if(!loginForm.username){
utils.showError('请输入用户名');
return;
}
// if(!loginForm.smscode){
// utils.showError('请输入短信验证码');
// return;
// }
// TODO 从后台获取短信验证码
curTime = 60;
timer = setInterval(() => {
curTime--;
smsCodeBtnText.value = curTime+'秒后重新获取';
if(curTime<=0){
smsCodeBtnText.value = '获取验证码';
clearInterval(timer);
// 清除时,值为空,防止重复点击触发多次
timer = null;
}
},1000);
};
// 登录提交事件
const onSubmit = () => {
// form表单中的值,校验,
loginFormRef.value.validate((valid:string, fileds:any)=>{
// 如果valid值为假,则遍历输出报错
if(!valid){
for(let key in fileds){
// 获取报错信息中的字段对应的key的索引为0的信息
utils.showError(fileds[key][0].message);
}
return;
}
// 登录表单的记住用户名如果被勾选
if(loginForm.saveUsername){
// 保存输入的用户名
utils.saveData('username', loginForm.username);
// 保存被勾选的操作
utils.saveData('saveUsername', loginForm.saveUsername);
}else{
// 如果记住用户名的勾选取消,则移除这两个存储的内容
utils.removeData('username');
utils.removeData('saveUsername');
}
// TODO 调用接口登录
// 登录成功信息提示
utils.showSuccess("登录成功");
});
};
// 挂载
onMounted(() => {
// 获取记住用户名的值
loginForm.saveUsername = utils.getData('saveUsername');
// 如果记住用户名被勾选,则获取用户名显示
if(loginForm.saveUsername){
loginForm.username = utils.getData('username');
}
});
// 清空定时器
onMounted(() => {
timer && clearInterval(timer);
});
</script>
<template>
<!-- 手机验证码登录 -->
<div class="phoneCodeLoginBox">
<el-form
ref="loginFormRef"
style="max-width: 600px"
:model="loginForm"
:rules="rules"
label-width="0"
class="loginFrom"
:size="formSize" status-icon>
<!-- 用户名 -->
<el-form-item prop="username">
<!-- 图标设置,动态绑定username,提示信息,设置输入框大小 -->
<el-input prefix-icon="UserFilled" v-model="loginForm.username" placeholder="请输入用户名" size='large' />
</el-form-item>
<!-- 短信验证 -->
<el-form-item prop="smscode">
<!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 -->
<div class="flex loginLine">
<div class="flexItem" >
<el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' />
</div>
<div class="codeBtn" >
<el-button type="primary" size="large" @click="getSmsCode" :disabled="curTime>0">{{ smsCodeBtnText }}</el-button>
</div>
</div>
</el-form-item>
<!-- 图片验证 -->
<el-form-item prop="imgcode">
<!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 -->
<div class="flex loginLine">
<div class="flexItem" >
<el-input prefix-icon="Picture" v-model="loginForm.imgcode" placeholder="请输入图片验证码" size='large' />
<!-- <el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' /> -->
</div>
<div class="codeBtn" >
<el-image :src="imgCodeSrc" size='large' @click="getImgCode" ></el-image>
<!-- <el-button type="primary" size="large" @click="getSmsCode" class="" >获取验证码</el-button> -->
</div>
</div>
</el-form-item>
<!-- 记住用户名 -->
<el-form-item prop="saveUsername">
<el-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox>
</el-form-item>
<!-- 登录按钮 -->
<el-form-item>
<el-button class="loginBtn" type="danger" size='large' @click="onSubmit">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
<style scoped>
/* 按钮宽度设为最大 */
.loginBtn{
width: 100%;
/* 登录按钮圆角边框 */
border-radius: 20px;
}
/* 验证码按钮样式配置 */
.codeBtn{
width: 100px;
margin-left: 10px;
}
/* 按钮和图片宽度100px */
.codeBtn:deep(.el-button),
.codeBtn:deep(img){
width: 100px;
/* height: 40px; */
}
/* 验证码图片高度 */
.codeBtn:deep(img){
height: 40px;
/* 鼠标移上去会变成手型 */
cursor: pointer;
}
/* 这一行宽度占满 */
.loginLine{
width: 100%
}
</style>
12. 用户密码登录功能的实现
参考手机验证码登录界面实现,复制代码进行修改
12.1 添加或修改的内容
密码输入的图标和是否可见密码的按钮实现
记住密码的实现
登录成功的提示
12.2 完整代码
完整的用户密码登录代码如下
UsernameForm.vue
<script setup lang="ts">
import { ref,reactive, onMounted } from 'vue'
// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'
// 登录表单的实例
let loginFormRef = ref(null);
// 登录表单的数据
const loginForm = reactive({
// 用户名
username: '',
// 密码
password: '',
// 图片验证
imgcode: '',
// 记住用户名,默认否
saveUsername: false,
// 记住用户名,默认否
savePassword: false
});
// 登录验证规则
const rules = ({
username:[{
required: true,
message: '请输入用户名',
trigger: 'blur'
}],
password:[{
required: true,
message: '请输入密码',
trigger: 'blur'
}],
imgcode:[{
required: true,
message: '请输入图片验证码',
trigger: 'blur'
}]
});
const formSize = ({});
// 图片验证码路径
let imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;
// const imgCodeSrc = '../../../assets/code.png';
// 刷新图片验证码
const getImgCode = () => {
// 后续改为从服务器上获取动态图片
imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;
};
// 登录提交事件
const onSubmit = () => {
// form表单中的值,校验,
loginFormRef.value.validate((valid:string, fileds:any)=>{
// 如果valid值为假,则遍历输出报错
if(!valid){
for(let key in fileds){
// 获取报错信息中的字段对应的key的索引为0的信息
utils.showError(fileds[key][0].message);
}
return;
}
// 登录表单的记住用户名如果被勾选
if(loginForm.saveUsername){
// 保存输入的用户名
utils.saveData('username', loginForm.username);
// 保存被勾选的操作
utils.saveData('saveUsername', loginForm.saveUsername);
}else{
// 如果记住用户名的勾选取消,则移除这两个存储的内容
utils.removeData('username');
utils.removeData('saveUsername');
}
// 登录表单的记住用户名如果被勾选
if(loginForm.savePassword){
// 保存输入的用户名
utils.saveData('password', loginForm.password);
// 保存被勾选的操作
utils.saveData('savePassword', loginForm.savePassword);
}else{
// 如果记住用户名的勾选取消,则移除这两个存储的内容
utils.removeData('password');
utils.removeData('savePassword');
}
// TODO 调用接口登录
// 登录成功提示
utils.showSuccess("登录成功");
});
};
// 挂载
onMounted(() => {
// 获取记住用户名的值
loginForm.saveUsername = utils.getData('saveUsername');
// 如果记住用户名被勾选,则获取用户名显示
if(loginForm.saveUsername){
loginForm.username = utils.getData('username');
}
// 获取记住密码的值
loginForm.savePassword = utils.getData('savePassword');
// 如果记住密码被勾选,则获取密码
if(loginForm.saveUsername){
loginForm.password = utils.getData('password');
}
});
</script>
<template>
<!-- 手机验证码登录 -->
<div class="usernameLoginBox">
<el-form
ref="loginFormRef"
style="max-width: 600px"
:model="loginForm"
:rules="rules"
label-width="0"
class="loginFrom"
:size="formSize" status-icon>
<!-- 用户名 -->
<el-form-item prop="username">
<!-- 图标设置,动态绑定username,提示信息,设置输入框大小 -->
<el-input prefix-icon="UserFilled" v-model="loginForm.username" placeholder="请输入用户名" size='large' />
</el-form-item>
<!-- 密码 -->
<el-form-item prop="password">
<!-- 密码 -->
<div class="flexItem" >
<!-- show-password 属性表示是否显示切换显示密码的图标,true为显示 -->
<el-input prefix-icon="Lock" show-password="off" type="password" v-model="loginForm.password" placeholder="请输入密码" size='large' />
</div>
</el-form-item>
<!-- 图片验证 -->
<el-form-item prop="imgcode">
<!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 -->
<div class="flex loginLine">
<div class="flexItem" >
<el-input prefix-icon="Picture" v-model="loginForm.imgcode" placeholder="请输入图片验证码" size='large' />
<!-- <el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' /> -->
</div>
<div class="codeBtn" >
<el-image :src="imgCodeSrc" size='large' @click="getImgCode" ></el-image>
<!-- <el-button type="primary" size="large" @click="getSmsCode" class="" >获取验证码</el-button> -->
</div>
</div>
</el-form-item>
<!-- <el-form-item prop="saveUsername"> -->
<el-form-item>
<!-- 记住账号密码的勾选 -->
<div class="flex loginLine" >
<!-- 记住用户名 -->
<div class="flexItem" >
<el-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox>
</div>
<!-- 记住密码 -->
<div class="flexItem" >
<el-checkbox v-model="loginForm.savePassword">记住密码</el-checkbox>
</div>
</div>
</el-form-item>
<el-form-item prop="savePassword">
</el-form-item>
<!-- 登录按钮 -->
<el-form-item>
<el-button class="loginBtn" type="danger" size='large' @click="onSubmit">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
<style scoped>
/* 按钮宽度设为最大 */
.loginBtn{
width: 100%;
/* 登录按钮圆角边框 */
border-radius: 20px;
}
/* 验证码按钮样式配置 */
.codeBtn{
width: 100px;
margin-left: 10px;
}
/* 按钮和图片宽度100px */
.codeBtn:deep(.el-button),
.codeBtn:deep(img){
width: 100px;
/* height: 40px; */
}
/* 验证码图片高度 */
.codeBtn:deep(img){
height: 40px;
/* 鼠标移上去会变成手型 */
cursor: pointer;
}
/* 这一行宽度占满 */
.loginLine{
width: 100%
}
</style>
12.3 页面效果展示
页面效果如下
13. 扫码登录功能的实现
13.1 扫码登录组件代码
QcodeForm.vue
<script setup lang="ts">
import { ref,reactive, onMounted, onUnmounted } from 'vue'
// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'
// 二维码
let qcodeSrc = new URL("../../../assets/hslb-qcode.png", import.meta.url).href;
// const imgCodeSrc = '../../../assets/code.png';
// 二维码对应的token, 用于判断当前二维码是否已经被扫码登录
const qcodeToken = ref('');
// 当前定时器事件
const curTime = ref(0);
let timer:any = null;
// 后台更新获取二维码
const loadQcode = () => {
// 后续改为从服务器上获取动态图片
qcodeSrc = new URL("../../../assets/hslb-qcode.png", import.meta.url).href;
// 初始化token的值
qcodeToken.value = "";
// 设定定时时间
// curTime.value = 60;
// 为了让二维码失效的效果及时,这里暂时设置10秒,后续改回60秒即可
curTime.value = 10;
// 定义定时器,倒计时
timer = setInterval(() => {
curTime.value--;
// 这里获取toekn,校验是否已经被登陆过
checkLogin();
if(curTime.value<=0){
// 事件为0则清空定时器
clearInterval(timer);
timer = null;
}
}, 1000);
};
// 登录提交事件
const onSubmit = () => {
};
// 挂载
onMounted(() => {
// 获取二维码
loadQcode();
});
// 清空计时器
onUnmounted(()=>{
timer && clearInterval(timer);
});
// 使用qcodeToken判断当前二维码是否已经被扫码登录
const checkLogin = () => {
// TODO
}
</script>
<template>
<!-- 扫码登录 -->
<div class="qcodeLoginBox">
<div class="qcodeBox" >
<img class="qcodeImg" :class="{'endImg':curTime<=0}" :src="qcodeSrc" alt="无法获取二维码,请联系客服解决">
<div v-if="curTime<=0" class="endBox" @click="loadQcode" >
当前二维码失效,点击重新加载{{ curTime }}秒
</div>
</div>
<div class="tipInfo" >
使用微信或移动端扫码登录 此二维码将在{{ curTime }}秒后刷新
</div>
</div>
</template>
<style scoped>
/* 二维码窗口样式 */
.qcodeBox{
width: 80%;
height: 80%;
position: relative;
/* 边框自动 */
margin: 0 auto;
}
/* 二维码图片样式 */
.qcodeBox .qcodeImg{
width: 100%;
height: 100%;
}
.qcodeBox .endBox{
width: 100%;
height: 100%;
/* 悬浮显示 */
position: absolute;
/* 靠左 */
/* left: 0%; */
/* 靠上 */
top: 0;
/* 居中 */
/* text-align: center; */
/* 字体大小 */
font-size: 14px;
/* 字体颜色 */
color: red;
display: flex;
/* 上下居中 */
align-items: center;
/* justify-items: center; */
/* 左右居中 */
justify-content: center;
/* 背景色为灰色 */
background-color: #00000055;
}
/* .endImg{
filter: brightness(10%);
} */
/* 提示信息样式 */
.tipInfo{
/* 行高 */
line-height: 30px;
/* 字体大小 */
font-size: 14px;
/* 居中 */
text-align: center;
/* 颜色 */
color: var(--el-text-color-placeholder);
}
</style>
13.2 扫码登录页面效果
刷新页面,选择扫码登录,可以看到二维码和倒计时时间
时间到了之后,二维码变黑色,出现红色提示,点击红色提示文本进行重新获取二维码
14. 手机验证码登录的接口、状态存储和路由跳转的实现
手机验证码登录方式
接口有:生成验证码、获取验证码
状态存储:用于存储状态,方便在全局调用
路由跳转:登录成功后跳转到主页页面
14.1 后端服务接口创建
使用你掌握的后端语言编写服务,我是java,使用springboot框架集成mysql和redis来存储数据
这里你可以不用管这个后端的项目,只需要知道我们需要一个后端的接口地址,并且知道如何在前端的代码中使用即可
14.1.1 路由前缀
后端的接口地址如下:
后端服务的地址为本机地址,所以正常访问接口前面都是
http://127.0.0.1:8888/
14.1.2 生成验证码
点击获取验证码按钮即调用生成验证码接口
生成验证码:
http://127.0.0.1:8888/login/redis/setMessageCode
请求方式为post
请求参数为username
14.1.3 获取验证码
这里只能去redis中查看,模拟手机收到验证码
获取验证码接口:
http://127.0.0.1:8888/login/redis/getMessageCode
请求方式为get
请求参数为username
14.2 api实例添加路由前缀
在src的api包下,将api.ts的代码中baseURL的值修改为后端服务访问的地址前缀
修改后的内容如下
api.ts
import axios from 'axios'
// 数据请求自定义配置(实例)
// console.log(import.meta.url,"------------");
const api = axios.create({
// baseURL: 'https://mo_sss.blog.csdn.net.cn',
// baseURL: import.meta.BaseURL,
// baseURL: 'https://hanshanlibai.gms.com',
baseURL: 'http://127.0.0.1:8888/',
timeout: 1000,
headers: {
// 'X-Custom-Header': 'foobar'
'Content-Type': 'application/json;charset=UTF-8'
}
// withCredentials 表示跨域请求时是否需要使用凭证,默认是true
// withCredentials: true,
// responseType 表示浏览器将要响应的数据类型,包括arraybuffer、document、json、text、stream
// 浏览器专属类型: blob
// 默认值就是json
// responseType: 'json',
// responseEncoding 表示用于解码响应的编码(Node.js专属),注意,忽略responseType值为stream或者客户端请求
// 默认值为utf-8
// responseEncoding: 'utf-8'
});
export default api;
14.3 配置全局状态存储store
为了记录登录状态并且方便全局调用,使用store进行存储
在src包下的store包中,修改index.ts代码,修改后如下
store/index.ts
// 引入, 用于存储全局的状态数据,可供其他地方调用
import { createStore } from "vuex";
// 引入工具方法
import utils from "@/utils/utils";
// 创建一个新的store实例
const store = createStore({
state() {
return{
// count: 0
// 当前登录的用户信息
userInfo: {},
// 当前登录的标识token
token: null,
}
},
getters: {
getUserInfo(state:any){
return state.userinfo;
},
getToken(state:any){
return state.token;
}
},
mutations: {
// increment(state) {
// state.count++
// }
// 存储用户信息
setUserInfo: function(state:any, userInfo:any){
state.userInfo = userInfo;
utils.saveData('userInfo', userInfo);
},
// 存储token
setToken: function(state:any, token:any){
state.toekn = token;
utils.saveData('token', token);
}
}
})
export default store;
14.4 工具类中添加页面加载优化的代码
src包下的utils包中的utils.ts代码修改如下
utils/utils.ts
import { ElLoading, ElMessage } from "element-plus";
const utils = {
// 加载动画
loading: null,
// loadingInstance:ref(),
// loading: String,
// 展示加载动画
showLoadding(msg:string){
if(utils.loading){
return;
}
utils.loading = ElLoading.service({
// const loadingInstance = ElLoading.service({
// loadingInstance.value = ElLoading.service({
// lock: true,
body: true,
fullscreen: true,
text: msg?msg:'Loading',
background: 'rgba(0,0,0,0.7)',
});
// return loadingInstance;
},
// 隐藏加载动画
hideLoadding(){
// showLoadding()
// loadingInstance.value.close();
utils.loading && utils.loading.close();
utils.loading = null;
},
// 消息提示
showError(msg:string){
return ElMessage({
message: msg,
grouping: true,
type: 'error'
})
} ,
showSuccess(msg:string){
return ElMessage({
message: msg,
grouping: true,
type: 'success'
})
} ,
showWarning(msg:string){
return ElMessage({
message: msg,
grouping: true,
type: 'warning'
})
} ,
showDefault(msg:string){
return ElMessage({
message: msg,
grouping: true,
type: 'info'
})
} ,
closeMessage(){
ElMessage.closeAll()
} ,
// 数据操作相关的方法
// 本地存储数据
saveData(key:any, data:any){
localStorage.setItem(key, JSON.stringify(data));
},
// 移除数据
removeData(key:any){
localStorage.removeItem(key);
},
// 获取数据
getData(key:any){
const data = localStorage.getItem(key);
if(data){
return JSON.parse(data);
}
return null;
}
}
export default utils;
14.5 手机验证码登录组件代码更新
应用store和路由进行登录接口访问,获取验证码后进行判断,正确则登陆成功跳转到主页,这里主页是一个假的地址,所以会跳转到空白,后续在添加主页组件
(关于图片验证码暂时未做实现,所以随便输入,后续进行完善)
PhoneCodeForm.vue
<script setup lang="ts">
import { ref, reactive, onMounted, onUnmounted } from 'vue'
// 引入状态存储工具store
import {useStore} from 'vuex'
// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'
// 路由引入
import { useRoute, useRouter } from 'vue-router';
// 登录表单的实例
// let loginFormRef = ref(null);
let loginFormRef = ref()
// 登录表单的数据
const loginForm = reactive({
// 用户名
username: '',
// 手机验证码
smscode: '',
// 图片验证
imgcode: '',
// 记住用户名,默认否
saveUsername: false
})
// 登录验证规则
const rules = {
username: [
{
required: true,
message: '请输入用户名',
trigger: 'blur'
}
],
smscode: [
{
required: true,
message: '请输入短信验证码',
trigger: 'blur'
}
],
imgcode: [
{
required: true,
message: '请输入图片验证码',
trigger: 'blur'
}
]
}
const formSize = {}
// 图片验证码路径
let imgCodeSrc = new URL('../../../assets/code.png', import.meta.url).href
// const imgCodeSrc = '../../../assets/code.png';
// 刷新图片验证码
const getImgCode = () => {
// 后续改为从服务器上获取动态图片
imgCodeSrc = new URL('../../../assets/code.png', import.meta.url).href
}
// 定时器
let timer: any = null
// 获取短信验证码的间隔时间
let curTime = 0
// 获取短信验证码按钮的文本显示内容
let smsCodeBtnText = ref('获取验证码')
// 获取短信验证码
const getSmsCode = () => {
// 当点击获取短信验证码时,如果其他信息没填则提示输入
if (!loginForm.username) {
utils.showError('请输入用户名')
return
}
// if(!loginForm.smscode){
// utils.showError('请输入短信验证码');
// return;
// }
// TODO 从后台获取短信验证码
// 调用接口生成短信验证码
// 1 直接使用axios请求后端完整地址请求
// axios({
// method: 'post',
// url: 'http://127.0.0.1:8888/login/redis/setMessageCode',
// // url: 'login/redis/setMessageCode',
// // 这里需要注意,不管请求方式是什么,这里是根据后端传参方式来定的,如果后端使用@RequestParam则这里使用params作为key
// params: {
// username: loginForm.username
// }
// });
// 2 使用axios实例传参请求后端接口地址的用法
api({
method: 'post',
url: '/login/redis/setMessageCode',
params: {
username: loginForm.username
}
})
curTime = 60
timer = setInterval(() => {
curTime--;
smsCodeBtnText.value = curTime + '秒后重新获取';
if (curTime <= 0) {
smsCodeBtnText.value = '获取验证码'
clearInterval(timer)
// 清除时,值为空,防止重复点击触发多次
timer = null
}
}, 1000)
}
// 状态存储的store
const store = useStore();
// 路由,转到指定页面,使用push
const route = useRoute();
const router = useRouter();
// 登录提交事件
const onSubmit = () => {
// form表单中的值,校验,
loginFormRef.value.validate((valid: string, fileds: any) => {
// 如果valid值为假,则遍历输出报错
if (!valid) {
for (let key in fileds) {
// 获取报错信息中的字段对应的key的索引为0的信息
utils.showError(fileds[key][0].message)
}
return
}
// 登录表单的记住用户名如果被勾选
if (loginForm.saveUsername) {
// 保存输入的用户名
utils.saveData('username', loginForm.username)
// 保存被勾选的操作
utils.saveData('saveUsername', loginForm.saveUsername)
} else {
// 如果记住用户名的勾选取消,则移除这两个存储的内容
utils.removeData('username')
utils.removeData('saveUsername')
}
// TODO 调用接口登录
// 因为太快了,所以可能看不到效果,可以将下方的hideLoading方法注掉,可以看到效果
utils.showLoadding('正在加载中')
api({
method: 'get',
url: '/login/redis/getMessageCode',
params: {
username: loginForm.username,
smscode: loginForm.smscode
// imgcode: loginForm.imgcode
}
})
.then((res) => {
utils.hideLoadding()
// console.log(res)
// console.log(res.status)
// if (!res || res.status != 200 || !res.data || res.data.result != 200 || !res.data.data) {
if (res.status != 200 || res.data.result != 200 || !res.data.data) {
utils.showError('登录失败-请求数据返回有误');
return;
}
// console.log(res.data.data, loginForm.smscode);
if(res.data.data == loginForm.smscode){
utils.showSuccess('登陆成功')
// 存储用户token信息并转到主页
let userInfo = res.data.data
let token = res.data.token
// 状态数据存储
store.commit('setUserInfo', userInfo);
store.commit('setToken', token);
// 登录成功后将页面转到主页
router.push('/index')
}else if(res.data.data != loginForm.smscode){
utils.showError('登录失败-验证码错误');
return;
}
// utils.showError('登录失败')
})
.catch((error) => {
// utils.hideLoadding();
console.log(error);
utils.showError('登录失败-出现异常')
})
// api.post("/api/login/code",{
// username: loginForm.username,
// smscode: loginForm.smscode,
// imgcode: loginForm.imgcode
// }).then((res)=>{
// utils.hideLoadding();
// console.log(res);
// console.log(res.status);
// if(!res || res.status != 200 || !res.data || res.data.code != 8888 || !res.data.data){
// if(res.data.message){
// utils.showError(res.data.message);
// return;
// }
// utils.showError('登录失败');
// return;
// }
// // 存储用户token信息并转到主页
// let userInfo = res.data.data;
// let token = res.data.token;
// utils.showSuccess('登陆成功');
// }).catch((error)=>{
// // utils.hideLoadding();
// utils.showError('登录失败');
// });
// 登录成功信息提示
// utils.showSuccess("登录成功");
})
}
// 挂载
onMounted(() => {
// 获取记住用户名的值
loginForm.saveUsername = utils.getData('saveUsername')
// 如果记住用户名被勾选,则获取用户名显示
if (loginForm.saveUsername) {
loginForm.username = utils.getData('username')
}
})
// 清空定时器
onUnmounted(() => {
timer && clearInterval(timer)
})
</script>
<template>
<!-- 手机验证码登录 -->
<div class="phoneCodeLoginBox">
<el-form
ref="loginFormRef"
style="max-width: 600px"
:model="loginForm"
:rules="rules"
label-width="0"
class="loginFrom"
:size="formSize"
status-icon
>
<!-- 用户名 -->
<el-form-item prop="username">
<!-- 图标设置,动态绑定username,提示信息,设置输入框大小 -->
<el-input
prefix-icon="UserFilled"
v-model="loginForm.username"
placeholder="请输入用户名"
size="large"
/>
</el-form-item>
<!-- 短信验证 -->
<el-form-item prop="smscode">
<!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 -->
<div class="flex loginLine">
<div class="flexItem">
<el-input
prefix-icon="Iphone"
v-model="loginForm.smscode"
placeholder="请输入验证码"
size="large"
/>
</div>
<div class="codeBtn">
<el-button type="primary" size="large" @click="getSmsCode" :disabled="curTime > 0">{{
smsCodeBtnText
}}</el-button>
</div>
</div>
</el-form-item>
<!-- 图片验证 -->
<el-form-item prop="imgcode">
<!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 -->
<div class="flex loginLine">
<div class="flexItem">
<el-input
prefix-icon="Picture"
v-model="loginForm.imgcode"
placeholder="请输入图片验证码"
size="large"
/>
<!-- <el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' /> -->
</div>
<div class="codeBtn">
<el-image :src="imgCodeSrc" size="large" @click="getImgCode"></el-image>
<!-- <el-button type="primary" size="large" @click="getSmsCode" class="" >获取验证码</el-button> -->
</div>
</div>
</el-form-item>
<!-- 记住用户名 -->
<el-form-item prop="saveUsername">
<el-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox>
</el-form-item>
<!-- 登录按钮 -->
<el-form-item>
<el-button class="loginBtn" type="danger" size="large" @click="onSubmit">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
<style scoped>
/* 按钮宽度设为最大 */
.loginBtn {
width: 100%;
/* 登录按钮圆角边框 */
border-radius: 20px;
}
/* 验证码按钮样式配置 */
.codeBtn {
width: 100px;
margin-left: 10px;
}
/* 按钮和图片宽度100px */
.codeBtn:deep(.el-button),
.codeBtn:deep(img) {
width: 100px;
/* height: 40px; */
}
/* 验证码图片高度 */
.codeBtn:deep(img) {
height: 40px;
/* 鼠标移上去会变成手型 */
cursor: pointer;
}
/* 这一行宽度占满 */
.loginLine {
width: 100%;
}
</style>
14.6 页面效果展示
访问浏览器页面
点击获取验证码
到redis中查看验证码
输入验证码,验证码60秒后过期
(图片验证码随便输入)
登录成功,完成跳转,此时未实现主页组件,此为假地址,故为空白页
15. 账号密码登录的接口、状态存储和路由跳转的实现
根据手机验证码登录的实现,对账号密码登录的代码进行功能实现
15.1 后端接口
账号密码登录使用的接口如下
login/login
请求方式为get
请求参数为username和password
15.2 代码实现
如下
UsernameForm.vue
<script setup lang="ts">
import { ref,reactive, onMounted } from 'vue'
// 引入状态存储store
import { useStore } from 'vuex'
// 引入路由工具router
import { useRouter } from 'vue-router'
// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'
// 登录表单的实例
// let loginFormRef = ref(null);
let loginFormRef = ref();
// 登录表单的数据
const loginForm = reactive({
// 用户名
username: '',
// 密码
password: '',
// 图片验证
imgcode: '',
// 记住用户名,默认否
saveUsername: false,
// 记住用户名,默认否
savePassword: false
});
// 登录验证规则
const rules = ({
username:[{
required: true,
message: '请输入用户名',
trigger: 'blur'
}],
password:[{
required: true,
message: '请输入密码',
trigger: 'blur'
}],
imgcode:[{
required: true,
message: '请输入图片验证码',
trigger: 'blur'
}]
});
const formSize = ({});
// 图片验证码路径
let imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;
// const imgCodeSrc = '../../../assets/code.png';
// 刷新图片验证码
const getImgCode = () => {
// 后续改为从服务器上获取动态图片
imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;
};
// 全局状态存储
const store = useStore();
// 路由调用
const router = useRouter();
// 登录提交事件
const onSubmit = () => {
// form表单中的值,校验,
loginFormRef.value.validate((valid:string, fileds:any)=>{
// 如果valid值为假,则遍历输出报错
if(!valid){
for(let key in fileds){
// 获取报错信息中的字段对应的key的索引为0的信息
utils.showError(fileds[key][0].message);
}
return;
}
// 登录表单的记住用户名如果被勾选
if(loginForm.saveUsername){
// 保存输入的用户名
utils.saveData('username', loginForm.username);
// 保存被勾选的操作
utils.saveData('saveUsername', loginForm.saveUsername);
}else{
// 如果记住用户名的勾选取消,则移除这两个存储的内容
utils.removeData('username');
utils.removeData('saveUsername');
}
// 登录表单的记住用户名如果被勾选
if(loginForm.savePassword){
// 保存输入的用户名
utils.saveData('password', loginForm.password);
// 保存被勾选的操作
utils.saveData('savePassword', loginForm.savePassword);
}else{
// 如果记住用户名的勾选取消,则移除这两个存储的内容
utils.removeData('password');
utils.removeData('savePassword');
}
// TODO 调用接口登录
utils.showLoadding("正在加载中");
api({
method: 'get',
url: '/login/login',
params: {
username: loginForm.username,
password: loginForm.password
}
}).then((res)=>{
utils.hideLoadding();
if(res.status != 200 || res.data.result != 200){
utils.showError("登录失败-请求数据返回有误");
return;
}
if(res.data.login == 1){
utils.showSuccess("登录成功");
// 存储用户信息
let userInfoLogin = res.data.login;
// let token = res.data.token;
store.commit('setUserInfo',userInfoLogin);
// 登录成功后跳转主页
router.push('/index');
}else if(res.data.login == 0){
utils.showError("登录失败-用户不存在");
return;
}else if(res.data.login == 2){
utils.showError("登录失败-密码错误");
return;
}
// utils.showError("登录失败-返回数据错误")
}).catch((error)=>{
console.log(error);
utils.showError("登录失败-发生异常");
});
// 登录成功提示
// utils.showSuccess("登录成功");
});
};
// 挂载
onMounted(() => {
// 获取记住用户名的值
loginForm.saveUsername = utils.getData('saveUsername');
// 如果记住用户名被勾选,则获取用户名显示
if(loginForm.saveUsername){
loginForm.username = utils.getData('username');
}
// 获取记住密码的值
loginForm.savePassword = utils.getData('savePassword');
// 如果记住密码被勾选,则获取密码
if(loginForm.saveUsername){
loginForm.password = utils.getData('password');
}
});
</script>
<template>
<!-- 用户密码登录 -->
<div class="usernameLoginBox">
<el-form
ref="loginFormRef"
style="max-width: 600px"
:model="loginForm"
:rules="rules"
label-width="0"
class="loginFrom"
:size="formSize" status-icon>
<!-- 用户名 -->
<el-form-item prop="username">
<!-- 图标设置,动态绑定username,提示信息,设置输入框大小 -->
<el-input prefix-icon="UserFilled" v-model="loginForm.username" placeholder="请输入用户名" size='large' />
</el-form-item>
<!-- 密码 -->
<el-form-item prop="password">
<!-- 密码 -->
<div class="flexItem" >
<!-- show-password 属性表示是否显示切换显示密码的图标,true为显示 -->
<el-input prefix-icon="Lock" show-password="off" type="password" v-model="loginForm.password" placeholder="请输入密码" size='large' />
</div>
</el-form-item>
<!-- 图片验证 -->
<el-form-item prop="imgcode">
<!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 -->
<div class="flex loginLine">
<div class="flexItem" >
<el-input prefix-icon="Picture" v-model="loginForm.imgcode" placeholder="请输入图片验证码" size='large' />
<!-- <el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' /> -->
</div>
<div class="codeBtn" >
<el-image :src="imgCodeSrc" size='large' @click="getImgCode" ></el-image>
<!-- <el-button type="primary" size="large" @click="getSmsCode" class="" >获取验证码</el-button> -->
</div>
</div>
</el-form-item>
<!-- <el-form-item prop="saveUsername"> -->
<el-form-item>
<!-- 记住账号密码的勾选 -->
<div class="flex loginLine" >
<!-- 记住用户名 -->
<div class="flexItem" >
<el-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox>
</div>
<!-- 记住密码 -->
<div class="flexItem" >
<el-checkbox v-model="loginForm.savePassword">记住密码</el-checkbox>
</div>
</div>
</el-form-item>
<el-form-item prop="savePassword">
</el-form-item>
<!-- 登录按钮 -->
<el-form-item>
<el-button class="loginBtn" type="danger" size='large' @click="onSubmit">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
<style scoped>
/* 按钮宽度设为最大 */
.loginBtn{
width: 100%;
/* 登录按钮圆角边框 */
border-radius: 20px;
}
/* 验证码按钮样式配置 */
.codeBtn{
width: 100px;
margin-left: 10px;
}
/* 按钮和图片宽度100px */
.codeBtn:deep(.el-button),
.codeBtn:deep(img){
width: 100px;
/* height: 40px; */
}
/* 验证码图片高度 */
.codeBtn:deep(img){
height: 40px;
/* 鼠标移上去会变成手型 */
cursor: pointer;
}
/* 这一行宽度占满 */
.loginLine{
width: 100%
}
</style>
15.3 页面效果展示
浏览器页面展示账号密码登录
填写数据,账号密码是提前定好的,图片验证码随便输入
登录,跳转到指定页面
16. 扫码登录的接口、状态存储和路由跳转的实现
这里应该需要有二维码获取的接口,同时存储token用于记录二维码
这里简单实现一下
16.1 接口地址
生成二维码的接口
login/qr/generateQrCodeAsFile
参数 无
请求方法 post
16.2 代码实现
QcodeForm.vue
<script setup lang="ts">
import { ref,reactive, onMounted, onUnmounted } from 'vue'
// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'
// 引入store
import {useStore} from 'vuex'
// 引入router
import {useRouter} from 'vue-router'
const store = useStore();
const router = useRouter();
// 二维码
// let qcodePath:any = null;
// 二维码对应的token, 用于判断当前二维码是否已经被扫码登录
let qrToken:string = "";
// 第一次获取验证码
// api({
// method: 'post',
// url: 'login/qr/generateQrCodeAsFile'
// }).then((res)=>{
// if(res.data.result != 200){
// utils.showError("登录失败");
// }
// utils.showSuccess("登录成功");
// qcodePath = res.data.data
// qrToken = res.data.token
// });
// qcodePath = 'E:\\WORKPROJECTS\\MySelfPro\\hslb-general-management-system\\src\\main\resources\\login_qr_pngs\\QRCode.png';
// 二维码
let qcodeSrc = new URL("../../../assets/hslb-qcode.png", import.meta.url).href;
// let qcodeSrc = new URL(qcodePath, import.meta.url).href;
// let qcodeSrc = qcodePath;
const qcodeToken = ref('');
// 当前定时器事件
const curTime = ref(0);
let timer:any = null;
// 后台更新获取二维码
const loadQcode = () => {
// 后续改为从服务器上获取动态图片
api({
method: 'post',
url: 'login/qr/generateQrCodeAsFile'
}).then((res)=>{
// if(res.data.result != 200){
// utils.showError("登录失败");
// }
// utils.showSuccess("登录成功");
// qcodePath = res.data.data
qrToken = res.data.token
});
qcodeSrc = new URL("../../../assets/hslb-qcode.png", import.meta.url).href;
// qcodeSrc = new URL(qcodePath, import.meta.url).href;
// qcodeSrc = qcodePath;
// 初始化token的值
qcodeToken.value = qrToken;
// 设定定时时间
// curTime.value = 60;
// 为了让二维码失效的效果及时,这里暂时设置10秒,后续改回60秒即可
curTime.value = 10;
// 定义定时器,倒计时
timer = setInterval(() => {
curTime.value--;
// 这里获取toekn,校验是否已经被登陆过
checkLogin();
if(curTime.value<=0){
// 事件为0则清空定时器
clearInterval(timer);
timer = null;
}
}, 1000);
};
// 登录提交事件
// const onSubmit = () => {
// };
// 挂载
onMounted(() => {
// 获取二维码
loadQcode();
});
// 清空计时器
onUnmounted(()=>{
timer && clearInterval(timer);
});
// 使用qcodeToken判断当前二维码是否已经被扫码登录
const checkLogin = () => {
// TODO
api({
method: 'post',
url: 'login/qr/generateQrCodeAsFile',
params: {
}
}).then((res)=>{
if(res.data.token){
utils.showSuccess("登录成功");
store.commit('setUserInfo',res.data.token);
router.push('/index1');
}
// res.data.token;
}).catch((error)=>{
console.log(error);
// utils.showError("登录失败")
});
}
</script>
<template>
<!-- 扫码登录 -->
<div class="qcodeLoginBox">
<div class="qcodeBox" >
<img class="qcodeImg" :class="{'endImg':curTime<=0}" :src="qcodeSrc" alt="无法获取二维码,请联系客服解决">
<div v-if="curTime<=0" class="endBox" @click="loadQcode" >
当前二维码失效,点击重新加载{{ curTime }}秒
</div>
</div>
<div class="tipInfo" >
使用微信或移动端扫码登录 此二维码将在{{ curTime }}秒后刷新
</div>
</div>
</template>
<style scoped>
/* 二维码窗口样式 */
.qcodeBox{
width: 80%;
height: 80%;
position: relative;
/* 边框自动 */
margin: 0 auto;
}
/* 二维码图片样式 */
.qcodeBox .qcodeImg{
width: 100%;
height: 100%;
}
.qcodeBox .endBox{
width: 100%;
height: 100%;
/* 悬浮显示 */
position: absolute;
/* 靠左 */
/* left: 0%; */
/* 靠上 */
top: 0;
/* 居中 */
/* text-align: center; */
/* 字体大小 */
font-size: 14px;
/* 字体颜色 */
color: red;
display: flex;
/* 上下居中 */
align-items: center;
/* justify-items: center; */
/* 左右居中 */
justify-content: center;
/* 背景色为灰色 */
background-color: #00000055;
}
/* .endImg{
filter: brightness(10%);
} */
/* 提示信息样式 */
.tipInfo{
/* 行高 */
line-height: 30px;
/* 字体大小 */
font-size: 14px;
/* 居中 */
text-align: center;
/* 颜色 */
color: var(--el-text-color-placeholder);
}
</style>
16.3 页面效果展示
现在自动校验通过,后续实现逻辑
成功后跳转到新的页面
17. 登录缓存验证的实现
在每次登录后,本地缓存存储token,在线存储临时token,在线的保存时间暂定60秒,使用的是redis存储,在前端代码中调用后端接口获取在线的redis中token与本地存储的token进行对比,一致则继续保持登录并刷新到主页,否则跳转到登录页面重新登陆
17.1 三种登录方式代码改写
在校验之前需要先在登录的时候将token存储到本地的LocalStorage缓存中,以下为改写后的三种登录方式的代码
PhoneForm.vue
<script setup lang="ts">
import { ref, reactive, onMounted, onUnmounted } from 'vue'
// 引入状态存储工具store
import {useStore} from 'vuex'
// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'
// 路由引入
import { useRoute, useRouter } from 'vue-router';
// 登录表单的实例
// let loginFormRef = ref(null);
let loginFormRef = ref()
// 登录表单的数据
const loginForm = reactive({
// 用户名
username: '',
// 手机验证码
smscode: '',
// 图片验证
imgcode: '',
// 记住用户名,默认否
saveUsername: false
})
// 登录验证规则
const rules = {
username: [
{
required: true,
message: '请输入用户名',
trigger: 'blur'
}
],
smscode: [
{
required: true,
message: '请输入短信验证码',
trigger: 'blur'
}
],
imgcode: [
{
required: true,
message: '请输入图片验证码',
trigger: 'blur'
}
]
}
const formSize = {}
// 图片验证码路径
let imgCodeSrc = new URL('../../../assets/code.png', import.meta.url).href
// const imgCodeSrc = '../../../assets/code.png';
// 刷新图片验证码
const getImgCode = () => {
// 后续改为从服务器上获取动态图片
imgCodeSrc = new URL('../../../assets/code.png', import.meta.url).href
}
// 定时器
let timer: any = null
// 获取短信验证码的间隔时间
let curTime = 0
// 获取短信验证码按钮的文本显示内容
let smsCodeBtnText = ref('获取验证码')
// 获取短信验证码
const getSmsCode = () => {
// 当点击获取短信验证码时,如果其他信息没填则提示输入
if (!loginForm.username) {
utils.showError('请输入用户名')
return
}
// if(!loginForm.smscode){
// utils.showError('请输入短信验证码');
// return;
// }
// TODO 从后台获取短信验证码
// 调用接口生成短信验证码
// 1 直接使用axios请求后端完整地址请求
// axios({
// method: 'post',
// url: 'http://127.0.0.1:8888/login/redis/setMessageCode',
// // url: 'login/redis/setMessageCode',
// // 这里需要注意,不管请求方式是什么,这里是根据后端传参方式来定的,如果后端使用@RequestParam则这里使用params作为key
// params: {
// username: loginForm.username
// }
// });
// 2 使用axios实例传参请求后端接口地址的用法
api({
method: 'post',
url: '/login/redis/setMessageCode',
params: {
username: loginForm.username
}
})
curTime = 60
timer = setInterval(() => {
curTime--;
smsCodeBtnText.value = curTime + '秒后重新获取';
if (curTime <= 0) {
smsCodeBtnText.value = '获取验证码'
clearInterval(timer)
// 清除时,值为空,防止重复点击触发多次
timer = null
}
}, 1000)
}
// 状态存储的store
const store = useStore();
// 路由,转到指定页面,使用push
const route = useRoute();
const router = useRouter();
// 登录提交事件
const onSubmit = () => {
// form表单中的值,校验,
loginFormRef.value.validate((valid: string, fileds: any) => {
// 如果valid值为假,则遍历输出报错
if (!valid) {
for (let key in fileds) {
// 获取报错信息中的字段对应的key的索引为0的信息
utils.showError(fileds[key][0].message)
}
return
}
// 登录表单的记住用户名如果被勾选
if (loginForm.saveUsername) {
// 保存输入的用户名
utils.saveData('username', loginForm.username)
// 保存被勾选的操作
utils.saveData('saveUsername', loginForm.saveUsername)
} else {
// 如果记住用户名的勾选取消,则移除这两个存储的内容
utils.removeData('username')
utils.removeData('saveUsername')
}
// TODO 调用接口登录
// 因为太快了,所以可能看不到效果,可以将下方的hideLoading方法注掉,可以看到效果
utils.showLoadding('正在加载中')
api({
method: 'get',
url: '/login/redis/getMessageCode',
params: {
username: loginForm.username,
smscode: loginForm.smscode
// imgcode: loginForm.imgcode
}
})
.then((res) => {
utils.hideLoadding()
console.log(res)
// console.log(res.status)
// if (!res || res.status != 200 || !res.data || res.data.result != 200 || !res.data.data) {
if (res.status != 200 || res.data.result != 200 || !res.data.msgCode) {
utils.showError('登录失败-请求数据返回有误');
return;
}
// console.log(res.data.data, loginForm.smscode);
if(res.data.msgCode == loginForm.smscode){
utils.showSuccess('登陆成功')
// 存储用户token信息并转到主页
// let userInfo = res.data.data
let userInfo = res.data
let token = res.data.token
// 状态数据存储
store.commit('setUserInfo', userInfo);
store.commit('setToken', token);
// 登录成功后将页面转到主页
router.push('/HomeIndex')
}else if(res.data.msgCode != loginForm.smscode){
utils.showError('登录失败-验证码错误');
return;
}
// utils.showError('登录失败')
})
.catch((error) => {
// utils.hideLoadding();
console.log(error);
utils.showError('登录失败-出现异常')
})
// api.post("/api/login/code",{
// username: loginForm.username,
// smscode: loginForm.smscode,
// imgcode: loginForm.imgcode
// }).then((res)=>{
// utils.hideLoadding();
// console.log(res);
// console.log(res.status);
// if(!res || res.status != 200 || !res.data || res.data.code != 8888 || !res.data.data){
// if(res.data.message){
// utils.showError(res.data.message);
// return;
// }
// utils.showError('登录失败');
// return;
// }
// // 存储用户token信息并转到主页
// let userInfo = res.data.data;
// let token = res.data.token;
// utils.showSuccess('登陆成功');
// }).catch((error)=>{
// // utils.hideLoadding();
// utils.showError('登录失败');
// });
// 登录成功信息提示
// utils.showSuccess("登录成功");
})
}
// 挂载
onMounted(() => {
// 获取记住用户名的值
loginForm.saveUsername = utils.getData('saveUsername')
// 如果记住用户名被勾选,则获取用户名显示
if (loginForm.saveUsername) {
loginForm.username = utils.getData('username')
}
})
// 清空定时器
onUnmounted(() => {
timer && clearInterval(timer)
})
</script>
<template>
<!-- 手机验证码登录 -->
<div class="phoneCodeLoginBox">
<el-form
ref="loginFormRef"
style="max-width: 600px"
:model="loginForm"
:rules="rules"
label-width="0"
class="loginFrom"
:size="formSize"
status-icon
>
<!-- 用户名 -->
<el-form-item prop="username">
<!-- 图标设置,动态绑定username,提示信息,设置输入框大小 -->
<el-input
prefix-icon="UserFilled"
v-model="loginForm.username"
placeholder="请输入用户名"
size="large"
/>
</el-form-item>
<!-- 短信验证 -->
<el-form-item prop="smscode">
<!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 -->
<div class="flex loginLine">
<div class="flexItem">
<el-input
prefix-icon="Iphone"
v-model="loginForm.smscode"
placeholder="请输入验证码"
size="large"
/>
</div>
<div class="codeBtn">
<el-button type="primary" size="large" @click="getSmsCode" :disabled="curTime > 0">{{
smsCodeBtnText
}}</el-button>
</div>
</div>
</el-form-item>
<!-- 图片验证 -->
<el-form-item prop="imgcode">
<!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 -->
<div class="flex loginLine">
<div class="flexItem">
<el-input
prefix-icon="Picture"
v-model="loginForm.imgcode"
placeholder="请输入图片验证码"
size="large"
/>
<!-- <el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' /> -->
</div>
<div class="codeBtn">
<el-image :src="imgCodeSrc" size="large" @click="getImgCode"></el-image>
<!-- <el-button type="primary" size="large" @click="getSmsCode" class="" >获取验证码</el-button> -->
</div>
</div>
</el-form-item>
<!-- 记住用户名 -->
<el-form-item prop="saveUsername">
<el-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox>
</el-form-item>
<!-- 登录按钮 -->
<el-form-item>
<el-button class="loginBtn" type="danger" size="large" @click="onSubmit">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
<style scoped>
/* 按钮宽度设为最大 */
.loginBtn {
width: 100%;
/* 登录按钮圆角边框 */
border-radius: 20px;
}
/* 验证码按钮样式配置 */
.codeBtn {
width: 100px;
margin-left: 10px;
}
/* 按钮和图片宽度100px */
.codeBtn:deep(.el-button),
.codeBtn:deep(img) {
width: 100px;
/* height: 40px; */
}
/* 验证码图片高度 */
.codeBtn:deep(img) {
height: 40px;
/* 鼠标移上去会变成手型 */
cursor: pointer;
}
/* 这一行宽度占满 */
.loginLine {
width: 100%;
}
</style>
QcodeForm.vue
<script setup lang="ts">
import { ref,reactive, onMounted, onUnmounted } from 'vue'
// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'
// 引入store
import {useStore} from 'vuex'
// 引入router
import {useRouter} from 'vue-router'
const store = useStore();
const router = useRouter();
// 二维码
// let qcodePath:any = null;
// 二维码对应的token, 用于判断当前二维码是否已经被扫码登录
let qrToken:string = "";
// 第一次获取验证码
// api({
// method: 'post',
// url: 'login/qr/generateQrCodeAsFile'
// }).then((res)=>{
// if(res.data.result != 200){
// utils.showError("登录失败");
// }
// utils.showSuccess("登录成功");
// qcodePath = res.data.data
// qrToken = res.data.token
// });
// qcodePath = 'E:\\WORKPROJECTS\\MySelfPro\\hslb-general-management-system\\src\\main\resources\\login_qr_pngs\\QRCode.png';
// 二维码
let qcodeSrc = new URL("../../../assets/hslb-qcode.png", import.meta.url).href;
// let qcodeSrc = new URL(qcodePath, import.meta.url).href;
// let qcodeSrc = qcodePath;
const qcodeToken = ref('');
// 当前定时器事件
const curTime = ref(0);
let timer:any = null;
let username:string = utils.getData("username");
const qrString = "100100100222";
// 后台更新获取二维码
const loadQcode = () => {
// 后续改为从服务器上获取动态图片
// const qrString = "100100100222";
console.log("9999999====== "+qrString);
// let username:string = utils.getData("username");
api({
method: 'post',
url: 'login/qr/generateQrCodeAsFile',
params: {
username: username,
qrContent: qrString
}
}).then((res)=>{
// if(res.data.result != 200){
// utils.showError("登录失败");
// }
// utils.showSuccess("登录成功");
// qcodePath = res.data.data
qrToken = res.data.token
});
qcodeSrc = new URL("../../../assets/hslb-qcode.png", import.meta.url).href;
// qcodeSrc = new URL(qcodePath, import.meta.url).href;
// qcodeSrc = qcodePath;
// 初始化token的值
qcodeToken.value = qrToken;
// 设定定时时间
// curTime.value = 60;
// 为了让二维码失效的效果及时,这里暂时设置10秒,后续改回60秒即可
curTime.value = 10;
// 定义定时器,倒计时
timer = setInterval(() => {
curTime.value--;
// 这里获取toekn,校验是否已经被登陆过
checkLogin();
if(curTime.value<=0){
// 事件为0则清空定时器
clearInterval(timer);
timer = null;
}
}, 1000);
};
// 登录提交事件
// const onSubmit = () => {
// };
// 挂载
onMounted(() => {
// 获取二维码
loadQcode();
});
// 清空计时器
onUnmounted(()=>{
timer && clearInterval(timer);
});
// 使用qcodeToken判断当前二维码是否已经被扫码登录
const checkLogin = () => {
// TODO
api({
method: 'post',
url: 'login/qr/generateQrCodeAsFile',
params: {
username: username,
qrContent: qrString
}
}).then((res)=>{
if(res.data.token){
utils.showSuccess("登录成功");
store.commit('setUserInfo',res.data);
store.commit('setToken',res.data.token);
router.push('/HomeIndex');
}
// res.data.token;
}).catch((error)=>{
console.log(error);
// utils.showError("登录失败")
});
}
</script>
<template>
<!-- 扫码登录 -->
<div class="qcodeLoginBox">
<div class="qcodeBox" >
<img class="qcodeImg" :class="{'endImg':curTime<=0}" :src="qcodeSrc" alt="无法获取二维码,请联系客服解决">
<div v-if="curTime<=0" class="endBox" @click="loadQcode" >
当前二维码失效,点击重新加载{{ curTime }}秒
</div>
</div>
<div class="tipInfo" >
使用微信或移动端扫码登录 此二维码将在{{ curTime }}秒后刷新
</div>
</div>
</template>
<style scoped>
/* 二维码窗口样式 */
.qcodeBox{
width: 80%;
height: 80%;
position: relative;
/* 边框自动 */
margin: 0 auto;
}
/* 二维码图片样式 */
.qcodeBox .qcodeImg{
width: 100%;
height: 100%;
}
.qcodeBox .endBox{
width: 100%;
height: 100%;
/* 悬浮显示 */
position: absolute;
/* 靠左 */
/* left: 0%; */
/* 靠上 */
top: 0;
/* 居中 */
/* text-align: center; */
/* 字体大小 */
font-size: 14px;
/* 字体颜色 */
color: red;
display: flex;
/* 上下居中 */
align-items: center;
/* justify-items: center; */
/* 左右居中 */
justify-content: center;
/* 背景色为灰色 */
background-color: #00000055;
}
/* .endImg{
filter: brightness(10%);
} */
/* 提示信息样式 */
.tipInfo{
/* 行高 */
line-height: 30px;
/* 字体大小 */
font-size: 14px;
/* 居中 */
text-align: center;
/* 颜色 */
color: var(--el-text-color-placeholder);
}
</style>
UserLogin.vue
<script setup lang="ts">
import { ref,reactive, onMounted } from 'vue'
// 引入状态存储store
import { useStore } from 'vuex'
// 引入路由工具router
import { useRouter } from 'vue-router'
// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'
// 登录表单的实例
// let loginFormRef = ref(null);
let loginFormRef = ref();
// 登录表单的数据
const loginForm = reactive({
// 用户名
username: '',
// 密码
password: '',
// 图片验证
imgcode: '',
// 记住用户名,默认否
saveUsername: false,
// 记住用户名,默认否
savePassword: false
});
// 登录验证规则
const rules = ({
username:[{
required: true,
message: '请输入用户名',
trigger: 'blur'
}],
password:[{
required: true,
message: '请输入密码',
trigger: 'blur'
}],
imgcode:[{
required: true,
message: '请输入图片验证码',
trigger: 'blur'
}]
});
const formSize = ({});
// 图片验证码路径
let imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;
// const imgCodeSrc = '../../../assets/code.png';
// 刷新图片验证码
const getImgCode = () => {
// 后续改为从服务器上获取动态图片
imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;
};
// 全局状态存储
const store = useStore();
// 路由调用
const router = useRouter();
// 登录提交事件
const onSubmit = () => {
// form表单中的值,校验,
loginFormRef.value.validate((valid:string, fileds:any)=>{
// 如果valid值为假,则遍历输出报错
if(!valid){
for(let key in fileds){
// 获取报错信息中的字段对应的key的索引为0的信息
utils.showError(fileds[key][0].message);
}
return;
}
// 登录表单的记住用户名如果被勾选
if(loginForm.saveUsername){
// 保存输入的用户名
utils.saveData('username', loginForm.username);
// 保存被勾选的操作
utils.saveData('saveUsername', loginForm.saveUsername);
}else{
// 如果记住用户名的勾选取消,则移除这两个存储的内容
utils.removeData('username');
utils.removeData('saveUsername');
}
// 登录表单的记住用户名如果被勾选
if(loginForm.savePassword){
// 保存输入的用户名
utils.saveData('password', loginForm.password);
// 保存被勾选的操作
utils.saveData('savePassword', loginForm.savePassword);
}else{
// 如果记住用户名的勾选取消,则移除这两个存储的内容
utils.removeData('password');
utils.removeData('savePassword');
}
// TODO 调用接口登录
utils.showLoadding("正在加载中");
api({
method: 'get',
url: '/login/login',
params: {
username: loginForm.username,
password: loginForm.password
}
}).then((res)=>{
utils.hideLoadding();
if(res.status != 200 || res.data.result != 200){
utils.showError("登录失败-请求数据返回有误");
return;
}
if(res.data.login == 1){
utils.showSuccess("登录成功");
// 存储用户信息
// let userInfoLogin = res.data.login;
let userInfoLogin = res.data;
let token = res.data.token;
console.log("usernamelogin:", token);
store.commit('setUserInfo', userInfoLogin);
store.commit('setToken', token);
console.log("----------------token: ", token);
// 登录成功后跳转主页
router.push('/HomeIndex');
}else if(res.data.login == 0){
utils.showError("登录失败-用户不存在");
return;
}else if(res.data.login == 2){
utils.showError("登录失败-密码错误");
return;
}
// utils.showError("登录失败-返回数据错误")
}).catch((error)=>{
console.log(error);
utils.showError("登录失败-发生异常");
});
// 登录成功提示
// utils.showSuccess("登录成功");
});
};
// 挂载
onMounted(() => {
// 获取记住用户名的值
loginForm.saveUsername = utils.getData('saveUsername');
// 如果记住用户名被勾选,则获取用户名显示
if(loginForm.saveUsername){
loginForm.username = utils.getData('username');
}
// 获取记住密码的值
loginForm.savePassword = utils.getData('savePassword');
// 如果记住密码被勾选,则获取密码
if(loginForm.saveUsername){
loginForm.password = utils.getData('password');
}
});
</script>
<template>
<!-- 用户密码登录 -->
<div class="usernameLoginBox">
<el-form
ref="loginFormRef"
style="max-width: 600px"
:model="loginForm"
:rules="rules"
label-width="0"
class="loginFrom"
:size="formSize" status-icon>
<!-- 用户名 -->
<el-form-item prop="username">
<!-- 图标设置,动态绑定username,提示信息,设置输入框大小 -->
<el-input prefix-icon="UserFilled" v-model="loginForm.username" placeholder="请输入用户名" size='large' />
</el-form-item>
<!-- 密码 -->
<el-form-item prop="password">
<!-- 密码 -->
<div class="flexItem" >
<!-- show-password 属性表示是否显示切换显示密码的图标,true为显示 -->
<el-input prefix-icon="Lock" show-password="off" type="password" v-model="loginForm.password" placeholder="请输入密码" size='large' />
</div>
</el-form-item>
<!-- 图片验证 -->
<el-form-item prop="imgcode">
<!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 -->
<div class="flex loginLine">
<div class="flexItem" >
<el-input prefix-icon="Picture" v-model="loginForm.imgcode" placeholder="请输入图片验证码" size='large' />
<!-- <el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' /> -->
</div>
<div class="codeBtn" >
<el-image :src="imgCodeSrc" size='large' @click="getImgCode" ></el-image>
<!-- <el-button type="primary" size="large" @click="getSmsCode" class="" >获取验证码</el-button> -->
</div>
</div>
</el-form-item>
<!-- <el-form-item prop="saveUsername"> -->
<el-form-item>
<!-- 记住账号密码的勾选 -->
<div class="flex loginLine" >
<!-- 记住用户名 -->
<div class="flexItem" >
<el-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox>
</div>
<!-- 记住密码 -->
<div class="flexItem" >
<el-checkbox v-model="loginForm.savePassword">记住密码</el-checkbox>
</div>
</div>
</el-form-item>
<el-form-item prop="savePassword">
</el-form-item>
<!-- 登录按钮 -->
<el-form-item>
<el-button class="loginBtn" type="danger" size='large' @click="onSubmit">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
<style scoped>
/* 按钮宽度设为最大 */
.loginBtn{
width: 100%;
/* 登录按钮圆角边框 */
border-radius: 20px;
}
/* 验证码按钮样式配置 */
.codeBtn{
width: 100px;
margin-left: 10px;
}
/* 按钮和图片宽度100px */
.codeBtn:deep(.el-button),
.codeBtn:deep(img){
width: 100px;
/* height: 40px; */
}
/* 验证码图片高度 */
.codeBtn:deep(img){
height: 40px;
/* 鼠标移上去会变成手型 */
cursor: pointer;
}
/* 这一行宽度占满 */
.loginLine{
width: 100%
}
</style>
17.2 页面缓存校验
在App.vue中编写代码,进行token的校验
App.vue
<script setup lang="ts">
import { onMounted } from 'vue';
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
import utils from './utils/utils';
import api from './api/api';
// // // 引入暗黑主题的动态切换
// import { useDark, useToggle } from '@vueuse/core'
// const isDark = useDark()
// // // 切换主题函数
// const toggleDark = useToggle(isDark)
// 状态存储
let store = useStore();
// 路由使用
const router = useRouter();
onMounted(()=>{
// let tt = localStorage.getItem("token");
// console.log("tt: ",tt);
console.log("=== ===");
let token = "";
// 由于token可能返回undefined报错,需要进行报错处理
try {
token = utils.getData("token");
} catch (error) {
error;
}
console.log("store-token",token);
let userInfo = utils.getData('userInfo');
if(token && userInfo){
console.log("token userInfo :",token," -- ", userInfo);
// 登录成功,验证
utils.showLoadding("正在加载")
const username = utils.getData('username');
if(!username){
// 登录失败,跳转到登录页
// token验证失败
utils.showError("用户名过期-请重新登录");
router.push('/UserLogin');
utils.hideLoadding();
}else{
console.log("username-", username);
api.get('/login/tokenCheck',{
params:{username}
}).then((res)=>{
console.log("res.data.token",res.data);
utils.hideLoadding();
if(res.data.token==token){
// 登陆成功
// store.commit('setUserInfo', userInfo);
// store.commit('setToken', token);
router.push('/HomeIndex');
utils.showSuccess("登录成功");
}else{
// 登录失败
utils.showError("Token已过期,请重新登录");
// 登录失败,跳转到登录页
router.push('/UserLogin');
}
});
utils.hideLoadding();
}
}else{
// 登录失败,跳转到登录页
utils.showError("用户登录缓存过期,请重新登录");
router.push('/UserLogin');
utils.hideLoadding();
}
});
</script>
<template>
<!-- 暗黑主题动态切换按钮实现 -->
<!-- <button @click="toggleDark()">
<i inline-block align-middle i="dark:carbon-moon carbon-sun"/>
<span class="ml-2">{{ isDark ? 'Dark' : 'Light' }}</span>
</button> -->
<RouterView></RouterView>
</template>
<style scoped>
</style>
17.3 主页代码
编写主页代码,暂时只有四个字显示,后续再做开发
<script setup lang="ts">
import utils from '@/utils/utils';
import { onMounted } from 'vue';
</script>
<template>
后台主页
</template>
<style scoped>
</style>
17.4 路由代码修改
在路由router/index.ts中添加路由组件,将默认登录跳转到主页,此时会在App.vue中进行校验,校验不通过则会跳转到登录页面
import { createRouter, createWebHistory } from 'vue-router'
// 自定义路由组件或从其他文件导入,这里选择从其他文件导入
import UserLogin from "../views/login/UserLogin.vue";
// import UserLogin from '@/views/login/UserLogin.vue';
import HomeIndex from '@/views/index/HomeIndex.vue';
// 定义一些路由,每个路由都需要映射到一个组件,
const routes = [
{
path: '/',
// component: UserLogin
redirect: "/HomeIndex"
},
{
path: '/UserLogin',
component: UserLogin
},
{
path: '/HomeIndex',
component: HomeIndex
}
]
// 创建路由实例并传递‘routes’配置 你可以在这里输入更多的配置
const router = createRouter({
history: createWebHistory(),
// routes:routes可以简写成routes,不会报错
// routes:[]
routes
})
export default router
18. 项目的源码下载地址
以上操作实现了项目的登录功能,有些粗糙,但基本的功能操作也还可以,源码下载地址如下
前端项目下载:hslb-vue3-elementplus-admin.zip
后端项目下载:java hslb-general-management-system.zip
以上就是项目功能的第一部分和第二部分实现,主要是项目的搭建和登录功能,后续的功能请继续阅读下一篇
感谢阅读,祝君暴富!