基于VUE3+Layui从头搭建通用后台管理系统(前端篇)二:登录界面及对应功能实现

这篇具有很好参考价值的文章主要介绍了基于VUE3+Layui从头搭建通用后台管理系统(前端篇)二:登录界面及对应功能实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、本章内容

  本章介绍系统登录界面、登录流程、登录接口等相关内容的开发,实现包括账号密码登录、短信验证登录等不同的登录方式,使用svg-capter生成图形验证码,使用expressjwt实现登录token的生成及验证。

1. 详细课程地址: https://edu.csdn.net/course/detail/38183
2. 源码下载地址: 点击下载

二、界面预览

基于VUE3+Layui从头搭建通用后台管理系统(前端篇)二:登录界面及对应功能实现,基于VUE3+Layui管理系统实例,前端,layui,登录,jwt,登录功能,登录界面,登录流程
基于VUE3+Layui从头搭建通用后台管理系统(前端篇)二:登录界面及对应功能实现,基于VUE3+Layui管理系统实例,前端,layui,登录,jwt,登录功能,登录界面,登录流程
基于VUE3+Layui从头搭建通用后台管理系统(前端篇)二:登录界面及对应功能实现,基于VUE3+Layui管理系统实例,前端,layui,登录,jwt,登录功能,登录界面,登录流程

三、开发视频

基于VUE3+Layui从头搭建通用后台管理系统合集-登录界面框架搭建

四、登录流程说明

五、登录接口实现

5.1 依赖库安装

  安装登录相关需要依赖的相关库文件,包括验证码生成、jwt登录验证token的生成及引用等。
1. svg库安装

npm install svg-captcha --save

2. jwt库安装

npm install jsonwebtoken --save
npm install express-jwt --save

3. 引入使用

//导入用于生成JWT字符串的包
const jwt = require('jsonwebtoken');
//导入用于将客户端发送过来的JWT字符串解析还原成JSON对象的包
const expressJWT = require('express-jwt');

