运动馆

This commit is contained in:
tanglong 2026-04-21 16:01:53 +08:00
parent feca273bcc
commit 41672a56aa
12 changed files with 394 additions and 98 deletions

View File

@ -63,9 +63,9 @@ namespace YD_WeChatApplet.Api.Controllers
/// </summary>
/// <returns></returns>
[HttpGet("StudentReport")]
public async Task<StudentReportDto> StudentReport()
public async Task<StudentReportDto> StudentReport(string studentNo)
{
var res = await _patriarchService.StudentReport();
var res = await _patriarchService.StudentReport(studentNo);
return res;
}

View File

@ -1,9 +1,10 @@
using System.ComponentModel.Design;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.Design;
using YD_WeChatApplet.Api.Services.Impl;
using YD_WeChatApplet.Commons.Dto;
using YD_WeChatApplet.Commons.Dto.ClassRoomRecord;
using YD_WeChatApplet.Commons.Dto.HomeWork;
using YD_WeChatApplet.Commons.Dto.Patriarch;
using YD_WeChatApplet.Commons.Dto.Resource;
using YD_WeChatApplet.Commons.Dto.Teacher;
using YD_WeChatApplet.Services;
@ -122,13 +123,23 @@ namespace YD_WeChatApplet.Api.Controllers
/// <summary>
/// 学员课堂报告
/// </summary>
/// <param name="classRoomId"></param>
/// <param name="studentNo"></param>
/// <param name="dto"></param>
/// <returns></returns>
[HttpGet("StudentClassRoomReport")]
public async Task<StudentClassRoomReportDto> StudentClassRoomReport(int classRoomId, string studentNo)
public async Task<StudentClassRoomReportDto> StudentClassRoomReport([FromQuery] StudentClassRoomReportRequestDto dto)
{
var res = await _teacherService.StudentClassRoomReport(classRoomId, studentNo);
var res = await _teacherService.StudentClassRoomReport(dto);
return res;
}
/// <summary>
/// 班级报告
/// </summary>
/// <returns></returns>
[HttpGet("ClassReport")]
public async Task<ClassReportDto> ClassReport(int classId)
{
var res = await _teacherService.ClassReport(classId);
return res;
}
}

View File

