This commit is contained in:
SpecialX
2025-06-27 19:03:10 +08:00
parent 14fbe6397a
commit a21ca80782
57 changed files with 3872 additions and 611 deletions

View File

@@ -76,4 +76,15 @@ namespace Entities.Contracts
Option Option
} }
public enum SubmissionStatus
{
Pending, // 待提交/未开始
Submitted, // 已提交
Graded, // 已批改
Resubmission, // 待重新提交 (如果允许)
Late, // 迟交
Draft, // 草稿
}
} }

View File

@@ -31,6 +31,9 @@ namespace Entities.Contracts
[Column("question_number")] [Column("question_number")]
public byte Index { get; set; } public byte Index { get; set; }
[Column("sequence")]
public string Sequence { get; set; } = string.Empty;
[Column("parent_question_group_id")] [Column("parent_question_group_id")]
public Guid? ParentAssignmentQuestionId { get; set; } public Guid? ParentAssignmentQuestionId { get; set; }

View File

@@ -37,7 +37,7 @@ namespace Entities.Contracts
public float? OverallGrade { get; set; } public float? OverallGrade { get; set; }
[Column("overall_feedback")] [Column("overall_feedback")]
public string OverallFeedback { get; set; } public string? OverallFeedback { get; set; }
[Column("graded_by")] [Column("graded_by")]
[ForeignKey("Grader")] [ForeignKey("Grader")]
@@ -66,13 +66,4 @@ namespace Entities.Contracts
} }
} }
public enum SubmissionStatus
{
Pending, // 待提交/未开始
Submitted, // 已提交
Graded, // 已批改
Resubmission, // 待重新提交 (如果允许)
Late, // 迟交
Draft, // 草稿
}
} }

View File

@@ -31,16 +31,16 @@ namespace Entities.Contracts
public Guid AssignmentQuestionId { get; set; } public Guid AssignmentQuestionId { get; set; }
[Column("student_answer")] [Column("student_answer")]
public string StudentAnswer { get; set; } public string? StudentAnswer { get; set; }
[Column("is_correct")] [Column("is_correct")]
public bool? IsCorrect { get; set; } public bool? IsCorrect { get; set; }
[Column("points_awarded")] [Column("points_awarded")]
public float? PointsAwarded { get; set; } public float? PointsAwarded { get; set; } // score
[Column("teacher_feedback")] [Column("teacher_feedback")]
public string TeacherFeedback { get; set; } public string? TeacherFeedback { get; set; }
[Column("created_at")] [Column("created_at")]
public DateTime CreatedAt { get; set; } public DateTime CreatedAt { get; set; }
@@ -54,8 +54,10 @@ namespace Entities.Contracts
[ForeignKey(nameof(StudentId))] [ForeignKey(nameof(StudentId))]
public User Student { get; set; } public User Student { get; set; }
[ForeignKey(nameof(SubmissionId))]
public Submission Submission { get; set; } public Submission Submission { get; set; }
[ForeignKey(nameof(AssignmentQuestionId))]
public AssignmentQuestion AssignmentQuestion { get; set; } public AssignmentQuestion AssignmentQuestion { get; set; }
public SubmissionDetail() public SubmissionDetail()

View File

@@ -0,0 +1,23 @@
using Entities.Contracts;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
namespace Entities.DTO
{
public class AssignmentClassDto
{
public AssignmentDto Assignment { get; set; }
public Class ClassId { get; set; }
public DateTime AssignedAt { get; set; }
}
}

View File

@@ -1,4 +1,5 @@
using System; using Entities.Contracts;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@@ -6,4 +7,20 @@ using System.Threading.Tasks;
namespace Entities.DTO namespace Entities.DTO
{ {
public class AssignmentDto
{
public Guid Id { get; set; } = Guid.Empty;
public string Title { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public byte TotalQuestions { get; set; }
public float Score { get; set; } = 0;
public SubjectAreaEnum SubjectArea { get; set; } = SubjectAreaEnum.Unknown;
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public DateTime DueDate { get; set; }
public Guid CreatorId { get; set; }
public AssignmentQuestionDto ExamStruct { get; set; } = new AssignmentQuestionDto();
}
} }

View File

@@ -15,6 +15,7 @@ namespace Entities.DTO
public byte Index { get; set; } = 0; public byte Index { get; set; } = 0;
public float Score { get; set; } = 0; public float Score { get; set; } = 0;
public string Sequence { get; set; } = string.Empty;
public Layout Layout { get; set; } = Layout.horizontal; public Layout Layout { get; set; } = Layout.horizontal;
public AssignmentStructType StructType { get; set; } = AssignmentStructType.Question; public AssignmentStructType StructType { get; set; } = AssignmentStructType.Question;
@@ -24,4 +25,6 @@ namespace Entities.DTO
public QuestionDto? Question { get; set; } public QuestionDto? Question { get; set; }
} }
} }

View File

@@ -1,46 +0,0 @@
using Entities.Contracts;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
namespace Entities.DTO
{
public class AssignmentDto
{
public Guid Id { get; set; } = Guid.Empty;
public string Title { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public byte TotalQuestions { get; set; }
public float Score { get; set; } = 0;
public SubjectAreaEnum SubjectArea { get; set; } = SubjectAreaEnum.Unknown;
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public DateTime DueDate { get; set; }
public Guid CreatorId { get; set; }
public AssignmentQuestionDto ExamStruct { get; set; } = new AssignmentQuestionDto();
}
public class AssignmentClassDto
{
public AssignmentDto Assignment { get; set; }
public Class ClassId { get; set; }
public DateTime AssignedAt { get; set; }
}
public class QuestionContextDto
{
public Guid Id { get; set; } = Guid.Empty;
public string Description { get; set; } = string.Empty;
}
public class OptionDto
{
public string? Value { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Entities.DTO
{
public class QuestionContextDto
{
public Guid Id { get; set; } = Guid.Empty;
public string Description { get; set; } = string.Empty;
}
}

View File

@@ -36,4 +36,13 @@ namespace Entities.DTO
public DateTime UpdatedAt { get; set; } = DateTime.Now; public DateTime UpdatedAt { get; set; } = DateTime.Now;
} }
/// <summary>
/// Can be removed because the class isn't used
/// </summary>
public class OptionDto
{
public string? Value { get; set; } = string.Empty;
}
} }

View File

@@ -0,0 +1,20 @@
using Entities.Contracts;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Entities.DTO
{
public class StudentDto
{
public Guid Id { get; set; }
public string? DisplayName { get; set; }
public UInt32 ErrorQuestionNum { get; set; }
public Dictionary<QuestionType, UInt32> ErrorQuestionTypes { get; set; } = new Dictionary<QuestionType, UInt32>();
public Dictionary<SubjectAreaEnum, UInt32> SubjectAreaErrorQuestionDis { get; set; } = new Dictionary<SubjectAreaEnum, UInt32>();
public Dictionary<byte, UInt32> LessonErrorDis { get; set; } = new Dictionary<byte, UInt32>();
}
}

View File

@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Entities.DTO
{
public class SubmissionDetailDto
{
public Guid Id { get; set; } = Guid.Empty;
public Guid StudentId { get; set; }
public Guid AssignmentQuestionId { get; set; }
public string? StudentAnswer { get; set; }
public bool? IsCorrect { get; set; }
public float? PointsAwarded { get; set; }
public string? TeacherFeedback { get; set; }
}
}

View File

@@ -0,0 +1,23 @@
using Entities.Contracts;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Entities.DTO
{
public class SubmissionDto
{
public Guid Id { get; set; } = Guid.Empty;
public Guid AssignmentId { get; set; }
public Guid StudentId { get; set; }
public DateTime SubmissionTime { get; set; }
public float OverallGrade { get; set; } = 0;
public string OverallFeedback { get; set; } = string.Empty;
public Guid? GraderId { get; set; }
public DateTime? GradedAt { get; set; }
public SubmissionStatus Status { get; set; }
public List<SubmissionDetailDto> SubmissionDetails { get; set; } = new List<SubmissionDetailDto>();
}
}

15
Entities/DTO/UserDto.cs Normal file
View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Entities.DTO
{
public class UserDto
{
public Guid Id { get; set; }
public string? DisplayName { get; set; }
}
}

View File

@@ -15,6 +15,9 @@ namespace TechHelper.Context
CreateMap<AssignmentQuestionEx, AssignmentQuestionDto>() CreateMap<AssignmentQuestionEx, AssignmentQuestionDto>()
.ForMember(d=>d.Description, o=>o.Ignore()); .ForMember(d=>d.Description, o=>o.Ignore());
CreateMap<AssignmentEx, AssignmentDto>(); CreateMap<AssignmentEx, AssignmentDto>();
CreateMap<AssignmentCheckData, Submission>();
} }
} }

View File

