添加项目文件。

This commit is contained in:
SpecialX
2025-05-23 19:03:00 +08:00
parent 6fa7679fd3
commit d36fef2bbb
185 changed files with 13413 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
<MudPaper Elevation="0" Class="ma-1 pa-1">
@if (IsSelected)
{
<MudStack>
<MudText Typo="Typo.caption" Color="Color.Primary">(选中状态,在此编辑具体题目内容)</MudText>
<MudTextField Label="Question" @bind-Value="QuestionItem.Question.QuestionText" AutoGrow="true"></MudTextField>
</MudStack>
<MudTextField Label="Answer" @bind-Value="QuestionItem.Question.CorrectAnswer"></MudTextField>
}
else
{
<MudTextField @bind-Value=QuestionItem.Question.QuestionText AutoGrow="true"> </MudTextField>
}
</MudPaper>
@code {
[Parameter]
public QuestionItem QuestionItem { get; set; }
[Parameter]
public bool IsSelected { get; set; }
}

View File

@@ -0,0 +1,27 @@

.hover-highlight-class
{
background-color: black; /* 使用 MudBlazor 的悬停背景颜色变量 */
cursor: pointer; /* 改变鼠标指针,提示可点击 */
}
/* 可选:如果您希望选中状态有特殊的背景色,可以添加这个 */
.selected-highlight-class {
/* 例如:浅蓝色背景 */
/* background-color: var(--mud-palette-primary-lighten); */
/* 或者仅是边框颜色变化 (已经在 Wrapper 中实现了) */
/* border-color: var(--mud-palette-primary) !important; */
/* border-width: 1px !important; */
}
/* 可选:当同时处于选中和悬停状态时的样式 */
.selected-highlight-class.hover-highlight-class {
/* 例如:比单独悬停颜色更深一点的背景 */
background-color: var(--mud-palette-primary-darken);
}
/* 确保 MudCard 的边框在选中时可见 */
.mud-card.selected-highlight-class {
border-style: solid; /* 确保边框样式为实线 */
}

View File

