refactor: ♻️ 登录页样式写法优化

This commit is contained in:
ray
2024-10-18 21:49:51 +08:00
parent efe61bd972
commit 42150877a3
2 changed files with 215 additions and 184 deletions

View File

@@ -1,103 +0,0 @@
.login-container {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
overflow-y: auto;
background: url("@/assets/images/login-background-light.jpg") no-repeat center
right;
.login-content {
display: flex;
width: 100%;
min-width: 400px;
max-width: 850px;
overflow: hidden;
background-color: #fff;
border-radius: 5px;
box-shadow: var(--el-box-shadow-light);
@media (width <= 768px) {
flex-direction: column;
max-width: 100%;
height: 100vh;
border-radius: 0;
box-shadow: none;
}
.login-image {
display: flex;
flex: 3;
align-items: center;
justify-content: center;
background: linear-gradient(60deg, #165dff, #6aa1ff);
@media (width <= 768px) {
display: none;
}
}
.login-box {
display: flex;
flex: 2;
flex-direction: column;
justify-content: center;
min-width: 400px;
padding: 30px;
@media (width <= 768px) {
width: 100%;
}
}
.input-wrapper {
display: flex;
align-items: center;
width: 100%;
}
.captcha-image {
height: 48px;
cursor: pointer;
border-top-right-radius: 6px;
border-bottom-right-radius: 6px;
}
}
.el-form-item {
background: var(--el-input-bg-color);
border: 1px solid var(--el-border-color);
border-radius: 5px;
}
.el-input {
.el-input__wrapper {
padding: 0;
background-color: transparent;
box-shadow: none;
&.is-focus,
&:hover {
box-shadow: none !important;
}
input:-webkit-autofill {
/* 通过延时渲染背景色变相去除背景颜色 */
transition: background-color 1000s ease-in-out 0s;
}
}
}
}
html.dark {
.login-container {
background: url("@/assets/images/login-background-dark.jpg") no-repeat
center right;
.login-content {
background: transparent;
box-shadow: var(--el-box-shadow);
}
}
}

View File

@@ -1,11 +1,10 @@
<template> <template>
<div class="login-container"> <div class="login">
<div class="flex-x-between absolute-lt w-full p-2"> <!-- 登录页头部 -->
<div class="flex-center"> <div class="login-header">
<el-image :src="logo" style="width: 30px; height: 30px" /> <div class="flex-y-center">
<span <el-image :src="logo" class="logo" />
class="text-2xl font-bold bg-gradient-to-r from-blue-500 to-teal-500 text-transparent bg-clip-text mx-1" <span class="title">
>
{{ defaultSettings.title }} {{ defaultSettings.title }}
</span> </span>
<el-tag size="small" type="success"> <el-tag size="small" type="success">
@@ -13,7 +12,7 @@
</el-tag> </el-tag>
</div> </div>
<div class="flex-center"> <div class="flex-y-center">
<el-switch <el-switch
v-model="isDark" v-model="isDark"
inline-prompt inline-prompt
@@ -25,19 +24,14 @@
</div> </div>
</div> </div>
<!-- 登录表单 --> <!-- 登录页内容 -->
<div class="login-content"> <div class="login-content">
<div class="login-image"> <div class="login-img">
<el-image :src="loginImage" style="width: 210px; height: 210px" /> <el-image :src="loginImage" style="width: 210px" />
</div> </div>
<div class="login-box"> <div class="login-form">
<el-form <el-form ref="loginFormRef" :model="loginData" :rules="loginRules">
ref="loginFormRef" <div class="form-title">
:model="loginData"
:rules="loginRules"
class="login-form"
>
<h2 class="text-xl font-medium text-center flex-center relative">
{{ $t("login.login") }} {{ $t("login.login") }}
<el-dropdown style="position: absolute; right: 0"> <el-dropdown style="position: absolute; right: 0">
<div class="cursor-pointer"> <div class="cursor-pointer">
@@ -65,7 +59,7 @@
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
</h2> </div>
<!-- 用户名 --> <!-- 用户名 -->
<el-form-item prop="username"> <el-form-item prop="username">
@@ -125,7 +119,7 @@
<el-image <el-image
:src="captchaBase64" :src="captchaBase64"
class="captcha-image" class="captcha-img"
@click="getCaptcha" @click="getCaptcha"
/> />
</div> </div>
@@ -152,79 +146,62 @@
{{ $t("login.login") }} {{ $t("login.login") }}
</el-button> </el-button>
<el-divider>
<span class="text-12px">{{ $t("login.otherLoginMethods") }}</span>
</el-divider>
<!-- 第三方登录 --> <!-- 第三方登录 -->
<div class="flex-x-center text-lg color-gray-5"> <el-divider>
<svg-icon icon-class="wechat" class="cursor-pointer" /> <el-text size="small">{{ $t("login.otherLoginMethods") }}</el-text>
<svg-icon icon-class="qq" class="cursor-pointer ml-5" /> </el-divider>
<svg-icon icon-class="github" class="cursor-pointer ml-5" /> <div class="third-party-login">
<svg-icon icon-class="gitee" class="cursor-pointer ml-5" /> <svg-icon icon-class="wechat" class="icon" />
<svg-icon icon-class="qq" class="icon" />
<svg-icon icon-class="github" class="icon" />
<svg-icon icon-class="gitee" class="icon" />
</div> </div>
</el-form> </el-form>
</div> </div>
</div> </div>
<!-- ICP备案 --> <!-- -->
<div class="absolute bottom-0 w-full text-center text-12px"> <div class="login-footer">
<p> <el-text size="small">
Copyright © 2021 - 2024 youlai.tech All Rights Reserved. Copyright © 2021 - 2024 youlai.tech All Rights Reserved.
<a <el-link
:underline="false"
href="http://beian.miit.gov.cn/" href="http://beian.miit.gov.cn/"
target="_blank" target="_blank"
rel="noopener noreferrer"
class="hover:underline"
> >
皖ICP备20006496号-2 皖ICP备20006496号-2
</a> </el-link>
</p> </el-text>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
// 外部库和依赖
import { LocationQuery, useRoute } from "vue-router"; import { LocationQuery, useRoute } from "vue-router";
// 内部依赖
import { useSettingsStore, useUserStore } from "@/store";
import AuthAPI, { type LoginData } from "@/api/auth"; import AuthAPI, { type LoginData } from "@/api/auth";
import router from "@/router"; import router from "@/router";
import type { FormInstance } from "element-plus";
import defaultSettings from "@/settings"; import defaultSettings from "@/settings";
import { ThemeEnum } from "@/enums/ThemeEnum"; import { ThemeEnum } from "@/enums/ThemeEnum";
// 类型定义 import { useSettingsStore, useUserStore, useDictStore } from "@/store";
import type { FormInstance } from "element-plus";
// 导入 login.scss 文件
import "@/styles/login.scss";
// 使用导入的依赖和库
const userStore = useUserStore(); const userStore = useUserStore();
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
const route = useRoute(); const dictStore = useDictStore();
// 窗口高度
const { height } = useWindowSize();
// 国际化 Internationalization
const { t } = useI18n();
// 是否暗黑模式 const route = useRoute();
const isDark = ref(settingsStore.theme === ThemeEnum.DARK); const { t } = useI18n();
// 是否显示 ICP 备案信息
const icpVisible = ref(true);
// 按钮 loading 状态
const loading = ref(false);
// 是否大写锁定
const isCapslock = ref(false);
// 验证码图片Base64字符串
const captchaBase64 = ref();
// 登录表单ref
const loginFormRef = ref<FormInstance>(); const loginFormRef = ref<FormInstance>();
const logo = ref(new URL("../../assets/logo.png", import.meta.url).href); const isDark = ref(settingsStore.theme === ThemeEnum.DARK); // 是否暗黑模式
const loading = ref(false); // 按钮 loading 状态
const isCapslock = ref(false); // 是否大写锁定
const captchaBase64 = ref(); // 验证码图片Base64字符串
const logo = ref(new URL("../../assets/logo.png", import.meta.url).href);
const loginImage = ref( const loginImage = ref(
new URL("../../assets/images/login-image.svg", import.meta.url).href new URL("../../assets/images/login-image.svg", import.meta.url).href
); );
@@ -276,13 +253,16 @@ function getCaptcha() {
} }
/** 登录表单提交 */ /** 登录表单提交 */
function handleLoginSubmit() { async function handleLoginSubmit() {
loginFormRef.value?.validate((valid: boolean) => { loginFormRef.value?.validate((valid: boolean) => {
if (valid) { if (valid) {
loading.value = true; loading.value = true;
userStore userStore
.login(loginData.value) .login(loginData.value)
.then(() => { .then(async () => {
await userStore.getUserInfo();
await dictStore.loadDictionaries(); // 需要在路由跳转前加载字典数据,否则会出现字典数据未加载完成导致页面渲染异常
// 跳转到登录前的页面
const { path, queryParams } = parseRedirect(); const { path, queryParams } = parseRedirect();
router.push({ path: path, query: queryParams }); router.push({ path: path, query: queryParams });
}) })
@@ -296,7 +276,11 @@ function handleLoginSubmit() {
}); });
} }
/** 解析 redirect 字符串 为 path 和 queryParams */ /**
* 解析 redirect 字符串 为 path 和 queryParams
*
* @returns { path: string, queryParams: Record<string, string> } 解析后的 path 和 queryParams
*/
function parseRedirect(): { function parseRedirect(): {
path: string; path: string;
queryParams: Record<string, string>; queryParams: Record<string, string>;
@@ -315,23 +299,14 @@ function parseRedirect(): {
return { path, queryParams }; return { path, queryParams };
} }
/** 主题切换 */ // 主题切换
const toggleTheme = () => { const toggleTheme = () => {
const newTheme = const newTheme =
settingsStore.theme === ThemeEnum.DARK ? ThemeEnum.LIGHT : ThemeEnum.DARK; settingsStore.theme === ThemeEnum.DARK ? ThemeEnum.LIGHT : ThemeEnum.DARK;
settingsStore.changeTheme(newTheme); settingsStore.changeTheme(newTheme);
}; };
/** 根据屏幕宽度切换设备模式 */ // 检查输入大小写
watchEffect(() => {
if (height.value < 600) {
icpVisible.value = false;
} else {
icpVisible.value = true;
}
});
/** 检查输入大小写 */
function checkCapslock(event: KeyboardEvent) { function checkCapslock(event: KeyboardEvent) {
// 防止浏览器密码自动填充时报错 // 防止浏览器密码自动填充时报错
if (event instanceof KeyboardEvent) { if (event instanceof KeyboardEvent) {
@@ -339,7 +314,7 @@ function checkCapslock(event: KeyboardEvent) {
} }
} }
/** 设置登录凭证 */ // 设置登录凭证
const setLoginCredentials = (username: string, password: string) => { const setLoginCredentials = (username: string, password: string) => {
loginData.value.username = username; loginData.value.username = username;
loginData.value.password = password; loginData.value.password = password;
@@ -350,4 +325,163 @@ onMounted(() => {
}); });
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped>
.login {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
overflow-y: auto;
background: url("@/assets/images/login-background-light.jpg") no-repeat center
right;
.login-header {
position: absolute;
top: 0;
display: flex;
justify-content: space-between;
width: 100%;
padding: 15px;
.logo {
width: 26px;
height: 26px;
}
.title {
margin: auto 5px;
font-size: 26px;
font-weight: bold;
color: transparent;
background: linear-gradient(to right, #3b82f6, #14b8a6);
background-clip: text;
-webkit-text-fill-color: transparent;
}
}
.login-content {
display: flex;
width: 850px;
overflow: hidden;
background-color: #fff;
border-radius: 5px;
box-shadow: var(--el-box-shadow-light);
@media (width <= 768px) {
flex-direction: column;
max-width: 100%;
height: 100vh;
border-radius: 0;
box-shadow: none;
}
.login-img {
display: flex;
flex: 3;
align-items: center;
justify-content: center;
background: linear-gradient(60deg, #165dff, #6aa1ff);
@media (width <= 768px) {
display: none;
}
}
.login-form {
display: flex;
flex: 2;
flex-direction: column;
justify-content: center;
min-width: 400px;
padding: 30px;
@media (width <= 768px) {
width: 100%;
padding: 0 20px;
}
.form-title {
position: relative;
display: flex;
align-items: center;
justify-content: center;
padding: 20px 0;
font-size: 20px;
text-align: center;
}
.input-wrapper {
display: flex;
align-items: center;
width: 100%;
}
.captcha-img {
height: 48px;
cursor: pointer;
border-top-right-radius: 6px;
border-bottom-right-radius: 6px;
}
.third-party-login {
display: flex;
justify-content: center;
width: 100%;
color: var(--el-text-color-secondary);
*:not(:first-child) {
margin-left: 20px;
}
.icon {
cursor: pointer;
}
}
}
}
.login-footer {
position: absolute;
bottom: 0;
width: 100%;
text-align: center;
}
}
:deep(.el-form-item) {
background: var(--el-input-bg-color);
border: 1px solid var(--el-border-color);
border-radius: 5px;
}
:deep(.el-input) {
.el-input__wrapper {
padding: 0;
background-color: transparent;
box-shadow: none;
&.is-focus,
&:hover {
box-shadow: none !important;
}
input:-webkit-autofill {
/* 通过延时渲染背景色变相去除背景颜色 */
transition: background-color 1000s ease-in-out 0s;
}
}
}
html.dark {
.login {
background: url("@/assets/images/login-background-dark.jpg") no-repeat
center right;
.login-content {
background: transparent;
box-shadow: var(--el-box-shadow);
}
}
}
</style>