//使用 app.use() 注册将JWT字符串解析还原成JSON对象的中间件
//.unless() 方法通过正则表达式 指定哪些接口不需要通过权限
//正则中 '\'用来转义 '^'表示指定以什么开头的字符串
app.use(expressJWT.expressjwt({
  secret: config.secretKey,
  requestProperty: 'token',
  algorithms: ['HS256'],
  getToken(req) {
    let token = req.headers.token;
    if (token) {
      try {//自动刷新token
        let decodedToken = jwt.decode(token, { complete: true });
        if (decodedToken && decodedToken.payload && decodedToken.payload.exp) {
          console.log("初始化时间:" + new Date(decodedToken.payload.iat * 1000).toLocaleString())
          console.log("当前时间:" + new Date().toLocaleString())
          console.log("过期时间:" + new Date(decodedToken.payload.exp * 1000).toLocaleString())
          if (decodedToken.payload.exp * 1000 > new Date().getTime()) {
            if (new Date().getTime() - req.session.last < config.expiresTime * 1000) {
              //白名单中清除此token
              delete tokenData[token];
              token = jwt.sign({ account: decodedToken.payload.account }, config.secretKey, { expiresIn: config.expiresTime });
              req.headers.newToken = token;
              //将有效的token添加到白名单中
              tokenData[token] = 1;
              console.log("自动刷新token")
            }
          }
        }
      }
      catch (err) {
      }
    }
    return token;
  },
  onExpired(req, err) {
    //白名单中清除此token
    delete tokenData[req.headers.token];
    throw err;
  }
}).unless({ path: [/^\/login\//, /^\/resetpwd\//, /^\/register\//] }));

5.2 图形验证码接口

//验证码生成
const svgCaptcha = require('svg-captcha');
/**
 * 生成登录验证码
 */
router.get('/captcha', function (req, res, next) {
    let width = req.query.width;
    let height = req.query.height;
    if (!width) {
        width = 100;
    }
    if (!height) {
        height = 50;
    }
    let captcha = svgCaptcha.create({
        size: 4,  //验证码长度
        width: width, //svg宽度
        height: height, //svg高度
        noise: 5, //干扰线条数
        fontSize: 35, //字体大小
        ignoreChars: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxz',   //验证码字符中排除
        color: false // 验证码的字符是否有颜色,默认没有,如果设定了背景,则默认有           
    });
    req.session.captcha = captcha.text;
    req.session.captchaDate = new Date().getTime();
    res.type('svg');
    res.status(200).send(captcha.data);
});

5.3 账号密码登录接口

/**
 * 账号登录
 */
router.post('/password', function (req, res, next) {
    let account = req.body.account;
    let password = req.body.password;
    let captchaText = req.body.captcha;
    if (!account || !password || !captchaText) {
        res.send({
            code: 400,
            msg: '参数为空',
            data: null
        });
        return;
    }
    if (!req.session.captcha
        || req.session.captcha != captchaText
        || !req.session.captchaDate
        || (new Date().getTime() - req.session.captchaDate) > config.captchaTimeout) {
        res.send({
            code: 400,
            msg: '验证码错误',
            data: null
        });
        return;
    }
    // 登录逻辑,检测账号以及密码是否正确,登录成功后生成jwt的token信息,并返回到前端
    let userInfo = uesrData.find((item) => {
        return item.account == account && item.password == password;
    })
    if (!userInfo) {
        res.send({
            code: 400,
            msg: '账号密码错误',
            data: null
        });
        return;
    }
    //登录成功
    //在登录成功之后 调用 jwt.sign() 方法生成JWT字符串 并通过 token 属性发送给客户端
    //参数1: 用户的信息对象
    //参数2: 加密的密钥
    //参数3: 配置对象 可以配置当前 token 的有效期
    const tokenStr = jwt.sign({ account: userInfo.account }, config.secretKey, { expiresIn: config.expiresTime });
    //将有效的token添加到白名单中
    tokenData[tokenStr] = 1;
    res.setHeader('token', tokenStr)
    res.send({
        code: 200,
        msg: '',
        data: userInfo
    });
});

5.4 获取短信验证码接口


//1.获取短信验证码
router.post('/getSmsCode', function (req, res, next) {
    let account = req.body.account;
    if (!account) {
        res.send({
            code: 400,
            msg: '参数为空',
            data: null
        })
        return;
    }
    let user = userData.find((item) => {
        return item.account == account || item.phone == account;
    })
    if (!user) {
        res.send({
            code: 400,
            msg: '账号未注册',
            data: null
        })
        return;
    }
    //校验发送是否太频繁
    let lastCodes = smsCodeData.filter((item) => {
        return item.account == user.account;
    })
    if (lastCodes && lastCodes.length > 0 && (new Date().getTime() - lastCodes[lastCodes.length - 1].date) < config.smsLimtTime) {
        res.send({
            code: 400,
            msg: '发送太频繁,请稍后再试……',
            data: null
        })
        return;
    }
    //生成验证码并发送短信
    let code = parseInt(Math.random() * 1000000);
    console.log(code);
    //TODO 调用短信发送接口,发送短信
    smsCodeData.push({
        code: code,
        account: user.account,
        date: new Date().getTime(),
        type: '1'//类型:1是登录验证码,2找回密码验证
    })
    res.send({
        code: 200,
        msg: '验证码已发送,5分钟之内有效。',
        data: null
    })
});

5.5 短信验证登录接口

//2.短信验证码登录
router.post('/smsCode', function (req, res, next) {
    let account = req.body.account;
    let smscode = req.body.smscode;
    let captchaText = req.body.captcha;
    if (!account || !smscode || !captchaText) {
        res.send({
            code: 400,
            msg: '参数为空',
            data: null
        });
        return;
    }
    if (!req.session.captcha
        || req.session.captcha != captchaText
        || !req.session.captchaDate
        || (new Date().getTime() - req.session.captchaDate) > config.captchaTimeount) {
        res.send({
            code: 400,
            msg: '验证码错误',
            data: null
        });
        return;
    }
    // 登录逻辑,检测账号以及密码是否正确,登录成功后生成jwt的token信息,并返回到前端
    let userInfo = uesrData.find((item) => {
        return item.account == account || item.phone == account;
    })
    if (!userInfo) {
        res.send({
            code: 400,
            msg: '账号不存在',
            data: null
        });
        return;
    }
    //短信验证码校验
    let lastCodes = smsCodeData.filter((item) => {
        return item.account == userInfo.account && item.code == smscode && item.type == '1';
    })
    if (!lastCodes || lastCodes.length <= 0 || (new Date().getTime() - lastCodes[lastCodes.length - 1].date) > config.smsCodeTimeout) {
        res.send({
            code: 400,
            msg: '验证码已过期',
            data: null
        })
        return;
    }
    //登录成功
    //在登录成功之后 调用 jwt.sign() 方法生成JWT字符串 并通过 token 属性发送给客户端
    //参数1: 用户的信息对象
    //参数2: 加密的密钥
    //参数3: 配置对象 可以配置当前 token 的有效期
    const tokenStr = jwt.sign({ account: userInfo.account }, config.secretKey, { expiresIn: config.expiresTime });
    //将有效的token添加到白名单中
    tokenData[tokenStr] = 1;
    res.setHeader('token', tokenStr)
    res.send({
        code: 200,
        msg: '',
        data: userInfo
    });
});

5.6 后端配置文件

module.exports = {
    /**
     * token加密秘钥
     */
    secretKey: 'junjunjun23141321321',
    /**
     * token的过期时间
     */
    expiresTime: 10,
    /**
     * 短信验证码发送限制时间
     */
    smsLimtTime: 60 * 1000,
    /**
     * 验证码过期时间
     */
    captchaTimeout: 5 * 60 * 1000,
    /**
     * 短信验证码的过期时间
     */
    smsCodeTimeout: 5 * 60 * 1000,
    /**
     * 发送邮件的邮箱stmp授权码
     */
    emailserce: "*************"
}

六、登录功能实现

6.1 登录主页框架

1. html代码:

<template>
    <lay-container>
        <div class="login-header">
            <div class="header-logo">
                <img src="@/assets/logo2.png" />
            </div>
            <div class="header-btns">
                <lay-button type="danger" @click="() => router.push('/')">官方网站</lay-button>
                <lay-button type="danger" @click="() => router.push('/register')">注册账号</lay-button>
            </div>
        </div>
        <div class="login-panel">
            <div class="content-panel">
                <div class="login-logo">
                    <img src="@/assets/logo1.png" />
                </div>
                <div class="split-line"></div>
                <div class="login-content">
                    <div class="tab-items">
                        <div @click="(e) => currentSelectTab = 0" class="tab-item "
                            :class="(currentSelectTab === 0) ? 'tab-item-selected' : ''">账号密码登录</div>
                        <div @click="(e) => currentSelectTab = 1" class="tab-item"
                            :class="(currentSelectTab === 1) ? 'tab-item-selected' : ''">短信验证登录</div>
                    </div>
                    <div>
                        <UsernameLoginVue v-if="currentSelectTab === 0"></UsernameLoginVue>
                        <PhoneCodeLogin v-else></PhoneCodeLogin>
                    </div>
                </div>
            </div>
        </div>
        <div class="login-footer">版权所有:@copyright 军军君</div>
    </lay-container>
</template>

2. js代码:

<script setup>
import { ref } from "vue"

import { useRouter } from 'vue-router';
//路由信息
const router = useRouter();

import PhoneCodeLogin from "./components/PhoneCodeLogin.vue";
import UsernameLoginVue from "./components/UsernameLogin.vue";

//当前选中的tab
const currentSelectTab = ref(0);

</script>

3. css代码:

<style scoped>
.layui-container {
    padding: 0;
}

.login-panel {
    width: 100vw;
    height: 100vh;
    background: linear-gradient(159deg, #1f6fc7, #2196f3, #1177c9);
    display: flex;
    justify-items: center;
    align-items: center;
}

.content-panel {
    background: #fff;
    border-radius: 10px;
    margin: 0 auto;
    width: 50vw;
    height: 50vh;
    box-shadow: 0 0 13px 2px #7c7c7c9e;
    display: flex;
}

.login-logo {
    width: 40%;
    text-align: center;
    display: flex;
    justify-items: center;
    align-items: center;
}

.login-logo img {
    margin: 0 auto;
    width: 200px;
}

.split-line {
    width: 1px;
    height: 100%;
    background: linear-gradient(182deg, #02835300, #03a9f4, #02835300);
}

.login-content {
    flex: 1;
    padding: 20px;
    padding-top: 40px;
}

.tab-items {
    width: 300px;
    height: 35px;
    line-height: 35px;
    display: flex;
    text-align: center;
    margin: 0 auto;
    font-size: 1rem;
}

.tab-items .tab-item {
    flex: 1;
    cursor: pointer;
}

.tab-items .tab-item-selected {
    color: #e91e63;
}

.login-header {
    position: fixed;
    height: 60px;
    padding: 10px;
    display: flex;
    width: calc(100% - 20px);
}

.login-header .header-logo {
    flex: 1;
}

.login-header .header-logo img {
    height: 60px;
}

.login-footer {
    position: fixed;
    height: 40px;
    width: 100%;
    bottom: 0px;
    line-height: 40px;
    text-align: center;
    font-size: .75rem;
    color: #fff;
}

.header-btns:deep(.layui-btn-danger) {
    background: #00000052;
    border: none;
}
</style>

6.2 账号登录界面

1. html代码:

<!-- 用户名密码登录 -->
<template>
    <div class="username-login">
        <lay-form :model="loginData" ref="usernameForm" :rules="rules" required>
            <lay-form-item label="账号:" prop="account" size="lg">
                <lay-input v-model="loginData.account" placeholder="请输入账号" suffix-icon="layui-icon-username"></lay-input>
            </lay-form-item>
            <lay-form-item label="密码:" prop="password" size="lg">
                <lay-input v-model="loginData.password" type="password" placeholder="请输入密码"
                    suffix-icon="layui-icon-password"></lay-input>
            </lay-form-item>
            <lay-form-item label="验证码:" prop="captcha">
                <div class="flex">
                    <div class="flex-item">
                        <lay-input v-model="loginData.captcha" placeholder="请输入验证码"></lay-input>
                    </div>
                    <div class="captcha">
                        <img :src="captchaSrc" @click="refreshCaptcha" />
                    </div>
                </div>
            </lay-form-item>
            <lay-form-item label="">
                <div class="flex">
                    <div class="flex-item">
                        <lay-checkbox name="like" skin="primary" v-model="rememberMe" value="1" label="记住账号"></lay-checkbox>
                    </div>
                    <div class="flex-item resetpwd">
                        <router-link to="/resetpwd" style="color:#FF5722">找回密码</router-link>
                        <router-link to="/register" style="margin-left:10px;color:#16baaa">注册账号</router-link>
                    </div>
                </div>
            </lay-form-item>
            <lay-form-item style="text-align: center">
                <lay-button class="submit-btn" @click="submit" type="normal">提交</lay-button>
            </lay-form-item>
        </lay-form>
    </div>
</template>

2. js代码:

<script setup>
import { reactive, ref, onMounted } from 'vue';
import api from '@/utils/api';
import { layer } from '@layui/layer-vue'
import { useRouter } from 'vue-router';
import { useUserStore } from '@/stores/user'

// 数据状态管理
const userStore = useUserStore()
//路由信息
const router = useRouter();

//登录表单绑定的数据
const loginData = reactive({
    account: '',
    password: '',
    captcha: ''
})
//表单示例
const usernameForm = ref();
//表单检验规则
const rules = ref({
    account: {
        type: 'string'
    },
    password: {
        type: 'string'
    },
    captcha: {
        type: 'string'
    },
})
//记住密码
const rememberMe = ref("1");
/**
 * 
 */
const captchaSrc = ref("/api/login/captcha?width=120&height=36");
/**
 * 刷新验证码
 */
const refreshCaptcha = () => {
    captchaSrc.value = "/api/login/captcha?width=120&height=36&t_=" + new Date().getTime()
}
/**
 * 提交表单,完成登录
 */
const submit = () => {
    let loadding = layer.load(0)
    usernameForm.value.validate((isValidate, model, errors) => {
        if (!isValidate) {
            layer.close(loadding);
            layer.msg(errors[0].message, { icon: 2, time: 1000 })
            return;
        }
        // 调用接口完成登录,登录成功后如果是选中了记住账号,则自动保存账号密码到本地
        api.login(loginData.account, loginData.password, loginData.captcha).then((res) => {
            refreshCaptcha();
            layer.close(loadding)
            if (!res || res.status != 200 || !res.data || !res.data.data || res.data.code != 200) {
                layer.msg(res.data.msg ? res.data.msg : '登录失败', { icon: 2, time: 1000 });
                return;
            }
            let userInfo = res.data.data;
            let token = res.headers['token'];
            if (!userInfo || !token) {
                layer.msg('登录失败', { icon: 2, time: 1000 });
                return;
            }
            //检测是否记录账号,如果需要记录,则记录到本地
            localStorage.setItem("rememberMe", rememberMe.value ? '1' : '0');
            if (rememberMe.value) {
                localStorage.setItem("account", loginData.account);
                localStorage.setItem("password", loginData.password);
            }
            console.log("登录成功", userInfo, token);
            // 存储登录状态
            userStore.login(userInfo, token);
            //并转到首页
            router.push({
                path: '/'
            })
        }).catch((error) => {
            refreshCaptcha();
            layer.close(loadding)
            layer.msg(error.message, { icon: 2, time: 1000 });
        })
    });
}
onMounted(() => {
    //读取本地存储的账号信息
    let rememberMe_ = localStorage.getItem("rememberMe");
    rememberMe.value = (rememberMe_ && rememberMe_ == '1') ? '1' : 0;
    if (rememberMe.value) {//记住账号密码了,则读取账号密码
        let account_ = localStorage.getItem("account");
        let password_ = localStorage.getItem("password");
        loginData.account = account_;
        loginData.password = password_;
    }
})
</script>

3. css代码:

<style scoped>
.username-login {
    width: 90%;
    margin: 0 auto;
    padding-top: 40px;
}

.username-login:deep(.layui-form-label),
.username-login:deep(input) {
    font-size: .875rem
}

.submit-btn {
    width: 78%;
    margin-left: 108px;
    --button-border-radius: 35px
}

.captcha {
    width: 120px;
}

.captcha img {
    width: 100%;
    cursor: pointer;
}

.resetpwd {
    text-align: right;
    font-size: .875rem;
    line-height: 36px;
}
</style>

6.3 短信验证登录

1. html代码:

<!-- 短信验证码登录 -->
<template>
    <div class="phonecode-login">
        <lay-form :model="loginData" ref="phonecodeForm" :rules="rules" required>
            <lay-form-item label="账号:" prop="account" size="lg">
                <lay-input v-model="loginData.account" placeholder="请输入账号" suffix-icon="layui-icon-username"></lay-input>
            </lay-form-item>
            <lay-form-item label="短信验证码:" prop="smscode" size="lg">
                <div class="flex">
                    <div class="flex-item">
                        <lay-input v-model="loginData.smscode" type="smscode" placeholder="请输入短信验证码"
                            suffix-icon="layui-icon-password"></lay-input>
                    </div>
                    <div class="smscode">
                        <lay-button class="smscode-btn" @click="getSmsCode" type="normal" :disabled="smsCodeTime > 0">{{
                            smsCodeTime > 0 ?
                            smsCodeTime + '秒后再试' : '获取验证码'
                        }}</lay-button>
                    </div>
                </div>
            </lay-form-item>
            <lay-form-item label="图形验证码:" prop="captcha">
                <div class="flex">
                    <div class="flex-item">
                        <lay-input v-model="loginData.captcha" placeholder="请输入图形验证码"></lay-input>
                    </div>
                    <div class="captcha">
                        <img :src="captchaSrc" @click="refreshCaptcha" />
                    </div>
                </div>
            </lay-form-item>
            <lay-form-item label="">
                <div class="flex">
                    <div class="flex-item">
                        <lay-checkbox name="like" skin="primary" v-model="rememberMe" value="1" label="记住账号"></lay-checkbox>
                    </div>
                    <div class="flex-item resetpwd">
                        <router-link to="/resetpwd" style="color:#FF5722">找回密码</router-link>
                        <router-link to="/register" style="margin-left:10px;color:#16baaa">注册账号</router-link>
                    </div>
                </div>
            </lay-form-item>
            <lay-form-item style="text-align: center">
                <lay-button class="submit-btn" @click="submit" type="normal">提交</lay-button>
            </lay-form-item>
        </lay-form>
    </div>
</template>

2. js代码:

<script setup>
import { reactive, ref, onMounted, onUnmounted } from 'vue';
import api from '@/utils/api';
import { layer } from '@layui/layer-vue'
import { useRouter } from 'vue-router';
import { useUserStore } from '@/stores/user'

// 数据状态管理
const userStore = useUserStore()
//路由信息
const router = useRouter();

//登录表单绑定的数据
const loginData = reactive({
    account: '',
    smscode: '',
    captcha: ''
})
//表单示例
const phonecodeForm = ref();
//表单检验规则
const rules = ref({
    account: {
        type: 'string'
    },
    smscode: {
        type: 'string'
    },
    captcha: {
        type: 'string'
    },
})
//获取验证码的到期时间
const smsCodeTime = ref(0);
/**
 * 验证码获取倒计时
 */
let smsCodeTimer = null;
//记住密码
const rememberMe = ref("1");
/**
 * 
 */
const captchaSrc = ref("/api/login/captcha?width=120&height=36");
/**
 * 刷新验证码
 */
const refreshCaptcha = () => {
    captchaSrc.value = "/api/login/captcha?width=120&height=36&t_=" + new Date().getTime()
}
/**
 * 获取短信验证码
 */
const getSmsCode = () => {
    let account = loginData.account;
    if (!account) {
        layer.msg("账号不能为空");
        return;
    }
    if (smsCodeTime.value > 0) {
        layer.msg(smsCodeTime.value + "秒之后再试");
        return;
    }
    let loadding = layer.load(0)
    api.getSmsCode(account).then((res) => {
        layer.close(loadding);
        if (!res || res.status != 200 || !res.data || res.data.code != 200) {
            layer.msg((res.data.msg ? res.data.msg : '验证码获取异常'), { icon: 2, time: 1000 });
            return;
        }
        //启动验证码获取倒计时
        smsCodeTime.value = 60;
        smsCodeTimer = setInterval(() => {
            smsCodeTime.value--;
            if (smsCodeTime.value <= 0) {
                clearInterval(smsCodeTimer);
                smsCodeTime.value = 0;
                smsCodeTimer = null;
            }
        }, 1000);

        layer.notifiy({
            title: "提示",
            content: res.data.msg,
            icon: 1
        })
    }).catch((error) => {
        layer.close(loadding)
        layer.msg(error.message, { icon: 2, time: 1000 });
    });
}
/**
 * 提交表单,完成登录
 */
const submit = () => {
    let loadding = layer.load(0)
    phonecodeForm.value.validate((isValidate, model, errors) => {
        if (!isValidate) {
            layer.close(loadding);
            layer.msg(errors[0].message, { icon: 2, time: 1000 })
            return;
        }
        // 调用接口完成登录,登录成功后如果是选中了记住账号,则自动保存账号密码到本地
        api.loginBySmsCode(loginData.account, loginData.smscode, loginData.captcha).then((res) => {
            refreshCaptcha();
            layer.close(loadding)
            if (!res || res.status != 200 || !res.data || !res.data.data || res.data.code != 200) {
                layer.msg(res.data.msg ? res.data.msg : '登录失败', { icon: 2, time: 1000 });
                return;
            }
            let userInfo = res.data.data;
            let token = res.headers['token'];
            if (!userInfo || !token) {
                layer.msg('登录失败', { icon: 2, time: 1000 });
                return;
            }
            //检测是否记录账号,如果需要记录,则记录到本地
            localStorage.setItem("rememberMe", rememberMe.value ? '1' : '0');
            if (rememberMe.value) {
                localStorage.setItem("account", loginData.account);
            }
            console.log("登录成功", userInfo, token);
            // 存储登录状态
            userStore.login(userInfo, token);
            //并转到首页
            router.push({
                path: '/'
            })
        }).catch((error) => {
            refreshCaptcha();
            layer.close(loadding)
            layer.msg(error.message, { icon: 2, time: 1000 });
        })
    });
}
onMounted(() => {
    //读取本地存储的账号信息
    let rememberMe_ = localStorage.getItem("rememberMe");
    rememberMe.value = (rememberMe_ && rememberMe_ == '1') ? '1' : 0;
    if (rememberMe.value) {//记住账号密码了,则读取账号密码
        let account_ = localStorage.getItem("account");
        loginData.account = account_;
    }
})
onUnmounted(() => {
    //停止定时器
    smsCodeTimer && clearInterval(smsCodeTimer);
})
</script>

3. css代码:

<style scoped>
.phonecode-login {
    width: 90%;
    margin: 0 auto;
    padding-top: 40px;
}

.phonecode-login:deep(.layui-form-label),
.phonecode-login:deep(input) {
    font-size: .875rem
}

.submit-btn {
    width: 78%;
    margin-left: 108px;
    --button-border-radius: 35px
}

.captcha {
    width: 120px;
}

.captcha img {
    width: 100%;
    cursor: pointer;
}

.smscode {
    width: 120px;
}

.smscode-btn {
    margin-left: 5px;
    width: 115px;
}

.resetpwd {
    text-align: right;
    font-size: .875rem;
    line-height: 36px;
}
</style>

6.4 登录状态检测

router/index.js

import { createRouter, createWebHashHistory } from 'vue-router';

import { useUserStore } from '@/stores/user'
// 系统路由信息
const routes = [
    {
        path: '/',
        component: () => import('../views/home/Home.vue'),
        // 只有经过身份验证的用户才能创建帖子
        meta: {
            requiresAuth: true
        }
    },
    {
        path: '/login',
        component: () => import('../views/login/Login.vue'),
        meta: {
            requiresAuth: false
        }
    },
    {
        path: '/resetpwd',
        component: () => import('../views/resetpwd/Resetpwd.vue'),
        meta: {
            requiresAuth: false
        }
    },
    {
        path: '/register',
        component: () => import('../views/register/Register.vue'),
        meta: {
            requiresAuth: false
        }
    },
]

//创建路由信息
const router = createRouter({
    // 4. 内部提供了 history 模式的实现。为了简单起见,我们在这里使用 hash 模式。
    history: createWebHashHistory(),
    routes, // `routes: routes` 的缩写
})

//添加路由守卫
router.beforeEach(async (to, from) => {
    // 数据状态管理
    const userStore = useUserStore()
    if (to.meta.requiresAuth && !userStore.token) {
        // 将用户重定向到登录页面
        return { path: '/login' }
    }
})
export default router;

6.5 请求接口实现

import axios from "axios";
//引入状态管理,获取登录后的token
import { useUserStore } from '@/stores/user'
import router from "@/routers";
/**
 * 创建api实例
 */
const api = axios.create({
    baseURL: "/api",
    timeout: 1000,
    headers: {}
});
// 添加请求拦截器
api.interceptors.request.use(function (config) {
    // 数据状态管理
    const userStore = useUserStore()
    if (userStore && userStore.token) {
        //附带上token信息
        config.headers.token = userStore.token;
        // config.headers.authorization = "Bearer " + userStore.token;
        // config.headers.auth = "Bearer " + userStore.token;
    }
    return config;
}, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
});

// 添加响应拦截器
api.interceptors.response.use(function (response) {
    // 数据状态管理
    const userStore = useUserStore()
    //刷新客户端的token信息
    if (response.headers.token && userStore.token != response.headers.token) {
        userStore.token = response.headers.token;
    }
    return response;
}, function (error) {
    switch (error.response.status) {
        case 401://提示先登录,自动转到登录界面
            // 数据状态管理
            const userStore = useUserStore()
            userStore.logout();
            router.push("/login")
            break;
        default:
            break;
    }
    return Promise.reject(error);
});
/**
 * 登录接口
 * @param {*} account 账号
 * @param {*} password 密码
 * @param {*} captcha 验证码
 */
const login = (account, password, captcha) => {
    return api.post('/login/password', {
        account: account,
        password: password,
        captcha: captcha
    })
}
/**
 * 获取短信验证码
 * @param {*} account 
 */
const getSmsCode = (account) => {
    return api.post('/login/getSmsCode', {
        account
    })
}
/**
 * 短信验证码登录
 * @param {*} account 
 * @param {*} smscode 
 * @param {*} captcha 
 * @returns 
 */
const loginBySmsCode = (account, smscode, captcha) => {
    return api.post('/login/smsCode', {
        account: account,
        smscode: smscode,
        captcha: captcha
    })
}
/**
 * 获取当前用户信息
 * @returns 
 */
const getUserInfo = () => {
    return api.get('/user/info')
}
/**
 * 获取短信验证码
 * @param {*} account 
 * @param {*} captcha 
 */
const getResetPhoneCode = (account, captcha) => {
    return api.get('/resetpwd/smscode?account=' + account + "&captcha=" + captcha)
}
/**
 * 校验找回密码短信验证码是否合法
 * @param {*} account 
 * @param {*} smsode 
 */
const validateResetpwdPhoneCode = (account, smscode) => {
    return api.post('/resetpwd/validate/smscode', {
        account, smscode
    })
}
/**
 * 通过短信验证码重置密码
 * @param {*} account 
 * @param {*} smsode 
 * @param {*} password 
 */
const resetpwdByPhoneCode = (account, smscode, password) => {
    return api.post('/resetpwd/reset/smscode', {
        account, smscode, password
    })
}
/**
 * 获取邮箱验证码
 * @param {*} account 
 * @param {*} captcha 
 * @returns 
 */
const getResetEmailCode = (account, captcha) => {
    return api.get('/resetpwd/emailcode?account=' + account + "&captcha=" + captcha)
}
/**
 * 验证邮箱验证码是否合法
 * @param {*} account 
 * @param {*} emailcode 
 * @returns 
 */
const validateResetpwdEmailCode = (account, emailcode) => {
    return api.post('/resetpwd/validate/emailcode', {
        account, emailcode
    })
}
/**
 * 通过邮箱验证码重置密码
 * @param {*} account 
 * @param {*} emailcode 
 * @param {*} password 
 * @returns 
 */
const resetpwdByEmailCode = (account, emailcode, password) => {
    return api.post('/resetpwd/reset/emailcode', {
        account, emailcode, password
    })
}
/**
 * 获取注册验证码
 * @param {*} phone 
 * @param {*} captcha 
 */
const getRegisterPhoneCode = (phone, captcha) => {
    return api.get('/register/smscode?phone=' + phone + "&captcha=" + captcha)
}
/**
 * 校验注册验证码是否合法
 * @param {*} phone 
 * @param {*} smscode 
 */
const validateRegisterPhoneCode = (phone, smscode) => {
    return api.post('/register/validate/smscode', {
        phone, smscode
    })
}
/**
 * 邮箱注册接口
 * @param {*} phone 
 * @param {*} smscode 
 * @param {*} account 
 * @param {*} name 
 * @param {*} sex 
 * @param {*} email 
 * @param {*} password 
 */
const registerByPhone = (phone, smscode, account, name, sex, email, password) => {
    return api.post('/register/phone', {
        phone, smscode, account, name, sex, email, password
    })
}
/**
 * 检测电话是否可用
 * @param {*} phone 
 */
const checkPhone = (phone) => {
    return api.get('/register/checkphone?phone=' + phone)
}
/**
 * 检测账号是否可用
 * @param {*} account 
 */
const checkAccount = (account) => {
    return api.get('/register/checkaccount?account=' + account)
}
/**
 * 检测邮箱是否可用
 * @param {*} email 
 */
const checkEmail = (email) => {
    return api.get('/register/checkemail?email=' + email)
}
export default {
    api,
    getUserInfo,
    login,
    getSmsCode, loginBySmsCode,
    getResetPhoneCode, validateResetpwdPhoneCode, resetpwdByPhoneCode, getResetEmailCode, validateResetpwdEmailCode, resetpwdByEmailCode,
    getRegisterPhoneCode, validateRegisterPhoneCode, registerByPhone, checkPhone, checkAccount, checkEmail,
}

七、登出功能实现

  登出功能使用白名单的方式实现,即将有效的token记录到白名单中,如果token登出、失效、过期时,将token从白名单中移除,登录检测token时,查询白名单,查看是否在白名单中,不是则token失效。文章来源地址https://www.toymoban.com/news/detail-516524.html

  1. token检测:
//导入用于生成JWT字符串的包
const jwt = require('jsonwebtoken');
const config = require("../config");
const tokenData = require('../data/tokenData');
module.exports = {
    /**
     * 检测当前是否已登录
     */
    validataLogin(req, res) {
        if (!req.token || !req.token.account) {
            res.status(401).send({
                code: 401,
                msg: '请先登录',
                data: null
            });
            return false;
        }

        //检测当前token是否在白名单中
        if (!(req.headers.token in tokenData)) {
            res.status(401).send({
                code: 401,
                msg: 'token已失效,请先登录',
                data: null
            });
            return false;
        }

        if (req.headers.newToken) {
            res.setHeader('token', req.headers.newToken);
        }
        //过期的处理
        // let expiresTime = req.token.exp;
        // let initTime = req.token.iat;
        // if (req.session.last) {
        //     initTime = req.session.last;
        // }
        //当前时间离过期时间还有5秒钟,最后一次访问时间
        // if ((expiresTime * 1000 - new Date().getTime()) < 5000) {//如果块要过期了,则自动刷新token
        //     let tokenStr = jwt.sign({ account: req.token.acount }, config.secretKey, { expiresIn: config.expiresTime });
        //     res.setHeader('token', tokenStr)
        // }
        return true;
    }
}
  1. 登出处理:
const express = require('express');
const router = express.Router();
/**
 * token白名单
 */
const tokenData = require('../data/tokenData');

/**
 *使用白名单的方式处理登录状态,
 */
router.get('/', function (req, res, next) {
    let token = req.headers.token;
    //从白名单中清除此token信息
    delete tokenData[token];
    res.send({
        code: 200,
        msg: '',
        data: null
    })
});
module.exports = router;

到了这里,关于基于VUE3+Layui从头搭建通用后台管理系统(前端篇)二:登录界面及对应功能实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包