From f1cc83e0ee7225034568f7aa8c5886b22490ce5e Mon Sep 17 00:00:00 2001 From: benjamin Date: Mon, 18 May 2026 21:09:11 +0800 Subject: [PATCH] =?UTF-8?q?fix(admin):=20=E4=BF=AE=E6=AD=A3=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E7=AB=AF=E6=97=A5=E5=8D=A1=E9=A2=9D=E5=BA=A6=E6=8F=90?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .../src/views/admin/SubscriptionsView.vue | 54 +++++++++++++------ 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/frontend/src/views/admin/SubscriptionsView.vue b/frontend/src/views/admin/SubscriptionsView.vue index 0a76809e..6c53064d 100644 --- a/frontend/src/views/admin/SubscriptionsView.vue +++ b/frontend/src/views/admin/SubscriptionsView.vue @@ -246,7 +246,7 @@ d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" /> - {{ formatResetTime(row.daily_window_start, 'daily') }} + {{ formatDailyUsageWindow(row) }} @@ -758,6 +758,7 @@ import Select from '@/components/common/Select.vue' import GroupBadge from '@/components/common/GroupBadge.vue' import GroupOptionItem from '@/components/common/GroupOptionItem.vue' import Icon from '@/components/icons/Icon.vue' +import { getRemainingDurationParts, isOneTimeDailyQuota, type RemainingDurationParts } from '@/utils/subscriptionQuota' const { t } = useI18n() const appStore = useAppStore() @@ -1313,8 +1314,41 @@ const getProgressClass = (used: number | null | undefined, limit: number | null) return 'bg-green-500' } +const formatResetDuration = (parts: RemainingDurationParts): string => { + if (parts.days > 0) { + return t('admin.subscriptions.resetInDaysHours', { days: parts.days, hours: parts.hours }) + } + + if (parts.hours > 0) { + return t('admin.subscriptions.resetInHoursMinutes', { hours: parts.hours, minutes: parts.minutes }) + } + + return t('admin.subscriptions.resetInMinutes', { minutes: parts.minutes }) +} + +const formatQuotaEndDuration = (parts: RemainingDurationParts): string => { + if (parts.days > 0) { + return t('admin.subscriptions.quotaEndsInDaysHours', { days: parts.days, hours: parts.hours }) + } + + if (parts.hours > 0) { + return t('admin.subscriptions.quotaEndsInHoursMinutes', { hours: parts.hours, minutes: parts.minutes }) + } + + return t('admin.subscriptions.quotaEndsInMinutes', { minutes: parts.minutes }) +} + +const formatDailyUsageWindow = (subscription: UserSubscription): string => { + if (isOneTimeDailyQuota(subscription) && subscription.expires_at) { + const parts = getRemainingDurationParts(subscription.expires_at) + return parts ? formatQuotaEndDuration(parts) : t('admin.subscriptions.windowNotActive') + } + + return formatResetTime(subscription.daily_window_start, 'daily') +} + // Format reset time based on window start and period type -const formatResetTime = (windowStart: string, period: 'daily' | 'weekly' | 'monthly'): string => { +const formatResetTime = (windowStart: string | null, period: 'daily' | 'weekly' | 'monthly'): string => { if (!windowStart) return t('admin.subscriptions.windowNotActive') const start = new Date(windowStart) @@ -1334,21 +1368,9 @@ const formatResetTime = (windowStart: string, period: 'daily' | 'weekly' | 'mont break } - const diffMs = resetTime.getTime() - now.getTime() - if (diffMs <= 0) return t('admin.subscriptions.windowNotActive') + const parts = getRemainingDurationParts(resetTime, now) - const diffSeconds = Math.floor(diffMs / 1000) - const days = Math.floor(diffSeconds / 86400) - const hours = Math.floor((diffSeconds % 86400) / 3600) - const minutes = Math.floor((diffSeconds % 3600) / 60) - - if (days > 0) { - return t('admin.subscriptions.resetInDaysHours', { days, hours }) - } else if (hours > 0) { - return t('admin.subscriptions.resetInHoursMinutes', { hours, minutes }) - } else { - return t('admin.subscriptions.resetInMinutes', { minutes }) - } + return parts ? formatResetDuration(parts) : t('admin.subscriptions.windowNotActive') } // Handle click outside to close dropdowns