exam_service

This commit is contained in:
SpecialX
2025-06-11 15:02:20 +08:00
parent 97843ab5fd
commit e26881ec2f
52 changed files with 3510 additions and 1174 deletions

View File

@@ -4,10 +4,6 @@
@inject ISnackbar Snackbar
<MudButton Variant="Variant.Filled" Color="Color.Primary" @onclick="@(() => Snackbar.Add("Simple Snackbar"))">
Open Snackbar
</MudButton>
<MudText Typo="Typo.h2"> Create Account </MudText>

View File

@@ -1,50 +0,0 @@
@page "/test"
<MudPaper Class="d-flex flex-column justify-space-around flex-grow-1 overflow-scroll">
<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>
</MudPaper>
@code {
}

View File

@@ -0,0 +1,50 @@
@using Blazored.TextEditor
<MudPaper Height="@Height" Class="@Class" Style="@Style">
<MudPaper Elevation="0" Style="height:calc(100% - 50px)">
<BlazoredTextEditor @ref="@BlazorTextEditor">
<ToolbarContent>
<select class="ql-header">
<option selected=""></option>
<option value="1"></option>
<option value="2"></option>
<option value="3"></option>
<option value="4"></option>
<option value="5"></option>
</select>
<span class="ql-formats">
<button class="ql-bold"></button>
<button class="ql-italic"></button>
<button class="ql-underline"></button>
<button class="ql-strike"></button>
</span>
<span class="ql-formats">
<select class="ql-color"></select>
<select class="ql-background"></select>
</span>
<span class="ql-formats">
<button class="ql-list" value="ordered"></button>
<button class="ql-list" value="bullet"></button>
</span>
<span class="ql-formats">
<button class="ql-link"></button>
</span>
</ToolbarContent>
<EditorContent>
</EditorContent>
</BlazoredTextEditor>
</MudPaper>
</MudPaper>
@code {
[Parameter]
public BlazoredTextEditor BlazorTextEditor { get; set; }
[Parameter]
public string Height { get; set; } = "100%";
[Parameter]
public string Class { get; set; } = "";
[Parameter]
public string Style { get; set; } = "";
}

View File

@@ -1 +0,0 @@
<h3>ChoiceQuestion</h3>

View File