@@ -2,18 +2,20 @@
namespace TechHelper.Client.Exam namespace TechHelper.Client.Exam
{ {
public class ExamStruct public class AssignmentCheckData
{ {
public string Title { get; set; } public string Title { get; set; }
public List<QuestionItem> Questions { get; set; } = new List<QuestionItem>(); public Guid AssignmentId { get; set; }
public Guid StudentId { get; set; }
public List<AssignmentCheckQuestion> Questions { get; set; } = new List<AssignmentCheckQuestion>();
}
public class QuestionItem public class AssignmentCheckQuestion
{ {
public string Sequence { get; set; } = string.Empty; public string Sequence { get; set; } = string.Empty;
public string QuestionText { get; set; } = string.Empty; public AssignmentQuestionDto AssignmentQuestionDto { get; set; } = new AssignmentQuestionDto();
public float Score { get; set; } public float Score { get; set; }
}
} }
public class Student public class Student
@@ -24,32 +26,32 @@ namespace TechHelper.Client.Exam
public class QuestionAnswerStatus public class QuestionAnswerStatus
{ {
public string QuestionSequence { get; set; } = string.Empty; // 题目序号,例如 "1.1" public string QuestionSequence { get; set; } = string.Empty; // 题目序号,例如 "1.1"
public string QuestionText { get; set; } = string.Empty; // 题目文本 public string QuestionText { get; set; } = string.Empty; // 题目文本
public float QuestionScore { get; set; } // 题目分值 public float QuestionScore { get; set; } // 题目分值
public Dictionary<Guid, bool> StudentCorrectStatus { get; set; } = new Dictionary<Guid, bool>(); public Dictionary<Guid, bool> StudentCorrectStatus { get; set; } = new Dictionary<Guid, bool>();
// Key: Student.Id, Value: true 表示正确false 表示错误 // Key: Student.Id, Value: true 表示正确false 表示错误
} }
public class QuestionRowData public class QuestionRowData
{ {
public ExamStruct.QuestionItem QuestionItem { get; set; } // 原始题目信息 public AssignmentCheckQuestion QuestionItem { get; set; } // 原始题目信息
public Dictionary<Guid, bool> StudentAnswers { get; set; } = new Dictionary<Guid, bool>(); public Dictionary<Guid, bool> StudentAnswers { get; set; } = new Dictionary<Guid, bool>();
} }
public static class ExamStructExtensions public static class ExamStructExtensions
{ {
public static ExamStruct GetStruct(this AssignmentDto dto) public static AssignmentCheckData GetStruct(this AssignmentDto dto)
{ {
if (dto == null) if (dto == null)
{ {
return new ExamStruct { Title = "无效试卷", Questions = new List<ExamStruct.QuestionItem>() }; return new AssignmentCheckData { Title = "无效试卷", Questions = new List<AssignmentCheckQuestion>() };
} }
var examStruct = new ExamStruct var examStruct = new AssignmentCheckData
{ {
Title = dto.Title Title = dto.Title
}; };
GetSeqRecursive(dto.ExamStruct, null, examStruct.Questions); GetSeqRecursive(dto.ExamStruct, null, examStruct.Questions);
@@ -65,8 +67,8 @@ namespace TechHelper.Client.Exam
/// <param name="allQuestions">用于收集所有生成题目项的列表。</param> /// <param name="allQuestions">用于收集所有生成题目项的列表。</param>
private static void GetSeqRecursive( private static void GetSeqRecursive(
AssignmentQuestionDto currentGroup, AssignmentQuestionDto currentGroup,
string? parentSequence, string? parentSequence,
List<ExamStruct.QuestionItem> allQuestions) List<AssignmentCheckQuestion> allQuestions)
{ {
string currentGroupSequence = parentSequence != null string currentGroupSequence = parentSequence != null
? $"{parentSequence}.{currentGroup.Index}" ? $"{parentSequence}.{currentGroup.Index}"
@@ -76,6 +78,17 @@ namespace TechHelper.Client.Exam
{ {
GetSeqRecursive(subGroup, currentGroupSequence, allQuestions); GetSeqRecursive(subGroup, currentGroupSequence, allQuestions);
} }
if (!string.IsNullOrEmpty(currentGroup.Sequence))
{
allQuestions.Add(new AssignmentCheckQuestion
{
AssignmentQuestionDto = currentGroup,
//Sequence = currentGroupSequence,
Sequence = currentGroup.Sequence,
Score = currentGroup.Score,
});
}
} }
} }
} }

View File

@@ -7,7 +7,7 @@ using AutoMapper;
namespace TechHelper.Client.Exam namespace TechHelper.Client.Exam
{ {
public static class ExamPaperExtensions public static class AssignmentExtensions
{ {
public static List<string> ParseOptionsFromText(this string optionsText) public static List<string> ParseOptionsFromText(this string optionsText)
@@ -25,9 +25,9 @@ namespace TechHelper.Client.Exam
public static void SeqQGroupIndex(this AssignmentQuestionDto dto) public static void SeqQGroupIndex(this AssignmentQuestionDto dto)
{ {
foreach(var sqg in dto.ChildrenAssignmentQuestion) foreach (var sqg in dto.ChildrenAssignmentQuestion)
{ {
sqg.Index = (byte)dto.ChildrenAssignmentQuestion.IndexOf(sqg); sqg.Index = (byte)(dto.ChildrenAssignmentQuestion.IndexOf(sqg) + 1);
sqg.SeqQGroupIndex(); sqg.SeqQGroupIndex();
} }

View File

@@ -56,6 +56,7 @@ namespace TechHelper.Client.Exam
public string Description { get; set; } = string.Empty; public string Description { get; set; } = string.Empty;
public byte Index { get; set; } = 0; public byte Index { get; set; } = 0;
public float Score { get; set; } public float Score { get; set; }
public string Sequence { get; set; } = string.Empty;
public QuestionEx? Question { get; set; } public QuestionEx? Question { get; set; }
public AssignmentStructType Type { get; set; } public AssignmentStructType Type { get; set; }
public List<AssignmentQuestionEx> ChildrenAssignmentQuestion { get; set; } = new List<AssignmentQuestionEx>(); public List<AssignmentQuestionEx> ChildrenAssignmentQuestion { get; set; } = new List<AssignmentQuestionEx>();
@@ -344,6 +345,8 @@ namespace TechHelper.Client.Exam
assignmentQuestionStack.Pop(); assignmentQuestionStack.Pop();
} }
string sequence = assignmentQuestionStack.Count > 0 ? assignmentQuestionStack.Peek().Sequence : string.Empty;
// 验证捕获组Group 1 是编号Group 2 是题目内容 // 验证捕获组Group 1 是编号Group 2 是题目内容
if (pm.RegexMatch.Groups.Count < 3 || !pm.RegexMatch.Groups[1].Success || string.IsNullOrWhiteSpace(pm.RegexMatch.Groups[2].Value)) if (pm.RegexMatch.Groups.Count < 3 || !pm.RegexMatch.Groups[1].Success || string.IsNullOrWhiteSpace(pm.RegexMatch.Groups[2].Value))
{ {
@@ -372,6 +375,8 @@ namespace TechHelper.Client.Exam
// 提取标题,这里使用 Group 2 的值,它不包含分数 // 提取标题,这里使用 Group 2 的值,它不包含分数
string title = pm.RegexMatch.Groups[2].Value.Trim(); string title = pm.RegexMatch.Groups[2].Value.Trim();
string seq = pm.RegexMatch.Groups[1].Value.Trim();
seq = string.IsNullOrEmpty(seq) || string.IsNullOrEmpty(sequence) ? seq : " ." + seq;
AssignmentQuestionEx newAssignmentQuestion; AssignmentQuestionEx newAssignmentQuestion;
if (pm.PatternConfig.Type == AssignmentStructType.Struct) if (pm.PatternConfig.Type == AssignmentStructType.Struct)
@@ -380,6 +385,7 @@ namespace TechHelper.Client.Exam
{ {
Title = title, Title = title,
Score = score, Score = score,
Sequence = sequence + seq,
Priority = pm.PatternConfig.Priority, Priority = pm.PatternConfig.Priority,
Type = pm.PatternConfig.Type Type = pm.PatternConfig.Type
}; };
@@ -390,6 +396,7 @@ namespace TechHelper.Client.Exam
{ {
Priority = pm.PatternConfig.Priority, Priority = pm.PatternConfig.Priority,
Type = pm.PatternConfig.Type, Type = pm.PatternConfig.Type,
Sequence = sequence + seq,
Score = score, Score = score,
Question = new QuestionEx Question = new QuestionEx
{ {

View File

@@ -92,7 +92,6 @@ namespace TechHelper.Client.HttpRepository
public async Task<ResponseDto> RegisterUserAsync(UserForRegistrationDto userForRegistrationDto) public async Task<ResponseDto> RegisterUserAsync(UserForRegistrationDto userForRegistrationDto)
{ {
// 移除 using (_client = _clientFactory.CreateClient("Default"))
userForRegistrationDto.ClientURI = Path.Combine( userForRegistrationDto.ClientURI = Path.Combine(
_navigationManager.BaseUri, "emailconfirmation"); _navigationManager.BaseUri, "emailconfirmation");

View File

@@ -9,216 +9,227 @@
@if (_isLoading) @if (_isLoading)
{ {
<MudProgressCircular Indeterminate="true" Color="Color.Primary" Class="d-flex justify-center my-8" /> <MudProgressCircular Indeterminate="true" Color="Color.Primary" Class="d-flex justify-center my-8" />
<MudText Class="text-center">正在加载试卷和学生数据...</MudText> <MudText Class="text-center">正在加载试卷和学生数据...</MudText>
} }
else if (_questionsForTable.Any() && _students.Any()) else if (_questionsForTable.Any() && _students.Any())
{ {
<MudTable @ref="_table" T="QuestionRowData" Items="@_questionsForTable" Hover="true" Breakpoint="Breakpoint.Sm" Class="mud-elevation-2" Dense="true"> <MudTable @ref="_table" T="QuestionRowData" Items="@_questionsForTable" Hover="true" Breakpoint="Breakpoint.Sm" Striped="true" Class="mud-elevation-2" Dense="true">
<HeaderContent> <HeaderContent>
<MudTh Style="width:100px;">序号</MudTh> <MudTh Style="width:100px;">序号</MudTh>
<MudTh Style="width:80px; text-align:center;">分值</MudTh> <MudTh Style="width:80px; text-align:center;">分值</MudTh>
@foreach (var student in _students) @foreach (var student in _students)
{ {
<MudTh Style="width:120px; text-align:center;"> <MudTh Style="width:120px; text-align:center;">
@student.Name @student.DisplayName
<MudTooltip Text="点击以切换此学生所有题目的对错"> <MudTooltip Text="点击以切换此学生所有题目的对错">
<MudIconButton Icon="@Icons.Material.Filled.Info" Size="Size.Small" Class="ml-1" <MudIconButton Icon="@Icons.Material.Filled.Info" Size="Size.Small" Class="ml-1"
@onclick="() => ToggleStudentAllAnswers(student.Id)" /> @onclick="() => ToggleStudentAllAnswers(student.Id)" />
</MudTooltip> </MudTooltip>
</MudTh> </MudTh>
} }
</HeaderContent> </HeaderContent>
<RowTemplate> <RowTemplate>
<MudTd DataLabel="序号">@context.QuestionItem.Sequence</MudTd> <MudTd DataLabel="序号">@context.QuestionItem.Sequence</MudTd>
<MudTd DataLabel="分值" Style="text-align:center;">@context.QuestionItem.Score</MudTd> <MudTd DataLabel="分值" Style="text-align:center;">@context.QuestionItem.Score</MudTd>
@foreach (var student in _students) @foreach (var student in _students)
{ {
<MudTd DataLabel="@student.Name" Style="text-align:center;"> <MudTd DataLabel="@student.DisplayName" Style="text-align:center;">
@if (context.StudentAnswers.ContainsKey(student.Id)) @if (context.StudentAnswers.ContainsKey(student.Id))
{ {
<MudCheckBox @bind-Value="context.StudentAnswers[student.Id]" Size="Size.Small" Color="Color.Primary"></MudCheckBox> <MudCheckBox @bind-Value="context.StudentAnswers[student.Id]" Size="Size.Small" Color="Color.Primary"></MudCheckBox>
} }
else else
{ {
<MudText Color="Color.Warning">N/A</MudText> <MudText Color="Color.Warning">N/A</MudText>
} }
</MudTd> </MudTd>
} }
</RowTemplate> </RowTemplate>
<PagerContent> <PagerContent>
<MudTablePager /> <MudTablePager />
</PagerContent> </PagerContent>
</MudTable> </MudTable>
<MudPaper Class="pa-4 mt-4 mud-elevation-2 d-flex flex-column align-end"> <MudPaper Class="pa-4 mt-4 mud-elevation-2 d-flex flex-column align-end">
<MudText Typo="Typo.h6">学生总分预览:</MudText> <MudText Typo="Typo.h6">学生总分预览:</MudText>
@foreach (var student in _students) @foreach (var student in _students)
{ {
<MudText Typo="Typo.subtitle1"> <MudText Typo="Typo.subtitle1">
@student.Name: <MudText Typo="Typo.h5" Color="Color.Primary" Class="d-inline-block ml-2">@GetStudentTotalScore(student.Id)</MudText> @student.DisplayName: <MudText Typo="Typo.h5" Color="Color.Primary" Class="d-inline-block ml-2">@GetStudentTotalScore(student.Id)</MudText>
</MudText> </MudText>
} }
<MudButton Variant="Variant.Filled" Color="Color.Success" Class="mt-4" @onclick="SubmitGrading"> <MudButton Variant="Variant.Filled" Color="Color.Success" Class="mt-4" @onclick="SubmitGrading">
提交批改结果 (模拟) 提交批改结果 (模拟)
</MudButton> </MudButton>
</MudPaper> </MudPaper>
} }
else else
{ {
<MudAlert Severity="Severity.Info" Class="mt-4">无法加载试卷或题目信息。</MudAlert> <MudAlert Severity="Severity.Info" Class="mt-4">无法加载试卷或题目信息。</MudAlert>
<MudButton Variant="Variant.Text" Color="Color.Primary" Class="mt-4" >返回试卷列表</MudButton> <MudButton Variant="Variant.Text" Color="Color.Primary" Class="mt-4">返回试卷列表</MudButton>
} }
@code { @code {
[Parameter] [Parameter]
public string ExamId { get; set; } public string ExamId { get; set; }
[Inject] [Inject]
public IExamService ExamService { get; set; } public IExamService ExamService { get; set; }
[Inject] [Inject]
private ISnackbar Snackbar { get; set; } private ISnackbar Snackbar { get; set; }
[Inject] [Inject]
private NavigationManager Navigation { get; set; } private NavigationManager Navigation { get; set; }
private MudTable<QuestionRowData> _table = new(); private MudTable<QuestionRowData> _table = new();
private AssignmentDto Assignment { get; set; } = new AssignmentDto(); private AssignmentDto Assignment { get; set; } = new AssignmentDto();
private ExamStruct _examStruct = new ExamStruct(); private AssignmentCheckData _examStruct = new AssignmentCheckData();
private List<Student> _students = new List<Student>(); private List<StudentDto> _students = new List<StudentDto>();
private List<QuestionRowData> _questionsForTable = new List<QuestionRowData>(); private List<QuestionRowData> _questionsForTable = new List<QuestionRowData>();
private bool _isLoading = true; private bool _isLoading = true;
[Inject]
public IClassServices ClassServices { get; set; }
protected override async Task OnInitializedAsync()
{
_isLoading = true;
await LoadExamData();
var result = await ClassServices.GetClassStudents();
if (!result.Status) Snackbar.Add($"获取学生失败, {result.Message}", Severity.Error);
_students = result.Result as List<StudentDto> ?? new List<StudentDto>();
BuildTable();
_isLoading = false;
}
private void BuildTable()
{
_questionsForTable = _examStruct.Questions.Select(q =>
{
var rowData = new QuestionRowData
{
QuestionItem = q,
StudentAnswers = new Dictionary<Guid, bool>()
};
foreach (var student in _students)
{
rowData.StudentAnswers[student.Id] = false;
}
return rowData;
}).ToList();
}
private async Task LoadExamData()
{
if (Guid.TryParse(ExamId, out Guid parsedExamId))
{
try
{
var result = await ExamService.GetExam(parsedExamId);
if (result.Status)
{
Assignment = result.Result as AssignmentDto ?? new AssignmentDto();
_examStruct = Assignment.GetStruct();
}
else
{
Snackbar?.Add($"获取试卷失败: {result.Message}", Severity.Error);
Navigation.NavigateTo("/exam/manager");
}
}
catch (Exception ex)
{
Console.Error.WriteLine($"获取试卷时发生错误: {ex.Message}");
Snackbar?.Add($"获取试卷失败: {ex.Message}", Severity.Error);
Navigation.NavigateTo("/exam/manager");
}
}
else
{
Console.Error.WriteLine($"错误:路由参数 ExamId '{ExamId}' 不是一个有效的 GUID 格式。");
Snackbar?.Add("无效的试卷ID无法加载。", Severity.Error);
Navigation.NavigateTo("/exam/manager");
}
}
protected override async Task OnInitializedAsync()
{ private float GetStudentTotalScore(Guid studentId)
_isLoading = true; {
await LoadExamData(); float totalScore = 0;
GenerateTemporaryStudentsAndAnswers(); foreach (var row in _questionsForTable)
_isLoading = false; {
} if (row.StudentAnswers.TryGetValue(studentId, out bool isCorrect) && isCorrect)
{
totalScore += row.QuestionItem.Score;
}
}
return totalScore;
}
private void ToggleStudentAllAnswers(Guid studentId)
{
bool allCorrect = _questionsForTable.All(row => row.StudentAnswers.ContainsKey(studentId) && row.StudentAnswers[studentId]);
foreach (var row in _questionsForTable)
{
if (row.StudentAnswers.ContainsKey(studentId))
{
row.StudentAnswers[studentId] = !allCorrect;
}
}
StateHasChanged();
}
private void SubmitGrading()
{
List<SubmissionDto> submissionDto = new List<SubmissionDto>();
private async Task LoadExamData() foreach (var student in _students)
{ {
if (Guid.TryParse(ExamId, out Guid parsedExamId)) var newSubmission = new SubmissionDto();
{ newSubmission.StudentId = student.Id;
try newSubmission.AssignmentId = Assignment.Id;
{ newSubmission.SubmissionTime = DateTime.Now;
var result = await ExamService.GetExam(parsedExamId); newSubmission.Status = Entities.Contracts.SubmissionStatus.Submitted;
if (result.Status)
{
Assignment = result.Result as AssignmentDto ?? new AssignmentDto();
_examStruct = Assignment.GetStruct();
}
else
{
Snackbar?.Add($"获取试卷失败: {result.Message}", Severity.Error);
Navigation.NavigateTo("/exam/manager");
}
}
catch (Exception ex)
{
Console.Error.WriteLine($"获取试卷时发生错误: {ex.Message}");
Snackbar?.Add($"获取试卷失败: {ex.Message}", Severity.Error);
Navigation.NavigateTo("/exam/manager");
}
}
else
{
Console.Error.WriteLine($"错误:路由参数 ExamId '{ExamId}' 不是一个有效的 GUID 格式。");
Snackbar?.Add("无效的试卷ID无法加载。", Severity.Error);
Navigation.NavigateTo("/exam/manager");
}
}
// 生成临时学生和作答数据
private void GenerateTemporaryStudentsAndAnswers()
{
_students = new List<Student>();
// 生成 40 个学生
for (int i = 1; i <= 40; i++)
{
_students.Add(new Student { Name = $"学生{i}" });
}
_questionsForTable = _examStruct.Questions.Select(qItem => foreach (var row in _questionsForTable)
{ {
var rowData = new QuestionRowData if (row.QuestionItem.AssignmentQuestionDto.StructType == Entities.Contracts.AssignmentStructType.Struct) continue;
{ if (row.StudentAnswers.TryGetValue(student.Id, out bool isCorrect))
QuestionItem = qItem, {
StudentAnswers = new Dictionary<Guid, bool>() newSubmission.SubmissionDetails.Add(new SubmissionDetailDto
}; {
IsCorrect = isCorrect,
StudentId = student.Id,
AssignmentQuestionId = row.QuestionItem.AssignmentQuestionDto.Id,
PointsAwarded = isCorrect ? row.QuestionItem.AssignmentQuestionDto.Score : 0
});
// 为每个学生随机生成初始的对错状态 newSubmission.OverallGrade += isCorrect ? row.QuestionItem.AssignmentQuestionDto.Score : 0;
var random = new Random(); }
foreach (var student in _students) }
{ submissionDto.Add(newSubmission);
// 模拟随机对错50%的概率 }
rowData.StudentAnswers[student.Id] = random.Next(0, 2) == 1;
}
return rowData;
}).ToList();
}
// 当某个学生的某个题目的作答状态改变时触发 submissionDto.ForEach(async s =>
private void OnAnswerChanged(string questionSequence, Guid studentId, bool isCorrect) {
{ Snackbar?.Add($"正在提交: {_students.FirstOrDefault(std => std.Id == s.StudentId)?.DisplayName} 的试卷", Severity.Info);
// 可以在这里添加额外的逻辑,例如记录更改 await ExamService.SubmissionAssignment(s);
Console.WriteLine($"题目 {questionSequence}, 学生 {studentId} 的答案变为: {isCorrect}"); });
// 由于是 @bind-Checked数据模型已经自动更新这里只是日志
}
// 计算某个学生的总分
private float GetStudentTotalScore(Guid studentId)
{
float totalScore = 0;
foreach (var row in _questionsForTable)
{
if (row.StudentAnswers.TryGetValue(studentId, out bool isCorrect) && isCorrect)
{
totalScore += row.QuestionItem.Score;
}
}
return totalScore;
}
// 切换某个学生所有题目的对错状态 (用于快速批改)
private void ToggleStudentAllAnswers(Guid studentId)
{
bool allCorrect = _questionsForTable.All(row => row.StudentAnswers.ContainsKey(studentId) && row.StudentAnswers[studentId]);
foreach (var row in _questionsForTable) Snackbar?.Add("批改结果已提交(模拟)", Severity.Success);
{ }
if (row.StudentAnswers.ContainsKey(studentId))
{
row.StudentAnswers[studentId] = !allCorrect; // 全部取反
}
}
StateHasChanged(); // 手动通知 Blazor 刷新 UI
}
// 提交批改结果(模拟)
private void SubmitGrading()
{
Console.WriteLine("--- 提交批改结果 ---");
foreach (var student in _students)
{
Console.WriteLine($"学生: {student.Name}, 总分: {GetStudentTotalScore(student.Id)}");
foreach (var row in _questionsForTable)
{
if (row.StudentAnswers.TryGetValue(student.Id, out bool isCorrect))
{
Console.WriteLine($" - 题目 {row.QuestionItem.Sequence}: {(isCorrect ? "正确" : "错误")}");
}
}
}
Snackbar?.Add("批改结果已提交(模拟)", Severity.Success);
// 实际应用中,这里会将 _questionsForTable 和 _students 的数据发送到后端API
}
} }

View File

@@ -49,7 +49,7 @@ else
{ {
isloding = true; isloding = true;
Snackbar.Add("正在加载", Severity.Info); Snackbar.Add("正在加载", Severity.Info);
var result = await ExamService.GetAllExam(authenticationStateTask.Result.User.Identity.Name); var result = await ExamService.GetAllExam();
examDtos = result.Result as List<AssignmentDto> ?? new List<AssignmentDto>(); examDtos = result.Result as List<AssignmentDto> ?? new List<AssignmentDto>();
isloding = false; isloding = false;
Snackbar.Add("加载成功", Severity.Info); Snackbar.Add("加载成功", Severity.Info);

View File

@@ -12,7 +12,11 @@
<MudButton OnClick="ExamClick"> 详情 </MudButton> <MudButton OnClick="ExamClick"> 详情 </MudButton>
<MudIconButton Icon="@Icons.Material.Filled.Delete" aria-label="delete" /> <MudIconButton Icon="@Icons.Material.Filled.Delete" aria-label="delete" />
<MudIconButton Icon="@Icons.Custom.Brands.GitHub" OnClick="CheckExam" Color="Color.Primary" aria-label="github" /> @if (bteacher)
{
<MudIconButton Icon="@Icons.Material.Filled.Check" OnClick="CheckExam" Color="Color.Primary" aria-label="github" />
}
<MudIconButton Icon="@Icons.Material.Filled.Favorite" Color="Color.Secondary" aria-label="add to favorite" /> <MudIconButton Icon="@Icons.Material.Filled.Favorite" Color="Color.Secondary" aria-label="add to favorite" />
</MudButtonGroup> </MudButtonGroup>
</MudPaper> </MudPaper>
@@ -21,6 +25,10 @@
</MudPaper> </MudPaper>
@code { @code {
[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; }
private bool bteacher = false;
[Inject] [Inject]
public NavigationManager navigationManager { get; set; } public NavigationManager navigationManager { get; set; }
@@ -45,6 +53,12 @@
public string? MaxHeight { get; set; } = "64"; public string? MaxHeight { get; set; } = "64";
protected override Task OnInitializedAsync()
{
bteacher = authenticationStateTask.Result.User.IsInRole("Teacher");
return base.OnInitializedAsync();
}
private void ExamClick() private void ExamClick()
{ {
navigationManager.NavigateTo($"exam/edit/{AssignmentDto.Id}"); navigationManager.NavigateTo($"exam/edit/{AssignmentDto.Id}");

View File

@@ -1,118 +1,10 @@
@page "/" @page "/"
@using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Authorization
<AuthorizeView Roles="Administrator"> <TechHelper.Client.Pages.Teacher.StudentsView/>
<MudText> Hello @context.User.Identity.Name</MudText>
@foreach (var item in context.User.Claims)
{
<MudPaper class="ma-2 pa-2">
<MudText> @item.Value </MudText>
<MudText> @item.Issuer </MudText>
<MudText> @item.Subject </MudText>
<MudText> @item.Properties </MudText>
<MudText> @item.ValueType </MudText>
</MudPaper>
}
Welcome to your new app. @code {
</AuthorizeView> [CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; }
<MudText>Hello </MudText> }
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>
<MudText>Hello </MudText>

View File

@@ -0,0 +1,28 @@
@using Entities.Contracts
@using Entities.DTO
@using TechHelper.Client.Services
<h3>StudentsView</h3>
@foreach(var cs in ClassStudents)
{
<MudText> @cs.DisplayName </MudText>
}
@code {
[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; }
private List<StudentDto> ClassStudents { get; set; } = new List<StudentDto>();
[Inject]
public IClassServices ClassServices { get; set; }
protected override async Task OnInitializedAsync()
{
var result = await ClassServices.GetClassStudents();
ClassStudents = result.Result as List<StudentDto> ?? new List<StudentDto>();
StateHasChanged();
}
}

View File

@@ -1,6 +1,9 @@
using Entities.DTO; using Entities.Contracts;
using Entities.DTO;
using Newtonsoft.Json;
using System.Net.Http.Json; using System.Net.Http.Json;
using TechHelper.Client.HttpRepository; using TechHelper.Client.HttpRepository;
using TechHelper.Services;
namespace TechHelper.Client.Services namespace TechHelper.Client.Services
{ {
@@ -20,6 +23,21 @@ namespace TechHelper.Client.Services
throw new NotImplementedException(); throw new NotImplementedException();
} }
public async Task<ApiResponse> GetClassStudents()
{
try
{
var result = await _client.PostAsJsonAsync("class/getClassStudents","");
var content = await result.Content.ReadAsStringAsync();
var users = JsonConvert.DeserializeObject<List<StudentDto>>(content);
return ApiResponse.Success(result: users);
}
catch(Exception ex)
{
return ApiResponse.Error($"获取失败,{ex.Message}, InnerException: {ex.InnerException}");
}
}
public async Task<ResponseDto> UserRegister(UserRegistrationToClassDto userRegistrationToClassDto) public async Task<ResponseDto> UserRegister(UserRegistrationToClassDto userRegistrationToClassDto)
{ {
try try

View File

@@ -9,13 +9,13 @@ namespace TechHelper.Client.Services
{ {
public class ExamService : IExamService public class ExamService : IExamService
{ {
private readonly IAIService _aIService; private readonly IAIService _aIService;
private readonly HttpClient _client; private readonly HttpClient _client;
public ExamService(IAIService aIService, HttpClient client) public ExamService(IAIService aIService, HttpClient client)
{ {
_aIService = aIService; _aIService = aIService;
_client = client; _client = client;
} }
public ApiResponse ConvertToXML<T>(string xmlContent) public ApiResponse ConvertToXML<T>(string xmlContent)
@@ -86,9 +86,9 @@ namespace TechHelper.Client.Services
} }
} }
public async Task<ApiResponse> GetAllExam(string user) public async Task<ApiResponse> GetAllExam()
{ {
var response = await _client.GetAsync($"exam/getAllPreview?user={user}"); var response = await _client.GetAsync($"exam/getAllPreview");
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
@@ -104,6 +104,25 @@ namespace TechHelper.Client.Services
} }
} }
public async Task<ApiResponse> GetAllSubmission()
{
try
{
var response = await _client.GetAsync($"exam/getAllSubmission");
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
var exam = JsonConvert.DeserializeObject<AssignmentDto>(content);
return ApiResponse.Success();
}
return ApiResponse.Error(message: "获取失败");
}
catch (Exception ex)
{
return ApiResponse.Error(message: $"内部错误{ex.Message}, InerEx{ex.InnerException}");
}
}
public async Task<ApiResponse> GetExam(Guid guid) public async Task<ApiResponse> GetExam(Guid guid)
{ {
@@ -158,5 +177,18 @@ namespace TechHelper.Client.Services
return ApiResponse.Error(message: $"保存试题失败: {response.StatusCode} - {errorContent}"); return ApiResponse.Error(message: $"保存试题失败: {response.StatusCode} - {errorContent}");
} }
} }
public async Task<ApiResponse> SubmissionAssignment(SubmissionDto submission)
{
var response = await _client.PostAsJsonAsync("exam/submission", submission);
if (response.IsSuccessStatusCode)
{
return ApiResponse.Success("提交成功");
}
else
{
return ApiResponse.Error("提交失败");
}
}
} }
} }

View File

@@ -8,5 +8,6 @@ namespace TechHelper.Client.Services
{ {
public Task<ResponseDto> UserRegister(UserRegistrationToClassDto userRegistrationToClassDto); public Task<ResponseDto> UserRegister(UserRegistrationToClassDto userRegistrationToClassDto);
public Task<ResponseDto> CreateClass(UserRegistrationToClassDto userClass); public Task<ResponseDto> CreateClass(UserRegistrationToClassDto userClass);
public Task<ApiResponse> GetClassStudents();
} }
} }

View File

@@ -11,7 +11,9 @@ namespace TechHelper.Client.Services
public Task<ApiResponse> ParseSingleQuestionGroup(string examContent); public Task<ApiResponse> ParseSingleQuestionGroup(string examContent);
public ApiResponse ConvertToXML<T>(string xmlContent); public ApiResponse ConvertToXML<T>(string xmlContent);
public Task<ApiResponse> GetAllExam(string user); public Task<ApiResponse> GetAllExam();
public Task<ApiResponse> GetExam(Guid guid); public Task<ApiResponse> GetExam(Guid guid);
public Task<ApiResponse> SubmissionAssignment(SubmissionDto submission);
public Task<ApiResponse> GetAllSubmission();
} }
} }

View File

@@ -36,20 +36,22 @@ namespace TechHelper.Context
// Assignment
CreateMap<AssignmentDto, Assignment>().ReverseMap();
CreateMap<Assignment, AssignmentDto>(); CreateMap<AssignmentQuestionDto, AssignmentQuestion>().ReverseMap();
CreateMap<QuestionDto, Question>().ReverseMap();
CreateMap<QuestionContext, QuestionContextDto>().ReverseMap(); CreateMap<QuestionContext, QuestionContextDto>().ReverseMap();
CreateMap<AssignmentQuestion, AssignmentQuestionDto>();
CreateMap<Question, QuestionDto>();
CreateMap<AssignmentDto, Assignment>();
CreateMap<AssignmentQuestionDto, AssignmentQuestion>(); // Submission
CreateMap<SubmissionDto, Submission>().ReverseMap();
CreateMap<QuestionDto, Question>(); CreateMap<SubmissionDetailDto, SubmissionDetail>().ReverseMap();
} }
} }

View File

@@ -57,26 +57,13 @@ namespace TechHelper.Context.Configuration
.HasForeignKey(a => a.CreatorId) .HasForeignKey(a => a.CreatorId)
.IsRequired(); // CreatedBy 是必填的,对应 [Required] 在外键属性上 .IsRequired(); // CreatedBy 是必填的,对应 [Required] 在外键属性上
// 关系: Assignment (一) 到 AssignmentClass (多)
// 假设 AssignmentClass 实体包含一个名为 AssignmentId 的外键属性
builder.HasMany(a => a.AssignmentClasses)
.WithOne(ac => ac.Assignment) // AssignmentClass 没有指向 Assignment 的导航属性 (或我们不知道)
.HasForeignKey("AssignmentId") // 指定外键名称为 AssignmentId
.OnDelete(DeleteBehavior.Cascade); // 如果 Assignment 被删除,关联的 AssignmentClass 也会被删除
// 关系: Assignment (一) 到 AssignmentAttachment (多)
// 假设 AssignmentAttachment 实体包含一个名为 AssignmentId 的外键属性
builder.HasMany(a => a.AssignmentAttachments)
.WithOne(aa => aa.Assignment)
.HasForeignKey("AssignmentId")
.OnDelete(DeleteBehavior.Cascade);
// 关系: Assignment (一) 到 Submission (多) builder.HasOne(a=>a.ExamStruct)
// 假设 Submission 实体包含一个名为 AssignmentId 的外键属性 .WithOne()
builder.HasMany(a => a.Submissions) .HasForeignKey<Assignment>(a=>a.ExamStructId)
.WithOne(s => s.Assignment) .OnDelete(DeleteBehavior.Cascade);
.HasForeignKey("AssignmentId")
.OnDelete(DeleteBehavior.Cascade);
} }
} }
} }

