ltx f741d95a52 feat: 接入体质评估报告生成与学员查询接口
教练提交学生体质数据前校验任教班级关系,调用评估服务生成报告并复用现有报告持久化逻辑。

新增当前登录学员或家长的最新报告查询接口,按令牌中的学校与学号限定查询范围;补充请求 DTO 与服务接口。

验证:dotnet build WeChatApplet/YD_WeChatApplet.Api.csproj --no-restore --nologo -clp:ErrorsOnly(0 错误)。
2026-10-23 16:48:14 +08:00

420 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using AutoMapper;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using YD_WeChatApplet.Api.Dto.Teacher;
using YD_WeChatApplet.Api.Entitys;
using YD_WeChatApplet.Api.SmartSportsEntitys;
using YD_WeChatApplet.Api.Utilities;
using YD_WeChatApplet.Commons.Dto;
using YD_WeChatApplet.Commons.Dto.HomeWork;
using YD_WeChatApplet.Commons.Dto.Patriarch;
using YD_WeChatApplet.Commons.Dto.Resource;
using YD_WeChatApplet.Commons.Dto.School;
using YD_WeChatApplet.Commons.Dto.SportsTest;
using YD_WeChatApplet.Commons.Dto.Teacher;
using YD_WeChatApplet.Commons.Enum;
using YD_WeChatApplet.Context;
using YD_WeChatApplet.Services;
namespace YD_WeChatApplet.Api.Services.Impl
{
/// <summary>
/// 家长端
/// </summary>
public class PatriarchService : IPatriarchService
{
public UserContext _userContext;
public SmartSportsContext _sportsContext;
private readonly IMapper _mapper;
public async Task<PostureReportContentDto> GetPostureReport()
{
var roleId = UserLoginContext.Current.RoleId;
if (roleId != 3 && roleId != 5)
throw new Exception("当前账号无权查看学员体质报告");
var schoolCode = UserLoginContext.Current.SchoolCode;
var studentNo = UserLoginContext.Current.UserNo;
if (string.IsNullOrWhiteSpace(schoolCode) || string.IsNullOrWhiteSpace(studentNo))
throw new Exception("未找到当前学员信息");
var content = await _userContext.PostureReport.AsNoTracking()
.Where(x => x.SchoolCode == schoolCode && x.StudentNo == studentNo)
.OrderByDescending(x => x.CreateTime)
.ThenByDescending(x => x.Id)
.Select(x => x.ReportContent)
.FirstOrDefaultAsync();
if (string.IsNullOrWhiteSpace(content))
throw new Exception("未找到该学生的体态报告");
return JsonConvert.DeserializeObject<PostureReportContentDto>(content)
?? throw new Exception("报告内容不是有效的JSON对象");
}
public PatriarchService(UserContext userContext, SmartSportsContext sportsContext, IMapper mapper)
{
_userContext = userContext;
_sportsContext = sportsContext;
_mapper = mapper;
}
/// <summary>
/// 新建训练数据
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task AddExerciseData(AddExerciseDataDto dto)
{
var entity = _mapper.Map<WCA_ExerciseData>(dto);
entity.RoleId = UserLoginContext.Current.RoleId;
entity.UserId = UserLoginContext.Current.UserId;
entity.UserName = UserLoginContext.Current.UserName;
entity.CreateTime = DateTime.Now;
if (dto.HomeWorkId == 0)
{
await _userContext.AddAsync(entity);
await _userContext.SaveChangesAsync();
return;
}
var work = await _sportsContext.HomeWork.Where(x => x.Id == dto.HomeWorkId).Select(x => new
{
x.Id,
x.Amount,
x.GroupNumber
}).FirstAsync();
entity.Status = dto.GroupNumber >= work.GroupNumber ? 2 : 1;
var student = await _sportsContext.HomeWorkStudents.FirstAsync(x => x.HomeWorkId == dto.HomeWorkId && x.StudentNo == UserLoginContext.Current.UserNo);
student.WorkStatus = entity.Status;
if (entity.RoleId == 3)
{
entity.StudentNo = UserLoginContext.Current.UserNo;
entity.StudentName = UserLoginContext.Current.UserName;
}
student.Value = dto.WorkModeType == 1 ? dto.Amount : dto.Duration;
_sportsContext.HomeWorkStudents.Update(student);
await _sportsContext.SaveChangesAsync();
await _userContext.AddAsync(entity);
await _userContext.SaveChangesAsync();
}
/// <summary>
/// 作业记录
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
public async Task<PageDataDto<HomeWorkRecordByPatriarchDto>> HomeWorkRecordByPatriarch(PatriarchHomeWorkHistoryDto dto)
{
var userNo = UserLoginContext.Current.UserNo;
var currentTime = DateTime.Now;
var query = _sportsContext.HomeWorkStudents
.Join(_sportsContext.HomeWork, a => a.HomeWorkId, s => s.Id, (a, s) => new
{
s.Id,
a.StudentNo,
a.WorkStatus,
s.WorkName,
s.WorkTypeName,
s.GroupNumber,
s.StartTime,
s.EndTime
})
.Where(a => a.StudentNo == userNo);
switch (dto.WorkStatus)
{
case 1: // 未开始
query = query.Where(x => x.StartTime > currentTime);
break;
case 2: // 进行中
query = query.Where(x => x.StartTime <= currentTime && x.EndTime >= currentTime);
break;
case 3: // 已结束
query = query.Where(x => x.EndTime < currentTime);
break;
default:
break;
}
//query = dto.IsHistory
// ? query.Where(x => x.EndTime < currentTime)
// : query.Where(x => x.EndTime >= currentTime);
var totalCount = await query.CountAsync();
var list = await query
.Select(x => new HomeWorkRecordByPatriarchDto
{
Id = x.Id,
WorkName = x.WorkName,
WorkTypeName = x.WorkTypeName,
//WorkStatus = x.EndTime < currentTime ? 3 : (x.StartTime > currentTime ? 1 : 2),
WorkStatus = dto.WorkStatus,
GroupNumber = x.GroupNumber,
IsComplete = x.WorkStatus == 2,
StartTime = x.StartTime,
EndTime = x.EndTime
})
.OrderByDescending(x => x.EndTime)
.Skip((dto.PageIndex - 1) * dto.PageSize)
.Take(dto.PageSize)
.ToListAsync();
return new PageDataDto<HomeWorkRecordByPatriarchDto>
{
Total = totalCount,
Datas = list
};
}
/// <summary>
/// 作业详情
/// </summary>
/// <param name="homeWorkId"></param>
/// <returns></returns>
public async Task<PatriarchHomeWorkDetailsDto> HomeWorkDetailsByPatriarch(int homeWorkId)
{
var res = await _sportsContext.HomeWork.Where(x => x.Id == homeWorkId).Select(x => new PatriarchHomeWorkDetailsDto()
{
Id = x.Id,
WorkName = x.WorkName,
StartTime = x.StartTime,
EndTime = x.EndTime,
WorkText = x.WorkText,
WorkType = (int)x.WorkType,
WorkTypeName = x.WorkTypeName,
WorkModeType = x.WorkModeType,
WorkModeTypeName = x.WorkModeTypeName,
Amount = x.Amount,
Duration = x.Duration,
GroupNumber = x.GroupNumber,
IsRepeat = x.IsRepeat,
RepetitionPeriod = x.RepetitionPeriod,
WorkStatus = x.WorkStatus
}).FirstOrDefaultAsync();
return res;
}
/// <summary>
/// 学生报告
/// </summary>
/// <returns></returns>
public async Task<StudentReportDto> StudentReport(string? studentNo)
{
var res = new StudentReportDto
{
CategoryScoreList = new List<CategoryScoreDto>()
};
// 若参数为空,则尝试从登录上下文获取当前用户编号
if (string.IsNullOrWhiteSpace(studentNo))
{
studentNo = UserLoginContext.Current.UserNo;
}
// 最终编号仍为空,则直接返回空结果
if (string.IsNullOrWhiteSpace(studentNo))
return res;
var student = await _sportsContext.Student.Where(x => x.StudentNo == studentNo).FirstOrDefaultAsync();
if (student == null)
return res;
var classInfo = await _sportsContext.Class.FirstOrDefaultAsync(x => x.Id == student.ClassId);
if (classInfo == null)
return res;
var categorys = new List<dynamic>
{
new
{
Id = 1,
CategoryValue = 1,
CategoryName = "BMI",
CategoryEnum = "BMI",
Weight = 0.15f
},
new
{
Id = 2,
CategoryValue = 2,
CategoryName = "肺活量",
CategoryEnum = "VitalCapacity",
Weight = 0.2f
},
new
{
Id = 4,
CategoryValue = 4,
CategoryName = "坐位体前屈",
CategoryEnum = "Sit_And_Reach",
Weight = 0.15f
},
new
{
Id = 5,
CategoryValue = 5,
CategoryName = "一分钟跳绳",
CategoryEnum = "OneMinuteJumpRope",
Weight = 0.2f
},
new
{
Id = 6,
CategoryValue = 6,
CategoryName = "一分钟仰卧起坐",
CategoryEnum = "One_Minute_Sit_Up",
Weight = 0.15f
},
new
{
Id = 8,
CategoryValue = 6,
CategoryName = "立定跳远",
CategoryEnum = "StandingLongJump",
Weight = 0.15f
}
};
var sportsTestResults = await _sportsContext.SportsTestValue.Where(x => x.IsDisplay && x.SchoolCode == student.SchoolCode).Select(x => new SportsTestValueModel()
{
StudentNo = x.StudentNo,
GradeId = x.GradeId,
Value = x.Value,
Score = x.Score,
Rank = x.Rank,
AdditionalScore = x.AdditionalScore,
CategoryValue = x.CategoryValue,
ScoreTime = x.ScoreTime
}).ToListAsync();
foreach (var item in categorys)
{
// ✅ 当前学生数据
var studentResults = sportsTestResults
.Where(x => x.CategoryValue == item.Id && x.StudentNo == studentNo)
.OrderByDescending(x => x.ScoreTime) // 倒序:最新在前
.ToList();
var lastResult = studentResults.FirstOrDefault();
var prevResult = studentResults.Skip(1).FirstOrDefault();
var maxScore = studentResults.Any()
? studentResults.Max(x => x.Value)
: 0;
//var classAvgScore = studentResults.Any()
// ? studentResults.Average(x => x.Value)
// : 0;
// 1. 获取每个学生最新的 ScoreTime
var latestRecords = sportsTestResults
.Where(x => x.CategoryValue == item.Id) // 先按指标过滤,提升效率
.GroupBy(x => x.StudentNo)
.Select(g => new
{
StudentNo = g.Key,
MaxScoreTime = g.Max(x => x.ScoreTime)
});
// 2. 关联原表,取得最新测试的那条记录(该指标的值)
var latestValues = from record in sportsTestResults
join latest in latestRecords
on new { record.StudentNo, record.ScoreTime }
equals new { latest.StudentNo, ScoreTime = latest.MaxScoreTime }
where record.CategoryValue == item.Id
select record.Value;
// 3. 计算平均值(如果没有数据则返回0)
var schoolAvgScore = latestValues.DefaultIfEmpty(0).Average();
// 🚀 趋势图(关键:反转集合)
var trendList = studentResults
.OrderBy(x => x.ScoreTime)
.ToList();
var trendChart = new ScoreTrendChartDataDto
{
AxisX = trendList.Select(x => x.ScoreTime.ToString("yyyy-MM-dd")).ToList(),
AxisY = trendList.Select(x => x.Value ?? 0f).ToList()
};
res.CategoryScoreList.Add(new CategoryScoreDto()
{
CategoryValue = item.Id,
Name = item.CategoryName,
Rank = lastResult?.Rank,
GradeId = lastResult?.GradeId,
PrevScore = prevResult?.Score ?? 0,
LastScore = lastResult?.Score ?? 0,
MaxScore = (float)maxScore,
//ClassAvgScore = (float)classAvgScore,
SchoolAvgScore = (float)schoolAvgScore,
ScoreTrendChart = trendChart ?? new ScoreTrendChartDataDto { AxisX = new List<string>(), AxisY = new List<float>() },
ImgUrl = $"{AppSettings.ALiYunOSS.CategoryImgUrl}{item.CategoryEnum}.png"
});
}
return res;
}
/// <summary>
/// 训练记录
/// </summary>
/// <returns></returns>
public async Task<PageDataDto<ExerciseRecordDto>> ExerciseRecord(PageDto dto)
{
var userId = UserLoginContext.Current.UserId;
var query = _userContext.ExerciseData.Where(x => x.UserId == userId);
var totalCount = await query.CountAsync();
var list = await query
.Select(x => new ExerciseRecordDto
{
Id = x.Id,
WorkTypeName = x.WorkTypeName,
WorkModeTypeName = x.WorkModeTypeName,
StartTime = x.StartTime,
EndTime = x.CreateTime,
GroupNumber = x.GroupNumber,
Amount = x.Amount ?? 0,
Duration = x.Duration ?? 0,
Calorie = x.Calorie ?? 0
})
.OrderByDescending(x => x.EndTime)
.Skip((dto.PageIndex - 1) * dto.PageSize)
.Take(dto.PageSize)
.ToListAsync();
return new PageDataDto<ExerciseRecordDto>
{
Total = totalCount,
Datas = list
};
}
}
}