Compare commits

..

4 Commits

Author SHA1 Message Date
SpecialX
0d19ec6bb6 更新班级和学生相关功能
Some checks failed
TechAct / explore-gitea-actions (push) Failing after 6s
2025-09-12 11:31:50 +08:00
SpecialX
439c8a2421 feat: 添加学生提交系统功能
Some checks failed
TechAct / explore-gitea-actions (push) Failing after 30s
- 添加学生提交管理服务 (StudentSubmissionService, StudentSubmissionDetailService)
- 新增学生提交相关控制器 (StudentSubmissionController, StudentSubmissionDetailController)
- 添加学生提交数据传输对象 (StudentSubmissionDetailDto, StudentSubmissionSummaryDto)
- 新增学生提交相关页面组件 (StudentExamView, ExamDetailView, StudentCard等)
- 添加学生提交信息卡片组件 (SubmissionInfoCard, TeacherSubmissionInfoCard)
- 更新数据库迁移文件以支持提交系统
2025-09-09 15:42:31 +08:00
SpecialX
6a65281850 重构作业结构:优化实体模型、DTO映射和前端界面
Some checks failed
TechAct / explore-gitea-actions (push) Failing after 13s
- 重构AppMainStruct、AssignmentQuestion、Question等实体模型
- 更新相关DTO以匹配新的数据结构
- 优化前端页面布局和组件
- 添加全局信息和笔记功能相关代码
- 更新数据库迁移和程序配置
2025-09-04 15:43:33 +08:00
SpecialX
730b0ba04b Update ci.yaml
Some checks failed
TechAct / explore-gitea-actions (push) Failing after 6m0s
2025-08-31 11:35:41 +08:00
101 changed files with 11112 additions and 326 deletions

View File

@@ -1,10 +1,10 @@
name: Tech
name: TechAct
on: [push] # 当有新的push事件发生时触发此工作流程
jobs:
explore-gitea-actions:
runs-on: Tech
runs-on: TechAct
steps:
- uses: actions/checkout@v4 # 使用actions/checkout来克隆您的仓库代码
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."

7
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,7 @@
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": []
}

View File