View File

@@ -42,35 +42,24 @@ namespace TechHelper.Context.Configuration
.HasColumnName("deleted") .HasColumnName("deleted")
.HasDefaultValue(false); // 适用于软删除策略 .HasDefaultValue(false); // 适用于软删除策略
// 4. 配置导航属性和外键关系
// --- builder.HasOne(aq => aq.Question)
// 配置 AssignmentQuestion 到 Question 的关系 (多对一) .WithMany(q => q.AssignmentQuestions)
// 一个 AssignmentQuestion 属于一个 Question。 .HasForeignKey(aq => aq.QuestionId)
// .OnDelete(DeleteBehavior.Cascade);
// 假设 `Question` 实体中有一个名为 `AssignmentQuestions` 的 `ICollection<AssignmentQuestion>` 集合属性。
builder.HasOne(aq => aq.Question) // 当前 AssignmentQuestion 有一个 Question
.WithMany(q => q.AssignmentQuestions) // 那个 Question 可以有多个 AssignmentQuestion
.HasForeignKey(aq => aq.QuestionId) // 外键是 AssignmentQuestion.QuestionId
.OnDelete(DeleteBehavior.Cascade); // 当 Question 被删除时,相关的 AssignmentQuestion 也级联删除。
builder.HasOne(aq => aq.ParentAssignmentQuestion)
.WithMany(aq => aq.ChildrenAssignmentQuestion)
.HasForeignKey(aq => aq.ParentAssignmentQuestionId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne(aq => aq.QuestionContext) builder.HasOne(aq => aq.QuestionContext)
.WithMany(qc => qc.Questions) .WithMany(qc => qc.Questions)
.HasForeignKey(aq => aq.QuestionContextId) .HasForeignKey(aq => aq.QuestionContextId)
.OnDelete(DeleteBehavior.SetNull); .OnDelete(DeleteBehavior.SetNull);
// ---
// 配置 AssignmentQuestion 到 SubmissionDetail 的关系 (一对多)
// 一个 AssignmentQuestion 可以有多个 SubmissionDetail。
//
// 这个关系通常从 "多" 的一方(`SubmissionDetail` 实体)来配置外键。
// 假设 `SubmissionDetail` 实体有一个 `AssignmentQuestionId` 外键和 `AssignmentQuestion` 导航属性。
builder.HasMany(aq => aq.SubmissionDetails) // 当前 AssignmentQuestion 有多个 SubmissionDetail
.WithOne(sd => sd.AssignmentQuestion); // 每一个 SubmissionDetail 都有一个 AssignmentQuestion
// .HasForeignKey(sd => sd.AssignmentQuestionId); // 外键的配置应在 `SubmissionDetailConfiguration` 中进行
}
}
}
}
} }