@@ -0,0 +1,132 @@
@page "/exam/create"
@using Blazored.TextEditor
@using Entities.DTO
@using TechHelper.Client.Exam
@inject ILogger<Home> Logger
@inject ISnackbar Snackbar;
@using Microsoft.AspNetCore.Components
@using System.Globalization;
@using TechHelper.Client.Pages.Editor
<MudPaper Elevation="5" Class="d-flex overflow-hidden flex-grow-1" Style="overflow:hidden; position:relative;height:100%">
<MudDrawerContainer Class="mud-height-full flex-grow-1" Style="height:100%">
<MudDrawer @bind-Open="@_open" Elevation="0" Variant="@DrawerVariant.Persistent" Color="Color.Primary" Anchor="Anchor.End" OverlayAutoClose="true">
<MudDrawerHeader>
<MudText Typo="Typo.h6"> 配置 </MudText>
</MudDrawerHeader>
<MudStack Class="overflow-auto">
<ParseRoleConfig />
<MudButton Color="Color.Success"> ParseExam </MudButton>
</MudStack>
</MudDrawer>
<MudStack Row="true" Class="flex-grow-1" Style="height:100%">
<ExamView Class="overflow-auto" ParsedExam="ExamContent"></ExamView>
<MudPaper Class="ma-2">
<MudPaper Elevation="0" Style="height:calc(100% - 80px)">
<BlazoredTextEditor @ref="@_textEditor">
<ToolbarContent>
<select class="ql-header">
<option selected=""></option>
<option value="1"></option>
<option value="2"></option>
<option value="3"></option>
<option value="4"></option>
<option value="5"></option>
</select>
<span class="ql-formats">
<button class="ql-bold"></button>
<button class="ql-italic"></button>
<button class="ql-underline"></button>
<button class="ql-strike"></button>
</span>
<span class="ql-formats">
<select class="ql-color"></select>
<select class="ql-background"></select>
</span>
<span class="ql-formats">
<button class="ql-list" value="ordered"></button>
<button class="ql-list" value="bullet"></button>
</span>
<span class="ql-formats">
<button class="ql-link"></button>
</span>
</ToolbarContent>
<EditorContent>
</EditorContent>
</BlazoredTextEditor>
</MudPaper>
</MudPaper>
<MudButtonGroup Vertical="true" Color="Color.Primary" Variant="Variant.Filled">
<MudIconButton Icon="@Icons.Material.Filled.Settings" OnClick="@ToggleDrawer" Color="Color.Secondary" />
<MudIconButton Icon="@Icons.Material.Filled.TransitEnterexit" OnClick="@ParseExam" Color="Color.Secondary" />
<MudIconButton Icon="@Icons.Material.Filled.Save" OnClick="@ToggleDrawer" Color="Color.Secondary" />
<MudIconButton Icon="@Icons.Material.Filled.Publish" OnClick="@Publish" Color="Color.Secondary" />
</MudButtonGroup>
</MudStack>
</MudDrawerContainer>
</MudPaper>
@code {
[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; }
private bool _open = false;
private void ToggleDrawer()
{
_open = !_open;
}
private BlazoredTextEditor _textEditor = new BlazoredTextEditor();
private ExamPaper _parsedExam = new ExamPaper();
private ExamDto ExamContent = new ExamDto();
private ExamParserConfig _examParserConfig { get; set; } = new ExamParserConfig();
private async Task ParseExam()
{
var plainText = await _textEditor.GetText();
if (!string.IsNullOrWhiteSpace(plainText))
{
try
{
var exampar = new ExamParser(_examParserConfig);
_parsedExam = exampar.ParseExamPaper(plainText);
Snackbar.Add("试卷解析成功。", Severity.Success);
Snackbar.Add($"{_parsedExam.Errors}。", Severity.Success);
ExamContent = _parsedExam.ConvertToExamDTO();
}
catch (Exception ex)
{
Console.WriteLine($"Error parsing exam paper: {ex.Message}");
Console.WriteLine($"Stack Trace: {ex.StackTrace}");
Snackbar.Add($"解析试卷时发生错误:{ex.Message}", Severity.Error);
}
}
else
{
Snackbar.Add("试卷文本为空,无法解析。", Severity.Warning);
}
StateHasChanged();
}
[Inject]
public IExamService examService { get; set; }
public async Task Publish()
{
ExamContent.CreaterEmail = authenticationStateTask.Result.User.Identity.Name;
var apiRespon = await examService.SaveParsedExam(ExamContent);
Snackbar.Add(apiRespon.Message);
}
}

View File

@@ -0,0 +1,15 @@
@page "/exam/edit/{ExamId:Guid}"
@using TechHelper.Client.Exam
@code {
[Parameter]
public Guid ExamId { get; set; }
[Inject]
public IExamService ExamService { get; set; }
protected override async Task OnInitializedAsync()
{
await ExamService.GetExam(Guid.NewGuid());
}
}

View File

@@ -0,0 +1,48 @@
@using Entities.DTO
@using TechHelper.Client.Exam
<MudPaper Elevation=@Elevation Class=@Class>
@foreach (var majorQG in MajorQGList)
{
<MudStack Row="true">
<MudText Typo="Typo.h6">@majorQG.Title</MudText>
@if (majorQG.Score > 0)
{
<MudText Typo="Typo.body2"><b>总分:</b> @majorQG.Score 分</MudText>
}
</MudStack>
@if (!string.IsNullOrWhiteSpace(majorQG.Descript))
{
<MudText Typo="Typo.body2">@((MarkupString)majorQG.Descript.Replace("\n", "<br />"))</MudText>
}
@if (majorQG.SubQuestions.Any())
{
@foreach (var question in majorQG.SubQuestions)
{
<QuestionCard Question="question" Elevation=@Elevation Class="my-2 pa-1" />
}
}
@if (majorQG.SubQuestionGroups.Any())
{
<ExamGroupView MajorQGList="majorQG.SubQuestionGroups" Elevation="1" />
}
}
</MudPaper>
@code {
[Parameter]
public List<QuestionGroupDto> MajorQGList { get; set; }
[Parameter]
public string Class { get; set; } = "my-2 pa-1";
[Parameter]
public int Elevation { get; set; } = 0;
}

