- 添加学生提交管理服务 (StudentSubmissionService, StudentSubmissionDetailService) - 新增学生提交相关控制器 (StudentSubmissionController, StudentSubmissionDetailController) - 添加学生提交数据传输对象 (StudentSubmissionDetailDto, StudentSubmissionSummaryDto) - 新增学生提交相关页面组件 (StudentExamView, ExamDetailView, StudentCard等) - 添加学生提交信息卡片组件 (SubmissionInfoCard, TeacherSubmissionInfoCard) - 更新数据库迁移文件以支持提交系统
This commit is contained in:
@@ -58,6 +58,17 @@ namespace TechHelper.Context
|
||||
|
||||
CreateMap<SubmissionDetailDto, SubmissionDetail>().ReverseMap();
|
||||
|
||||
// Student Submission Detail
|
||||
CreateMap<Submission, StudentSubmissionDetailDto>()
|
||||
.ForMember(dest => dest.AssignmentId, opt => opt.MapFrom(src => src.AssignmentId))
|
||||
.ForMember(dest => dest.StudentId, opt => opt.MapFrom(src => src.StudentId))
|
||||
.ForMember(dest => dest.SubmissionTime, opt => opt.MapFrom(src => src.SubmissionTime))
|
||||
.ForMember(dest => dest.OverallGrade, opt => opt.MapFrom(src => src.OverallGrade))
|
||||
.ForMember(dest => dest.OverallFeedback, opt => opt.MapFrom(src => src.OverallFeedback))
|
||||
.ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.Status));
|
||||
|
||||
CreateMap<Assignment, AssignmentDto>().ReverseMap();
|
||||
|
||||
|
||||
CreateMap<SubjectTypeMetadataDto, Global>()
|
||||
.ForMember(dest => dest.Info, opt => opt.MapFrom(src => JsonConvert.SerializeObject(src.Data)));
|
||||
|
@@ -39,6 +39,7 @@ namespace TechHelper.Context.Configuration
|
||||
|
||||
builder.Property(s => s.OverallGrade)
|
||||
.HasColumnName("overall_grade")
|
||||
.IsRequired()
|
||||
.HasPrecision(5, 2); // 应用精度设置
|
||||
|
||||
// OverallFeedback
|
||||
|
127
TechHelper.Server/Controllers/StudentSubmissionController.cs
Normal file
127
TechHelper.Server/Controllers/StudentSubmissionController.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using Entities.Contracts;
|
||||
using Entities.DTO;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using TechHelper.Server.Services;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace TechHelper.Server.Controllers
|
||||
{
|
||||
[Route("api/student-submission")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public class StudentSubmissionController : ControllerBase
|
||||
{
|
||||
private readonly IStudentSubmissionService _studentSubmissionService;
|
||||
private readonly UserManager<User> _userManager;
|
||||
|
||||
public StudentSubmissionController(
|
||||
IStudentSubmissionService studentSubmissionService,
|
||||
UserManager<User> userManager)
|
||||
{
|
||||
_studentSubmissionService = studentSubmissionService;
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前学生的所有提交摘要
|
||||
/// </summary>
|
||||
/// <returns>学生提交摘要列表</returns>
|
||||
[HttpGet("my-submissions")]
|
||||
public async Task<IActionResult> GetMySubmissions()
|
||||
{
|
||||
var user = await _userManager.FindByEmailAsync(User.Identity.Name);
|
||||
if (user == null)
|
||||
return NotFound("未找到用户信息");
|
||||
|
||||
var result = await _studentSubmissionService.GetStudentSubmissionsAsync(user.Id);
|
||||
|
||||
if (result.Status)
|
||||
{
|
||||
return Ok(result.Result);
|
||||
}
|
||||
else
|
||||
{
|
||||
return BadRequest(result.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前学生的提交摘要(分页)
|
||||
/// </summary>
|
||||
/// <param name="pageNumber">页码,默认为1</param>
|
||||
/// <param name="pageSize">每页数量,默认为10</param>
|
||||
/// <returns>分页的学生提交摘要列表</returns>
|
||||
[HttpGet("my-submissions-paged")]
|
||||
public async Task<IActionResult> GetMySubmissionsPaged(int pageNumber = 1, int pageSize = 10)
|
||||
{
|
||||
if (pageNumber < 1) pageNumber = 1;
|
||||
if (pageSize < 1) pageSize = 10;
|
||||
if (pageSize > 100) pageSize = 100; // 限制最大页面大小
|
||||
|
||||
var user = await _userManager.FindByEmailAsync(User.Identity.Name);
|
||||
if (user == null)
|
||||
return NotFound("未找到用户信息");
|
||||
|
||||
var result = await _studentSubmissionService.GetStudentSubmissionsPagedAsync(user.Id, pageNumber, pageSize);
|
||||
|
||||
if (result.Status)
|
||||
{
|
||||
return Ok(result.Result);
|
||||
}
|
||||
else
|
||||
{
|
||||
return BadRequest(result.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定学生的提交摘要(仅教师可使用)
|
||||
/// </summary>
|
||||
/// <param name="studentId">学生ID</param>
|
||||
/// <returns>学生提交摘要列表</returns>
|
||||
[HttpGet("student/{studentId:guid}")]
|
||||
[Authorize(Roles = "Teacher")]
|
||||
public async Task<IActionResult> GetStudentSubmissions(Guid studentId)
|
||||
{
|
||||
var result = await _studentSubmissionService.GetStudentSubmissionsAsync(studentId);
|
||||
|
||||
if (result.Status)
|
||||
{
|
||||
return Ok(result.Result);
|
||||
}
|
||||
else
|
||||
{
|
||||
return BadRequest(result.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定学生的提交摘要(分页,仅教师可使用)
|
||||
/// </summary>
|
||||
/// <param name="studentId">学生ID</param>
|
||||
/// <param name="pageNumber">页码,默认为1</param>
|
||||
/// <param name="pageSize">每页数量,默认为10</param>
|
||||
/// <returns>分页的学生提交摘要列表</returns>
|
||||
[HttpGet("student/{studentId:guid}/paged")]
|
||||
[Authorize(Roles = "Teacher")]
|
||||
public async Task<IActionResult> GetStudentSubmissionsPaged(Guid studentId, int pageNumber = 1, int pageSize = 10)
|
||||
{
|
||||
if (pageNumber < 1) pageNumber = 1;
|
||||
if (pageSize < 1) pageSize = 10;
|
||||
if (pageSize > 100) pageSize = 100; // 限制最大页面大小
|
||||
|
||||
var result = await _studentSubmissionService.GetStudentSubmissionsPagedAsync(studentId, pageNumber, pageSize);
|
||||
|
||||
if (result.Status)
|
||||
{
|
||||
return Ok(result.Result);
|
||||
}
|
||||
else
|
||||
{
|
||||
return BadRequest(result.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,81 @@
|
||||
using Entities.Contracts;
|
||||
using Entities.DTO;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using TechHelper.Server.Services;
|
||||
using TechHelper.Context;
|
||||
using TechHelper.Repository;
|
||||
using SharedDATA.Api;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace TechHelper.Server.Controllers
|
||||
{
|
||||
[Route("api/student-submission-detail")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public class StudentSubmissionDetailController : ControllerBase
|
||||
{
|
||||
private readonly IStudentSubmissionDetailService _studentSubmissionDetailService;
|
||||
private readonly UserManager<User> _userManager;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
||||
public StudentSubmissionDetailController(
|
||||
IStudentSubmissionDetailService studentSubmissionDetailService,
|
||||
UserManager<User> userManager,
|
||||
IUnitOfWork unitOfWork)
|
||||
{
|
||||
_studentSubmissionDetailService = studentSubmissionDetailService;
|
||||
_userManager = userManager;
|
||||
_unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取学生提交的详细信息
|
||||
/// </summary>
|
||||
/// <param name="submissionId">提交ID</param>
|
||||
/// <returns>学生提交详细信息</returns>
|
||||
[HttpGet("{submissionId:guid}")]
|
||||
public async Task<IActionResult> GetSubmissionDetail(Guid submissionId)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 验证用户权限 - 只有学生本人或教师可以查看
|
||||
var user = await _userManager.FindByEmailAsync(User.Identity.Name);
|
||||
if (user == null)
|
||||
{
|
||||
return NotFound("未找到用户信息");
|
||||
}
|
||||
|
||||
var submission = await _unitOfWork.GetRepository<Submission>()
|
||||
.GetFirstOrDefaultAsync(predicate: s => s.Id == submissionId);
|
||||
|
||||
if (submission == null)
|
||||
{
|
||||
return NotFound("未找到指定的提交记录");
|
||||
}
|
||||
|
||||
// 检查权限:学生只能查看自己的提交,教师可以查看所有提交
|
||||
if (user.Id != submission.StudentId && !User.IsInRole("Teacher"))
|
||||
{
|
||||
return Forbid("您没有权限查看此提交记录");
|
||||
}
|
||||
|
||||
var result = await _studentSubmissionDetailService.GetSubmissionDetailAsync(submissionId);
|
||||
|
||||
if (result.Status)
|
||||
{
|
||||
return Ok(result.Result);
|
||||
}
|
||||
else
|
||||
{
|
||||
return BadRequest(result.Message);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, $"获取学生提交详细信息失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1299
TechHelper.Server/Migrations/20250904101811_submission_up_2.Designer.cs
generated
Normal file
1299
TechHelper.Server/Migrations/20250904101811_submission_up_2.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
|
||||
|
||||
namespace TechHelper.Server.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class submission_up_2 : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DeleteData(
|
||||
table: "AspNetRoles",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("2670f35a-df0c-4071-8879-80eb99d138a1"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "AspNetRoles",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("8c6c5e8e-ef00-444c-9c7c-cba5cd6f7043"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "AspNetRoles",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("9eda9d90-0cd2-4fbe-b07e-f90bd01f32db"));
|
||||
|
||||
migrationBuilder.AlterColumn<float>(
|
||||
name: "overall_grade",
|
||||
table: "submissions",
|
||||
type: "float",
|
||||
precision: 5,
|
||||
scale: 2,
|
||||
nullable: false,
|
||||
defaultValue: 0f,
|
||||
oldClrType: typeof(float),
|
||||
oldType: "float",
|
||||
oldPrecision: 5,
|
||||
oldScale: 2,
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "AspNetRoles",
|
||||
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ new Guid("264e4290-9d15-478d-8c49-8d0935e5a6e1"), null, "Administrator", "ADMINISTRATOR" },
|
||||
{ new Guid("73cafcee-3e99-43ae-86c5-c01a1cbc6124"), null, "Teacher", "TEACHER" },
|
||||
{ new Guid("f06927ff-4bba-4ab6-8f0a-e45a765c2fcc"), null, "Student", "STUDENT" }
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DeleteData(
|
||||
table: "AspNetRoles",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("264e4290-9d15-478d-8c49-8d0935e5a6e1"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "AspNetRoles",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("73cafcee-3e99-43ae-86c5-c01a1cbc6124"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "AspNetRoles",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("f06927ff-4bba-4ab6-8f0a-e45a765c2fcc"));
|
||||
|
||||
migrationBuilder.AlterColumn<float>(
|
||||
name: "overall_grade",
|
||||
table: "submissions",
|
||||
type: "float",
|
||||
precision: 5,
|
||||
scale: 2,
|
||||
nullable: true,
|
||||
oldClrType: typeof(float),
|
||||
oldType: "float",
|
||||
oldPrecision: 5,
|
||||
oldScale: 2);
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "AspNetRoles",
|
||||
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ new Guid("2670f35a-df0c-4071-8879-80eb99d138a1"), null, "Teacher", "TEACHER" },
|
||||
{ new Guid("8c6c5e8e-ef00-444c-9c7c-cba5cd6f7043"), null, "Student", "STUDENT" },
|
||||
{ new Guid("9eda9d90-0cd2-4fbe-b07e-f90bd01f32db"), null, "Administrator", "ADMINISTRATOR" }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
1299
TechHelper.Server/Migrations/20250904102023_submission_up_3.Designer.cs
generated
Normal file
1299
TechHelper.Server/Migrations/20250904102023_submission_up_3.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
|
||||
|
||||
namespace TechHelper.Server.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class submission_up_3 : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DeleteData(
|
||||
table: "AspNetRoles",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("264e4290-9d15-478d-8c49-8d0935e5a6e1"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "AspNetRoles",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("73cafcee-3e99-43ae-86c5-c01a1cbc6124"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "AspNetRoles",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("f06927ff-4bba-4ab6-8f0a-e45a765c2fcc"));
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "AspNetRoles",
|
||||
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ new Guid("388fdb1d-8cd5-4e8f-b49c-06dbee60527b"), null, "Administrator", "ADMINISTRATOR" },
|
||||
{ new Guid("ba4054d5-2f8a-4c7f-bd56-0fc864720c7d"), null, "Teacher", "TEACHER" },
|
||||
{ new Guid("c758a0d2-faea-4cf1-aa14-d162f3d0a1e9"), null, "Student", "STUDENT" }
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DeleteData(
|
||||
table: "AspNetRoles",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("388fdb1d-8cd5-4e8f-b49c-06dbee60527b"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "AspNetRoles",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("ba4054d5-2f8a-4c7f-bd56-0fc864720c7d"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "AspNetRoles",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("c758a0d2-faea-4cf1-aa14-d162f3d0a1e9"));
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "AspNetRoles",
|
||||
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ new Guid("264e4290-9d15-478d-8c49-8d0935e5a6e1"), null, "Administrator", "ADMINISTRATOR" },
|
||||
{ new Guid("73cafcee-3e99-43ae-86c5-c01a1cbc6124"), null, "Teacher", "TEACHER" },
|
||||
{ new Guid("f06927ff-4bba-4ab6-8f0a-e45a765c2fcc"), null, "Student", "STUDENT" }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
1302
TechHelper.Server/Migrations/20250905101308_tee.Designer.cs
generated
Normal file
1302
TechHelper.Server/Migrations/20250905101308_tee.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
82
TechHelper.Server/Migrations/20250905101308_tee.cs
Normal file
82
TechHelper.Server/Migrations/20250905101308_tee.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
|
||||
|
||||
namespace TechHelper.Server.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class tee : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DeleteData(
|
||||
table: "AspNetRoles",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("388fdb1d-8cd5-4e8f-b49c-06dbee60527b"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "AspNetRoles",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("ba4054d5-2f8a-4c7f-bd56-0fc864720c7d"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "AspNetRoles",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("c758a0d2-faea-4cf1-aa14-d162f3d0a1e9"));
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "BCorrect",
|
||||
table: "assignment_questions",
|
||||
type: "tinyint(1)",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "AspNetRoles",
|
||||
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ new Guid("d480cdca-7de2-4abe-8129-73bbaa6c1b32"), null, "Student", "STUDENT" },
|
||||
{ new Guid("d7bcfb37-3f1c-467b-a3f0-b2339a8a990d"), null, "Teacher", "TEACHER" },
|
||||
{ new Guid("f4a6788a-04d8-499c-9e64-73dfba97ca6b"), null, "Administrator", "ADMINISTRATOR" }
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DeleteData(
|
||||
table: "AspNetRoles",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("d480cdca-7de2-4abe-8129-73bbaa6c1b32"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "AspNetRoles",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("d7bcfb37-3f1c-467b-a3f0-b2339a8a990d"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "AspNetRoles",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("f4a6788a-04d8-499c-9e64-73dfba97ca6b"));
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BCorrect",
|
||||
table: "assignment_questions");
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "AspNetRoles",
|
||||
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ new Guid("388fdb1d-8cd5-4e8f-b49c-06dbee60527b"), null, "Administrator", "ADMINISTRATOR" },
|
||||
{ new Guid("ba4054d5-2f8a-4c7f-bd56-0fc864720c7d"), null, "Teacher", "TEACHER" },
|
||||
{ new Guid("c758a0d2-faea-4cf1-aa14-d162f3d0a1e9"), null, "Student", "STUDENT" }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -175,6 +175,9 @@ namespace TechHelper.Server.Migrations
|
||||
b.Property<Guid?>("AssignmentId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<bool>("BCorrect")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("created_at");
|
||||
@@ -558,7 +561,7 @@ namespace TechHelper.Server.Migrations
|
||||
.HasColumnType("longtext")
|
||||
.HasColumnName("overall_feedback");
|
||||
|
||||
b.Property<float?>("OverallGrade")
|
||||
b.Property<float>("OverallGrade")
|
||||
.HasPrecision(5, 2)
|
||||
.HasColumnType("float")
|
||||
.HasColumnName("overall_grade");
|
||||
@@ -800,19 +803,19 @@ namespace TechHelper.Server.Migrations
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = new Guid("8c6c5e8e-ef00-444c-9c7c-cba5cd6f7043"),
|
||||
Id = new Guid("d480cdca-7de2-4abe-8129-73bbaa6c1b32"),
|
||||
Name = "Student",
|
||||
NormalizedName = "STUDENT"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("2670f35a-df0c-4071-8879-80eb99d138a1"),
|
||||
Id = new Guid("d7bcfb37-3f1c-467b-a3f0-b2339a8a990d"),
|
||||
Name = "Teacher",
|
||||
NormalizedName = "TEACHER"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("9eda9d90-0cd2-4fbe-b07e-f90bd01f32db"),
|
||||
Id = new Guid("f4a6788a-04d8-499c-9e64-73dfba97ca6b"),
|
||||
Name = "Administrator",
|
||||
NormalizedName = "ADMINISTRATOR"
|
||||
});
|
||||
|
@@ -35,8 +35,10 @@ builder.Services.AddDbContext<ApplicationContext>(options =>
|
||||
.AddCustomRepository<Question, QuestionRepository>()
|
||||
.AddCustomRepository<QuestionContext, QuestionContextRepository>()
|
||||
.AddCustomRepository<Submission, SubmissionRepository>()
|
||||
.AddCustomRepository<User, UserRepository>()
|
||||
.AddCustomRepository<Global, GlobalRepository>();
|
||||
|
||||
|
||||
builder.Services.AddAutoMapper(typeof(AutoMapperProFile).Assembly);
|
||||
|
||||
// 3. 配置服务 (IOptions)
|
||||
@@ -90,6 +92,8 @@ builder.Services.AddScoped<IClassService, ClassService>();
|
||||
builder.Services.AddScoped<IExamService, ExamService>();
|
||||
builder.Services.AddScoped<IUserSerivces, UserServices>();
|
||||
builder.Services.AddScoped<ISubmissionServices, SubmissionServices>();
|
||||
builder.Services.AddScoped<IStudentSubmissionService, StudentSubmissionService>();
|
||||
builder.Services.AddScoped<IStudentSubmissionDetailService, StudentSubmissionDetailService>();
|
||||
builder.Services.AddScoped<IExamRepository, ExamRepository>();
|
||||
builder.Services.AddScoped<INoteService, NoteService>();
|
||||
|
||||
|
@@ -0,0 +1,15 @@
|
||||
using Entities.DTO;
|
||||
using TechHelper.Services;
|
||||
|
||||
namespace TechHelper.Server.Services
|
||||
{
|
||||
public interface IStudentSubmissionDetailService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取学生提交的详细信息
|
||||
/// </summary>
|
||||
/// <param name="submissionId">提交ID</param>
|
||||
/// <returns>学生提交详细信息</returns>
|
||||
Task<ApiResponse> GetSubmissionDetailAsync(Guid submissionId);
|
||||
}
|
||||
}
|
24
TechHelper.Server/Services/IStudentSubmissionService.cs
Normal file
24
TechHelper.Server/Services/IStudentSubmissionService.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Entities.DTO;
|
||||
using TechHelper.Services;
|
||||
|
||||
namespace TechHelper.Server.Services
|
||||
{
|
||||
public interface IStudentSubmissionService : IBaseService<StudentSubmissionSummaryDto, Guid>
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取学生提交的作业摘要列表
|
||||
/// </summary>
|
||||
/// <param name="studentId">学生ID</param>
|
||||
/// <returns>学生提交摘要列表</returns>
|
||||
Task<ApiResponse> GetStudentSubmissionsAsync(Guid studentId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取学生提交的作业摘要列表(分页)
|
||||
/// </summary>
|
||||
/// <param name="studentId">学生ID</param>
|
||||
/// <param name="pageNumber">页码</param>
|
||||
/// <param name="pageSize">每页数量</param>
|
||||
/// <returns>分页的学生提交摘要列表</returns>
|
||||
Task<ApiResponse> GetStudentSubmissionsPagedAsync(Guid studentId, int pageNumber = 1, int pageSize = 10);
|
||||
}
|
||||
}
|
144
TechHelper.Server/Services/StudentSubmissionDetailService.cs
Normal file
144
TechHelper.Server/Services/StudentSubmissionDetailService.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using AutoMapper;
|
||||
using AutoMapper.Internal.Mappers;
|
||||
using Entities.Contracts;
|
||||
using Entities.DTO;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SharedDATA.Api;
|
||||
using TechHelper.Context;
|
||||
using TechHelper.Repository;
|
||||
using TechHelper.Services;
|
||||
|
||||
namespace TechHelper.Server.Services
|
||||
{
|
||||
public class StudentSubmissionDetailService : IStudentSubmissionDetailService
|
||||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly IExamService examService;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public StudentSubmissionDetailService(
|
||||
IUnitOfWork unitOfWork,
|
||||
IExamService examService,
|
||||
IMapper mapper)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
this.examService = examService;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<ApiResponse> GetSubmissionDetailAsync(Guid submissionId)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取submission基本信息
|
||||
var submission = await _unitOfWork.GetRepository<Submission>()
|
||||
.GetAll(s => s.Id == submissionId)
|
||||
.Include(s => s.Assignment)
|
||||
.ThenInclude(a => a.Creator)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (submission == null)
|
||||
{
|
||||
return ApiResponse.Error("未找到指定的提交记录");
|
||||
}
|
||||
|
||||
var assignment = await examService.GetAsync(submission.AssignmentId);
|
||||
if (assignment == null)
|
||||
{
|
||||
return ApiResponse.Error("未找到指定的作业");
|
||||
}
|
||||
|
||||
// 获取所有提交详情
|
||||
var submissionDetails = await _unitOfWork.GetRepository<SubmissionDetail>()
|
||||
.GetAll(sd => sd.SubmissionId == submissionId)
|
||||
.Include(sd => sd.AssignmentQuestion)
|
||||
.ThenInclude(aq => aq.Question)
|
||||
.ThenInclude(q => q.Lesson)
|
||||
.ThenInclude(q => q.KeyPoints)
|
||||
.ToListAsync();
|
||||
|
||||
// 获取同作业的所有提交用于排名和成绩分布
|
||||
var allSubmissions = await _unitOfWork.GetRepository<Submission>()
|
||||
.GetAll(s => s.AssignmentId == submission.AssignmentId)
|
||||
.ToListAsync();
|
||||
|
||||
// 映射基本信息
|
||||
var result = _mapper.Map<StudentSubmissionDetailDto>(submission);
|
||||
result.Assignment = assignment.Result as AssignmentDto ?? new AssignmentDto();
|
||||
|
||||
var errorQuestion = submissionDetails
|
||||
.Where(sd => sd.IsCorrect == false && sd.AssignmentQuestion?.StructType == AssignmentStructType.Question && sd.AssignmentQuestion?.Question != null)
|
||||
.ToList();
|
||||
|
||||
// 计算基础统计
|
||||
result.TotalQuestions = submissionDetails.Select(x => x.AssignmentQuestion.StructType == AssignmentStructType.Question && x.AssignmentQuestion?.Question != null).Count();
|
||||
result.ErrorCount = errorQuestion.Count;
|
||||
result.CorrectCount = result.TotalQuestions - result.ErrorCount;
|
||||
result.AccuracyRate = result.TotalQuestions > 0 ?
|
||||
(float)result.CorrectCount / result.TotalQuestions : 0;
|
||||
|
||||
// 计算错误类型分布 - 只获取题目类型的错误
|
||||
result.ErrorTypeDistribution = errorQuestion
|
||||
.GroupBy(sd => sd.AssignmentQuestion.Question.Type.ToString())
|
||||
.ToDictionary(g => g.Key, g => g.Count()); ;
|
||||
|
||||
// 计算错误类型成绩分布 - 只获取题目类型的错误
|
||||
result.ErrorTypeScoreDistribution = errorQuestion
|
||||
.GroupBy(sd => sd.AssignmentQuestion.Question.Type.ToString())
|
||||
.ToDictionary(g => g.Key, g => g.Sum(sd => sd.PointsAwarded ?? 0));
|
||||
|
||||
// 计算成绩排名
|
||||
var orderedSubmissions = allSubmissions
|
||||
.OrderByDescending(s => s.OverallGrade)
|
||||
.ToList();
|
||||
result.TotalRank = orderedSubmissions.FindIndex(s => s.Id == submissionId) + 1;
|
||||
|
||||
SetBCorrect(result.Assignment, errorQuestion);
|
||||
// 计算成绩分布
|
||||
result.AllScores = allSubmissions.Select(s => s.OverallGrade).ToList();
|
||||
result.AverageScore = submission.OverallGrade;
|
||||
result.ClassAverageScore = allSubmissions.Average(s => s.OverallGrade);
|
||||
|
||||
// 计算课文错误分布
|
||||
result.LessonErrorDistribution = errorQuestion
|
||||
.Where(eq => eq.AssignmentQuestion.Question.Lesson != null)
|
||||
.GroupBy(sd => sd.AssignmentQuestion.Question.Lesson.Title)
|
||||
.ToDictionary(g => g.Key, g => g.Count());
|
||||
|
||||
// 计算关键点错误分布
|
||||
result.KeyPointErrorDistribution = errorQuestion
|
||||
.Where(eq => eq.AssignmentQuestion.Question.Lesson != null)
|
||||
.GroupBy(sd => sd.AssignmentQuestion.Question.KeyPoint.Key)
|
||||
.ToDictionary(g => g.Key, g => g.Count());
|
||||
|
||||
return ApiResponse.Success(result: result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ApiResponse.Error($"获取学生提交详细信息失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public void SetBCorrect(AssignmentDto assignment, List<SubmissionDetail> submissionDetails)
|
||||
{
|
||||
SetBCorrect(assignment.ExamStruct, submissionDetails);
|
||||
}
|
||||
|
||||
public void SetBCorrect(AssignmentQuestionDto assignmentQuestion, List<SubmissionDetail> submissionDetails)
|
||||
{
|
||||
var sd = submissionDetails.FirstOrDefault(x => x.AssignmentQuestionId == assignmentQuestion.Id);
|
||||
if (sd != null)
|
||||
assignmentQuestion.BCorrect = sd.AssignmentQuestion.BCorrect;
|
||||
else
|
||||
assignmentQuestion.BCorrect = false;
|
||||
|
||||
assignmentQuestion.ChildrenAssignmentQuestion?.ForEach(
|
||||
cq => SetBCorrect(cq, submissionDetails));
|
||||
}
|
||||
|
||||
//Task<ApiResponse> IStudentSubmissionDetailService.GetSubmissionDetailAsync(Guid submissionId)
|
||||
//{
|
||||
// throw new NotImplementedException();
|
||||
//}
|
||||
}
|
||||
}
|
142
TechHelper.Server/Services/StudentSubmissionService.cs
Normal file
142
TechHelper.Server/Services/StudentSubmissionService.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
using AutoMapper;
|
||||
using Entities.Contracts;
|
||||
using Entities.DTO;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SharedDATA.Api;
|
||||
using TechHelper.Context;
|
||||
using TechHelper.Repository;
|
||||
using TechHelper.Server.Repositories;
|
||||
using TechHelper.Services;
|
||||
|
||||
namespace TechHelper.Server.Services
|
||||
{
|
||||
public class StudentSubmissionService : IStudentSubmissionService
|
||||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IRepository<Submission> _submissionRepository;
|
||||
private readonly IRepository<Assignment> _assignmentRepository;
|
||||
private readonly IRepository<User> _userRepository;
|
||||
|
||||
public StudentSubmissionService(
|
||||
IUnitOfWork unitOfWork,
|
||||
IMapper mapper,
|
||||
IRepository<Submission> submissionRepository,
|
||||
IRepository<Assignment> assignmentRepository,
|
||||
IRepository<User> userRepository)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_mapper = mapper;
|
||||
_submissionRepository = submissionRepository;
|
||||
_assignmentRepository = assignmentRepository;
|
||||
_userRepository = userRepository;
|
||||
}
|
||||
|
||||
public async Task<ApiResponse> GetStudentSubmissionsAsync(Guid studentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var submissions = await _submissionRepository
|
||||
.GetAll(s => s.StudentId == studentId)
|
||||
.Include(s => s.Assignment)
|
||||
.ThenInclude(a => a.Creator)
|
||||
.OrderByDescending(s => s.SubmissionTime)
|
||||
.ToListAsync();
|
||||
|
||||
var result = new List<StudentSubmissionSummaryDto>();
|
||||
|
||||
foreach (var submission in submissions)
|
||||
{
|
||||
var summary = new StudentSubmissionSummaryDto
|
||||
{
|
||||
Id = submission.Id,
|
||||
AssignmentName = submission.Assignment?.Title ?? "未知作业",
|
||||
ErrorCount = await CalculateErrorCountAsync(submission.Id),
|
||||
CreatedDate = submission.SubmissionTime,
|
||||
Score = (int)submission.OverallGrade,
|
||||
TotalQuestions = submission.Assignment?.TotalQuestions ?? 0,
|
||||
StudentName = submission.Assignment?.Creator?.UserName ?? "未知老师",
|
||||
Status = submission.Status.ToString()
|
||||
};
|
||||
result.Add(summary);
|
||||
}
|
||||
|
||||
return ApiResponse.Success(result: result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ApiResponse.Error($"获取学生提交信息失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ApiResponse> GetStudentSubmissionsPagedAsync(Guid studentId, int pageNumber = 1, int pageSize = 10)
|
||||
{
|
||||
try
|
||||
{
|
||||
var totalCount = await _submissionRepository
|
||||
.GetAll(s => s.StudentId == studentId)
|
||||
.CountAsync();
|
||||
|
||||
var submissions = await _submissionRepository
|
||||
.GetAll(s => s.StudentId == studentId)
|
||||
.Include(s => s.Assignment)
|
||||
.ThenInclude(a => a.Creator)
|
||||
.OrderByDescending(s => s.SubmissionTime)
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
var result = new List<StudentSubmissionSummaryDto>();
|
||||
|
||||
foreach (var submission in submissions)
|
||||
{
|
||||
var summary = new StudentSubmissionSummaryDto
|
||||
{
|
||||
Id = submission.Id,
|
||||
AssignmentName = submission.Assignment?.Title ?? "未知作业",
|
||||
ErrorCount = await CalculateErrorCountAsync(submission.Id),
|
||||
CreatedDate = submission.SubmissionTime,
|
||||
Score = submission.OverallGrade,
|
||||
TotalQuestions = submission.Assignment?.TotalQuestions ?? 0,
|
||||
StudentName = submission.Assignment?.Creator?.UserName ?? "未知老师",
|
||||
Status = submission.Status.ToString()
|
||||
};
|
||||
result.Add(summary);
|
||||
}
|
||||
|
||||
var response = new StudentSubmissionSummaryResponseDto
|
||||
{
|
||||
Submissions = result,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
|
||||
return ApiResponse.Success(result: response);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ApiResponse.Error($"获取学生提交信息失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<int> CalculateErrorCountAsync(Guid submissionId)
|
||||
{
|
||||
|
||||
var submissionDetails = await _unitOfWork.GetRepository<SubmissionDetail>()
|
||||
.GetAll(sd => sd.SubmissionId == submissionId)
|
||||
.ToListAsync();
|
||||
return submissionDetails.Select(x => !x.IsCorrect).Count();
|
||||
}
|
||||
|
||||
// 以下方法是IBaseService接口的实现,可以根据需要实现
|
||||
public Task<ApiResponse> GetAllAsync() => throw new NotImplementedException();
|
||||
public Task<ApiResponse> GetAsync(Guid id) => throw new NotImplementedException();
|
||||
public Task<ApiResponse> AddAsync(StudentSubmissionSummaryDto model) => throw new NotImplementedException();
|
||||
public Task<ApiResponse> UpdateAsync(StudentSubmissionSummaryDto model) => throw new NotImplementedException();
|
||||
public Task<ApiResponse> DeleteAsync(Guid id) => throw new NotImplementedException();
|
||||
|
||||
public Task<ApiResponse> GetAllAsync(QueryParameter query)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user