View File

@@ -75,16 +75,13 @@ namespace TechHelper.Context.Configuration
builder.HasOne(s => s.Student) // 当前 Submission 有一个 Student (User) builder.HasOne(s => s.Student) // 当前 Submission 有一个 Student (User)
.WithMany(u => u.SubmissionsAsStudent) // 那个 User (Student) 可以有多个 Submission .WithMany(u => u.SubmissionsAsStudent) // 那个 User (Student) 可以有多个 Submission
.HasForeignKey(s => s.StudentId) // 外键是 Submission.StudentId .HasForeignKey(s => s.StudentId) // 外键是 Submission.StudentId
.OnDelete(DeleteBehavior.Restrict); // 当 User (Student) 被删除时,如果还有其提交的 Submission则会阻止删除。 .OnDelete(DeleteBehavior.Cascade); // 当 User (Student) 被删除时,如果还有其提交的 Submission则会阻止删除。
builder.HasOne(s => s.Grader) // 当前 Submission 有一个 Grader (User),可以是空的 builder.HasOne(s => s.Grader) // 当前 Submission 有一个 Grader (User),可以是空的
.WithMany(u => u.GradedSubmissions) // 那个 User (Grader) 可以批改多个 Submission .WithMany(u => u.GradedSubmissions) // 那个 User (Grader) 可以批改多个 Submission
.HasForeignKey(s => s.GraderId) // 外键是 Submission.GradedBy .HasForeignKey(s => s.GraderId) // 外键是 Submission.GradedBy
.OnDelete(DeleteBehavior.SetNull); // 当 User (Grader) 被删除时,如果 GradedBy 是可空的,则将其设置为 NULL。 .OnDelete(DeleteBehavior.SetNull); // 当 User (Grader) 被删除时,如果 GradedBy 是可空的,则将其设置为 NULL。
builder.HasMany(s => s.SubmissionDetails) // 当前 Submission 有多个 SubmissionDetail
.WithOne(sd => sd.Submission); // 每一个 SubmissionDetail 都有一个 Submission
} }
} }
} }

View File

@@ -64,7 +64,7 @@ namespace TechHelper.Context.Configuration
builder.HasOne(sd => sd.Student) // 当前 SubmissionDetail 有一个 User (作为学生) builder.HasOne(sd => sd.Student) // 当前 SubmissionDetail 有一个 User (作为学生)
.WithMany(u => u.SubmissionDetails) .WithMany(u => u.SubmissionDetails)
.HasForeignKey(sd => sd.StudentId) // 外键是 SubmissionDetail.StudentId .HasForeignKey(sd => sd.StudentId) // 外键是 SubmissionDetail.StudentId
.OnDelete(DeleteBehavior.Restrict); // 当 User (学生) 被删除时,如果他/她还有提交详情,则会阻止删除。 .OnDelete(DeleteBehavior.Cascade); // 当 User (学生) 被删除时,如果他/她还有提交详情,则会阻止删除。
// 这是一个更安全的选择,以防止意外数据丢失。 // 这是一个更安全的选择,以防止意外数据丢失。
builder.HasOne(sd => sd.AssignmentQuestion) // 当前 SubmissionDetail 有一个 AssignmentQuestion builder.HasOne(sd => sd.AssignmentQuestion) // 当前 SubmissionDetail 有一个 AssignmentQuestion

View File

