Commit 3b3075584bd52466df71a0a1fdaaf9eef51c655c

Authored by 李曜臣
1 parent 7ba2d701

统计接口实现

美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Dashboard/DashboardCategoryDistributionDto.cs 0 → 100644
  1 +namespace FoodLabeling.Application.Contracts.Dtos.Dashboard;
  2 +
  3 +/// <summary>
  4 +/// 分类分布项
  5 +/// </summary>
  6 +public class DashboardCategoryDistributionDto
  7 +{
  8 + /// <summary>分类Id</summary>
  9 + public string CategoryId { get; set; } = string.Empty;
  10 +
  11 + /// <summary>分类名称</summary>
  12 + public string CategoryName { get; set; } = string.Empty;
  13 +
  14 + /// <summary>数量</summary>
  15 + public int Count { get; set; }
  16 +
  17 + /// <summary>占比(百分比)</summary>
  18 + public decimal Ratio { get; set; }
  19 +}
... ...
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Dashboard/DashboardDailyTrendPointDto.cs 0 → 100644
  1 +namespace FoodLabeling.Application.Contracts.Dtos.Dashboard;
  2 +
  3 +/// <summary>
  4 +/// 日趋势点
  5 +/// </summary>
  6 +public class DashboardDailyTrendPointDto
  7 +{
  8 + /// <summary>日期(yyyy-MM-dd)</summary>
  9 + public string Date { get; set; } = string.Empty;
  10 +
  11 + /// <summary>值</summary>
  12 + public int Value { get; set; }
  13 +}
... ...
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Dashboard/DashboardMetricCardDto.cs 0 → 100644
  1 +namespace FoodLabeling.Application.Contracts.Dtos.Dashboard;
  2 +
  3 +/// <summary>
  4 +/// 仪表盘指标卡片
  5 +/// </summary>
  6 +public class DashboardMetricCardDto
  7 +{
  8 + /// <summary>指标唯一标识(如 labelsPrintedToday)</summary>
  9 + public string Key { get; set; } = string.Empty;
  10 +
  11 + /// <summary>指标标题</summary>
  12 + public string Title { get; set; } = string.Empty;
  13 +
  14 + /// <summary>当前值</summary>
  15 + public int Value { get; set; }
  16 +
  17 + /// <summary>对比周期值</summary>
  18 + public int PreviousValue { get; set; }
  19 +
  20 + /// <summary>增减值(Value - PreviousValue)</summary>
  21 + public int ChangeValue { get; set; }
  22 +
  23 + /// <summary>增减比例(百分比)</summary>
  24 + public decimal ChangeRate { get; set; }
  25 +}
... ...
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Dashboard/DashboardOverviewOutputDto.cs 0 → 100644
  1 +namespace FoodLabeling.Application.Contracts.Dtos.Dashboard;
  2 +
  3 +/// <summary>
  4 +/// 仪表盘总览输出
  5 +/// </summary>
  6 +public class DashboardOverviewOutputDto
  7 +{
  8 + /// <summary>今日打印标签</summary>
  9 + public DashboardMetricCardDto LabelsPrintedToday { get; set; } = new();
  10 +
  11 + /// <summary>启用模板数</summary>
  12 + public DashboardMetricCardDto ActiveTemplates { get; set; } = new();
  13 +
  14 + /// <summary>活跃用户数</summary>
  15 + public DashboardMetricCardDto ActiveUsers { get; set; } = new();
  16 +
  17 + /// <summary>门店数</summary>
  18 + public DashboardMetricCardDto Locations { get; set; } = new();
  19 +
  20 + /// <summary>人员数</summary>
  21 + public DashboardMetricCardDto People { get; set; } = new();
  22 +
  23 + /// <summary>产品数</summary>
  24 + public DashboardMetricCardDto Products { get; set; } = new();
  25 +
  26 + /// <summary>指标卡片</summary>
  27 + public List<DashboardMetricCardDto> MetricCards { get; set; } = new();
  28 +
  29 + /// <summary>近7天打印趋势</summary>
  30 + public List<DashboardDailyTrendPointDto> WeeklyPrintVolume { get; set; } = new();
  31 +
  32 + /// <summary>按分类分布</summary>
  33 + public List<DashboardCategoryDistributionDto> CategoryDistribution { get; set; } = new();
  34 +
  35 + /// <summary>分类分布总数</summary>
  36 + public int CategoryDistributionTotal { get; set; }
  37 +
  38 + /// <summary>按分类分布(前端直观命名)</summary>
  39 + public List<DashboardCategoryDistributionDto> ByCategory { get; set; } = new();
  40 +
  41 + /// <summary>按分类分布总数(前端直观命名)</summary>
  42 + public int ByCategoryTotal { get; set; }
  43 +
  44 + /// <summary>统计时间</summary>
  45 + public DateTime GeneratedAt { get; set; }
  46 +}
