diff --git a/src/pages/LoginPage.module.scss b/src/pages/LoginPage.module.scss new file mode 100644 index 0000000..4203ca0 --- /dev/null +++ b/src/pages/LoginPage.module.scss @@ -0,0 +1,234 @@ +@use '../styles/variables.scss' as *; + +// 主容器 - 左右分栏布局 +.container { + min-height: 100vh; + display: flex; + background: var(--bg-primary); +} + +// 左侧品牌展示区 +.brandPanel { + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background: #000000; + padding: $spacing-2xl; + position: relative; + overflow: hidden; + + // 移动端隐藏 + @media (max-width: $breakpoint-mobile) { + display: none; + } +} + +// 品牌文字容器 +.brandContent { + position: relative; + z-index: 1; + display: flex; + flex-direction: column; + align-items: flex-end; + width: 100%; + padding: 0; + gap: 0; +} + +// 品牌大字 +.brandWord { + font-size: 13vw; + font-weight: 900; + color: rgba(255, 255, 255, 0.9); + letter-spacing: -0.02em; + line-height: 0.85; + text-transform: uppercase; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif; + text-align: right; + padding-right: 0; + + // 不同字有不同的透明度形成层次感 + &:nth-child(1) { + color: rgba(255, 255, 255, 0.95); + } + + &:nth-child(2) { + color: rgba(255, 255, 255, 0.7); + } + + &:nth-child(3) { + color: rgba(255, 255, 255, 0.45); + } +} + +// 右侧功能交互区 +.formPanel { + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: $spacing-2xl; + background: var(--bg-primary); + position: relative; + + @media (max-width: $breakpoint-mobile) { + padding: $spacing-lg; + min-height: 100vh; + } +} + +// 右侧内容容器 +.formContent { + width: 100%; + max-width: 420px; + display: flex; + flex-direction: column; + align-items: center; + gap: $spacing-xl; +} + +// Logo +.logo { + width: 80px; + height: 80px; + border-radius: $radius-full; + object-fit: cover; + box-shadow: var(--shadow-lg); + border: 3px solid var(--border-color); +} + +// 登录表单卡片 +.loginCard { + width: 100%; + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: $radius-lg; + box-shadow: var(--shadow-lg); + padding: $spacing-xl; + display: flex; + flex-direction: column; + gap: $spacing-lg; + + @media (max-width: $breakpoint-mobile) { + padding: $spacing-lg; + box-shadow: none; + border: none; + background: transparent; + } +} + +// 登录头部 +.loginHeader { + display: flex; + flex-direction: column; + gap: $spacing-sm; + text-align: center; +} + +// 标题行 +.titleRow { + display: flex; + align-items: center; + justify-content: center; + gap: $spacing-sm; + flex-wrap: wrap; +} + +// 标题 +.title { + font-size: 22px; + font-weight: 800; + color: var(--text-primary); +} + +// 副标题 +.subtitle { + color: var(--text-secondary); + font-size: 14px; +} + +// 语言切换按钮 +.languageBtn { + white-space: nowrap; +} + +// 连接信息框 +.connectionBox { + background: var(--bg-secondary); + border: 1px dashed var(--border-color); + border-radius: $radius-md; + padding: $spacing-md; + display: flex; + flex-direction: column; + gap: $spacing-xs; + + .label { + color: var(--text-secondary); + font-size: 14px; + } + + .value { + font-weight: 700; + color: var(--text-primary); + word-break: break-all; + } + + .hint { + color: var(--text-secondary); + font-size: 12px; + } +} + +// 复选框行 +.toggleAdvanced { + display: flex; + justify-content: flex-start; + align-items: center; + gap: $spacing-xs; + color: var(--text-secondary); + cursor: pointer; + + input[type='checkbox'] { + cursor: pointer; + } + + label { + cursor: pointer; + font-size: 14px; + } +} + +// 错误提示框 +.errorBox { + background: rgba(239, 68, 68, 0.1); + border: 1px solid rgba(239, 68, 68, 0.4); + border-radius: $radius-md; + padding: $spacing-sm $spacing-md; + color: $error-color; + font-size: 14px; +} + +// 自动登录提示 +.autoLoginBox { + background: var(--bg-secondary); + border: 1px dashed var(--border-color); + border-radius: $radius-md; + padding: $spacing-md; + display: flex; + flex-direction: column; + gap: $spacing-xs; + text-align: center; + + .label { + color: var(--text-secondary); + font-size: 14px; + } + + .value { + font-weight: 600; + color: var(--text-primary); + } +} diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index e48dd89..0022079 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -6,6 +6,8 @@ import { Input } from '@/components/ui/Input'; import { IconEye, IconEyeOff } from '@/components/ui/icons'; import { useAuthStore, useLanguageStore, useNotificationStore } from '@/stores'; import { detectApiBaseFromLocation, normalizeApiBase } from '@/utils/connection'; +import { INLINE_LOGO_JPEG } from '@/assets/logoInline'; +import styles from './LoginPage.module.scss'; export function LoginPage() { const { t } = useTranslation(); @@ -50,7 +52,7 @@ export function LoginPage() { init(); }, [detectedBase, restoreSession, storedBase, storedKey, storedRememberPassword]); - const handleSubmit = async () => { + const handleSubmit = useCallback(async () => { if (!managementKey.trim()) { setError(t('login.error_required')); return; @@ -74,7 +76,7 @@ export function LoginPage() { } finally { setLoading(false); } - }; + }, [apiBase, detectedBase, login, managementKey, navigate, rememberPassword, showNotification, t]); const handleSubmitKeyDown = useCallback( (event: React.KeyboardEvent) => { @@ -92,103 +94,121 @@ export function LoginPage() { } return ( -