@@ -1,7 +1,10 @@
using Entities.DTO; using Entities.Contracts;
using Entities.DTO;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System.Net; using System.Net;
using System.Security.Claims;
using TechHelper.Services; using TechHelper.Services;
namespace TechHelper.Server.Controllers namespace TechHelper.Server.Controllers
@@ -11,25 +14,76 @@ namespace TechHelper.Server.Controllers
public class ClassController : ControllerBase public class ClassController : ControllerBase
{ {
private IClassService _classService; private IClassService _classService;
private UserManager<User> _userManager;
public ClassController(IClassService classService) public ClassController(IClassService classService, UserManager<User> userManager)
{ {
_classService = classService; _classService = classService;
_userManager = userManager;
} }
[HttpPost("userRegiste")] [HttpPost("userRegiste")]
public async Task<IActionResult> UserRegisterToClass( public async Task<IActionResult> UserRegisterToClass(
[FromBody] UserRegistrationToClassDto toClass) [FromBody] UserRegistrationToClassDto toClass)
{ {
var result = await _classService.UserRegister(toClass); var result = await _classService.UserRegister(toClass);
if(!result.Status) return BadRequest(result.Message); if (!result.Status) return BadRequest(result.Message);
return Ok(); return Ok();
} }
[HttpPost("getClassStudents")]
public async Task<IActionResult> GetClassStudents()
{
if (User.IsInRole("Teacher"))
{
var gradeClaim = User.FindFirst("Grade")?.Value;
var classClaim = User.FindFirst("Class")?.Value;
if (string.IsNullOrEmpty(gradeClaim) || string.IsNullOrEmpty(classClaim))
{
return BadRequest("未识别到你加入的班级信息(年级或班级声明缺失)。");
}
if (!byte.TryParse(gradeClaim, out byte grade) || !byte.TryParse(classClaim, out byte cla))
{
return BadRequest("你班级或年级信息格式不正确。");
}
var classDto = new ClassDto
{
Grade = grade,
Class = cla
};
var result = await _classService.GetClassStudents(classDto);
var css = result.Result as ICollection<ClassStudent>;
if(css == null) return BadRequest("你还没有学生");
List<StudentDto> sts = new List<StudentDto>();
css?.ToList().ForEach(s => sts.Add(new StudentDto
{
DisplayName = s.Student.DisplayName,
Id = s.Student.Id,
}));
if (!result.Status)
{
return BadRequest(result.Message);
}
return Ok(sts);
}
else
{
return BadRequest("你没有权限查看,未识别到你的教师身份。");
}
}
[HttpPost("Create")] [HttpPost("Create")]
public async Task<IActionResult> Create( public async Task<IActionResult> Create(
[FromBody] ClassDto classDto) [FromBody] ClassDto classDto)

View File

@@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using TechHelper.Server.Services; using TechHelper.Server.Services;
using System.Security.Claims; using System.Security.Claims;
using TechHelper.Services;
namespace TechHelper.Server.Controllers namespace TechHelper.Server.Controllers
@@ -31,7 +32,7 @@ namespace TechHelper.Server.Controllers
[FromBody] AssignmentDto examDto) [FromBody] AssignmentDto examDto)
{ {
var user = await _userManager.FindByEmailAsync(User.Identity?.Name ?? ""); var user = await _userManager.FindByEmailAsync(User.Identity?.Name ?? "");
if(user == null) return BadRequest("无效的用户"); if (user == null) return BadRequest("无效的用户");
examDto.CreatorId = user.Id; examDto.CreatorId = user.Id;
var result = await _examService.CreateExamAsync(examDto); var result = await _examService.CreateExamAsync(examDto);
@@ -45,6 +46,29 @@ namespace TechHelper.Server.Controllers
} }
} }
[HttpPost("submission")]
public async Task<IActionResult> SubmissionAssignment(
[FromBody] SubmissionDto submissionDto)
{
if (User == null) return BadRequest("无效的用户");
if (User.IsInRole("Teacher"))
{
var result = await _examService.SubmissionAssignment(submissionDto);
if (result.Status)
{
return Ok(result);
}
else
{
return BadRequest(result.Message);
}
}
else
{
return BadRequest("你没有权限修改");
}
}
[HttpGet("get")] [HttpGet("get")]
public async Task<IActionResult> GetExamById(Guid id) public async Task<IActionResult> GetExamById(Guid id)
{ {
@@ -58,16 +82,26 @@ namespace TechHelper.Server.Controllers
[HttpGet("getAllPreview")] [HttpGet("getAllPreview")]
public async Task<IActionResult> GetAllExamPreview(string user) public async Task<IActionResult> GetAllExamPreview()
{ {
string? userId = User.Identity.Name; if (User == null) return BadRequest("用户验证失败, 无效用户");
var userid = await _userManager.FindByEmailAsync(user); var userid = await _userManager.FindByEmailAsync(User.Identity.Name);
if (userid == null) return BadRequest("用户验证失败, 无效用户");
var result = new ApiResponse();
var result = await _examService.GetAllExamPreviewsAsync(userid.Id); if (User.IsInRole("Teacher"))
{
result = await _examService.GetAllExamPreviewsAsync(userid.Id);
}
else if (User.IsInRole("Student"))
{
result = await _examService.GetAllSubmissionAsync(userid.Id);
}
else
{
return BadRequest("你没有相应的权限");
}
if (result.Status) if (result.Status)
{ {
@@ -76,5 +110,34 @@ namespace TechHelper.Server.Controllers
return BadRequest(result); return BadRequest(result);
} }
[HttpGet("getAllSubmission")]
public async Task<IActionResult> GetAllSubmission()
{
if (User == null) return BadRequest("用户验证失败, 无效用户");
var userid = await _userManager.FindByEmailAsync(User.Identity.Name);
var result = await _examService.GetAllSubmissionAsync(userid.Id);
if (result.Status)
{
return Ok(result.Result);
}
return BadRequest(result);
}
[Authorize(Roles = "Teacher")]
[HttpDelete("{guid}")]
public async Task<IActionResult> DeleteAsync(Guid guid)
{
var deleteResult = await _examService.DeleteAsync(guid);
if (deleteResult.Status)
{
return Ok();
}
return BadRequest();
}
} }
} }

View File

@@ -0,0 +1,30 @@
using Entities.Contracts;
using Entities.DTO;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using TechHelper.Services;
namespace TechHelper.Server.Controllers
{
[Route("api/user")]
[ApiController]
public class UserController : ControllerBase
{
private IClassService _classService;
private UserManager<User> _userManager;
public UserController(IClassService classService, UserManager<User> userManager)
{
_classService = classService;
_userManager = userManager;
}
[HttpPost("get")]
public async Task<IActionResult> GetAsync(
[FromBody] UserRegistrationToClassDto toClass)
{
return Ok();
}
}
}

View File

@@ -1,95 +0,0 @@
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 up : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_class_teachers_AspNetUsers_teacher_id",
table: "class_teachers");
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("8b814a50-fd96-4bfa-a666-b247c0f13e22"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("ab2c8f8c-1ade-4ff5-9eb4-2925a89567b1"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("c6f92bbf-190f-47a5-b4d6-ed6874a378e8"));
migrationBuilder.InsertData(
table: "AspNetRoles",
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
values: new object[,]
{
{ new Guid("195b19c5-fd30-455c-9f38-9842b44bf5c3"), null, "Teacher", "TEACHER" },
{ new Guid("53cc63db-74bc-47a8-b71a-7e120d4018a9"), null, "Administrator", "ADMINISTRATOR" },
{ new Guid("a203eb76-97f0-418f-bc06-9549297d2ac3"), null, "Student", "STUDENT" }
});
migrationBuilder.AddForeignKey(
name: "FK_class_teachers_AspNetUsers_teacher_id",
table: "class_teachers",
column: "teacher_id",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_class_teachers_AspNetUsers_teacher_id",
table: "class_teachers");
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("195b19c5-fd30-455c-9f38-9842b44bf5c3"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("53cc63db-74bc-47a8-b71a-7e120d4018a9"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("a203eb76-97f0-418f-bc06-9549297d2ac3"));
migrationBuilder.InsertData(
table: "AspNetRoles",
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
values: new object[,]
{
{ new Guid("8b814a50-fd96-4bfa-a666-b247c0f13e22"), null, "Administrator", "ADMINISTRATOR" },
{ new Guid("ab2c8f8c-1ade-4ff5-9eb4-2925a89567b1"), null, "Student", "STUDENT" },
{ new Guid("c6f92bbf-190f-47a5-b4d6-ed6874a378e8"), null, "Teacher", "TEACHER" }
});
migrationBuilder.AddForeignKey(
name: "FK_class_teachers_AspNetUsers_teacher_id",
table: "class_teachers",
column: "teacher_id",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
}
}
}

View File

