| | |
| | | |
| | | import { Copyright } from '../basic/copyright'; |
| | | import AuthenticationFormView from './form.vue'; |
| | | import SloganIcon from './icons/slogan.vue'; |
| | | import Toolbar from './toolbar.vue'; |
| | | |
| | | interface Props { |
| | |
| | | * @zh_CN 根据主题选择合适的 logo 图标 |
| | | */ |
| | | const logoSrc = computed(() => { |
| | | // 如果是暗色主题且提供了 logoDark,则使用暗色主题的 logo |
| | | if (isDark.value && props.logoDark) { |
| | | return props.logoDark; |
| | | } |
| | | // 否则使用默认的 logo |
| | | return props.logo; |
| | | }); |
| | | |
| | | </script> |
| | | |
| | | <template> |
| | | <div |
| | | :class="[isDark ? 'dark' : '']" |
| | | class="flex min-h-full flex-1 overflow-x-hidden select-none" |
| | | class="agri-auth flex min-h-full flex-1 overflow-x-hidden select-none" |
| | | > |
| | | <template v-if="toolbar"> |
| | | <slot name="toolbar"> |
| | |
| | | <!-- 左侧认证面板 --> |
| | | <AuthenticationFormView |
| | | v-if="authPanelLeft" |
| | | class="min-h-full w-2/5 flex-1" |
| | | class="agri-left-panel min-h-full w-2/5 flex-1" |
| | | data-side="left" |
| | | > |
| | | <template v-if="copyright" #copyright> |
| | |
| | | </AuthenticationFormView> |
| | | |
| | | <slot name="logo"> |
| | | <!-- 头部 Logo 和应用名称 --> |
| | | <!-- 头部 Logo 和应用名称(小屏展示,大屏由品牌区代替) --> |
| | | <div |
| | | v-if="logoSrc || appName" |
| | | class="absolute top-0 left-0 z-10 flex flex-1" |
| | | class="agri-mini-logo absolute top-0 left-0 z-10 flex flex-1" |
| | | @click="clickLogo" |
| | | > |
| | | <div |
| | |
| | | </slot> |
| | | |
| | | <!-- 系统介绍 --> |
| | | <div v-if="!authPanelCenter" class="relative hidden w-0 flex-1 lg:block"> |
| | | <div |
| | | class="absolute inset-0 size-full bg-background-deep dark:bg-[#070709]" |
| | | > |
| | | <div class="login-background absolute top-0 left-0 size-full"></div> |
| | | <div v-if="!authPanelCenter" class="relative hidden w-0 flex-[1.18] lg:block"> |
| | | <div class="agri-promo-layer absolute inset-0 size-full overflow-hidden"> |
| | | <div |
| | | :key="authPanelLeft ? 'left' : authPanelRight ? 'right' : 'center'" |
| | | class="mr-20 flex-col-center h-full px-8 text-center" |
| | | :class="{ |
| | | 'enter-x': authPanelLeft, |
| | | '-enter-x': authPanelRight, |
| | | }" |
| | | class="agri-promo relative flex h-full flex-col items-end justify-start px-10 pt-16 text-left" |
| | | > |
| | | <template v-if="sloganImage"> |
| | | <img |
| | | :alt="appName" |
| | | :src="sloganImage" |
| | | class="h-64 w-2/5 animate-float" |
| | | /> |
| | | </template> |
| | | <SloganIcon v-else :alt="appName" class="h-64 w-2/5 animate-float" /> |
| | | <div class="text-1xl mt-6 font-sans text-foreground lg:text-2xl"> |
| | | {{ pageTitle }} |
| | | <!-- 品牌区 --> |
| | | <div class="agri-brand"> |
| | | <div class="flex items-center gap-4"> |
| | | <img |
| | | src="/images/tahe-logo.png" |
| | | alt="塔河种业" |
| | | class="h-16 w-auto shrink-0 rounded-xl bg-white/95 p-1.5 shadow-lg" |
| | | /> |
| | | <div> |
| | | <div class="agri-brand-title text-3xl font-semibold tracking-[0.14em] xl:text-4xl"> |
| | | {{ pageTitle }} |
| | | </div> |
| | | <div class="agri-brand-sub mt-2 text-xs tracking-[0.3em] xl:text-sm"> |
| | | AGRICULTURE DIGITAL PLATFORM |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="agri-brand-tag mt-6 flex items-center justify-end gap-4"> |
| | | <span class="agri-brand-tag-line h-px w-12"></span> |
| | | <span class="text-base tracking-[0.22em]">{{ pageDescription }}</span> |
| | | <span class="agri-brand-tag-line h-px w-12"></span> |
| | | </div> |
| | | </div> |
| | | <div class="mt-2 dark:text-muted-foreground"> |
| | | {{ pageDescription }} |
| | | </div> |
| | | |
| | | </div> |
| | | |
| | | </div> |
| | | </div> |
| | | |
| | |
| | | </div> |
| | | |
| | | <!-- 右侧认证面板 --> |
| | | <AuthenticationFormView |
| | | v-if="authPanelRight" |
| | | class="min-h-full w-2/5 flex-1" |
| | | 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 v-if="authPanelRight" class="agri-form-wrap relative"> |
| | | <AuthenticationFormView class="agri-form-panel min-h-full w-full" data-side="right" /> |
| | | |
| | | <!-- 卡片右上树叶装饰 --> |
| | | <svg |
| | | class="pointer-events-none absolute top-0 right-0 h-36 w-40" |
| | | viewBox="0 0 180 150" |
| | | aria-hidden="true" |
| | | > |
| | | <path d="M180 4c-26 22-38 52-36 92" fill="none" stroke="#4ade80" stroke-width="2" stroke-linecap="round" /> |
| | | <g fill="#4ade80" opacity="0.85"> |
| | | <path d="M160 22q-16-10-30-2 12 12 30 2Z" /> |
| | | <path d="M150 44q-18-6-30 4 14 10 30-4Z" /> |
| | | <path d="M146 70q-18-2-28 10 16 7 28-10Z" /> |
| | | <path d="M146 96q-16 2-24 14 16 4 24-14Z" /> |
| | | </g> |
| | | <g fill="#86efac" opacity="0.7"> |
| | | <path d="M164 34q4-16 18-22 2 16-18 22Z" /> |
| | | <path d="M156 60q6-14 20-18 0 16-20 18Z" /> |
| | | <path d="M152 86q8-12 22-14-2 16-22 14Z" /> |
| | | </g> |
| | | <g fill="#bbf7d0" opacity="0.45"> |
| | | <path d="M96 118q-14-8-26-1 10 10 26 1Z" /> |
| | | <path d="M70 136q-12-6-22 0 9 8 22 0Z" /> |
| | | </g> |
| | | </svg> |
| | | |
| | | <!-- 卡片底部农场插画 --> |
| | | <svg |
| | | class="agri-farm pointer-events-none absolute inset-x-0 bottom-0 h-24 w-full" |
| | | viewBox="0 0 560 130" |
| | | preserveAspectRatio="xMidYMax slice" |
| | | aria-hidden="true" |
| | | > |
| | | <path d="M0 92 Q120 62 260 88 T560 80 V130 H0 Z" fill="#dcfce7" /> |
| | | <path d="M0 112 Q160 88 320 110 T560 104 V130 H0 Z" fill="#bbf7d0" /> |
| | | <g fill="#a7f3d0"> |
| | | <path d="M36 76 L58 58 L80 76 Z" /> |
| | | <rect x="42" y="76" width="32" height="22" fill="#ecfdf5" /> |
| | | <rect x="54" y="84" width="9" height="14" fill="#86efac" /> |
| | | </g> |
| | | <g> |
| | | <rect x="116" y="78" width="4" height="16" fill="#86efac" /> |
| | | <circle cx="118" cy="70" r="12" fill="#86efac" /> |
| | | <rect x="146" y="84" width="3.5" height="12" fill="#6ee7b7" /> |
| | | <circle cx="148" cy="78" r="8" fill="#6ee7b7" /> |
| | | </g> |
| | | <g stroke="#6ee7b7" stroke-width="3" stroke-linecap="round"> |
| | | <path d="M470 100V64" /> |
| | | <path d="M470 64l-14-10M470 64l16-6M470 64l-2 18" /> |
| | | </g> |
| | | <g fill="#a7f3d0"> |
| | | <path d="M498 82 L514 68 L530 82 Z" /> |
| | | <rect x="503" y="82" width="22" height="16" fill="#ecfdf5" /> |
| | | </g> |
| | | </svg> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <style scoped> |
| | | .login-background { |
| | | background: |
| | | radial-gradient(circle at 72% 38%, hsl(var(--primary) / 24%), transparent 42%), |
| | | linear-gradient(145deg, hsl(var(--background-deep)) 0%, hsl(var(--background)) 100%); |
| | | filter: blur(70px); |
| | | .agri-auth { |
| | | position: relative; |
| | | min-height: 100dvh; |
| | | overflow-y: auto; |
| | | background-color: #124b39; |
| | | background-image: url('/agriculture-login-bg.png'); |
| | | background-position: center; |
| | | background-size: cover; |
| | | isolation: isolate; |
| | | } |
| | | |
| | | .dark { |
| | | .login-background { |
| | | background: |
| | | radial-gradient(circle at 72% 38%, hsl(var(--primary) / 18%), transparent 42%), |
| | | linear-gradient(145deg, hsl(var(--background-deep)) 0%, hsl(var(--background)) 100%); |
| | | filter: blur(70px); |
| | | .login-background { |
| | | background-image: none; |
| | | filter: saturate(1.06) contrast(1.01); |
| | | } |
| | | |
| | | .agri-left-panel, |
| | | .agri-left-panel :deep(*) { |
| | | background-color: transparent !important; |
| | | } |
| | | |
| | | /* 品牌文字:白色 + 文字阴影保证在背景图上的可读性 */ |
| | | .agri-brand-title { |
| | | color: #fff; |
| | | text-shadow: 0 2px 10px rgba(2, 44, 34, 0.6); |
| | | } |
| | | |
| | | .agri-brand-sub { |
| | | color: rgba(240, 253, 250, 0.92); |
| | | text-shadow: 0 1px 6px rgba(2, 44, 34, 0.55); |
| | | } |
| | | |
| | | .agri-brand-tag { |
| | | color: rgba(240, 253, 250, 0.92); |
| | | text-shadow: 0 1px 6px rgba(2, 44, 34, 0.55); |
| | | } |
| | | |
| | | .agri-brand-tag-line { background: rgba(167, 243, 208, 0.8); } |
| | | |
| | | /* 大屏隐藏左上角小 logo,由品牌区展示 */ |
| | | @media (min-width: 1024px) { |
| | | .agri-mini-logo { |
| | | display: none; |
| | | } |
| | | } |
| | | |
| | | /* ---------- 品牌与内容 ---------- */ |
| | | .agri-promo { |
| | | max-width: 780px; |
| | | margin-left: clamp(2rem, 6vw, 7rem); |
| | | padding-top: 3rem; |
| | | padding-bottom: 4.5rem; |
| | | } |
| | | |
| | | /* ---------- 右侧登录卡片 ---------- */ |
| | | .agri-form-wrap { |
| | | align-self: center; |
| | | max-height: calc(100dvh - 5rem); |
| | | margin: 2.5rem clamp(1rem, 4vw, 4.5rem) 2.5rem 1.5rem; |
| | | width: 40%; |
| | | flex: 0.82 1 0%; |
| | | /* 外层统一圆角裁剪,避免底部插画等兄弟装饰把卡片直角化 */ |
| | | overflow: hidden; |
| | | border-radius: 1.75rem; |
| | | box-shadow: 0 24px 70px rgba(2, 44, 34, 0.24); |
| | | } |
| | | |
| | | .agri-form-panel { |
| | | overflow: auto; |
| | | border: 1px solid rgba(255, 255, 255, 0.9); |
| | | border-radius: 1.75rem; |
| | | background: rgba(250, 253, 251, 0.96) !important; |
| | | backdrop-filter: blur(18px); |
| | | } |
| | | |
| | | /* 表单内容上移,为底部插画留出空间 */ |
| | | .agri-form-wrap :deep(.side-content) { |
| | | margin-bottom: 4.5rem; |
| | | } |
| | | |
| | | /* 标题下的绿色装饰条 */ |
| | | .agri-form-wrap :deep(h2 + p)::after { |
| | | content: ''; |
| | | display: block; |
| | | width: 2.75rem; |
| | | height: 0.28rem; |
| | | margin-top: 0.8rem; |
| | | border-radius: 9999px; |
| | | background: #16a34a; |
| | | } |
| | | |
| | | /* 输入框前缀图标 */ |
| | | .agri-form-wrap :deep(input[autocomplete='username']), |
| | | .agri-form-wrap :deep(input[autocomplete='current-password']) { |
| | | padding-left: 2.6rem; |
| | | background-repeat: no-repeat; |
| | | background-position: 0.85rem center; |
| | | background-size: 1.05rem; |
| | | } |
| | | |
| | | .agri-form-wrap :deep(input[autocomplete='username']) { |
| | | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2'/%3E%3Ccircle cx='12' cy='7' r='4'/%3E%3C/svg%3E"); |
| | | } |
| | | |
| | | .agri-form-wrap :deep(input[autocomplete='current-password']) { |
| | | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='11' width='18' height='11' rx='2'/%3E%3Cpath d='M7 11V7a5 5 0 0 1 10 0v4'/%3E%3C/svg%3E"); |
| | | } |
| | | |
| | | /* 输入框整体质感:加高、圆角、柔和边框、聚焦绿色光圈 */ |
| | | .agri-form-wrap :deep(input) { |
| | | height: 3rem; |
| | | border-radius: 0.75rem; |
| | | border-color: rgba(15, 118, 110, 0.16); |
| | | background-color: #fff; |
| | | font-size: 0.95rem; |
| | | transition: border-color 200ms ease, box-shadow 200ms ease; |
| | | } |
| | | |
| | | .agri-form-wrap :deep(input:hover) { |
| | | border-color: rgba(16, 185, 129, 0.45); |
| | | } |
| | | |
| | | .agri-form-wrap :deep(input:focus), |
| | | .agri-form-wrap :deep(input:focus-visible) { |
| | | border-color: #10b981; |
| | | box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.16); |
| | | outline: none; |
| | | } |
| | | |
| | | .dark .agri-form-wrap :deep(input) { |
| | | border-color: rgba(255, 255, 255, 0.16); |
| | | background-color: rgba(255, 255, 255, 0.06); |
| | | } |
| | | |
| | | .dark .agri-form-wrap :deep(input:focus), |
| | | .dark .agri-form-wrap :deep(input:focus-visible) { |
| | | border-color: #34d399; |
| | | box-shadow: 0 0 0 3px rgba(52, 211, 153, 0.2); |
| | | } |
| | | |
| | | @media (max-width: 1023px) { |
| | | .agri-farm { |
| | | display: none; |
| | | } |
| | | } |
| | | |
| | | /* ---------- 暗色模式 ---------- */ |
| | | .dark .login-background { |
| | | background-image: |
| | | linear-gradient(90deg, rgba(2, 26, 20, 0.9), rgba(2, 26, 20, 0.62)), |
| | | url('/agriculture-login-bg.png'); |
| | | } |
| | | |
| | | .dark .agri-form-panel { |
| | | background: rgba(13, 31, 25, 0.96) !important; |
| | | } |
| | | |
| | | .dark .agri-form-wrap > svg { |
| | | opacity: 0.35; |
| | | } |
| | | |
| | | /* ---------- 响应式 ---------- */ |
| | | @media (max-width: 1023px) { |
| | | .agri-promo { margin-left: 2rem; } |
| | | .agri-form-wrap { margin-right: 1.25rem; } |
| | | } |
| | | |
| | | @media (max-height: 720px) and (min-width: 1024px) { |
| | | .agri-form-wrap { |
| | | max-height: calc(100dvh - 2rem); |
| | | margin-top: 1rem; |
| | | margin-bottom: 1rem; |
| | | } |
| | | |
| | | .agri-promo { |
| | | padding-top: 1.5rem; |
| | | padding-bottom: 3rem; |
| | | } |
| | | |
| | | .agri-promo .mt-12 { |
| | | margin-top: 2rem; |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 767px) { |
| | | .agri-form-wrap { |
| | | align-self: stretch; |
| | | margin: 1rem; |
| | | width: auto; |
| | | flex: none; |
| | | } |
| | | |
| | | .agri-form-panel { |
| | | border-radius: 1.5rem; |
| | | } |
| | | } |
| | | |
| | | /* ---------- 入场动画(纯 CSS,动画结束必然停留于可见态) ---------- */ |
| | | @media (prefers-reduced-motion: no-preference) { |
| | | .agri-promo { |
| | | animation: agri-in-left 0.75s cubic-bezier(0.22, 0.61, 0.36, 1) both; |
| | | } |
| | | |
| | | .agri-form-wrap { |
| | | animation: agri-in-right 0.75s cubic-bezier(0.22, 0.61, 0.36, 1) both; |
| | | } |
| | | |
| | | } |
| | | |
| | | @keyframes agri-in-left { |
| | | from { |
| | | opacity: 0; |
| | | transform: translateX(-28px); |
| | | } |
| | | } |
| | | |
| | | @keyframes agri-in-right { |
| | | from { |
| | | opacity: 0; |
| | | transform: translateX(28px); |
| | | } |
| | | } |
| | | |
| | | @keyframes agri-in-up { |
| | | from { |
| | | opacity: 0; |
| | | transform: translateY(18px); |
| | | } |
| | | } |
| | | |
| | | @media (prefers-reduced-motion: reduce) { |
| | | .agri-auth :deep(*) { |
| | | animation-duration: 0.01ms !important; |
| | | animation-iteration-count: 1 !important; |
| | | transition-duration: 0.01ms !important; |
| | | } |
| | | } |
| | | </style> |