@@ -0,0 +1,198 @@
@rendermode InteractiveServer
@if (GroupSelected)
{
<MudPaper @onclick="HandleClick" Class="pa-1 ma-1" Outlined="false" Elevation="0">
<MudStack Row="true">
<MudButton Color="Color.Surface" Variant="Variant.Outlined" OnClick="OnAddText"> TEXT </MudButton>
<MudButton Color="Color.Surface" Variant="Variant.Outlined" OnClick="OnAddRadio"> Radio </MudButton>
@* <MudSelect @bind-Value="GropType" Variant="Variant.Outlined">
<MudSelectItem Value="@(GropType.Stack)" />
<MudSelectItem Value="@(GropType.Grid)" />
</MudSelect> *@
</MudStack>
<MudDivider/>
@switch (@GropType)
{
case GropType.Stack:
<MudStack Spacing="6">
@foreach (var question in QuestionGroupElement.GroupsQuestions)
{
<QuestionBase QuestionElement="question"
IsSelected="question.IsSelected"
MoveDown="HandleMoveDown"
MoveUp="HandleMoveUp"
OnDeleted="HandleQuestionDeleted"
OnSelected="HandleSelected" />
}
</MudStack>
break;
case GropType.Grid:
<MudGrid Spacing="6" xs="3">
@foreach (var question in QuestionGroupElement.GroupsQuestions)
{
<MudItem>
<QuestionBase QuestionElement="question"
IsSelected="question.IsSelected"
MoveDown="HandleMoveDown"
MoveUp="HandleMoveUp"
OnDeleted="HandleQuestionDeleted"
OnSelected="HandleSelected" />
</MudItem>
}
</MudGrid>
break;
default:
<MudText Typo="Typo.h6">布局类型: 未知</MudText>
break;
}
</MudPaper>
}
else
{
@switch (@GropType)
{
case GropType.Stack:
<MudStack Spacing="6">
@foreach (var question in QuestionGroupElement.GroupsQuestions)
{
<QuestionBase QuestionElement="question"
IsSelected="question.IsSelected"
MoveDown="HandleMoveDown"
MoveUp="HandleMoveUp"
OnDeleted="HandleQuestionDeleted"
OnSelected="HandleSelected" />
}
</MudStack>
break;
case GropType.Grid:
<MudGrid Spacing="6" xs="3">
@foreach (var question in QuestionGroupElement.GroupsQuestions)
{
<MudItem>
<QuestionBase QuestionElement="question"
IsSelected="question.IsSelected"
MoveDown="HandleMoveDown"
MoveUp="HandleMoveUp"
OnDeleted="HandleQuestionDeleted"
OnSelected="HandleSelected" />
</MudItem>
}
</MudGrid>
break;
default:
<MudText Typo="Typo.h6">布局类型: 未知</MudText>
break;
}
}
@code {
[Parameter]
public GropType GropType { get; set; } = GropType.Stack;
[Parameter]
public bool GroupSelected { get; set; }
[Parameter]
public QuestionGroupElement QuestionGroupElement { get; set; }
[Parameter]
public bool IsSelected { get; set; }
private int preSelected = 0;
protected override void OnInitialized()
{
QuestionGroupElement = new QuestionGroupElement();
QuestionGroupElement.GroupsQuestions = new List<QuestionElement>();
}
private void HandleMoveUp(int index)
{
if (index >= QuestionGroupElement.GroupsQuestions.Count) return;
QuestionGroupElement.GroupsQuestions.MoveUp(QuestionGroupElement.GroupsQuestions[index]);
ReOrderIndex();
StateHasChanged();
}
private void HandleMoveDown(int index)
{
if (index >= QuestionGroupElement.GroupsQuestions.Count) return;
QuestionGroupElement.GroupsQuestions.MoveDown(QuestionGroupElement.GroupsQuestions[index]);
ReOrderIndex();
StateHasChanged();
}
private void HandleQuestionDeleted(int questionId)
{
var questionToRemove = QuestionGroupElement.GroupsQuestions.FirstOrDefault(q => q.Index == questionId);
if (questionToRemove != null)
{
QuestionGroupElement.GroupsQuestions.Remove(questionToRemove);
StateHasChanged();
}
ReOrderIndex();
}
private void ReOrderIndex()
{
foreach (var que in QuestionGroupElement.GroupsQuestions)
{
que.Index = QuestionGroupElement.GroupsQuestions.IndexOf(que);
}
}
public void OnAddText()
{
QuestionGroupElement.GroupsQuestions.Add(new QuestionElement { Index = QuestionGroupElement.GroupsQuestions.Count });
StateHasChanged();
}
public void OnAddRadio()
{
QuestionGroupElement.GroupsQuestions.Add(new QuestionElement { Index = QuestionGroupElement.GroupsQuestions.Count, QuestionType = BaseQuestionType.Radio });
StateHasChanged();
}
private void HandleClick(MouseEventArgs e)
{
HandleSelected(-1);
}
private void HandleSelected(int id)
{
var ques = QuestionGroupElement.GroupsQuestions.FirstOrDefault(x => x.Index == preSelected);
if (ques != null) ques.IsSelected = false;
if (id < 0) return;
var ques2 = QuestionGroupElement.GroupsQuestions.FirstOrDefault(x => x.Index == id);
if (ques2 != null) ques2.IsSelected = true;
preSelected = id;
StateHasChanged();
}
}

View File

@@ -0,0 +1,98 @@
<MudPaper @onclick:stopPropagation>
<MudPaper Outlined="false" Elevation="0"
@onclick="HandleClick"
@onmouseover="HandleMouseOver"
@onmouseout="HandleMouseOut">
@if (IsSelected)
{
<MudIconButton Icon="@Icons.Material.Filled.MoveUp" Color="Color.Success" Size="Size.Small" OnClick="HandleMoveUp" />
<MudIconButton Icon="@Icons.Material.Filled.MoveDown" Color="Color.Success" Size="Size.Small" OnClick="HandleMoveDown" />
<MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error" Size="Size.Small" OnClick="HandleDelete" />
}
@switch (QuestionElement.QuestionType)
{
case BaseQuestionType.Text:
<Blank IsSelected="IsSelected" QuestionItem="QuestionElement.QuestionItem" />
break;
case BaseQuestionType.Radio:
<RadioChoice IsSelected="IsSelected" QuestionItem="QuestionElement.QuestionItem" />
break;
}
</MudPaper>
</MudPaper>
@code {
[Parameter]
public QuestionElement QuestionElement { get; set; }
[Parameter]
public bool IsSelected { get; set; }
[Parameter]
public EventCallback<int> OnSelected { get; set; }
[Parameter]
public EventCallback<int> OnDeleted { get; set; }
[Parameter]
public EventCallback<int> MoveUp { get; set; }
[Parameter]
public EventCallback<int> MoveDown { get; set; }
private bool _isHovered = false;
private async Task HandleDelete()
{
await OnDeleted.InvokeAsync(QuestionElement.Index);
}
private async Task HandleMoveUp()
{
await MoveUp.InvokeAsync(QuestionElement.Index);
}
private async Task HandleMoveDown()
{
await MoveDown.InvokeAsync(QuestionElement.Index);
}
private async Task HandleClick(MouseEventArgs args)
{
if (QuestionElement.QuestionItem != null && OnSelected.HasDelegate)
{
await OnSelected.InvokeAsync(QuestionElement.Index);
}
}
private void HandleMouseOver()
{
_isHovered = true;
StateHasChanged();
}
private void HandleMouseOut()
{
_isHovered = false;
StateHasChanged();
}
private void OnCompeli()
{
IsSelected = false;
StateHasChanged();
}
}