@@ -12,8 +12,8 @@ using TechHelper.Context;
namespace TechHelper.Server.Migrations namespace TechHelper.Server.Migrations
{ {
[DbContext(typeof(ApplicationContext))] [DbContext(typeof(ApplicationContext))]
[Migration("20250625032845_up")] [Migration("20250626073834_init")]
partial class up partial class init
{ {
/// <inheritdoc /> /// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder) protected override void BuildTargetModel(ModelBuilder modelBuilder)
@@ -512,7 +512,6 @@ namespace TechHelper.Server.Migrations
.HasColumnName("deleted"); .HasColumnName("deleted");
b.Property<string>("OverallFeedback") b.Property<string>("OverallFeedback")
.IsRequired()
.HasColumnType("longtext") .HasColumnType("longtext")
.HasColumnName("overall_feedback"); .HasColumnName("overall_feedback");
@@ -579,7 +578,6 @@ namespace TechHelper.Server.Migrations
.HasColumnName("points_awarded"); .HasColumnName("points_awarded");
b.Property<string>("StudentAnswer") b.Property<string>("StudentAnswer")
.IsRequired()
.HasColumnType("longtext") .HasColumnType("longtext")
.HasColumnName("student_answer"); .HasColumnName("student_answer");
@@ -592,7 +590,6 @@ namespace TechHelper.Server.Migrations
.HasColumnName("submission_id"); .HasColumnName("submission_id");
b.Property<string>("TeacherFeedback") b.Property<string>("TeacherFeedback")
.IsRequired()
.HasColumnType("longtext") .HasColumnType("longtext")
.HasColumnName("teacher_feedback"); .HasColumnName("teacher_feedback");
@@ -747,19 +744,19 @@ namespace TechHelper.Server.Migrations
b.HasData( b.HasData(
new new
{ {
Id = new Guid("a203eb76-97f0-418f-bc06-9549297d2ac3"), Id = new Guid("3cfe35e8-73d5-4170-9856-f1d078554822"),
Name = "Student", Name = "Student",
NormalizedName = "STUDENT" NormalizedName = "STUDENT"
}, },
new new
{ {
Id = new Guid("195b19c5-fd30-455c-9f38-9842b44bf5c3"), Id = new Guid("754c4967-6af2-4a81-b970-1e90a3a269b3"),
Name = "Teacher", Name = "Teacher",
NormalizedName = "TEACHER" NormalizedName = "TEACHER"
}, },
new new
{ {
Id = new Guid("53cc63db-74bc-47a8-b71a-7e120d4018a9"), Id = new Guid("8546457c-185c-4b79-bece-bc21e41d02e7"),
Name = "Administrator", Name = "Administrator",
NormalizedName = "ADMINISTRATOR" NormalizedName = "ADMINISTRATOR"
}); });

View File

@@ -323,7 +323,7 @@ namespace TechHelper.Server.Migrations
column: x => x.teacher_id, column: x => x.teacher_id,
principalTable: "AspNetUsers", principalTable: "AspNetUsers",
principalColumn: "Id", principalColumn: "Id",
onDelete: ReferentialAction.Restrict); onDelete: ReferentialAction.Cascade);
table.ForeignKey( table.ForeignKey(
name: "FK_class_teachers_classes_class_id", name: "FK_class_teachers_classes_class_id",
column: x => x.class_id, column: x => x.class_id,
@@ -565,7 +565,7 @@ namespace TechHelper.Server.Migrations
attempt_number = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), attempt_number = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
submission_time = table.Column<DateTime>(type: "datetime(6)", nullable: false), submission_time = table.Column<DateTime>(type: "datetime(6)", nullable: false),
overall_grade = table.Column<float>(type: "float", precision: 5, scale: 2, nullable: true), overall_grade = table.Column<float>(type: "float", precision: 5, scale: 2, nullable: true),
overall_feedback = table.Column<string>(type: "longtext", nullable: false) overall_feedback = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"), .Annotation("MySql:CharSet", "utf8mb4"),
graded_by = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"), graded_by = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
graded_at = table.Column<DateTime>(type: "datetime(6)", nullable: true), graded_at = table.Column<DateTime>(type: "datetime(6)", nullable: true),
@@ -604,11 +604,11 @@ namespace TechHelper.Server.Migrations
submission_id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), submission_id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
student_id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), student_id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
assignment_question_id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), assignment_question_id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
student_answer = table.Column<string>(type: "longtext", nullable: false) student_answer = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"), .Annotation("MySql:CharSet", "utf8mb4"),
is_correct = table.Column<bool>(type: "tinyint(1)", nullable: true), is_correct = table.Column<bool>(type: "tinyint(1)", nullable: true),
points_awarded = table.Column<float>(type: "float", precision: 5, scale: 2, nullable: true), points_awarded = table.Column<float>(type: "float", precision: 5, scale: 2, nullable: true),
teacher_feedback = table.Column<string>(type: "longtext", nullable: false) teacher_feedback = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"), .Annotation("MySql:CharSet", "utf8mb4"),
created_at = table.Column<DateTime>(type: "datetime(6)", nullable: false) created_at = table.Column<DateTime>(type: "datetime(6)", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
@@ -644,9 +644,9 @@ namespace TechHelper.Server.Migrations
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
values: new object[,] values: new object[,]
{ {
{ new Guid("8b814a50-fd96-4bfa-a666-b247c0f13e22"), null, "Administrator", "ADMINISTRATOR" }, { new Guid("3cfe35e8-73d5-4170-9856-f1d078554822"), null, "Student", "STUDENT" },
{ new Guid("ab2c8f8c-1ade-4ff5-9eb4-2925a89567b1"), null, "Student", "STUDENT" }, { new Guid("754c4967-6af2-4a81-b970-1e90a3a269b3"), null, "Teacher", "TEACHER" },
{ new Guid("c6f92bbf-190f-47a5-b4d6-ed6874a378e8"), null, "Teacher", "TEACHER" } { new Guid("8546457c-185c-4b79-bece-bc21e41d02e7"), null, "Administrator", "ADMINISTRATOR" }
}); });
migrationBuilder.CreateIndex( migrationBuilder.CreateIndex(

View File

@@ -12,8 +12,8 @@ using TechHelper.Context;
namespace TechHelper.Server.Migrations namespace TechHelper.Server.Migrations
{ {
[DbContext(typeof(ApplicationContext))] [DbContext(typeof(ApplicationContext))]
[Migration("20250624103910_init")] [Migration("20250627101025_upd")]
partial class init partial class upd
{ {
/// <inheritdoc /> /// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder) protected override void BuildTargetModel(ModelBuilder modelBuilder)
@@ -198,6 +198,11 @@ namespace TechHelper.Server.Migrations
.HasColumnType("float") .HasColumnType("float")
.HasColumnName("score"); .HasColumnName("score");
b.Property<string>("Sequence")
.IsRequired()
.HasColumnType("longtext")
.HasColumnName("sequence");
b.Property<byte>("StructType") b.Property<byte>("StructType")
.HasColumnType("tinyint unsigned") .HasColumnType("tinyint unsigned")
.HasColumnName("group_state"); .HasColumnName("group_state");
@@ -512,7 +517,6 @@ namespace TechHelper.Server.Migrations
.HasColumnName("deleted"); .HasColumnName("deleted");
b.Property<string>("OverallFeedback") b.Property<string>("OverallFeedback")
.IsRequired()
.HasColumnType("longtext") .HasColumnType("longtext")
.HasColumnName("overall_feedback"); .HasColumnName("overall_feedback");
@@ -579,7 +583,6 @@ namespace TechHelper.Server.Migrations
.HasColumnName("points_awarded"); .HasColumnName("points_awarded");
b.Property<string>("StudentAnswer") b.Property<string>("StudentAnswer")
.IsRequired()
.HasColumnType("longtext") .HasColumnType("longtext")
.HasColumnName("student_answer"); .HasColumnName("student_answer");
@@ -592,7 +595,6 @@ namespace TechHelper.Server.Migrations
.HasColumnName("submission_id"); .HasColumnName("submission_id");
b.Property<string>("TeacherFeedback") b.Property<string>("TeacherFeedback")
.IsRequired()
.HasColumnType("longtext") .HasColumnType("longtext")
.HasColumnName("teacher_feedback"); .HasColumnName("teacher_feedback");
@@ -747,19 +749,19 @@ namespace TechHelper.Server.Migrations
b.HasData( b.HasData(
new new
{ {
Id = new Guid("ab2c8f8c-1ade-4ff5-9eb4-2925a89567b1"), Id = new Guid("c310acf7-9605-4c55-8b9f-9bf9cd2dadb9"),
Name = "Student", Name = "Student",
NormalizedName = "STUDENT" NormalizedName = "STUDENT"
}, },
new new
{ {
Id = new Guid("c6f92bbf-190f-47a5-b4d6-ed6874a378e8"), Id = new Guid("5f0c1b3c-ad05-4ca9-b9fd-a359cb518236"),
Name = "Teacher", Name = "Teacher",
NormalizedName = "TEACHER" NormalizedName = "TEACHER"
}, },
new new
{ {
Id = new Guid("8b814a50-fd96-4bfa-a666-b247c0f13e22"), Id = new Guid("a81f5de2-9691-45fa-8d31-ae4ffeb34453"),
Name = "Administrator", Name = "Administrator",
NormalizedName = "ADMINISTRATOR" NormalizedName = "ADMINISTRATOR"
}); });
@@ -925,7 +927,8 @@ namespace TechHelper.Server.Migrations
{ {
b.HasOne("Entities.Contracts.AssignmentQuestion", "ParentAssignmentQuestion") b.HasOne("Entities.Contracts.AssignmentQuestion", "ParentAssignmentQuestion")
.WithMany("ChildrenAssignmentQuestion") .WithMany("ChildrenAssignmentQuestion")
.HasForeignKey("ParentAssignmentQuestionId"); .HasForeignKey("ParentAssignmentQuestionId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Entities.Contracts.QuestionContext", "QuestionContext") b.HasOne("Entities.Contracts.QuestionContext", "QuestionContext")
.WithMany("Questions") .WithMany("Questions")
@@ -985,7 +988,7 @@ namespace TechHelper.Server.Migrations
b.HasOne("Entities.Contracts.User", "Teacher") b.HasOne("Entities.Contracts.User", "Teacher")
.WithMany("TaughtClassesLink") .WithMany("TaughtClassesLink")
.HasForeignKey("TeacherId") .HasForeignKey("TeacherId")
.OnDelete(DeleteBehavior.Restrict) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Class"); b.Navigation("Class");
@@ -1067,7 +1070,7 @@ namespace TechHelper.Server.Migrations
b.HasOne("Entities.Contracts.User", "Student") b.HasOne("Entities.Contracts.User", "Student")
.WithMany("SubmissionsAsStudent") .WithMany("SubmissionsAsStudent")
.HasForeignKey("StudentId") .HasForeignKey("StudentId")
.OnDelete(DeleteBehavior.Restrict) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Assignment"); b.Navigation("Assignment");
@@ -1088,7 +1091,7 @@ namespace TechHelper.Server.Migrations
b.HasOne("Entities.Contracts.User", "Student") b.HasOne("Entities.Contracts.User", "Student")
.WithMany("SubmissionDetails") .WithMany("SubmissionDetails")
.HasForeignKey("StudentId") .HasForeignKey("StudentId")
.OnDelete(DeleteBehavior.Restrict) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("Entities.Contracts.Submission", "Submission") b.HasOne("Entities.Contracts.Submission", "Submission")

View File

@@ -0,0 +1,153 @@
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 upd : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_assignment_questions_assignment_questions_parent_question_gr~",
table: "assignment_questions");
migrationBuilder.DropForeignKey(
name: "FK_submission_details_AspNetUsers_student_id",
table: "submission_details");
migrationBuilder.DropForeignKey(
name: "FK_submissions_AspNetUsers_student_id",
table: "submissions");
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("3cfe35e8-73d5-4170-9856-f1d078554822"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("754c4967-6af2-4a81-b970-1e90a3a269b3"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("8546457c-185c-4b79-bece-bc21e41d02e7"));
migrationBuilder.AddColumn<string>(
name: "sequence",
table: "assignment_questions",
type: "longtext",
nullable: false)
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.InsertData(
table: "AspNetRoles",
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
values: new object[,]
{
{ new Guid("5f0c1b3c-ad05-4ca9-b9fd-a359cb518236"), null, "Teacher", "TEACHER" },
{ new Guid("a81f5de2-9691-45fa-8d31-ae4ffeb34453"), null, "Administrator", "ADMINISTRATOR" },
{ new Guid("c310acf7-9605-4c55-8b9f-9bf9cd2dadb9"), null, "Student", "STUDENT" }
});
migrationBuilder.AddForeignKey(
name: "FK_assignment_questions_assignment_questions_parent_question_gr~",
table: "assignment_questions",
column: "parent_question_group_id",
principalTable: "assignment_questions",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_submission_details_AspNetUsers_student_id",
table: "submission_details",
column: "student_id",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_submissions_AspNetUsers_student_id",
table: "submissions",
column: "student_id",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_assignment_questions_assignment_questions_parent_question_gr~",
table: "assignment_questions");
migrationBuilder.DropForeignKey(
name: "FK_submission_details_AspNetUsers_student_id",
table: "submission_details");
migrationBuilder.DropForeignKey(
name: "FK_submissions_AspNetUsers_student_id",
table: "submissions");
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("5f0c1b3c-ad05-4ca9-b9fd-a359cb518236"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("a81f5de2-9691-45fa-8d31-ae4ffeb34453"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("c310acf7-9605-4c55-8b9f-9bf9cd2dadb9"));
migrationBuilder.DropColumn(
name: "sequence",
table: "assignment_questions");
migrationBuilder.InsertData(
table: "AspNetRoles",
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
values: new object[,]
{
{ new Guid("3cfe35e8-73d5-4170-9856-f1d078554822"), null, "Student", "STUDENT" },
{ new Guid("754c4967-6af2-4a81-b970-1e90a3a269b3"), null, "Teacher", "TEACHER" },
{ new Guid("8546457c-185c-4b79-bece-bc21e41d02e7"), null, "Administrator", "ADMINISTRATOR" }
});
migrationBuilder.AddForeignKey(
name: "FK_assignment_questions_assignment_questions_parent_question_gr~",
table: "assignment_questions",
column: "parent_question_group_id",
principalTable: "assignment_questions",
principalColumn: "id");
migrationBuilder.AddForeignKey(
name: "FK_submission_details_AspNetUsers_student_id",
table: "submission_details",
column: "student_id",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
migrationBuilder.AddForeignKey(
name: "FK_submissions_AspNetUsers_student_id",
table: "submissions",
column: "student_id",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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 upde : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("5f0c1b3c-ad05-4ca9-b9fd-a359cb518236"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("a81f5de2-9691-45fa-8d31-ae4ffeb34453"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("c310acf7-9605-4c55-8b9f-9bf9cd2dadb9"));
migrationBuilder.InsertData(
table: "AspNetRoles",
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
values: new object[,]
{
{ new Guid("c0f247ab-b12a-432e-8ce7-d0e28811957e"), null, "Student", "STUDENT" },
{ new Guid("f282e759-deb5-4366-aaf1-51366131cf75"), null, "Administrator", "ADMINISTRATOR" },
{ new Guid("f9eeea07-eeda-4bbe-a2e4-6aef2f3c7c9a"), null, "Teacher", "TEACHER" }
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("c0f247ab-b12a-432e-8ce7-d0e28811957e"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("f282e759-deb5-4366-aaf1-51366131cf75"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("f9eeea07-eeda-4bbe-a2e4-6aef2f3c7c9a"));
migrationBuilder.InsertData(
table: "AspNetRoles",
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
values: new object[,]
{
{ new Guid("5f0c1b3c-ad05-4ca9-b9fd-a359cb518236"), null, "Teacher", "TEACHER" },
{ new Guid("a81f5de2-9691-45fa-8d31-ae4ffeb34453"), null, "Administrator", "ADMINISTRATOR" },
{ new Guid("c310acf7-9605-4c55-8b9f-9bf9cd2dadb9"), null, "Student", "STUDENT" }
});
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,102 @@
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 updedd : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("c0f247ab-b12a-432e-8ce7-d0e28811957e"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("f282e759-deb5-4366-aaf1-51366131cf75"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("f9eeea07-eeda-4bbe-a2e4-6aef2f3c7c9a"));
migrationBuilder.AddColumn<Guid>(
name: "AssignmentId",
table: "assignment_questions",
type: "char(36)",
nullable: true,
collation: "ascii_general_ci");
migrationBuilder.InsertData(
table: "AspNetRoles",
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
values: new object[,]
{
{ new Guid("cf16c215-63f8-4962-8ad0-058274ecf944"), null, "Administrator", "ADMINISTRATOR" },
{ new Guid("e3bff43c-36af-497a-971c-ed0a487bdd38"), null, "Student", "STUDENT" },
{ new Guid("f05c125e-e70f-40eb-9e19-6e69c3426849"), null, "Teacher", "TEACHER" }
});
migrationBuilder.CreateIndex(
name: "IX_assignment_questions_AssignmentId",
table: "assignment_questions",
column: "AssignmentId");
migrationBuilder.AddForeignKey(
name: "FK_assignment_questions_assignments_AssignmentId",
table: "assignment_questions",
column: "AssignmentId",
principalTable: "assignments",
principalColumn: "id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_assignment_questions_assignments_AssignmentId",
table: "assignment_questions");
migrationBuilder.DropIndex(
name: "IX_assignment_questions_AssignmentId",
table: "assignment_questions");
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("cf16c215-63f8-4962-8ad0-058274ecf944"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("e3bff43c-36af-497a-971c-ed0a487bdd38"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("f05c125e-e70f-40eb-9e19-6e69c3426849"));
migrationBuilder.DropColumn(
name: "AssignmentId",
table: "assignment_questions");
migrationBuilder.InsertData(
table: "AspNetRoles",
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
values: new object[,]
{
{ new Guid("c0f247ab-b12a-432e-8ce7-d0e28811957e"), null, "Student", "STUDENT" },
{ new Guid("f282e759-deb5-4366-aaf1-51366131cf75"), null, "Administrator", "ADMINISTRATOR" },
{ new Guid("f9eeea07-eeda-4bbe-a2e4-6aef2f3c7c9a"), null, "Teacher", "TEACHER" }
});
}
}
}

View File

@@ -165,6 +165,9 @@ namespace TechHelper.Server.Migrations
.HasColumnType("char(36)") .HasColumnType("char(36)")
.HasColumnName("id"); .HasColumnName("id");
b.Property<Guid?>("AssignmentId")
.HasColumnType("char(36)");
b.Property<DateTime>("CreatedAt") b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)") .HasColumnType("datetime(6)")
.HasColumnName("created_at"); .HasColumnName("created_at");
@@ -195,6 +198,11 @@ namespace TechHelper.Server.Migrations
.HasColumnType("float") .HasColumnType("float")
.HasColumnName("score"); .HasColumnName("score");
b.Property<string>("Sequence")
.IsRequired()
.HasColumnType("longtext")
.HasColumnName("sequence");
b.Property<byte>("StructType") b.Property<byte>("StructType")
.HasColumnType("tinyint unsigned") .HasColumnType("tinyint unsigned")
.HasColumnName("group_state"); .HasColumnName("group_state");
@@ -206,6 +214,8 @@ namespace TechHelper.Server.Migrations
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("AssignmentId");
b.HasIndex("ParentAssignmentQuestionId"); b.HasIndex("ParentAssignmentQuestionId");
b.HasIndex("QuestionContextId"); b.HasIndex("QuestionContextId");
@@ -509,7 +519,6 @@ namespace TechHelper.Server.Migrations
.HasColumnName("deleted"); .HasColumnName("deleted");
b.Property<string>("OverallFeedback") b.Property<string>("OverallFeedback")
.IsRequired()
.HasColumnType("longtext") .HasColumnType("longtext")
.HasColumnName("overall_feedback"); .HasColumnName("overall_feedback");
@@ -576,7 +585,6 @@ namespace TechHelper.Server.Migrations
.HasColumnName("points_awarded"); .HasColumnName("points_awarded");
b.Property<string>("StudentAnswer") b.Property<string>("StudentAnswer")
.IsRequired()
.HasColumnType("longtext") .HasColumnType("longtext")
.HasColumnName("student_answer"); .HasColumnName("student_answer");
@@ -589,7 +597,6 @@ namespace TechHelper.Server.Migrations
.HasColumnName("submission_id"); .HasColumnName("submission_id");
b.Property<string>("TeacherFeedback") b.Property<string>("TeacherFeedback")
.IsRequired()
.HasColumnType("longtext") .HasColumnType("longtext")
.HasColumnName("teacher_feedback"); .HasColumnName("teacher_feedback");
@@ -744,19 +751,19 @@ namespace TechHelper.Server.Migrations
b.HasData( b.HasData(
new new
{ {
Id = new Guid("a203eb76-97f0-418f-bc06-9549297d2ac3"), Id = new Guid("e3bff43c-36af-497a-971c-ed0a487bdd38"),
Name = "Student", Name = "Student",
NormalizedName = "STUDENT" NormalizedName = "STUDENT"
}, },
new new
{ {
Id = new Guid("195b19c5-fd30-455c-9f38-9842b44bf5c3"), Id = new Guid("f05c125e-e70f-40eb-9e19-6e69c3426849"),
Name = "Teacher", Name = "Teacher",
NormalizedName = "TEACHER" NormalizedName = "TEACHER"
}, },
new new
{ {
Id = new Guid("53cc63db-74bc-47a8-b71a-7e120d4018a9"), Id = new Guid("cf16c215-63f8-4962-8ad0-058274ecf944"),
Name = "Administrator", Name = "Administrator",
NormalizedName = "ADMINISTRATOR" NormalizedName = "ADMINISTRATOR"
}); });
@@ -874,7 +881,7 @@ namespace TechHelper.Server.Migrations
.IsRequired(); .IsRequired();
b.HasOne("Entities.Contracts.AssignmentQuestion", "ExamStruct") b.HasOne("Entities.Contracts.AssignmentQuestion", "ExamStruct")
.WithOne("Assignment") .WithOne()
.HasForeignKey("Entities.Contracts.Assignment", "ExamStructId") .HasForeignKey("Entities.Contracts.Assignment", "ExamStructId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
@@ -920,9 +927,14 @@ namespace TechHelper.Server.Migrations
modelBuilder.Entity("Entities.Contracts.AssignmentQuestion", b => modelBuilder.Entity("Entities.Contracts.AssignmentQuestion", b =>
{ {
b.HasOne("Entities.Contracts.Assignment", "Assignment")
.WithMany()
.HasForeignKey("AssignmentId");
b.HasOne("Entities.Contracts.AssignmentQuestion", "ParentAssignmentQuestion") b.HasOne("Entities.Contracts.AssignmentQuestion", "ParentAssignmentQuestion")
.WithMany("ChildrenAssignmentQuestion") .WithMany("ChildrenAssignmentQuestion")
.HasForeignKey("ParentAssignmentQuestionId"); .HasForeignKey("ParentAssignmentQuestionId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Entities.Contracts.QuestionContext", "QuestionContext") b.HasOne("Entities.Contracts.QuestionContext", "QuestionContext")
.WithMany("Questions") .WithMany("Questions")
@@ -934,6 +946,8 @@ namespace TechHelper.Server.Migrations
.HasForeignKey("QuestionId") .HasForeignKey("QuestionId")
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
b.Navigation("Assignment");
b.Navigation("ParentAssignmentQuestion"); b.Navigation("ParentAssignmentQuestion");
b.Navigation("Question"); b.Navigation("Question");
@@ -1064,7 +1078,7 @@ namespace TechHelper.Server.Migrations
b.HasOne("Entities.Contracts.User", "Student") b.HasOne("Entities.Contracts.User", "Student")
.WithMany("SubmissionsAsStudent") .WithMany("SubmissionsAsStudent")
.HasForeignKey("StudentId") .HasForeignKey("StudentId")
.OnDelete(DeleteBehavior.Restrict) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Assignment"); b.Navigation("Assignment");
@@ -1085,7 +1099,7 @@ namespace TechHelper.Server.Migrations
b.HasOne("Entities.Contracts.User", "Student") b.HasOne("Entities.Contracts.User", "Student")
.WithMany("SubmissionDetails") .WithMany("SubmissionDetails")
.HasForeignKey("StudentId") .HasForeignKey("StudentId")
.OnDelete(DeleteBehavior.Restrict) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("Entities.Contracts.Submission", "Submission") b.HasOne("Entities.Contracts.Submission", "Submission")
@@ -1163,8 +1177,6 @@ namespace TechHelper.Server.Migrations
modelBuilder.Entity("Entities.Contracts.AssignmentQuestion", b => modelBuilder.Entity("Entities.Contracts.AssignmentQuestion", b =>
{ {
b.Navigation("Assignment");
b.Navigation("ChildrenAssignmentQuestion"); b.Navigation("ChildrenAssignmentQuestion");
b.Navigation("SubmissionDetails"); b.Navigation("SubmissionDetails");

View File

@@ -12,6 +12,7 @@ using TechHelper.Features;
using TechHelper.Services; using TechHelper.Services;
using TechHelper.Server.Services; using TechHelper.Server.Services;
using TechHelper.Server.Repositories; using TechHelper.Server.Repositories;
using Microsoft.OpenApi.Models;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@@ -90,7 +91,35 @@ builder.Services.AddScoped<IExamRepository, ExamRepository>();
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(); builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Your API Name", Version = "v1" });
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Name = "Authorization",
Type = SecuritySchemeType.Http,
Scheme = "bearer",
BearerFormat = "JWT",
In = ParameterLocation.Header,
Description = "JWT Authorization header using the Bearer scheme. \r\n\r\n Enter 'Bearer' [space] and then your token in the text input below.\r\n\r\nExample: \"Bearer 12345abcdef\"",
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] {}
}
});
});
builder.Services.AddCors(options => builder.Services.AddCors(options =>

View File

@@ -17,7 +17,7 @@ namespace TechHelper.Server.Repositories
{ {
_unitOfWork = unitOfWork; _unitOfWork = unitOfWork;
_assignmentRepo = _unitOfWork.GetRepository<Assignment>(); _assignmentRepo = _unitOfWork.GetRepository<Assignment>();
_assignQuestionRepo = _unitOfWork.GetRepository<AssignmentQuestion>(); _assignQuestionRepo = _unitOfWork.GetRepository<AssignmentQuestion>();
} }
public async Task<Assignment?> GetFullExamByIdAsync(Guid assignmentId) public async Task<Assignment?> GetFullExamByIdAsync(Guid assignmentId)
@@ -29,18 +29,18 @@ namespace TechHelper.Server.Repositories
i => i.Include(a => a.ExamStruct) i => i.Include(a => a.ExamStruct)
); );
result.ExamStruct = await GetNeed(result.ExamStructId)?? null; result.ExamStruct = await GetNeed(result.ExamStructId) ?? null;
return result; return result;
} }
public async Task<AssignmentQuestion?> GetNeed(Guid id) public async Task<AssignmentQuestion?> GetNeed(Guid id)
{ {
var result = await _assignQuestionRepo.GetFirstOrDefaultAsync( var result = await _assignQuestionRepo.GetFirstOrDefaultAsync(
predicate: aq => aq.Id == id, predicate: aq => aq.Id == id,
include: i => i include: i => i
.Include(aq => aq.ChildrenAssignmentQuestion) .Include(aq => aq.ChildrenAssignmentQuestion)
.Include(aq => aq.Question) .Include(aq => aq.Question)
.ThenInclude(q => q.Lesson) .ThenInclude(q => q.Lesson)
.Include(aq => aq.Question) .Include(aq => aq.Question)
@@ -55,13 +55,13 @@ namespace TechHelper.Server.Repositories
var loadedChildren = new List<AssignmentQuestion>(); var loadedChildren = new List<AssignmentQuestion>();
foreach (var child in result.ChildrenAssignmentQuestion) foreach (var child in result.ChildrenAssignmentQuestion)
{ {
var loadedChild = await GetNeed(child.Id); var loadedChild = await GetNeed(child.Id);
if (loadedChild != null) if (loadedChild != null)
{ {
loadedChildren.Add(loadedChild); loadedChildren.Add(loadedChild);
} }
} }
result.ChildrenAssignmentQuestion = loadedChildren; result.ChildrenAssignmentQuestion = loadedChildren;
return result; return result;
} }
@@ -90,5 +90,18 @@ namespace TechHelper.Server.Repositories
public async Task AddAsync(AssignmentClass assignment) public async Task AddAsync(AssignmentClass assignment)
{ {
} }
public async Task AddAsync(Submission submission)
{
await _unitOfWork.GetRepository<Submission>().InsertAsync(submission);
}
public async Task<IEnumerable<Assignment>> GetAllSubmissionPreviewsByUserAsync(Guid id)
{
var submissions = await _unitOfWork.GetRepository<Submission>().GetAllAsync(predicate: s => s.StudentId == id, include: i => i.Include(s => s.Assignment));
if (submissions == null || !submissions.Any())
return Enumerable.Empty<Assignment>();
return submissions.ToList().Select(s => s.Assignment).Where(a => a != null).Distinct().ToList();
}
} }
} }

View File

@@ -1,4 +1,5 @@
using Entities.Contracts; using Entities.Contracts;
using Entities.DTO;
namespace TechHelper.Server.Repositories namespace TechHelper.Server.Repositories
{ {
@@ -23,6 +24,7 @@ namespace TechHelper.Server.Repositories
/// </summary> /// </summary>
/// <param name="assignment">要添加的试卷实体。</param> /// <param name="assignment">要添加的试卷实体。</param>
Task AddAsync(Assignment assignment); Task AddAsync(Assignment assignment);
Task AddAsync(Submission submission);
@@ -32,6 +34,6 @@ namespace TechHelper.Server.Repositories
Task AddAsync(Question assignment); Task AddAsync(Question assignment);
Task AddAsync(AssignmentClass assignment); Task AddAsync(AssignmentClass assignment);
Task<IEnumerable<Assignment>> GetAllSubmissionPreviewsByUserAsync(Guid id);
} }
} }

View File

@@ -67,15 +67,13 @@ namespace TechHelper.Services
} }
} }
// 实现 IBaseService<ClassDto, int>.GetAllAsync
public async Task<ApiResponse> GetAllAsync(QueryParameter query) public async Task<ApiResponse> GetAllAsync(QueryParameter query)
{ {
try try
{ {
var repository = _work.GetRepository<Class>(); var repository = _work.GetRepository<Class>();
// 构建查询条件 (可根据 QueryParameter.Search 进行筛选) Func<IQueryable<Class>, IOrderedQueryable<Class>> orderBy = null;
Func<IQueryable<Class>, IOrderedQueryable<Class>> orderBy = null; // 默认不排序
if (query.Search != null && !string.IsNullOrWhiteSpace(query.Search)) if (query.Search != null && !string.IsNullOrWhiteSpace(query.Search))
{ {
// 在 Name 字段中进行模糊搜索 // 在 Name 字段中进行模糊搜索
@@ -127,9 +125,22 @@ namespace TechHelper.Services
} }
} }
public Task<ApiResponse> GetClassStudents(ClassDto classDto) public async Task<ApiResponse> GetClassStudents(ClassDto classDto)
{ {
throw new NotImplementedException(); try
{
var result = await _work.GetRepository<Class>().GetFirstOrDefaultAsync(predicate:
c => c.Grade == classDto.Grade && c.Number == classDto.Class,
include: i => i
.Include(c => c.ClassStudents)
.ThenInclude(cs => cs.Student));
return ApiResponse.Success(result: result.ClassStudents);
}
catch (Exception ex)
{
return ApiResponse.Error($"获取学生列表错误, {ex.Message}, {ex.InnerException}");
}
} }
public async Task<ApiResponse> GetUserClass(Guid id) public async Task<ApiResponse> GetUserClass(Guid id)
@@ -185,9 +196,6 @@ namespace TechHelper.Services
var existingClass = await _work.GetRepository<Class>().GetFirstOrDefaultAsync( var existingClass = await _work.GetRepository<Class>().GetFirstOrDefaultAsync(
predicate: (c => c.Number == user.ClassId && c.Grade == user.GradeId)); predicate: (c => c.Number == user.ClassId && c.Grade == user.GradeId));
// finduser and usrinfo are redundant if they are for the same user.
// Let's just use usrinfo
// var finduser = await _userManager.FindByEmailAsync(user.User);
if (existingClass == null || usrinfo == null) // Simplified check if (existingClass == null || usrinfo == null) // Simplified check
{ {

View File

@@ -57,7 +57,7 @@ namespace TechHelper.Server.Services
} }
catch (Exception ex) catch (Exception ex)
{ {
return ApiResponse.Error(ex.Message); return ApiResponse.Error(ex.Message);
} }
} }
@@ -78,7 +78,7 @@ namespace TechHelper.Server.Services
public async Task<ApiResponse> GetAllExamPreviewsAsync(Guid userId) public async Task<ApiResponse> GetAllExamPreviewsAsync(Guid userId)
{ {
var assignments = await _examRepository.GetExamPreviewsByUserAsync(userId); var assignments = await _examRepository.GetExamPreviewsByUserAsync(userId);
var result = _mapper.Map<List<AssignmentDto>>(assignments); var result = _mapper.Map<List<AssignmentDto>>(assignments);
return ApiResponse.Success(result: result); return ApiResponse.Success(result: result);
} }
@@ -109,11 +109,72 @@ namespace TechHelper.Server.Services
throw new NotImplementedException(); throw new NotImplementedException();
} }
public Task<ApiResponse> DeleteAsync(Guid id) public async Task<ApiResponse> DeleteAsync(Guid id)
{
try
{
var assignment = await _unitOfWork.GetRepository<Assignment>().GetFirstOrDefaultAsync(predicate: a => a.Id == id);
if (assignment == null) return ApiResponse.Error("找不到该试卷");
_unitOfWork.GetRepository<Assignment>().Delete(id);
_unitOfWork.GetRepository<AssignmentQuestion>().Delete(assignment.ExamStructId);
if (await _unitOfWork.SaveChangesAsync() > 0)
{
return ApiResponse.Success();
}
return ApiResponse.Error("删除失败");
}
catch (Exception ex)
{
return ApiResponse.Error("内部问题");
}
}
public async Task<ApiResponse> SubmissionAssignment(SubmissionDto submissionDto)
{
try
{
var submission = _mapper.Map<Submission>(submissionDto);
await _examRepository.AddAsync(submission);
if (await _unitOfWork.SaveChangesAsync() > 0)
{
return ApiResponse.Success("保存成功");
}
return ApiResponse.Error("保存失败");
}
catch (Exception ex)
{
return ApiResponse.Error($"出现了错误,{ex.Message} innerEx:{ex.InnerException}");
}
}
public Task<ApiResponse> AssignmentToAllStudentsAsync(Guid id)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public Task<ApiResponse> AssignmentToStudentsAsync(Guid assignementId, Guid studentId)
{
throw new NotImplementedException();
}
public async Task<ApiResponse> GetAllSubmissionAsync(Guid id)
{
try
{
var result = await _examRepository.GetAllSubmissionPreviewsByUserAsync(id);
var allExam = _mapper.Map<List<AssignmentDto>>(result);
return ApiResponse.Success(result: allExam);
}
catch (Exception ex)
{
return ApiResponse.Error($"Submission 内部错误, {ex.Message}");
}
}
} }
} }

