重构作业结构:优化实体模型、DTO映射和前端界面
Some checks failed
TechAct / explore-gitea-actions (push) Failing after 13s

- 重构AppMainStruct、AssignmentQuestion、Question等实体模型
- 更新相关DTO以匹配新的数据结构
- 优化前端页面布局和组件
- 添加全局信息和笔记功能相关代码
- 更新数据库迁移和程序配置
This commit is contained in:
SpecialX
2025-09-04 15:43:33 +08:00
parent 730b0ba04b
commit 6a65281850
58 changed files with 5459 additions and 244 deletions

View File

@@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -34,9 +36,11 @@ namespace Entities.Contracts
public enum DifficultyLevel : byte public enum DifficultyLevel : byte
{ {
simple,
easy, easy,
medium, medium,
hard hard,
veryHard
} }
public enum QuestionType : byte public enum QuestionType : byte
@@ -55,47 +59,131 @@ namespace Entities.Contracts
public enum SubjectAreaEnum : byte public enum SubjectAreaEnum : byte
{ {
[Display(Name = "未知", Description = "未知")]
Unknown = 0, Unknown = 0,
[Display(Name = "数学", Description = "数")]
Mathematics, // 数学 Mathematics, // 数学
[Display(Name = "物理", Description = "物")]
Physics, // 物理 Physics, // 物理
[Display(Name = "化学", Description = "化")]
Chemistry, // 化学 Chemistry, // 化学
[Display(Name = "生物", Description = "生")]
Biology, // 生物 Biology, // 生物
[Display(Name = "历史", Description = "史")]
History, // 历史 History, // 历史
[Display(Name = "地理", Description = "地")]
Geography, // 地理 Geography, // 地理
[Display(Name = "语文", Description = "语")]
Literature, // 语文/文学 Literature, // 语文/文学
[Display(Name = "英语", Description = "英")]
English, // 英语 English, // 英语
ComputerScience, // 计算机科学
[Display(Name = "计算机科学", Description = "计")]
ComputerScience // 计算机科学
} }
public enum AssignmentStructType : byte public enum AssignmentStructType : byte
{ {
[Display(Name = "根节点", Description = "根")]
Root, Root,
[Display(Name = "单个问题", Description = "问")]
Question, Question,
[Display(Name = "问题组", Description = "组")]
Group, Group,
[Display(Name = "结构", Description = "结")]
Struct, Struct,
[Display(Name = "子问题", Description = "子")]
SubQuestion, SubQuestion,
[Display(Name = "选项", Description = "选")]
Option Option
} }
public enum ExamType : byte public enum ExamType : byte
{ {
MidtermExam, // 期中 [Display(Name = "期中考试", Description = "中")]
FinalExam, // 期末 MidtermExam,
MonthlyExam, // 月考
WeeklyExam, // 周考
DailyTest, // 平时测试
AITest, // AI测试
}
[Display(Name = "期末考试", Description = "末")]
FinalExam,
[Display(Name = "月考", Description = "月")]
MonthlyExam,
[Display(Name = "周考", Description = "周")]
WeeklyExam,
[Display(Name = "平时测试", Description = "平")]
DailyTest,
[Display(Name = "AI测试", Description = "AI")]
AITest,
}
public enum SubmissionStatus public enum SubmissionStatus
{ {
Pending, // 待提交/未开始 [Display(Name = "待提交/未开始", Description = "待")]
Submitted, // 已提交 Pending,
Graded, // 已批改
Resubmission, // 待重新提交 (如果允许) [Display(Name = "已提交", Description = "提")]
Late, // 迟交 Submitted,
Draft, // 草稿
[Display(Name = "已批改", Description = "批")]
Graded,
[Display(Name = "待重新提交", Description = "重")]
Resubmission,
[Display(Name = "迟交", Description = "迟")]
Late,
[Display(Name = "草稿", Description = "草")]
Draft,
}
public static class EnumExtensions
{
public static string GetDisplayName(this Enum enumValue)
{
var fieldInfo = enumValue.GetType().GetField(enumValue.ToString());
if (fieldInfo == null)
{
return enumValue.ToString();
}
var displayAttribute = fieldInfo.GetCustomAttribute<DisplayAttribute>();
if (displayAttribute != null)
{
return displayAttribute.Name;
}
return enumValue.ToString();
}
public static string GetShortName(this Enum enumValue)
{
var memberInfo = enumValue.GetType().GetMember(enumValue.ToString()).FirstOrDefault();
if (memberInfo != null)
{
var displayAttribute = memberInfo.GetCustomAttribute<DisplayAttribute>();
if (displayAttribute != null && !string.IsNullOrEmpty(displayAttribute.Description))
{
return displayAttribute.Description;
}
}
return enumValue.ToString();
}
} }
} }

View File

@@ -40,6 +40,8 @@ namespace Entities.Contracts
[Column("group_state")] [Column("group_state")]
public AssignmentStructType StructType { get; set; } = AssignmentStructType.Question; public AssignmentStructType StructType { get; set; } = AssignmentStructType.Question;
public QuestionType Type { get; set; } = QuestionType.Unknown;
[Column("created_at")] [Column("created_at")]
public DateTime CreatedAt { get; set; } public DateTime CreatedAt { get; set; }

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Entities.Contracts
{
[Table("global")]
public class Global
{
[Key]
[Column("id")]
public Guid Id { get; set; } = Guid.NewGuid();
public SubjectAreaEnum Area { get; set; }
public string Info { get; set; } = string.Empty;
}
}

View File

@@ -38,6 +38,7 @@ namespace Entities.Contracts
[Column("subject_area")] [Column("subject_area")]
public SubjectAreaEnum SubjectArea { get; set; } = SubjectAreaEnum.Unknown; public SubjectAreaEnum SubjectArea { get; set; } = SubjectAreaEnum.Unknown;
public string QType { get; set; } = string.Empty;
[Column("options")] [Column("options")]
public string? Options { get; set; } public string? Options { get; set; }

View File

@@ -16,7 +16,7 @@ namespace Entities.Contracts
public DateTime? RefreshTokenExpiryTime { get; set; } public DateTime? RefreshTokenExpiryTime { get; set; }
public string? Address { get; set; } public string? Address { get; set; }
public string? DisplayName { get; set; } public string? DisplayName { get; set; }
public SubjectAreaEnum SubjectArea { get; set; } = SubjectAreaEnum.Unknown;
[Column("deleted")] [Column("deleted")]
public bool IsDeleted { get; set; } public bool IsDeleted { get; set; }

View File

@@ -17,6 +17,8 @@ namespace Entities.DTO
public float Score { get; set; } = 0; public float Score { get; set; } = 0;
public string Sequence { get; set; } = string.Empty; public string Sequence { get; set; } = string.Empty;
public bool BCorrect { get; set; } = true; public bool BCorrect { get; set; } = true;
public QuestionType Type { get; set; } = QuestionType.Unknown;
public string QType { 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;

23
Entities/DTO/GlobalDto.cs Normal file
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 GlobalDto
{
public SubjectAreaEnum SubjectArea { get; set; } = SubjectAreaEnum.Unknown;
public string Data { get; set; } = string.Empty;
}
public class QuestionDisplayTypeData
{
public string Color { get; set; }
public string DisplayName { get; set; }
}
}

View File

@@ -16,6 +16,7 @@ namespace Entities.DTO
public string Title { get; set; } = string.Empty; public string Title { get; set; } = string.Empty;
public QuestionType Type { get; set; } = QuestionType.Unknown; public QuestionType Type { get; set; } = QuestionType.Unknown;
public string QType { get; set; } = string.Empty;
public string? Answer { get; set; } = string.Empty; public string? Answer { get; set; } = string.Empty;

View File

@@ -0,0 +1,14 @@
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using Entities.Contracts;
namespace Entities.DTO
{
public class SubjectTypeMetadataDto
{
public SubjectAreaEnum SubjectArea { get; set; } = SubjectAreaEnum.Unknown;
//public Dictionary<string, (string Color, string DisplayName)> Data = new Dictionary<string, (string Color, string DisplayName)>();
public string Data = string.Empty;
}
}

View File

@@ -0,0 +1,31 @@
using MudBlazor;
namespace TechHelper.Client.Helper
{
public static class Helper
{
public static Color GetColorFromInt(int value)
{
var v = value % Enum.GetValues(typeof(Color)).Length;
switch (value)
{
case 1:
return MudBlazor.Color.Primary;
case 2:
return MudBlazor.Color.Secondary;
case 3:
return MudBlazor.Color.Success;
case 4:
return MudBlazor.Color.Info;
case 5:
return MudBlazor.Color.Warning;
case 6:
return MudBlazor.Color.Error;
case 7:
return MudBlazor.Color.Dark;
default:
return MudBlazor.Color.Default;
}
}
}
}

View File