View File

@@ -0,0 +1,124 @@
@rendermode InteractiveServer
<MudPaper @onclick="HandleClick" Outlined="true" Elevation="@(IsSelected ? 8 : 2)" Class="ma-5 pa-2">
@if (IsSelected)
{
<MudPaper Elevation="0" Class="my-2">
<MudIconButton Icon="@Icons.Material.Filled.MoveUp" Color="Color.Success" Size="Size.Small" OnClick="HandleMoveUp" />
<MudIconButton Icon="@Icons.Material.Filled.MoveDown" Color="Color.Success" Size="Size.Small" OnClick="HandleMoveDown" />
<MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error" Size="Size.Small" OnClick="HandleDelete" />
<MudDivider />
<MudStack Row="true">
<MudText> @QuestionGroupElement.Number </MudText>
<MudTextField @bind-Value=QuestionGroupElement.Title></MudTextField>
</MudStack>
<MudDivider />
<MudTextField Label="Descript" @bind-Value=QuestionGroupElement.Descript AutoGrow></MudTextField>
</MudPaper>
}
else
{
<MudStack>
<MudStack Row="true">
<MudText> @QuestionGroupElement.Number </MudText>
<MudText Typo="Typo.h6">@QuestionGroupElement.Title</MudText>
</MudStack>
<MudDivider />
@if (!string.IsNullOrEmpty(QuestionGroupElement.Descript))
{
<MudTextField ReadOnly="true" @bind-Value=QuestionGroupElement.Descript AutoGrow></MudTextField>
}
</MudStack>
}
@switch (QuestionGroupElement.QuestionType)
{
case QuestionType.Spelling:
<CommonGroup QuestionGroupElement="QuestionGroupElement" IsSelected="QuestionGroupElement.IsSelected" GroupSelected="IsSelected" />
break;
default:
<MudText Color="Color.Warning">未知或未实现的编辑器类型: @QuestionGroupElement.QuestionType</MudText>
@if (IsSelected)
{
<MudText Typo="Typo.body2">选中此题,但无对应编辑器可编辑内容。</MudText>
}
break;
}
<MudDivider />
</MudPaper>
@code {
[Parameter]
public QuestionGroupElement QuestionGroupElement { get; set; }
[Parameter]
public bool IsSelected { get; set; }
[Parameter]
public EventCallback<int> OnSelected { get; set; }
[Parameter]
public EventCallback<int> OnDeleted { get; set; }
[Parameter]
public EventCallback<int> MoveUp { get; set; }
[Parameter]
public EventCallback<int> MoveDown { get; set; }
private async Task HandleClick()
{
if (!IsSelected)
{
await OnSelected.InvokeAsync(QuestionGroupElement.Number);
}
}
private async Task HandleDelete()
{
await OnDeleted.InvokeAsync(QuestionGroupElement.Number);
}
private async Task HandleMoveUp()
{
await MoveUp.InvokeAsync(QuestionGroupElement.Number);
}
private async Task HandleMoveDown()
{
await MoveDown.InvokeAsync(QuestionGroupElement.Number);
}
private string GetQuestionTypeName(QuestionType type)
{
return type switch
{
QuestionType.Spelling => "拼写题",
QuestionType.Pronunciation => "读音选择题",
QuestionType.WordFormation => "组词题",
QuestionType.FillInTheBlanks => "选词填空/补充词语",
QuestionType.SentenceDictation => "默写句子",
QuestionType.SentenceRewriting => "仿句/改写",
QuestionType.ReadingComprehension => "阅读理解",
QuestionType.Composition => "作文题",
_ => "未知类型"
};
}
}

View File