@ -184,11 +184,14 @@ namespace YD_WeChatApplet.Api.Services.Impl
/// 学生报告
/// </summary>
/// <returns></returns>
public async Task<StudentReportDto> StudentReport()
public async Task<StudentReportDto> StudentReport(string studentNo)
{
var res = new StudentReportDto();
var studentNo = UserLoginContext.Current.UserNo;
if (string.IsNullOrWhiteSpace(studentNo))
return res;
//var studentNo = UserLoginContext.Current.UserNo;
var student = await _sportsContext.Student.Where(x => x.StudentNo == studentNo).FirstOrDefaultAsync();
@ -218,68 +221,71 @@ namespace YD_WeChatApplet.Api.Services.Impl
if (categorys == null)
return res;
var iotQuery = await _sportsContext.SportsTestValue.Where(x => x.IsDisplay && x.SchoolCode == student.SchoolCode && x.StudentNo == studentNo).Select(x => new SportsTestValueModel()
var sportsTestResults = await _sportsContext.SportsTestValue.Where(x => x.IsDisplay && x.SchoolCode == student.SchoolCode).Select(x => new SportsTestValueModel()
{
StudentNo = x.StudentNo,
Value = x.Value,
Score = x.Score,
AdditionalScore = x.AdditionalScore,
CategoryValue = x.CategoryValue
CategoryValue = x.CategoryValue,
ScoreTime = x.ScoreTime
}).ToListAsync();
var aiQuery = await _sportsContext.SportsTestData.Where(x => x.IsDisplay && x.SchoolCode == student.SchoolCode && x.StudentNo == studentNo).Select(x => new SportsTestValueModel()
{
StudentNo = x.StudentNo,
Value = (float)x.Value,
Score = x.Score,
AdditionalScore = x.AdditionalScore,
CategoryValue = x.CategoryValue
}).ToListAsync();
var sportsTestResults = iotQuery.Concat(aiQuery).ToList();
res.CategoryScoreList = new List<CategoryScoreDto>();
double totalScore = 0;
var currentYear = DateTime.Now.Year;
var currentMonth = DateTime.Now.Month;
foreach (var item in categorys)
{
var maxResultInMonth = sportsTestResults
.Where(x => x.CategoryValue == item.Id
&& x.ScoreTime?.Year == currentYear
&& x.ScoreTime?.Month == currentMonth)
.OrderByDescending(x => x.Value)
.FirstOrDefault();
// ✅ 当前学生数据
var studentResults = sportsTestResults
.Where(x => x.CategoryValue == item.Id && x.StudentNo == studentNo)
.OrderByDescending(x => x.ScoreTime) // 倒序:最新在前
.ToList();
var maxResult = maxResultInMonth ?? sportsTestResults
var lastResult = studentResults.FirstOrDefault();
var prevResult = studentResults.Skip(1).FirstOrDefault();
var maxScore = studentResults.Any()
? studentResults.Max(x => x.Score)
: 0;
var classAvgScore = studentResults.Any()
? studentResults.Average(x => x.Score)
: 0;
var schoolAvgScore = sportsTestResults
.Where(x => x.CategoryValue == item.Id)
.OrderByDescending(x => x.Value)
.FirstOrDefault();
.Select(x => x.Score)
.DefaultIfEmpty(0)
.Average();
if (item.CategoryName == "BMI")
res.BodyShape = maxResult?.Score ?? 0;
// 🚀 趋势图(关键:反转集合)
var trendList = studentResults
.OrderBy(x => x.ScoreTime)
.ToList();
if (item.CategoryName == "肺活量")
res.BodyFunction = maxResult?.Score ?? 0;
var trendChart = new ScoreTrendChartDataDto
{
AxisX = trendList.Select(x => x.ScoreTime.ToString("MM-dd HH:mm")).ToList(),
AxisY = trendList.Select(x => x.Score ?? 0f).ToList()
};
res.CategoryScoreList.Add(new CategoryScoreDto()
{
CategoryValue = item.CategoryValue,
Name = item.CategoryName,
Score = maxResult?.Score ?? 0,
PrevScore = prevResult?.Score ?? 0,
LastScore = lastResult?.Score ?? 0,
MaxScore = (float)maxScore,
ClassAvgScore = (float)classAvgScore,
SchoolAvgScore = (float)schoolAvgScore,
ScoreTrendChart = trendChart,
ImgUrl = $"{AppSettings.ALiYunOSS.CategoryImgUrl}{item.CategoryEnum}.png"
});
double currentScore = maxResult?.Score ?? 0;
double currentAdditionalScore = maxResult?.AdditionalScore ?? 0;
totalScore += ((currentScore + currentAdditionalScore) * item.Weight);
}
res.TotalScore = (float)totalScore;
var bodyFunction = res.CategoryScoreList.Where(x => x.Name != "BMI" && x.Name != "肺活量").ToList();
res.BodyFunction = bodyFunction.Average(x => x.Score);
return res;
}

View File

@ -428,6 +428,9 @@ namespace YD_WeChatApplet.Services
res.HighIntensity = heartRateDataList.Where(x => x.Strength > 50).Sum(x => x.MotionDuration);
double totalDuration = heartRateDataList.Sum(x => x.MotionDuration);
res.HighIntensityDurationRatio = totalDuration > 0 ? res.HighIntensity / totalDuration : 0;
var studentTrainingRecordList = classRoom.ClassroomStudentRecord.ToList();
List<StudentTrainingRecordDto> studentList = new List<StudentTrainingRecordDto>();
@ -469,36 +472,62 @@ namespace YD_WeChatApplet.Services
/// <summary>
/// 学员报告
/// </summary>
/// <param name="classRoomId"></param>
/// <param name="studentNo"></param>
/// <returns></returns>
public async Task<StudentClassRoomReportDto> StudentClassRoomReport(int classRoomId, string studentNo)
public async Task<StudentClassRoomReportDto> StudentClassRoomReport(StudentClassRoomReportRequestDto dto)
{
var res = new StudentClassRoomReportDto();
if (string.IsNullOrWhiteSpace(studentNo))
// 处理学号:若未传则从登录上下文获取
string studentNo = dto.StudentNo.ToString();
if (string.IsNullOrWhiteSpace(studentNo) || studentNo == "0")
studentNo = UserLoginContext.Current.UserNo;
var query = from hrd in _sportsContext.Ai_HeartRateData
where hrd.ClassRoomRecordId == classRoomId &&
hrd.StudentNo == studentNo
select hrd;
// 构建心跳数据查询
IQueryable<Ai_HeartRateData> heartRateQuery = _sportsContext.Ai_HeartRateData;
var heartRateDataList = await query.ToListAsync();
if (dto.Type == 1) // 按课堂查询
{
heartRateQuery = heartRateQuery.Where(x => x.ClassRoomRecordId == dto.classRoomId && x.StudentNo == studentNo);
}
else if (dto.Type == 2) // 按时间范围查询
{
if (dto.StartTime.HasValue)
heartRateQuery = heartRateQuery.Where(x => x.ScoreTime >= dto.StartTime.Value);
if (dto.EndTime.HasValue)
heartRateQuery = heartRateQuery.Where(x => x.ScoreTime <= dto.EndTime.Value);
heartRateQuery = heartRateQuery.Where(x => x.StudentNo == studentNo);
}
else
{
return res; // 无效类型
}
var heartRateDataList = await heartRateQuery.ToListAsync();
if (heartRateDataList.Count == 0)
return res;
var student = heartRateDataList[0];
// 基本心率指标
res.MaxHR = (int)heartRateDataList.Max(x => x.Value);
res.MinHR = (int)heartRateDataList.Min(x => x.Value);
res.AvgHR = (int)heartRateDataList.Average(x => x.Value);
res.HighIntensity = heartRateDataList.Where(x => x.Strength > 50).Sum(x => x.MotionDuration);
double totalDuration = heartRateDataList.Sum(x => x.MotionDuration);
res.HighIntensityDurationRatio = totalDuration > 0 ? res.HighIntensity / totalDuration : 0;
var baseTime = await _sportsContext.Ai_HeartRateData.Where(x => x.ClassRoomRecordId == classRoomId).MinAsync(x => x.ScoreTime);
// 计算时间轴(每分钟平均心率)
DateTime baseTime;
if (dto.Type == 1)
{
// 课堂模式:以该课堂所有心跳数据的最早时间为基准
baseTime = await _sportsContext.Ai_HeartRateData
.Where(x => x.ClassRoomRecordId == dto.classRoomId)
.MinAsync(x => x.ScoreTime);
}
else
{
// 时间模式:以当前查询到的心跳数据的最早时间为基准
baseTime = heartRateDataList.Min(x => x.ScoreTime);
}
var heartRateWithMinutes = heartRateDataList
.Select(data => new
@ -508,8 +537,7 @@ namespace YD_WeChatApplet.Services
})
.ToList();
var maxMinute = heartRateWithMinutes.Max(x => x.MinuteBucket);
int maxMinute = heartRateWithMinutes.Max(x => x.MinuteBucket);
for (int minute = 0; minute <= maxMinute; minute++)
{
var minuteData = heartRateWithMinutes
@ -524,16 +552,37 @@ namespace YD_WeChatApplet.Services
}
}
var jumpRecordList = await _sportsContext.Ai_FastJumpRopeData.Where(x => x.ClassRoomRecordId == classRoomId && x.StudentNo == studentNo).OrderByDescending(x => x.ScoreTime).Select(x => new JumpRecordListDto()
// 查询跳绳数据
IQueryable<Ai_FastJumpRopeData> jumpQuery = _sportsContext.Ai_FastJumpRopeData;
if (dto.Type == 1)
{
TotalCount = x.JumpValue ?? 0,
TotalDuration = x.MotionDuration ?? 0,
jumpQuery = jumpQuery.Where(x => x.ClassRoomRecordId == dto.classRoomId && x.StudentNo == studentNo);
}
else
{
if (dto.StartTime.HasValue)
jumpQuery = jumpQuery.Where(x => x.ScoreTime >= dto.StartTime.Value);
if (dto.EndTime.HasValue)
jumpQuery = jumpQuery.Where(x => x.ScoreTime <= dto.EndTime.Value);
jumpQuery = jumpQuery.Where(x => x.StudentNo == studentNo);
}
}).ToListAsync();
var jumpRecordList = await jumpQuery
.OrderByDescending(x => x.ScoreTime)
.Select(x => new JumpRecordListDto()
{
TotalCount = x.JumpValue ?? 0,
TotalDuration = x.MotionDuration ?? 0
})
.ToListAsync();
res.JumpRecordList = jumpRecordList;
int totalSeconds = jumpRecordList.Sum(x => x.TotalDuration);
res.TotalCount = jumpRecordList.Sum(x => x.TotalCount);
res.TotalDuration = totalSeconds / 60; // 秒转分钟,向下取整
res.AvgSpeed = totalSeconds > 0 ? (res.TotalCount * 60) / totalSeconds : 0;
return res;
}
@ -578,5 +627,90 @@ namespace YD_WeChatApplet.Services
return result;
}
/// <summary>
/// 班级报告
/// </summary>
/// <param name="classId"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<ClassReportDto> ClassReport(int classId)
{
var res = new ClassReportDto();
var classInfo = await _sportsContext.Class
.FirstOrDefaultAsync(x => x.Id == classId);
if (classInfo == null)
return res;
var students = await _sportsContext.Student
.Where(x => x.ClassId == classId)
.ToListAsync();
var studentNos = students.Select(x => x.StudentNo).ToList();
var categorys = await (
from g in _sportsContext.GradeAssocCategory
join s in _sportsContext.SportsTestCategory
on g.CategoryValue equals s.CategoryValue
where g.GradeId == classInfo.GradeId
select new
{
s.Id,
s.CategoryValue,
s.CategoryName,
s.CategoryEnum
}).ToListAsync();
var allResults = await _sportsContext.SportsTestValue
.Where(x => x.IsDisplay
&& studentNos.Contains(x.StudentNo)
&& x.SchoolCode == classInfo.SchoolCode)
.Select(x => new SportsTestValueModel
{
StudentNo = x.StudentNo,
Score = x.Score,
CategoryValue = x.CategoryValue,
ScoreTime = x.ScoreTime
})
.ToListAsync();
res.ClassCategoryScoreList = new List<ClassCategoryScoreDto>();
foreach (var item in categorys)
{
var categoryResults = allResults
.Where(x => x.CategoryValue == item.Id)
.OrderByDescending(x => x.ScoreTime)
.ToList();
var last = categoryResults.FirstOrDefault();
var prev = categoryResults.Skip(1).FirstOrDefault();
res.ClassCategoryScoreList.Add(new ClassCategoryScoreDto
{
CategoryValue = item.CategoryValue,
Name = item.CategoryName,
PrevScore = prev?.Score ?? 0,
LastScore = last?.Score ?? 0,
ImgUrl = $"{AppSettings.ALiYunOSS.CategoryImgUrl}{item.CategoryEnum}.png"
});
}
// 学生列表(保留)
res.StudentTrainingRecordList = students.Select(x => new StudentTrainingRecordDto
{
StudentNo = x.StudentNo,
StudentName = x.StudentName,
Sex = x.Sex,
Age = x.Age,
Photo = x.Photo
}).ToList();
return res;
}
}
}