... ...
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IDashboardAppService.cs 0 → 100644
  1 +using FoodLabeling.Application.Contracts.Dtos.Dashboard;
  2 +using Volo.Abp.Application.Services;
  3 +
  4 +namespace FoodLabeling.Application.Contracts.IServices;
  5 +
  6 +/// <summary>
  7 +/// Dashboard 统计接口(美国版)
  8 +/// </summary>
  9 +public interface IDashboardAppService : IApplicationService
  10 +{
  11 + /// <summary>
  12 + /// 获取 Dashboard 总览统计(卡片 + 周趋势 + 分类分布)
  13 + /// </summary>
  14 + Task<DashboardOverviewOutputDto> GetOverviewAsync();
  15 +}
... ...
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DashboardAppService.cs 0 → 100644
  1 +using FoodLabeling.Application.Contracts.Dtos.Dashboard;
  2 +using FoodLabeling.Application.Contracts.IServices;
  3 +using FoodLabeling.Application.Services.DbModels;
  4 +using FoodLabeling.Domain.Entities;
  5 +using Volo.Abp.Application.Services;
  6 +using Yi.Framework.Rbac.Domain.Entities;
  7 +using Yi.Framework.SqlSugarCore.Abstractions;
  8 +
  9 +namespace FoodLabeling.Application.Services;
  10 +
  11 +/// <summary>
  12 +/// Dashboard 统计服务(美国版)
  13 +/// </summary>
  14 +public class DashboardAppService : ApplicationService, IDashboardAppService
  15 +{
  16 + private readonly ISqlSugarDbContext _dbContext;
  17 + private readonly ISqlSugarRepository<LocationAggregateRoot, Guid> _locationRepository;
  18 + private readonly ISqlSugarRepository<UserAggregateRoot, Guid> _userRepository;
  19 +
  20 + public DashboardAppService(
  21 + ISqlSugarDbContext dbContext,
  22 + ISqlSugarRepository<LocationAggregateRoot, Guid> locationRepository,
  23 + ISqlSugarRepository<UserAggregateRoot, Guid> userRepository)
  24 + {
  25 + _dbContext = dbContext;
  26 + _locationRepository = locationRepository;
  27 + _userRepository = userRepository;
  28 + }
  29 +
  30 + /// <inheritdoc />
  31 + public async Task<DashboardOverviewOutputDto> GetOverviewAsync()
  32 + {
  33 + var now = DateTime.Now;
  34 + var todayStart = now.Date;
  35 + var tomorrowStart = todayStart.AddDays(1);
  36 + var yesterdayStart = todayStart.AddDays(-1);
  37 + var weekStart = todayStart.AddDays(-6);
  38 + var prevWeekStart = todayStart.AddDays(-13);
  39 +
  40 + var printedToday = await _dbContext.SqlSugarClient.Queryable<FlLabelPrintTaskDbEntity>()
  41 + .CountAsync(x => x.CreationTime >= todayStart && x.CreationTime < tomorrowStart);
  42 +
  43 + var printedYesterday = await _dbContext.SqlSugarClient.Queryable<FlLabelPrintTaskDbEntity>()
  44 + .CountAsync(x => x.CreationTime >= yesterdayStart && x.CreationTime < todayStart);
  45 +
  46 + var activeTemplates = await _dbContext.SqlSugarClient.Queryable<FlLabelTemplateDbEntity>()
  47 + .CountAsync(x => !x.IsDeleted && x.State);
  48 + var activeTemplatesPrevWeek = await _dbContext.SqlSugarClient.Queryable<FlLabelTemplateDbEntity>()
  49 + .CountAsync(x => !x.IsDeleted && x.State && x.CreationTime < weekStart);
  50 +
  51 + var activeUsers = await _userRepository._DbQueryable
  52 + .CountAsync(x => !x.IsDeleted && x.State);
  53 + var activeUsersPrevWeek = await _userRepository._DbQueryable
  54 + .CountAsync(x => !x.IsDeleted && x.State && x.CreationTime < weekStart);
  55 +
  56 + var locations = await _locationRepository._DbQueryable
  57 + .CountAsync(x => !x.IsDeleted);
  58 + var locationsPrevWeek = await _locationRepository._DbQueryable
  59 + .CountAsync(x => !x.IsDeleted && x.CreationTime < weekStart);
  60 +
  61 + var people = await _userRepository._DbQueryable
  62 + .CountAsync(x => !x.IsDeleted);
  63 + var peoplePrevWeek = await _userRepository._DbQueryable
  64 + .CountAsync(x => !x.IsDeleted && x.CreationTime < weekStart);
  65 +
  66 + var products = await _dbContext.SqlSugarClient.Queryable<FlProductDbEntity>()
  67 + .CountAsync(x => !x.IsDeleted);
  68 + // fl_product 当前实体未映射 CreationTime,无法按时间切分对比,先回退为同口径总量对比
  69 + var productsPrevWeek = products;
  70 +
  71 + var weeklyPrintRaw = await _dbContext.SqlSugarClient.Queryable<FlLabelPrintTaskDbEntity>()
  72 + .Where(x => x.CreationTime >= weekStart && x.CreationTime < tomorrowStart)
  73 + .Select(x => x.CreationTime)
  74 + .ToListAsync();
  75 +
  76 + var weeklyDict = weeklyPrintRaw
  77 + .GroupBy(x => x.Date)
  78 + .ToDictionary(g => g.Key, g => g.Count());
  79 +
  80 + var weeklyTrend = Enumerable.Range(0, 7)
  81 + .Select(i =>
  82 + {
  83 + var d = weekStart.AddDays(i).Date;
  84 + return new DashboardDailyTrendPointDto
  85 + {
  86 + Date = d.ToString("yyyy-MM-dd"),
  87 + Value = weeklyDict.TryGetValue(d, out var v) ? v : 0
  88 + };
  89 + })
  90 + .ToList();
  91 +
  92 + var categories = await _dbContext.SqlSugarClient.Queryable<FlLabelCategoryDbEntity>()
  93 + .Where(x => !x.IsDeleted && x.State)
  94 + .ToListAsync();
  95 +
  96 + var labelCategoryIds = categories.Select(x => x.Id).ToList();
  97 + var labelRows = labelCategoryIds.Count == 0
  98 + ? new List<string?>()
  99 + : await _dbContext.SqlSugarClient.Queryable<FlLabelDbEntity>()
  100 + .Where(x => !x.IsDeleted && x.LabelCategoryId != null && labelCategoryIds.Contains(x.LabelCategoryId))
  101 + .Select(x => x.LabelCategoryId)
  102 + .ToListAsync();
  103 +
  104 + var labelCountByCategory = labelRows
  105 + .Where(x => !string.IsNullOrWhiteSpace(x))
  106 + .GroupBy(x => x!)
  107 + .ToDictionary(g => g.Key, g => g.Count());
  108 +
  109 + var categoryDistributionTotal = labelCountByCategory.Values.Sum();
  110 + var categoryDistribution = categories
  111 + .Select(c =>
  112 + {
  113 + var count = labelCountByCategory.TryGetValue(c.Id, out var v) ? v : 0;
  114 + var ratio = categoryDistributionTotal == 0
  115 + ? 0m
  116 + : Math.Round(count * 100m / categoryDistributionTotal, 2);
  117 + return new DashboardCategoryDistributionDto
  118 + {
  119 + CategoryId = c.Id,
  120 + CategoryName = c.CategoryName,
  121 + Count = count,
  122 + Ratio = ratio
  123 + };
  124 + })
  125 + .Where(x => x.Count > 0)
  126 + .OrderByDescending(x => x.Count)
  127 + .ThenBy(x => x.CategoryName)
  128 + .ToList();
  129 +
  130 + var labelsPrintedTodayCard = BuildMetricCard("labelsPrintedToday", "Labels Printed Today", printedToday, printedYesterday);
  131 + var activeTemplatesCard = BuildMetricCard("activeTemplates", "Active Templates", activeTemplates, activeTemplatesPrevWeek);
  132 + var activeUsersCard = BuildMetricCard("activeUsers", "Active Users", activeUsers, activeUsersPrevWeek);
  133 + var locationsCard = BuildMetricCard("locations", "Locations", locations, locationsPrevWeek);
  134 + var peopleCard = BuildMetricCard("people", "People", people, peoplePrevWeek);
  135 + var productsCard = BuildMetricCard("products", "Products", products, productsPrevWeek);
  136 +
  137 + var output = new DashboardOverviewOutputDto
  138 + {
  139 + LabelsPrintedToday = labelsPrintedTodayCard,
  140 + ActiveTemplates = activeTemplatesCard,
  141 + ActiveUsers = activeUsersCard,
  142 + Locations = locationsCard,
  143 + People = peopleCard,
  144 + Products = productsCard,
  145 + MetricCards = new List<DashboardMetricCardDto>
  146 + {
  147 + labelsPrintedTodayCard,
  148 + activeTemplatesCard,
  149 + activeUsersCard,
  150 + locationsCard,
  151 + peopleCard,
  152 + productsCard
  153 + },
  154 + WeeklyPrintVolume = weeklyTrend,
  155 + CategoryDistribution = categoryDistribution,
  156 + CategoryDistributionTotal = categoryDistributionTotal,
  157 + ByCategory = categoryDistribution,
  158 + ByCategoryTotal = categoryDistributionTotal,
  159 + GeneratedAt = now
  160 + };
  161 +
  162 + return output;
  163 + }
  164 +
  165 + private static DashboardMetricCardDto BuildMetricCard(string key, string title, int value, int previousValue)
  166 + {
  167 + var changeValue = value - previousValue;
  168 + var changeRate = previousValue <= 0
  169 + ? (value > 0 ? 100m : 0m)
  170 + : Math.Round(changeValue * 100m / previousValue, 2);
  171 +
  172 + return new DashboardMetricCardDto
  173 + {
  174 + Key = key,
  175 + Title = title,
  176 + Value = value,
  177 + PreviousValue = previousValue,
  178 + ChangeValue = changeValue,
  179 + ChangeRate = changeRate
  180 + };
  181 + }
  182 +}