@@ -4,83 +4,67 @@
<MudSnackbarProvider /> <MudSnackbarProvider />
<MudPopoverProvider /> <MudPopoverProvider />
@* <MudLayout>
<MudPaper Style="position: fixed; <MudAppBar Elevation="0" Class="rounded-xl" Style="background-color: transparent; border:none">
top: 0; <MudBreakpointProvider>
left: 0; <MudHidden Breakpoint="Breakpoint.SmAndDown" Invert=true>
width: 100vw; <MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Primary" Edge="Edge.Start" OnClick="@((e) => DrawerToggle())" />
height: 100vh; </MudHidden>
background-image: url('/ref/bg4.jpg'); <MudHidden Breakpoint="Breakpoint.SmAndDown">
background-size: cover; <SearchBar></SearchBar>
background-position: center center; <MudButton Class="mt-1">application</MudButton>
background-repeat: no-repeat; </MudHidden>
filter: blur(10px); </MudBreakpointProvider>
z-index: -1;"> <MudSpacer />
</MudPaper> <MudIconButton Icon="@Icons.Material.Filled.MoreVert" Color="Color.Primary" Edge="Edge.End" />
<MudPaper Class="d-flex flex-column flex-grow-0 overflow-auto" Style="height: 100vh; background-color:#22222200"> </MudAppBar>
<MudDrawer @bind-Open="_drawerOpen" Height="100%" Elevation="0" Style="background-color:#f5f6fb">
<MudDrawerHeader Class="h-100 d-flex flex-grow-1" Style="background-color:#f5f6fb">
<MudPaper Width="250px" Class="d-flex py-3 flex-column justify-content-between rounded-xl" Elevation="3">
<MudNavMenu Bordered="true" Dense="true" Rounded="true" Color="Color.Error" Margin="Margin.Dense">
<ApplicationMainIconCard></ApplicationMainIconCard>
<MudDivider Class="my-2" />
<MudNavLink Href="/">Dashboard</MudNavLink>
<MudNavLink Href="/exam">Exam</MudNavLink>
<MudNavLink Href="/exam">Billing</MudNavLink>
<MudNavGroup Title="Settings" Expanded="true">
<MudNavLink Href="/users">Users</MudNavLink>
<MudNavLink Href="/security">Security</MudNavLink>
</MudNavGroup>
<MudSpacer />
<MudPaper Class="d-flex flex-column flex-grow-1 overflow-hidden" Style="background-color:transparent"> <MudNavLink Class="align-content-end" Href="/about">About</MudNavLink>
</MudNavMenu>
<MudSpacer />
<MudPaper Elevation="3" Height="10%" Class=" d-flex justify-content-around flex-grow-0" Style="background-color:#ffffff55"> <MudNavMenu Class="align-content-end " Bordered="true" Dense="true" Rounded="true" Margin="Margin.Dense">
<NavBar Class="flex-column flex-grow-1 " Style="background-color:transparent" /> <TechHelper.Client.Pages.Global.LoginInOut.LoginInOut></TechHelper.Client.Pages.Global.LoginInOut.LoginInOut>
<AuthLinks Class="flex-column flex-grow-0 " Style="background-color:transparent" /> <MudNavLink Class="align-content-end" Href="/about">Setting</MudNavLink>
</MudNavMenu>
</MudPaper> </MudPaper>
</MudDrawerHeader>
</MudDrawer>
<MudPaper Elevation="3" Class="d-flex flex-row flex-grow-1 overflow-hidden" Style="background-color:transparent"> <MudMainContent Style="background: #f5f6fb">
<SnackErrorBoundary @ref="errorBoundary">
<MudPaper Height="calc(100vh - 64px)" Style="background-color:transparent" Class="overflow-hidden px-1 py-2" Elevation="0">
<MudPaper Width="10%" Class="pa-2 ma-1 d-flex flex-column flex-grow-0 justify-content-between" Style="background-color:#ffffffaa"> <MudPaper Style="background-color:#eeeeeeef" Elevation="3" Class="d-flex w-100 h-100 overflow-hidden pa-2 rounded-xl">
</MudPaper>
<MudPaper Elevation="3" Class="d-flex flex-grow-1 pa-3 ma-1 overflow-hidden" Style="background-color:#ffffff22 ">
@Body
</MudPaper>
</MudPaper>
</MudPaper>
</MudPaper>
*@
<MudPaper Style="position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-image: url('/ref/bg4.jpg');
background-size: cover;
background-position: center center;
background-repeat: no-repeat;
filter: blur(10px);
z-index: -1;">
</MudPaper>
<MudPaper Style="background-color:transparent ; height:100vh" Class="overflow-hidden">
<MudPaper Class="justify-content-center" Style="background-color:blue; height: 50px">
<MudStack Row="true" Class="justify-content-between">
<NavBar Class="flex-grow-1" Style="background-color:transparent; color:white" />
<AuthLinks Class="justify-content-end " Style="background-color:transparent; color:white" />
</MudStack>
</MudPaper>
<MudPaper Class="d-flex flex-grow-0 " Style="background-color:#30303022; height:calc(100vh - 50px)">
@* <MudPaper Class="ma-1" Width="200px">
</MudPaper> *@
<MudPaper Class="d-flex ma-1 flex-grow-1 overflow-auto">
@Body @Body
</MudPaper> </MudPaper>
</MudPaper> </MudPaper>
</MudPaper> </SnackErrorBoundary>
</MudMainContent>
</MudLayout>
@code {
ErrorBoundary? errorBoundary;
protected override void OnParametersSet()
{
errorBoundary?.Recover();
}
bool _drawerOpen = true;
void DrawerToggle()
{
_drawerOpen = !_drawerOpen;
}
}

View File

