实现登录界面的开发,包括用户名密码登录、短信验证码登录、二维码扫码登录等登录方式,并实现两种不同风格的登录样式,完整课程地址
VUE3+ElementPlus通用管理系统实例:登录界面规划及框架搭建
常见的登录方式:
1. 用户名密码登录
2. 短信验证码登录
3. 二维码扫码登录
各登录方式试下如下:
<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 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
}
<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 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;
}
模板代码:
<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;
}
模板代码:
<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);
}