... ...
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppAuthAppService.cs
... ... @@ -38,7 +38,6 @@ namespace FoodLabeling.Application.Services;
38 38 public class UsAppAuthAppService : ApplicationService, IUsAppAuthAppService
39 39 {
40 40 private readonly IAccountManager _accountManager;
41   -
42 41 private readonly ISqlSugarRepository<UserAggregateRoot, Guid> _userRepository;
43 42 private readonly ISqlSugarDbContext _dbContext;
44 43 private readonly IHttpContextAccessor _httpContextAccessor;
... ...
项目相关文档/Dashboard统计接口对接说明.md 0 → 100644
  1 +# Dashboard 统计接口对接说明
  2 +
  3 +> 适用范围:美国版 Web 管理端 Dashboard 首页统计
  4 +>
  5 +> 接口实现:`IDashboardAppService.GetOverviewAsync` / `DashboardAppService.GetOverviewAsync`
  6 +
  7 +---
  8 +
  9 +## 1. 接口信息
  10 +
  11 +- **方法**:`GET`
  12 +- **路径**:`/api/app/dashboard/overview`
  13 +- **鉴权**:需要登录(Bearer Token)
  14 +- **请求参数**:无
  15 +
  16 +---
  17 +
  18 +## 2. 返回结构(顶层)
  19 +
  20 +```json
  21 +{
  22 + "labelsPrintedToday": {},
  23 + "activeTemplates": {},
  24 + "activeUsers": {},
  25 + "locations": {},
  26 + "people": {},
  27 + "products": {},
  28 + "weeklyPrintVolume": [],
  29 + "byCategory": [],
  30 + "byCategoryTotal": 0,
  31 + "generatedAt": "2026-04-22T10:00:00+08:00",
  32 +
  33 + "metricCards": [],
  34 + "categoryDistribution": [],
  35 + "categoryDistributionTotal": 0
  36 +}
  37 +```
  38 +
  39 +说明:
  40 +- `labelsPrintedToday/activeTemplates/...`、`byCategory/byCategoryTotal` 是**前端直观命名**(推荐使用)。
  41 +- `metricCards`、`categoryDistribution`、`categoryDistributionTotal` 为**兼容字段**(与旧版返回一致)。
  42 +
  43 +---
  44 +
  45 +## 3. 字段说明
  46 +
  47 +### 3.1 指标卡片对象(`DashboardMetricCardDto`)
  48 +
  49 +用于以下字段:
  50 +- `labelsPrintedToday`
  51 +- `activeTemplates`
  52 +- `activeUsers`
  53 +- `locations`
  54 +- `people`
  55 +- `products`
  56 +- `metricCards[]`(同结构)
  57 +
  58 +| 字段 | 类型 | 说明 |
  59 +|---|---|---|
  60 +| `key` | string | 指标标识(如 `labelsPrintedToday`) |
  61 +| `title` | string | 指标标题 |
  62 +| `value` | int | 当前值 |
  63 +| `previousValue` | int | 对比周期值 |
  64 +| `changeValue` | int | 增减值(`value - previousValue`) |
  65 +| `changeRate` | decimal | 增减比例(百分比,保留 2 位) |
  66 +
  67 +---
  68 +
  69 +### 3.2 周趋势(`weeklyPrintVolume`)
  70 +
  71 +| 字段 | 类型 | 说明 |
  72 +|---|---|---|
  73 +| `date` | string | 日期,格式 `yyyy-MM-dd` |
  74 +| `value` | int | 当天打印量 |
  75 +
  76 +---
  77 +
  78 +### 3.3 分类分布(`byCategory`)
  79 +
  80 +`byCategory` 与 `categoryDistribution` 结构一致。
  81 +
  82 +| 字段 | 类型 | 说明 |
  83 +|---|---|---|
  84 +| `categoryId` | string | 分类 Id(`fl_label_category.Id`) |
  85 +| `categoryName` | string | 分类名称 |
  86 +| `count` | int | 该分类下标签数量 |
  87 +| `ratio` | decimal | 占比(百分比,保留 2 位) |
  88 +
  89 +---
  90 +
  91 +## 4. 返回示例
  92 +
  93 +```json
  94 +{
  95 + "labelsPrintedToday": {
  96 + "key": "labelsPrintedToday",
  97 + "title": "Labels Printed Today",
  98 + "value": 342,
  99 + "previousValue": 305,
  100 + "changeValue": 37,
  101 + "changeRate": 12.13
  102 + },
  103 + "activeTemplates": {
  104 + "key": "activeTemplates",
  105 + "title": "Active Templates",
  106 + "value": 24,
  107 + "previousValue": 22,
  108 + "changeValue": 2,
  109 + "changeRate": 9.09
  110 + },
  111 + "activeUsers": {
  112 + "key": "activeUsers",
  113 + "title": "Active Users",
  114 + "value": 8,
  115 + "previousValue": 7,
  116 + "changeValue": 1,
  117 + "changeRate": 14.29
  118 + },
  119 + "locations": {
  120 + "key": "locations",
  121 + "title": "Locations",
  122 + "value": 12,
  123 + "previousValue": 11,
  124 + "changeValue": 1,
  125 + "changeRate": 9.09
  126 + },
  127 + "people": {
  128 + "key": "people",
  129 + "title": "People",
  130 + "value": 48,
  131 + "previousValue": 45,
  132 + "changeValue": 3,
  133 + "changeRate": 6.67
  134 + },
  135 + "products": {
  136 + "key": "products",
  137 + "title": "Products",
  138 + "value": 156,
  139 + "previousValue": 156,
  140 + "changeValue": 0,
  141 + "changeRate": 0
  142 + },
  143 + "weeklyPrintVolume": [
  144 + { "date": "2026-04-16", "value": 142 },
  145 + { "date": "2026-04-17", "value": 226 },
  146 + { "date": "2026-04-18", "value": 185 },
  147 + { "date": "2026-04-19", "value": 261 },
  148 + { "date": "2026-04-20", "value": 192 },
  149 + { "date": "2026-04-21", "value": 121 },
  150 + { "date": "2026-04-22", "value": 342 }
  151 + ],
  152 + "byCategory": [
  153 + { "categoryId": "CAT001", "categoryName": "Breakfast", "count": 420, "ratio": 42.00 },
  154 + { "categoryId": "CAT002", "categoryName": "Lunch", "count": 350, "ratio": 35.00 },
  155 + { "categoryId": "CAT003", "categoryName": "Dinner", "count": 230, "ratio": 23.00 }
  156 + ],
  157 + "byCategoryTotal": 1000,
  158 + "generatedAt": "2026-04-22T10:00:00+08:00",
  159 + "metricCards": [],
  160 + "categoryDistribution": [],
  161 + "categoryDistributionTotal": 1000
  162 +}
  163 +```
  164 +
  165 +---
  166 +
  167 +## 5. 统计口径说明
  168 +
  169 +### 5.1 Labels Printed Today
  170 +- 当前值:`fl_label_print_task` 在“今日 00:00~次日 00:00”的记录数。
  171 +- 对比值:昨日同口径记录数。
  172 +
  173 +### 5.2 Active Templates
  174 +- 当前值:`fl_label_template` 中 `IsDeleted = false AND State = true` 数量。
  175 +- 对比值:同口径且 `CreationTime < 最近7天起始日` 的数量。
  176 +
  177 +### 5.3 Active Users
  178 +- 当前值:`User` 表中 `IsDeleted = false AND State = true` 数量。
  179 +- 对比值:同口径且 `CreationTime < 最近7天起始日` 的数量。
  180 +
  181 +### 5.4 Locations
  182 +- 当前值:`location` 表中 `IsDeleted = false` 数量。
  183 +- 对比值:同口径且 `CreationTime < 最近7天起始日` 的数量。
  184 +
  185 +### 5.5 People
  186 +- 当前值:`User` 表中 `IsDeleted = false` 数量。
  187 +- 对比值:同口径且 `CreationTime < 最近7天起始日` 的数量。
  188 +
  189 +### 5.6 Products
  190 +- 当前值:`fl_product` 表中 `IsDeleted = false` 数量。
  191 +- 对比值:当前版本由于 `FlProductDbEntity` 未映射 `CreationTime`,临时按同口径总量返回(即变化可能为 0)。
  192 +
  193 +### 5.7 Weekly Print Volume
  194 +- 统计最近 7 天(含今天)每天 `fl_label_print_task` 数量。
  195 +- 无数据日期补 0。
  196 +
  197 +### 5.8 By Category
  198 +- 基于启用且未删除的 `fl_label_category` 作为分类集合。
  199 +- 统计 `fl_label` 中未删除且 `LabelCategoryId` 命中的数量。
  200 +- 占比按 `count / byCategoryTotal * 100` 计算,保留 2 位。
  201 +
  202 +---
  203 +
  204 +## 6. 前端接入建议
  205 +
  206 +- 新页面优先使用:
  207 + - 指标:`labelsPrintedToday`、`activeTemplates`、`activeUsers`、`locations`、`people`、`products`
  208 + - 图表:`weeklyPrintVolume`
  209 + - 环图:`byCategory` + `byCategoryTotal`
  210 +- 旧逻辑仍可使用兼容字段:
  211 + - `metricCards`
  212 + - `categoryDistribution`
  213 + - `categoryDistributionTotal`
  214 +
... ...