398 lines
8.8 KiB
Vue
398 lines
8.8 KiB
Vue
<!-- Author: fruit.com.cn -->
|
|
|
|
<script setup lang="ts">
|
|
import { LoginForm } from './components'
|
|
import { ThemeSwitch } from '@/components/ThemeSwitch'
|
|
import { LocaleDropdown } from '@/components/LocaleDropdown'
|
|
import { useI18n } from '@/hooks/web/useI18n'
|
|
import { getCssVar, underlineToHump } from '@/utils'
|
|
import { useAppStore } from '@/store/modules/app'
|
|
import { computed } from 'vue'
|
|
import { ElScrollbar } from 'element-plus'
|
|
|
|
const appStore = useAppStore()
|
|
const { t } = useI18n()
|
|
const isDark = computed(() => appStore.getIsDark)
|
|
const toolColor = computed(() => (isDark.value ? '#e5eaf3' : '#303133'))
|
|
|
|
const themeChange = () => {
|
|
const color = getCssVar('--el-bg-color')
|
|
appStore.setMenuTheme(color)
|
|
appStore.setHeaderTheme(color)
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="login-page" :class="{ 'is-dark': isDark }">
|
|
<ElScrollbar class="login-scroll">
|
|
<div class="login-shell">
|
|
<section class="login-hero">
|
|
<div class="hero-brand">
|
|
<img src="@/assets/imgs/brand-mark.png" alt="" />
|
|
<span>{{ underlineToHump(appStore.getTitle) }}</span>
|
|
</div>
|
|
<div class="hero-fruits" aria-hidden="true">
|
|
<svg class="fruit cherry" viewBox="0 0 120 140">
|
|
<path
|
|
d="M62 38c8-16 24-24 36-22"
|
|
fill="none"
|
|
stroke="#7d9a62"
|
|
stroke-width="3"
|
|
stroke-linecap="round"
|
|
/>
|
|
<path
|
|
d="M58 40c2-18-8-30-6-36"
|
|
fill="none"
|
|
stroke="#6d8a55"
|
|
stroke-width="3"
|
|
stroke-linecap="round"
|
|
/>
|
|
<ellipse
|
|
cx="34"
|
|
cy="28"
|
|
rx="14"
|
|
ry="7"
|
|
fill="#8fb56f"
|
|
transform="rotate(-28 34 28)"
|
|
/>
|
|
<circle cx="42" cy="78" r="28" fill="#c4322a" />
|
|
<circle cx="78" cy="86" r="26" fill="#a82822" />
|
|
<circle cx="32" cy="68" r="8" fill="#fff" opacity=".28" />
|
|
</svg>
|
|
<svg class="fruit mango" viewBox="0 0 150 170">
|
|
<path d="M78 28c18 2 28 8 34 16-22 4-36 2-48-8 4-4 8-8 14-8z" fill="#6d8a55" />
|
|
<path
|
|
d="M86 36c6-16 2-24-2-28"
|
|
fill="none"
|
|
stroke="#5f7a4c"
|
|
stroke-width="3"
|
|
stroke-linecap="round"
|
|
/>
|
|
<path
|
|
d="M46 58c-18 28-16 62 8 84 28 24 62 10 74-16 14-30 4-62-16-80-18-16-46-10-66 12z"
|
|
fill="#f0b429"
|
|
/>
|
|
<path
|
|
d="M58 78c8 22 10 40 4 58"
|
|
fill="none"
|
|
stroke="#ffe08a"
|
|
stroke-width="8"
|
|
stroke-linecap="round"
|
|
opacity=".55"
|
|
/>
|
|
<ellipse
|
|
cx="62"
|
|
cy="86"
|
|
rx="14"
|
|
ry="22"
|
|
fill="#fff"
|
|
opacity=".2"
|
|
transform="rotate(-24 62 86)"
|
|
/>
|
|
</svg>
|
|
<svg class="fruit banana" viewBox="0 0 160 150">
|
|
<path
|
|
d="M28 108c18-46 48-74 92-86 8 10 10 22 4 34-28 10-50 28-66 58-6 12-16 16-30 12-8-2-12-10 0-18z"
|
|
fill="#f2d34b"
|
|
/>
|
|
<path
|
|
d="M36 102c16-38 42-62 80-74"
|
|
fill="none"
|
|
stroke="#ffe98a"
|
|
stroke-width="7"
|
|
stroke-linecap="round"
|
|
opacity=".7"
|
|
/>
|
|
<path
|
|
d="M118 24c8 2 14 10 12 16"
|
|
fill="none"
|
|
stroke="#6d8a55"
|
|
stroke-width="4"
|
|
stroke-linecap="round"
|
|
/>
|
|
<path
|
|
d="M30 112c-6 8-4 16 4 16"
|
|
fill="none"
|
|
stroke="#c9a227"
|
|
stroke-width="4"
|
|
stroke-linecap="round"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<div class="hero-copy">
|
|
<p class="hero-kicker">{{ t('login.consoleLabel') }}</p>
|
|
<h1>{{ t('login.welcome') }}</h1>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="login-panel">
|
|
<div class="login-tools">
|
|
<ThemeSwitch @change="themeChange" />
|
|
<LocaleDropdown :color="toolColor" />
|
|
</div>
|
|
<div class="login-card">
|
|
<LoginForm />
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</ElScrollbar>
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="less" scoped>
|
|
.login-page {
|
|
height: 100%;
|
|
color: var(--el-text-color-primary);
|
|
background: var(--el-bg-color-page);
|
|
}
|
|
|
|
.login-scroll {
|
|
height: 100%;
|
|
}
|
|
|
|
.login-shell {
|
|
display: flex;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.login-hero {
|
|
position: relative;
|
|
display: flex;
|
|
padding: 40px 56px 56px;
|
|
overflow: hidden;
|
|
color: #fff;
|
|
background:
|
|
radial-gradient(circle at 18% 18%, rgb(255 255 255 / 10%), transparent 28%),
|
|
radial-gradient(circle at 82% 78%, rgb(196 50 42 / 34%), transparent 34%),
|
|
linear-gradient(155deg, #17100f 0%, #241714 55%, #3b1d19 100%);
|
|
flex: 1.05;
|
|
flex-direction: column;
|
|
justify-content: space-between;
|
|
|
|
&::before,
|
|
&::after {
|
|
position: absolute;
|
|
border: 1px solid rgb(255 255 255 / 12%);
|
|
border-radius: 50%;
|
|
content: '';
|
|
}
|
|
|
|
&::before {
|
|
top: -140px;
|
|
left: -80px;
|
|
width: 420px;
|
|
height: 420px;
|
|
}
|
|
|
|
&::after {
|
|
right: -120px;
|
|
bottom: -160px;
|
|
width: 460px;
|
|
height: 460px;
|
|
}
|
|
}
|
|
|
|
.hero-fruits {
|
|
position: absolute;
|
|
inset: 0;
|
|
z-index: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.fruit {
|
|
position: absolute;
|
|
filter: drop-shadow(0 18px 24px rgb(0 0 0 / 28%));
|
|
}
|
|
|
|
.cherry {
|
|
top: 16%;
|
|
right: 14%;
|
|
width: 150px;
|
|
}
|
|
|
|
.mango {
|
|
top: 30%;
|
|
left: 38%;
|
|
width: 210px;
|
|
}
|
|
|
|
.banana {
|
|
top: 54%;
|
|
left: 12%;
|
|
width: 200px;
|
|
transform: rotate(-8deg);
|
|
}
|
|
|
|
.hero-brand {
|
|
position: relative;
|
|
z-index: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
font-size: 20px;
|
|
font-weight: 700;
|
|
|
|
img {
|
|
width: var(--brand-mark-size);
|
|
height: var(--brand-mark-size);
|
|
border-radius: var(--brand-mark-radius);
|
|
box-shadow: 0 10px 24px rgb(196 50 42 / 28%);
|
|
}
|
|
}
|
|
|
|
.hero-copy {
|
|
position: relative;
|
|
z-index: 1;
|
|
max-width: 420px;
|
|
|
|
h1 {
|
|
margin: 12px 0 0;
|
|
font-size: 44px;
|
|
font-weight: 600;
|
|
line-height: 1.15;
|
|
letter-spacing: -0.03em;
|
|
color: #fff;
|
|
}
|
|
}
|
|
|
|
.hero-kicker {
|
|
margin: 0;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
letter-spacing: 0.08em;
|
|
color: var(--el-color-primary-light-8) !important;
|
|
}
|
|
|
|
.login-panel {
|
|
position: relative;
|
|
display: flex;
|
|
flex: 0.95;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 88px 32px 48px;
|
|
background: var(--el-bg-color-page);
|
|
}
|
|
|
|
.login-tools {
|
|
position: absolute;
|
|
top: 22px;
|
|
right: 24px;
|
|
display: flex;
|
|
gap: 12px;
|
|
align-items: center;
|
|
}
|
|
|
|
.login-card {
|
|
width: min(420px, 100%);
|
|
padding: 36px 32px 28px;
|
|
background: var(--el-bg-color);
|
|
border: 1px solid var(--el-border-color-light);
|
|
border-radius: 18px;
|
|
box-shadow: 0 18px 50px rgb(42 24 16 / 10%);
|
|
}
|
|
|
|
.is-dark {
|
|
background: var(--el-bg-color-page);
|
|
|
|
.login-hero {
|
|
background:
|
|
radial-gradient(circle at 18% 18%, rgb(255 255 255 / 8%), transparent 30%),
|
|
linear-gradient(155deg, #0e0a09 0%, #1a1110 58%, #2c1513 100%);
|
|
}
|
|
|
|
.login-panel {
|
|
background: var(--el-bg-color-page);
|
|
}
|
|
|
|
.login-card {
|
|
background: var(--el-bg-color-overlay);
|
|
border-color: var(--el-border-color);
|
|
box-shadow: none;
|
|
}
|
|
}
|
|
|
|
@media (width <= 960px) {
|
|
.login-shell {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.login-hero {
|
|
flex: none;
|
|
min-height: 132px;
|
|
padding: 16px 24px 20px;
|
|
}
|
|
|
|
.hero-fruits {
|
|
display: none;
|
|
}
|
|
|
|
.hero-copy h1 {
|
|
margin: 6px 0 0;
|
|
font-size: 28px;
|
|
}
|
|
|
|
.login-panel {
|
|
padding: 44px 16px 12px;
|
|
}
|
|
|
|
.login-card {
|
|
padding-top: 28px;
|
|
padding-bottom: 20px;
|
|
}
|
|
|
|
.login-tools {
|
|
top: 8px;
|
|
right: 16px;
|
|
}
|
|
}
|
|
|
|
@media (width <= 600px) {
|
|
.login-hero {
|
|
min-height: 118px;
|
|
padding: 16px 18px 18px;
|
|
}
|
|
|
|
.login-card {
|
|
padding: 28px 20px 24px;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<style lang="less">
|
|
.login-heading {
|
|
margin-bottom: 8px;
|
|
|
|
h2 {
|
|
margin: 0;
|
|
font-size: 28px;
|
|
font-weight: 700;
|
|
line-height: 1.2;
|
|
color: var(--el-text-color-primary);
|
|
}
|
|
|
|
p {
|
|
margin: 8px 0 0;
|
|
font-size: 14px;
|
|
color: var(--el-text-color-secondary);
|
|
}
|
|
}
|
|
|
|
.login-card :deep(.el-button--primary) {
|
|
--el-button-bg-color: var(--app-brand);
|
|
--el-button-border-color: var(--app-brand);
|
|
--el-button-hover-bg-color: var(--app-brand-pressed);
|
|
--el-button-hover-border-color: var(--app-brand-pressed);
|
|
--el-button-active-bg-color: var(--app-brand-pressed);
|
|
--el-button-active-border-color: var(--app-brand-pressed);
|
|
}
|
|
|
|
.login-card :deep(.el-input__wrapper.is-focus) {
|
|
box-shadow: 0 0 0 1px var(--app-brand) inset;
|
|
}
|
|
|
|
body:has(.login-page) {
|
|
min-width: 0;
|
|
background: transparent;
|
|
}
|
|
</style>
|