通用后台管理系统实战演示(Vue3 + element-plus)汇总篇一


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


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


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


云想衣裳花想容,春风拂槛露华浓。
——《清平调》


文章目录

  • 通用后台管理系统
  • `第一部分:项目的构建以及基础组件的安装配置`
    • 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'// 当前选中的tabconst curtab = ref(1);// tab切换监听事件const changeTab = (tabIndex) => {curtab.value = tabIndex;}
10.4.2 template标签代码

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

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

在style标签中添加以下代码


/* 表单登录部分的布局设置 */
.login-page .login-panel .login-form-panel{/* 此项下的布局均分 */flex: 1;
}/* tabs对应的块的样式设置 */
.login-page .login-panel .login-form-panel .tabs{height: 45px;line-height: 45px;text-align: center;display: flex;
}/* 登录界面的登录方式表单样式配置 */
.login-page .login-panel .login-form-panel .tabs .tab-item{/* 每个部分均分 */flex: 1;cursor: pointer;
}/* 鼠标悬浮变红,以及选中后变红 */
.login-page .login-panel .login-form-panel .tabs .tab-item:hover,
.login-page .login-panel .login-form-panel .tabs .tab-item-selected{color:red;
}
10.4.4 页面效果

此时页面效果如下
在这里插入图片描述

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

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

10.5.1 登录方式vue组件

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

<script setup lang="ts"></script><template>手机验证码登录
</template><style scoped>
</style>

QcodeForm.vue

<script setup lang="ts"></script><template>扫码登录
</template><style scoped>
</style>

UsernameForm.vue

<script setup lang="ts"></script><template>用户名密码登录
</template><style scoped>
</style>
10.5.2 script标签代码

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

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

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

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

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

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

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

10.6.1 script标签代码

完整的ts代码如下

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

完整的template代码

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

完整的style代码如下