View File

@@ -0,0 +1,55 @@
@using Entities.DTO
@using Microsoft.AspNetCore.Authorization
@using TechHelper.Client.Exam
@page "/exam/manage"
@attribute [Authorize]
@if (isloding)
{
<MudText> 正在加载 </MudText>
}
else
{
}
@foreach (var item in examDtos)
{
<ExamPreview examDto="item"> </ExamPreview>
}
@code {
[Inject]
public IExamService ExamService { get; set; }
[Inject]
public ISnackbar Snackbar { get; set; }
[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; }
private List<ExamDto> examDtos = new List<ExamDto>();
private bool isloding = true;
protected override async Task OnInitializedAsync()
{
GetExam();
}
private async void GetExam()
{
isloding = true;
Snackbar.Add("正在加载", Severity.Info);
var result = await ExamService.GetAllExam(authenticationStateTask.Result.User.Identity.Name);
examDtos = result.Result as List<ExamDto> ?? new List<ExamDto>();
isloding = false;
Snackbar.Add("加载成功", Severity.Info);
StateHasChanged();
}
}

View File

@@ -0,0 +1,37 @@
@using Entities.DTO
<MudPaper Width="@Width" Height="@Height" @onclick="ExamClick">
<MudCard>
<MudCardHeader>
<MudText> @examDto.AssignmentTitle </MudText>
</MudCardHeader>
<MudCardContent>
<MudText> @examDto.Description </MudText>
</MudCardContent>
</MudCard>
</MudPaper>
@code {
[Inject]
public NavigationManager navigationManager { get; set; }
[Parameter]
public ExamDto examDto { get; set; }
[Parameter]
public string? Width { get; set; } = "200";
[Parameter]
public string? Height { get; set; } = "400";
private void ExamClick()
{
navigationManager.NavigateTo($"exam/Edit/{examDto.AssignmentId}");
}
}

View File

@@ -0,0 +1,23 @@
@using Entities.DTO
@using TechHelper.Client.Exam
<MudPaper Height="@Height" Class="@Class" Style="@Style" Width="@Width">
<MudText Class="d-flex justify-content-center" Typo="Typo.h6"> @ParsedExam.AssignmentTitle </MudText>
<MudText Typo="Typo.body1"> @ParsedExam.Description </MudText>
<ExamGroupView MajorQGList="@ParsedExam.QuestionGroups.SubQuestionGroups" Elevation="1" Class="ma-0 pa-2" />
</MudPaper>
@code {
[Parameter]
public ExamDto ParsedExam { get; set; } = new ExamDto();
[Parameter]
public string Height { get; set; } = "100%";
[Parameter]
public string Width { get; set; } = "100%";
[Parameter]
public string Class { get; set; } = "";
[Parameter]
public string Style { get; set; } = "";
}

View File

@@ -0,0 +1,7 @@
@page "/exam"
<MudText>HELLO WORLD</MudText>
@code {
}

View File

