fix(channel): 统一渠道分析时间筛选范围

让渠道分析弹窗的概览卡片和趋势图共用同一时间筛选范围,
并将默认行为调整为历史全量展示。前端改为在未选择日期时
不再回退最近 12 天,后端新增统一时间窗口解析逻辑,使
overview 与 daily 在全量、指定区间和 days 模式下保持一致。
This commit is contained in:
Zuncle 2026-04-09 17:51:42 +08:00
parent 03214dddf2
commit fb4b266bac

View File

@ -396,6 +396,46 @@ func (s *service) List(ctx context.Context, in ListInput) (items []*ChannelWithS
return return
} }
func (s *service) resolveStatsRange(ctx context.Context, channelID int64, days int, startDateStr, endDateStr string, now time.Time) (*time.Time, *time.Time, error) {
if startDateStr != "" && endDateStr != "" {
startDate, err := time.ParseInLocation("2006-01-02", startDateStr, time.Local)
if err != nil {
return nil, nil, err
}
endDate, err := time.ParseInLocation("2006-01-02", endDateStr, time.Local)
if err != nil {
return nil, nil, err
}
endDate = endDate.Add(24*time.Hour - time.Second)
return &startDate, &endDate, nil
}
if days > 0 {
startDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).AddDate(0, 0, -days+1)
endDate := now
return &startDate, &endDate, nil
}
type minTimeRow struct {
MinCreatedAt *time.Time `gorm:"column:min_created_at"`
}
var row minTimeRow
err := s.readDB.Orders.WithContext(ctx).UnderlyingDB().Table("orders").
Joins("JOIN users ON users.id = orders.user_id").
Select("MIN(orders.created_at) as min_created_at").
Where("users.channel_id = ? AND users.deleted_at IS NULL AND orders.status = 2 AND orders.total_amount > 0 AND orders.actual_amount > 0 AND orders.source_type IN (2,3,4) AND (orders.ext_order_id = '' OR orders.ext_order_id IS NULL)", channelID).
Scan(&row).Error
if err != nil {
return nil, nil, err
}
if row.MinCreatedAt == nil {
return nil, nil, nil
}
startDate := time.Date(row.MinCreatedAt.Year(), row.MinCreatedAt.Month(), row.MinCreatedAt.Day(), 0, 0, 0, 0, now.Location())
endDate := now
return &startDate, &endDate, nil
}
func (s *service) GetStats(ctx context.Context, channelID int64, days int, startDateStr, endDateStr string) (*StatsOutput, error) { func (s *service) GetStats(ctx context.Context, channelID int64, days int, startDateStr, endDateStr string) (*StatsOutput, error) {
now := time.Now() now := time.Now()
@ -411,49 +451,53 @@ func (s *service) GetStats(ctx context.Context, channelID int64, days int, start
// source_type: 2=小程序抽奖 3=对对碰 4=一番赏/次卡 5=直播间抽奖抖店(不计入);排除商城直购(1) // source_type: 2=小程序抽奖 3=对对碰 4=一番赏/次卡 5=直播间抽奖抖店(不计入);排除商城直购(1)
// actual_amount>0 排除次卡免费使用的订单避免与购买次卡的订单重复计入GMV // actual_amount>0 排除次卡免费使用的订单避免与购买次卡的订单重复计入GMV
orderFilter := "users.channel_id = ? AND users.deleted_at IS NULL AND orders.status = 2 AND orders.total_amount > 0 AND orders.actual_amount > 0 AND orders.source_type IN (2,3,4) AND (orders.ext_order_id = '' OR orders.ext_order_id IS NULL)" orderFilter := "users.channel_id = ? AND users.deleted_at IS NULL AND orders.status = 2 AND orders.total_amount > 0 AND orders.actual_amount > 0 AND orders.source_type IN (2,3,4) AND (orders.ext_order_id = '' OR orders.ext_order_id IS NULL)"
statsStart, statsEnd, err := s.resolveStatsRange(ctx, channelID, days, startDateStr, endDateStr, now)
if err != nil {
return nil, err
}
// ========== 1. Overview全量不限时间========== // ========== 1. Overview与当前筛选窗口保持一致==========
userCount, _ := s.readDB.Users.WithContext(ctx).Where(s.readDB.Users.ChannelID.Eq(channelID)).Count() userCountQuery := s.readDB.Users.WithContext(ctx)
if statsStart != nil && statsEnd != nil {
userCountQuery = userCountQuery.Where(s.readDB.Users.CreatedAt.Gte(*statsStart)).Where(s.readDB.Users.CreatedAt.Lte(*statsEnd))
}
userCount, _ := userCountQuery.Where(s.readDB.Users.ChannelID.Eq(channelID)).Count()
out.Overview.TotalUsers = userCount out.Overview.TotalUsers = userCount
type countResult struct{ Count int64 } type countResult struct{ Count int64 }
var cr countResult var cr countResult
s.readDB.Orders.WithContext(ctx).UnderlyingDB().Table("orders"). overviewOrders := s.readDB.Orders.WithContext(ctx).UnderlyingDB().Table("orders").
Joins("JOIN users ON users.id = orders.user_id"). Joins("JOIN users ON users.id = orders.user_id").
Select("count(*) as count"). Select("count(*) as count").
Where(orderFilter, channelID). Where(orderFilter, channelID)
Scan(&cr) if statsStart != nil && statsEnd != nil {
overviewOrders = overviewOrders.Where("orders.created_at >= ? AND orders.created_at <= ?", *statsStart, *statsEnd)
}
overviewOrders.Scan(&cr)
out.Overview.TotalOrders = cr.Count out.Overview.TotalOrders = cr.Count
totalGMV, _ := s.calcGMVByTotalAmount(ctx, channelID, "2006-01-02", orderFilter, nil, nil) totalGMV, _ := s.calcGMVByTotalAmount(ctx, channelID, "2006-01-02", orderFilter, statsStart, statsEnd)
out.Overview.TotalPaidCents = totalGMV.Total out.Overview.TotalPaidCents = totalGMV.Total
out.Overview.TotalGMV = totalGMV.Total / 100 out.Overview.TotalGMV = totalGMV.Total / 100
out.Overview.CashCents = totalGMV.Cash out.Overview.CashCents = totalGMV.Cash
out.Overview.CouponCents = totalGMV.Coupon out.Overview.CouponCents = totalGMV.Coupon
out.Overview.PointsCents = totalGMV.Points out.Overview.PointsCents = totalGMV.Points
// 1d. 累计成本(全量,按原始订单/抽奖来源归因) // 1d. 累计成本(当前筛选窗口,按原始订单/抽奖来源归因)
totalCost, _ := s.calcCostByDrawSource(ctx, channelID, "2006-01-02", nil, nil) totalCost, _ := s.calcCostByDrawSource(ctx, channelID, "2006-01-02", statsStart, statsEnd)
out.Overview.TotalCostCents = totalCost out.Overview.TotalCostCents = totalCost
out.Overview.TotalCost = totalCost / 100 out.Overview.TotalCost = totalCost / 100
out.Overview.TotalProfitCents = totalGMV.Total - totalCost out.Overview.TotalProfitCents = totalGMV.Total - totalCost
out.Overview.TotalProfit = out.Overview.TotalProfitCents / 100 out.Overview.TotalProfit = out.Overview.TotalProfitCents / 100
// ========== 2. 趋势图(按天分组,受 days 限制========== // ========== 2. 趋势图(按天分组,与 overview 保持相同时间窗口==========
var startDate, endDate time.Time if statsStart == nil || statsEnd == nil {
if startDateStr != "" && endDateStr != "" { return out, nil
startDate, _ = time.Parse("2006-01-02", startDateStr)
endDate, _ = time.Parse("2006-01-02", endDateStr)
endDate = endDate.Add(24*time.Hour - time.Second)
} else {
if days <= 0 {
days = 12
}
startDate = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).AddDate(0, 0, -days+1)
endDate = now
} }
startDate := *statsStart
endDate := *statsEnd
dateMap := make(map[string]*StatsDailyItem) dateMap := make(map[string]*StatsDailyItem)
var dateList []string var dateList []string