mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-20 11:40:50 +08:00
feat(login): redesign login page with split layout
This commit is contained in:
@@ -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 (
|
||||
<div className="login-page">
|
||||
<div className="login-card">
|
||||
<div className="login-header">
|
||||
<div className="login-title-row">
|
||||
<div className="title">{t('title.login')}</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="login-language-btn"
|
||||
onClick={toggleLanguage}
|
||||
title={t('language.switch')}
|
||||
aria-label={t('language.switch')}
|
||||
>
|
||||
{nextLanguageLabel}
|
||||
<div className={styles.container}>
|
||||
{/* 左侧品牌展示区 */}
|
||||
<div className={styles.brandPanel}>
|
||||
<div className={styles.brandContent}>
|
||||
<span className={styles.brandWord}>CLI</span>
|
||||
<span className={styles.brandWord}>PROXY</span>
|
||||
<span className={styles.brandWord}>API</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 右侧功能交互区 */}
|
||||
<div className={styles.formPanel}>
|
||||
<div className={styles.formContent}>
|
||||
{/* Logo */}
|
||||
<img src={INLINE_LOGO_JPEG} alt="Logo" className={styles.logo} />
|
||||
|
||||
{/* 登录表单卡片 */}
|
||||
<div className={styles.loginCard}>
|
||||
<div className={styles.loginHeader}>
|
||||
<div className={styles.titleRow}>
|
||||
<div className={styles.title}>{t('title.login')}</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className={styles.languageBtn}
|
||||
onClick={toggleLanguage}
|
||||
title={t('language.switch')}
|
||||
aria-label={t('language.switch')}
|
||||
>
|
||||
{nextLanguageLabel}
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles.subtitle}>{t('login.subtitle')}</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.connectionBox}>
|
||||
<div className={styles.label}>{t('login.connection_current')}</div>
|
||||
<div className={styles.value}>{apiBase || detectedBase}</div>
|
||||
<div className={styles.hint}>{t('login.connection_auto_hint')}</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.toggleAdvanced}>
|
||||
<input
|
||||
id="custom-connection-toggle"
|
||||
type="checkbox"
|
||||
checked={showCustomBase}
|
||||
onChange={(e) => setShowCustomBase(e.target.checked)}
|
||||
/>
|
||||
<label htmlFor="custom-connection-toggle">{t('login.custom_connection_label')}</label>
|
||||
</div>
|
||||
|
||||
{showCustomBase && (
|
||||
<Input
|
||||
label={t('login.custom_connection_label')}
|
||||
placeholder={t('login.custom_connection_placeholder')}
|
||||
value={apiBase}
|
||||
onChange={(e) => setApiBase(e.target.value)}
|
||||
hint={t('login.custom_connection_hint')}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Input
|
||||
autoFocus
|
||||
label={t('login.management_key_label')}
|
||||
placeholder={t('login.management_key_placeholder')}
|
||||
type={showKey ? 'text' : 'password'}
|
||||
value={managementKey}
|
||||
onChange={(e) => setManagementKey(e.target.value)}
|
||||
onKeyDown={handleSubmitKeyDown}
|
||||
rightElement={
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-ghost btn-sm"
|
||||
onClick={() => setShowKey((prev) => !prev)}
|
||||
aria-label={
|
||||
showKey
|
||||
? t('login.hide_key', { defaultValue: '隐藏密钥' })
|
||||
: t('login.show_key', { defaultValue: '显示密钥' })
|
||||
}
|
||||
title={
|
||||
showKey
|
||||
? t('login.hide_key', { defaultValue: '隐藏密钥' })
|
||||
: t('login.show_key', { defaultValue: '显示密钥' })
|
||||
}
|
||||
>
|
||||
{showKey ? <IconEyeOff size={16} /> : <IconEye size={16} />}
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className={styles.toggleAdvanced}>
|
||||
<input
|
||||
id="remember-password-toggle"
|
||||
type="checkbox"
|
||||
checked={rememberPassword}
|
||||
onChange={(e) => setRememberPassword(e.target.checked)}
|
||||
/>
|
||||
<label htmlFor="remember-password-toggle">{t('login.remember_password_label')}</label>
|
||||
</div>
|
||||
|
||||
<Button fullWidth onClick={handleSubmit} loading={loading}>
|
||||
{loading ? t('login.submitting') : t('login.submit_button')}
|
||||
</Button>
|
||||
|
||||
{error && <div className={styles.errorBox}>{error}</div>}
|
||||
|
||||
{autoLoading && (
|
||||
<div className={styles.autoLoginBox}>
|
||||
<div className={styles.label}>{t('auto_login.title')}</div>
|
||||
<div className={styles.value}>{t('auto_login.message')}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="subtitle">{t('login.subtitle')}</div>
|
||||
</div>
|
||||
|
||||
<div className="connection-box">
|
||||
<div className="label">{t('login.connection_current')}</div>
|
||||
<div className="value">{apiBase || detectedBase}</div>
|
||||
<div className="hint">{t('login.connection_auto_hint')}</div>
|
||||
</div>
|
||||
|
||||
<div className="toggle-advanced">
|
||||
<input
|
||||
id="custom-connection-toggle"
|
||||
type="checkbox"
|
||||
checked={showCustomBase}
|
||||
onChange={(e) => setShowCustomBase(e.target.checked)}
|
||||
/>
|
||||
<label htmlFor="custom-connection-toggle">{t('login.custom_connection_label')}</label>
|
||||
</div>
|
||||
|
||||
{showCustomBase && (
|
||||
<Input
|
||||
label={t('login.custom_connection_label')}
|
||||
placeholder={t('login.custom_connection_placeholder')}
|
||||
value={apiBase}
|
||||
onChange={(e) => setApiBase(e.target.value)}
|
||||
hint={t('login.custom_connection_hint')}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Input
|
||||
autoFocus
|
||||
label={t('login.management_key_label')}
|
||||
placeholder={t('login.management_key_placeholder')}
|
||||
type={showKey ? 'text' : 'password'}
|
||||
value={managementKey}
|
||||
onChange={(e) => setManagementKey(e.target.value)}
|
||||
onKeyDown={handleSubmitKeyDown}
|
||||
rightElement={
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-ghost btn-sm"
|
||||
onClick={() => setShowKey((prev) => !prev)}
|
||||
aria-label={
|
||||
showKey
|
||||
? t('login.hide_key', { defaultValue: '隐藏密钥' })
|
||||
: t('login.show_key', { defaultValue: '显示密钥' })
|
||||
}
|
||||
title={
|
||||
showKey
|
||||
? t('login.hide_key', { defaultValue: '隐藏密钥' })
|
||||
: t('login.show_key', { defaultValue: '显示密钥' })
|
||||
}
|
||||
>
|
||||
{showKey ? <IconEyeOff size={16} /> : <IconEye size={16} />}
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="toggle-advanced">
|
||||
<input
|
||||
id="remember-password-toggle"
|
||||
type="checkbox"
|
||||
checked={rememberPassword}
|
||||
onChange={(e) => setRememberPassword(e.target.checked)}
|
||||
/>
|
||||
<label htmlFor="remember-password-toggle">{t('login.remember_password_label')}</label>
|
||||
</div>
|
||||
|
||||
<Button fullWidth onClick={handleSubmit} loading={loading}>
|
||||
{loading ? t('login.submitting') : t('login.submit_button')}
|
||||
</Button>
|
||||
|
||||
{error && <div className="error-box">{error}</div>}
|
||||
|
||||
{autoLoading && (
|
||||
<div className="connection-box">
|
||||
<div className="label">{t('auto_login.title')}</div>
|
||||
<div className="value">{t('auto_login.message')}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user