@@ -4,7 +4,7 @@
<MudItem xs="12" sm="2" Class="h-100 pa-1 mt-1"> <MudItem xs="12" sm="2" Class="h-100 pa-1 mt-1">
<MudStack Class="h-100"> <MudStack Class="h-100">
<MudText Style="color:white"> 期中测试BETA版本 </MudText> <MudText Style="color:white"> BETA版本 </MudText>
<MudText Style="color:white" Typo="Typo.h3"><b> 75 </b></MudText> <MudText Style="color:white" Typo="Typo.h3"><b> 75 </b></MudText>
<MudPaper Elevation=0 Class="h-100 w-100" Style="background-color:transparent"> <MudPaper Elevation=0 Class="h-100 w-100" Style="background-color:transparent">
@@ -12,13 +12,13 @@
<MudPaper Elevation=0 Height="100%" Width="100%" Style="background-color:transparent"> <MudPaper Elevation=0 Height="100%" Width="100%" Style="background-color:transparent">
<MudPaper Elevation=0 Height="50%" Style="background-color:transparent"> <MudPaper Elevation=0 Height="50%" Style="background-color:transparent">
<MudText Style="color:#9ed5f7" Typo="Typo.body2"> <MudText Style="color:#9ed5f7" Typo="Typo.body2">
TotalNumber: 总数:
<span style="color: #fefefe;">15</span> <span style="color: #fefefe;">15</span>
</MudText> </MudText>
</MudPaper> </MudPaper>
<MudPaper Elevation=0 Height="50%" Style="background-color:transparent"> <MudPaper Elevation=0 Height="50%" Style="background-color:transparent">
<MudText Style="color:#9ed5f7" Typo="Typo.body2"> <MudText Style="color:#9ed5f7" Typo="Typo.body2">
TotalScore: 总分:
<span style="color: #fefefe;">15</span> <span style="color: #fefefe;">15</span>
</MudText> </MudText>
</MudPaper> </MudPaper>
@@ -27,7 +27,7 @@
<MudPaper Elevation=0 Height="100%" Width="100%" Style="background-color:transparent"> <MudPaper Elevation=0 Height="100%" Width="100%" Style="background-color:transparent">
<MudPaper Elevation=0 Height="50%" Style="background-color:transparent"> <MudPaper Elevation=0 Height="50%" Style="background-color:transparent">
<MudText Style="color:#9ed5f7" Typo="Typo.body2"> <MudText Style="color:#9ed5f7" Typo="Typo.body2">
中位: 中位:
<span style="color: #fefefe;">15</span> <span style="color: #fefefe;">15</span>
</MudText> </MudText>
</MudPaper> </MudPaper>
@@ -67,17 +67,17 @@
<MudChipSet T="string" SelectedValuesChanged="HandleSelectedValuesChanged" SelectedValues="@_selected" SelectionMode="SelectionMode.MultiSelection" CheckMark="true"> <MudChipSet T="string" SelectedValuesChanged="HandleSelectedValuesChanged" SelectedValues="@_selected" SelectionMode="SelectionMode.MultiSelection" CheckMark="true">
<MudChip Size="Size.Small" Text="类型错误数量分布" Variant="Variant.Text" Color="Color.Default">类型分布</MudChip> <MudChip Size="Size.Small" Text="类型错误数量分布" Variant="Variant.Text" Color="Color.Default">类型分布</MudChip>
<MudChip Size="Size.Small" Text="类型错误成绩分布" Variant="Variant.Text" Color="Color.Primary">课时分布</MudChip> <MudChip Size="Size.Small" Text="类型错误成绩分布" Variant="Variant.Text" Color="Color.Primary">课时分布</MudChip>
<MudChip Size="Size.Small" Text="pink" Variant="Variant.Text" Color="Color.Secondary">成绩趋势</MudChip>
<MudChip Size="Size.Small" Text="blue" Variant="Variant.Text" Color="Color.Info">分值区间</MudChip>
<MudChip Size="Size.Small" Text="green" Variant="Variant.Text" Color="Color.Success">Success</MudChip>
<MudChip Size="Size.Small" Text="orange" Variant="Variant.Text" Color="Color.Warning">Warning</MudChip>
<MudChip Size="Size.Small" Text="red" Variant="Variant.Text" Color="Color.Error">Error</MudChip>
<MudChip Size="Size.Small" Text="black" Variant="Variant.Text" Color="Color.Dark">Dark</MudChip>
</MudChipSet> </MudChipSet>
</MudItem> </MudItem>
</MudGrid> </MudGrid>
</MudPaper> </MudPaper>
@* <MudChip Size="Size.Small" Text="pink" Variant="Variant.Text" Color="Color.Secondary">成绩趋势</MudChip>
<MudChip Size="Size.Small" Text="blue" Variant="Variant.Text" Color="Color.Info">分值区间</MudChip>
<MudChip Size="Size.Small" Text="green" Variant="Variant.Text" Color="Color.Success">Success</MudChip>
<MudChip Size="Size.Small" Text="orange" Variant="Variant.Text" Color="Color.Warning">Warning</MudChip>
<MudChip Size="Size.Small" Text="red" Variant="Variant.Text" Color="Color.Error">Error</MudChip>
<MudChip Size="Size.Small" Text="black" Variant="Variant.Text" Color="Color.Dark">Dark</MudChip> *@
@code { @code {
public double[] data = { 25, 77, 28, 5 }; public double[] data = { 25, 77, 28, 5 };

View File

@@ -0,0 +1,34 @@
@using Entities.DTO
@inject ISnackbar Snackbar
<MudDialog Class="rounded-xl" Style="background-color: #dedede" >
<TitleContent>
<MudText Typo="Typo.h6">
<MudIcon Icon="@Icons.Material.Filled.EditAttributes" Class="mr-3 mb-n1" />
<b> 编辑属性 </b>
</MudText>
</TitleContent>
<DialogContent>
<GlobalInfoCard AssignmentDto="Assignment"></GlobalInfoCard>
</DialogContent>
<DialogActions>
<MudButton OnClick="Cancel">Cancel</MudButton>
<MudButton Color="Color.Error" OnClick="Confirm">确认</MudButton>
</DialogActions>
</MudDialog>
@code {
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; }
[Parameter]
public AssignmentDto Assignment { get; set; } = new AssignmentDto();
private void Cancel() => MudDialog.Cancel();
private void Confirm()
{
Snackbar.Add("属性已更新", Severity.Success);
MudDialog.Close(DialogResult.Ok(Assignment));
}
}

View File

@@ -0,0 +1,37 @@
@using Entities.DTO
@using Entities.Contracts
@using Helper
<MudPaper Elevation=5 Class="w-100 pa-5 rounded-xl" Height="@Height" Style="@Style">
<MudTextField Value="@AssignmentDto.Title"></MudTextField>
<MudTextField Value="@AssignmentDto.Score">SCORE</MudTextField>
<MudTextField Value="@AssignmentDto.TotalQuestions">NUMQUESTION</MudTextField>
<MudChipSet T="SubjectAreaEnum" SelectedValue="@AssignmentDto.SubjectArea" CheckMark SelectionMode="SelectionMode.SingleSelection" SelectedValueChanged="HandleQTSelectedValueChanged">
@foreach (SubjectAreaEnum item in Enum.GetValues(typeof(SubjectAreaEnum)))
{
var color = Helper.GetColorFromInt((int)item);
<MudChip Color=@color
Value="@item">
</MudChip>
}
</MudChipSet>
<MudText>DUETIME</MudText>
<MudText>EXAMTYPE</MudText>
</MudPaper>
@code {
[Parameter]
public AssignmentDto AssignmentDto { get; set; }
[Parameter]
public string Style { get; set; }
[Parameter]
public string Height { get; set; } = "auto";
public void HandleQTSelectedValueChanged(SubjectAreaEnum subject)
{
AssignmentDto.SubjectArea = subject;
}
}

View File

@@ -1,4 +1,4 @@
<MudPaper Elevation=5 Class="w-100 rounded-xl" Height="@Height" Style="@Style"> <MudPaper Elevation=1 Class="w-100 rounded-xl ma-2 pa-2" Height="@Height" Style="@Style">
<MudPaper Elevation=0 Class="w-100 pa-2 align-content-center" Height="20%" Style="background-color:transparent"> @TitleContent </MudPaper> <MudPaper Elevation=0 Class="w-100 pa-2 align-content-center" Height="20%" Style="background-color:transparent"> @TitleContent </MudPaper>
<MudPaper Elevation=0 Class="w-100 pa-2" Style="background-color:transparent" Height="60%"> @BodyContent </MudPaper> <MudPaper Elevation=0 Class="w-100 pa-2" Style="background-color:transparent" Height="60%"> @BodyContent </MudPaper>
<MudPaper Elevation=0 Class="w-100 pa-2 align-content-center" Style="background-color:transparent" Height="20%"> @FooterContent </MudPaper> <MudPaper Elevation=0 Class="w-100 pa-2 align-content-center" Style="background-color:transparent" Height="20%"> @FooterContent </MudPaper>

View File

@@ -1,5 +1,6 @@
@using Entities.DTO @using Entities.DTO
@using Entities.Contracts @using Entities.Contracts
@using Newtonsoft.Json
@using TechHelper.Client.Exam @using TechHelper.Client.Exam
@using TechHelper.Client.Pages.Exam.QuestionCard @using TechHelper.Client.Pages.Exam.QuestionCard
@@ -12,12 +13,30 @@
<MudTextField @bind-Value="AssignmentQuestion.Index" Label="Index" Variant="Variant.Text" Adornment="Adornment.End" AdornmentText="." Margin="Margin.Dense" AutoFocus="true" /> <MudTextField @bind-Value="AssignmentQuestion.Index" Label="Index" Variant="Variant.Text" Adornment="Adornment.End" AdornmentText="." Margin="Margin.Dense" AutoFocus="true" />
<MudTextField @bind-Value="AssignmentQuestion.Score" Label="Score" Variant="Variant.Text" Adornment="Adornment.End" AdornmentText="." Margin="Margin.Dense" AutoFocus="true" /> <MudTextField @bind-Value="AssignmentQuestion.Score" Label="Score" Variant="Variant.Text" Adornment="Adornment.End" AdornmentText="." Margin="Margin.Dense" AutoFocus="true" />
<MudChipSet T="AssignmentStructType" SelectedValue="AssignmentQuestion.StructType" CheckMark SelectionMode="SelectionMode.SingleSelection" SelectedValueChanged="HandleSelectedValueChanged"> <MudChipSet T="AssignmentStructType" SelectedValue="AssignmentQuestion.StructType" CheckMark SelectionMode="SelectionMode.SingleSelection" SelectedValueChanged="HandleSelectedValueChanged">
<MudChip Text="pink" Color="Color.Secondary" Value="@AssignmentStructType.Root">@AssignmentStructType.Root</MudChip>
<MudChip Text="pink" Color="Color.Secondary" Value="@AssignmentStructType.Struct">@AssignmentStructType.Struct</MudChip> <MudChip Text="pink" Color="Color.Secondary" Value="@AssignmentStructType.Struct">@AssignmentStructType.Struct</MudChip>
<MudChip Text="purple" Color="Color.Primary" Value="@AssignmentStructType.Group">@AssignmentStructType.Group</MudChip> <MudChip Text="purple" Color="Color.Primary" Value="@AssignmentStructType.Group">@AssignmentStructType.Group</MudChip>
<MudChip Text="blue" Color="Color.Info" Value="@AssignmentStructType.Question">@AssignmentStructType.Question</MudChip> <MudChip Text="blue" Color="Color.Info" Value="@AssignmentStructType.Question">@AssignmentStructType.Question</MudChip>
<MudChip Text="green" Color="Color.Warning" Value="@AssignmentStructType.SubQuestion">@AssignmentStructType.SubQuestion</MudChip> <MudChip Text="green" Color="Color.Warning" Value="@AssignmentStructType.SubQuestion">@AssignmentStructType.SubQuestion</MudChip>
<MudChip Text="orange" Color="Color.Error" Value="@AssignmentStructType.Option">@AssignmentStructType.Option</MudChip> <MudChip Text="orange" Color="Color.Error" Value="@AssignmentStructType.Option">@AssignmentStructType.Option</MudChip>
</MudChipSet> </MudChipSet>
<MudChipSet T="string" SelectedValue="@AssignmentQuestion.QType" CheckMark SelectionMode="SelectionMode.SingleSelection" SelectedValueChanged="HandleQTSelectedValueChanged">
@foreach (var item in QuestionTypes)
{
var qt = item;
@* Style = "@($"background - color:{ item.Value.Color} ")"*@
<MudChip Style="@(qt.Key == AssignmentQuestion.QType ?
$"background-color:#ffffff; color:{item.Value.Color}" :
$"background-color:{item.Value.Color}; color:#ffffff")"
Value="@item.Key">
@item.Value.DisplayName
</MudChip>
}
</MudChipSet>
</MudPaper> </MudPaper>
@if (AssignmentQuestion.Question != null) @if (AssignmentQuestion.Question != null)
{ {
@@ -30,6 +49,10 @@
[Parameter] [Parameter]
public AssignmentQuestionDto AssignmentQuestion { get; set; } = new AssignmentQuestionDto(); public AssignmentQuestionDto AssignmentQuestion { get; set; } = new AssignmentQuestionDto();
public QuestionDto TempQuesdto; public QuestionDto TempQuesdto;
Dictionary<string, QuestionDisplayTypeData> QuestionTypes = new Dictionary<string, QuestionDisplayTypeData>();
[Inject]
private ILocalStorageService LocalStorageService { get; set; }
protected override void OnInitialized() protected override void OnInitialized()
{ {
base.OnInitialized(); base.OnInitialized();
@@ -37,6 +60,30 @@
{ {
TempQuesdto = AssignmentQuestion.Question; TempQuesdto = AssignmentQuestion.Question;
} }
var cs = LocalStorageService.GetItem<string>("GlobalInfo");
var GlobalInfo = JsonConvert.DeserializeObject<Dictionary<string, QuestionDisplayTypeData>>(cs);
if(GlobalInfo != null)
{
QuestionTypes = GlobalInfo;
}
}
private void HandleQTSelectedValueChanged(string type)
{
AssignmentQuestion.QType = type;
if (AssignmentQuestion.ChildrenAssignmentQuestion.Count > 0 && AssignmentQuestion.StructType == AssignmentStructType.Group)
{
foreach (var item in AssignmentQuestion.ChildrenAssignmentQuestion)
{
item.QType = type;
if (item.Question != null)
{
item.Question.QType = type;
}
}
}
StateHasChanged();
} }
private void HandleSelectedValueChanged(AssignmentStructType type) private void HandleSelectedValueChanged(AssignmentStructType type)

View File

@@ -1,5 +1,7 @@
@page "/exam/create" @page "/exam/create"
@using AutoMapper @using AutoMapper
@using Entities.Contracts
@using Newtonsoft.Json
@using TechHelper.Client.Pages.Common @using TechHelper.Client.Pages.Common
@using TechHelper.Client.Pages.Exam.ExamView @using TechHelper.Client.Pages.Exam.ExamView
@using TechHelper.Client.Services @using TechHelper.Client.Services
@@ -43,6 +45,8 @@
<MudButton Variant="Variant.Filled" Color="Color.Secondary" Class="rounded-xl" Size="Size.Small" OnClick="ParseExam">载入</MudButton> <MudButton Variant="Variant.Filled" Color="Color.Secondary" Class="rounded-xl" Size="Size.Small" OnClick="ParseExam">载入</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Secondary" Class="rounded-xl" Size="Size.Small" OnClick="OpenPublish">发布</MudButton> <MudButton Variant="Variant.Filled" Color="Color.Secondary" Class="rounded-xl" Size="Size.Small" OnClick="OpenPublish">发布</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Secondary" Class="rounded-xl" Size="Size.Small" OnClick="OpenPublish">指派</MudButton> <MudButton Variant="Variant.Filled" Color="Color.Secondary" Class="rounded-xl" Size="Size.Small" OnClick="OpenPublish">指派</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Secondary" Class="rounded-xl" Size="Size.Small" OnClick="OpenTest">Test</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Secondary" Class="rounded-xl" Size="Size.Small" OnClick="HandleGlobalInfo">GlobalExamInfo</MudButton>
</MudPaper> </MudPaper>
@@ -84,11 +88,32 @@
private ExamParserConfig _examParserConfig { get; set; } = new ExamParserConfig(); private ExamParserConfig _examParserConfig { get; set; } = new ExamParserConfig();
private string EditorText = ""; private string EditorText = "";
[Inject]
private ILocalStorageService LocalStorageService { get; set; }
[Inject] [Inject]
public IMapper Mapper { get; set; } public IMapper Mapper { get; set; }
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
var response = await NoteService.GetNote((byte)SubjectAreaEnum.Literature);
if (response.Status)
{
try
{
LocalStorageService.SetItem("GlobalInfo", response.Result);
}
catch (Exception ex)
{
}
}
}
private async void OpenEditor() private async void OpenEditor()
{ {
var parameters = new DialogParameters<TextEditorDialog> { { x => x.TextEditor, _textEditor } }; var parameters = new DialogParameters<TextEditorDialog> { { x => x.TextEditor, _textEditor } };
@@ -169,13 +194,43 @@
[Inject] [Inject]
public IExamService examService { get; set; } public IExamService examService { get; set; }
[Inject]
public INoteService NoteService { get; set; }
public async Task Publish() public async Task Publish()
{ {
var apiRespon = await examService.SaveParsedExam(ExamContent); var apiRespon = await examService.SaveParsedExam(ExamContent);
Snackbar.Add(apiRespon.Message); Snackbar.Add(apiRespon.Message);
} }
public async Task OpenTest()
{
Dictionary<string, (Color, string)> Note = new Dictionary<string, (Color, string)> { { "Hello", (Color.Surface, "World") }, { "My", (Color.Surface, "App") }, };
var json = JsonConvert.SerializeObject(Note);
var result = await NoteService.AddNote(new GlobalDto { SubjectArea = Entities.Contracts.SubjectAreaEnum.Physics, Data = json });
Console.WriteLine(json);
var res = JsonConvert.DeserializeObject<Dictionary<string, (Color, string)>>(json);
}
private async void HandleGlobalInfo()
{
// _open = true;
// _edit = true;
// StateHasChanged();
var parameters = new DialogParameters<ExamGlobalInfoDialog> { { x => x.Assignment, ExamContent } };
var dialog = await DialogService.ShowAsync<ExamGlobalInfoDialog>("Exam_GlobalInfo", parameters);
var result = await dialog.Result;
if (!result.Canceled)
{
}
StateHasChanged();
}
} }

View File

@@ -1,5 +1,6 @@
@using Entities.Contracts @using Entities.Contracts
@using Entities.DTO @using Entities.DTO
@using Newtonsoft.Json
@using TechHelper.Client.Exam @using TechHelper.Client.Exam
@using TechHelper.Client.Pages.Exam.QuestionCard @using TechHelper.Client.Pages.Exam.QuestionCard
@@ -22,6 +23,11 @@
<MudIconButton Color="Color.Tertiary" Icon="@Icons.Material.Filled.ExpandMore" Size="Size.Small" /> <MudIconButton Color="Color.Tertiary" Icon="@Icons.Material.Filled.ExpandMore" Size="Size.Small" />
<MudIconButton Icon="@Icons.Material.Filled.Delete" aria-label="delete" Size="Size.Small" /> <MudIconButton Icon="@Icons.Material.Filled.Delete" aria-label="delete" Size="Size.Small" />
<MudChip T="string" Color="Color.Info" Class="justify-content-end">@ExamStruct.StructType</MudChip> <MudChip T="string" Color="Color.Info" Class="justify-content-end">@ExamStruct.StructType</MudChip>
<MudChip T="string" Color="Color.Warning" Class="justify-content-end">@(ExamStruct.QType == string.Empty ? "" : QuestionTypes[ExamStruct.QType].DisplayName)</MudChip>
@if(ExamStruct.Question!=null)
{
<MudRating SelectedValue="@((int)ExamStruct.Question.DifficultyLevel)" ReadOnly="true" Size="Size.Small" />
}
</MudStack> </MudStack>
</MudStack> </MudStack>
@@ -75,6 +81,22 @@
[Parameter] [Parameter]
public string Style { get; set; } = "background-color : #eeeeee"; public string Style { get; set; } = "background-color : #eeeeee";
Dictionary<string, QuestionDisplayTypeData> QuestionTypes = new Dictionary<string, QuestionDisplayTypeData>();
[Inject]
private ILocalStorageService LocalStorageService { get; set; }
protected override void OnInitialized()
{
base.OnInitialized();
var cs = LocalStorageService.GetItem<string>("GlobalInfo");
var GlobalInfo = JsonConvert.DeserializeObject<Dictionary<string, QuestionDisplayTypeData>>(cs);
if (GlobalInfo != null)
{
QuestionTypes = GlobalInfo;
}
}
private async void HandleClick() private async void HandleClick()
{ {
await ClickedStruct.InvokeAsync(ExamStruct); await ClickedStruct.InvokeAsync(ExamStruct);
@@ -84,4 +106,10 @@
{ {
await ClickedStruct.InvokeAsync(clickedChildExamStruct); await ClickedStruct.InvokeAsync(clickedChildExamStruct);
} }
private void HandleSelected(int num)
{
ExamStruct.Question.DifficultyLevel = (DifficultyLevel)num;
}
} }

View File

@@ -1,10 +1,29 @@
@using Entities.DTO @using Entities.DTO
@using Entities.Contracts
@using Newtonsoft.Json
@using TechHelper.Client.Exam @using TechHelper.Client.Exam
<MudPaper Elevation="1" Class="ma-4 pa-5 rounded-xl"> <MudPaper Elevation="1" Class="ma-4 pa-5 rounded-xl">
@* <MudText>@Question.Id</MudText> *@ @* <MudText>@Question.Id</MudText> *@
<MudText Class="mt-3" Typo="Typo.button"><b>问题属性</b></MudText> <MudText Class="mt-3" Typo="Typo.button"><b>问题属性</b></MudText>
<MudChipSet T="string" SelectedValue="@Question.QType" CheckMark SelectionMode="SelectionMode.SingleSelection" SelectedValueChanged="HandleQTSelectedValueChanged">
@foreach (var item in QuestionTypes)
{
var qt = item;
@* Style = "@($"background - color:{ item.Value.Color} ")"*@
<MudChip Style="@(qt.Key == Question.QType ?
$"background-color:#ffffff; color:{item.Value.Color}" :
$"background-color:{item.Value.Color}; color:#ffffff")"
Value="@item.Key">
@item.Value.DisplayName
</MudChip>
}
</MudChipSet>
<MudRating SelectedValue="@(diffi)" SelectedValueChanged="HandleSelected" Size="Size.Small" />
<MudTextField @bind-Value="Question.Title" Label="Title" Variant="Variant.Text" Margin="Margin.Dense" AutoGrow="true" /> <MudTextField @bind-Value="Question.Title" Label="Title" Variant="Variant.Text" Margin="Margin.Dense" AutoGrow="true" />
<MudTextField @bind-Value="Question.Answer" Label="Answer" Variant="Variant.Text" Adornment="Adornment.End" AdornmentText="." Margin="Margin.Dense" AutoGrow="true" /> <MudTextField @bind-Value="Question.Answer" Label="Answer" Variant="Variant.Text" Adornment="Adornment.End" AdornmentText="." Margin="Margin.Dense" AutoGrow="true" />
<MudTextField @bind-Value="Question.Options" Label="Options" Variant="Variant.Text" Adornment="Adornment.End" AdornmentText="." Margin="Margin.Dense" AutoGrow="true" /> <MudTextField @bind-Value="Question.Options" Label="Options" Variant="Variant.Text" Adornment="Adornment.End" AdornmentText="." Margin="Margin.Dense" AutoGrow="true" />
@@ -15,4 +34,35 @@
@code { @code {
[Parameter] [Parameter]
public QuestionDto Question { get; set; } = new QuestionDto(); public QuestionDto Question { get; set; } = new QuestionDto();
public int diffi = 0;
Dictionary<string, QuestionDisplayTypeData> QuestionTypes = new Dictionary<string, QuestionDisplayTypeData>();
[Inject]
private ILocalStorageService LocalStorageService { get; set; }
protected override void OnInitialized()
{
base.OnInitialized();
var cs = LocalStorageService.GetItem<string>("GlobalInfo");
var GlobalInfo = JsonConvert.DeserializeObject<Dictionary<string, QuestionDisplayTypeData>>(cs);
if (GlobalInfo != null)
{
QuestionTypes = GlobalInfo;
}
}
private void HandleSelectedValueChanged(QuestionType type)
{
Question.Type = type;
}
private void HandleSelected(int num)
{
Question.DifficultyLevel = (DifficultyLevel)num;
}
private void HandleQTSelectedValueChanged(string type)
{
Question.QType = type;
StateHasChanged();
}
} }

