This commit is contained in:
2026-04-24 16:15:00 +08:00
parent f65177b147
commit b355568189
16 changed files with 495 additions and 217 deletions

View File

@@ -15,11 +15,11 @@ import {
import {
type AuthAuditLogEntry,
type AuthCaptchaChallenge,
authEntry,
type AuthLoginMethod,
type AuthRiskBlockSummary,
type AuthSessionSummary,
type AuthUser,
authEntry,
bindWechatPhone,
changePassword,
changePhoneNumber,
@@ -38,6 +38,7 @@ import {
resetPassword,
revokeAuthSession,
sendPhoneLoginCode,
setStoredLastLoginPhone,
startWechatLogin,
} from '../../services/authService';
import { AccountModal } from './AccountModal';
@@ -694,6 +695,7 @@ export function AuthGate({ children }: AuthGateProps) {
setError('');
try {
const nextUser = await loginWithPhoneCode(phone, code);
setStoredLastLoginPhone(phone);
setLoginCaptchaChallenge(null);
activateReadyUser(nextUser);
} catch (loginError) {
@@ -711,6 +713,7 @@ export function AuthGate({ children }: AuthGateProps) {
setError('');
try {
const nextUser = await authEntry(username, password);
setStoredLastLoginPhone(username);
activateReadyUser(nextUser);
} catch (loginError) {
setError(
@@ -727,6 +730,7 @@ export function AuthGate({ children }: AuthGateProps) {
setError('');
try {
const nextUser = await resetPassword(phone, code, newPassword);
setStoredLastLoginPhone(phone);
activateReadyUser(nextUser);
} catch (resetError) {
setError(

View File

@@ -6,6 +6,7 @@ import type {
AuthCaptchaChallenge,
AuthLoginMethod,
} from '../../services/authService';
import { getStoredLastLoginPhone } from '../../services/authService';
import { CaptchaChallengeField } from './CaptchaChallengeField';
type SmsScene = 'login' | 'reset_password';
@@ -57,11 +58,9 @@ export function LoginScreen({
onResetPassword,
onStartWechatLogin,
}: LoginScreenProps) {
const [activeTab, setActiveTab] = useState<'login' | 'register'>('login');
const [isResetPanelOpen, setIsResetPanelOpen] = useState(false);
const [username, setUsername] = useState('');
const [phone, setPhone] = useState(() => getStoredLastLoginPhone());
const [password, setPassword] = useState('');
const [phone, setPhone] = useState('');
const [code, setCode] = useState('');
const [resetPhone, setResetPhone] = useState('');
const [resetCode, setResetCode] = useState('');
@@ -154,75 +153,55 @@ export function LoginScreen({
/>
) : (
<div className="flex flex-col gap-4 px-5 py-5">
<div className="grid grid-cols-2 gap-2 rounded-full bg-[var(--platform-subpanel-bg)] p-1">
<TabButton
active={activeTab === 'login'}
label="登录"
onClick={() => setActiveTab('login')}
/>
<TabButton
active={activeTab === 'register'}
label="注册"
onClick={() => setActiveTab('register')}
/>
</div>
{activeTab === 'login' ? (
{passwordLoginEnabled ? (
<form
className="flex flex-col gap-4"
onSubmit={(event) => {
event.preventDefault();
if (!passwordLoginEnabled) {
return;
}
void onPasswordSubmit(username, password);
void onPasswordSubmit(phone, password);
}}
>
{passwordLoginEnabled ? (
<>
<label className="grid gap-2 text-sm text-[var(--platform-text-base)]">
<span></span>
<input
className="platform-input"
autoComplete="username"
value={username}
onChange={(event) => setUsername(event.target.value)}
placeholder="用户名"
/>
</label>
<label className="grid gap-2 text-sm text-[var(--platform-text-base)]">
<span></span>
<input
className="platform-input"
autoComplete="current-password"
type="password"
value={password}
onChange={(event) => setPassword(event.target.value)}
placeholder="输入密码"
/>
</label>
</>
) : null}
<label className="grid gap-2 text-sm text-[var(--platform-text-base)]">
<span></span>
<input
className="platform-input"
autoComplete="tel"
inputMode="numeric"
value={phone}
onChange={(event) => setPhone(event.target.value)}
placeholder="13800000000"
/>
</label>
<label className="grid gap-2 text-sm text-[var(--platform-text-base)]">
<span></span>
<input
className="platform-input"
autoComplete="current-password"
type="password"
value={password}
onChange={(event) => setPassword(event.target.value)}
placeholder="输入密码"
/>
</label>
{error ? <ErrorBanner message={error} /> : null}
{passwordLoginEnabled ? (
<div className="flex flex-col gap-2">
<button
type="submit"
disabled={submitDisabled || !username.trim() || !password.trim()}
disabled={submitDisabled || !phone.trim() || !password.trim()}
className="platform-button platform-button--primary h-12 px-4 text-base disabled:cursor-not-allowed disabled:opacity-60"
>
{loggingIn ? '登录中' : '登录'}
{loggingIn ? '登录中' : '注册/登录'}
</button>
) : null}
<button
type="button"
className="self-center text-sm text-[var(--platform-accent)]"
onClick={() => setIsResetPanelOpen(true)}
>
</button>
<button
type="button"
className="self-end text-sm text-[var(--platform-accent)]"
onClick={() => setIsResetPanelOpen(true)}
>
</button>
</div>
{wechatLoginEnabled ? (
<WechatButton
@@ -232,7 +211,9 @@ export function LoginScreen({
/>
) : null}
</form>
) : (
) : null}
{phoneLoginEnabled ? (
<PhoneCodeForm
phone={phone}
code={code}
@@ -243,8 +224,9 @@ export function LoginScreen({
loggingIn={loggingIn}
error={error}
hint={hint}
submitLabel="注册登录"
submitLabel="注册/登录"
enabled={phoneLoginEnabled}
showPhoneField={!passwordLoginEnabled}
onPhoneChange={setPhone}
onCodeChange={setCode}
onCaptchaAnswerChange={setCaptchaAnswer}
@@ -262,7 +244,7 @@ export function LoginScreen({
}}
onSubmit={() => onPhoneSubmit(phone, code)}
/>
)}
) : null}
{!passwordLoginEnabled && !phoneLoginEnabled && !wechatLoginEnabled ? (
<div className="platform-subpanel rounded-2xl px-4 py-4 text-sm text-[var(--platform-text-base)]">
@@ -276,30 +258,6 @@ export function LoginScreen({
);
}
function TabButton({
active,
label,
onClick,
}: {
active: boolean;
label: string;
onClick: () => void;
}) {
return (
<button
type="button"
className={`h-10 rounded-full text-sm font-medium transition ${
active
? 'bg-[var(--platform-panel-bg)] text-[var(--platform-text-strong)] shadow-sm'
: 'text-[var(--platform-text-muted)]'
}`}
onClick={onClick}
>
{label}
</button>
);
}
function PhoneCodeForm({
phone,
code,
@@ -312,6 +270,7 @@ function PhoneCodeForm({
hint,
submitLabel,
enabled,
showPhoneField,
onPhoneChange,
onCodeChange,
onCaptchaAnswerChange,
@@ -329,6 +288,7 @@ function PhoneCodeForm({
hint: string;
submitLabel: string;
enabled: boolean;
showPhoneField: boolean;
onPhoneChange: (value: string) => void;
onCodeChange: (value: string) => void;
onCaptchaAnswerChange: (value: string) => void;
@@ -347,17 +307,19 @@ function PhoneCodeForm({
void onSubmit();
}}
>
<label className="grid gap-2 text-sm text-[var(--platform-text-base)]">
<span></span>
<input
className="platform-input"
autoComplete="tel"
inputMode="numeric"
value={phone}
onChange={(event) => onPhoneChange(event.target.value)}
placeholder="13800000000"
/>
</label>
{showPhoneField ? (
<label className="grid gap-2 text-sm text-[var(--platform-text-base)]">
<span></span>
<input
className="platform-input"
autoComplete="tel"
inputMode="numeric"
value={phone}
onChange={(event) => onPhoneChange(event.target.value)}
placeholder="13800000000"
/>
</label>
) : null}
<label className="grid gap-2 text-sm text-[var(--platform-text-base)]">
<span></span>