refactor: M5 useQuotaNotifyState composable + H14 Vue file splits

M5: New composable frontend/src/composables/useQuotaNotifyState.ts
  - Replaces 9 individual refs in both Create/Edit modals with reactive state
  - Provides loadFromExtra/writeToExtra/reset helpers
  - Eliminates ~120 lines of duplicated code across the two modals

H14: Vue file length violations fixed
  - AdminPaymentPlansView.vue: 325 → 183 lines (extracted PlanEditDialog.vue)
  - QuotaLimitCard.vue: 327 → 268 lines (extracted QuotaDimensionRow.vue)
  - PlanEditDialog.vue: 181 lines (new, plan create/edit form)
  - QuotaDimensionRow.vue: 108 lines (new, single quota dimension row)
This commit is contained in:
erio
2026-04-13 22:35:24 +08:00
parent 594f0d17d1
commit 1b7c295199
8 changed files with 565 additions and 464 deletions

View File

@@ -1493,15 +1493,15 @@
:dailyLimit="editQuotaDailyLimit"
:weeklyLimit="editQuotaWeeklyLimit"
:quotaNotifyGlobalEnabled="quotaNotifyGlobalEnabled"
:quotaNotifyDailyEnabled="quotaNotifyDailyEnabled"
:quotaNotifyDailyThreshold="quotaNotifyDailyThreshold"
:quotaNotifyDailyThresholdType="quotaNotifyDailyThresholdType"
:quotaNotifyWeeklyEnabled="quotaNotifyWeeklyEnabled"
:quotaNotifyWeeklyThreshold="quotaNotifyWeeklyThreshold"
:quotaNotifyWeeklyThresholdType="quotaNotifyWeeklyThresholdType"
:quotaNotifyTotalEnabled="quotaNotifyTotalEnabled"
:quotaNotifyTotalThreshold="quotaNotifyTotalThreshold"
:quotaNotifyTotalThresholdType="quotaNotifyTotalThresholdType"
:quotaNotifyDailyEnabled="quotaNotifyState.daily.enabled"
:quotaNotifyDailyThreshold="quotaNotifyState.daily.threshold"
:quotaNotifyDailyThresholdType="quotaNotifyState.daily.thresholdType"
:quotaNotifyWeeklyEnabled="quotaNotifyState.weekly.enabled"
:quotaNotifyWeeklyThreshold="quotaNotifyState.weekly.threshold"
:quotaNotifyWeeklyThresholdType="quotaNotifyState.weekly.thresholdType"
:quotaNotifyTotalEnabled="quotaNotifyState.total.enabled"
:quotaNotifyTotalThreshold="quotaNotifyState.total.threshold"
:quotaNotifyTotalThresholdType="quotaNotifyState.total.thresholdType"
:dailyResetMode="editDailyResetMode"
:dailyResetHour="editDailyResetHour"
:weeklyResetMode="editWeeklyResetMode"
@@ -1511,15 +1511,15 @@
@update:totalLimit="editQuotaLimit = $event"
@update:dailyLimit="editQuotaDailyLimit = $event"
@update:weeklyLimit="editQuotaWeeklyLimit = $event"
@update:quotaNotifyDailyEnabled="quotaNotifyDailyEnabled = $event"
@update:quotaNotifyDailyThreshold="quotaNotifyDailyThreshold = $event"
@update:quotaNotifyDailyThresholdType="quotaNotifyDailyThresholdType = $event"
@update:quotaNotifyWeeklyEnabled="quotaNotifyWeeklyEnabled = $event"
@update:quotaNotifyWeeklyThreshold="quotaNotifyWeeklyThreshold = $event"
@update:quotaNotifyWeeklyThresholdType="quotaNotifyWeeklyThresholdType = $event"
@update:quotaNotifyTotalEnabled="quotaNotifyTotalEnabled = $event"
@update:quotaNotifyTotalThreshold="quotaNotifyTotalThreshold = $event"
@update:quotaNotifyTotalThresholdType="quotaNotifyTotalThresholdType = $event"
@update:quotaNotifyDailyEnabled="quotaNotifyState.daily.enabled = $event"
@update:quotaNotifyDailyThreshold="quotaNotifyState.daily.threshold = $event"
@update:quotaNotifyDailyThresholdType="quotaNotifyState.daily.thresholdType = $event"
@update:quotaNotifyWeeklyEnabled="quotaNotifyState.weekly.enabled = $event"
@update:quotaNotifyWeeklyThreshold="quotaNotifyState.weekly.threshold = $event"
@update:quotaNotifyWeeklyThresholdType="quotaNotifyState.weekly.thresholdType = $event"
@update:quotaNotifyTotalEnabled="quotaNotifyState.total.enabled = $event"
@update:quotaNotifyTotalThreshold="quotaNotifyState.total.threshold = $event"
@update:quotaNotifyTotalThresholdType="quotaNotifyState.total.thresholdType = $event"
@update:dailyResetMode="editDailyResetMode = $event"
@update:dailyResetHour="editDailyResetHour = $event"
@update:weeklyResetMode="editWeeklyResetMode = $event"
@@ -1545,15 +1545,15 @@
:dailyLimit="editQuotaDailyLimit"
:weeklyLimit="editQuotaWeeklyLimit"
:quotaNotifyGlobalEnabled="quotaNotifyGlobalEnabled"
:quotaNotifyDailyEnabled="quotaNotifyDailyEnabled"
:quotaNotifyDailyThreshold="quotaNotifyDailyThreshold"
:quotaNotifyDailyThresholdType="quotaNotifyDailyThresholdType"
:quotaNotifyWeeklyEnabled="quotaNotifyWeeklyEnabled"
:quotaNotifyWeeklyThreshold="quotaNotifyWeeklyThreshold"
:quotaNotifyWeeklyThresholdType="quotaNotifyWeeklyThresholdType"
:quotaNotifyTotalEnabled="quotaNotifyTotalEnabled"
:quotaNotifyTotalThreshold="quotaNotifyTotalThreshold"
:quotaNotifyTotalThresholdType="quotaNotifyTotalThresholdType"
:quotaNotifyDailyEnabled="quotaNotifyState.daily.enabled"
:quotaNotifyDailyThreshold="quotaNotifyState.daily.threshold"
:quotaNotifyDailyThresholdType="quotaNotifyState.daily.thresholdType"
:quotaNotifyWeeklyEnabled="quotaNotifyState.weekly.enabled"
:quotaNotifyWeeklyThreshold="quotaNotifyState.weekly.threshold"
:quotaNotifyWeeklyThresholdType="quotaNotifyState.weekly.thresholdType"
:quotaNotifyTotalEnabled="quotaNotifyState.total.enabled"
:quotaNotifyTotalThreshold="quotaNotifyState.total.threshold"
:quotaNotifyTotalThresholdType="quotaNotifyState.total.thresholdType"
:dailyResetMode="editDailyResetMode"
:dailyResetHour="editDailyResetHour"
:weeklyResetMode="editWeeklyResetMode"
@@ -1563,15 +1563,15 @@
@update:totalLimit="editQuotaLimit = $event"
@update:dailyLimit="editQuotaDailyLimit = $event"
@update:weeklyLimit="editQuotaWeeklyLimit = $event"
@update:quotaNotifyDailyEnabled="quotaNotifyDailyEnabled = $event"
@update:quotaNotifyDailyThreshold="quotaNotifyDailyThreshold = $event"
@update:quotaNotifyDailyThresholdType="quotaNotifyDailyThresholdType = $event"
@update:quotaNotifyWeeklyEnabled="quotaNotifyWeeklyEnabled = $event"
@update:quotaNotifyWeeklyThreshold="quotaNotifyWeeklyThreshold = $event"
@update:quotaNotifyWeeklyThresholdType="quotaNotifyWeeklyThresholdType = $event"
@update:quotaNotifyTotalEnabled="quotaNotifyTotalEnabled = $event"
@update:quotaNotifyTotalThreshold="quotaNotifyTotalThreshold = $event"
@update:quotaNotifyTotalThresholdType="quotaNotifyTotalThresholdType = $event"
@update:quotaNotifyDailyEnabled="quotaNotifyState.daily.enabled = $event"
@update:quotaNotifyDailyThreshold="quotaNotifyState.daily.threshold = $event"
@update:quotaNotifyDailyThresholdType="quotaNotifyState.daily.thresholdType = $event"
@update:quotaNotifyWeeklyEnabled="quotaNotifyState.weekly.enabled = $event"
@update:quotaNotifyWeeklyThreshold="quotaNotifyState.weekly.threshold = $event"
@update:quotaNotifyWeeklyThresholdType="quotaNotifyState.weekly.thresholdType = $event"
@update:quotaNotifyTotalEnabled="quotaNotifyState.total.enabled = $event"
@update:quotaNotifyTotalThreshold="quotaNotifyState.total.threshold = $event"
@update:quotaNotifyTotalThresholdType="quotaNotifyState.total.thresholdType = $event"
@update:dailyResetMode="editDailyResetMode = $event"
@update:dailyResetHour="editDailyResetHour = $event"
@update:weeklyResetMode="editWeeklyResetMode = $event"
@@ -2903,6 +2903,7 @@ import {
} from '@/composables/useModelWhitelist'
import { useAuthStore } from '@/stores/auth'
import { adminAPI } from '@/api/admin'
import { useQuotaNotifyState } from '@/composables/useQuotaNotifyState'
import {
useAccountOAuth,
type AddMethod,
@@ -3076,25 +3077,19 @@ const codexCLIOnlyEnabled = ref(false)
const anthropicPassthroughEnabled = ref(false)
const webSearchEmulationMode = ref('default')
const webSearchGlobalEnabled = ref(false)
const quotaNotifyGlobalEnabled = ref(false)
const quotaNotifyDailyEnabled = ref<boolean | null>(null)
const quotaNotifyDailyThreshold = ref<number | null>(null)
const quotaNotifyDailyThresholdType = ref<string | null>(null)
const quotaNotifyWeeklyEnabled = ref<boolean | null>(null)
const quotaNotifyWeeklyThreshold = ref<number | null>(null)
const quotaNotifyWeeklyThresholdType = ref<string | null>(null)
const quotaNotifyTotalEnabled = ref<boolean | null>(null)
const quotaNotifyTotalThreshold = ref<number | null>(null)
const quotaNotifyTotalThresholdType = ref<string | null>(null)
const {
globalEnabled: quotaNotifyGlobalEnabled,
state: quotaNotifyState,
loadGlobalState: loadQuotaNotifyGlobal,
writeToExtra: writeQuotaNotifyToExtra,
} = useQuotaNotifyState()
// Load global feature states once
adminAPI.settings.getWebSearchEmulationConfig().then(cfg => {
webSearchGlobalEnabled.value = cfg?.enabled === true && (cfg?.providers?.length ?? 0) > 0
}).catch(() => { webSearchGlobalEnabled.value = false })
adminAPI.settings.getSettings().then(settings => {
quotaNotifyGlobalEnabled.value = settings.account_quota_notify_enabled === true
}).catch(() => { quotaNotifyGlobalEnabled.value = false })
loadQuotaNotifyGlobal()
const mixedScheduling = ref(false) // For antigravity accounts: enable mixed scheduling
const allowOverages = ref(false) // For antigravity accounts: enable AI Credits overages
const antigravityAccountType = ref<'oauth' | 'upstream'>('oauth') // For antigravity: oauth or upstream
@@ -4199,21 +4194,7 @@ const createAccountAndFinish = async (
quotaExtra.quota_reset_timezone = editResetTimezone.value || 'UTC'
}
// Quota notify config
if (quotaNotifyDailyEnabled.value) {
quotaExtra.quota_notify_daily_enabled = true
if (quotaNotifyDailyThreshold.value != null) quotaExtra.quota_notify_daily_threshold = quotaNotifyDailyThreshold.value
quotaExtra.quota_notify_daily_threshold_type = quotaNotifyDailyThresholdType.value || 'fixed'
}
if (quotaNotifyWeeklyEnabled.value) {
quotaExtra.quota_notify_weekly_enabled = true
if (quotaNotifyWeeklyThreshold.value != null) quotaExtra.quota_notify_weekly_threshold = quotaNotifyWeeklyThreshold.value
quotaExtra.quota_notify_weekly_threshold_type = quotaNotifyWeeklyThresholdType.value || 'fixed'
}
if (quotaNotifyTotalEnabled.value) {
quotaExtra.quota_notify_total_enabled = true
if (quotaNotifyTotalThreshold.value != null) quotaExtra.quota_notify_total_threshold = quotaNotifyTotalThreshold.value
quotaExtra.quota_notify_total_threshold_type = quotaNotifyTotalThresholdType.value || 'fixed'
}
writeQuotaNotifyToExtra(quotaExtra, 'create')
if (Object.keys(quotaExtra).length > 0) {
finalExtra = quotaExtra
}

View File

@@ -1191,15 +1191,15 @@
:weeklyResetHour="editWeeklyResetHour"
:resetTimezone="editResetTimezone"
:quotaNotifyGlobalEnabled="quotaNotifyGlobalEnabled"
:quotaNotifyDailyEnabled="editQuotaNotifyDailyEnabled"
:quotaNotifyDailyThreshold="editQuotaNotifyDailyThreshold"
:quotaNotifyDailyThresholdType="editQuotaNotifyDailyThresholdType"
:quotaNotifyWeeklyEnabled="editQuotaNotifyWeeklyEnabled"
:quotaNotifyWeeklyThreshold="editQuotaNotifyWeeklyThreshold"
:quotaNotifyWeeklyThresholdType="editQuotaNotifyWeeklyThresholdType"
:quotaNotifyTotalEnabled="editQuotaNotifyTotalEnabled"
:quotaNotifyTotalThreshold="editQuotaNotifyTotalThreshold"
:quotaNotifyTotalThresholdType="editQuotaNotifyTotalThresholdType"
:quotaNotifyDailyEnabled="quotaNotifyState.daily.enabled"
:quotaNotifyDailyThreshold="quotaNotifyState.daily.threshold"
:quotaNotifyDailyThresholdType="quotaNotifyState.daily.thresholdType"
:quotaNotifyWeeklyEnabled="quotaNotifyState.weekly.enabled"
:quotaNotifyWeeklyThreshold="quotaNotifyState.weekly.threshold"
:quotaNotifyWeeklyThresholdType="quotaNotifyState.weekly.thresholdType"
:quotaNotifyTotalEnabled="quotaNotifyState.total.enabled"
:quotaNotifyTotalThreshold="quotaNotifyState.total.threshold"
:quotaNotifyTotalThresholdType="quotaNotifyState.total.thresholdType"
@update:totalLimit="editQuotaLimit = $event"
@update:dailyLimit="editQuotaDailyLimit = $event"
@update:weeklyLimit="editQuotaWeeklyLimit = $event"
@@ -1209,15 +1209,15 @@
@update:weeklyResetDay="editWeeklyResetDay = $event"
@update:weeklyResetHour="editWeeklyResetHour = $event"
@update:resetTimezone="editResetTimezone = $event"
@update:quotaNotifyDailyEnabled="editQuotaNotifyDailyEnabled = $event"
@update:quotaNotifyDailyThreshold="editQuotaNotifyDailyThreshold = $event"
@update:quotaNotifyDailyThresholdType="editQuotaNotifyDailyThresholdType = $event"
@update:quotaNotifyWeeklyEnabled="editQuotaNotifyWeeklyEnabled = $event"
@update:quotaNotifyWeeklyThreshold="editQuotaNotifyWeeklyThreshold = $event"
@update:quotaNotifyWeeklyThresholdType="editQuotaNotifyWeeklyThresholdType = $event"
@update:quotaNotifyTotalEnabled="editQuotaNotifyTotalEnabled = $event"
@update:quotaNotifyTotalThreshold="editQuotaNotifyTotalThreshold = $event"
@update:quotaNotifyTotalThresholdType="editQuotaNotifyTotalThresholdType = $event"
@update:quotaNotifyDailyEnabled="quotaNotifyState.daily.enabled = $event"
@update:quotaNotifyDailyThreshold="quotaNotifyState.daily.threshold = $event"
@update:quotaNotifyDailyThresholdType="quotaNotifyState.daily.thresholdType = $event"
@update:quotaNotifyWeeklyEnabled="quotaNotifyState.weekly.enabled = $event"
@update:quotaNotifyWeeklyThreshold="quotaNotifyState.weekly.threshold = $event"
@update:quotaNotifyWeeklyThresholdType="quotaNotifyState.weekly.thresholdType = $event"
@update:quotaNotifyTotalEnabled="quotaNotifyState.total.enabled = $event"
@update:quotaNotifyTotalThreshold="quotaNotifyState.total.threshold = $event"
@update:quotaNotifyTotalThresholdType="quotaNotifyState.total.thresholdType = $event"
/>
</div>
<!-- 配额控制 ( Anthropic apikey/bedrock) -->
@@ -1242,15 +1242,15 @@
:weeklyResetHour="editWeeklyResetHour"
:resetTimezone="editResetTimezone"
:quotaNotifyGlobalEnabled="quotaNotifyGlobalEnabled"
:quotaNotifyDailyEnabled="editQuotaNotifyDailyEnabled"
:quotaNotifyDailyThreshold="editQuotaNotifyDailyThreshold"
:quotaNotifyDailyThresholdType="editQuotaNotifyDailyThresholdType"
:quotaNotifyWeeklyEnabled="editQuotaNotifyWeeklyEnabled"
:quotaNotifyWeeklyThreshold="editQuotaNotifyWeeklyThreshold"
:quotaNotifyWeeklyThresholdType="editQuotaNotifyWeeklyThresholdType"
:quotaNotifyTotalEnabled="editQuotaNotifyTotalEnabled"
:quotaNotifyTotalThreshold="editQuotaNotifyTotalThreshold"
:quotaNotifyTotalThresholdType="editQuotaNotifyTotalThresholdType"
:quotaNotifyDailyEnabled="quotaNotifyState.daily.enabled"
:quotaNotifyDailyThreshold="quotaNotifyState.daily.threshold"
:quotaNotifyDailyThresholdType="quotaNotifyState.daily.thresholdType"
:quotaNotifyWeeklyEnabled="quotaNotifyState.weekly.enabled"
:quotaNotifyWeeklyThreshold="quotaNotifyState.weekly.threshold"
:quotaNotifyWeeklyThresholdType="quotaNotifyState.weekly.thresholdType"
:quotaNotifyTotalEnabled="quotaNotifyState.total.enabled"
:quotaNotifyTotalThreshold="quotaNotifyState.total.threshold"
:quotaNotifyTotalThresholdType="quotaNotifyState.total.thresholdType"
@update:totalLimit="editQuotaLimit = $event"
@update:dailyLimit="editQuotaDailyLimit = $event"
@update:weeklyLimit="editQuotaWeeklyLimit = $event"
@@ -1260,15 +1260,15 @@
@update:weeklyResetDay="editWeeklyResetDay = $event"
@update:weeklyResetHour="editWeeklyResetHour = $event"
@update:resetTimezone="editResetTimezone = $event"
@update:quotaNotifyDailyEnabled="editQuotaNotifyDailyEnabled = $event"
@update:quotaNotifyDailyThreshold="editQuotaNotifyDailyThreshold = $event"
@update:quotaNotifyDailyThresholdType="editQuotaNotifyDailyThresholdType = $event"
@update:quotaNotifyWeeklyEnabled="editQuotaNotifyWeeklyEnabled = $event"
@update:quotaNotifyWeeklyThreshold="editQuotaNotifyWeeklyThreshold = $event"
@update:quotaNotifyWeeklyThresholdType="editQuotaNotifyWeeklyThresholdType = $event"
@update:quotaNotifyTotalEnabled="editQuotaNotifyTotalEnabled = $event"
@update:quotaNotifyTotalThreshold="editQuotaNotifyTotalThreshold = $event"
@update:quotaNotifyTotalThresholdType="editQuotaNotifyTotalThresholdType = $event"
@update:quotaNotifyDailyEnabled="quotaNotifyState.daily.enabled = $event"
@update:quotaNotifyDailyThreshold="quotaNotifyState.daily.threshold = $event"
@update:quotaNotifyDailyThresholdType="quotaNotifyState.daily.thresholdType = $event"
@update:quotaNotifyWeeklyEnabled="quotaNotifyState.weekly.enabled = $event"
@update:quotaNotifyWeeklyThreshold="quotaNotifyState.weekly.threshold = $event"
@update:quotaNotifyWeeklyThresholdType="quotaNotifyState.weekly.thresholdType = $event"
@update:quotaNotifyTotalEnabled="quotaNotifyState.total.enabled = $event"
@update:quotaNotifyTotalThreshold="quotaNotifyState.total.threshold = $event"
@update:quotaNotifyTotalThresholdType="quotaNotifyState.total.thresholdType = $event"
/>
</div>
@@ -1844,6 +1844,7 @@ import { useI18n } from 'vue-i18n'
import { useAppStore } from '@/stores/app'
import { useAuthStore } from '@/stores/auth'
import { adminAPI } from '@/api/admin'
import { useQuotaNotifyState } from '@/composables/useQuotaNotifyState'
import type { Account, Proxy, AdminGroup, CheckMixedChannelResponse } from '@/types'
import BaseDialog from '@/components/common/BaseDialog.vue'
import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
@@ -1993,16 +1994,21 @@ const codexCLIOnlyEnabled = ref(false)
const anthropicPassthroughEnabled = ref(false)
const webSearchEmulationMode = ref('default')
const webSearchGlobalEnabled = ref(false)
const quotaNotifyGlobalEnabled = ref(false)
const {
globalEnabled: quotaNotifyGlobalEnabled,
state: quotaNotifyState,
loadGlobalState: loadQuotaNotifyGlobal,
loadFromExtra: loadQuotaNotifyFromExtra,
writeToExtra: writeQuotaNotifyToExtra,
reset: resetQuotaNotify,
} = useQuotaNotifyState()
// Load global feature states once
adminAPI.settings.getWebSearchEmulationConfig().then(cfg => {
webSearchGlobalEnabled.value = cfg?.enabled === true && (cfg?.providers?.length ?? 0) > 0
}).catch(() => { webSearchGlobalEnabled.value = false })
adminAPI.settings.getSettings().then(settings => {
quotaNotifyGlobalEnabled.value = settings.account_quota_notify_enabled === true
}).catch(() => { quotaNotifyGlobalEnabled.value = false })
loadQuotaNotifyGlobal()
const editQuotaLimit = ref<number | null>(null)
const editQuotaDailyLimit = ref<number | null>(null)
const editQuotaWeeklyLimit = ref<number | null>(null)
@@ -2012,15 +2018,6 @@ const editWeeklyResetMode = ref<'rolling' | 'fixed' | null>(null)
const editWeeklyResetDay = ref<number | null>(null)
const editWeeklyResetHour = ref<number | null>(null)
const editResetTimezone = ref<string | null>(null)
const editQuotaNotifyDailyEnabled = ref<boolean | null>(null)
const editQuotaNotifyDailyThreshold = ref<number | null>(null)
const editQuotaNotifyDailyThresholdType = ref<string | null>(null)
const editQuotaNotifyWeeklyEnabled = ref<boolean | null>(null)
const editQuotaNotifyWeeklyThreshold = ref<number | null>(null)
const editQuotaNotifyWeeklyThresholdType = ref<string | null>(null)
const editQuotaNotifyTotalEnabled = ref<boolean | null>(null)
const editQuotaNotifyTotalThreshold = ref<number | null>(null)
const editQuotaNotifyTotalThresholdType = ref<string | null>(null)
const openAIWSModeOptions = computed(() => [
{ value: OPENAI_WS_MODE_OFF, label: t('admin.accounts.openai.wsModeOff') },
// TODO: ctx_pool 选项暂时隐藏,待测试完成后恢复
@@ -2229,15 +2226,7 @@ const syncFormFromAccount = (newAccount: Account | null) => {
editWeeklyResetHour.value = (extra?.quota_weekly_reset_hour as number) ?? null
editResetTimezone.value = (extra?.quota_reset_timezone as string) || null
// Load quota notify config
editQuotaNotifyDailyEnabled.value = (extra?.quota_notify_daily_enabled as boolean) ?? null
editQuotaNotifyDailyThreshold.value = (extra?.quota_notify_daily_threshold as number) ?? null
editQuotaNotifyDailyThresholdType.value = (extra?.quota_notify_daily_threshold_type as string) ?? null
editQuotaNotifyWeeklyEnabled.value = (extra?.quota_notify_weekly_enabled as boolean) ?? null
editQuotaNotifyWeeklyThreshold.value = (extra?.quota_notify_weekly_threshold as number) ?? null
editQuotaNotifyWeeklyThresholdType.value = (extra?.quota_notify_weekly_threshold_type as string) ?? null
editQuotaNotifyTotalEnabled.value = (extra?.quota_notify_total_enabled as boolean) ?? null
editQuotaNotifyTotalThreshold.value = (extra?.quota_notify_total_threshold as number) ?? null
editQuotaNotifyTotalThresholdType.value = (extra?.quota_notify_total_threshold_type as string) ?? null
loadQuotaNotifyFromExtra(extra)
} else {
editQuotaLimit.value = null
editQuotaDailyLimit.value = null
@@ -2248,12 +2237,7 @@ const syncFormFromAccount = (newAccount: Account | null) => {
editWeeklyResetDay.value = null
editWeeklyResetHour.value = null
editResetTimezone.value = null
editQuotaNotifyDailyEnabled.value = null
editQuotaNotifyDailyThreshold.value = null
editQuotaNotifyWeeklyEnabled.value = null
editQuotaNotifyWeeklyThreshold.value = null
editQuotaNotifyTotalEnabled.value = null
editQuotaNotifyTotalThreshold.value = null
resetQuotaNotify()
}
// Load antigravity model mapping (Antigravity 只支持映射模式)
@@ -2369,12 +2353,7 @@ const syncFormFromAccount = (newAccount: Account | null) => {
editQuotaDailyLimit.value = typeof bedrockExtra.quota_daily_limit === 'number' ? bedrockExtra.quota_daily_limit : null
editQuotaWeeklyLimit.value = typeof bedrockExtra.quota_weekly_limit === 'number' ? bedrockExtra.quota_weekly_limit : null
// Load quota notify for bedrock
editQuotaNotifyDailyEnabled.value = (bedrockExtra.quota_notify_daily_enabled as boolean) ?? null
editQuotaNotifyDailyThreshold.value = (bedrockExtra.quota_notify_daily_threshold as number) ?? null
editQuotaNotifyWeeklyEnabled.value = (bedrockExtra.quota_notify_weekly_enabled as boolean) ?? null
editQuotaNotifyWeeklyThreshold.value = (bedrockExtra.quota_notify_weekly_threshold as number) ?? null
editQuotaNotifyTotalEnabled.value = (bedrockExtra.quota_notify_total_enabled as boolean) ?? null
editQuotaNotifyTotalThreshold.value = (bedrockExtra.quota_notify_total_threshold as number) ?? null
loadQuotaNotifyFromExtra(bedrockExtra)
// Load model mappings for bedrock
const existingMappings = bedrockCreds.model_mapping as Record<string, string> | undefined
@@ -3291,45 +3270,7 @@ const handleSubmit = async () => {
delete newExtra.quota_reset_timezone
}
// Quota notify config
if (editQuotaNotifyDailyEnabled.value) {
newExtra.quota_notify_daily_enabled = true
if (editQuotaNotifyDailyThreshold.value != null) {
newExtra.quota_notify_daily_threshold = editQuotaNotifyDailyThreshold.value
} else {
delete newExtra.quota_notify_daily_threshold
}
newExtra.quota_notify_daily_threshold_type = editQuotaNotifyDailyThresholdType.value || 'fixed'
} else {
delete newExtra.quota_notify_daily_enabled
delete newExtra.quota_notify_daily_threshold
delete newExtra.quota_notify_daily_threshold_type
}
if (editQuotaNotifyWeeklyEnabled.value) {
newExtra.quota_notify_weekly_enabled = true
if (editQuotaNotifyWeeklyThreshold.value != null) {
newExtra.quota_notify_weekly_threshold = editQuotaNotifyWeeklyThreshold.value
} else {
delete newExtra.quota_notify_weekly_threshold
}
newExtra.quota_notify_weekly_threshold_type = editQuotaNotifyWeeklyThresholdType.value || 'fixed'
} else {
delete newExtra.quota_notify_weekly_enabled
delete newExtra.quota_notify_weekly_threshold
delete newExtra.quota_notify_weekly_threshold_type
}
if (editQuotaNotifyTotalEnabled.value) {
newExtra.quota_notify_total_enabled = true
if (editQuotaNotifyTotalThreshold.value != null) {
newExtra.quota_notify_total_threshold = editQuotaNotifyTotalThreshold.value
} else {
delete newExtra.quota_notify_total_threshold
}
newExtra.quota_notify_total_threshold_type = editQuotaNotifyTotalThresholdType.value || 'fixed'
} else {
delete newExtra.quota_notify_total_enabled
delete newExtra.quota_notify_total_threshold
delete newExtra.quota_notify_total_threshold_type
}
writeQuotaNotifyToExtra(newExtra, 'update')
updatePayload.extra = newExtra
}

View File

@@ -0,0 +1,108 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import QuotaNotifyToggle from './QuotaNotifyToggle.vue'
const { t } = useI18n()
const props = defineProps<{
dim: 'daily' | 'weekly' | 'total'
label: string
limit: number | null
quotaNotifyGlobalEnabled: boolean
notifyEnabled: boolean | null
notifyThreshold: number | null
notifyThresholdType: string | null
// Reset mode (only for daily/weekly, null for total)
resetMode: 'rolling' | 'fixed' | null
resetHour: number | null
resetDay: number | null // weekly only
resetTimezone: string | null
hintRolling: string
hintFixed: string
// Shared options passed from parent
hourOptions: number[]
dayOptions: { value: number; key: string }[]
}>()
const emit = defineEmits<{
'update:limit': [value: number | null]
'update:notifyEnabled': [value: boolean | null]
'update:notifyThreshold': [value: number | null]
'update:notifyThresholdType': [value: string | null]
'update:resetMode': [value: 'rolling' | 'fixed' | null]
'update:resetHour': [value: number | null]
'update:resetDay': [value: number | null]
'update:resetTimezone': [value: string | null]
}>()
const hasResetMode = props.dim !== 'total'
const onLimitInput = (e: Event) => {
const raw = (e.target as HTMLInputElement).valueAsNumber
emit('update:limit', Number.isNaN(raw) ? null : raw)
}
const onModeChange = (e: Event) => {
const val = (e.target as HTMLSelectElement).value as 'rolling' | 'fixed'
emit('update:resetMode', val)
if (val === 'fixed') {
if (props.resetHour == null) emit('update:resetHour', 0)
if (props.dim === 'weekly' && props.resetDay == null) emit('update:resetDay', 1)
if (!props.resetTimezone) emit('update:resetTimezone', 'UTC')
}
}
</script>
<template>
<div>
<!-- Title row (only when global notify is enabled) -->
<div v-if="quotaNotifyGlobalEnabled" class="flex items-center gap-2 mb-1">
<span class="text-xs font-medium text-gray-700 dark:text-gray-300 flex-1 min-w-0">{{ label }}</span>
<span v-if="limit && limit > 0" class="text-xs font-medium text-gray-700 dark:text-gray-300 flex-1 min-w-0">{{ t('admin.accounts.quotaNotify.alert') }}</span>
</div>
<label v-else class="text-xs font-medium text-gray-700 dark:text-gray-300 mb-1 block">{{ label }}</label>
<!-- Input row -->
<div class="flex items-center gap-2">
<div :class="['relative', quotaNotifyGlobalEnabled ? 'flex-1 min-w-0' : 'flex-1']">
<span class="absolute left-2.5 top-1/2 -translate-y-1/2 text-gray-500 dark:text-gray-400 text-sm">$</span>
<input :value="limit" @input="onLimitInput" type="number" min="0" step="0.01" class="input pl-6 py-1.5 text-sm" :placeholder="t('admin.accounts.quotaLimitPlaceholder')" />
</div>
<QuotaNotifyToggle
v-if="quotaNotifyGlobalEnabled && limit && limit > 0"
class="flex-1 min-w-0"
:enabled="notifyEnabled" :threshold="notifyThreshold" :threshold-type="notifyThresholdType"
@update:enabled="emit('update:notifyEnabled', $event)" @update:threshold="emit('update:notifyThreshold', $event)" @update:threshold-type="emit('update:notifyThresholdType', $event)"
/>
</div>
<!-- Reset mode row (daily/weekly only) -->
<div v-if="hasResetMode" class="mt-1 flex items-center gap-2 flex-wrap">
<label class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap">{{ t('admin.accounts.quotaResetMode') }}</label>
<select :value="resetMode || 'rolling'" @change="onModeChange" class="input py-1 text-xs w-auto">
<option value="rolling">{{ t('admin.accounts.quotaResetModeRolling') }}</option>
<option value="fixed">{{ t('admin.accounts.quotaResetModeFixed') }}</option>
</select>
<template v-if="resetMode === 'fixed'">
<!-- Weekly: day of week selector -->
<template v-if="dim === 'weekly'">
<label class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap">{{ t('admin.accounts.quotaWeeklyResetDay') }}</label>
<select :value="resetDay ?? 1" @change="emit('update:resetDay', Number(($event.target as HTMLSelectElement).value))" class="input py-1 text-xs w-28">
<option v-for="d in dayOptions" :key="d.value" :value="d.value">{{ t('admin.accounts.dayOfWeek.' + d.key) }}</option>
</select>
</template>
<label class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap">{{ t('admin.accounts.quotaResetHour') }}</label>
<select :value="resetHour ?? 0" @change="emit('update:resetHour', Number(($event.target as HTMLSelectElement).value))" class="input py-1 text-xs w-24">
<option v-for="h in hourOptions" :key="h" :value="h">{{ String(h).padStart(2, '0') }}:00</option>
</select>
</template>
<span class="text-[11px] text-gray-500 dark:text-gray-400">
<template v-if="resetMode === 'fixed'">{{ hintFixed }}</template>
<template v-else>{{ hintRolling }}</template>
</span>
</div>
<!-- Total dimension hint (no reset mode) -->
<p v-if="!hasResetMode" class="input-hint mb-0 text-[11px]">{{ hintRolling }}</p>
</div>
</template>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref, watch, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import QuotaNotifyToggle from './QuotaNotifyToggle.vue'
import QuotaDimensionRow from './QuotaDimensionRow.vue'
const { t } = useI18n()
@@ -96,26 +96,24 @@ const hasFixedMode = computed(() =>
// Common timezone options
const timezoneOptions = [
'UTC',
'Asia/Shanghai',
'Asia/Tokyo',
'Asia/Seoul',
'Asia/Singapore',
'Asia/Kolkata',
'Asia/Dubai',
'Europe/London',
'Europe/Paris',
'Europe/Berlin',
'Europe/Moscow',
'America/New_York',
'America/Chicago',
'America/Denver',
'America/Los_Angeles',
'America/Sao_Paulo',
'Australia/Sydney',
'Pacific/Auckland',
'UTC', 'Asia/Shanghai', 'Asia/Tokyo', 'Asia/Seoul', 'Asia/Singapore', 'Asia/Kolkata',
'Asia/Dubai', 'Europe/London', 'Europe/Paris', 'Europe/Berlin', 'Europe/Moscow',
'America/New_York', 'America/Chicago', 'America/Denver', 'America/Los_Angeles',
'America/Sao_Paulo', 'Australia/Sydney', 'Pacific/Auckland',
]
// Compute GMT offset label (e.g. "GMT+8", "GMT-5") for a given IANA timezone.
function getTimezoneOffsetLabel(tz: string): string {
try {
const dtf = new Intl.DateTimeFormat('en-US', { timeZone: tz, timeZoneName: 'shortOffset' })
const parts = dtf.formatToParts(new Date())
const tzPart = parts.find(p => p.type === 'timeZoneName')
return tzPart ? (tzPart.value === 'GMT' ? 'GMT+0' : tzPart.value) : ''
} catch {
return ''
}
}
// Hours for dropdown (0-23)
const hourOptions = Array.from({ length: 24 }, (_, i) => i)
@@ -130,37 +128,22 @@ const dayOptions = [
{ value: 0, key: 'sunday' },
]
const onTotalInput = (e: Event) => {
const raw = (e.target as HTMLInputElement).valueAsNumber
emit('update:totalLimit', Number.isNaN(raw) ? null : raw)
}
const onDailyInput = (e: Event) => {
const raw = (e.target as HTMLInputElement).valueAsNumber
emit('update:dailyLimit', Number.isNaN(raw) ? null : raw)
}
const onWeeklyInput = (e: Event) => {
const raw = (e.target as HTMLInputElement).valueAsNumber
emit('update:weeklyLimit', Number.isNaN(raw) ? null : raw)
}
// Precomputed hint strings for the weekly fixed mode
const weeklyFixedHint = computed(() => {
const dayKey = dayOptions.find(d => d.value === (props.weeklyResetDay ?? 1))?.key || 'monday'
return t('admin.accounts.quotaWeeklyLimitHintFixed', {
day: t('admin.accounts.dayOfWeek.' + dayKey),
hour: String(props.weeklyResetHour ?? 0).padStart(2, '0'),
timezone: props.resetTimezone || 'UTC',
})
})
const onDailyModeChange = (e: Event) => {
const val = (e.target as HTMLSelectElement).value as 'rolling' | 'fixed'
emit('update:dailyResetMode', val)
if (val === 'fixed') {
if (props.dailyResetHour == null) emit('update:dailyResetHour', 0)
if (!props.resetTimezone) emit('update:resetTimezone', 'UTC')
}
}
const onWeeklyModeChange = (e: Event) => {
const val = (e.target as HTMLSelectElement).value as 'rolling' | 'fixed'
emit('update:weeklyResetMode', val)
if (val === 'fixed') {
if (props.weeklyResetDay == null) emit('update:weeklyResetDay', 1)
if (props.weeklyResetHour == null) emit('update:weeklyResetHour', 0)
if (!props.resetTimezone) emit('update:resetTimezone', 'UTC')
}
}
const dailyFixedHint = computed(() =>
t('admin.accounts.quotaDailyLimitHintFixed', {
hour: String(props.dailyResetHour ?? 0).padStart(2, '0'),
timezone: props.resetTimezone || 'UTC',
})
)
</script>
<template>
@@ -197,117 +180,89 @@ const onWeeklyModeChange = (e: Event) => {
<!-- Collapsible content -->
<div v-if="localEnabled && !collapsed" class="space-y-2 p-4 pt-3">
<!-- 日配额 -->
<div>
<!-- 标题行仅全局通知开启时显示 -->
<div v-if="quotaNotifyGlobalEnabled" class="flex items-center gap-2 mb-1">
<span class="text-xs font-medium text-gray-700 dark:text-gray-300 flex-1 min-w-0">{{ t('admin.accounts.quotaDailyLimit') }}</span>
<span v-if="dailyLimit && dailyLimit > 0" class="text-xs font-medium text-gray-700 dark:text-gray-300 flex-1 min-w-0">{{ t('admin.accounts.quotaNotify.alert') }}</span>
</div>
<label v-else class="text-xs font-medium text-gray-700 dark:text-gray-300 mb-1 block">{{ t('admin.accounts.quotaDailyLimit') }}</label>
<!-- 输入行 -->
<div class="flex items-center gap-2">
<div :class="['relative', quotaNotifyGlobalEnabled ? 'w-28 flex-shrink-0' : 'flex-1']">
<span class="absolute left-2.5 top-1/2 -translate-y-1/2 text-gray-500 dark:text-gray-400 text-sm">$</span>
<input :value="dailyLimit" @input="onDailyInput" type="number" min="0" step="0.01" class="input pl-6 py-1.5 text-sm" :placeholder="t('admin.accounts.quotaLimitPlaceholder')" />
</div>
<QuotaNotifyToggle
v-if="quotaNotifyGlobalEnabled && dailyLimit && dailyLimit > 0"
class="flex-1 min-w-0"
:enabled="props.quotaNotifyDailyEnabled" :threshold="props.quotaNotifyDailyThreshold" :threshold-type="props.quotaNotifyDailyThresholdType"
@update:enabled="emit('update:quotaNotifyDailyEnabled', $event)" @update:threshold="emit('update:quotaNotifyDailyThreshold', $event)" @update:threshold-type="emit('update:quotaNotifyDailyThresholdType', $event)"
/>
</div>
<div class="mt-1 flex items-center gap-2 flex-wrap">
<label class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap">{{ t('admin.accounts.quotaResetMode') }}</label>
<select :value="dailyResetMode || 'rolling'" @change="onDailyModeChange" class="input py-1 text-xs w-auto">
<option value="rolling">{{ t('admin.accounts.quotaResetModeRolling') }}</option>
<option value="fixed">{{ t('admin.accounts.quotaResetModeFixed') }}</option>
</select>
<template v-if="dailyResetMode === 'fixed'">
<label class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap">{{ t('admin.accounts.quotaResetHour') }}</label>
<select :value="dailyResetHour ?? 0" @change="emit('update:dailyResetHour', Number(($event.target as HTMLSelectElement).value))" class="input py-1 text-xs w-24">
<option v-for="h in hourOptions" :key="h" :value="h">{{ String(h).padStart(2, '0') }}:00</option>
</select>
</template>
</div>
<p class="input-hint mb-0 text-[11px]">
<template v-if="dailyResetMode === 'fixed'">{{ t('admin.accounts.quotaDailyLimitHintFixed', { hour: String(dailyResetHour ?? 0).padStart(2, '0'), timezone: resetTimezone || 'UTC' }) }}</template>
<template v-else>{{ t('admin.accounts.quotaDailyLimitHint') }}</template>
</p>
</div>
<!-- Daily quota -->
<QuotaDimensionRow
dim="daily"
:label="t('admin.accounts.quotaDailyLimit')"
:limit="dailyLimit"
:quota-notify-global-enabled="quotaNotifyGlobalEnabled"
:notify-enabled="props.quotaNotifyDailyEnabled"
:notify-threshold="props.quotaNotifyDailyThreshold"
:notify-threshold-type="props.quotaNotifyDailyThresholdType"
:reset-mode="dailyResetMode"
:reset-hour="dailyResetHour"
:reset-day="null"
:reset-timezone="resetTimezone"
:hint-rolling="t('admin.accounts.quotaDailyLimitHint')"
:hint-fixed="dailyFixedHint"
:hour-options="hourOptions"
:day-options="dayOptions"
@update:limit="emit('update:dailyLimit', $event)"
@update:notify-enabled="emit('update:quotaNotifyDailyEnabled', $event)"
@update:notify-threshold="emit('update:quotaNotifyDailyThreshold', $event)"
@update:notify-threshold-type="emit('update:quotaNotifyDailyThresholdType', $event)"
@update:reset-mode="emit('update:dailyResetMode', $event)"
@update:reset-hour="emit('update:dailyResetHour', $event)"
@update:reset-timezone="emit('update:resetTimezone', $event)"
/>
<!-- 周配额 -->
<div>
<div v-if="quotaNotifyGlobalEnabled" class="flex items-center gap-2 mb-1">
<span class="text-xs font-medium text-gray-700 dark:text-gray-300 flex-1 min-w-0">{{ t('admin.accounts.quotaWeeklyLimit') }}</span>
<span v-if="weeklyLimit && weeklyLimit > 0" class="text-xs font-medium text-gray-700 dark:text-gray-300 flex-1 min-w-0">{{ t('admin.accounts.quotaNotify.alert') }}</span>
</div>
<label v-else class="text-xs font-medium text-gray-700 dark:text-gray-300 mb-1 block">{{ t('admin.accounts.quotaWeeklyLimit') }}</label>
<div class="flex items-center gap-2">
<div :class="['relative', quotaNotifyGlobalEnabled ? 'w-28 flex-shrink-0' : 'flex-1']">
<span class="absolute left-2.5 top-1/2 -translate-y-1/2 text-gray-500 dark:text-gray-400 text-sm">$</span>
<input :value="weeklyLimit" @input="onWeeklyInput" type="number" min="0" step="0.01" class="input pl-6 py-1.5 text-sm" :placeholder="t('admin.accounts.quotaLimitPlaceholder')" />
</div>
<QuotaNotifyToggle
v-if="quotaNotifyGlobalEnabled && weeklyLimit && weeklyLimit > 0"
class="flex-1 min-w-0"
:enabled="props.quotaNotifyWeeklyEnabled" :threshold="props.quotaNotifyWeeklyThreshold" :threshold-type="props.quotaNotifyWeeklyThresholdType"
@update:enabled="emit('update:quotaNotifyWeeklyEnabled', $event)" @update:threshold="emit('update:quotaNotifyWeeklyThreshold', $event)" @update:threshold-type="emit('update:quotaNotifyWeeklyThresholdType', $event)"
/>
</div>
<div class="mt-1 flex items-center gap-2 flex-wrap">
<label class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap">{{ t('admin.accounts.quotaResetMode') }}</label>
<select :value="weeklyResetMode || 'rolling'" @change="onWeeklyModeChange" class="input py-1 text-xs w-auto">
<option value="rolling">{{ t('admin.accounts.quotaResetModeRolling') }}</option>
<option value="fixed">{{ t('admin.accounts.quotaResetModeFixed') }}</option>
</select>
<template v-if="weeklyResetMode === 'fixed'">
<label class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap">{{ t('admin.accounts.quotaWeeklyResetDay') }}</label>
<select :value="weeklyResetDay ?? 1" @change="emit('update:weeklyResetDay', Number(($event.target as HTMLSelectElement).value))" class="input py-1 text-xs w-28">
<option v-for="d in dayOptions" :key="d.value" :value="d.value">{{ t('admin.accounts.dayOfWeek.' + d.key) }}</option>
</select>
<label class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap">{{ t('admin.accounts.quotaResetHour') }}</label>
<select :value="weeklyResetHour ?? 0" @change="emit('update:weeklyResetHour', Number(($event.target as HTMLSelectElement).value))" class="input py-1 text-xs w-24">
<option v-for="h in hourOptions" :key="h" :value="h">{{ String(h).padStart(2, '0') }}:00</option>
</select>
</template>
</div>
<p class="input-hint mb-0 text-[11px]">
<template v-if="weeklyResetMode === 'fixed'">{{ t('admin.accounts.quotaWeeklyLimitHintFixed', { day: t('admin.accounts.dayOfWeek.' + (dayOptions.find(d => d.value === (weeklyResetDay ?? 1))?.key || 'monday')), hour: String(weeklyResetHour ?? 0).padStart(2, '0'), timezone: resetTimezone || 'UTC' }) }}</template>
<template v-else>{{ t('admin.accounts.quotaWeeklyLimitHint') }}</template>
</p>
</div>
<!-- Weekly quota -->
<QuotaDimensionRow
dim="weekly"
:label="t('admin.accounts.quotaWeeklyLimit')"
:limit="weeklyLimit"
:quota-notify-global-enabled="quotaNotifyGlobalEnabled"
:notify-enabled="props.quotaNotifyWeeklyEnabled"
:notify-threshold="props.quotaNotifyWeeklyThreshold"
:notify-threshold-type="props.quotaNotifyWeeklyThresholdType"
:reset-mode="weeklyResetMode"
:reset-hour="weeklyResetHour"
:reset-day="weeklyResetDay"
:reset-timezone="resetTimezone"
:hint-rolling="t('admin.accounts.quotaWeeklyLimitHint')"
:hint-fixed="weeklyFixedHint"
:hour-options="hourOptions"
:day-options="dayOptions"
@update:limit="emit('update:weeklyLimit', $event)"
@update:notify-enabled="emit('update:quotaNotifyWeeklyEnabled', $event)"
@update:notify-threshold="emit('update:quotaNotifyWeeklyThreshold', $event)"
@update:notify-threshold-type="emit('update:quotaNotifyWeeklyThresholdType', $event)"
@update:reset-mode="emit('update:weeklyResetMode', $event)"
@update:reset-hour="emit('update:weeklyResetHour', $event)"
@update:reset-day="emit('update:weeklyResetDay', $event)"
@update:reset-timezone="emit('update:resetTimezone', $event)"
/>
<!-- 时区选择 -->
<!-- Timezone selector (shared by daily/weekly when fixed mode is active) -->
<div v-if="hasFixedMode">
<label class="input-label">{{ t('admin.accounts.quotaResetTimezone') }}</label>
<select :value="resetTimezone || 'UTC'" @change="emit('update:resetTimezone', ($event.target as HTMLSelectElement).value)" class="input text-sm">
<option v-for="tz in timezoneOptions" :key="tz" :value="tz">{{ tz }}</option>
<option v-for="tz in timezoneOptions" :key="tz" :value="tz">{{ tz }} ({{ getTimezoneOffsetLabel(tz) }})</option>
</select>
</div>
<!-- 总配额 -->
<div>
<div v-if="quotaNotifyGlobalEnabled" class="flex items-center gap-2 mb-1">
<span class="text-xs font-medium text-gray-700 dark:text-gray-300 flex-1 min-w-0">{{ t('admin.accounts.quotaTotalLimit') }}</span>
<span v-if="totalLimit && totalLimit > 0" class="text-xs font-medium text-gray-700 dark:text-gray-300 flex-1 min-w-0">{{ t('admin.accounts.quotaNotify.alert') }}</span>
</div>
<label v-else class="text-xs font-medium text-gray-700 dark:text-gray-300 mb-1 block">{{ t('admin.accounts.quotaTotalLimit') }}</label>
<div class="flex items-center gap-2">
<div :class="['relative', quotaNotifyGlobalEnabled ? 'w-28 flex-shrink-0' : 'flex-1']">
<span class="absolute left-2.5 top-1/2 -translate-y-1/2 text-gray-500 dark:text-gray-400 text-sm">$</span>
<input :value="totalLimit" @input="onTotalInput" type="number" min="0" step="0.01" class="input pl-6 py-1.5 text-sm" :placeholder="t('admin.accounts.quotaLimitPlaceholder')" />
</div>
<QuotaNotifyToggle
v-if="quotaNotifyGlobalEnabled && totalLimit && totalLimit > 0"
class="flex-1 min-w-0"
:enabled="props.quotaNotifyTotalEnabled" :threshold="props.quotaNotifyTotalThreshold" :threshold-type="props.quotaNotifyTotalThresholdType"
@update:enabled="emit('update:quotaNotifyTotalEnabled', $event)" @update:threshold="emit('update:quotaNotifyTotalThreshold', $event)" @update:threshold-type="emit('update:quotaNotifyTotalThresholdType', $event)"
/>
</div>
<p class="input-hint mb-0 text-[11px]">{{ t('admin.accounts.quotaTotalLimitHint') }}</p>
</div>
<!-- Total quota -->
<QuotaDimensionRow
dim="total"
:label="t('admin.accounts.quotaTotalLimit')"
:limit="totalLimit"
:quota-notify-global-enabled="quotaNotifyGlobalEnabled"
:notify-enabled="props.quotaNotifyTotalEnabled"
:notify-threshold="props.quotaNotifyTotalThreshold"
:notify-threshold-type="props.quotaNotifyTotalThresholdType"
:reset-mode="null"
:reset-hour="null"
:reset-day="null"
:reset-timezone="null"
:hint-rolling="t('admin.accounts.quotaTotalLimitHint')"
hint-fixed=""
:hour-options="hourOptions"
:day-options="dayOptions"
@update:limit="emit('update:totalLimit', $event)"
@update:notify-enabled="emit('update:quotaNotifyTotalEnabled', $event)"
@update:notify-threshold="emit('update:quotaNotifyTotalThreshold', $event)"
@update:notify-threshold-type="emit('update:quotaNotifyTotalThresholdType', $event)"
/>
</div>
</div>
</template>