From 5bbfbcdae97a795b884a40f7a6149ac5936eaf85 Mon Sep 17 00:00:00 2001 From: shaw Date: Tue, 23 Dec 2025 10:38:15 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=AE=A2=E9=98=85?= =?UTF-8?q?=E7=AA=97=E5=8F=A3=E8=BF=87=E6=9C=9F=E5=90=8E=E8=BF=9B=E5=BA=A6?= =?UTF-8?q?=E6=9D=A1=E6=98=BE=E7=A4=BA=E4=B8=8D=E6=AD=A3=E7=A1=AE=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题:滑动窗口过期后(如昨天用满额度),前端仍显示历史数据(红色进度条100%、"即将重置") 解决: - 后端返回数据前检查窗口是否过期,过期则清零展示数据 - 前端处理 window_start 为 null 的情况,显示"窗口未激活" - 不影响实际的窗口激活逻辑,窗口仍从当天零点开始 --- .../internal/service/subscription_service.go | 51 +++++++++++++++++-- frontend/src/i18n/locales/en.ts | 2 + frontend/src/i18n/locales/zh.ts | 2 + .../src/views/admin/SubscriptionsView.vue | 4 +- frontend/src/views/user/SubscriptionsView.vue | 4 +- 5 files changed, 56 insertions(+), 7 deletions(-) diff --git a/backend/internal/service/subscription_service.go b/backend/internal/service/subscription_service.go index 5d3c4d15..9c2a0e92 100644 --- a/backend/internal/service/subscription_service.go +++ b/backend/internal/service/subscription_service.go @@ -335,24 +335,67 @@ func (s *SubscriptionService) GetActiveSubscription(ctx context.Context, userID, // ListUserSubscriptions 获取用户的所有订阅 func (s *SubscriptionService) ListUserSubscriptions(ctx context.Context, userID int64) ([]model.UserSubscription, error) { - return s.userSubRepo.ListByUserID(ctx, userID) + subs, err := s.userSubRepo.ListByUserID(ctx, userID) + if err != nil { + return nil, err + } + normalizeExpiredWindows(subs) + return subs, nil } // ListActiveUserSubscriptions 获取用户的所有有效订阅 func (s *SubscriptionService) ListActiveUserSubscriptions(ctx context.Context, userID int64) ([]model.UserSubscription, error) { - return s.userSubRepo.ListActiveByUserID(ctx, userID) + subs, err := s.userSubRepo.ListActiveByUserID(ctx, userID) + if err != nil { + return nil, err + } + normalizeExpiredWindows(subs) + return subs, nil } // ListGroupSubscriptions 获取分组的所有订阅 func (s *SubscriptionService) ListGroupSubscriptions(ctx context.Context, groupID int64, page, pageSize int) ([]model.UserSubscription, *pagination.PaginationResult, error) { params := pagination.PaginationParams{Page: page, PageSize: pageSize} - return s.userSubRepo.ListByGroupID(ctx, groupID, params) + subs, pag, err := s.userSubRepo.ListByGroupID(ctx, groupID, params) + if err != nil { + return nil, nil, err + } + normalizeExpiredWindows(subs) + return subs, pag, nil } // List 获取所有订阅(分页,支持筛选) func (s *SubscriptionService) List(ctx context.Context, page, pageSize int, userID, groupID *int64, status string) ([]model.UserSubscription, *pagination.PaginationResult, error) { params := pagination.PaginationParams{Page: page, PageSize: pageSize} - return s.userSubRepo.List(ctx, params, userID, groupID, status) + subs, pag, err := s.userSubRepo.List(ctx, params, userID, groupID, status) + if err != nil { + return nil, nil, err + } + normalizeExpiredWindows(subs) + return subs, pag, nil +} + +// normalizeExpiredWindows 将已过期窗口的数据清零(仅影响返回数据,不影响数据库) +// 这确保前端显示正确的当前窗口状态,而不是过期窗口的历史数据 +func normalizeExpiredWindows(subs []model.UserSubscription) { + for i := range subs { + sub := &subs[i] + // 日窗口过期:清零展示数据 + if sub.NeedsDailyReset() { + sub.DailyWindowStart = nil + sub.DailyUsageUSD = 0 + } + // 周窗口过期:清零展示数据 + if sub.NeedsWeeklyReset() { + sub.WeeklyWindowStart = nil + sub.WeeklyUsageUSD = 0 + } + // 月窗口过期:清零展示数据 + if sub.NeedsMonthlyReset() { + sub.MonthlyWindowStart = nil + sub.MonthlyUsageUSD = 0 + } + } } // startOfDay 返回给定时间所在日期的零点(保持原时区) diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index e699d3c1..ba766465 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -582,6 +582,7 @@ export default { monthly: 'Monthly', noLimits: 'No limits configured', resetNow: 'Resetting soon', + windowNotActive: 'Window not active', resetInMinutes: 'Resets in {minutes}m', resetInHoursMinutes: 'Resets in {hours}h {minutes}m', resetInDaysHours: 'Resets in {days}d {hours}h', @@ -1121,6 +1122,7 @@ export default { daysRemaining: '{days} days remaining', expiresOn: 'Expires on {date}', resetIn: 'Resets in {time}', + windowNotActive: 'Awaiting first use', usageOf: '{used} of {limit}', }, } diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts index f8bc5f66..539cda34 100644 --- a/frontend/src/i18n/locales/zh.ts +++ b/frontend/src/i18n/locales/zh.ts @@ -639,6 +639,7 @@ export default { monthly: '每月', noLimits: '未配置限额', resetNow: '即将重置', + windowNotActive: '窗口未激活', resetInMinutes: '{minutes} 分钟后重置', resetInHoursMinutes: '{hours} 小时 {minutes} 分钟后重置', resetInDaysHours: '{days} 天 {hours} 小时后重置', @@ -1302,6 +1303,7 @@ export default { daysRemaining: '剩余 {days} 天', expiresOn: '{date} 到期', resetIn: '{time} 后重置', + windowNotActive: '等待首次使用', usageOf: '已用 {used} / {limit}', }, } diff --git a/frontend/src/views/admin/SubscriptionsView.vue b/frontend/src/views/admin/SubscriptionsView.vue index 19aa0c63..5621db2a 100644 --- a/frontend/src/views/admin/SubscriptionsView.vue +++ b/frontend/src/views/admin/SubscriptionsView.vue @@ -592,6 +592,8 @@ const getProgressClass = (used: number, limit: number | null): string => { // Format reset time based on window start and period type const formatResetTime = (windowStart: string, period: 'daily' | 'weekly' | 'monthly'): string => { + if (!windowStart) return t('admin.subscriptions.windowNotActive') + const start = new Date(windowStart) const now = new Date() @@ -610,7 +612,7 @@ const formatResetTime = (windowStart: string, period: 'daily' | 'weekly' | 'mont } const diffMs = resetTime.getTime() - now.getTime() - if (diffMs <= 0) return t('admin.subscriptions.resetNow') + if (diffMs <= 0) return t('admin.subscriptions.windowNotActive') const diffSeconds = Math.floor(diffMs / 1000) const days = Math.floor(diffSeconds / 86400) diff --git a/frontend/src/views/user/SubscriptionsView.vue b/frontend/src/views/user/SubscriptionsView.vue index acf5996c..a96935cb 100644 --- a/frontend/src/views/user/SubscriptionsView.vue +++ b/frontend/src/views/user/SubscriptionsView.vue @@ -229,14 +229,14 @@ function getExpirationClass(expiresAt: string): string { } function formatResetTime(windowStart: string | null, windowHours: number): string { - if (!windowStart) return '--'; + if (!windowStart) return t('userSubscriptions.windowNotActive'); const start = new Date(windowStart); const end = new Date(start.getTime() + windowHours * 60 * 60 * 1000); const now = new Date(); const diff = end.getTime() - now.getTime(); - if (diff <= 0) return 'Now'; + if (diff <= 0) return t('userSubscriptions.windowNotActive'); const hours = Math.floor(diff / (1000 * 60 * 60)); const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));