@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
@@ -34,9 +36,11 @@ namespace Entities.Contracts
public enum DifficultyLevel : byte
{
simple,
easy,
medium,
hard
hard,
veryHard
}
public enum QuestionType : byte
@@ -55,47 +59,131 @@ namespace Entities.Contracts
public enum SubjectAreaEnum : byte
{
[Display(Name = "未知", Description = "未知")]
Unknown = 0,
[Display(Name = "数学", Description = "数")]
Mathematics, // 数学
[Display(Name = "物理", Description = "物")]
Physics, // 物理
[Display(Name = "化学", Description = "化")]
Chemistry, // 化学
[Display(Name = "生物", Description = "生")]
Biology, // 生物
[Display(Name = "历史", Description = "史")]
History, // 历史
[Display(Name = "地理", Description = "地")]
Geography, // 地理
[Display(Name = "语文", Description = "语")]
Literature, // 语文/文学
[Display(Name = "英语", Description = "英")]
English, // 英语
ComputerScience, // 计算机科学
[Display(Name = "计算机科学", Description = "计")]
ComputerScience // 计算机科学
}
public enum AssignmentStructType : byte
{
[Display(Name = "根节点", Description = "根")]
Root,
[Display(Name = "单个问题", Description = "问")]
Question,
[Display(Name = "问题组", Description = "组")]
Group,
[Display(Name = "结构", Description = "结")]
Struct,
[Display(Name = "子问题", Description = "子")]
SubQuestion,
[Display(Name = "选项", Description = "选")]
Option
}
public enum ExamType : byte
{
MidtermExam, // 期中
FinalExam, // 期末
MonthlyExam, // 月考
WeeklyExam, // 周考
DailyTest, // 平时测试
AITest, // AI测试
}
[Display(Name = "期中考试", Description = "中")]
MidtermExam,
[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
{
Pending, // 待提交/未开始
Submitted, // 已提交
Graded, // 已批改
Resubmission, // 待重新提交 (如果允许)
Late, // 迟交
Draft, // 草稿
[Display(Name = "待提交/未开始", Description = "待")]
Pending,
[Display(Name = "已提交", Description = "提")]
Submitted,
[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,12 +40,16 @@ namespace Entities.Contracts
[Column("group_state")]
public AssignmentStructType StructType { get; set; } = AssignmentStructType.Question;
public QuestionType Type { get; set; } = QuestionType.Unknown;
[Column("created_at")]
public DateTime CreatedAt { get; set; }
[Column("score")]
public float? Score { get; set; }
public bool BCorrect { get; set; }
[Column("deleted")]
public bool IsDeleted { 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")]
public SubjectAreaEnum SubjectArea { get; set; } = SubjectAreaEnum.Unknown;
public string QType { get; set; } = string.Empty;
[Column("options")]
public string? Options { get; set; }

View File

@@ -34,7 +34,7 @@ namespace Entities.Contracts
public DateTime SubmissionTime { get; set; }
[Column("overall_grade")]
public float? OverallGrade { get; set; }
public float OverallGrade { get; set; } = 0;
[Column("overall_feedback")]
public string? OverallFeedback { get; set; }

View File

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

View File

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

View File

@@ -13,8 +13,9 @@ namespace Entities.DTO
public string? DisplayName { get; set; }
public UInt32 ErrorQuestionNum { get; set; }
public Dictionary<QuestionType, UInt32> ErrorQuestionTypes { get; set; } = new Dictionary<QuestionType, UInt32>();
public Dictionary<string, UInt32> ErrorQuestionTypes { get; set; } = new Dictionary<string, UInt32>();
public Dictionary<SubjectAreaEnum, UInt32> SubjectAreaErrorQuestionDis { get; set; } = new Dictionary<SubjectAreaEnum, UInt32>();
public Dictionary<byte, UInt32> LessonErrorDis { get; set; } = new Dictionary<byte, UInt32>();
public float Score { get; set; }
}
}

View File

@@ -0,0 +1,41 @@
using Entities.Contracts;
using System;
using System.Collections.Generic;
namespace Entities.DTO
{
public class StudentSubmissionDetailDto
{
// 基本信息
public Guid Id { get; set; }
public Guid AssignmentId { get; set; }
public Guid StudentId { get; set; }
public DateTime SubmissionTime { get; set; }
public float OverallGrade { get; set; }
public string OverallFeedback { get; set; } = string.Empty;
public SubmissionStatus Status { get; set; }
// Assignment信息
public AssignmentDto Assignment { get; set; } = new AssignmentDto();
// 错误分析
public Dictionary<string, int> ErrorTypeDistribution { get; set; } = new Dictionary<string, int>();
public Dictionary<string, float> ErrorTypeScoreDistribution { get; set; } = new Dictionary<string, float>();
// 成绩统计
public int TotalRank { get; set; }
public List<float> AllScores { get; set; } = new List<float>();
public float AverageScore { get; set; }
public float ClassAverageScore { get; set; }
// 课文分布
public Dictionary<string, int> LessonErrorDistribution { get; set; } = new Dictionary<string, int>();
public Dictionary<string, int> KeyPointErrorDistribution { get; set; } = new Dictionary<string, int>();
// 基础统计
public int TotalQuestions { get; set; }
public int CorrectCount { get; set; }
public int ErrorCount { get; set; }
public float AccuracyRate { get; set; }
}
}

View File

@@ -0,0 +1,22 @@
using System;
namespace Entities.DTO
{
public class StudentSubmissionSummaryDto
{
public Guid Id { get; set; }
public string AssignmentName { get; set; }
public int ErrorCount { get; set; }
public DateTime CreatedDate { get; set; }
public float Score { get; set; }
public int TotalQuestions { get; set; }
public string StudentName { get; set; }
public string Status { get; set; }
}
public class StudentSubmissionSummaryResponseDto
{
public List<StudentSubmissionSummaryDto> Submissions { get; set; }
public int TotalCount { get; set; }
}
}

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,63 @@
<MudSnackbarProvider />
<MudPopoverProvider />
@*
<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;">
<MudLayout>
<MudAppBar Elevation="0" Class="rounded-xl" Style="background-color: transparent; border:none">
<MudBreakpointProvider>
<MudHidden Breakpoint="Breakpoint.SmAndDown" Invert=true>
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Primary" Edge="Edge.Start" OnClick="@((e) => DrawerToggle())" />
</MudHidden>
<MudHidden Breakpoint="Breakpoint.SmAndDown">
<SearchBar></SearchBar>
<MudButton Class="mt-1">application</MudButton>
</MudHidden>
</MudBreakpointProvider>
<MudSpacer />
<MudIconButton Icon="@Icons.Material.Filled.MoreVert" Color="Color.Primary" Edge="Edge.End" />
</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="/">Home</MudNavLink>
<MudNavLink Href="/exam">Exam</MudNavLink>
<MudNavLink Href="/students">Students</MudNavLink>
<MudSpacer />
<MudNavLink Class="align-content-end" Href="/about">About</MudNavLink>
</MudNavMenu>
<MudSpacer />
<MudNavMenu Class="align-content-end " Bordered="true" Dense="true" Rounded="true" Margin="Margin.Dense">
<TechHelper.Client.Pages.Global.LoginInOut.LoginInOut></TechHelper.Client.Pages.Global.LoginInOut.LoginInOut>
<MudNavLink Class="align-content-end" Href="/Account/Manage">Setting</MudNavLink>
</MudNavMenu>
</MudPaper>
<MudPaper Class="d-flex flex-column flex-grow-0 overflow-auto" Style="height: 100vh; background-color:#22222200">
<MudPaper Class="d-flex flex-column flex-grow-1 overflow-hidden" Style="background-color:transparent">
<MudPaper Elevation="3" Height="10%" Class=" d-flex justify-content-around flex-grow-0" Style="background-color:#ffffff55">
<NavBar Class="flex-column flex-grow-1 " Style="background-color:transparent" />
<AuthLinks Class="flex-column flex-grow-0 " Style="background-color:transparent" />
</MudPaper>
<MudPaper Elevation="3" Class="d-flex flex-row flex-grow-1 overflow-hidden" Style="background-color:transparent">
<MudPaper Width="10%" Class="pa-2 ma-1 d-flex flex-column flex-grow-0 justify-content-between" Style="background-color:#ffffffaa">
</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">
</MudDrawerHeader>
</MudDrawer>
<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 Style="background-color:transparent" Elevation="0" Class="d-flex w-100 h-100 overflow-hidden pa-2 rounded-xl">
@Body
</MudPaper>
</MudPaper>
</MudPaper>
</SnackErrorBoundary>
</MudMainContent>
</MudLayout>
@code {
ErrorBoundary? errorBoundary;
protected override void OnParametersSet()
{
errorBoundary?.Recover();
}
bool _drawerOpen = true;
void DrawerToggle()
{
_drawerOpen = !_drawerOpen;
}
}

View File

@@ -2,9 +2,9 @@
<MudGrid Class="h-100">
<MudItem xs="12" sm="2" Class="h-100 pa-1 mt-1">
<MudItem sm="2" Class="h-100 pa-1 mt-1">
<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>
<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="50%" Style="background-color:transparent">
<MudText Style="color:#9ed5f7" Typo="Typo.body2">
TotalNumber:
总数:
<span style="color: #fefefe;">15</span>
</MudText>
</MudPaper>
<MudPaper Elevation=0 Height="50%" Style="background-color:transparent">
<MudText Style="color:#9ed5f7" Typo="Typo.body2">
TotalScore:
总分:
<span style="color: #fefefe;">15</span>
</MudText>
</MudPaper>
@@ -27,7 +27,7 @@
<MudPaper Elevation=0 Height="100%" Width="100%" Style="background-color:transparent">
<MudPaper Elevation=0 Height="50%" Style="background-color:transparent">
<MudText Style="color:#9ed5f7" Typo="Typo.body2">
中位:
中位:
<span style="color: #fefefe;">15</span>
</MudText>
</MudPaper>
@@ -44,7 +44,7 @@
</MudItem>
<MudItem xs="12" sm="9">
<MudItem sm="9">
<MudPaper Style="background-color:transparent" Class="w-100 mt-n3" Height="100%" Elevation="0">
<MudChart ChartType="ChartType.Line" Class="pt-0" ChartSeries="@Series" XAxisLabels="@XAxisLabels" CanHideSeries
Height="150px" Width="100%" AxisChartOptions="_axisChartOptions" ChartOptions="options">
@@ -67,12 +67,6 @@
<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.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>
</MudItem>
</MudGrid>

View File

@@ -0,0 +1,191 @@
@using Entities.DTO
@using TechHelper.Client.Services
<MudPaper Class="rounded-xl w-100 px-10 ma-3 pt-5" Elevation="5" Height="170px" Style="background-color:#6bc6be">
<MudGrid Class="h-100">
<MudItem sm="2" Class="h-100 pa-1 mt-1">
<MudStack Class="h-100">
<MudText Style="color:white"> BETA版本 </MudText>
<MudText Style="color:white" Typo="Typo.h3"><b> @StudentSubmissionDetail.AverageScore </b></MudText>
<MudPaper Elevation=0 Class="h-100 w-100" Style="background-color:transparent">
<MudStack Class="h-100" Row=true>
<MudPaper Elevation=0 Height="100%" Width="100%" Style="background-color:transparent">
<MudPaper Elevation=0 Height="50%" Style="background-color:transparent">
<MudText Style="color:#9ed5f7" Typo="Typo.body2">
总数:
<span style="color: #fefefe;"> @StudentSubmissionDetail.TotalQuestions </span>
</MudText>
</MudPaper>
<MudPaper Elevation=0 Height="50%" Style="background-color:transparent">
<MudText Style="color:#9ed5f7" Typo="Typo.body2">
总分:
<span style="color: #fefefe;"> 150 </span>
</MudText>
</MudPaper>
</MudPaper>
<MudPaper Elevation=0 Height="100%" Width="100%" Style="background-color:transparent">
<MudPaper Elevation=0 Height="50%" Style="background-color:transparent">
<MudText Style="color:#9ed5f7" Typo="Typo.body2">
排名:
<span style="color: #fefefe;"> @StudentSubmissionDetail.TotalRank </span>
</MudText>
</MudPaper>
<MudPaper Elevation=0 Height="50%" Style="background-color:transparent">
<MudText Style="color:#9ed5f7" Typo="Typo.body2">
平均:
<span style="color: #fefefe;"> @StudentSubmissionDetail.ClassAverageScore </span>
</MudText>
</MudPaper>
</MudPaper>
</MudStack>
</MudPaper>
</MudStack>
</MudItem>
<MudItem sm="9">
<MudPaper Style="background-color:transparent" Class="w-100 mt-n3" Height="100%" Elevation="0">
<MudChart ChartType="ChartType.Line" Class="pt-0" ChartSeries="@Series" XAxisLabels="@XAxisLabels" CanHideSeries
Height="150px" Width="100%" AxisChartOptions="_axisChartOptions" ChartOptions="options">
<CustomGraphics>
<style>
.heavy {
font: normal 12px helvetica;
fill: rgb(255,255,255);
letter-spacing: 2px;
}
</style>
<text x="60" y="15" class="heavy"> 成绩的整体分布情况 </text>
</CustomGraphics>
</MudChart>
</MudPaper>
</MudItem>
<MudItem xs="12" sm="1">
<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.Primary">课时分布</MudChip>
</MudChipSet>
</MudItem>
</MudGrid>
</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 {
private AxisChartOptions _axisChartOptions = new AxisChartOptions
{
};
private ChartOptions options = new ChartOptions
{
InterpolationOption = InterpolationOption.NaturalSpline,
YAxisFormat = "c2",
ShowLegend = false,
YAxisLines = false,
XAxisLines = false,
XAxisLabelPosition = XAxisLabelPosition.None,
YAxisLabelPosition = YAxisLabelPosition.None,
YAxisTicks = 100,
ShowLabels = false,
ShowLegendLabels = false
};
public List<ChartSeries> Series = new List<ChartSeries>()
{
new ChartSeries() { Name = "类型错误数量分布", Data = new double[] { 35, 41, 35, 51, 49, 62, 69, 91, 148 } },
};
public string[] XAxisLabels = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep" };
Random random = new Random();
protected override void OnInitialized()
{
options.InterpolationOption = InterpolationOption.NaturalSpline;
options.YAxisFormat = "c2";
options.ShowLegend = false;
options.YAxisLines = false;
options.XAxisLines = false;
options.XAxisLabelPosition = XAxisLabelPosition.None;
options.YAxisLabelPosition = YAxisLabelPosition.None;
options.ShowLabels = false;
options.ShowLegendLabels = false;
options.LineStrokeWidth = 1;
_axisChartOptions.MatchBoundsToSize = true;
Series[0].LineDisplayType = LineDisplayType.Area;
}
[Parameter]
public Guid SubmissionID { get; set; } = Guid.Empty;
private StudentSubmissionDetailDto StudentSubmissionDetail { get; set; } = new StudentSubmissionDetailDto();
private IReadOnlyCollection<string> _selected;
#pragma warning restore 1998
#nullable restore
#line (82, 8) - (143, 1) "D:\AllWX\AllC\TechHelper\TechHelper.Client\Pages\Common\Exam\SubmissionInfoCard.razor"
[Inject]
public IStudentSubmissionDetailService StudentSubmissionDetailService { get; set; }
[Inject]
public ISnackbar Snackbar { get; set; }
protected override async Task OnInitializedAsync()
{
if (SubmissionID != Guid.Empty)
{
StudentSubmissionDetailDto result;
try
{
result = await StudentSubmissionDetailService.GetSubmissionDetailAsync(SubmissionID);
if (result != null)
{
StudentSubmissionDetail = result;
XAxisLabels = result.ErrorTypeDistribution.Keys.ToArray();
Series.Clear();
Series.Add(new ChartSeries
{
Name = "类型错误数量分布",
Data = result.ErrorTypeDistribution.Values.Select(d => (double)d).ToArray()
});
Series.Add(new ChartSeries
{
Name = "类型错误成绩分布",
Data = result.ErrorTypeScoreDistribution.Values.Select(d => (double)d).ToArray()
});
}
}
catch (Exception ex)
{
Snackbar.Add($"获取提交错误, 请重试, {ex.Message}", Severity.Warning);
}
}
}
private void HandleSelectedValuesChanged(IReadOnlyCollection<string> selected)
{
Series.ForEach(x => x.Visible = false);
foreach (var item in selected)
{
var sv = Series.FirstOrDefault(predicate: x => x.Name == item);
if (sv != null)
{
sv.Visible = true;
}
}
}
}

View File

@@ -0,0 +1,143 @@
<MudPaper Class="rounded-xl w-100 px-10 ma-3 pt-5" Elevation="5" Height="170px" Style="background-color:#6bc6be">
<MudGrid Class="h-100">
<MudItem xs="12" sm="2" Class="h-100 pa-1 mt-1">
<MudStack Class="h-100">
<MudText Style="color:white"> BETA版本 </MudText>
<MudText Style="color:white" Typo="Typo.h3"><b> 75 </b></MudText>
<MudPaper Elevation=0 Class="h-100 w-100" Style="background-color:transparent">
<MudStack Class="h-100" Row=true>
<MudPaper Elevation=0 Height="100%" Width="100%" Style="background-color:transparent">
<MudPaper Elevation=0 Height="50%" Style="background-color:transparent">
<MudText Style="color:#9ed5f7" Typo="Typo.body2">
总数:
<span style="color: #fefefe;">15</span>
</MudText>
</MudPaper>
<MudPaper Elevation=0 Height="50%" Style="background-color:transparent">
<MudText Style="color:#9ed5f7" Typo="Typo.body2">
总分:
<span style="color: #fefefe;">15</span>
</MudText>
</MudPaper>
</MudPaper>
<MudPaper Elevation=0 Height="100%" Width="100%" Style="background-color:transparent">
<MudPaper Elevation=0 Height="50%" Style="background-color:transparent">
<MudText Style="color:#9ed5f7" Typo="Typo.body2">
中位:
<span style="color: #fefefe;">15</span>
</MudText>
</MudPaper>
<MudPaper Elevation=0 Height="50%" Style="background-color:transparent">
<MudText Style="color:#9ed5f7" Typo="Typo.body2">
方差:
<span style="color: #fefefe;">15</span>
</MudText>
</MudPaper>
</MudPaper>
</MudStack>
</MudPaper>
</MudStack>
</MudItem>
<MudItem xs="12" sm="9">
<MudPaper Style="background-color:transparent" Class="w-100 mt-n3" Height="100%" Elevation="0">
<MudChart ChartType="ChartType.Line" Class="pt-0" ChartSeries="@Series" XAxisLabels="@XAxisLabels" CanHideSeries
Height="150px" Width="100%" AxisChartOptions="_axisChartOptions" ChartOptions="options">
<CustomGraphics>
<style>
.heavy {
font: normal 12px helvetica;
fill: rgb(255,255,255);
letter-spacing: 2px;
}
</style>
<text x="60" y="15" class="heavy"> 成绩的整体分布情况 </text>
</CustomGraphics>
</MudChart>
</MudPaper>
</MudItem>
<MudItem xs="12" sm="1">
<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.Primary">课时分布</MudChip>
</MudChipSet>
</MudItem>
</MudGrid>
</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 {
public double[] data = { 25, 77, 28, 5 };
public string[] labels = { "Oil", "Coal", "Gas", "Biomass" };
private AxisChartOptions _axisChartOptions = new AxisChartOptions
{
};
private ChartOptions options = new ChartOptions
{
InterpolationOption = InterpolationOption.NaturalSpline,
YAxisFormat = "c2",
ShowLegend = false,
YAxisLines = false,
XAxisLines = false,
XAxisLabelPosition = XAxisLabelPosition.None,
YAxisLabelPosition = YAxisLabelPosition.None,
YAxisTicks = 100,
ShowLabels = false,
ShowLegendLabels = false
};
public List<ChartSeries> Series = new List<ChartSeries>()
{
new ChartSeries() { Name = "类型错误数量分布", Data = new double[] { 35, 41, 35, 51, 49, 62, 69, 91, 148 } },
new ChartSeries() { Name = "类型错误成绩分布", Data = new double[] { 55, 21, 45, 11, 45, 23, 11, 56, 13 } },
};
public string[] XAxisLabels = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep" };
Random random = new Random();
protected override void OnInitialized()
{
options.InterpolationOption = InterpolationOption.NaturalSpline;
options.YAxisFormat = "c2";
options.ShowLegend = false;
options.YAxisLines = false;
options.XAxisLines = false;
options.XAxisLabelPosition = XAxisLabelPosition.None;
options.YAxisLabelPosition = YAxisLabelPosition.None;
options.ShowLabels = false;
options.ShowLegendLabels = false;
options.LineStrokeWidth = 1;
_axisChartOptions.MatchBoundsToSize = true;
Series[0].LineDisplayType = LineDisplayType.Area;
}
private IReadOnlyCollection<string> _selected;
private void HandleSelectedValuesChanged(IReadOnlyCollection<string> selected)
{
Series.ForEach(x => x.Visible = false);
foreach(var item in selected)
{
var sv = Series.FirstOrDefault(predicate: x => x.Name == item);
if(sv != null)
{
sv.Visible = true;
}
}
}
}

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" 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>

View File

@@ -0,0 +1,7 @@
<MudPaper>
<MudText> ExamName </MudText>
<MudText> 已经指派人数 </MudText>
<MudText> 总人数 </MudText>
<MudText> 平均S </MudText>
<MudText> 指派 </MudText>
</MudPaper>

View File

@@ -1,5 +1,6 @@
@using Entities.DTO
@using Entities.Contracts
@using Newtonsoft.Json
@using TechHelper.Client.Exam
@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.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">
<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="purple" Color="Color.Primary" Value="@AssignmentStructType.Group">@AssignmentStructType.Group</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="orange" Color="Color.Error" Value="@AssignmentStructType.Option">@AssignmentStructType.Option</MudChip>
</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>
@if (AssignmentQuestion.Question != null)
{
@@ -30,6 +49,10 @@
[Parameter]
public AssignmentQuestionDto AssignmentQuestion { get; set; } = new AssignmentQuestionDto();
public QuestionDto TempQuesdto;
Dictionary<string, QuestionDisplayTypeData> QuestionTypes = new Dictionary<string, QuestionDisplayTypeData>();
[Inject]
private ILocalStorageService LocalStorageService { get; set; }
protected override void OnInitialized()
{
base.OnInitialized();
@@ -37,6 +60,30 @@
{
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)

View File

@@ -1,5 +1,7 @@
@page "/exam/create"
@using AutoMapper
@using Entities.Contracts
@using Newtonsoft.Json
@using TechHelper.Client.Pages.Common
@using TechHelper.Client.Pages.Exam.ExamView
@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="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>
@@ -84,11 +88,32 @@
private ExamParserConfig _examParserConfig { get; set; } = new ExamParserConfig();
private string EditorText = "";
[Inject]
private ILocalStorageService LocalStorageService { get; set; }
[Inject]
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()
{
var parameters = new DialogParameters<TextEditorDialog> { { x => x.TextEditor, _textEditor } };
@@ -169,13 +194,43 @@
[Inject]
public IExamService examService { get; set; }
[Inject]
public INoteService NoteService { get; set; }
public async Task Publish()
{
var apiRespon = await examService.SaveParsedExam(ExamContent);
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

@@ -21,8 +21,6 @@ else
<MudPaper Class="d-flex flex-wrap flex-grow-0 gap-4" Height="100%" Width="100%">
@foreach (var item in examDtos)
{
@* <ExamPreview AssignmentDto="item" Width="256px" Height="256px"> </ExamPreview> *@
<AssignmentInfoCard></AssignmentInfoCard>
}
</MudPaper>

View File

@@ -1,5 +1,6 @@
@using Entities.Contracts
@using Entities.DTO
@using Newtonsoft.Json
@using TechHelper.Client.Exam
@using TechHelper.Client.Pages.Exam.QuestionCard
@@ -22,6 +23,11 @@
<MudIconButton Color="Color.Tertiary" Icon="@Icons.Material.Filled.ExpandMore" 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.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>
@@ -75,6 +81,22 @@
[Parameter]
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()
{
await ClickedStruct.InvokeAsync(ExamStruct);
@@ -84,4 +106,10 @@
{
await ClickedStruct.InvokeAsync(clickedChildExamStruct);
}
private void HandleSelected(int num)
{
ExamStruct.Question.DifficultyLevel = (DifficultyLevel)num;
}
}

View File

@@ -1,7 +1,34 @@
@page "/exam"
@using TechHelper.Client.Pages.Student.BaseInfoCard
@inject NavigationManager NavigationManager
<AuthorizeView Roles="Teacher">
<Authorized>
<MudPaper Class="rounded-xl ma-2 px-2 overflow-auto w-100 h-100">
<StudentSubmissionPreviewTableCard />
</MudPaper>
</Authorized>
</AuthorizeView>
<MudText>HELLO WORLD</MudText>
<AuthorizeView Roles="Student">
<Authorized>
<MudPaper Class="rounded-xl ma-2 px-2 overflow-auto w-100 h-100">
<StudentSubmissionPreviewTableCard />
</MudPaper>
</Authorized>
</AuthorizeView>
@code {
[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; }
protected override void OnParametersSet()
{
if (authenticationStateTask is null)
{
NavigationManager.Refresh(forceReload: true);
}
}
}

View File

@@ -1,10 +1,29 @@
@using Entities.DTO
@using Entities.Contracts
@using Newtonsoft.Json
@using TechHelper.Client.Exam
<MudPaper Elevation="1" Class="ma-4 pa-5 rounded-xl">
@* <MudText>@Question.Id</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.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" />
@@ -15,4 +34,35 @@
@code {
[Parameter]
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,4 @@

@code {
}

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

@@ -7,10 +7,6 @@
<TechHelper.Client.Pages.Student.HomePage />
</Authorized>
</AuthorizeView>
<AssignmentInfoCard></AssignmentInfoCard>
@code {
[CascadingParameter]
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,97 @@
@using TechHelper.Client.Services
@inject IStudentSubmissionService StudentSubmissionService
<MudPaper Class="ma-2 pa-2 rounded-xl d-flex flex-column flex-grow-1 overflow-auto" MaxHeight="100%">
<StudentSubmissionPreviewCard />
@if (_isLoading)
{
<div class="d-flex justify-content-center align-items-center" style="height: 200px;">
<MudProgressCircular Color="Color.Primary" Size="Size.Large" />
</div>
}
else if (_studentSubmissions == null || _studentSubmissions.Count == 0)
{
<div class="d-flex justify-content-center align-items-center" style="height: 200px;">
<MudText TextColor="Color.TextSecondary" Align="Align.Center">暂无提交记录</MudText>
</div>
}
else
{
@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 DateTime CreatedDate { get; set; }
public float Score { get; set; }
public string AssignmentName { get; set; }
public string Status { get; set; }
public TimeSpan TimeSpent { get; set; }
}
// 学生提交列表
private List<StudentSubmission> _studentSubmissions = new();
private bool _isLoading = true;
protected override async Task OnInitializedAsync()
{
await LoadStudentSubmissions();
}
private async Task LoadStudentSubmissions()
{
try
{
_isLoading = true;
StateHasChanged();
var result = await StudentSubmissionService.GetMySubmissionsAsync();
if (result.Status && result.Result != null)
{
// 从服务器获取的数据映射到我们的模型
var submissions = result.Result as List<Entities.DTO.StudentSubmissionSummaryDto>;
if (submissions != null)
{
_studentSubmissions = submissions.Select(submission => new StudentSubmission
{
AssignmentName = submission.AssignmentName,
CreatedDate = submission.CreatedDate,
ErrorCount = submission.ErrorCount,
Score = submission.Score,
StudentName = submission.StudentName,
Status = submission.Status,
TotalProblems = submission.TotalQuestions,
TimeSpent = TimeSpan.FromMinutes(30) // 默认值,实际应用中可以从服务器获取
}).ToList();
}
}
else
{
// 如果API调用失败使用空列表
_studentSubmissions = new List<StudentSubmission>();
}
}
catch (Exception ex)
{
// 处理异常,可以记录日志
_studentSubmissions = new List<StudentSubmission>();
}
finally
{
_isLoading = false;
StateHasChanged();
}
}
}

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,141 +1,25 @@

@using TechHelper.Client.Pages.Common.Exam;
@using TechHelper.Client.Pages.Student.BaseInfoCard;
@using TechHelper.Client.Pages.Common;
<MudPaper Class="flex-grow-1 w-100 h-100 ma-auto">
<MudGrid Class="w-100 h-100">
<MudItem xs="12" sm="4">
<MudPaper Style="background-color:transparent" Class="w-100 justify-content-center">
<MudChart ChartType="ChartType.Donut" Width="200px" Height="200px" InputData="@data" InputLabels="@labels" Class="ma-auto">
<CustomGraphics>
<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>
<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 Class="w-100 h-100 d-flex flex-row" Style="background-color: transparent" Elevation="0">
<MudPaper Class="flex-grow-1 mx-2 d-flex flex-column" Style="background-color:transparent" Elevation="0">
<SubmissionInfoCard ></SubmissionInfoCard>
<MudPaper Class="d-flex flex-row" Style="background-color:transparent" Elevation="0">
<NotifyCard></NotifyCard>
<HomeworkCard></HomeworkCard>
<NotifyCard></NotifyCard>
<HomeworkCard></HomeworkCard>
</MudPaper>
<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>
<StudentSubmissionPreviewTableCard></StudentSubmissionPreviewTableCard>
</MudPaper>
</MudItem>
<MudPaper Width="300px" Class="mx-2 align-content-center d-flex flex-column flex-grow-1" Style="background-color: transparent" Elevation="0">
<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>
</MudItem>
</MudGrid>
</MudPaper>
@code {
public double[] data = { 25, 77, 28, 5 };
public string[] labels = { "Oil", "Coal", "Gas", "Biomass" };
private AxisChartOptions _axisChartOptions = new AxisChartOptions();
private ChartOptions options = new ChartOptions();
public List<ChartSeries> Series = new List<ChartSeries>()
{
new ChartSeries() { Name = "Series 1", Data = new double[] { 90, 79, 72, 69, 62, 62, 55, 65, 70 } },
new ChartSeries() { Name = "Series 2", Data = new double[] { 35, 41, 35, 51, 49, 62, 69, 91, 148 } },
};
public string[] XAxisLabels = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep" };
Random random = new Random();
protected override void OnInitialized()
{
options.InterpolationOption = InterpolationOption.NaturalSpline;
options.YAxisFormat = "c2";
_axisChartOptions.MatchBoundsToSize = true;
}
public void RandomizeData()
{
foreach (var series in Series)
{
for (int i = 0; i < series.Data.Length - 1; i++)
{
series.Data[i] = random.NextDouble() * 100 + 10;
}
}
StateHasChanged();
}
void OnClickMenu(InterpolationOption interpolationOption)
{
options.InterpolationOption = interpolationOption;
StateHasChanged();
}
}

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

@@ -0,0 +1,3 @@
<MudPaper>
<MudText> EXAM NAME</MudText>
</MudPaper>

View File

@@ -0,0 +1,14 @@
@using Entities.DTO
<h3>StudentCard</h3>
@code {
[Parameter]
public StudentDto StudentDto{ get; set; }
}

View File

@@ -6,7 +6,9 @@
@foreach(var cs in ClassStudents)
{
<MudText> @cs.DisplayName </MudText>
<StudentCard StudentDto="@cs">
</StudentCard>
}

View File

@@ -39,12 +39,15 @@ builder.Services.AddLocalStorageServices();
builder.Services.AddScoped<IAuthenticationClientService, AuthenticationClientService>();
builder.Services.AddScoped<IExamService, ExamService>();
builder.Services.AddScoped<IStudentSubmissionService, StudentSubmissionService>();
builder.Services.AddScoped<IStudentSubmissionDetailService, StudentSubmissionDetailService>();
builder.Services.AddScoped<AuthenticationStateProvider, AuthStateProvider>();
builder.Services.AddScoped<IRefreshTokenService, RefreshTokenService>();
builder.Services.AddScoped<IClassServices, ClasssServices>();
builder.Services.AddScoped<IEmailSender, QEmailSender>();
builder.Services.AddScoped<HttpInterceptorHandlerService>();
builder.Services.AddScoped<IAIService, AiService>();
builder.Services.AddScoped<INoteService, NoteService>();
builder.Services.AddScoped<IUserServices, UserServices>();
builder.Services.AddHttpClient("WebApiClient", client =>
{

View File

@@ -20,7 +20,16 @@ namespace TechHelper.Client.Services
public Task<ResponseDto> CreateClass(UserRegistrationToClassDto userClass)
{
throw new NotImplementedException();
try
{
return Task.FromResult(new ResponseDto { IsSuccessfulRegistration = true });
}
catch (Exception ex)
{
// 实际应用中,这里应该加入日志记录
Console.WriteLine($"Error in CreateClass: {ex.Message}");
return Task.FromResult(new ResponseDto { IsSuccessfulRegistration = false, Errors = new string[] { ex.Message } });
}
}
public async Task<ApiResponse> GetClassStudents()
@@ -54,6 +63,29 @@ namespace TechHelper.Client.Services
}
}
public StudentDto GetStudents(byte Class)
{
try
{
var result = _client.PostAsJsonAsync("class/getClassStudents", Class);
var content = result.Result.Content.ReadAsStringAsync();
if (content.Result != null)
{
var users = JsonConvert.DeserializeObject<StudentDto>(content.Result);
return users;
}
else
{
return new StudentDto();
}
}
catch (Exception ex)
{
Console.WriteLine($"获取失败,{ex.Message}, InnerException: {ex.InnerException}");
return new StudentDto();
}
}
public async Task<ResponseDto> UserRegister(UserRegistrationToClassDto userRegistrationToClassDto)
{
try

View File

@@ -10,5 +10,6 @@ namespace TechHelper.Client.Services
public Task<ResponseDto> CreateClass(UserRegistrationToClassDto userClass);
public Task<ApiResponse> GetClassStudents();
public Task<ApiResponse> GetGradeClasses(byte grade);
public StudentDto GetStudents(byte Class);
}
}

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,14 @@
using Entities.DTO;
namespace TechHelper.Client.Services
{
public interface IStudentSubmissionDetailService
{
/// <summary>
/// 获取学生提交的详细信息
/// </summary>
/// <param name="submissionId">提交ID</param>
/// <returns>学生提交详细信息</returns>
Task<StudentSubmissionDetailDto> GetSubmissionDetailAsync(Guid submissionId);
}
}

View File

@@ -0,0 +1,22 @@
using Entities.DTO;
using TechHelper.Services;
namespace TechHelper.Client.Services
{
public interface IStudentSubmissionService
{
/// <summary>
/// 获取当前学生的所有提交摘要
/// </summary>
/// <returns>学生提交摘要列表</returns>
Task<ApiResponse> GetMySubmissionsAsync();
/// <summary>
/// 获取当前学生的提交摘要(分页)
/// </summary>
/// <param name="pageNumber">页码默认为1</param>
/// <param name="pageSize">每页数量默认为10</param>
/// <returns>分页的学生提交摘要列表</returns>
Task<ApiResponse> GetMySubmissionsPagedAsync(int pageNumber = 1, int pageSize = 10);
}
}

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

@@ -0,0 +1,26 @@
using Entities.DTO;
using TechHelper.Client.HttpRepository;
using System.Net.Http.Json;
namespace TechHelper.Client.Services
{
public class StudentSubmissionDetailService : IStudentSubmissionDetailService
{
private readonly HttpClient _httpClient;
public StudentSubmissionDetailService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<StudentSubmissionDetailDto> GetSubmissionDetailAsync(Guid submissionId)
{
var response = await _httpClient.GetAsync($"api/student-submission-detail/{submissionId}");
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadFromJsonAsync<StudentSubmissionDetailDto>();
}
throw new HttpRequestException($"获取学生提交详细信息失败: {response.StatusCode}");
}
}
}

View File

@@ -0,0 +1,75 @@
using Entities.DTO;
using TechHelper.Services;
using System.Net.Http.Json;
using Newtonsoft.Json;
namespace TechHelper.Client.Services
{
public class StudentSubmissionService : IStudentSubmissionService
{
private readonly HttpClient _client;
public StudentSubmissionService(HttpClient client)
{
_client = client;
}
/// <summary>
/// 获取当前学生的所有提交摘要
/// </summary>
/// <returns>学生提交摘要列表</returns>
public async Task<ApiResponse> GetMySubmissionsAsync()
{
try
{
var response = await _client.GetAsync("student-submission/my-submissions");
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
var submissions = JsonConvert.DeserializeObject<List<StudentSubmissionSummaryDto>>(content);
return ApiResponse.Success(result: submissions);
}
else
{
var errorContent = await response.Content.ReadAsStringAsync();
return ApiResponse.Error(message: $"获取学生提交信息失败: {response.StatusCode} - {errorContent}");
}
}
catch (Exception ex)
{
return ApiResponse.Error(message: $"内部错误: {ex.Message}");
}
}
/// <summary>
/// 获取当前学生的提交摘要(分页)
/// </summary>
/// <param name="pageNumber">页码默认为1</param>
/// <param name="pageSize">每页数量默认为10</param>
/// <returns>分页的学生提交摘要列表</returns>
public async Task<ApiResponse> GetMySubmissionsPagedAsync(int pageNumber = 1, int pageSize = 10)
{
try
{
var response = await _client.GetAsync($"student-submission/my-submissions-paged?pageNumber={pageNumber}&pageSize={pageSize}");
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<StudentSubmissionSummaryResponseDto>(content);
return ApiResponse.Success(result: result);
}
else
{
var errorContent = await response.Content.ReadAsStringAsync();
return ApiResponse.Error(message: $"获取学生提交信息失败: {response.StatusCode} - {errorContent}");
}
}
catch (Exception ex)
{
return ApiResponse.Error(message: $"内部错误: {ex.Message}");
}
}
}
}

View File

@@ -1,16 +1,44 @@
@inherits LayoutComponentBase
@layout AccountLayout
@inject NavigationManager NavigationManager
<AuthorizeView Roles="Teacher">
<Authorized>
<MudPaper Class="d-flex flex-row flex-grow-1 overflow-hidden" Height="100%">
<MudPaper Width="200px">
<MudDivider Class="flex-grow-0" />
<ExamNavMenu />
</MudPaper>
<MudPaper Class="flex-grow-1 overflow-auto">
<MudPaper Class="d-flex flex-grow-1 overflow-auto">
@Body
</MudPaper>
</MudPaper>
</Authorized>
</AuthorizeView>
<AuthorizeView Roles="Student">
<Authorized>
<MudPaper Class="d-flex flex-row flex-grow-1 overflow-hidden" Height="100%">
<MudPaper Width="200px">
<MudDivider Class="flex-grow-0" />
<ExamNavMenu />
</MudPaper>
<MudPaper Class="d-flex h-100 w-100 flex-grow-1 overflow-auto">
@Body
</MudPaper>
</MudPaper>
</Authorized>
</AuthorizeView>
@code {
[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; }
protected override void OnParametersSet()
{
if (authenticationStateTask is null)
{
NavigationManager.Refresh(forceReload: true);
}
}
}

View File

@@ -1,6 +1,10 @@
@using Microsoft.AspNetCore.Identity
<AuthorizeView Roles="Teacher">
<Authorized>
<ul class="nav nav-pills flex-column">
<li class="nav-item">
<NavLink class="nav-link" href="exam/create" Match="NavLinkMatch.All">创建</NavLink>
@@ -11,11 +15,24 @@
<li class="nav-item">
<NavLink class="nav-link" href="Account/Manage/ChangePassword">Password</NavLink>
</li>
@* <li class="nav-item">
<NavLink class="nav-link" href="Account/Manage/TwoFactorAuthentication">Two-factor authentication</NavLink>
</li> *@
</ul>
</Authorized>
</AuthorizeView>
<AuthorizeView Roles="Student">
<Authorized>
<ul class="nav nav-pills flex-column">
<li class="nav-item">
<NavLink class="nav-link" href="exam/studentHomework" Match="NavLinkMatch.All">Homework</NavLink>
</li>
<li class="nav-item">
<NavLink class="nav-link" href="exam/manage">管理</NavLink>
</li>
</ul>
</Authorized>
</AuthorizeView>
@code {
private bool hasExternalLogins;

View File

@@ -9,3 +9,17 @@
@Body
</MudStack>
</MudPaper>
@code {
[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; }
protected override void OnParametersSet()
{
if (authenticationStateTask is null)
{
// NavigationManager.Refresh(forceReload: true);
}
}
}

View File

@@ -9,11 +9,8 @@
<NavLink class="nav-link" href="Account/Manage/Class">Class</NavLink>
</li>
<li class="nav-item">
<NavLink class="nav-link" href="Account/Manage/ChangePassword">Password</NavLink>
<NavLink class="nav-link" href="Account/Manage/ChangePassword">重设密码</NavLink>
</li>
@* <li class="nav-item">
<NavLink class="nav-link" href="Account/Manage/TwoFactorAuthentication">Two-factor authentication</NavLink>
</li> *@
</ul>
@code {

View File

@@ -20,3 +20,4 @@
@using TechHelper.Client.Pages.Author
@using TechHelper.Client.Pages
@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="_content/Blazored.TextEditor/quill-blot-formatter.min.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>

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<SubmissionDetail> SubmissionDetails { get; set; }
public DbSet<QuestionContext> QuestionContexts { get; set; }
public DbSet<Global> Globals { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{

View File

@@ -2,6 +2,10 @@
using AutoMapper.Internal.Mappers;
using Entities.Contracts;
using Entities.DTO;
using Newtonsoft.Json;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace TechHelper.Context
{
@@ -54,6 +58,23 @@ namespace TechHelper.Context
CreateMap<SubmissionDetailDto, SubmissionDetail>().ReverseMap();
// Student Submission Detail
CreateMap<Submission, StudentSubmissionDetailDto>()
.ForMember(dest => dest.AssignmentId, opt => opt.MapFrom(src => src.AssignmentId))
.ForMember(dest => dest.StudentId, opt => opt.MapFrom(src => src.StudentId))
.ForMember(dest => dest.SubmissionTime, opt => opt.MapFrom(src => src.SubmissionTime))
.ForMember(dest => dest.OverallGrade, opt => opt.MapFrom(src => src.OverallGrade))
.ForMember(dest => dest.OverallFeedback, opt => opt.MapFrom(src => src.OverallFeedback))
.ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.Status));
CreateMap<Assignment, AssignmentDto>().ReverseMap();
CreateMap<SubjectTypeMetadataDto, Global>()
.ForMember(dest => dest.Info, opt => opt.MapFrom(src => JsonConvert.SerializeObject(src.Data)));
CreateMap<Global, SubjectTypeMetadataDto>()
.ForMember(dest => dest.Data, opt => opt.MapFrom(src => JsonConvert.DeserializeObject<Dictionary<string, (string Color, string DisplayName)>>(src.Info)));
}
}

View File

@@ -39,6 +39,7 @@ namespace TechHelper.Context.Configuration
builder.Property(s => s.OverallGrade)
.HasColumnName("overall_grade")
.IsRequired()
.HasPrecision(5, 2); // 应用精度设置
// OverallFeedback

View File

@@ -10,6 +10,10 @@ using Entities.Contracts;
namespace TechHelper.Controllers
{
/// <summary>
/// 账户管理控制器
/// 处理用户注册、登录、密码重置等认证相关操作
/// </summary>
[Route("api/account")]
[ApiController]
public class AccountController : ControllerBase
@@ -19,6 +23,13 @@ namespace TechHelper.Controllers
private IAuthenticationService _authenticationService;
private readonly IEmailSender _emailSender;
/// <summary>
/// 初始化账户控制器
/// </summary>
/// <param name="userManager">用户管理服务</param>
/// <param name="userRegistrationService">用户注册服务</param>
/// <param name="emailSender">邮件发送服务</param>
/// <param name="authenticationService">认证服务</param>
public AccountController(UserManager<User> userManager,
IUserRegistrationService userRegistrationService,
IEmailSender emailSender,
@@ -30,6 +41,13 @@ namespace TechHelper.Controllers
_authenticationService = authenticationService;
}
/// <summary>
/// 注册新用户
/// </summary>
/// <param name="userForRegistrationDto">用户注册信息数据传输对象</param>
/// <returns>注册结果响应</returns>
/// <response code="201">用户注册成功</response>
/// <response code="400">注册请求无效或验证失败</response>
[HttpPost("register")]
public async Task<IActionResult> RegisterUsesr(
[FromBody] UserForRegistrationDto userForRegistrationDto)
@@ -93,6 +111,14 @@ namespace TechHelper.Controllers
#endregion
}
/// <summary>
/// 用户登录认证
/// </summary>
/// <param name="userForAuthentication">用户认证信息数据传输对象</param>
/// <returns>认证结果响应</returns>
/// <response code="200">登录成功,返回认证令牌</response>
/// <response code="401">认证失败,用户名或密码错误</response>
/// <response code="400">请求无效或验证失败</response>
[HttpPost("login")]
public async Task<IActionResult> Logion(
[FromBody] UserForAuthenticationDto userForAuthentication)
@@ -158,6 +184,11 @@ namespace TechHelper.Controllers
});
}
/// <summary>
/// 生成两步验证的OTP令牌
/// </summary>
/// <param name="user">用户对象</param>
/// <returns>两步验证响应</returns>
private async Task<IActionResult> GenerateOTPFor2StepVerification(User user)
{
var providers = await _userManager.GetValidTwoFactorProvidersAsync(user);
@@ -180,6 +211,14 @@ namespace TechHelper.Controllers
});
}
/// <summary>
/// 忘记密码请求
/// 发送密码重置令牌到用户邮箱
/// </summary>
/// <param name="forgotPasswordDto">忘记密码请求数据传输对象</param>
/// <returns>操作结果</returns>
/// <response code="200">密码重置邮件发送成功</response>
/// <response code="400">请求无效或用户不存在</response>
[HttpPost("forgotPassword")]
public async Task<IActionResult> ForgotPassword(
[FromBody] ForgotPasswordDto forgotPasswordDto)
@@ -203,6 +242,13 @@ namespace TechHelper.Controllers
}
/// <summary>
/// 重置用户密码
/// </summary>
/// <param name="resetPasswordDto">密码重置数据传输对象</param>
/// <returns>重置结果响应</returns>
/// <response code="200">密码重置成功</response>
/// <response code="400">密码重置失败</response>
[HttpPost("resetPassword")]
public async Task<IActionResult> ResetPassword(
[FromBody] ResetPasswordDto resetPasswordDto)
@@ -231,6 +277,15 @@ namespace TechHelper.Controllers
return Ok(new ResetPasswordResponseDto { IsResetPasswordSuccessful = true});
}
/// <summary>
/// 邮箱确认验证
/// 验证用户邮箱确认令牌
/// </summary>
/// <param name="email">用户邮箱地址</param>
/// <param name="token">邮箱确认令牌</param>
/// <returns>验证结果</returns>
/// <response code="200">邮箱确认成功</response>
/// <response code="400">邮箱确认失败</response>
[HttpGet("emailconfirmation")]
public async Task<IActionResult> EmailConfirmaation([FromQuery] string email,
[FromQuery] string token)
@@ -245,6 +300,14 @@ namespace TechHelper.Controllers
}
/// <summary>
/// 两步验证确认
/// 验证用户提供的两步验证令牌
/// </summary>
/// <param name="twoFactorVerificationDto">两步验证数据传输对象</param>
/// <returns>验证结果响应</returns>
/// <response code="200">验证成功,返回认证令牌</response>
/// <response code="400">验证失败</response>
[HttpPost("TwoStepVerification")]
public async Task<IActionResult> TwoStepVerification(
[FromBody] TwoFactorVerificationDto twoFactorVerificationDto)

