Skip to content

Commit 5f7e518

Browse files
committed
feat: admin/subscriptions新增重置时间显示
1 parent 008a111 commit 5f7e518

3 files changed

Lines changed: 133 additions & 34 deletions

File tree

frontend/src/i18n/locales/en.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,10 @@ export default {
581581
weekly: 'Weekly',
582582
monthly: 'Monthly',
583583
noLimits: 'No limits configured',
584+
resetNow: 'Resetting soon',
585+
resetInMinutes: 'Resets in {minutes}m',
586+
resetInHoursMinutes: 'Resets in {hours}h {minutes}m',
587+
resetInDaysHours: 'Resets in {days}d {hours}h',
584588
daysRemaining: 'days remaining',
585589
noExpiration: 'No expiration',
586590
status: {

frontend/src/i18n/locales/zh.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,10 @@ export default {
638638
weekly: '每周',
639639
monthly: '每月',
640640
noLimits: '未配置限额',
641+
resetNow: '即将重置',
642+
resetInMinutes: '{minutes} 分钟后重置',
643+
resetInHoursMinutes: '{hours} 小时 {minutes} 分钟后重置',
644+
resetInDaysHours: '{days} 天 {hours} 小时后重置',
641645
daysRemaining: '天剩余',
642646
noExpiration: '无过期时间',
643647
status: {

frontend/src/views/admin/SubscriptionsView.vue

Lines changed: 125 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -66,46 +66,83 @@
6666
</template>
6767

6868
<template #cell-usage="{ row }">
69-
<div class="space-y-1 min-w-[200px]">
70-
<div v-if="row.group?.daily_limit_usd" class="flex items-center gap-2">
71-
<span class="text-xs text-gray-500 w-12">{{ t('admin.subscriptions.daily') }}</span>
72-
<div class="flex-1 bg-gray-200 dark:bg-dark-600 rounded-full h-2">
73-
<div
74-
class="h-2 rounded-full transition-all"
75-
:class="getProgressClass(row.daily_usage_usd, row.group?.daily_limit_usd)"
76-
:style="{ width: getProgressWidth(row.daily_usage_usd, row.group?.daily_limit_usd) }"
77-
></div>
69+
<div class="space-y-2 min-w-[280px]">
70+
<!-- Daily Usage -->
71+
<div v-if="row.group?.daily_limit_usd" class="usage-row">
72+
<div class="flex items-center gap-2">
73+
<span class="usage-label">{{ t('admin.subscriptions.daily') }}</span>
74+
<div class="flex-1 bg-gray-200 dark:bg-dark-600 rounded-full h-1.5">
75+
<div
76+
class="h-1.5 rounded-full transition-all"
77+
:class="getProgressClass(row.daily_usage_usd, row.group?.daily_limit_usd)"
78+
:style="{ width: getProgressWidth(row.daily_usage_usd, row.group?.daily_limit_usd) }"
79+
></div>
80+
</div>
81+
<span class="usage-amount">
82+
${{ row.daily_usage_usd?.toFixed(2) || '0.00' }}
83+
<span class="text-gray-400">/</span>
84+
${{ row.group?.daily_limit_usd?.toFixed(2) }}
85+
</span>
86+
</div>
87+
<div class="reset-info" v-if="row.daily_window_start">
88+
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
89+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
90+
</svg>
91+
<span>{{ formatResetTime(row.daily_window_start, 'daily') }}</span>
7892
</div>
79-
<span class="text-xs text-gray-500 w-20 text-right">
80-
${{ row.daily_usage_usd?.toFixed(2) || '0.00' }} / ${{ row.group?.daily_limit_usd?.toFixed(2) }}
81-
</span>
8293
</div>
83-
<div v-if="row.group?.weekly_limit_usd" class="flex items-center gap-2">
84-
<span class="text-xs text-gray-500 w-12">{{ t('admin.subscriptions.weekly') }}</span>
85-
<div class="flex-1 bg-gray-200 dark:bg-dark-600 rounded-full h-2">
86-
<div
87-
class="h-2 rounded-full transition-all"
88-
:class="getProgressClass(row.weekly_usage_usd, row.group?.weekly_limit_usd)"
89-
:style="{ width: getProgressWidth(row.weekly_usage_usd, row.group?.weekly_limit_usd) }"
90-
></div>
94+
95+
<!-- Weekly Usage -->
96+
<div v-if="row.group?.weekly_limit_usd" class="usage-row">
97+
<div class="flex items-center gap-2">
98+
<span class="usage-label">{{ t('admin.subscriptions.weekly') }}</span>
99+
<div class="flex-1 bg-gray-200 dark:bg-dark-600 rounded-full h-1.5">
100+
<div
101+
class="h-1.5 rounded-full transition-all"
102+
:class="getProgressClass(row.weekly_usage_usd, row.group?.weekly_limit_usd)"
103+
:style="{ width: getProgressWidth(row.weekly_usage_usd, row.group?.weekly_limit_usd) }"
104+
></div>
105+
</div>
106+
<span class="usage-amount">
107+
${{ row.weekly_usage_usd?.toFixed(2) || '0.00' }}
108+
<span class="text-gray-400">/</span>
109+
${{ row.group?.weekly_limit_usd?.toFixed(2) }}
110+
</span>
111+
</div>
112+
<div class="reset-info" v-if="row.weekly_window_start">
113+
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
114+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
115+
</svg>
116+
<span>{{ formatResetTime(row.weekly_window_start, 'weekly') }}</span>
91117
</div>
92-
<span class="text-xs text-gray-500 w-20 text-right">
93-
${{ row.weekly_usage_usd?.toFixed(2) || '0.00' }} / ${{ row.group?.weekly_limit_usd?.toFixed(2) }}
94-
</span>
95118
</div>
96-
<div v-if="row.group?.monthly_limit_usd" class="flex items-center gap-2">
97-
<span class="text-xs text-gray-500 w-12">{{ t('admin.subscriptions.monthly') }}</span>
98-
<div class="flex-1 bg-gray-200 dark:bg-dark-600 rounded-full h-2">
99-
<div
100-
class="h-2 rounded-full transition-all"
101-
:class="getProgressClass(row.monthly_usage_usd, row.group?.monthly_limit_usd)"
102-
:style="{ width: getProgressWidth(row.monthly_usage_usd, row.group?.monthly_limit_usd) }"
103-
></div>
119+
120+
<!-- Monthly Usage -->
121+
<div v-if="row.group?.monthly_limit_usd" class="usage-row">
122+
<div class="flex items-center gap-2">
123+
<span class="usage-label">{{ t('admin.subscriptions.monthly') }}</span>
124+
<div class="flex-1 bg-gray-200 dark:bg-dark-600 rounded-full h-1.5">
125+
<div
126+
class="h-1.5 rounded-full transition-all"
127+
:class="getProgressClass(row.monthly_usage_usd, row.group?.monthly_limit_usd)"
128+
:style="{ width: getProgressWidth(row.monthly_usage_usd, row.group?.monthly_limit_usd) }"
129+
></div>
130+
</div>
131+
<span class="usage-amount">
132+
${{ row.monthly_usage_usd?.toFixed(2) || '0.00' }}
133+
<span class="text-gray-400">/</span>
134+
${{ row.group?.monthly_limit_usd?.toFixed(2) }}
135+
</span>
136+
</div>
137+
<div class="reset-info" v-if="row.monthly_window_start">
138+
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
139+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
140+
</svg>
141+
<span>{{ formatResetTime(row.monthly_window_start, 'monthly') }}</span>
104142
</div>
105-
<span class="text-xs text-gray-500 w-20 text-right">
106-
${{ row.monthly_usage_usd?.toFixed(2) || '0.00' }} / ${{ row.group?.monthly_limit_usd?.toFixed(2) }}
107-
</span>
108143
</div>
144+
145+
<!-- No Limits -->
109146
<div v-if="!row.group?.daily_limit_usd && !row.group?.weekly_limit_usd && !row.group?.monthly_limit_usd" class="text-xs text-gray-500">
110147
{{ t('admin.subscriptions.noLimits') }}
111148
</div>
@@ -553,9 +590,63 @@ const getProgressClass = (used: number, limit: number | null): string => {
553590
return 'bg-green-500'
554591
}
555592
593+
// Format reset time based on window start and period type
594+
const formatResetTime = (windowStart: string, period: 'daily' | 'weekly' | 'monthly'): string => {
595+
const start = new Date(windowStart)
596+
const now = new Date()
597+
598+
// Calculate reset time based on period
599+
let resetTime: Date
600+
switch (period) {
601+
case 'daily':
602+
resetTime = new Date(start.getTime() + 24 * 60 * 60 * 1000)
603+
break
604+
case 'weekly':
605+
resetTime = new Date(start.getTime() + 7 * 24 * 60 * 60 * 1000)
606+
break
607+
case 'monthly':
608+
resetTime = new Date(start.getTime() + 30 * 24 * 60 * 60 * 1000)
609+
break
610+
}
611+
612+
const diffMs = resetTime.getTime() - now.getTime()
613+
if (diffMs <= 0) return t('admin.subscriptions.resetNow')
614+
615+
const diffSeconds = Math.floor(diffMs / 1000)
616+
const days = Math.floor(diffSeconds / 86400)
617+
const hours = Math.floor((diffSeconds % 86400) / 3600)
618+
const minutes = Math.floor((diffSeconds % 3600) / 60)
619+
620+
if (days > 0) {
621+
return t('admin.subscriptions.resetInDaysHours', { days, hours })
622+
} else if (hours > 0) {
623+
return t('admin.subscriptions.resetInHoursMinutes', { hours, minutes })
624+
} else {
625+
return t('admin.subscriptions.resetInMinutes', { minutes })
626+
}
627+
}
628+
556629
onMounted(() => {
557630
loadSubscriptions()
558631
loadGroups()
559632
loadUsers()
560633
})
561634
</script>
635+
636+
<style scoped>
637+
.usage-row {
638+
@apply space-y-1;
639+
}
640+
641+
.usage-label {
642+
@apply text-xs font-medium text-gray-500 dark:text-gray-400 w-10 flex-shrink-0;
643+
}
644+
645+
.usage-amount {
646+
@apply text-xs text-gray-600 dark:text-gray-300 tabular-nums whitespace-nowrap;
647+
}
648+
649+
.reset-info {
650+
@apply flex items-center gap-1 text-[10px] text-blue-600 dark:text-blue-400 pl-12;
651+
}
652+
</style>

0 commit comments

Comments
 (0)