/* 主页背样式 */
.login-page{position: fixed;top: 0;left: 0;width: 100%;height: 100%;/* background: v-bind(bgColor); *//* 页面背景色的渐变 45度渐变 */background: linear-gradient(45deg, #5198d3, #0f9fe2, #61a2d6);/* 上下渐变色 *//* background: linear-gradient(to bottom, #5198d3, #0f9fe2, #61a2d6); */}/* 登录模块样式 */
.login-page .login-panel{/* text-align: center; *//* width: 800px; *//* height: 450px; */width: v-bind(loginPagePanelWidth);height: v-bind(loginPagePanelHeight);background: #ffff;padding: 10px;border-radius: 5px;/* 水平居中配置,使用margin的auto值 */margin: 0 auto;/* 垂直居中配置,使用cacl函数,使用100vh减去高度height的值除以2 */margin-top: calc((100vh - 450px)/2);/* margin-top: calc((100vh - v-bind(loginPagePanelHeight)) / 2); *//* 登录窗口边界阴影效果 */box-shadow: 0 0 20px 20px #00000055;}/* 登录logo和表单的父类标签布局 */
.login-page .login-panel .login-inner{/* 自动伸缩均分展示 */display: flex;
}/* logo 图标样式配置 */
.login-page .login-panel .logo-panel{/* 宽度占比40% */width: 40%;text-align: center;
}/* logo图标样式设置 */
.login-page .login-panel .logo-panel img{/* width: 300px;margin-top: 125px; */width: 80%;margin-top: calc(v-bind(loginPagePanelHeight)*0.4);
}/* 分割线样式 */
.login-page .login-panel .login-inner-split{width: 3px;/* height: 450px; */height: v-bind(loginPagePanelHeight);/* 分割线左右间隔 */margin: 0 10px;/* 分割线背景色,灰色 */background: #f8f8f8;
}/* 表单登录部分的布局设置 */
.login-page .login-panel .login-form-panel{/* 此项下的布局均分 */flex: 1;
}/* tabs对应的块的样式设置 */
.login-page .login-panel .login-form-panel .tabs{height: 45px;line-height: 45px;/* 增加tabs上边框的距离 */margin-top: 20px;text-align: center;display: flex;
}/* 登录界面的登录方式表单样式配置 */
.login-page .login-panel .login-form-panel .tabs .tab-item{/* 每个部分均分 */flex: 1;cursor: pointer;
}/* 鼠标悬浮变红,以及选中后变红 */
.login-page .login-panel .login-form-panel .tabs .tab-item:hover,
.login-page .login-panel .login-form-panel .tabs .tab-item-selected{color:red;
}/* 页脚声明样式配置 */
.login-page .login-footer{position: fixed;bottom: 0;left: 0;width: 100%;height: 60px;text-align: center;color: #fff;font-size: 14px;
}

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

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

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

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

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

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

11.2.1 script标签代码

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

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

template中的代码如下

    <!-- 手机验证码登录 --><div class="phoneCodeLoginBox"><el-form ref="loginFormRef"style="max-width: 600px":model="loginForm":rules="rules"label-width="0"class="loginFrom":size="formSize" status-icon><!-- 用户名 --><el-form-item prop="username"><!-- 图标设置,动态绑定username,提示信息,设置输入框大小 --><el-input prefix-icon="UserFilled" v-model="loginForm.username" placeholder="请输入用户名" size='large' /></el-form-item><!-- 短信验证 --><el-form-item prop="smscode"><el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' /></el-form-item><!-- 图片验证 --><el-form-item prop="imgcode"><el-input prefix-icon="Picture" v-model="loginForm.imgcode" placeholder="请输入图片验证码" size='large' /></el-form-item><!-- 记住用户名 --><el-form-item prop="saveUsername"><el-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox></el-form-item><!-- 登录按钮 --><el-form-item prop="saveUsername"><el-button class="loginBtn" type="danger" size='large' @click="onSubmit">登录</el-button></el-form-item></el-form></div>
11.2.3 style标签代码

style标签的代码如下

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

此时页面展示效果如下
在这里插入图片描述

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

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

11.3.1 引入全局css样式

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

@charset 'utf-8';
html,
body {margin: 0;padding: 0;
}.flex {display: flex;
}.flexItem {flex: 1;
}

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

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

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

<script setup lang="ts">import { ref,reactive } from 'vue'// 登录表单的实例const loginFormRef = ref(null);// 登录表单的数据const loginForm = reactive({// 用户名username: '',// 手机验证码smscode: '',// 图片验证imgcode: '',// 记住用户名,默认否saveUsername: false});// 登录验证规则const rules = ({});// 图片验证码图片const imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;// const imgCodeSrc = '../../../assets/code.png';</script><template><!-- 手机验证码登录 --><div class="phoneCodeLoginBox"><el-form ref="loginFormRef"style="max-width: 600px":model="loginForm":rules="rules"label-width="0"class="loginFrom":size="formSize" status-icon><!-- 用户名 --><el-form-item prop="username"><!-- 图标设置,动态绑定username,提示信息,设置输入框大小 --><el-input prefix-icon="UserFilled" v-model="loginForm.username" placeholder="请输入用户名" size='large' /></el-form-item><!-- 短信验证 --><el-form-item prop="smscode"><!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 --><div class="flex loginLine"><div class="flexItem" ><el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' /></div><div class="codeBtn" ><el-button type="primary" size="large" @click="getSmsCode" class="" >获取验证码</el-button></div></div></el-form-item><!-- 图片验证 --><el-form-item prop="imgcode"><!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 --><div class="flex loginLine"><div class="flexItem" ><el-input prefix-icon="Picture" v-model="loginForm.imgcode" placeholder="请输入图片验证码" size='large' /><!-- <el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' /> --></div><div class="codeBtn" ><el-image  :src="imgCodeSrc" ></el-image><!-- <el-button type="primary" size="large" @click="getSmsCode" class="" >获取验证码</el-button> --></div></div></el-form-item><!-- 记住用户名 --><el-form-item prop="saveUsername"><el-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox></el-form-item><!-- 登录按钮 --><el-form-item prop="saveUsername"><el-button class="loginBtn" type="danger" size='large' @click="onSubmit">登录</el-button></el-form-item></el-form></div></template><style scoped>/* 按钮宽度设为最大 */.loginBtn{width: 100%;/* 登录按钮圆角边框 */border-radius: 20px;}/* 验证码按钮样式配置 */.codeBtn{width: 100px;margin-left: 10px;}/* 按钮和图片宽度100px */.codeBtn:deep(.el-button),.codeBtn:deep(img){width: 100px;}/* 验证码图片高度 */.codeBtn:deep(img){height: 40px;}/* 这一行宽度占满 */.loginLine{width: 100%}</style>

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

11.3.3 页面效果展示

此时页面如下
在这里插入图片描述

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

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

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

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

11.4.1 表单验证逻辑实现

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

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

页面效果如下,点击输入框后不输入内容会提示
在这里插入图片描述

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

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

// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'// 定时器let timer = null;// 获取短信验证码的间隔时间let curTime = 0;// 获取短信验证码按钮的文本显示内容let smsCodeBtnText = ref('获取验证码');// 获取短信验证码const getSmsCode = () => {// 当点击获取短信验证码时,如果其他信息没填则提示输入if(!loginForm.username){utils.showError('请输入用户名');return;}// if(!loginForm.smscode){//     utils.showError('请输入短信验证码');//     return;// }// TODO 从后台获取短信验证码curTime = 60;timer = setInterval(() => {curTime--;smsCodeBtnText.value = curTime+'秒后重新获取';if(curTime<=0){smsCodeBtnText.value = '获取验证码';clearInterval(timer);}},1000);};

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

            <!-- 短信验证 --><el-form-item prop="smscode"><!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 --><div class="flex loginLine"><div class="flexItem" ><el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' /></div><div class="codeBtn" ><el-button type="primary" size="large" @click="getSmsCode" :disabled="curTime>0">{{ smsCodeBtnText }}</el-button></div></div></el-form-item>

此时页面效果如下,不输入用户名点击获取验证码,顶部提示输入信息
在这里插入图片描述

输入用户名后再点击获取验证码,开始进行读秒,读完后方可重新获取
在这里插入图片描述

11.4.3 记住用户名功能实现

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

    // 登录提交事件const onSubmit = () => {// form表单中的值,校验,loginFormRef.value.validate((valid:string, fileds:any)=>{// 如果valid值为假,则遍历输出报错if(!valid){for(let key in fileds){// 获取报错信息中的字段对应的key的索引为0的信息utils.showError(fileds[key][0].message);}return;}// 登录表单的记住用户名如果被勾选if(loginForm.saveUsername){// 保存输入的用户名utils.saveData('username', loginForm.username);// 保存被勾选的操作utils.saveData('saveUsername', loginForm.saveUsername);}else{// 如果记住用户名的勾选取消,则移除这两个存储的内容utils.removeData('username');utils.removeData('saveUsername');}});};// 挂载onMounted(() => {// 获取记住用户名的值loginForm.saveUsername = utils.getData('saveUsername');// 如果记住用户名被勾选,则获取用户名显示if(loginForm.saveUsername){loginForm.username = utils.getData('username');}})

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

            <!-- 记住用户名 --><el-form-item prop="saveUsername"><el-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox></el-form-item><!-- 登录按钮 --><el-form-item><el-button class="loginBtn" type="danger" size='large' @click="onSubmit">登录</el-button></el-form-item>

此时访问浏览器页面,输入数据,登录
在这里插入图片描述
刷新页面,用户名还在,记住用户名也还被勾选着
在这里插入图片描述

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

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

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

登录成功提示

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

11.6 完整代码

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

<script setup lang="ts">import { ref,reactive, onMounted } from 'vue'// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'// 登录表单的实例let loginFormRef = ref(null);// 登录表单的数据const loginForm = reactive({// 用户名username: '',// 手机验证码smscode: '',// 图片验证imgcode: '',// 记住用户名,默认否saveUsername: false});// 登录验证规则const rules = ({username:[{required: true,message: '请输入用户名',trigger: 'blur'}],smscode:[{required: true,message: '请输入短信验证码',trigger: 'blur'}],imgcode:[{required: true,message: '请输入图片验证码',trigger: 'blur'}]});const formSize = ({});// 图片验证码路径let imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;// const imgCodeSrc = '../../../assets/code.png';// 刷新图片验证码const getImgCode = () => {// 后续改为从服务器上获取动态图片imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;};// 定时器let timer:any = null;// 获取短信验证码的间隔时间let curTime = 0;// 获取短信验证码按钮的文本显示内容let smsCodeBtnText = ref('获取验证码');// 获取短信验证码const getSmsCode = () => {// 当点击获取短信验证码时,如果其他信息没填则提示输入if(!loginForm.username){utils.showError('请输入用户名');return;}// if(!loginForm.smscode){//     utils.showError('请输入短信验证码');//     return;// }// TODO 从后台获取短信验证码curTime = 60;timer = setInterval(() => {curTime--;smsCodeBtnText.value = curTime+'秒后重新获取';if(curTime<=0){smsCodeBtnText.value = '获取验证码';clearInterval(timer);// 清除时,值为空,防止重复点击触发多次timer = null;}},1000);};// 登录提交事件const onSubmit = () => {// form表单中的值,校验,loginFormRef.value.validate((valid:string, fileds:any)=>{// 如果valid值为假,则遍历输出报错if(!valid){for(let key in fileds){// 获取报错信息中的字段对应的key的索引为0的信息utils.showError(fileds[key][0].message);}return;}// 登录表单的记住用户名如果被勾选if(loginForm.saveUsername){// 保存输入的用户名utils.saveData('username', loginForm.username);// 保存被勾选的操作utils.saveData('saveUsername', loginForm.saveUsername);}else{// 如果记住用户名的勾选取消,则移除这两个存储的内容utils.removeData('username');utils.removeData('saveUsername');}// TODO 调用接口登录// 登录成功信息提示utils.showSuccess("登录成功");});};// 挂载onMounted(() => {// 获取记住用户名的值loginForm.saveUsername = utils.getData('saveUsername');// 如果记住用户名被勾选,则获取用户名显示if(loginForm.saveUsername){loginForm.username = utils.getData('username');}});// 清空定时器onMounted(() => {timer && clearInterval(timer);});</script><template><!-- 手机验证码登录 --><div class="phoneCodeLoginBox"><el-form ref="loginFormRef"style="max-width: 600px":model="loginForm":rules="rules"label-width="0"class="loginFrom":size="formSize" status-icon><!-- 用户名 --><el-form-item prop="username"><!-- 图标设置,动态绑定username,提示信息,设置输入框大小 --><el-input prefix-icon="UserFilled" v-model="loginForm.username" placeholder="请输入用户名" size='large' /></el-form-item><!-- 短信验证 --><el-form-item prop="smscode"><!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 --><div class="flex loginLine"><div class="flexItem" ><el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' /></div><div class="codeBtn" ><el-button type="primary" size="large" @click="getSmsCode" :disabled="curTime>0">{{ smsCodeBtnText }}</el-button></div></div></el-form-item><!-- 图片验证 --><el-form-item prop="imgcode"><!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 --><div class="flex loginLine"><div class="flexItem" ><el-input prefix-icon="Picture" v-model="loginForm.imgcode" placeholder="请输入图片验证码" size='large' /><!-- <el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' /> --></div><div class="codeBtn" ><el-image  :src="imgCodeSrc" size='large' @click="getImgCode" ></el-image><!-- <el-button type="primary" size="large" @click="getSmsCode" class="" >获取验证码</el-button> --></div></div></el-form-item><!-- 记住用户名 --><el-form-item prop="saveUsername"><el-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox></el-form-item><!-- 登录按钮 --><el-form-item><el-button class="loginBtn" type="danger" size='large' @click="onSubmit">登录</el-button></el-form-item></el-form></div></template><style scoped>/* 按钮宽度设为最大 */.loginBtn{width: 100%;/* 登录按钮圆角边框 */border-radius: 20px;}/* 验证码按钮样式配置 */.codeBtn{width: 100px;margin-left: 10px;}/* 按钮和图片宽度100px */.codeBtn:deep(.el-button),.codeBtn:deep(img){width: 100px;/* height: 40px; */}/* 验证码图片高度 */.codeBtn:deep(img){height: 40px;/* 鼠标移上去会变成手型 */cursor: pointer;}/* 这一行宽度占满 */.loginLine{width: 100%}</style>

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

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

12.1 添加或修改的内容

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

12.2 完整代码

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

<script setup lang="ts">import { ref,reactive, onMounted } from 'vue'// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'// 登录表单的实例let loginFormRef = ref(null);// 登录表单的数据const loginForm = reactive({// 用户名username: '',// 密码password: '',// 图片验证imgcode: '',// 记住用户名,默认否saveUsername: false,// 记住用户名,默认否savePassword: false});// 登录验证规则const rules = ({username:[{required: true,message: '请输入用户名',trigger: 'blur'}],password:[{required: true,message: '请输入密码',trigger: 'blur'}],imgcode:[{required: true,message: '请输入图片验证码',trigger: 'blur'}]});const formSize = ({});// 图片验证码路径let imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;// const imgCodeSrc = '../../../assets/code.png';// 刷新图片验证码const getImgCode = () => {// 后续改为从服务器上获取动态图片imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;};// 登录提交事件const onSubmit = () => {// form表单中的值,校验,loginFormRef.value.validate((valid:string, fileds:any)=>{// 如果valid值为假,则遍历输出报错if(!valid){for(let key in fileds){// 获取报错信息中的字段对应的key的索引为0的信息utils.showError(fileds[key][0].message);}return;}// 登录表单的记住用户名如果被勾选if(loginForm.saveUsername){// 保存输入的用户名utils.saveData('username', loginForm.username);// 保存被勾选的操作utils.saveData('saveUsername', loginForm.saveUsername);}else{// 如果记住用户名的勾选取消,则移除这两个存储的内容utils.removeData('username');utils.removeData('saveUsername');}// 登录表单的记住用户名如果被勾选if(loginForm.savePassword){// 保存输入的用户名utils.saveData('password', loginForm.password);// 保存被勾选的操作utils.saveData('savePassword', loginForm.savePassword);}else{// 如果记住用户名的勾选取消,则移除这两个存储的内容utils.removeData('password');utils.removeData('savePassword');}// TODO 调用接口登录// 登录成功提示utils.showSuccess("登录成功");});};// 挂载onMounted(() => {// 获取记住用户名的值loginForm.saveUsername = utils.getData('saveUsername');// 如果记住用户名被勾选,则获取用户名显示if(loginForm.saveUsername){loginForm.username = utils.getData('username');}// 获取记住密码的值loginForm.savePassword = utils.getData('savePassword');// 如果记住密码被勾选,则获取密码if(loginForm.saveUsername){loginForm.password = utils.getData('password');}});</script><template><!-- 手机验证码登录 --><div class="usernameLoginBox"><el-form ref="loginFormRef"style="max-width: 600px":model="loginForm":rules="rules"label-width="0"class="loginFrom":size="formSize" status-icon><!-- 用户名 --><el-form-item prop="username"><!-- 图标设置,动态绑定username,提示信息,设置输入框大小 --><el-input prefix-icon="UserFilled" v-model="loginForm.username" placeholder="请输入用户名" size='large' /></el-form-item><!-- 密码 --><el-form-item prop="password"><!-- 密码 --><div class="flexItem" ><!-- show-password 属性表示是否显示切换显示密码的图标,true为显示 --><el-input prefix-icon="Lock" show-password="off" type="password" v-model="loginForm.password" placeholder="请输入密码" size='large' /></div></el-form-item><!-- 图片验证 --><el-form-item prop="imgcode"><!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 --><div class="flex loginLine"><div class="flexItem" ><el-input prefix-icon="Picture" v-model="loginForm.imgcode" placeholder="请输入图片验证码" size='large' /><!-- <el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' /> --></div><div class="codeBtn" ><el-image  :src="imgCodeSrc" size='large' @click="getImgCode" ></el-image><!-- <el-button type="primary" size="large" @click="getSmsCode" class="" >获取验证码</el-button> --></div></div></el-form-item><!-- <el-form-item prop="saveUsername"> --><el-form-item><!-- 记住账号密码的勾选 --><div class="flex loginLine" ><!-- 记住用户名 --><div class="flexItem" ><el-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox></div><!-- 记住密码 --><div class="flexItem" ><el-checkbox v-model="loginForm.savePassword">记住密码</el-checkbox></div></div></el-form-item><el-form-item prop="savePassword"></el-form-item><!-- 登录按钮 --><el-form-item><el-button class="loginBtn" type="danger" size='large' @click="onSubmit">登录</el-button></el-form-item></el-form></div></template><style scoped>/* 按钮宽度设为最大 */.loginBtn{width: 100%;/* 登录按钮圆角边框 */border-radius: 20px;}/* 验证码按钮样式配置 */.codeBtn{width: 100px;margin-left: 10px;}/* 按钮和图片宽度100px */.codeBtn:deep(.el-button),.codeBtn:deep(img){width: 100px;/* height: 40px; */}/* 验证码图片高度 */.codeBtn:deep(img){height: 40px;/* 鼠标移上去会变成手型 */cursor: pointer;}/* 这一行宽度占满 */.loginLine{width: 100%}</style>

12.3 页面效果展示

页面效果如下
在这里插入图片描述
在这里插入图片描述

13. 扫码登录功能的实现

13.1 扫码登录组件代码

QcodeForm.vue

<script setup lang="ts">import { ref,reactive, onMounted, onUnmounted } from 'vue'// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'// 二维码let qcodeSrc = new URL("../../../assets/hslb-qcode.png", import.meta.url).href;// const imgCodeSrc = '../../../assets/code.png';// 二维码对应的token, 用于判断当前二维码是否已经被扫码登录const qcodeToken = ref('');// 当前定时器事件const curTime = ref(0);let timer:any = null;// 后台更新获取二维码const loadQcode = () => {// 后续改为从服务器上获取动态图片qcodeSrc = new URL("../../../assets/hslb-qcode.png", import.meta.url).href;// 初始化token的值qcodeToken.value = "";// 设定定时时间// curTime.value = 60;// 为了让二维码失效的效果及时,这里暂时设置10秒,后续改回60秒即可curTime.value = 10;// 定义定时器,倒计时timer = setInterval(() => {curTime.value--;// 这里获取toekn,校验是否已经被登陆过checkLogin();if(curTime.value<=0){// 事件为0则清空定时器clearInterval(timer);timer = null;}}, 1000);};// 登录提交事件const onSubmit = () => {};// 挂载onMounted(() => {// 获取二维码loadQcode();});// 清空计时器onUnmounted(()=>{timer && clearInterval(timer);});// 使用qcodeToken判断当前二维码是否已经被扫码登录const checkLogin = () => {// TODO}</script><template><!-- 扫码登录 --><div class="qcodeLoginBox"><div class="qcodeBox" ><img class="qcodeImg" :class="{'endImg':curTime<=0}" :src="qcodeSrc" alt="无法获取二维码,请联系客服解决"><div v-if="curTime<=0" class="endBox" @click="loadQcode" >当前二维码失效,点击重新加载{{ curTime }}</div></div><div class="tipInfo" >使用微信或移动端扫码登录 此二维码将在{{ curTime }}秒后刷新</div></div></template><style scoped>/* 二维码窗口样式 */.qcodeBox{width: 80%;height: 80%;position: relative;/* 边框自动 */margin: 0 auto;   }/* 二维码图片样式 */.qcodeBox .qcodeImg{width: 100%;height: 100%;}.qcodeBox .endBox{width: 100%;height: 100%;/* 悬浮显示 */position: absolute;/* 靠左 *//* left: 0%; *//* 靠上 */top: 0;/* 居中 *//* text-align: center; *//* 字体大小 */font-size: 14px;/* 字体颜色 */color: red;display: flex;/* 上下居中 */align-items: center;/* justify-items: center; *//* 左右居中 */justify-content: center;/* 背景色为灰色 */background-color: #00000055;}/* .endImg{filter: brightness(10%);} *//* 提示信息样式 */.tipInfo{/* 行高 */line-height: 30px;/* 字体大小 */font-size: 14px;/* 居中 */text-align: center;/* 颜色 */color: var(--el-text-color-placeholder);}</style>

13.2 扫码登录页面效果

刷新页面,选择扫码登录,可以看到二维码和倒计时时间
在这里插入图片描述

时间到了之后,二维码变黑色,出现红色提示,点击红色提示文本进行重新获取二维码
在这里插入图片描述

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

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

14.1 后端服务接口创建

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

14.1.1 路由前缀

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

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

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

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

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

14.1.3 获取验证码

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

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

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

14.2 api实例添加路由前缀

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

import axios from 'axios'// 数据请求自定义配置(实例)
// console.log(import.meta.url,"------------");
const api = axios.create({// baseURL: 'https://mo_sss.blog.csdn.net.cn',// baseURL: import.meta.BaseURL,// baseURL: 'https://hanshanlibai.gms.com',baseURL: 'http://127.0.0.1:8888/',timeout: 1000,headers: {// 'X-Custom-Header': 'foobar''Content-Type': 'application/json;charset=UTF-8'}// withCredentials 表示跨域请求时是否需要使用凭证,默认是true// withCredentials: true,// responseType 表示浏览器将要响应的数据类型,包括arraybuffer、document、json、text、stream// 浏览器专属类型: blob// 默认值就是json// responseType: 'json',// responseEncoding 表示用于解码响应的编码(Node.js专属),注意,忽略responseType值为stream或者客户端请求// 默认值为utf-8// responseEncoding: 'utf-8'
});export default api;

14.3 配置全局状态存储store

为了记录登录状态并且方便全局调用,使用store进行存储
在src包下的store包中,修改index.ts代码,修改后如下
store/index.ts

// 引入, 用于存储全局的状态数据,可供其他地方调用
import { createStore } from "vuex";
// 引入工具方法
import utils from "@/utils/utils";// 创建一个新的store实例
const store = createStore({state() {return{// count: 0// 当前登录的用户信息userInfo: {},// 当前登录的标识tokentoken: null,}},getters: {getUserInfo(state:any){return state.userinfo;},getToken(state:any){return state.token;}},mutations: {// increment(state) {//     state.count++// }// 存储用户信息setUserInfo: function(state:any, userInfo:any){state.userInfo = userInfo;utils.saveData('userInfo', userInfo);},// 存储tokensetToken: function(state:any, token:any){state.toekn = token;utils.saveData('token', token);}}})export default store;

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

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

import { ElLoading, ElMessage } from "element-plus";const utils = {// 加载动画loading: null,// loadingInstance:ref(),// loading: String,// 展示加载动画showLoadding(msg:string){if(utils.loading){return;}utils.loading = ElLoading.service({// const loadingInstance = ElLoading.service({// loadingInstance.value = ElLoading.service({// lock: true,body: true,fullscreen: true,text: msg?msg:'Loading',background: 'rgba(0,0,0,0.7)',});// return loadingInstance;},// 隐藏加载动画hideLoadding(){// showLoadding()// loadingInstance.value.close();utils.loading && utils.loading.close();utils.loading = null;},// 消息提示showError(msg:string){return ElMessage({message: msg,grouping: true,type: 'error'})} ,showSuccess(msg:string){return ElMessage({message: msg,grouping: true,type: 'success'})} ,showWarning(msg:string){return ElMessage({message: msg,grouping: true,type: 'warning'})} ,showDefault(msg:string){return ElMessage({message: msg,grouping: true,type: 'info'})} ,closeMessage(){ElMessage.closeAll()} ,// 数据操作相关的方法// 本地存储数据saveData(key:any, data:any){localStorage.setItem(key, JSON.stringify(data));},// 移除数据removeData(key:any){localStorage.removeItem(key);},// 获取数据getData(key:any){const data = localStorage.getItem(key);if(data){return JSON.parse(data);}return null;}
}export default utils;

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

应用store和路由进行登录接口访问,获取验证码后进行判断,正确则登陆成功跳转到主页,这里主页是一个假的地址,所以会跳转到空白,后续在添加主页组件
(关于图片验证码暂时未做实现,所以随便输入,后续进行完善)
PhoneCodeForm.vue

<script setup lang="ts">
import { ref, reactive, onMounted, onUnmounted } from 'vue'// 引入状态存储工具store
import {useStore} from 'vuex'// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'
// 路由引入
import { useRoute, useRouter } from 'vue-router';// 登录表单的实例
// let loginFormRef = ref(null);
let loginFormRef = ref()
// 登录表单的数据
const loginForm = reactive({// 用户名username: '',// 手机验证码smscode: '',// 图片验证imgcode: '',// 记住用户名,默认否saveUsername: false
})// 登录验证规则
const rules = {username: [{required: true,message: '请输入用户名',trigger: 'blur'}],smscode: [{required: true,message: '请输入短信验证码',trigger: 'blur'}],imgcode: [{required: true,message: '请输入图片验证码',trigger: 'blur'}]
}const formSize = {}// 图片验证码路径
let imgCodeSrc = new URL('../../../assets/code.png', import.meta.url).href
// const imgCodeSrc = '../../../assets/code.png';// 刷新图片验证码
const getImgCode = () => {// 后续改为从服务器上获取动态图片imgCodeSrc = new URL('../../../assets/code.png', import.meta.url).href
}// 定时器
let timer: any = null
// 获取短信验证码的间隔时间
let curTime = 0
// 获取短信验证码按钮的文本显示内容
let smsCodeBtnText = ref('获取验证码')// 获取短信验证码
const getSmsCode = () => {// 当点击获取短信验证码时,如果其他信息没填则提示输入if (!loginForm.username) {utils.showError('请输入用户名')return}// if(!loginForm.smscode){//     utils.showError('请输入短信验证码');//     return;// }// TODO 从后台获取短信验证码// 调用接口生成短信验证码// 1 直接使用axios请求后端完整地址请求
//   axios({
//     method: 'post',
//     url: 'http://127.0.0.1:8888/login/redis/setMessageCode',
//     // url: 'login/redis/setMessageCode',
//     // 这里需要注意,不管请求方式是什么,这里是根据后端传参方式来定的,如果后端使用@RequestParam则这里使用params作为key
//     params: {
//       username: loginForm.username
//     }
//   });// 2 使用axios实例传参请求后端接口地址的用法  api({method: 'post',url: '/login/redis/setMessageCode',params: {username: loginForm.username}})curTime = 60timer = setInterval(() => {curTime--;smsCodeBtnText.value = curTime + '秒后重新获取';if (curTime <= 0) {smsCodeBtnText.value = '获取验证码'clearInterval(timer)// 清除时,值为空,防止重复点击触发多次timer = null}}, 1000)
}// 状态存储的store
const store = useStore();
// 路由,转到指定页面,使用push
const route = useRoute();
const router = useRouter();// 登录提交事件
const onSubmit = () => {// form表单中的值,校验,loginFormRef.value.validate((valid: string, fileds: any) => {// 如果valid值为假,则遍历输出报错if (!valid) {for (let key in fileds) {// 获取报错信息中的字段对应的key的索引为0的信息utils.showError(fileds[key][0].message)}return}// 登录表单的记住用户名如果被勾选if (loginForm.saveUsername) {// 保存输入的用户名utils.saveData('username', loginForm.username)// 保存被勾选的操作utils.saveData('saveUsername', loginForm.saveUsername)} else {// 如果记住用户名的勾选取消,则移除这两个存储的内容utils.removeData('username')utils.removeData('saveUsername')}// TODO 调用接口登录// 因为太快了,所以可能看不到效果,可以将下方的hideLoading方法注掉,可以看到效果utils.showLoadding('正在加载中')api({method: 'get',url: '/login/redis/getMessageCode',params: {username: loginForm.username,smscode: loginForm.smscode// imgcode: loginForm.imgcode}}).then((res) => {utils.hideLoadding()// console.log(res)// console.log(res.status)// if (!res || res.status != 200 || !res.data || res.data.result != 200 || !res.data.data) {if (res.status != 200 || res.data.result != 200 || !res.data.data) {utils.showError('登录失败-请求数据返回有误');return;}// console.log(res.data.data, loginForm.smscode);if(res.data.data == loginForm.smscode){utils.showSuccess('登陆成功')// 存储用户token信息并转到主页let userInfo = res.data.datalet token = res.data.token// 状态数据存储store.commit('setUserInfo', userInfo);store.commit('setToken', token);// 登录成功后将页面转到主页router.push('/index')}else if(res.data.data != loginForm.smscode){utils.showError('登录失败-验证码错误');return;}// utils.showError('登录失败')}).catch((error) => {// utils.hideLoadding();console.log(error);utils.showError('登录失败-出现异常')})// api.post("/api/login/code",{//     username: loginForm.username,//     smscode: loginForm.smscode,//     imgcode: loginForm.imgcode// }).then((res)=>{//     utils.hideLoadding();//     console.log(res);//     console.log(res.status);//     if(!res || res.status != 200 || !res.data || res.data.code != 8888 || !res.data.data){//         if(res.data.message){//             utils.showError(res.data.message);//             return;//         }//         utils.showError('登录失败');//         return;//     }//     // 存储用户token信息并转到主页//     let userInfo = res.data.data;//     let token = res.data.token;//     utils.showSuccess('登陆成功');// }).catch((error)=>{//     // utils.hideLoadding();//     utils.showError('登录失败');// });// 登录成功信息提示// utils.showSuccess("登录成功");})
}// 挂载
onMounted(() => {// 获取记住用户名的值loginForm.saveUsername = utils.getData('saveUsername')// 如果记住用户名被勾选,则获取用户名显示if (loginForm.saveUsername) {loginForm.username = utils.getData('username')}
})// 清空定时器
onUnmounted(() => {timer && clearInterval(timer)
})
</script><template><!-- 手机验证码登录 --><div class="phoneCodeLoginBox"><el-formref="loginFormRef"style="max-width: 600px":model="loginForm":rules="rules"label-width="0"class="loginFrom":size="formSize"status-icon><!-- 用户名 --><el-form-item prop="username"><!-- 图标设置,动态绑定username,提示信息,设置输入框大小 --><el-inputprefix-icon="UserFilled"v-model="loginForm.username"placeholder="请输入用户名"size="large"/></el-form-item><!-- 短信验证 --><el-form-item prop="smscode"><!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 --><div class="flex loginLine"><div class="flexItem"><el-inputprefix-icon="Iphone"v-model="loginForm.smscode"placeholder="请输入验证码"size="large"/></div><div class="codeBtn"><el-button type="primary" size="large" @click="getSmsCode" :disabled="curTime > 0">{{smsCodeBtnText}}</el-button></div></div></el-form-item><!-- 图片验证 --><el-form-item prop="imgcode"><!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 --><div class="flex loginLine"><div class="flexItem"><el-inputprefix-icon="Picture"v-model="loginForm.imgcode"placeholder="请输入图片验证码"size="large"/><!-- <el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' /> --></div><div class="codeBtn"><el-image :src="imgCodeSrc" size="large" @click="getImgCode"></el-image><!-- <el-button type="primary" size="large" @click="getSmsCode" class="" >获取验证码</el-button> --></div></div></el-form-item><!-- 记住用户名 --><el-form-item prop="saveUsername"><el-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox></el-form-item><!-- 登录按钮 --><el-form-item><el-button class="loginBtn" type="danger" size="large" @click="onSubmit">登录</el-button></el-form-item></el-form></div>
</template><style scoped>
/* 按钮宽度设为最大 */
.loginBtn {width: 100%;/* 登录按钮圆角边框 */border-radius: 20px;
}/* 验证码按钮样式配置 */
.codeBtn {width: 100px;margin-left: 10px;
}
/* 按钮和图片宽度100px */
.codeBtn:deep(.el-button),
.codeBtn:deep(img) {width: 100px;/* height: 40px; */
}
/* 验证码图片高度 */
.codeBtn:deep(img) {height: 40px;/* 鼠标移上去会变成手型 */cursor: pointer;
}/* 这一行宽度占满 */
.loginLine {width: 100%;
}
</style>

14.6 页面效果展示

访问浏览器页面
在这里插入图片描述
点击获取验证码
在这里插入图片描述
到redis中查看验证码
在这里插入图片描述

输入验证码,验证码60秒后过期
(图片验证码随便输入)
在这里插入图片描述
登录成功,完成跳转,此时未实现主页组件,此为假地址,故为空白页
在这里插入图片描述

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

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

15.1 后端接口

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

login/login

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

15.2 代码实现

如下
UsernameForm.vue

<script setup lang="ts">import { ref,reactive, onMounted } from 'vue'
// 引入状态存储store
import { useStore } from 'vuex'
// 引入路由工具router
import { useRouter } from 'vue-router'// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'// 登录表单的实例// let loginFormRef = ref(null);let loginFormRef = ref();// 登录表单的数据const loginForm = reactive({// 用户名username: '',// 密码password: '',// 图片验证imgcode: '',// 记住用户名,默认否saveUsername: false,// 记住用户名,默认否savePassword: false});// 登录验证规则const rules = ({username:[{required: true,message: '请输入用户名',trigger: 'blur'}],password:[{required: true,message: '请输入密码',trigger: 'blur'}],imgcode:[{required: true,message: '请输入图片验证码',trigger: 'blur'}]});const formSize = ({});// 图片验证码路径let imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;// const imgCodeSrc = '../../../assets/code.png';// 刷新图片验证码const getImgCode = () => {// 后续改为从服务器上获取动态图片imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;};// 全局状态存储const store = useStore();// 路由调用const router = useRouter();// 登录提交事件const onSubmit = () => {// form表单中的值,校验,loginFormRef.value.validate((valid:string, fileds:any)=>{// 如果valid值为假,则遍历输出报错if(!valid){for(let key in fileds){// 获取报错信息中的字段对应的key的索引为0的信息utils.showError(fileds[key][0].message);}return;}// 登录表单的记住用户名如果被勾选if(loginForm.saveUsername){// 保存输入的用户名utils.saveData('username', loginForm.username);// 保存被勾选的操作utils.saveData('saveUsername', loginForm.saveUsername);}else{// 如果记住用户名的勾选取消,则移除这两个存储的内容utils.removeData('username');utils.removeData('saveUsername');}// 登录表单的记住用户名如果被勾选if(loginForm.savePassword){// 保存输入的用户名utils.saveData('password', loginForm.password);// 保存被勾选的操作utils.saveData('savePassword', loginForm.savePassword);}else{// 如果记住用户名的勾选取消,则移除这两个存储的内容utils.removeData('password');utils.removeData('savePassword');}// TODO 调用接口登录utils.showLoadding("正在加载中");api({method: 'get',url: '/login/login',params: {username: loginForm.username,password: loginForm.password}}).then((res)=>{utils.hideLoadding();if(res.status != 200 || res.data.result != 200){utils.showError("登录失败-请求数据返回有误");return;}if(res.data.login == 1){utils.showSuccess("登录成功");// 存储用户信息let userInfoLogin = res.data.login;// let token = res.data.token;store.commit('setUserInfo',userInfoLogin);// 登录成功后跳转主页router.push('/index');}else if(res.data.login == 0){utils.showError("登录失败-用户不存在");return;}else if(res.data.login == 2){utils.showError("登录失败-密码错误");return;}// utils.showError("登录失败-返回数据错误")}).catch((error)=>{console.log(error);utils.showError("登录失败-发生异常");});// 登录成功提示// utils.showSuccess("登录成功");});};// 挂载onMounted(() => {// 获取记住用户名的值loginForm.saveUsername = utils.getData('saveUsername');// 如果记住用户名被勾选,则获取用户名显示if(loginForm.saveUsername){loginForm.username = utils.getData('username');}// 获取记住密码的值loginForm.savePassword = utils.getData('savePassword');// 如果记住密码被勾选,则获取密码if(loginForm.saveUsername){loginForm.password = utils.getData('password');}});</script><template><!-- 用户密码登录 --><div class="usernameLoginBox"><el-form ref="loginFormRef"style="max-width: 600px":model="loginForm":rules="rules"label-width="0"class="loginFrom":size="formSize" status-icon><!-- 用户名 --><el-form-item prop="username"><!-- 图标设置,动态绑定username,提示信息,设置输入框大小 --><el-input prefix-icon="UserFilled" v-model="loginForm.username" placeholder="请输入用户名" size='large' /></el-form-item><!-- 密码 --><el-form-item prop="password"><!-- 密码 --><div class="flexItem" ><!-- show-password 属性表示是否显示切换显示密码的图标,true为显示 --><el-input prefix-icon="Lock" show-password="off" type="password" v-model="loginForm.password" placeholder="请输入密码" size='large' /></div></el-form-item><!-- 图片验证 --><el-form-item prop="imgcode"><!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 --><div class="flex loginLine"><div class="flexItem" ><el-input prefix-icon="Picture" v-model="loginForm.imgcode" placeholder="请输入图片验证码" size='large' /><!-- <el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' /> --></div><div class="codeBtn" ><el-image  :src="imgCodeSrc" size='large' @click="getImgCode" ></el-image><!-- <el-button type="primary" size="large" @click="getSmsCode" class="" >获取验证码</el-button> --></div></div></el-form-item><!-- <el-form-item prop="saveUsername"> --><el-form-item><!-- 记住账号密码的勾选 --><div class="flex loginLine" ><!-- 记住用户名 --><div class="flexItem" ><el-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox></div><!-- 记住密码 --><div class="flexItem" ><el-checkbox v-model="loginForm.savePassword">记住密码</el-checkbox></div></div></el-form-item><el-form-item prop="savePassword"></el-form-item><!-- 登录按钮 --><el-form-item><el-button class="loginBtn" type="danger" size='large' @click="onSubmit">登录</el-button></el-form-item></el-form></div></template><style scoped>/* 按钮宽度设为最大 */.loginBtn{width: 100%;/* 登录按钮圆角边框 */border-radius: 20px;}/* 验证码按钮样式配置 */.codeBtn{width: 100px;margin-left: 10px;}/* 按钮和图片宽度100px */.codeBtn:deep(.el-button),.codeBtn:deep(img){width: 100px;/* height: 40px; */}/* 验证码图片高度 */.codeBtn:deep(img){height: 40px;/* 鼠标移上去会变成手型 */cursor: pointer;}/* 这一行宽度占满 */.loginLine{width: 100%}</style>

15.3 页面效果展示

浏览器页面展示账号密码登录
填写数据,账号密码是提前定好的,图片验证码随便输入
在这里插入图片描述
登录,跳转到指定页面
在这里插入图片描述

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

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

16.1 接口地址

生成二维码的接口

login/qr/generateQrCodeAsFile

参数 无
请求方法 post

16.2 代码实现

QcodeForm.vue

<script setup lang="ts">import { ref,reactive, onMounted, onUnmounted } from 'vue'// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'// 引入store
import {useStore} from 'vuex'
// 引入router
import {useRouter} from 'vue-router'const store = useStore();const router = useRouter();// 二维码// let qcodePath:any = null;// 二维码对应的token, 用于判断当前二维码是否已经被扫码登录let qrToken:string = "";// 第一次获取验证码// api({//     method: 'post',//     url: 'login/qr/generateQrCodeAsFile'// }).then((res)=>{// if(res.data.result != 200){// utils.showError("登录失败");// }// utils.showSuccess("登录成功");//     qcodePath = res.data.data//     qrToken = res.data.token// });// qcodePath = 'E:\\WORKPROJECTS\\MySelfPro\\hslb-general-management-system\\src\\main\resources\\login_qr_pngs\\QRCode.png';// 二维码let qcodeSrc = new URL("../../../assets/hslb-qcode.png", import.meta.url).href;// let qcodeSrc = new URL(qcodePath, import.meta.url).href;// let qcodeSrc = qcodePath;const qcodeToken = ref('');// 当前定时器事件const curTime = ref(0);let timer:any = null;// 后台更新获取二维码const loadQcode = () => {// 后续改为从服务器上获取动态图片api({method: 'post',url: 'login/qr/generateQrCodeAsFile'}).then((res)=>{// if(res.data.result != 200){// utils.showError("登录失败");// }// utils.showSuccess("登录成功");// qcodePath = res.data.dataqrToken = res.data.token});qcodeSrc = new URL("../../../assets/hslb-qcode.png", import.meta.url).href;// qcodeSrc = new URL(qcodePath, import.meta.url).href;// qcodeSrc = qcodePath;// 初始化token的值qcodeToken.value = qrToken;// 设定定时时间// curTime.value = 60;// 为了让二维码失效的效果及时,这里暂时设置10秒,后续改回60秒即可curTime.value = 10;// 定义定时器,倒计时timer = setInterval(() => {curTime.value--;// 这里获取toekn,校验是否已经被登陆过checkLogin();if(curTime.value<=0){// 事件为0则清空定时器clearInterval(timer);timer = null;}}, 1000);};// 登录提交事件// const onSubmit = () => {// };// 挂载onMounted(() => {// 获取二维码loadQcode();});// 清空计时器onUnmounted(()=>{timer && clearInterval(timer);});// 使用qcodeToken判断当前二维码是否已经被扫码登录const checkLogin = () => {// TODOapi({method: 'post',url: 'login/qr/generateQrCodeAsFile',params: {}}).then((res)=>{if(res.data.token){utils.showSuccess("登录成功");store.commit('setUserInfo',res.data.token);router.push('/index1');}// res.data.token;}).catch((error)=>{console.log(error);// utils.showError("登录失败")});}</script><template><!-- 扫码登录 --><div class="qcodeLoginBox"><div class="qcodeBox" ><img class="qcodeImg" :class="{'endImg':curTime<=0}" :src="qcodeSrc" alt="无法获取二维码,请联系客服解决"><div v-if="curTime<=0" class="endBox" @click="loadQcode" >当前二维码失效,点击重新加载{{ curTime }}</div></div><div class="tipInfo" >使用微信或移动端扫码登录 此二维码将在{{ curTime }}秒后刷新</div></div></template><style scoped>/* 二维码窗口样式 */.qcodeBox{width: 80%;height: 80%;position: relative;/* 边框自动 */margin: 0 auto;   }/* 二维码图片样式 */.qcodeBox .qcodeImg{width: 100%;height: 100%;}.qcodeBox .endBox{width: 100%;height: 100%;/* 悬浮显示 */position: absolute;/* 靠左 *//* left: 0%; *//* 靠上 */top: 0;/* 居中 *//* text-align: center; *//* 字体大小 */font-size: 14px;/* 字体颜色 */color: red;display: flex;/* 上下居中 */align-items: center;/* justify-items: center; *//* 左右居中 */justify-content: center;/* 背景色为灰色 */background-color: #00000055;}/* .endImg{filter: brightness(10%);} *//* 提示信息样式 */.tipInfo{/* 行高 */line-height: 30px;/* 字体大小 */font-size: 14px;/* 居中 */text-align: center;/* 颜色 */color: var(--el-text-color-placeholder);}</style>

16.3 页面效果展示

现在自动校验通过,后续实现逻辑
在这里插入图片描述
成功后跳转到新的页面

在这里插入图片描述

17. 登录缓存验证的实现

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

17.1 三种登录方式代码改写

在校验之前需要先在登录的时候将token存储到本地的LocalStorage缓存中,以下为改写后的三种登录方式的代码
PhoneForm.vue

<script setup lang="ts">
import { ref, reactive, onMounted, onUnmounted } from 'vue'// 引入状态存储工具store
import {useStore} from 'vuex'// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'
// 路由引入
import { useRoute, useRouter } from 'vue-router';// 登录表单的实例
// let loginFormRef = ref(null);
let loginFormRef = ref()
// 登录表单的数据
const loginForm = reactive({// 用户名username: '',// 手机验证码smscode: '',// 图片验证imgcode: '',// 记住用户名,默认否saveUsername: false
})// 登录验证规则
const rules = {username: [{required: true,message: '请输入用户名',trigger: 'blur'}],smscode: [{required: true,message: '请输入短信验证码',trigger: 'blur'}],imgcode: [{required: true,message: '请输入图片验证码',trigger: 'blur'}]
}const formSize = {}// 图片验证码路径
let imgCodeSrc = new URL('../../../assets/code.png', import.meta.url).href
// const imgCodeSrc = '../../../assets/code.png';// 刷新图片验证码
const getImgCode = () => {// 后续改为从服务器上获取动态图片imgCodeSrc = new URL('../../../assets/code.png', import.meta.url).href
}// 定时器
let timer: any = null
// 获取短信验证码的间隔时间
let curTime = 0
// 获取短信验证码按钮的文本显示内容
let smsCodeBtnText = ref('获取验证码')// 获取短信验证码
const getSmsCode = () => {// 当点击获取短信验证码时,如果其他信息没填则提示输入if (!loginForm.username) {utils.showError('请输入用户名')return}// if(!loginForm.smscode){//     utils.showError('请输入短信验证码');//     return;// }// TODO 从后台获取短信验证码// 调用接口生成短信验证码// 1 直接使用axios请求后端完整地址请求
//   axios({
//     method: 'post',
//     url: 'http://127.0.0.1:8888/login/redis/setMessageCode',
//     // url: 'login/redis/setMessageCode',
//     // 这里需要注意,不管请求方式是什么,这里是根据后端传参方式来定的,如果后端使用@RequestParam则这里使用params作为key
//     params: {
//       username: loginForm.username
//     }
//   });// 2 使用axios实例传参请求后端接口地址的用法  api({method: 'post',url: '/login/redis/setMessageCode',params: {username: loginForm.username}})curTime = 60timer = setInterval(() => {curTime--;smsCodeBtnText.value = curTime + '秒后重新获取';if (curTime <= 0) {smsCodeBtnText.value = '获取验证码'clearInterval(timer)// 清除时,值为空,防止重复点击触发多次timer = null}}, 1000)
}// 状态存储的store
const store = useStore();
// 路由,转到指定页面,使用push
const route = useRoute();
const router = useRouter();// 登录提交事件
const onSubmit = () => {// form表单中的值,校验,loginFormRef.value.validate((valid: string, fileds: any) => {// 如果valid值为假,则遍历输出报错if (!valid) {for (let key in fileds) {// 获取报错信息中的字段对应的key的索引为0的信息utils.showError(fileds[key][0].message)}return}// 登录表单的记住用户名如果被勾选if (loginForm.saveUsername) {// 保存输入的用户名utils.saveData('username', loginForm.username)// 保存被勾选的操作utils.saveData('saveUsername', loginForm.saveUsername)} else {// 如果记住用户名的勾选取消,则移除这两个存储的内容utils.removeData('username')utils.removeData('saveUsername')}// TODO 调用接口登录// 因为太快了,所以可能看不到效果,可以将下方的hideLoading方法注掉,可以看到效果utils.showLoadding('正在加载中')api({method: 'get',url: '/login/redis/getMessageCode',params: {username: loginForm.username,smscode: loginForm.smscode// imgcode: loginForm.imgcode}}).then((res) => {utils.hideLoadding()console.log(res)// console.log(res.status)// if (!res || res.status != 200 || !res.data || res.data.result != 200 || !res.data.data) {if (res.status != 200 || res.data.result != 200 || !res.data.msgCode) {utils.showError('登录失败-请求数据返回有误');return;}// console.log(res.data.data, loginForm.smscode);if(res.data.msgCode == loginForm.smscode){utils.showSuccess('登陆成功')// 存储用户token信息并转到主页// let userInfo = res.data.datalet userInfo = res.datalet token = res.data.token// 状态数据存储store.commit('setUserInfo', userInfo);store.commit('setToken', token);// 登录成功后将页面转到主页router.push('/HomeIndex')}else if(res.data.msgCode != loginForm.smscode){utils.showError('登录失败-验证码错误');return;}// utils.showError('登录失败')}).catch((error) => {// utils.hideLoadding();console.log(error);utils.showError('登录失败-出现异常')})// api.post("/api/login/code",{//     username: loginForm.username,//     smscode: loginForm.smscode,//     imgcode: loginForm.imgcode// }).then((res)=>{//     utils.hideLoadding();//     console.log(res);//     console.log(res.status);//     if(!res || res.status != 200 || !res.data || res.data.code != 8888 || !res.data.data){//         if(res.data.message){//             utils.showError(res.data.message);//             return;//         }//         utils.showError('登录失败');//         return;//     }//     // 存储用户token信息并转到主页//     let userInfo = res.data.data;//     let token = res.data.token;//     utils.showSuccess('登陆成功');// }).catch((error)=>{//     // utils.hideLoadding();//     utils.showError('登录失败');// });// 登录成功信息提示// utils.showSuccess("登录成功");})
}// 挂载
onMounted(() => {// 获取记住用户名的值loginForm.saveUsername = utils.getData('saveUsername')// 如果记住用户名被勾选,则获取用户名显示if (loginForm.saveUsername) {loginForm.username = utils.getData('username')}
})// 清空定时器
onUnmounted(() => {timer && clearInterval(timer)
})
</script><template><!-- 手机验证码登录 --><div class="phoneCodeLoginBox"><el-formref="loginFormRef"style="max-width: 600px":model="loginForm":rules="rules"label-width="0"class="loginFrom":size="formSize"status-icon><!-- 用户名 --><el-form-item prop="username"><!-- 图标设置,动态绑定username,提示信息,设置输入框大小 --><el-inputprefix-icon="UserFilled"v-model="loginForm.username"placeholder="请输入用户名"size="large"/></el-form-item><!-- 短信验证 --><el-form-item prop="smscode"><!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 --><div class="flex loginLine"><div class="flexItem"><el-inputprefix-icon="Iphone"v-model="loginForm.smscode"placeholder="请输入验证码"size="large"/></div><div class="codeBtn"><el-button type="primary" size="large" @click="getSmsCode" :disabled="curTime > 0">{{smsCodeBtnText}}</el-button></div></div></el-form-item><!-- 图片验证 --><el-form-item prop="imgcode"><!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 --><div class="flex loginLine"><div class="flexItem"><el-inputprefix-icon="Picture"v-model="loginForm.imgcode"placeholder="请输入图片验证码"size="large"/><!-- <el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' /> --></div><div class="codeBtn"><el-image :src="imgCodeSrc" size="large" @click="getImgCode"></el-image><!-- <el-button type="primary" size="large" @click="getSmsCode" class="" >获取验证码</el-button> --></div></div></el-form-item><!-- 记住用户名 --><el-form-item prop="saveUsername"><el-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox></el-form-item><!-- 登录按钮 --><el-form-item><el-button class="loginBtn" type="danger" size="large" @click="onSubmit">登录</el-button></el-form-item></el-form></div>
</template><style scoped>
/* 按钮宽度设为最大 */
.loginBtn {width: 100%;/* 登录按钮圆角边框 */border-radius: 20px;
}/* 验证码按钮样式配置 */
.codeBtn {width: 100px;margin-left: 10px;
}
/* 按钮和图片宽度100px */
.codeBtn:deep(.el-button),
.codeBtn:deep(img) {width: 100px;/* height: 40px; */
}
/* 验证码图片高度 */
.codeBtn:deep(img) {height: 40px;/* 鼠标移上去会变成手型 */cursor: pointer;
}/* 这一行宽度占满 */
.loginLine {width: 100%;
}
</style>

QcodeForm.vue

<script setup lang="ts">import { ref,reactive, onMounted, onUnmounted } from 'vue'// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'// 引入store
import {useStore} from 'vuex'
// 引入router
import {useRouter} from 'vue-router'const store = useStore();const router = useRouter();// 二维码// let qcodePath:any = null;// 二维码对应的token, 用于判断当前二维码是否已经被扫码登录let qrToken:string = "";// 第一次获取验证码// api({//     method: 'post',//     url: 'login/qr/generateQrCodeAsFile'// }).then((res)=>{// if(res.data.result != 200){// utils.showError("登录失败");// }// utils.showSuccess("登录成功");//     qcodePath = res.data.data//     qrToken = res.data.token// });// qcodePath = 'E:\\WORKPROJECTS\\MySelfPro\\hslb-general-management-system\\src\\main\resources\\login_qr_pngs\\QRCode.png';// 二维码let qcodeSrc = new URL("../../../assets/hslb-qcode.png", import.meta.url).href;// let qcodeSrc = new URL(qcodePath, import.meta.url).href;// let qcodeSrc = qcodePath;const qcodeToken = ref('');// 当前定时器事件const curTime = ref(0);let timer:any = null;let username:string = utils.getData("username");const qrString = "100100100222";// 后台更新获取二维码const loadQcode = () => {// 后续改为从服务器上获取动态图片// const qrString = "100100100222";console.log("9999999====== "+qrString);// let username:string = utils.getData("username");api({method: 'post',url: 'login/qr/generateQrCodeAsFile',params: {username: username,qrContent: qrString}}).then((res)=>{// if(res.data.result != 200){// utils.showError("登录失败");// }// utils.showSuccess("登录成功");// qcodePath = res.data.dataqrToken = res.data.token});qcodeSrc = new URL("../../../assets/hslb-qcode.png", import.meta.url).href;// qcodeSrc = new URL(qcodePath, import.meta.url).href;// qcodeSrc = qcodePath;// 初始化token的值qcodeToken.value = qrToken;// 设定定时时间// curTime.value = 60;// 为了让二维码失效的效果及时,这里暂时设置10秒,后续改回60秒即可curTime.value = 10;// 定义定时器,倒计时timer = setInterval(() => {curTime.value--;// 这里获取toekn,校验是否已经被登陆过checkLogin();if(curTime.value<=0){// 事件为0则清空定时器clearInterval(timer);timer = null;}}, 1000);};// 登录提交事件// const onSubmit = () => {// };// 挂载onMounted(() => {// 获取二维码loadQcode();});// 清空计时器onUnmounted(()=>{timer && clearInterval(timer);});// 使用qcodeToken判断当前二维码是否已经被扫码登录const checkLogin = () => {// TODOapi({method: 'post',url: 'login/qr/generateQrCodeAsFile',params: {username: username,qrContent: qrString}}).then((res)=>{if(res.data.token){utils.showSuccess("登录成功");store.commit('setUserInfo',res.data);store.commit('setToken',res.data.token);router.push('/HomeIndex');}// res.data.token;}).catch((error)=>{console.log(error);// utils.showError("登录失败")});}</script><template><!-- 扫码登录 --><div class="qcodeLoginBox"><div class="qcodeBox" ><img class="qcodeImg" :class="{'endImg':curTime<=0}" :src="qcodeSrc" alt="无法获取二维码,请联系客服解决"><div v-if="curTime<=0" class="endBox" @click="loadQcode" >当前二维码失效,点击重新加载{{ curTime }}</div></div><div class="tipInfo" >使用微信或移动端扫码登录 此二维码将在{{ curTime }}秒后刷新</div></div></template><style scoped>/* 二维码窗口样式 */.qcodeBox{width: 80%;height: 80%;position: relative;/* 边框自动 */margin: 0 auto;   }/* 二维码图片样式 */.qcodeBox .qcodeImg{width: 100%;height: 100%;}.qcodeBox .endBox{width: 100%;height: 100%;/* 悬浮显示 */position: absolute;/* 靠左 *//* left: 0%; *//* 靠上 */top: 0;/* 居中 *//* text-align: center; *//* 字体大小 */font-size: 14px;/* 字体颜色 */color: red;display: flex;/* 上下居中 */align-items: center;/* justify-items: center; *//* 左右居中 */justify-content: center;/* 背景色为灰色 */background-color: #00000055;}/* .endImg{filter: brightness(10%);} *//* 提示信息样式 */.tipInfo{/* 行高 */line-height: 30px;/* 字体大小 */font-size: 14px;/* 居中 */text-align: center;/* 颜色 */color: var(--el-text-color-placeholder);}</style>

UserLogin.vue

<script setup lang="ts">import { ref,reactive, onMounted } from 'vue'
// 引入状态存储store
import { useStore } from 'vuex'
// 引入路由工具router
import { useRouter } from 'vue-router'// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'// 登录表单的实例// let loginFormRef = ref(null);let loginFormRef = ref();// 登录表单的数据const loginForm = reactive({// 用户名username: '',// 密码password: '',// 图片验证imgcode: '',// 记住用户名,默认否saveUsername: false,// 记住用户名,默认否savePassword: false});// 登录验证规则const rules = ({username:[{required: true,message: '请输入用户名',trigger: 'blur'}],password:[{required: true,message: '请输入密码',trigger: 'blur'}],imgcode:[{required: true,message: '请输入图片验证码',trigger: 'blur'}]});const formSize = ({});// 图片验证码路径let imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;// const imgCodeSrc = '../../../assets/code.png';// 刷新图片验证码const getImgCode = () => {// 后续改为从服务器上获取动态图片imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;};// 全局状态存储const store = useStore();// 路由调用const router = useRouter();// 登录提交事件const onSubmit = () => {// form表单中的值,校验,loginFormRef.value.validate((valid:string, fileds:any)=>{// 如果valid值为假,则遍历输出报错if(!valid){for(let key in fileds){// 获取报错信息中的字段对应的key的索引为0的信息utils.showError(fileds[key][0].message);}return;}// 登录表单的记住用户名如果被勾选if(loginForm.saveUsername){// 保存输入的用户名utils.saveData('username', loginForm.username);// 保存被勾选的操作utils.saveData('saveUsername', loginForm.saveUsername);}else{// 如果记住用户名的勾选取消,则移除这两个存储的内容utils.removeData('username');utils.removeData('saveUsername');}// 登录表单的记住用户名如果被勾选if(loginForm.savePassword){// 保存输入的用户名utils.saveData('password', loginForm.password);// 保存被勾选的操作utils.saveData('savePassword', loginForm.savePassword);}else{// 如果记住用户名的勾选取消,则移除这两个存储的内容utils.removeData('password');utils.removeData('savePassword');}// TODO 调用接口登录utils.showLoadding("正在加载中");api({method: 'get',url: '/login/login',params: {username: loginForm.username,password: loginForm.password}}).then((res)=>{utils.hideLoadding();if(res.status != 200 || res.data.result != 200){utils.showError("登录失败-请求数据返回有误");return;}if(res.data.login == 1){utils.showSuccess("登录成功");// 存储用户信息// let userInfoLogin = res.data.login;let userInfoLogin = res.data;let token = res.data.token;console.log("usernamelogin:", token);store.commit('setUserInfo', userInfoLogin);store.commit('setToken', token);console.log("----------------token: ", token);// 登录成功后跳转主页router.push('/HomeIndex');}else if(res.data.login == 0){utils.showError("登录失败-用户不存在");return;}else if(res.data.login == 2){utils.showError("登录失败-密码错误");return;}// utils.showError("登录失败-返回数据错误")}).catch((error)=>{console.log(error);utils.showError("登录失败-发生异常");});// 登录成功提示// utils.showSuccess("登录成功");});};// 挂载onMounted(() => {// 获取记住用户名的值loginForm.saveUsername = utils.getData('saveUsername');// 如果记住用户名被勾选,则获取用户名显示if(loginForm.saveUsername){loginForm.username = utils.getData('username');}// 获取记住密码的值loginForm.savePassword = utils.getData('savePassword');// 如果记住密码被勾选,则获取密码if(loginForm.saveUsername){loginForm.password = utils.getData('password');}});</script><template><!-- 用户密码登录 --><div class="usernameLoginBox"><el-form ref="loginFormRef"style="max-width: 600px":model="loginForm":rules="rules"label-width="0"class="loginFrom":size="formSize" status-icon><!-- 用户名 --><el-form-item prop="username"><!-- 图标设置,动态绑定username,提示信息,设置输入框大小 --><el-input prefix-icon="UserFilled" v-model="loginForm.username" placeholder="请输入用户名" size='large' /></el-form-item><!-- 密码 --><el-form-item prop="password"><!-- 密码 --><div class="flexItem" ><!-- show-password 属性表示是否显示切换显示密码的图标,true为显示 --><el-input prefix-icon="Lock" show-password="off" type="password" v-model="loginForm.password" placeholder="请输入密码" size='large' /></div></el-form-item><!-- 图片验证 --><el-form-item prop="imgcode"><!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 --><div class="flex loginLine"><div class="flexItem" ><el-input prefix-icon="Picture" v-model="loginForm.imgcode" placeholder="请输入图片验证码" size='large' /><!-- <el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' /> --></div><div class="codeBtn" ><el-image  :src="imgCodeSrc" size='large' @click="getImgCode" ></el-image><!-- <el-button type="primary" size="large" @click="getSmsCode" class="" >获取验证码</el-button> --></div></div></el-form-item><!-- <el-form-item prop="saveUsername"> --><el-form-item><!-- 记住账号密码的勾选 --><div class="flex loginLine" ><!-- 记住用户名 --><div class="flexItem" ><el-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox></div><!-- 记住密码 --><div class="flexItem" ><el-checkbox v-model="loginForm.savePassword">记住密码</el-checkbox></div></div></el-form-item><el-form-item prop="savePassword"></el-form-item><!-- 登录按钮 --><el-form-item><el-button class="loginBtn" type="danger" size='large' @click="onSubmit">登录</el-button></el-form-item></el-form></div></template><style scoped>/* 按钮宽度设为最大 */.loginBtn{width: 100%;/* 登录按钮圆角边框 */border-radius: 20px;}/* 验证码按钮样式配置 */.codeBtn{width: 100px;margin-left: 10px;}/* 按钮和图片宽度100px */.codeBtn:deep(.el-button),.codeBtn:deep(img){width: 100px;/* height: 40px; */}/* 验证码图片高度 */.codeBtn:deep(img){height: 40px;/* 鼠标移上去会变成手型 */cursor: pointer;}/* 这一行宽度占满 */.loginLine{width: 100%}</style>

17.2 页面缓存校验

在App.vue中编写代码,进行token的校验
App.vue

<script setup lang="ts">
import { onMounted } from 'vue';
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
import utils from './utils/utils';
import api from './api/api';// // // 引入暗黑主题的动态切换
// import { useDark, useToggle } from '@vueuse/core'// const isDark = useDark()
// // // 切换主题函数
// const toggleDark = useToggle(isDark)// 状态存储
let store = useStore();// 路由使用
const router = useRouter();onMounted(()=>{// let tt = localStorage.getItem("token");// console.log("tt: ",tt);console.log("=== ===");let token = "";// 由于token可能返回undefined报错,需要进行报错处理try {token = utils.getData("token");} catch (error) {error;}console.log("store-token",token);let userInfo = utils.getData('userInfo');if(token && userInfo){console.log("token userInfo :",token," -- ", userInfo);// 登录成功,验证utils.showLoadding("正在加载")const username = utils.getData('username');if(!username){// 登录失败,跳转到登录页// token验证失败utils.showError("用户名过期-请重新登录");router.push('/UserLogin');utils.hideLoadding();}else{console.log("username-", username);api.get('/login/tokenCheck',{params:{username}}).then((res)=>{console.log("res.data.token",res.data);utils.hideLoadding();if(res.data.token==token){// 登陆成功// store.commit('setUserInfo', userInfo);// store.commit('setToken', token);router.push('/HomeIndex');utils.showSuccess("登录成功");}else{// 登录失败utils.showError("Token已过期,请重新登录");// 登录失败,跳转到登录页router.push('/UserLogin');}});utils.hideLoadding();}}else{// 登录失败,跳转到登录页utils.showError("用户登录缓存过期,请重新登录");router.push('/UserLogin');utils.hideLoadding();}});</script><template><!-- 暗黑主题动态切换按钮实现 --><!-- <button @click="toggleDark()"><i inline-block align-middle i="dark:carbon-moon carbon-sun"/><span class="ml-2">{{ isDark ? 'Dark' : 'Light' }}</span></button> --><RouterView></RouterView></template><style scoped></style>

17.3 主页代码

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

<script setup lang="ts">import utils from '@/utils/utils';
import { onMounted } from 'vue';</script><template>后台主页
</template><style scoped>
</style>

17.4 路由代码修改

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


import { createRouter, createWebHistory } from 'vue-router'// 自定义路由组件或从其他文件导入,这里选择从其他文件导入
import UserLogin from "../views/login/UserLogin.vue";
// import UserLogin from '@/views/login/UserLogin.vue';
import HomeIndex from '@/views/index/HomeIndex.vue';// 定义一些路由,每个路由都需要映射到一个组件,
const routes = [{path: '/',// component: UserLoginredirect: "/HomeIndex"},{path: '/UserLogin',component: UserLogin},{path: '/HomeIndex',component: HomeIndex}
]// 创建路由实例并传递‘routes’配置 你可以在这里输入更多的配置
const router = createRouter({history: createWebHistory(),// routes:routes可以简写成routes,不会报错// routes:[]routes
})export default router

18. 项目的源码下载地址

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

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

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


感谢阅读,祝君暴富!


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

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

相关文章

【Windows学习笔记】1:OneCore和Windows API

1 OneCore 主流Windows跑在PC上&#xff0c;Xbox跑的是从Windows 2000分支出来的版本&#xff0c;Windows Phone跑的是基于Windows CE&#xff08;实时操作系统&#xff09;的版本。 为了维护和扩展这些code方便&#xff0c;微软将kernels和base platform集成在一起叫OneCore…

宿舍|基于SprinBoot+vue的宿舍管理系统(源码+数据库+文档)

宿舍管理系统 基于SprinBootvue的私人诊所管理系统 一、前言 二、系统设计 三、系统功能设计 系统功能实现 后台模块实现 管理员功能实现 学生功能实现 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&…

【计算机组成原理】六、总线:3.操作和定时

5.操作和定时 文章目录 5.操作和定时5.1总线传输的四个阶段5.2总线定时5.2.1同步通信5.2.2异步通信5.2.3半同步通信5.2.4分离式通信 2.3按时序控制方式 同步总线异步总线 5.1总线传输的四个阶段 总线周期&#xff1a; 申请分配阶段&#xff1a;由需要使用总线的主模块&#…

【C++11及其特性】左值和右值

左值和右值目录 一.左值和右值的报错1.简单定义2.函数返回值作左值3.表达式作左值 二.存储的层次结构1.CPU2.内存3.磁盘4.三者联系5.寄存器 三.左值和右值的概念1.左值2.右值3.转换 一.左值和右值的报错 1.简单定义 赋值号’左边的为左值,右边的为右值. 2.函数返回值作左值 …

力扣刷题(3)

整数反转 整数反转-力扣 思路&#xff1a; 利用%和/不断循环取待反转整数的最后一位&#xff0c;注意判断是否超出范围。 int reverse(int x){int y0;while(x){if(y > INT_MAX/10 || y < INT_MIN/10)return 0;int tmpx%10;yy*10tmp;x/10;}return y; }字符串转换整数 …

自动化代码报错:ElementClickInterceptedException 解决方案

在自动化测试中&#xff0c;如果有多个弹窗出现&#xff0c;代码执行可能会遇到ElementClickInterceptedException的错误&#xff0c;表明元素点击被拦截&#xff1a; 一般由于以下原因&#xff1a; 一、页面加载未完成 在页面尚未完全加载完成时尝试点击某个元素&#xff0c…

ENVI SARscape||笔记

介绍就不介绍了&#xff0c;直入主题&#xff01; 第一章 ENVI和SARscape 下载与安装&#xff1a; ENVI 5.6 软件安装包下载及安装激活教程&#xff01; (qq.com)https://mp.weixin.qq.com/s/kH0g5g9AALgDNPssfdZ8wQ 启动 ENVI 的启动模式有两种&#xff1a;ENVI和ENVIIDL&…

前端性能优化:提升网站加载速度的五个关键技巧

聚沙成塔每天进步一点点 本文回顾 ⭐ 专栏简介前端性能优化&#xff1a;提升网站加载速度的五个关键技巧1. 引言2. 前端性能优化的五个关键技巧2.1 减少HTTP请求技巧说明实现示例 2.2 启用浏览器缓存技巧说明实现示例 2.3 使用内容分发网络&#xff08;CDN&#xff09;技巧说明…

Adobe DC 2022提示无法识别的错误 - 解决方案

Adobe DC 2022提示无法识别的错误 - 解决方案 问题解决方案更改安装&#xff08;推荐&#xff09;重新安装&#xff08;推荐&#xff09;降级安装&#xff08;不推荐&#xff09; 问题 使用Adobe DC 2022合并图片创建PDF时&#xff0c;会提示无法识别的错误&#xff0c;这是因…

Mac 安装Hadoop教程

1. 引言 本教程旨在介绍在Mac 电脑上安装Hadoop&#xff0c;便于编程开发人员对大数据技术的熟悉和掌握。 2.前提条件 2.1 安装JDK 想要在你的Mac电脑上安装Hadoop&#xff0c;你必须首先安装JDK。具体安装步骤这里就不详细描述了。你可参考Mac 下载JDK8。 2.2 配置ssh环境…

2024如何开始进入美业?美业创业步骤分享|博弈美业系统管理系统源码

进入美业可以是一个令人兴奋且具有挑战性的决定。以下是一些步骤&#xff0c;希望可以帮助你在美业建立自己的职业生涯&#xff1a; 1.确定你的兴趣和专长&#xff1a; 首先要考虑你对美业的兴趣和擅长的领域&#xff0c;比如化妆、美发、美甲、美容护理等。确定自己的兴趣和优…

另一种关于类的小例

前言 我们还是以一段关于构造函数的代码作为开端&#xff0c;我们以之前银行家的小项目为背景 class Account {constructor(owner, currency, pin) {this.owner owner;this.currency currency;this.pin pin;} }const ITshare new Account(ITshare, EUR, 21211); console.…

视频:Python深度学习量化交易策略、股价预测:LSTM、GRU深度门控循环神经网络|附代码数据...

全文链接&#xff1a;https://tecdat.cn/?p37539 分析师&#xff1a;Shuo Zhang 本文以上证综指近 22 年的日交易数据为样本&#xff0c;构建深度门控循环神经网络模型&#xff0c;从股价预测和制定交易策略两方面入手&#xff0c;量化循环神经网络在股票预测以及交易策略中的…

zabbix对接Grafana

1.grafana安装 Download Grafana | Grafana Labs sudo yum install -y https://dl.grafana.com/oss/release/grafana-11.1.4-1.x86_64.rpm 2.zabbix插件安装 Grafana 默认并没有 zabbix 数据源的支持&#xff0c;只有安装了zabbix插件&#xff0c;才可以在grafana中添加zabbi…

Simulink代码生成:关系运算与逻辑运算

文章目录 1 引言2 模块使用实例2.1 关系运算2.2 关系运算 3 代码生成4 总结 1 引言 在Simulink中经常需要判断两个信号的大小关系、是否相等&#xff0c;或者判断布尔类型信号的与、或、非等。本文研究通过关系运算与逻辑运算模块实现上述需求。 2 模块使用实例 2.1 关系运算…

操作系统:实验一进程控制实验

一、实验目的 1、掌握进程的概念&#xff0c;理解进程和程序的区别。 2、认识和了解并发执行的实质。 3、学习使用系统调用fork()创建新的子进程方法&#xff0c;理解进程树的概念。 4、学习使用系统调用wait()或waitpid()实现父子进程同步。 5、学习使用getpid()和getppi…

CentOS全面停服,国产化提速,央国企信创即时通讯/协同门户如何选型?

01. CentOS停服带来安全新风险&#xff0c; 国产操作系统迎来新的发展机遇 2024年6月30日&#xff0c;CentOS 7版本全面停服&#xff0c;于2014年发布的开源类服务器操作系统——CentOS全系列版本生命周期画上了句号。国内大量基于CentOS开发和适配的服务器及平台&#xff0c…

挂个人-CSDN Java优秀内容博主rundreamsFly抄袭

事件起因 今天点开自己的CSDN博客&#xff0c;发现给我推了一篇文章抄袭我自己昨天18点发的文章。 就是这篇&#xff0c;一字不差&#xff0c;博主昵称是&#xff1a;rundreamsFly&#xff0c;账号是rundreams。 抄袭者文章 发布于2024-8-26 19:37:41秒&#xff0c;比我发布…

CoMat——解决文本与图像之间的差异

概述 论文地址&#xff1a;https://arxiv.org/abs/2404.03653 在文本到图像生成领域&#xff0c;扩散模型近年来取得了巨大成功。然而&#xff0c;提高生成图像与文本提示之间的一致性仍然是一个挑战。 论文指出&#xff0c;扩散模型中文本条件利用不足是对齐的根本原因。论…

巧用scss实现一个通用的媒介查询代码

巧用scss实现一个通用的媒介查询代码 效果展示 实现代码 <template><div class"page-root"><div class"header"></div><div class"content"><div class"car-item" v-for"item in 9">…