Files
EasyFlow/easyflow-ui-usercenter/packages/effects/layouts/src/authentication/authentication.vue
2026-02-22 18:56:10 +08:00

323 lines
7.4 KiB
Vue

<script setup lang="ts">
import type { ToolbarType } from './types';
import { computed, ref, watch } from 'vue';
import { preferences, usePreferences } from '@easyflow/preferences';
import { Copyright } from '../basic/copyright';
import AuthenticationFormView from './form.vue';
import SloganIcon from './icons/slogan.vue';
import Toolbar from './toolbar.vue';
interface Props {
appName?: string;
logo?: string;
logoDark?: string;
pageTitle?: string;
pageDescription?: string;
sloganImage?: string;
toolbar?: boolean;
copyright?: boolean;
toolbarList?: ToolbarType[];
clickLogo?: () => void;
}
const props = withDefaults(defineProps<Props>(), {
appName: '',
copyright: true,
logo: '',
logoDark: '',
pageDescription: '',
pageTitle: '',
sloganImage: '',
toolbar: true,
toolbarList: () => ['color', 'language', 'layout', 'theme'],
clickLogo: () => {},
});
const { authPanelCenter, authPanelLeft, authPanelRight, isDark } =
usePreferences();
const isSloganLoadError = ref(false);
/**
* @zh_CN 根据主题选择合适的 logo 图标
*/
const logoSrc = computed(() => {
if (isDark.value && props.logoDark) {
return props.logoDark;
}
return props.logo;
});
watch(
() => props.sloganImage,
() => {
isSloganLoadError.value = false;
},
);
function handleSloganError() {
isSloganLoadError.value = true;
}
</script>
<template>
<div
:class="[isDark ? 'dark' : '']"
class="auth-shell relative flex min-h-full flex-1 select-none overflow-x-hidden"
>
<template v-if="toolbar">
<slot name="toolbar">
<Toolbar :toolbar-list="toolbarList" />
</slot>
</template>
<AuthenticationFormView
v-if="authPanelLeft"
class="auth-panel-form min-h-full w-full flex-1 lg:w-[42%] lg:flex-initial"
data-side="left"
>
<template v-if="copyright" #copyright>
<slot name="copyright">
<Copyright
v-if="preferences.copyright.enable"
v-bind="preferences.copyright"
/>
</slot>
</template>
</AuthenticationFormView>
<slot name="logo">
<div
v-if="logoSrc || appName"
class="absolute left-0 top-0 z-20 flex flex-1"
@click="clickLogo"
>
<div
class="text-foreground ml-4 mt-4 flex flex-1 items-center sm:left-6 sm:top-6"
>
<img
v-if="logoSrc"
:key="logoSrc"
:alt="appName"
:src="logoSrc"
class="mr-2"
width="120"
/>
</div>
</div>
</slot>
<div
v-if="!authPanelCenter"
class="auth-hero relative hidden min-h-full w-0 flex-1 overflow-hidden lg:block"
>
<div class="auth-hero-base absolute inset-0"></div>
<div class="auth-orb auth-orb-left"></div>
<div class="auth-orb auth-orb-right"></div>
<div class="auth-orb auth-orb-bottom"></div>
<div
:key="authPanelLeft ? 'left' : authPanelRight ? 'right' : 'center'"
class="auth-hero-content flex-col-center relative h-full px-12"
:class="{
'enter-x': authPanelLeft,
'-enter-x': authPanelRight,
}"
>
<div class="auth-hero-visual">
<template v-if="sloganImage && !isSloganLoadError">
<img
:alt="appName"
:src="sloganImage"
class="auth-hero-image"
@error="handleSloganError"
/>
</template>
<SloganIcon v-else :alt="appName" class="auth-hero-fallback" />
</div>
<div class="auth-page-title text-foreground">
{{ pageTitle }}
</div>
<div class="auth-page-desc text-muted-foreground">
{{ pageDescription }}
</div>
</div>
</div>
<div v-if="authPanelCenter" class="auth-center relative w-full">
<div class="auth-hero-base absolute inset-0"></div>
<div class="auth-orb auth-orb-left"></div>
<div class="auth-orb auth-orb-right"></div>
<AuthenticationFormView
class="auth-center-card shadow-float w-full rounded-3xl pb-20 md:w-2/3 lg:w-1/2 xl:w-[36%]"
data-side="bottom"
>
<template v-if="copyright" #copyright>
<slot name="copyright">
<Copyright
v-if="preferences.copyright.enable"
v-bind="preferences.copyright"
/>
</slot>
</template>
</AuthenticationFormView>
</div>
<AuthenticationFormView
v-if="authPanelRight"
class="auth-panel-form min-h-full w-full flex-1 lg:w-[42%] lg:flex-initial"
data-side="right"
>
<template v-if="copyright" #copyright>
<slot name="copyright">
<Copyright
v-if="preferences.copyright.enable"
v-bind="preferences.copyright"
/>
</slot>
</template>
</AuthenticationFormView>
</div>
</template>
<style scoped>
.auth-shell {
background: linear-gradient(180deg, #f8fbff 0%, #f1f6ff 100%);
}
.auth-panel-form {
backdrop-filter: blur(6px);
}
.auth-hero {
border-inline: 1px solid rgb(29 108 255 / 6%);
}
.auth-hero-base {
background:
radial-gradient(circle at 22% 18%, rgb(56 131 255 / 14%) 0, transparent 42%),
radial-gradient(circle at 82% 16%, rgb(88 179 255 / 12%) 0, transparent 35%),
radial-gradient(circle at 70% 86%, rgb(112 146 255 / 10%) 0, transparent 40%),
linear-gradient(145deg, #eef5ff 0%, #f4f8ff 45%, #eef4ff 100%);
}
.auth-hero-content {
z-index: 2;
}
.auth-hero-visual {
width: min(860px, 90%);
max-width: 920px;
}
.auth-hero-image {
width: 100%;
height: auto;
object-fit: contain;
filter: drop-shadow(0 24px 48px rgb(24 78 173 / 10%));
}
.auth-hero-fallback {
width: 100%;
height: auto;
}
.auth-page-title {
margin-top: 1.2rem;
font-size: clamp(1.9rem, 2.5vw, 2.45rem);
font-weight: 700;
letter-spacing: -0.01em;
text-align: center;
}
.auth-page-desc {
margin-top: 0.9rem;
max-width: 42rem;
font-size: clamp(0.96rem, 1.1vw, 1.12rem);
line-height: 1.7;
text-align: center;
}
.auth-center {
align-items: center;
display: flex;
justify-content: center;
overflow: hidden;
}
.auth-center-card {
position: relative;
z-index: 2;
}
.auth-orb {
border-radius: 9999px;
position: absolute;
z-index: 1;
}
.auth-orb-left {
background: rgb(101 149 255 / 18%);
height: 18rem;
left: -4rem;
top: 4.5rem;
width: 18rem;
}
.auth-orb-right {
background: rgb(124 188 255 / 16%);
height: 10rem;
right: 3rem;
top: 5rem;
width: 10rem;
}
.auth-orb-bottom {
background: rgb(93 154 255 / 12%);
bottom: 2rem;
height: 14rem;
right: 5rem;
width: 14rem;
}
.dark.auth-shell {
background: linear-gradient(180deg, #05080f 0%, #070b14 100%);
.auth-hero {
border-inline: 1px solid rgb(125 168 255 / 10%);
}
.auth-hero-base {
background:
radial-gradient(circle at 20% 18%, rgb(63 115 255 / 22%) 0, transparent 45%),
radial-gradient(circle at 82% 16%, rgb(56 149 255 / 18%) 0, transparent 38%),
radial-gradient(circle at 72% 86%, rgb(96 124 255 / 20%) 0, transparent 42%),
linear-gradient(150deg, #0a1326 0%, #091022 45%, #0b1529 100%);
}
.auth-page-desc {
color: rgb(181 194 226 / 82%);
}
.auth-hero-image {
filter: drop-shadow(0 26px 52px rgb(8 19 42 / 48%));
}
.auth-orb-left {
background: rgb(81 126 255 / 24%);
}
.auth-orb-right {
background: rgb(59 140 248 / 22%);
}
.auth-orb-bottom {
background: rgb(94 105 239 / 20%);
}
}
</style>