From 43c8ec3c8271f0d292cca2bae07d50d6588d5607 Mon Sep 17 00:00:00 2001 From: tanglong <842690096@qq.com> Date: Wed, 8 Apr 2026 16:09:51 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Dto/DeviceSync/DeviceSyncLastGradeDto.cs | 32 + .../Dto/DeviceSync/DeviceSyncRecordAppDto.cs | 2 +- .../DeviceSync/DeviceSyncTrainDetailsDto.cs | 4 +- .../Dto/DeviceSync/DeviceSyncTrainPushDto.cs | 2 +- .../DeviceSyncYiDongZhanPostDtos.cs | 15 + YD_YiLu.Commons/Dto/ResponseMsg.cs | 3 - YD_YiLu.Commons/Dto/WebResponseContent.cs | 6 +- YD_YiLu.Commons/Enum/CourseType.cs | 94 ++- YD_YiLu.Commons/Enum/DataSource.cs | 2 +- YD_YiLu.Commons/Enum/DeviceType.cs | 2 +- YD_YiLu.Commons/Enum/ResponseType.cs | 2 +- YD_YiLu.Commons/Enum/SportsTestItemType.cs | 2 +- YD_YiLu.Commons/Utils/Util.cs | 34 ++ YD_YiLu/Context/SmartSportsContext.cs | 1 + YD_YiLu/Controllers/DeviceSyncController.cs | 15 +- YD_YiLu/Services/Impl/DeviceSyncService.cs | 570 ++++++++++++++---- YD_YiLu/Services/Impl/LargeScreenService.cs | 7 +- YD_YiLu/Services/Impl/XinWeiService.cs | 6 +- .../Services/Interface/IDeviceSyncService.cs | 5 + .../SmartSportsEntitys/N_SportsTestValue.cs | 2 +- 20 files changed, 627 insertions(+), 179 deletions(-) create mode 100644 YD_YiLu.Commons/Dto/DeviceSync/DeviceSyncLastGradeDto.cs create mode 100644 YD_YiLu.Commons/Dto/DeviceSync/DeviceSyncYiDongZhanPostDtos.cs diff --git a/YD_YiLu.Commons/Dto/DeviceSync/DeviceSyncLastGradeDto.cs b/YD_YiLu.Commons/Dto/DeviceSync/DeviceSyncLastGradeDto.cs new file mode 100644 index 0000000..cb7da5a --- /dev/null +++ b/YD_YiLu.Commons/Dto/DeviceSync/DeviceSyncLastGradeDto.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; + +namespace YD_YiLu.Commons.Dto.DeviceSync +{ + /// + /// 人员上次成绩(批量按人员序列号) + /// + public class DeviceSyncLastGradeRequestDto + { + public string DeviceSerial { get; set; } + public int DeviceType { get; set; } + public List UserSerials { get; set; } + } + + public class DeviceSyncLastGradeItemDto + { + public string UserSerial { get; set; } + public int UserId { get; set; } + public long TrainId { get; set; } + public string Nickname { get; set; } + public int OrganizationId { get; set; } + public string OrganizationName { get; set; } + public int Gender { get; set; } + public string FacePictureUrl { get; set; } + public string Score { get; set; } + public int UnStandardCounts { get; set; } + public string StartTime { get; set; } + public string EndTime { get; set; } + public int CourseId { get; set; } + public string CourseName { get; set; } + } +} diff --git a/YD_YiLu.Commons/Dto/DeviceSync/DeviceSyncRecordAppDto.cs b/YD_YiLu.Commons/Dto/DeviceSync/DeviceSyncRecordAppDto.cs index 01a1228..a52eab3 100644 --- a/YD_YiLu.Commons/Dto/DeviceSync/DeviceSyncRecordAppDto.cs +++ b/YD_YiLu.Commons/Dto/DeviceSync/DeviceSyncRecordAppDto.cs @@ -23,7 +23,7 @@ namespace YD_YiLu.Commons.Dto.DeviceSync public class DeviceSyncRecordAppItemDto { - public int TrainId { get; set; } + public long TrainId { get; set; } public string Nickname { get; set; } public int OrganizationId { get; set; } public string OrganizationName { get; set; } diff --git a/YD_YiLu.Commons/Dto/DeviceSync/DeviceSyncTrainDetailsDto.cs b/YD_YiLu.Commons/Dto/DeviceSync/DeviceSyncTrainDetailsDto.cs index 4d81800..b16dd05 100644 --- a/YD_YiLu.Commons/Dto/DeviceSync/DeviceSyncTrainDetailsDto.cs +++ b/YD_YiLu.Commons/Dto/DeviceSync/DeviceSyncTrainDetailsDto.cs @@ -7,7 +7,7 @@ namespace YD_YiLu.Commons.Dto.DeviceSync /// public class DeviceSyncTrainDetailsDto { - public int TrainId { get; set; } + public long TrainId { get; set; } public string UserSerial { get; set; } public int UserType { get; set; } public string Nickname { get; set; } @@ -33,7 +33,7 @@ namespace YD_YiLu.Commons.Dto.DeviceSync public class CountListItem { - public int TrainId { get; set; } + public long TrainId { get; set; } public string Score { get; set; } public string Time { get; set; } } diff --git a/YD_YiLu.Commons/Dto/DeviceSync/DeviceSyncTrainPushDto.cs b/YD_YiLu.Commons/Dto/DeviceSync/DeviceSyncTrainPushDto.cs index da23ab9..992e5eb 100644 --- a/YD_YiLu.Commons/Dto/DeviceSync/DeviceSyncTrainPushDto.cs +++ b/YD_YiLu.Commons/Dto/DeviceSync/DeviceSyncTrainPushDto.cs @@ -29,7 +29,7 @@ namespace YD_YiLu.Commons.Dto.DeviceSync /// public class DeviceSyncTrainPushResponseItem { - public int TrainId { get; set; } + public long TrainId { get; set; } public string UserSerial { get; set; } } } diff --git a/YD_YiLu.Commons/Dto/DeviceSync/DeviceSyncYiDongZhanPostDtos.cs b/YD_YiLu.Commons/Dto/DeviceSync/DeviceSyncYiDongZhanPostDtos.cs new file mode 100644 index 0000000..4686bfc --- /dev/null +++ b/YD_YiLu.Commons/Dto/DeviceSync/DeviceSyncYiDongZhanPostDtos.cs @@ -0,0 +1,15 @@ +namespace YD_YiLu.Commons.Dto.DeviceSync +{ + /// 壹动站:人员详情 POST 请求体(与 GET /device/sync/user/detail 等价) + public class DeviceSyncUserDetailPostDto + { + public string DeviceSerial { get; set; } + public int UserId { get; set; } + } + + /// 壹动站:训练详情 POST 请求体(与 GET /device/sync/train/details 等价) + public class DeviceSyncTrainDetailPostDto + { + public int TrainId { get; set; } + } +} diff --git a/YD_YiLu.Commons/Dto/ResponseMsg.cs b/YD_YiLu.Commons/Dto/ResponseMsg.cs index f0aab84..77faa94 100644 --- a/YD_YiLu.Commons/Dto/ResponseMsg.cs +++ b/YD_YiLu.Commons/Dto/ResponseMsg.cs @@ -1,7 +1,4 @@  - -using YD_YiLu.Commons.Enum; - namespace YD_YiLu.Commons.Dto { public static class ResponseMsg diff --git a/YD_YiLu.Commons/Dto/WebResponseContent.cs b/YD_YiLu.Commons/Dto/WebResponseContent.cs index a660afa..1cea425 100644 --- a/YD_YiLu.Commons/Dto/WebResponseContent.cs +++ b/YD_YiLu.Commons/Dto/WebResponseContent.cs @@ -1,8 +1,4 @@ - - -using YD_YiLu.Commons.Enum; - -namespace YD_YiLu.Commons.Dto +namespace YD_YiLu.Commons.Dto { public class WebResponseContent { diff --git a/YD_YiLu.Commons/Enum/CourseType.cs b/YD_YiLu.Commons/Enum/CourseType.cs index ddba9a4..207815f 100644 --- a/YD_YiLu.Commons/Enum/CourseType.cs +++ b/YD_YiLu.Commons/Enum/CourseType.cs @@ -1,40 +1,84 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; -namespace YD_YiLu.Commons.Enum +namespace YD_YiLu.Commons { /// /// 课目类型枚举 /// public enum CourseType { - PullUp = 3, // 引体向上 - StandingLongJump = 7, // 立定跳远 - JumpRope = 8, // 跳绳 - JumpingJack = 9, // 开合跳 - HighKnee = 10, // 高抬腿 - Squat = 11, // 深蹲 - SitAndReach = 23, // 坐位体前屈 - SingleLegJump = 24, // 单脚跳 - DynamicHipBridge = 25, // 动态臀桥 - LateralJump = 26, // 左右跳 - WhackAMole = 27, // 打地鼠 - SevenUpEightDown = 28, // 七上八下 - KillMosquito = 29, // 消灭蚊子 - RockPaperScissors = 30, // 石头剪刀布 - FaceChange = 31, // 变脸 - NailHammer = 32, // 打钉子 - BMI = 1001, // 体重系数 - VitalCapacity = 1002, // 肺活量 + [Description("引体向上")] + PullUp = 3, - // 新增跑步类科目 - Run50m = 12, // 50米跑 - Run100m = 13, // 100米跑 - Run800m = 14, // 800米跑 - Run1000m = 15, // 1000米跑 - Run50mX8 = 20 // 50米x8 + [Description("立定跳远")] + StandingLongJump = 7, + + [Description("跳绳")] + JumpRope = 8, + + [Description("开合跳")] + JumpingJack = 9, + + [Description("高抬腿")] + HighKnee = 10, + + [Description("深蹲")] + Squat = 11, + + [Description("坐位体前屈")] + SitAndReach = 23, + + [Description("单脚跳")] + SingleLegJump = 24, + + [Description("动态臀桥")] + DynamicHipBridge = 25, + + [Description("左右跳")] + LateralJump = 26, + + [Description("打地鼠")] + WhackAMole = 27, + + [Description("七上八下")] + SevenUpEightDown = 28, + + [Description("消灭蚊子")] + KillMosquito = 29, + + [Description("石头剪刀布")] + RockPaperScissors = 30, + + [Description("变脸")] + FaceChange = 31, + + [Description("打钉子")] + NailHammer = 32, + + [Description("体重系数")] + BMI = 1001, + + [Description("肺活量")] + VitalCapacity = 1002, + + [Description("50米跑")] + Run50m = 12, + + [Description("100米跑")] + Run100m = 13, + + [Description("800米跑")] + Run800m = 14, + + [Description("1000米跑")] + Run1000m = 15, + + [Description("50米x8")] + Run50mX8 = 20 } } diff --git a/YD_YiLu.Commons/Enum/DataSource.cs b/YD_YiLu.Commons/Enum/DataSource.cs index 584ae4d..51f91af 100644 --- a/YD_YiLu.Commons/Enum/DataSource.cs +++ b/YD_YiLu.Commons/Enum/DataSource.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace VOL.Entity.Enum +namespace VOL.Commons { /// /// 数据来源 diff --git a/YD_YiLu.Commons/Enum/DeviceType.cs b/YD_YiLu.Commons/Enum/DeviceType.cs index eb19aee..16a7700 100644 --- a/YD_YiLu.Commons/Enum/DeviceType.cs +++ b/YD_YiLu.Commons/Enum/DeviceType.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace YD_YiLu.Commons.Enum +namespace YD_YiLu.Commons { /// /// 设备类型枚举 diff --git a/YD_YiLu.Commons/Enum/ResponseType.cs b/YD_YiLu.Commons/Enum/ResponseType.cs index 9065e10..1b56310 100644 --- a/YD_YiLu.Commons/Enum/ResponseType.cs +++ b/YD_YiLu.Commons/Enum/ResponseType.cs @@ -1,6 +1,6 @@  -namespace YD_YiLu.Commons.Enum +namespace YD_YiLu.Commons { public enum ResponseType { diff --git a/YD_YiLu.Commons/Enum/SportsTestItemType.cs b/YD_YiLu.Commons/Enum/SportsTestItemType.cs index e52484e..60fa54a 100644 --- a/YD_YiLu.Commons/Enum/SportsTestItemType.cs +++ b/YD_YiLu.Commons/Enum/SportsTestItemType.cs @@ -6,7 +6,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace YD_YiLu.Commons.Enum +namespace YD_YiLu.Commons { /// /// 体测项目 diff --git a/YD_YiLu.Commons/Utils/Util.cs b/YD_YiLu.Commons/Utils/Util.cs index c692f7d..09142b5 100644 --- a/YD_YiLu.Commons/Utils/Util.cs +++ b/YD_YiLu.Commons/Utils/Util.cs @@ -91,6 +91,15 @@ namespace YD_YiLu.Commons.Utils return (dateTime.ToUniversalTime().Ticks - longTime) / samllTime; } + public static DateTime ParseDateTime(this string time) + { + if (DateTime.TryParse(time, out var dt)) + return dt; + + // 👉 如果解析失败,给默认值 or 抛异常(看你业务) + return DateTime.Now; + } + /// /// 时间戳转换成日期 /// @@ -144,5 +153,30 @@ namespace YD_YiLu.Commons.Utils _ => "低分" }; } + + public static string GetEnumDescription(object value) where TEnum : struct, IConvertible + { + if (value == null) return string.Empty; + + try + { + // 尝试转换为枚举值 + if (!Enum.TryParse(typeof(TEnum), value.ToString(), out object enumValue)) + { + return value.ToString(); // 转换失败直接返回原值 + } + + string name = enumValue.ToString(); + FieldInfo field = typeof(TEnum).GetField(name); + if (field == null) return name; + + var attr = field.GetCustomAttribute(); + return attr?.Description ?? name; + } + catch + { + return value.ToString(); + } + } } } diff --git a/YD_YiLu/Context/SmartSportsContext.cs b/YD_YiLu/Context/SmartSportsContext.cs index cea9ce2..f03947a 100644 --- a/YD_YiLu/Context/SmartSportsContext.cs +++ b/YD_YiLu/Context/SmartSportsContext.cs @@ -27,6 +27,7 @@ namespace YD_YiLu.Api.Context public DbSet XW_ProjectMode { get; set; } public DbSet XW_TestingProject { get; set; } public DbSet YL_Device { get; set; } + public DbSet YL_Train { get; set; } public DbSet XW_SportsTestData { get; set; } public DbSet XW_TrainStudents { get; set; } public DbSet XW_TrainSetting { get; set; } diff --git a/YD_YiLu/Controllers/DeviceSyncController.cs b/YD_YiLu/Controllers/DeviceSyncController.cs index f17ae2d..b6fc3d6 100644 --- a/YD_YiLu/Controllers/DeviceSyncController.cs +++ b/YD_YiLu/Controllers/DeviceSyncController.cs @@ -9,7 +9,7 @@ namespace YD_YiLu.Api.Controllers /// 壹动站/壹体屏/活力课间屏 设备同步 HTTP 接口(小站登录与心跳见 WebSocket:/ws/camera/device) /// [AllowAnonymous] - public class DeviceSyncController : ControllerBase + public class DeviceSyncController : ControllerBase { private readonly IDeviceSyncService _deviceSyncService; @@ -71,6 +71,19 @@ namespace YD_YiLu.Api.Controllers return await _deviceSyncService.OrganizationAsync(dto); } + + /// + /// 人员上次成绩(按人员序列号批量) + /// + [HttpPost] + [Route("/device/sync/grade/last_grade")] + public async Task LastGrade([FromBody] DeviceSyncLastGradeRequestDto dto) + { + if (dto == null || string.IsNullOrWhiteSpace(dto.DeviceSerial)) + return new List(); + return await _deviceSyncService.LastGradeAsync(dto); + } + /// /// 成绩批量上报 /// diff --git a/YD_YiLu/Services/Impl/DeviceSyncService.cs b/YD_YiLu/Services/Impl/DeviceSyncService.cs index bd1ebc2..00540dd 100644 --- a/YD_YiLu/Services/Impl/DeviceSyncService.cs +++ b/YD_YiLu/Services/Impl/DeviceSyncService.cs @@ -3,6 +3,7 @@ using VOL.Entity.DomainModels.XinWei; using YD_YiLu.Api.Context; using YD_YiLu.Api.Services.Interface; using YD_YiLu.Api.SmartSportsEntitys; +using YD_YiLu.Commons; using YD_YiLu.Commons.Dto.DeviceSync; using YD_YiLu.Commons.Utils; @@ -135,205 +136,513 @@ namespace YD_YiLu.Api.Services.Impl public async Task> TrainPushAsync(List list) { - var orgId = await GetOrgIdByDeviceAsync(list.FirstOrDefault()?.DeviceSerial ?? ""); - if (!orgId.HasValue) return new List(); + if (list == null || !list.Any()) + return new List(); var result = new List(); - var projects = await _context.XW_TestingProject.ToListAsync(); - string schoolCode = await _context.School.Where(x => x.Id == orgId).Select(x => x.SchoolCode).FirstAsync(); + + // 👉 1. 设备校验(可选,看你业务) + var orgId = await GetOrgIdByDeviceAsync(list.First().DeviceSerial); + if (!orgId.HasValue) + return result; + + // 👉 2. 批量查已存在的 TrainSerial(防重复) + var trainSerials = list.Select(x => x.TrainSerial).ToList(); + + var existSerials = await _context.YL_Train + .Where(x => trainSerials.Contains(x.TrainSerial)) + .Select(x => x.TrainSerial) + .ToListAsync(); + + var newEntities = new List(); foreach (var item in list) { - var student = await _context.Student.FirstOrDefaultAsync(x => - x.SchoolCode == schoolCode && x.StudentNo == item.UserSerial); - if (student == null) continue; + // 👉 3. 唯一校验(跳过已存在) + if (existSerials.Contains(item.TrainSerial)) + continue; - var project = projects.FirstOrDefault(x => x.ProjectId == item.CourseId) ?? projects.FirstOrDefault(); - var trainData = new XW_SportsTestData + var entity = new YL_Train { - OrgId = orgId, - ProjectId = item.CourseId, - ProjectName = project?.ProjectName ?? $"课目{item.CourseId}", - StartTime = item.StartTime, - EndTime = item.EndTime, - CreateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") - }; - _context.XW_SportsTestData.Add(trainData); - await _context.SaveChangesAsync(); + DeviceSerial = item.DeviceSerial, + DeviceType = item.DeviceType, + UserSerial = item.UserSerial, - var c = await _context.Class.FindAsync(student.ClassId); - var trainStudent = new XW_TrainStudents - { - XW_SportsTestDataId = trainData.Id, - StudentId = student.Id, - StudentName = student.StudentName, - StudentNo = student.StudentNo, - Sex = student.Sex, - GradeId = c?.GradeId ?? 0, - GradeName = c?.GradeName ?? "", - ClassId = student.ClassId ?? 0, - ClassName = student.ClassName ?? c?.ClassName ?? "", - Achievement = item.StandardCounts, - ErrorCount = item.UnStandardCounts, - CreateTime = item.StartTime - }; - _context.XW_TrainStudents.Add(trainStudent); - await _context.SaveChangesAsync(); + TrainSerial = item.TrainSerial, + VideoSerial = item.VideoSerial, - result.Add(new DeviceSyncTrainPushResponseItem { TrainId = trainData.Id, UserSerial = item.UserSerial }); + TrainExam = item.TrainExam, + QuizId = item.QuizId, + + StandardCounts = item.StandardCounts, + UnStandardCounts = item.UnStandardCounts, + + TimeKeeping = item.TimeKeeping, + Duration = item.Duration, + Distance = item.Distance, + + CourseId = item.CourseId, + + StartTime = Util.ParseDateTime(item.StartTime), + EndTime = Util.ParseDateTime(item.EndTime), + + // 👉 成绩计算(建议统一规则) + Score = CalculateScore(item), + + IsValid = 1, // 默认有效 + UploadTime = DateTime.Now, + CreateTime = DateTime.Now + }; + + newEntities.Add(entity); } + + // 👉 4. 批量入库(关键优化点) + if (newEntities.Any()) + { + _context.YL_Train.AddRange(newEntities); + await _context.SaveChangesAsync(); + } + + // 👉 5. 返回结果 + result = newEntities.Select(x => new DeviceSyncTrainPushResponseItem + { + TrainId = x.Id, + UserSerial = x.UserSerial + }).ToList(); + return result; } + private double? CalculateScore(DeviceSyncTrainPushRequestDto item) + { + // 👉 根据课目类型决定成绩字段 + // 这里给你一个通用策略(你可以按课目细化) + + // 计数类(跳绳/仰卧起坐) + if (item.StandardCounts > 0) + return item.StandardCounts; + + // 计时类(悬垂) + if (item.TimeKeeping.HasValue) + return item.TimeKeeping; + + // 距离类(跳远) + if (item.Distance.HasValue) + return item.Distance; + + return null; + } + public async Task TrainDetailsAsync(int trainId) { - var train = await _context.XW_SportsTestData.FindAsync(trainId); + // 1. 获取训练记录 + var train = await _context.YL_Train.FirstOrDefaultAsync(t => t.Id == trainId); if (train == null) return null; - var ts = await _context.XW_TrainStudents - .FirstOrDefaultAsync(x => x.XW_SportsTestDataId == trainId); - if (ts == null) return null; - var student = await _context.Student.FindAsync(ts.StudentId); + // 2. 获取学生信息 + var student = await _context.Student.FirstOrDefaultAsync(s => s.StudentNo == train.UserSerial); + if (student == null) return null; - var recentTrains = await (from t in _context.XW_TrainStudents - join td in _context.XW_SportsTestData on t.XW_SportsTestDataId equals td.Id - where t.StudentId == ts.StudentId && td.ProjectId == train.ProjectId - orderby t.CreateTime descending - select new CountListItem { TrainId = td.Id, Score = t.Achievement.ToString(), Time = t.CreateTime ?? "" }) + // 3. 最近10次训练记录(CountList) + var recentTrains = await _context.YL_Train + .Where(t => t.UserSerial == train.UserSerial && t.CourseId == train.CourseId) + .OrderByDescending(t => t.StartTime) .Take(10) + .Select(t => new CountListItem + { + TrainId = t.Id, + Score = (t.Score ?? 0).ToString("0"), // 先拿数值 + Time = t.StartTime.ToString("yyyy-MM-dd HH:mm:ss") + }) + .AsNoTracking() // 可选,提高性能 .ToListAsync(); + // 在内存中格式化 + var recentTrainsDto = recentTrains + .Select(t => new CountListItem + { + TrainId = t.TrainId, + Score = t.Score, // 格式化整数 + Time = t.Time + }) + .ToList(); + + // 4. 近10日最佳成绩(DailyList) + var tenDaysAgo = DateTime.Now.Date.AddDays(-10); + + var dailyScores = await _context.YL_Train + .Where(t => t.UserSerial == train.UserSerial + && t.CourseId == train.CourseId + && t.StartTime >= tenDaysAgo) + .GroupBy(t => t.StartTime.Date) + .Select(g => new + { + Date = g.Key, + MaxScore = g.Max(t => t.Score) ?? 0 + }) + .OrderByDescending(g => g.Date) + .ToListAsync(); + + // 在内存中格式化 + var dailyListDto = dailyScores + .Select(d => new DailyListItem + { + Score = d.MaxScore.ToString("0"), + Time = d.Date.ToString("yyyy-MM-dd 00:00:00") + }) + .ToList(); + + // 5. 课程名称(枚举描述) + string courseName = Util.GetEnumDescription(train.CourseId); + + // 6. 返回 DTO return new DeviceSyncTrainDetailsDto { - TrainId = trainId, - UserSerial = student?.StudentNo ?? "", - UserType = 2, - Nickname = ts.StudentName, - FacePictureUrl = student?.Photo ?? "", + TrainId = train.Id, + UserSerial = train.UserSerial, + UserType = 2, // 学生 + Nickname = student.StudentName, + FacePictureUrl = student.Photo ?? "", Identity = "学生", - OrganizationId = ts.ClassId, - OrganizationName = ts.ClassName, - Gender = ts.Sex == 1 ? "男" : "女", - Score = ts.Achievement?.ToString() ?? "0", - UnStandardCounts = ts.ErrorCount ?? 0, - CourseId = train.ProjectId ?? 0, - CourseName = train.ProjectName ?? "", + OrganizationId = (int)student.ClassId, + OrganizationName = student.ClassName, + Gender = student.Sex == 1 ? "男" : "女", + Score = train.Score?.ToString("0") ?? "0", + UnStandardCounts = train.UnStandardCounts, + CourseId = train.CourseId, + CourseName = courseName, TrainType = "自由练习", - VideoUrl = ts.VideoRecord, - StartTime = train.StartTime, - EndTime = train.EndTime, - CountList = recentTrains + VideoUrl = train.VideoSerial, + StartTime = train.StartTime.ToString("yyyy-MM-dd HH:mm:ss"), + EndTime = train.EndTime.ToString("yyyy-MM-dd HH:mm:ss"), + CountList = recentTrains, + DailyList = dailyListDto, + KeyframeList = new List() // 暂时为空 }; } public async Task> GradeOrderAsync(DeviceSyncGradeOrderRequestDto dto) { + // 1️⃣ 获取组织ID var orgId = await GetOrgIdByDeviceAsync(dto.DeviceSerial); if (!orgId.HasValue) return new List(); + // 2️⃣ 获取学校编码 + string schoolCode = await _context.School + .Where(x => x.Id == orgId) + .Select(x => x.SchoolCode) + .FirstAsync(); + + // 3️⃣ 时间范围 var (start, end) = GetDateRange(dto.DateType, dto.QueryStartTime, dto.QueryEndTime); - string schoolCode = await _context.School.Where(x => x.Id == orgId).Select(x => x.SchoolCode).FirstAsync(); - var query = from ts in _context.XW_TrainStudents - join td in _context.XW_SportsTestData on ts.XW_SportsTestDataId equals td.Id - join s in _context.Student on ts.StudentId equals s.Id - where s.SchoolCode == schoolCode && td.ProjectId == (dto.CourseId ?? td.ProjectId) - select new { ts, td, s }; + // 4️⃣ 主查询(YL_Train + Student) + var query = from t in _context.YL_Train + join s in _context.Student on t.UserSerial equals s.StudentNo + where s.SchoolCode == schoolCode + select new { t, s }; - if (dto.Gender.HasValue) query = query.Where(x => x.s.Sex == dto.Gender); - if (dto.OrganizeId?.Count > 0) query = query.Where(x => dto.OrganizeId.Contains(x.ts.ClassId)); - if (start.HasValue) query = query.Where(x => x.ts.CreateTime != null && string.Compare(x.ts.CreateTime, start.Value.ToString("yyyy-MM-dd")) >= 0); - if (end.HasValue) query = query.Where(x => x.ts.CreateTime != null && string.Compare(x.ts.CreateTime, end.Value.AddDays(1).ToString("yyyy-MM-dd")) < 0); + // 5️⃣ 条件过滤 + if (dto.CourseId.HasValue) + query = query.Where(x => x.t.CourseId == dto.CourseId.Value); + if (dto.Gender.HasValue) + query = query.Where(x => x.s.Sex == dto.Gender.Value); + + if (dto.OrganizeId != null && dto.OrganizeId.Count > 0) + query = query.Where(x => dto.OrganizeId.Contains(x.s.ClassId ?? 0)); + + if (start.HasValue) + query = query.Where(x => x.t.StartTime >= start.Value); + + if (end.HasValue) + query = query.Where(x => x.t.StartTime < end.Value.AddDays(1)); + + // 6️⃣ 分组求总成绩并排序 var grouped = await query - .GroupBy(x => new { x.s.Id, x.s.StudentName, x.ts.ClassId, x.ts.ClassName, x.s.Sex, x.td.ProjectId, x.td.ProjectName }) - .Select(g => new { g.Key, Score = g.Sum(x => x.ts.Achievement ?? 0) }) + .GroupBy(x => new + { + x.s.Id, + x.s.StudentName, + x.s.ClassId, + x.s.ClassName, + x.s.Sex, + x.t.CourseId, + }) + .Select(g => new + { + Key = g.Key, + Score = g.Sum(x => x.t.Score ?? 0) + }) .OrderByDescending(x => x.Score) .Skip((dto.Page - 1) * dto.Size) .Take(dto.Size) .ToListAsync(); - return grouped.Select((x, i) => new DeviceSyncGradeOrderItemDto + // 7️⃣ 返回结果 + return grouped.Select(x => new DeviceSyncGradeOrderItemDto { UserId = x.Key.Id, Nickname = x.Key.StudentName, - OrganizationId = x.Key.ClassId, + OrganizationId = x.Key.ClassId ?? 0, OrganizationName = x.Key.ClassName ?? "", Gender = x.Key.Sex, - Score = x.Score.ToString(), - CourseId = x.Key.ProjectId ?? 0, - CourseName = x.Key.ProjectName ?? "" + Score = x.Score.ToString("0.##"), + CourseId = x.Key.CourseId, + CourseName = Util.GetEnumDescription(x.Key.CourseId) + }).ToList(); } + private static DateTime? TryParseTrainTime(string tsCreateTime, string tdStartTime, string tdEndTime) + { + foreach (var s in new[] { tdEndTime, tdStartTime, tsCreateTime }) + { + if (string.IsNullOrWhiteSpace(s)) continue; + if (DateTime.TryParse(s, out var d)) return d; + } + return null; + } + + public async Task> LastGradeAsync(DeviceSyncLastGradeRequestDto dto) + { + if (dto == null || dto.UserSerials == null || dto.UserSerials.Count == 0) + return new List(); + + // 去掉空白、重复序列号 + var orderedUnique = dto.UserSerials + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Select(x => x.Trim()) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + if (!orderedUnique.Any()) return new List(); + + // 查询学生信息 + var students = await _context.Student + .Where(s => orderedUnique.Contains(s.StudentNo)) + .ToListAsync(); + + S_Student MatchStudent(string serial) => + students.FirstOrDefault(s => string.Equals(s.StudentNo, serial, StringComparison.OrdinalIgnoreCase)); + + // 获取学生最近一次训练记录 + var studentSerials = students.Select(s => s.StudentNo).ToList(); + var trainRows = await _context.YL_Train + .Where(t => studentSerials.Contains(t.UserSerial)) + .GroupBy(t => t.UserSerial) + .Select(g => g + .OrderByDescending(t => t.EndTime) + .FirstOrDefault()) + .ToListAsync(); + + var trainMap = trainRows.ToDictionary(t => t.UserSerial, t => t); + + var result = new List(); + + foreach (var serial in orderedUnique) + { + var st = MatchStudent(serial); + if (st == null) + { + result.Add(new DeviceSyncLastGradeItemDto + { + UserSerial = serial, + UserId = 0, + Nickname = "", + OrganizationId = 0, + OrganizationName = "", + Gender = 0, + FacePictureUrl = "", + Score = "0", + UnStandardCounts = 0, + StartTime = "", + EndTime = "", + CourseId = 0, + CourseName = "" + }); + continue; + } + + trainMap.TryGetValue(serial, out var train); + + result.Add(new DeviceSyncLastGradeItemDto + { + UserSerial = st.StudentNo, + UserId = st.Id, + TrainId = train?.Id ?? 0, + Nickname = st.StudentName, + OrganizationId = st.ClassId ?? 0, + OrganizationName = st.ClassName ?? "", + Gender = st.Sex, + FacePictureUrl = st.Photo ?? "", + Score = train?.Score?.ToString("0") ?? "0", + UnStandardCounts = train?.UnStandardCounts ?? 0, + StartTime = train?.StartTime.ToString("yyyy-MM-dd HH:mm:ss") ?? "", + EndTime = train?.EndTime.ToString("yyyy-MM-dd HH:mm:ss") ?? "", + CourseId = train?.CourseId ?? 0, + CourseName = "" // 如果你的 YL_Train 表里有课程名称字段,可以填充 + }); + } + + return result; + } + public async Task> RecordAppAsync(DeviceSyncRecordAppRequestDto dto) { var orgId = await GetOrgIdByDeviceAsync(dto.DeviceSerial); - if (!orgId.HasValue) return new List(); + if (!orgId.HasValue) + return new List(); + // 👉 1. 学校编码 + string schoolCode = await _context.School + .Where(x => x.Id == orgId) + .Select(x => x.SchoolCode) + .FirstAsync(); + + // 👉 2. 时间范围 var (start, end) = GetDateRange(dto.DateType, dto.QueryStartTime, dto.QueryEndTime); - string schoolCode = await _context.School.Where(x => x.Id == orgId).Select(x => x.SchoolCode).FirstAsync(); - var query = from ts in _context.XW_TrainStudents - join td in _context.XW_SportsTestData on ts.XW_SportsTestDataId equals td.Id - join s in _context.Student on ts.StudentId equals s.Id + // 👉 3. 主查询(YL_Train + Student) + var query = from t in _context.YL_Train + join s in _context.Student on t.UserSerial equals s.StudentNo where s.SchoolCode == schoolCode - select new { ts, td, s }; + select new { t, s }; - if (dto.CourseId.HasValue) query = query.Where(x => x.td.ProjectId == dto.CourseId); - if (dto.Gender.HasValue) query = query.Where(x => x.s.Sex == dto.Gender); - if (!string.IsNullOrEmpty(dto.UserSerial)) query = query.Where(x => x.s.StudentNo == dto.UserSerial); - if (!string.IsNullOrEmpty(dto.UserName)) query = query.Where(x => x.s.StudentName.Contains(dto.UserName)); - if (dto.OrganizeId?.Count > 0) query = query.Where(x => dto.OrganizeId.Contains(x.ts.ClassId)); - if (start.HasValue) query = query.Where(x => x.ts.CreateTime != null && string.Compare(x.ts.CreateTime, start.Value.ToString("yyyy-MM-dd")) >= 0); - if (end.HasValue) query = query.Where(x => x.ts.CreateTime != null && string.Compare(x.ts.CreateTime, end.Value.AddDays(1).ToString("yyyy-MM-dd")) < 0); + // 👉 4. 条件过滤 + if (dto.CourseId.HasValue) + query = query.Where(x => x.t.CourseId == dto.CourseId.Value); - return await query - .OrderByDescending(x => x.ts.CreateTime) + if (dto.Gender.HasValue) + query = query.Where(x => x.s.Sex == dto.Gender.Value); + + if (!string.IsNullOrEmpty(dto.UserSerial)) + query = query.Where(x => x.t.UserSerial == dto.UserSerial); + + if (!string.IsNullOrEmpty(dto.UserName)) + query = query.Where(x => x.s.StudentName.Contains(dto.UserName)); + + if (dto.OrganizeId != null && dto.OrganizeId.Count > 0) + query = query.Where(x => dto.OrganizeId.Contains(x.s.ClassId ?? 0)); + + if (start.HasValue) + query = query.Where(x => x.t.StartTime >= start.Value); + + if (end.HasValue) + query = query.Where(x => x.t.StartTime < end.Value.AddDays(1)); + + // 👉 5. 分页 + 排序 + var list = await query + .OrderByDescending(x => x.t.StartTime) .Skip((dto.Page - 1) * dto.Size) .Take(dto.Size) .Select(x => new DeviceSyncRecordAppItemDto { - TrainId = x.td.Id, - Nickname = x.ts.StudentName, - OrganizationId = x.ts.ClassId, - OrganizationName = x.ts.ClassName, - Gender = x.ts.Sex, + TrainId = x.t.Id, + + Nickname = x.s.StudentName, + OrganizationId = x.s.ClassId ?? 0, + OrganizationName = x.s.ClassName, + + Gender = x.s.Sex, + FacePictureUrl = x.s.Photo, - Score = x.ts.Achievement.ToString(), - StartTime = x.td.StartTime, - EndTime = x.td.EndTime, - CourseId = x.td.ProjectId ?? 0, - CourseName = x.td.ProjectName + + Score = x.t.Score.HasValue ? x.t.Score.Value.ToString("0.##") : "0", + + StartTime = x.t.StartTime.ToString("yyyy-MM-dd HH:mm:ss"), + EndTime = x.t.EndTime.ToString("yyyy-MM-dd HH:mm:ss"), + + CourseId = x.t.CourseId, + CourseName = $"课目{x.t.CourseId}" // 👉 建议后面接字典表 }) .ToListAsync(); + + return list; } public async Task MyRecordAsync(DeviceSyncMyRecordRequestDto dto) { + var result = new DeviceSyncMyRecordResponseDto(); + var orgId = await GetOrgIdByDeviceAsync(dto.DeviceSerial); - if (!orgId.HasValue) return new DeviceSyncMyRecordResponseDto(); + if (!orgId.HasValue) return result; - string schoolCode = await _context.School.Where(x => x.Id == orgId).Select(x => x.SchoolCode).FirstAsync(); - var student = await _context.Student.FirstOrDefaultAsync(x => x.SchoolCode == schoolCode && x.StudentNo == dto.UserSerial); - if (student == null) return new DeviceSyncMyRecordResponseDto(); + // 👉 1. 查学生 + string schoolCode = await _context.School + .Where(x => x.Id == orgId) + .Select(x => x.SchoolCode) + .FirstAsync(); - var (start, end) = GetDateRange(dto.DateType, dto.QueryStartTime, dto.QueryEndTime); - var trainQuery = _context.XW_TrainStudents.Where(x => x.StudentId == student.Id); - if (start.HasValue) trainQuery = trainQuery.Where(x => x.CreateTime != null && string.Compare(x.CreateTime, start.Value.ToString("yyyy-MM-dd")) >= 0); - if (end.HasValue) trainQuery = trainQuery.Where(x => x.CreateTime != null && string.Compare(x.CreateTime, end.Value.AddDays(1).ToString("yyyy-MM-dd")) < 0); + var student = await _context.Student + .FirstOrDefaultAsync(x => x.SchoolCode == schoolCode && x.StudentNo == dto.UserSerial); - var trains = await trainQuery.Include(x => x.XW_SportsTestData).ToListAsync(); - var graphList = trains.Where(x => x.XW_SportsTestData != null).GroupBy(x => x.XW_SportsTestData.ProjectId ?? 0).Select(g => new GraphTrainListItem - { - CourseId = g.Key, - CourseName = g.First().XW_SportsTestData?.ProjectName ?? "", - Time = g.Count().ToString(), - Count = g.Count() - }).ToList(); + if (student == null) return result; var c = await _context.Class.FindAsync(student.ClassId); - return new DeviceSyncMyRecordResponseDto + + // 👉 2. 时间范围 + var (start, end) = GetDateRange(dto.DateType, dto.QueryStartTime, dto.QueryEndTime); + + var query = _context.YL_Train + .Where(x => x.UserSerial == dto.UserSerial); + + if (start.HasValue) + query = query.Where(x => x.StartTime >= start.Value); + + if (end.HasValue) + query = query.Where(x => x.StartTime < end.Value.AddDays(1)); + + if (dto.CourseType > 0) + query = query.Where(x => x.CourseId == dto.CourseType); + + var trains = await query.ToListAsync(); + + if (!trains.Any()) + return result; + + // 👉 3. 统计 + var trainDays = trains.Select(x => x.StartTime.Date).Distinct().Count(); + var totalSeconds = trains.Sum(x => x.Duration); + var trainHours = (totalSeconds / 3600.0).ToString("0.00"); + + // 👉 4. gradeList(按天统计) + var gradeList = trains + .GroupBy(x => x.StartTime.Date) + .Select(g => new GradeListItem + { + Time = g.Key.ToString("yyyy-MM-dd"), + Duration = g.Sum(x => x.Duration).ToString(), + Count = g.Sum(x => x.StandardCounts).ToString() + }).ToList(); + + // 👉 5. graphTrainList(课目统计) + var graphList = trains + .GroupBy(x => x.CourseId) + .Select(g => new GraphTrainListItem + { + CourseId = g.Key, + CourseName = $"课目{g.Key}", // 👉 可换成字典表 + Time = g.Sum(x => x.Duration).ToString(), + Count = g.Count() + }).ToList(); + + // 👉 6. courseBestList(最好/平均/最差) + var courseBestList = trains + .Where(x => x.Score.HasValue) + .GroupBy(x => x.CourseId) + .Select(g => new CourseBestListItem + { + CourseId = g.Key, + CourseName = $"课目{g.Key}", + BestGrade = g.Max(x => x.Score).ToString(), + AvgGrade = g.Average(x => x.Score).ToString(), + WorstGrade = g.Min(x => x.Score).ToString() + }).ToList(); + + // 👉 7. 返回 + result = new DeviceSyncMyRecordResponseDto { UserSync = new UserSyncDto { @@ -346,14 +655,22 @@ namespace YD_YiLu.Api.Services.Impl Identity = "学生", Gender = student.Sex == 1 ? "男" : "女", Birthday = student.Birthday?.ToString("yyyy-MM-dd") ?? "", - UserSerial = student.StudentNo + UserSerial = student.StudentNo, + Height = "", + Weight = "" }, + TrainCourseSum = graphList.Count, - TrainDays = trains.Select(x => x.CreateTime?.Substring(0, 10)).Distinct().Count(), - TrainHours = "0", + TrainDays = trainDays, + TrainHours = trainHours, TrainCounts = trains.Count, - GraphTrainList = graphList + + GradeList = gradeList, + GraphTrainList = graphList, + CourseBestList = courseBestList }; + + return result; } public async Task TeacherLoginAsync(TeacherLoginRequestDto dto) @@ -436,5 +753,6 @@ namespace YD_YiLu.Api.Services.Impl _ => (null, null) }; } + } } diff --git a/YD_YiLu/Services/Impl/LargeScreenService.cs b/YD_YiLu/Services/Impl/LargeScreenService.cs index 655fd2b..8a791cc 100644 --- a/YD_YiLu/Services/Impl/LargeScreenService.cs +++ b/YD_YiLu/Services/Impl/LargeScreenService.cs @@ -1,15 +1,12 @@ using AutoMapper; using Microsoft.EntityFrameworkCore; -using VOL.Entity.DomainModels.XinWei; -using VOL.Entity.Enum; +using VOL.Commons; using YD_YiLu.Api.Context; using YD_YiLu.Api.Services.Interface; using YD_YiLu.Api.SmartSportsEntitys; using YD_YiLu.Api.Utilities; -using YD_YiLu.Commons.Dto.Common; +using YD_YiLu.Commons; using YD_YiLu.Commons.Dto.LargeScreen; -using YD_YiLu.Commons.Enum; -using YD_YiLu.Commons.MemoryCaches; namespace YD_YiLu.Api.Services.Impl { diff --git a/YD_YiLu/Services/Impl/XinWeiService.cs b/YD_YiLu/Services/Impl/XinWeiService.cs index 850f28c..48d355f 100644 --- a/YD_YiLu/Services/Impl/XinWeiService.cs +++ b/YD_YiLu/Services/Impl/XinWeiService.cs @@ -10,13 +10,9 @@ using YD_YiLu.Api.SmartSportsEntitys; using YD_YiLu.Api.Utilities; using YD_YiLu.Commons.Dto.Common; using YD_YiLu.Commons.Dto.Open; -using YD_YiLu.Commons.Dto.School; -using YD_YiLu.Commons; using YD_YiLu.Commons.Utils; -using static Microsoft.EntityFrameworkCore.DbLoggerCategory; -using Newtonsoft.Json.Linq; -using VOL.Entity.Enum; using System.Text.Json; +using VOL.Commons; namespace YD_YiLu.Api.Services.Impl { diff --git a/YD_YiLu/Services/Interface/IDeviceSyncService.cs b/YD_YiLu/Services/Interface/IDeviceSyncService.cs index c0ced31..7a3218f 100644 --- a/YD_YiLu/Services/Interface/IDeviceSyncService.cs +++ b/YD_YiLu/Services/Interface/IDeviceSyncService.cs @@ -38,6 +38,11 @@ namespace YD_YiLu.Api.Services.Interface /// Task> GradeOrderAsync(DeviceSyncGradeOrderRequestDto dto); + /// + /// 人员上次成绩(按人员序列号批量查询) + /// + Task> LastGradeAsync(DeviceSyncLastGradeRequestDto dto); + /// /// 查询运动记录 /// diff --git a/YD_YiLu/SmartSportsEntitys/N_SportsTestValue.cs b/YD_YiLu/SmartSportsEntitys/N_SportsTestValue.cs index b7b8712..f040a4e 100644 --- a/YD_YiLu/SmartSportsEntitys/N_SportsTestValue.cs +++ b/YD_YiLu/SmartSportsEntitys/N_SportsTestValue.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using VOL.Entity.Enum; +using VOL.Commons; using YD_YiLu.Commons.Dto.Open; namespace YD_YiLu.Api.SmartSportsEntitys