Files
intc-vue3/src/views/login.vue
2026-01-08 11:51:16 +08:00

394 lines
9.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="login">
<div class="leftPick">
<img :src="defaultBg" alt="" />
</div>
<div class="rightForm">
<el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
<h3 class="title">{{ title }}</h3>
<el-form-item prop="username">
<el-input v-model="loginForm.username" type="text" size="large" auto-complete="off" placeholder="账号">
<template #prefix><img class="userName" src="../assets/images/userName.png" /></template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
:type="iconStatus ? 'text' : 'password'"
size="large"
auto-complete="off"
placeholder="密码"
@keyup.enter="handleLogin"
>
<template #prefix><img class="userName" src="../assets/images/password.png" /></template>
<template #suffix> <img class="userName" src="../assets/images/showPass.png" @click="handleIconClick" /></template>
</el-input>
</el-form-item>
<el-form-item prop="code" v-if="captchaEnabled" style="position: relative; padding-right: 120px">
<el-input v-model="loginForm.code" size="large" auto-complete="off" placeholder="验证码" @keyup.enter="handleLogin">
<template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
</el-input>
<div class="login-code">
<img :src="codeUrl" @click="getCode" class="login-code-img" />
</div>
</el-form-item>
<el-checkbox v-model="loginForm.rememberMe" class="mt-16 mb-60">记住密码</el-checkbox>
<el-form-item>
<el-button :loading="loading" size="large" type="primary" @click.prevent="handleLogin">
<span v-if="!loading"> </span>
<span v-else> 中...</span>
</el-button>
</el-form-item>
<div v-if="register" class="register-link">
<span class="register-text">还没有账号</span>
<router-link class="link-type" :to="'/register'">立即注册</router-link>
</div>
</el-form>
</div>
</div>
</template>
<script setup>
import { getCodeImg } from '@/api/login'
import { getConfigKey } from '@/api/system/config'
import Cookies from 'js-cookie'
import { encrypt, decrypt } from '@/utils/jsencrypt'
import useUserStore from '@/store/modules/user'
const userStore = useUserStore()
const router = useRouter()
const { proxy } = getCurrentInstance()
const iconStatus = ref(false)
const loginForm = ref({
username: '',
password: '',
rememberMe: false,
code: '',
uuid: ''
})
const loginRules = {
username: [{ required: true, trigger: 'blur', message: '请输入您的账号' }],
password: [{ required: true, trigger: 'blur', message: '请输入您的密码' }],
code: [{ required: true, trigger: 'change', message: '请输入验证码' }]
}
const title = ref('')
const defaultBg = ref('/fileUrl/light/template/loginBack.png')
const codeUrl = ref('')
const loading = ref(false)
// 验证码开关
const captchaEnabled = ref(false)
// 注册开关
const register = ref(true)
const redirect = ref(undefined)
function handleIconClick() {
iconStatus.value = !iconStatus.value
}
function handleLogin() {
proxy.$refs.loginRef.validate((valid) => {
if (valid) {
loading.value = true
// 勾选了需要记住密码设置在 cookie 中设置记住用户名和密码
if (loginForm.value.rememberMe) {
Cookies.set('username', loginForm.value.username, { expires: 30 })
Cookies.set('password', encrypt(loginForm.value.password), { expires: 30 })
Cookies.set('rememberMe', loginForm.value.rememberMe, { expires: 30 })
} else {
// 否则移除
Cookies.remove('username')
Cookies.remove('password')
Cookies.remove('rememberMe')
}
// 调用action的登录方法
userStore
.login(loginForm.value)
.then(() => {
router.push({ path: redirect.value || '/' })
})
.catch(() => {
loading.value = false
// 重新获取验证码
if (captchaEnabled.value) {
getCode()
}
})
}
})
}
function getCode() {
getCodeImg().then((res) => {
captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled
if (captchaEnabled.value) {
codeUrl.value = 'data:image/gif;base64,' + res.img
loginForm.value.uuid = res.uuid
}
})
}
function getDefaultBg() {
getConfigKey('healthBg').then((res) => {
defaultBg.value = res.msg
})
}
function getDefaultTitle() {
getConfigKey('healthTitle').then((res) => {
title.value = res.msg
document.title = res.msg
})
}
function getCookie() {
const username = Cookies.get('username')
const password = Cookies.get('password')
const rememberMe = Cookies.get('rememberMe')
loginForm.value = {
username: username === undefined ? loginForm.value.username : username,
password: password === undefined ? loginForm.value.password : decrypt(password),
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
}
}
getDefaultBg()
getDefaultTitle()
getCode()
getCookie()
</script>
<style lang="scss" scoped>
.login {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
.leftPick {
width: 60%;
height: 100%;
overflow: hidden; // 防止图片溢出
img {
width: 100%;
height: 100%;
display: block;
object-fit: cover; // 保持图片比例,裁剪填充
object-position: center; // 图片居中显示
}
}
.rightForm {
width: 40%;
height: 100%;
padding: 3rem 1rem;
background: #ffffff;
display: flex;
align-items: center;
justify-content: center;
.login-form {
width: 100%;
max-width: 450px;
.title {
text-align: center;
font-size: 32px;
font-family: 'PingFangSC-Heavy', 'PingFang SC', sans-serif;
font-weight: 800;
color: #1a1a1a;
line-height: 1.4;
letter-spacing: 1px;
margin-bottom: 50px;
position: relative;
&::after {
content: '';
position: absolute;
bottom: -15px;
left: 50%;
transform: translateX(-50%);
width: 50px;
height: 4px;
background: linear-gradient(90deg, #667eea, #764ba2);
border-radius: 2px;
}
}
.userName {
width: 20px;
height: 20px;
opacity: 0.6;
}
:deep(.el-form-item) {
margin-bottom: 28px;
}
:deep(.el-input__prefix-inner),
:deep(.el-input__suffix) {
display: flex;
align-items: center;
}
:deep(.el-input__wrapper) {
width: 100%;
height: 52px;
border-radius: 12px;
border: 2px solid #e8e8e8;
background: #fafafa;
font-family: 'PingFangSC-Regular', 'PingFang SC', sans-serif;
font-size: 15px;
box-shadow: none;
transition: all 0.3s ease;
padding: 0 18px;
&:hover {
border-color: #667eea;
background: #ffffff;
}
&.is-focus {
border-color: #667eea;
background: #ffffff;
box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1);
}
.el-input__inner {
color: #333;
&::placeholder {
color: #999;
}
}
}
// 验证码样式
.login-code {
position: absolute;
right: 0;
top: 0;
width: 120px;
height: 52px;
.login-code-img {
width: 100%;
height: 100%;
cursor: pointer;
border-radius: 12px;
object-fit: cover;
transition: all 0.3s ease;
border: 2px solid #e8e8e8;
&:hover {
border-color: #667eea;
transform: scale(1.02);
}
}
}
:deep(.el-checkbox) {
.el-checkbox__label {
color: #666;
font-size: 14px;
font-weight: 400;
font-family: 'PingFangSC-Regular', 'PingFang SC', sans-serif;
}
.el-checkbox__inner {
border-radius: 4px;
width: 18px;
height: 18px;
&::after {
left: 6px;
top: 2px;
}
}
&.is-checked .el-checkbox__inner {
background: #667eea;
border-color: #667eea;
}
}
.mt-16 {
margin-top: 16px;
}
.mb-60 {
margin-bottom: 35px;
}
:deep(.el-button) {
width: 100%;
height: 52px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
border-radius: 12px;
font-size: 16px;
font-family: 'PingFang-SC-Medium', 'PingFang-SC', sans-serif;
font-weight: 600;
color: #ffffff;
letter-spacing: 2px;
transition: all 0.3s ease;
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.35);
&:hover {
transform: translateY(-2px);
box-shadow: 0 12px 28px rgba(102, 126, 234, 0.5);
}
&:active {
transform: translateY(0);
}
}
.register-link {
text-align: center;
margin-top: 30px;
.register-text {
color: #999;
font-size: 14px;
margin-right: 8px;
}
.link-type {
display: inline-block;
font-size: 14px;
font-weight: 600;
color: #667eea;
text-decoration: none;
transition: all 0.3s ease;
position: relative;
padding-bottom: 2px;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 0;
height: 2px;
background: linear-gradient(90deg, #667eea, #764ba2);
transition: width 0.3s ease;
}
&:hover {
color: #764ba2;
&::after {
width: 100%;
}
}
}
}
}
}
</style>