1.介绍
管理后台的入口页面,在没有登录之前,所有路由跳转都定向到login页面。
本页面特点:
- 输入路由,会添加redirect标识,等待登录成功后重定向到redirect记录的路由中,避免每次都跳转首页。
- 动画,伪2.5d动画登录表单会根据鼠标的移动而转向。
2.详解
2.1 登录表单
通过函数返回值定义登录表单,函数每次执行都会返回初始值,通过Object.assign()
可以用于reset重置表单功能。
function loginSource(){
return {
user: 'admin', // 默认
password: 'hanrui', // 默认
verifyCode:''
}
}
let loginForm = reactive(loginSource())
// 重置表单
function resetLoginForm(){
Object.assign(loginForm,loginSource())
}
2.2 验证码处理
验证码是通过这篇文章js创建验证码创建的,通过mock来模拟传给前端。只要登录提交了并失败了都要刷新一遍验证码。
注:
验证码的验证:验证码是包括验证码的id和验证码的值,后台拿到id后去验证值是否正确。在此获取验证码是在pinia(vueX)中实现的,获取时会记录验证码的id,登录时随着表单一起提交。
代码如下:
// login.vue
const userStore = useUserStore()
const router = useRouter()
const captchaImg = ref<string>('')
async function getCaptcha(){
const captcha = await userStore.getCaptcha(100,50)
captchaImg.value = captcha
}
// pinia user.ts
async getCaptcha(width,height){
const res = await loginApi.getImageCaptcha({width,height})
this.captchaId = res.data.id
return res.data.img
},
2.3 提交登录
登录的逻辑:
在这里我没有做表单验证,正常情况下login中先进行验证再去提交表单,避免携带脏数据的垃圾请求。如果你使用本模板进行二次开发,这是需要添加的功能。
将用户名、密码、验证码、验证码id传递给后台,如果成功,跳转到用户之前的页面或首页,如果失败,清空表单重新获取验证码。
// login.vue
function login(){
userStore.login(
loginForm.user,
loginForm.password,
loginForm.verifyCode
).then(res=>{
if(res){
router.replace((route.query.redirect as string) ?? '/')
}else{
resetLoginForm()
getCaptcha()
}
})
}
类型如下:
// api/login/type
export type LoginParams = {
username:string;
password:string;
captchaId:string;
verifyCode:string;
}
2.4动画
这个动画放在哪里都行,我这直接就用于了整个表单。
思路:
先计算整个模块的中心点位置。再通过鼠标位置计算鼠标位于中心点的方向和距离,再根据方向和距离大小进行偏转。
perspective是用来调节z轴的视觉感,再通过rotate进行旋转,切勿旋转过多。
function setAnimation(){
const dom = document.querySelector('.main-wrapper') as HTMLHtmlElement
if(dom){
// 计算dom中心点
const {top,left} = dom.getBoundingClientRect()
const centerX = dom.offsetWidth/2+left
const centerY = dom.offsetHeight/2+top
// 添加锁帧
let lock = false
dom.addEventListener('mousemove', (event:MouseEvent)=>{
if(lock) return
lock = true
window.requestAnimationFrame(()=>{
lock = false
const reactiveX = event.pageX - centerX
const reactiveY = event.pageY -centerY
const rateX = reactiveX/centerX *2
const rateY = reactiveY/centerY *2
dom.style.transform = `perspective(800px) rotateX(${rateX}deg) rotateY(${rateY}deg)`
})
})
}
}
完整代码如下:
<template>
<section class="mask center-center-flex">
<div class="main-wrapper">
<div class="address">
</div>
<section class="login-wrapper ">
<div class="logo-wrapper">
<span class="title">登录</span>
</div>
<el-form :inline="false" :model="loginForm" class=" login-form">
<el-form-item>
<p> 用户名</p>
<el-input v-model.trim="loginForm.user" placeholder="用户名" clearable />
</el-form-item>
<el-form-item icon>
<p>密码</p>
<el-input v-model.trim="loginForm.password" type="password" placeholder="密码" clearable />
</el-form-item>
<el-form-item>
<p>验证码</p>
<div class="verify-wrapper">
<el-input class="verify-input" v-model.trim="loginForm.verifyCode" minlength="4" @keyup.enter="login" placeholder="验证码" />
<el-image @click="getCaptcha" class="verify-img" :src="captchaImg" ></el-image>
</div>
</el-form-item>
<el-form-item>
<el-button type="primary" class="login-btn" @click="login">登录</el-button>
</el-form-item>
</el-form>
</section>
</div>
</section>
</template>
<script lang="ts" setup>
import {onMounted, reactive, ref} from 'vue'
import {useUserStore} from '@/store/user'
import {useRoute, useRouter} from 'vue-router'
/** 登录表单数据 */
function loginSource(){
return {
user: 'admin',
password: 'hanrui',
verifyCode:''
}
}
let loginForm = reactive(loginSource())
// 重置表单
function resetLoginForm(){
Object.assign(loginForm,loginSource())
}
/** 路由 */
const route = useRoute()
/** 验证码 */
const userStore = useUserStore()
const router = useRouter()
const captchaImg = ref<string>('')
async function getCaptcha(){
const captcha = await userStore.getCaptcha(100,50)
captchaImg.value = captcha
}
/** 登录 */
function login(){
userStore.login(
loginForm.user,
loginForm.password,
loginForm.verifyCode
).then(res=>{
if(res){
router.replace((route.query.redirect as string) ?? '/')
}else{
resetLoginForm()
getCaptcha()
}
})
}
onMounted(()=>{
getCaptcha()
setAnimation()
})
// 动画
function setAnimation(){
const dom = document.querySelector('.main-wrapper') as HTMLHtmlElement
if(dom){
// 计算dom中心点
const {top,left} = dom.getBoundingClientRect()
const centerX = dom.offsetWidth/2+left
const centerY = dom.offsetHeight/2+top
let lock = false
dom.addEventListener('mousemove', (event:MouseEvent)=>{
if(lock) return
lock = true
window.requestAnimationFrame(()=>{
lock = false
const reactiveX = event.pageX - centerX
const reactiveY = event.pageY -centerY
const rateX = reactiveX/centerX *2
const rateY = reactiveY/centerY *2
dom.style.transform = `perspective(800px) rotateX(${rateX}deg) rotateY(${rateY}deg)`
})
})
}
}
</script>
<style lang="less" scoped>
@login-font-size:18px;
@login-line-height:40px;
.mask{
position: fixed;
width: 100vw;
height:100vh;
background-color: #eaeffb;
}
.main-wrapper {
overflow: hidden;
position: relative;
width: 800px;
height: 500px;
border-radius: 20px;
box-shadow: 0 5px 20px #999;
.address{
position: absolute;
width: 520px;
height: 100%;
background-color: #79bbff;
background-image: url("@/assets/image/loginLogo.png");
background-size: cover;
}
.login-wrapper {
position: absolute;
right: 0;
width: 300px;
height: 500px;
padding: 20px;
background-color: #fff;
border-radius: 20px;
box-shadow: -1px 0 5px #999;
.logo-wrapper {
.title {
font-size: 30px;
line-height: 80px;
}
}
.login-form {
::v-deep(.el-input__wrapper) {
height: @login-line-height;
margin-top: 10px;
font-size: @login-font-size;
border-radius: 0;
border-bottom: 1px solid #000;
box-shadow: none;
}
::v-deep(.el-form-item__content) {
font-size: @login-font-size;
}
.verify-wrapper {
display: flex;
align-items: flex-end;
width: 100%;
.verify-input {
width: 50%;
margin-right: 5%;
}
.verify-img {
position: absolute;
right: 0;
float: right;
width: 45%;
}
}
.login-btn {
width: 50%;
height: @login-line-height;
margin: 10px auto;
border-radius: 25px;
}
}
}
}
</style>
3.本框架其他文章链接
GitHub开源链接:GitHub - grxynl/vue3-admin-template: vue3+TypeScript+pinia 后台管理系统模板
其他文章
从0开始搭建后台管理系统(首篇)
4.结束语
本框架完全免费,框架尚有不足,供前端爱好者一起讨论,一起学习。 源码和文档都制作不易,如果觉得您还可以的话,求一个stars,这是对我最大的支持,也是本框架前进的最大动力。