初始化
This commit is contained in:
@@ -0,0 +1,322 @@
|
||||
<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>
|
||||
@@ -0,0 +1,48 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'AuthenticationFormView',
|
||||
});
|
||||
|
||||
defineProps<{
|
||||
dataSide?: 'bottom' | 'left' | 'right' | 'top';
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="auth-form-wrap flex-col-center bg-background dark:bg-background-deep relative min-h-full px-6 py-12 lg:px-10"
|
||||
>
|
||||
<slot></slot>
|
||||
|
||||
<RouterView v-slot="{ Component, route }">
|
||||
<Transition appear mode="out-in" name="slide-right">
|
||||
<KeepAlive :include="['Login']">
|
||||
<component
|
||||
:is="Component"
|
||||
:key="route.fullPath"
|
||||
class="side-content mt-8 w-full sm:mx-auto md:max-w-md"
|
||||
:data-side="dataSide"
|
||||
/>
|
||||
</KeepAlive>
|
||||
</Transition>
|
||||
</RouterView>
|
||||
|
||||
<div
|
||||
class="text-muted-foreground absolute bottom-4 flex text-center text-xs"
|
||||
>
|
||||
<slot name="copyright"> </slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.auth-form-wrap {
|
||||
background-image:
|
||||
linear-gradient(180deg, rgb(255 255 255 / 98%) 0%, rgb(247 250 255 / 98%) 100%);
|
||||
}
|
||||
|
||||
.dark .auth-form-wrap {
|
||||
background-image:
|
||||
linear-gradient(180deg, rgb(13 20 34 / 96%) 0%, rgb(9 16 29 / 98%) 100%);
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
export { default as AuthPageLayout } from './authentication.vue';
|
||||
export * from './types';
|
||||
@@ -0,0 +1,64 @@
|
||||
<script setup lang="ts">
|
||||
import type { ToolbarType } from './types';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { preferences } from '@easyflow/preferences';
|
||||
|
||||
import {
|
||||
AuthenticationColorToggle,
|
||||
AuthenticationLayoutToggle,
|
||||
LanguageToggle,
|
||||
ThemeToggle,
|
||||
} from '../widgets';
|
||||
|
||||
interface Props {
|
||||
toolbarList?: ToolbarType[];
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'AuthenticationToolbar',
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
toolbarList: () => ['color', 'language', 'layout', 'theme'],
|
||||
});
|
||||
|
||||
const showColor = computed(() => props.toolbarList.includes('color'));
|
||||
const showLayout = computed(() => props.toolbarList.includes('layout'));
|
||||
const showLanguage = computed(() => props.toolbarList.includes('language'));
|
||||
const showTheme = computed(() => props.toolbarList.includes('theme'));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="{
|
||||
'auth-toolbar': toolbarList.length > 1,
|
||||
}"
|
||||
class="flex-center absolute right-3 top-4 z-20"
|
||||
>
|
||||
<div class="hidden md:flex">
|
||||
<AuthenticationColorToggle v-if="showColor" />
|
||||
<AuthenticationLayoutToggle v-if="showLayout" />
|
||||
</div>
|
||||
<LanguageToggle v-if="showLanguage && preferences.widget.languageToggle" />
|
||||
<ThemeToggle v-if="showTheme && preferences.widget.themeToggle" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.auth-toolbar {
|
||||
backdrop-filter: blur(10px);
|
||||
background: rgb(255 255 255 / 72%);
|
||||
border: 1px solid rgb(29 108 255 / 10%);
|
||||
border-radius: 9999px;
|
||||
box-shadow: 0 12px 26px rgb(30 72 152 / 12%);
|
||||
padding: 0.25rem 0.72rem;
|
||||
}
|
||||
|
||||
:deep(.dark) .auth-toolbar {
|
||||
background: rgb(11 19 34 / 66%);
|
||||
border-color: rgb(122 167 255 / 22%);
|
||||
box-shadow: 0 12px 28px rgb(0 0 0 / 32%);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1 @@
|
||||
export type ToolbarType = 'color' | 'language' | 'layout' | 'theme';
|
||||
Reference in New Issue
Block a user