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` +