diff --git a/backend/internal/handler/admin/setting_handler.go b/backend/internal/handler/admin/setting_handler.go index 7ad51660670..cbba4db1571 100644 --- a/backend/internal/handler/admin/setting_handler.go +++ b/backend/internal/handler/admin/setting_handler.go @@ -197,6 +197,7 @@ func (h *SettingHandler) GetSettings(c *gin.Context) { TablePageSizeOptions: settings.TablePageSizeOptions, CustomMenuItems: dto.ParseCustomMenuItems(settings.CustomMenuItems), CustomEndpoints: dto.ParseCustomEndpoints(settings.CustomEndpoints), + DefaultProxyID: settings.DefaultProxyID, DefaultConcurrency: settings.DefaultConcurrency, DefaultBalance: settings.DefaultBalance, RiskControlEnabled: settings.RiskControlEnabled, @@ -442,6 +443,7 @@ type UpdateSettingsRequest struct { TablePageSizeOptions []int `json:"table_page_size_options"` CustomMenuItems *[]dto.CustomMenuItem `json:"custom_menu_items"` CustomEndpoints *[]dto.CustomEndpoint `json:"custom_endpoints"` + DefaultProxyID *int64 `json:"default_proxy_id"` // 默认配置 DefaultConcurrency int `json:"default_concurrency"` @@ -1348,25 +1350,35 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { TablePageSizeOptions: req.TablePageSizeOptions, CustomMenuItems: customMenuJSON, CustomEndpoints: customEndpointsJSON, - DefaultConcurrency: req.DefaultConcurrency, - DefaultBalance: req.DefaultBalance, - AffiliateRebateRate: affiliateRebateRate, - AffiliateRebateFreezeHours: affiliateRebateFreezeHours, - AffiliateRebateDurationDays: affiliateRebateDurationDays, - AffiliateRebatePerInviteeCap: affiliateRebatePerInviteeCap, - DefaultUserRPMLimit: req.DefaultUserRPMLimit, - DefaultSubscriptions: defaultSubscriptions, - EnableModelFallback: req.EnableModelFallback, - FallbackModelAnthropic: req.FallbackModelAnthropic, - FallbackModelOpenAI: req.FallbackModelOpenAI, - FallbackModelGemini: req.FallbackModelGemini, - FallbackModelAntigravity: req.FallbackModelAntigravity, - EnableIdentityPatch: req.EnableIdentityPatch, - IdentityPatchPrompt: req.IdentityPatchPrompt, - MinClaudeCodeVersion: req.MinClaudeCodeVersion, - MaxClaudeCodeVersion: req.MaxClaudeCodeVersion, - AllowUngroupedKeyScheduling: req.AllowUngroupedKeyScheduling, - BackendModeEnabled: req.BackendModeEnabled, + DefaultProxyID: func() *int64 { + if req.DefaultProxyID != nil { + if *req.DefaultProxyID <= 0 { + return nil + } + value := *req.DefaultProxyID + return &value + } + return previousSettings.DefaultProxyID + }(), + DefaultConcurrency: req.DefaultConcurrency, + DefaultBalance: req.DefaultBalance, + AffiliateRebateRate: affiliateRebateRate, + AffiliateRebateFreezeHours: affiliateRebateFreezeHours, + AffiliateRebateDurationDays: affiliateRebateDurationDays, + AffiliateRebatePerInviteeCap: affiliateRebatePerInviteeCap, + DefaultUserRPMLimit: req.DefaultUserRPMLimit, + DefaultSubscriptions: defaultSubscriptions, + EnableModelFallback: req.EnableModelFallback, + FallbackModelAnthropic: req.FallbackModelAnthropic, + FallbackModelOpenAI: req.FallbackModelOpenAI, + FallbackModelGemini: req.FallbackModelGemini, + FallbackModelAntigravity: req.FallbackModelAntigravity, + EnableIdentityPatch: req.EnableIdentityPatch, + IdentityPatchPrompt: req.IdentityPatchPrompt, + MinClaudeCodeVersion: req.MinClaudeCodeVersion, + MaxClaudeCodeVersion: req.MaxClaudeCodeVersion, + AllowUngroupedKeyScheduling: req.AllowUngroupedKeyScheduling, + BackendModeEnabled: req.BackendModeEnabled, OpsMonitoringEnabled: func() bool { if req.OpsMonitoringEnabled != nil { return *req.OpsMonitoringEnabled @@ -1720,6 +1732,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { TablePageSizeOptions: updatedSettings.TablePageSizeOptions, CustomMenuItems: dto.ParseCustomMenuItems(updatedSettings.CustomMenuItems), CustomEndpoints: dto.ParseCustomEndpoints(updatedSettings.CustomEndpoints), + DefaultProxyID: updatedSettings.DefaultProxyID, DefaultConcurrency: updatedSettings.DefaultConcurrency, DefaultBalance: updatedSettings.DefaultBalance, AffiliateRebateRate: updatedSettings.AffiliateRebateRate, @@ -2047,6 +2060,9 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings, if before.HideCcsImportButton != after.HideCcsImportButton { changed = append(changed, "hide_ccs_import_button") } + if !equalInt64Ptr(before.DefaultProxyID, after.DefaultProxyID) { + changed = append(changed, "default_proxy_id") + } if before.DefaultConcurrency != after.DefaultConcurrency { changed = append(changed, "default_concurrency") } @@ -2298,6 +2314,13 @@ func defaultSubscriptionsValueOrDefault(input *[]dto.DefaultSubscriptionSetting, return result } +func equalInt64Ptr(a, b *int64) bool { + if a == nil || b == nil { + return a == nil && b == nil + } + return *a == *b +} + func systemSettingsResponseData(settings dto.SystemSettings, authSourceDefaults *service.AuthSourceDefaultSettings) map[string]any { data := make(map[string]any) raw, err := json.Marshal(settings) diff --git a/backend/internal/handler/dto/settings.go b/backend/internal/handler/dto/settings.go index 2d4cefa1595..6dab9e3af5a 100644 --- a/backend/internal/handler/dto/settings.go +++ b/backend/internal/handler/dto/settings.go @@ -121,6 +121,7 @@ type SystemSettings struct { TablePageSizeOptions []int `json:"table_page_size_options"` CustomMenuItems []CustomMenuItem `json:"custom_menu_items"` CustomEndpoints []CustomEndpoint `json:"custom_endpoints"` + DefaultProxyID *int64 `json:"default_proxy_id"` DefaultConcurrency int `json:"default_concurrency"` DefaultBalance float64 `json:"default_balance"` diff --git a/backend/internal/repository/billing_cache.go b/backend/internal/repository/billing_cache.go index 6922b4c8bbd..044d9467105 100644 --- a/backend/internal/repository/billing_cache.go +++ b/backend/internal/repository/billing_cache.go @@ -33,7 +33,7 @@ func jitteredTTL() time.Duration { if billingCacheJitter <= 0 { return billingCacheTTL } - jitter := time.Duration(rand.IntN(int(billingCacheJitter))) + jitter := time.Duration(rand.Int64N(int64(billingCacheJitter))) return billingCacheTTL - jitter } diff --git a/backend/internal/service/domain_constants.go b/backend/internal/service/domain_constants.go index 8eb90a6ba24..b62dd49120f 100644 --- a/backend/internal/service/domain_constants.go +++ b/backend/internal/service/domain_constants.go @@ -206,6 +206,7 @@ const ( SettingKeyTablePageSizeOptions = "table_page_size_options" // 表格可选每页条数(JSON 数组) SettingKeyCustomMenuItems = "custom_menu_items" // 自定义菜单项(JSON 数组) SettingKeyCustomEndpoints = "custom_endpoints" // 自定义端点列表(JSON 数组) + SettingKeyDefaultProxyID = "default_proxy_id" // 添加账号时默认选中的代理 ID // 默认配置 SettingKeyDefaultConcurrency = "default_concurrency" // 新用户默认并发量 diff --git a/backend/internal/service/gateway_request_test.go b/backend/internal/service/gateway_request_test.go index 40bd1186728..7152edb1e0c 100644 --- a/backend/internal/service/gateway_request_test.go +++ b/backend/internal/service/gateway_request_test.go @@ -981,7 +981,7 @@ func TestParseGatewayRequest_MaxTokensBoundary(t *testing.T) { { name: "超大值不 panic", body: `{"max_tokens":9999999999999999}`, - wantMaxTokens: 10000000000000000, // float64 精度导致 9999999999999999 → 1e16 + wantMaxTokens: 0, // 超出 math.MaxInt 时应被忽略,但不能 panic }, { name: "null 值被忽略", diff --git a/backend/internal/service/gemini_oauth_service.go b/backend/internal/service/gemini_oauth_service.go index 08a74a37245..50eba209d69 100644 --- a/backend/internal/service/gemini_oauth_service.go +++ b/backend/internal/service/gemini_oauth_service.go @@ -39,8 +39,8 @@ const ( ) const ( - GB = 1024 * 1024 * 1024 - TB = 1024 * GB + GB int64 = 1024 * 1024 * 1024 + TB int64 = 1024 * GB StorageTierUnlimited = 100 * TB // 100TB StorageTierAIPremium = 2 * TB // 2TB diff --git a/backend/internal/service/setting_service.go b/backend/internal/service/setting_service.go index 283a239b8d1..98ab44d6ac2 100644 --- a/backend/internal/service/setting_service.go +++ b/backend/internal/service/setting_service.go @@ -1504,6 +1504,11 @@ func (s *SettingService) buildSystemSettingsUpdates(ctx context.Context, setting updates[SettingKeyTablePageSizeOptions] = string(tablePageSizeOptionsJSON) updates[SettingKeyCustomMenuItems] = settings.CustomMenuItems updates[SettingKeyCustomEndpoints] = settings.CustomEndpoints + if settings.DefaultProxyID != nil && *settings.DefaultProxyID > 0 { + updates[SettingKeyDefaultProxyID] = strconv.FormatInt(*settings.DefaultProxyID, 10) + } else { + updates[SettingKeyDefaultProxyID] = "" + } // 默认配置 updates[SettingKeyDefaultConcurrency] = strconv.Itoa(settings.DefaultConcurrency) @@ -2233,6 +2238,7 @@ func (s *SettingService) InitializeDefaultSettings(ctx context.Context) error { SettingKeyTablePageSizeOptions: "[10,20,50,100]", SettingKeyCustomMenuItems: "[]", SettingKeyCustomEndpoints: "[]", + SettingKeyDefaultProxyID: "", SettingKeyWeChatConnectEnabled: "false", SettingKeyWeChatConnectAppID: "", SettingKeyWeChatConnectAppSecret: "", @@ -2412,6 +2418,9 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin CustomEndpoints: settings[SettingKeyCustomEndpoints], BackendModeEnabled: settings[SettingKeyBackendModeEnabled] == "true", } + if defaultProxyID, err := strconv.ParseInt(strings.TrimSpace(settings[SettingKeyDefaultProxyID]), 10, 64); err == nil && defaultProxyID > 0 { + result.DefaultProxyID = &defaultProxyID + } result.TableDefaultPageSize, result.TablePageSizeOptions = parseTablePreferences( settings[SettingKeyTableDefaultPageSize], settings[SettingKeyTablePageSizeOptions], diff --git a/backend/internal/service/setting_service_update_test.go b/backend/internal/service/setting_service_update_test.go index 9dc0ca59a3f..56fa3425602 100644 --- a/backend/internal/service/setting_service_update_test.go +++ b/backend/internal/service/setting_service_update_test.go @@ -224,6 +224,39 @@ func TestSettingService_UpdateSettings_TablePreferences(t *testing.T) { require.Equal(t, "[20,100]", repo.updates[SettingKeyTablePageSizeOptions]) } +func TestSettingService_UpdateSettings_DefaultProxyID(t *testing.T) { + repo := &settingUpdateRepoStub{} + svc := NewSettingService(repo, &config.Config{}) + defaultProxyID := int64(42) + + err := svc.UpdateSettings(context.Background(), &SystemSettings{ + DefaultProxyID: &defaultProxyID, + }) + require.NoError(t, err) + require.Equal(t, "42", repo.updates[SettingKeyDefaultProxyID]) + + err = svc.UpdateSettings(context.Background(), &SystemSettings{ + DefaultProxyID: nil, + }) + require.NoError(t, err) + require.Equal(t, "", repo.updates[SettingKeyDefaultProxyID]) +} + +func TestSettingService_ParseSettings_DefaultProxyID(t *testing.T) { + svc := NewSettingService(&settingUpdateRepoStub{}, &config.Config{}) + + got := svc.parseSettings(map[string]string{ + SettingKeyDefaultProxyID: "88", + }) + require.NotNil(t, got.DefaultProxyID) + require.EqualValues(t, 88, *got.DefaultProxyID) + + got = svc.parseSettings(map[string]string{ + SettingKeyDefaultProxyID: "", + }) + require.Nil(t, got.DefaultProxyID) +} + func TestSettingService_UpdateSettings_PaymentVisibleMethodsAndAdvancedScheduler(t *testing.T) { repo := &settingUpdateRepoStub{} svc := NewSettingService(repo, &config.Config{}) diff --git a/backend/internal/service/settings_view.go b/backend/internal/service/settings_view.go index 80b8b32a901..bfae8f7fdff 100644 --- a/backend/internal/service/settings_view.go +++ b/backend/internal/service/settings_view.go @@ -121,6 +121,7 @@ type SystemSettings struct { TablePageSizeOptions []int CustomMenuItems string // JSON array of custom menu items CustomEndpoints string // JSON array of custom endpoints + DefaultProxyID *int64 DefaultConcurrency int DefaultBalance float64 diff --git a/frontend/src/api/admin/settings.ts b/frontend/src/api/admin/settings.ts index 01d6969db5b..9ca8e337400 100644 --- a/frontend/src/api/admin/settings.ts +++ b/frontend/src/api/admin/settings.ts @@ -377,6 +377,7 @@ export interface SystemSettings { backend_mode_enabled: boolean; custom_menu_items: CustomMenuItem[]; custom_endpoints: CustomEndpoint[]; + default_proxy_id?: number | null; // SMTP settings smtp_host: string; smtp_port: number; @@ -593,6 +594,7 @@ export interface UpdateSettingsRequest { backend_mode_enabled?: boolean; custom_menu_items?: CustomMenuItem[]; custom_endpoints?: CustomEndpoint[]; + default_proxy_id?: number | null; smtp_host?: string; smtp_port?: number; smtp_username?: string; diff --git a/frontend/src/components/account/CreateAccountModal.vue b/frontend/src/components/account/CreateAccountModal.vue index 9ef6c9d2451..398aa559d80 100644 --- a/frontend/src/components/account/CreateAccountModal.vue +++ b/frontend/src/components/account/CreateAccountModal.vue @@ -2441,6 +2441,9 @@
+

+ {{ t('admin.accounts.defaultProxyAppliedHint') }} +

@@ -2509,6 +2512,88 @@
+
+
+
+ +

+ {{ t('admin.accounts.openai.quotaStrategyDesc') }} +

+
+ +
+ +
+
+ +
+ + +
+
+ +
+ + +

+ {{ t('admin.accounts.openai.quotaStopThresholdDesc') }} +

+
+
+
+
('auto') const openaiOAuthResponsesWebSocketV2Mode = ref(OPENAI_WS_MODE_OFF) const openaiAPIKeyResponsesWebSocketV2Mode = ref(OPENAI_WS_MODE_OFF) const codexCLIOnlyEnabled = ref(false) +const openAIQuotaStrategyEnabled = ref(true) +const openAIQuotaStrategy = ref<'prefer_5h' | 'prefer_7d'>('prefer_7d') +const openAIQuotaStopThresholdPercent = ref(10) const anthropicPassthroughEnabled = ref(false) const webSearchEmulationMode = ref('default') const webSearchGlobalEnabled = ref(false) +const defaultProxyId = ref(null) const { globalEnabled: quotaNotifyGlobalEnabled, state: quotaNotifyState, @@ -3538,11 +3627,50 @@ const canExchangeCode = computed(() => { return authCode.trim() && oauth.sessionId.value && !oauth.loading.value }) +const normalizeQuotaStopThreshold = (value: number) => { + if (!Number.isFinite(value)) { + return 10 + } + return Math.min(100, Math.max(1, Math.trunc(value))) +} + +const resolveDefaultProxyId = () => { + if (defaultProxyId.value == null) { + return null + } + const matched = props.proxies.find((proxy) => proxy.id === defaultProxyId.value && proxy.status === 'active') + return matched ? matched.id : null +} + +const applyDefaultProxySelection = () => { + if (form.proxy_id != null) { + return + } + const resolved = resolveDefaultProxyId() + if (resolved != null) { + form.proxy_id = resolved + } +} + +const loadCreateDefaults = async () => { + try { + const settings = await adminAPI.settings.getSettings() + defaultProxyId.value = + typeof settings.default_proxy_id === 'number' && settings.default_proxy_id > 0 + ? settings.default_proxy_id + : null + applyDefaultProxySelection() + } catch { + defaultProxyId.value = null + } +} + // Watchers watch( () => props.show, (newVal) => { if (newVal) { + loadCreateDefaults() // Load TLS fingerprint profiles adminAPI.tlsFingerprintProfiles.list() .then(profiles => { tlsFingerprintProfiles.value = profiles.map(p => ({ id: p.id, name: p.name })) }) @@ -3567,6 +3695,15 @@ watch( } ) +watch( + () => props.proxies, + () => { + if (props.show) { + applyDefaultProxySelection() + } + } +) + // Sync form.type based on accountCategory, addMethod, and platform-specific type watch( [accountCategory, addMethod, antigravityAccountType, () => form.platform], @@ -3648,6 +3785,9 @@ watch( openaiOAuthResponsesWebSocketV2Mode.value = OPENAI_WS_MODE_OFF openaiAPIKeyResponsesWebSocketV2Mode.value = OPENAI_WS_MODE_OFF codexCLIOnlyEnabled.value = false + openAIQuotaStrategyEnabled.value = true + openAIQuotaStrategy.value = 'prefer_7d' + openAIQuotaStopThresholdPercent.value = 10 } if (newPlatform !== 'anthropic') { anthropicPassthroughEnabled.value = false @@ -4045,6 +4185,9 @@ const resetForm = () => { openaiOAuthResponsesWebSocketV2Mode.value = OPENAI_WS_MODE_OFF openaiAPIKeyResponsesWebSocketV2Mode.value = OPENAI_WS_MODE_OFF codexCLIOnlyEnabled.value = false + openAIQuotaStrategyEnabled.value = true + openAIQuotaStrategy.value = 'prefer_7d' + openAIQuotaStopThresholdPercent.value = 10 anthropicPassthroughEnabled.value = false webSearchEmulationMode.value = 'default' // Reset quota control state @@ -4128,6 +4271,13 @@ const buildOpenAIExtra = (base?: Record): Record 0 ? extra : undefined } diff --git a/frontend/src/components/account/__tests__/CreateAccountModal.spec.ts b/frontend/src/components/account/__tests__/CreateAccountModal.spec.ts new file mode 100644 index 00000000000..470ec7c4757 --- /dev/null +++ b/frontend/src/components/account/__tests__/CreateAccountModal.spec.ts @@ -0,0 +1,211 @@ +import { defineComponent, ref } from 'vue' +import { flushPromises, mount } from '@vue/test-utils' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import CreateAccountModal from '../CreateAccountModal.vue' + +const { + getSettingsMock, + getWebSearchEmulationConfigMock, + listTlsProfilesMock +} = vi.hoisted(() => ({ + getSettingsMock: vi.fn(), + getWebSearchEmulationConfigMock: vi.fn(), + listTlsProfilesMock: vi.fn() +})) + +vi.mock('@/stores/app', () => ({ + useAppStore: () => ({ + showError: vi.fn(), + showSuccess: vi.fn(), + showInfo: vi.fn() + }) +})) + +vi.mock('@/stores/auth', () => ({ + useAuthStore: () => ({ + isSimpleMode: false + }) +})) + +vi.mock('@/composables/useModelWhitelist', () => ({ + claudeModels: ['claude-3-7-sonnet'], + getPresetMappingsByPlatform: vi.fn(() => []), + getModelsByPlatform: vi.fn(() => ['claude-3-7-sonnet']), + commonErrorCodes: [], + buildModelMappingObject: vi.fn(() => undefined), + fetchAntigravityDefaultMappings: vi.fn().mockResolvedValue([]), + isValidWildcardPattern: vi.fn(() => true) +})) + +vi.mock('@/composables/useQuotaNotifyState', () => ({ + useQuotaNotifyState: () => ({ + globalEnabled: ref(false), + state: ref({}), + loadGlobalState: vi.fn(), + writeToExtra: vi.fn() + }) +})) + +function createOAuthStub() { + return { + authUrl: ref(''), + sessionId: ref(''), + loading: ref(false), + error: ref(''), + resetState: vi.fn(), + parseSessionKeys: vi.fn(() => []), + getCapabilities: vi.fn().mockResolvedValue({ ai_studio_oauth_enabled: false }) + } +} + +vi.mock('@/composables/useAccountOAuth', () => ({ + useAccountOAuth: () => createOAuthStub() +})) + +vi.mock('@/composables/useOpenAIOAuth', () => ({ + useOpenAIOAuth: () => createOAuthStub() +})) + +vi.mock('@/composables/useGeminiOAuth', () => ({ + useGeminiOAuth: () => createOAuthStub() +})) + +vi.mock('@/composables/useAntigravityOAuth', () => ({ + useAntigravityOAuth: () => createOAuthStub() +})) + +vi.mock('@/api/admin', () => ({ + adminAPI: { + settings: { + getSettings: getSettingsMock, + getWebSearchEmulationConfig: getWebSearchEmulationConfigMock + }, + tlsFingerprintProfiles: { + list: listTlsProfilesMock + } + } +})) + +vi.mock('vue-i18n', async () => { + const actual = await vi.importActual('vue-i18n') + return { + ...actual, + useI18n: () => ({ + t: (key: string) => key + }) + } +}) + +const BaseDialogStub = defineComponent({ + name: 'BaseDialogStub', + props: { + show: { + type: Boolean, + default: false + } + }, + template: '
' +}) + +const ProxySelectorStub = defineComponent({ + name: 'ProxySelectorStub', + props: { + modelValue: { + type: [Number, null], + default: null + } + }, + emits: ['update:modelValue'], + template: '
{{ modelValue ?? "" }}
' +}) + +const SelectStub = defineComponent({ + name: 'SelectStub', + props: { + modelValue: { + type: [String, Number, Boolean, null], + default: '' + }, + options: { + type: Array, + default: () => [] + } + }, + emits: ['update:modelValue'], + template: ` + + ` +}) + +const createWrapper = () => + mount(CreateAccountModal, { + props: { + show: false, + proxies: [ + { + id: 7, + name: 'Default Proxy', + protocol: 'http', + host: 'proxy.example.com', + port: 8080, + username: null, + password: null, + status: 'active', + created_at: '2026-04-27T00:00:00Z', + updated_at: '2026-04-27T00:00:00Z' + } + ] as any, + groups: [] + }, + global: { + stubs: { + BaseDialog: BaseDialogStub, + ConfirmDialog: true, + Select: SelectStub, + Icon: true, + ProxySelector: ProxySelectorStub, + GroupSelector: true, + ModelWhitelistSelector: true, + QuotaLimitCard: true, + OAuthAuthorizationFlow: true + } + } + }) + +describe('CreateAccountModal', () => { + beforeEach(() => { + getSettingsMock.mockReset() + getWebSearchEmulationConfigMock.mockReset() + listTlsProfilesMock.mockReset() + + getSettingsMock.mockResolvedValue({ default_proxy_id: 7 }) + getWebSearchEmulationConfigMock.mockResolvedValue({ enabled: false, providers: [] }) + listTlsProfilesMock.mockResolvedValue([]) + }) + + it('打开时自动选中默认代理,并在 OpenAI OAuth 下默认启用 7D/10% 额度策略', async () => { + const wrapper = createWrapper() + + await wrapper.setProps({ show: true }) + await flushPromises() + + expect(wrapper.get('[data-testid="proxy-selector"]').text()).toBe('7') + expect(wrapper.text()).toContain('admin.accounts.defaultProxyAppliedHint') + + const openAIButton = wrapper + .findAll('button') + .find((button) => button.text().includes('OpenAI')) + + expect(openAIButton).toBeTruthy() + await openAIButton!.trigger('click') + await flushPromises() + + expect((wrapper.get('#create-openai-quota-stop-threshold').element as HTMLInputElement).value).toBe('10') + expect(wrapper.text()).toContain('admin.accounts.openai.quotaStrategyPrefer7d') + }) +}) diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index d18a895c007..3f98990cbc1 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -3335,6 +3335,7 @@ export default { expired: 'Expired', proxy: 'Proxy', noProxy: 'No Proxy', + defaultProxyAppliedHint: 'Default proxy has been preselected. You can still change it manually.', concurrency: 'Concurrency', loadFactor: 'Load Factor', loadFactorHint: 'Higher load factor increases scheduling frequency', @@ -3930,6 +3931,13 @@ export default { batchDeleteDone: 'Deleted {deleted} proxies, skipped {skipped}', batchDeleteSkipped: 'Skipped {skipped} proxies', batchDeleteFailed: 'Batch delete failed', + defaultBadge: 'Default Proxy', + defaultBadgeShort: 'Default', + setAsDefault: 'Set Default', + settingDefault: 'Saving...', + defaultProxySet: 'Default proxy updated', + failedToSetDefault: 'Failed to set default proxy', + failedToClearDefault: 'Failed to clear default proxy', deleteBlockedInUse: 'This proxy is in use and cannot be deleted', accountsTitle: 'Accounts using this IP', accountsEmpty: 'No accounts are using this proxy', diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts index 4f473f946b7..6c49b9c9132 100644 --- a/frontend/src/i18n/locales/zh.ts +++ b/frontend/src/i18n/locales/zh.ts @@ -3476,6 +3476,7 @@ export default { expired: '已过期', proxy: '代理', noProxy: '无代理', + defaultProxyAppliedHint: '已自动选中默认代理,仍可手动切换。', concurrency: '并发数', loadFactor: '负载因子', loadFactorHint: '提高负载因子可以提高对账号的调度频率', @@ -4064,6 +4065,13 @@ export default { batchDeleteDone: '已删除 {deleted} 个代理,跳过 {skipped} 个', batchDeleteSkipped: '已跳过 {skipped} 个代理', batchDeleteFailed: '批量删除失败', + defaultBadge: '默认代理', + defaultBadgeShort: '默认', + setAsDefault: '设默认', + settingDefault: '设置中', + defaultProxySet: '默认代理已更新', + failedToSetDefault: '设置默认代理失败', + failedToClearDefault: '清理默认代理失败', deleteBlockedInUse: '该代理已有账号使用,无法删除', accountsTitle: '使用该IP的账号', accountsEmpty: '暂无账号使用此代理', diff --git a/frontend/src/views/admin/ProxiesView.vue b/frontend/src/views/admin/ProxiesView.vue index 1e4df356096..8d2c4bd79f0 100644 --- a/frontend/src/views/admin/ProxiesView.vue +++ b/frontend/src/views/admin/ProxiesView.vue @@ -118,8 +118,16 @@ /> -