View File

@ -42,7 +42,7 @@ namespace YD_WeChatApplet.Services
/// 学生报告
/// </summary>
/// <returns></returns>
Task<StudentReportDto> StudentReport();
Task<StudentReportDto> StudentReport(string studentNo);
/// <summary>
/// 训练记录

View File

@ -6,6 +6,7 @@ using YD_WeChatApplet.Commons;
using YD_WeChatApplet.Commons.Dto;
using YD_WeChatApplet.Commons.Dto.ClassRoomRecord;
using YD_WeChatApplet.Commons.Dto.HomeWork;
using YD_WeChatApplet.Commons.Dto.Patriarch;
using YD_WeChatApplet.Commons.Dto.Teacher;
namespace YD_WeChatApplet.Services
@ -68,8 +69,15 @@ namespace YD_WeChatApplet.Services
/// <summary>
/// 学员课堂报告
/// </summary>
/// <param name="studentNo"></param>
/// <param name="dto"></param>
/// <returns></returns>
Task<StudentClassRoomReportDto> StudentClassRoomReport(int classRoomId, string studentNo);
Task<StudentClassRoomReportDto> StudentClassRoomReport(StudentClassRoomReportRequestDto dto);
/// <summary>
/// 班级体测报告
/// </summary>
/// <returns></returns>
Task<ClassReportDto> ClassReport(int classId);
}
}

