教练提交学生体质数据前校验任教班级关系,调用评估服务生成报告并复用现有报告持久化逻辑。 新增当前登录学员或家长的最新报告查询接口,按令牌中的学校与学号限定查询范围;补充请求 DTO 与服务接口。 验证:dotnet build WeChatApplet/YD_WeChatApplet.Api.csproj --no-restore --nologo -clp:ErrorsOnly(0 错误)。
420 lines
15 KiB
C#
420 lines
15 KiB
C#
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
|
||
};
|
||
}
|
||
}
|
||
}
|