@@ -0,0 +1,127 @@
@using TechHelper.Client.Exam
<MudPaper Outlined="true" Class="mt-2">
<MudRadioGroup @bind-Value="_examParser">
@foreach (ExamParserEnum item in Enum.GetValues(typeof(ExamParserEnum)))
{
<MudRadio T="ExamParserEnum" Value="@item">@item</MudRadio>
}
</MudRadioGroup>
<MudTextField @bind-Value="_ParserConfig" Label="正则表达式模式" Variant="Variant.Outlined" FullWidth="true" Class="mb-2" />
<MudNumericField Label="优先级" @bind-Value="_Priority" Variant="Variant.Outlined" Min="1" Max="100" />
<MudButton OnClick="AddPattern" Variant="Variant.Filled" Color="Color.Primary" Class="mt-2">添加模式</MudButton>
<MudText Typo="Typo.subtitle1" Class="mb-2">所有已配置模式:</MudText>
@if (ExamParserConfig.MajorQuestionGroupPatterns.Any())
{
<MudExpansionPanel Text="大题组模式详情" Class="mb-2">
<MudStack>
@foreach (var config in ExamParserConfig.MajorQuestionGroupPatterns)
{
<MudChip T="string">
**模式:** <code>@config.Pattern</code>, **优先级:** @config.Priority
</MudChip>
}
</MudStack>
</MudExpansionPanel>
}
else
{
<MudText Typo="Typo.body2" Class="mb-2">暂无大题组模式。</MudText>
}
@* 题目模式详情 *@
@if (ExamParserConfig.QuestionPatterns.Any())
{
<MudExpansionPanel Text="题目模式详情" Class="mb-2">
<MudStack>
@foreach (var config in ExamParserConfig.QuestionPatterns)
{
<MudChip T="string">
**模式:** <code>@config.Pattern</code>, **优先级:** @config.Priority
</MudChip>
}
</MudStack>
</MudExpansionPanel>
}
else
{
<MudText Typo="Typo.body2" Class="mb-2">暂无题目模式。</MudText>
}
@if (ExamParserConfig.OptionPatterns.Any())
{
<MudExpansionPanel Text="选项模式详情" Class="mb-2">
<MudStack>
@foreach (var config in ExamParserConfig.OptionPatterns)
{
<MudChip T="string">
**模式:** <code>@config.Pattern</code>, **优先级:** @config.Priority
</MudChip>
}
</MudStack>
</MudExpansionPanel>
}
else
{
<MudText Typo="Typo.body2" Class="mb-2">暂无选项模式。</MudText>
}
<MudButton Variant="Variant.Filled" Color="Color.Secondary" OnClick="ResetPatterns">重置默认规则</MudButton>
</MudPaper>
@code {
public ExamParserEnum _examParser { get; set; } = ExamParserEnum.MajorQuestionGroupPatterns;
private string _ParserConfig;
private int _Priority = 1;
[Parameter]
public ExamParserConfig ExamParserConfig { get; set; } = new ExamParserConfig();
[Inject]
public ISnackbar Snackbar { get; set; }
private void AddPattern()
{
switch ((ExamParserEnum)_examParser)
{
case ExamParserEnum.MajorQuestionGroupPatterns:
ExamParserConfig.MajorQuestionGroupPatterns.Add(new RegexPatternConfig(_ParserConfig, _Priority));
Snackbar.Add($"已添加大题组模式: {_ParserConfig}, 优先级: {_Priority}", Severity.Success);
break;
case ExamParserEnum.QuestionPatterns:
ExamParserConfig.QuestionPatterns.Add(new RegexPatternConfig(_ParserConfig, _Priority));
Snackbar.Add($"已添加题目模式: {_ParserConfig}, 优先级: {_Priority}", Severity.Success);
break;
case ExamParserEnum.OptionPatterns:
ExamParserConfig.OptionPatterns.Add(new RegexPatternConfig(_ParserConfig, _Priority));
Snackbar.Add($"已添加选项模式: {_ParserConfig}, 优先级: {_Priority}", Severity.Success);
break;
default:
Snackbar.Add("请选择要添加的模式类型。");
break;
}
StateHasChanged();
}
private void ResetPatterns()
{
ExamParserConfig = new ExamParserConfig();
StateHasChanged();
}
}

View File

@@ -1,11 +0,0 @@
<MudText> @Title </MudText>
@code {
[Parameter]
public string Title { get; set; }
[Parameter]
public string Answer { get; set; }
}

View File