View File

@@ -2,10 +2,10 @@
{ {
public interface IBaseService<T, TId> public interface IBaseService<T, TId>
{ {
Task<TechHelper.Services.ApiResponse> GetAllAsync(QueryParameter query); Task<ApiResponse> GetAllAsync(QueryParameter query);
Task<TechHelper.Services.ApiResponse> GetAsync(TId id); Task<ApiResponse> GetAsync(TId id);
Task<TechHelper.Services.ApiResponse> AddAsync(T model); Task<ApiResponse> AddAsync(T model);
Task<TechHelper.Services.ApiResponse> UpdateAsync(T model); Task<ApiResponse> UpdateAsync(T model);
Task<TechHelper.Services.ApiResponse> DeleteAsync(TId id); Task<ApiResponse> DeleteAsync(TId id);
} }
} }

View File

@@ -22,5 +22,17 @@ namespace TechHelper.Server.Services
/// <returns>创建成功的试卷ID</returns> /// <returns>创建成功的试卷ID</returns>
Task<ApiResponse> CreateExamAsync(AssignmentDto examDto); Task<ApiResponse> CreateExamAsync(AssignmentDto examDto);
Task<ApiResponse> SubmissionAssignment(SubmissionDto submissionDto);
Task<ApiResponse> AssignmentToAllStudentsAsync(Guid id);
Task<ApiResponse> AssignmentToStudentsAsync(Guid assignementId, Guid studentId);
Task<ApiResponse> GetAllSubmissionAsync(Guid id);
} }
} }

