feat: 完善运动记录与体测数据接口

AI运动站:按设备学校和学生学号关联学生、班级,返回课目编号与成绩值,并修正壹路训练有效次数的数值类型。

AI阳光跑:在分页查询与导出共用条件中排除零有效里程、零有效次数记录。

体测数据记录:补全年级与课目编号、姓名和测试时间,新增按当前筛选条件导出完整记录的 Excel 接口;800/1000 米成绩格式化为分秒。
This commit is contained in:
ltx 2026-10-13 14:27:08 +08:00
parent cd7206ede5
commit c4b01d2f53
8 changed files with 170 additions and 14 deletions

View File

@ -82,28 +82,44 @@ namespace VOL.Ai.Services
from train in _dbContext.Set<YL_Train>().AsNoTracking()
join device in _dbContext.Set<YL_Device>().AsNoTracking()
on train.DeviceSerial equals device.SerialNumber
join school in _dbContext.Set<S_School>().AsNoTracking()
on device.OrgId equals (int?)school.Id into schoolGroup
from school in schoolGroup.DefaultIfEmpty()
join student in _dbContext.Set<S_Student>().AsNoTracking()
on train.UserSerial equals student.StudentNo into studentGroup
on new { StudentNo = train.UserSerial, SchoolCode = school.SchoolCode }
equals new { student.StudentNo, student.SchoolCode } into studentGroup
from student in studentGroup.DefaultIfEmpty()
join studentClass in _dbContext.Set<S_Class>().AsNoTracking()
on new { Id = student.ClassId, student.SchoolCode }
equals new { studentClass.Id, studentClass.SchoolCode } into classGroup
from studentClass in classGroup.DefaultIfEmpty()
select new
{
train.Id,
device.OrgId,
StudentSchoolCode = student == null ? schoolCode : student.SchoolCode,
DeviceSchoolCode = school.SchoolCode,
train.CourseId,
train.StartTime,
train.EndTime,
train.Score,
train.StandardCounts,
train.UnStandardCounts,
train.TimeKeeping,
train.Duration,
train.Distance,
train.UserSerial,
StudentName = student == null ? train.UserSerial : student.StudentName,
Sex = student == null ? (SexType?)null : student.Sex,
ClassId = student == null ? 0 : student.ClassId,
ClassName = student == null ? string.Empty : student.ClassName
ClassName = studentClass == null || studentClass.ClassName == null || studentClass.ClassName == ""
? (student == null ? string.Empty : student.ClassName)
: (studentClass.GradeName ?? string.Empty) + studentClass.ClassName
};
if (!isAdmin)
{
var currentSchoolId = schoolId.GetValueOrDefault();
query = query.Where(x => x.OrgId == currentSchoolId && x.StudentSchoolCode == schoolCode);
query = query.Where(x => x.OrgId == currentSchoolId && x.DeviceSchoolCode == schoolCode);
}
if (!string.IsNullOrWhiteSpace(studentName))
@ -138,8 +154,15 @@ namespace VOL.Ai.Services
Ind = (paramDto.PageIndex - 1) * paramDto.PageSize + index + 1,
StudentName = x.StudentName,
Sex = GetSexName(x.Sex),
ClassName = x.ClassName,
ClassName = string.IsNullOrWhiteSpace(x.ClassName) ? "未匹配班级" : x.ClassName,
ProjectName = GetCourseName(x.CourseId),
CourseId = x.CourseId,
Score = x.Score,
StandardCounts = x.StandardCounts,
UnStandardCounts = x.UnStandardCounts,
TimeKeeping = x.TimeKeeping,
Duration = x.Duration,
Distance = x.Distance,
StartTime = x.StartTime,
EndTime = x.EndTime
})
@ -157,7 +180,7 @@ namespace VOL.Ai.Services
private static string GetCourseName(int courseId)
{
return CourseNames.TryGetValue(courseId, out var name) ? name : courseId.ToString();
return CourseNames.TryGetValue(courseId, out var name) ? name : $"待确认课目(编号{courseId})";
}
private static SexType? GetSexType(string sex)

