mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-09 10:57:03 +08:00
feat(wallet): 钱包系统替代配额系统,新增支付与退款机制
- 新增钱包余额管理、充值、扣费、退款完整流程 - 新增支付网关抽象层(支持手动/支付宝/微信) - 用量计费从配额系统迁移到钱包余额扣费 - 新增管理员钱包管理与支付订单管理页面 - 新增用户钱包中心页面 - 移除独立 Key 锁定机制,统一由钱包余额控制 - 新增相关 API 路由、序列化器与数据库迁移 - 新增钱包、支付、退款相关测试
This commit is contained in:
@@ -18,19 +18,19 @@
|
||||
for="default-quota"
|
||||
class="block text-sm font-medium"
|
||||
>
|
||||
默认用户配额(美元)
|
||||
默认用户初始赠款(美元)
|
||||
</Label>
|
||||
<Input
|
||||
id="default-quota"
|
||||
:model-value="defaultUserQuotaUsd"
|
||||
:model-value="defaultUserInitialGiftUsd"
|
||||
type="number"
|
||||
step="0.01"
|
||||
placeholder="10.00"
|
||||
class="mt-1"
|
||||
@update:model-value="$emit('update:defaultUserQuotaUsd', Number($event))"
|
||||
@update:model-value="$emit('update:defaultUserInitialGiftUsd', Number($event))"
|
||||
/>
|
||||
<p class="mt-1 text-xs text-muted-foreground">
|
||||
新用户注册时的默认配额
|
||||
新用户注册时的默认初始赠款
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -128,7 +128,7 @@ import Checkbox from '@/components/ui/checkbox.vue'
|
||||
import { CardSection } from '@/components/layout'
|
||||
|
||||
defineProps<{
|
||||
defaultUserQuotaUsd: number
|
||||
defaultUserInitialGiftUsd: number
|
||||
rateLimitPerMinute: number
|
||||
enableRegistration: boolean
|
||||
autoDeleteExpiredKeys: boolean
|
||||
@@ -139,7 +139,7 @@ defineProps<{
|
||||
|
||||
defineEmits<{
|
||||
save: []
|
||||
'update:defaultUserQuotaUsd': [value: number]
|
||||
'update:defaultUserInitialGiftUsd': [value: number]
|
||||
'update:rateLimitPerMinute': [value: number]
|
||||
'update:enableRegistration': [value: boolean]
|
||||
'update:autoDeleteExpiredKeys': [value: boolean]
|
||||
|
||||
@@ -14,9 +14,7 @@
|
||||
? 'border-primary/30 bg-primary/[0.02] shadow-sm shadow-primary/5'
|
||||
: 'border-border bg-card hover:border-border/80'"
|
||||
>
|
||||
<!-- 主行 -->
|
||||
<div class="flex items-center gap-4 p-4">
|
||||
<!-- 左侧:开关 -->
|
||||
<div class="shrink-0">
|
||||
<Switch
|
||||
:id="`enable-${task.id}`"
|
||||
@@ -25,7 +23,6 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 中间:图标、标题、描述 -->
|
||||
<div class="flex items-center gap-3 flex-1 min-w-0">
|
||||
<div
|
||||
class="w-9 h-9 rounded-lg flex items-center justify-center shrink-0 transition-colors duration-300"
|
||||
@@ -48,7 +45,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:时间选择器 + 保存按钮 -->
|
||||
<div
|
||||
v-if="task.enabled && task.hasTimeConfig"
|
||||
class="flex items-center gap-2 shrink-0"
|
||||
@@ -118,123 +114,6 @@
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 额外配置区域(仅用户配额重置任务有) -->
|
||||
<div
|
||||
v-if="task.id === 'user-quota-reset' && task.enabled"
|
||||
class="px-4 pb-4 pt-0"
|
||||
>
|
||||
<div class="flex items-center gap-3 p-3 rounded-lg bg-muted/30 border border-border/50">
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<span class="text-muted-foreground">重置周期</span>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="text-muted-foreground">每</span>
|
||||
<Input
|
||||
:model-value="quotaResetIntervalDays"
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
class="w-14 h-7 text-xs text-center px-2"
|
||||
@update:model-value="$emit('update:quotaResetIntervalDays', Number($event))"
|
||||
/>
|
||||
<span class="text-muted-foreground">天</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-[11px] text-muted-foreground mt-2 ml-1">
|
||||
滚动计算:距离上次成功执行满 N 天后再次执行
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 独立密钥额度重置额外配置 -->
|
||||
<div
|
||||
v-if="task.id === 'standalone-key-quota-reset' && task.enabled"
|
||||
class="px-4 pb-4 pt-0 space-y-3"
|
||||
>
|
||||
<!-- 重置周期 -->
|
||||
<div class="flex items-center gap-3 p-3 rounded-lg bg-muted/30 border border-border/50">
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<span class="text-muted-foreground">重置周期</span>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="text-muted-foreground">每</span>
|
||||
<Input
|
||||
:model-value="standaloneKeyResetIntervalDays"
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
class="w-14 h-7 text-xs text-center px-2"
|
||||
@update:model-value="$emit('update:standaloneKeyResetIntervalDays', Number($event))"
|
||||
/>
|
||||
<span class="text-muted-foreground">天</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 重置范围 -->
|
||||
<div class="p-3 rounded-lg bg-muted/30 border border-border/50 space-y-3">
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<span class="text-muted-foreground">重置范围</span>
|
||||
<Select
|
||||
:model-value="standaloneKeyResetMode"
|
||||
@update:model-value="$emit('update:standaloneKeyResetMode', $event)"
|
||||
>
|
||||
<SelectTrigger class="w-32 h-7 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">
|
||||
全部独立密钥
|
||||
</SelectItem>
|
||||
<SelectItem value="selected">
|
||||
指定密钥
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<!-- 密钥多选列表 -->
|
||||
<div
|
||||
v-if="standaloneKeyResetMode === 'selected'"
|
||||
class="space-y-2"
|
||||
>
|
||||
<div class="text-xs text-muted-foreground">
|
||||
选择需要重置的密钥:
|
||||
</div>
|
||||
<div
|
||||
v-if="standaloneKeys.length === 0"
|
||||
class="text-xs text-muted-foreground/60 py-2"
|
||||
>
|
||||
暂无独立密钥
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="max-h-48 overflow-y-auto space-y-1"
|
||||
>
|
||||
<label
|
||||
v-for="key in standaloneKeys"
|
||||
:key="key.id"
|
||||
class="flex items-center gap-2 p-2 rounded hover:bg-muted/50 cursor-pointer text-xs"
|
||||
>
|
||||
<Checkbox
|
||||
:model-value="standaloneKeyResetKeyIds.includes(key.id)"
|
||||
@update:model-value="$emit('toggleStandaloneKeyResetKeyId', key.id)"
|
||||
/>
|
||||
<span class="truncate">{{ key.name || key.key_display || 'sk-****' }}</span>
|
||||
<span
|
||||
v-if="key.current_balance_usd != null"
|
||||
class="text-muted-foreground ml-auto shrink-0"
|
||||
>
|
||||
${{ key.current_balance_usd.toFixed(2) }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-[11px] text-muted-foreground ml-1">
|
||||
滚动计算:距离上次成功执行满 N 天后再次执行
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@@ -244,9 +123,7 @@
|
||||
<script setup lang="ts">
|
||||
import { Clock, Check, Loader2, X } from 'lucide-vue-next'
|
||||
import Button from '@/components/ui/button.vue'
|
||||
import Input from '@/components/ui/input.vue'
|
||||
import Switch from '@/components/ui/switch.vue'
|
||||
import Checkbox from '@/components/ui/checkbox.vue'
|
||||
import Select from '@/components/ui/select.vue'
|
||||
import SelectTrigger from '@/components/ui/select-trigger.vue'
|
||||
import SelectValue from '@/components/ui/select-value.vue'
|
||||
@@ -272,26 +149,7 @@ interface ScheduledTask {
|
||||
onCancel: () => void
|
||||
}
|
||||
|
||||
interface StandaloneKeyOption {
|
||||
id: string
|
||||
name?: string
|
||||
key_display?: string
|
||||
current_balance_usd?: number | null
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
scheduledTasks: ScheduledTask[]
|
||||
quotaResetIntervalDays: number
|
||||
standaloneKeyResetIntervalDays: number
|
||||
standaloneKeyResetMode: string
|
||||
standaloneKeyResetKeyIds: string[]
|
||||
standaloneKeys: StandaloneKeyOption[]
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
'update:quotaResetIntervalDays': [value: number]
|
||||
'update:standaloneKeyResetIntervalDays': [value: number]
|
||||
'update:standaloneKeyResetMode': [value: string]
|
||||
'toggleStandaloneKeyResetKeyId': [keyId: string]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ref, computed, type Ref } from 'vue'
|
||||
import { CalendarCheck, RotateCcw, RefreshCw, KeyRound } from 'lucide-vue-next'
|
||||
import { CalendarCheck, RefreshCw } from 'lucide-vue-next'
|
||||
import { useToast } from '@/composables/useToast'
|
||||
import { adminApi } from '@/api/admin'
|
||||
import { log } from '@/utils/logger'
|
||||
@@ -9,25 +9,13 @@ export function useScheduledTasks(systemConfig: Ref<SystemConfig>) {
|
||||
const { success, error } = useToast()
|
||||
|
||||
const checkinConfigLoading = ref(false)
|
||||
const quotaResetConfigLoading = ref(false)
|
||||
const standaloneKeyResetConfigLoading = ref(false)
|
||||
|
||||
// 签到时间的原始值(用于回滚)
|
||||
const previousCheckinTime = ref('')
|
||||
// 用户配额重置时间的原始值
|
||||
const previousUserQuotaResetTime = ref('')
|
||||
const previousUserQuotaResetIntervalDays = ref(1)
|
||||
// 独立密钥额度重置的原始值
|
||||
const previousStandaloneKeyResetTime = ref('')
|
||||
const previousStandaloneKeyResetIntervalDays = ref(1)
|
||||
|
||||
// 初始化原始值(在配置加载完成后调用)
|
||||
function initPreviousValues() {
|
||||
previousCheckinTime.value = systemConfig.value.provider_checkin_time
|
||||
previousUserQuotaResetTime.value = systemConfig.value.user_quota_reset_time
|
||||
previousUserQuotaResetIntervalDays.value = systemConfig.value.user_quota_reset_interval_days
|
||||
previousStandaloneKeyResetTime.value = systemConfig.value.standalone_key_quota_reset_time
|
||||
previousStandaloneKeyResetIntervalDays.value = systemConfig.value.standalone_key_quota_reset_interval_days
|
||||
}
|
||||
|
||||
// 签到时间
|
||||
@@ -51,70 +39,6 @@ export function useScheduledTasks(systemConfig: Ref<SystemConfig>) {
|
||||
return systemConfig.value.provider_checkin_time !== previousCheckinTime.value
|
||||
})
|
||||
|
||||
// 用户配额重置时间
|
||||
const userQuotaResetHour = computed(() => {
|
||||
const time = systemConfig.value.user_quota_reset_time
|
||||
if (!time || !time.includes(':')) return '05'
|
||||
return time.split(':')[0]
|
||||
})
|
||||
|
||||
const userQuotaResetMinute = computed(() => {
|
||||
const time = systemConfig.value.user_quota_reset_time
|
||||
if (!time || !time.includes(':')) return '00'
|
||||
return time.split(':')[1]
|
||||
})
|
||||
|
||||
function updateUserQuotaResetTime(hour: string, minute: string) {
|
||||
systemConfig.value.user_quota_reset_time = `${hour}:${minute}`
|
||||
}
|
||||
|
||||
const hasUserQuotaResetTimeChanged = computed(() => {
|
||||
return systemConfig.value.user_quota_reset_time !== previousUserQuotaResetTime.value
|
||||
})
|
||||
|
||||
const hasUserQuotaResetIntervalChanged = computed(() => {
|
||||
return (
|
||||
systemConfig.value.user_quota_reset_interval_days !==
|
||||
previousUserQuotaResetIntervalDays.value
|
||||
)
|
||||
})
|
||||
|
||||
const hasQuotaResetConfigChanged = computed(() => {
|
||||
return hasUserQuotaResetTimeChanged.value || hasUserQuotaResetIntervalChanged.value
|
||||
})
|
||||
|
||||
// 独立密钥额度重置时间
|
||||
const standaloneKeyResetHour = computed(() => {
|
||||
const time = systemConfig.value.standalone_key_quota_reset_time
|
||||
if (!time || !time.includes(':')) return '05'
|
||||
return time.split(':')[0]
|
||||
})
|
||||
|
||||
const standaloneKeyResetMinute = computed(() => {
|
||||
const time = systemConfig.value.standalone_key_quota_reset_time
|
||||
if (!time || !time.includes(':')) return '00'
|
||||
return time.split(':')[1]
|
||||
})
|
||||
|
||||
function updateStandaloneKeyResetTime(hour: string, minute: string) {
|
||||
systemConfig.value.standalone_key_quota_reset_time = `${hour}:${minute}`
|
||||
}
|
||||
|
||||
const hasStandaloneKeyResetTimeChanged = computed(() => {
|
||||
return systemConfig.value.standalone_key_quota_reset_time !== previousStandaloneKeyResetTime.value
|
||||
})
|
||||
|
||||
const hasStandaloneKeyResetIntervalChanged = computed(() => {
|
||||
return (
|
||||
systemConfig.value.standalone_key_quota_reset_interval_days !==
|
||||
previousStandaloneKeyResetIntervalDays.value
|
||||
)
|
||||
})
|
||||
|
||||
const hasStandaloneKeyResetConfigChanged = computed(() => {
|
||||
return hasStandaloneKeyResetTimeChanged.value || hasStandaloneKeyResetIntervalChanged.value
|
||||
})
|
||||
|
||||
// Toggle handlers
|
||||
async function handleProviderCheckinToggle(enabled: boolean) {
|
||||
const previousValue = systemConfig.value.enable_provider_checkin
|
||||
@@ -133,23 +57,6 @@ export function useScheduledTasks(systemConfig: Ref<SystemConfig>) {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleUserQuotaResetToggle(enabled: boolean) {
|
||||
const previousValue = systemConfig.value.enable_user_quota_reset
|
||||
systemConfig.value.enable_user_quota_reset = enabled
|
||||
try {
|
||||
await adminApi.updateSystemConfig(
|
||||
'enable_user_quota_reset',
|
||||
enabled,
|
||||
'是否启用用户配额自动重置任务'
|
||||
)
|
||||
success(enabled ? '已启用用户配额自动重置' : '已禁用用户配额自动重置')
|
||||
} catch (err) {
|
||||
error('保存配置失败')
|
||||
log.error('保存用户配额自动重置配置失败:', err)
|
||||
systemConfig.value.enable_user_quota_reset = previousValue
|
||||
}
|
||||
}
|
||||
|
||||
async function handleOAuthTokenRefreshToggle(enabled: boolean) {
|
||||
const previousValue = systemConfig.value.enable_oauth_token_refresh
|
||||
systemConfig.value.enable_oauth_token_refresh = enabled
|
||||
@@ -167,38 +74,11 @@ export function useScheduledTasks(systemConfig: Ref<SystemConfig>) {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleStandaloneKeyResetToggle(enabled: boolean) {
|
||||
const previousValue = systemConfig.value.enable_standalone_key_quota_reset
|
||||
systemConfig.value.enable_standalone_key_quota_reset = enabled
|
||||
try {
|
||||
await adminApi.updateSystemConfig(
|
||||
'enable_standalone_key_quota_reset',
|
||||
enabled,
|
||||
'是否启用独立密钥额度自动重置任务'
|
||||
)
|
||||
success(enabled ? '已启用独立密钥额度自动重置' : '已禁用独立密钥额度自动重置')
|
||||
} catch (err) {
|
||||
error('保存配置失败')
|
||||
log.error('保存独立密钥额度自动重置配置失败:', err)
|
||||
systemConfig.value.enable_standalone_key_quota_reset = previousValue
|
||||
}
|
||||
}
|
||||
|
||||
// Cancel handlers
|
||||
function handleCheckinTimeCancel() {
|
||||
systemConfig.value.provider_checkin_time = previousCheckinTime.value
|
||||
}
|
||||
|
||||
function handleQuotaResetConfigCancel() {
|
||||
systemConfig.value.user_quota_reset_time = previousUserQuotaResetTime.value
|
||||
systemConfig.value.user_quota_reset_interval_days = previousUserQuotaResetIntervalDays.value
|
||||
}
|
||||
|
||||
function handleStandaloneKeyResetConfigCancel() {
|
||||
systemConfig.value.standalone_key_quota_reset_time = previousStandaloneKeyResetTime.value
|
||||
systemConfig.value.standalone_key_quota_reset_interval_days = previousStandaloneKeyResetIntervalDays.value
|
||||
}
|
||||
|
||||
// Save handlers
|
||||
async function handleCheckinTimeSave() {
|
||||
const newTime = systemConfig.value.provider_checkin_time
|
||||
@@ -224,171 +104,6 @@ export function useScheduledTasks(systemConfig: Ref<SystemConfig>) {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleQuotaResetConfigSave() {
|
||||
const configItems: Array<{
|
||||
key: string
|
||||
value: unknown
|
||||
description: string
|
||||
onSuccess: () => void
|
||||
}> = []
|
||||
|
||||
if (hasUserQuotaResetTimeChanged.value) {
|
||||
const newTime = systemConfig.value.user_quota_reset_time
|
||||
if (!newTime || !/^\d{2}:\d{2}$/.test(newTime)) {
|
||||
error('请输入有效的时间格式 (HH:MM)')
|
||||
return
|
||||
}
|
||||
configItems.push({
|
||||
key: 'user_quota_reset_time',
|
||||
value: newTime,
|
||||
description: '用户配额自动重置执行时间(HH:MM 格式)',
|
||||
onSuccess: () => {
|
||||
previousUserQuotaResetTime.value = newTime
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (hasUserQuotaResetIntervalChanged.value) {
|
||||
let intervalDays = Number(systemConfig.value.user_quota_reset_interval_days)
|
||||
if (!Number.isFinite(intervalDays) || intervalDays < 1) intervalDays = 1
|
||||
intervalDays = Math.trunc(intervalDays)
|
||||
|
||||
systemConfig.value.user_quota_reset_interval_days = intervalDays
|
||||
|
||||
configItems.push({
|
||||
key: 'user_quota_reset_interval_days',
|
||||
value: intervalDays,
|
||||
description: '用户配额重置周期(天数),滚动计算',
|
||||
onSuccess: () => {
|
||||
previousUserQuotaResetIntervalDays.value = intervalDays
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (configItems.length === 0) return
|
||||
|
||||
quotaResetConfigLoading.value = true
|
||||
const failedKeys: string[] = []
|
||||
|
||||
try {
|
||||
for (const item of configItems) {
|
||||
try {
|
||||
await adminApi.updateSystemConfig(item.key, item.value, item.description)
|
||||
item.onSuccess()
|
||||
} catch (err) {
|
||||
failedKeys.push(item.key)
|
||||
log.error(`保存配额重置配置失败: ${item.key}`, err)
|
||||
}
|
||||
}
|
||||
|
||||
if (failedKeys.length > 0) {
|
||||
error(`部分配置保存失败: ${failedKeys.join(', ')}`)
|
||||
return
|
||||
}
|
||||
|
||||
success('配额重置配置已保存')
|
||||
} finally {
|
||||
quotaResetConfigLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleStandaloneKeyResetConfigSave() {
|
||||
const configItems: Array<{
|
||||
key: string
|
||||
value: unknown
|
||||
description: string
|
||||
onSuccess: () => void
|
||||
}> = []
|
||||
|
||||
if (hasStandaloneKeyResetTimeChanged.value) {
|
||||
const newTime = systemConfig.value.standalone_key_quota_reset_time
|
||||
if (!newTime || !/^\d{2}:\d{2}$/.test(newTime)) {
|
||||
error('请输入有效的时间格式 (HH:MM)')
|
||||
return
|
||||
}
|
||||
configItems.push({
|
||||
key: 'standalone_key_quota_reset_time',
|
||||
value: newTime,
|
||||
description: '独立密钥额度自动重置执行时间(HH:MM 格式)',
|
||||
onSuccess: () => {
|
||||
previousStandaloneKeyResetTime.value = newTime
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (hasStandaloneKeyResetIntervalChanged.value) {
|
||||
let intervalDays = Number(systemConfig.value.standalone_key_quota_reset_interval_days)
|
||||
if (!Number.isFinite(intervalDays) || intervalDays < 1) intervalDays = 1
|
||||
intervalDays = Math.trunc(intervalDays)
|
||||
|
||||
systemConfig.value.standalone_key_quota_reset_interval_days = intervalDays
|
||||
|
||||
configItems.push({
|
||||
key: 'standalone_key_quota_reset_interval_days',
|
||||
value: intervalDays,
|
||||
description: '独立密钥额度重置周期(天数),滚动计算',
|
||||
onSuccess: () => {
|
||||
previousStandaloneKeyResetIntervalDays.value = intervalDays
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (configItems.length === 0) return
|
||||
|
||||
standaloneKeyResetConfigLoading.value = true
|
||||
const failedKeys: string[] = []
|
||||
|
||||
try {
|
||||
for (const item of configItems) {
|
||||
try {
|
||||
await adminApi.updateSystemConfig(item.key, item.value, item.description)
|
||||
item.onSuccess()
|
||||
} catch (err) {
|
||||
failedKeys.push(item.key)
|
||||
log.error(`保存独立密钥额度重置配置失败: ${item.key}`, err)
|
||||
}
|
||||
}
|
||||
|
||||
if (failedKeys.length > 0) {
|
||||
error(`部分配置保存失败: ${failedKeys.join(', ')}`)
|
||||
return
|
||||
}
|
||||
|
||||
success('独立密钥额度重置配置已保存')
|
||||
} finally {
|
||||
standaloneKeyResetConfigLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 保存独立密钥额度重置模式和选中密钥
|
||||
async function saveStandaloneKeyResetMode(mode: string) {
|
||||
try {
|
||||
await adminApi.updateSystemConfig(
|
||||
'standalone_key_quota_reset_mode',
|
||||
mode,
|
||||
'独立密钥额度重置模式'
|
||||
)
|
||||
success('重置模式已保存')
|
||||
} catch (err) {
|
||||
error('保存重置模式失败')
|
||||
log.error('保存独立密钥额度重置模式失败:', err)
|
||||
}
|
||||
}
|
||||
|
||||
async function saveStandaloneKeyResetKeyIds(keyIds: string[]) {
|
||||
try {
|
||||
await adminApi.updateSystemConfig(
|
||||
'standalone_key_quota_reset_key_ids',
|
||||
keyIds,
|
||||
'独立密钥额度重置指定的密钥 ID 列表'
|
||||
)
|
||||
success('已保存选中密钥')
|
||||
} catch (err) {
|
||||
error('保存选中密钥失败')
|
||||
log.error('保存独立密钥额度重置密钥列表失败:', err)
|
||||
}
|
||||
}
|
||||
|
||||
// 定时任务配置列表
|
||||
const scheduledTasks = computed(() => [
|
||||
{
|
||||
@@ -407,22 +122,6 @@ export function useScheduledTasks(systemConfig: Ref<SystemConfig>) {
|
||||
onSave: handleCheckinTimeSave,
|
||||
onCancel: handleCheckinTimeCancel,
|
||||
},
|
||||
{
|
||||
id: 'user-quota-reset',
|
||||
icon: RotateCcw,
|
||||
title: '用户配额自动重置',
|
||||
description: '定时将用户已使用配额重置为零',
|
||||
enabled: systemConfig.value.enable_user_quota_reset,
|
||||
hasTimeConfig: true,
|
||||
hour: userQuotaResetHour.value,
|
||||
minute: userQuotaResetMinute.value,
|
||||
updateTime: updateUserQuotaResetTime,
|
||||
hasChanges: hasQuotaResetConfigChanged.value,
|
||||
loading: quotaResetConfigLoading.value,
|
||||
onToggle: handleUserQuotaResetToggle,
|
||||
onSave: handleQuotaResetConfigSave,
|
||||
onCancel: handleQuotaResetConfigCancel,
|
||||
},
|
||||
{
|
||||
id: 'oauth-token-refresh',
|
||||
icon: RefreshCw,
|
||||
@@ -439,31 +138,11 @@ export function useScheduledTasks(systemConfig: Ref<SystemConfig>) {
|
||||
onSave: () => {},
|
||||
onCancel: () => {},
|
||||
},
|
||||
{
|
||||
id: 'standalone-key-quota-reset',
|
||||
icon: KeyRound,
|
||||
title: '独立密钥额度自动重置',
|
||||
description: '定时将独立密钥已使用额度重置为零',
|
||||
enabled: systemConfig.value.enable_standalone_key_quota_reset,
|
||||
hasTimeConfig: true,
|
||||
hour: standaloneKeyResetHour.value,
|
||||
minute: standaloneKeyResetMinute.value,
|
||||
updateTime: updateStandaloneKeyResetTime,
|
||||
hasChanges: hasStandaloneKeyResetConfigChanged.value,
|
||||
loading: standaloneKeyResetConfigLoading.value,
|
||||
onToggle: handleStandaloneKeyResetToggle,
|
||||
onSave: handleStandaloneKeyResetConfigSave,
|
||||
onCancel: handleStandaloneKeyResetConfigCancel,
|
||||
},
|
||||
])
|
||||
|
||||
return {
|
||||
checkinConfigLoading,
|
||||
quotaResetConfigLoading,
|
||||
standaloneKeyResetConfigLoading,
|
||||
scheduledTasks,
|
||||
initPreviousValues,
|
||||
saveStandaloneKeyResetMode,
|
||||
saveStandaloneKeyResetKeyIds,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export interface SystemConfig {
|
||||
// 网络代理
|
||||
system_proxy_node_id: string | null
|
||||
// 基础配置
|
||||
default_user_quota_usd: number
|
||||
default_user_initial_gift_usd: number
|
||||
rate_limit_per_minute: number
|
||||
enable_registration: boolean
|
||||
// 独立余额 Key 过期管理
|
||||
@@ -34,16 +34,7 @@ export interface SystemConfig {
|
||||
// 定时任务
|
||||
enable_provider_checkin: boolean
|
||||
provider_checkin_time: string
|
||||
enable_user_quota_reset: boolean
|
||||
user_quota_reset_time: string
|
||||
user_quota_reset_interval_days: number
|
||||
enable_oauth_token_refresh: boolean
|
||||
// 独立密钥额度重置
|
||||
enable_standalone_key_quota_reset: boolean
|
||||
standalone_key_quota_reset_time: string
|
||||
standalone_key_quota_reset_interval_days: number
|
||||
standalone_key_quota_reset_mode: string
|
||||
standalone_key_quota_reset_key_ids: string[]
|
||||
}
|
||||
|
||||
const CONFIG_KEYS = [
|
||||
@@ -53,7 +44,7 @@ const CONFIG_KEYS = [
|
||||
// 网络代理
|
||||
'system_proxy_node_id',
|
||||
// 基础配置
|
||||
'default_user_quota_usd',
|
||||
'default_user_initial_gift_usd',
|
||||
'rate_limit_per_minute',
|
||||
'enable_registration',
|
||||
// 独立余额 Key 过期管理
|
||||
@@ -76,16 +67,7 @@ const CONFIG_KEYS = [
|
||||
// 定时任务
|
||||
'enable_provider_checkin',
|
||||
'provider_checkin_time',
|
||||
'enable_user_quota_reset',
|
||||
'user_quota_reset_time',
|
||||
'user_quota_reset_interval_days',
|
||||
'enable_oauth_token_refresh',
|
||||
// 独立密钥额度重置
|
||||
'enable_standalone_key_quota_reset',
|
||||
'standalone_key_quota_reset_time',
|
||||
'standalone_key_quota_reset_interval_days',
|
||||
'standalone_key_quota_reset_mode',
|
||||
'standalone_key_quota_reset_key_ids',
|
||||
]
|
||||
|
||||
function createDefaultConfig(): SystemConfig {
|
||||
@@ -96,7 +78,7 @@ function createDefaultConfig(): SystemConfig {
|
||||
// 网络代理
|
||||
system_proxy_node_id: null,
|
||||
// 基础配置
|
||||
default_user_quota_usd: 10.0,
|
||||
default_user_initial_gift_usd: 10.0,
|
||||
rate_limit_per_minute: 0,
|
||||
enable_registration: false,
|
||||
// 独立余额 Key 过期管理
|
||||
@@ -119,16 +101,7 @@ function createDefaultConfig(): SystemConfig {
|
||||
// 定时任务
|
||||
enable_provider_checkin: true,
|
||||
provider_checkin_time: '01:05',
|
||||
enable_user_quota_reset: false,
|
||||
user_quota_reset_time: '05:00',
|
||||
user_quota_reset_interval_days: 1,
|
||||
enable_oauth_token_refresh: true,
|
||||
// 独立密钥额度重置
|
||||
enable_standalone_key_quota_reset: false,
|
||||
standalone_key_quota_reset_time: '05:00',
|
||||
standalone_key_quota_reset_interval_days: 1,
|
||||
standalone_key_quota_reset_mode: 'all',
|
||||
standalone_key_quota_reset_key_ids: [],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +137,7 @@ export function useSystemConfig() {
|
||||
const hasBasicConfigChanges = computed(() => {
|
||||
if (!originalConfig.value) return false
|
||||
return (
|
||||
systemConfig.value.default_user_quota_usd !== originalConfig.value.default_user_quota_usd ||
|
||||
systemConfig.value.default_user_initial_gift_usd !== originalConfig.value.default_user_initial_gift_usd ||
|
||||
systemConfig.value.rate_limit_per_minute !== originalConfig.value.rate_limit_per_minute ||
|
||||
systemConfig.value.enable_registration !== originalConfig.value.enable_registration ||
|
||||
systemConfig.value.auto_delete_expired_keys !== originalConfig.value.auto_delete_expired_keys ||
|
||||
@@ -234,7 +207,7 @@ export function useSystemConfig() {
|
||||
; (systemConfig.value as Record<string, unknown>)[key] = response.value
|
||||
}
|
||||
} catch {
|
||||
// 配置不存在时使用默认值,无需处理
|
||||
// 单个配置项加载失败时忽略,使用默认值
|
||||
}
|
||||
}
|
||||
originalConfig.value = JSON.parse(JSON.stringify(systemConfig.value))
|
||||
@@ -309,9 +282,9 @@ export function useSystemConfig() {
|
||||
try {
|
||||
const configItems = [
|
||||
{
|
||||
key: 'default_user_quota_usd',
|
||||
value: systemConfig.value.default_user_quota_usd,
|
||||
description: '默认用户配额(美元)',
|
||||
key: 'default_user_initial_gift_usd',
|
||||
value: systemConfig.value.default_user_initial_gift_usd,
|
||||
description: '默认用户初始赠款(美元)',
|
||||
},
|
||||
{
|
||||
key: 'rate_limit_per_minute',
|
||||
@@ -341,7 +314,7 @@ export function useSystemConfig() {
|
||||
)
|
||||
)
|
||||
if (originalConfig.value) {
|
||||
originalConfig.value.default_user_quota_usd = systemConfig.value.default_user_quota_usd
|
||||
originalConfig.value.default_user_initial_gift_usd = systemConfig.value.default_user_initial_gift_usd
|
||||
originalConfig.value.rate_limit_per_minute = systemConfig.value.rate_limit_per_minute
|
||||
originalConfig.value.enable_registration = systemConfig.value.enable_registration
|
||||
originalConfig.value.auto_delete_expired_keys =
|
||||
|
||||
Reference in New Issue
Block a user