using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using OfficeOpenXml; using OfficeOpenXml.Style; using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Threading.Tasks; using VOL.Business.IServices.ShangWaiMao; using VOL.Core.Extensions.AutofacManager; using VOL.Core.ManageUser; using VOL.Entity.DomainModels; using VOL.Entity.Enum; using VOL.Model; using VOL.System.IRepositories; namespace VOL.Business.Services.ShangWaiMao { /// /// 上外贸导出服务 /// public class ShangWaiMaoService : IShangWaiMaoService, IDependency { private const int TrendStartColumn = 8; private readonly IS_StudentRepository _studentRepository; [ActivatorUtilitiesConstructor] public ShangWaiMaoService(IS_StudentRepository studentRepository) { _studentRepository = studentRepository; } public async Task ExportHeartRateDataAsync(ShangWaiMaoHeartRateExportParam paramDto) { paramDto ??= new ShangWaiMaoHeartRateExportParam(); if (paramDto.StartTime.HasValue && paramDto.EndTime.HasValue && paramDto.StartTime > paramDto.EndTime) { throw new Exception("结束时间不能早于开始时间"); } var schoolCode = UserContext.Current.TenantId; if (string.IsNullOrWhiteSpace(schoolCode)) { throw new Exception("未获取到当前学校编码"); } var dbContext = _studentRepository.DbContext; var query = from heartRate in dbContext.Set().AsNoTracking() join classRoom in dbContext.Set().AsNoTracking() on heartRate.ClassRoomRecordId equals classRoom.Id into heartRateClassRoom from classRoom in heartRateClassRoom.DefaultIfEmpty() where heartRate.SchoolCode == schoolCode select new HeartRateExportRawData { ClassRoomRecordId = heartRate.ClassRoomRecordId, StudentNo = heartRate.StudentNo, StudentName = heartRate.StudentName, Sex = heartRate.Sex, GradeName = !string.IsNullOrWhiteSpace(classRoom.GradeName) ? classRoom.GradeName : heartRate.GradeName, ClassName = classRoom != null && !string.IsNullOrWhiteSpace(classRoom.ClassName) ? classRoom.ClassName : heartRate.ClassName, ScoreTime = heartRate.ScoreTime, MotionDuration = heartRate.MotionDuration, Value = heartRate.Value, Strength = heartRate.Strength, Consumption = heartRate.Consumption, ClassRoomName = classRoom != null ? classRoom.Name : string.Empty, TeacherName = classRoom != null ? classRoom.TeacherName : string.Empty, CourseStartTime = classRoom != null ? classRoom.StartTime : null, CourseEndTime = classRoom != null ? classRoom.EndTime : null }; if (paramDto.StartTime.HasValue) { query = query.Where(x => x.ScoreTime >= paramDto.StartTime.Value); } if (paramDto.EndTime.HasValue) { query = query.Where(x => x.ScoreTime <= paramDto.EndTime.Value); } var rawDataList = await query .OrderBy(x => x.CourseStartTime ?? x.ScoreTime) .ThenBy(x => x.ClassRoomRecordId) .ThenBy(x => x.StudentNo) .ThenBy(x => x.ScoreTime) .ToListAsync(); return BuildExcel(rawDataList); } private static byte[] BuildExcel(List rawDataList) { using var package = new ExcelPackage(); var summarySheet = package.Workbook.Worksheets.Add("总表"); var trendSheet = package.Workbook.Worksheets.Add("学生课程平均心率"); InitializeSummarySheet(summarySheet); var groupedData = rawDataList .GroupBy(x => new { x.ClassRoomRecordId, x.StudentNo, x.StudentName, x.Sex, x.GradeName, x.ClassName, x.ClassRoomName, x.CourseStartTime, x.CourseEndTime, x.TeacherName }) .OrderBy(x => x.Key.CourseStartTime ?? x.Min(m => m.ScoreTime)) .ThenBy(x => x.Key.ClassRoomName) .ThenBy(x => x.Key.StudentNo) .ToList(); var maxTrendMinuteNumber = groupedData .Select(group => { var baseTime = GetMinuteBaseTime(group.Key.CourseStartTime, group.Min(x => x.ScoreTime)); var minuteData = BuildMinuteData(group, baseTime); return minuteData.Count == 0 ? 0 : minuteData.Keys.Max(); }) .DefaultIfEmpty(0) .Max(); InitializeTrendSheet(trendSheet, maxTrendMinuteNumber); var summaryRowIndex = 2; var trendRowIndex = 2; foreach (var group in groupedData) { var baseTime = GetMinuteBaseTime(group.Key.CourseStartTime, group.Min(x => x.ScoreTime)); var minuteData = BuildMinuteData(group, baseTime); var duration = minuteData.Count == 0 ? 0 : minuteData.Keys.Max(); var avgHeartRate = (int)group.Average(x => x.Value); var avgStrength = (int)group.Average(x => x.Strength); var density = group.Any() ? Math.Round(group.Count(x => x.Strength > 50) * 100D / group.Count(), 0) : 0D; var totalConsumption = Math.Abs((int)group.Average(x => x.Consumption ?? 0)); summarySheet.Cells[summaryRowIndex, 1].Value = group.Key.StudentName; summarySheet.Cells[summaryRowIndex, 2].Value = group.Key.StudentNo; summarySheet.Cells[summaryRowIndex, 3].Value = GetSexText(group.Key.Sex); summarySheet.Cells[summaryRowIndex, 4].Value = group.Key.GradeName; summarySheet.Cells[summaryRowIndex, 5].Value = group.Key.ClassName; summarySheet.Cells[summaryRowIndex, 6].Value = group.Key.ClassRoomName; summarySheet.Cells[summaryRowIndex, 7].Value = FormatCourseTimeRange( group.Key.CourseStartTime, group.Key.CourseEndTime, group.Min(x => x.ScoreTime), group.Max(x => x.ScoreTime)); summarySheet.Cells[summaryRowIndex, 8].Value = group.Key.TeacherName; summarySheet.Cells[summaryRowIndex, 9].Value = duration; summarySheet.Cells[summaryRowIndex, 10].Value = avgHeartRate; summarySheet.Cells[summaryRowIndex, 11].Value = avgStrength; summarySheet.Cells[summaryRowIndex, 12].Value = density; summarySheet.Cells[summaryRowIndex, 13].Value = totalConsumption; summaryRowIndex++; trendSheet.Cells[trendRowIndex, 1].Value = group.Key.StudentName; trendSheet.Cells[trendRowIndex, 2].Value = group.Key.StudentNo; trendSheet.Cells[trendRowIndex, 3].Value = GetSexText(group.Key.Sex); trendSheet.Cells[trendRowIndex, 4].Value = group.Key.ClassName; trendSheet.Cells[trendRowIndex, 5].Value = group.Key.ClassRoomName; trendSheet.Cells[trendRowIndex, 6].Value = FormatCourseTimeRange( group.Key.CourseStartTime, group.Key.CourseEndTime, group.Min(x => x.ScoreTime), group.Max(x => x.ScoreTime)); trendSheet.Cells[trendRowIndex, 7].Value = group.Key.TeacherName; foreach (var item in minuteData) { trendSheet.Cells[trendRowIndex, TrendStartColumn + item.Key - 1].Value = item.Value; } trendRowIndex++; } FormatDataArea(summarySheet, summaryRowIndex - 1, 13); FormatDataArea( trendSheet, trendRowIndex - 1, maxTrendMinuteNumber > 0 ? TrendStartColumn + maxTrendMinuteNumber - 1 : TrendStartColumn - 1); return package.GetAsByteArray(); } private static void InitializeSummarySheet(ExcelWorksheet worksheet) { string[] headers = { "姓名", "学号", "性别", "年级", "班级名称", "课程名称", "课程日期", "教师姓名", "课程总时长", "平均心率", "运动强度", "运动密度", "消耗/千卡" }; double[] widths = { 14, 16, 10, 14, 18, 24, 36, 14, 14, 12, 12, 12, 12 }; InitializeHeader(worksheet, headers, widths); } private static Dictionary BuildMinuteData(IEnumerable data, DateTime baseTime) { return data .Select(x => new { MinuteNumber = (int)(x.ScoreTime - baseTime).TotalMinutes + 1, x.ScoreTime, x.Value }) .Where(x => x.MinuteNumber > 0) .GroupBy(x => x.MinuteNumber) .ToDictionary( x => x.Key, x => x.OrderBy(m => m.ScoreTime).First().Value); } private static DateTime GetMinuteBaseTime(DateTime? courseStartTime, DateTime firstScoreTime) { if (!courseStartTime.HasValue) { return firstScoreTime; } return courseStartTime.Value <= firstScoreTime ? courseStartTime.Value : firstScoreTime; } private static void InitializeTrendSheet(ExcelWorksheet worksheet, int maxTrendMinuteNumber) { var headers = new List { "姓名", "学号", "性别", "班级名称", "课程名称", "课程日期", "教师姓名" }; headers.AddRange(Enumerable.Range(1, maxTrendMinuteNumber).Select(x => $"{x}分钟")); var widths = new List { 14, 16, 10, 18, 24, 36, 14 }; widths.AddRange(Enumerable.Repeat(10D, maxTrendMinuteNumber)); InitializeHeader(worksheet, headers.ToArray(), widths.ToArray()); } private static string FormatCourseTimeRange(DateTime? courseStartTime, DateTime? courseEndTime, DateTime defaultStartTime, DateTime defaultEndTime) { var startTime = courseStartTime ?? defaultStartTime; var endTime = courseEndTime ?? defaultEndTime; return $"{startTime:yyyy-MM-dd HH:mm:ss} - {endTime:yyyy-MM-dd HH:mm:ss}"; } private static void InitializeHeader(ExcelWorksheet worksheet, string[] headers, double[] widths) { for (int i = 0; i < headers.Length; i++) { worksheet.Cells[1, i + 1].Value = headers[i]; worksheet.Column(i + 1).Width = widths[i]; } using var range = worksheet.Cells[1, 1, 1, headers.Length]; range.Style.Font.Bold = true; range.Style.Fill.PatternType = ExcelFillStyle.Solid; range.Style.Fill.BackgroundColor.SetColor(Color.FromArgb(217, 217, 217)); range.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center; range.Style.VerticalAlignment = ExcelVerticalAlignment.Center; range.Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191)); worksheet.Row(1).Height = 22; worksheet.View.FreezePanes(2, 1); } private static void FormatDataArea(ExcelWorksheet worksheet, int lastRow, int lastColumn) { if (lastRow < 2) { return; } using var range = worksheet.Cells[1, 1, lastRow, lastColumn]; range.Style.Border.Top.Style = ExcelBorderStyle.Thin; range.Style.Border.Bottom.Style = ExcelBorderStyle.Thin; range.Style.Border.Left.Style = ExcelBorderStyle.Thin; range.Style.Border.Right.Style = ExcelBorderStyle.Thin; range.Style.VerticalAlignment = ExcelVerticalAlignment.Center; using var dataRange = worksheet.Cells[2, 1, lastRow, lastColumn]; dataRange.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center; } private static string GetSexText(SexType sex) { return sex switch { SexType.Male => "男", SexType.Female => "女", _ => string.Empty }; } private class HeartRateExportRawData { public int ClassRoomRecordId { get; set; } public string? StudentNo { get; set; } public string? StudentName { get; set; } public SexType Sex { get; set; } public string? GradeName { get; set; } public string? ClassName { get; set; } public DateTime ScoreTime { get; set; } public int MotionDuration { get; set; } public double Value { get; set; } public double Strength { get; set; } public int? Consumption { get; set; } public string? ClassRoomName { get; set; } public string? TeacherName { get; set; } public DateTime? CourseStartTime { get; set; } public DateTime? CourseEndTime { get; set; } } } }