diff --git a/backend/app/http/super/v1/contents.go b/backend/app/http/super/v1/contents.go index b8b1660..620346c 100644 --- a/backend/app/http/super/v1/contents.go +++ b/backend/app/http/super/v1/contents.go @@ -50,6 +50,20 @@ func (c *contents) ListTenantContents(ctx fiber.Ctx, tenantID int64, filter *dto return services.Super.ListContents(ctx, filter) } +// Content statistics +// +// @Router /super/v1/contents/statistics [get] +// @Summary Content statistics +// @Description Content statistics +// @Tags Content +// @Accept json +// @Produce json +// @Success 200 {object} dto.SuperContentStatisticsResponse +// @Bind filter query +func (c *contents) Statistics(ctx fiber.Ctx, filter *dto.SuperContentStatisticsFilter) (*dto.SuperContentStatisticsResponse, error) { + return services.Super.ContentStatistics(ctx, filter) +} + // Update content status // // @Router /super/v1/tenants/:tenantID/contents/:contentID/status [patch] diff --git a/backend/app/http/super/v1/dto/super.go b/backend/app/http/super/v1/dto/super.go index e475055..7e24d99 100644 --- a/backend/app/http/super/v1/dto/super.go +++ b/backend/app/http/super/v1/dto/super.go @@ -131,6 +131,18 @@ type SuperContentListFilter struct { Desc *string `query:"desc"` } +// SuperContentStatisticsFilter 超管内容统计查询条件。 +type SuperContentStatisticsFilter struct { + // TenantID 租户ID(不传代表全平台)。 + TenantID *int64 `query:"tenant_id"` + // StartAt 统计开始时间(RFC3339,可选;默认当前时间往前 7 天)。 + StartAt *string `query:"start_at"` + // EndAt 统计结束时间(RFC3339,可选;默认当前时间)。 + EndAt *string `query:"end_at"` + // Granularity 统计粒度(day;目前仅支持 day)。 + Granularity *string `query:"granularity"` +} + type SuperOrderListFilter struct { requests.Pagination // ID 订单ID,精确匹配。 @@ -635,3 +647,19 @@ type AdminContentOwnerLite struct { // Status 用户状态。 Status consts.UserStatus `json:"status"` } + +// SuperContentStatisticsResponse 超管内容统计响应。 +type SuperContentStatisticsResponse struct { + // TotalCount 内容总量。 + TotalCount int64 `json:"total_count"` + // Trend 按天新增内容趋势。 + Trend []SuperContentTrendItem `json:"trend"` +} + +// SuperContentTrendItem 内容新增趋势条目。 +type SuperContentTrendItem struct { + // Date 日期(YYYY-MM-DD)。 + Date string `json:"date"` + // CreatedCount 当日新增内容数量。 + CreatedCount int64 `json:"created_count"` +} diff --git a/backend/app/http/super/v1/routes.gen.go b/backend/app/http/super/v1/routes.gen.go index 231ef85..2c38d2c 100644 --- a/backend/app/http/super/v1/routes.gen.go +++ b/backend/app/http/super/v1/routes.gen.go @@ -58,6 +58,11 @@ func (r *Routes) Register(router fiber.Router) { r.contents.List, Query[dto.SuperContentListFilter]("filter"), )) + r.log.Debugf("Registering route: Get /super/v1/contents/statistics -> contents.Statistics") + router.Get("/super/v1/contents/statistics"[len(r.Path()):], DataFunc1( + r.contents.Statistics, + Query[dto.SuperContentStatisticsFilter]("filter"), + )) r.log.Debugf("Registering route: Get /super/v1/tenants/:tenantID/contents -> contents.ListTenantContents") router.Get("/super/v1/tenants/:tenantID/contents"[len(r.Path()):], DataFunc2( r.contents.ListTenantContents, diff --git a/backend/app/services/super.go b/backend/app/services/super.go index bb057fe..36bed68 100644 --- a/backend/app/services/super.go +++ b/backend/app/services/super.go @@ -2059,6 +2059,74 @@ func (s *super) BatchReviewContents(ctx context.Context, operatorID int64, form return nil } +func (s *super) ContentStatistics(ctx context.Context, filter *super_dto.SuperContentStatisticsFilter) (*super_dto.SuperContentStatisticsResponse, error) { + // 统一统计时间范围与粒度,默认最近 7 天。 + reportFilter := &super_dto.SuperReportOverviewFilter{} + if filter != nil { + reportFilter.TenantID = filter.TenantID + reportFilter.StartAt = filter.StartAt + reportFilter.EndAt = filter.EndAt + reportFilter.Granularity = filter.Granularity + } + rg, err := s.normalizeReportRange(reportFilter) + if err != nil { + return nil, err + } + + tenantID := int64(0) + if filter != nil && filter.TenantID != nil { + tenantID = *filter.TenantID + } + + // 统计内容总量,支持租户维度过滤。 + tbl, q := models.ContentQuery.QueryContext(ctx) + if tenantID > 0 { + q = q.Where(tbl.TenantID.Eq(tenantID)) + } + total, err := q.Count() + if err != nil { + return nil, errorx.ErrDatabaseError.WithCause(err) + } + + // 按天聚合新增内容数量,补齐趋势序列。 + type contentAggRow struct { + Day time.Time `gorm:"column:day"` + Count int64 `gorm:"column:count"` + } + rows := make([]contentAggRow, 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) + } + + trendMap := make(map[string]int64, len(rows)) + for _, row := range rows { + key := row.Day.Format("2006-01-02") + trendMap[key] = row.Count + } + + trend := make([]super_dto.SuperContentTrendItem, 0) + for day := rg.startDay; !day.After(rg.endDay); day = day.AddDate(0, 0, 1) { + key := day.Format("2006-01-02") + trend = append(trend, super_dto.SuperContentTrendItem{ + Date: key, + CreatedCount: trendMap[key], + }) + } + + return &super_dto.SuperContentStatisticsResponse{ + TotalCount: total, + Trend: trend, + }, nil +} + func (s *super) ListOrders(ctx context.Context, filter *super_dto.SuperOrderListFilter) (*requests.Pager, error) { tbl, q := models.OrderQuery.QueryContext(ctx) diff --git a/backend/docs/docs.go b/backend/docs/docs.go index 0882fd6..d89c2e9 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -167,6 +167,29 @@ const docTemplate = `{ } } }, + "/super/v1/contents/statistics": { + "get": { + "description": "Content statistics", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Content" + ], + "summary": "Content statistics", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.SuperContentStatisticsResponse" + } + } + } + } + }, "/super/v1/contents/{id}/review": { "post": { "description": "Review content", @@ -6374,6 +6397,22 @@ const docTemplate = `{ } } }, + "dto.SuperContentStatisticsResponse": { + "type": "object", + "properties": { + "total_count": { + "description": "TotalCount 内容总量。", + "type": "integer" + }, + "trend": { + "description": "Trend 按天新增内容趋势。", + "type": "array", + "items": { + "$ref": "#/definitions/dto.SuperContentTrendItem" + } + } + } + }, "dto.SuperContentTenantLite": { "type": "object", "properties": { @@ -6391,6 +6430,19 @@ const docTemplate = `{ } } }, + "dto.SuperContentTrendItem": { + "type": "object", + "properties": { + "created_count": { + "description": "CreatedCount 当日新增内容数量。", + "type": "integer" + }, + "date": { + "description": "Date 日期(YYYY-MM-DD)。", + "type": "string" + } + } + }, "dto.SuperCouponGrantItem": { "type": "object", "properties": { diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 1f877c5..38d8fc9 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -161,6 +161,29 @@ } } }, + "/super/v1/contents/statistics": { + "get": { + "description": "Content statistics", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Content" + ], + "summary": "Content statistics", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.SuperContentStatisticsResponse" + } + } + } + } + }, "/super/v1/contents/{id}/review": { "post": { "description": "Review content", @@ -6368,6 +6391,22 @@ } } }, + "dto.SuperContentStatisticsResponse": { + "type": "object", + "properties": { + "total_count": { + "description": "TotalCount 内容总量。", + "type": "integer" + }, + "trend": { + "description": "Trend 按天新增内容趋势。", + "type": "array", + "items": { + "$ref": "#/definitions/dto.SuperContentTrendItem" + } + } + } + }, "dto.SuperContentTenantLite": { "type": "object", "properties": { @@ -6385,6 +6424,19 @@ } } }, + "dto.SuperContentTrendItem": { + "type": "object", + "properties": { + "created_count": { + "description": "CreatedCount 当日新增内容数量。", + "type": "integer" + }, + "date": { + "description": "Date 日期(YYYY-MM-DD)。", + "type": "string" + } + } + }, "dto.SuperCouponGrantItem": { "type": "object", "properties": { diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 895fc5b..cd9d6f2 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -1048,6 +1048,17 @@ definitions: required: - action type: object + dto.SuperContentStatisticsResponse: + properties: + total_count: + description: TotalCount 内容总量。 + type: integer + trend: + description: Trend 按天新增内容趋势。 + items: + $ref: '#/definitions/dto.SuperContentTrendItem' + type: array + type: object dto.SuperContentTenantLite: properties: code: @@ -1060,6 +1071,15 @@ definitions: description: Name 租户名称。 type: string type: object + dto.SuperContentTrendItem: + properties: + created_count: + description: CreatedCount 当日新增内容数量。 + type: integer + date: + description: Date 日期(YYYY-MM-DD)。 + type: string + type: object dto.SuperCouponGrantItem: properties: coupon_id: @@ -2500,6 +2520,21 @@ paths: summary: Batch review contents tags: - Content + /super/v1/contents/statistics: + get: + consumes: + - application/json + description: Content statistics + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/dto.SuperContentStatisticsResponse' + summary: Content statistics + tags: + - Content /super/v1/coupon-grants: get: consumes: diff --git a/docs/superadmin_progress.md b/docs/superadmin_progress.md index 226e7a4..f216a28 100644 --- a/docs/superadmin_progress.md +++ b/docs/superadmin_progress.md @@ -4,8 +4,8 @@ ## 1) 总体结论 -- **已落地**:登录、租户/用户/订单/内容基础管理、内容审核(含批量)、提现审核、报表概览与导出、用户钱包/通知/优惠券/实名/充值记录视图、创作者申请/成员审核/邀请、优惠券创建/编辑/发放/冻结/发放记录。 -- **部分落地**:平台概览(缺内容统计/趋势、退款率/漏斗)、租户详情(缺财务/报表聚合)、内容治理(缺评论/举报)、创作者治理(缺提现审核联动与结算账户审批流)、财务(缺钱包流水/异常排查)。 +- **已落地**:登录、租户/用户/订单/内容基础管理、内容审核(含批量)、平台概览(内容趋势/退款率/漏斗)、提现审核、报表概览与导出、用户钱包/通知/优惠券/实名/充值记录视图、创作者申请/成员审核/邀请、优惠券创建/编辑/发放/冻结/发放记录。 +- **部分落地**:租户详情(缺财务/报表聚合)、内容治理(缺评论/举报)、创作者治理(缺提现审核联动与结算账户审批流)、财务(缺钱包流水/异常排查)、报表(缺提现/内容深度指标与钻取)。 - **未落地**:资产治理、通知中心、审计与系统配置类能力。 ## 2) 按页面完成度(对照 2.x) @@ -16,9 +16,9 @@ - 缺口:无显著功能缺口。 ### 2.2 平台概览 `/` -- 状态:**部分完成** -- 已有:用户统计、订单统计、租户总数(来自租户列表分页)。 -- 缺口:平台级内容总量、趋势图、退款率/订单漏斗等运营指标未落地。 +- 状态:**已完成** +- 已有:用户统计、订单统计、租户总数、内容总量与趋势、退款率与订单漏斗。 +- 缺口:无显著功能缺口。 ### 2.3 租户管理 `/superadmin/tenants` - 状态:**已完成** @@ -87,7 +87,7 @@ ## 4) 建议的下一步(按优先级) -1. **平台概览增强**:补齐内容总量与趋势、退款率、订单漏斗等核心指标。 -2. **资产与通知中心**:补齐资产治理与通知中心接口/页面,形成治理闭环。 -3. **用户互动明细**:补齐收藏/点赞/关注等互动明细视图与聚合能力。 -4. **优惠券异常核查**:完善发放/核销异常监测与风控处理流程。 +1. **资产与通知中心**:补齐资产治理与通知中心接口/页面,形成治理闭环。 +2. **用户互动明细**:补齐收藏/点赞/关注等互动明细视图与聚合能力。 +3. **优惠券异常核查**:完善发放/核销异常监测与风控处理流程。 +4. **报表深化**:补齐提现/内容维度指标与多维钻取能力。 diff --git a/frontend/superadmin/src/service/ContentService.js b/frontend/superadmin/src/service/ContentService.js index 47706aa..1f0b64b 100644 --- a/frontend/superadmin/src/service/ContentService.js +++ b/frontend/superadmin/src/service/ContentService.js @@ -130,5 +130,21 @@ export const ContentService = { reason } }); + }, + async getContentStatistics({ tenant_id, start_at, end_at, granularity } = {}) { + const iso = (d) => { + if (!d) return undefined; + const date = d instanceof Date ? d : new Date(d); + if (Number.isNaN(date.getTime())) return undefined; + return date.toISOString(); + }; + + const query = { + tenant_id, + start_at: iso(start_at), + end_at: iso(end_at), + granularity + }; + return requestJson('/super/v1/contents/statistics', { query }); } }; diff --git a/frontend/superadmin/src/views/Dashboard.vue b/frontend/superadmin/src/views/Dashboard.vue index 7aa08f2..bd9ca15 100644 --- a/frontend/superadmin/src/views/Dashboard.vue +++ b/frontend/superadmin/src/views/Dashboard.vue @@ -1,12 +1,16 @@ @@ -136,6 +279,32 @@ onMounted(() => { +
+
+
+
+
+

内容趋势

+
近7日
+
+
+ + +
+
+
+
+
+
+

订单漏斗

+
近7日退款率:{{ refundRateText }}
+
+
+ +
+
+
+

快捷入口