View File

@ -1625,7 +1625,7 @@ namespace VOL.Business.Services
select new StudentsTestDataModel()
{
Id = r.Id,
GradeId = r.Id,
GradeId = r.GradeId,
GradeName = r.GradeName,
StudentNo = r.StudentNo,
StudentName = r.StudentName,
@ -1641,6 +1641,7 @@ namespace VOL.Business.Services
Value = r.Value,
Rank = r.Rank,
CategoryId = c.Id,
CategoryValue = c.CategoryValue,
Score = r.Score,
Sex = r.Sex == SexType.Male ? "男" : "女",
ScoreTime = r.ScoreTime
@ -1697,9 +1698,10 @@ namespace VOL.Business.Services
select new StudentsTestDataModel()
{
Id = r.Id,
GradeId = r.Id,
GradeId = r.GradeId,
GradeName = r.GradeName,
StudentNo = r.StudentNo,
StudentName = r.StudentName,
CategoryName = c.CategoryName,
ClassId = r.ClassId,
ClassName = r.ClassName,
@ -1712,8 +1714,10 @@ namespace VOL.Business.Services
Value = r.Value,
Rank = r.Rank,
CategoryId = c.Id,
CategoryValue = c.CategoryValue,
Score = r.Score,
Sex = r.Sex == SexType.Male ? "男" : "女"
Sex = r.Sex == SexType.Male ? "男" : "女",
ScoreTime = r.ScoreTime
};
if (!string.IsNullOrWhiteSpace(paramDto.StudentNo))
@ -1747,7 +1751,7 @@ namespace VOL.Business.Services
}
var list = await query.ToListAsync();
var list = await query.OrderByDescending(x => x.ScoreTime).ThenByDescending(x => x.Id).ToListAsync();
return list;
}
@ -2090,4 +2094,4 @@ namespace VOL.Business.Services
return scoreToAdditionalPoints[score];
}
}
}
}

View File

