From 7d4b7deea921e19ce779881f40243d90a8aef7a9 Mon Sep 17 00:00:00 2001 From: shaw Date: Sun, 28 Dec 2025 22:19:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=AE=80=E5=8D=95?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增简单模式设置,适合个人使用场景: - 隐藏多用户管理相关菜单(用户管理、兑换码等) - 自动关闭用户注册功能 - 管理员并发数自动设为无限制(99999) - 侧边栏根据模式动态调整菜单项 同时优化分组页面的"专属分组"功能,添加帮助提示说明使用场景 --- backend/cmd/server/wire_gen.go | 2 +- .../internal/handler/admin/setting_handler.go | 26 ++- backend/internal/handler/dto/settings.go | 3 + backend/internal/handler/setting_handler.go | 1 + backend/internal/service/domain_constants.go | 3 + backend/internal/service/setting_service.go | 6 + backend/internal/service/settings_view.go | 3 + backend/internal/service/user_service.go | 8 + frontend/src/api/admin/settings.ts | 2 + frontend/src/components/layout/AppSidebar.vue | 75 +++++--- frontend/src/i18n/locales/en.ts | 21 ++- frontend/src/i18n/locales/zh.ts | 29 ++- frontend/src/stores/app.ts | 6 +- frontend/src/types/index.ts | 1 + frontend/src/views/admin/GroupsView.vue | 174 +++++++++++++----- frontend/src/views/admin/SettingsView.vue | 108 ++++++++++- 16 files changed, 378 insertions(+), 90 deletions(-) diff --git a/backend/cmd/server/wire_gen.go b/backend/cmd/server/wire_gen.go index 9904aa0d..1ff07f1e 100644 --- a/backend/cmd/server/wire_gen.go +++ b/backend/cmd/server/wire_gen.go @@ -99,7 +99,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) { geminiOAuthHandler := admin.NewGeminiOAuthHandler(geminiOAuthService) proxyHandler := admin.NewProxyHandler(adminService) adminRedeemHandler := admin.NewRedeemHandler(adminService) - settingHandler := admin.NewSettingHandler(settingService, emailService) + settingHandler := admin.NewSettingHandler(settingService, emailService, userService) updateCache := repository.NewUpdateCache(client) gitHubReleaseClient := repository.NewGitHubReleaseClient() serviceBuildInfo := provideServiceBuildInfo(buildInfo) diff --git a/backend/internal/handler/admin/setting_handler.go b/backend/internal/handler/admin/setting_handler.go index 14b569de..50ac3e68 100644 --- a/backend/internal/handler/admin/setting_handler.go +++ b/backend/internal/handler/admin/setting_handler.go @@ -12,13 +12,15 @@ import ( type SettingHandler struct { settingService *service.SettingService emailService *service.EmailService + userService *service.UserService } // NewSettingHandler 创建系统设置处理器 -func NewSettingHandler(settingService *service.SettingService, emailService *service.EmailService) *SettingHandler { +func NewSettingHandler(settingService *service.SettingService, emailService *service.EmailService, userService *service.UserService) *SettingHandler { return &SettingHandler{ settingService: settingService, emailService: emailService, + userService: userService, } } @@ -52,6 +54,7 @@ func (h *SettingHandler) GetSettings(c *gin.Context) { DocUrl: settings.DocUrl, DefaultConcurrency: settings.DefaultConcurrency, DefaultBalance: settings.DefaultBalance, + SimpleMode: settings.SimpleMode, }) } @@ -86,6 +89,9 @@ type UpdateSettingsRequest struct { // 默认配置 DefaultConcurrency int `json:"default_concurrency"` DefaultBalance float64 `json:"default_balance"` + + // 使用模式 + SimpleMode bool `json:"simple_mode"` } // UpdateSettings 更新系统设置 @@ -108,8 +114,14 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { req.SmtpPort = 587 } + // 简单模式下自动关闭开放注册 + registrationEnabled := req.RegistrationEnabled + if req.SimpleMode { + registrationEnabled = false + } + settings := &service.SystemSettings{ - RegistrationEnabled: req.RegistrationEnabled, + RegistrationEnabled: registrationEnabled, EmailVerifyEnabled: req.EmailVerifyEnabled, SmtpHost: req.SmtpHost, SmtpPort: req.SmtpPort, @@ -129,6 +141,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { DocUrl: req.DocUrl, DefaultConcurrency: req.DefaultConcurrency, DefaultBalance: req.DefaultBalance, + SimpleMode: req.SimpleMode, } if err := h.settingService.UpdateSettings(c.Request.Context(), settings); err != nil { @@ -136,6 +149,14 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { return } + // 如果切换到简单模式,自动将管理员并发数设为 99999 + if req.SimpleMode { + admin, err := h.userService.GetFirstAdmin(c.Request.Context()) + if err == nil && admin != nil { + _ = h.userService.UpdateConcurrency(c.Request.Context(), admin.ID, 99999) + } + } + // 重新获取设置返回 updatedSettings, err := h.settingService.GetAllSettings(c.Request.Context()) if err != nil { @@ -164,6 +185,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { DocUrl: updatedSettings.DocUrl, DefaultConcurrency: updatedSettings.DefaultConcurrency, DefaultBalance: updatedSettings.DefaultBalance, + SimpleMode: updatedSettings.SimpleMode, }) } diff --git a/backend/internal/handler/dto/settings.go b/backend/internal/handler/dto/settings.go index 96e59e3f..bb1f8475 100644 --- a/backend/internal/handler/dto/settings.go +++ b/backend/internal/handler/dto/settings.go @@ -26,6 +26,8 @@ type SystemSettings struct { DefaultConcurrency int `json:"default_concurrency"` DefaultBalance float64 `json:"default_balance"` + + SimpleMode bool `json:"simple_mode"` // 简单模式 } type PublicSettings struct { @@ -40,4 +42,5 @@ type PublicSettings struct { ContactInfo string `json:"contact_info"` DocUrl string `json:"doc_url"` Version string `json:"version"` + SimpleMode bool `json:"simple_mode"` // 简单模式 } diff --git a/backend/internal/handler/setting_handler.go b/backend/internal/handler/setting_handler.go index 90165288..038280dd 100644 --- a/backend/internal/handler/setting_handler.go +++ b/backend/internal/handler/setting_handler.go @@ -43,5 +43,6 @@ func (h *SettingHandler) GetPublicSettings(c *gin.Context) { ContactInfo: settings.ContactInfo, DocUrl: settings.DocUrl, Version: h.version, + SimpleMode: settings.SimpleMode, }) } diff --git a/backend/internal/service/domain_constants.go b/backend/internal/service/domain_constants.go index b0f3fc9e..b8da35f6 100644 --- a/backend/internal/service/domain_constants.go +++ b/backend/internal/service/domain_constants.go @@ -90,6 +90,9 @@ const ( // 管理员 API Key SettingKeyAdminApiKey = "admin_api_key" // 全局管理员 API Key(用于外部系统集成) + + // 使用模式 + SettingKeySimpleMode = "simple_mode" // 简单模式(隐藏多用户管理功能) ) // Admin API Key prefix (distinct from user "sk-" keys) diff --git a/backend/internal/service/setting_service.go b/backend/internal/service/setting_service.go index 0ffe991d..7f17fe8e 100644 --- a/backend/internal/service/setting_service.go +++ b/backend/internal/service/setting_service.go @@ -64,6 +64,7 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings SettingKeyApiBaseUrl, SettingKeyContactInfo, SettingKeyDocUrl, + SettingKeySimpleMode, } settings, err := s.settingRepo.GetMultiple(ctx, keys) @@ -82,6 +83,7 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings ApiBaseUrl: settings[SettingKeyApiBaseUrl], ContactInfo: settings[SettingKeyContactInfo], DocUrl: settings[SettingKeyDocUrl], + SimpleMode: settings[SettingKeySimpleMode] == "true", }, nil } @@ -123,6 +125,9 @@ func (s *SettingService) UpdateSettings(ctx context.Context, settings *SystemSet updates[SettingKeyDefaultConcurrency] = strconv.Itoa(settings.DefaultConcurrency) updates[SettingKeyDefaultBalance] = strconv.FormatFloat(settings.DefaultBalance, 'f', 8, 64) + // 使用模式 + updates[SettingKeySimpleMode] = strconv.FormatBool(settings.SimpleMode) + return s.settingRepo.SetMultiple(ctx, updates) } @@ -223,6 +228,7 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin ApiBaseUrl: settings[SettingKeyApiBaseUrl], ContactInfo: settings[SettingKeyContactInfo], DocUrl: settings[SettingKeyDocUrl], + SimpleMode: settings[SettingKeySimpleMode] == "true", } // 解析整数类型 diff --git a/backend/internal/service/settings_view.go b/backend/internal/service/settings_view.go index cb9751d1..f67d5e9d 100644 --- a/backend/internal/service/settings_view.go +++ b/backend/internal/service/settings_view.go @@ -25,6 +25,8 @@ type SystemSettings struct { DefaultConcurrency int DefaultBalance float64 + + SimpleMode bool // 简单模式 } type PublicSettings struct { @@ -39,4 +41,5 @@ type PublicSettings struct { ContactInfo string DocUrl string Version string + SimpleMode bool // 简单模式 } diff --git a/backend/internal/service/user_service.go b/backend/internal/service/user_service.go index 3ff47e7d..6b190cf3 100644 --- a/backend/internal/service/user_service.go +++ b/backend/internal/service/user_service.go @@ -164,6 +164,14 @@ func (s *UserService) UpdateBalance(ctx context.Context, userID int64, amount fl return nil } +// UpdateConcurrency 更新用户并发数(管理员功能) +func (s *UserService) UpdateConcurrency(ctx context.Context, userID int64, concurrency int) error { + if err := s.userRepo.UpdateConcurrency(ctx, userID, concurrency); err != nil { + return fmt.Errorf("update concurrency: %w", err) + } + return nil +} + // UpdateStatus 更新用户状态(管理员功能) func (s *UserService) UpdateStatus(ctx context.Context, userID int64, status string) error { user, err := s.userRepo.GetByID(ctx, userID) diff --git a/frontend/src/api/admin/settings.ts b/frontend/src/api/admin/settings.ts index cf5cba6d..7da99351 100644 --- a/frontend/src/api/admin/settings.ts +++ b/frontend/src/api/admin/settings.ts @@ -34,6 +34,8 @@ export interface SystemSettings { turnstile_enabled: boolean turnstile_site_key: string turnstile_secret_key: string + // Usage mode + simple_mode: boolean } /** diff --git a/frontend/src/components/layout/AppSidebar.vue b/frontend/src/components/layout/AppSidebar.vue index cfbd7c14..ce553e1d 100644 --- a/frontend/src/components/layout/AppSidebar.vue +++ b/frontend/src/components/layout/AppSidebar.vue @@ -45,8 +45,8 @@ - -
-

- {{ t('admin.groups.subscription.title') }} -

- -
+
[ { value: 'inactive', label: t('common.inactive') } ]) -const subscriptionTypeOptions = computed(() => [ - { value: 'standard', label: t('admin.groups.subscription.standard') }, - { value: 'subscription', label: t('admin.groups.subscription.subscription') } -]) +const subscriptionTypeOptions = computed(() => { + // 简单模式下只显示订阅模式(配额模式) + if (appStore.simpleMode) { + return [ + { value: 'subscription', label: t('admin.groups.subscription.subscription') } + ] + } + return [ + { value: 'standard', label: t('admin.groups.subscription.standard') }, + { value: 'subscription', label: t('admin.groups.subscription.subscription') } + ] +}) const groups = ref([]) const loading = ref(false) @@ -732,7 +804,7 @@ const closeCreateModal = () => { createForm.platform = 'anthropic' createForm.rate_multiplier = 1.0 createForm.is_exclusive = false - createForm.subscription_type = 'standard' + createForm.subscription_type = appStore.simpleMode ? 'subscription' : 'standard' createForm.daily_limit_usd = null createForm.weekly_limit_usd = null createForm.monthly_limit_usd = null @@ -823,5 +895,9 @@ watch( onMounted(() => { loadGroups() + // 简单模式下默认使用订阅模式 + if (appStore.simpleMode) { + createForm.subscription_type = 'subscription' + } }) diff --git a/frontend/src/views/admin/SettingsView.vue b/frontend/src/views/admin/SettingsView.vue index e8d64ab1..cada1c95 100644 --- a/frontend/src/views/admin/SettingsView.vue +++ b/frontend/src/views/admin/SettingsView.vue @@ -153,6 +153,58 @@
+ +
+
+

+ {{ t('admin.settings.usageMode.title') }} +

+

+ {{ t('admin.settings.usageMode.description') }} +

+
+
+ +
+
+ +

+ {{ t('admin.settings.usageMode.simpleModeHint') }} +

+
+ +
+ + +
+
+ + + +

+ {{ t('admin.settings.usageMode.simpleModeWarning') }} +

+
+
+
+
+
@@ -706,6 +758,19 @@
+ + + @@ -716,6 +781,7 @@ import { adminAPI } from '@/api' import type { SystemSettings } from '@/api/admin/settings' import AppLayout from '@/components/layout/AppLayout.vue' import Toggle from '@/components/common/Toggle.vue' +import ConfirmDialog from '@/components/common/ConfirmDialog.vue' import { useAppStore } from '@/stores' const { t } = useI18n() @@ -728,6 +794,10 @@ const sendingTestEmail = ref(false) const testEmailAddress = ref('') const logoError = ref('') +// Simple mode confirmation dialog +const showSimpleModeConfirm = ref(false) +const pendingSimpleModeValue = ref(false) + // Admin API Key 状态 const adminApiKeyLoading = ref(true) const adminApiKeyExists = ref(false) @@ -756,7 +826,9 @@ const form = reactive({ // Cloudflare Turnstile turnstile_enabled: false, turnstile_site_key: '', - turnstile_secret_key: '' + turnstile_secret_key: '', + // Usage mode + simple_mode: false }) function handleLogoUpload(event: Event) { @@ -827,6 +899,40 @@ async function saveSettings() { } } +// Simple mode toggle handlers +function onSimpleModeToggle(value: boolean) { + pendingSimpleModeValue.value = value + showSimpleModeConfirm.value = true +} + +async function confirmSimpleModeChange() { + showSimpleModeConfirm.value = false + form.simple_mode = pendingSimpleModeValue.value + + saving.value = true + try { + await adminAPI.settings.updateSettings(form) + await appStore.fetchPublicSettings(true) + appStore.showSuccess(t('admin.settings.settingsSaved')) + // Reload page to apply menu changes + setTimeout(() => { + window.location.reload() + }, 500) + } catch (error: any) { + // Revert on error + form.simple_mode = !pendingSimpleModeValue.value + appStore.showError( + t('admin.settings.failedToSave') + ': ' + (error.message || t('common.unknownError')) + ) + } finally { + saving.value = false + } +} + +function cancelSimpleModeChange() { + showSimpleModeConfirm.value = false +} + async function testSmtpConnection() { testingSmtp.value = true try {