fix(desktop): ClaudeOfficialLogin — i18n、错误反馈、冗余清理

Code review follow-ups on ae5437a:
- I1: 使用 useTranslation 替代硬编码中文, 新增 settings.claudeOfficialLogin.* 双语 key
- I2: shellOpen 失败时 console.error + 写入 store.error, 不再静默吞
- M1: disabled={isLoading || false} → disabled={isLoading}
- M2: onClick={() => logout()} → onClick={logout}
- M3: 首次 fetchStatus 失败时显示 error 而非永远 "加载中…"

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
程序员阿江(Relakkes) 2026-04-17 20:33:08 +08:00
parent ae5437af43
commit 8ae68c1805
3 changed files with 55 additions and 12 deletions

View File

@ -7,8 +7,10 @@
import { useEffect } from 'react' import { useEffect } from 'react'
import { open as shellOpen } from '@tauri-apps/plugin-shell' import { open as shellOpen } from '@tauri-apps/plugin-shell'
import { useHahaOAuthStore } from '../../stores/hahaOAuthStore' import { useHahaOAuthStore } from '../../stores/hahaOAuthStore'
import { useTranslation } from '../../i18n'
export function ClaudeOfficialLogin() { export function ClaudeOfficialLogin() {
const t = useTranslation()
const { status, isLoading, error, fetchStatus, login, logout, stopPolling } = const { status, isLoading, error, fetchStatus, login, logout, stopPolling } =
useHahaOAuthStore() useHahaOAuthStore()
@ -20,34 +22,52 @@ export function ClaudeOfficialLogin() {
const handleLogin = async () => { const handleLogin = async () => {
try { try {
const { authorizeUrl } = await login() const { authorizeUrl } = await login()
try {
await shellOpen(authorizeUrl) await shellOpen(authorizeUrl)
} catch (err) {
console.error('[ClaudeOfficialLogin] shellOpen failed:', err)
useHahaOAuthStore.setState({
error: t('settings.claudeOfficialLogin.openBrowserFailed'),
})
}
} catch { } catch {
// error 已经在 store 里,不做额外处理 // store.login() errors are already captured into store.error
} }
} }
if (status === null) { if (status === null) {
if (error) {
return ( return (
<div className="text-xs text-[var(--color-text-tertiary)]"></div> <div className="text-xs text-[var(--color-error,#dc2626)]">
{t('settings.claudeOfficialLogin.errorPrefix')}{error}
</div>
)
}
return (
<div className="text-xs text-[var(--color-text-tertiary)]">
{t('common.loading')}
</div>
) )
} }
if (status.loggedIn) { if (status.loggedIn) {
const subTypeLabel = status.subscriptionType const subTypeLabel = status.subscriptionType
? status.subscriptionType.toUpperCase() ? status.subscriptionType.toUpperCase()
: '未知' : t('settings.claudeOfficialLogin.subTypeUnknown')
return ( return (
<div className="flex items-center gap-3 text-sm"> <div className="flex items-center gap-3 text-sm">
<span className="text-[var(--color-success)]"> <span className="text-[var(--color-success)]">
(Claude {subTypeLabel}) {t('settings.claudeOfficialLogin.loggedInPrefix')} {subTypeLabel})
</span> </span>
<button <button
type="button" type="button"
onClick={() => logout()} onClick={logout}
disabled={isLoading} disabled={isLoading}
className="px-3 py-1 text-xs rounded-md border border-[var(--color-border-separator)] bg-[var(--color-surface)] hover:bg-[var(--color-surface-hover)] disabled:opacity-50 transition-colors" className="px-3 py-1 text-xs rounded-md border border-[var(--color-border-separator)] bg-[var(--color-surface)] hover:bg-[var(--color-surface-hover)] disabled:opacity-50 transition-colors"
> >
{isLoading ? '处理中…' : '登出'} {isLoading
? t('settings.claudeOfficialLogin.logoutProcessing')
: t('settings.claudeOfficialLogin.logoutButton')}
</button> </button>
</div> </div>
) )
@ -56,20 +76,21 @@ export function ClaudeOfficialLogin() {
return ( return (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="text-sm text-[var(--color-text-secondary)]"> <div className="text-sm text-[var(--color-text-secondary)]">
使 Claude Claude.ai , {t('settings.claudeOfficialLogin.intro')}
Claude ,
</div> </div>
<button <button
type="button" type="button"
onClick={handleLogin} onClick={handleLogin}
disabled={isLoading || false} disabled={isLoading}
className="self-start px-4 py-2 text-sm rounded-md bg-[var(--color-accent,#c96342)] text-white hover:opacity-90 disabled:opacity-50 transition-opacity" className="self-start px-4 py-2 text-sm rounded-md bg-[var(--color-accent,#c96342)] text-white hover:opacity-90 disabled:opacity-50 transition-opacity"
> >
{isLoading ? '正在启动…' : '登录 Claude 账号'} {isLoading
? t('settings.claudeOfficialLogin.loginStarting')
: t('settings.claudeOfficialLogin.loginButton')}
</button> </button>
{error && ( {error && (
<div className="text-xs text-[var(--color-error,#dc2626)]"> <div className="text-xs text-[var(--color-error,#dc2626)]">
:{error} {t('settings.claudeOfficialLogin.errorPrefix')}{error}
</div> </div>
)} )}
</div> </div>

View File

@ -50,6 +50,17 @@ export const en = {
'settings.tab.general': 'General', 'settings.tab.general': 'General',
'settings.tab.skills': 'Skills', 'settings.tab.skills': 'Skills',
// Settings > Claude Official Login
'settings.claudeOfficialLogin.intro': 'Using official Claude models requires signing in to your Claude.ai account. Click the button below to open the official Claude login page in your browser; you\'ll be returned here after authorizing.',
'settings.claudeOfficialLogin.loginButton': 'Sign in to Claude',
'settings.claudeOfficialLogin.loginStarting': 'Opening…',
'settings.claudeOfficialLogin.logoutButton': 'Sign out',
'settings.claudeOfficialLogin.logoutProcessing': 'Processing…',
'settings.claudeOfficialLogin.loggedInPrefix': 'Signed in (Claude',
'settings.claudeOfficialLogin.subTypeUnknown': 'Unknown',
'settings.claudeOfficialLogin.errorPrefix': 'Error: ',
'settings.claudeOfficialLogin.openBrowserFailed': 'Failed to open browser; please visit the authorization URL manually.',
// Settings > Providers // Settings > Providers
'settings.providers.title': 'Providers', 'settings.providers.title': 'Providers',
'settings.providers.description': 'Manage API providers for model access.', 'settings.providers.description': 'Manage API providers for model access.',

View File

@ -52,6 +52,17 @@ export const zh: Record<TranslationKey, string> = {
'settings.tab.general': '通用', 'settings.tab.general': '通用',
'settings.tab.skills': '技能', 'settings.tab.skills': '技能',
// Settings > Claude Official Login
'settings.claudeOfficialLogin.intro': '使用官方 Claude 模型需要登录你的 Claude.ai 账号。点击下方按钮,浏览器会打开 Claude 官方登录页面,授权后自动回到这里。',
'settings.claudeOfficialLogin.loginButton': '登录 Claude 账号',
'settings.claudeOfficialLogin.loginStarting': '正在启动…',
'settings.claudeOfficialLogin.logoutButton': '登出',
'settings.claudeOfficialLogin.logoutProcessing': '处理中…',
'settings.claudeOfficialLogin.loggedInPrefix': '已登录(Claude',
'settings.claudeOfficialLogin.subTypeUnknown': '未知',
'settings.claudeOfficialLogin.errorPrefix': '错误:',
'settings.claudeOfficialLogin.openBrowserFailed': '无法打开浏览器,请手动访问授权链接。',
// Settings > Providers // Settings > Providers
'settings.providers.title': '服务商', 'settings.providers.title': '服务商',
'settings.providers.description': '管理 API 服务商以访问模型。', 'settings.providers.description': '管理 API 服务商以访问模型。',