From ff3f514f6b75dfe3e033dfac3faab65080a2eea7 Mon Sep 17 00:00:00 2001 From: ianshaw Date: Sat, 3 Jan 2026 06:35:50 -0800 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20=E5=A2=9E=E5=BC=BA=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E7=95=8C=E9=9D=A2=E5=92=8C=E4=BD=BF=E7=94=A8=E6=95=99?= =?UTF-8?q?=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要改进: - 扩展 UseKeyModal 支持 Antigravity/Gemini 平台教程 - 添加 CCS (Claude Code Settings) 导入说明 - 添加混合渠道风险警告提示 - 优化登录/注册页面样式 - 更新 Antigravity 混合调度选项文案 - 完善中英文国际化文案 --- frontend/src/api/admin/dashboard.ts | 4 +- frontend/src/api/admin/index.ts | 3 - frontend/src/components/keys/UseKeyModal.vue | 185 ++++++++++++++-- frontend/src/components/layout/AppSidebar.vue | 16 -- frontend/src/i18n/locales/en.ts | 199 +++++++----------- frontend/src/i18n/locales/zh.ts | 199 +++++++----------- frontend/src/router/index.ts | 12 -- frontend/src/views/admin/SettingsView.vue | 2 +- frontend/src/views/auth/LoginView.vue | 18 +- frontend/src/views/auth/RegisterView.vue | 18 +- frontend/src/views/user/KeysView.vue | 108 +++++++++- frontend/src/views/user/RedeemView.vue | 2 +- frontend/src/views/user/SubscriptionsView.vue | 2 +- 13 files changed, 440 insertions(+), 328 deletions(-) diff --git a/frontend/src/api/admin/dashboard.ts b/frontend/src/api/admin/dashboard.ts index 3e50f1ca..83e56c0e 100644 --- a/frontend/src/api/admin/dashboard.ts +++ b/frontend/src/api/admin/dashboard.ts @@ -8,7 +8,7 @@ import type { DashboardStats, TrendDataPoint, ModelStat, - APIKeyUsageTrendPoint, + ApiKeyUsageTrendPoint, UserUsageTrendPoint } from '@/types' @@ -93,7 +93,7 @@ export interface ApiKeyTrendParams extends TrendParams { } export interface ApiKeyTrendResponse { - trend: APIKeyUsageTrendPoint[] + trend: ApiKeyUsageTrendPoint[] start_date: string end_date: string granularity: string diff --git a/frontend/src/api/admin/index.ts b/frontend/src/api/admin/index.ts index 7ebbaa50..ea12f6d2 100644 --- a/frontend/src/api/admin/index.ts +++ b/frontend/src/api/admin/index.ts @@ -15,7 +15,6 @@ import subscriptionsAPI from './subscriptions' import usageAPI from './usage' import geminiAPI from './gemini' import antigravityAPI from './antigravity' -import opsAPI from './ops' import userAttributesAPI from './userAttributes' /** @@ -34,7 +33,6 @@ export const adminAPI = { usage: usageAPI, gemini: geminiAPI, antigravity: antigravityAPI, - ops: opsAPI, userAttributes: userAttributesAPI } @@ -51,7 +49,6 @@ export { usageAPI, geminiAPI, antigravityAPI, - opsAPI, userAttributesAPI } diff --git a/frontend/src/components/keys/UseKeyModal.vue b/frontend/src/components/keys/UseKeyModal.vue index 9414523d..7db67b96 100644 --- a/frontend/src/components/keys/UseKeyModal.vue +++ b/frontend/src/components/keys/UseKeyModal.vue @@ -28,7 +28,29 @@ {{ platformDescription }}

- + +
+ +
+ +
diff --git a/frontend/src/views/auth/LoginView.vue b/frontend/src/views/auth/LoginView.vue index 4817ab3d..71269052 100644 --- a/frontend/src/views/auth/LoginView.vue +++ b/frontend/src/views/auth/LoginView.vue @@ -295,12 +295,12 @@ function onTurnstileVerify(token: string): void { function onTurnstileExpire(): void { turnstileToken.value = '' - errors.turnstile = 'Verification expired, please try again' + errors.turnstile = t('auth.turnstileExpired') } function onTurnstileError(): void { turnstileToken.value = '' - errors.turnstile = 'Verification failed, please try again' + errors.turnstile = t('auth.turnstileFailed') } // ==================== Validation ==================== @@ -315,25 +315,25 @@ function validateForm(): boolean { // Email validation if (!formData.email.trim()) { - errors.email = 'Email is required' + errors.email = t('auth.emailRequired') isValid = false } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) { - errors.email = 'Please enter a valid email address' + errors.email = t('auth.invalidEmail') isValid = false } // Password validation if (!formData.password) { - errors.password = 'Password is required' + errors.password = t('auth.passwordRequired') isValid = false } else if (formData.password.length < 6) { - errors.password = 'Password must be at least 6 characters' + errors.password = t('auth.passwordMinLength') isValid = false } // Turnstile validation if (turnstileEnabled.value && !turnstileToken.value) { - errors.turnstile = 'Please complete the verification' + errors.turnstile = t('auth.completeVerification') isValid = false } @@ -362,7 +362,7 @@ async function handleLogin(): Promise { }) // Show success toast - appStore.showSuccess('Login successful! Welcome back.') + appStore.showSuccess(t('auth.loginSuccess')) // Redirect to dashboard or intended route const redirectTo = (router.currentRoute.value.query.redirect as string) || '/dashboard' @@ -382,7 +382,7 @@ async function handleLogin(): Promise { } else if (err.message) { errorMessage.value = err.message } else { - errorMessage.value = 'Login failed. Please check your credentials and try again.' + errorMessage.value = t('auth.loginFailed') } // Also show error toast diff --git a/frontend/src/views/auth/RegisterView.vue b/frontend/src/views/auth/RegisterView.vue index dcf9537e..65865a11 100644 --- a/frontend/src/views/auth/RegisterView.vue +++ b/frontend/src/views/auth/RegisterView.vue @@ -340,12 +340,12 @@ function onTurnstileVerify(token: string): void { function onTurnstileExpire(): void { turnstileToken.value = '' - errors.turnstile = 'Verification expired, please try again' + errors.turnstile = t('auth.turnstileExpired') } function onTurnstileError(): void { turnstileToken.value = '' - errors.turnstile = 'Verification failed, please try again' + errors.turnstile = t('auth.turnstileFailed') } // ==================== Validation ==================== @@ -365,25 +365,25 @@ function validateForm(): boolean { // Email validation if (!formData.email.trim()) { - errors.email = 'Email is required' + errors.email = t('auth.emailRequired') isValid = false } else if (!validateEmail(formData.email)) { - errors.email = 'Please enter a valid email address' + errors.email = t('auth.invalidEmail') isValid = false } // Password validation if (!formData.password) { - errors.password = 'Password is required' + errors.password = t('auth.passwordRequired') isValid = false } else if (formData.password.length < 6) { - errors.password = 'Password must be at least 6 characters' + errors.password = t('auth.passwordMinLength') isValid = false } // Turnstile validation if (turnstileEnabled.value && !turnstileToken.value) { - errors.turnstile = 'Please complete the verification' + errors.turnstile = t('auth.completeVerification') isValid = false } @@ -429,7 +429,7 @@ async function handleRegister(): Promise { }) // Show success toast - appStore.showSuccess('Account created successfully! Welcome to ' + siteName.value + '.') + appStore.showSuccess(t('auth.accountCreatedSuccess', { siteName: siteName.value })) // Redirect to dashboard await router.push('/dashboard') @@ -448,7 +448,7 @@ async function handleRegister(): Promise { } else if (err.message) { errorMessage.value = err.message } else { - errorMessage.value = 'Registration failed. Please try again.' + errorMessage.value = t('auth.registrationFailed') } // Also show error toast diff --git a/frontend/src/views/user/KeysView.vue b/frontend/src/views/user/KeysView.vue index ac37485d..bc8986f6 100644 --- a/frontend/src/views/user/KeysView.vue +++ b/frontend/src/views/user/KeysView.vue @@ -173,7 +173,7 @@ + + + + + +
(null) const selectedKey = ref(null) const copiedKeyId = ref(null) const groupSelectorKeyId = ref(null) @@ -871,8 +916,48 @@ const closeModals = () => { } } -const importToCcswitch = (apiKey: string) => { +const importToCcswitch = (row: ApiKey) => { + const platform = row.group?.platform || 'anthropic' + + // For antigravity platform, show client selection dialog + if (platform === 'antigravity') { + pendingCcsRow.value = row + showCcsClientSelect.value = true + return + } + + // For other platforms, execute directly + executeCcsImport(row, platform === 'gemini' ? 'gemini' : 'claude') +} + +const executeCcsImport = (row: ApiKey, clientType: 'claude' | 'gemini') => { const baseUrl = publicSettings.value?.api_base_url || window.location.origin + const platform = row.group?.platform || 'anthropic' + + // Determine app name and endpoint based on platform and client type + let app: string + let endpoint: string + + if (platform === 'antigravity') { + // Antigravity always uses /antigravity suffix + app = clientType === 'gemini' ? 'gemini' : 'claude' + endpoint = `${baseUrl}/antigravity` + } else { + switch (platform) { + case 'openai': + app = 'codex' + endpoint = baseUrl + break + case 'gemini': + app = 'gemini' + endpoint = baseUrl + break + default: // anthropic + app = 'claude' + endpoint = baseUrl + } + } + const usageScript = `({ request: { url: "{{baseUrl}}/v1/usage", @@ -889,11 +974,11 @@ const importToCcswitch = (apiKey: string) => { })` const params = new URLSearchParams({ resource: 'provider', - app: 'claude', + app: app, name: 'sub2api', homepage: baseUrl, - endpoint: baseUrl, - apiKey: apiKey, + endpoint: endpoint, + apiKey: row.key, configFormat: 'json', usageEnabled: 'true', usageScript: btoa(usageScript), @@ -916,6 +1001,19 @@ const importToCcswitch = (apiKey: string) => { } } +const handleCcsClientSelect = (clientType: 'claude' | 'gemini') => { + if (pendingCcsRow.value) { + executeCcsImport(pendingCcsRow.value, clientType) + } + showCcsClientSelect.value = false + pendingCcsRow.value = null +} + +const closeCcsClientSelect = () => { + showCcsClientSelect.value = false + pendingCcsRow.value = null +} + onMounted(() => { loadApiKeys() loadGroups() diff --git a/frontend/src/views/user/RedeemView.vue b/frontend/src/views/user/RedeemView.vue index 6fa29c5b..78650aac 100644 --- a/frontend/src/views/user/RedeemView.vue +++ b/frontend/src/views/user/RedeemView.vue @@ -500,7 +500,7 @@ const getHistoryItemTitle = (item: RedeemHistoryItem) => { } else if (item.type === 'subscription') { return t('redeem.subscriptionAssigned') } - return 'Unknown' + return t('common.unknown') } const formatHistoryValue = (item: RedeemHistoryItem) => { diff --git a/frontend/src/views/user/SubscriptionsView.vue b/frontend/src/views/user/SubscriptionsView.vue index b03b665a..ef85f9d3 100644 --- a/frontend/src/views/user/SubscriptionsView.vue +++ b/frontend/src/views/user/SubscriptionsView.vue @@ -279,7 +279,7 @@ async function loadSubscriptions() { subscriptions.value = await subscriptionsAPI.getMySubscriptions() } catch (error) { console.error('Failed to load subscriptions:', error) - appStore.showError('Failed to load subscriptions') + appStore.showError(t('userSubscriptions.failedToLoad')) } finally { loading.value = false }