View File

@@ -0,0 +1,32 @@
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject NavigationManager Navigation
@inject IAuthenticationClientService AuthenticationClientService
<AuthorizeView>
<Authorized>
<MudText>
Hello, @context.User.Identity.Name!
</MudText>
<MudButton OnClick="Logout"> LOGOUT </MudButton>
</Authorized>
<NotAuthorized>
<MudButton Class="" Href="Login"> Login </MudButton>
</NotAuthorized>
</AuthorizeView>
@code {
[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; }
private async Task Logout()
{
await AuthenticationClientService.LogoutAsync();
Navigation.NavigateTo("/");
}
private void LoginIN()
{
Navigation.NavigateToLogin("/login");
}
}

View File

@@ -0,0 +1,11 @@
<MudPaper Class="d-flex flex-row my-3" Height="@Height" Width="@Width" Elevation="0">
<MudIcon Icon="@Icons.Custom.Brands.MudBlazor" Color="Color.Primary" />
<MudText Class="mx-3"><b>TechHelper</b></MudText>
</MudPaper>
@code {
[Parameter]
public string Height { get; set; } = "30px";
[Parameter]
public string Width { get; set; } = "100%";
}

View File

@@ -0,0 +1,8 @@
<MudPaper Class="d-flex flex-grow-1 rounded-xl pl-6" Elevation="0">
<MudTextField @bind-Value="TextValue" Label="Search for everything" Variant="Variant.Text"></MudTextField>
<MudIconButton Icon="@Icons.Material.Filled.Search"></MudIconButton>
</MudPaper>
@code {
public string TextValue { get; set; }
}

View File

@@ -0,0 +1,35 @@
@inherits ErrorBoundary
@inject ISnackbar Snackbar
@if (CurrentException is null)
{
@ChildContent
}
else if (ErrorContent is not null)
{
@ErrorContent(CurrentException)
}
else
{
<div class="custom-error-ui">
<MudAlert Severity="Severity.Error" Icon="@Icons.Material.Filled.Error">
<MudText>组件加载或执行时出现了问题。</MudText>
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
Class="mt-3">
重试
</MudButton>
</MudAlert>
</div>
}
@code {
protected override async Task OnErrorAsync(Exception exception)
{
Snackbar.Add("操作失败,请重试或联系管理员。", Severity.Error);
await base.OnErrorAsync(exception);
}
}

View File