View File

@@ -0,0 +1,16 @@
using Entities.Contracts;
using TechHelper.Services;
namespace TechHelper.Server.Services
{
public interface ISubmissionServices : IBaseService<Submission, Guid>
{
Task<ApiResponse> GetAssignmentErrorQuestionsAsync(Guid assignmentId, Guid userId);
Task<ApiResponse> GetAllErrorQuestionsAsync(Guid userId);
Task<ApiResponse> GetAssignmentErrorQuestionTypeDisAsync(Guid assignmentId, Guid userId);
Task<ApiResponse> GetAllErrorQuestionTypeDisAsync(Guid assignmentId, Guid userId);
Task<ApiResponse> GetAssignmentAllStudentsError(Guid assignmentId, Guid teacherId);
Task<ApiResponse> GetQuestionErrorStudents(Guid assignmentId);
}
}

View File

@@ -0,0 +1,10 @@
using Entities.Contracts;
using TechHelper.Services;
namespace TechHelper.Server.Services
{
public interface IUserSerivces : IBaseService<User, Guid>
{
Task<ApiResponse> GetStudentDetailInfo(Guid userId);
}
}

View File

@@ -0,0 +1,91 @@
using AutoMapper;
using Entities.Contracts;
using Microsoft.EntityFrameworkCore;
using SharedDATA.Api;
using TechHelper.Services;
namespace TechHelper.Server.Services
{
public class SubmissionServices : ISubmissionServices
{
private readonly IUnitOfWork _unitOfWork;
private readonly IMapper _mapper;
private readonly IRepository<Submission> _submissionRepository;
private readonly IRepository<SubmissionDetail> _submissionDetailRepository;
public SubmissionServices(IMapper mapper, IUnitOfWork unitOfWork)
{
_mapper = mapper;
_unitOfWork = unitOfWork;
_submissionRepository = _unitOfWork.GetRepository<Submission>();
_submissionDetailRepository = _unitOfWork.GetRepository<SubmissionDetail>();
}
public Task<ApiResponse> AddAsync(Submission model)
{
throw new NotImplementedException();
}
public Task<ApiResponse> DeleteAsync(Guid id)
{
throw new NotImplementedException();
}
public Task<ApiResponse> GetAllAsync(QueryParameter query)
{
throw new NotImplementedException();
}
public async Task<ApiResponse> GetAllErrorQuestionsAsync(Guid userId)
{
try
{
var errorSDs = await _submissionDetailRepository.GetPagedListAsync(predicate: sd => sd.StudentId == userId && sd.IsCorrect == false,
include: i => i
.Include(s => s.AssignmentQuestion)
.ThenInclude(aq => aq.Question));
var errorQuestion = errorSDs.Items.Select(sd => sd.AssignmentQuestion).ToList();
return ApiResponse.Success();
}
catch (Exception ex)
{
return ApiResponse.Error();
}
}
public Task<ApiResponse> GetAllErrorQuestionTypeDisAsync(Guid assignmentId, Guid userId)
{
throw new NotImplementedException();
}
public Task<ApiResponse> GetAssignmentAllStudentsError(Guid assignmentId, Guid teacherId)
{
throw new NotImplementedException();
}
public Task<ApiResponse> GetAssignmentErrorQuestionsAsync(Guid assignmentId, Guid userId)
{
throw new NotImplementedException();
}
public Task<ApiResponse> GetAssignmentErrorQuestionTypeDisAsync(Guid assignmentId, Guid userId)
{
throw new NotImplementedException();
}
public Task<ApiResponse> GetAsync(Guid id)
{
throw new NotImplementedException();
}
public Task<ApiResponse> GetQuestionErrorStudents(Guid assignmentId)
{
throw new NotImplementedException();
}
public Task<ApiResponse> UpdateAsync(Submission model)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,38 @@
using Entities.Contracts;
using TechHelper.Services;
namespace TechHelper.Server.Services
{
public class UserServices : IUserSerivces
{
public Task<ApiResponse> AddAsync(User model)
{
throw new NotImplementedException();
}
public Task<ApiResponse> DeleteAsync(Guid id)
{
throw new NotImplementedException();
}
public Task<ApiResponse> GetAllAsync(QueryParameter query)
{
throw new NotImplementedException();
}
public Task<ApiResponse> GetAsync(Guid id)
{
throw new NotImplementedException();
}
public Task<ApiResponse> GetStudentDetailInfo(Guid userId)
{
throw new NotImplementedException();
}
public Task<ApiResponse> UpdateAsync(User model)
{
throw new NotImplementedException();
}
}
}