View File

@@ -9,18 +9,35 @@ using TechHelper.Services;
namespace TechHelper.Server.Controllers
{
/// <summary>
/// 班级管理控制器
/// 处理班级相关的操作,如用户注册到班级、获取班级学生等
/// </summary>
[Route("api/class")]
[ApiController]
public class ClassController : ControllerBase
{
private IClassService _classService;
private UserManager<User> _userManager;
/// <summary>
/// 初始化班级控制器
/// </summary>
/// <param name="classService">班级服务</param>
/// <param name="userManager">用户管理服务</param>
public ClassController(IClassService classService, UserManager<User> userManager)
{
_classService = classService;
_userManager = userManager;
}
/// <summary>
/// 用户注册到班级
/// </summary>
/// <param name="toClass">用户注册到班级的数据传输对象</param>
/// <returns>操作结果</returns>
/// <response code="200">注册成功</response>
/// <response code="400">注册失败</response>
[HttpPost("userRegiste")]
public async Task<IActionResult> UserRegisterToClass(
[FromBody] UserRegistrationToClassDto toClass)
@@ -36,6 +53,13 @@ namespace TechHelper.Server.Controllers
/// <summary>
/// 获取班级学生列表
/// 仅限教师角色访问,根据教师所在班级信息获取学生列表
/// </summary>
/// <returns>班级学生列表</returns>
/// <response code="200">成功获取学生列表</response>
/// <response code="400">权限不足或班级信息缺失</response>
[HttpPost("getClassStudents")]
public async Task<IActionResult> GetClassStudents()
{
@@ -84,6 +108,13 @@ namespace TechHelper.Server.Controllers
}
}
/// <summary>
/// 创建新班级
/// </summary>
/// <param name="classDto">班级数据传输对象</param>
/// <returns>操作结果</returns>
/// <response code="200">班级创建成功</response>
/// <response code="400">班级创建失败</response>
[HttpPost("Create")]
public async Task<IActionResult> Create(
[FromBody] ClassDto classDto)
@@ -94,6 +125,13 @@ namespace TechHelper.Server.Controllers
return Ok();
}
/// <summary>
/// 获取指定年级的所有班级列表
/// </summary>
/// <param name="classDto">年级编号</param>
/// <returns>班级列表</returns>
/// <response code="200">成功获取班级列表</response>
/// <response code="400">获取失败</response>
[HttpPost("GetGradeClasses")]
public async Task<IActionResult> GetGradeClasses(
[FromBody] byte classDto)

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);
}
}
}

