Files
EasyFlow/easyflow-ui-admin/packages/effects/layouts/src/authentication/authentication.vue
陈子默 7e7c236c2a fix: 修复管理端前端 lint 与构建问题
- 收敛 easyflow-ui-admin 的 lint、格式和类型问题

- 修正 demo 页面与管理端前端构建失败点

- 验证 pnpm lint 与 pnpm build 均已通过
2026-04-05 21:39:13 +08:00

439 lines
9.9 KiB
Vue

<script setup lang="ts">
import type { ToolbarType } from './types';
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import { $t } from '@easyflow/locales';
import { preferences, usePreferences } from '@easyflow/preferences';
import { Copyright } from '../basic/copyright';
import AuthenticationFormView from './form.vue';
import Toolbar from './toolbar.vue';
interface Props {
appName?: string;
logo?: string;
logoDark?: string;
pageTitle?: string;
pageDescription?: string;
toolbar?: boolean;
copyright?: boolean;
toolbarList?: ToolbarType[];
clickLogo?: () => void;
}
const props = withDefaults(defineProps<Props>(), {
appName: '',
copyright: true,
logo: '',
logoDark: '',
pageDescription: '',
pageTitle: '',
toolbar: true,
toolbarList: () => ['language', 'theme'],
clickLogo: () => {},
});
const { isDark } = usePreferences();
const route = useRoute();
const logoSrc = computed(() => {
if (isDark.value && props.logoDark) {
return props.logoDark;
}
return props.logo;
});
const activeCapabilityIndex = ref(0);
let capabilityTimer: null | number = null;
const currentHour = ref(new Date().getHours());
const isLoginRoute = computed(() => route.path === '/auth/login');
const capabilityLabels = computed(() => [
$t('authentication.capabilityModel'),
$t('authentication.capabilityAgent'),
$t('authentication.capabilityWorkflow'),
$t('authentication.capabilityKnowledge'),
]);
const stageGreeting = computed(() => {
if (currentHour.value >= 5 && currentHour.value < 11) {
return $t('authentication.greetingMorning');
}
if (currentHour.value >= 11 && currentHour.value < 14) {
return $t('authentication.greetingNoon');
}
if (currentHour.value >= 14 && currentHour.value < 18) {
return $t('authentication.greetingAfternoon');
}
return $t('authentication.greetingEvening');
});
onMounted(() => {
capabilityTimer = window.setInterval(() => {
activeCapabilityIndex.value =
(activeCapabilityIndex.value + 1) % capabilityLabels.value.length;
}, 2400);
});
onBeforeUnmount(() => {
if (capabilityTimer) {
clearInterval(capabilityTimer);
}
});
</script>
<template>
<div
:class="[isDark ? 'dark' : '']"
class="auth-shell relative flex min-h-full flex-1 select-none overflow-hidden"
>
<div class="auth-shell-grid absolute inset-0"></div>
<div class="auth-shell-noise absolute inset-0"></div>
<div class="auth-glow auth-glow-primary"></div>
<div class="auth-glow auth-glow-secondary"></div>
<div class="auth-glow auth-glow-tertiary"></div>
<template v-if="toolbar">
<slot name="toolbar">
<Toolbar :toolbar-list="toolbarList" />
</slot>
</template>
<slot name="logo">
<div
v-if="logoSrc || appName"
class="absolute left-0 top-0 z-20 flex flex-1"
@click="clickLogo"
>
<div
class="auth-brand-chip text-foreground ml-4 mt-4 flex items-center sm:ml-6 sm:mt-6"
>
<img
v-if="logoSrc"
:key="logoSrc"
:alt="appName"
:src="logoSrc"
class="mr-2.5"
width="112"
/>
<span v-else class="auth-brand-name">{{ appName }}</span>
</div>
</div>
</slot>
<main
class="auth-stage relative z-10 flex min-h-full w-full items-center justify-center px-6 pb-10 pt-28 sm:px-10 sm:pt-32"
>
<div
class="auth-stage-inner mx-auto flex w-full max-w-[1080px] flex-col items-center"
>
<div
v-if="isLoginRoute"
class="auth-stage-copy w-full max-w-[780px] text-center"
>
<h1 class="auth-page-title text-foreground">
{{ stageGreeting }}
</h1>
<div
class="auth-stage-switcher text-muted-foreground"
aria-label="同一入口能力切换"
>
<span class="auth-stage-switcher-label">在同一入口管理</span>
<span class="auth-stage-pill" aria-live="polite">
<Transition mode="out-in" name="auth-pill">
<span
:key="capabilityLabels[activeCapabilityIndex]"
class="auth-stage-pill-text"
>
{{ capabilityLabels[activeCapabilityIndex] }}
</span>
</Transition>
</span>
</div>
</div>
<AuthenticationFormView
class="auth-window-host w-full"
:class="[
isLoginRoute ? 'max-w-[25rem] sm:max-w-[26rem]' : 'max-w-[31rem]',
isLoginRoute ? 'mt-8 sm:mt-10' : 'mt-0 sm:mt-4',
]"
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>
</main>
</div>
</template>
<style scoped>
.auth-shell {
background:
radial-gradient(
circle at top,
rgb(255 255 255 / 78%),
rgb(255 255 255 / 0%) 36%
),
linear-gradient(180deg, #f7faff 0%, #eef4fd 55%, #edf3fb 100%);
}
.auth-shell-grid {
background-image:
linear-gradient(rgb(13 74 160 / 6%) 1px, transparent 1px),
linear-gradient(90deg, rgb(13 74 160 / 6%) 1px, transparent 1px);
background-position: center center;
background-size: 120px 120px;
opacity: 0.42;
mask-image: linear-gradient(180deg, rgb(0 0 0 / 28%), transparent 75%);
}
.auth-shell-noise {
background-image:
radial-gradient(
circle at 20% 20%,
rgb(255 255 255 / 35%) 0 0.9px,
transparent 1.2px
),
radial-gradient(
circle at 80% 30%,
rgb(255 255 255 / 22%) 0 1px,
transparent 1.3px
),
radial-gradient(
circle at 40% 70%,
rgb(11 111 211 / 8%) 0 1px,
transparent 1.4px
);
background-size:
180px 180px,
240px 240px,
200px 200px;
mix-blend-mode: soft-light;
opacity: 0.65;
}
.auth-glow {
position: absolute;
pointer-events: none;
border-radius: 9999px;
}
.auth-glow-primary {
top: 4rem;
left: 50%;
width: 26rem;
height: 26rem;
background: radial-gradient(
circle,
rgb(87 150 255 / 24%) 0%,
rgb(87 150 255 / 0%) 68%
);
transform: translateX(-50%);
}
.auth-glow-secondary {
top: 46%;
left: 14%;
width: 20rem;
height: 20rem;
background: radial-gradient(
circle,
rgb(36 189 211 / 18%) 0%,
rgb(36 189 211 / 0%) 72%
);
}
.auth-glow-tertiary {
right: 10%;
bottom: 8%;
width: 22rem;
height: 22rem;
background: radial-gradient(
circle,
rgb(66 116 255 / 14%) 0%,
rgb(66 116 255 / 0%) 74%
);
}
.auth-brand-chip {
min-height: 3rem;
padding: 0.45rem 0.95rem;
background: rgb(255 255 255 / 72%);
border: 1px solid rgb(255 255 255 / 84%);
border-radius: 9999px;
box-shadow: 0 20px 44px -32px rgb(11 59 132 / 26%);
backdrop-filter: blur(12px);
}
.auth-brand-name {
font-size: 0.95rem;
font-weight: 600;
letter-spacing: -0.01em;
}
.auth-page-title {
max-width: 16ch;
margin: 0 auto;
font-size: clamp(2.1rem, 4vw, 3.7rem);
font-weight: 700;
line-height: 1.05;
letter-spacing: -0.04em;
}
.auth-stage-switcher {
display: inline-flex;
flex-wrap: wrap;
gap: 0.7rem;
align-items: center;
justify-content: center;
margin-top: 1rem;
}
.auth-stage-switcher-label {
font-size: 0.98rem;
line-height: 1.6;
}
.auth-stage-pill {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 6.5rem;
padding: 0.55rem 1rem;
font-size: 0.95rem;
font-weight: 600;
color: hsl(var(--nav-item-active-foreground));
background: rgb(255 255 255 / 74%);
border: 1px solid rgb(255 255 255 / 90%);
border-radius: 9999px;
box-shadow: 0 18px 34px -28px rgb(14 61 132 / 30%);
backdrop-filter: blur(10px);
}
.auth-stage-pill-text {
display: inline-flex;
justify-content: center;
min-width: 4em;
}
.auth-pill-enter-active,
.auth-pill-leave-active {
transition:
opacity 180ms ease,
transform 180ms ease;
}
.auth-pill-enter-from {
opacity: 0;
transform: translateY(8px);
}
.auth-pill-leave-to {
opacity: 0;
transform: translateY(-8px);
}
.auth-window-host {
position: relative;
z-index: 1;
}
.dark.auth-shell {
background:
radial-gradient(
circle at top,
rgb(35 66 114 / 32%),
rgb(15 22 35 / 0%) 40%
),
linear-gradient(180deg, #06101b 0%, #08111d 52%, #09131f 100%);
.auth-shell-grid {
background-image:
linear-gradient(rgb(118 160 241 / 8%) 1px, transparent 1px),
linear-gradient(90deg, rgb(118 160 241 / 8%) 1px, transparent 1px);
opacity: 0.36;
}
.auth-shell-noise {
opacity: 0.38;
}
.auth-brand-chip {
background: rgb(11 19 31 / 68%);
border-color: rgb(138 174 255 / 18%);
box-shadow: 0 24px 48px -34px rgb(0 0 0 / 52%);
}
.auth-stage-switcher-label {
color: rgb(195 206 230 / 80%);
}
.auth-stage-pill {
color: rgb(144 196 255 / 92%);
background: rgb(11 19 31 / 70%);
border-color: rgb(138 174 255 / 18%);
box-shadow: 0 18px 34px -28px rgb(0 0 0 / 46%);
}
.auth-glow-primary {
background: radial-gradient(
circle,
rgb(70 120 255 / 26%) 0%,
rgb(70 120 255 / 0%) 70%
);
}
.auth-glow-secondary {
background: radial-gradient(
circle,
rgb(41 170 201 / 18%) 0%,
rgb(41 170 201 / 0%) 72%
);
}
.auth-glow-tertiary {
background: radial-gradient(
circle,
rgb(95 128 255 / 16%) 0%,
rgb(95 128 255 / 0%) 74%
);
}
}
@media (max-width: 768px) {
.auth-page-title {
max-width: 14ch;
}
}
@media (max-width: 640px) {
.auth-stage {
padding-top: 6.75rem;
}
.auth-stage-copy {
text-align: left;
}
.auth-page-title {
max-width: none;
margin-right: 0;
margin-left: 0;
}
.auth-stage-switcher {
justify-content: flex-start;
}
}
</style>