328 lines
14 KiB
C#
328 lines
14 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.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
|
|
{
|
|
/// <summary>
|
|
/// 上外贸导出服务
|
|
/// </summary>
|
|
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<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; }
|
|
}
|
|
}
|
|
}
|