View File

@ -137,7 +137,7 @@ namespace YD_WeChatApplet.Api.SmartSportsEntitys
[Comment("测试时间")]
[Column(TypeName = "datetime")]
[Editable(true)]
public DateTime? ScoreTime { get; set; }
public DateTime ScoreTime { get; set; }
/// <summary>
/// 是否显示

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using YD_WeChatApplet.Commons.Dto.Patriarch;
namespace YD_WeChatApplet.Commons.Dto.ClassRoomRecord
{
@ -31,6 +32,11 @@ namespace YD_WeChatApplet.Commons.Dto.ClassRoomRecord
/// </summary>
public int HighIntensity { get; set; }
/// <summary>
/// 中高强度时长占比
/// </summary>
public double HighIntensityDurationRatio { get; set; }
/// <summary>
/// 学生运动记录
/// </summary>
@ -69,4 +75,52 @@ namespace YD_WeChatApplet.Commons.Dto.ClassRoomRecord
public string Photo { get; set; }
}
/// <summary>
/// 班级报告
/// </summary>
public class ClassReportDto
{
/// <summary>
/// 各项目得分
/// </summary>
public List<ClassCategoryScoreDto> ClassCategoryScoreList { get; set; }
/// <summary>
/// 学生运动记录
/// </summary>
public List<StudentTrainingRecordDto> StudentTrainingRecordList { get; set; } = new List<StudentTrainingRecordDto>();
}
/// <summary>
/// 各项目得分
/// </summary>
public class ClassCategoryScoreDto
{
/// <summary>
/// 项目枚举
/// </summary>
public int CategoryValue { get; set; }
/// <summary>
/// 项目名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 上一次得分
/// </summary>
public float PrevScore { get; set; }
/// <summary>
/// 最新得分
/// </summary>
public float LastScore { get; set; }
/// <summary>
/// 图片地址
/// </summary>
public string ImgUrl { get; set; }
}
}