@ -69,7 +69,11 @@ namespace VOL.Business.Services.SunshineRun
private static string BuildWhere(SunshineRunFilterParam param, string schoolId)
{
var conditions = new List<string> { "1 = 1" };
var conditions = new List<string>
{
"ISNULL(r.EffectiveDistance, 0) > 0",
"ISNULL(r.Times, 0) - ISNULL(r.InvalidTimes, 0) > 0"
};
if (schoolId != null)
conditions.Add("r.OrgId = @OrgId");
if (!string.IsNullOrWhiteSpace(param.StudentName))

View File

@ -72,7 +72,7 @@ namespace VOL.Entity.DomainModels.YiLu
/// <summary>
/// 有效个数
/// </summary>
public int StandardCounts { get; set; }
public double StandardCounts { get; set; }
/// <summary>
/// 不合格动作数

View File

@ -32,6 +32,35 @@ namespace VOL.Model.Ai.Response
/// </summary>
public string ProjectName { get; set; }
/// <summary>
/// 壹路课目编号
/// </summary>
public int CourseId { get; set; }
/// <summary>
/// 统一成绩值
/// </summary>
public double? Score { get; set; }
public double StandardCounts { get; set; }
public int UnStandardCounts { get; set; }
/// <summary>
/// 有效时长(秒)
/// </summary>
public double? TimeKeeping { get; set; }
/// <summary>
/// 训练时长(秒)
/// </summary>
public int Duration { get; set; }
/// <summary>
/// 距离(米)
/// </summary>
public double? Distance { get; set; }
/// <summary>
/// 运动开始时间
/// </summary>

View File

@ -39,6 +39,9 @@ namespace VOL.Model
[Display(Name = "IgnoreColumnAttribute")]
public int CategoryId { get; set; }
[Display(Name = "IgnoreColumnAttribute")]
public int CategoryValue { get; set; }
/// <summary>
///年级名称
/// </summary>

View File

@ -0,0 +1,40 @@
using System.ComponentModel.DataAnnotations;
namespace VOL.Model
{
public class SummaryOfGradesTestRecordExportModel
{
[Display(Name = "序号")]
public int Index { get; set; }
[Display(Name = "年级-班级")]
public string GradeAndClass { get; set; }
[Display(Name = "学生姓名")]
public string StudentName { get; set; }
[Display(Name = "学生学号")]
public string StudentNo { get; set; }
[Display(Name = "性别")]
public string Sex { get; set; }
[Display(Name = "测试类别")]
public string CategoryName { get; set; }
[Display(Name = "测试结果")]
public string TestResult { get; set; }
[Display(Name = "得分")]
public float Score { get; set; }
[Display(Name = "等级")]
public string Rank { get; set; }
[Display(Name = "班级排名")]
public int ClassRank { get; set; }
[Display(Name = "测试时间")]
public string ScoreTime { get; set; }
}
}

View File

@ -3,6 +3,8 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using VOL.Business.IServices;
using VOL.Business.Services;
@ -79,6 +81,57 @@ namespace VOL.WebApi.Controllers.Business
return File(excelBytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", $"学生成绩{DateTime.Now.ToString("yyyyMMddHHmmss")}");
}
[HttpGet(nameof(ExportSummaryOfGrades))]
public async Task<ActionResult> ExportSummaryOfGrades(StudentsTestDataListParam paramDto)
{
var records = await _sportsTestResultService.GetStudentsTestDataList(paramDto);
var rows = records.Select((row, index) => new SummaryOfGradesTestRecordExportModel
{
Index = index + 1,
GradeAndClass = string.Join("-", new[] { row.GradeName, row.ClassName }
.Where(x => !string.IsNullOrWhiteSpace(x))),
StudentName = row.StudentName,
StudentNo = row.StudentNo,
Sex = row.Sex,
CategoryName = row.CategoryName,
TestResult = FormatTestResult(row),
Score = row.Score,
Rank = row.Rank,
ClassRank = row.ClassRank,
ScoreTime = row.ScoreTime.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)
}).ToList();
var columns = new List<string>
{
nameof(SummaryOfGradesTestRecordExportModel.Index),
nameof(SummaryOfGradesTestRecordExportModel.GradeAndClass),
nameof(SummaryOfGradesTestRecordExportModel.StudentName),
nameof(SummaryOfGradesTestRecordExportModel.StudentNo),
nameof(SummaryOfGradesTestRecordExportModel.Sex),
nameof(SummaryOfGradesTestRecordExportModel.CategoryName),
nameof(SummaryOfGradesTestRecordExportModel.TestResult),
nameof(SummaryOfGradesTestRecordExportModel.Score),
nameof(SummaryOfGradesTestRecordExportModel.Rank),
nameof(SummaryOfGradesTestRecordExportModel.ClassRank),
nameof(SummaryOfGradesTestRecordExportModel.ScoreTime)
};
var bytes = Tool.ExportToExcel(
new Dictionary<string, List<SummaryOfGradesTestRecordExportModel>> { ["体测数据记录"] = rows },
columns, autoFitColumns: false);
return File(bytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
$"体测数据记录{DateTime.Now:yyyyMMddHHmmss}.xlsx");
}
private static string FormatTestResult(StudentsTestDataModel row)
{
if (row.CategoryValue != 10 && row.CategoryValue != 11)
return row.Value.ToString("0.##", CultureInfo.InvariantCulture);
// Some historical imports contain millisecond values despite the seconds-based category unit.
var seconds = row.Value >= 10000 ? row.Value / 1000d : row.Value;
var totalSeconds = (int)Math.Round(Math.Max(0, seconds), MidpointRounding.AwayFromZero);
return $"{totalSeconds / 60}分{totalSeconds % 60:D2}秒";
}
/// <summary>
/// 新增
/// </summary>
@ -115,4 +168,4 @@ namespace VOL.WebApi.Controllers.Business
return Ok("更新成功");
}
}
}
}