@@ -0,0 +1,36 @@
@using Entities.DTO
@using TechHelper.Client.Exam
<MudPaper Class=@Class Elevation=@Elevation Outlined="false">
<MudText Typo="Typo.subtitle1">
<b>@Question.Index</b> @((MarkupString)Question.Stem.Replace("\n", "<br />"))
@if (Question.Score > 0)
{
<MudText Typo="Typo.body2" Class="d-inline ml-2">(@Question.Score 分)</MudText>
}
</MudText>
@if (Question.Options.Any())
{
<div class="mt-2">
@foreach (var option in Question.Options)
{
var tempOption = option;
<p>@((MarkupString)(tempOption.Value.Replace("\n", "<br />")))</p>
}
</div>
}
</MudPaper>
@code {
[Parameter]
public SubQuestionDto Question { get; set; }
[Parameter]
public string Class { get; set; }
[Parameter]
public int Elevation { get; set; }
}

View File

@@ -1,4 +0,0 @@

@code {
}

View File

@@ -1,69 +0,0 @@
@using TechHelper.Client.Exam
<MudCard Class="@(IsNested ? "mb-3 pa-2" : "my-4")" Outlined="@IsNested">
@if (QuestionGroup.Title != string.Empty)
{
<MudCardHeader>
<MudStack>
<MudStack Row="true" AlignItems="AlignItems.Center">
<MudText Typo="@(IsNested ? Typo.h6 : Typo.h5)">@QuestionGroup.Id. </MudText> @* 嵌套时字号稍小 *@
<MudText Typo="@(IsNested ? Typo.h6 : Typo.h5)">@QuestionGroup.Title</MudText>
</MudStack>
@if (!string.IsNullOrEmpty(QuestionGroup.QuestionReference))
{
<MudText Class="mt-2" Style="white-space: pre-wrap;">@QuestionGroup.QuestionReference</MudText>
}
</MudStack>
</MudCardHeader>
}
<MudCardContent>
@* 渲染直接子题目 *@
@if (QuestionGroup.SubQuestions != null && QuestionGroup.SubQuestions.Any())
{
@foreach (var qitem in QuestionGroup.SubQuestions)
{
<MudStack Row="true" AlignItems="AlignItems.Baseline" Class="mb-2">
<MudText Typo="Typo.body1">@qitem.SubId. </MudText>
<MudText Typo="Typo.body1">@qitem.Stem</MudText>
</MudStack>
@if (qitem.Options != null && qitem.Options.Any())
{
@foreach (var oitem in qitem.Options)
{
<MudText Typo="Typo.body2" Class="ml-6 mb-2">@oitem.Value</MudText>
}
}
@if (!string.IsNullOrEmpty(qitem.SampleAnswer))
{
<MudText Typo="Typo.body2" Color="Color.Tertiary" Class="ml-6 mb-2">示例答案: @qitem.SampleAnswer</MudText>
}
}
}
@* 递归渲染子题组 *@
@if (QuestionGroup.SubQuestionGroups != null && QuestionGroup.SubQuestionGroups.Any())
{
<MudDivider Class="my-4" />
@if (!IsNested) // 只有顶级大题才显示“嵌套题组”标题
{
<MudText Typo="Typo.subtitle1" Class="mb-2">相关题组:</MudText>
}
@foreach (var subGroup in QuestionGroup.SubQuestionGroups)
{
<QuestionGroupDisplay QuestionGroup="subGroup" IsNested="true" /> @* 递归调用自身 *@
}
}
</MudCardContent>
</MudCard>
@code {
[Parameter]
public TechHelper.Client.Exam.QuestionGroup QuestionGroup { get; set; } = new TechHelper.Client.Exam.QuestionGroup();
[Parameter]
public bool IsNested { get; set; } = false; // 判断是否是嵌套的题组,用于调整样式和显示标题
}

View File

@@ -0,0 +1,2 @@
@using TechHelper.Client.Shared
@layout ExamLayout

View File

@@ -19,3 +19,100 @@
</AuthorizeView>
<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>