@@ -8,9 +8,6 @@
</Authorized> </Authorized>
</AuthorizeView> </AuthorizeView>
<AssignmentInfoCard></AssignmentInfoCard>
@code { @code {
[CascadingParameter] [CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; } private Task<AuthenticationState> authenticationStateTask { get; set; }

View File

@@ -0,0 +1,11 @@
<MudPaper Class="d-flex my-3 flex-column justify-content-center mx-auto" Height="@Height" Width="@Width" Elevation="0">
<MudImage Width="150" Height="150" Class="rounded-pill justify-content-center" Src="ref/Keda.png"></MudImage>
<MudText Class="mx-3"><b>TechHelper</b></MudText>
</MudPaper>
@code {
[Parameter]
public string Height { get; set; } = "250px";
[Parameter]
public string Width { get; set; } = "100%";
}

View File

@@ -0,0 +1,28 @@
@using static TechHelper.Client.Pages.Student.BaseInfoCard.StudentSubmissionPreviewTableCard
@if(StudentSubmission!=null)
{
<MudPaper Class="ma-2 pa-2 rounded-xl d-flex w-100 flex-nowrap">
<MudText Class="flex-grow-0 flex-shrink-0" Style="width:60%"> @StudentSubmission.StudentName </MudText>
<MudText Class="flex-grow-0 flex-shrink-0 text-start" Style="width:10%"> @StudentSubmission.TotalProblems </MudText>
<MudText Class="flex-grow-0 flex-shrink-0 text-start" Style="width:10%"> @StudentSubmission.ErrorCount </MudText>
<MudText Class="flex-grow-0 flex-shrink-0 text-start" Style="width:10%"> @StudentSubmission.TimeSpent </MudText>
<MudText Class="flex-grow-0 flex-shrink-0 text-start" Style="width:10%"> @StudentSubmission.Score </MudText>
</MudPaper>
}
else
{
<MudPaper Class="ma-1 pa-2 rounded-xl d-flex w-100 flex-nowrap">
<MudText Class="flex-grow-0 flex-shrink-0" Style="width:60%"> 名称 </MudText>
<MudText Class="flex-grow-0 flex-shrink-0 text-start" Style="width:10%"> 题目总数 </MudText>
<MudText Class="flex-grow-0 flex-shrink-0 text-start" Style="width:10%"> 错误总数 </MudText>
<MudText Class="flex-grow-0 flex-shrink-0 text-start" Style="width:10%"> 时间 </MudText>
<MudText Class="flex-grow-0 flex-shrink-0 text-start" Style="width:10%"> 得分 </MudText>
</MudPaper>
}
@code{
[Parameter]
public StudentSubmission StudentSubmission{ get; set; }
}

View File

@@ -0,0 +1,39 @@
<MudPaper Class="ma-2 pa-2 rounded-xl d-flex flex-column flex-grow-1 overflow-auto" MaxHeight="100%">
<StudentSubmissionPreviewCard />
@foreach (var submission in _studentSubmissions)
{
<StudentSubmissionPreviewCard StudentSubmission="@submission" />
}
</MudPaper>
@code {
// 假设的学生提交数据模型
public class StudentSubmission
{
public string StudentName { get; set; }
public int TotalProblems { get; set; }
public int ErrorCount { get; set; }
public TimeSpan TimeSpent { get; set; }
public int Score { get; set; }
}
// 模拟数据列表
private List<StudentSubmission> _studentSubmissions = new();
protected override void OnInitialized()
{
// 模拟获取或初始化数据实际应用中可能来自数据库或API
_studentSubmissions = new List<StudentSubmission>
{
new() { StudentName = "张三", TotalProblems = 10, ErrorCount = 2, TimeSpent = TimeSpan.FromMinutes(25), Score = 80 },
new() { StudentName = "李四", TotalProblems = 10, ErrorCount = 1, TimeSpent = TimeSpan.FromMinutes(20), Score = 90 },
new() { StudentName = "王五", TotalProblems = 10, ErrorCount = 5, TimeSpent = TimeSpan.FromMinutes(30), Score = 50 },
new() { StudentName = "赵六", TotalProblems = 10, ErrorCount = 3, TimeSpent = TimeSpan.FromMinutes(28), Score = 70 },
new() { StudentName = "钱七", TotalProblems = 10, ErrorCount = 0, TimeSpent = TimeSpan.FromMinutes(18), Score = 100 }
// ... 可以添加更多模拟数据
};
}
}

View File

@@ -0,0 +1,79 @@
@using MudBlazor
@using System.Collections.Generic
<MudDataGrid Items="@Elements.Take(4)" Hover="@_hover" Dense="@_dense" Striped="@_striped" Bordered="@_bordered"
RowStyleFunc="@_rowStyleFunc" RowClass="my-2 rounded-xl">
<Columns >
<PropertyColumn Property="x => x.Number" Title="Nr" />
<PropertyColumn Property="x => x.Sign" />
<PropertyColumn Property="x => x.Name" CellStyleFunc="@_cellStyleFunc" />
<PropertyColumn Property="x => x.Position" />
<PropertyColumn Property="x => x.Molar" Title="Molar mass" />
</Columns>
</MudDataGrid>
<div class="d-flex flex-wrap mt-4">
<MudSwitch @bind-Value="_hover" Color="Color.Primary">Hover</MudSwitch>
<MudSwitch @bind-Value="_dense" Color="Color.Secondary">Dense</MudSwitch>
<MudSwitch @bind-Value="_striped" Color="Color.Tertiary">Striped</MudSwitch>
<MudSwitch @bind-Value="_bordered" Color="Color.Warning">Bordered</MudSwitch>
</div>
@code {
// Element类定义
public class Element
{
public int Number { get; set; }
public string Sign { get; set; }
public string Name { get; set; }
public int Position { get; set; }
public decimal Molar { get; set; }
}
// 示例数据
private IEnumerable<Element> Elements = new List<Element>
{
new Element { Number = 1, Sign = "H", Name = "Hydrogen", Position = 1, Molar = 1.008m },
new Element { Number = 2, Sign = "He", Name = "Helium", Position = 0, Molar = 4.0026m },
new Element { Number = 3, Sign = "Li", Name = "Lithium", Position = 1, Molar = 6.94m },
new Element { Number = 4, Sign = "Be", Name = "Beryllium", Position = 2, Molar = 9.0122m },
new Element { Number = 5, Sign = "B", Name = "Boron", Position = 13, Molar = 10.81m }
};
private bool _hover;
private bool _dense;
private bool _striped;
private bool _bordered;
// 行样式函数Position为0的行显示为斜体
private Func<Element, int, string> _rowStyleFunc => (x, i) =>
{
if (x.Position == 0)
return "font-style:italic";
return "";
};
// 单元格样式函数:根据元素编号设置背景色,根据摩尔质量设置字体粗细
private Func<Element, string> _cellStyleFunc => x =>
{
string style = "";
if (x.Number == 1)
style += "background-color:#8CED8C"; // 浅绿色
else if (x.Number == 2)
style += "background-color:#E5BDE5"; // 浅紫色
else if (x.Number == 3)
style += "background-color:#EACE5D"; // 浅黄色
else if (x.Number == 4)
style += "background-color:#F1F165"; // 浅黄色
if (x.Molar > 5)
style += ";font-weight:bold";
return style;
};
}

View File

@@ -0,0 +1,18 @@
<MudPaper Class="doc-section-component-container">
<MudChart ChartType="ChartType.Bar" ChartSeries="@_series" Height="150px" Width="100%" XAxisLabels="@_xAxisLabels" AxisChartOptions="_axisChartOptions"></MudChart>
</MudPaper>
@code{
private string[] _xAxisLabels = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep" };
private AxisChartOptions _axisChartOptions = new AxisChartOptions
{
};
protected override void OnInitialized()
{
_axisChartOptions.MatchBoundsToSize = true;
}
private List<ChartSeries> _series = new List<ChartSeries>()
{
new ChartSeries() { Name = "United States", Data = new double[] { 40, 20, 25, 27, 46, 60, 48, 80, 15 } }
};
}

View File

@@ -1,104 +1,26 @@
 @using TechHelper.Client.Pages.Common.Exam;
@using TechHelper.Client.Pages.Student.BaseInfoCard;
@using TechHelper.Client.Pages.Common;
<MudPaper Class="w-100 h-100 d-flex flex-row">
<MudPaper Class="flex-grow-1 w-100 h-100 ma-auto"> <MudPaper Class="flex-grow-1 mx-2 d-flex flex-column">
<MudGrid Class="w-100 h-100"> <AssignmentInfoCard></AssignmentInfoCard>
<MudItem xs="12" sm="4"> <MudPaper Class="d-flex flex-row">
<MudPaper Style="background-color:transparent" Class="w-100 justify-content-center"> <NotifyCard></NotifyCard>
<MudChart ChartType="ChartType.Donut" Width="200px" Height="200px" InputData="@data" InputLabels="@labels" Class="ma-auto"> <HomeworkCard></HomeworkCard>
<CustomGraphics> <NotifyCard></NotifyCard>
<text class="donut-inner-text" x="50%" y="35%" dominant-baseline="middle" text-anchor="middle" fill="black" font-family="Helvetica" font-size="20">Total</text> <HomeworkCard></HomeworkCard>
<text class="donut-inner-text" x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" fill="black" font-family="Helvetica" font-size="50">@data.Sum().ToString()</text>
</CustomGraphics>
</MudChart>
</MudPaper> </MudPaper>
<StudentSubmissionPreviewTableCard></StudentSubmissionPreviewTableCard>
<MudPaper Style="background-color:transparent" Class="w-100 pa-5">
<TechHelper.Client.Pages.Common.SimpleCard Style="background-color:#ff4081">
<BodyContent>
<MudText>BodyContent</MudText>
</BodyContent>
<TitleContent>
<MudText>TitleContent</MudText>
</TitleContent>
<FooterContent>
<MudText>FooterContent</MudText>
</FooterContent>
</TechHelper.Client.Pages.Common.SimpleCard>
<TechHelper.Client.Pages.Common.SimpleCard Style="background-color:#1ec8a5">
<BodyContent>
<MudText>BodyContent</MudText>
</BodyContent>
<TitleContent>
<MudText>TitleContent</MudText>
</TitleContent>
<FooterContent>
<MudText>FooterContent</MudText>
</FooterContent>
</TechHelper.Client.Pages.Common.SimpleCard>
<TechHelper.Client.Pages.Common.SimpleCard Style="background-color:#4680ff">
<TitleContent>
<MudText Typo="Typo.body1">TitleContent</MudText>
</TitleContent>
<BodyContent>
<MudText Typo="Typo.button"><b>BodyContent</b></MudText>
</BodyContent>
<FooterContent>
<MudText Typo="Typo.body2">leran about this curson</MudText>
</FooterContent>
</TechHelper.Client.Pages.Common.SimpleCard>
</MudPaper> </MudPaper>
</MudItem> <MudPaper Width="300px" Class="mx-2 align-content-center d-flex flex-column flex-grow-1">
<HeadIconCard></HeadIconCard>
<TotalErrorQuestionType></TotalErrorQuestionType>
<MudItem xs="12" sm="8">
<MudPaper Style="background-color:transparent" Class="w-100 h-100">
<TechHelper.Client.Pages.Common.SimpleCard Style="background-color:#c2bef8" Height="350px">
<TitleContent>
<MudText Typo="Typo.button"><b>Visits Summary:</b></MudText>
</TitleContent>
<BodyContent>
<MudChart ChartType="ChartType.Line" LegendPosition="Position.Left" Class="pt-55" ChartSeries="@Series" XAxisLabels="@XAxisLabels" Height="110%" Width="100%" AxisChartOptions="_axisChartOptions" ChartOptions="options"></MudChart>
</BodyContent>
<FooterContent>
<MudText Typo="Typo.body2">leran about this curson</MudText>
</FooterContent>
</TechHelper.Client.Pages.Common.SimpleCard>
@* <TechHelper.Client.Pages.Common.SimpleCard Style="background-color:#c2bef8" Height="100%">
<TitleContent>
<MudText Typo="Typo.button"><b>Visits Summary:</b></MudText>
</TitleContent>
<BodyContent>
<MudDataGrid Items="@Elements" Filterable="true" FilterMode="@_filterMode" FilterCaseSensitivity="@_caseSensitivity">
<Columns>
<PropertyColumn Property="x => x.Number" Title="Nr" Filterable="false" />
<PropertyColumn Property="x => x.Sign" />
<PropertyColumn Property="x => x.Name" />
<PropertyColumn Property="x => x.Position" Filterable="false" />
<PropertyColumn Property="x => x.Molar" Title="Molar mass" />
<PropertyColumn Property="x => x.Group" Title="Category" />
</Columns>
<PagerContent>
<MudDataGridPager T="Element" />
</PagerContent>
</MudDataGrid>
</BodyContent>
<FooterContent>
<MudText Typo="Typo.body2">leran about this curson</MudText>
</FooterContent>
</TechHelper.Client.Pages.Common.SimpleCard> *@
</MudPaper> </MudPaper>
</MudItem>
</MudGrid>
</MudPaper> </MudPaper>
@code { @code {
public double[] data = { 25, 77, 28, 5 }; public double[] data = { 25, 77, 28, 5 };
public string[] labels = { "Oil", "Coal", "Gas", "Biomass" }; public string[] labels = { "Oil", "Coal", "Gas", "Biomass" };

View File

@@ -0,0 +1,12 @@
@using TechHelper.Client.Pages.Common;
<SimpleCard>
<TitleContent>
<MudText> 作业</MudText>
</TitleContent>
<BodyContent>
<MudText> 你暂时还没有任何作业 </MudText>
</BodyContent>
</SimpleCard>
@code {
}

View File

@@ -0,0 +1,12 @@
@using TechHelper.Client.Pages.Common;
<SimpleCard>
<TitleContent>
<MudText> 通知</MudText>
</TitleContent>
<BodyContent>
<MudText> 暂时没有任何通知</MudText>
</BodyContent>
</SimpleCard>
@code {
}

View File

@@ -0,0 +1,11 @@
@page "/studentSubmissionView"
@using TechHelper.Client.Pages.Student.BaseInfoCard
<MudPaper Class="rounded-xl ma-2 px-2 overflow-auto">
<TechHelper.Client.Pages.Common.ExamGlobalInfoDialog>
</TechHelper.Client.Pages.Common.ExamGlobalInfoDialog>
<StudentSubmissionPreviewTableCard />
</MudPaper>

View File

@@ -45,6 +45,7 @@ builder.Services.AddScoped<IClassServices, ClasssServices>();
builder.Services.AddScoped<IEmailSender, QEmailSender>(); builder.Services.AddScoped<IEmailSender, QEmailSender>();
builder.Services.AddScoped<HttpInterceptorHandlerService>(); builder.Services.AddScoped<HttpInterceptorHandlerService>();
builder.Services.AddScoped<IAIService, AiService>(); builder.Services.AddScoped<IAIService, AiService>();
builder.Services.AddScoped<INoteService, NoteService>();
builder.Services.AddScoped<IUserServices, UserServices>(); builder.Services.AddScoped<IUserServices, UserServices>();
builder.Services.AddHttpClient("WebApiClient", client => builder.Services.AddHttpClient("WebApiClient", client =>
{ {

View File

@@ -0,0 +1,14 @@
using Entities.DTO;
using TechHelper.Services;
namespace TechHelper.Client.Services
{
public interface INoteService
{
public Task<ApiResponse> AddNote(GlobalDto dto);
public Task<ApiResponse> DeleteNote(byte id);
public Task<ApiResponse> GetAllNotes();
public Task<ApiResponse> GetNote(byte id);
public Task<ApiResponse> UpdateNote(GlobalDto dto);
}
}

View File

@@ -0,0 +1,151 @@
using Entities.DTO;
using System.Net.Http.Json;
using TechHelper.Client.AI;
using TechHelper.Services;
namespace TechHelper.Client.Services
{
public class NoteService : INoteService
{
private readonly HttpClient _client;
public NoteService(HttpClient client)
{
_client = client;
}
/// <summary>
/// 添加一个新笔记
/// </summary>
/// <param name="dto">包含笔记数据的数据传输对象</param>
/// <returns>操作结果</returns>
public async Task<ApiResponse> AddNote(GlobalDto dto)
{
try
{
var response = await _client.PostAsJsonAsync("note", dto);
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadFromJsonAsync<ApiResponse>();
return result ?? ApiResponse.Success("操作成功。");
}
else
{
var errorContent = await response.Content.ReadAsStringAsync();
return ApiResponse.Error($"添加失败。状态码: {response.StatusCode}。详情: {errorContent}");
}
}
catch (HttpRequestException ex)
{
return ApiResponse.Error($"网络请求错误: {ex.Message}");
}
}
/// <summary>
/// 根据 ID 删除一个笔记
/// </summary>
/// <param name="id">要删除的笔记的 ID</param>
/// <returns>操作结果</returns>
public async Task<ApiResponse> DeleteNote(byte id)
{
try
{
var response = await _client.DeleteAsync($"note/{id}");
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadFromJsonAsync<ApiResponse>();
return result ?? ApiResponse.Success("删除成功。");
}
else
{
var errorContent = await response.Content.ReadAsStringAsync();
return ApiResponse.Error($"删除失败。状态码: {response.StatusCode}。详情: {errorContent}");
}
}
catch (HttpRequestException ex)
{
return ApiResponse.Error($"网络请求错误: {ex.Message}");
}
}
/// <summary>
/// 获取所有笔记
/// </summary>
/// <returns>包含所有笔记列表的操作结果</returns>
public async Task<ApiResponse> GetAllNotes()
{
try
{
var response = await _client.GetAsync("note");
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadFromJsonAsync<ApiResponse>();
return result ?? ApiResponse.Success("获取成功。");
}
else
{
var errorContent = await response.Content.ReadAsStringAsync();
return ApiResponse.Error($"获取失败。状态码: {response.StatusCode}。详情: {errorContent}");
}
}
catch (HttpRequestException ex)
{
return ApiResponse.Error($"网络请求错误: {ex.Message}");
}
}
/// <summary>
/// 根据 ID 获取单个笔记
/// </summary>
/// <param name="id">要获取的笔记的 ID</param>
/// <returns>包含单个笔记数据的操作结果</returns>
public async Task<ApiResponse> GetNote(byte id)
{
try
{
var response = await _client.GetAsync($"note/{id}");
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadFromJsonAsync<ApiResponse>();
return result ?? ApiResponse.Success("获取成功。");
}
else
{
var errorContent = await response.Content.ReadAsStringAsync();
return ApiResponse.Error($"获取失败。状态码: {response.StatusCode}。详情: {errorContent}");
}
}
catch (HttpRequestException ex)
{
return ApiResponse.Error($"网络请求错误: {ex.Message}");
}
}
public async Task<ApiResponse> UpdateNote(GlobalDto dto)
{
try
{
var response = await _client.PutAsJsonAsync("note", dto);
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadFromJsonAsync<ApiResponse>();
return result ?? ApiResponse.Success("更新成功。");
}
else
{
var errorContent = await response.Content.ReadAsStringAsync();
return ApiResponse.Error($"更新失败。状态码: {response.StatusCode}。详情: {errorContent}");
}
}
catch (HttpRequestException ex)
{
return ApiResponse.Error($"网络请求错误: {ex.Message}");
}
}
}
}

View File

@@ -20,3 +20,4 @@
@using TechHelper.Client.Pages.Author @using TechHelper.Client.Pages.Author
@using TechHelper.Client.Pages @using TechHelper.Client.Pages
@using Blazored.TextEditor @using Blazored.TextEditor
@using TechHelper.Client.Pages.Global.MainStruct

View File

@@ -36,6 +36,11 @@
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.js"></script> <script src="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.js"></script>
<script src="_content/Blazored.TextEditor/quill-blot-formatter.min.js"></script> <script src="_content/Blazored.TextEditor/quill-blot-formatter.min.js"></script>
<script src="_content/Blazored.TextEditor/Blazored-BlazorQuill.js"></script> <script src="_content/Blazored.TextEditor/Blazored-BlazorQuill.js"></script>
<script async
defer
src="https://maxkb.eazygame.cn/chat/api/embed?protocol=https&host=maxkb.eazygame.cn&token=be77837293de870e">
</script>
</body> </body>

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -21,6 +21,7 @@ namespace TechHelper.Context
public DbSet<Submission> Submissions { get; set; } public DbSet<Submission> Submissions { get; set; }
public DbSet<SubmissionDetail> SubmissionDetails { get; set; } public DbSet<SubmissionDetail> SubmissionDetails { get; set; }
public DbSet<QuestionContext> QuestionContexts { get; set; } public DbSet<QuestionContext> QuestionContexts { get; set; }
public DbSet<Global> Globals { get; set; }
protected override void OnModelCreating(ModelBuilder builder) protected override void OnModelCreating(ModelBuilder builder)
{ {

View File

@@ -2,6 +2,10 @@
using AutoMapper.Internal.Mappers; using AutoMapper.Internal.Mappers;
using Entities.Contracts; using Entities.Contracts;
using Entities.DTO; using Entities.DTO;
using Newtonsoft.Json;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace TechHelper.Context namespace TechHelper.Context
{ {
@@ -54,6 +58,12 @@ namespace TechHelper.Context
CreateMap<SubmissionDetailDto, SubmissionDetail>().ReverseMap(); CreateMap<SubmissionDetailDto, SubmissionDetail>().ReverseMap();
CreateMap<SubjectTypeMetadataDto, Global>()
.ForMember(dest => dest.Info, opt => opt.MapFrom(src => JsonConvert.SerializeObject(src.Data)));
CreateMap<Global, SubjectTypeMetadataDto>()
.ForMember(dest => dest.Data, opt => opt.MapFrom(src => JsonConvert.DeserializeObject<Dictionary<string, (string Color, string DisplayName)>>(src.Info)));
} }
} }

View File

@@ -0,0 +1,91 @@
using Entities.DTO;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using TechHelper.Services;
namespace TechHelper.Server.Controllers
{
[Route("api/note")]
[ApiController]
public class NoteController : ControllerBase
{
private readonly INoteService _noteService;
// 通过依赖注入获取 NoteService
public NoteController(INoteService noteService)
{
_noteService = noteService;
}
/// <summary>
/// 获取所有全局数据。
/// GET: api/Note
/// </summary>
[HttpGet]
public async Task<IActionResult> GetAll([FromQuery] QueryParameter query)
{
var response = await _noteService.GetAllAsync(query);
return Ok(response);
}
/// <summary>
/// 根据 ID 获取单个全局数据。
/// GET: api/Note/{id}
/// </summary>
[HttpGet("{id}")]
public async Task<IActionResult> Get(byte id)
{
var response = await _noteService.GetAsync(id);
if (!response.Status)
{
return NotFound(response);
}
return Ok(response);
}
/// <summary>
/// 添加新的全局数据。
/// POST: api/Note
/// </summary>
[HttpPost]
public async Task<IActionResult> Add([FromBody] GlobalDto model)
{
var response = await _noteService.AddAsync(model);
if (!response.Status)
{
return BadRequest(response);
}
return Ok(response);
}
/// <summary>
/// 更新已存在的全局数据。
/// PUT: api/Note
/// </summary>
[HttpPut]
public async Task<IActionResult> Update([FromBody] GlobalDto model)
{
var response = await _noteService.UpdateAsync(model);
if (!response.Status)
{
return NotFound(response);
}
return Ok(response);
}
/// <summary>
/// 根据 ID 删除全局数据。
/// DELETE: api/Note/{id}
/// </summary>
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(byte id)
{
var response = await _noteService.DeleteAsync(id);
if (!response.Status)
{
return NotFound(response);
}
return Ok(response);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,93 @@
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 question_qt_update : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("0775702a-5db7-4747-94d0-4376fad2b58b"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("37f41430-0cb7-44e5-988b-976200bd602d"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("df89b9a0-65ef-42dd-b2cb-e59997a72e70"));
migrationBuilder.AddColumn<string>(
name: "QType",
table: "questions",
type: "longtext",
nullable: false)
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AddColumn<byte>(
name: "Type",
table: "assignment_questions",
type: "tinyint unsigned",
nullable: false,
defaultValue: (byte)0);
migrationBuilder.InsertData(
table: "AspNetRoles",
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
values: new object[,]
{
{ new Guid("67de6514-79a5-4a9c-b54c-13cac296b0c6"), null, "Teacher", "TEACHER" },
{ new Guid("94f0d8d9-ffba-4e28-b578-8596363d42ae"), null, "Student", "STUDENT" },
{ new Guid("bf46ed67-2dc9-40f8-8717-37dd3572f274"), null, "Administrator", "ADMINISTRATOR" }
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("67de6514-79a5-4a9c-b54c-13cac296b0c6"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("94f0d8d9-ffba-4e28-b578-8596363d42ae"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("bf46ed67-2dc9-40f8-8717-37dd3572f274"));
migrationBuilder.DropColumn(
name: "QType",
table: "questions");
migrationBuilder.DropColumn(
name: "Type",
table: "assignment_questions");
migrationBuilder.InsertData(
table: "AspNetRoles",
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
values: new object[,]
{
{ new Guid("0775702a-5db7-4747-94d0-4376fad2b58b"), null, "Teacher", "TEACHER" },
{ new Guid("37f41430-0cb7-44e5-988b-976200bd602d"), null, "Administrator", "ADMINISTRATOR" },
{ new Guid("df89b9a0-65ef-42dd-b2cb-e59997a72e70"), null, "Student", "STUDENT" }
});
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,89 @@
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 question_qt_update_2 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("67de6514-79a5-4a9c-b54c-13cac296b0c6"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("94f0d8d9-ffba-4e28-b578-8596363d42ae"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("bf46ed67-2dc9-40f8-8717-37dd3572f274"));
migrationBuilder.CreateTable(
name: "global",
columns: table => new
{
id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
Area = table.Column<byte>(type: "tinyint unsigned", nullable: false),
Info = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_global", x => x.id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.InsertData(
table: "AspNetRoles",
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
values: new object[,]
{
{ new Guid("49854839-b861-4d42-bdbe-96b1a66c25ef"), null, "Teacher", "TEACHER" },
{ new Guid("5c7a7971-2610-4bce-9e41-0caffd5a5558"), null, "Student", "STUDENT" },
{ new Guid("83ff7de8-edc9-47f8-8de8-22f892ca6bb5"), null, "Administrator", "ADMINISTRATOR" }
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "global");
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("49854839-b861-4d42-bdbe-96b1a66c25ef"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("5c7a7971-2610-4bce-9e41-0caffd5a5558"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("83ff7de8-edc9-47f8-8de8-22f892ca6bb5"));
migrationBuilder.InsertData(
table: "AspNetRoles",
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
values: new object[,]
{
{ new Guid("67de6514-79a5-4a9c-b54c-13cac296b0c6"), null, "Teacher", "TEACHER" },
{ new Guid("94f0d8d9-ffba-4e28-b578-8596363d42ae"), null, "Student", "STUDENT" },
{ new Guid("bf46ed67-2dc9-40f8-8717-37dd3572f274"), null, "Administrator", "ADMINISTRATOR" }
});
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,82 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
namespace TechHelper.Server.Migrations
{
/// <inheritdoc />
public partial class question_qt_update_3 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("49854839-b861-4d42-bdbe-96b1a66c25ef"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("5c7a7971-2610-4bce-9e41-0caffd5a5558"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("83ff7de8-edc9-47f8-8de8-22f892ca6bb5"));
migrationBuilder.AddColumn<byte>(
name: "SubjectArea",
table: "AspNetUsers",
type: "tinyint unsigned",
nullable: false,
defaultValue: (byte)0);
migrationBuilder.InsertData(
table: "AspNetRoles",
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
values: new object[,]
{
{ new Guid("2670f35a-df0c-4071-8879-80eb99d138a1"), null, "Teacher", "TEACHER" },
{ new Guid("8c6c5e8e-ef00-444c-9c7c-cba5cd6f7043"), null, "Student", "STUDENT" },
{ new Guid("9eda9d90-0cd2-4fbe-b07e-f90bd01f32db"), null, "Administrator", "ADMINISTRATOR" }
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("2670f35a-df0c-4071-8879-80eb99d138a1"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("8c6c5e8e-ef00-444c-9c7c-cba5cd6f7043"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("9eda9d90-0cd2-4fbe-b07e-f90bd01f32db"));
migrationBuilder.DropColumn(
name: "SubjectArea",
table: "AspNetUsers");
migrationBuilder.InsertData(
table: "AspNetRoles",
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
values: new object[,]
{
{ new Guid("49854839-b861-4d42-bdbe-96b1a66c25ef"), null, "Teacher", "TEACHER" },
{ new Guid("5c7a7971-2610-4bce-9e41-0caffd5a5558"), null, "Student", "STUDENT" },
{ new Guid("83ff7de8-edc9-47f8-8de8-22f892ca6bb5"), null, "Administrator", "ADMINISTRATOR" }
});
}
}
}

View File

@@ -219,6 +219,9 @@ namespace TechHelper.Server.Migrations
.HasColumnType("varchar(1024)") .HasColumnType("varchar(1024)")
.HasColumnName("title"); .HasColumnName("title");
b.Property<byte>("Type")
.HasColumnType("tinyint unsigned");
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("AssignmentId"); b.HasIndex("AssignmentId");
@@ -332,6 +335,25 @@ namespace TechHelper.Server.Migrations
b.ToTable("class_teachers", (string)null); b.ToTable("class_teachers", (string)null);
}); });
modelBuilder.Entity("Entities.Contracts.Global", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)")
.HasColumnName("id");
b.Property<byte>("Area")
.HasColumnType("tinyint unsigned");
b.Property<string>("Info")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("global");
});
modelBuilder.Entity("Entities.Contracts.KeyPoint", b => modelBuilder.Entity("Entities.Contracts.KeyPoint", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
@@ -445,6 +467,10 @@ namespace TechHelper.Server.Migrations
.HasColumnType("longtext") .HasColumnType("longtext")
.HasColumnName("options"); .HasColumnName("options");
b.Property<string>("QType")
.IsRequired()
.HasColumnType("longtext");
b.Property<byte>("SubjectArea") b.Property<byte>("SubjectArea")
.HasMaxLength(100) .HasMaxLength(100)
.HasColumnType("tinyint unsigned") .HasColumnType("tinyint unsigned")
@@ -723,6 +749,9 @@ namespace TechHelper.Server.Migrations
b.Property<string>("SecurityStamp") b.Property<string>("SecurityStamp")
.HasColumnType("longtext"); .HasColumnType("longtext");
b.Property<byte>("SubjectArea")
.HasColumnType("tinyint unsigned");
b.Property<bool>("TwoFactorEnabled") b.Property<bool>("TwoFactorEnabled")
.HasColumnType("tinyint(1)"); .HasColumnType("tinyint(1)");
@@ -771,19 +800,19 @@ namespace TechHelper.Server.Migrations
b.HasData( b.HasData(
new new
{ {
Id = new Guid("df89b9a0-65ef-42dd-b2cb-e59997a72e70"), Id = new Guid("8c6c5e8e-ef00-444c-9c7c-cba5cd6f7043"),
Name = "Student", Name = "Student",
NormalizedName = "STUDENT" NormalizedName = "STUDENT"
}, },
new new
{ {
Id = new Guid("0775702a-5db7-4747-94d0-4376fad2b58b"), Id = new Guid("2670f35a-df0c-4071-8879-80eb99d138a1"),
Name = "Teacher", Name = "Teacher",
NormalizedName = "TEACHER" NormalizedName = "TEACHER"
}, },
new new
{ {
Id = new Guid("37f41430-0cb7-44e5-988b-976200bd602d"), Id = new Guid("9eda9d90-0cd2-4fbe-b07e-f90bd01f32db"),
Name = "Administrator", Name = "Administrator",
NormalizedName = "ADMINISTRATOR" NormalizedName = "ADMINISTRATOR"
}); });

View File

@@ -17,9 +17,9 @@ using Microsoft.OpenApi.Models;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(); // <EFBFBD><EFBFBD><EFBFBD><EFBFBD> MVC <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD> API) builder.Services.AddControllers(); // 添加 MVC 控制器服务 (用于 API)
// 2. <EFBFBD><EFBFBD><EFBFBD>ݿ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (DbContext) // 2. 数据库服务 (DbContext)
builder.Services.AddDbContext<ApplicationContext>(options => builder.Services.AddDbContext<ApplicationContext>(options =>
options.UseMySql( options.UseMySql(
builder.Configuration.GetConnectionString("XSDB"), builder.Configuration.GetConnectionString("XSDB"),
@@ -34,17 +34,18 @@ builder.Services.AddDbContext<ApplicationContext>(options =>
.AddCustomRepository<ClassTeacher, ClassTeacherRepository>() .AddCustomRepository<ClassTeacher, ClassTeacherRepository>()
.AddCustomRepository<Question, QuestionRepository>() .AddCustomRepository<Question, QuestionRepository>()
.AddCustomRepository<QuestionContext, QuestionContextRepository>() .AddCustomRepository<QuestionContext, QuestionContextRepository>()
.AddCustomRepository<Submission, SubmissionRepository>(); .AddCustomRepository<Submission, SubmissionRepository>()
.AddCustomRepository<Global, GlobalRepository>();
builder.Services.AddAutoMapper(typeof(AutoMapperProFile).Assembly); builder.Services.AddAutoMapper(typeof(AutoMapperProFile).Assembly);
// 3. <EFBFBD><EFBFBD><EFBFBD>÷<EFBFBD><EFBFBD><EFBFBD> (IOptions) // 3. 配置服务 (IOptions)
builder.Services.Configure<ApiConfiguration>(builder.Configuration.GetSection("ApiConfiguration")); builder.Services.Configure<ApiConfiguration>(builder.Configuration.GetSection("ApiConfiguration"));
builder.Services.Configure<JwtConfiguration>(builder.Configuration.GetSection("JWTSettings")); builder.Services.Configure<JwtConfiguration>(builder.Configuration.GetSection("JWTSettings"));
// 4. <EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><EFBFBD><EFBFBD><EFBFBD> (Identity, JWT, <EFBFBD>Զ<EFBFBD><EFBFBD><EFBFBD> Auth) // 4. 认证和授权服务 (Identity, JWT, 自定义 Auth)
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD> ASP.NET Core Identity (<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĭ<EFBFBD>ϵ<EFBFBD> Cookie <20><>֤<EFBFBD><D6A4><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD>) // 添加 ASP.NET Core Identity (包含默认的 Cookie 认证和授权服务)
builder.Services.AddIdentity<User, IdentityRole<Guid>>(opt => builder.Services.AddIdentity<User, IdentityRole<Guid>>(opt =>
{ {
opt.User.AllowedUserNameCharacters = ""; opt.User.AllowedUserNameCharacters = "";
@@ -60,25 +61,25 @@ builder.Services.Configure<DataProtectionTokenProviderOptions>(Options =>
}); });
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD> JWT Bearer <EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD><EFBFBD><EFBFBD> // 添加 JWT Bearer 认证方案
var jwtSettings = builder.Configuration.GetSection("JWTSettings"); var jwtSettings = builder.Configuration.GetSection("JWTSettings");
builder.Services.AddAuthentication(options => builder.Services.AddAuthentication(options =>
{ {
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĭ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ JWT Bearer options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; // 设置默认认证方案为 JWT Bearer
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĭ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ս<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ JWT Bearer options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; // 设置默认挑战方案为 JWT Bearer
}) })
.AddJwtBearer(options => .AddJwtBearer(options =>
{ {
options.TokenValidationParameters = new TokenValidationParameters options.TokenValidationParameters = new TokenValidationParameters
{ {
ValidateIssuer = true, // <EFBFBD><EFBFBD>֤ǩ<EFBFBD><EFBFBD><EFBFBD><EFBFBD> ValidateIssuer = true, // 验证签发人
ValidateAudience = true, // <EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD><EFBFBD><EFBFBD> ValidateAudience = true, // 验证受众
ValidateLifetime = true, // <EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ч<EFBFBD><EFBFBD> ValidateLifetime = true, // 验证令牌有效期
ValidateIssuerSigningKey = true, // <EFBFBD><EFBFBD>֤ǩ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Կ ValidateIssuerSigningKey = true, // 验证签名密钥
ValidIssuer = jwtSettings["validIssuer"], // <EFBFBD>Ϸ<EFBFBD><EFBFBD><EFBFBD>ǩ<EFBFBD><EFBFBD><EFBFBD><EFBFBD> ValidIssuer = jwtSettings["validIssuer"], // 合法的签发人
ValidAudience = jwtSettings["validAudience"], // <EFBFBD>Ϸ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ValidAudience = jwtSettings["validAudience"], // 合法的受众
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings["securityKey"])) // ǩ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Կ IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings["securityKey"])) // 签名密钥
}; };
}); });
@@ -90,6 +91,7 @@ builder.Services.AddScoped<IExamService, ExamService>();
builder.Services.AddScoped<IUserSerivces, UserServices>(); builder.Services.AddScoped<IUserSerivces, UserServices>();
builder.Services.AddScoped<ISubmissionServices, SubmissionServices>(); builder.Services.AddScoped<ISubmissionServices, SubmissionServices>();
builder.Services.AddScoped<IExamRepository, ExamRepository>(); builder.Services.AddScoped<IExamRepository, ExamRepository>();
builder.Services.AddScoped<INoteService, NoteService>();
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
@@ -128,7 +130,7 @@ builder.Services.AddCors(options =>
{ {
options.AddPolicy("AllowSpecificOrigin", options.AddPolicy("AllowSpecificOrigin",
builder => builder builder => builder
.WithOrigins("https://localhost:7047", "http://localhost:7047") .WithOrigins("https://localhost:7047", "http://localhost:7047", "https://localhost:5001", "http://localhost:5001")
.AllowAnyHeader() .AllowAnyHeader()
.AllowAnyMethod() .AllowAnyMethod()
.AllowCredentials()); .AllowCredentials());

View File

@@ -0,0 +1,17 @@
using Entities.Contracts;
using Microsoft.EntityFrameworkCore;
using SharedDATA.Api;
using TechHelper.Context;
namespace TechHelper.Repository
{
public class GlobalRepository : Repository<Global>, IRepository<Global>
{
public GlobalRepository(ApplicationContext dbContext) : base(dbContext)
{
}
}
}

View File

@@ -0,0 +1,13 @@
using Entities.Contracts;
using Entities.DTO;
using System.Net;
namespace TechHelper.Services
{
public interface INoteService : IBaseService<GlobalDto, byte>
{
}
}

View File

@@ -0,0 +1,107 @@
using AutoMapper;
using Entities.Contracts;
using Entities.DTO;
using Microsoft.AspNetCore.Identity;
using SharedDATA.Api;
using System.Net;
namespace TechHelper.Services
{
public class NoteService : INoteService
{
private readonly IUnitOfWork _work;
private readonly IMapper _mapper;
public NoteService(IUnitOfWork work)
{
_work = work;
}
public async Task<ApiResponse> AddAsync(GlobalDto model)
{
try
{
var globalEntity = new Global
{
Area = model.SubjectArea,
Info = model.Data
};
await _work.GetRepository<Global>().InsertAsync(globalEntity);
await _work.SaveChangesAsync();
return ApiResponse.Success("数据已成功添加。");
}
catch (Exception ex)
{
return ApiResponse.Error($"添加数据时发生错误: {ex.Message}");
}
}
public async Task<ApiResponse> DeleteAsync(byte id)
{
var globalRepo = _work.GetRepository<Global>();
var globalEntity = await globalRepo.GetFirstOrDefaultAsync(predicate: x => x.Area == (SubjectAreaEnum)id);
if (globalEntity == null)
{
return ApiResponse.Error("未找到要删除的数据。");
}
globalRepo.Delete(globalEntity);
await _work.SaveChangesAsync();
return ApiResponse.Success("数据已成功删除。");
}
public async Task<ApiResponse> GetAllAsync(QueryParameter query)
{
var repository = _work.GetRepository<Global>();
// 获取所有实体,并将其 Info 属性作为结果返回
var entities = await repository.GetAllAsync();
// 直接返回字符串列表
var resultData = entities.Select(e => e.Info).ToList();
return ApiResponse.Success("数据已成功检索。", resultData);
}
public async Task<ApiResponse> GetAsync(byte id)
{
var globalEntity = await _work.GetRepository<Global>().GetFirstOrDefaultAsync(predicate: x => x.Area == (SubjectAreaEnum)id);
if (globalEntity == null)
{
return ApiResponse.Error("未找到数据。");
}
// 直接返回 Info 字符串
return ApiResponse.Success("数据已成功检索。", globalEntity.Info);
}
public async Task<ApiResponse> UpdateAsync(GlobalDto model)
{
try
{
var repository = _work.GetRepository<Global>();
var existingEntity = await repository.GetFirstOrDefaultAsync(predicate: x => x.Area == (SubjectAreaEnum)model.SubjectArea);
if (existingEntity == null)
{
return ApiResponse.Error("未找到要更新的数据。");
}
// 直接将传入的字符串赋值给 Info 属性
existingEntity.Info = model.Data;
repository.Update(existingEntity);
await _work.SaveChangesAsync();
return ApiResponse.Success("数据已成功更新。");
}
catch (Exception ex)
{
return ApiResponse.Error($"更新数据时发生错误: {ex.Message}");
}
}
}
}

View File

@@ -23,6 +23,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" /> <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.3" /> <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" />
</ItemGroup> </ItemGroup>