diff --git a/VOL.Business/IServices/Norm/IN_SportsTestResultService.cs b/VOL.Business/IServices/Norm/IN_SportsTestResultService.cs index beed536..d4d403c 100644 --- a/VOL.Business/IServices/Norm/IN_SportsTestResultService.cs +++ b/VOL.Business/IServices/Norm/IN_SportsTestResultService.cs @@ -22,6 +22,12 @@ namespace VOL.Business.IServices Task LargeScreenDataStat(LargeScreenDataStatParam paramDto); + /// + /// 智慧操场 + /// + /// + Task SmartPlayground(); + /// /// 学期数据 /// diff --git a/VOL.Business/Services/Norm/N_SportsTestResultService.cs b/VOL.Business/Services/Norm/N_SportsTestResultService.cs index 25ddb69..e5a6db5 100644 --- a/VOL.Business/Services/Norm/N_SportsTestResultService.cs +++ b/VOL.Business/Services/Norm/N_SportsTestResultService.cs @@ -33,6 +33,7 @@ using VOL.Entity.DomainModels.Business.People; using VOL.Entity.Enum; using VOL.Model; using VOL.Model.Ai; +using VOL.Model.IOT.Request; using VOL.Model.Norm.Request; using VOL.Model.Norm.Response; using VOL.Model.School.Response; @@ -506,6 +507,343 @@ namespace VOL.Business.Services return res; } + + #region 智慧操场数据 + /// + /// 智慧操场 + /// + /// + public async Task SmartPlayground() + { + var res = new SmartPlaygroundModel(); + var tenantId = UserContext.Current.TenantId; + + //学校信息 + var smartPlaygroundSchoolInfoKey = $"SmartPlaygroundSchoolInfo_{tenantId}"; + var schoolInfo = _cacheService.Get(smartPlaygroundSchoolInfoKey); + if (schoolInfo == null) + { + schoolInfo = await SmartPlaygroundSchoolInfo(tenantId); + _cacheService.AddObject(smartPlaygroundSchoolInfoKey, schoolInfo, 600); + } + res.SchoolInfo = schoolInfo; + + var today = DateTime.Today; + + // 先确定当前学期的时间范围 + DateTime startDate, endDate; + + // 规则:上学期 9月1日 – 次年2月1日;下学期 2月1日 – 9月1日 + if (today.Month >= 9 || today.Month < 2) // 上学期 + { + startDate = new DateTime(today.Month >= 9 ? today.Year : today.Year - 1, 1, 1); + endDate = new DateTime(today.Month >= 9 ? today.Year + 1 : today.Year, 2, 1); + } + else // 下学期 + { + startDate = new DateTime(today.Year, 2, 1); + endDate = new DateTime(today.Year, 10, 1); + } + + var SmartPlaygroundKey = $"SmartPlayground_{tenantId}"; + + // 从缓存中获取数据 + var sportsTestResults = _cacheService.Get>(SmartPlaygroundKey); + + if (sportsTestResults == null) + { + // 查询条件加上 ScoreTime 范围 + var testResults = await _sportsTestResultRepository + .FindAsIQueryable(x => x.SchoolCode == tenantId + && x.IsDisplay + && x.DataSource == DataSource.XW + && x.ScoreTime >= startDate + && x.ScoreTime < endDate) + .ToListAsync(); + + sportsTestResults = _mapper.Map>(testResults); + _cacheService.AddObject(smartPlaygroundSchoolInfoKey, schoolInfo, 600); + } + + var classListKey = $"ClassList_{tenantId}"; + + var classList = _cacheService.Get>(classListKey); + + if (classList == null || classList.Count == 0) + { + classList = await _classRepository.FindAsIQueryable(x => x.SchoolCode == tenantId).ToListAsync(); + _cacheService.AddObject(classListKey, classList, 3600); + } + + + // 统计逻辑 + var rankValues = new List { "优秀", "良好", "及格", "不及格" }; + + var totalCount = sportsTestResults.Count; + var totalMale = sportsTestResults.Count(x => x.Sex == SexType.Male); + var totalFemale = sportsTestResults.Count(x => x.Sex == SexType.Female); + + var tempStats = new List(); + + for (int i = 0; i < rankValues.Count; i++) + { + var rank = rankValues[i]; + var count = sportsTestResults.Count(x => x.Score.GetRank() == rank); + var maleCount = sportsTestResults.Count(x => x.Sex == SexType.Male && x.Score.GetRank() == rank); + var femaleCount = sportsTestResults.Count(x => x.Sex == SexType.Female && x.Score.GetRank() == rank); + + int rate = totalCount == 0 ? 0 : (i < rankValues.Count - 1 ? (int)Math.Round((double)count / totalCount * 100) : 100 - tempStats.Sum(s => s.Rate)); + int maleRate = totalMale == 0 ? 0 : (i < rankValues.Count - 1 ? (int)Math.Round((double)maleCount / totalMale * 100) : 100 - tempStats.Sum(s => s.MaleRate)); + int femaleRate = totalFemale == 0 ? 0 : (i < rankValues.Count - 1 ? (int)Math.Round((double)femaleCount / totalFemale * 100) : 100 - tempStats.Sum(s => s.FemaleRate)); + + tempStats.Add(new RankStatsDto + { + Rank = rank, + Count = count, + MaleCount = maleCount, + FemaleCount = femaleCount, + Rate = rate, + MaleRate = maleRate, + FemaleRate = femaleRate + }); + + } + + res.RankStatsList = tempStats; + + res.ResultSituationDic = sportsTestResults + .GroupBy(x => x.CategoryValue) + .ToDictionary( + g => ((SportsTestItemType)g.Key).GetDisplayName(), + g => + { + var result = new ResultSituation(); + + var categoryTotal = g.Count(); + var categoryMaleTotal = g.Count(x => x.Sex == SexType.Male); + var categoryFemaleTotal = g.Count(x => x.Sex == SexType.Female); + + int sumRate = 0, sumMaleRate = 0, sumFemaleRate = 0; + + var tempStats = new List(); + + for (int i = 0; i < rankValues.Count; i++) + { + var rank = rankValues[i]; + var count = g.Count(x => x.Score.GetRank() == rank); + var maleCount = g.Count(x => x.Sex == SexType.Male && x.Score.GetRank() == rank); + var femaleCount = g.Count(x => x.Sex == SexType.Female && x.Score.GetRank() == rank); + + int rate = categoryTotal == 0 ? 0 : (i < rankValues.Count - 1 + ? (int)Math.Round((double)count / categoryTotal * 100) + : 100 - sumRate); + + int activityLevel = categoryTotal == 0 ? 0 : (int)Math.Round((double)categoryTotal / sportsTestResults.Count * 100); + + tempStats.Add(new TestSituationsModel + { + Rank = rank, + Count = count, + MaleCount = maleCount, + FemaleCount = femaleCount, + Rate = rate, + ActivityLevel = activityLevel + }); + } + + result.TestSituationsList = tempStats; + + var maleData = g.Where(x => x.Sex == SexType.Male).ToList(); + var femaleData = g.Where(x => x.Sex == SexType.Female).ToList(); + + var maleProportion = GetMonthlyAverageScore(maleData); + var femaleProportion = GetMonthlyAverageScore(femaleData); + + result.AvgTestResultDic.Add("男生", maleProportion); + result.AvgTestResultDic.Add("女生", femaleProportion); + + result.Vitality = (int)Math.Round((double)g.Count() / sportsTestResults.Count() * 100); + + return result; + }); + + + res.ClassSportsRankList = sportsTestResults + .GroupBy(x => new { x.ClassId }) + .Select(g => new ClassSportsRankingDto + { + ClassId = g.Key.ClassId, + ClassName = classList.Where(c => c.Id == g.Key.ClassId).Select(x => $"{x.GradeName}-{x.ClassName}").FirstOrDefault() ?? "", + Count = g.Count() + }) + .OrderByDescending(x => x.Count) + .Take(10) + .Select((x, index) => + { + x.Rank = index + 1; + return x; + }) + .ToList(); + + res.GradeExcellentRateDic = classList + .GroupBy(x => x.GradeId) + .ToDictionary( + g => g.First().GradeName, + g => + { + var gradeStudents = sportsTestResults.Where(x => x.GradeId == g.Key).ToList(); + int total = gradeStudents.Count; + + var goodRanks = new[] { "优秀", "良好" }; + + int femaleTotal = gradeStudents.Count(x => x.Sex == SexType.Female); + int maleTotal = gradeStudents.Count(x => x.Sex == SexType.Male); + + var result = new GradeExcellentRate() + { + // 优良率(全体) + Rate = total == 0 ? 0 : + (int)Math.Round((double)gradeStudents.Count(x => goodRanks.Contains(x.Score.GetRank())) / total * 100), + + // 女生优良率 + FemaleRate = femaleTotal == 0 ? 0 : + (int)Math.Round((double)gradeStudents.Count(x => x.Sex == SexType.Female && goodRanks.Contains(x.Score.GetRank())) / femaleTotal * 100), + + // 男生优良率 + MaleRate = maleTotal == 0 ? 0 : + (int)Math.Round((double)gradeStudents.Count(x => x.Sex == SexType.Male && goodRanks.Contains(x.Score.GetRank())) / maleTotal * 100) + }; + + return result; + }); + + + return res; + } + + /// + /// 智慧操场学校信息 + /// + /// + public async Task SmartPlaygroundSchoolInfo(string tenantId) + { + var schoolInfo = new SchoolInfoModel(); + + // 使用单个查询获取年级、班级和教师信息 + var gradeModels = await ( + from g in _gradeRepository.DbContext.Set() + join a in _gradeRepository.DbContext.Set() on g.Id equals a.GradeId + where a.SchoolCode == tenantId + select new + { + g.Id, + g.GradeName, + Classes = _gradeRepository.DbContext.Set() + .Where(c => c.GradeId == g.Id && c.SchoolCode == tenantId) + .Select(c => new { c.Id }) + .ToList() + }) + .Select(x => new + { + x.Id, + x.GradeName, + ClassCount = x.Classes.Count, + ClassIds = x.Classes.Select(c => c.Id).ToList() + }) + .ToListAsync(); + + // 获取教师总数(简化查询) + var tCount = await _gradeRepository.DbContext.Set() + .Where(t => t.SchoolCode == tenantId && t.TeacherStatus != TeacherStatus.Depart) + .CountAsync(); + + // 获取班级与教师的关联信息 + var classTeacherMap = await ( + from a in _gradeRepository.DbContext.Set() + join t in _gradeRepository.DbContext.Set() on a.TeacherId equals t.Id + where t.SchoolCode == tenantId && t.TeacherStatus != TeacherStatus.Depart + group a.TeacherId by a.ClassId into g + select new { ClassId = g.Key, TeacherIds = g.Distinct().ToList() }) + .ToDictionaryAsync(x => x.ClassId, x => x.TeacherIds); + + // 获取学生信息(简化查询) + var studentCounts = await _studentRepository.DbContext.Set() + .Where(s => s.SchoolCode == tenantId && s.StudentStatus == StudentStatus.Normal) + .GroupBy(s => s.Sex) + .Select(g => new { Sex = g.Key, Count = g.Count() }) + .ToListAsync(); + + // 处理数据 + var allClassIds = gradeModels.SelectMany(x => x.ClassIds).ToList(); + var gradeTeacherCounts = new Dictionary(); // 假设年级ID类型为Guid + + foreach (var grade in gradeModels) + { + schoolInfo.ClassData.Add(new ClassModel + { + GradeName = grade.GradeName, + ClassCount = grade.ClassCount + }); + + // 计算该年级的教师数量 + var teacherIds = new HashSet(); // 假设教师ID类型为Guid + foreach (var classId in grade.ClassIds) + { + if (classTeacherMap.TryGetValue(classId, out var classTeacherIds)) + { + foreach (var teacherId in classTeacherIds) + { + teacherIds.Add(teacherId); + } + } + } + + schoolInfo.TeacherData.Add(new TeacherModel + { + GradeName = grade.GradeName, + TeacherCount = teacherIds.Count + }); + } + + // 设置学生数据 + schoolInfo.StudentData.MaleCount = studentCounts.FirstOrDefault(x => x.Sex == SexType.Male)?.Count ?? 0; + schoolInfo.StudentData.FemaleCount = studentCounts.FirstOrDefault(x => x.Sex == SexType.Female)?.Count ?? 0; + + // 设置总计数据 + schoolInfo.TotalClassCount = gradeModels.Sum(x => x.ClassCount); + schoolInfo.TotalTeacherCount = tCount; + + return schoolInfo; + } + + /// + /// 月份分组 + /// + /// + /// + public VariousSportsProportion GetMonthlyAverageScore(List data) + { + var result = new VariousSportsProportion(); + + // 按月份分组 + var monthGroups = data + .GroupBy(x => x.ScoreTime.Month) + .OrderBy(g => g.Key); // 按月份排序 + + foreach (var g in monthGroups) + { + result.AxisX.Add($"{g.Key}月"); // 1月、2月... + // 计算平均值,如果没有数据就填 0 + var avg = g.Any() ? (float)Math.Round(g.Average(x => x.Score), 2) : 0; + result.AxisY.Add(avg); + } + + return result; + } + + #endregion + + #region 大屏信息 /// @@ -618,16 +956,16 @@ namespace VOL.Business.Services var sportsTestResults = await _cacheQueryService.GeSportsTestDataCacheAsync(x => x.ScoreTime >= paramDto.StartTime && x.ScoreTime <= paramDto.EndTime); // 运动时长 - schoolInfo.AccumulatedTrainDuration = sportsTestResults.Select(x => x.MotionDuration).Sum(); + //schoolInfo.AccumulatedTrainDuration = sportsTestResults.Select(x => x.MotionDuration).Sum(); - var largeScreenTrainingDataModel = new List - { - await GeLargeScreenTrainingInfo(1, paramDto), - await GeLargeScreenTrainingInfo(2, paramDto), - await GeLargeScreenTrainingInfo(3, paramDto) - }; + //var largeScreenTrainingDataModel = new List + //{ + // await GeLargeScreenTrainingInfo(1, paramDto), + // await GeLargeScreenTrainingInfo(2, paramDto), + // await GeLargeScreenTrainingInfo(3, paramDto) + //}; - schoolInfo.LargeScreenTrainingDataModel = largeScreenTrainingDataModel; + //schoolInfo.LargeScreenTrainingDataModel = largeScreenTrainingDataModel; return schoolInfo; } diff --git a/VOL.Model/Norm/Response/SchoolInfoModel.cs b/VOL.Model/Norm/Response/SchoolInfoModel.cs index 94859f3..6d91a48 100644 --- a/VOL.Model/Norm/Response/SchoolInfoModel.cs +++ b/VOL.Model/Norm/Response/SchoolInfoModel.cs @@ -40,12 +40,12 @@ namespace VOL.Model.Norm.Response /// /// 累计训练时长 /// - public int AccumulatedTrainDuration { get; set; } + //public int AccumulatedTrainDuration { get; set; } /// /// 大屏训练数据 /// - public List LargeScreenTrainingDataModel { get; set; } = new List { }; + //public List LargeScreenTrainingDataModel { get; set; } = new List { }; } /// diff --git a/VOL.Model/Norm/Response/SmartPlaygroundModel.cs b/VOL.Model/Norm/Response/SmartPlaygroundModel.cs new file mode 100644 index 0000000..8b9be97 --- /dev/null +++ b/VOL.Model/Norm/Response/SmartPlaygroundModel.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace VOL.Model.Norm.Response +{ + /// + /// 智慧操场 + /// + public class SmartPlaygroundModel + { + /// + /// 学校信息 + /// + public SchoolInfoModel SchoolInfo { get; set; } = new SchoolInfoModel(); + + /// + /// 等级占比 + /// + public List RankStatsList { get; set; } = new List(); + + /// + /// 各项目成绩情况 + /// + public Dictionary ResultSituationDic { get; set; } = new Dictionary(); + + /// + /// 班级运动榜 + /// + public List ClassSportsRankList { get; set; } = new List(); + + /// + /// 年级优良率 + /// + public Dictionary GradeExcellentRateDic { get; set; } = new Dictionary(); + } + + public class ResultSituation + { + public int Vitality { get; set; } + public List TestSituationsList { get; set; } = new List(); + + public Dictionary AvgTestResultDic { get; set; } = new Dictionary(); + + } + + public class RankStatsDto : TestSituationsModel + { + public int MaleRate { get; set; } + public int FemaleRate { get; set; } + } + + public class TestSituationsModel + { + public string Rank { get; set; } + public int Count { get; set; } + public int Rate { get; set; } + public int MaleCount { get; set; } + public int FemaleCount { get; set; } + + public int ActivityLevel { get; set; } + } + public class ClassSportsRankingDto + { + /// + /// 排名 + /// + public int Rank { get; set; } + + /// + /// 班级Id + /// + public int ClassId { get; set; } + + /// + /// 班级名称 + /// + public string ClassName { get; set; } + + /// + /// 运动次数 + /// + public int Count { get; set; } + } + + public class GradeExcellentRate + { + public int Rate { get; set; } + public int MaleRate { get; set; } + public int FemaleRate { get; set; } + } +} diff --git a/VOL.Model/School/Response/SportsTestValueModel.cs b/VOL.Model/School/Response/SportsTestValueModel.cs index 6722081..a10da55 100644 --- a/VOL.Model/School/Response/SportsTestValueModel.cs +++ b/VOL.Model/School/Response/SportsTestValueModel.cs @@ -68,6 +68,7 @@ namespace VOL.Model.School.Response /// ///类别枚举值 /// + //public SportsTestItemType CategoryValue { get; set; } public int CategoryValue { get; set; } /// diff --git a/VOL.WebApi/Controllers/Business/LargeScreenController.cs b/VOL.WebApi/Controllers/Business/LargeScreenController.cs index bb89a8d..759eeb1 100644 --- a/VOL.WebApi/Controllers/Business/LargeScreenController.cs +++ b/VOL.WebApi/Controllers/Business/LargeScreenController.cs @@ -66,5 +66,15 @@ namespace VOL.WebApi.Controllers.Business { return await _sportsTestResultService.LargeScreenAverageClassExerciseIntensity(paramDto); } + + /// + /// 智慧操场 + /// + /// + [HttpGet(nameof(SmartPlayground))] + public async Task SmartPlayground() + { + return await _sportsTestResultService.SmartPlayground(); + } } }