fix: 登录页重构

develop
TangShanDD 2025-11-06 16:01:50 +08:00
parent 0f9f99063e
commit 2b92c69e75
2 changed files with 443 additions and 114 deletions

View File

@ -1,42 +1,35 @@
<template> <template>
<div class="login-container" :style="{ backgroundImage: `url(${configs.loginBg || '/web-common-resource/img/bg_login.png'})` }"> <div class="login-container" :style="{ backgroundImage: `url(${configs.loginBg || '/web-common-resource/img/bg_login.png'})` }">
<div class="login-logo"> <div class="login-content">
<img :src="configs.loginLogo" alt /> <!-- 左侧蓝色面板 -->
<div class="left-panel">
<span class="title-text">多云管理平台</span>
</div> </div>
<div class="login-center">
<div class="desc-content"> <!-- 右侧白色登录表单 -->
<span class="desc-title">{{ configs.promotionalTitle }}</span> <div class="right-panel">
<span class="desc-remark">{{ configs.promotionalContent }}</span> <div class="right-panel-content">
<div class="login-title">欢迎登录</div>
<el-form :model="loginForm" ref="loginFormRef" label-position="top" class="login-form" @keyup.enter="handleLogin">
<el-form-item class="login-form-item" prop="account" label="账号" :rules="[{ ...required, message: '请输入账户' }]">
<el-input v-model="loginForm.account" autocomplete="off" placeholder="请输入账户" size="large" />
</el-form-item>
<el-form-item class="login-form-item" prop="password" label="密码" :rules="[{ ...required, message: '请输入密码' }]">
<el-input v-model="loginForm.password" type="password" show-password placeholder="请输入密码" autocomplete="off" size="large" @blur="capsTooltip = false" @keyup="checkCapslock" />
</el-form-item>
<div class="form-options">
<div class="remember-password">
<el-checkbox v-model="remember"></el-checkbox>
</div>
<a :href="`mailto:${configs.helpInformationLink}`" class="help-link">{{ configs.helpInformationContent || '获取帮助' }}</a>
</div>
<el-button class="login-btn" type="primary" size="large" :loading="loading" @click="handleLogin"></el-button>
</el-form>
</div> </div>
<a-form :model="loginForm" ref="loginFormRef" label-position="left" label-width="0px" class="card-box login-form" @keyup.enter="handleLogin">
<div class="login-title">账号登录</div>
<a-form-item class="login-form-item" name="account" :rules="[{ ...required, message: '请输入用户名' }]">
<a-input v-model:value="loginForm.account" autocomplete="off" placeholder="登录账户">
<template #prefix>
<UserOutlined />
</template>
</a-input>
</a-form-item>
<a-tooltip v-model="capsTooltip" content="大写锁定已打开" placement="right" manual>
<a-form-item class="login-form-item" name="password" :rules="[{ ...required, message: '请输入密码' }]">
<a-input-password name="password" prefix-icon="el-icon-lock" v-model:value="loginForm.password" placeholder="密码" autocomplete="false" @blur="capsTooltip = false" @keyup="checkCapslock">
<template #prefix>
<LockOutlined />
</template>
</a-input-password>
</a-form-item>
</a-tooltip>
<a-form-item class="login-form-item">
<div class="operate-region">
<span class="remember">
<a-switch v-model:checked="remember"></a-switch>
<span class="m-l-xs">记住密码</span>
</span>
<a :href="`mailto:${configs.helpInformationLink}`" type="text" class="text-info pull-right help-info" :title="configs.helpInformationContent">{{ configs.helpInformationContent }}</a>
</div> </div>
</a-form-item>
<a-button class="login-btn" type="primary" size="medium" :loading="loading" @click="handleLogin"></a-button>
</a-form>
</div> </div>
<div class="copyright-info">{{ configs.copyrightInformation }}</div> <div class="copyright-info">{{ configs.copyrightInformation }}</div>
</div> </div>
@ -46,23 +39,23 @@
import { decrypt, encrypt } from 'utils/crypto' import { decrypt, encrypt } from 'utils/crypto'
import { login } from 'services' import { login } from 'services'
import { reactive, toRefs, ref, computed } from 'vue' import { reactive, toRefs, ref, computed } from 'vue'
import { UserOutlined, LockOutlined } from '@ant-design/icons-vue'
import { useStore } from 'vuex' import { useStore } from 'vuex'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import setLoginData from './tools' import setLoginData from './tools'
import { required } from '@/validate' import { required } from '@/validate'
export default { export default {
components: { UserOutlined, LockOutlined }, name: 'LoginPage',
setup() { setup() {
const state = reactive({ const state = reactive({
remember: false, remember: false,
userType: 'tenant', // 'tenant' 'user'
loginForm: { loginForm: {
account: '', account: '',
password: '', password: ''
}, },
loading: false, loading: false,
capsTooltip: false, capsTooltip: false
}) })
const store = useStore() const store = useStore()
const configs = computed(() => store.getters.pageConfig) const configs = computed(() => store.getters.pageConfig)
@ -84,7 +77,7 @@ export default {
if (state.remember) { if (state.remember) {
const obj = { const obj = {
account: state.loginForm.account, account: state.loginForm.account,
password: encrypt(state.loginForm.password), password: encrypt(state.loginForm.password)
} }
localStorage.setItem('cmcLoginData', JSON.stringify(obj)) localStorage.setItem('cmcLoginData', JSON.stringify(obj))
} else { } else {
@ -99,17 +92,19 @@ export default {
async function handleLogin() { async function handleLogin() {
try { try {
state.loading = true state.loading = true
const values = await loginFormRef.value.validate() await loginFormRef.value.validate()
const { account, password } = values const { account, password } = state.loginForm
const res = await login({ const res = await login({
account, account,
password: encrypt(password), password: encrypt(password),
isManager: true, isManager: true
}) })
if (res.success) { if (res.success) {
goLogin(res.data) goLogin(res.data)
} }
} catch (error) {} } catch (error) {
//
}
state.loading = false state.loading = false
} }
@ -121,7 +116,7 @@ export default {
state.capsTooltip = false state.capsTooltip = false
} }
} }
if (key === 'CapsLock' && this.capsTooltip === true) { if (key === 'CapsLock' && state.capsTooltip === true) {
state.capsTooltip = false state.capsTooltip = false
} }
} }
@ -131,107 +126,204 @@ export default {
configs, configs,
loginFormRef, loginFormRef,
handleLogin, handleLogin,
checkCapslock, checkCapslock
}
} }
},
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.login-container { .login-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100vh; height: 100vh;
position: relative;
background: url('/web-common-resource/img/bg_login.png') #2d3a4b no-repeat; background: url('/web-common-resource/img/bg_login.png') #2d3a4b no-repeat;
background-size: cover; background-size: cover;
.login-logo { .login-content {
padding: 5px 40px;
img {
height: 40px;
}
}
.login-center {
display: flex; display: flex;
flex: 1; position: absolute;
justify-content: space-around; top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 90%;
max-width: 1200px;
z-index: 1;
align-items: center; align-items: center;
.desc-content { justify-content: center;
color: #fff; min-height: 600px;
display: flex;
flex-direction: column; //
margin-top: -150x; .left-panel {
width: 600px;
.desc-title {
font-weight: 700;
font-style: normal;
font-size: 38px;
color: #ffffff;
margin-bottom: 56px;
}
.desc-remark {
line-height: 40px;
font-weight: 400;
font-style: normal;
font-size: 22px;
color: #ffffff;
}
}
.login-form {
box-sizing: border-box;
width: 462px;
padding: 50px 30px;
background: #fff;
font-size: 17px;
.login-title {
font-size: 32px;
text-align: center;
color: #333;
margin-bottom: 50px;
}
.operate-region {
display: flex;
align-items: center;
.remember {
flex: 1; flex: 1;
text-align: left;
}
}
.login-btn {
margin-top: 20px;
height: 60px;
width: 100%;
}
.help-info {
max-width: 130px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.login-form-item {
::v-deep(.ant-input-affix-wrapper) {
height: 60px;
line-height: 60px;
}
::v-deep(.ant-input-prefix) {
padding-left: 50px;
}
::v-deep {
.a-input__icon {
height: 100%; height: 100%;
min-height: 700px;
background: rgba(4, 121, 231, 0.463); //
display: flex;
align-items: center;
justify-content: center;
// backdrop-filter: blur(1px);
padding: 60px 50px;
.title-text {
width: 80%;
font-size: 28px;
font-weight: 700;
color: #ffffff;
position: absolute;
top: 5%;
left: 5%;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
} }
.ant-input-prefix { }
font-size: 20px;
padding: 0 10px; //
} .right-panel {
flex: 1;
height: 100%;
background: #ffffff;
display: flex;
align-items: center;
justify-content: center;
padding: 60px 50px;
.right-panel-content {
width: 83%;
min-height: 580px;
}
.login-title {
font-size: 30px;
font-weight: 600;
color: #1890ff;
text-align: center;
margin-bottom: 40px;
text-align: left;
line-height: 130px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
.login-form {
width: 100%;
max-width: 400px;
//
.user-type-selector {
display: flex;
margin-bottom: 30px;
background: #f5f5f5;
border-radius: 8px;
padding: 4px;
gap: 4px;
.type-item {
flex: 1;
text-align: center;
padding: 12px 20px;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s;
font-size: 16px;
color: #666;
background: transparent;
&:hover {
background: rgba(24, 144, 255, 0.1);
}
&.active {
background: #595959;
color: #ffffff;
font-weight: 500;
}
}
}
.login-form-item {
margin-bottom: 24px;
::v-deep(.el-form-item__label) {
padding-bottom: 8px;
font-size: 14px;
color: #333;
font-weight: 500;
}
::v-deep(.el-input__wrapper) {
min-height: 48px;
border-radius: 4px;
.el-input__inner {
height: 48px;
line-height: 48px;
font-size: 14px;
}
&:hover {
box-shadow: 0 0 0 1px #40a9ff inset;
}
&.is-focus {
box-shadow: 0 0 0 1px #1890ff inset;
}
}
}
.form-options {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
.remember-password {
::v-deep(.el-checkbox) {
font-size: 14px;
color: #666;
.el-checkbox__label {
font-size: 14px;
color: #666;
}
}
}
.help-link {
color: #1890ff;
font-size: 14px;
text-decoration: none;
cursor: pointer;
&:hover {
color: #40a9ff;
text-decoration: underline;
}
}
}
.login-btn {
width: 100%;
height: 48px;
font-size: 16px;
font-weight: 500;
border-radius: 4px;
} }
} }
} }
} }
.copyright-info { .copyright-info {
width: 100%;
padding: 10px 0; padding: 10px 0;
text-align: center; text-align: center;
background: rgba(10, 37, 68, 0.6); background: rgba(10, 37, 68, 0.6);
color: #bbbbbb; color: #bbbbbb;
position: fixed;
z-index: 1;
font-size: 12px;
bottom: 0;
} }
} }
</style> </style>

View File

@ -0,0 +1,237 @@
<template>
<div class="login-container" :style="{ backgroundImage: `url(${configs.loginBg || '/web-common-resource/img/bg_login.png'})` }">
<div class="login-logo">
<img :src="configs.loginLogo" alt />
</div>
<div class="login-center">
<div class="desc-content">
<span class="desc-title">{{ configs.promotionalTitle }}</span>
<span class="desc-remark">{{ configs.promotionalContent }}</span>
</div>
<a-form :model="loginForm" ref="loginFormRef" label-position="left" label-width="0px" class="card-box login-form" @keyup.enter="handleLogin">
<div class="login-title">账号登录</div>
<a-form-item class="login-form-item" name="account" :rules="[{ ...required, message: '请输入用户名' }]">
<a-input v-model:value="loginForm.account" autocomplete="off" placeholder="登录账户">
<template #prefix>
<UserOutlined />
</template>
</a-input>
</a-form-item>
<a-tooltip v-model="capsTooltip" content="大写锁定已打开" placement="right" manual>
<a-form-item class="login-form-item" name="password" :rules="[{ ...required, message: '请输入密码' }]">
<a-input-password name="password" prefix-icon="el-icon-lock" v-model:value="loginForm.password" placeholder="密码" autocomplete="false" @blur="capsTooltip = false" @keyup="checkCapslock">
<template #prefix>
<LockOutlined />
</template>
</a-input-password>
</a-form-item>
</a-tooltip>
<a-form-item class="login-form-item">
<div class="operate-region">
<span class="remember">
<a-switch v-model:checked="remember"></a-switch>
<span class="m-l-xs">记住密码</span>
</span>
<a :href="`mailto:${configs.helpInformationLink}`" type="text" class="text-info pull-right help-info" :title="configs.helpInformationContent">{{ configs.helpInformationContent }}</a>
</div>
</a-form-item>
<a-button class="login-btn" type="primary" size="medium" :loading="loading" @click="handleLogin"></a-button>
</a-form>
</div>
<div class="copyright-info">{{ configs.copyrightInformation }}</div>
</div>
</template>
<script>
import { decrypt, encrypt } from 'utils/crypto'
import { login } from 'services'
import { reactive, toRefs, ref, computed } from 'vue'
import { UserOutlined, LockOutlined } from '@ant-design/icons-vue'
import { useStore } from 'vuex'
import { useRouter, useRoute } from 'vue-router'
import setLoginData from './tools'
import { required } from '@/validate'
export default {
components: { UserOutlined, LockOutlined },
setup() {
const state = reactive({
remember: false,
loginForm: {
account: '',
password: ''
},
loading: false,
capsTooltip: false
})
const store = useStore()
const configs = computed(() => store.getters.pageConfig)
const loginFormRef = ref(null)
const init = () => {
const local = localStorage.getItem('cmcLoginData')
if (local) {
const obj = JSON.parse(local)
state.loginForm.account = obj.account
state.loginForm.password = decrypt(obj.password)
state.remember = true
}
}
init()
const router = useRouter()
const route = useRoute()
function goLogin(data) {
setLoginData(data)
if (state.remember) {
const obj = {
account: state.loginForm.account,
password: encrypt(state.loginForm.password)
}
localStorage.setItem('cmcLoginData', JSON.stringify(obj))
} else {
localStorage.removeItem('cmcLoginData')
}
const { redirect } = route.query
const path = redirect ? redirect.split('/#')[1] : '/sms-web/resource_dashboard'
router.replace(path)
localStorage.removeItem('lockData')
}
async function handleLogin() {
try {
state.loading = true
const values = await loginFormRef.value.validate()
const { account, password } = values
const res = await login({
account,
password: encrypt(password),
isManager: true
})
if (res.success) {
goLogin(res.data)
}
} catch (error) {}
state.loading = false
}
function checkCapslock({ shiftKey, key } = {}) {
if (key && key.length === 1) {
if ((shiftKey && key >= 'a' && key <= 'z') || (!shiftKey && key >= 'A' && key <= 'Z')) {
state.capsTooltip = true
} else {
state.capsTooltip = false
}
}
if (key === 'CapsLock' && this.capsTooltip === true) {
state.capsTooltip = false
}
}
return {
...toRefs(state),
required,
configs,
loginFormRef,
handleLogin,
checkCapslock
}
}
}
</script>
<style lang="scss" scoped>
.login-container {
display: flex;
flex-direction: column;
height: 100vh;
background: url('/web-common-resource/img/bg_login.png') #2d3a4b no-repeat;
background-size: cover;
.login-logo {
padding: 5px 40px;
img {
height: 40px;
}
}
.login-center {
display: flex;
flex: 1;
justify-content: space-around;
align-items: center;
.desc-content {
color: #fff;
display: flex;
flex-direction: column;
margin-top: -150x;
width: 600px;
.desc-title {
font-weight: 700;
font-style: normal;
font-size: 38px;
color: #ffffff;
margin-bottom: 56px;
}
.desc-remark {
line-height: 40px;
font-weight: 400;
font-style: normal;
font-size: 22px;
color: #ffffff;
}
}
.login-form {
box-sizing: border-box;
width: 462px;
padding: 50px 30px;
background: #fff;
font-size: 17px;
.login-title {
font-size: 32px;
text-align: center;
color: #333;
margin-bottom: 50px;
}
.operate-region {
display: flex;
align-items: center;
.remember {
flex: 1;
text-align: left;
}
}
.login-btn {
margin-top: 20px;
height: 60px;
width: 100%;
}
.help-info {
max-width: 130px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.login-form-item {
::v-deep(.ant-input-affix-wrapper) {
height: 60px;
line-height: 60px;
}
::v-deep(.ant-input-prefix) {
padding-left: 50px;
}
::v-deep {
.a-input__icon {
height: 100%;
}
.ant-input-prefix {
font-size: 20px;
padding: 0 10px;
}
}
}
}
}
.copyright-info {
padding: 10px 0;
text-align: center;
background: rgba(10, 37, 68, 0.6);
color: #bbbbbb;
}
}
</style>