feat: 添加简单模式功能
新增简单模式设置,适合个人使用场景: - 隐藏多用户管理相关菜单(用户管理、兑换码等) - 自动关闭用户注册功能 - 管理员并发数自动设为无限制(99999) - 侧边栏根据模式动态调整菜单项 同时优化分组页面的"专属分组"功能,添加帮助提示说明使用场景
This commit is contained in:
@@ -99,7 +99,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
|||||||
geminiOAuthHandler := admin.NewGeminiOAuthHandler(geminiOAuthService)
|
geminiOAuthHandler := admin.NewGeminiOAuthHandler(geminiOAuthService)
|
||||||
proxyHandler := admin.NewProxyHandler(adminService)
|
proxyHandler := admin.NewProxyHandler(adminService)
|
||||||
adminRedeemHandler := admin.NewRedeemHandler(adminService)
|
adminRedeemHandler := admin.NewRedeemHandler(adminService)
|
||||||
settingHandler := admin.NewSettingHandler(settingService, emailService)
|
settingHandler := admin.NewSettingHandler(settingService, emailService, userService)
|
||||||
updateCache := repository.NewUpdateCache(client)
|
updateCache := repository.NewUpdateCache(client)
|
||||||
gitHubReleaseClient := repository.NewGitHubReleaseClient()
|
gitHubReleaseClient := repository.NewGitHubReleaseClient()
|
||||||
serviceBuildInfo := provideServiceBuildInfo(buildInfo)
|
serviceBuildInfo := provideServiceBuildInfo(buildInfo)
|
||||||
|
|||||||
@@ -12,13 +12,15 @@ import (
|
|||||||
type SettingHandler struct {
|
type SettingHandler struct {
|
||||||
settingService *service.SettingService
|
settingService *service.SettingService
|
||||||
emailService *service.EmailService
|
emailService *service.EmailService
|
||||||
|
userService *service.UserService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSettingHandler 创建系统设置处理器
|
// NewSettingHandler 创建系统设置处理器
|
||||||
func NewSettingHandler(settingService *service.SettingService, emailService *service.EmailService) *SettingHandler {
|
func NewSettingHandler(settingService *service.SettingService, emailService *service.EmailService, userService *service.UserService) *SettingHandler {
|
||||||
return &SettingHandler{
|
return &SettingHandler{
|
||||||
settingService: settingService,
|
settingService: settingService,
|
||||||
emailService: emailService,
|
emailService: emailService,
|
||||||
|
userService: userService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +54,7 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
|
|||||||
DocUrl: settings.DocUrl,
|
DocUrl: settings.DocUrl,
|
||||||
DefaultConcurrency: settings.DefaultConcurrency,
|
DefaultConcurrency: settings.DefaultConcurrency,
|
||||||
DefaultBalance: settings.DefaultBalance,
|
DefaultBalance: settings.DefaultBalance,
|
||||||
|
SimpleMode: settings.SimpleMode,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,6 +89,9 @@ type UpdateSettingsRequest struct {
|
|||||||
// 默认配置
|
// 默认配置
|
||||||
DefaultConcurrency int `json:"default_concurrency"`
|
DefaultConcurrency int `json:"default_concurrency"`
|
||||||
DefaultBalance float64 `json:"default_balance"`
|
DefaultBalance float64 `json:"default_balance"`
|
||||||
|
|
||||||
|
// 使用模式
|
||||||
|
SimpleMode bool `json:"simple_mode"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateSettings 更新系统设置
|
// UpdateSettings 更新系统设置
|
||||||
@@ -108,8 +114,14 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
|||||||
req.SmtpPort = 587
|
req.SmtpPort = 587
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 简单模式下自动关闭开放注册
|
||||||
|
registrationEnabled := req.RegistrationEnabled
|
||||||
|
if req.SimpleMode {
|
||||||
|
registrationEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
settings := &service.SystemSettings{
|
settings := &service.SystemSettings{
|
||||||
RegistrationEnabled: req.RegistrationEnabled,
|
RegistrationEnabled: registrationEnabled,
|
||||||
EmailVerifyEnabled: req.EmailVerifyEnabled,
|
EmailVerifyEnabled: req.EmailVerifyEnabled,
|
||||||
SmtpHost: req.SmtpHost,
|
SmtpHost: req.SmtpHost,
|
||||||
SmtpPort: req.SmtpPort,
|
SmtpPort: req.SmtpPort,
|
||||||
@@ -129,6 +141,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
|||||||
DocUrl: req.DocUrl,
|
DocUrl: req.DocUrl,
|
||||||
DefaultConcurrency: req.DefaultConcurrency,
|
DefaultConcurrency: req.DefaultConcurrency,
|
||||||
DefaultBalance: req.DefaultBalance,
|
DefaultBalance: req.DefaultBalance,
|
||||||
|
SimpleMode: req.SimpleMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.settingService.UpdateSettings(c.Request.Context(), settings); err != nil {
|
if err := h.settingService.UpdateSettings(c.Request.Context(), settings); err != nil {
|
||||||
@@ -136,6 +149,14 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
|||||||
return
|
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())
|
updatedSettings, err := h.settingService.GetAllSettings(c.Request.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -164,6 +185,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
|||||||
DocUrl: updatedSettings.DocUrl,
|
DocUrl: updatedSettings.DocUrl,
|
||||||
DefaultConcurrency: updatedSettings.DefaultConcurrency,
|
DefaultConcurrency: updatedSettings.DefaultConcurrency,
|
||||||
DefaultBalance: updatedSettings.DefaultBalance,
|
DefaultBalance: updatedSettings.DefaultBalance,
|
||||||
|
SimpleMode: updatedSettings.SimpleMode,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ type SystemSettings struct {
|
|||||||
|
|
||||||
DefaultConcurrency int `json:"default_concurrency"`
|
DefaultConcurrency int `json:"default_concurrency"`
|
||||||
DefaultBalance float64 `json:"default_balance"`
|
DefaultBalance float64 `json:"default_balance"`
|
||||||
|
|
||||||
|
SimpleMode bool `json:"simple_mode"` // 简单模式
|
||||||
}
|
}
|
||||||
|
|
||||||
type PublicSettings struct {
|
type PublicSettings struct {
|
||||||
@@ -40,4 +42,5 @@ type PublicSettings struct {
|
|||||||
ContactInfo string `json:"contact_info"`
|
ContactInfo string `json:"contact_info"`
|
||||||
DocUrl string `json:"doc_url"`
|
DocUrl string `json:"doc_url"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
|
SimpleMode bool `json:"simple_mode"` // 简单模式
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,5 +43,6 @@ func (h *SettingHandler) GetPublicSettings(c *gin.Context) {
|
|||||||
ContactInfo: settings.ContactInfo,
|
ContactInfo: settings.ContactInfo,
|
||||||
DocUrl: settings.DocUrl,
|
DocUrl: settings.DocUrl,
|
||||||
Version: h.version,
|
Version: h.version,
|
||||||
|
SimpleMode: settings.SimpleMode,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,9 @@ const (
|
|||||||
|
|
||||||
// 管理员 API Key
|
// 管理员 API Key
|
||||||
SettingKeyAdminApiKey = "admin_api_key" // 全局管理员 API Key(用于外部系统集成)
|
SettingKeyAdminApiKey = "admin_api_key" // 全局管理员 API Key(用于外部系统集成)
|
||||||
|
|
||||||
|
// 使用模式
|
||||||
|
SettingKeySimpleMode = "simple_mode" // 简单模式(隐藏多用户管理功能)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Admin API Key prefix (distinct from user "sk-" keys)
|
// Admin API Key prefix (distinct from user "sk-" keys)
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings
|
|||||||
SettingKeyApiBaseUrl,
|
SettingKeyApiBaseUrl,
|
||||||
SettingKeyContactInfo,
|
SettingKeyContactInfo,
|
||||||
SettingKeyDocUrl,
|
SettingKeyDocUrl,
|
||||||
|
SettingKeySimpleMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
settings, err := s.settingRepo.GetMultiple(ctx, keys)
|
settings, err := s.settingRepo.GetMultiple(ctx, keys)
|
||||||
@@ -82,6 +83,7 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings
|
|||||||
ApiBaseUrl: settings[SettingKeyApiBaseUrl],
|
ApiBaseUrl: settings[SettingKeyApiBaseUrl],
|
||||||
ContactInfo: settings[SettingKeyContactInfo],
|
ContactInfo: settings[SettingKeyContactInfo],
|
||||||
DocUrl: settings[SettingKeyDocUrl],
|
DocUrl: settings[SettingKeyDocUrl],
|
||||||
|
SimpleMode: settings[SettingKeySimpleMode] == "true",
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,6 +125,9 @@ func (s *SettingService) UpdateSettings(ctx context.Context, settings *SystemSet
|
|||||||
updates[SettingKeyDefaultConcurrency] = strconv.Itoa(settings.DefaultConcurrency)
|
updates[SettingKeyDefaultConcurrency] = strconv.Itoa(settings.DefaultConcurrency)
|
||||||
updates[SettingKeyDefaultBalance] = strconv.FormatFloat(settings.DefaultBalance, 'f', 8, 64)
|
updates[SettingKeyDefaultBalance] = strconv.FormatFloat(settings.DefaultBalance, 'f', 8, 64)
|
||||||
|
|
||||||
|
// 使用模式
|
||||||
|
updates[SettingKeySimpleMode] = strconv.FormatBool(settings.SimpleMode)
|
||||||
|
|
||||||
return s.settingRepo.SetMultiple(ctx, updates)
|
return s.settingRepo.SetMultiple(ctx, updates)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,6 +228,7 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin
|
|||||||
ApiBaseUrl: settings[SettingKeyApiBaseUrl],
|
ApiBaseUrl: settings[SettingKeyApiBaseUrl],
|
||||||
ContactInfo: settings[SettingKeyContactInfo],
|
ContactInfo: settings[SettingKeyContactInfo],
|
||||||
DocUrl: settings[SettingKeyDocUrl],
|
DocUrl: settings[SettingKeyDocUrl],
|
||||||
|
SimpleMode: settings[SettingKeySimpleMode] == "true",
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析整数类型
|
// 解析整数类型
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ type SystemSettings struct {
|
|||||||
|
|
||||||
DefaultConcurrency int
|
DefaultConcurrency int
|
||||||
DefaultBalance float64
|
DefaultBalance float64
|
||||||
|
|
||||||
|
SimpleMode bool // 简单模式
|
||||||
}
|
}
|
||||||
|
|
||||||
type PublicSettings struct {
|
type PublicSettings struct {
|
||||||
@@ -39,4 +41,5 @@ type PublicSettings struct {
|
|||||||
ContactInfo string
|
ContactInfo string
|
||||||
DocUrl string
|
DocUrl string
|
||||||
Version string
|
Version string
|
||||||
|
SimpleMode bool // 简单模式
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,6 +164,14 @@ func (s *UserService) UpdateBalance(ctx context.Context, userID int64, amount fl
|
|||||||
return nil
|
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 更新用户状态(管理员功能)
|
// UpdateStatus 更新用户状态(管理员功能)
|
||||||
func (s *UserService) UpdateStatus(ctx context.Context, userID int64, status string) error {
|
func (s *UserService) UpdateStatus(ctx context.Context, userID int64, status string) error {
|
||||||
user, err := s.userRepo.GetByID(ctx, userID)
|
user, err := s.userRepo.GetByID(ctx, userID)
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ export interface SystemSettings {
|
|||||||
turnstile_enabled: boolean
|
turnstile_enabled: boolean
|
||||||
turnstile_site_key: string
|
turnstile_site_key: string
|
||||||
turnstile_secret_key: string
|
turnstile_secret_key: string
|
||||||
|
// Usage mode
|
||||||
|
simple_mode: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -45,8 +45,8 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Personal Section for Admin -->
|
<!-- Personal Section for Admin (hidden in simple mode) -->
|
||||||
<div class="sidebar-section">
|
<div v-if="!simpleMode" class="sidebar-section">
|
||||||
<div v-if="!sidebarCollapsed" class="sidebar-section-title">
|
<div v-if="!sidebarCollapsed" class="sidebar-section-title">
|
||||||
{{ t('nav.myAccount') }}
|
{{ t('nav.myAccount') }}
|
||||||
</div>
|
</div>
|
||||||
@@ -154,6 +154,7 @@ const isDark = ref(document.documentElement.classList.contains('dark'))
|
|||||||
const siteName = computed(() => appStore.siteName)
|
const siteName = computed(() => appStore.siteName)
|
||||||
const siteLogo = computed(() => appStore.siteLogo)
|
const siteLogo = computed(() => appStore.siteLogo)
|
||||||
const siteVersion = computed(() => appStore.siteVersion)
|
const siteVersion = computed(() => appStore.siteVersion)
|
||||||
|
const simpleMode = computed(() => appStore.simpleMode)
|
||||||
|
|
||||||
// SVG Icon Components
|
// SVG Icon Components
|
||||||
const DashboardIcon = {
|
const DashboardIcon = {
|
||||||
@@ -402,36 +403,54 @@ const ChevronDoubleRightIcon = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// User navigation items (for regular users)
|
// User navigation items (for regular users)
|
||||||
const userNavItems = computed(() => [
|
const userNavItems = computed(() => {
|
||||||
{ path: '/dashboard', label: t('nav.dashboard'), icon: DashboardIcon },
|
const items = [
|
||||||
{ path: '/keys', label: t('nav.apiKeys'), icon: KeyIcon },
|
{ path: '/dashboard', label: t('nav.dashboard'), icon: DashboardIcon },
|
||||||
{ path: '/usage', label: t('nav.usage'), icon: ChartIcon },
|
{ path: '/keys', label: t('nav.apiKeys'), icon: KeyIcon },
|
||||||
{ path: '/subscriptions', label: t('nav.mySubscriptions'), icon: CreditCardIcon },
|
{ path: '/usage', label: t('nav.usage'), icon: ChartIcon, hideInSimpleMode: true },
|
||||||
{ path: '/redeem', label: t('nav.redeem'), icon: GiftIcon },
|
{ path: '/subscriptions', label: t('nav.mySubscriptions'), icon: CreditCardIcon, hideInSimpleMode: true },
|
||||||
{ path: '/profile', label: t('nav.profile'), icon: UserIcon }
|
{ path: '/redeem', label: t('nav.redeem'), icon: GiftIcon, hideInSimpleMode: true },
|
||||||
])
|
{ path: '/profile', label: t('nav.profile'), icon: UserIcon }
|
||||||
|
]
|
||||||
|
return simpleMode.value ? items.filter(item => !item.hideInSimpleMode) : items
|
||||||
|
})
|
||||||
|
|
||||||
// Personal navigation items (for admin's "My Account" section, without Dashboard)
|
// Personal navigation items (for admin's "My Account" section, without Dashboard)
|
||||||
const personalNavItems = computed(() => [
|
const personalNavItems = computed(() => {
|
||||||
{ path: '/keys', label: t('nav.apiKeys'), icon: KeyIcon },
|
const items = [
|
||||||
{ path: '/usage', label: t('nav.usage'), icon: ChartIcon },
|
{ path: '/keys', label: t('nav.apiKeys'), icon: KeyIcon },
|
||||||
{ path: '/subscriptions', label: t('nav.mySubscriptions'), icon: CreditCardIcon },
|
{ path: '/usage', label: t('nav.usage'), icon: ChartIcon, hideInSimpleMode: true },
|
||||||
{ path: '/redeem', label: t('nav.redeem'), icon: GiftIcon },
|
{ path: '/subscriptions', label: t('nav.mySubscriptions'), icon: CreditCardIcon, hideInSimpleMode: true },
|
||||||
{ path: '/profile', label: t('nav.profile'), icon: UserIcon }
|
{ path: '/redeem', label: t('nav.redeem'), icon: GiftIcon, hideInSimpleMode: true },
|
||||||
])
|
{ path: '/profile', label: t('nav.profile'), icon: UserIcon }
|
||||||
|
]
|
||||||
|
return simpleMode.value ? items.filter(item => !item.hideInSimpleMode) : items
|
||||||
|
})
|
||||||
|
|
||||||
// Admin navigation items
|
// Admin navigation items
|
||||||
const adminNavItems = computed(() => [
|
const adminNavItems = computed(() => {
|
||||||
{ path: '/admin/dashboard', label: t('nav.dashboard'), icon: DashboardIcon },
|
const baseItems = [
|
||||||
{ path: '/admin/users', label: t('nav.users'), icon: UsersIcon },
|
{ path: '/admin/dashboard', label: t('nav.dashboard'), icon: DashboardIcon },
|
||||||
{ path: '/admin/groups', label: t('nav.groups'), icon: FolderIcon },
|
{ path: '/admin/users', label: t('nav.users'), icon: UsersIcon, hideInSimpleMode: true },
|
||||||
{ path: '/admin/subscriptions', label: t('nav.subscriptions'), icon: CreditCardIcon },
|
{ path: '/admin/groups', label: t('nav.groups'), icon: FolderIcon },
|
||||||
{ path: '/admin/accounts', label: t('nav.accounts'), icon: GlobeIcon },
|
{ path: '/admin/subscriptions', label: t('nav.subscriptions'), icon: CreditCardIcon },
|
||||||
{ path: '/admin/proxies', label: t('nav.proxies'), icon: ServerIcon },
|
{ path: '/admin/accounts', label: t('nav.accounts'), icon: GlobeIcon },
|
||||||
{ path: '/admin/redeem', label: t('nav.redeemCodes'), icon: TicketIcon },
|
{ path: '/admin/proxies', label: t('nav.proxies'), icon: ServerIcon },
|
||||||
{ path: '/admin/usage', label: t('nav.usage'), icon: ChartIcon },
|
{ path: '/admin/redeem', label: t('nav.redeemCodes'), icon: TicketIcon, hideInSimpleMode: true },
|
||||||
{ path: '/admin/settings', label: t('nav.settings'), icon: CogIcon }
|
{ path: '/admin/usage', label: t('nav.usage'), icon: ChartIcon },
|
||||||
])
|
]
|
||||||
|
|
||||||
|
// 简单模式下,在系统设置前插入 API密钥
|
||||||
|
if (simpleMode.value) {
|
||||||
|
const filtered = baseItems.filter(item => !item.hideInSimpleMode)
|
||||||
|
filtered.push({ path: '/keys', label: t('nav.apiKeys'), icon: KeyIcon })
|
||||||
|
filtered.push({ path: '/admin/settings', label: t('nav.settings'), icon: CogIcon })
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
|
|
||||||
|
baseItems.push({ path: '/admin/settings', label: t('nav.settings'), icon: CogIcon })
|
||||||
|
return baseItems
|
||||||
|
})
|
||||||
|
|
||||||
function toggleSidebar() {
|
function toggleSidebar() {
|
||||||
appStore.toggleSidebar()
|
appStore.toggleSidebar()
|
||||||
|
|||||||
@@ -676,14 +676,21 @@ export default {
|
|||||||
description: 'Description',
|
description: 'Description',
|
||||||
platform: 'Platform',
|
platform: 'Platform',
|
||||||
rateMultiplier: 'Rate Multiplier',
|
rateMultiplier: 'Rate Multiplier',
|
||||||
status: 'Status'
|
status: 'Status',
|
||||||
|
exclusive: 'Exclusive Group'
|
||||||
},
|
},
|
||||||
enterGroupName: 'Enter group name',
|
enterGroupName: 'Enter group name',
|
||||||
optionalDescription: 'Optional description',
|
optionalDescription: 'Optional description',
|
||||||
platformHint: 'Select the platform this group is associated with',
|
platformHint: 'Select the platform this group is associated with',
|
||||||
platformNotEditable: 'Platform cannot be changed after creation',
|
platformNotEditable: 'Platform cannot be changed after creation',
|
||||||
rateMultiplierHint: 'Cost multiplier for this group (e.g., 1.5 = 150% of base cost)',
|
rateMultiplierHint: 'Cost multiplier for this group (e.g., 1.5 = 150% of base cost)',
|
||||||
exclusiveHint: 'Exclusive (requires explicit user access)',
|
exclusiveHint: 'Exclusive group, manually assign to specific users',
|
||||||
|
exclusiveTooltip: {
|
||||||
|
title: 'What is an exclusive group?',
|
||||||
|
description: 'When enabled, users cannot see this group when creating API Keys. Only after an admin manually assigns a user to this group can they use it.',
|
||||||
|
example: 'Use case:',
|
||||||
|
exampleContent: 'Public group rate is 0.8. Create an exclusive group with 0.7 rate, manually assign VIP users to give them better pricing.'
|
||||||
|
},
|
||||||
noGroupsYet: 'No groups yet',
|
noGroupsYet: 'No groups yet',
|
||||||
createFirstGroup: 'Create your first group to organize API keys.',
|
createFirstGroup: 'Create your first group to organize API keys.',
|
||||||
creating: 'Creating...',
|
creating: 'Creating...',
|
||||||
@@ -1400,6 +1407,16 @@ export default {
|
|||||||
securityWarning: 'Warning: This key provides full admin access. Keep it secure.',
|
securityWarning: 'Warning: This key provides full admin access. Keep it secure.',
|
||||||
usage: 'Usage: Add to request header - x-api-key: <your-admin-api-key>'
|
usage: 'Usage: Add to request header - x-api-key: <your-admin-api-key>'
|
||||||
},
|
},
|
||||||
|
usageMode: {
|
||||||
|
title: 'Usage Mode',
|
||||||
|
description: 'Toggle simple mode for a simplified interface',
|
||||||
|
simpleMode: 'Simple Mode',
|
||||||
|
simpleModeHint: 'Hide multi-user management features, suitable for personal use',
|
||||||
|
simpleModeWarning: 'When enabled, user management and redeem code menus will be hidden, registration will be disabled, and admin concurrency will be set to unlimited',
|
||||||
|
confirmTitle: 'Confirm Mode Change',
|
||||||
|
confirmEnableMessage: 'Are you sure you want to enable Simple Mode? This will hide multi-user management menus, disable registration, and set admin concurrency to 99999 (unlimited).',
|
||||||
|
confirmDisableMessage: 'Are you sure you want to disable Simple Mode? This will restore all management menus.'
|
||||||
|
},
|
||||||
saveSettings: 'Save Settings',
|
saveSettings: 'Save Settings',
|
||||||
saving: 'Saving...',
|
saving: 'Saving...',
|
||||||
settingsSaved: 'Settings saved successfully',
|
settingsSaved: 'Settings saved successfully',
|
||||||
|
|||||||
@@ -733,14 +733,15 @@ export default {
|
|||||||
platform: '平台',
|
platform: '平台',
|
||||||
rateMultiplier: '费率倍数',
|
rateMultiplier: '费率倍数',
|
||||||
status: '状态',
|
status: '状态',
|
||||||
|
exclusive: '专属分组',
|
||||||
nameLabel: '分组名称',
|
nameLabel: '分组名称',
|
||||||
namePlaceholder: '请输入分组名称',
|
namePlaceholder: '请输入分组名称',
|
||||||
descriptionLabel: '描述',
|
descriptionLabel: '描述',
|
||||||
descriptionPlaceholder: '请输入描述(可选)',
|
descriptionPlaceholder: '请输入描述(可选)',
|
||||||
rateMultiplierLabel: '费率倍数',
|
rateMultiplierLabel: '费率倍数',
|
||||||
rateMultiplierHint: '1.0 = 标准费率,0.5 = 半价,2.0 = 双倍',
|
rateMultiplierHint: '1.0 = 标准费率,0.5 = 半价,2.0 = 双倍',
|
||||||
exclusiveLabel: '独占模式',
|
exclusiveLabel: '专属分组',
|
||||||
exclusiveHint: '启用后,此分组的用户将独占使用分配的账号',
|
exclusiveHint: '专属分组,可以手动指定给用户',
|
||||||
platformLabel: '平台限制',
|
platformLabel: '平台限制',
|
||||||
platformPlaceholder: '选择平台(留空则不限制)',
|
platformPlaceholder: '选择平台(留空则不限制)',
|
||||||
accountsLabel: '指定账号',
|
accountsLabel: '指定账号',
|
||||||
@@ -753,8 +754,14 @@ export default {
|
|||||||
yes: '是',
|
yes: '是',
|
||||||
no: '否'
|
no: '否'
|
||||||
},
|
},
|
||||||
exclusive: '独占',
|
exclusive: '专属',
|
||||||
exclusiveHint: '启用后,此分组的用户将独占使用分配的账号',
|
exclusiveHint: '专属分组,可以手动指定给特定用户',
|
||||||
|
exclusiveTooltip: {
|
||||||
|
title: '什么是专属分组?',
|
||||||
|
description: '开启后,用户在创建 API Key 时将无法看到此分组。只有管理员手动将用户分配到此分组后,用户才能使用。',
|
||||||
|
example: '使用场景:',
|
||||||
|
exampleContent: '公开分组费率 0.8,您可以创建一个费率 0.7 的专属分组,手动分配给 VIP 用户,让他们享受更优惠的价格。'
|
||||||
|
},
|
||||||
rateMultiplierHint: '1.0 = 标准费率,0.5 = 半价,2.0 = 双倍',
|
rateMultiplierHint: '1.0 = 标准费率,0.5 = 半价,2.0 = 双倍',
|
||||||
platforms: {
|
platforms: {
|
||||||
all: '全部平台',
|
all: '全部平台',
|
||||||
@@ -773,8 +780,8 @@ export default {
|
|||||||
allPlatforms: '全部平台',
|
allPlatforms: '全部平台',
|
||||||
allStatus: '全部状态',
|
allStatus: '全部状态',
|
||||||
allGroups: '全部分组',
|
allGroups: '全部分组',
|
||||||
exclusiveFilter: '独占',
|
exclusiveFilter: '专属',
|
||||||
nonExclusive: '非独占',
|
nonExclusive: '公开',
|
||||||
public: '公开',
|
public: '公开',
|
||||||
rateAndAccounts: '{rate}x 费率 · {count} 个账号',
|
rateAndAccounts: '{rate}x 费率 · {count} 个账号',
|
||||||
accountsCount: '{count} 个账号',
|
accountsCount: '{count} 个账号',
|
||||||
@@ -1597,6 +1604,16 @@ export default {
|
|||||||
securityWarning: '警告:此密钥拥有完整的管理员权限,请妥善保管。',
|
securityWarning: '警告:此密钥拥有完整的管理员权限,请妥善保管。',
|
||||||
usage: '使用方法:在请求头中添加 x-api-key: <your-admin-api-key>'
|
usage: '使用方法:在请求头中添加 x-api-key: <your-admin-api-key>'
|
||||||
},
|
},
|
||||||
|
usageMode: {
|
||||||
|
title: '使用模式',
|
||||||
|
description: '切换简单模式以简化界面',
|
||||||
|
simpleMode: '简单模式',
|
||||||
|
simpleModeHint: '隐藏多用户管理功能,适合个人使用',
|
||||||
|
simpleModeWarning: '启用后将隐藏用户管理、兑换码管理等菜单,关闭用户注册功能,并将管理员并发数设为无限制',
|
||||||
|
confirmTitle: '确认切换使用模式',
|
||||||
|
confirmEnableMessage: '确定要启用简单模式吗?启用后将隐藏多用户管理相关菜单、关闭用户注册功能,并将管理员并发数设为 99999(无限制)。',
|
||||||
|
confirmDisableMessage: '确定要关闭简单模式吗?关闭后将恢复显示所有管理菜单。'
|
||||||
|
},
|
||||||
saveSettings: '保存设置',
|
saveSettings: '保存设置',
|
||||||
saving: '保存中...',
|
saving: '保存中...',
|
||||||
settingsSaved: '设置保存成功',
|
settingsSaved: '设置保存成功',
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export const useAppStore = defineStore('app', () => {
|
|||||||
const contactInfo = ref<string>('')
|
const contactInfo = ref<string>('')
|
||||||
const apiBaseUrl = ref<string>('')
|
const apiBaseUrl = ref<string>('')
|
||||||
const docUrl = ref<string>('')
|
const docUrl = ref<string>('')
|
||||||
|
const simpleMode = ref<boolean>(false)
|
||||||
|
|
||||||
// Version cache state
|
// Version cache state
|
||||||
const versionLoaded = ref<boolean>(false)
|
const versionLoaded = ref<boolean>(false)
|
||||||
@@ -296,7 +297,8 @@ export const useAppStore = defineStore('app', () => {
|
|||||||
api_base_url: apiBaseUrl.value,
|
api_base_url: apiBaseUrl.value,
|
||||||
contact_info: contactInfo.value,
|
contact_info: contactInfo.value,
|
||||||
doc_url: docUrl.value,
|
doc_url: docUrl.value,
|
||||||
version: siteVersion.value
|
version: siteVersion.value,
|
||||||
|
simple_mode: simpleMode.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,6 +316,7 @@ export const useAppStore = defineStore('app', () => {
|
|||||||
contactInfo.value = data.contact_info || ''
|
contactInfo.value = data.contact_info || ''
|
||||||
apiBaseUrl.value = data.api_base_url || ''
|
apiBaseUrl.value = data.api_base_url || ''
|
||||||
docUrl.value = data.doc_url || ''
|
docUrl.value = data.doc_url || ''
|
||||||
|
simpleMode.value = data.simple_mode || false
|
||||||
publicSettingsLoaded.value = true
|
publicSettingsLoaded.value = true
|
||||||
return data
|
return data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -348,6 +351,7 @@ export const useAppStore = defineStore('app', () => {
|
|||||||
contactInfo,
|
contactInfo,
|
||||||
apiBaseUrl,
|
apiBaseUrl,
|
||||||
docUrl,
|
docUrl,
|
||||||
|
simpleMode,
|
||||||
|
|
||||||
// Version state
|
// Version state
|
||||||
versionLoaded,
|
versionLoaded,
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export interface PublicSettings {
|
|||||||
contact_info: string
|
contact_info: string
|
||||||
doc_url: string
|
doc_url: string
|
||||||
version: string
|
version: string
|
||||||
|
simple_mode: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthResponse {
|
export interface AuthResponse {
|
||||||
|
|||||||
@@ -272,34 +272,66 @@
|
|||||||
/>
|
/>
|
||||||
<p class="input-hint">{{ t('admin.groups.rateMultiplierHint') }}</p>
|
<p class="input-hint">{{ t('admin.groups.rateMultiplierHint') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="createForm.subscription_type !== 'subscription'" class="flex items-center gap-3">
|
<div v-if="createForm.subscription_type !== 'subscription'">
|
||||||
<button
|
<div class="mb-1.5 flex items-center gap-1">
|
||||||
type="button"
|
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
@click="createForm.is_exclusive = !createForm.is_exclusive"
|
{{ t('admin.groups.form.exclusive') }}
|
||||||
:class="[
|
</label>
|
||||||
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors',
|
<!-- Help Tooltip -->
|
||||||
createForm.is_exclusive ? 'bg-primary-500' : 'bg-gray-300 dark:bg-dark-600'
|
<div class="group relative inline-flex">
|
||||||
]"
|
<svg
|
||||||
>
|
class="h-3.5 w-3.5 cursor-help text-gray-400 transition-colors hover:text-primary-500 dark:text-gray-500 dark:hover:text-primary-400"
|
||||||
<span
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
<!-- Tooltip Popover -->
|
||||||
|
<div class="pointer-events-none absolute bottom-full left-0 z-50 mb-2 w-72 opacity-0 transition-all duration-200 group-hover:pointer-events-auto group-hover:opacity-100">
|
||||||
|
<div class="rounded-lg bg-gray-900 p-3 text-white shadow-lg dark:bg-gray-800">
|
||||||
|
<p class="mb-2 text-xs font-medium">{{ t('admin.groups.exclusiveTooltip.title') }}</p>
|
||||||
|
<p class="mb-2 text-xs leading-relaxed text-gray-300">
|
||||||
|
{{ t('admin.groups.exclusiveTooltip.description') }}
|
||||||
|
</p>
|
||||||
|
<div class="rounded bg-gray-800 p-2 dark:bg-gray-700">
|
||||||
|
<p class="text-xs leading-relaxed text-gray-300">
|
||||||
|
<span class="text-primary-400">💡 {{ t('admin.groups.exclusiveTooltip.example') }}</span>
|
||||||
|
{{ t('admin.groups.exclusiveTooltip.exampleContent') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<!-- Arrow -->
|
||||||
|
<div class="absolute -bottom-1.5 left-3 h-3 w-3 rotate-45 bg-gray-900 dark:bg-gray-800"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
@click="createForm.is_exclusive = !createForm.is_exclusive"
|
||||||
:class="[
|
:class="[
|
||||||
'inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform',
|
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors',
|
||||||
createForm.is_exclusive ? 'translate-x-6' : 'translate-x-1'
|
createForm.is_exclusive ? 'bg-primary-500' : 'bg-gray-300 dark:bg-dark-600'
|
||||||
]"
|
]"
|
||||||
/>
|
>
|
||||||
</button>
|
<span
|
||||||
<label class="text-sm text-gray-700 dark:text-gray-300">
|
:class="[
|
||||||
{{ t('admin.groups.exclusiveHint') }}
|
'inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform',
|
||||||
</label>
|
createForm.is_exclusive ? 'translate-x-6' : 'translate-x-1'
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<span class="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
{{ createForm.is_exclusive ? t('admin.groups.exclusive') : t('admin.groups.public') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Subscription Configuration -->
|
<!-- Subscription Configuration -->
|
||||||
<div class="mt-4 border-t pt-4">
|
<div class="mt-4 border-t pt-4">
|
||||||
<h4 class="mb-4 text-sm font-medium text-gray-900 dark:text-white">
|
<div>
|
||||||
{{ t('admin.groups.subscription.title') }}
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<div class="mb-4">
|
|
||||||
<label class="input-label">{{ t('admin.groups.subscription.type') }}</label>
|
<label class="input-label">{{ t('admin.groups.subscription.type') }}</label>
|
||||||
<Select v-model="createForm.subscription_type" :options="subscriptionTypeOptions" />
|
<Select v-model="createForm.subscription_type" :options="subscriptionTypeOptions" />
|
||||||
<p class="input-hint">{{ t('admin.groups.subscription.typeHint') }}</p>
|
<p class="input-hint">{{ t('admin.groups.subscription.typeHint') }}</p>
|
||||||
@@ -422,25 +454,61 @@
|
|||||||
class="input"
|
class="input"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="editForm.subscription_type !== 'subscription'" class="flex items-center gap-3">
|
<div v-if="editForm.subscription_type !== 'subscription'">
|
||||||
<button
|
<div class="mb-1.5 flex items-center gap-1">
|
||||||
type="button"
|
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
@click="editForm.is_exclusive = !editForm.is_exclusive"
|
{{ t('admin.groups.form.exclusive') }}
|
||||||
:class="[
|
</label>
|
||||||
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors',
|
<!-- Help Tooltip -->
|
||||||
editForm.is_exclusive ? 'bg-primary-500' : 'bg-gray-300 dark:bg-dark-600'
|
<div class="group relative inline-flex">
|
||||||
]"
|
<svg
|
||||||
>
|
class="h-3.5 w-3.5 cursor-help text-gray-400 transition-colors hover:text-primary-500 dark:text-gray-500 dark:hover:text-primary-400"
|
||||||
<span
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
<!-- Tooltip Popover -->
|
||||||
|
<div class="pointer-events-none absolute bottom-full left-0 z-50 mb-2 w-72 opacity-0 transition-all duration-200 group-hover:pointer-events-auto group-hover:opacity-100">
|
||||||
|
<div class="rounded-lg bg-gray-900 p-3 text-white shadow-lg dark:bg-gray-800">
|
||||||
|
<p class="mb-2 text-xs font-medium">{{ t('admin.groups.exclusiveTooltip.title') }}</p>
|
||||||
|
<p class="mb-2 text-xs leading-relaxed text-gray-300">
|
||||||
|
{{ t('admin.groups.exclusiveTooltip.description') }}
|
||||||
|
</p>
|
||||||
|
<div class="rounded bg-gray-800 p-2 dark:bg-gray-700">
|
||||||
|
<p class="text-xs leading-relaxed text-gray-300">
|
||||||
|
<span class="text-primary-400">💡 {{ t('admin.groups.exclusiveTooltip.example') }}</span>
|
||||||
|
{{ t('admin.groups.exclusiveTooltip.exampleContent') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<!-- Arrow -->
|
||||||
|
<div class="absolute -bottom-1.5 left-3 h-3 w-3 rotate-45 bg-gray-900 dark:bg-gray-800"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
@click="editForm.is_exclusive = !editForm.is_exclusive"
|
||||||
:class="[
|
:class="[
|
||||||
'inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform',
|
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors',
|
||||||
editForm.is_exclusive ? 'translate-x-6' : 'translate-x-1'
|
editForm.is_exclusive ? 'bg-primary-500' : 'bg-gray-300 dark:bg-dark-600'
|
||||||
]"
|
]"
|
||||||
/>
|
>
|
||||||
</button>
|
<span
|
||||||
<label class="text-sm text-gray-700 dark:text-gray-300">
|
:class="[
|
||||||
{{ t('admin.groups.exclusiveHint') }}
|
'inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform',
|
||||||
</label>
|
editForm.is_exclusive ? 'translate-x-6' : 'translate-x-1'
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<span class="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
{{ editForm.is_exclusive ? t('admin.groups.exclusive') : t('admin.groups.public') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="input-label">{{ t('admin.groups.form.status') }}</label>
|
<label class="input-label">{{ t('admin.groups.form.status') }}</label>
|
||||||
@@ -449,11 +517,7 @@
|
|||||||
|
|
||||||
<!-- Subscription Configuration -->
|
<!-- Subscription Configuration -->
|
||||||
<div class="mt-4 border-t pt-4">
|
<div class="mt-4 border-t pt-4">
|
||||||
<h4 class="mb-4 text-sm font-medium text-gray-900 dark:text-white">
|
<div>
|
||||||
{{ t('admin.groups.subscription.title') }}
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<div class="mb-4">
|
|
||||||
<label class="input-label">{{ t('admin.groups.subscription.type') }}</label>
|
<label class="input-label">{{ t('admin.groups.subscription.type') }}</label>
|
||||||
<Select
|
<Select
|
||||||
v-model="editForm.subscription_type"
|
v-model="editForm.subscription_type"
|
||||||
@@ -619,10 +683,18 @@ const editStatusOptions = computed(() => [
|
|||||||
{ value: 'inactive', label: t('common.inactive') }
|
{ value: 'inactive', label: t('common.inactive') }
|
||||||
])
|
])
|
||||||
|
|
||||||
const subscriptionTypeOptions = computed(() => [
|
const subscriptionTypeOptions = computed(() => {
|
||||||
{ value: 'standard', label: t('admin.groups.subscription.standard') },
|
// 简单模式下只显示订阅模式(配额模式)
|
||||||
{ value: 'subscription', label: t('admin.groups.subscription.subscription') }
|
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<Group[]>([])
|
const groups = ref<Group[]>([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@@ -732,7 +804,7 @@ const closeCreateModal = () => {
|
|||||||
createForm.platform = 'anthropic'
|
createForm.platform = 'anthropic'
|
||||||
createForm.rate_multiplier = 1.0
|
createForm.rate_multiplier = 1.0
|
||||||
createForm.is_exclusive = false
|
createForm.is_exclusive = false
|
||||||
createForm.subscription_type = 'standard'
|
createForm.subscription_type = appStore.simpleMode ? 'subscription' : 'standard'
|
||||||
createForm.daily_limit_usd = null
|
createForm.daily_limit_usd = null
|
||||||
createForm.weekly_limit_usd = null
|
createForm.weekly_limit_usd = null
|
||||||
createForm.monthly_limit_usd = null
|
createForm.monthly_limit_usd = null
|
||||||
@@ -823,5 +895,9 @@ watch(
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadGroups()
|
loadGroups()
|
||||||
|
// 简单模式下默认使用订阅模式
|
||||||
|
if (appStore.simpleMode) {
|
||||||
|
createForm.subscription_type = 'subscription'
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -153,6 +153,58 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Usage Mode Settings -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700">
|
||||||
|
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||||
|
{{ t('admin.settings.usageMode.title') }}
|
||||||
|
</h2>
|
||||||
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
{{ t('admin.settings.usageMode.description') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-5 p-6">
|
||||||
|
<!-- Simple Mode Toggle -->
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<label class="font-medium text-gray-900 dark:text-white">
|
||||||
|
{{ t('admin.settings.usageMode.simpleMode') }}
|
||||||
|
</label>
|
||||||
|
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
{{ t('admin.settings.usageMode.simpleModeHint') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Toggle
|
||||||
|
:model-value="form.simple_mode"
|
||||||
|
@update:model-value="onSimpleModeToggle"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Warning when simple mode is enabled -->
|
||||||
|
<div
|
||||||
|
v-if="form.simple_mode"
|
||||||
|
class="rounded-lg border border-amber-200 bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-900/20"
|
||||||
|
>
|
||||||
|
<div class="flex items-start">
|
||||||
|
<svg
|
||||||
|
class="mt-0.5 h-5 w-5 flex-shrink-0 text-amber-500"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<p class="ml-3 text-sm text-amber-700 dark:text-amber-300">
|
||||||
|
{{ t('admin.settings.usageMode.simpleModeWarning') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Registration Settings -->
|
<!-- Registration Settings -->
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700">
|
<div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700">
|
||||||
@@ -706,6 +758,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Simple Mode Confirmation Dialog -->
|
||||||
|
<ConfirmDialog
|
||||||
|
:show="showSimpleModeConfirm"
|
||||||
|
:title="t('admin.settings.usageMode.confirmTitle')"
|
||||||
|
:message="pendingSimpleModeValue
|
||||||
|
? t('admin.settings.usageMode.confirmEnableMessage')
|
||||||
|
: t('admin.settings.usageMode.confirmDisableMessage')"
|
||||||
|
:confirm-text="t('common.confirm')"
|
||||||
|
:cancel-text="t('common.cancel')"
|
||||||
|
@confirm="confirmSimpleModeChange"
|
||||||
|
@cancel="cancelSimpleModeChange"
|
||||||
|
/>
|
||||||
</AppLayout>
|
</AppLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -716,6 +781,7 @@ import { adminAPI } from '@/api'
|
|||||||
import type { SystemSettings } from '@/api/admin/settings'
|
import type { SystemSettings } from '@/api/admin/settings'
|
||||||
import AppLayout from '@/components/layout/AppLayout.vue'
|
import AppLayout from '@/components/layout/AppLayout.vue'
|
||||||
import Toggle from '@/components/common/Toggle.vue'
|
import Toggle from '@/components/common/Toggle.vue'
|
||||||
|
import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
|
||||||
import { useAppStore } from '@/stores'
|
import { useAppStore } from '@/stores'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
@@ -728,6 +794,10 @@ const sendingTestEmail = ref(false)
|
|||||||
const testEmailAddress = ref('')
|
const testEmailAddress = ref('')
|
||||||
const logoError = ref('')
|
const logoError = ref('')
|
||||||
|
|
||||||
|
// Simple mode confirmation dialog
|
||||||
|
const showSimpleModeConfirm = ref(false)
|
||||||
|
const pendingSimpleModeValue = ref(false)
|
||||||
|
|
||||||
// Admin API Key 状态
|
// Admin API Key 状态
|
||||||
const adminApiKeyLoading = ref(true)
|
const adminApiKeyLoading = ref(true)
|
||||||
const adminApiKeyExists = ref(false)
|
const adminApiKeyExists = ref(false)
|
||||||
@@ -756,7 +826,9 @@ const form = reactive<SystemSettings>({
|
|||||||
// Cloudflare Turnstile
|
// Cloudflare Turnstile
|
||||||
turnstile_enabled: false,
|
turnstile_enabled: false,
|
||||||
turnstile_site_key: '',
|
turnstile_site_key: '',
|
||||||
turnstile_secret_key: ''
|
turnstile_secret_key: '',
|
||||||
|
// Usage mode
|
||||||
|
simple_mode: false
|
||||||
})
|
})
|
||||||
|
|
||||||
function handleLogoUpload(event: Event) {
|
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() {
|
async function testSmtpConnection() {
|
||||||
testingSmtp.value = true
|
testingSmtp.value = true
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user