View File

@@ -0,0 +1,127 @@
using Entities.Contracts;
using Entities.DTO;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using TechHelper.Server.Services;
using System.Security.Claims;
namespace TechHelper.Server.Controllers
{
[Route("api/student-submission")]
[ApiController]
[Authorize]
public class StudentSubmissionController : ControllerBase
{
private readonly IStudentSubmissionService _studentSubmissionService;
private readonly UserManager<User> _userManager;
public StudentSubmissionController(
IStudentSubmissionService studentSubmissionService,
UserManager<User> userManager)
{
_studentSubmissionService = studentSubmissionService;
_userManager = userManager;
}
/// <summary>
/// 获取当前学生的所有提交摘要
/// </summary>
/// <returns>学生提交摘要列表</returns>
[HttpGet("my-submissions")]
public async Task<IActionResult> GetMySubmissions()
{
var user = await _userManager.FindByEmailAsync(User.Identity.Name);
if (user == null)
return NotFound("未找到用户信息");
var result = await _studentSubmissionService.GetStudentSubmissionsAsync(user.Id);
if (result.Status)
{
return Ok(result.Result);
}
else
{
return BadRequest(result.Message);
}
}
/// <summary>
/// 获取当前学生的提交摘要(分页)
/// </summary>
/// <param name="pageNumber">页码默认为1</param>
/// <param name="pageSize">每页数量默认为10</param>
/// <returns>分页的学生提交摘要列表</returns>
[HttpGet("my-submissions-paged")]
public async Task<IActionResult> GetMySubmissionsPaged(int pageNumber = 1, int pageSize = 10)
{
if (pageNumber < 1) pageNumber = 1;
if (pageSize < 1) pageSize = 10;
if (pageSize > 100) pageSize = 100; // 限制最大页面大小
var user = await _userManager.FindByEmailAsync(User.Identity.Name);
if (user == null)
return NotFound("未找到用户信息");
var result = await _studentSubmissionService.GetStudentSubmissionsPagedAsync(user.Id, pageNumber, pageSize);
if (result.Status)
{
return Ok(result.Result);
}
else
{
return BadRequest(result.Message);
}
}
/// <summary>
/// 获取指定学生的提交摘要(仅教师可使用)
/// </summary>
/// <param name="studentId">学生ID</param>
/// <returns>学生提交摘要列表</returns>
[HttpGet("student/{studentId:guid}")]
[Authorize(Roles = "Teacher")]
public async Task<IActionResult> GetStudentSubmissions(Guid studentId)
{
var result = await _studentSubmissionService.GetStudentSubmissionsAsync(studentId);
if (result.Status)
{
return Ok(result.Result);
}
else
{
return BadRequest(result.Message);
}
}
/// <summary>
/// 获取指定学生的提交摘要(分页,仅教师可使用)
/// </summary>
/// <param name="studentId">学生ID</param>
/// <param name="pageNumber">页码默认为1</param>
/// <param name="pageSize">每页数量默认为10</param>
/// <returns>分页的学生提交摘要列表</returns>
[HttpGet("student/{studentId:guid}/paged")]
[Authorize(Roles = "Teacher")]
public async Task<IActionResult> GetStudentSubmissionsPaged(Guid studentId, int pageNumber = 1, int pageSize = 10)
{
if (pageNumber < 1) pageNumber = 1;
if (pageSize < 1) pageSize = 10;
if (pageSize > 100) pageSize = 100; // 限制最大页面大小
var result = await _studentSubmissionService.GetStudentSubmissionsPagedAsync(studentId, pageNumber, pageSize);
if (result.Status)
{
return Ok(result.Result);
}
else
{
return BadRequest(result.Message);
}
}
}
}

