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 ( -
-
-
-
-
{t('title.login')}
- +
+
{t('login.subtitle')}
+
+ +
+
{t('login.connection_current')}
+
{apiBase || detectedBase}
+
{t('login.connection_auto_hint')}
+
+ +
+ setShowCustomBase(e.target.checked)} + /> + +
+ + {showCustomBase && ( + setApiBase(e.target.value)} + hint={t('login.custom_connection_hint')} + /> + )} + + setManagementKey(e.target.value)} + onKeyDown={handleSubmitKeyDown} + rightElement={ + + } + /> + +
+ setRememberPassword(e.target.checked)} + /> + +
+ + + + {error &&
{error}
} + + {autoLoading && ( +
+
{t('auto_login.title')}
+
{t('auto_login.message')}
+
+ )}
-
{t('login.subtitle')}
- -
-
{t('login.connection_current')}
-
{apiBase || detectedBase}
-
{t('login.connection_auto_hint')}
-
- -
- setShowCustomBase(e.target.checked)} - /> - -
- - {showCustomBase && ( - setApiBase(e.target.value)} - hint={t('login.custom_connection_hint')} - /> - )} - - setManagementKey(e.target.value)} - onKeyDown={handleSubmitKeyDown} - rightElement={ - - } - /> - -
- setRememberPassword(e.target.checked)} - /> - -
- - - - {error &&
{error}
} - - {autoLoading && ( -
-
{t('auto_login.title')}
-
{t('auto_login.message')}
-
- )} ); diff --git a/src/styles/layout.scss b/src/styles/layout.scss index bb452ba..0ba8d9d 100644 --- a/src/styles/layout.scss +++ b/src/styles/layout.scss @@ -407,98 +407,6 @@ } } -.login-page { - min-height: 100vh; - display: flex; - align-items: center; - justify-content: center; - background: var(--bg-secondary); - padding: $spacing-lg; - - .login-card { - width: 100%; - max-width: 520px; - 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; - } - - .login-header { - display: flex; - flex-direction: column; - gap: $spacing-sm; - text-align: center; - - .title { - font-size: 22px; - font-weight: 800; - color: var(--text-primary); - } - - .subtitle { - color: var(--text-secondary); - } - } - - .login-title-row { - display: flex; - align-items: center; - justify-content: center; - gap: $spacing-sm; - flex-wrap: wrap; - } - - .login-language-btn { - white-space: nowrap; - } - - .connection-box { - 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); - } - - .item-actions { - display: flex; - gap: $spacing-md; - } - } - - .toggle-advanced { - display: flex; - justify-content: flex-start; - align-items: center; - gap: $spacing-xs; - color: var(--text-secondary); - cursor: pointer; - } - - .error-box { - 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; - } -} .grid { display: grid;