视觉与品牌 - public/logo.svg、public/login-illustration.svg、favicon 替换为 logo.svg。 - preferences.ts 新增 logo.source 指向 /logo.svg。 - packages/effects/layouts 导出 LoginIllustration 共享组件(浮动插画,加载失败自动隐藏)。 Auth 布局重构 - layouts/auth.vue 替换默认 AuthPageLayout:主题色渐变背景 + 左侧浮动插画 + 品牌卡片 + 右上 LanguageToggle + 圆角登录卡片;KeepAlive 仅缓存 Login (CodeLogin/QrCodeLogin 需要每次刷新验证码/二维码)。 - 表单样式通过 :deep(.login-form-container) 覆盖输入框、选择框、主按钮, 全部走 --primary 变量,与主题色联动。 登录组件 - views/_core/authentication/login.vue: 关闭默认 codeLogin/qrcodeLogin/ thirdPartyLogin/register/docLink,改为手机 / 二维码 / 企微三按钮(IconifyIcon); 三方登录前检查租户,缺失时 message.warning + validateField。 - packages/effects/common-ui/authentication/login.vue + types.ts: 新增 showDocLink prop(默认 true),替代原本 HTML 注释掉 DocLink 的做法。 - sso-callback.vue: 等待提示改为 LottieLoading 动画。 Lottie 启动动画 - 新增 loading.html(注入进 index.html)+ public/loading.json + 运行时拷贝的 public/lottie_light.min.js(.gitignore 忽略)。 - 新增 public/lottie-tint.js:共享主题色适配器(LIGHTNESSES / SHADE_MAP / hslToRgb / patchColors / readPrimary),同时被启动白屏脚本与 Vue 组件使用, 消除两份几乎一致的实现。 - 新增 src/components/lottie-loading/LottieLoading.vue:按需加载 lottie-tint.js 并根据 CSS 变量 --primary 重新上色。 - vite.config.mts 新增 copyLottiePlayer 插件:configResolved 时无条件把 node_modules/lottie-web/.../lottie_light.min.js 拷到 public/,避免 mtime 误判。 - package.json 新增 lottie-web ^5.13.0 依赖。 i18n - 新增 otherLoginMethods / contactSupport / weComLogin 三个文案(zh-CN / en-US)。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
176 lines
5.7 KiB
Vue
176 lines
5.7 KiB
Vue
<script lang="ts" setup>
|
||
import { computed } from 'vue';
|
||
|
||
import { LanguageToggle, LoginIllustration } from '@vben/layouts';
|
||
import { preferences, usePreferences } from '@vben/preferences';
|
||
|
||
import { $t } from '#/locales';
|
||
|
||
const appName = computed(() => preferences.app.name);
|
||
const logo = computed(() => preferences.logo.source);
|
||
const { isDark } = usePreferences();
|
||
</script>
|
||
|
||
<template>
|
||
<div
|
||
:class="[isDark ? 'dark' : '']"
|
||
class="relative flex h-screen w-full overflow-hidden font-sans"
|
||
>
|
||
<!-- 主题色渐变背景 + 插画(主题色走 CSS 变量 --primary) -->
|
||
<div class="absolute inset-0 z-0 size-full">
|
||
<!-- 渐变:主色 -> 主色淡 -> 主色更淡 -->
|
||
<div
|
||
class="absolute inset-0 size-full bg-gradient-to-r from-[hsl(var(--primary)/0.85)] via-[hsl(var(--primary)/0.3)] to-[hsl(var(--primary)/0.08)] dark:from-[hsl(var(--primary)/0.85)] dark:via-[hsl(var(--primary)/0.4)] dark:to-[hsl(var(--primary)/0.12)]"
|
||
>
|
||
<!-- 浮动插画 - 左侧区域 -->
|
||
<div
|
||
class="absolute -left-[5%] top-1/2 hidden h-[48rem] w-[65%] -translate-y-1/2 lg:block"
|
||
>
|
||
<LoginIllustration :alt="appName" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 左上角品牌标识 -->
|
||
<div
|
||
class="absolute left-4 top-4 z-20 flex items-center gap-3 lg:left-6 lg:top-6"
|
||
@click.prevent
|
||
>
|
||
<div
|
||
class="flex items-center justify-center rounded-lg bg-white/90 p-0.5 shadow-[0_2px_8px_rgba(0,0,0,0.12)] backdrop-blur-md transition-all hover:bg-white/95 hover:shadow-[0_4px_12px_rgba(0,0,0,0.18)] lg:p-1"
|
||
>
|
||
<img
|
||
v-if="logo"
|
||
:src="logo"
|
||
:alt="appName"
|
||
class="size-10 lg:size-12"
|
||
/>
|
||
<span
|
||
v-else
|
||
class="text-xl text-[hsl(var(--primary))] lg:text-2xl"
|
||
>💡</span>
|
||
</div>
|
||
<span
|
||
class="relative top-[1px] text-xl font-semibold tracking-tight text-white drop-shadow-[0_2px_4px_rgba(0,0,0,0.3)] lg:top-[2px] lg:text-2xl"
|
||
>
|
||
{{ appName }}
|
||
</span>
|
||
</div>
|
||
|
||
<!-- 右上角 Toolbar -->
|
||
<div
|
||
class="absolute right-2 top-4 z-20 flex items-center gap-1 rounded-3xl bg-accent px-3 py-1 lg:right-6 lg:top-6"
|
||
>
|
||
<LanguageToggle v-if="preferences.widget.languageToggle" />
|
||
</div>
|
||
|
||
<!-- 内容区域 -->
|
||
<div
|
||
class="relative z-10 flex w-full flex-1 items-center justify-center px-6 py-5 lg:justify-end lg:px-12"
|
||
>
|
||
<!-- 登录卡片 - 响应式居中/靠右 -->
|
||
<div class="w-full md:w-[480px] lg:mr-[10%]">
|
||
<div
|
||
class="shadow-[0_30px_60px_-15px_hsl(var(--primary)/0.2)] dark:shadow-[0_30px_60px_-15px_hsl(var(--primary)/0.3)] relative overflow-hidden rounded-[2.5rem] bg-background p-8"
|
||
>
|
||
<!-- 登录表单 - RouterView 渲染实际的登录组件
|
||
KeepAlive 只缓存 Login:CodeLogin/QrCodeLogin 需要每次进入刷新验证码/二维码,不应缓存 -->
|
||
<div class="login-form-container">
|
||
<RouterView v-slot="{ Component, route }">
|
||
<Transition appear mode="out-in" name="fade">
|
||
<KeepAlive :include="['Login']">
|
||
<component :is="Component" :key="route.fullPath" />
|
||
</KeepAlive>
|
||
</Transition>
|
||
</RouterView>
|
||
</div>
|
||
|
||
<!-- 遇到问题提示(纯文本,无跳转;如需接入客服链接再改回 <a>) -->
|
||
<div class="mt-6 text-center">
|
||
<span class="text-xs text-muted-foreground">
|
||
{{ $t('authentication.contactSupport') }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
/* 登录表单容器样式覆盖(主题色走 --primary 变量) */
|
||
:deep(.login-form-container) {
|
||
/* 标题靠左对齐 */
|
||
.mb-7,
|
||
.mb-7 h2,
|
||
.mb-7 p {
|
||
text-align: left !important;
|
||
}
|
||
|
||
/* 输入框样式 */
|
||
.vben-input,
|
||
.vben-input-password input {
|
||
@apply rounded-2xl border-slate-100 bg-slate-50 px-6 py-4 text-sm transition-all;
|
||
@apply focus:border-[hsl(var(--primary)/0.3)] focus:bg-white focus:ring-4 focus:ring-[hsl(var(--primary)/0.1)];
|
||
}
|
||
|
||
/* 深色模式输入框 */
|
||
.dark .vben-input,
|
||
.dark .vben-input-password input {
|
||
@apply border-slate-700 bg-slate-800;
|
||
@apply focus:border-[hsl(var(--primary)/0.5)] focus:bg-slate-900 focus:ring-[hsl(var(--primary)/0.2)];
|
||
}
|
||
|
||
/* 选择框样式 */
|
||
.vben-select .vben-input {
|
||
@apply rounded-2xl border-slate-100 bg-slate-50 px-6 py-4 text-sm;
|
||
}
|
||
|
||
.dark .vben-select .vben-input {
|
||
@apply border-slate-700 bg-slate-800;
|
||
}
|
||
|
||
/* 登录按钮样式 */
|
||
.vben-button[aria-label='login'] {
|
||
@apply w-full rounded-2xl bg-[hsl(var(--primary))] py-4 text-base font-bold tracking-wide text-white shadow-lg shadow-[hsl(var(--primary)/0.3)];
|
||
@apply transition-all hover:-translate-y-1 hover:bg-[hsl(var(--primary)/0.9)] active:translate-y-0;
|
||
}
|
||
|
||
.dark .vben-button[aria-label='login'] {
|
||
@apply shadow-[hsl(var(--primary)/0.4)];
|
||
}
|
||
|
||
/* 记住我和忘记密码 */
|
||
.vben-checkbox label {
|
||
@apply text-xs text-slate-400 dark:text-slate-500;
|
||
}
|
||
|
||
.vben-link {
|
||
@apply text-xs text-slate-400 transition-colors hover:text-slate-600 dark:text-slate-500 dark:hover:text-slate-400;
|
||
}
|
||
}
|
||
|
||
/* 过渡动画 - 快速响应 */
|
||
.fade-enter-active {
|
||
transition:
|
||
opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1),
|
||
transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
|
||
.fade-leave-active {
|
||
transition:
|
||
opacity 0.15s cubic-bezier(0.4, 0, 1, 1),
|
||
transform 0.15s cubic-bezier(0.4, 0, 1, 1);
|
||
}
|
||
|
||
.fade-enter-from {
|
||
opacity: 0;
|
||
transform: translateY(8px);
|
||
}
|
||
|
||
.fade-leave-to {
|
||
opacity: 0;
|
||
transform: translateY(-8px);
|
||
}
|
||
</style>
|