View File

@@ -0,0 +1,81 @@
using Entities.Contracts;
using Entities.DTO;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using TechHelper.Server.Services;
using TechHelper.Context;
using TechHelper.Repository;
using SharedDATA.Api;
using System.Security.Claims;
namespace TechHelper.Server.Controllers
{
[Route("api/student-submission-detail")]
[ApiController]
[Authorize]
public class StudentSubmissionDetailController : ControllerBase
{
private readonly IStudentSubmissionDetailService _studentSubmissionDetailService;
private readonly UserManager<User> _userManager;
private readonly IUnitOfWork _unitOfWork;
public StudentSubmissionDetailController(
IStudentSubmissionDetailService studentSubmissionDetailService,
UserManager<User> userManager,
IUnitOfWork unitOfWork)
{
_studentSubmissionDetailService = studentSubmissionDetailService;
_userManager = userManager;
_unitOfWork = unitOfWork;
}
/// <summary>
/// 获取学生提交的详细信息
/// </summary>
/// <param name="submissionId">提交ID</param>
/// <returns>学生提交详细信息</returns>
[HttpGet("{submissionId:guid}")]
public async Task<IActionResult> GetSubmissionDetail(Guid submissionId)
{
try
{
// 验证用户权限 - 只有学生本人或教师可以查看
var user = await _userManager.FindByEmailAsync(User.Identity.Name);
if (user == null)
{
return NotFound("未找到用户信息");
}
var submission = await _unitOfWork.GetRepository<Submission>()
.GetFirstOrDefaultAsync(predicate: s => s.Id == submissionId);
if (submission == null)
{
return NotFound("未找到指定的提交记录");
}
// 检查权限:学生只能查看自己的提交,教师可以查看所有提交
if (user.Id != submission.StudentId && !User.IsInRole("Teacher"))
{
return Forbid("您没有权限查看此提交记录");
}
var result = await _studentSubmissionDetailService.GetSubmissionDetailAsync(submissionId);
if (result.Status)
{
return Ok(result.Result);
}
else
{
return BadRequest(result.Message);
}
}
catch (Exception ex)
{
return StatusCode(500, $"获取学生提交详细信息失败: {ex.Message}");
}
}
}
}

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" }
});
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,97 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
namespace TechHelper.Server.Migrations
{
/// <inheritdoc />
public partial class submission_up_2 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("2670f35a-df0c-4071-8879-80eb99d138a1"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("8c6c5e8e-ef00-444c-9c7c-cba5cd6f7043"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("9eda9d90-0cd2-4fbe-b07e-f90bd01f32db"));
migrationBuilder.AlterColumn<float>(
name: "overall_grade",
table: "submissions",
type: "float",
precision: 5,
scale: 2,
nullable: false,
defaultValue: 0f,
oldClrType: typeof(float),
oldType: "float",
oldPrecision: 5,
oldScale: 2,
oldNullable: true);
migrationBuilder.InsertData(
table: "AspNetRoles",
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
values: new object[,]
{
{ new Guid("264e4290-9d15-478d-8c49-8d0935e5a6e1"), null, "Administrator", "ADMINISTRATOR" },
{ new Guid("73cafcee-3e99-43ae-86c5-c01a1cbc6124"), null, "Teacher", "TEACHER" },
{ new Guid("f06927ff-4bba-4ab6-8f0a-e45a765c2fcc"), null, "Student", "STUDENT" }
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("264e4290-9d15-478d-8c49-8d0935e5a6e1"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("73cafcee-3e99-43ae-86c5-c01a1cbc6124"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("f06927ff-4bba-4ab6-8f0a-e45a765c2fcc"));
migrationBuilder.AlterColumn<float>(
name: "overall_grade",
table: "submissions",
type: "float",
precision: 5,
scale: 2,
nullable: true,
oldClrType: typeof(float),
oldType: "float",
oldPrecision: 5,
oldScale: 2);
migrationBuilder.InsertData(
table: "AspNetRoles",
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
values: new object[,]
{
{ new Guid("2670f35a-df0c-4071-8879-80eb99d138a1"), null, "Teacher", "TEACHER" },
{ new Guid("8c6c5e8e-ef00-444c-9c7c-cba5cd6f7043"), null, "Student", "STUDENT" },
{ new Guid("9eda9d90-0cd2-4fbe-b07e-f90bd01f32db"), null, "Administrator", "ADMINISTRATOR" }
});
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,71 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
namespace TechHelper.Server.Migrations
{
/// <inheritdoc />
public partial class submission_up_3 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("264e4290-9d15-478d-8c49-8d0935e5a6e1"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("73cafcee-3e99-43ae-86c5-c01a1cbc6124"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("f06927ff-4bba-4ab6-8f0a-e45a765c2fcc"));
migrationBuilder.InsertData(
table: "AspNetRoles",
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
values: new object[,]
{
{ new Guid("388fdb1d-8cd5-4e8f-b49c-06dbee60527b"), null, "Administrator", "ADMINISTRATOR" },
{ new Guid("ba4054d5-2f8a-4c7f-bd56-0fc864720c7d"), null, "Teacher", "TEACHER" },
{ new Guid("c758a0d2-faea-4cf1-aa14-d162f3d0a1e9"), null, "Student", "STUDENT" }
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("388fdb1d-8cd5-4e8f-b49c-06dbee60527b"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("ba4054d5-2f8a-4c7f-bd56-0fc864720c7d"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("c758a0d2-faea-4cf1-aa14-d162f3d0a1e9"));
migrationBuilder.InsertData(
table: "AspNetRoles",
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
values: new object[,]
{
{ new Guid("264e4290-9d15-478d-8c49-8d0935e5a6e1"), null, "Administrator", "ADMINISTRATOR" },
{ new Guid("73cafcee-3e99-43ae-86c5-c01a1cbc6124"), null, "Teacher", "TEACHER" },
{ new Guid("f06927ff-4bba-4ab6-8f0a-e45a765c2fcc"), null, "Student", "STUDENT" }
});
}
}
}

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 tee : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("388fdb1d-8cd5-4e8f-b49c-06dbee60527b"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("ba4054d5-2f8a-4c7f-bd56-0fc864720c7d"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("c758a0d2-faea-4cf1-aa14-d162f3d0a1e9"));
migrationBuilder.AddColumn<bool>(
name: "BCorrect",
table: "assignment_questions",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
migrationBuilder.InsertData(
table: "AspNetRoles",
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
values: new object[,]
{
{ new Guid("d480cdca-7de2-4abe-8129-73bbaa6c1b32"), null, "Student", "STUDENT" },
{ new Guid("d7bcfb37-3f1c-467b-a3f0-b2339a8a990d"), null, "Teacher", "TEACHER" },
{ new Guid("f4a6788a-04d8-499c-9e64-73dfba97ca6b"), null, "Administrator", "ADMINISTRATOR" }
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("d480cdca-7de2-4abe-8129-73bbaa6c1b32"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("d7bcfb37-3f1c-467b-a3f0-b2339a8a990d"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("f4a6788a-04d8-499c-9e64-73dfba97ca6b"));
migrationBuilder.DropColumn(
name: "BCorrect",
table: "assignment_questions");
migrationBuilder.InsertData(
table: "AspNetRoles",
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
values: new object[,]
{
{ new Guid("388fdb1d-8cd5-4e8f-b49c-06dbee60527b"), null, "Administrator", "ADMINISTRATOR" },
{ new Guid("ba4054d5-2f8a-4c7f-bd56-0fc864720c7d"), null, "Teacher", "TEACHER" },
{ new Guid("c758a0d2-faea-4cf1-aa14-d162f3d0a1e9"), null, "Student", "STUDENT" }
});
}
}
}

View File

@@ -175,6 +175,9 @@ namespace TechHelper.Server.Migrations
b.Property<Guid?>("AssignmentId")
.HasColumnType("char(36)");
b.Property<bool>("BCorrect")
.HasColumnType("tinyint(1)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)")
.HasColumnName("created_at");
@@ -219,6 +222,9 @@ namespace TechHelper.Server.Migrations
.HasColumnType("varchar(1024)")
.HasColumnName("title");
b.Property<byte>("Type")
.HasColumnType("tinyint unsigned");
b.HasKey("Id");
b.HasIndex("AssignmentId");
@@ -332,6 +338,25 @@ namespace TechHelper.Server.Migrations
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 =>
{
b.Property<Guid>("Id")
@@ -445,6 +470,10 @@ namespace TechHelper.Server.Migrations
.HasColumnType("longtext")
.HasColumnName("options");
b.Property<string>("QType")
.IsRequired()
.HasColumnType("longtext");
b.Property<byte>("SubjectArea")
.HasMaxLength(100)
.HasColumnType("tinyint unsigned")
@@ -532,7 +561,7 @@ namespace TechHelper.Server.Migrations
.HasColumnType("longtext")
.HasColumnName("overall_feedback");
b.Property<float?>("OverallGrade")
b.Property<float>("OverallGrade")
.HasPrecision(5, 2)
.HasColumnType("float")
.HasColumnName("overall_grade");
@@ -723,6 +752,9 @@ namespace TechHelper.Server.Migrations
b.Property<string>("SecurityStamp")
.HasColumnType("longtext");
b.Property<byte>("SubjectArea")
.HasColumnType("tinyint unsigned");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("tinyint(1)");
@@ -771,19 +803,19 @@ namespace TechHelper.Server.Migrations
b.HasData(
new
{
Id = new Guid("df89b9a0-65ef-42dd-b2cb-e59997a72e70"),
Id = new Guid("d480cdca-7de2-4abe-8129-73bbaa6c1b32"),
Name = "Student",
NormalizedName = "STUDENT"
},
new
{
Id = new Guid("0775702a-5db7-4747-94d0-4376fad2b58b"),
Id = new Guid("d7bcfb37-3f1c-467b-a3f0-b2339a8a990d"),
Name = "Teacher",
NormalizedName = "TEACHER"
},
new
{
Id = new Guid("37f41430-0cb7-44e5-988b-976200bd602d"),
Id = new Guid("f4a6788a-04d8-499c-9e64-73dfba97ca6b"),
Name = "Administrator",
NormalizedName = "ADMINISTRATOR"
});

View File

@@ -14,12 +14,15 @@ using TechHelper.Server.Services;
using TechHelper.Server.Repositories;
using Microsoft.OpenApi.Models;
/// <summary>
/// TechHelper 服务器应用程序的主入口点
/// 配置和启动 ASP.NET Core Web API 应用程序
/// </summary>
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 =>
options.UseMySql(
builder.Configuration.GetConnectionString("XSDB"),
@@ -34,17 +37,20 @@ builder.Services.AddDbContext<ApplicationContext>(options =>
.AddCustomRepository<ClassTeacher, ClassTeacherRepository>()
.AddCustomRepository<Question, QuestionRepository>()
.AddCustomRepository<QuestionContext, QuestionContextRepository>()
.AddCustomRepository<Submission, SubmissionRepository>();
.AddCustomRepository<Submission, SubmissionRepository>()
.AddCustomRepository<User, UserRepository>()
.AddCustomRepository<Global, GlobalRepository>();
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<JwtConfiguration>(builder.Configuration.GetSection("JWTSettings"));
// 4. <EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><EFBFBD><EFBFBD><EFBFBD> (Identity, JWT, <EFBFBD>Զ<EFBFBD><EFBFBD><EFBFBD> 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>)
// 4. 认证和授权服务 (Identity, JWT, 自定义 Auth)
// 添加 ASP.NET Core Identity (包含默认的 Cookie 认证和授权服务)
builder.Services.AddIdentity<User, IdentityRole<Guid>>(opt =>
{
opt.User.AllowedUserNameCharacters = "";
@@ -60,25 +66,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");
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĭ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ JWT Bearer
options.DefaultChallengeScheme = 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; // 设置默认挑战方案为 JWT Bearer
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true, // <EFBFBD><EFBFBD>֤ǩ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
ValidateAudience = true, // <EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
ValidateLifetime = true, // <EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ч<EFBFBD><EFBFBD>
ValidateIssuerSigningKey = true, // <EFBFBD><EFBFBD>֤ǩ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Կ
ValidateIssuer = true, // 验证签发人
ValidateAudience = true, // 验证受众
ValidateLifetime = true, // 验证令牌有效期
ValidateIssuerSigningKey = true, // 验证签名密钥
ValidIssuer = jwtSettings["validIssuer"], // <EFBFBD>Ϸ<EFBFBD><EFBFBD><EFBFBD>ǩ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
ValidAudience = jwtSettings["validAudience"], // <EFBFBD>Ϸ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings["securityKey"])) // ǩ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Կ
ValidIssuer = jwtSettings["validIssuer"], // 合法的签发人
ValidAudience = jwtSettings["validAudience"], // 合法的受众
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings["securityKey"])) // 签名密钥
};
});
@@ -89,7 +95,10 @@ builder.Services.AddScoped<IClassService, ClassService>();
builder.Services.AddScoped<IExamService, ExamService>();
builder.Services.AddScoped<IUserSerivces, UserServices>();
builder.Services.AddScoped<ISubmissionServices, SubmissionServices>();
builder.Services.AddScoped<IStudentSubmissionService, StudentSubmissionService>();
builder.Services.AddScoped<IStudentSubmissionDetailService, StudentSubmissionDetailService>();
builder.Services.AddScoped<IExamRepository, ExamRepository>();
builder.Services.AddScoped<INoteService, NoteService>();
builder.Services.AddEndpointsApiExplorer();
@@ -128,7 +137,7 @@ builder.Services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOrigin",
builder => builder
.WithOrigins("https://localhost:7047", "http://localhost:7047")
.WithOrigins("https://localhost:7047", "http://localhost:7047", "https://localhost:5001", "http://localhost:5001")
.AllowAnyHeader()
.AllowAnyMethod()
.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

@@ -143,6 +143,21 @@ namespace TechHelper.Services
}
}
public Task GetClassStudents(byte Grade, byte Class)
{
try
{
var result = _work.GetRepository<Class>().GetFirstOrDefault(predicate:
c => c.Grade == Grade && c.Number == Class,
include: i => i
.Include(c => c.ClassStudents)
.ThenInclude(cs => cs.Student));
}
catch (Exception ex)
{ }
}
public async Task<ApiResponse> GetGradeClasses(byte Grade)
{
try

View File

@@ -11,6 +11,6 @@ namespace TechHelper.Services
public Task<ApiResponse> GetUserClassRole(Guid user); // List<UserClassRoleDto>
public Task<ApiResponse> GetClassStudents(ClassDto classDto); // Class
public Task<ApiResponse> GetGradeClasses(byte Grade); // Class
Task GetClassStudents(byte grade, byte id);
}
}

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,15 @@
using Entities.DTO;
using TechHelper.Services;
namespace TechHelper.Server.Services
{
public interface IStudentSubmissionDetailService
{
/// <summary>
/// 获取学生提交的详细信息
/// </summary>
/// <param name="submissionId">提交ID</param>
/// <returns>学生提交详细信息</returns>
Task<ApiResponse> GetSubmissionDetailAsync(Guid submissionId);
}
}

View File

@@ -0,0 +1,24 @@
using Entities.DTO;
using TechHelper.Services;
namespace TechHelper.Server.Services
{
public interface IStudentSubmissionService : IBaseService<StudentSubmissionSummaryDto, Guid>
{
/// <summary>
/// 获取学生提交的作业摘要列表
/// </summary>
/// <param name="studentId">学生ID</param>
/// <returns>学生提交摘要列表</returns>
Task<ApiResponse> GetStudentSubmissionsAsync(Guid studentId);
/// <summary>
/// 获取学生提交的作业摘要列表(分页)
/// </summary>
/// <param name="studentId">学生ID</param>
/// <param name="pageNumber">页码</param>
/// <param name="pageSize">每页数量</param>
/// <returns>分页的学生提交摘要列表</returns>
Task<ApiResponse> GetStudentSubmissionsPagedAsync(Guid studentId, int pageNumber = 1, int pageSize = 10);
}
}

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

@@ -0,0 +1,144 @@
using AutoMapper;
using AutoMapper.Internal.Mappers;
using Entities.Contracts;
using Entities.DTO;
using Microsoft.EntityFrameworkCore;
using SharedDATA.Api;
using TechHelper.Context;
using TechHelper.Repository;
using TechHelper.Services;
namespace TechHelper.Server.Services
{
public class StudentSubmissionDetailService : IStudentSubmissionDetailService
{
private readonly IUnitOfWork _unitOfWork;
private readonly IExamService examService;
private readonly IMapper _mapper;
public StudentSubmissionDetailService(
IUnitOfWork unitOfWork,
IExamService examService,
IMapper mapper)
{
_unitOfWork = unitOfWork;
this.examService = examService;
_mapper = mapper;
}
public async Task<ApiResponse> GetSubmissionDetailAsync(Guid submissionId)
{
try
{
// 获取submission基本信息
var submission = await _unitOfWork.GetRepository<Submission>()
.GetAll(s => s.Id == submissionId)
.Include(s => s.Assignment)
.ThenInclude(a => a.Creator)
.FirstOrDefaultAsync();
if (submission == null)
{
return ApiResponse.Error("未找到指定的提交记录");
}
var assignment = await examService.GetAsync(submission.AssignmentId);
if (assignment == null)
{
return ApiResponse.Error("未找到指定的作业");
}
// 获取所有提交详情
var submissionDetails = await _unitOfWork.GetRepository<SubmissionDetail>()
.GetAll(sd => sd.SubmissionId == submissionId)
.Include(sd => sd.AssignmentQuestion)
.ThenInclude(aq => aq.Question)
.ThenInclude(q => q.Lesson)
.ThenInclude(q => q.KeyPoints)
.ToListAsync();
// 获取同作业的所有提交用于排名和成绩分布
var allSubmissions = await _unitOfWork.GetRepository<Submission>()
.GetAll(s => s.AssignmentId == submission.AssignmentId)
.ToListAsync();
// 映射基本信息
var result = _mapper.Map<StudentSubmissionDetailDto>(submission);
result.Assignment = assignment.Result as AssignmentDto ?? new AssignmentDto();
var errorQuestion = submissionDetails
.Where(sd => sd.IsCorrect == false && sd.AssignmentQuestion?.StructType == AssignmentStructType.Question && sd.AssignmentQuestion?.Question != null)
.ToList();
// 计算基础统计
result.TotalQuestions = submissionDetails.Select(x => x.AssignmentQuestion.StructType == AssignmentStructType.Question && x.AssignmentQuestion?.Question != null).Count();
result.ErrorCount = errorQuestion.Count;
result.CorrectCount = result.TotalQuestions - result.ErrorCount;
result.AccuracyRate = result.TotalQuestions > 0 ?
(float)result.CorrectCount / result.TotalQuestions : 0;
// 计算错误类型分布 - 只获取题目类型的错误
result.ErrorTypeDistribution = errorQuestion
.GroupBy(sd => sd.AssignmentQuestion.Question.Type.ToString())
.ToDictionary(g => g.Key, g => g.Count()); ;
// 计算错误类型成绩分布 - 只获取题目类型的错误
result.ErrorTypeScoreDistribution = errorQuestion
.GroupBy(sd => sd.AssignmentQuestion.Question.Type.ToString())
.ToDictionary(g => g.Key, g => g.Sum(sd => sd.PointsAwarded ?? 0));
// 计算成绩排名
var orderedSubmissions = allSubmissions
.OrderByDescending(s => s.OverallGrade)
.ToList();
result.TotalRank = orderedSubmissions.FindIndex(s => s.Id == submissionId) + 1;
SetBCorrect(result.Assignment, errorQuestion);
// 计算成绩分布
result.AllScores = allSubmissions.Select(s => s.OverallGrade).ToList();
result.AverageScore = submission.OverallGrade;
result.ClassAverageScore = allSubmissions.Average(s => s.OverallGrade);
// 计算课文错误分布
result.LessonErrorDistribution = errorQuestion
.Where(eq => eq.AssignmentQuestion.Question.Lesson != null)
.GroupBy(sd => sd.AssignmentQuestion.Question.Lesson.Title)
.ToDictionary(g => g.Key, g => g.Count());
// 计算关键点错误分布
result.KeyPointErrorDistribution = errorQuestion
.Where(eq => eq.AssignmentQuestion.Question.Lesson != null)
.GroupBy(sd => sd.AssignmentQuestion.Question.KeyPoint.Key)
.ToDictionary(g => g.Key, g => g.Count());
return ApiResponse.Success(result: result);
}
catch (Exception ex)
{
return ApiResponse.Error($"获取学生提交详细信息失败: {ex.Message}");
}
}
public void SetBCorrect(AssignmentDto assignment, List<SubmissionDetail> submissionDetails)
{
SetBCorrect(assignment.ExamStruct, submissionDetails);
}
public void SetBCorrect(AssignmentQuestionDto assignmentQuestion, List<SubmissionDetail> submissionDetails)
{
var sd = submissionDetails.FirstOrDefault(x => x.AssignmentQuestionId == assignmentQuestion.Id);
if (sd != null)
assignmentQuestion.BCorrect = sd.AssignmentQuestion.BCorrect;
else
assignmentQuestion.BCorrect = false;
assignmentQuestion.ChildrenAssignmentQuestion?.ForEach(
cq => SetBCorrect(cq, submissionDetails));
}
//Task<ApiResponse> IStudentSubmissionDetailService.GetSubmissionDetailAsync(Guid submissionId)
//{
// throw new NotImplementedException();
//}
}
}

View File

@@ -0,0 +1,142 @@
using AutoMapper;
using Entities.Contracts;
using Entities.DTO;
using Microsoft.EntityFrameworkCore;
using SharedDATA.Api;
using TechHelper.Context;
using TechHelper.Repository;
using TechHelper.Server.Repositories;
using TechHelper.Services;
namespace TechHelper.Server.Services
{
public class StudentSubmissionService : IStudentSubmissionService
{
private readonly IUnitOfWork _unitOfWork;
private readonly IMapper _mapper;
private readonly IRepository<Submission> _submissionRepository;
private readonly IRepository<Assignment> _assignmentRepository;
private readonly IRepository<User> _userRepository;
public StudentSubmissionService(
IUnitOfWork unitOfWork,
IMapper mapper,
IRepository<Submission> submissionRepository,
IRepository<Assignment> assignmentRepository,
IRepository<User> userRepository)
{
_unitOfWork = unitOfWork;
_mapper = mapper;
_submissionRepository = submissionRepository;
_assignmentRepository = assignmentRepository;
_userRepository = userRepository;
}
public async Task<ApiResponse> GetStudentSubmissionsAsync(Guid studentId)
{
try
{
var submissions = await _submissionRepository
.GetAll(s => s.StudentId == studentId)
.Include(s => s.Assignment)
.ThenInclude(a => a.Creator)
.OrderByDescending(s => s.SubmissionTime)
.ToListAsync();
var result = new List<StudentSubmissionSummaryDto>();
foreach (var submission in submissions)
{
var summary = new StudentSubmissionSummaryDto
{
Id = submission.Id,
AssignmentName = submission.Assignment?.Title ?? "未知作业",
ErrorCount = await CalculateErrorCountAsync(submission.Id),
CreatedDate = submission.SubmissionTime,
Score = (int)submission.OverallGrade,
TotalQuestions = submission.Assignment?.TotalQuestions ?? 0,
StudentName = submission.Assignment?.Creator?.UserName ?? "未知老师",
Status = submission.Status.ToString()
};
result.Add(summary);
}
return ApiResponse.Success(result: result);
}
catch (Exception ex)
{
return ApiResponse.Error($"获取学生提交信息失败: {ex.Message}");
}
}
public async Task<ApiResponse> GetStudentSubmissionsPagedAsync(Guid studentId, int pageNumber = 1, int pageSize = 10)
{
try
{
var totalCount = await _submissionRepository
.GetAll(s => s.StudentId == studentId)
.CountAsync();
var submissions = await _submissionRepository
.GetAll(s => s.StudentId == studentId)
.Include(s => s.Assignment)
.ThenInclude(a => a.Creator)
.OrderByDescending(s => s.SubmissionTime)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
var result = new List<StudentSubmissionSummaryDto>();
foreach (var submission in submissions)
{
var summary = new StudentSubmissionSummaryDto
{
Id = submission.Id,
AssignmentName = submission.Assignment?.Title ?? "未知作业",
ErrorCount = await CalculateErrorCountAsync(submission.Id),
CreatedDate = submission.SubmissionTime,
Score = submission.OverallGrade,
TotalQuestions = submission.Assignment?.TotalQuestions ?? 0,
StudentName = submission.Assignment?.Creator?.UserName ?? "未知老师",
Status = submission.Status.ToString()
};
result.Add(summary);
}
var response = new StudentSubmissionSummaryResponseDto
{
Submissions = result,
TotalCount = totalCount
};
return ApiResponse.Success(result: response);
}
catch (Exception ex)
{
return ApiResponse.Error($"获取学生提交信息失败: {ex.Message}");
}
}
private async Task<int> CalculateErrorCountAsync(Guid submissionId)
{
var submissionDetails = await _unitOfWork.GetRepository<SubmissionDetail>()
.GetAll(sd => sd.SubmissionId == submissionId)
.ToListAsync();
return submissionDetails.Select(x => !x.IsCorrect).Count();
}
// 以下方法是IBaseService接口的实现可以根据需要实现
public Task<ApiResponse> GetAllAsync() => throw new NotImplementedException();
public Task<ApiResponse> GetAsync(Guid id) => throw new NotImplementedException();
public Task<ApiResponse> AddAsync(StudentSubmissionSummaryDto model) => throw new NotImplementedException();
public Task<ApiResponse> UpdateAsync(StudentSubmissionSummaryDto model) => throw new NotImplementedException();
public Task<ApiResponse> DeleteAsync(Guid id) => throw new NotImplementedException();
public Task<ApiResponse> GetAllAsync(QueryParameter query)
{
throw new NotImplementedException();
}
}
}

View File

@@ -6,6 +6,10 @@ using TechHelper.Services;
namespace TechHelper.Server.Services
{
/// <summary>
/// 用户服务实现类
/// 处理用户相关的业务逻辑操作
/// </summary>
public class UserServices : IUserSerivces
{
@@ -13,6 +17,12 @@ namespace TechHelper.Server.Services
private readonly IClassService _classService;
private readonly UserManager<User> _userManager;
/// <summary>
/// 初始化用户服务
/// </summary>
/// <param name="unitOfWork">工作单元实例</param>
/// <param name="classService">班级服务实例</param>
/// <param name="userManager">用户管理实例</param>
public UserServices(IUnitOfWork unitOfWork, IClassService classService, UserManager<User> userManager)
{
_unitOfWork = unitOfWork;
@@ -20,31 +30,62 @@ namespace TechHelper.Server.Services
_userManager = userManager;
}
/// <summary>
/// 添加新用户
/// </summary>
/// <param name="model">用户实体对象</param>
/// <returns>操作结果响应</returns>
public Task<ApiResponse> AddAsync(User model)
{
throw new NotImplementedException();
}
/// <summary>
/// 删除指定用户
/// </summary>
/// <param name="id">用户唯一标识符</param>
/// <returns>操作结果响应</returns>
public Task<ApiResponse> DeleteAsync(Guid id)
{
throw new NotImplementedException();
}
/// <summary>
/// 获取所有用户列表
/// </summary>
/// <param name="query">查询参数对象</param>
/// <returns>用户列表响应</returns>
public Task<ApiResponse> GetAllAsync(QueryParameter query)
{
throw new NotImplementedException();
}
/// <summary>
/// 获取指定用户信息
/// </summary>
/// <param name="id">用户唯一标识符</param>
/// <returns>用户信息响应</returns>
public Task<ApiResponse> GetAsync(Guid id)
{
throw new NotImplementedException();
}
/// <summary>
/// 获取学生详细信息
/// </summary>
/// <param name="userId">用户唯一标识符</param>
/// <returns>学生详细信息响应</returns>
public Task<ApiResponse> GetStudentDetailInfo(Guid userId)
{
throw new NotImplementedException();
}
/// <summary>
/// 恢复用户角色信息
/// 根据用户所在班级信息恢复用户的角色权限
/// </summary>
/// <param name="user">用户实体对象</param>
/// <returns>操作结果响应</returns>
public async Task<ApiResponse> RestoreUserRoleInformation(User user)
{
var result = await _classService.GetUserClassRole(user.Id);
@@ -64,11 +105,21 @@ namespace TechHelper.Server.Services
return ApiResponse.Error();
}
/// <summary>
/// 更新用户信息
/// </summary>
/// <param name="model">用户实体对象</param>
/// <returns>操作结果响应</returns>
public Task<ApiResponse> UpdateAsync(User model)
{
throw new NotImplementedException();
}
/// <summary>
/// 验证用户信息
/// </summary>
/// <param name="userId">用户唯一标识符</param>
/// <returns>验证结果响应</returns>
public Task<ApiResponse> VerifyUserInformation(Guid userId)
{
throw new NotImplementedException();

View File

@@ -23,6 +23,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<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="Swashbuckle.AspNetCore" Version="8.1.1" />
</ItemGroup>