using AutoMapper; using Castle.DynamicProxy.Generators; using Google.Protobuf.Collections; using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using OfficeOpenXml.FormulaParsing.Excel.Functions.DateTime; using System; using System.Collections.Generic; using System.IO.Compression; using System.Linq; using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; using VOL.Business.IRepositories; using VOL.Business.IServices.Norm; using VOL.Business.IServices.School; using VOL.Core.CacheManager; using VOL.Core.Extensions; using VOL.Core.Extensions.AutofacManager; using VOL.Core.ManageUser; using VOL.Core.Utilities; using VOL.Entity.DomainModels; 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.School.Request; using VOL.Model.School.Response; using VOL.System.IRepositories; using VOL.System.Repositories; using static Microsoft.EntityFrameworkCore.DbLoggerCategory; namespace VOL.Business.Services.School { public class S_StudentService : IS_StudentService, IDependency { #region 初始化 private readonly IMapper _mapper; private readonly ICacheService _cacheService; private readonly ICacheQueryService _cacheQueryService; private readonly IotDataSyncService _iotDataSyncService; private readonly IS_StudentRepository _studentRepository; private readonly IS_GradeRepository _gradeRepository; private readonly IS_ClassRepository _classRepository; private readonly IN_SportsTestResultRepository _sportsTestResultRepository; private readonly IN_SportsTestCategoryRepository _sportsTestCategoryRepository; [ActivatorUtilitiesConstructor] public S_StudentService(IMapper mapper, ICacheService cacheService, ICacheQueryService cacheQueryService, IS_StudentRepository studentRepository, IS_GradeRepository gradeRepository, IS_ClassRepository classRepository, IN_SportsTestResultRepository sportsTestResultRepository, IN_SportsTestCategoryRepository sportsTestCategoryRepository, IotDataSyncService iotDataSyncService) { _mapper = mapper; _cacheService = cacheService; _cacheQueryService = cacheQueryService; _studentRepository = studentRepository; _gradeRepository = gradeRepository; _classRepository = classRepository; _sportsTestResultRepository = sportsTestResultRepository; _sportsTestCategoryRepository = sportsTestCategoryRepository; _iotDataSyncService = iotDataSyncService; } #endregion public async Task> GetStudentPageList(StudentPageListParam paramDto) { var res = new PageDataDto(); var classIds = new List(); var isTeacher = (UserContext.Current.RoleId == 3); if (isTeacher) { var teacher = await _studentRepository.DbContext.Set().FirstOrDefaultAsync(x => x.SchoolCode == UserContext.Current.TenantId && x.TeacherPhoneNo == UserContext.Current.UserInfo.PhoneNo); var teacherClassList = await ( from at in _studentRepository.DbContext.Set() join c in _studentRepository.DbContext.Set() on at.ClassId equals c.Id where at.TeacherId == teacher.Id select c).ToListAsync(); classIds = teacherClassList.Select(c => c.Id).Distinct().ToList(); } var query = from s in _studentRepository.DbContext.Set() join c in _studentRepository.DbContext.Set() on s.ClassId equals c.Id into sc from c in sc.DefaultIfEmpty() where s.SchoolCode.Equals(UserContext.Current.TenantId) && s.StudentStatus == StudentStatus.Normal && (!isTeacher || classIds.Contains(c.Id)) select new StudentPageListModel() { Id = s.Id, Age = s.Age, ClassId = s.ClassId, ClassName = c.ClassName, GradeName = c.GradeName, HeartRateFrontNo = s.HeartRateFrontNo, HeartRateId = s.HeartRateId, Photo = s.Photo, RopeSkipQRCode = s.RopeSkipQRCode, SchoolRollNo = s.SchoolRollNo, Sex = s.Sex, StudentName = s.StudentName, StudentNo = s.StudentNo, OrderNo = s.OrderNo, StudentStatus = s.StudentStatus }; if (!string.IsNullOrWhiteSpace(paramDto.StudentName)) { query = query.Where(x => x.StudentName.Contains(paramDto.StudentName)); } if (!string.IsNullOrWhiteSpace(paramDto.StudentNo)) { query = query.Where(x => x.StudentNo.Contains(paramDto.StudentNo)); } if (paramDto.Sex > 0) { query = query.Where(x => x.Sex == paramDto.Sex); } if (paramDto.ClassId > 0) { query = query.Where(x => x.ClassId == paramDto.ClassId); } if (paramDto.StudentStatus > 0) { query = query.Where(x => x.StudentStatus == paramDto.StudentStatus); } res.Total = await query.CountAsync(); var list = await query .OrderBy(x => x.Id) .Skip((paramDto.PageIndex - 1) * paramDto.PageSize) .Take(paramDto.PageSize) .ToListAsync(); res.Datas = list; return res; } public async Task> GetStudentList(StudentExportParam paramDto) { var classIds = new List(); var isTeacher = (UserContext.Current.RoleId == 3); if (isTeacher) { var teacher = await _studentRepository.DbContext.Set().FirstOrDefaultAsync(x => x.SchoolCode == UserContext.Current.TenantId && x.TeacherPhoneNo == UserContext.Current.UserInfo.PhoneNo); var teacherClassList = await ( from at in _studentRepository.DbContext.Set() join c in _studentRepository.DbContext.Set() on at.ClassId equals c.Id where at.TeacherId == teacher.Id select c).ToListAsync(); classIds = teacherClassList.Select(c => c.Id).Distinct().ToList(); } var query = from s in _studentRepository.DbContext.Set() join c in _studentRepository.DbContext.Set() on s.ClassId equals c.Id where c.SchoolCode.Equals(UserContext.Current.TenantId) && s.StudentStatus == StudentStatus.Normal && s.StudentStatus == StudentStatus.Normal && (!isTeacher || classIds.Contains(c.Id)) select new StudentPageListModel() { Id = s.Id, Age = s.Age, ClassId = s.ClassId, ClassName = c.ClassName, GradeName = c.GradeName, HeartRateFrontNo = s.HeartRateFrontNo, HeartRateId = s.HeartRateId, Photo = s.Photo, RopeSkipQRCode = s.RopeSkipQRCode, SchoolRollNo = s.SchoolRollNo, Sex = s.Sex, StudentName = s.StudentName, StudentNo = s.StudentNo, StudentStatus = s.StudentStatus }; if (!string.IsNullOrWhiteSpace(paramDto.StudentName)) { query = query.Where(x => x.StudentName.Contains(paramDto.StudentName)); } if (!string.IsNullOrWhiteSpace(paramDto.StudentNo)) { query = query.Where(x => x.StudentNo.Contains(paramDto.StudentNo)); } if (paramDto.Sex > 0) { query = query.Where(x => x.Sex == paramDto.Sex); } if (paramDto.ClassId > 0) { query = query.Where(x => x.ClassId == paramDto.ClassId); } if (paramDto.StudentStatus > 0) { query = query.Where(x => x.StudentStatus == paramDto.StudentStatus); } var list = await query.OrderBy(x => x.Id).ToListAsync(); return list; } /// /// 获取场馆学员 /// /// /// /// public async Task> GetStudentListBySportsHall(StudentExportParam paramDto) { var query = from s in _studentRepository.DbContext.Set() where s.SchoolCode.Equals(UserContext.Current.TenantId) && s.StudentStatus == StudentStatus.Normal && s.StudentStatus == StudentStatus.Normal select new StudentPageListModel() { Id = s.Id, Age = s.Age, ClassId = s.ClassId, ClassName = s.ClassName, GradeName = "运动馆", HeartRateFrontNo = s.HeartRateFrontNo, HeartRateId = s.HeartRateId, Photo = s.Photo, RopeSkipQRCode = s.RopeSkipQRCode, SchoolRollNo = s.SchoolRollNo, Sex = s.Sex, StudentName = s.StudentName, StudentNo = s.StudentNo, StudentStatus = s.StudentStatus, OrderNo = s.OrderNo, }; if (!string.IsNullOrWhiteSpace(paramDto.StudentName)) { query = query.Where(x => x.StudentName.Contains(paramDto.StudentName)); } if (!string.IsNullOrWhiteSpace(paramDto.StudentNo)) { query = query.Where(x => x.StudentNo.Contains(paramDto.StudentNo)); } if (paramDto.Sex > 0) { query = query.Where(x => x.Sex == paramDto.Sex); } if (paramDto.ClassId > 0) { query = query.Where(x => x.ClassId == paramDto.ClassId); } if (paramDto.StudentStatus > 0) { query = query.Where(x => x.StudentStatus == paramDto.StudentStatus); } var list = await query.OrderBy(x => x.OrderNo).ToListAsync(); return list; } public async Task AddStudent(AddStudentParam paramDto) { var stuExists = await _studentRepository.ExistsAsync(x => x.StudentNo == paramDto.StudentNo); if (stuExists) throw new Exception($"学号{paramDto.StudentNo}的学生已存在!"); var studentEntity = _mapper.Map(paramDto); studentEntity.StudentStatus = StudentStatus.Normal; studentEntity.SchoolCode = UserContext.Current.TenantId; studentEntity.Creator = UserContext.Current.UserId; studentEntity.CreateDate = DateTime.Now; await _studentRepository.AddAsync(studentEntity); await _studentRepository.SaveChangesAsync(); //调用回调函数,同步数据到IOT //_ = Task.Run(() => _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam //{ // EventType = EventType.Add.GetDisplayName(), // DataType = IOTDataSyncType.Student.GetDisplayName(), // Json = JsonConvert.SerializeObject(new List() { studentEntity }) //})); await _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam { EventType = EventType.Add.GetDisplayName(), DataType = IOTDataSyncType.Student.GetDisplayName(), Json = JsonConvert.SerializeObject(new List() { studentEntity }) }); } public async Task ModifyStudent(AddStudentParam paramDto) { if (paramDto == null || string.IsNullOrEmpty(paramDto.StudentNo)) { throw new ArgumentNullException("学生学号必填"); } var student = await _studentRepository.FindAsyncFirst(x => x.Id == paramDto.Id); if (student == null) throw new ArgumentNullException("未找到要更新的数据"); var stuExists = await _studentRepository.ExistsAsync(x => x.StudentNo == paramDto.StudentNo && x.Id != student.Id); if (stuExists) throw new Exception($"学号{paramDto.StudentNo}的学生已存在!"); student.StudentName = paramDto.StudentName; // 学生名称 student.ClassId = paramDto.ClassId; // 班级 student.Sex = paramDto.Sex; // 性别 student.Age = paramDto.Age; // 年龄 student.Birthday = paramDto.Birthday; // 出生日期 student.IDCard = paramDto.IDCard; // 身份证 student.Photo = paramDto.Photo; // 学生照片 student.HeartRateFrontNo = paramDto.HeartRateFrontNo; // 心率设备正面编号 student.HeartRateId = paramDto.HeartRateId; // 心率设备ID student.RunChipNo = paramDto.RunChipNo; // 中长跑芯片编号 student.RopeSkipNo = paramDto.RopeSkipQRCode; // 跳绳编号 student.OrderNo = 1; student.Creator = UserContext.Current.UserId; student.Modifier = UserContext.Current.UserId; student.ModifyDate = DateTime.Now; student.StudentStatus = StudentStatus.Normal; _studentRepository.Update(student); await _studentRepository.SaveChangesAsync(); //调用回调函数,同步数据到IOT //_ = Task.Run(() => _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam //{ // EventType = EventType.Update.GetDisplayName(), // DataType = IOTDataSyncType.Student.GetDisplayName(), // Json = JsonConvert.SerializeObject(student) //})); await _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam { EventType = EventType.Update.GetDisplayName(), DataType = IOTDataSyncType.Student.GetDisplayName(), Json = JsonConvert.SerializeObject(student) }); } public async Task UpdateStudentStatus(UpdateStudentStatusParam paramDto) { var student = await _studentRepository.FindAsyncFirst(x => x.StudentNo == paramDto.StudentNo); if (student == null) throw new ArgumentNullException("未找到要更新的数据"); student.StudentStatus = paramDto.Status; _studentRepository.Update(student); await _studentRepository.SaveChangesAsync(); //调用回调函数,同步数据到IOT _ = Task.Run(() => _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam { EventType = EventType.Update.GetDisplayName(), DataType = IOTDataSyncType.Student.GetDisplayName(), Json = JsonConvert.SerializeObject(student) })); } public async Task UpdateStudentPwd(UpdateStudentPwdParam paramDto) { var student = await _studentRepository.FindAsyncFirst(x => x.StudentNo == paramDto.StudentNo); if (student == null) throw new ArgumentNullException("未找到要更新的数据"); student.AppletPwd = paramDto.StudentNo; _studentRepository.Update(student); await _studentRepository.SaveChangesAsync(); //调用回调函数,同步数据到IOT //_ = Task.Run(() => _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam //{ // EventType = EventType.Update.GetDisplayName(), // DataType = IOTDataSyncType.Student.GetDisplayName(), // Json = JsonConvert.SerializeObject(student) //})); await _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam { EventType = EventType.Update.GetDisplayName(), DataType = IOTDataSyncType.Student.GetDisplayName(), Json = JsonConvert.SerializeObject(student) }); } public async Task ChangeClasses(string studentNo, int classId) { var student = await _studentRepository.FindAsyncFirst(x => x.StudentNo == studentNo); if (student == null) throw new ArgumentNullException("未找到更新的数据"); var classModel = await _classRepository.FindAsyncFirst(x => x.Id == student.ClassId); var sportsTestResults = await _sportsTestResultRepository.FindAsIQueryable(x => x.ClassId == student.ClassId && x.StudentNo == studentNo).ToListAsync(); using (var transaction = _studentRepository.DbContext.Database.BeginTransaction()) { try { student.ClassId = classId; student.ClassName = classModel.ClassName; student.Modifier = UserContext.Current.UserId; student.ModifyDate = DateTime.Now; _studentRepository.Update(student); foreach (var item in sportsTestResults) { item.ClassId = classId; item.ClassName = classModel.ClassName; item.Modifier = UserContext.Current.UserId; item.ModifyDate = DateTime.Now; } _sportsTestResultRepository.UpdateRange(sportsTestResults); await _studentRepository.SaveChangesAsync(); // 提交事务 transaction.Commit(); } catch (Exception ex) { // 发生错误,回滚事务 transaction.Rollback(); throw new Exception("操作失败"); } } } public async Task ImportStudents(IFormFile file) { if (file == null || file.Length <= 0) throw new Exception("操作失败"); var tenantId = UserContext.Current.TenantId; var dataObjects = new List(); using (var fileStream = file.OpenReadStream()) { dataObjects = Tool.ConvertExcelToList(fileStream); } var greadNames = dataObjects.Select(x => x.GradeName).Distinct().ToList(); var greadList = await _gradeRepository.FindAsync(x => greadNames.Contains(x.GradeName)); var greadIds = greadList.Select(x => x.Id).ToList(); var classList = await _classRepository.FindAsync(x => greadIds.Contains(x.GradeId) && x.SchoolCode == tenantId); var groupedStudentNos = dataObjects.GroupBy(x => x.StudentNo); foreach (var group in groupedStudentNos) { if (!string.IsNullOrWhiteSpace(group.Key) && group.Count() > 1) { throw new Exception($"存在重复的学号:{group.Key},请检查后重新操作"); } } var stuNos = groupedStudentNos.Select(g => g.Key).ToList(); var stuList = await _studentRepository.FindAsync(x => x.SchoolCode == tenantId && stuNos.Contains(x.StudentNo)); var entitys = new List(); foreach (var data in dataObjects) { if (string.IsNullOrWhiteSpace(data.StudentNo)) { throw new Exception("学号不能为空"); } var stuExists = stuList.Exists(x => x.StudentNo == data.StudentNo); if (stuExists) throw new Exception($"学号{data.StudentNo}的学生已存在!"); if (string.IsNullOrWhiteSpace(data.StudentNo)) { throw new Exception("姓名不能为空"); } if (string.IsNullOrWhiteSpace(data.StudentNo)) { throw new Exception("性别不能为空"); } if (string.IsNullOrWhiteSpace(data.StudentNo)) { throw new Exception("年龄不能为空"); } var greadModel = greadList.Where(x => x.GradeName.Equals(data.GradeName)).FirstOrDefault(); if (greadModel == null) throw new Exception("未找到年级信息"); var classModel = classList.Where(x => x.GradeId == greadModel.Id && x.ClassName == data.ClassName).FirstOrDefault(); if (classModel == null) throw new Exception("未找到班级信息"); var studentEntity = new S_Student() { SchoolCode = tenantId, StudentName = data.StudentName, StudentNo = data.StudentNo, ClassId = classModel.Id, Age = data.Age, IDCard = data.IDCard, Birthday = data.Birthday, HeartRateFrontNo = data.HeartRateFrontNo, HeartRateId = data.HeartRateId, RopeSkipQRCode = data.RopeSkipQRCode, Sex = data.Sex == "男" ? SexType.Male : SexType.Female, RunChipNo = data.RunChipNo, StudentStatus = StudentStatus.Normal, Creator = UserContext.Current.UserId, CreateDate = DateTime.Now }; entitys.Add(studentEntity); } await _studentRepository.AddRangeAsync(entitys); await _studentRepository.SaveChangesAsync(); //调用回调函数,同步数据到IOT //_ = Task.Run(() => _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam //{ // EventType = EventType.Add.GetDisplayName(), // DataType = IOTDataSyncType.Student.GetDisplayName(), // Json = JsonConvert.SerializeObject(entitys) //})); await _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam { EventType = EventType.Add.GetDisplayName(), DataType = IOTDataSyncType.Student.GetDisplayName(), Json = JsonConvert.SerializeObject(entitys) }); } /// /// 上传学生头像 /// public async Task UploadPhoto(IFormFile file, int gradeId, int classId, string studentNo, string studentName) { //var student = _studentRepository.FindFirst(c => c.StudentNo == studentNo); //if (student == null) //{ // throw new Exception("学生未找到"); //} // 设置照片最大允许大小 (5MB) const long MaxFileSize = 2 * 1024 * 1024; // 检查文件大小是否超过限制 if (file.Length > MaxFileSize) { throw new Exception("上传的照片文件大小不能超过2MB"); } var url = ALiYunOss.Upload(file, $"Upload/{UserContext.Current.TenantId}/Photo/Student/{gradeId}/{classId}/", studentNo); var faceEntityWithRequest = new FaceEntityWithDto() { EntityId = studentNo, SchoolCode = UserContext.Current.TenantId, IsStudent = true }; await ALiYunFace.DeleteFaceEntityWith(faceEntityWithRequest); var faceEntityWithReuslt = await ALiYunFace.FaceEntityWith(faceEntityWithRequest); if (!faceEntityWithReuslt) { throw new Exception("上传失败"); } var faceWithReuslt = await ALiYunFace.FaceWith(new FaceWithDto() { SchoolCode = UserContext.Current.TenantId, Name = studentName, IsStudent = true, EntityId = studentNo, ImageUrl = url }); if (!faceWithReuslt) { throw new Exception("上传失败"); } return url; } public async Task BatchUploadPhoto(IFormFile zipFile) { // 解压缩 zip 文件 var uploadPath = Path.Combine("Upload", "Student"); Directory.CreateDirectory(uploadPath); var zipFilePath = Path.Combine(uploadPath, zipFile.FileName); using (var fileStream = new FileStream(zipFilePath, FileMode.Create)) { await zipFile.CopyToAsync(fileStream); } // 解压后的目录路径 var extractPath = Path.Combine(uploadPath, Path.GetFileNameWithoutExtension(zipFile.FileName)); // 检查并删除已存在的目录 if (Directory.Exists(extractPath)) { Directory.Delete(extractPath, true); } ZipFile.ExtractToDirectory(zipFilePath, extractPath); // 递归获取所有文件 var files = Directory.GetFiles(extractPath, "*.*", SearchOption.AllDirectories) .Select(filePath => new FileInfo(filePath)) .ToList(); // 获取学号列表 var studentNos = files.Select(x => Path.GetFileNameWithoutExtension(x.Name)).ToList(); var students = await _studentRepository.FindAsync(x => studentNos.Contains(x.StudentNo) && x.SchoolCode.Equals(UserContext.Current.TenantId)); var classList = await _classRepository.FindAsync(x => x.SchoolCode.Equals(UserContext.Current.TenantId)); var studentEntitys = new List(); foreach (var fileInfo in files) { try { using (var stream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read)) { var formFile = new FormFile(stream, 0, stream.Length, fileInfo.FullName, fileInfo.Name); var stuNo = Path.GetFileNameWithoutExtension(fileInfo.Name); var student = students.FirstOrDefault(x => x.StudentNo.Equals(stuNo, StringComparison.OrdinalIgnoreCase)); if (student != null) { var cla = classList.FirstOrDefault(x => x.Id == student.ClassId); var url = await UploadPhoto(formFile, cla == null ? 0 : cla.GradeId, student.ClassId, stuNo, student.StudentName); student.Photo = url; studentEntitys.Add(student); } else { //throw new Exception($"未找到学号:{stuNo}"); continue; } } } catch (Exception) { continue; } } _studentRepository.UpdateRange(studentEntitys); await _studentRepository.SaveChangesAsync(); // 删除临时文件和目录 Directory.Delete(extractPath, true); File.Delete(zipFilePath); } /// /// 学生数据统计 /// /// /// /// public async Task StudentWholeDataStats(StudentDataStatsParam paramDto) { var res = new StudentDataStatsModel(); var tenantId = UserContext.Current.TenantId; if (string.IsNullOrEmpty(paramDto.StudentNo)) { paramDto.StudentNo = _studentRepository.DbContext.Set().First().StudentNo; } var student = await (from s in _studentRepository.DbContext.Set() join c in _studentRepository.DbContext.Set() on s.ClassId equals c.Id where s.StudentNo == paramDto.StudentNo && s.SchoolCode.Equals(UserContext.Current.TenantId) select new StudentPageListModel() { StudentName = s.StudentName, GradeId = c.GradeId, ClassId = s.ClassId, ClassName = c.ClassName, GradeName = c.GradeName, Photo = s.Photo, Age = s.Age, Sex = s.Sex }).FirstOrDefaultAsync(); if (student == null) throw new ArgumentNullException("未找到学生数据"); res.StudentName = student.StudentName; res.ClassName = student.ClassName; res.GradeName = student.GradeName; res.Photo = student.Photo; res.Sex = student.Sex.Description(); res.Age = student.Age; var heartRateData = await (from t in _studentRepository.DbContext.Set() join s in _studentRepository.DbContext.Set() on t.Id equals s.TrainingDataId where t.SchoolCode.Equals(UserContext.Current.TenantId) && t.ItemType == (int)TrainingItemType.HeartRate && s.StudentNo == paramDto.StudentNo select new { t.Id, s.HighHeartRate }) .OrderByDescending(x => x.Id) .Take(5) .ToListAsync(); var keys = new[] { "一", "二", "三", "四", "五" }; if (heartRateData.Count == 0) { res.HeartRateData = keys.ToDictionary(key => key, key => 0); } else { res.HeartRateData = heartRateData .Select((x, index) => new { Key = keys[index], Value = x.HighHeartRate ?? 0 }) .ToDictionary(x => x.Key, x => x.Value); } // 从缓存中获取数据 var sportsTestResults = await _cacheQueryService.GeSportsTestDataCacheAsync(x => x.SchoolCode.Equals(tenantId) && x.StudentNo == paramDto.StudentNo, paramDto.Mode); var categoryList = await (from s in _sportsTestResultRepository.DbContext.Set() join n in _sportsTestResultRepository.DbContext.Set() on s.CategoryValue equals n.CategoryValue where s.GradeId == student.GradeId select new { n.CategoryValue, n.CategoryName }).ToListAsync(); foreach (var category in categoryList) { var categoryResults = sportsTestResults.Where(x => x.CategoryValue == category.CategoryValue).ToList(); var maxValue = categoryResults.Any() ? categoryResults.Max(x => x.Score + x.AdditionalScore) : 0f; var lastValue = categoryResults.OrderByDescending(x => x.ScoreTime).Select(x => x.Score + x.AdditionalScore).FirstOrDefault(); var resultContrast = new ResultContrast { MaxValue = maxValue, LastValue = lastValue }; res.TestResultCompare.AxisX.Add(category.CategoryName); res.TestResultCompare.AxisY.Add(resultContrast); } return res; } /// /// 各项目平均成绩 /// /// /// /// public async Task> TestResultAvg(StudentCategoryParam paramDto) { var sportsTestResults = await _cacheQueryService.GeSportsTestDataCacheAsync(x => x.StudentNo == paramDto.StudentNo && x.SchoolCode.Equals(UserContext.Current.TenantId) && x.CategoryValue == paramDto.CategoryValue , paramDto.Mode); var now = DateTime.Now; var currentQuarter = (now.Month - 1) / 3 + 1; var currentYear = now.Year; // 处理跨年情况 var quarters = new[] { new { Label = "本季度", Quarter = currentQuarter, Year = currentYear }, new { Label = "上季度", Quarter = currentQuarter - 1 == 0 ? 4 : currentQuarter - 1, Year = currentQuarter == 1 ? currentYear - 1 : currentYear }, new { Label = "前季度", Quarter = currentQuarter - 2 == 0 ? 4 : currentQuarter - 2 == -1 ? 3 : currentQuarter - 2, Year = currentQuarter <= 2 ? currentYear - 1 : currentYear } }; return quarters.ToDictionary( q => q.Label, q => { var filteredResults = sportsTestResults .Where(x => x.ScoreTime >= Tool.GetQuarterStartDate(q.Quarter, q.Year) && x.ScoreTime <= Tool.GetQuarterEndDate(q.Quarter, q.Year)) .Select(x => x.Score + x.AdditionalScore) .ToList(); return filteredResults.Any() ? (float)Math.Round(filteredResults.Average(), 2) : 0; } ); } /// /// 各项目成绩对比 /// /// /// /// public async Task> TestResultContrast(StudentCategoryParam paramDto) { // 获取指定班级和类别的测试结果 var sportsTestResults = await _cacheQueryService.GeSportsTestDataCacheAsync(x => x.ClassId == paramDto.ClassId && x.SchoolCode.Equals(UserContext.Current.TenantId) && x.CategoryValue == paramDto.CategoryValue , paramDto.Mode); // 提取班级所有成绩 var filteredResults = sportsTestResults .Select(x => x.Score + x.AdditionalScore) .ToList(); // 提取个人成绩 var personalScore = sportsTestResults .Where(x => x.StudentNo == paramDto.StudentNo) .Select(x => x.Score + x.AdditionalScore) .FirstOrDefault(); var quarters = new[] { new { Label = "个人成绩", Value = personalScore }, new { Label = "班级平均分", Value = filteredResults.Any() ? (float)Math.Round(filteredResults.Average(),2) : 0f }, new { Label = "班级中位数", Value = filteredResults.Any() ? (float)Math.Round(filteredResults.Median(),2) : 0f }, new { Label = "班级最高分", Value = filteredResults.Any() ? (float)Math.Round(filteredResults.Max(),2) : 0f } }; return quarters.ToDictionary(q => q.Label, q => q.Value); } /// /// 成绩趋势 /// /// /// /// public async Task ResultTrends(StudentResultTrendsParam paramDto) { var sportsTestResults = await _cacheQueryService.GeSportsTestDataCacheAsync(x => x.StudentNo == paramDto.StudentNo && x.SchoolCode.Equals(UserContext.Current.TenantId) && x.CategoryValue == paramDto.CategoryValue , paramDto.Mode); var result = new VariousSportsProportion(); DateTime currentDate = DateTime.Now; switch (paramDto.CycleTime) { case CycleTimeEnum.InThePastWeek: // 近一周:按天展示 var pastWeekResults = sportsTestResults .Where(x => x.ScoreTime >= currentDate.AddDays(-7)) .GroupBy(x => x.ScoreTime.Date); foreach (var group in pastWeekResults) { result.AxisX.Add(group.Key.ToString("yyyy-MM-dd")); var score = (group.Sum(x => x.Score) + group.Sum(x => x.AdditionalScore)) / (group.Select(x => x.StudentNo).Distinct().Count()); result.AxisY.Add((float)Math.Round(score)); } break; case CycleTimeEnum.InThePastTwoWeeks: // 近两周:按天展示 var pastTwoWeeksResults = sportsTestResults .Where(x => x.ScoreTime >= currentDate.AddDays(-14)) .GroupBy(x => x.ScoreTime.Date); foreach (var group in pastTwoWeeksResults) { result.AxisX.Add(group.Key.ToString("yyyy-MM-dd")); var score = (group.Sum(x => x.Score) + group.Sum(x => x.AdditionalScore)) / (group.Select(x => x.StudentNo).Distinct().Count()); result.AxisY.Add((float)Math.Round(score)); } break; case CycleTimeEnum.InThePastMonth: // 近一月:每5天一个阶段展示 var pastMonthResults = sportsTestResults .Where(x => x.ScoreTime >= currentDate.AddDays(-30)) .GroupBy(x => (currentDate - x.ScoreTime).Days / 5); foreach (var group in pastMonthResults) { var minDate = group.Min(r => r.ScoreTime).ToString("yyyy-MM-dd"); var maxDate = group.Max(r => r.ScoreTime).ToString("yyyy-MM-dd"); result.AxisX.Add($"{minDate} - {maxDate}"); var score = (group.Sum(x => x.Score) + group.Sum(x => x.AdditionalScore)) / (group.Select(x => x.StudentNo).Distinct().Count()); result.AxisY.Add((float)Math.Round(score)); } break; case CycleTimeEnum.InThePastYear: // 近一年:按月展示 var pastYearResults = sportsTestResults .Where(x => x.ScoreTime >= currentDate.AddYears(-1)) .GroupBy(x => new { x.ScoreTime.Year, x.ScoreTime.Month }); foreach (var group in pastYearResults) { result.AxisX.Add($"{group.Key.Year}-{group.Key.Month:D2}"); var score = (group.Sum(x => x.Score) + group.Sum(x => x.AdditionalScore)) / (group.Select(x => x.StudentNo).Distinct().Count()); result.AxisY.Add((float)Math.Round(score)); } break; default: // 默认处理,如果没有匹配的情况 break; } return result; } /// /// 训练记录 /// /// /// /// public async Task> TrainingRecords(StudentTrainingRecordsParam paramDto) { var res = new PageDataDto(); var tenantId = UserContext.Current.TenantId; if (string.IsNullOrEmpty(paramDto.StudentNo)) { paramDto.StudentNo = _studentRepository.DbContext.Set().First(s => s.SchoolCode == tenantId).StudentNo; } // 查询 I_TrainingData 表 var iotQuery = from student in _studentRepository.DbContext.Set() join trainingData in _studentRepository.DbContext.Set() on student.TrainingDataId equals trainingData.Id where trainingData.IsDisplay && trainingData.SchoolCode == tenantId select new StudentTrainingRecordsModel { StudentNo = student.StudentNo, // 训练模式 TrainType = trainingData.ModelType ?? 0, //项目模式 ModelName = trainingData.ModelName, CategoryValue = (TrainingItemType)trainingData.ItemType, // 成绩 Result = (float?)trainingData.ClassNumAvg, ResultLevel = trainingData.ClassScore, // 训练时间 ScoreTime = trainingData.InsertTime, }; // 查询 Ai_SportsTestData 表 var aiQuery = from trainingData in _studentRepository.DbContext.Set() where trainingData.IsDisplay && trainingData.SchoolCode == tenantId select new StudentTrainingRecordsModel { StudentNo = trainingData.StudentNo, TrainType = (int)trainingData.ModeType, ModelName = trainingData.ModelName, CategoryValue = (TrainingItemType)trainingData.CategoryValue, Result = (float?)trainingData.Value, ResultLevel = (int?)trainingData.RankEnum, ScoreTime = trainingData.ScoreTime }; // 合并两个查询结果 var combinedQuery = iotQuery.Concat(aiQuery); // 根据学号过滤 if (!string.IsNullOrEmpty(paramDto.StudentNo)) { combinedQuery = combinedQuery.Where(x => x.StudentNo == paramDto.StudentNo); } // 根据开始时间过滤 if (paramDto.StartTime.HasValue) { combinedQuery = combinedQuery.Where(x => x.ScoreTime >= paramDto.StartTime); } // 根据结束时间过滤 if (paramDto.EndTime.HasValue) { combinedQuery = combinedQuery.Where(x => x.ScoreTime <= paramDto.EndTime); } // 根据项目类型过滤 if (paramDto.CategoryValue > 0) { combinedQuery = combinedQuery.Where(x => x.CategoryValue == (TrainingItemType)paramDto.CategoryValue); } // 根据成绩等级过滤 if (paramDto.Rank > 0) { combinedQuery = combinedQuery.Where(x => x.ResultLevel == (int)paramDto.Rank); } // 获取总数 res.Total = await combinedQuery.CountAsync(); // 分页和排序 var list = await combinedQuery .OrderBy(c => c.ScoreTime) // 按训练时间排序 .Skip((paramDto.PageIndex - 1) * paramDto.PageSize) .Take(paramDto.PageSize) .ToListAsync(); foreach (var item in list) { // 项目 item.CategoryName = item.CategoryValue.GetDisplayName(); var grade = ((AchievementRank)(item.ResultLevel.HasValue && item.ResultLevel.Value > 0 ? item.ResultLevel.Value : 4)); var gradeName = grade.GetDescription(); // 等级 item.Grade = gradeName; var trainName = ((Ai_ModeEnum)(item.TrainType)).GetDescription(); // 训练模式 item.TrainName = trainName; } res.Datas = list; return res; } /// /// 体测记录 /// /// /// /// public async Task> TestRecords(StudentTrainingRecordsParam paramDto) { var res = new PageDataDto(); var tenantId = UserContext.Current.TenantId; if (string.IsNullOrEmpty(paramDto.StudentNo)) { paramDto.StudentNo = this._studentRepository.DbContext.Set().First().StudentNo; } // 初始化基础查询条件 Expression> query = (x => x.SchoolCode == tenantId); if (!string.IsNullOrEmpty(paramDto.StudentNo)) { query = query.And(x => x.StudentNo == paramDto.StudentNo); } if (paramDto.StartTime.HasValue) { query = query.And(x => x.ScoreTime >= paramDto.StartTime); } if (paramDto.EndTime.HasValue) { query = query.And(x => x.ScoreTime <= paramDto.StartTime); } if (paramDto.CategoryValue > 0) { query = query.And(x => x.CategoryValue == paramDto.CategoryValue); } if (paramDto.Rank > 0) { query = query.And(x => x.RankEnum == paramDto.Rank); } var sportsTestResults = await _cacheQueryService.GeSportsTestDataCacheAsync(query.Compile(), paramDto.TrainType); // 获取总数 res.Total = sportsTestResults.Count(); // 分页和排序 res.Datas = sportsTestResults .OrderBy(c => c.ScoreTime) // 按训练时间排序 .Select(c => new StudentTestRecordsModel { TrainName = c.ModeType.GetDescription(), CategoryValue = (SportsTestItemType)c.CategoryValue, CategoryName = ((SportsTestItemType)c.CategoryValue).GetDisplayName(), Result = c.Value, Score = (c.Score + c.AdditionalScore), Rank = c.Rank, ScoreTime = c.ScoreTime }) .Skip((paramDto.PageIndex - 1) * paramDto.PageSize) .Take(paramDto.PageSize) .ToList(); return res; } // 构建课堂模式查询 private IQueryable BuildClassRoomModeQuery(string tenantId, string studentNo, StudentTrainingRecordsParam paramDto) { var query = from td in _studentRepository.DbContext.Set() join tr in _studentRepository.DbContext.Set() on td.Id equals tr.TrainingDataId into ranking from r in ranking.DefaultIfEmpty() where td.SchoolCode == tenantId && r.StudentNo == studentNo select new StudentTrainingRecordsModel { TrainName = "课堂模式", ModelName = td.ModelName, CategoryValue = (TrainingItemType)td.ItemType, CategoryName = ((TrainingItemType)td.ItemType).GetDescription(), Result = r.JumpValue, ResultLevel = r.ResultLevel, ScoreTime = td.InsertTime }; return ApplyFilters(query, paramDto); } // 构建自由模式查询 private IQueryable BuildFreeModeQuery(string tenantId, string studentNo, StudentTrainingRecordsParam paramDto) { var query = from a in _studentRepository.DbContext.Set() join n in _studentRepository.DbContext.Set() on a.StartTime equals n.ScoreTime where a.SchoolCode == tenantId && a.StudentNo == studentNo select new StudentTrainingRecordsModel { TrainName = "自由模式", ScoreTime = a.StartTime, CategoryValue = a.CategoryValue, CategoryName = ((TrainingItemType)Convert.ToInt32(a.CategoryValue)).GetDescription(), ModelName = "/", Grade = n.Rank }; return ApplyFilters(query, paramDto); } // 通用过滤逻辑 private IQueryable ApplyFilters(IQueryable query, StudentTrainingRecordsParam paramDto) { return query .WhereIF(paramDto.StartTime.HasValue, x => x.ScoreTime >= paramDto.StartTime) .WhereIF(paramDto.EndTime.HasValue, x => x.ScoreTime <= paramDto.EndTime) .WhereIF(paramDto.CategoryValue > 0, x => x.CategoryValue == (TrainingItemType)paramDto.CategoryValue) .WhereIF(paramDto.Rank > 0, x => x.ResultLevel == (int)paramDto.Rank); } // 构建体测课堂模式查询 private IQueryable BuildTestRoomModeQuery(string tenantId, string studentNo, StudentTrainingRecordsParam paramDto) { var query = from a in _studentRepository.DbContext.Set() join r in _studentRepository.DbContext.Set() on a.ScoreTime equals r.ScoreTime where a.SchoolCode == tenantId && r.StudentNo == studentNo select new StudentTestRecordsModel { TrainName = "课堂模式", CategoryValue = (SportsTestItemType)r.CategoryValue, CategoryName = ((SportsTestItemType)r.CategoryValue).GetDescription(), Result = r.Value, Rank = r.Rank, Score = r.Score, ScoreTime = r.ScoreTime }; return TestApplyFilters(query, paramDto); } private IQueryable BuildTestFreeModeQuery(string tenantId, string studentNo, StudentTrainingRecordsParam paramDto) { var query = from a in _studentRepository.DbContext.Set() join n in _studentRepository.DbContext.Set() on a.StartTime equals n.ScoreTime where a.SchoolCode == tenantId && a.StudentNo == studentNo select new StudentTestRecordsModel { TrainName = "自由模式", ScoreTime = a.StartTime, CategoryValue = (SportsTestItemType)a.CategoryValue, CategoryName = ((TrainingItemType)Convert.ToInt32(a.CategoryValue)).GetDescription(), Rank = n.Rank, Score = n.Score, Result = n.Value }; return TestApplyFilters(query, paramDto); } // 通用过滤逻辑 private IQueryable TestApplyFilters(IQueryable query, StudentTrainingRecordsParam paramDto) { return query .WhereIF(paramDto.StartTime.HasValue, x => x.ScoreTime >= paramDto.StartTime) .WhereIF(paramDto.EndTime.HasValue, x => x.ScoreTime <= paramDto.EndTime) .WhereIF(paramDto.CategoryValue > 0, x => x.CategoryValue == (SportsTestItemType)paramDto.CategoryValue) .WhereIF(paramDto.Rank > 0, x => x.Rank == paramDto.RankStr); } /// /// 学生名单列表 /// /// /// /// public async Task> GetStudentNamePageList(StudentNamePageListParam paramDto) { // 查询学生信息 var students = await _studentRepository.FindAsIQueryable(x => x.ClassId == paramDto.ClassId) .Select(x => new StudentNamePageListModel() { StudentNo = x.StudentNo, StudentName = x.StudentName, Photo = x.Photo }) .ToListAsync(); // 查询两个数据源的成绩并合并 var combinedResults = await _sportsTestResultRepository.FindAsIQueryable(x => x.ClassId == paramDto.ClassId && x.SchoolCode.Equals(UserContext.Current.TenantId) ) .Select(x => new { x.StudentNo, x.Score }) .Concat( _sportsTestResultRepository.DbContext.Set().Where(x => x.ClassId == paramDto.ClassId && x.SchoolCode.Equals(UserContext.Current.TenantId) ) .Select(x => new { x.StudentNo, x.Score }) ) .ToListAsync(); // 使用字典来存储平均成绩,便于后续查找 var averageScoresDict = combinedResults .GroupBy(x => x.StudentNo) .ToDictionary(g => g.Key, g => g.Average(x => x.Score)); // 遍历学生信息,为每个学生分配 Rank students.ForEach(student => { if (averageScoresDict.TryGetValue(student.StudentNo, out var averageScore)) { student.Rank = averageScore.GetRank(); // 有平均成绩的情况 } else { student.Rank = 0.GetRank(); // 没有成绩时默认处理 } }); return students; } /// /// 更换班级 /// /// /// /// public async Task ReplaceClasses(List studentNoList, int classId) { var students = await _studentRepository.FindAsync(x => studentNoList.Contains(x.StudentNo)); if (students == null || students.Count == 0) throw new ArgumentNullException("未找到更新的数据"); foreach (var student in students) { student.ClassId = classId; student.Modifier = UserContext.Current.UserId; student.ModifyDate = DateTime.Now; } _studentRepository.UpdateRange(students); await _studentRepository.SaveChangesAsync(); } } }