fix(usercenter): 删冗余 Preferences 卡片 + 修登录态进用户中心闪登录页

1. Settings 页删掉 Preferences 卡片(语言/主题切换)——顶栏已有语言下拉 + 主题
   切换按钮,重复。连带删只被它用的 Preferences/Row/Seg 函数 + useUI 导入。
2. 修 bug:从官网登录态点"用户中心"进 /user/ 会先闪登录页再跳面板。根因:渲染
   守卫在 !ready(会话续期中)时也直接渲染 Login。改为:本地有 refresh token 且
   续期未完成时显示加载态(非登录页),续期成功→面板、失败/无 token→登录页。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-07-08 11:22:11 +08:00
parent 87647cae34
commit 9ed7588ec0
2 changed files with 10 additions and 55 deletions
-53
View File
@@ -5,7 +5,6 @@ import React, { useEffect, useState } from 'react';
import { Icon } from './icons';
import { card, input } from './shared';
import { ErrorLine } from './Login';
import { useUI } from '../lib/theme';
import type { TFn, Lang } from '../lib/i18n';
import { getClient } from '../lib/api/client';
import { bilingual } from '../lib/api/errors';
@@ -15,7 +14,6 @@ export default function Settings({ t, lang, totpEnabled, onTotpChange }: { t: TF
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
<div style={{ fontFamily: 'var(--font-display)', fontSize: 25, fontWeight: 700, color: 'var(--fg1)' }}>{t('settingsTitle')}</div>
<Preferences t={t} />
<TotpSection t={t} lang={lang} enabled={totpEnabled} onChange={onTotpChange} />
<Devices t={t} lang={lang} />
</div>
@@ -24,57 +22,6 @@ export default function Settings({ t, lang, totpEnabled, onTotpChange }: { t: TF
const sectionTitle: React.CSSProperties = { fontSize: 14.5, fontWeight: 700, color: 'var(--fg1)' };
function Preferences({ t }: { t: TFn }) {
const { lang, setLang, theme, setTheme } = useUI();
return (
<div style={{ ...card, padding: '18px 20px', display: 'flex', flexDirection: 'column', gap: 16 }}>
<div style={sectionTitle}>{t('prefTitle')}</div>
<Row label={t('prefLang')}>
<Seg
value={lang}
options={[['zh', '中文'], ['en', 'EN']]}
onPick={(v) => setLang(v as Lang)}
/>
</Row>
<Row label={t('prefTheme')}>
<Seg
value={theme}
options={[['light', t('themeLight')], ['dark', t('themeDark')]]}
icons={{ light: 'sun', dark: 'moon' }}
onPick={(v) => setTheme(v as 'light' | 'dark')}
/>
</Row>
</div>
);
}
function Row({ label, children }: { label: string; children: React.ReactNode }) {
return (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12 }}>
<span style={{ fontSize: 13.5, fontWeight: 600, color: 'var(--fg2)' }}>{label}</span>
{children}
</div>
);
}
function Seg({ value, options, onPick, icons }: { value: string; options: [string, string][]; onPick: (v: string) => void; icons?: Record<string, string> }) {
return (
<div style={{ display: 'flex', background: 'var(--bg-subtle)', borderRadius: 999, padding: 3, gap: 2 }}>
{options.map(([v, l]) => (
<button
key={v}
onClick={() => onPick(v)}
aria-pressed={value === v}
style={{ display: 'inline-flex', alignItems: 'center', gap: 6, border: 'none', cursor: 'pointer', borderRadius: 999, padding: '6px 14px', fontFamily: 'var(--font-sans)', fontSize: 12.5, fontWeight: 700, background: value === v ? 'var(--accent)' : 'transparent', color: value === v ? 'var(--fg-on-accent)' : 'var(--fg3)' }}
>
{icons && <Icon name={icons[v]} size={14} color={value === v ? 'var(--fg-on-accent)' : 'var(--fg3)'} />}
{l}
</button>
))}
</div>
);
}
/* ───────── TOTP 2FA ───────── */
function TotpSection({ t, lang, enabled, onChange }: { t: TFn; lang: Lang; enabled: boolean; onChange: () => void }) {
const api = getClient();
+10 -2
View File
@@ -120,8 +120,16 @@ export default function UserCenter() {
setView('overview');
}
// 静态导出无服务端会话:首屏(!ready)与未登录一律直接渲染登录页,避免出现空白
// 背景(慢网络下用户会看到"空的")。已登录用户(有 refresh)会话续期完成后再切面板
// 已登录(本地有 refresh token)但会话续期尚未完成:显示加载态而非登录页,
// 避免"先闪登录页再跳面板"(从官网带登录态进来时的 bug)。续期成功→面板、失败→登录页
if (!ready && hasRefresh()) {
return (
<div style={{ minHeight: '70vh', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--fg3)', fontSize: 14 }}>
{t('loading')}
</div>
);
}
// 无 refresh token(未登录)或续期失败:渲染登录页。
if (!ready || !authed) {
return <Login onDone={onLoginDone} />;
}