@@ -0,0 +1,165 @@
@if (IsSelected)
{
<MudPaper Outlined="true" Class="ma-1 pa-1">
<MudGrid>
<MudItem xs="12" md="8">
<MudStack>
<MudStack Row="true" Spacing="2">
<MudText Typo="Typo.caption" Color="Color.Primary">(选中状态,在此编辑具体题目内容)</MudText>
</MudStack>
<MudTextField @bind-Value="QuestionItem.Question.QuestionText" AutoGrow="true"></MudTextField>
<MudNumericField @bind-Value="Num" Label="选项数量" Variant="Variant.Outlined" Dense="true" Min="0" Max="byte.MaxValue" />
<MudTextField @bind-Value="QuestionItem.Question.CorrectAnswer" AutoGrow="true"></MudTextField>
</MudStack>
</MudItem>
<MudItem xs="12" md="4">
<MudStack>
@for (int index = 0; index < QuestionItem.Radio.Count; index++)
{
var tempIndex = index;
<MudTextField @bind-Value="QuestionItem.Radio[tempIndex]"
Label="@($"选项 {tempIndex + 1}")"
Color="Color.Primary"
Variant="Variant.Outlined" Dense="true">
</MudTextField>
}
</MudStack>
@if (QuestionItem.Radio.Count > 0)
{
<MudRadioGroup T="string" @bind-Value="QuestionItem.Question.CorrectAnswer">
<MudText Typo="Typo.body2">设置正确选项:</MudText>
@foreach (var optionText in QuestionItem.Radio)
{
<MudRadio Value="@optionText" Color="Color.Success">@optionText</MudRadio>
}
</MudRadioGroup>
}
</MudItem>
</MudGrid>
</MudPaper>
}
<MudStack Spacing="1">
<MudText> @QuestionItem.Title </MudText>
@if (QuestionItem.Radio.Count > 0)
{
<MudRadioGroup T="string" ReadOnly="true">
@foreach (var optionText in QuestionItem.Radio)
{
<MudRadio Value="@optionText">@optionText</MudRadio>
}
</MudRadioGroup>
}
<MudText Typo="Typo.body2">
正确答案: @(QuestionItem?.Question.CorrectAnswer ?? "(未设置)")
</MudText>
</MudStack>
@using Microsoft.AspNetCore.Components.Web
@code {
private bool _oldIsSelected;
private const string OptionsDelimiter = "[OPTIONS]";
private const string ItemDelimiter = "[SEP]";
public string SelectedOption { get; set; }
public int Num
{
get { return QuestionItem.Radio.Count; }
set
{
if (QuestionItem.Radio == null)
{
QuestionItem.Radio = new List<string>();
}
int targetCount = value;
while (QuestionItem.Radio.Count < targetCount)
{
QuestionItem.Radio.Add($"选项 {QuestionItem.Radio.Count + 1}");
}
while (QuestionItem.Radio.Count > targetCount)
{
QuestionItem.Radio.RemoveAt(QuestionItem.Radio.Count - 1);
}
StateHasChanged();
}
}
[Parameter]
public QuestionItem QuestionItem { get; set; }
[Parameter]
public bool IsSelected { get; set; }
protected override async Task OnParametersSetAsync()
{
if (IsSelected && !_oldIsSelected)
{
ParseCombinedString(QuestionItem.Question.QuestionText);
}
else if (!IsSelected && _oldIsSelected)
{
CombineDataIntoString(QuestionItem.Question);
}
_oldIsSelected = IsSelected;
await base.OnParametersSetAsync();
}
private void ParseCombinedString(string combinedText)
{
QuestionItem.Radio.Clear();
if (!string.IsNullOrEmpty(combinedText) && combinedText.Contains(OptionsDelimiter))
{
var parts = combinedText.Split(new[] { OptionsDelimiter }, 2, StringSplitOptions.None);
if (parts.Length == 2)
{
QuestionItem.Question.QuestionText = parts[0];
var optionStrings = parts[1].Split(new[] { ItemDelimiter }, StringSplitOptions.None);
foreach (var opt in optionStrings)
{
QuestionItem.Radio.Add(opt);
}
}
else
{
QuestionItem.Question.QuestionText = combinedText;
}
}
else
{
}
StateHasChanged();
}
private void CombineDataIntoString(Question questionToUpdate)
{
QuestionItem.Title = QuestionItem.Question.QuestionText;
string combinedText = questionToUpdate.QuestionText + OptionsDelimiter + string.Join(ItemDelimiter, QuestionItem.Radio);
questionToUpdate.QuestionText = combinedText;
}
}