From 3b3075584bd52466df71a0a1fdaaf9eef51c655c Mon Sep 17 00:00:00 2001
From: 李曜臣
Date: Thu, 23 Apr 2026 20:56:17 +0800
Subject: [PATCH] 统计接口实现
---
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Dashboard/DashboardCategoryDistributionDto.cs | 19 +++++++++++++++++++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Dashboard/DashboardDailyTrendPointDto.cs | 13 +++++++++++++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Dashboard/DashboardMetricCardDto.cs | 25 +++++++++++++++++++++++++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Dashboard/DashboardOverviewOutputDto.cs | 46 ++++++++++++++++++++++++++++++++++++++++++++++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IDashboardAppService.cs | 15 +++++++++++++++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DashboardAppService.cs | 182 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppAuthAppService.cs | 1 -
项目相关文档/Dashboard统计接口对接说明.md | 214 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 514 insertions(+), 1 deletion(-)
create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Dashboard/DashboardCategoryDistributionDto.cs
create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Dashboard/DashboardDailyTrendPointDto.cs
create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Dashboard/DashboardMetricCardDto.cs
create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Dashboard/DashboardOverviewOutputDto.cs
create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IDashboardAppService.cs
create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DashboardAppService.cs
create mode 100644 项目相关文档/Dashboard统计接口对接说明.md
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Dashboard/DashboardCategoryDistributionDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Dashboard/DashboardCategoryDistributionDto.cs
new file mode 100644
index 0000000..755ec97
--- /dev/null
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Dashboard/DashboardCategoryDistributionDto.cs
@@ -0,0 +1,19 @@
+namespace FoodLabeling.Application.Contracts.Dtos.Dashboard;
+
+///
+/// 分类分布项
+///
+public class DashboardCategoryDistributionDto
+{
+ /// 分类Id
+ public string CategoryId { get; set; } = string.Empty;
+
+ /// 分类名称
+ public string CategoryName { get; set; } = string.Empty;
+
+ /// 数量
+ public int Count { get; set; }
+
+ /// 占比(百分比)
+ public decimal Ratio { get; set; }
+}
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Dashboard/DashboardDailyTrendPointDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Dashboard/DashboardDailyTrendPointDto.cs
new file mode 100644
index 0000000..1c58d7c
--- /dev/null
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Dashboard/DashboardDailyTrendPointDto.cs
@@ -0,0 +1,13 @@
+namespace FoodLabeling.Application.Contracts.Dtos.Dashboard;
+
+///
+/// 日趋势点
+///
+public class DashboardDailyTrendPointDto
+{
+ /// 日期(yyyy-MM-dd)
+ public string Date { get; set; } = string.Empty;
+
+ /// 值
+ public int Value { get; set; }
+}
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Dashboard/DashboardMetricCardDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Dashboard/DashboardMetricCardDto.cs
new file mode 100644
index 0000000..c6807c0
--- /dev/null
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Dashboard/DashboardMetricCardDto.cs
@@ -0,0 +1,25 @@
+namespace FoodLabeling.Application.Contracts.Dtos.Dashboard;
+
+///
+/// 仪表盘指标卡片
+///
+public class DashboardMetricCardDto
+{
+ /// 指标唯一标识(如 labelsPrintedToday)
+ public string Key { get; set; } = string.Empty;
+
+ /// 指标标题
+ public string Title { get; set; } = string.Empty;
+
+ /// 当前值
+ public int Value { get; set; }
+
+ /// 对比周期值
+ public int PreviousValue { get; set; }
+
+ /// 增减值(Value - PreviousValue)
+ public int ChangeValue { get; set; }
+
+ /// 增减比例(百分比)
+ public decimal ChangeRate { get; set; }
+}
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Dashboard/DashboardOverviewOutputDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Dashboard/DashboardOverviewOutputDto.cs
new file mode 100644
index 0000000..03bc5fc
--- /dev/null
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Dashboard/DashboardOverviewOutputDto.cs
@@ -0,0 +1,46 @@
+namespace FoodLabeling.Application.Contracts.Dtos.Dashboard;
+
+///
+/// 仪表盘总览输出
+///
+public class DashboardOverviewOutputDto
+{
+ /// 今日打印标签
+ public DashboardMetricCardDto LabelsPrintedToday { get; set; } = new();
+
+ /// 启用模板数
+ public DashboardMetricCardDto ActiveTemplates { get; set; } = new();
+
+ /// 活跃用户数
+ public DashboardMetricCardDto ActiveUsers { get; set; } = new();
+
+ /// 门店数
+ public DashboardMetricCardDto Locations { get; set; } = new();
+
+ /// 人员数
+ public DashboardMetricCardDto People { get; set; } = new();
+
+ /// 产品数
+ public DashboardMetricCardDto Products { get; set; } = new();
+
+ /// 指标卡片
+ public List MetricCards { get; set; } = new();
+
+ /// 近7天打印趋势
+ public List WeeklyPrintVolume { get; set; } = new();
+
+ /// 按分类分布
+ public List CategoryDistribution { get; set; } = new();
+
+ /// 分类分布总数
+ public int CategoryDistributionTotal { get; set; }
+
+ /// 按分类分布(前端直观命名)
+ public List ByCategory { get; set; } = new();
+
+ /// 按分类分布总数(前端直观命名)
+ public int ByCategoryTotal { get; set; }
+
+ /// 统计时间
+ public DateTime GeneratedAt { get; set; }
+}
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IDashboardAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IDashboardAppService.cs
new file mode 100644
index 0000000..f14fb75
--- /dev/null
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IDashboardAppService.cs
@@ -0,0 +1,15 @@
+using FoodLabeling.Application.Contracts.Dtos.Dashboard;
+using Volo.Abp.Application.Services;
+
+namespace FoodLabeling.Application.Contracts.IServices;
+
+///
+/// Dashboard 统计接口(美国版)
+///
+public interface IDashboardAppService : IApplicationService
+{
+ ///
+ /// 获取 Dashboard 总览统计(卡片 + 周趋势 + 分类分布)
+ ///
+ Task GetOverviewAsync();
+}
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DashboardAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DashboardAppService.cs
new file mode 100644
index 0000000..287be59
--- /dev/null
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DashboardAppService.cs
@@ -0,0 +1,182 @@
+using FoodLabeling.Application.Contracts.Dtos.Dashboard;
+using FoodLabeling.Application.Contracts.IServices;
+using FoodLabeling.Application.Services.DbModels;
+using FoodLabeling.Domain.Entities;
+using Volo.Abp.Application.Services;
+using Yi.Framework.Rbac.Domain.Entities;
+using Yi.Framework.SqlSugarCore.Abstractions;
+
+namespace FoodLabeling.Application.Services;
+
+///
+/// Dashboard 统计服务(美国版)
+///
+public class DashboardAppService : ApplicationService, IDashboardAppService
+{
+ private readonly ISqlSugarDbContext _dbContext;
+ private readonly ISqlSugarRepository _locationRepository;
+ private readonly ISqlSugarRepository _userRepository;
+
+ public DashboardAppService(
+ ISqlSugarDbContext dbContext,
+ ISqlSugarRepository locationRepository,
+ ISqlSugarRepository userRepository)
+ {
+ _dbContext = dbContext;
+ _locationRepository = locationRepository;
+ _userRepository = userRepository;
+ }
+
+ ///
+ public async Task GetOverviewAsync()
+ {
+ var now = DateTime.Now;
+ var todayStart = now.Date;
+ var tomorrowStart = todayStart.AddDays(1);
+ var yesterdayStart = todayStart.AddDays(-1);
+ var weekStart = todayStart.AddDays(-6);
+ var prevWeekStart = todayStart.AddDays(-13);
+
+ var printedToday = await _dbContext.SqlSugarClient.Queryable()
+ .CountAsync(x => x.CreationTime >= todayStart && x.CreationTime < tomorrowStart);
+
+ var printedYesterday = await _dbContext.SqlSugarClient.Queryable()
+ .CountAsync(x => x.CreationTime >= yesterdayStart && x.CreationTime < todayStart);
+
+ var activeTemplates = await _dbContext.SqlSugarClient.Queryable()
+ .CountAsync(x => !x.IsDeleted && x.State);
+ var activeTemplatesPrevWeek = await _dbContext.SqlSugarClient.Queryable()
+ .CountAsync(x => !x.IsDeleted && x.State && x.CreationTime < weekStart);
+
+ var activeUsers = await _userRepository._DbQueryable
+ .CountAsync(x => !x.IsDeleted && x.State);
+ var activeUsersPrevWeek = await _userRepository._DbQueryable
+ .CountAsync(x => !x.IsDeleted && x.State && x.CreationTime < weekStart);
+
+ var locations = await _locationRepository._DbQueryable
+ .CountAsync(x => !x.IsDeleted);
+ var locationsPrevWeek = await _locationRepository._DbQueryable
+ .CountAsync(x => !x.IsDeleted && x.CreationTime < weekStart);
+
+ var people = await _userRepository._DbQueryable
+ .CountAsync(x => !x.IsDeleted);
+ var peoplePrevWeek = await _userRepository._DbQueryable
+ .CountAsync(x => !x.IsDeleted && x.CreationTime < weekStart);
+
+ var products = await _dbContext.SqlSugarClient.Queryable()
+ .CountAsync(x => !x.IsDeleted);
+ // fl_product 当前实体未映射 CreationTime,无法按时间切分对比,先回退为同口径总量对比
+ var productsPrevWeek = products;
+
+ var weeklyPrintRaw = await _dbContext.SqlSugarClient.Queryable()
+ .Where(x => x.CreationTime >= weekStart && x.CreationTime < tomorrowStart)
+ .Select(x => x.CreationTime)
+ .ToListAsync();
+
+ var weeklyDict = weeklyPrintRaw
+ .GroupBy(x => x.Date)
+ .ToDictionary(g => g.Key, g => g.Count());
+
+ var weeklyTrend = Enumerable.Range(0, 7)
+ .Select(i =>
+ {
+ var d = weekStart.AddDays(i).Date;
+ return new DashboardDailyTrendPointDto
+ {
+ Date = d.ToString("yyyy-MM-dd"),
+ Value = weeklyDict.TryGetValue(d, out var v) ? v : 0
+ };
+ })
+ .ToList();
+
+ var categories = await _dbContext.SqlSugarClient.Queryable()
+ .Where(x => !x.IsDeleted && x.State)
+ .ToListAsync();
+
+ var labelCategoryIds = categories.Select(x => x.Id).ToList();
+ var labelRows = labelCategoryIds.Count == 0
+ ? new List()
+ : await _dbContext.SqlSugarClient.Queryable()
+ .Where(x => !x.IsDeleted && x.LabelCategoryId != null && labelCategoryIds.Contains(x.LabelCategoryId))
+ .Select(x => x.LabelCategoryId)
+ .ToListAsync();
+
+ var labelCountByCategory = labelRows
+ .Where(x => !string.IsNullOrWhiteSpace(x))
+ .GroupBy(x => x!)
+ .ToDictionary(g => g.Key, g => g.Count());
+
+ var categoryDistributionTotal = labelCountByCategory.Values.Sum();
+ var categoryDistribution = categories
+ .Select(c =>
+ {
+ var count = labelCountByCategory.TryGetValue(c.Id, out var v) ? v : 0;
+ var ratio = categoryDistributionTotal == 0
+ ? 0m
+ : Math.Round(count * 100m / categoryDistributionTotal, 2);
+ return new DashboardCategoryDistributionDto
+ {
+ CategoryId = c.Id,
+ CategoryName = c.CategoryName,
+ Count = count,
+ Ratio = ratio
+ };
+ })
+ .Where(x => x.Count > 0)
+ .OrderByDescending(x => x.Count)
+ .ThenBy(x => x.CategoryName)
+ .ToList();
+
+ var labelsPrintedTodayCard = BuildMetricCard("labelsPrintedToday", "Labels Printed Today", printedToday, printedYesterday);
+ var activeTemplatesCard = BuildMetricCard("activeTemplates", "Active Templates", activeTemplates, activeTemplatesPrevWeek);
+ var activeUsersCard = BuildMetricCard("activeUsers", "Active Users", activeUsers, activeUsersPrevWeek);
+ var locationsCard = BuildMetricCard("locations", "Locations", locations, locationsPrevWeek);
+ var peopleCard = BuildMetricCard("people", "People", people, peoplePrevWeek);
+ var productsCard = BuildMetricCard("products", "Products", products, productsPrevWeek);
+
+ var output = new DashboardOverviewOutputDto
+ {
+ LabelsPrintedToday = labelsPrintedTodayCard,
+ ActiveTemplates = activeTemplatesCard,
+ ActiveUsers = activeUsersCard,
+ Locations = locationsCard,
+ People = peopleCard,
+ Products = productsCard,
+ MetricCards = new List
+ {
+ labelsPrintedTodayCard,
+ activeTemplatesCard,
+ activeUsersCard,
+ locationsCard,
+ peopleCard,
+ productsCard
+ },
+ WeeklyPrintVolume = weeklyTrend,
+ CategoryDistribution = categoryDistribution,
+ CategoryDistributionTotal = categoryDistributionTotal,
+ ByCategory = categoryDistribution,
+ ByCategoryTotal = categoryDistributionTotal,
+ GeneratedAt = now
+ };
+
+ return output;
+ }
+
+ private static DashboardMetricCardDto BuildMetricCard(string key, string title, int value, int previousValue)
+ {
+ var changeValue = value - previousValue;
+ var changeRate = previousValue <= 0
+ ? (value > 0 ? 100m : 0m)
+ : Math.Round(changeValue * 100m / previousValue, 2);
+
+ return new DashboardMetricCardDto
+ {
+ Key = key,
+ Title = title,
+ Value = value,
+ PreviousValue = previousValue,
+ ChangeValue = changeValue,
+ ChangeRate = changeRate
+ };
+ }
+}
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppAuthAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppAuthAppService.cs
index d8f0e66..93cd84c 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppAuthAppService.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppAuthAppService.cs
@@ -38,7 +38,6 @@ namespace FoodLabeling.Application.Services;
public class UsAppAuthAppService : ApplicationService, IUsAppAuthAppService
{
private readonly IAccountManager _accountManager;
-
private readonly ISqlSugarRepository _userRepository;
private readonly ISqlSugarDbContext _dbContext;
private readonly IHttpContextAccessor _httpContextAccessor;
diff --git a/项目相关文档/Dashboard统计接口对接说明.md b/项目相关文档/Dashboard统计接口对接说明.md
new file mode 100644
index 0000000..e62e28b
--- /dev/null
+++ b/项目相关文档/Dashboard统计接口对接说明.md
@@ -0,0 +1,214 @@
+# Dashboard 统计接口对接说明
+
+> 适用范围:美国版 Web 管理端 Dashboard 首页统计
+>
+> 接口实现:`IDashboardAppService.GetOverviewAsync` / `DashboardAppService.GetOverviewAsync`
+
+---
+
+## 1. 接口信息
+
+- **方法**:`GET`
+- **路径**:`/api/app/dashboard/overview`
+- **鉴权**:需要登录(Bearer Token)
+- **请求参数**:无
+
+---
+
+## 2. 返回结构(顶层)
+
+```json
+{
+ "labelsPrintedToday": {},
+ "activeTemplates": {},
+ "activeUsers": {},
+ "locations": {},
+ "people": {},
+ "products": {},
+ "weeklyPrintVolume": [],
+ "byCategory": [],
+ "byCategoryTotal": 0,
+ "generatedAt": "2026-04-22T10:00:00+08:00",
+
+ "metricCards": [],
+ "categoryDistribution": [],
+ "categoryDistributionTotal": 0
+}
+```
+
+说明:
+- `labelsPrintedToday/activeTemplates/...`、`byCategory/byCategoryTotal` 是**前端直观命名**(推荐使用)。
+- `metricCards`、`categoryDistribution`、`categoryDistributionTotal` 为**兼容字段**(与旧版返回一致)。
+
+---
+
+## 3. 字段说明
+
+### 3.1 指标卡片对象(`DashboardMetricCardDto`)
+
+用于以下字段:
+- `labelsPrintedToday`
+- `activeTemplates`
+- `activeUsers`
+- `locations`
+- `people`
+- `products`
+- `metricCards[]`(同结构)
+
+| 字段 | 类型 | 说明 |
+|---|---|---|
+| `key` | string | 指标标识(如 `labelsPrintedToday`) |
+| `title` | string | 指标标题 |
+| `value` | int | 当前值 |
+| `previousValue` | int | 对比周期值 |
+| `changeValue` | int | 增减值(`value - previousValue`) |
+| `changeRate` | decimal | 增减比例(百分比,保留 2 位) |
+
+---
+
+### 3.2 周趋势(`weeklyPrintVolume`)
+
+| 字段 | 类型 | 说明 |
+|---|---|---|
+| `date` | string | 日期,格式 `yyyy-MM-dd` |
+| `value` | int | 当天打印量 |
+
+---
+
+### 3.3 分类分布(`byCategory`)
+
+`byCategory` 与 `categoryDistribution` 结构一致。
+
+| 字段 | 类型 | 说明 |
+|---|---|---|
+| `categoryId` | string | 分类 Id(`fl_label_category.Id`) |
+| `categoryName` | string | 分类名称 |
+| `count` | int | 该分类下标签数量 |
+| `ratio` | decimal | 占比(百分比,保留 2 位) |
+
+---
+
+## 4. 返回示例
+
+```json
+{
+ "labelsPrintedToday": {
+ "key": "labelsPrintedToday",
+ "title": "Labels Printed Today",
+ "value": 342,
+ "previousValue": 305,
+ "changeValue": 37,
+ "changeRate": 12.13
+ },
+ "activeTemplates": {
+ "key": "activeTemplates",
+ "title": "Active Templates",
+ "value": 24,
+ "previousValue": 22,
+ "changeValue": 2,
+ "changeRate": 9.09
+ },
+ "activeUsers": {
+ "key": "activeUsers",
+ "title": "Active Users",
+ "value": 8,
+ "previousValue": 7,
+ "changeValue": 1,
+ "changeRate": 14.29
+ },
+ "locations": {
+ "key": "locations",
+ "title": "Locations",
+ "value": 12,
+ "previousValue": 11,
+ "changeValue": 1,
+ "changeRate": 9.09
+ },
+ "people": {
+ "key": "people",
+ "title": "People",
+ "value": 48,
+ "previousValue": 45,
+ "changeValue": 3,
+ "changeRate": 6.67
+ },
+ "products": {
+ "key": "products",
+ "title": "Products",
+ "value": 156,
+ "previousValue": 156,
+ "changeValue": 0,
+ "changeRate": 0
+ },
+ "weeklyPrintVolume": [
+ { "date": "2026-04-16", "value": 142 },
+ { "date": "2026-04-17", "value": 226 },
+ { "date": "2026-04-18", "value": 185 },
+ { "date": "2026-04-19", "value": 261 },
+ { "date": "2026-04-20", "value": 192 },
+ { "date": "2026-04-21", "value": 121 },
+ { "date": "2026-04-22", "value": 342 }
+ ],
+ "byCategory": [
+ { "categoryId": "CAT001", "categoryName": "Breakfast", "count": 420, "ratio": 42.00 },
+ { "categoryId": "CAT002", "categoryName": "Lunch", "count": 350, "ratio": 35.00 },
+ { "categoryId": "CAT003", "categoryName": "Dinner", "count": 230, "ratio": 23.00 }
+ ],
+ "byCategoryTotal": 1000,
+ "generatedAt": "2026-04-22T10:00:00+08:00",
+ "metricCards": [],
+ "categoryDistribution": [],
+ "categoryDistributionTotal": 1000
+}
+```
+
+---
+
+## 5. 统计口径说明
+
+### 5.1 Labels Printed Today
+- 当前值:`fl_label_print_task` 在“今日 00:00~次日 00:00”的记录数。
+- 对比值:昨日同口径记录数。
+
+### 5.2 Active Templates
+- 当前值:`fl_label_template` 中 `IsDeleted = false AND State = true` 数量。
+- 对比值:同口径且 `CreationTime < 最近7天起始日` 的数量。
+
+### 5.3 Active Users
+- 当前值:`User` 表中 `IsDeleted = false AND State = true` 数量。
+- 对比值:同口径且 `CreationTime < 最近7天起始日` 的数量。
+
+### 5.4 Locations
+- 当前值:`location` 表中 `IsDeleted = false` 数量。
+- 对比值:同口径且 `CreationTime < 最近7天起始日` 的数量。
+
+### 5.5 People
+- 当前值:`User` 表中 `IsDeleted = false` 数量。
+- 对比值:同口径且 `CreationTime < 最近7天起始日` 的数量。
+
+### 5.6 Products
+- 当前值:`fl_product` 表中 `IsDeleted = false` 数量。
+- 对比值:当前版本由于 `FlProductDbEntity` 未映射 `CreationTime`,临时按同口径总量返回(即变化可能为 0)。
+
+### 5.7 Weekly Print Volume
+- 统计最近 7 天(含今天)每天 `fl_label_print_task` 数量。
+- 无数据日期补 0。
+
+### 5.8 By Category
+- 基于启用且未删除的 `fl_label_category` 作为分类集合。
+- 统计 `fl_label` 中未删除且 `LabelCategoryId` 命中的数量。
+- 占比按 `count / byCategoryTotal * 100` 计算,保留 2 位。
+
+---
+
+## 6. 前端接入建议
+
+- 新页面优先使用:
+ - 指标:`labelsPrintedToday`、`activeTemplates`、`activeUsers`、`locations`、`people`、`products`
+ - 图表:`weeklyPrintVolume`
+ - 环图:`byCategory` + `byCategoryTotal`
+- 旧逻辑仍可使用兼容字段:
+ - `metricCards`
+ - `categoryDistribution`
+ - `categoryDistributionTotal`
+
--
libgit2 0.21.4