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


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


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


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


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


文章目录


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

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


感谢阅读,祝君暴富!


Vue 3.0是一个JavaScript框架,而Element-Plus是一个基于Vue 3.0开发的UI组件库,可以用于构建后台管理系统。开发Vue 3.0 Element-Plus的后台管理系统需要使用Vite 2.0作为构建工具,Vue-Router 4.0作为路由管理,Echarts 5.0作为数据可视化工具,以及Axios作为HTTP请求库。 要创建一个使用Vue 3.0和Element-Plus的后台管理系统,可以使用以下步骤: 1. 首先,使用命令行工具创建一个新的Vue项目,可以使用以下命令: ``` yarn create vite my-vue-app --template vue ``` 这将使用Vite模板创建一个名为"my-vue-app"的项目。 2. 安装Element-Plus包,可以使用以下命令: ``` yarn add element-plus ``` 这将安装Element-Plus UI组件库。 3. 在项目的主入口文件中引入Element-Plus并注册它,可以使用以下代码: ```javascript import { createApp } from 'vue'; import ElementPlus from 'element-plus'; import 'element-plus/dist/index.css'; const app = createApp(App); app.use(ElementPlus); ``` 4. 在需要使用Element-Plus组件的Vue文件中,可以通过引入包并配置el-config-provider来使用Element-Plus,例如: ```html <template> <el-config-provider :locale="zhCn"> <Vab-App /> </el-config-provider> </template> <script setup> import zhCn from 'element-plus/lib/locale/lang/zh-cn'; </script> ``` 这将使用中文语言配置Element-Plus,并在Vab-App组件中使用Element-Plus组件。 通过以上步骤,你就可以开始开发使用Vue 3.0和Element-Plus的后台管理系统了。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Vue 3.0+Vite 2.0+Vue-Router4.0+Element-Plus+Echarts 5.0后台管理系统](https://download.csdn.net/download/weixin_47367099/85260580)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [vue3+ElementPlus后台管理搭建](https://blog.csdn.net/qq_25286361/article/details/122132722)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寒山李白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值