View File

@ -31,6 +31,28 @@ namespace YD_WeChatApplet.Commons.Dto.ClassRoomRecord
/// </summary>
public int HighIntensity { get; set; }
/// <summary>
/// 中高强度时长占比
/// </summary>
public double HighIntensityDurationRatio { get; set; }
/// <summary>
/// 总个数
/// </summary>
public int TotalCount { get; set; }
/// <summary>
/// 总时长
/// </summary>
public int TotalDuration { get; set; }
/// <summary>
/// 平均速度
/// </summary>
public int AvgSpeed { get; set; }
/// <summary>
/// 跳绳记录
/// </summary>

View File

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace YD_WeChatApplet.Commons.Dto.ClassRoomRecord
{
/// <summary>
/// 学生报告
/// </summary>
public class StudentClassRoomReportRequestDto
{
/// <summary>
/// 查询类型[1:课堂/2:时间]
/// </summary>
public int Type { get; set; }
/// <summary>
/// 课堂Id
/// </summary>
public int classRoomId { get; set; }
/// <summary>
/// 学号
/// </summary>
public int StudentNo { get; set; }
/// <summary>
/// 开始时间
/// </summary>
public DateTime? StartTime { get; set; }
/// <summary>
/// 结束时间
/// </summary>
public DateTime? EndTime { get; set; }
}
}

View File

@ -11,26 +11,6 @@ namespace YD_WeChatApplet.Commons.Dto.Patriarch
/// </summary>
public class StudentReportDto
{
/// <summary>
/// 身体形态
/// </summary>
public float BodyShape { get; set; }
/// <summary>
/// 身体机能
/// </summary>
public float BodyFunction { get; set; }
/// <summary>
/// 身体素质
/// </summary>
public float BodyQuality { get; set; }
/// <summary>
/// 总得分
/// </summary>
public float TotalScore { get; set; }
/// <summary>
/// 各项目得分
/// </summary>
@ -42,19 +22,62 @@ namespace YD_WeChatApplet.Commons.Dto.Patriarch
/// </summary>
public class CategoryScoreDto
{
/// <summary>
/// 项目枚举
/// </summary>
public int CategoryValue { get; set; }
/// <summary>
/// 项目名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 得分
/// 上一次得分
/// </summary>
public float Score { get; set; }
public float PrevScore { get; set; }
/// <summary>
/// 最新得分
/// </summary>
public float LastScore { get; set; }
/// <summary>
/// 最好得分
/// </summary>
public float MaxScore { get; set; }
/// <summary>
/// 班级平均得分
/// </summary>
public float ClassAvgScore { get; set; }
/// <summary>
/// 学校平均得分
/// </summary>
public float SchoolAvgScore { get; set; }
/// <summary>
/// 图片地址
/// </summary>
public string ImgUrl { get; set; }
/// <summary>
/// 成绩趋势图
/// </summary>
public ScoreTrendChartDataDto ScoreTrendChart { get; set; }
}
public class ScoreTrendChartDataDto
{
/// <summary>
/// X轴
/// </summary>
public List<string> AxisX { get; set; }
/// <summary>
/// Y轴
/// </summary>
public List<float> AxisY { get; set; }
}
}

View File

@ -83,7 +83,7 @@ namespace YD_WeChatApplet.Commons.Dto.SportsTest
/// <summary>
///测试时间
/// </summary>
public DateTime? ScoreTime { get; set; }
public DateTime ScoreTime { get; set; }
}
}