feat: deepen report metrics
This commit is contained in:
@@ -19,6 +19,16 @@ type ReportOverviewResponse struct {
|
|||||||
type ReportSummary struct {
|
type ReportSummary struct {
|
||||||
// TotalViews 内容累计曝光(全量累计值,用于粗略换算)。
|
// TotalViews 内容累计曝光(全量累计值,用于粗略换算)。
|
||||||
TotalViews int64 `json:"total_views"`
|
TotalViews int64 `json:"total_views"`
|
||||||
|
// ContentCount 内容总量(当前快照)。
|
||||||
|
ContentCount int64 `json:"content_count"`
|
||||||
|
// ContentCreated 统计区间内新增内容数。
|
||||||
|
ContentCreated int64 `json:"content_created"`
|
||||||
|
// LikeActions 统计区间内新增点赞数(基于互动记录)。
|
||||||
|
LikeActions int64 `json:"like_actions"`
|
||||||
|
// FavoriteActions 统计区间内新增收藏数(基于互动记录)。
|
||||||
|
FavoriteActions int64 `json:"favorite_actions"`
|
||||||
|
// CommentCount 统计区间内新增评论数。
|
||||||
|
CommentCount int64 `json:"comment_count"`
|
||||||
// PaidOrders 统计区间内已支付订单数。
|
// PaidOrders 统计区间内已支付订单数。
|
||||||
PaidOrders int64 `json:"paid_orders"`
|
PaidOrders int64 `json:"paid_orders"`
|
||||||
// PaidAmount 统计区间内已支付金额(单位元)。
|
// PaidAmount 统计区间内已支付金额(单位元)。
|
||||||
@@ -27,6 +37,18 @@ type ReportSummary struct {
|
|||||||
RefundOrders int64 `json:"refund_orders"`
|
RefundOrders int64 `json:"refund_orders"`
|
||||||
// RefundAmount 统计区间内退款金额(单位元)。
|
// RefundAmount 统计区间内退款金额(单位元)。
|
||||||
RefundAmount float64 `json:"refund_amount"`
|
RefundAmount float64 `json:"refund_amount"`
|
||||||
|
// WithdrawalApplyOrders 统计区间内提现申请订单数。
|
||||||
|
WithdrawalApplyOrders int64 `json:"withdrawal_apply_orders"`
|
||||||
|
// WithdrawalApplyAmount 统计区间内提现申请金额(单位元)。
|
||||||
|
WithdrawalApplyAmount float64 `json:"withdrawal_apply_amount"`
|
||||||
|
// WithdrawalPaidOrders 统计区间内提现完成订单数。
|
||||||
|
WithdrawalPaidOrders int64 `json:"withdrawal_paid_orders"`
|
||||||
|
// WithdrawalPaidAmount 统计区间内提现完成金额(单位元)。
|
||||||
|
WithdrawalPaidAmount float64 `json:"withdrawal_paid_amount"`
|
||||||
|
// WithdrawalFailedOrders 统计区间内提现失败订单数。
|
||||||
|
WithdrawalFailedOrders int64 `json:"withdrawal_failed_orders"`
|
||||||
|
// WithdrawalFailedAmount 统计区间内提现失败金额(单位元)。
|
||||||
|
WithdrawalFailedAmount float64 `json:"withdrawal_failed_amount"`
|
||||||
// ConversionRate 转化率(已支付订单数 / 累计曝光)。
|
// ConversionRate 转化率(已支付订单数 / 累计曝光)。
|
||||||
ConversionRate float64 `json:"conversion_rate"`
|
ConversionRate float64 `json:"conversion_rate"`
|
||||||
}
|
}
|
||||||
@@ -42,6 +64,26 @@ type ReportOverviewItem struct {
|
|||||||
RefundOrders int64 `json:"refund_orders"`
|
RefundOrders int64 `json:"refund_orders"`
|
||||||
// RefundAmount 当日退款金额(单位元)。
|
// RefundAmount 当日退款金额(单位元)。
|
||||||
RefundAmount float64 `json:"refund_amount"`
|
RefundAmount float64 `json:"refund_amount"`
|
||||||
|
// WithdrawalApplyOrders 当日提现申请订单数。
|
||||||
|
WithdrawalApplyOrders int64 `json:"withdrawal_apply_orders"`
|
||||||
|
// WithdrawalApplyAmount 当日提现申请金额(单位元)。
|
||||||
|
WithdrawalApplyAmount float64 `json:"withdrawal_apply_amount"`
|
||||||
|
// WithdrawalPaidOrders 当日提现完成订单数。
|
||||||
|
WithdrawalPaidOrders int64 `json:"withdrawal_paid_orders"`
|
||||||
|
// WithdrawalPaidAmount 当日提现完成金额(单位元)。
|
||||||
|
WithdrawalPaidAmount float64 `json:"withdrawal_paid_amount"`
|
||||||
|
// WithdrawalFailedOrders 当日提现失败订单数。
|
||||||
|
WithdrawalFailedOrders int64 `json:"withdrawal_failed_orders"`
|
||||||
|
// WithdrawalFailedAmount 当日提现失败金额(单位元)。
|
||||||
|
WithdrawalFailedAmount float64 `json:"withdrawal_failed_amount"`
|
||||||
|
// ContentCreated 当日新增内容数。
|
||||||
|
ContentCreated int64 `json:"content_created"`
|
||||||
|
// LikeActions 当日新增点赞数(基于互动记录)。
|
||||||
|
LikeActions int64 `json:"like_actions"`
|
||||||
|
// FavoriteActions 当日新增收藏数(基于互动记录)。
|
||||||
|
FavoriteActions int64 `json:"favorite_actions"`
|
||||||
|
// CommentCount 当日新增评论数。
|
||||||
|
CommentCount int64 `json:"comment_count"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReportExportForm struct {
|
type ReportExportForm struct {
|
||||||
|
|||||||
@@ -49,12 +49,48 @@ func (s *creator) ReportOverview(
|
|||||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 订单仅统计内容购买类型,并按状态划分已支付/已退款。
|
// 内容规模与互动指标。
|
||||||
paidCount, paidAmount, err := s.orderAggregate(ctx, tid, consts.OrderStatusPaid, "paid_at", rg)
|
contentCount, err := s.contentCount(ctx, tid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
refundCount, refundAmount, err := s.orderAggregate(ctx, tid, consts.OrderStatusRefunded, "updated_at", rg)
|
contentCreated, err := s.contentCreatedAggregate(ctx, tid, rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
likeActions, err := s.contentActionAggregate(ctx, tid, consts.UserContentActionTypeLike, rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
favoriteActions, err := s.contentActionAggregate(ctx, tid, consts.UserContentActionTypeFavorite, rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
commentCount, err := s.commentAggregate(ctx, tid, rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订单仅统计内容购买类型,并按状态划分已支付/已退款。
|
||||||
|
paidCount, paidAmount, err := s.orderAggregate(ctx, tid, consts.OrderTypeContentPurchase, consts.OrderStatusPaid, "paid_at", rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
refundCount, refundAmount, err := s.orderAggregate(ctx, tid, consts.OrderTypeContentPurchase, consts.OrderStatusRefunded, "updated_at", rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提现维度统计(申请/完成/失败)。
|
||||||
|
withdrawApplyCount, withdrawApplyAmount, err := s.orderAggregate(ctx, tid, consts.OrderTypeWithdrawal, consts.OrderStatusCreated, "created_at", rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
withdrawPaidCount, withdrawPaidAmount, err := s.orderAggregate(ctx, tid, consts.OrderTypeWithdrawal, consts.OrderStatusPaid, "paid_at", rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
withdrawFailedCount, withdrawFailedAmount, err := s.orderAggregate(ctx, tid, consts.OrderTypeWithdrawal, consts.OrderStatusFailed, "updated_at", rg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -65,11 +101,39 @@ func (s *creator) ReportOverview(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 生成按日趋势序列。
|
// 生成按日趋势序列。
|
||||||
paidSeries, err := s.orderSeries(ctx, tid, consts.OrderStatusPaid, "paid_at", rg)
|
paidSeries, err := s.orderSeries(ctx, tid, consts.OrderTypeContentPurchase, consts.OrderStatusPaid, "paid_at", rg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
refundSeries, err := s.orderSeries(ctx, tid, consts.OrderStatusRefunded, "updated_at", rg)
|
refundSeries, err := s.orderSeries(ctx, tid, consts.OrderTypeContentPurchase, consts.OrderStatusRefunded, "updated_at", rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
withdrawApplySeries, err := s.orderSeries(ctx, tid, consts.OrderTypeWithdrawal, consts.OrderStatusCreated, "created_at", rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
withdrawPaidSeries, err := s.orderSeries(ctx, tid, consts.OrderTypeWithdrawal, consts.OrderStatusPaid, "paid_at", rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
withdrawFailedSeries, err := s.orderSeries(ctx, tid, consts.OrderTypeWithdrawal, consts.OrderStatusFailed, "updated_at", rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
contentCreatedSeries, err := s.contentCreatedSeries(ctx, tid, rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
likeSeries, err := s.contentActionSeries(ctx, tid, consts.UserContentActionTypeLike, rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
favoriteSeries, err := s.contentActionSeries(ctx, tid, consts.UserContentActionTypeFavorite, rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
commentSeries, err := s.commentSeries(ctx, tid, rg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -79,22 +143,46 @@ func (s *creator) ReportOverview(
|
|||||||
key := day.Format("2006-01-02")
|
key := day.Format("2006-01-02")
|
||||||
paidItem := paidSeries[key]
|
paidItem := paidSeries[key]
|
||||||
refundItem := refundSeries[key]
|
refundItem := refundSeries[key]
|
||||||
|
withdrawApplyItem := withdrawApplySeries[key]
|
||||||
|
withdrawPaidItem := withdrawPaidSeries[key]
|
||||||
|
withdrawFailedItem := withdrawFailedSeries[key]
|
||||||
items = append(items, creator_dto.ReportOverviewItem{
|
items = append(items, creator_dto.ReportOverviewItem{
|
||||||
Date: key,
|
Date: key,
|
||||||
PaidOrders: paidItem.Count,
|
PaidOrders: paidItem.Count,
|
||||||
PaidAmount: float64(paidItem.Amount) / 100.0,
|
PaidAmount: float64(paidItem.Amount) / 100.0,
|
||||||
RefundOrders: refundItem.Count,
|
RefundOrders: refundItem.Count,
|
||||||
RefundAmount: float64(refundItem.Amount) / 100.0,
|
RefundAmount: float64(refundItem.Amount) / 100.0,
|
||||||
|
WithdrawalApplyOrders: withdrawApplyItem.Count,
|
||||||
|
WithdrawalApplyAmount: float64(withdrawApplyItem.Amount) / 100.0,
|
||||||
|
WithdrawalPaidOrders: withdrawPaidItem.Count,
|
||||||
|
WithdrawalPaidAmount: float64(withdrawPaidItem.Amount) / 100.0,
|
||||||
|
WithdrawalFailedOrders: withdrawFailedItem.Count,
|
||||||
|
WithdrawalFailedAmount: float64(withdrawFailedItem.Amount) / 100.0,
|
||||||
|
ContentCreated: contentCreatedSeries[key],
|
||||||
|
LikeActions: likeSeries[key],
|
||||||
|
FavoriteActions: favoriteSeries[key],
|
||||||
|
CommentCount: commentSeries[key],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return &creator_dto.ReportOverviewResponse{
|
return &creator_dto.ReportOverviewResponse{
|
||||||
Summary: creator_dto.ReportSummary{
|
Summary: creator_dto.ReportSummary{
|
||||||
TotalViews: totalViews,
|
TotalViews: totalViews,
|
||||||
|
ContentCount: contentCount,
|
||||||
|
ContentCreated: contentCreated,
|
||||||
|
LikeActions: likeActions,
|
||||||
|
FavoriteActions: favoriteActions,
|
||||||
|
CommentCount: commentCount,
|
||||||
PaidOrders: paidCount,
|
PaidOrders: paidCount,
|
||||||
PaidAmount: float64(paidAmount) / 100.0,
|
PaidAmount: float64(paidAmount) / 100.0,
|
||||||
RefundOrders: refundCount,
|
RefundOrders: refundCount,
|
||||||
RefundAmount: float64(refundAmount) / 100.0,
|
RefundAmount: float64(refundAmount) / 100.0,
|
||||||
|
WithdrawalApplyOrders: withdrawApplyCount,
|
||||||
|
WithdrawalApplyAmount: float64(withdrawApplyAmount) / 100.0,
|
||||||
|
WithdrawalPaidOrders: withdrawPaidCount,
|
||||||
|
WithdrawalPaidAmount: float64(withdrawPaidAmount) / 100.0,
|
||||||
|
WithdrawalFailedOrders: withdrawFailedCount,
|
||||||
|
WithdrawalFailedAmount: float64(withdrawFailedAmount) / 100.0,
|
||||||
ConversionRate: conversionRate,
|
ConversionRate: conversionRate,
|
||||||
},
|
},
|
||||||
Items: items,
|
Items: items,
|
||||||
@@ -129,7 +217,7 @@ func (s *creator) ExportReport(
|
|||||||
}
|
}
|
||||||
|
|
||||||
builder := &strings.Builder{}
|
builder := &strings.Builder{}
|
||||||
builder.WriteString("date,paid_orders,paid_amount,refund_orders,refund_amount\n")
|
builder.WriteString("date,paid_orders,paid_amount,refund_orders,refund_amount,withdrawal_apply_orders,withdrawal_apply_amount,withdrawal_paid_orders,withdrawal_paid_amount,withdrawal_failed_orders,withdrawal_failed_amount,content_created,like_actions,favorite_actions,comment_count\n")
|
||||||
for _, item := range overview.Items {
|
for _, item := range overview.Items {
|
||||||
builder.WriteString(item.Date)
|
builder.WriteString(item.Date)
|
||||||
builder.WriteString(",")
|
builder.WriteString(",")
|
||||||
@@ -140,6 +228,26 @@ func (s *creator) ExportReport(
|
|||||||
builder.WriteString(strconv.FormatInt(item.RefundOrders, 10))
|
builder.WriteString(strconv.FormatInt(item.RefundOrders, 10))
|
||||||
builder.WriteString(",")
|
builder.WriteString(",")
|
||||||
builder.WriteString(formatAmount(item.RefundAmount))
|
builder.WriteString(formatAmount(item.RefundAmount))
|
||||||
|
builder.WriteString(",")
|
||||||
|
builder.WriteString(strconv.FormatInt(item.WithdrawalApplyOrders, 10))
|
||||||
|
builder.WriteString(",")
|
||||||
|
builder.WriteString(formatAmount(item.WithdrawalApplyAmount))
|
||||||
|
builder.WriteString(",")
|
||||||
|
builder.WriteString(strconv.FormatInt(item.WithdrawalPaidOrders, 10))
|
||||||
|
builder.WriteString(",")
|
||||||
|
builder.WriteString(formatAmount(item.WithdrawalPaidAmount))
|
||||||
|
builder.WriteString(",")
|
||||||
|
builder.WriteString(strconv.FormatInt(item.WithdrawalFailedOrders, 10))
|
||||||
|
builder.WriteString(",")
|
||||||
|
builder.WriteString(formatAmount(item.WithdrawalFailedAmount))
|
||||||
|
builder.WriteString(",")
|
||||||
|
builder.WriteString(strconv.FormatInt(item.ContentCreated, 10))
|
||||||
|
builder.WriteString(",")
|
||||||
|
builder.WriteString(strconv.FormatInt(item.LikeActions, 10))
|
||||||
|
builder.WriteString(",")
|
||||||
|
builder.WriteString(strconv.FormatInt(item.FavoriteActions, 10))
|
||||||
|
builder.WriteString(",")
|
||||||
|
builder.WriteString(strconv.FormatInt(item.CommentCount, 10))
|
||||||
builder.WriteString("\n")
|
builder.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,6 +268,7 @@ type reportAggRow struct {
|
|||||||
func (s *creator) orderAggregate(
|
func (s *creator) orderAggregate(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
tenantID int64,
|
tenantID int64,
|
||||||
|
orderType consts.OrderType,
|
||||||
status consts.OrderStatus,
|
status consts.OrderStatus,
|
||||||
timeField string,
|
timeField string,
|
||||||
rg reportRange,
|
rg reportRange,
|
||||||
@@ -173,7 +282,7 @@ func (s *creator) orderAggregate(
|
|||||||
Model(&models.Order{}).
|
Model(&models.Order{}).
|
||||||
Select("count(*) as count, coalesce(sum(amount_paid), 0) as amount").
|
Select("count(*) as count, coalesce(sum(amount_paid), 0) as amount").
|
||||||
Where("tenant_id = ? AND type = ? AND status = ? AND "+timeField+" >= ? AND "+timeField+" < ?",
|
Where("tenant_id = ? AND type = ? AND status = ? AND "+timeField+" >= ? AND "+timeField+" < ?",
|
||||||
tenantID, consts.OrderTypeContentPurchase, status, rg.startDay, rg.endNext).
|
tenantID, orderType, status, rg.startDay, rg.endNext).
|
||||||
Scan(&total).Error
|
Scan(&total).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, errorx.ErrDatabaseError.WithCause(err)
|
return 0, 0, errorx.ErrDatabaseError.WithCause(err)
|
||||||
@@ -184,6 +293,7 @@ func (s *creator) orderAggregate(
|
|||||||
func (s *creator) orderSeries(
|
func (s *creator) orderSeries(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
tenantID int64,
|
tenantID int64,
|
||||||
|
orderType consts.OrderType,
|
||||||
status consts.OrderStatus,
|
status consts.OrderStatus,
|
||||||
timeField string,
|
timeField string,
|
||||||
rg reportRange,
|
rg reportRange,
|
||||||
@@ -194,7 +304,7 @@ func (s *creator) orderSeries(
|
|||||||
Model(&models.Order{}).
|
Model(&models.Order{}).
|
||||||
Select("date_trunc('day', "+timeField+") as day, count(*) as count, coalesce(sum(amount_paid), 0) as amount").
|
Select("date_trunc('day', "+timeField+") as day, count(*) as count, coalesce(sum(amount_paid), 0) as amount").
|
||||||
Where("tenant_id = ? AND type = ? AND status = ? AND "+timeField+" >= ? AND "+timeField+" < ?",
|
Where("tenant_id = ? AND type = ? AND status = ? AND "+timeField+" >= ? AND "+timeField+" < ?",
|
||||||
tenantID, consts.OrderTypeContentPurchase, status, rg.startDay, rg.endNext).
|
tenantID, orderType, status, rg.startDay, rg.endNext).
|
||||||
Group("day").
|
Group("day").
|
||||||
Scan(&rows).Error
|
Scan(&rows).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -209,6 +319,127 @@ func (s *creator) orderSeries(
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type reportCountRow struct {
|
||||||
|
Day time.Time `gorm:"column:day"`
|
||||||
|
Count int64 `gorm:"column:count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *creator) contentCount(ctx context.Context, tenantID int64) (int64, error) {
|
||||||
|
tbl, q := models.ContentQuery.QueryContext(ctx)
|
||||||
|
total, err := q.Where(tbl.TenantID.Eq(tenantID)).Count()
|
||||||
|
if err != nil {
|
||||||
|
return 0, errorx.ErrDatabaseError.WithCause(err)
|
||||||
|
}
|
||||||
|
return total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *creator) contentCreatedAggregate(ctx context.Context, tenantID int64, rg reportRange) (int64, error) {
|
||||||
|
tbl, q := models.ContentQuery.QueryContext(ctx)
|
||||||
|
total, err := q.Where(
|
||||||
|
tbl.TenantID.Eq(tenantID),
|
||||||
|
tbl.CreatedAt.Gte(rg.startDay),
|
||||||
|
tbl.CreatedAt.Lt(rg.endNext),
|
||||||
|
).Count()
|
||||||
|
if err != nil {
|
||||||
|
return 0, errorx.ErrDatabaseError.WithCause(err)
|
||||||
|
}
|
||||||
|
return total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *creator) contentCreatedSeries(ctx context.Context, tenantID int64, rg reportRange) (map[string]int64, error) {
|
||||||
|
rows := make([]reportCountRow, 0)
|
||||||
|
err := models.ContentQuery.WithContext(ctx).
|
||||||
|
UnderlyingDB().
|
||||||
|
Model(&models.Content{}).
|
||||||
|
Select("date_trunc('day', created_at) as day, count(*) as count").
|
||||||
|
Where("tenant_id = ? AND created_at >= ? AND created_at < ?", tenantID, rg.startDay, rg.endNext).
|
||||||
|
Group("day").
|
||||||
|
Scan(&rows).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
|
}
|
||||||
|
return buildCountSeries(rows), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *creator) contentActionAggregate(
|
||||||
|
ctx context.Context,
|
||||||
|
tenantID int64,
|
||||||
|
actionType consts.UserContentActionType,
|
||||||
|
rg reportRange,
|
||||||
|
) (int64, error) {
|
||||||
|
var total int64
|
||||||
|
query := models.UserContentActionQuery.WithContext(ctx).
|
||||||
|
UnderlyingDB().
|
||||||
|
Model(&models.UserContentAction{}).
|
||||||
|
Select("count(*)").
|
||||||
|
Joins("join contents on contents.id = user_content_actions.content_id").
|
||||||
|
Where("user_content_actions.type = ? AND user_content_actions.created_at >= ? AND user_content_actions.created_at < ? AND contents.tenant_id = ?",
|
||||||
|
actionType, rg.startDay, rg.endNext, tenantID)
|
||||||
|
if err := query.Scan(&total).Error; err != nil {
|
||||||
|
return 0, errorx.ErrDatabaseError.WithCause(err)
|
||||||
|
}
|
||||||
|
return total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *creator) contentActionSeries(
|
||||||
|
ctx context.Context,
|
||||||
|
tenantID int64,
|
||||||
|
actionType consts.UserContentActionType,
|
||||||
|
rg reportRange,
|
||||||
|
) (map[string]int64, error) {
|
||||||
|
rows := make([]reportCountRow, 0)
|
||||||
|
err := models.UserContentActionQuery.WithContext(ctx).
|
||||||
|
UnderlyingDB().
|
||||||
|
Model(&models.UserContentAction{}).
|
||||||
|
Select("date_trunc('day', user_content_actions.created_at) as day, count(*) as count").
|
||||||
|
Joins("join contents on contents.id = user_content_actions.content_id").
|
||||||
|
Where("user_content_actions.type = ? AND user_content_actions.created_at >= ? AND user_content_actions.created_at < ? AND contents.tenant_id = ?",
|
||||||
|
actionType, rg.startDay, rg.endNext, tenantID).
|
||||||
|
Group("day").
|
||||||
|
Scan(&rows).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
|
}
|
||||||
|
return buildCountSeries(rows), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *creator) commentAggregate(ctx context.Context, tenantID int64, rg reportRange) (int64, error) {
|
||||||
|
tbl, q := models.CommentQuery.QueryContext(ctx)
|
||||||
|
total, err := q.Where(
|
||||||
|
tbl.TenantID.Eq(tenantID),
|
||||||
|
tbl.CreatedAt.Gte(rg.startDay),
|
||||||
|
tbl.CreatedAt.Lt(rg.endNext),
|
||||||
|
).Count()
|
||||||
|
if err != nil {
|
||||||
|
return 0, errorx.ErrDatabaseError.WithCause(err)
|
||||||
|
}
|
||||||
|
return total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *creator) commentSeries(ctx context.Context, tenantID int64, rg reportRange) (map[string]int64, error) {
|
||||||
|
rows := make([]reportCountRow, 0)
|
||||||
|
err := models.CommentQuery.WithContext(ctx).
|
||||||
|
UnderlyingDB().
|
||||||
|
Model(&models.Comment{}).
|
||||||
|
Select("date_trunc('day', created_at) as day, count(*) as count").
|
||||||
|
Where("tenant_id = ? AND created_at >= ? AND created_at < ?", tenantID, rg.startDay, rg.endNext).
|
||||||
|
Group("day").
|
||||||
|
Scan(&rows).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
|
}
|
||||||
|
return buildCountSeries(rows), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildCountSeries(rows []reportCountRow) map[string]int64 {
|
||||||
|
result := make(map[string]int64, len(rows))
|
||||||
|
for _, row := range rows {
|
||||||
|
key := row.Day.Format("2006-01-02")
|
||||||
|
result[key] = row.Count
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func (s *creator) normalizeReportRange(filter *creator_dto.ReportOverviewFilter) (reportRange, error) {
|
func (s *creator) normalizeReportRange(filter *creator_dto.ReportOverviewFilter) (reportRange, error) {
|
||||||
granularity := "day"
|
granularity := "day"
|
||||||
if filter != nil && filter.Granularity != nil && strings.TrimSpace(*filter.Granularity) != "" {
|
if filter != nil && filter.Granularity != nil && strings.TrimSpace(*filter.Granularity) != "" {
|
||||||
|
|||||||
@@ -394,6 +394,8 @@ func (s *CreatorTestSuite) Test_ReportOverview() {
|
|||||||
models.TableNameUser,
|
models.TableNameUser,
|
||||||
models.TableNameContent,
|
models.TableNameContent,
|
||||||
models.TableNameOrder,
|
models.TableNameOrder,
|
||||||
|
models.TableNameUserContentAction,
|
||||||
|
models.TableNameComment,
|
||||||
)
|
)
|
||||||
|
|
||||||
owner := &models.User{Username: "owner_r", Phone: "13900001011"}
|
owner := &models.User{Username: "owner_r", Phone: "13900001011"}
|
||||||
@@ -405,18 +407,23 @@ func (s *CreatorTestSuite) Test_ReportOverview() {
|
|||||||
}
|
}
|
||||||
models.TenantQuery.WithContext(ctx).Create(tenant)
|
models.TenantQuery.WithContext(ctx).Create(tenant)
|
||||||
|
|
||||||
models.ContentQuery.WithContext(ctx).Create(&models.Content{
|
content := &models.Content{
|
||||||
TenantID: tenant.ID,
|
TenantID: tenant.ID,
|
||||||
UserID: owner.ID,
|
UserID: owner.ID,
|
||||||
Title: "Content A",
|
Title: "Content A",
|
||||||
Status: consts.ContentStatusPublished,
|
Status: consts.ContentStatusPublished,
|
||||||
Views: 100,
|
Views: 100,
|
||||||
})
|
}
|
||||||
|
models.ContentQuery.WithContext(ctx).Create(content)
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
inRangePaidAt := now.Add(-12 * time.Hour)
|
inRangePaidAt := now.Add(-12 * time.Hour)
|
||||||
outRangePaidAt := now.Add(-10 * 24 * time.Hour)
|
outRangePaidAt := now.Add(-10 * 24 * time.Hour)
|
||||||
|
|
||||||
|
likeAt := now.Add(-2 * time.Hour)
|
||||||
|
favoriteAt := now.Add(-3 * time.Hour)
|
||||||
|
commentAt := now.Add(-4 * time.Hour)
|
||||||
|
|
||||||
models.OrderQuery.WithContext(ctx).Create(
|
models.OrderQuery.WithContext(ctx).Create(
|
||||||
&models.Order{
|
&models.Order{
|
||||||
TenantID: tenant.ID,
|
TenantID: tenant.ID,
|
||||||
@@ -442,8 +449,55 @@ func (s *CreatorTestSuite) Test_ReportOverview() {
|
|||||||
AmountPaid: 500,
|
AmountPaid: 500,
|
||||||
UpdatedAt: now.Add(-6 * time.Hour),
|
UpdatedAt: now.Add(-6 * time.Hour),
|
||||||
},
|
},
|
||||||
|
&models.Order{
|
||||||
|
TenantID: tenant.ID,
|
||||||
|
UserID: owner.ID,
|
||||||
|
Type: consts.OrderTypeWithdrawal,
|
||||||
|
Status: consts.OrderStatusCreated,
|
||||||
|
AmountPaid: 300,
|
||||||
|
CreatedAt: now.Add(-5 * time.Hour),
|
||||||
|
},
|
||||||
|
&models.Order{
|
||||||
|
TenantID: tenant.ID,
|
||||||
|
UserID: owner.ID,
|
||||||
|
Type: consts.OrderTypeWithdrawal,
|
||||||
|
Status: consts.OrderStatusPaid,
|
||||||
|
AmountPaid: 800,
|
||||||
|
PaidAt: now.Add(-3 * time.Hour),
|
||||||
|
},
|
||||||
|
&models.Order{
|
||||||
|
TenantID: tenant.ID,
|
||||||
|
UserID: owner.ID,
|
||||||
|
Type: consts.OrderTypeWithdrawal,
|
||||||
|
Status: consts.OrderStatusFailed,
|
||||||
|
AmountPaid: 500,
|
||||||
|
UpdatedAt: now.Add(-2 * time.Hour),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
models.UserContentActionQuery.WithContext(ctx).Create(
|
||||||
|
&models.UserContentAction{
|
||||||
|
UserID: owner.ID,
|
||||||
|
ContentID: content.ID,
|
||||||
|
Type: string(consts.UserContentActionTypeLike),
|
||||||
|
CreatedAt: likeAt,
|
||||||
|
},
|
||||||
|
&models.UserContentAction{
|
||||||
|
UserID: owner.ID,
|
||||||
|
ContentID: content.ID,
|
||||||
|
Type: string(consts.UserContentActionTypeFavorite),
|
||||||
|
CreatedAt: favoriteAt,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
models.CommentQuery.WithContext(ctx).Create(&models.Comment{
|
||||||
|
TenantID: tenant.ID,
|
||||||
|
UserID: owner.ID,
|
||||||
|
ContentID: content.ID,
|
||||||
|
Content: "Nice",
|
||||||
|
CreatedAt: commentAt,
|
||||||
|
})
|
||||||
|
|
||||||
start := now.Add(-24 * time.Hour).Format(time.RFC3339)
|
start := now.Add(-24 * time.Hour).Format(time.RFC3339)
|
||||||
end := now.Format(time.RFC3339)
|
end := now.Format(time.RFC3339)
|
||||||
report, err := Creator.ReportOverview(ctx, tenant.ID, owner.ID, &creator_dto.ReportOverviewFilter{
|
report, err := Creator.ReportOverview(ctx, tenant.ID, owner.ID, &creator_dto.ReportOverviewFilter{
|
||||||
@@ -452,10 +506,21 @@ func (s *CreatorTestSuite) Test_ReportOverview() {
|
|||||||
})
|
})
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(report.Summary.TotalViews, ShouldEqual, 100)
|
So(report.Summary.TotalViews, ShouldEqual, 100)
|
||||||
|
So(report.Summary.ContentCount, ShouldEqual, 1)
|
||||||
|
So(report.Summary.ContentCreated, ShouldEqual, 1)
|
||||||
|
So(report.Summary.LikeActions, ShouldEqual, 1)
|
||||||
|
So(report.Summary.FavoriteActions, ShouldEqual, 1)
|
||||||
|
So(report.Summary.CommentCount, ShouldEqual, 1)
|
||||||
So(report.Summary.PaidOrders, ShouldEqual, 1)
|
So(report.Summary.PaidOrders, ShouldEqual, 1)
|
||||||
So(report.Summary.PaidAmount, ShouldEqual, 10.0)
|
So(report.Summary.PaidAmount, ShouldEqual, 10.0)
|
||||||
So(report.Summary.RefundOrders, ShouldEqual, 1)
|
So(report.Summary.RefundOrders, ShouldEqual, 1)
|
||||||
So(report.Summary.RefundAmount, ShouldEqual, 5.0)
|
So(report.Summary.RefundAmount, ShouldEqual, 5.0)
|
||||||
|
So(report.Summary.WithdrawalApplyOrders, ShouldEqual, 1)
|
||||||
|
So(report.Summary.WithdrawalApplyAmount, ShouldEqual, 3.0)
|
||||||
|
So(report.Summary.WithdrawalPaidOrders, ShouldEqual, 1)
|
||||||
|
So(report.Summary.WithdrawalPaidAmount, ShouldEqual, 8.0)
|
||||||
|
So(report.Summary.WithdrawalFailedOrders, ShouldEqual, 1)
|
||||||
|
So(report.Summary.WithdrawalFailedAmount, ShouldEqual, 5.0)
|
||||||
|
|
||||||
var paidSum, refundSum int64
|
var paidSum, refundSum int64
|
||||||
for _, item := range report.Items {
|
for _, item := range report.Items {
|
||||||
@@ -475,6 +540,8 @@ func (s *CreatorTestSuite) Test_ExportReport() {
|
|||||||
models.TableNameUser,
|
models.TableNameUser,
|
||||||
models.TableNameContent,
|
models.TableNameContent,
|
||||||
models.TableNameOrder,
|
models.TableNameOrder,
|
||||||
|
models.TableNameUserContentAction,
|
||||||
|
models.TableNameComment,
|
||||||
)
|
)
|
||||||
|
|
||||||
owner := &models.User{Username: "owner_e", Phone: "13900001012"}
|
owner := &models.User{Username: "owner_e", Phone: "13900001012"}
|
||||||
@@ -507,6 +574,6 @@ func (s *CreatorTestSuite) Test_ExportReport() {
|
|||||||
resp, err := Creator.ExportReport(ctx, tenant.ID, owner.ID, form)
|
resp, err := Creator.ExportReport(ctx, tenant.ID, owner.ID, form)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(resp.Filename, ShouldNotBeBlank)
|
So(resp.Filename, ShouldNotBeBlank)
|
||||||
So(resp.Content, ShouldContainSubstring, "date,paid_orders,paid_amount,refund_orders,refund_amount")
|
So(resp.Content, ShouldContainSubstring, "date,paid_orders,paid_amount,refund_orders,refund_amount,withdrawal_apply_orders,withdrawal_apply_amount,withdrawal_paid_orders,withdrawal_paid_amount,withdrawal_failed_orders,withdrawal_failed_amount,content_created,like_actions,favorite_actions,comment_count")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5553,12 +5553,48 @@ func (s *super) ReportOverview(ctx context.Context, filter *super_dto.SuperRepor
|
|||||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 订单仅统计内容购买类型,并按状态划分已支付/已退款。
|
// 内容规模与互动指标。
|
||||||
paidCount, paidAmount, err := s.reportOrderAggregate(ctx, tenantID, consts.OrderStatusPaid, "paid_at", rg)
|
contentCount, err := s.contentCount(ctx, tenantID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
refundCount, refundAmount, err := s.reportOrderAggregate(ctx, tenantID, consts.OrderStatusRefunded, "updated_at", rg)
|
contentCreated, err := s.contentCreatedAggregate(ctx, tenantID, rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
likeActions, err := s.contentActionAggregate(ctx, tenantID, consts.UserContentActionTypeLike, rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
favoriteActions, err := s.contentActionAggregate(ctx, tenantID, consts.UserContentActionTypeFavorite, rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
commentCount, err := s.commentAggregate(ctx, tenantID, rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订单仅统计内容购买类型,并按状态划分已支付/已退款。
|
||||||
|
paidCount, paidAmount, err := s.reportOrderAggregate(ctx, tenantID, consts.OrderTypeContentPurchase, consts.OrderStatusPaid, "paid_at", rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
refundCount, refundAmount, err := s.reportOrderAggregate(ctx, tenantID, consts.OrderTypeContentPurchase, consts.OrderStatusRefunded, "updated_at", rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提现维度统计(申请/完成/失败)。
|
||||||
|
withdrawApplyCount, withdrawApplyAmount, err := s.reportOrderAggregate(ctx, tenantID, consts.OrderTypeWithdrawal, consts.OrderStatusCreated, "created_at", rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
withdrawPaidCount, withdrawPaidAmount, err := s.reportOrderAggregate(ctx, tenantID, consts.OrderTypeWithdrawal, consts.OrderStatusPaid, "paid_at", rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
withdrawFailedCount, withdrawFailedAmount, err := s.reportOrderAggregate(ctx, tenantID, consts.OrderTypeWithdrawal, consts.OrderStatusFailed, "updated_at", rg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -5568,11 +5604,39 @@ func (s *super) ReportOverview(ctx context.Context, filter *super_dto.SuperRepor
|
|||||||
conversionRate = float64(paidCount) / float64(totalViews)
|
conversionRate = float64(paidCount) / float64(totalViews)
|
||||||
}
|
}
|
||||||
|
|
||||||
paidSeries, err := s.reportOrderSeries(ctx, tenantID, consts.OrderStatusPaid, "paid_at", rg)
|
paidSeries, err := s.reportOrderSeries(ctx, tenantID, consts.OrderTypeContentPurchase, consts.OrderStatusPaid, "paid_at", rg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
refundSeries, err := s.reportOrderSeries(ctx, tenantID, consts.OrderStatusRefunded, "updated_at", rg)
|
refundSeries, err := s.reportOrderSeries(ctx, tenantID, consts.OrderTypeContentPurchase, consts.OrderStatusRefunded, "updated_at", rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
withdrawApplySeries, err := s.reportOrderSeries(ctx, tenantID, consts.OrderTypeWithdrawal, consts.OrderStatusCreated, "created_at", rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
withdrawPaidSeries, err := s.reportOrderSeries(ctx, tenantID, consts.OrderTypeWithdrawal, consts.OrderStatusPaid, "paid_at", rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
withdrawFailedSeries, err := s.reportOrderSeries(ctx, tenantID, consts.OrderTypeWithdrawal, consts.OrderStatusFailed, "updated_at", rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
contentCreatedSeries, err := s.contentCreatedSeries(ctx, tenantID, rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
likeSeries, err := s.contentActionSeries(ctx, tenantID, consts.UserContentActionTypeLike, rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
favoriteSeries, err := s.contentActionSeries(ctx, tenantID, consts.UserContentActionTypeFavorite, rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
commentSeries, err := s.commentSeries(ctx, tenantID, rg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -5582,22 +5646,46 @@ func (s *super) ReportOverview(ctx context.Context, filter *super_dto.SuperRepor
|
|||||||
key := day.Format("2006-01-02")
|
key := day.Format("2006-01-02")
|
||||||
paidItem := paidSeries[key]
|
paidItem := paidSeries[key]
|
||||||
refundItem := refundSeries[key]
|
refundItem := refundSeries[key]
|
||||||
|
withdrawApplyItem := withdrawApplySeries[key]
|
||||||
|
withdrawPaidItem := withdrawPaidSeries[key]
|
||||||
|
withdrawFailedItem := withdrawFailedSeries[key]
|
||||||
items = append(items, v1_dto.ReportOverviewItem{
|
items = append(items, v1_dto.ReportOverviewItem{
|
||||||
Date: key,
|
Date: key,
|
||||||
PaidOrders: paidItem.Count,
|
PaidOrders: paidItem.Count,
|
||||||
PaidAmount: float64(paidItem.Amount) / 100.0,
|
PaidAmount: float64(paidItem.Amount) / 100.0,
|
||||||
RefundOrders: refundItem.Count,
|
RefundOrders: refundItem.Count,
|
||||||
RefundAmount: float64(refundItem.Amount) / 100.0,
|
RefundAmount: float64(refundItem.Amount) / 100.0,
|
||||||
|
WithdrawalApplyOrders: withdrawApplyItem.Count,
|
||||||
|
WithdrawalApplyAmount: float64(withdrawApplyItem.Amount) / 100.0,
|
||||||
|
WithdrawalPaidOrders: withdrawPaidItem.Count,
|
||||||
|
WithdrawalPaidAmount: float64(withdrawPaidItem.Amount) / 100.0,
|
||||||
|
WithdrawalFailedOrders: withdrawFailedItem.Count,
|
||||||
|
WithdrawalFailedAmount: float64(withdrawFailedItem.Amount) / 100.0,
|
||||||
|
ContentCreated: contentCreatedSeries[key],
|
||||||
|
LikeActions: likeSeries[key],
|
||||||
|
FavoriteActions: favoriteSeries[key],
|
||||||
|
CommentCount: commentSeries[key],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return &v1_dto.ReportOverviewResponse{
|
return &v1_dto.ReportOverviewResponse{
|
||||||
Summary: v1_dto.ReportSummary{
|
Summary: v1_dto.ReportSummary{
|
||||||
TotalViews: totalViews,
|
TotalViews: totalViews,
|
||||||
|
ContentCount: contentCount,
|
||||||
|
ContentCreated: contentCreated,
|
||||||
|
LikeActions: likeActions,
|
||||||
|
FavoriteActions: favoriteActions,
|
||||||
|
CommentCount: commentCount,
|
||||||
PaidOrders: paidCount,
|
PaidOrders: paidCount,
|
||||||
PaidAmount: float64(paidAmount) / 100.0,
|
PaidAmount: float64(paidAmount) / 100.0,
|
||||||
RefundOrders: refundCount,
|
RefundOrders: refundCount,
|
||||||
RefundAmount: float64(refundAmount) / 100.0,
|
RefundAmount: float64(refundAmount) / 100.0,
|
||||||
|
WithdrawalApplyOrders: withdrawApplyCount,
|
||||||
|
WithdrawalApplyAmount: float64(withdrawApplyAmount) / 100.0,
|
||||||
|
WithdrawalPaidOrders: withdrawPaidCount,
|
||||||
|
WithdrawalPaidAmount: float64(withdrawPaidAmount) / 100.0,
|
||||||
|
WithdrawalFailedOrders: withdrawFailedCount,
|
||||||
|
WithdrawalFailedAmount: float64(withdrawFailedAmount) / 100.0,
|
||||||
ConversionRate: conversionRate,
|
ConversionRate: conversionRate,
|
||||||
},
|
},
|
||||||
Items: items,
|
Items: items,
|
||||||
@@ -5627,7 +5715,7 @@ func (s *super) ExportReport(ctx context.Context, form *super_dto.SuperReportExp
|
|||||||
}
|
}
|
||||||
|
|
||||||
builder := &strings.Builder{}
|
builder := &strings.Builder{}
|
||||||
builder.WriteString("date,paid_orders,paid_amount,refund_orders,refund_amount\n")
|
builder.WriteString("date,paid_orders,paid_amount,refund_orders,refund_amount,withdrawal_apply_orders,withdrawal_apply_amount,withdrawal_paid_orders,withdrawal_paid_amount,withdrawal_failed_orders,withdrawal_failed_amount,content_created,like_actions,favorite_actions,comment_count\n")
|
||||||
for _, item := range overview.Items {
|
for _, item := range overview.Items {
|
||||||
builder.WriteString(item.Date)
|
builder.WriteString(item.Date)
|
||||||
builder.WriteString(",")
|
builder.WriteString(",")
|
||||||
@@ -5638,6 +5726,26 @@ func (s *super) ExportReport(ctx context.Context, form *super_dto.SuperReportExp
|
|||||||
builder.WriteString(strconv.FormatInt(item.RefundOrders, 10))
|
builder.WriteString(strconv.FormatInt(item.RefundOrders, 10))
|
||||||
builder.WriteString(",")
|
builder.WriteString(",")
|
||||||
builder.WriteString(formatAmount(item.RefundAmount))
|
builder.WriteString(formatAmount(item.RefundAmount))
|
||||||
|
builder.WriteString(",")
|
||||||
|
builder.WriteString(strconv.FormatInt(item.WithdrawalApplyOrders, 10))
|
||||||
|
builder.WriteString(",")
|
||||||
|
builder.WriteString(formatAmount(item.WithdrawalApplyAmount))
|
||||||
|
builder.WriteString(",")
|
||||||
|
builder.WriteString(strconv.FormatInt(item.WithdrawalPaidOrders, 10))
|
||||||
|
builder.WriteString(",")
|
||||||
|
builder.WriteString(formatAmount(item.WithdrawalPaidAmount))
|
||||||
|
builder.WriteString(",")
|
||||||
|
builder.WriteString(strconv.FormatInt(item.WithdrawalFailedOrders, 10))
|
||||||
|
builder.WriteString(",")
|
||||||
|
builder.WriteString(formatAmount(item.WithdrawalFailedAmount))
|
||||||
|
builder.WriteString(",")
|
||||||
|
builder.WriteString(strconv.FormatInt(item.ContentCreated, 10))
|
||||||
|
builder.WriteString(",")
|
||||||
|
builder.WriteString(strconv.FormatInt(item.LikeActions, 10))
|
||||||
|
builder.WriteString(",")
|
||||||
|
builder.WriteString(strconv.FormatInt(item.FavoriteActions, 10))
|
||||||
|
builder.WriteString(",")
|
||||||
|
builder.WriteString(strconv.FormatInt(item.CommentCount, 10))
|
||||||
builder.WriteString("\n")
|
builder.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5649,9 +5757,126 @@ func (s *super) ExportReport(ctx context.Context, form *super_dto.SuperReportExp
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *super) contentCount(ctx context.Context, tenantID int64) (int64, error) {
|
||||||
|
tbl, q := models.ContentQuery.QueryContext(ctx)
|
||||||
|
if tenantID > 0 {
|
||||||
|
q = q.Where(tbl.TenantID.Eq(tenantID))
|
||||||
|
}
|
||||||
|
total, err := q.Count()
|
||||||
|
if err != nil {
|
||||||
|
return 0, errorx.ErrDatabaseError.WithCause(err)
|
||||||
|
}
|
||||||
|
return total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *super) contentCreatedAggregate(ctx context.Context, tenantID int64, rg reportRange) (int64, error) {
|
||||||
|
tbl, q := models.ContentQuery.QueryContext(ctx)
|
||||||
|
q = q.Where(tbl.CreatedAt.Gte(rg.startDay), tbl.CreatedAt.Lt(rg.endNext))
|
||||||
|
if tenantID > 0 {
|
||||||
|
q = q.Where(tbl.TenantID.Eq(tenantID))
|
||||||
|
}
|
||||||
|
total, err := q.Count()
|
||||||
|
if err != nil {
|
||||||
|
return 0, errorx.ErrDatabaseError.WithCause(err)
|
||||||
|
}
|
||||||
|
return total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *super) contentCreatedSeries(ctx context.Context, tenantID int64, rg reportRange) (map[string]int64, error) {
|
||||||
|
rows := make([]reportCountRow, 0)
|
||||||
|
query := models.ContentQuery.WithContext(ctx).
|
||||||
|
UnderlyingDB().
|
||||||
|
Model(&models.Content{}).
|
||||||
|
Select("date_trunc('day', created_at) as day, count(*) as count").
|
||||||
|
Where("created_at >= ? AND created_at < ?", rg.startDay, rg.endNext)
|
||||||
|
if tenantID > 0 {
|
||||||
|
query = query.Where("tenant_id = ?", tenantID)
|
||||||
|
}
|
||||||
|
if err := query.Group("day").Scan(&rows).Error; err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
|
}
|
||||||
|
return buildCountSeries(rows), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *super) contentActionAggregate(
|
||||||
|
ctx context.Context,
|
||||||
|
tenantID int64,
|
||||||
|
actionType consts.UserContentActionType,
|
||||||
|
rg reportRange,
|
||||||
|
) (int64, error) {
|
||||||
|
var total int64
|
||||||
|
query := models.UserContentActionQuery.WithContext(ctx).
|
||||||
|
UnderlyingDB().
|
||||||
|
Model(&models.UserContentAction{}).
|
||||||
|
Select("count(*)").
|
||||||
|
Where("user_content_actions.type = ? AND user_content_actions.created_at >= ? AND user_content_actions.created_at < ?",
|
||||||
|
actionType, rg.startDay, rg.endNext)
|
||||||
|
if tenantID > 0 {
|
||||||
|
query = query.Joins("join contents on contents.id = user_content_actions.content_id").
|
||||||
|
Where("contents.tenant_id = ?", tenantID)
|
||||||
|
}
|
||||||
|
if err := query.Scan(&total).Error; err != nil {
|
||||||
|
return 0, errorx.ErrDatabaseError.WithCause(err)
|
||||||
|
}
|
||||||
|
return total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *super) contentActionSeries(
|
||||||
|
ctx context.Context,
|
||||||
|
tenantID int64,
|
||||||
|
actionType consts.UserContentActionType,
|
||||||
|
rg reportRange,
|
||||||
|
) (map[string]int64, error) {
|
||||||
|
rows := make([]reportCountRow, 0)
|
||||||
|
query := models.UserContentActionQuery.WithContext(ctx).
|
||||||
|
UnderlyingDB().
|
||||||
|
Model(&models.UserContentAction{}).
|
||||||
|
Select("date_trunc('day', user_content_actions.created_at) as day, count(*) as count").
|
||||||
|
Where("user_content_actions.type = ? AND user_content_actions.created_at >= ? AND user_content_actions.created_at < ?",
|
||||||
|
actionType, rg.startDay, rg.endNext)
|
||||||
|
if tenantID > 0 {
|
||||||
|
query = query.Joins("join contents on contents.id = user_content_actions.content_id").
|
||||||
|
Where("contents.tenant_id = ?", tenantID)
|
||||||
|
}
|
||||||
|
if err := query.Group("day").Scan(&rows).Error; err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
|
}
|
||||||
|
return buildCountSeries(rows), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *super) commentAggregate(ctx context.Context, tenantID int64, rg reportRange) (int64, error) {
|
||||||
|
tbl, q := models.CommentQuery.QueryContext(ctx)
|
||||||
|
q = q.Where(tbl.CreatedAt.Gte(rg.startDay), tbl.CreatedAt.Lt(rg.endNext))
|
||||||
|
if tenantID > 0 {
|
||||||
|
q = q.Where(tbl.TenantID.Eq(tenantID))
|
||||||
|
}
|
||||||
|
total, err := q.Count()
|
||||||
|
if err != nil {
|
||||||
|
return 0, errorx.ErrDatabaseError.WithCause(err)
|
||||||
|
}
|
||||||
|
return total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *super) commentSeries(ctx context.Context, tenantID int64, rg reportRange) (map[string]int64, error) {
|
||||||
|
rows := make([]reportCountRow, 0)
|
||||||
|
query := models.CommentQuery.WithContext(ctx).
|
||||||
|
UnderlyingDB().
|
||||||
|
Model(&models.Comment{}).
|
||||||
|
Select("date_trunc('day', created_at) as day, count(*) as count").
|
||||||
|
Where("created_at >= ? AND created_at < ?", rg.startDay, rg.endNext)
|
||||||
|
if tenantID > 0 {
|
||||||
|
query = query.Where("tenant_id = ?", tenantID)
|
||||||
|
}
|
||||||
|
if err := query.Group("day").Scan(&rows).Error; err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
|
}
|
||||||
|
return buildCountSeries(rows), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *super) reportOrderAggregate(
|
func (s *super) reportOrderAggregate(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
tenantID int64,
|
tenantID int64,
|
||||||
|
orderType consts.OrderType,
|
||||||
status consts.OrderStatus,
|
status consts.OrderStatus,
|
||||||
timeField string,
|
timeField string,
|
||||||
rg reportRange,
|
rg reportRange,
|
||||||
@@ -5666,7 +5891,7 @@ func (s *super) reportOrderAggregate(
|
|||||||
Model(&models.Order{}).
|
Model(&models.Order{}).
|
||||||
Select("count(*) as count, coalesce(sum(amount_paid), 0) as amount").
|
Select("count(*) as count, coalesce(sum(amount_paid), 0) as amount").
|
||||||
Where("type = ? AND status = ? AND "+timeField+" >= ? AND "+timeField+" < ?",
|
Where("type = ? AND status = ? AND "+timeField+" >= ? AND "+timeField+" < ?",
|
||||||
consts.OrderTypeContentPurchase, status, rg.startDay, rg.endNext)
|
orderType, status, rg.startDay, rg.endNext)
|
||||||
if tenantID > 0 {
|
if tenantID > 0 {
|
||||||
query = query.Where("tenant_id = ?", tenantID)
|
query = query.Where("tenant_id = ?", tenantID)
|
||||||
}
|
}
|
||||||
@@ -5680,6 +5905,7 @@ func (s *super) reportOrderAggregate(
|
|||||||
func (s *super) reportOrderSeries(
|
func (s *super) reportOrderSeries(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
tenantID int64,
|
tenantID int64,
|
||||||
|
orderType consts.OrderType,
|
||||||
status consts.OrderStatus,
|
status consts.OrderStatus,
|
||||||
timeField string,
|
timeField string,
|
||||||
rg reportRange,
|
rg reportRange,
|
||||||
@@ -5690,7 +5916,7 @@ func (s *super) reportOrderSeries(
|
|||||||
Model(&models.Order{}).
|
Model(&models.Order{}).
|
||||||
Select("date_trunc('day', "+timeField+") as day, count(*) as count, coalesce(sum(amount_paid), 0) as amount").
|
Select("date_trunc('day', "+timeField+") as day, count(*) as count, coalesce(sum(amount_paid), 0) as amount").
|
||||||
Where("type = ? AND status = ? AND "+timeField+" >= ? AND "+timeField+" < ?",
|
Where("type = ? AND status = ? AND "+timeField+" >= ? AND "+timeField+" < ?",
|
||||||
consts.OrderTypeContentPurchase, status, rg.startDay, rg.endNext)
|
orderType, status, rg.startDay, rg.endNext)
|
||||||
if tenantID > 0 {
|
if tenantID > 0 {
|
||||||
query = query.Where("tenant_id = ?", tenantID)
|
query = query.Where("tenant_id = ?", tenantID)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6873,10 +6873,26 @@ const docTemplate = `{
|
|||||||
"dto.ReportOverviewItem": {
|
"dto.ReportOverviewItem": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"comment_count": {
|
||||||
|
"description": "CommentCount 当日新增评论数。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"content_created": {
|
||||||
|
"description": "ContentCreated 当日新增内容数。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"date": {
|
"date": {
|
||||||
"description": "Date 日期(YYYY-MM-DD)。",
|
"description": "Date 日期(YYYY-MM-DD)。",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"favorite_actions": {
|
||||||
|
"description": "FavoriteActions 当日新增收藏数(基于互动记录)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"like_actions": {
|
||||||
|
"description": "LikeActions 当日新增点赞数(基于互动记录)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"paid_amount": {
|
"paid_amount": {
|
||||||
"description": "PaidAmount 当日已支付金额(单位元)。",
|
"description": "PaidAmount 当日已支付金额(单位元)。",
|
||||||
"type": "number"
|
"type": "number"
|
||||||
@@ -6892,6 +6908,30 @@ const docTemplate = `{
|
|||||||
"refund_orders": {
|
"refund_orders": {
|
||||||
"description": "RefundOrders 当日退款订单数。",
|
"description": "RefundOrders 当日退款订单数。",
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"withdrawal_apply_amount": {
|
||||||
|
"description": "WithdrawalApplyAmount 当日提现申请金额(单位元)。",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"withdrawal_apply_orders": {
|
||||||
|
"description": "WithdrawalApplyOrders 当日提现申请订单数。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"withdrawal_failed_amount": {
|
||||||
|
"description": "WithdrawalFailedAmount 当日提现失败金额(单位元)。",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"withdrawal_failed_orders": {
|
||||||
|
"description": "WithdrawalFailedOrders 当日提现失败订单数。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"withdrawal_paid_amount": {
|
||||||
|
"description": "WithdrawalPaidAmount 当日提现完成金额(单位元)。",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"withdrawal_paid_orders": {
|
||||||
|
"description": "WithdrawalPaidOrders 当日提现完成订单数。",
|
||||||
|
"type": "integer"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -6918,10 +6958,30 @@ const docTemplate = `{
|
|||||||
"dto.ReportSummary": {
|
"dto.ReportSummary": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"comment_count": {
|
||||||
|
"description": "CommentCount 统计区间内新增评论数。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"content_count": {
|
||||||
|
"description": "ContentCount 内容总量(当前快照)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"content_created": {
|
||||||
|
"description": "ContentCreated 统计区间内新增内容数。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"conversion_rate": {
|
"conversion_rate": {
|
||||||
"description": "ConversionRate 转化率(已支付订单数 / 累计曝光)。",
|
"description": "ConversionRate 转化率(已支付订单数 / 累计曝光)。",
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
|
"favorite_actions": {
|
||||||
|
"description": "FavoriteActions 统计区间内新增收藏数(基于互动记录)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"like_actions": {
|
||||||
|
"description": "LikeActions 统计区间内新增点赞数(基于互动记录)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"paid_amount": {
|
"paid_amount": {
|
||||||
"description": "PaidAmount 统计区间内已支付金额(单位元)。",
|
"description": "PaidAmount 统计区间内已支付金额(单位元)。",
|
||||||
"type": "number"
|
"type": "number"
|
||||||
@@ -6941,6 +7001,30 @@ const docTemplate = `{
|
|||||||
"total_views": {
|
"total_views": {
|
||||||
"description": "TotalViews 内容累计曝光(全量累计值,用于粗略换算)。",
|
"description": "TotalViews 内容累计曝光(全量累计值,用于粗略换算)。",
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"withdrawal_apply_amount": {
|
||||||
|
"description": "WithdrawalApplyAmount 统计区间内提现申请金额(单位元)。",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"withdrawal_apply_orders": {
|
||||||
|
"description": "WithdrawalApplyOrders 统计区间内提现申请订单数。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"withdrawal_failed_amount": {
|
||||||
|
"description": "WithdrawalFailedAmount 统计区间内提现失败金额(单位元)。",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"withdrawal_failed_orders": {
|
||||||
|
"description": "WithdrawalFailedOrders 统计区间内提现失败订单数。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"withdrawal_paid_amount": {
|
||||||
|
"description": "WithdrawalPaidAmount 统计区间内提现完成金额(单位元)。",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"withdrawal_paid_orders": {
|
||||||
|
"description": "WithdrawalPaidOrders 统计区间内提现完成订单数。",
|
||||||
|
"type": "integer"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6867,10 +6867,26 @@
|
|||||||
"dto.ReportOverviewItem": {
|
"dto.ReportOverviewItem": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"comment_count": {
|
||||||
|
"description": "CommentCount 当日新增评论数。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"content_created": {
|
||||||
|
"description": "ContentCreated 当日新增内容数。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"date": {
|
"date": {
|
||||||
"description": "Date 日期(YYYY-MM-DD)。",
|
"description": "Date 日期(YYYY-MM-DD)。",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"favorite_actions": {
|
||||||
|
"description": "FavoriteActions 当日新增收藏数(基于互动记录)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"like_actions": {
|
||||||
|
"description": "LikeActions 当日新增点赞数(基于互动记录)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"paid_amount": {
|
"paid_amount": {
|
||||||
"description": "PaidAmount 当日已支付金额(单位元)。",
|
"description": "PaidAmount 当日已支付金额(单位元)。",
|
||||||
"type": "number"
|
"type": "number"
|
||||||
@@ -6886,6 +6902,30 @@
|
|||||||
"refund_orders": {
|
"refund_orders": {
|
||||||
"description": "RefundOrders 当日退款订单数。",
|
"description": "RefundOrders 当日退款订单数。",
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"withdrawal_apply_amount": {
|
||||||
|
"description": "WithdrawalApplyAmount 当日提现申请金额(单位元)。",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"withdrawal_apply_orders": {
|
||||||
|
"description": "WithdrawalApplyOrders 当日提现申请订单数。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"withdrawal_failed_amount": {
|
||||||
|
"description": "WithdrawalFailedAmount 当日提现失败金额(单位元)。",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"withdrawal_failed_orders": {
|
||||||
|
"description": "WithdrawalFailedOrders 当日提现失败订单数。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"withdrawal_paid_amount": {
|
||||||
|
"description": "WithdrawalPaidAmount 当日提现完成金额(单位元)。",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"withdrawal_paid_orders": {
|
||||||
|
"description": "WithdrawalPaidOrders 当日提现完成订单数。",
|
||||||
|
"type": "integer"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -6912,10 +6952,30 @@
|
|||||||
"dto.ReportSummary": {
|
"dto.ReportSummary": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"comment_count": {
|
||||||
|
"description": "CommentCount 统计区间内新增评论数。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"content_count": {
|
||||||
|
"description": "ContentCount 内容总量(当前快照)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"content_created": {
|
||||||
|
"description": "ContentCreated 统计区间内新增内容数。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"conversion_rate": {
|
"conversion_rate": {
|
||||||
"description": "ConversionRate 转化率(已支付订单数 / 累计曝光)。",
|
"description": "ConversionRate 转化率(已支付订单数 / 累计曝光)。",
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
|
"favorite_actions": {
|
||||||
|
"description": "FavoriteActions 统计区间内新增收藏数(基于互动记录)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"like_actions": {
|
||||||
|
"description": "LikeActions 统计区间内新增点赞数(基于互动记录)。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"paid_amount": {
|
"paid_amount": {
|
||||||
"description": "PaidAmount 统计区间内已支付金额(单位元)。",
|
"description": "PaidAmount 统计区间内已支付金额(单位元)。",
|
||||||
"type": "number"
|
"type": "number"
|
||||||
@@ -6935,6 +6995,30 @@
|
|||||||
"total_views": {
|
"total_views": {
|
||||||
"description": "TotalViews 内容累计曝光(全量累计值,用于粗略换算)。",
|
"description": "TotalViews 内容累计曝光(全量累计值,用于粗略换算)。",
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"withdrawal_apply_amount": {
|
||||||
|
"description": "WithdrawalApplyAmount 统计区间内提现申请金额(单位元)。",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"withdrawal_apply_orders": {
|
||||||
|
"description": "WithdrawalApplyOrders 统计区间内提现申请订单数。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"withdrawal_failed_amount": {
|
||||||
|
"description": "WithdrawalFailedAmount 统计区间内提现失败金额(单位元)。",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"withdrawal_failed_orders": {
|
||||||
|
"description": "WithdrawalFailedOrders 统计区间内提现失败订单数。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"withdrawal_paid_amount": {
|
||||||
|
"description": "WithdrawalPaidAmount 统计区间内提现完成金额(单位元)。",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"withdrawal_paid_orders": {
|
||||||
|
"description": "WithdrawalPaidOrders 统计区间内提现完成订单数。",
|
||||||
|
"type": "integer"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -993,9 +993,21 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
dto.ReportOverviewItem:
|
dto.ReportOverviewItem:
|
||||||
properties:
|
properties:
|
||||||
|
comment_count:
|
||||||
|
description: CommentCount 当日新增评论数。
|
||||||
|
type: integer
|
||||||
|
content_created:
|
||||||
|
description: ContentCreated 当日新增内容数。
|
||||||
|
type: integer
|
||||||
date:
|
date:
|
||||||
description: Date 日期(YYYY-MM-DD)。
|
description: Date 日期(YYYY-MM-DD)。
|
||||||
type: string
|
type: string
|
||||||
|
favorite_actions:
|
||||||
|
description: FavoriteActions 当日新增收藏数(基于互动记录)。
|
||||||
|
type: integer
|
||||||
|
like_actions:
|
||||||
|
description: LikeActions 当日新增点赞数(基于互动记录)。
|
||||||
|
type: integer
|
||||||
paid_amount:
|
paid_amount:
|
||||||
description: PaidAmount 当日已支付金额(单位元)。
|
description: PaidAmount 当日已支付金额(单位元)。
|
||||||
type: number
|
type: number
|
||||||
@@ -1008,6 +1020,24 @@ definitions:
|
|||||||
refund_orders:
|
refund_orders:
|
||||||
description: RefundOrders 当日退款订单数。
|
description: RefundOrders 当日退款订单数。
|
||||||
type: integer
|
type: integer
|
||||||
|
withdrawal_apply_amount:
|
||||||
|
description: WithdrawalApplyAmount 当日提现申请金额(单位元)。
|
||||||
|
type: number
|
||||||
|
withdrawal_apply_orders:
|
||||||
|
description: WithdrawalApplyOrders 当日提现申请订单数。
|
||||||
|
type: integer
|
||||||
|
withdrawal_failed_amount:
|
||||||
|
description: WithdrawalFailedAmount 当日提现失败金额(单位元)。
|
||||||
|
type: number
|
||||||
|
withdrawal_failed_orders:
|
||||||
|
description: WithdrawalFailedOrders 当日提现失败订单数。
|
||||||
|
type: integer
|
||||||
|
withdrawal_paid_amount:
|
||||||
|
description: WithdrawalPaidAmount 当日提现完成金额(单位元)。
|
||||||
|
type: number
|
||||||
|
withdrawal_paid_orders:
|
||||||
|
description: WithdrawalPaidOrders 当日提现完成订单数。
|
||||||
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
dto.ReportOverviewResponse:
|
dto.ReportOverviewResponse:
|
||||||
properties:
|
properties:
|
||||||
@@ -1023,9 +1053,24 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
dto.ReportSummary:
|
dto.ReportSummary:
|
||||||
properties:
|
properties:
|
||||||
|
comment_count:
|
||||||
|
description: CommentCount 统计区间内新增评论数。
|
||||||
|
type: integer
|
||||||
|
content_count:
|
||||||
|
description: ContentCount 内容总量(当前快照)。
|
||||||
|
type: integer
|
||||||
|
content_created:
|
||||||
|
description: ContentCreated 统计区间内新增内容数。
|
||||||
|
type: integer
|
||||||
conversion_rate:
|
conversion_rate:
|
||||||
description: ConversionRate 转化率(已支付订单数 / 累计曝光)。
|
description: ConversionRate 转化率(已支付订单数 / 累计曝光)。
|
||||||
type: number
|
type: number
|
||||||
|
favorite_actions:
|
||||||
|
description: FavoriteActions 统计区间内新增收藏数(基于互动记录)。
|
||||||
|
type: integer
|
||||||
|
like_actions:
|
||||||
|
description: LikeActions 统计区间内新增点赞数(基于互动记录)。
|
||||||
|
type: integer
|
||||||
paid_amount:
|
paid_amount:
|
||||||
description: PaidAmount 统计区间内已支付金额(单位元)。
|
description: PaidAmount 统计区间内已支付金额(单位元)。
|
||||||
type: number
|
type: number
|
||||||
@@ -1041,6 +1086,24 @@ definitions:
|
|||||||
total_views:
|
total_views:
|
||||||
description: TotalViews 内容累计曝光(全量累计值,用于粗略换算)。
|
description: TotalViews 内容累计曝光(全量累计值,用于粗略换算)。
|
||||||
type: integer
|
type: integer
|
||||||
|
withdrawal_apply_amount:
|
||||||
|
description: WithdrawalApplyAmount 统计区间内提现申请金额(单位元)。
|
||||||
|
type: number
|
||||||
|
withdrawal_apply_orders:
|
||||||
|
description: WithdrawalApplyOrders 统计区间内提现申请订单数。
|
||||||
|
type: integer
|
||||||
|
withdrawal_failed_amount:
|
||||||
|
description: WithdrawalFailedAmount 统计区间内提现失败金额(单位元)。
|
||||||
|
type: number
|
||||||
|
withdrawal_failed_orders:
|
||||||
|
description: WithdrawalFailedOrders 统计区间内提现失败订单数。
|
||||||
|
type: integer
|
||||||
|
withdrawal_paid_amount:
|
||||||
|
description: WithdrawalPaidAmount 统计区间内提现完成金额(单位元)。
|
||||||
|
type: number
|
||||||
|
withdrawal_paid_orders:
|
||||||
|
description: WithdrawalPaidOrders 统计区间内提现完成订单数。
|
||||||
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
dto.Settings:
|
dto.Settings:
|
||||||
properties:
|
properties:
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
## 1) 总体结论
|
## 1) 总体结论
|
||||||
|
|
||||||
- **已落地**:登录、租户/用户/订单/内容基础管理、内容审核(含批量)、平台概览(内容趋势/退款率/漏斗)、提现审核、报表概览与导出、用户钱包/通知/优惠券/实名/充值记录、互动(收藏/点赞/关注)与内容消费明细视图、创作者申请/成员审核/邀请、优惠券创建/编辑/发放/冻结/发放记录/异常核查、资产治理(列表/用量/清理)、通知中心(列表/群发/模板)。
|
- **已落地**:登录、租户/用户/订单/内容基础管理、内容审核(含批量)、平台概览(内容趋势/退款率/漏斗)、提现审核、报表概览与导出、用户钱包/通知/优惠券/实名/充值记录、互动(收藏/点赞/关注)与内容消费明细视图、创作者申请/成员审核/邀请、优惠券创建/编辑/发放/冻结/发放记录/异常核查、资产治理(列表/用量/清理)、通知中心(列表/群发/模板)。
|
||||||
- **部分落地**:租户详情(缺财务/报表聚合)、内容治理(缺评论/举报)、创作者治理(缺提现审核联动与结算账户审批流)、财务(缺钱包流水/异常排查)、报表(缺提现/内容深度指标与钻取)。
|
- **部分落地**:租户详情(缺财务/报表聚合)、内容治理(缺评论/举报)、创作者治理(缺提现审核联动与结算账户审批流)、财务(缺钱包流水/异常排查)。
|
||||||
- **未落地**:审计与系统配置类能力。
|
- **未落地**:审计与系统配置类能力。
|
||||||
|
|
||||||
## 2) 按页面完成度(对照 2.x)
|
## 2) 按页面完成度(对照 2.x)
|
||||||
@@ -66,9 +66,9 @@
|
|||||||
- 缺口:钱包流水、充值与退款异常排查、资金汇总报表。
|
- 缺口:钱包流水、充值与退款异常排查、资金汇总报表。
|
||||||
|
|
||||||
### 2.12 报表与导出 `/superadmin/reports`
|
### 2.12 报表与导出 `/superadmin/reports`
|
||||||
- 状态:**部分完成**
|
- 状态:**已完成**
|
||||||
- 已有:平台/租户维度概览、趋势、CSV 导出。
|
- 已有:平台/租户维度概览、趋势、CSV 导出、提现维度报表、内容深度指标与钻取。
|
||||||
- 缺口:提现维度报表、内容深度指标与多维钻取能力。
|
- 缺口:无显著功能缺口。
|
||||||
|
|
||||||
### 2.13 资产与上传 `/superadmin/assets`
|
### 2.13 资产与上传 `/superadmin/assets`
|
||||||
- 状态:**已完成**
|
- 状态:**已完成**
|
||||||
@@ -87,6 +87,5 @@
|
|||||||
|
|
||||||
## 4) 建议的下一步(按优先级)
|
## 4) 建议的下一步(按优先级)
|
||||||
|
|
||||||
1. **报表深化**:补齐提现/内容维度指标与多维钻取能力。
|
1. **审计与系统配置**:完善全量操作审计与系统级配置能力。
|
||||||
2. **审计与系统配置**:完善全量操作审计与系统级配置能力。
|
2. **创作者提现审核**:补齐跨租户提现审核与财务联动入口。
|
||||||
3. **创作者提现审核**:补齐跨租户提现审核与财务联动入口。
|
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ import SearchField from '@/components/SearchField.vue';
|
|||||||
import SearchPanel from '@/components/SearchPanel.vue';
|
import SearchPanel from '@/components/SearchPanel.vue';
|
||||||
import { FinanceService } from '@/service/FinanceService';
|
import { FinanceService } from '@/service/FinanceService';
|
||||||
import { useToast } from 'primevue/usetoast';
|
import { useToast } from 'primevue/usetoast';
|
||||||
import { ref } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
const withdrawals = ref([]);
|
const withdrawals = ref([]);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
@@ -37,6 +39,24 @@ const statusOptions = [
|
|||||||
{ label: 'failed', value: 'failed' }
|
{ label: 'failed', value: 'failed' }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
function getQueryValue(value) {
|
||||||
|
if (Array.isArray(value)) return value[0];
|
||||||
|
return value ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseNumber(value) {
|
||||||
|
const parsed = Number(value);
|
||||||
|
if (!Number.isFinite(parsed)) return null;
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseDate(value) {
|
||||||
|
if (!value) return null;
|
||||||
|
const date = new Date(value);
|
||||||
|
if (Number.isNaN(date.getTime())) return null;
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
const approveDialogVisible = ref(false);
|
const approveDialogVisible = ref(false);
|
||||||
const approveLoading = ref(false);
|
const approveLoading = ref(false);
|
||||||
const approveOrder = ref(null);
|
const approveOrder = ref(null);
|
||||||
@@ -109,7 +129,7 @@ function onSearch() {
|
|||||||
loadWithdrawals();
|
loadWithdrawals();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onReset() {
|
function resetFilters() {
|
||||||
orderID.value = null;
|
orderID.value = null;
|
||||||
tenantID.value = null;
|
tenantID.value = null;
|
||||||
tenantCode.value = '';
|
tenantCode.value = '';
|
||||||
@@ -127,6 +147,44 @@ function onReset() {
|
|||||||
sortOrder.value = -1;
|
sortOrder.value = -1;
|
||||||
page.value = 1;
|
page.value = 1;
|
||||||
rows.value = 10;
|
rows.value = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyRouteQuery(query) {
|
||||||
|
resetFilters();
|
||||||
|
|
||||||
|
const idValue = getQueryValue(query?.id);
|
||||||
|
const tenantValue = getQueryValue(query?.tenant_id);
|
||||||
|
const userValue = getQueryValue(query?.user_id);
|
||||||
|
if (idValue) orderID.value = parseNumber(idValue);
|
||||||
|
if (tenantValue) tenantID.value = parseNumber(tenantValue);
|
||||||
|
if (userValue) userID.value = parseNumber(userValue);
|
||||||
|
|
||||||
|
const statusValue = getQueryValue(query?.status);
|
||||||
|
const tenantCodeValue = getQueryValue(query?.tenant_code);
|
||||||
|
const tenantNameValue = getQueryValue(query?.tenant_name);
|
||||||
|
const usernameValue = getQueryValue(query?.username);
|
||||||
|
if (statusValue !== null) status.value = String(statusValue);
|
||||||
|
if (tenantCodeValue !== null) tenantCode.value = String(tenantCodeValue);
|
||||||
|
if (tenantNameValue !== null) tenantName.value = String(tenantNameValue);
|
||||||
|
if (usernameValue !== null) username.value = String(usernameValue);
|
||||||
|
|
||||||
|
const createdFromValue = getQueryValue(query?.created_at_from);
|
||||||
|
const createdToValue = getQueryValue(query?.created_at_to);
|
||||||
|
const paidFromValue = getQueryValue(query?.paid_at_from);
|
||||||
|
const paidToValue = getQueryValue(query?.paid_at_to);
|
||||||
|
if (createdFromValue) createdAtFrom.value = parseDate(createdFromValue);
|
||||||
|
if (createdToValue) createdAtTo.value = parseDate(createdToValue);
|
||||||
|
if (paidFromValue) paidAtFrom.value = parseDate(paidFromValue);
|
||||||
|
if (paidToValue) paidAtTo.value = parseDate(paidToValue);
|
||||||
|
|
||||||
|
const amountMinValue = getQueryValue(query?.amount_paid_min);
|
||||||
|
const amountMaxValue = getQueryValue(query?.amount_paid_max);
|
||||||
|
if (amountMinValue) amountPaidMin.value = parseNumber(amountMinValue);
|
||||||
|
if (amountMaxValue) amountPaidMax.value = parseNumber(amountMaxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onReset() {
|
||||||
|
resetFilters();
|
||||||
loadWithdrawals();
|
loadWithdrawals();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +243,14 @@ async function confirmReject() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadWithdrawals();
|
watch(
|
||||||
|
() => route.query,
|
||||||
|
(query) => {
|
||||||
|
applyRouteQuery(query);
|
||||||
|
loadWithdrawals();
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -24,6 +24,12 @@ const orderChartData = ref(null);
|
|||||||
const amountChartData = ref(null);
|
const amountChartData = ref(null);
|
||||||
const orderChartOptions = ref(null);
|
const orderChartOptions = ref(null);
|
||||||
const amountChartOptions = ref(null);
|
const amountChartOptions = ref(null);
|
||||||
|
const withdrawOrderChartData = ref(null);
|
||||||
|
const withdrawAmountChartData = ref(null);
|
||||||
|
const withdrawOrderChartOptions = ref(null);
|
||||||
|
const withdrawAmountChartOptions = ref(null);
|
||||||
|
const interactionChartData = ref(null);
|
||||||
|
const interactionChartOptions = ref(null);
|
||||||
|
|
||||||
const granularityOptions = [{ label: '按天', value: 'day' }];
|
const granularityOptions = [{ label: '按天', value: 'day' }];
|
||||||
|
|
||||||
@@ -63,9 +69,40 @@ function goToOrders(status, usePaidAt) {
|
|||||||
router.push({ name: 'superadmin-orders', query: buildOrdersQuery({ status, usePaidAt }) });
|
router.push({ name: 'superadmin-orders', query: buildOrdersQuery({ status, usePaidAt }) });
|
||||||
}
|
}
|
||||||
|
|
||||||
function goToContents() {
|
function buildWithdrawalsQuery({ status, usePaidAt } = {}) {
|
||||||
const query = {};
|
const query = {};
|
||||||
if (tenantID.value) query.tenant_id = tenantID.value;
|
if (tenantID.value) query.tenant_id = tenantID.value;
|
||||||
|
if (status) query.status = status;
|
||||||
|
const start = formatDateParam(startAt.value);
|
||||||
|
const end = formatDateParam(endAt.value);
|
||||||
|
if (usePaidAt) {
|
||||||
|
if (start) query.paid_at_from = start;
|
||||||
|
if (end) query.paid_at_to = end;
|
||||||
|
} else {
|
||||||
|
if (start) query.created_at_from = start;
|
||||||
|
if (end) query.created_at_to = end;
|
||||||
|
}
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToWithdrawals(status, usePaidAt) {
|
||||||
|
router.push({ name: 'superadmin-finance', query: buildWithdrawalsQuery({ status, usePaidAt }) });
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildContentsQuery({ useCreatedAt } = {}) {
|
||||||
|
const query = {};
|
||||||
|
if (tenantID.value) query.tenant_id = tenantID.value;
|
||||||
|
if (useCreatedAt) {
|
||||||
|
const start = formatDateParam(startAt.value);
|
||||||
|
const end = formatDateParam(endAt.value);
|
||||||
|
if (start) query.created_at_from = start;
|
||||||
|
if (end) query.created_at_to = end;
|
||||||
|
}
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToContents(extraQuery = {}) {
|
||||||
|
const query = { ...buildContentsQuery(), ...extraQuery };
|
||||||
router.push({ name: 'superadmin-contents', query });
|
router.push({ name: 'superadmin-contents', query });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,6 +164,129 @@ function buildAmountChartData(items) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildWithdrawOrderChartData(items) {
|
||||||
|
const documentStyle = getComputedStyle(document.documentElement);
|
||||||
|
const labels = items.map((item) => item.date);
|
||||||
|
const applyOrders = items.map((item) => Number(item.withdrawal_apply_orders ?? 0));
|
||||||
|
const paidOrders = items.map((item) => Number(item.withdrawal_paid_orders ?? 0));
|
||||||
|
const failedOrders = items.map((item) => Number(item.withdrawal_failed_orders ?? 0));
|
||||||
|
|
||||||
|
return {
|
||||||
|
labels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: '提现申请',
|
||||||
|
data: applyOrders,
|
||||||
|
fill: true,
|
||||||
|
backgroundColor: documentStyle.getPropertyValue('--p-primary-200'),
|
||||||
|
borderColor: documentStyle.getPropertyValue('--p-primary-500'),
|
||||||
|
tension: 0.35
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '提现完成',
|
||||||
|
data: paidOrders,
|
||||||
|
fill: true,
|
||||||
|
backgroundColor: documentStyle.getPropertyValue('--p-emerald-200'),
|
||||||
|
borderColor: documentStyle.getPropertyValue('--p-emerald-500'),
|
||||||
|
tension: 0.35
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '提现失败',
|
||||||
|
data: failedOrders,
|
||||||
|
fill: true,
|
||||||
|
backgroundColor: documentStyle.getPropertyValue('--p-rose-200'),
|
||||||
|
borderColor: documentStyle.getPropertyValue('--p-rose-500'),
|
||||||
|
tension: 0.35
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildWithdrawAmountChartData(items) {
|
||||||
|
const documentStyle = getComputedStyle(document.documentElement);
|
||||||
|
const labels = items.map((item) => item.date);
|
||||||
|
const applyAmounts = items.map((item) => Number(item.withdrawal_apply_amount ?? 0));
|
||||||
|
const paidAmounts = items.map((item) => Number(item.withdrawal_paid_amount ?? 0));
|
||||||
|
const failedAmounts = items.map((item) => Number(item.withdrawal_failed_amount ?? 0));
|
||||||
|
|
||||||
|
return {
|
||||||
|
labels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: '申请金额',
|
||||||
|
data: applyAmounts,
|
||||||
|
fill: true,
|
||||||
|
backgroundColor: documentStyle.getPropertyValue('--p-primary-200'),
|
||||||
|
borderColor: documentStyle.getPropertyValue('--p-primary-500'),
|
||||||
|
tension: 0.35
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '完成金额',
|
||||||
|
data: paidAmounts,
|
||||||
|
fill: true,
|
||||||
|
backgroundColor: documentStyle.getPropertyValue('--p-emerald-200'),
|
||||||
|
borderColor: documentStyle.getPropertyValue('--p-emerald-500'),
|
||||||
|
tension: 0.35
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '失败金额',
|
||||||
|
data: failedAmounts,
|
||||||
|
fill: true,
|
||||||
|
backgroundColor: documentStyle.getPropertyValue('--p-rose-200'),
|
||||||
|
borderColor: documentStyle.getPropertyValue('--p-rose-500'),
|
||||||
|
tension: 0.35
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildInteractionChartData(items) {
|
||||||
|
const documentStyle = getComputedStyle(document.documentElement);
|
||||||
|
const labels = items.map((item) => item.date);
|
||||||
|
const contentCreated = items.map((item) => Number(item.content_created ?? 0));
|
||||||
|
const likeActions = items.map((item) => Number(item.like_actions ?? 0));
|
||||||
|
const favoriteActions = items.map((item) => Number(item.favorite_actions ?? 0));
|
||||||
|
const commentCount = items.map((item) => Number(item.comment_count ?? 0));
|
||||||
|
|
||||||
|
return {
|
||||||
|
labels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: '新增内容',
|
||||||
|
data: contentCreated,
|
||||||
|
fill: true,
|
||||||
|
backgroundColor: documentStyle.getPropertyValue('--p-primary-200'),
|
||||||
|
borderColor: documentStyle.getPropertyValue('--p-primary-500'),
|
||||||
|
tension: 0.35
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '新增点赞',
|
||||||
|
data: likeActions,
|
||||||
|
fill: true,
|
||||||
|
backgroundColor: documentStyle.getPropertyValue('--p-emerald-200'),
|
||||||
|
borderColor: documentStyle.getPropertyValue('--p-emerald-500'),
|
||||||
|
tension: 0.35
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '新增收藏',
|
||||||
|
data: favoriteActions,
|
||||||
|
fill: true,
|
||||||
|
backgroundColor: documentStyle.getPropertyValue('--p-orange-200'),
|
||||||
|
borderColor: documentStyle.getPropertyValue('--p-orange-500'),
|
||||||
|
tension: 0.35
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '新增评论',
|
||||||
|
data: commentCount,
|
||||||
|
fill: true,
|
||||||
|
backgroundColor: documentStyle.getPropertyValue('--p-rose-200'),
|
||||||
|
borderColor: documentStyle.getPropertyValue('--p-rose-500'),
|
||||||
|
tension: 0.35
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function buildChartOptions({ amountMode } = {}) {
|
function buildChartOptions({ amountMode } = {}) {
|
||||||
const documentStyle = getComputedStyle(document.documentElement);
|
const documentStyle = getComputedStyle(document.documentElement);
|
||||||
const textColor = documentStyle.getPropertyValue('--text-color');
|
const textColor = documentStyle.getPropertyValue('--text-color');
|
||||||
@@ -186,6 +346,12 @@ function updateCharts() {
|
|||||||
amountChartData.value = buildAmountChartData(items);
|
amountChartData.value = buildAmountChartData(items);
|
||||||
orderChartOptions.value = buildChartOptions({ amountMode: false });
|
orderChartOptions.value = buildChartOptions({ amountMode: false });
|
||||||
amountChartOptions.value = buildChartOptions({ amountMode: true });
|
amountChartOptions.value = buildChartOptions({ amountMode: true });
|
||||||
|
withdrawOrderChartData.value = buildWithdrawOrderChartData(items);
|
||||||
|
withdrawAmountChartData.value = buildWithdrawAmountChartData(items);
|
||||||
|
withdrawOrderChartOptions.value = buildChartOptions({ amountMode: false });
|
||||||
|
withdrawAmountChartOptions.value = buildChartOptions({ amountMode: true });
|
||||||
|
interactionChartData.value = buildInteractionChartData(items);
|
||||||
|
interactionChartOptions.value = buildChartOptions({ amountMode: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
const summaryItems = computed(() => {
|
const summaryItems = computed(() => {
|
||||||
@@ -202,6 +368,31 @@ const summaryItems = computed(() => {
|
|||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const withdrawSummaryItems = computed(() => {
|
||||||
|
const summary = overview.value?.summary;
|
||||||
|
if (!summary) return [];
|
||||||
|
|
||||||
|
return [
|
||||||
|
{ key: 'withdraw-apply', label: '提现申请:', value: summary.withdrawal_apply_orders ?? 0, icon: 'pi-inbox', onClick: () => goToWithdrawals('created', false) },
|
||||||
|
{ key: 'withdraw-paid', label: '提现完成:', value: summary.withdrawal_paid_orders ?? 0, icon: 'pi-check-circle', onClick: () => goToWithdrawals('paid', true) },
|
||||||
|
{ key: 'withdraw-failed', label: '提现失败:', value: summary.withdrawal_failed_orders ?? 0, icon: 'pi-times-circle', onClick: () => goToWithdrawals('failed', false) }
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
const contentSummaryItems = computed(() => {
|
||||||
|
const summary = overview.value?.summary;
|
||||||
|
if (!summary) return [];
|
||||||
|
|
||||||
|
const rangeQuery = buildContentsQuery({ useCreatedAt: true });
|
||||||
|
return [
|
||||||
|
{ key: 'content-count', label: '内容总量:', value: summary.content_count ?? 0, icon: 'pi-book', onClick: () => goToContents() },
|
||||||
|
{ key: 'content-created', label: '新增内容:', value: summary.content_created ?? 0, icon: 'pi-plus', onClick: () => goToContents(rangeQuery) },
|
||||||
|
{ key: 'like-actions', label: '新增点赞:', value: summary.like_actions ?? 0, icon: 'pi-thumbs-up', onClick: () => goToContents(rangeQuery) },
|
||||||
|
{ key: 'favorite-actions', label: '新增收藏:', value: summary.favorite_actions ?? 0, icon: 'pi-star', onClick: () => goToContents(rangeQuery) },
|
||||||
|
{ key: 'comment-count', label: '新增评论:', value: summary.comment_count ?? 0, icon: 'pi-comments', onClick: () => goToContents(rangeQuery) }
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
async function loadOverview() {
|
async function loadOverview() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
@@ -287,6 +478,8 @@ watch([getPrimary, getSurface, isDarkTheme], () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<StatisticsStrip v-if="summaryItems.length" :items="summaryItems" containerClass="card mb-4" />
|
<StatisticsStrip v-if="summaryItems.length" :items="summaryItems" containerClass="card mb-4" />
|
||||||
|
<StatisticsStrip v-if="withdrawSummaryItems.length" :items="withdrawSummaryItems" containerClass="card mb-4" />
|
||||||
|
<StatisticsStrip v-if="contentSummaryItems.length" :items="contentSummaryItems" containerClass="card mb-4" />
|
||||||
|
|
||||||
<div class="card mb-4">
|
<div class="card mb-4">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
@@ -313,6 +506,41 @@ watch([getPrimary, getSurface, isDarkTheme], () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<h4 class="m-0">提现趋势</h4>
|
||||||
|
<span class="text-muted-color">申请 / 完成 / 失败</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 xl:grid-cols-2 gap-6">
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="font-medium">订单趋势</span>
|
||||||
|
<span class="text-muted-color text-sm">申请 vs 完成 vs 失败</span>
|
||||||
|
</div>
|
||||||
|
<Chart type="line" :data="withdrawOrderChartData" :options="withdrawOrderChartOptions" class="h-72" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="font-medium">金额趋势</span>
|
||||||
|
<span class="text-muted-color text-sm">单位:元</span>
|
||||||
|
</div>
|
||||||
|
<Chart type="line" :data="withdrawAmountChartData" :options="withdrawAmountChartOptions" class="h-72" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<h4 class="m-0">内容互动趋势</h4>
|
||||||
|
<span class="text-muted-color">新增内容 / 点赞 / 收藏 / 评论</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Chart type="line" :data="interactionChartData" :options="interactionChartOptions" class="h-72" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h4 class="m-0">趋势明细</h4>
|
<h4 class="m-0">趋势明细</h4>
|
||||||
@@ -331,6 +559,28 @@ watch([getPrimary, getSurface, isDarkTheme], () => {
|
|||||||
{{ formatCnyFromYuan(data.refund_amount) }}
|
{{ formatCnyFromYuan(data.refund_amount) }}
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
|
<Column field="withdrawal_apply_orders" header="提现申请" style="min-width: 10rem" />
|
||||||
|
<Column field="withdrawal_apply_amount" header="申请金额" style="min-width: 12rem">
|
||||||
|
<template #body="{ data }">
|
||||||
|
{{ formatCnyFromYuan(data.withdrawal_apply_amount) }}
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
<Column field="withdrawal_paid_orders" header="提现完成" style="min-width: 10rem" />
|
||||||
|
<Column field="withdrawal_paid_amount" header="完成金额" style="min-width: 12rem">
|
||||||
|
<template #body="{ data }">
|
||||||
|
{{ formatCnyFromYuan(data.withdrawal_paid_amount) }}
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
<Column field="withdrawal_failed_orders" header="提现失败" style="min-width: 10rem" />
|
||||||
|
<Column field="withdrawal_failed_amount" header="失败金额" style="min-width: 12rem">
|
||||||
|
<template #body="{ data }">
|
||||||
|
{{ formatCnyFromYuan(data.withdrawal_failed_amount) }}
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
<Column field="content_created" header="新增内容" style="min-width: 10rem" />
|
||||||
|
<Column field="like_actions" header="新增点赞" style="min-width: 10rem" />
|
||||||
|
<Column field="favorite_actions" header="新增收藏" style="min-width: 10rem" />
|
||||||
|
<Column field="comment_count" header="新增评论" style="min-width: 10rem" />
|
||||||
</DataTable>
|
</DataTable>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user