一、本章内容
实现登录界面的开发,包括用户名密码登录、短信验证码登录、二维码扫码登录等登录方式,并实现两种不同风格的登录样式,完整课程地址
源码下载地址:点击下载
二、效果预览
三、开发视频
VUE3+ElementPlus通用管理系统实例:登录界面规划及框架搭建
四、项目代码
常见的登录方式:
1. 用户名密码登录
2. 短信验证码登录
3. 二维码扫码登录
各登录方式试下如下:
- 登录界面样式一:/src/views/Login/Nomal/Login.vue
样式说明:
使用常见的纯色或者渐变色作为登录界面的背景色,将登录框放置到界面中间,登录框顶部放logo、下方放tab切换,可以切换不同的登录方式,将不同登录方式提取到单独的组件中,实现不同登录方式的重用。
模板代码:
<div class="login-page" :class="[curTheme]">
<div class="login-header">
</div>
<div class="login-panel">
<div class="inner">
<div class="logo">
<el-image :src="logoImage" class="logo-img"></el-image>
</div>
<div class="login-text">
<!-- <div>用户登录</div> -->
<div class="tabs">
<div class="tab-item" @click="checkTab('1')" :class="{'tab-item-active':tabCurIndex==1}">
密码登录
</div>
<div class="tab-split"></div>
<div class="tab-item" @click="checkTab('2')" :class="{'tab-item-active':tabCurIndex==2}">
免密登录
</div>
<div class="tab-split"></div>
<div class="tab-item" @click="checkTab('3')" :class="{'tab-item-active':tabCurIndex==3}">
扫码登录
</div>
</div>
</div>
<div class="form">
<PasswordLogin :lineHeight="lineHeight" textColor="#fff" v-if="tabCurIndex==1"></PasswordLogin>
<CodeLogin :lineHeight="lineHeight" textColor="#fff" v-else-if="tabCurIndex==2"></CodeLogin>
<QcordLogin :lineHeight="lineHeight" textColor="#fff" v-else></QcordLogin>
</div>
</div>
</div>
<div class="login-footer">
版权:军军君xxxx有限公司
</div>
</div>
JS代码:
import {
ref,
reactive,
onMounted
} from "vue";
import utils from "@/utils/utils.js";
import config from "@/store/config.js";
let curTheme = config.curTheme;
import PasswordLogin from "../components/PasswordLogin.vue";
import CodeLogin from "../components/CodeLogin.vue";
import QcordLogin from "../components/QcordLogin.vue";
const lineHeight = config.login.lineHeight; //输入行高
const bgColor = config.login.bgColor; //背景色
//logo图片路径
const logoImage = new URL("../../../assets/logo.png",
import.meta.url).href;
const tabCurIndex = ref(1);
const checkTab = (index) => {
tabCurIndex.value = index;
}
样式代码:
.theme-default .login-page {
background: v-bind(bgColor);
position: fixed;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
}
.theme-default .login-page .login-panel {
justify-content: center;
justify-items: center;
vertical-align: middle;
align-items: center;
display: flex;
height: 100vh;
}
.theme-default .login-page .login-panel .inner {
width: 400px;
height: 515px;
margin: 0 auto;
}
.theme-default .login-page .login-panel .logo {
text-align: center;
}
.theme-default .login-page .login-panel .logo .logo-img:deep(img) {
width: 80px;
height: 80px;
border-radius: 100%;
box-shadow: 0 0 10px #ffffff55;
}
.theme-default .login-page .login-panel .logo .logo-img:deep(img) {
width: 80px;
height: 80px;
border-radius: 100%;
background: #fff;
}
.theme-default .login-page .login-panel .login-text {
height: v-bind(lineHeight);
line-height: v-bind(lineHeight);
text-align: center;
color: #fff;
margin: 20px 0;
}
.theme-default .login-page .login-panel .tabs {
font-size: 16px;
letter-spacing: 5px;
display: flex;
height: v-bind(lineHeight);
border-radius: 6vh;
}
.theme-default .login-page .login-panel .tabs .tab-item {
flex: 1;
height: v-bind(lineHeight);
cursor: pointer;
user-select: none;
background: rgba(0, 0, 0, 0.5);
}
.theme-default .login-page .login-panel .tabs .tab-split{
width:1px;
height: v-bind(lineHeight);
background:rgba(255,255,255,0.5)
}
.theme-default .login-page .login-panel .tabs .tab-item-active {
background: #ff9800;
}
.theme-default .login-page .login-panel .tabs .tab-item:first-child {
border-bottom-left-radius: 5px;
border-top-left-radius: 5px;
}
.theme-default .login-page .login-panel .tabs .tab-item:last-child {
border-bottom-right-radius: 5px;
border-top-right-radius: 5px;
}
.theme-default .login-page .login-footer {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
height: 4vh;
font-size: 14px;
text-align: center;
color: #fff
}
- 登录界面样式二:/src/views/Login/Spec/Login.vue
样式说明:
使用常见的纯色或者渐变色作为登录界面的背景色,中间放置白底的登录框,登录框左边放置系统名称和系统logo,右边放置登录表单,使用tab切换的方式实现不同登录方式的切换,将提取的不同登录方式的组件单独引入,根据不同的登录方式展示不同的界面。
模板代码:
<div class="login-page" :class="[curTheme]">
<div class="login-header">
</div>
<div class="login-panel">
<div class="inner flex">
<div class=" logo-panel">
<div class="logo">
<div class="title">军军君通用管理系统</div>
<el-image :src="logoImage" class="logo-img"></el-image>
</div>
</div>
<div class="v-split"></div>
<div class="flex-item login-form">
<div class="login-text">
<!-- <div>用户登录</div> -->
<div class="tabs">
<div class="tab-item" @click="checkTab('1')" :class="{'tab-item-active':tabCurIndex==1}">
密码登录
</div>
<div class="tab-split"></div>
<div class="tab-item" @click="checkTab('2')" :class="{'tab-item-active':tabCurIndex==2}">
免密登录
</div>
<div class="tab-split"></div>
<div class="tab-item" @click="checkTab('3')" :class="{'tab-item-active':tabCurIndex==3}">
扫码登录
</div>
</div>
</div>
<div class="form">
<PasswordLogin :lineHeight="lineHeight" v-if="tabCurIndex==1"></PasswordLogin>
<CodeLogin :lineHeight="lineHeight" v-else-if="tabCurIndex==2"></CodeLogin>
<QcordLogin :lineHeight="lineHeight" bgColor="#fff" v-else></QcordLogin>
</div>
</div>
</div>
</div>
<div class="login-footer">
版权:军军君xxxx有限公司
</div>
</div>
JS代码:
import {
ref,
reactive,
onMounted
} from "vue";
import utils from "@/utils/utils.js";
import config from "@/store/config.js";
let curTheme = config.curTheme;
import PasswordLogin from "../components/PasswordLogin.vue";
import CodeLogin from "../components/CodeLogin.vue";
import QcordLogin from "../components/QcordLogin.vue";
const lineHeight = config.login.lineHeight; //输入行高
const bgColor = config.login.bgColor; //背景色
//logo图片路径
const logoImage = new URL("../../../assets/logo.png",
import.meta.url).href;
const tabCurIndex = ref(1);
const checkTab = (index) => {
tabCurIndex.value = index;
}
样式代码:
.theme-default .login-page {
background: v-bind(bgColor);
position: fixed;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
}
.theme-default .login-page .login-panel {
justify-content: center;
justify-items: center;
vertical-align: middle;
align-items: center;
display: flex;
height: 100vh;
}
.theme-default .login-page .login-panel .inner {
width: 800px;
height: 450px;
margin: 0 auto;
background-color: #fff;
border-radius: 10px;
box-shadow: 0 0 30px #00000055;
}
.theme-default .login-page .logo-panel {
border-bottom-left-radius: 10px;
border-top-left-radius: 10px;
padding-top: 80px;
width: 45%;
}
.theme-default .login-page .logo-panel .title {
line-height: 120px;
font-size: 30px;
letter-spacing: 5px;
text-align: center;
background-image: -webkit-linear-gradient(right, red, #ff5722, #ff9800);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.theme-default .login-page .logo-panel .logo {
text-align: center;
}
.theme-default .login-page .logo-panel .logo :deep(.el-image__inner) {
width: 130px;
height: 130px;
}
.theme-default .login-page .v-split {
width: 1px;
height: 450px;
background: linear-gradient(45deg, #f4433600, #f44336, #f4433600);
}
.theme-default .login-page .login-form {
margin-left: 10px;
padding: 0 20px;
}
.theme-default .login-page .login-panel .tabs {
margin-top: 20px;
font-size: 16px;
letter-spacing: 5px;
display: flex;
text-align: center;
height: v-bind(lineHeight);
line-height: v-bind(lineHeight);
}
.theme-default .login-page .login-panel .tabs .tab-item {
flex: 1;
height: v-bind(lineHeight);
line-height: v-bind(lineHeight);
cursor: pointer;
user-select: none;
}
.theme-default .login-page .login-panel .tabs .tab-item:hover {
color: #ff5722;
}
.theme-default .login-page .login-panel .tabs .tab-split {
width: 1px;
height: v-bind(lineHeight);
line-height: v-bind(lineHeight);
}
.theme-default .login-page .login-panel .tabs .tab-item-active {
color: #ff5722;
}
.theme-default .login-page .login-panel .tabs .tab-item:first-child {
border-bottom-left-radius: 5px;
border-top-left-radius: 5px;
}
.theme-default .login-page .login-panel .tabs .tab-item:last-child {
border-bottom-right-radius: 5px;
border-top-right-radius: 5px;
}
.theme-default .login-page .login-panel .form {
padding: 0 20px;
margin-top: 20px;
}
.theme-default .login-page .login-footer {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
height: 4vh;
font-size: 14px;
text-align: center;
color: #fff
}
- 短信验证码登录组件
流程说明:
短信验证码登录包括输入手机号码、获取短信验证码按钮以及图片验证码。再输入手机号码及图片验证码后点击获取短信验证码可获取短信验证码,在获取短信验证码后对应按钮需有一段时间的禁用,防止重复获取短信验证码造成资源损失。具体流程如下:
模板代码:
<el-form ref="loginForm" :model="loginFormData" :label-width="0" :rules="rules" status-icon autocomplete="off"
class="formPanel" :class="[curTheme]">
<el-form-item label="" prop="username">
<el-input prefix-icon="Avatar" :autocomplete="false" v-model="loginFormData.username"
placeholder="请输入电话号码" />
</el-form-item>
<div class="flex">
<div class="flex-item">
<el-form-item label="" prop="smscode">
<el-input autocomplete="new-password" prefix-icon="Unlock" v-model="loginFormData.smscode"
placeholder="请输入短信验证码" />
</el-form-item>
</div>
<div class="code">
<el-button type="primary" @click="getSmsCode" class="smscode-btn" :disabled="isCodeIng">{{smsCodeBtnText}}</el-button>
</div>
</div>
<div class="flex">
<div class="flex-item">
<el-form-item label="" prop="code">
<el-input autocomplete="new-password" prefix-icon="Picture" v-model="loginFormData.code"
placeholder="请输入验证码" @click="changeCode" />
</el-form-item>
</div>
<div class="code">
<vcode ></vcode>
</div>
</div>
<div class="check-line">
<div>
<el-form-item label="">
<el-checkbox v-model="loginFormData.rememberUserName" label="记住用户名" size="large" />
</el-form-item>
</div>
<div class="line-item"></div>
</div>
<el-form-item>
<el-button type="danger" @click="onSubmit">登录</el-button>
</el-form-item>
</el-form>
JS代码:
import {
ref,
reactive,
defineProps,
onMounted,
onUnmounted
} from "vue";
import utils from "@/utils/utils.js";
import config from "@/store/config.js";
import vcode from "@/components/vcode.vue";
let curTheme = config.curTheme;
//传递的参数
const option = defineProps({
lineHeight: {
type: String,
required: true
},
textColor: {
type: String,
default:'#222',
required: false
}
})
//标识是否正在获取短信验证码
let isCodeIng = ref(false);
//定时器
let timer = null;
//定时器的时间
let curTime = ref(60);
//获取验证码按钮的提示文字
let smsCodeBtnText = ref("获取验证码");
// 登录表单数据
const loginFormData = reactive({
username: '',
smscode: '',
code: '',
rememberUserName: false,
rememberPassword: false
});
const loginForm = ref();
const rules = reactive({
username: [{
required: true,
message: '请输入电话号码',
trigger: 'blur'
}],
smscode: [{
required: true,
message: '请输入短信验证码',
trigger: 'blur'
}],
code: [{
required: true,
message: '请输入验证码',
trigger: 'blur'
}],
})
//获取验证码
const getSmsCode = ()=>{
if(!loginFormData.username){
utils.showError("电话号码不能为空!");
return;
}
if(!loginFormData.code){
utils.showError("请先输入验证码");
return;
}
isCodeIng.value = true;
timer && clearInterval(timer);
curTime.value = 60;
timer = setInterval(()=>{
curTime.value --;
smsCodeBtnText.value = curTime.value+"秒后重新获取";
if(curTime<=0){
isCodeIng.value = false;
timer && clearInterval(timer);
smsCodeBtnText.value = "获取验证码";
}
},1000);
//TODO 请求后台获取短信验证码
}
//登录操作
const onSubmit = () => {
let isOk = true;
loginForm.value.validate((valid, fields) => {
if (!valid) {
if (fields) {
for (let key in fields) {
utils.showError(fields[key][0].message);
}
}
isOk = false;
}
});
if (!isOk) {
return;
}
utils.saveData("rememberUserName", loginFormData.rememberUserName);
utils.saveData("username", loginFormData.username);
//TODO 调用登录接口进行登录
}
onMounted(() => {
loginFormData.rememberUserName = utils.getData("rememberUserName") === 'true' ? true : false;
if (loginFormData.rememberUserName) {
loginFormData.username = utils.getData("username");
} else {
loginFormData.username = "";
}
});
onUnmounted(()=>{
timer && clearInterval(timer);
});
样式代码:
.theme-default .formPanel:deep(.el-input__prefix-inner) {
color: #000;
font-size: 20px;
}
.theme-default .formPanel:deep(.el-form-item) {
margin-bottom: 30px;
}
.theme-default .formPanel:deep(.el-checkbox__label) {
color: v-bind(option.textColor);
}
.theme-default .formPanel:deep(.el-form-item__error) {
height: 3vh;
line-height: 3vh;
}
.theme-default .formPanel:deep(.el-form-item__label) {
line-height: v-bind(option.lineHeight);
height: v-bind(option.lineHeight);
}
.theme-default .formPanel:deep(.el-form-item__content),
.theme-default .formPanel:deep(.el-input__inner) {
line-height: v-bind(option.lineHeight);
height: v-bind(option.lineHeight);
}
.theme-default .formPanel:deep(input) {
background: #fff !important;
}
.theme-default .formPanel .check-line {
display: flex;
}
.theme-default .formPanel .check-line .line-item {
flex: 1;
}
.theme-default .formPanel:deep(.el-button) {
width: 100%;
line-height: v-bind(option.lineHeight);
height: v-bind(option.lineHeight);
border-radius: v-bind(option.lineHeight);
}
.theme-default .formPanel :deep(.smscode-btn) {
width: calc(100% - 0px);
line-height: v-bind(option.lineHeight);
height: v-bind(option.lineHeight);
margin-left: 0px;
border-radius: 0;
}
.theme-default .formPanel .code {
width: 110px;
height: calc(v-bind(option.lineHeight) + 2px);
padding-left: 10px;
cursor: pointer;
}
.theme-default .formPanel .resetPassword {
line-height: v-bind(option.lineHeight);
height: v-bind(option.lineHeight);
text-decoration: none;
color: #f44336;
font-size: 14px;
}
- 用户名密码登录组件
流程说明:
用户名密码登录方式比较常见,包括用户名输入框、密码输入框、图片验证码输入框、保存用户名、保存密码等组件,用户输入用户名、密码及图片验证码后请求后台完成登录,若选择了保存用户名和密码则在请求登录时自动将用户名和密码保存到浏览器localStorage中,在每次进入登录界面时,自动读取localStorage并将对应的值填充到登录界面中,具体流程如下:
模板代码:
<el-form ref="loginForm" :model="loginFormData" :label-width="0" :rules="rules" status-icon autocomplete="off"
class="formPanel" :class="[curTheme]">
<el-form-item label="" prop="username">
<el-input prefix-icon="Avatar" :autocomplete="false" v-model="loginFormData.username"
placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="" prop="password">
<el-input type="password" autocomplete="new-password" prefix-icon="Unlock" v-model="loginFormData.password"
placeholder="请输入密码" />
</el-form-item>
<div class="flex">
<div class="flex-item">
<el-form-item label="" prop="code">
<el-input autocomplete="new-password" prefix-icon="Picture" v-model="loginFormData.code"
placeholder="请输入验证码" @click="changeCode" />
</el-form-item>
</div>
<div class="code">
<vcode ></vcode>
</div>
</div>
<div class="check-line">
<div>
<el-form-item label="">
<el-checkbox v-model="loginFormData.rememberUserName" label="记住用户名" size="large" />
</el-form-item>
</div>
<div class="line-item"></div>
<div>
<el-form-item label="">
<el-checkbox v-model="loginFormData.rememberPassword" label="记住密码" size="large" />
</el-form-item>
</div>
<div class="line-item"></div>
<div>
<router-link to="/resetPassword" class="resetPassword">找回密码</router-link>
</div>
</div>
<el-form-item>
<el-button type="danger" @click="onSubmit">登录</el-button>
</el-form-item>
</el-form>
JS代码:
import {
ref,
reactive,
defineProps,
onMounted
} from "vue";
import utils from "@/utils/utils.js";
import config from "@/store/config.js";
import vcode from "@/components/vcode.vue";
let curTheme = config.curTheme;
//传递的参数
const option = defineProps({
lineHeight: {
type: String,
required: true
},
textColor: {
type: String,
default:'#222',
required: false
}
})
// 登录表单数据
const loginFormData = reactive({
username: '',
password: '',
code: '',
rememberUserName: false,
rememberPassword: false
});
const loginForm = ref();
const rules = reactive({
username: [{
required: true,
message: '请输入用户名',
trigger: 'blur'
}],
code: [{
required: true,
message: '请输入验证码',
trigger: 'blur'
}],
password: [{
required: true,
message: '请输入密码',
trigger: 'blur'
}],
})
//登录操作
const onSubmit = () => {
let isOk = true;
loginForm.value.validate((valid, fields) => {
if (!valid) {
if (fields) {
for (let key in fields) {
utils.showError(fields[key][0].message);
}
}
isOk = false;
}
});
if (!isOk) {
return;
}
utils.saveData("rememberUserName", loginFormData.rememberUserName);
utils.saveData("rememberPassword", loginFormData.rememberPassword);
utils.saveData("username", loginFormData.username);
utils.saveData("password", loginFormData.password);
//TODO 调用登录接口进行登录
}
onMounted(() => {
loginFormData.rememberUserName = utils.getData("rememberUserName") === 'true' ? true : false;
loginFormData.rememberPassword = utils.getData("rememberPassword") === 'true' ? true : false;
if (loginFormData.rememberUserName) {
loginFormData.username = utils.getData("username");
} else {
loginFormData.username = "";
}
if (loginFormData.rememberPassword) {
loginFormData.password = utils.getData("password");
} else {
loginFormData.password = "";
}
});
样式代码:
.theme-default .formPanel:deep(.el-input__prefix-inner) {
color: #000;
font-size: 20px;
}
.theme-default .formPanel:deep(.el-form-item) {
margin-bottom: 30px;
}
.theme-default .formPanel:deep(.el-checkbox__label) {
color: v-bind(option.textColor);
}
.theme-default .formPanel:deep(.el-form-item__error) {
height: 3vh;
line-height: 3vh;
}
.theme-default .formPanel:deep(.el-form-item__label) {
line-height: v-bind(option.lineHeight);
height: v-bind(option.lineHeight);
}
.theme-default .formPanel:deep(.el-form-item__content),
.theme-default .formPanel:deep(.el-input__inner) {
line-height: v-bind(option.lineHeight);
height: v-bind(option.lineHeight);
}
.theme-default .formPanel>>>input {
background: #fff !important;
}
.theme-default .formPanel .check-line {
display: flex;
}
.theme-default .formPanel .check-line .line-item {
flex: 1;
}
.theme-default .formPanel>>>.el-button {
width: 100%;
line-height: v-bind(option.lineHeight);
height: v-bind(option.lineHeight);
border-radius: v-bind(option.lineHeight);
}
.theme-default .formPanel .code {
width: 110px;
height: calc(v-bind(option.lineHeight) + 2px);
padding-left: 10px;
cursor: pointer;
}
.theme-default .formPanel .resetPassword {
line-height: v-bind(option.lineHeight);
height: v-bind(option.lineHeight);
text-decoration: none;
color: #f44336;
font-size: 14px;
}
- 二维码扫码登录组件
流程说明:
进入该组件时,自动从后台请求合法的登录二维码并进行展示,二维码可设置失效时间,每次刷新二维码的时候通过定时器定时,如果二维码失效,则提示当前二维码失效,使用websocket或者定时请求的方式判断当前用户是否已经完成扫码登录操作,如果已完成扫码登录则自动转到系统主页,具体流程如下:
模板代码:
<div class="qcode" :class="[curTheme]">
<img :src="qcodeSrc" @click="getQcode" :class="{'end':curTime<=0}" />
<div class="info">请使用微信或者客户端APP扫描登录系统</div>
<div class="endInfo" @click="getQcode" v-if="curTime<=0">二维码已失效,点击重新加载</div>
</div>
JS代码:
import {
ref,
reactive,
onMounted,
onUnmounted
} from "vue";
import config from "@/store/config.js";
let curTheme = config.curTheme;
//传递的参数
const option = defineProps({
lineHeight: {
type: String,
required: true
},
textColor: {
type: String,
default: '#222',
required: false
},
bgColor: {
type: String,
default: '#00000055',
required: false
}
})
//定时器
let timer = null;
//定时器的时间
let curTime = ref(60);
//二维码路径
const qcodeSrc = ref("");
//从后台获取二维码图片
const getQcode = () => {
qcodeSrc.value = new URL("../../../assets/qcode.png",
import.meta.url).href;
curTime.value = 60;
timer && clearInterval(timer);
timer = setInterval(() => {
curTime.value--;
if (curTime.value <= 0) {
timer && clearInterval(timer);
}
}, 1000);
}
onMounted(() => {
getQcode();
});
onUnmounted(() => {
timer && clearInterval(timer);
});
样式代码:
.theme-default .qcode {
padding: 20px;
background: v-bind(option.bgColor);
box-shadow: 0 0 20px v-bind(option.bgColor);
border-radius: 5px;
height: 305px;
text-align: center;
cursor: pointer;
position: relative;
}
.theme-default .qcode img {
height: calc(100% - 30px)
}
.theme-default .qcode .info {
font-size: 14px;
line-height: 30px;
text-align: center;
color: v-bind(option.textColor);
}
.theme-default .qcode .endInfo {
position: absolute;
left: 0;
top: 0;
color: red;
font-size: 14px;
width: 100%;
height: calc(100% - 30px);
text-align: center;
justify-content: center;
justify-items: center;
vertical-align: middle;
align-items: center;
display: flex;
}
.theme-default .qcode .end {
-webkit-filter: brightness(0.1);
}
更多推荐
Vue3+elementplus搭建通用管理系统实例二:登录界面的规划及实现
发布评论