2026-06-01 10:36:40 +08:00

347 lines
15 KiB
C#

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.Business.IRepositories.ShangWaiMao;
using VOL.Core.Extensions.AutofacManager;
using VOL.Core.ManageUser;
using VOL.Entity.DomainModels;
using VOL.Entity.Enum;
using VOL.Model;
using VOL.Model.ShangWaiMao.Request;
using VOL.Model.ShangWaiMao.Response;
using VOL.System.IRepositories;
namespace VOL.Business.Services.ShangWaiMao
{
/// <summary>
/// 上外贸导出服务
/// </summary>
public class ShangWaiMaoService : IShangWaiMaoService, IDependency
{
private const int TrendStartColumn = 8;
private readonly IS_StudentRepository _studentRepository;
private readonly ITempTccjRepository _tempTccjRepository;
[ActivatorUtilitiesConstructor]
public ShangWaiMaoService(IS_StudentRepository studentRepository, ITempTccjRepository tempTccjRepository)
{
_studentRepository = studentRepository;
_tempTccjRepository = tempTccjRepository;
}
public Task<ShangWaiMaoTempTccjFilterOptions> GetTempTccjFilterOptionsAsync()
{
return _tempTccjRepository.GetFilterOptionsAsync();
}
public Task<PageDataDto<ShangWaiMaoTempTccjListItem>> GetTempTccjPageListAsync(ShangWaiMaoTempTccjPageParam paramDto)
{
return _tempTccjRepository.GetPageListAsync(paramDto);
}
public Task<ShangWaiMaoTempTccjDetailDto> GetTempTccjDetailAsync(ShangWaiMaoTempTccjDetailParam paramDto)
{
return _tempTccjRepository.GetDetailAsync(paramDto);
}
public async Task<byte[]> 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<Ai_HeartRateData>().AsNoTracking()
join classRoom in dbContext.Set<Ai_ClassRoomRecord>().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<HeartRateExportRawData> 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<int, double> BuildMinuteData(IEnumerable<HeartRateExportRawData> 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<string> { "姓名", "学号", "性别", "班级名称", "课程名称", "课程日期", "教师姓名" };
headers.AddRange(Enumerable.Range(1, maxTrendMinuteNumber).Select(x => $"{x}分钟"));
var widths = new List<double> { 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; }
}
}
}