Compare commits
14 Commits
34ab5abbb0
...
RestructAs
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0d19ec6bb6 | ||
![]() |
439c8a2421 | ||
![]() |
6a65281850 | ||
![]() |
730b0ba04b | ||
![]() |
c59762a392 | ||
![]() |
017cc2169c | ||
![]() |
a21ca80782 | ||
![]() |
14fbe6397a | ||
![]() |
262e7d6396 | ||
![]() |
f9ff57ff72 | ||
![]() |
0ee411bf50 | ||
![]() |
681c0862b6 | ||
![]() |
d20c051c51 | ||
![]() |
f37262d72e |
@@ -1,10 +1,10 @@
|
|||||||
name: Tech
|
name: TechAct
|
||||||
|
|
||||||
on: [push] # 当有新的push事件发生时触发此工作流程
|
on: [push] # 当有新的push事件发生时触发此工作流程
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
explore-gitea-actions:
|
explore-gitea-actions:
|
||||||
runs-on: Tech
|
runs-on: TechAct
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4 # 使用actions/checkout来克隆您的仓库代码
|
- uses: actions/checkout@v4 # 使用actions/checkout来克隆您的仓库代码
|
||||||
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
|
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
|
||||||
|
7
.vscode/launch.json
vendored
Normal file
7
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
// 使用 IntelliSense 了解相关属性。
|
||||||
|
// 悬停以查看现有属性的描述。
|
||||||
|
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": []
|
||||||
|
}
|
8
EmailLib/Dockerfile
Normal file
8
EmailLib/Dockerfile
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# ./EmailLib/Dockerfile
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS eamillib
|
||||||
|
WORKDIR /src
|
||||||
|
COPY ../EmailLib/*.csproj ./EmailLib/
|
||||||
|
RUN dotnet restore "EmailLib/EmailLib.csproj"
|
||||||
|
|
||||||
|
COPY ../EmailLib/. ./EmailLib/
|
||||||
|
RUN dotnet publish "EmailLib/EmailLib.csproj" -c Release -o /publish
|
189
Entities/Contracts/AppMainStruct.cs
Normal file
189
Entities/Contracts/AppMainStruct.cs
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Entities.Contracts
|
||||||
|
{
|
||||||
|
public enum Layout : byte
|
||||||
|
{
|
||||||
|
horizontal = 0,
|
||||||
|
vertical = 1,
|
||||||
|
|
||||||
|
Auto = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Publisher : byte
|
||||||
|
{
|
||||||
|
Unknown = 0,
|
||||||
|
统编版,
|
||||||
|
部编版,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum GradeEnum : byte
|
||||||
|
{
|
||||||
|
Unknown = 0,
|
||||||
|
一年级 = 1,
|
||||||
|
二年级 = 2,
|
||||||
|
三年级 = 3,
|
||||||
|
四年级 = 4,
|
||||||
|
五年级 = 5,
|
||||||
|
六年级 = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DifficultyLevel : byte
|
||||||
|
{
|
||||||
|
simple,
|
||||||
|
easy,
|
||||||
|
medium,
|
||||||
|
hard,
|
||||||
|
veryHard
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum QuestionType : byte
|
||||||
|
{
|
||||||
|
Unknown = 0,
|
||||||
|
Spelling, // 拼写
|
||||||
|
Pronunciation, // 给带点字选择正确读音
|
||||||
|
WordFormation, // 组词
|
||||||
|
FillInTheBlanks, // 选词填空 / 补充词语
|
||||||
|
SentenceDictation, // 默写句子
|
||||||
|
SentenceRewriting, // 仿句 / 改写句子
|
||||||
|
ReadingComprehension, // 阅读理解
|
||||||
|
Composition // 作文
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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, // 英语
|
||||||
|
|
||||||
|
[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
|
||||||
|
{
|
||||||
|
[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
|
||||||
|
{
|
||||||
|
[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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -5,6 +5,7 @@ using System.ComponentModel.DataAnnotations;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Entities.DTO;
|
||||||
|
|
||||||
namespace Entities.Contracts
|
namespace Entities.Contracts
|
||||||
{
|
{
|
||||||
@@ -24,32 +25,46 @@ namespace Entities.Contracts
|
|||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
|
|
||||||
[Column("subject_area")]
|
[Column("subject_area")]
|
||||||
public string SubjectArea { get; set; }
|
public SubjectAreaEnum SubjectArea { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[Column("exam_struct_id")]
|
||||||
|
public Guid ExamStructId { get; set; }
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
[Column("due_date")]
|
[Column("due_date")]
|
||||||
public DateTime DueDate { get; set; }
|
public DateTime DueDate { get; set; }
|
||||||
|
|
||||||
[Column("total_points")]
|
[Column("total_points")]
|
||||||
public float? TotalPoints { get; set; }
|
public byte TotalQuestions { get; set; }
|
||||||
|
|
||||||
|
[Column("score")]
|
||||||
|
public float Score { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public ExamType ExamType { get; set; } = ExamType.DailyTest;
|
||||||
|
|
||||||
[Column("created_by")]
|
[Column("created_by")]
|
||||||
[ForeignKey("Creator")]
|
public Guid CreatorId { get; set; }
|
||||||
public Guid CreatedBy { get; set; }
|
|
||||||
|
|
||||||
[Column("created_at")]
|
[Column("created_at")]
|
||||||
public DateTime CreatedAt { get; set; }
|
public DateTime CreatedAt { get; set; }
|
||||||
|
|
||||||
[Column("updated_at")]
|
[Column("updated_at")]
|
||||||
public DateTime UpdatedAt { get; set; }
|
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
[Column("deleted")]
|
[Column("deleted")]
|
||||||
public bool IsDeleted { get; set; }
|
public bool IsDeleted { get; set; } = false;
|
||||||
|
|
||||||
// Navigation Properties
|
// Navigation Properties
|
||||||
|
|
||||||
|
[ForeignKey(nameof(CreatorId))]
|
||||||
public User Creator { get; set; }
|
public User Creator { get; set; }
|
||||||
public ICollection<AssignmentClass> AssignmentClasses { get; set; }
|
public ICollection<AssignmentClass> AssignmentClasses { get; set; }
|
||||||
public ICollection<AssignmentGroup> AssignmentGroups { get; set; }
|
|
||||||
|
[ForeignKey(nameof(ExamStructId))]
|
||||||
|
public AssignmentQuestion ExamStruct { get; set; }
|
||||||
public ICollection<AssignmentAttachment> AssignmentAttachments { get; set; }
|
public ICollection<AssignmentAttachment> AssignmentAttachments { get; set; }
|
||||||
public ICollection<Submission> Submissions { get; set; }
|
public ICollection<Submission> Submissions { get; set; }
|
||||||
|
|
||||||
@@ -58,9 +73,48 @@ namespace Entities.Contracts
|
|||||||
Id = Guid.NewGuid();
|
Id = Guid.NewGuid();
|
||||||
|
|
||||||
Submissions = new HashSet<Submission>();
|
Submissions = new HashSet<Submission>();
|
||||||
AssignmentGroups = new HashSet<AssignmentGroup>();
|
|
||||||
AssignmentClasses = new HashSet<AssignmentClass>();
|
AssignmentClasses = new HashSet<AssignmentClass>();
|
||||||
AssignmentAttachments = new HashSet<AssignmentAttachment>();
|
AssignmentAttachments = new HashSet<AssignmentAttachment>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class AssignmentExt
|
||||||
|
{
|
||||||
|
public static Submission ConvertToSubmission(this Assignment assignment, Guid studentId, Guid GraderId)
|
||||||
|
{
|
||||||
|
if (assignment == null) return new Submission();
|
||||||
|
var submission = new Submission();
|
||||||
|
|
||||||
|
submission.StudentId = studentId;
|
||||||
|
submission.SubmissionTime = DateTime.Now;
|
||||||
|
submission.Status = SubmissionStatus.Pending;
|
||||||
|
submission.GraderId = GraderId;
|
||||||
|
submission.AssignmentId = assignment.Id;
|
||||||
|
|
||||||
|
ConvertExamSturctToSubmissionDetails(assignment.ExamStruct, studentId, submission.SubmissionDetails);
|
||||||
|
|
||||||
|
return submission;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void ConvertExamSturctToSubmissionDetails(AssignmentQuestion examStruct, Guid studentId, ICollection<SubmissionDetail> submissions)
|
||||||
|
{
|
||||||
|
if (examStruct == null) return;
|
||||||
|
submissions.Add(new SubmissionDetail
|
||||||
|
{
|
||||||
|
StudentId = studentId,
|
||||||
|
AssignmentQuestionId = examStruct.Id,
|
||||||
|
IsCorrect = true,
|
||||||
|
CreatedAt = DateTime.Now,
|
||||||
|
UpdatedAt = DateTime.Now,
|
||||||
|
Status = SubmissionStatus.Pending,
|
||||||
|
});
|
||||||
|
|
||||||
|
examStruct.ChildrenAssignmentQuestion?.ToList().ForEach(s =>
|
||||||
|
{
|
||||||
|
ConvertExamSturctToSubmissionDetails(s, studentId, submissions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,61 +0,0 @@
|
|||||||
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("assignment_group")]
|
|
||||||
public class AssignmentGroup
|
|
||||||
{
|
|
||||||
[Key]
|
|
||||||
[Column("id")]
|
|
||||||
public Guid Id { get; set; }
|
|
||||||
|
|
||||||
[Column("assignment")]
|
|
||||||
[ForeignKey("Assignment")]
|
|
||||||
public Guid? AssignmentId { get; set; }
|
|
||||||
|
|
||||||
[Required]
|
|
||||||
[Column("title")]
|
|
||||||
[MaxLength(65535)]
|
|
||||||
public string Title { get; set; }
|
|
||||||
|
|
||||||
[Column("descript")]
|
|
||||||
[MaxLength(65535)]
|
|
||||||
public string Descript { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
[Column("total_points")]
|
|
||||||
public float? TotalPoints { get; set; }
|
|
||||||
|
|
||||||
[Column("number")]
|
|
||||||
public byte Number { get; set; }
|
|
||||||
|
|
||||||
[Column("parent_group")]
|
|
||||||
public Guid? ParentGroup { get; set; }
|
|
||||||
|
|
||||||
[Column("deleted")]
|
|
||||||
public bool IsDeleted { get; set; }
|
|
||||||
|
|
||||||
[Column("valid_question_group")]
|
|
||||||
public bool ValidQuestionGroup { get; set; }
|
|
||||||
|
|
||||||
// Navigation Properties
|
|
||||||
public Assignment? Assignment { get; set; }
|
|
||||||
public AssignmentGroup? ParentAssignmentGroup { get; set;}
|
|
||||||
public ICollection<AssignmentGroup> ChildAssignmentGroups { get; set; }
|
|
||||||
public ICollection<AssignmentQuestion> AssignmentQuestions { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
public AssignmentGroup()
|
|
||||||
{
|
|
||||||
Id = Guid.NewGuid();
|
|
||||||
ChildAssignmentGroups = new HashSet<AssignmentGroup>();
|
|
||||||
AssignmentQuestions = new HashSet<AssignmentQuestion>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -6,6 +6,7 @@ using System.ComponentModel.DataAnnotations;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Entities.DTO;
|
||||||
|
|
||||||
namespace Entities.Contracts
|
namespace Entities.Contracts
|
||||||
{
|
{
|
||||||
@@ -17,22 +18,29 @@ namespace Entities.Contracts
|
|||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
[Column("question_id")]
|
[Column("question_id")]
|
||||||
public Guid? QuestionId { get; set; } // 设为可空
|
public Guid? QuestionId { get; set; }
|
||||||
|
|
||||||
// 当 IsGroup 为 true 时,此为 QuestionGroup 的外键
|
[Column("title")]
|
||||||
[Column("question_group_id")] // 新增一个外键列
|
[MaxLength(1024)]
|
||||||
public Guid? QuestionGroupId { get; set; } // 设为可空
|
public string? Title { get; set; }
|
||||||
|
|
||||||
[Required]
|
|
||||||
[Column("group_id")]
|
|
||||||
[ForeignKey("AssignmentGroup")]
|
|
||||||
public Guid AssignmentGroupId { get; set; }
|
|
||||||
|
|
||||||
|
[Column("description")]
|
||||||
|
public Guid? QuestionContextId { get; set; }
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
[Column("question_number")]
|
[Column("question_number")]
|
||||||
public byte QuestionNumber { get; set; }
|
public byte Index { get; set; }
|
||||||
|
|
||||||
|
[Column("sequence")]
|
||||||
|
public string Sequence { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Column("parent_question_group_id")]
|
||||||
|
public Guid? ParentAssignmentQuestionId { get; set; }
|
||||||
|
|
||||||
|
[Column("group_state")]
|
||||||
|
public AssignmentStructType StructType { get; set; } = AssignmentStructType.Question;
|
||||||
|
|
||||||
|
public QuestionType Type { get; set; } = QuestionType.Unknown;
|
||||||
|
|
||||||
[Column("created_at")]
|
[Column("created_at")]
|
||||||
public DateTime CreatedAt { get; set; }
|
public DateTime CreatedAt { get; set; }
|
||||||
@@ -40,18 +48,24 @@ namespace Entities.Contracts
|
|||||||
[Column("score")]
|
[Column("score")]
|
||||||
public float? Score { get; set; }
|
public float? Score { get; set; }
|
||||||
|
|
||||||
[Required]
|
public bool BCorrect { get; set; }
|
||||||
[Column("bgroup")]
|
|
||||||
public bool IsGroup { get; set; }
|
|
||||||
|
|
||||||
[Column("deleted")]
|
[Column("deleted")]
|
||||||
public bool IsDeleted { get; set; }
|
public bool IsDeleted { get; set; }
|
||||||
|
|
||||||
|
|
||||||
public Question Question { get; set; }
|
public Question? Question { get; set; }
|
||||||
public QuestionGroup QuestionGroup { get; set; }
|
public Assignment? Assignment { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey(nameof(QuestionContextId))]
|
||||||
|
public QuestionContext? QuestionContext { get; set; }
|
||||||
|
|
||||||
public ICollection<SubmissionDetail> SubmissionDetails { get; set; }
|
public ICollection<SubmissionDetail> SubmissionDetails { get; set; }
|
||||||
public AssignmentGroup AssignmentGroup { get; set; }
|
|
||||||
|
[ForeignKey(nameof(ParentAssignmentQuestionId))]
|
||||||
|
public AssignmentQuestion? ParentAssignmentQuestion { get; set; }
|
||||||
|
public ICollection<AssignmentQuestion> ChildrenAssignmentQuestion { get; set; } = new List<AssignmentQuestion>();
|
||||||
|
|
||||||
|
|
||||||
public AssignmentQuestion()
|
public AssignmentQuestion()
|
||||||
{
|
{
|
||||||
|
@@ -14,14 +14,16 @@ namespace Entities.Contracts
|
|||||||
[Key]
|
[Key]
|
||||||
[Column("class_id")]
|
[Column("class_id")]
|
||||||
public Guid ClassId { get; set; }
|
public Guid ClassId { get; set; }
|
||||||
|
[ForeignKey(nameof(ClassId))]
|
||||||
public Class Class { get; set; }
|
public Class Class { get; set; }
|
||||||
|
|
||||||
[Key]
|
[Key]
|
||||||
[Column("teacher_id")]
|
[Column("teacher_id")]
|
||||||
public Guid TeacherId { get; set; }
|
public Guid TeacherId { get; set; }
|
||||||
|
[ForeignKey(nameof(TeacherId))]
|
||||||
public User Teacher { get; set; }
|
public User Teacher { get; set; }
|
||||||
|
|
||||||
[Column("subject_taught")]
|
[Column("subject_taught")]
|
||||||
public string SubjectTaught { get; set; }
|
public SubjectAreaEnum SubjectTaught { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
Entities/Contracts/Global.cs
Normal file
24
Entities/Contracts/Global.cs
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@@ -16,36 +16,43 @@ namespace Entities.Contracts
|
|||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
[Column("question_text")]
|
[Column("title")]
|
||||||
[MaxLength(65535)]
|
[MaxLength(65535)]
|
||||||
public string QuestionText { get; set; }
|
public string Title { get; set; }
|
||||||
|
|
||||||
|
[Column("answer")]
|
||||||
|
[MaxLength(65535)]
|
||||||
|
public string? Answer { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
[Column("question_type")]
|
[Column("type")]
|
||||||
[MaxLength(20)]
|
[MaxLength(20)]
|
||||||
public QuestionType QuestionType { get; set; }
|
public QuestionType Type { get; set; } = QuestionType.Unknown;
|
||||||
|
|
||||||
[Column("correct_answer")]
|
|
||||||
[MaxLength(65535)]
|
|
||||||
public string CorrectAnswer { get; set; }
|
|
||||||
|
|
||||||
[Column("question_group_id")]
|
|
||||||
public Guid? QuestionGroupId { get; set; }
|
|
||||||
|
|
||||||
[Column("difficulty_level")]
|
[Column("difficulty_level")]
|
||||||
[MaxLength(10)]
|
[MaxLength(10)]
|
||||||
public DifficultyLevel DifficultyLevel { get; set; }
|
public DifficultyLevel DifficultyLevel { get; set; } = DifficultyLevel.easy;
|
||||||
|
|
||||||
[Column("subject_area")]
|
[Column("subject_area")]
|
||||||
public SubjectAreaEnum SubjectArea { get; set; }
|
public SubjectAreaEnum SubjectArea { get; set; } = SubjectAreaEnum.Unknown;
|
||||||
|
|
||||||
|
public string QType { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Column("options")]
|
[Column("options")]
|
||||||
public string? Options { get; set; }
|
public string? Options { get; set; }
|
||||||
|
|
||||||
|
[Column("key_point")]
|
||||||
|
public Guid? KeyPointId { get; set; }
|
||||||
|
|
||||||
|
[Column("lesson")]
|
||||||
|
public Guid? LessonId { get; set; }
|
||||||
|
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
[Column("created_by")]
|
[Column("created_by")]
|
||||||
[ForeignKey("Creator")]
|
public Guid CreatorId { get; set; }
|
||||||
public Guid CreatedBy { get; set; }
|
|
||||||
|
|
||||||
[Column("created_at")]
|
[Column("created_at")]
|
||||||
public DateTime CreatedAt { get; set; }
|
public DateTime CreatedAt { get; set; }
|
||||||
@@ -56,13 +63,17 @@ namespace Entities.Contracts
|
|||||||
[Column("deleted")]
|
[Column("deleted")]
|
||||||
public bool IsDeleted { get; set; }
|
public bool IsDeleted { get; set; }
|
||||||
|
|
||||||
[Column("valid_question")]
|
|
||||||
public bool ValidQuestion { get; set; }
|
|
||||||
|
|
||||||
// Navigation Properties
|
// Navigation Properties
|
||||||
|
[ForeignKey(nameof(CreatorId))]
|
||||||
public User Creator { get; set; }
|
public User Creator { get; set; }
|
||||||
public QuestionGroup QuestionGroup { get; set; }
|
|
||||||
public ICollection<AssignmentQuestion> AssignmentQuestions { get; set; }
|
[ForeignKey(nameof(KeyPointId))]
|
||||||
|
public KeyPoint? KeyPoint { get; set; }
|
||||||
|
[ForeignKey(nameof(LessonId))]
|
||||||
|
public Lesson? Lesson { get; set; }
|
||||||
|
|
||||||
|
public ICollection<AssignmentQuestion>? AssignmentQuestions { get; set; }
|
||||||
|
|
||||||
public Question()
|
public Question()
|
||||||
{
|
{
|
||||||
@@ -71,39 +82,5 @@ namespace Entities.Contracts
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum DifficultyLevel
|
|
||||||
{
|
|
||||||
easy,
|
|
||||||
medium,
|
|
||||||
hard
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum QuestionType
|
|
||||||
{
|
|
||||||
Unknown, // 可以有一个未知类型或作为默认
|
|
||||||
Spelling, // 拼写
|
|
||||||
Pronunciation, // 给带点字选择正确读音
|
|
||||||
WordFormation, // 组词
|
|
||||||
FillInTheBlanks, // 选词填空 / 补充词语
|
|
||||||
SentenceDictation, // 默写句子
|
|
||||||
SentenceRewriting, // 仿句 / 改写句子
|
|
||||||
ReadingComprehension, // 阅读理解
|
|
||||||
Composition // 作文
|
|
||||||
// ... 添加您其他题目类型
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum SubjectAreaEnum // 建议命名为 SubjectAreaEnum 以避免与属性名冲突
|
|
||||||
{
|
|
||||||
Unknown, // 未知或默认
|
|
||||||
Mathematics, // 数学
|
|
||||||
Physics, // 物理
|
|
||||||
Chemistry, // 化学
|
|
||||||
Biology, // 生物
|
|
||||||
History, // 历史
|
|
||||||
Geography, // 地理
|
|
||||||
Literature, // 语文/文学
|
|
||||||
English, // 英语
|
|
||||||
ComputerScience, // 计算机科学
|
|
||||||
// ... 你可以根据需要添加更多科目
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
25
Entities/Contracts/QuestionContext.cs
Normal file
25
Entities/Contracts/QuestionContext.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Entities.Contracts
|
||||||
|
{
|
||||||
|
public class QuestionContext
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
public string Description { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[InverseProperty(nameof(AssignmentQuestion.QuestionContext))]
|
||||||
|
public ICollection<AssignmentQuestion>? Questions { get; set; } = new List<AssignmentQuestion>();
|
||||||
|
|
||||||
|
|
||||||
|
public QuestionContext()
|
||||||
|
{
|
||||||
|
Questions = new HashSet<AssignmentQuestion>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,79 +0,0 @@
|
|||||||
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("question_groups")]
|
|
||||||
public class QuestionGroup
|
|
||||||
{
|
|
||||||
[Key]
|
|
||||||
[Column("id")]
|
|
||||||
public Guid Id { get; set; }
|
|
||||||
|
|
||||||
[Column("title")]
|
|
||||||
[MaxLength(255)]
|
|
||||||
public string Title { get; set; }
|
|
||||||
|
|
||||||
[Required]
|
|
||||||
[Column("description")]
|
|
||||||
[MaxLength(65535)]
|
|
||||||
public string Description { get; set; }
|
|
||||||
|
|
||||||
[Column("type")]
|
|
||||||
[MaxLength(50)]
|
|
||||||
public string Type { get; set; }
|
|
||||||
|
|
||||||
[Column("difficulty_level")]
|
|
||||||
[MaxLength(10)]
|
|
||||||
public DifficultyLevel DifficultyLevel { get; set; }
|
|
||||||
|
|
||||||
[Column("subject_area")]
|
|
||||||
public SubjectAreaEnum SubjectArea { get; set; }
|
|
||||||
|
|
||||||
[Column("total_questions")]
|
|
||||||
public int TotalQuestions { get; set; } = 0;
|
|
||||||
|
|
||||||
[Column("parent_question_group")]
|
|
||||||
public Guid? ParentQG { get; set; }
|
|
||||||
|
|
||||||
[Required]
|
|
||||||
[Column("created_by")]
|
|
||||||
[ForeignKey("Creator")]
|
|
||||||
public Guid CreatedBy { get; set; }
|
|
||||||
|
|
||||||
[Column("created_at")]
|
|
||||||
public DateTime CreatedAt { get; set; }
|
|
||||||
|
|
||||||
[Column("updated_at")]
|
|
||||||
public DateTime UpdatedAt { get; set; }
|
|
||||||
|
|
||||||
[Column("deleted")]
|
|
||||||
public bool IsDeleted { get; set; }
|
|
||||||
|
|
||||||
[Column("valid_group")]
|
|
||||||
public bool ValidGroup { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public User Creator { get; set; }
|
|
||||||
public QuestionGroup ParentQuestionGroup { get; set; }
|
|
||||||
public ICollection<QuestionGroup> ChildQuestionGroups { get; set; }
|
|
||||||
public ICollection<AssignmentQuestion> AssignmentQuestions { get; set; }
|
|
||||||
public ICollection<Question> Questions { get; set; }
|
|
||||||
|
|
||||||
public QuestionGroup()
|
|
||||||
{
|
|
||||||
Id = Guid.NewGuid();
|
|
||||||
Questions = new HashSet<Question>();
|
|
||||||
CreatedAt = DateTime.UtcNow;
|
|
||||||
UpdatedAt = DateTime.UtcNow;
|
|
||||||
IsDeleted = false;
|
|
||||||
ValidGroup = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -28,20 +28,20 @@ namespace Entities.Contracts
|
|||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
[Column("attempt_number")]
|
[Column("attempt_number")]
|
||||||
public Guid AttemptNumber { get; set; }
|
public byte AttemptNumber { get; set; }
|
||||||
|
|
||||||
[Column("submission_time")]
|
[Column("submission_time")]
|
||||||
public DateTime SubmissionTime { get; set; }
|
public DateTime SubmissionTime { get; set; }
|
||||||
|
|
||||||
[Column("overall_grade")]
|
[Column("overall_grade")]
|
||||||
public float? OverallGrade { get; set; }
|
public float OverallGrade { get; set; } = 0;
|
||||||
|
|
||||||
[Column("overall_feedback")]
|
[Column("overall_feedback")]
|
||||||
public string OverallFeedback { get; set; }
|
public string? OverallFeedback { get; set; }
|
||||||
|
|
||||||
[Column("graded_by")]
|
[Column("graded_by")]
|
||||||
[ForeignKey("Grader")]
|
[ForeignKey("Grader")]
|
||||||
public Guid? GradedBy { get; set; }
|
public Guid? GraderId { get; set; }
|
||||||
|
|
||||||
[Column("graded_at")]
|
[Column("graded_at")]
|
||||||
public DateTime? GradedAt { get; set; }
|
public DateTime? GradedAt { get; set; }
|
||||||
@@ -49,6 +49,12 @@ namespace Entities.Contracts
|
|||||||
[Column("deleted")]
|
[Column("deleted")]
|
||||||
public bool IsDeleted { get; set; }
|
public bool IsDeleted { get; set; }
|
||||||
|
|
||||||
|
public byte TotalQuesNum { get; set; }
|
||||||
|
|
||||||
|
public byte ErrorQuesNum { get; set; }
|
||||||
|
|
||||||
|
public byte TotalScore { get; set; }
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
[Column("status")]
|
[Column("status")]
|
||||||
public SubmissionStatus Status { get; set; }
|
public SubmissionStatus Status { get; set; }
|
||||||
@@ -66,14 +72,4 @@ namespace Entities.Contracts
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum SubmissionStatus
|
|
||||||
{
|
|
||||||
Pending, // 待提交/未开始
|
|
||||||
Submitted, // 已提交
|
|
||||||
Graded, // 已批改
|
|
||||||
Resubmission, // 待重新提交 (如果允许)
|
|
||||||
Late, // 迟交
|
|
||||||
Draft, // 草稿
|
|
||||||
// ... 添加你需要的其他状态
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -23,7 +23,6 @@ namespace Entities.Contracts
|
|||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
[Column("student_id")]
|
[Column("student_id")]
|
||||||
[ForeignKey("User")]
|
|
||||||
public Guid StudentId { get; set; }
|
public Guid StudentId { get; set; }
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
@@ -32,16 +31,16 @@ namespace Entities.Contracts
|
|||||||
public Guid AssignmentQuestionId { get; set; }
|
public Guid AssignmentQuestionId { get; set; }
|
||||||
|
|
||||||
[Column("student_answer")]
|
[Column("student_answer")]
|
||||||
public string StudentAnswer { get; set; }
|
public string? StudentAnswer { get; set; }
|
||||||
|
|
||||||
[Column("is_correct")]
|
[Column("is_correct")]
|
||||||
public bool? IsCorrect { get; set; }
|
public bool? IsCorrect { get; set; }
|
||||||
|
|
||||||
[Column("points_awarded")]
|
[Column("points_awarded")]
|
||||||
public float? PointsAwarded { get; set; }
|
public float? PointsAwarded { get; set; } // score
|
||||||
|
|
||||||
[Column("teacher_feedback")]
|
[Column("teacher_feedback")]
|
||||||
public string TeacherFeedback { get; set; }
|
public string? TeacherFeedback { get; set; }
|
||||||
|
|
||||||
[Column("created_at")]
|
[Column("created_at")]
|
||||||
public DateTime CreatedAt { get; set; }
|
public DateTime CreatedAt { get; set; }
|
||||||
@@ -52,8 +51,19 @@ namespace Entities.Contracts
|
|||||||
[Column("deleted")]
|
[Column("deleted")]
|
||||||
public bool IsDeleted { get; set; }
|
public bool IsDeleted { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[Column("status")]
|
||||||
|
public SubmissionStatus Status { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[ForeignKey(nameof(StudentId))]
|
||||||
|
public User Student { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey(nameof(SubmissionId))]
|
||||||
public Submission Submission { get; set; }
|
public Submission Submission { get; set; }
|
||||||
public User User { get; set; }
|
|
||||||
|
[ForeignKey(nameof(AssignmentQuestionId))]
|
||||||
public AssignmentQuestion AssignmentQuestion { get; set; }
|
public AssignmentQuestion AssignmentQuestion { get; set; }
|
||||||
|
|
||||||
public SubmissionDetail()
|
public SubmissionDetail()
|
||||||
|
36
Entities/Contracts/Textbook/KeyPoint.cs
Normal file
36
Entities/Contracts/Textbook/KeyPoint.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Entities.Contracts
|
||||||
|
{
|
||||||
|
[Table("key_point")]
|
||||||
|
public class KeyPoint
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
[StringLength(255)]
|
||||||
|
public string Key { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public Guid LessonID { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey(nameof(LessonID))]
|
||||||
|
public Lesson Lesson { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public ICollection<Question> Questions { get; set; }
|
||||||
|
|
||||||
|
public KeyPoint()
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid();
|
||||||
|
Questions = new HashSet<Question>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
Entities/Contracts/Textbook/Lesson.cs
Normal file
48
Entities/Contracts/Textbook/Lesson.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Entities.Contracts
|
||||||
|
{
|
||||||
|
[Table("lesson")]
|
||||||
|
public class Lesson
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
[StringLength(255)]
|
||||||
|
public string Title { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Description { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public Guid TextbookID { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
[ForeignKey(nameof(TextbookID))]
|
||||||
|
public Textbook Textbook { get; set; }
|
||||||
|
|
||||||
|
[InverseProperty(nameof(KeyPoint.Lesson))]
|
||||||
|
public ICollection<KeyPoint>? KeyPoints { get; set; }
|
||||||
|
|
||||||
|
[InverseProperty(nameof(Question.Lesson))]
|
||||||
|
public ICollection<Question>? Questions { get; set; }
|
||||||
|
|
||||||
|
[InverseProperty(nameof(LessonQuestion.Lesson))]
|
||||||
|
public ICollection<LessonQuestion>? LessonQuestions { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public Lesson()
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid();
|
||||||
|
KeyPoints = new HashSet<KeyPoint>();
|
||||||
|
Questions = new HashSet<Question>();
|
||||||
|
LessonQuestions = new HashSet<LessonQuestion>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
Entities/Contracts/Textbook/LessonQuestion.cs
Normal file
32
Entities/Contracts/Textbook/LessonQuestion.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Entities.Contracts
|
||||||
|
{
|
||||||
|
[Table("lesson_question")]
|
||||||
|
public class LessonQuestion
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
[MaxLength(65535)]
|
||||||
|
public string Question { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public Guid LessonID { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey(nameof(LessonID))]
|
||||||
|
public Lesson Lesson { get; set; }
|
||||||
|
|
||||||
|
public LessonQuestion()
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
36
Entities/Contracts/Textbook/Textbook.cs
Normal file
36
Entities/Contracts/Textbook/Textbook.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Entities.Contracts
|
||||||
|
{
|
||||||
|
[Table("textbook")]
|
||||||
|
public class Textbook
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
public GradeEnum Grade { get; set; } = GradeEnum.Unknown;
|
||||||
|
|
||||||
|
public string Title { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public Publisher Publisher { get; set; } = Publisher.部编版;
|
||||||
|
|
||||||
|
public SubjectAreaEnum SubjectArea { get; set; } = SubjectAreaEnum.Unknown;
|
||||||
|
|
||||||
|
[InverseProperty(nameof(Lesson.Textbook))]
|
||||||
|
public ICollection<Lesson> Lessons { get; set; }
|
||||||
|
|
||||||
|
public Textbook()
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid();
|
||||||
|
Lessons = new HashSet<Lesson>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -15,13 +15,15 @@ namespace Entities.Contracts
|
|||||||
public string? RefreshToken { get; set; }
|
public string? RefreshToken { get; set; }
|
||||||
public DateTime? RefreshTokenExpiryTime { get; set; }
|
public DateTime? RefreshTokenExpiryTime { get; set; }
|
||||||
public string? Address { get; set; }
|
public string? Address { get; set; }
|
||||||
public string? DisplayName { get; set; }
|
public string? DisplayName { get; set; }
|
||||||
|
public SubjectAreaEnum SubjectArea { get; set; } = SubjectAreaEnum.Unknown;
|
||||||
|
|
||||||
[Column("deleted")]
|
[Column("deleted")]
|
||||||
public bool IsDeleted { get; set; }
|
public bool IsDeleted { get; set; }
|
||||||
|
|
||||||
|
[InverseProperty(nameof(ClassTeacher.Teacher))]
|
||||||
public ICollection<ClassTeacher> TaughtClassesLink { get; set; }
|
public ICollection<ClassTeacher> TaughtClassesLink { get; set; }
|
||||||
|
[InverseProperty(nameof(ClassStudent.Student))]
|
||||||
public ICollection<ClassStudent> EnrolledClassesLink { get; set; }
|
public ICollection<ClassStudent> EnrolledClassesLink { get; set; }
|
||||||
|
|
||||||
public ICollection<Question> CreatedQuestions { get; set; }
|
public ICollection<Question> CreatedQuestions { get; set; }
|
||||||
|
15
Entities/DTO/AssigExamToStudentsDto.cs
Normal file
15
Entities/DTO/AssigExamToStudentsDto.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Entities.DTO
|
||||||
|
{
|
||||||
|
public class AssigExamToStudentsDto
|
||||||
|
{
|
||||||
|
public Guid CreaterId { get; set; }
|
||||||
|
public Guid AssignmentId { get; set; }
|
||||||
|
public List<Guid> StudentIds { get; set; } = new List<Guid>();
|
||||||
|
}
|
||||||
|
}
|
23
Entities/DTO/AssignmentClassDto.cs
Normal file
23
Entities/DTO/AssignmentClassDto.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using Entities.Contracts;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
|
||||||
|
namespace Entities.DTO
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
public class AssignmentClassDto
|
||||||
|
{
|
||||||
|
public AssignmentDto Assignment { get; set; }
|
||||||
|
public Class ClassId { get; set; }
|
||||||
|
public DateTime AssignedAt { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
29
Entities/DTO/AssignmentDto.cs
Normal file
29
Entities/DTO/AssignmentDto.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using Entities.Contracts;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Entities.DTO
|
||||||
|
{
|
||||||
|
public class AssignmentDto
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.Empty;
|
||||||
|
public string Title { get; set; } = string.Empty;
|
||||||
|
public string Description { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public byte TotalQuestions { get; set; }
|
||||||
|
public float Score { get; set; } = 0;
|
||||||
|
public SubjectAreaEnum SubjectArea { get; set; } = SubjectAreaEnum.Unknown;
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
public DateTime UpdatedAt { get; set; }
|
||||||
|
public DateTime DueDate { get; set; }
|
||||||
|
public Guid CreatorId { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public ExamType ExamType { get; set; } = ExamType.DailyTest;
|
||||||
|
|
||||||
|
public AssignmentQuestionDto ExamStruct { get; set; } = new AssignmentQuestionDto();
|
||||||
|
}
|
||||||
|
}
|
33
Entities/DTO/AssignmentQuestionDto.cs
Normal file
33
Entities/DTO/AssignmentQuestionDto.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using Entities.Contracts;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Entities.DTO
|
||||||
|
{
|
||||||
|
public class AssignmentQuestionDto
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.Empty;
|
||||||
|
public string Title { get; set; } = string.Empty;
|
||||||
|
public QuestionContextDto? Description { get; set; }
|
||||||
|
|
||||||
|
public byte Index { get; set; } = 0;
|
||||||
|
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;
|
||||||
|
|
||||||
|
public AssignmentQuestionDto? ParentAssignmentQuestion { get; set; }
|
||||||
|
public List<AssignmentQuestionDto> ChildrenAssignmentQuestion { get; set; } = new List<AssignmentQuestionDto>();
|
||||||
|
|
||||||
|
public QuestionDto? Question { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@@ -1,81 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Xml.Serialization;
|
|
||||||
|
|
||||||
namespace Entities.DTO
|
|
||||||
{
|
|
||||||
public class ExamDto
|
|
||||||
{
|
|
||||||
public Guid? AssignmentId { get; set; }
|
|
||||||
public string CreaterEmail { get; set; }
|
|
||||||
public string AssignmentTitle { get; set; } = string.Empty;
|
|
||||||
public string Description { get; set; }
|
|
||||||
public string SubjectArea { get; set; }
|
|
||||||
public QuestionGroupDto QuestionGroups { get; set; } = new QuestionGroupDto();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class QuestionGroupDto
|
|
||||||
{
|
|
||||||
public byte Index { get; set; }
|
|
||||||
|
|
||||||
public string? Title { get; set; }
|
|
||||||
|
|
||||||
public float Score { get; set; }
|
|
||||||
|
|
||||||
public string? Descript { get; set; }
|
|
||||||
public List<SubQuestionDto> SubQuestions { get; set; } = new List<SubQuestionDto>();
|
|
||||||
public List<QuestionGroupDto> SubQuestionGroups { get; set; } = new List<QuestionGroupDto>();
|
|
||||||
|
|
||||||
// 标记是否是一个具有上下文的单独问题
|
|
||||||
public bool ValidQuestionGroup { get; set; } = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SubQuestionDto
|
|
||||||
{
|
|
||||||
|
|
||||||
public byte Index { get; set; }
|
|
||||||
|
|
||||||
public string? Stem { get; set; }
|
|
||||||
|
|
||||||
public float Score { get; set; }
|
|
||||||
|
|
||||||
public List<OptionDto> Options { get; set; } = new List<OptionDto>();
|
|
||||||
|
|
||||||
public string? SampleAnswer { get; set; }
|
|
||||||
|
|
||||||
public string? QuestionType { get; set; }
|
|
||||||
public string? DifficultyLevel { get; set; }
|
|
||||||
|
|
||||||
// 标记是否是一个独立的问题
|
|
||||||
public bool ValidQuestion { get; set; } = false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public class OptionDto
|
|
||||||
{
|
|
||||||
public string? Value { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static class ExamDtoExtension
|
|
||||||
{
|
|
||||||
public static void Convert(this ExamDto examDto)
|
|
||||||
{
|
|
||||||
var qg = examDto.QuestionGroups;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Convert(this QuestionGroupDto examDto)
|
|
||||||
{
|
|
||||||
if(examDto.ValidQuestionGroup)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
23
Entities/DTO/GlobalDto.cs
Normal file
23
Entities/DTO/GlobalDto.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
15
Entities/DTO/QuestionContextDto.cs
Normal file
15
Entities/DTO/QuestionContextDto.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Entities.DTO
|
||||||
|
{
|
||||||
|
|
||||||
|
public class QuestionContextDto
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.Empty;
|
||||||
|
public string Description { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
49
Entities/DTO/QuestionDto.cs
Normal file
49
Entities/DTO/QuestionDto.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using Entities.Contracts;
|
||||||
|
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.DTO
|
||||||
|
{
|
||||||
|
public class QuestionDto
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.Empty;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
public string? Options { get; set; }
|
||||||
|
|
||||||
|
public DifficultyLevel DifficultyLevel { get; set; } = DifficultyLevel.easy;
|
||||||
|
|
||||||
|
public SubjectAreaEnum SubjectArea { get; set; } = SubjectAreaEnum.Unknown;
|
||||||
|
|
||||||
|
public Guid CreatorId { get; set; }
|
||||||
|
|
||||||
|
public Guid? KeyPointId { get; set; }
|
||||||
|
|
||||||
|
public Guid? LessonId { get; set; }
|
||||||
|
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
|
||||||
|
public DateTime UpdatedAt { get; set; } = DateTime.Now;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Can be removed because the class isn't used
|
||||||
|
/// </summary>
|
||||||
|
public class OptionDto
|
||||||
|
{
|
||||||
|
public string? Value { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
21
Entities/DTO/StudentDto.cs
Normal file
21
Entities/DTO/StudentDto.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using Entities.Contracts;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Entities.DTO
|
||||||
|
{
|
||||||
|
public class StudentDto
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public string? DisplayName { get; set; }
|
||||||
|
|
||||||
|
public UInt32 ErrorQuestionNum { get; set; }
|
||||||
|
public Dictionary<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; }
|
||||||
|
}
|
||||||
|
}
|
41
Entities/DTO/StudentSubmissionDetailDto.cs
Normal file
41
Entities/DTO/StudentSubmissionDetailDto.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
22
Entities/DTO/StudentSubmissionSummaryDto.cs
Normal file
22
Entities/DTO/StudentSubmissionSummaryDto.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
14
Entities/DTO/SubjectTypeMetadataDto.cs
Normal file
14
Entities/DTO/SubjectTypeMetadataDto.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
21
Entities/DTO/SubmissionDetailDto.cs
Normal file
21
Entities/DTO/SubmissionDetailDto.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using Entities.Contracts;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Entities.DTO
|
||||||
|
{
|
||||||
|
public class SubmissionDetailDto
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.Empty;
|
||||||
|
public Guid StudentId { get; set; }
|
||||||
|
public Guid AssignmentQuestionId { get; set; }
|
||||||
|
public string? StudentAnswer { get; set; }
|
||||||
|
public bool? IsCorrect { get; set; }
|
||||||
|
public float? PointsAwarded { get; set; }
|
||||||
|
public string? TeacherFeedback { get; set; }
|
||||||
|
public SubmissionStatus Status { get; set; } = SubmissionStatus.Graded;
|
||||||
|
}
|
||||||
|
}
|
23
Entities/DTO/SubmissionDto.cs
Normal file
23
Entities/DTO/SubmissionDto.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using Entities.Contracts;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Entities.DTO
|
||||||
|
{
|
||||||
|
public class SubmissionDto
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.Empty;
|
||||||
|
public Guid AssignmentId { get; set; }
|
||||||
|
public Guid StudentId { get; set; }
|
||||||
|
public DateTime SubmissionTime { get; set; }
|
||||||
|
public float OverallGrade { get; set; } = 0;
|
||||||
|
public string OverallFeedback { get; set; } = string.Empty;
|
||||||
|
public Guid? GraderId { get; set; }
|
||||||
|
public DateTime? GradedAt { get; set; }
|
||||||
|
public SubmissionStatus Status { get; set; }
|
||||||
|
public List<SubmissionDetailDto> SubmissionDetails { get; set; } = new List<SubmissionDetailDto>();
|
||||||
|
}
|
||||||
|
}
|
14
Entities/DTO/UserClassRoleDto.cs
Normal file
14
Entities/DTO/UserClassRoleDto.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Entities.DTO
|
||||||
|
{
|
||||||
|
public class UserClassRoleDto
|
||||||
|
{
|
||||||
|
public List<(byte, byte)> ClassInfo { get; set; } = new List<(byte, byte)> ();
|
||||||
|
public string Role { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
15
Entities/DTO/UserDto.cs
Normal file
15
Entities/DTO/UserDto.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Entities.DTO
|
||||||
|
{
|
||||||
|
public class UserDto
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public string? DisplayName { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
8
Entities/Dockerfile
Normal file
8
Entities/Dockerfile
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# ./Entities/Dockerfile
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS entities
|
||||||
|
WORKDIR /src
|
||||||
|
COPY ../Entities/*.csproj ./Entities/
|
||||||
|
RUN dotnet restore "Entities/Entities.csproj"
|
||||||
|
|
||||||
|
COPY ../Entities/. ./Entities/
|
||||||
|
RUN dotnet publish "Entities/Entities.csproj" -c Release -o /publish
|
@@ -1,23 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Components.Authorization;
|
|
||||||
using System.Security.Claims;
|
|
||||||
|
|
||||||
namespace TechHelper.Client.AuthProviders
|
|
||||||
{
|
|
||||||
public class TestAuthStateProvider : AuthenticationStateProvider
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
|
|
||||||
{
|
|
||||||
var claims = new List<Claim>
|
|
||||||
{
|
|
||||||
new Claim(ClaimTypes.Name, "John Doe"),
|
|
||||||
new Claim(ClaimTypes.Role, "Administrator")
|
|
||||||
};
|
|
||||||
|
|
||||||
var anonymous = new ClaimsIdentity();
|
|
||||||
|
|
||||||
return await Task.FromResult(new AuthenticationState(new ClaimsPrincipal(anonymous)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
24
TechHelper.Client/Content/AutoMapperProFile.cs
Normal file
24
TechHelper.Client/Content/AutoMapperProFile.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using AutoMapper.Internal.Mappers;
|
||||||
|
using Entities.Contracts;
|
||||||
|
using Entities.DTO;
|
||||||
|
using TechHelper.Client.Exam;
|
||||||
|
|
||||||
|
namespace TechHelper.Context
|
||||||
|
{
|
||||||
|
public class AutoMapperProFile : Profile
|
||||||
|
{
|
||||||
|
public AutoMapperProFile()
|
||||||
|
{
|
||||||
|
CreateMap<QuestionEx, QuestionDto>()
|
||||||
|
.ForMember(d => d.Options, o => o.MapFrom(s => string.Join(Environment.NewLine, s.Options.Select(op => op.Text))));
|
||||||
|
CreateMap<AssignmentQuestionEx, AssignmentQuestionDto>()
|
||||||
|
.ForMember(d=>d.Description, o=>o.Ignore());
|
||||||
|
CreateMap<AssignmentEx, AssignmentDto>();
|
||||||
|
|
||||||
|
|
||||||
|
CreateMap<AssignmentCheckData, Submission>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
26
TechHelper.Client/Dockerfile
Normal file
26
TechHelper.Client/Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# 构建阶段
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS client
|
||||||
|
|
||||||
|
|
||||||
|
#COPY --from=entitieslib:latest /publish /publish/entities
|
||||||
|
#COPY --from=emaillib:latest /publish /publish/emaillib
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 复制依赖文件 & 恢复
|
||||||
|
#COPY ./TechHelper.Client.sln ./
|
||||||
|
COPY ../TechHelper.Client/*.csproj ../TechHelper.Client/
|
||||||
|
|
||||||
|
RUN dotnet nuget locals all --clear
|
||||||
|
RUN dotnet restore "/app/TechHelper.Client/TechHelper.Client.csproj"
|
||||||
|
|
||||||
|
# 复制代码 & 发布
|
||||||
|
COPY . ./
|
||||||
|
WORKDIR /app/TechHelper.Client
|
||||||
|
RUN dotnet publish "/app/TechHelper.Client/TechHelper.Client.csproj" -c Release -o /publish
|
||||||
|
|
||||||
|
FROM nginx:alpine AS final
|
||||||
|
RUN rm -rf /usr/share/nginx/html
|
||||||
|
COPY --from=client /publish/wwwroot /usr/share/nginx/html
|
||||||
|
|
||||||
|
EXPOSE 80
|
94
TechHelper.Client/Exam/AssignmentCheckData.cs
Normal file
94
TechHelper.Client/Exam/AssignmentCheckData.cs
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
using Entities.DTO;
|
||||||
|
|
||||||
|
namespace TechHelper.Client.Exam
|
||||||
|
{
|
||||||
|
public class AssignmentCheckData
|
||||||
|
{
|
||||||
|
public string Title { get; set; }
|
||||||
|
public Guid AssignmentId { get; set; }
|
||||||
|
public Guid StudentId { get; set; }
|
||||||
|
public List<AssignmentCheckQuestion> Questions { get; set; } = new List<AssignmentCheckQuestion>();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class AssignmentCheckQuestion
|
||||||
|
{
|
||||||
|
public string Sequence { get; set; } = string.Empty;
|
||||||
|
public AssignmentQuestionDto AssignmentQuestionDto { get; set; } = new AssignmentQuestionDto();
|
||||||
|
public float Score { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Student
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class QuestionAnswerStatus
|
||||||
|
{
|
||||||
|
public string QuestionSequence { get; set; } = string.Empty; // 题目序号,例如 "1.1"
|
||||||
|
public string QuestionText { get; set; } = string.Empty; // 题目文本
|
||||||
|
public float QuestionScore { get; set; } // 题目分值
|
||||||
|
public Dictionary<Guid, bool> StudentCorrectStatus { get; set; } = new Dictionary<Guid, bool>();
|
||||||
|
// Key: Student.Id, Value: true 表示正确,false 表示错误
|
||||||
|
}
|
||||||
|
|
||||||
|
public class QuestionRowData
|
||||||
|
{
|
||||||
|
public AssignmentCheckQuestion QuestionItem { get; set; } // 原始题目信息
|
||||||
|
public Dictionary<Guid, bool> StudentAnswers { get; set; } = new Dictionary<Guid, bool>();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class ExamStructExtensions
|
||||||
|
{
|
||||||
|
public static AssignmentCheckData GetStruct(this AssignmentDto dto)
|
||||||
|
{
|
||||||
|
if (dto == null)
|
||||||
|
{
|
||||||
|
return new AssignmentCheckData { Title = "无效试卷", Questions = new List<AssignmentCheckQuestion>() };
|
||||||
|
}
|
||||||
|
|
||||||
|
var examStruct = new AssignmentCheckData
|
||||||
|
{
|
||||||
|
Title = dto.Title
|
||||||
|
};
|
||||||
|
|
||||||
|
GetSeqRecursive(dto.ExamStruct, null, examStruct.Questions);
|
||||||
|
|
||||||
|
return examStruct;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 递归方法,用于生成所有题目和子题目的完整序号。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="currentGroup">当前正在处理的题目组。</param>
|
||||||
|
/// <param name="parentSequence">当前题目组的父级序号(例如:"1", "2.1")。如果为空,则表示顶级题目组。</param>
|
||||||
|
/// <param name="allQuestions">用于收集所有生成题目项的列表。</param>
|
||||||
|
private static void GetSeqRecursive(
|
||||||
|
AssignmentQuestionDto currentGroup,
|
||||||
|
string? parentSequence,
|
||||||
|
List<AssignmentCheckQuestion> allQuestions)
|
||||||
|
{
|
||||||
|
string currentGroupSequence = parentSequence != null
|
||||||
|
? $"{parentSequence}.{currentGroup.Index}"
|
||||||
|
: currentGroup.Index.ToString();
|
||||||
|
|
||||||
|
foreach (var subGroup in currentGroup.ChildrenAssignmentQuestion)
|
||||||
|
{
|
||||||
|
GetSeqRecursive(subGroup, currentGroupSequence, allQuestions);
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(currentGroup.Sequence))
|
||||||
|
{
|
||||||
|
|
||||||
|
allQuestions.Add(new AssignmentCheckQuestion
|
||||||
|
{
|
||||||
|
AssignmentQuestionDto = currentGroup,
|
||||||
|
//Sequence = currentGroupSequence,
|
||||||
|
Sequence = currentGroup.Sequence,
|
||||||
|
Score = currentGroup.Score,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -5,245 +5,4 @@ using System.IO; // 用于 XML 反序列化
|
|||||||
|
|
||||||
namespace TechHelper.Client.Exam
|
namespace TechHelper.Client.Exam
|
||||||
{
|
{
|
||||||
|
|
||||||
//[XmlRoot("EP")]
|
|
||||||
//public class StringsList
|
|
||||||
//{
|
|
||||||
|
|
||||||
// [XmlElement("Q")]
|
|
||||||
// public List<string> Items { get; set; }
|
|
||||||
//}
|
|
||||||
|
|
||||||
//// XML 根元素 <EP>
|
|
||||||
//[XmlRoot("EP")]
|
|
||||||
//public class ExamPaper
|
|
||||||
//{
|
|
||||||
// // XML 特性:<QGs> 包含 <QG> 列表
|
|
||||||
// [XmlArray("QGs")]
|
|
||||||
// [XmlArrayItem("QG")]
|
|
||||||
// [JsonProperty("QuestionGroups")]
|
|
||||||
// public List<QuestionGroup> QuestionGroups { get; set; } = new List<QuestionGroup>();
|
|
||||||
//}
|
|
||||||
|
|
||||||
//[XmlRoot("QG")]
|
|
||||||
//public class QuestionGroup
|
|
||||||
//{
|
|
||||||
// // JSON 特性
|
|
||||||
// [JsonProperty("题号")]
|
|
||||||
// // XML 特性:作为 <QG Id="X"> 属性
|
|
||||||
// [XmlAttribute("Id")]
|
|
||||||
// public byte Id { get; set; }
|
|
||||||
|
|
||||||
// [JsonProperty("标题")]
|
|
||||||
// [XmlElement("T")] // T for Title
|
|
||||||
// public string Title { get; set; }
|
|
||||||
|
|
||||||
// [JsonProperty("分值")]
|
|
||||||
// [XmlAttribute("S")] // S for Score
|
|
||||||
// public int Score { get; set; }
|
|
||||||
|
|
||||||
// [JsonProperty("题目引用")]
|
|
||||||
// [XmlElement("QR")] // QR for QuestionReference,作为 <QR> 元素
|
|
||||||
// public string QuestionReference { get; set; } = ""; // 初始化为空字符串
|
|
||||||
|
|
||||||
// [JsonProperty("子题目")]
|
|
||||||
// [XmlArray("SQs")] // SQs 包含 <SQ> 列表
|
|
||||||
// [XmlArrayItem("SQ")]
|
|
||||||
// public List<SubQuestion> SubQuestions { get; set; } = new List<SubQuestion>();
|
|
||||||
|
|
||||||
// [JsonProperty("子题组")]
|
|
||||||
// [XmlArray("SQGs")] // SQGs 包含 <QG> 列表 (嵌套题组)
|
|
||||||
// [XmlArrayItem("QG")]
|
|
||||||
// public List<QuestionGroup> SubQuestionGroups { get; set; } = new List<QuestionGroup>();
|
|
||||||
//}
|
|
||||||
|
|
||||||
//// 子题目类
|
|
||||||
//public class SubQuestion
|
|
||||||
//{
|
|
||||||
|
|
||||||
// [JsonProperty("子题号")]
|
|
||||||
// [XmlAttribute("Id")] // Id for SubId
|
|
||||||
// public byte SubId { get; set; }
|
|
||||||
|
|
||||||
// [JsonProperty("题干")]
|
|
||||||
// [XmlElement("T")] // T for Text (Stem)
|
|
||||||
// public string Stem { get; set; }
|
|
||||||
|
|
||||||
// [JsonProperty("分值")]
|
|
||||||
// [XmlAttribute("S")] // S for Score
|
|
||||||
// public int Score { get; set; } // 分值通常为整数
|
|
||||||
|
|
||||||
// [JsonProperty("选项")]
|
|
||||||
// [XmlArray("Os")] // Os 包含 <O> 列表
|
|
||||||
// [XmlArrayItem("O")]
|
|
||||||
// public List<Option> Options { get; set; } = new List<Option>();
|
|
||||||
|
|
||||||
// [JsonProperty("示例答案")]
|
|
||||||
// [XmlElement("SA")] // SA for SampleAnswer
|
|
||||||
// public string SampleAnswer { get; set; } = "";
|
|
||||||
//}
|
|
||||||
|
|
||||||
//// 选项类,用于适配 <O V="X"/> 结构
|
|
||||||
//public class Option
|
|
||||||
//{
|
|
||||||
// // XML 特性:作为 <O V="X"> 属性
|
|
||||||
// [XmlAttribute("V")] // V for Value
|
|
||||||
// // JSON 特性:如果 JSON 中的选项是 {"Value": "A"} 这样的对象,则需要 JsonProperty("Value")
|
|
||||||
// // 但如果 JSON 选项只是 ["A", "B"] 这样的字符串数组,则此Option类不适合JSON Options
|
|
||||||
// // 需要明确你的JSON Options的结构。我假设你JSON Options是 List<string>
|
|
||||||
// // 如果是 List<string>,则Options属性在SubQuestion中直接是List<string>,Option类则不需要
|
|
||||||
// // 但根据你的精简XML需求,Option类是必要的。
|
|
||||||
// // 所以这里需要你自己根据实际JSON Options结构选择。
|
|
||||||
// // 为了兼容XML,我会保留Option类,但如果JSON是List<string>,Options属性会很复杂
|
|
||||||
// public string Value { get; set; }
|
|
||||||
//}
|
|
||||||
|
|
||||||
//// 独立的服务类来处理序列化和反序列化
|
|
||||||
//public static class ExamParser
|
|
||||||
//{
|
|
||||||
// // JSON 反序列化方法
|
|
||||||
// public static List<T> ParseExamJson<T>(string jsonContent)
|
|
||||||
// {
|
|
||||||
// string cleanedJson = jsonContent.Trim();
|
|
||||||
|
|
||||||
// // 移除可能存在的 Markdown 代码块标记
|
|
||||||
// if (cleanedJson.StartsWith("```json") && cleanedJson.EndsWith("```"))
|
|
||||||
// {
|
|
||||||
// cleanedJson = cleanedJson.Substring("```json".Length, cleanedJson.Length - "```json".Length - "```".Length).Trim();
|
|
||||||
// }
|
|
||||||
// // 移除可能存在的单引号包围(如果 AI 偶尔会这样输出)
|
|
||||||
// if (cleanedJson.StartsWith("'") && cleanedJson.EndsWith("'"))
|
|
||||||
// {
|
|
||||||
// cleanedJson = cleanedJson.Substring(1, cleanedJson.Length - 2).Trim();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// // 注意:这里假设你的 JSON 根直接是一个 QuestionGroup 列表
|
|
||||||
// // 如果你的 JSON 根是 { "QuestionGroups": [...] },则需要先反序列化到 ExamPaper
|
|
||||||
// List<T> examQuestions = JsonConvert.DeserializeObject<List<T>>(cleanedJson);
|
|
||||||
// return examQuestions;
|
|
||||||
// }
|
|
||||||
// catch (JsonSerializationException ex)
|
|
||||||
// {
|
|
||||||
// Console.WriteLine($"JSON 反序列化错误: {ex.Message}");
|
|
||||||
// Console.WriteLine($"内部异常: {ex.InnerException?.Message}");
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
// catch (Exception ex)
|
|
||||||
// {
|
|
||||||
// Console.WriteLine($"处理错误: {ex.Message}");
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// #region TEST
|
|
||||||
// [XmlRoot("User")]
|
|
||||||
// public class User
|
|
||||||
// {
|
|
||||||
// [XmlAttribute("id")]
|
|
||||||
// public string Id { get; set; }
|
|
||||||
|
|
||||||
// [XmlElement("PersonalInfo")]
|
|
||||||
// public PersonalInfo PersonalInfo { get; set; }
|
|
||||||
|
|
||||||
// [XmlArray("Roles")] // 包装元素 <Roles>
|
|
||||||
// [XmlArrayItem("Role")] // 集合中的每个项是 <Role>
|
|
||||||
// public List<Role> Roles { get; set; } = new List<Role>();
|
|
||||||
|
|
||||||
// // 构造函数,方便测试
|
|
||||||
// public User() { }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public class PersonalInfo
|
|
||||||
// {
|
|
||||||
// [XmlElement("FullName")]
|
|
||||||
// public string FullName { get; set; }
|
|
||||||
|
|
||||||
// [XmlElement("EmailAddress")]
|
|
||||||
// public string EmailAddress { get; set; }
|
|
||||||
|
|
||||||
// // 构造函数,方便测试
|
|
||||||
// public PersonalInfo() { }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public class Role
|
|
||||||
// {
|
|
||||||
// [XmlAttribute("type")]
|
|
||||||
// public string Type { get; set; }
|
|
||||||
|
|
||||||
// // 构造函数,方便测试
|
|
||||||
// public Role() { }
|
|
||||||
// }
|
|
||||||
// #endregion
|
|
||||||
|
|
||||||
// // XML 反序列化方法
|
|
||||||
// public static T ParseExamXml<T>(string xmlContent)
|
|
||||||
// {
|
|
||||||
// string cleanedXml = xmlContent.Trim();
|
|
||||||
// if (cleanedXml.StartsWith("'") && cleanedXml.EndsWith("'"))
|
|
||||||
// {
|
|
||||||
// cleanedXml = cleanedXml.Substring(1, cleanedXml.Length - 2);
|
|
||||||
// }
|
|
||||||
// if (cleanedXml.StartsWith("```xml") && cleanedXml.EndsWith("```"))
|
|
||||||
// {
|
|
||||||
// cleanedXml = cleanedXml.Substring("```xml".Length, cleanedXml.Length - "```xml".Length - "```".Length).Trim();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// XmlSerializer serializer = new XmlSerializer(typeof(T));
|
|
||||||
|
|
||||||
// using (StringReader reader = new StringReader(cleanedXml))
|
|
||||||
// {
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// T user = (T)serializer.Deserialize(reader);
|
|
||||||
// return user;
|
|
||||||
// }
|
|
||||||
// catch (InvalidOperationException ex)
|
|
||||||
// {
|
|
||||||
// Console.WriteLine($"XML 反序列化操作错误: {ex.Message}");
|
|
||||||
// Console.WriteLine($"内部异常: {ex.InnerException?.Message}");
|
|
||||||
// return default(T);
|
|
||||||
// }
|
|
||||||
// catch (Exception ex)
|
|
||||||
// {
|
|
||||||
// Console.WriteLine($"处理错误: {ex.Message}");
|
|
||||||
// return default(T);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public static List<QuestionGroup> ParseExamXmlFormQG(string xmlContent)
|
|
||||||
// {
|
|
||||||
// // 移除可能存在的 Markdown 代码块标记
|
|
||||||
// if (xmlContent.StartsWith("```xml") && xmlContent.EndsWith("```"))
|
|
||||||
// {
|
|
||||||
// xmlContent = xmlContent.Substring("```xml".Length, xmlContent.Length - "```xml".Length - "```".Length).Trim();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var serializer = new XmlSerializer(typeof(List<QuestionGroup>), new XmlRootAttribute("QGs"));
|
|
||||||
|
|
||||||
// using (StringReader reader = new StringReader(xmlContent))
|
|
||||||
// {
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// List<QuestionGroup> questionGroups = (List<QuestionGroup>)serializer.Deserialize(reader);
|
|
||||||
// return questionGroups;
|
|
||||||
// }
|
|
||||||
// catch (InvalidOperationException ex)
|
|
||||||
// {
|
|
||||||
// Console.WriteLine($"XML 反序列化操作错误: {ex.Message}");
|
|
||||||
// Console.WriteLine($"内部异常: {ex.InnerException?.Message}");
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
// catch (Exception ex)
|
|
||||||
// {
|
|
||||||
// Console.WriteLine($"处理错误: {ex.Message}");
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
}
|
}
|
@@ -1,233 +1,36 @@
|
|||||||
using Entities.DTO;
|
using Entities.DTO;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using Entities.Contracts;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using AutoMapper;
|
||||||
|
|
||||||
namespace TechHelper.Client.Exam
|
namespace TechHelper.Client.Exam
|
||||||
{
|
{
|
||||||
public static class ExamPaperExtensions
|
public static class AssignmentExtensions
|
||||||
{
|
{
|
||||||
public static ExamDto ConvertToExamDTO(this ExamPaper examPaper)
|
|
||||||
|
public static List<string> ParseOptionsFromText(this string optionsText)
|
||||||
{
|
{
|
||||||
ExamDto dto = new ExamDto();
|
return optionsText.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None)
|
||||||
|
.Where(line => !string.IsNullOrWhiteSpace(line)).ToList();
|
||||||
dto.AssignmentTitle = examPaper.AssignmentTitle;
|
|
||||||
dto.Description = examPaper.Description;
|
|
||||||
dto.SubjectArea = examPaper.SubjectArea;
|
|
||||||
dto.QuestionGroups.Title = examPaper.AssignmentTitle;
|
|
||||||
dto.QuestionGroups.Descript = examPaper.Description;
|
|
||||||
|
|
||||||
foreach (var qg in examPaper.QuestionGroups)
|
|
||||||
{
|
|
||||||
var qgd = new QuestionGroupDto();
|
|
||||||
ParseMajorQuestionGroup(qg, qgd, false);
|
|
||||||
dto.QuestionGroups.SubQuestionGroups.Add(qgd);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
foreach (var question in examPaper.TopLevelQuestions)
|
|
||||||
{
|
|
||||||
if (question.SubQuestions != null && question.SubQuestions.Any())
|
|
||||||
{
|
|
||||||
var qgDto = new QuestionGroupDto
|
|
||||||
{
|
|
||||||
Title = question.Stem,
|
|
||||||
Score = (int)question.Score,
|
|
||||||
Descript = "",
|
|
||||||
};
|
|
||||||
|
|
||||||
qgDto.ValidQuestionGroup = !string.IsNullOrEmpty(qgDto.Descript);
|
|
||||||
|
|
||||||
|
|
||||||
ParseQuestionWithSubQuestions(question, qgDto, qgDto.ValidQuestionGroup);
|
|
||||||
dto.QuestionGroups.SubQuestionGroups.Add(qgDto);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var qgDto = new QuestionGroupDto
|
|
||||||
{
|
|
||||||
Title = question.Stem,
|
|
||||||
Score = (int)question.Score,
|
|
||||||
Descript = "",
|
|
||||||
};
|
|
||||||
|
|
||||||
qgDto.ValidQuestionGroup = !string.IsNullOrEmpty(qgDto.Descript);
|
|
||||||
|
|
||||||
var subQuestionDto = new SubQuestionDto();
|
|
||||||
ParseSingleQuestion(question, subQuestionDto, !qgDto.ValidQuestionGroup);
|
|
||||||
qgDto.SubQuestions.Add(subQuestionDto);
|
|
||||||
dto.QuestionGroups.SubQuestionGroups.Add(qgDto);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ParseMajorQuestionGroup(MajorQuestionGroup qg, QuestionGroupDto qgd, bool isParentGroupValidChain)
|
public static void SeqIndex(this AssignmentDto dto)
|
||||||
{
|
{
|
||||||
qgd.Title = qg.Title;
|
dto.ExamStruct.SeqQGroupIndex();
|
||||||
qgd.Score = (int)qg.Score;
|
|
||||||
qgd.Descript = qg.Descript;
|
|
||||||
|
|
||||||
|
|
||||||
qgd.ValidQuestionGroup = !string.IsNullOrEmpty(qg.Descript) && !isParentGroupValidChain;
|
|
||||||
|
|
||||||
|
|
||||||
bool nextIsParentGroupValidChain = qgd.ValidQuestionGroup || isParentGroupValidChain;
|
|
||||||
|
|
||||||
|
|
||||||
if (qg.SubQuestionGroups != null)
|
|
||||||
{
|
|
||||||
qg.SubQuestionGroups.ForEach(sqg =>
|
|
||||||
{
|
|
||||||
var sqgd = new QuestionGroupDto();
|
|
||||||
sqgd.Index = (byte)qg.SubQuestionGroups.IndexOf(sqg);
|
|
||||||
ParseMajorQuestionGroup(sqg, sqgd, nextIsParentGroupValidChain);
|
|
||||||
qgd.SubQuestionGroups.Add(sqgd);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理 MajorQuestionGroup 下的 SubQuestions
|
|
||||||
if (qg.SubQuestions != null)
|
|
||||||
{
|
|
||||||
qg.SubQuestions.ForEach(sq =>
|
|
||||||
{
|
|
||||||
// 如果 MajorQuestionGroup 下的 Question 包含子问题,则转为 QuestionGroupDto
|
|
||||||
if (sq.SubQuestions != null && sq.SubQuestions.Any())
|
|
||||||
{
|
|
||||||
var subQgd = new QuestionGroupDto
|
|
||||||
{
|
|
||||||
Title = sq.Stem,
|
|
||||||
Index = (byte)qg.SubQuestions.IndexOf(sq),
|
|
||||||
Score = (int)sq.Score,
|
|
||||||
Descript = "" // 默认为空
|
|
||||||
};
|
|
||||||
// 判断当前组是否有效:如果有描述,并且其父级链中没有任何一个组是有效组,则当前组有效
|
|
||||||
subQgd.ValidQuestionGroup = !string.IsNullOrEmpty(subQgd.Descript) && !nextIsParentGroupValidChain;
|
|
||||||
|
|
||||||
ParseQuestionWithSubQuestions(sq, subQgd, subQgd.ValidQuestionGroup || nextIsParentGroupValidChain);
|
|
||||||
qgd.SubQuestionGroups.Add(subQgd);
|
|
||||||
}
|
|
||||||
else // 如果 MajorQuestionGroup 下的 Question 没有子问题,则转为 SubQuestionDto
|
|
||||||
{
|
|
||||||
var subQd = new SubQuestionDto();
|
|
||||||
// 只有当所有父组(包括当前组)都不是有效组时,这个题目才有效
|
|
||||||
ParseSingleQuestion(sq, subQd, !nextIsParentGroupValidChain);
|
|
||||||
subQd.Index = (byte)qg.SubQuestions.IndexOf(sq);
|
|
||||||
qgd.SubQuestions.Add(subQd);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析包含子问题的 Question,将其转换为 QuestionGroupDto
|
|
||||||
// isParentGroupValidChain 参数表示从顶层到当前组的任一父组是否已经是“有效组”
|
|
||||||
private static void ParseQuestionWithSubQuestions(Question question, QuestionGroupDto qgd, bool isParentGroupValidChain)
|
|
||||||
{
|
|
||||||
qgd.Title = question.Stem;
|
|
||||||
qgd.Score = (int)question.Score;
|
|
||||||
qgd.Descript = ""; // 默认为空
|
|
||||||
|
|
||||||
// 判断当前组是否有效:如果有描述,并且其父级链中没有任何一个组是有效组,则当前组有效
|
|
||||||
qgd.ValidQuestionGroup = !string.IsNullOrEmpty(qgd.Descript) && !isParentGroupValidChain;
|
|
||||||
|
|
||||||
// 更新传递给子项的 isParentGroupValidChain 状态
|
|
||||||
bool nextIsParentGroupValidChain = qgd.ValidQuestionGroup || isParentGroupValidChain;
|
|
||||||
|
|
||||||
|
|
||||||
if (question.SubQuestions != null)
|
|
||||||
{
|
|
||||||
question.SubQuestions.ForEach(subQ =>
|
|
||||||
{
|
|
||||||
// 如果子问题本身还有子问题(多层嵌套),则继续创建 QuestionGroupDto
|
|
||||||
if (subQ.SubQuestions != null && subQ.SubQuestions.Any())
|
|
||||||
{
|
|
||||||
var nestedQgd = new QuestionGroupDto
|
|
||||||
{
|
|
||||||
Title = subQ.Stem,
|
|
||||||
Score = (int)subQ.Score,
|
|
||||||
Descript = "" // 默认为空
|
|
||||||
};
|
|
||||||
// 判断当前组是否有效:如果有描述,并且其父级链中没有任何一个组是有效组,则当前组有效
|
|
||||||
nestedQgd.ValidQuestionGroup = !string.IsNullOrEmpty(nestedQgd.Descript) && !nextIsParentGroupValidChain;
|
|
||||||
|
|
||||||
ParseQuestionWithSubQuestions(subQ, nestedQgd, nestedQgd.ValidQuestionGroup || nextIsParentGroupValidChain);
|
|
||||||
qgd.SubQuestionGroups.Add(nestedQgd);
|
|
||||||
}
|
|
||||||
else // 如果子问题没有子问题,则直接创建 SubQuestionDto
|
|
||||||
{
|
|
||||||
var subQd = new SubQuestionDto();
|
|
||||||
// 只有当所有父组(包括当前组)都不是有效组时,这个题目才有效
|
|
||||||
ParseSingleQuestion(subQ, subQd, !nextIsParentGroupValidChain);
|
|
||||||
qgd.SubQuestions.Add(subQd);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析单个 Question (没有子问题) 为 SubQuestionDto
|
|
||||||
private static void ParseSingleQuestion(Question question, SubQuestionDto subQd, bool validQ)
|
|
||||||
{
|
|
||||||
subQd.Stem = question.Stem;
|
|
||||||
subQd.Score = (int)question.Score;
|
|
||||||
subQd.ValidQuestion = validQ; // 根据传入的 validQ 确定是否是“有效题目”
|
|
||||||
subQd.SampleAnswer = question.SampleAnswer;
|
|
||||||
subQd.QuestionType = question.QuestionType;
|
|
||||||
// 注意:DifficultyLevel 在本地 Question 中没有,如果服务器需要,可能需要补充默认值或从其他地方获取
|
|
||||||
// subQd.DifficultyLevel = ...;
|
|
||||||
|
|
||||||
if (question.Options != null)
|
|
||||||
{
|
|
||||||
question.Options.ForEach(o =>
|
|
||||||
{
|
|
||||||
subQd.Options.Add(new OptionDto { Value = o.Label + o.Text });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void SeqIndex(this ExamDto dto)
|
public static void SeqQGroupIndex(this AssignmentQuestionDto dto)
|
||||||
{
|
{
|
||||||
dto.QuestionGroups.SeqQGroupIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
foreach (var sqg in dto.ChildrenAssignmentQuestion)
|
||||||
public static void SeqQGroupIndex(this QuestionGroupDto dto)
|
|
||||||
{
|
|
||||||
dto.SubQuestions?.ForEach(sq =>
|
|
||||||
{
|
{
|
||||||
sq.Index = (byte)dto.SubQuestions.IndexOf(sq);
|
sqg.Index = (byte)(dto.ChildrenAssignmentQuestion.IndexOf(sqg) + 1);
|
||||||
});
|
|
||||||
|
|
||||||
dto.SubQuestionGroups?.ForEach(sqg =>
|
|
||||||
{
|
|
||||||
sqg.Index = (byte)dto.SubQuestionGroups.IndexOf(sqg);
|
|
||||||
sqg.SeqQGroupIndex();
|
sqg.SeqQGroupIndex();
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
public static string SerializeExamDto(this ExamDto dto)
|
|
||||||
{
|
|
||||||
// 配置序列化选项(可选)
|
|
||||||
var options = new JsonSerializerOptions
|
|
||||||
{
|
|
||||||
WriteIndented = true,
|
|
||||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
||||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
|
||||||
};
|
|
||||||
|
|
||||||
return JsonSerializer.Serialize(dto, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ExamDto DeserializeExamDto(string jsonString)
|
|
||||||
{
|
|
||||||
|
|
||||||
var options = new JsonSerializerOptions
|
|
||||||
{
|
|
||||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
|
||||||
};
|
|
||||||
|
|
||||||
return JsonSerializer.Deserialize<ExamDto>(jsonString, options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,19 +1,25 @@
|
|||||||
using Entities.DTO;
|
using Entities.Contracts; // 假设这些实体合约仍然是必需的
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace TechHelper.Client.Exam
|
namespace TechHelper.Client.Exam
|
||||||
{
|
{
|
||||||
// --- 新增错误处理相关类 ---
|
public enum ParseErrorType
|
||||||
|
{
|
||||||
|
Validation = 1,
|
||||||
|
DataParsing = 2,
|
||||||
|
Structural = 3,
|
||||||
|
RegexMatchIssue = 4,
|
||||||
|
UnexpectedError = 5
|
||||||
|
}
|
||||||
|
|
||||||
public class ParseError
|
public class ParseError
|
||||||
{
|
{
|
||||||
public ParseErrorType Type { get; }
|
public ParseErrorType Type { get; }
|
||||||
public string Message { get; }
|
public string Message { get; }
|
||||||
public int? Index { get; } // 错误发生的文本索引或匹配项索引
|
public int? Index { get; }
|
||||||
public string MatchedText { get; } // 如果与某个匹配项相关,记录其文本
|
public string MatchedText { get; }
|
||||||
public Exception InnerException { get; } // 捕获到的原始异常
|
public Exception InnerException { get; }
|
||||||
|
|
||||||
public ParseError(ParseErrorType type, string message, int? index = null, string matchedText = null, Exception innerException = null)
|
public ParseError(ParseErrorType type, string message, int? index = null, string matchedText = null, Exception innerException = null)
|
||||||
{
|
{
|
||||||
@@ -26,7 +32,7 @@ namespace TechHelper.Client.Exam
|
|||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
var sb = new System.Text.StringBuilder();
|
var sb = new StringBuilder();
|
||||||
sb.Append($"[{Type}] {Message}");
|
sb.Append($"[{Type}] {Message}");
|
||||||
if (Index.HasValue) sb.Append($" (Index: {Index.Value})");
|
if (Index.HasValue) sb.Append($" (Index: {Index.Value})");
|
||||||
if (!string.IsNullOrEmpty(MatchedText)) sb.Append($" (MatchedText: '{MatchedText}')");
|
if (!string.IsNullOrEmpty(MatchedText)) sb.Append($" (MatchedText: '{MatchedText}')");
|
||||||
@@ -35,46 +41,34 @@ namespace TechHelper.Client.Exam
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ParseErrorType
|
public class AssignmentEx
|
||||||
{
|
{
|
||||||
Validation = 1, // 输入验证失败
|
public string Title { get; set; } = "Title";
|
||||||
DataParsing = 2, // 数据解析失败(如数字转换)
|
public string Description { get; set; } = "Description";
|
||||||
Structural = 3, // 结构性问题(如选项没有对应的问题)
|
public SubjectAreaEnum SubjectArea { get; set; } = SubjectAreaEnum.Unknown;
|
||||||
RegexMatchIssue = 4, // 正则表达式匹配结果不符合预期
|
public AssignmentQuestionEx ExamStruct { get; set; } = new AssignmentQuestionEx();
|
||||||
UnexpectedError = 5 // 未预料到的通用错误
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public class ExamPaper
|
|
||||||
{
|
|
||||||
public string AssignmentTitle { get; set; } = "未识别试卷标题";
|
|
||||||
public string Description { get; set; } = "未识别试卷描述";
|
|
||||||
public string SubjectArea { get; set; } = "试卷类别";
|
|
||||||
public List<MajorQuestionGroup> QuestionGroups { get; set; } = new List<MajorQuestionGroup>();
|
|
||||||
public List<Question> TopLevelQuestions { get; set; } = new List<Question>();
|
|
||||||
public List<ParseError> Errors { get; set; } = new List<ParseError>();
|
public List<ParseError> Errors { get; set; } = new List<ParseError>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MajorQuestionGroup
|
// 试题的包裹器, 或者 单独作为一个试题结构存在
|
||||||
|
public class AssignmentQuestionEx
|
||||||
{
|
{
|
||||||
public string Title { get; set; } = string.Empty;
|
public string Title { get; set; } = string.Empty;
|
||||||
public string Descript { get; set; } = string.Empty;
|
public string Description { get; set; } = string.Empty;
|
||||||
|
public byte Index { get; set; } = 0;
|
||||||
public float Score { get; set; }
|
public float Score { get; set; }
|
||||||
public List<MajorQuestionGroup> SubQuestionGroups { get; set; } = new List<MajorQuestionGroup>();
|
public string Sequence { get; set; } = string.Empty;
|
||||||
public List<Question> SubQuestions { get; set; } = new List<Question>();
|
public QuestionEx? Question { get; set; }
|
||||||
|
public AssignmentStructType Type { get; set; }
|
||||||
|
public List<AssignmentQuestionEx> ChildrenAssignmentQuestion { get; set; } = new List<AssignmentQuestionEx>();
|
||||||
public int Priority { get; set; }
|
public int Priority { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Question
|
public class QuestionEx
|
||||||
{
|
{
|
||||||
public string Number { get; set; } = string.Empty;
|
public string Title { get; set; } = string.Empty;
|
||||||
public string Stem { get; set; } = string.Empty;
|
public string Answer { get; set; } = string.Empty;
|
||||||
public float Score { get; set; }
|
|
||||||
public List<Option> Options { get; set; } = new List<Option>();
|
public List<Option> Options { get; set; } = new List<Option>();
|
||||||
public List<Question> SubQuestions { get; set; } = new List<Question>();
|
|
||||||
public string SampleAnswer { get; set; } = string.Empty;
|
|
||||||
public string QuestionType { get; set; } = string.Empty;
|
|
||||||
public int Priority { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Option
|
public class Option
|
||||||
@@ -89,155 +83,116 @@ namespace TechHelper.Client.Exam
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class RegexPatternConfig
|
public class RegexPatternConfig
|
||||||
{
|
{
|
||||||
public string Pattern { get; set; } // 正则表达式字符串
|
public string Pattern { get; set; }
|
||||||
public int Priority { get; set; } // 优先级,数字越小优先级越高
|
public int Priority { get; set; }
|
||||||
public Regex Regex { get; private set; } // 编译后的Regex对象,用于性能优化
|
public AssignmentStructType Type { get; set; }
|
||||||
|
public Regex Regex { get; private set; }
|
||||||
|
|
||||||
public RegexPatternConfig(string pattern, int priority)
|
public RegexPatternConfig(string pattern, int priority, AssignmentStructType type = AssignmentStructType.Question)
|
||||||
{
|
{
|
||||||
Pattern = pattern;
|
Pattern = pattern;
|
||||||
Priority = priority;
|
Priority = priority;
|
||||||
Regex = new Regex(pattern, RegexOptions.Multiline | RegexOptions.Compiled); // 多行模式,编译以提高性能
|
Type = type;
|
||||||
|
Regex = new Regex(pattern, RegexOptions.Multiline | RegexOptions.Compiled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ExamParserEnum
|
|
||||||
{
|
|
||||||
MajorQuestionGroupPatterns = 0,
|
|
||||||
QuestionPatterns,
|
|
||||||
OptionPatterns
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 试卷解析的配置类,包含所有正则表达式
|
/// 试卷解析的配置类,包含所有正则表达式
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ExamParserConfig
|
public class ExamParserConfig
|
||||||
{
|
{
|
||||||
public List<RegexPatternConfig> MajorQuestionGroupPatterns { get; set; } = new List<RegexPatternConfig>();
|
|
||||||
public List<RegexPatternConfig> QuestionPatterns { get; set; } = new List<RegexPatternConfig>();
|
public List<RegexPatternConfig> QuestionPatterns { get; set; } = new List<RegexPatternConfig>();
|
||||||
public List<RegexPatternConfig> OptionPatterns { get; set; } = new List<RegexPatternConfig>();
|
public List<RegexPatternConfig> OptionPatterns { get; set; } = new List<RegexPatternConfig>();
|
||||||
|
public Regex ScoreRegex { get; private set; } // 独立的得分正则表达式
|
||||||
|
|
||||||
public ExamParserConfig()
|
public ExamParserConfig()
|
||||||
{
|
{
|
||||||
MajorQuestionGroupPatterns.Add(new RegexPatternConfig(@"^([一二三四五六七八九十]+)[、\.]\s*(.+?)(?:\s*\(((\d+(?:\.\d+)?))\s*分\))?\s*$", 1));
|
// 题目/题组模式:只匹配行开头,并按优先级区分
|
||||||
MajorQuestionGroupPatterns.Add(new RegexPatternConfig(@"^\(([一二三四五六七八九十]{1,2}|十[一二三四五六七八九])\)\s*(.+?)(?:\s*\(((\d+(?:\.\d+)?))\s*分\))?\s*$", 2));
|
// Group 1: 编号部分
|
||||||
|
// Group 2: 题目/题组标题内容
|
||||||
|
|
||||||
|
// 例如:一. 这是大题一
|
||||||
|
QuestionPatterns.Add(new RegexPatternConfig(@"^([一二三四五六七八九十]+)[.\、]\s*(.+)", 1, AssignmentStructType.Struct));
|
||||||
|
|
||||||
|
// 例如:(一) 这是第一子题组
|
||||||
|
QuestionPatterns.Add(new RegexPatternConfig(@"^\(([一二三四五六七八九十]{1,2}|十[一二三四五六七八九])\)\s*(.+)", 2, AssignmentStructType.Group));
|
||||||
|
|
||||||
|
// 例如:1. 这是第一道题目 或 1 这是第一道题目
|
||||||
|
QuestionPatterns.Add(new RegexPatternConfig(@"^(\d+)\.?\s*(.+)", 3, AssignmentStructType.Question));
|
||||||
|
|
||||||
|
// 例如:(1). 这是小问一 或 (1) 这是小问一
|
||||||
|
QuestionPatterns.Add(new RegexPatternConfig(@"^\((\d+)\)\.?\s*(.+)", 4, AssignmentStructType.Question));
|
||||||
|
|
||||||
|
// 例如:① 这是另一种小问 或 ①. 这是另一种小问 (如果 ① 后面会跟点,这个更通用)
|
||||||
|
// 如果 ① 后面通常没有点,但您希望它也能匹配,则保留原样或根据实际情况调整
|
||||||
|
QuestionPatterns.Add(new RegexPatternConfig(@"^[①②③④⑤⑥⑦⑧⑨⑩]+\.?\s*(.+)", 5, AssignmentStructType.Question));
|
||||||
|
|
||||||
|
|
||||||
// 模式 1: "1. 这是一个题目 (5分)" 或 "1. 这是一个题目"
|
|
||||||
QuestionPatterns.Add(new RegexPatternConfig(@"^(\d+)\.\s*(.+?)(?:\s*\(((\d+(?:\.\d+)?))\s*分\))?\s*$", 1));
|
|
||||||
|
|
||||||
// 模式 2: "(1) 这是一个子题目 (3分)" 或 "(1) 这是一个子题目"
|
// 选项模式 (保持不变,使用 AssignmentStructType.Option 区分)
|
||||||
QuestionPatterns.Add(new RegexPatternConfig(@"^\((\d+)\)\s*(.+?)(?:\s*\(((\d+(?:\.\d+)?))\s*分\))?\s*$", 2));
|
OptionPatterns.Add(new RegexPatternConfig(@"([A-Z]\.)\s*(.*?)(?=[A-Z]\.|$)", 1, AssignmentStructType.Option));
|
||||||
|
OptionPatterns.Add(new RegexPatternConfig(@"([a-z]\.)\s*(.*?)(?=[a-z]\.|$)", 2, AssignmentStructType.Option));
|
||||||
// 模式 3: "① 这是一个更深层次的子题目 (2分)" 或 "① 这是一个更深层次的子题目"
|
|
||||||
QuestionPatterns.Add(new RegexPatternConfig(@"^[①②③④⑤⑥⑦⑧⑨⑩]+\s*(.+?)(?:\s*\(((\d+(?:\.\d+)?))\s*分\))?\s*$", 3));
|
|
||||||
|
|
||||||
|
|
||||||
OptionPatterns.Add(new RegexPatternConfig(@"([A-Z]\.)\s*(.*?)(?=[A-Z]\.|$)", 1)); // 大写字母选项
|
|
||||||
OptionPatterns.Add(new RegexPatternConfig(@"([a-z]\.)\s*(.*?)(?=[a-z]\.|$)", 1)); // 小写字母选项
|
|
||||||
|
|
||||||
|
// 独立的得分正则表达式:匹配行末尾的 "(X分)" 格式
|
||||||
|
// Group 1: 捕获分数(如 "10" 或 "0.5")
|
||||||
|
ScoreRegex = new Regex(@"(?:\s*\(((\d+(?:\.\d+)?))\s*分\)\s*$)", RegexOptions.Multiline | RegexOptions.Compiled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public class PotentialMatch
|
public class PotentialMatch
|
||||||
{
|
{
|
||||||
public int StartIndex { get; set; }
|
public int StartIndex { get; set; }
|
||||||
public int EndIndex { get; set; } // 匹配到的结构在原始文本中的结束位置
|
public int EndIndex { get; set; }
|
||||||
public string MatchedText { get; set; } // 匹配到的完整行或段落
|
public string MatchedText { get; set; }
|
||||||
public Match RegexMatch { get; set; } // 原始的Regex.Match对象,方便获取捕获组
|
public Match RegexMatch { get; set; }
|
||||||
public RegexPatternConfig PatternConfig { get; set; } // 匹配到的模式配置
|
public RegexPatternConfig PatternConfig { get; set; }
|
||||||
public MatchType Type { get; set; } // 枚举:MajorQuestionGroup, Question, Option, etc.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum MatchType
|
|
||||||
{
|
|
||||||
MajorQuestionGroup,
|
|
||||||
Question,
|
|
||||||
Option,
|
|
||||||
Other // 如果有其他需要识别的类型
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 负责扫描原始文本,收集所有潜在的匹配项(题组、题目、选项)。
|
|
||||||
/// 它只进行匹配,不进行结构化归属。
|
|
||||||
/// </summary>
|
|
||||||
public class ExamDocumentScanner
|
public class ExamDocumentScanner
|
||||||
{
|
{
|
||||||
private readonly ExamParserConfig _config;
|
private readonly ExamParserConfig _config;
|
||||||
|
|
||||||
public ExamDocumentScanner(ExamParserConfig config)
|
public ExamDocumentScanner(ExamParserConfig config)
|
||||||
{
|
{
|
||||||
_config = config ?? throw new ArgumentNullException(nameof(config)); // 确保配置不为空
|
_config = config ?? throw new ArgumentNullException(nameof(config));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public List<PotentialMatch> Scan(string text, List<ParseError> errors)
|
||||||
/// 扫描给定的文本,返回所有潜在的匹配项,并按起始位置排序。
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="text">要扫描的文本</param>
|
|
||||||
/// <returns>所有匹配到的 PotentialMatch 列表</returns>
|
|
||||||
public List<PotentialMatch> Scan(string text)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(text))
|
if (string.IsNullOrEmpty(text))
|
||||||
{
|
{
|
||||||
return new List<PotentialMatch>(); // 对于空文本,直接返回空列表
|
return new List<PotentialMatch>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var allPotentialMatches = new List<PotentialMatch>();
|
var allPotentialMatches = new List<PotentialMatch>();
|
||||||
|
var allPatternConfigs = new List<RegexPatternConfig>();
|
||||||
|
allPatternConfigs.AddRange(_config.QuestionPatterns);
|
||||||
|
allPatternConfigs.AddRange(_config.OptionPatterns);
|
||||||
|
|
||||||
// 扫描所有题组模式
|
foreach (var patternConfig in allPatternConfigs)
|
||||||
foreach (var patternConfig in _config.MajorQuestionGroupPatterns)
|
|
||||||
{
|
{
|
||||||
foreach (Match match in patternConfig.Regex.Matches(text))
|
try
|
||||||
{
|
{
|
||||||
allPotentialMatches.Add(new PotentialMatch
|
foreach (Match match in patternConfig.Regex.Matches(text))
|
||||||
{
|
{
|
||||||
StartIndex = match.Index,
|
allPotentialMatches.Add(new PotentialMatch
|
||||||
EndIndex = match.Index + match.Length,
|
{
|
||||||
MatchedText = match.Value,
|
StartIndex = match.Index,
|
||||||
RegexMatch = match,
|
EndIndex = match.Index + match.Length,
|
||||||
PatternConfig = patternConfig,
|
MatchedText = match.Value,
|
||||||
Type = MatchType.MajorQuestionGroup
|
RegexMatch = match,
|
||||||
});
|
PatternConfig = patternConfig,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
errors.Add(new ParseError(ParseErrorType.UnexpectedError,
|
||||||
|
$"An error occurred during regex matching for pattern: '{patternConfig.Pattern}'.",
|
||||||
|
innerException: ex));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 扫描所有题目模式
|
|
||||||
foreach (var patternConfig in _config.QuestionPatterns)
|
|
||||||
{
|
|
||||||
foreach (Match match in patternConfig.Regex.Matches(text))
|
|
||||||
{
|
|
||||||
allPotentialMatches.Add(new PotentialMatch
|
|
||||||
{
|
|
||||||
StartIndex = match.Index,
|
|
||||||
EndIndex = match.Index + match.Length,
|
|
||||||
MatchedText = match.Value,
|
|
||||||
RegexMatch = match,
|
|
||||||
PatternConfig = patternConfig,
|
|
||||||
Type = MatchType.Question
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 扫描所有选项模式
|
|
||||||
foreach (var patternConfig in _config.OptionPatterns)
|
|
||||||
{
|
|
||||||
foreach (Match match in patternConfig.Regex.Matches(text))
|
|
||||||
{
|
|
||||||
allPotentialMatches.Add(new PotentialMatch
|
|
||||||
{
|
|
||||||
StartIndex = match.Index,
|
|
||||||
EndIndex = match.Index + match.Length,
|
|
||||||
MatchedText = match.Value,
|
|
||||||
RegexMatch = match,
|
|
||||||
PatternConfig = patternConfig,
|
|
||||||
Type = MatchType.Option
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 统一按起始位置排序
|
|
||||||
return allPotentialMatches.OrderBy(pm => pm.StartIndex).ToList();
|
return allPotentialMatches.OrderBy(pm => pm.StartIndex).ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -251,18 +206,8 @@ namespace TechHelper.Client.Exam
|
|||||||
_config = config ?? throw new ArgumentNullException(nameof(config), "ExamParserConfig cannot be null.");
|
_config = config ?? throw new ArgumentNullException(nameof(config), "ExamParserConfig cannot be null.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public AssignmentEx BuildExam(string fullExamText, List<PotentialMatch> allPotentialMatches)
|
||||||
/// Builds the ExamPaper structure from raw text and potential matches.
|
|
||||||
/// Collects and returns parsing errors encountered during the process.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="fullExamText">The complete text of the exam paper.</param>
|
|
||||||
/// <param name="allPotentialMatches">A list of all identified potential matches.</param>
|
|
||||||
/// <returns>An ExamPaper object containing the parsed structure and a list of errors.</returns>
|
|
||||||
/// <exception cref="ArgumentException">Thrown if fullExamText is null or empty.</exception>
|
|
||||||
/// <exception cref="ArgumentNullException">Thrown if allPotentialMatches is null.</exception>
|
|
||||||
public ExamPaper BuildExamPaper(string fullExamText, List<PotentialMatch> allPotentialMatches)
|
|
||||||
{
|
{
|
||||||
// 核心输入验证仍然是必要的,因为这些错误是无法恢复的
|
|
||||||
if (string.IsNullOrWhiteSpace(fullExamText))
|
if (string.IsNullOrWhiteSpace(fullExamText))
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Full exam text cannot be null or empty.", nameof(fullExamText));
|
throw new ArgumentException("Full exam text cannot be null or empty.", nameof(fullExamText));
|
||||||
@@ -272,269 +217,79 @@ namespace TechHelper.Client.Exam
|
|||||||
throw new ArgumentNullException(nameof(allPotentialMatches), "Potential matches list cannot be null.");
|
throw new ArgumentNullException(nameof(allPotentialMatches), "Potential matches list cannot be null.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var examPaper = new ExamPaper(); // ExamPaper 现在包含一个 Errors 列表
|
var assignment = new AssignmentEx();
|
||||||
|
|
||||||
// 尝试获取试卷标题
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
examPaper.AssignmentTitle = GetExamTitle(fullExamText);
|
assignment.Title = GetExamTitle(fullExamText);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// 如果获取标题失败,记录错误而不是抛出致命异常
|
assignment.Errors.Add(new ParseError(ParseErrorType.UnexpectedError, "Failed to extract exam title.", innerException: ex));
|
||||||
examPaper.Errors.Add(new ParseError(ParseErrorType.UnexpectedError, "Failed to extract exam title.", innerException: ex));
|
assignment.Title = "未识别试卷标题";
|
||||||
examPaper.AssignmentTitle = "未识别试卷标题"; // 提供默认值
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var majorQGStack = new Stack<MajorQuestionGroup>();
|
var assignmentQuestionStack = new Stack<AssignmentQuestionEx>();
|
||||||
MajorQuestionGroup currentMajorQG = null;
|
var rootAssignmentQuestion = new AssignmentQuestionEx { Type = AssignmentStructType.Struct, Priority = 0, Title = "Root Exam Structure" };
|
||||||
|
assignmentQuestionStack.Push(rootAssignmentQuestion);
|
||||||
var questionStack = new Stack<Question>();
|
assignment.ExamStruct = rootAssignmentQuestion;
|
||||||
Question currentQuestion = null;
|
|
||||||
|
|
||||||
int currentContentStart = 0;
|
int currentContentStart = 0;
|
||||||
|
|
||||||
// 处理试卷开头的描述性文本
|
|
||||||
if (allPotentialMatches.Any() && allPotentialMatches[0].StartIndex > 0)
|
if (allPotentialMatches.Any() && allPotentialMatches[0].StartIndex > 0)
|
||||||
{
|
{
|
||||||
string introText = fullExamText.Substring(0, allPotentialMatches[0].StartIndex).Trim();
|
string introText = fullExamText.Substring(0, allPotentialMatches[0].StartIndex).Trim();
|
||||||
if (!string.IsNullOrWhiteSpace(introText))
|
if (!string.IsNullOrWhiteSpace(introText))
|
||||||
{
|
{
|
||||||
examPaper.Description += (string.IsNullOrWhiteSpace(examPaper.Description) ? "" : "\n") + introText;
|
assignment.Description += (string.IsNullOrWhiteSpace(assignment.Description) ? "" : "\n") + introText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
currentContentStart = allPotentialMatches.Any() ? allPotentialMatches[0].StartIndex : 0;
|
||||||
currentContentStart = allPotentialMatches[0].StartIndex;
|
|
||||||
|
|
||||||
for (int i = 0; i < allPotentialMatches.Count; i++)
|
for (int i = 0; i < allPotentialMatches.Count; i++)
|
||||||
{
|
{
|
||||||
var pm = allPotentialMatches[i];
|
var pm = allPotentialMatches[i];
|
||||||
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// **数据验证:不再抛出,而是记录错误**
|
if (!IsValidPotentialMatch(pm, i, fullExamText.Length, currentContentStart, assignment.Errors))
|
||||||
if (pm.StartIndex < currentContentStart || pm.EndIndex > fullExamText.Length || pm.StartIndex > pm.EndIndex)
|
|
||||||
{
|
{
|
||||||
examPaper.Errors.Add(new ParseError(ParseErrorType.Validation,
|
currentContentStart = Math.Max(currentContentStart, pm.EndIndex);
|
||||||
$"PotentialMatch at index {i} has invalid start/end indices. Start: {pm.StartIndex}, End: {pm.EndIndex}, CurrentContentStart: {currentContentStart}, FullTextLength: {fullExamText.Length}",
|
continue;
|
||||||
index: i, matchedText: pm.MatchedText));
|
|
||||||
currentContentStart = Math.Max(currentContentStart, pm.EndIndex); // 尝试跳过这个损坏的匹配项
|
|
||||||
continue; // 跳过当前循环迭代,处理下一个匹配项
|
|
||||||
}
|
|
||||||
if (pm.RegexMatch == null || pm.PatternConfig == null)
|
|
||||||
{
|
|
||||||
examPaper.Errors.Add(new ParseError(ParseErrorType.Validation,
|
|
||||||
$"PotentialMatch at index {i} is missing RegexMatch or PatternConfig.",
|
|
||||||
index: i, matchedText: pm.MatchedText));
|
|
||||||
currentContentStart = Math.Max(currentContentStart, pm.EndIndex); // 尝试跳过这个损坏的匹配项
|
|
||||||
continue; // 跳过当前循环迭代,处理下一个匹配项
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string precedingText = fullExamText.Substring(currentContentStart, pm.StartIndex - currentContentStart).Trim();
|
string precedingText = fullExamText.Substring(currentContentStart, pm.StartIndex - currentContentStart).Trim();
|
||||||
if (!string.IsNullOrWhiteSpace(precedingText))
|
if (!string.IsNullOrWhiteSpace(precedingText))
|
||||||
{
|
{
|
||||||
if (currentQuestion != null)
|
if (assignmentQuestionStack.Peek().Question != null)
|
||||||
{
|
{
|
||||||
// 将 examPaper.Errors 传递给 ProcessQuestionContent 收集错误
|
ProcessQuestionContent(assignmentQuestionStack.Peek(), precedingText, assignment.Errors);
|
||||||
ProcessQuestionContent(currentQuestion, precedingText,
|
|
||||||
GetSubMatchesForRange(allPotentialMatches, currentContentStart, pm.StartIndex, examPaper.Errors),
|
|
||||||
examPaper.Errors);
|
|
||||||
}
|
|
||||||
else if (currentMajorQG != null)
|
|
||||||
{
|
|
||||||
currentMajorQG.Descript += (string.IsNullOrWhiteSpace(currentMajorQG.Descript) ? "" : "\n") + precedingText;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
examPaper.Description += (string.IsNullOrWhiteSpace(examPaper.Description) ? "" : "\n") + precedingText;
|
assignment.Description += (string.IsNullOrWhiteSpace(assignment.Description) ? "" : "\n") + precedingText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pm.Type == MatchType.MajorQuestionGroup)
|
if (pm.PatternConfig.Type == AssignmentStructType.Option)
|
||||||
{
|
{
|
||||||
// 对 MajorQuestionGroup 的处理
|
HandleOptionMatch(pm, i, assignmentQuestionStack.Peek(), assignment.Errors);
|
||||||
try
|
|
||||||
{
|
|
||||||
while (majorQGStack.Any() && pm.PatternConfig.Priority <= majorQGStack.Peek().Priority)
|
|
||||||
{
|
|
||||||
majorQGStack.Pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegexMatch Groups 验证:不再抛出,记录错误
|
|
||||||
if (pm.RegexMatch.Groups.Count < 2 || string.IsNullOrWhiteSpace(pm.RegexMatch.Groups[1].Value))
|
|
||||||
{
|
|
||||||
examPaper.Errors.Add(new ParseError(ParseErrorType.RegexMatchIssue,
|
|
||||||
$"MajorQuestionGroup match at index {i} does not have enough regex groups or a valid title group (Group 1). Skipping this group.",
|
|
||||||
index: i, matchedText: pm.MatchedText));
|
|
||||||
currentContentStart = pm.EndIndex; // 继续,尝试跳过此项
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
float score = 0;
|
|
||||||
// 使用 float.TryParse 避免异常
|
|
||||||
if (pm.RegexMatch.Groups.Count > 3 && pm.RegexMatch.Groups[4].Success) // 假设纯数字分数是 Group 4
|
|
||||||
{
|
|
||||||
if (!float.TryParse(pm.RegexMatch.Groups[4].Value, out score))
|
|
||||||
{
|
|
||||||
examPaper.Errors.Add(new ParseError(ParseErrorType.DataParsing,
|
|
||||||
$"Failed to parse score '{pm.RegexMatch.Groups[4].Value}' for MajorQuestionGroup at index {i}. Defaulting to 0.",
|
|
||||||
index: i, matchedText: pm.MatchedText));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MajorQuestionGroup newMajorQG = new MajorQuestionGroup
|
|
||||||
{
|
|
||||||
Title = pm.RegexMatch.Groups[2].Value.Trim(), // 标题是 Group 2
|
|
||||||
Score = score,
|
|
||||||
Priority = pm.PatternConfig.Priority,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (majorQGStack.Any())
|
|
||||||
{
|
|
||||||
majorQGStack.Peek().SubQuestionGroups.Add(newMajorQG);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
examPaper.QuestionGroups.Add(newMajorQG);
|
|
||||||
}
|
|
||||||
|
|
||||||
currentContentStart = pm.EndIndex;
|
|
||||||
majorQGStack.Push(newMajorQG);
|
|
||||||
currentMajorQG = newMajorQG;
|
|
||||||
questionStack.Clear();
|
|
||||||
currentQuestion = null;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
examPaper.Errors.Add(new ParseError(ParseErrorType.UnexpectedError,
|
|
||||||
$"An unexpected error occurred during processing MajorQuestionGroup at index {i}.",
|
|
||||||
index: i, matchedText: pm.MatchedText, innerException: ex));
|
|
||||||
currentContentStart = pm.EndIndex; // 尝试跳过此项
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (pm.Type == MatchType.Question)
|
else
|
||||||
{
|
{
|
||||||
// 对 Question 的处理
|
HandleQuestionGroupMatch(pm, i, assignmentQuestionStack, assignment.Errors);
|
||||||
try
|
|
||||||
{
|
|
||||||
while (questionStack.Any() && pm.PatternConfig.Priority <= questionStack.Peek().Priority)
|
|
||||||
{
|
|
||||||
questionStack.Pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegexMatch Groups 验证
|
|
||||||
if (pm.RegexMatch.Groups.Count < 3 || string.IsNullOrWhiteSpace(pm.RegexMatch.Groups[1].Value) || string.IsNullOrWhiteSpace(pm.RegexMatch.Groups[2].Value))
|
|
||||||
{
|
|
||||||
examPaper.Errors.Add(new ParseError(ParseErrorType.RegexMatchIssue,
|
|
||||||
$"Question match at index {i} does not have enough regex groups or valid number/text groups (Group 1/2). Skipping this question.",
|
|
||||||
index: i, matchedText: pm.MatchedText));
|
|
||||||
currentContentStart = pm.EndIndex; // 尝试跳过此项
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
float score = 0;
|
|
||||||
// 使用 float.TryParse 避免异常
|
|
||||||
if (pm.RegexMatch.Groups.Count > 4 && pm.RegexMatch.Groups[4].Success) // 假设纯数字分数是 Group 4
|
|
||||||
{
|
|
||||||
if (!float.TryParse(pm.RegexMatch.Groups[4].Value, out score))
|
|
||||||
{
|
|
||||||
examPaper.Errors.Add(new ParseError(ParseErrorType.DataParsing,
|
|
||||||
$"Failed to parse score '{pm.RegexMatch.Groups[4].Value}' for Question at index {i}. Defaulting to 0.",
|
|
||||||
index: i, matchedText: pm.MatchedText));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Question newQuestion = new Question
|
|
||||||
{
|
|
||||||
Number = pm.RegexMatch.Groups[1].Value.Trim(),
|
|
||||||
Stem = pm.RegexMatch.Groups[2].Value.Trim(),
|
|
||||||
Priority = pm.PatternConfig.Priority,
|
|
||||||
Score = score // 赋值解析到的分数
|
|
||||||
};
|
|
||||||
|
|
||||||
if (questionStack.Any())
|
|
||||||
{
|
|
||||||
questionStack.Peek().SubQuestions.Add(newQuestion);
|
|
||||||
}
|
|
||||||
else if (currentMajorQG != null)
|
|
||||||
{
|
|
||||||
currentMajorQG.SubQuestions.Add(newQuestion);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
examPaper.TopLevelQuestions.Add(newQuestion);
|
|
||||||
}
|
|
||||||
|
|
||||||
currentContentStart = pm.EndIndex;
|
|
||||||
questionStack.Push(newQuestion);
|
|
||||||
currentQuestion = newQuestion;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
examPaper.Errors.Add(new ParseError(ParseErrorType.UnexpectedError,
|
|
||||||
$"An unexpected error occurred during processing Question at index {i}.",
|
|
||||||
index: i, matchedText: pm.MatchedText, innerException: ex));
|
|
||||||
currentContentStart = pm.EndIndex; // 尝试跳过此项
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (pm.Type == MatchType.Option)
|
|
||||||
{
|
|
||||||
// 对 Option 的处理
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (currentQuestion != null)
|
|
||||||
{
|
|
||||||
// RegexMatch Groups 验证
|
|
||||||
if (pm.RegexMatch.Groups.Count < 3 || string.IsNullOrWhiteSpace(pm.RegexMatch.Groups[1].Value) || string.IsNullOrWhiteSpace(pm.RegexMatch.Groups[2].Value))
|
|
||||||
{
|
|
||||||
examPaper.Errors.Add(new ParseError(ParseErrorType.RegexMatchIssue,
|
|
||||||
$"Option match at index {i} does not have enough regex groups or valid label/text groups (Group 1/2). Skipping this option.",
|
|
||||||
index: i, matchedText: pm.MatchedText));
|
|
||||||
currentContentStart = pm.EndIndex; // 尝试跳过此项
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Option newOption = new Option
|
|
||||||
{
|
|
||||||
Label = pm.RegexMatch.Groups[1].Value.Trim(),
|
|
||||||
Text = pm.RegexMatch.Groups[2].Value.Trim()
|
|
||||||
};
|
|
||||||
currentQuestion.Options.Add(newOption);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 结构性问题:找到孤立的选项,记录错误但继续
|
|
||||||
examPaper.Errors.Add(new ParseError(ParseErrorType.Structural,
|
|
||||||
$"Found isolated Option at index {i}. Options must belong to a question. Ignoring this option.",
|
|
||||||
index: i, matchedText: pm.MatchedText));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
examPaper.Errors.Add(new ParseError(ParseErrorType.UnexpectedError,
|
|
||||||
$"An unexpected error occurred during processing Option at index {i}.",
|
|
||||||
index: i, matchedText: pm.MatchedText, innerException: ex));
|
|
||||||
// 这里不需要 `continue`,因为即使出错也可能只是该选项的问题,不影响后续处理
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
currentContentStart = pm.EndIndex; // 更新当前内容起点
|
currentContentStart = pm.EndIndex;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// 捕获任何在处理单个 PotentialMatch 过程中未被更具体 catch 块捕获的意外错误
|
assignment.Errors.Add(new ParseError(ParseErrorType.UnexpectedError,
|
||||||
examPaper.Errors.Add(new ParseError(ParseErrorType.UnexpectedError,
|
|
||||||
$"An unexpected error occurred during main loop processing of PotentialMatch at index {i}.",
|
$"An unexpected error occurred during main loop processing of PotentialMatch at index {i}.",
|
||||||
index: i, matchedText: pm.MatchedText, innerException: ex));
|
index: i, matchedText: pm.MatchedText, innerException: ex));
|
||||||
currentContentStart = Math.Max(currentContentStart, pm.EndIndex); // 尝试跳过当前匹配项,继续下一项
|
currentContentStart = Math.Max(currentContentStart, pm.EndIndex);
|
||||||
// 这里不 `continue` 是因为外层循环会推进 `i`,但确保 `currentContentStart` 更新以避免无限循环
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 处理所有匹配项之后的剩余内容 ---
|
|
||||||
if (currentContentStart < fullExamText.Length)
|
if (currentContentStart < fullExamText.Length)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -542,169 +297,183 @@ namespace TechHelper.Client.Exam
|
|||||||
string remainingText = fullExamText.Substring(currentContentStart).Trim();
|
string remainingText = fullExamText.Substring(currentContentStart).Trim();
|
||||||
if (!string.IsNullOrWhiteSpace(remainingText))
|
if (!string.IsNullOrWhiteSpace(remainingText))
|
||||||
{
|
{
|
||||||
if (currentQuestion != null)
|
if (assignmentQuestionStack.Peek().Question != null)
|
||||||
{
|
{
|
||||||
ProcessQuestionContent(currentQuestion, remainingText,
|
ProcessQuestionContent(assignmentQuestionStack.Peek(), remainingText, assignment.Errors);
|
||||||
GetSubMatchesForRange(allPotentialMatches, currentContentStart, fullExamText.Length, examPaper.Errors),
|
|
||||||
examPaper.Errors);
|
|
||||||
}
|
|
||||||
else if (currentMajorQG != null)
|
|
||||||
{
|
|
||||||
currentMajorQG.Descript += (string.IsNullOrWhiteSpace(currentMajorQG.Descript) ? "" : "\n") + remainingText;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
examPaper.Description += (string.IsNullOrWhiteSpace(examPaper.Description) ? "" : "\n") + remainingText;
|
assignment.Description += (string.IsNullOrWhiteSpace(assignment.Description) ? "" : "\n") + remainingText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
examPaper.Errors.Add(new ParseError(ParseErrorType.UnexpectedError,
|
assignment.Errors.Add(new ParseError(ParseErrorType.UnexpectedError,
|
||||||
"An unexpected error occurred while processing remaining text after all potential matches.",
|
"An unexpected error occurred while processing remaining text after all potential matches.",
|
||||||
innerException: ex));
|
innerException: ex));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return examPaper;
|
return assignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsValidPotentialMatch(PotentialMatch pm, int index, int fullTextLength, int currentContentStart, List<ParseError> errors)
|
||||||
|
{
|
||||||
|
if (pm.StartIndex < currentContentStart || pm.EndIndex > fullTextLength || pm.StartIndex > pm.EndIndex)
|
||||||
|
{
|
||||||
|
errors.Add(new ParseError(ParseErrorType.Validation,
|
||||||
|
$"PotentialMatch at index {index} has invalid start/end indices. Start: {pm.StartIndex}, End: {pm.EndIndex}, CurrentContentStart: {currentContentStart}, FullTextLength: {fullTextLength}",
|
||||||
|
index: index, matchedText: pm.MatchedText));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (pm.RegexMatch == null || pm.PatternConfig == null)
|
||||||
|
{
|
||||||
|
errors.Add(new ParseError(ParseErrorType.Validation,
|
||||||
|
$"PotentialMatch at index {index} is missing RegexMatch or PatternConfig.",
|
||||||
|
index: index, matchedText: pm.MatchedText));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleQuestionGroupMatch(PotentialMatch pm, int index, Stack<AssignmentQuestionEx> assignmentQuestionStack, List<ParseError> errors)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (assignmentQuestionStack.Count > 1 && pm.PatternConfig.Priority <= assignmentQuestionStack.Peek().Priority)
|
||||||
|
{
|
||||||
|
assignmentQuestionStack.Pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
string sequence = assignmentQuestionStack.Count > 0 ? assignmentQuestionStack.Peek().Sequence : string.Empty;
|
||||||
|
|
||||||
|
// 验证捕获组:Group 1 是编号,Group 2 是题目内容
|
||||||
|
if (pm.RegexMatch.Groups.Count < 3 || !pm.RegexMatch.Groups[1].Success || string.IsNullOrWhiteSpace(pm.RegexMatch.Groups[2].Value))
|
||||||
|
{
|
||||||
|
errors.Add(new ParseError(ParseErrorType.RegexMatchIssue,
|
||||||
|
$"Question/Group match at index {index} does not have enough regex groups (expected 3 for number and title) or a valid title group (Group 2). Skipping this group.",
|
||||||
|
index: index, matchedText: pm.MatchedText));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float score = 0;
|
||||||
|
// 尝试从 MatchedText 的末尾匹配分数
|
||||||
|
Match scoreMatch = _config.ScoreRegex.Match(pm.MatchedText);
|
||||||
|
if (scoreMatch.Success && scoreMatch.Groups.Count > 1 && scoreMatch.Groups[1].Success)
|
||||||
|
{
|
||||||
|
if (!float.TryParse(scoreMatch.Groups[1].Value, out score))
|
||||||
|
{
|
||||||
|
errors.Add(new ParseError(ParseErrorType.DataParsing,
|
||||||
|
$"Failed to parse score '{scoreMatch.Groups[1].Value}' for match at index {index}. Defaulting to 0.",
|
||||||
|
index: index, matchedText: pm.MatchedText));
|
||||||
|
}
|
||||||
|
// 从 MatchedText 中移除分数部分,使其只包含编号和标题
|
||||||
|
// 注意:这里修改的是pm.MatchedText,这不会影响原始文本,只是当前匹配项的“内容”
|
||||||
|
pm.MatchedText = pm.MatchedText.Substring(0, scoreMatch.Index).Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取标题,这里使用 Group 2 的值,它不包含分数
|
||||||
|
string title = pm.RegexMatch.Groups[2].Value.Trim();
|
||||||
|
|
||||||
|
string seq = pm.RegexMatch.Groups[1].Value.Trim();
|
||||||
|
seq = string.IsNullOrEmpty(seq) || string.IsNullOrEmpty(sequence) ? seq : " ." + seq;
|
||||||
|
|
||||||
|
AssignmentQuestionEx newAssignmentQuestion;
|
||||||
|
if (pm.PatternConfig.Type == AssignmentStructType.Struct)
|
||||||
|
{
|
||||||
|
newAssignmentQuestion = new AssignmentQuestionEx
|
||||||
|
{
|
||||||
|
Title = title,
|
||||||
|
Score = score,
|
||||||
|
Sequence = sequence + seq,
|
||||||
|
Priority = pm.PatternConfig.Priority,
|
||||||
|
Type = pm.PatternConfig.Type
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else // AssignmentStructType.Question 类型
|
||||||
|
{
|
||||||
|
newAssignmentQuestion = new AssignmentQuestionEx
|
||||||
|
{
|
||||||
|
Priority = pm.PatternConfig.Priority,
|
||||||
|
Type = pm.PatternConfig.Type,
|
||||||
|
Sequence = sequence + seq,
|
||||||
|
Score = score,
|
||||||
|
Question = new QuestionEx
|
||||||
|
{
|
||||||
|
Title = title,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
assignmentQuestionStack.Peek().ChildrenAssignmentQuestion.Add(newAssignmentQuestion);
|
||||||
|
assignmentQuestionStack.Push(newAssignmentQuestion);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
errors.Add(new ParseError(ParseErrorType.UnexpectedError,
|
||||||
|
$"An unexpected error occurred during processing a non-option match (type: {pm.PatternConfig.Type}) at index {index}.",
|
||||||
|
index: index, matchedText: pm.MatchedText, innerException: ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleOptionMatch(PotentialMatch pm, int index, AssignmentQuestionEx currentAssignmentQuestion, List<ParseError> errors)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (currentAssignmentQuestion.Question == null)
|
||||||
|
{
|
||||||
|
errors.Add(new ParseError(ParseErrorType.Structural,
|
||||||
|
$"Found isolated Option at index {index}. Options must belong to a 'Question' type structure. Ignoring this option.",
|
||||||
|
index: index, matchedText: pm.MatchedText));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pm.RegexMatch.Groups.Count < 3 || !pm.RegexMatch.Groups[1].Success || string.IsNullOrWhiteSpace(pm.RegexMatch.Groups[2].Value))
|
||||||
|
{
|
||||||
|
errors.Add(new ParseError(ParseErrorType.RegexMatchIssue,
|
||||||
|
$"Option match at index {index} does not have enough regex groups or valid label/text groups (Group 1/2). Skipping this option.",
|
||||||
|
index: index, matchedText: pm.MatchedText));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Option newOption = new Option
|
||||||
|
{
|
||||||
|
Label = pm.RegexMatch.Groups[1].Value.Trim(),
|
||||||
|
Text = pm.RegexMatch.Groups[2].Value.Trim()
|
||||||
|
};
|
||||||
|
currentAssignmentQuestion.Question.Options.Add(newOption);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
errors.Add(new ParseError(ParseErrorType.UnexpectedError,
|
||||||
|
$"An unexpected error occurred during processing Option at index {index}.",
|
||||||
|
index: index, matchedText: pm.MatchedText, innerException: ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessQuestionContent(AssignmentQuestionEx question, string contentText, List<ParseError> errors)
|
||||||
|
{
|
||||||
|
if (question?.Question == null)
|
||||||
|
{
|
||||||
|
errors.Add(new ParseError(ParseErrorType.Structural,
|
||||||
|
$"Attempted to process content for a non-question type AssignmentQuestionEx (Type: {question?.Type}). Content: '{contentText}'",
|
||||||
|
matchedText: contentText));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(contentText))
|
||||||
|
{
|
||||||
|
question.Question.Title += (string.IsNullOrWhiteSpace(question.Question.Title) ? "" : "\n") + contentText;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Extracts the exam title (simple implementation).
|
|
||||||
/// Logs errors to the provided error list instead of throwing.
|
|
||||||
/// </summary>
|
|
||||||
private string GetExamTitle(string examPaperText)
|
private string GetExamTitle(string examPaperText)
|
||||||
{
|
{
|
||||||
// 内部不再直接抛出异常,而是让外部的 try-catch 负责
|
|
||||||
var firstLine = examPaperText.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
var firstLine = examPaperText.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
||||||
.FirstOrDefault(line => !string.IsNullOrWhiteSpace(line));
|
.FirstOrDefault(line => !string.IsNullOrWhiteSpace(line));
|
||||||
return firstLine ?? "未识别试卷标题";
|
return firstLine ?? "未识别试卷标题";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a subset of the given PotentialMatch list within a specified range.
|
|
||||||
/// Logs errors to the provided error list instead of throwing.
|
|
||||||
/// </summary>
|
|
||||||
private List<PotentialMatch> GetSubMatchesForRange(List<PotentialMatch> allMatches, int start, int end, List<ParseError> errors)
|
|
||||||
{
|
|
||||||
// 输入验证,如果输入错误,记录错误并返回空列表
|
|
||||||
if (start < 0 || end < start)
|
|
||||||
{
|
|
||||||
errors.Add(new ParseError(ParseErrorType.Validation,
|
|
||||||
$"Invalid range provided to GetSubMatchesForRange. Start: {start}, End: {end}.",
|
|
||||||
index: start)); // 使用 start 作为大概索引
|
|
||||||
return new List<PotentialMatch>();
|
|
||||||
}
|
|
||||||
// allMatches 为 null 的情况已经在 BuildExamPaper 顶部处理,这里为了方法的健壮性可以再加一次检查
|
|
||||||
if (allMatches == null)
|
|
||||||
{
|
|
||||||
return new List<PotentialMatch>();
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return allMatches.Where(pm => pm.StartIndex >= start && pm.StartIndex < end).ToList();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
errors.Add(new ParseError(ParseErrorType.UnexpectedError,
|
|
||||||
$"An unexpected error occurred getting sub-matches for range [{start}, {end}).",
|
|
||||||
innerException: ex));
|
|
||||||
return new List<PotentialMatch>(); // 出错时返回空列表
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Processes the content of a Question, mainly for parsing Options and identifying unstructured text.
|
|
||||||
/// Logs errors to the provided error list instead of throwing.
|
|
||||||
/// </summary>
|
|
||||||
private void ProcessQuestionContent(Question question, string contentText, List<PotentialMatch> potentialMatchesInScope, List<ParseError> errors)
|
|
||||||
{
|
|
||||||
// 参数验证,这些是内部方法的契约,如果违反则直接抛出,因为这意味着调用者有错
|
|
||||||
if (question == null) throw new ArgumentNullException(nameof(question), "Question cannot be null in ProcessQuestionContent.");
|
|
||||||
if (contentText == null) throw new ArgumentNullException(nameof(contentText), "Content text cannot be null in ProcessQuestionContent.");
|
|
||||||
if (potentialMatchesInScope == null) throw new ArgumentNullException(nameof(potentialMatchesInScope), "Potential matches in scope cannot be null.");
|
|
||||||
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
int lastOptionEndIndex = 0;
|
|
||||||
|
|
||||||
foreach (var pm in potentialMatchesInScope.OrderBy(p => p.StartIndex))
|
|
||||||
{
|
|
||||||
// 对每个匹配项的内部处理,记录错误但继续
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (pm.Type == MatchType.Option)
|
|
||||||
{
|
|
||||||
// 验证索引,记录错误但继续
|
|
||||||
if (pm.StartIndex < lastOptionEndIndex || pm.StartIndex > contentText.Length || pm.EndIndex > contentText.Length)
|
|
||||||
{
|
|
||||||
errors.Add(new ParseError(ParseErrorType.Validation,
|
|
||||||
$"Option match at index {pm.StartIndex} has invalid indices within content text. MatchedText: '{pm.MatchedText}'. Skipping.",
|
|
||||||
index: pm.StartIndex, matchedText: pm.MatchedText));
|
|
||||||
continue; // 跳过当前选项
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理选项前的文本
|
|
||||||
if (pm.StartIndex > lastOptionEndIndex)
|
|
||||||
{
|
|
||||||
string textBeforeOption = contentText.Substring(lastOptionEndIndex, pm.StartIndex - lastOptionEndIndex).Trim();
|
|
||||||
if (!string.IsNullOrWhiteSpace(textBeforeOption))
|
|
||||||
{
|
|
||||||
question.Stem += (string.IsNullOrWhiteSpace(question.Stem) ? "" : "\n") + textBeforeOption;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegexMatch Groups 验证,记录错误但继续
|
|
||||||
if (pm.RegexMatch.Groups.Count < 3 || string.IsNullOrWhiteSpace(pm.RegexMatch.Groups[1].Value) || string.IsNullOrWhiteSpace(pm.RegexMatch.Groups[2].Value))
|
|
||||||
{
|
|
||||||
errors.Add(new ParseError(ParseErrorType.RegexMatchIssue,
|
|
||||||
$"Option regex match '{pm.MatchedText}' does not have enough groups (expected 3) for label and text. Skipping option.",
|
|
||||||
index: pm.StartIndex, matchedText: pm.MatchedText));
|
|
||||||
lastOptionEndIndex = pm.EndIndex; // 更新索引,避免卡死
|
|
||||||
continue; // 跳过当前选项
|
|
||||||
}
|
|
||||||
|
|
||||||
var newOption = new Option
|
|
||||||
{
|
|
||||||
Label = pm.RegexMatch.Groups[1].Value.Trim(),
|
|
||||||
Text = pm.RegexMatch.Groups[2].Value.Trim()
|
|
||||||
};
|
|
||||||
question.Options.Add(newOption);
|
|
||||||
lastOptionEndIndex = pm.EndIndex;
|
|
||||||
}
|
|
||||||
// TODO: If there are SubQuestion types, they can be processed similarly here.
|
|
||||||
// 你可以在此处添加对子问题的处理逻辑,同样需要小心处理其内容和嵌套。
|
|
||||||
}
|
|
||||||
catch (Exception innerEx)
|
|
||||||
{
|
|
||||||
errors.Add(new ParseError(ParseErrorType.UnexpectedError,
|
|
||||||
$"An unexpected error occurred during processing a potential match ({pm.Type}) within question content.",
|
|
||||||
index: pm.StartIndex, matchedText: pm.MatchedText, innerException: innerEx));
|
|
||||||
lastOptionEndIndex = pm.EndIndex; // 尝试更新索引,避免无限循环
|
|
||||||
continue; // 尝试继续下一个匹配项
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理所有选项之后的剩余文本
|
|
||||||
if (lastOptionEndIndex < contentText.Length)
|
|
||||||
{
|
|
||||||
string remainingContent = contentText.Substring(lastOptionEndIndex).Trim();
|
|
||||||
if (!string.IsNullOrWhiteSpace(remainingContent))
|
|
||||||
{
|
|
||||||
question.Stem += (string.IsNullOrWhiteSpace(question.Stem) ? "" : "\n") + remainingContent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// 捕获 ProcessQuestionContent 整个方法内部的意外错误
|
|
||||||
errors.Add(new ParseError(ParseErrorType.UnexpectedError,
|
|
||||||
$"An unexpected error occurred while processing content for Question '{question.Number}'.",
|
|
||||||
innerException: ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ExamParser
|
public class ExamParser
|
||||||
@@ -721,20 +490,16 @@ namespace TechHelper.Client.Exam
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 解析给定的试卷文本,返回结构化的 ExamPaper 对象。
|
/// 解析给定的试卷文本,返回结构化的 AssignmentEx 对象。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="examPaperText">完整的试卷文本</param>
|
/// <param name="examPaperText">完整的试卷文本</param>
|
||||||
/// <returns>解析后的 ExamPaper 对象</returns>
|
/// <returns>解析后的 AssignmentEx 对象</returns>
|
||||||
public ExamPaper ParseExamPaper(string examPaperText)
|
public AssignmentEx ParseExamPaper(string examPaperText)
|
||||||
{
|
{
|
||||||
// 1. 扫描:一次性扫描整个文本,收集所有潜在的匹配项
|
var assignment = new AssignmentEx();
|
||||||
// Scan 方法现在已经优化为不抛出 ArgumentNullException
|
List<PotentialMatch> allPotentialMatches = _scanner.Scan(examPaperText, assignment.Errors);
|
||||||
List<PotentialMatch> allPotentialMatches = _scanner.Scan(examPaperText);
|
assignment = _builder.BuildExam(examPaperText, allPotentialMatches);
|
||||||
|
return assignment;
|
||||||
// 2. 构建:根据扫描结果和原始文本,线性遍历并构建层级结构
|
|
||||||
// BuildExamPaper 现在会返回一个包含错误列表的 ExamPaper 对象
|
|
||||||
// 外部不再需要捕获内部解析异常,只需检查 ExamPaper.Errors 列表
|
|
||||||
return _builder.BuildExamPaper(examPaperText, allPotentialMatches);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,92 +0,0 @@
|
|||||||
using Entities.DTO;
|
|
||||||
|
|
||||||
namespace TechHelper.Client.Exam
|
|
||||||
{
|
|
||||||
public class ExamStruct
|
|
||||||
{
|
|
||||||
public string Title { get; set; }
|
|
||||||
public List<QuestionItem> Questions { get; set; } = new List<QuestionItem>();
|
|
||||||
|
|
||||||
|
|
||||||
public class QuestionItem
|
|
||||||
{
|
|
||||||
public string Sequence { get; set; } = string.Empty;
|
|
||||||
public string QuestionText { get; set; } = string.Empty;
|
|
||||||
public float Score { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Student
|
|
||||||
{
|
|
||||||
public Guid Id { get; set; } = Guid.NewGuid();
|
|
||||||
public string Name { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class QuestionAnswerStatus
|
|
||||||
{
|
|
||||||
public string QuestionSequence { get; set; } = string.Empty; // 题目序号,例如 "1.1"
|
|
||||||
public string QuestionText { get; set; } = string.Empty; // 题目文本
|
|
||||||
public float QuestionScore { get; set; } // 题目分值
|
|
||||||
public Dictionary<Guid, bool> StudentCorrectStatus { get; set; } = new Dictionary<Guid, bool>();
|
|
||||||
// Key: Student.Id, Value: true 表示正确,false 表示错误
|
|
||||||
}
|
|
||||||
|
|
||||||
public class QuestionRowData
|
|
||||||
{
|
|
||||||
public ExamStruct.QuestionItem QuestionItem { get; set; } // 原始题目信息
|
|
||||||
public Dictionary<Guid, bool> StudentAnswers { get; set; } = new Dictionary<Guid, bool>();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static class ExamStructExtensions
|
|
||||||
{
|
|
||||||
public static ExamStruct GetStruct(this ExamDto dto)
|
|
||||||
{
|
|
||||||
if (dto == null)
|
|
||||||
{
|
|
||||||
return new ExamStruct { Title = "无效试卷", Questions = new List<ExamStruct.QuestionItem>() };
|
|
||||||
}
|
|
||||||
|
|
||||||
var examStruct = new ExamStruct
|
|
||||||
{
|
|
||||||
Title = dto.AssignmentTitle
|
|
||||||
};
|
|
||||||
|
|
||||||
GetSeqRecursive(dto.QuestionGroups, null, examStruct.Questions);
|
|
||||||
|
|
||||||
return examStruct;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 递归方法,用于生成所有题目和子题目的完整序号。
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="currentGroup">当前正在处理的题目组。</param>
|
|
||||||
/// <param name="parentSequence">当前题目组的父级序号(例如:"1", "2.1")。如果为空,则表示顶级题目组。</param>
|
|
||||||
/// <param name="allQuestions">用于收集所有生成题目项的列表。</param>
|
|
||||||
private static void GetSeqRecursive(
|
|
||||||
QuestionGroupDto currentGroup,
|
|
||||||
string? parentSequence,
|
|
||||||
List<ExamStruct.QuestionItem> allQuestions)
|
|
||||||
{
|
|
||||||
string currentGroupSequence = parentSequence != null
|
|
||||||
? $"{parentSequence}.{currentGroup.Index}"
|
|
||||||
: currentGroup.Index.ToString();
|
|
||||||
|
|
||||||
foreach (var subQuestion in currentGroup.SubQuestions)
|
|
||||||
{
|
|
||||||
string fullSequence = $"{currentGroupSequence}.{subQuestion.Index}";
|
|
||||||
allQuestions.Add(new ExamStruct.QuestionItem
|
|
||||||
{
|
|
||||||
Sequence = fullSequence,
|
|
||||||
QuestionText = subQuestion.Stem ?? string.Empty,
|
|
||||||
Score = subQuestion.Score
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var subGroup in currentGroup.SubQuestionGroups)
|
|
||||||
{
|
|
||||||
GetSeqRecursive(subGroup, currentGroupSequence, allQuestions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,36 +0,0 @@
|
|||||||
using System.Xml.Serialization;
|
|
||||||
|
|
||||||
namespace TechHelper.Client.Exam.QuestionSimple
|
|
||||||
{
|
|
||||||
|
|
||||||
[XmlRoot("QG")]
|
|
||||||
public class QuestionGroup
|
|
||||||
{
|
|
||||||
[XmlElement("T")]
|
|
||||||
public string Title { get; set; }
|
|
||||||
[XmlElement("QR")]
|
|
||||||
public string QuestionReference { get; set; } = "";
|
|
||||||
[XmlArray("SQs")]
|
|
||||||
[XmlArrayItem("SQ")]
|
|
||||||
public List<SubQuestion> SubQuestions { get; set; } = new List<SubQuestion>();
|
|
||||||
[XmlArray("SQGs")]
|
|
||||||
[XmlArrayItem("QG")]
|
|
||||||
public List<QuestionGroup> SubQuestionGroups { get; set; } = new List<QuestionGroup>();
|
|
||||||
}
|
|
||||||
public class SubQuestion
|
|
||||||
{
|
|
||||||
[XmlElement("T")]
|
|
||||||
public string Stem { get; set; }
|
|
||||||
[XmlArray("Os")]
|
|
||||||
[XmlArrayItem("O")]
|
|
||||||
public List<Option> Options { get; set; } = new List<Option>();
|
|
||||||
[XmlElement("SA")]
|
|
||||||
public string SampleAnswer { get; set; } = "";
|
|
||||||
}
|
|
||||||
public class Option
|
|
||||||
{
|
|
||||||
[XmlAttribute("V")]
|
|
||||||
public string Value { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
31
TechHelper.Client/Helper/Helper.cs
Normal file
31
TechHelper.Client/Helper/Helper.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,19 +1,19 @@
|
|||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using TechHelper.Client.HttpRepository;
|
using TechHelper.Client.HttpRepository;
|
||||||
|
|
||||||
|
|
||||||
namespace BlazorProducts.Client.HttpInterceptor
|
namespace BlazorProducts.Client.HttpInterceptor
|
||||||
{
|
{
|
||||||
public class HttpInterceptorHandlerService : DelegatingHandler
|
public class HttpInterceptorHandlerService : DelegatingHandler
|
||||||
{
|
{
|
||||||
private readonly NavigationManager _navManager;
|
private readonly NavigationManager _navManager;
|
||||||
private readonly RefreshTokenService2 _refreshTokenService;
|
private readonly IRefreshTokenService _refreshTokenService;
|
||||||
|
|
||||||
public HttpInterceptorHandlerService(
|
public HttpInterceptorHandlerService(
|
||||||
NavigationManager navManager,
|
NavigationManager navManager,
|
||||||
RefreshTokenService2 refreshTokenService)
|
IRefreshTokenService refreshTokenService)
|
||||||
{
|
{
|
||||||
_navManager = navManager;
|
_navManager = navManager;
|
||||||
_refreshTokenService = refreshTokenService;
|
_refreshTokenService = refreshTokenService;
|
||||||
@@ -25,15 +25,14 @@ namespace BlazorProducts.Client.HttpInterceptor
|
|||||||
|
|
||||||
if (absolutePath != null && !absolutePath.Contains("token") && !absolutePath.Contains("account"))
|
if (absolutePath != null && !absolutePath.Contains("token") && !absolutePath.Contains("account"))
|
||||||
{
|
{
|
||||||
var token = await _refreshTokenService.TryRefreshToken();
|
var token = await _refreshTokenService.TryRefreshToken();
|
||||||
if (!string.IsNullOrEmpty(token))
|
if (!string.IsNullOrEmpty(token))
|
||||||
{
|
{
|
||||||
|
|
||||||
request.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
|
request.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var response = await base.SendAsync(request, cancellationToken);
|
var response = await base.SendAsync(request, cancellationToken);
|
||||||
|
|
||||||
await HandleResponse(response);
|
await HandleResponse(response);
|
||||||
@@ -45,17 +44,17 @@ namespace BlazorProducts.Client.HttpInterceptor
|
|||||||
{
|
{
|
||||||
if (response is null)
|
if (response is null)
|
||||||
{
|
{
|
||||||
|
|
||||||
_navManager.NavigateTo("/error");
|
_navManager.NavigateTo("/error");
|
||||||
throw new HttpResponseException("服务器不可用。");
|
throw new HttpResponseException("服务器不可用。");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
|
|
||||||
var errorContent = await response.Content.ReadAsStringAsync();
|
var errorContent = await response.Content.ReadAsStringAsync();
|
||||||
Console.WriteLine($"HTTP 错误: {response.StatusCode}. 详情: {errorContent}");
|
Console.WriteLine($"HTTP 错误: {response.StatusCode}. 详情: {errorContent}");
|
||||||
|
|
||||||
switch (response.StatusCode)
|
switch (response.StatusCode)
|
||||||
{
|
{
|
||||||
@@ -67,8 +66,15 @@ namespace BlazorProducts.Client.HttpInterceptor
|
|||||||
// 400 Bad Request error. Often, you don't navigate for this; you display validation errors on the UI.
|
// 400 Bad Request error. Often, you don't navigate for this; you display validation errors on the UI.
|
||||||
break;
|
break;
|
||||||
case HttpStatusCode.Unauthorized:
|
case HttpStatusCode.Unauthorized:
|
||||||
// 401 Unauthorized error. Navigate to an unauthorized page or login page.
|
var token = await _refreshTokenService.TryRefreshToken();
|
||||||
_navManager.NavigateTo("/unauthorized"); // Or: _navManager.NavigateTo("/authentication/login");
|
if (!string.IsNullOrEmpty(token))
|
||||||
|
{
|
||||||
|
_navManager.NavigateTo(_navManager.Uri, forceLoad: true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_navManager.NavigateTo("/unauthorized");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// For all other errors, navigate to a general error page.
|
// For all other errors, navigate to a general error page.
|
||||||
|
@@ -20,13 +20,12 @@ namespace TechHelper.Client.HttpRepository
|
|||||||
private readonly ILocalStorageService _localStorageService;
|
private readonly ILocalStorageService _localStorageService;
|
||||||
private readonly NavigationManager _navigationManager;
|
private readonly NavigationManager _navigationManager;
|
||||||
|
|
||||||
// 构造函数现在直接接收 HttpClient
|
public AuthenticationClientService(HttpClient client,
|
||||||
public AuthenticationClientService(HttpClient client, // <-- 修正点:直接注入 HttpClient
|
|
||||||
AuthenticationStateProvider authenticationStateProvider,
|
AuthenticationStateProvider authenticationStateProvider,
|
||||||
ILocalStorageService localStorageService,
|
ILocalStorageService localStorageService,
|
||||||
NavigationManager navigationManager)
|
NavigationManager navigationManager)
|
||||||
{
|
{
|
||||||
_client = client; // <-- 修正点:直接赋值
|
_client = client;
|
||||||
_localStorageService = localStorageService;
|
_localStorageService = localStorageService;
|
||||||
_stateProvider = authenticationStateProvider;
|
_stateProvider = authenticationStateProvider;
|
||||||
_navigationManager = navigationManager;
|
_navigationManager = navigationManager;
|
||||||
@@ -34,8 +33,6 @@ namespace TechHelper.Client.HttpRepository
|
|||||||
|
|
||||||
public async Task<AuthResponseDto> LoginAsync(UserForAuthenticationDto userForAuthenticationDto)
|
public async Task<AuthResponseDto> LoginAsync(UserForAuthenticationDto userForAuthenticationDto)
|
||||||
{
|
{
|
||||||
// 移除 using (_client = _clientFactory.CreateClient("Default"))
|
|
||||||
// _client 已经是注入的实例,直接使用它
|
|
||||||
var reponse = await _client.PostAsJsonAsync("account/login",
|
var reponse = await _client.PostAsJsonAsync("account/login",
|
||||||
userForAuthenticationDto);
|
userForAuthenticationDto);
|
||||||
|
|
||||||
@@ -71,7 +68,6 @@ namespace TechHelper.Client.HttpRepository
|
|||||||
|
|
||||||
public async Task<string> RefreshTokenAsync()
|
public async Task<string> RefreshTokenAsync()
|
||||||
{
|
{
|
||||||
// 移除 using (_client = _clientFactory.CreateClient("Default"))
|
|
||||||
var token = _localStorageService.GetItem<string>("authToken");
|
var token = _localStorageService.GetItem<string>("authToken");
|
||||||
var refreshToken = _localStorageService.GetItem<string>("refreshToken");
|
var refreshToken = _localStorageService.GetItem<string>("refreshToken");
|
||||||
|
|
||||||
@@ -96,7 +92,6 @@ namespace TechHelper.Client.HttpRepository
|
|||||||
|
|
||||||
public async Task<ResponseDto> RegisterUserAsync(UserForRegistrationDto userForRegistrationDto)
|
public async Task<ResponseDto> RegisterUserAsync(UserForRegistrationDto userForRegistrationDto)
|
||||||
{
|
{
|
||||||
// 移除 using (_client = _clientFactory.CreateClient("Default"))
|
|
||||||
userForRegistrationDto.ClientURI = Path.Combine(
|
userForRegistrationDto.ClientURI = Path.Combine(
|
||||||
_navigationManager.BaseUri, "emailconfirmation");
|
_navigationManager.BaseUri, "emailconfirmation");
|
||||||
|
|
||||||
@@ -167,6 +162,9 @@ namespace TechHelper.Client.HttpRepository
|
|||||||
((AuthStateProvider)_stateProvider).NotifyUserAuthentication(
|
((AuthStateProvider)_stateProvider).NotifyUserAuthentication(
|
||||||
result.Token);
|
result.Token);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
_client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(
|
_client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(
|
||||||
"bearer", result.Token);
|
"bearer", result.Token);
|
||||||
|
|
||||||
|
@@ -24,16 +24,15 @@ namespace TechHelper.Client.HttpRepository
|
|||||||
var authState = await _authenticationStateProvider.Value.GetAuthenticationStateAsync();
|
var authState = await _authenticationStateProvider.Value.GetAuthenticationStateAsync();
|
||||||
var user = authState.User;
|
var user = authState.User;
|
||||||
|
|
||||||
// 如果 user 或 claims 为空,表示用户未认证,直接返回空字符串
|
|
||||||
if (user?.Identity == null || !user.Identity.IsAuthenticated)
|
if (user?.Identity == null || !user.Identity.IsAuthenticated)
|
||||||
{
|
{
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
var expClaim = user.FindFirst(c => c.Type.Equals("exp"))?.Value; // 使用 ?. 防止空引用
|
var expClaim = user.FindFirst(c => c.Type.Equals("exp"))?.Value;
|
||||||
if (string.IsNullOrEmpty(expClaim))
|
if (string.IsNullOrEmpty(expClaim))
|
||||||
{
|
{
|
||||||
return string.Empty; // 没有过期时间声明,也直接返回
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
var expTime = DateTimeOffset.FromUnixTimeSeconds(
|
var expTime = DateTimeOffset.FromUnixTimeSeconds(
|
||||||
@@ -41,9 +40,11 @@ namespace TechHelper.Client.HttpRepository
|
|||||||
|
|
||||||
var diff = expTime - DateTime.UtcNow;
|
var diff = expTime - DateTime.UtcNow;
|
||||||
|
|
||||||
// 只有当令牌即将过期时才尝试刷新
|
|
||||||
|
var n = DateTime.UtcNow;
|
||||||
|
|
||||||
if (diff.TotalMinutes <= 2)
|
if (diff.TotalMinutes <= 2)
|
||||||
return await _authenticationClientService.Value.RefreshTokenAsync(); // 访问 .Value 来调用方法
|
return await _authenticationClientService.Value.RefreshTokenAsync();
|
||||||
|
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
@@ -1,46 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Components.Authorization;
|
|
||||||
|
|
||||||
namespace TechHelper.Client.HttpRepository
|
|
||||||
{
|
|
||||||
public class RefreshTokenService2
|
|
||||||
{
|
|
||||||
private readonly Lazy<AuthenticationStateProvider> _authenticationStateProvider;
|
|
||||||
private readonly Lazy<IAuthenticationClientService> _authenticationClientService;
|
|
||||||
|
|
||||||
public RefreshTokenService2(IServiceProvider serviceProvider)
|
|
||||||
{
|
|
||||||
_authenticationStateProvider = new Lazy<AuthenticationStateProvider>(
|
|
||||||
() => serviceProvider.GetRequiredService<AuthenticationStateProvider>());
|
|
||||||
|
|
||||||
_authenticationClientService = new Lazy<IAuthenticationClientService>(
|
|
||||||
() => serviceProvider.GetRequiredService<IAuthenticationClientService>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string> TryRefreshToken()
|
|
||||||
{
|
|
||||||
var authState = await _authenticationStateProvider.Value.GetAuthenticationStateAsync();
|
|
||||||
var user = authState.User;
|
|
||||||
|
|
||||||
if (user?.Identity == null || !user.Identity.IsAuthenticated)
|
|
||||||
{
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
var expClaim = user.FindFirst(c => c.Type.Equals("exp"))?.Value;
|
|
||||||
if (string.IsNullOrEmpty(expClaim))
|
|
||||||
{
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
var expTime = DateTimeOffset.FromUnixTimeSeconds(
|
|
||||||
Convert.ToInt64(expClaim));
|
|
||||||
|
|
||||||
var diff = expTime - DateTime.UtcNow;
|
|
||||||
|
|
||||||
if (diff.TotalMinutes <= 2)
|
|
||||||
return await _authenticationClientService.Value.RefreshTokenAsync();
|
|
||||||
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -4,83 +4,63 @@
|
|||||||
<MudSnackbarProvider />
|
<MudSnackbarProvider />
|
||||||
<MudPopoverProvider />
|
<MudPopoverProvider />
|
||||||
|
|
||||||
@*
|
<MudLayout>
|
||||||
<MudPaper Style="position: fixed;
|
<MudAppBar Elevation="0" Class="rounded-xl" Style="background-color: transparent; border:none">
|
||||||
top: 0;
|
<MudBreakpointProvider>
|
||||||
left: 0;
|
<MudHidden Breakpoint="Breakpoint.SmAndDown" Invert=true>
|
||||||
width: 100vw;
|
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Primary" Edge="Edge.Start" OnClick="@((e) => DrawerToggle())" />
|
||||||
height: 100vh;
|
</MudHidden>
|
||||||
background-image: url('/ref/bg4.jpg');
|
<MudHidden Breakpoint="Breakpoint.SmAndDown">
|
||||||
background-size: cover;
|
<SearchBar></SearchBar>
|
||||||
background-position: center center;
|
<MudButton Class="mt-1">application</MudButton>
|
||||||
background-repeat: no-repeat;
|
</MudHidden>
|
||||||
filter: blur(10px);
|
</MudBreakpointProvider>
|
||||||
z-index: -1;">
|
<MudSpacer />
|
||||||
</MudPaper>
|
<MudIconButton Icon="@Icons.Material.Filled.MoreVert" Color="Color.Primary" Edge="Edge.End" />
|
||||||
<MudPaper Class="d-flex flex-column flex-grow-0 overflow-auto" Style="height: 100vh; background-color:#22222200">
|
</MudAppBar>
|
||||||
|
<MudDrawer @bind-Open="_drawerOpen" Height="100%" Elevation="0" Style="background-color:#f5f6fb">
|
||||||
|
<MudDrawerHeader Class="h-100 d-flex flex-grow-1" Style="background-color:#f5f6fb">
|
||||||
|
<MudPaper Width="250px" Class="d-flex py-3 flex-column justify-content-between rounded-xl" Elevation="3">
|
||||||
|
<MudNavMenu Bordered="true" Dense="true" Rounded="true" Color="Color.Error" Margin="Margin.Dense">
|
||||||
|
<ApplicationMainIconCard></ApplicationMainIconCard>
|
||||||
|
<MudDivider Class="my-2" />
|
||||||
|
<MudNavLink Href="/">Home</MudNavLink>
|
||||||
|
<MudNavLink Href="/exam">Exam</MudNavLink>
|
||||||
|
<MudNavLink Href="/students">Students</MudNavLink>
|
||||||
|
|
||||||
|
<MudSpacer />
|
||||||
<MudPaper Class="d-flex flex-column flex-grow-1 overflow-hidden" Style="background-color:transparent">
|
<MudNavLink Class="align-content-end" Href="/about">About</MudNavLink>
|
||||||
|
</MudNavMenu>
|
||||||
|
<MudSpacer />
|
||||||
<MudPaper Elevation="3" Height="10%" Class=" d-flex justify-content-around flex-grow-0" Style="background-color:#ffffff55">
|
<MudNavMenu Class="align-content-end " Bordered="true" Dense="true" Rounded="true" Margin="Margin.Dense">
|
||||||
<NavBar Class="flex-column flex-grow-1 " Style="background-color:transparent" />
|
<TechHelper.Client.Pages.Global.LoginInOut.LoginInOut></TechHelper.Client.Pages.Global.LoginInOut.LoginInOut>
|
||||||
<AuthLinks Class="flex-column flex-grow-0 " Style="background-color:transparent" />
|
<MudNavLink Class="align-content-end" Href="/Account/Manage">Setting</MudNavLink>
|
||||||
</MudPaper>
|
</MudNavMenu>
|
||||||
|
|
||||||
|
|
||||||
<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>
|
||||||
|
</MudDrawerHeader>
|
||||||
|
</MudDrawer>
|
||||||
<MudPaper Elevation="3" Class="d-flex flex-grow-1 pa-3 ma-1 overflow-hidden" Style="background-color:#ffffff22 ">
|
<MudMainContent Style="background: #f5f6fb">
|
||||||
@Body
|
<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;
|
||||||
|
|
||||||
</MudPaper>
|
void DrawerToggle()
|
||||||
|
{
|
||||||
|
_drawerOpen = !_drawerOpen;
|
||||||
</MudPaper>
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</MudPaper>
|
|
||||||
|
|
||||||
*@
|
|
||||||
|
|
||||||
<MudPaper Style="position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
background-image: url('/ref/bg4.jpg');
|
|
||||||
background-size: cover;
|
|
||||||
background-position: center center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
filter: blur(10px);
|
|
||||||
z-index: -1;">
|
|
||||||
</MudPaper>
|
|
||||||
<MudPaper Style="background-color:transparent ; height:100vh" Class="overflow-hidden">
|
|
||||||
|
|
||||||
<MudPaper Class="justify-content-center" Style="background-color:blue; height: 50px">
|
|
||||||
<MudStack Row="true" Class="justify-content-between">
|
|
||||||
|
|
||||||
<NavBar Class="flex-grow-1" Style="background-color:transparent; color:white" />
|
|
||||||
<AuthLinks Class="justify-content-end " Style="background-color:transparent; color:white" />
|
|
||||||
</MudStack>
|
|
||||||
</MudPaper>
|
|
||||||
|
|
||||||
<MudPaper Class="d-flex flex-grow-0 " Style="background-color:#30303022; height:calc(100vh - 50px)">
|
|
||||||
@* <MudPaper Class="ma-1" Width="200px">
|
|
||||||
|
|
||||||
</MudPaper> *@
|
|
||||||
|
|
||||||
<MudPaper Class="d-flex ma-1 flex-grow-1 overflow-auto">
|
|
||||||
@Body
|
|
||||||
</MudPaper>
|
|
||||||
</MudPaper>
|
|
||||||
</MudPaper>
|
|
0
TechHelper.Client/Pages/Author/Component.razor
Normal file
0
TechHelper.Client/Pages/Author/Component.razor
Normal file
@@ -1,48 +1,59 @@
|
|||||||
@page "/login"
|
@page "/login"
|
||||||
|
|
||||||
<MudText Typo="Typo.h2"> Login Account </MudText>
|
<div class="d-flex flex-column flex-grow-1 page-containerr">
|
||||||
|
|
||||||
<EditForm Model="@_userForAuth" OnValidSubmit="Logining" FormName="LoginingForm">
|
<div class="d-flex mud-paper-80-percent-centeredd w-75">
|
||||||
<DataAnnotationsValidator />
|
<MudPaper Class="d-flex flex-grow-1 ma-0 pa-0" style="background-color:transparent; min-height:100%" Elevation="0">
|
||||||
<MudGrid>
|
|
||||||
<MudItem xs="12" sm="7">
|
|
||||||
<MudCard>
|
|
||||||
<MudCardContent>
|
|
||||||
<MudTextField Label="Email" Class="mt-3"
|
|
||||||
@bind-Value="_userForAuth.Email" For="@(() => _userForAuth.Email)" />
|
|
||||||
<MudTextField Label="Password" HelperText="Choose a strong password" Class="mt-3"
|
|
||||||
@bind-Value="_userForAuth.Password" For="@(() => _userForAuth.Password)" InputType="InputType.Password" />
|
|
||||||
</MudCardContent>
|
|
||||||
<MudCardActions>
|
|
||||||
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary" Class="ml-auto">Register</MudButton>
|
|
||||||
</MudCardActions>
|
|
||||||
|
|
||||||
<div class="nav-item px-3">
|
<MudGrid Class="d-flex flex-grow-1" style="background-color:transparent; min-height:100%">
|
||||||
<NavLink class="nav-link" href="forgotpassword">
|
<MudItem xs="12" sm="4" style="background-color:transparent">
|
||||||
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span>Forgot Password
|
<MudPaper Class="d-flex flex-column align-start justify-start mud-width-full h-100 pa-8" Elevation="0" Style="background-color:transparent">
|
||||||
</NavLink>
|
<MudText Style="color:#ffffff" Typo="Typo.h4">TechHelper</MudText>
|
||||||
</div>
|
<MudText Style="color:#ffffff" Typo="Typo.body2">轻松管理,高效学习。</MudText>
|
||||||
</MudCard>
|
<MudSpacer />
|
||||||
</MudItem>
|
<MudText Style="color:#ffffff" Typo="Typo.h4">教育不是注满一桶水,</MudText>
|
||||||
<MudItem xs="12" sm="5">
|
<MudText Style="color:#ffffff" Typo="Typo.h4"> 而是点燃一把火。</MudText>
|
||||||
<MudPaper Class="pa-4 mud-height-full">
|
<MudImage Alt="Hello World" Fluid="true" Src="ref/UnFinish.png" />
|
||||||
<MudText Typo="Typo.subtitle2">Validation Summary</MudText>
|
</MudPaper>
|
||||||
@if (!ShowRegistrationErrors)
|
</MudItem>
|
||||||
{
|
|
||||||
<MudText Color="Color.Success">Success</MudText>
|
<MudItem xs="12" sm="8" style="background-color:transparent">
|
||||||
}
|
|
||||||
else
|
<MudPaper Class="d-flex flex-row flex-grow-1 justify-center rounded-xl px-0 mud-height-full" >
|
||||||
{
|
|
||||||
<MudText Color="@Color.Error">
|
<EditForm Model="@_userForAuth" OnValidSubmit="Logining" FormName="LoginingForm" class="w-100">
|
||||||
<ValidationSummary />
|
<DataAnnotationsValidator />
|
||||||
</MudText>
|
<MudPaper Class="d-flex flex-column flex-grow-1 rounded-xl px-15 justify-content-center pt-15 w-100 " Elevation="0" Outlined="false">
|
||||||
}
|
<MudText Typo="Typo.h5"> <b>登录账户</b> </MudText>
|
||||||
</MudPaper>
|
<MudTextField Label="Email" Class="mt-3"
|
||||||
</MudItem>
|
@bind-Value="_userForAuth.Email" For="@(() => _userForAuth.Email)" />
|
||||||
<MudItem xs="12">
|
<MudTextField Label="Password" HelperText="Choose a strong password" Class="mt-3"
|
||||||
<MudText Typo="Typo.body2" Align="Align.Center">
|
@bind-Value="_userForAuth.Password" For="@(() => _userForAuth.Password)" InputType="InputType.Password" />
|
||||||
Fill out the form correctly to see the success message.
|
|
||||||
</MudText>
|
|
||||||
</MudItem>
|
<MudStack Row=true Class="align-content-center justify-content-start my-3">
|
||||||
</MudGrid>
|
<MudCheckBox @bind-Value="Basic_CheckBox2" Color="Color.Primary"></MudCheckBox>
|
||||||
</EditForm>
|
<MudText Typo="Typo.body2" Align=Align.Center Class="align-content-center"> 点击登录,即表示你同意我们的服务条款和隐私政策。 </MudText>
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary" Class="w-100 mx-0 my-3 justify-content-center rounded-pill">LOGIN</MudButton>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<MudText Typo="Typo.body2" Class="justify-content-center mx-auto mt-5" Color="Color.Dark">
|
||||||
|
还没有账户?
|
||||||
|
<a href="/register" style="color: blue;">Sign in</a>
|
||||||
|
</MudText>
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
</EditForm>
|
||||||
|
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@@ -16,6 +16,8 @@ namespace TechHelper.Client.Pages.Author
|
|||||||
[Inject]
|
[Inject]
|
||||||
public NavigationManager NavigationManager { get; set; }
|
public NavigationManager NavigationManager { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public bool Basic_CheckBox2 { get; set; } = true;
|
||||||
public bool ShowRegistrationErrors { get; set; }
|
public bool ShowRegistrationErrors { get; set; }
|
||||||
public string Error { get; set; }
|
public string Error { get; set; }
|
||||||
|
|
||||||
|
29
TechHelper.Client/Pages/Author/Login.razor.css
Normal file
29
TechHelper.Client/Pages/Author/Login.razor.css
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
.page-containerr {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%; /* <20>ӿڸ߶ȣ<DFB6>ȷ<EFBFBD><C8B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>㹻<EFBFBD>ĸ߶<C4B8><DFB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ԫ<EFBFBD><D4AA> */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column; /* <20><>ֱ<EFBFBD><D6B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ԫ<EFBFBD><D4AA> */
|
||||||
|
align-items: center; /* ˮƽ<CBAE><C6BD><EFBFBD><EFBFBD> */
|
||||||
|
justify-content: center; /* <20><>ֱ<EFBFBD><D6B1><EFBFBD><EFBFBD> */
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mud-paper-full-width {
|
||||||
|
width: 80%; /* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><D2BB>paper<65>Ŀ<EFBFBD><C4BF><EFBFBD> */
|
||||||
|
height: auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mud-paper-80-percent-centeredd {
|
||||||
|
width: 80%;
|
||||||
|
min-height: 80%;
|
||||||
|
padding: 0px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background-color: #959dff;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 40px;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
@@ -1,96 +1,96 @@
|
|||||||
@page "/register"
|
@page "/register"
|
||||||
@using System.ComponentModel.DataAnnotations
|
@using System.ComponentModel.DataAnnotations
|
||||||
|
@using Microsoft.AspNetCore.Components
|
||||||
@using Entities.Contracts
|
@using Entities.Contracts
|
||||||
|
@using System.Globalization;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@inject ISnackbar Snackbar
|
@inject ISnackbar Snackbar
|
||||||
|
|
||||||
|
<div class="d-flex flex-grow-1 page-container">
|
||||||
|
|
||||||
<MudText Typo="Typo.h2"> Create Account </MudText>
|
<div class="d-flex mud-paper-80-percent-centered w-75">
|
||||||
|
<MudPaper Class="d-flex flex-grow-1 ma-0 pa-0" style="background-color:transparent; min-height:100%" Elevation="0">
|
||||||
|
|
||||||
<EditForm Model="@_userForRegistration" OnValidSubmit="Register" FormName="RegistrationForm">
|
<MudGrid Class="d-flex flex-grow-1" style="background-color:transparent; min-height:100%">
|
||||||
<DataAnnotationsValidator />
|
<MudItem xs="12" sm="4" style="background-color:transparent">
|
||||||
<MudGrid>
|
<MudPaper Class="d-flex flex-column align-start justify-start mud-width-full h-100 pa-8" Elevation="0" Style="background-color:transparent">
|
||||||
<MudItem xs="12" sm="7">
|
<MudText Style="color:#ffffff" Typo="Typo.h4">TechHelper</MudText>
|
||||||
<MudCard>
|
<MudText Style="color:#ffffff" Typo="Typo.body2">快速注册,开始你的管理之旅。</MudText>
|
||||||
<MudCardContent>
|
<MudSpacer />
|
||||||
<MudTextField Label="Name" HelperText="Max. 8 characters"
|
<MudText Style="color:#ffffff" Typo="Typo.h4">学而不思则罔,</MudText>
|
||||||
@bind-Value="_userForRegistration.Name" For="@(() => _userForRegistration.Email)" />
|
<MudText Style="color:#ffffff" Typo="Typo.h4"> 思而不学则殆。</MudText>
|
||||||
<MudTextField Label="Email" Class="mt-3"
|
<MudImage Alt="Hello World" Fluid="true" Src="ref/UnFinish.png" />
|
||||||
@bind-Value="_userForRegistration.Email" For="@(() => _userForRegistration.Email)" />
|
</MudPaper>
|
||||||
<MudTextField Label="Password" HelperText="Choose a strong password" Class="mt-3"
|
</MudItem>
|
||||||
@bind-Value="_userForRegistration.Password" For="@(() => _userForRegistration.Password)" InputType="InputType.Password" />
|
|
||||||
<MudTextField Label="Password" HelperText="Repeat the password" Class="mt-3"
|
|
||||||
@bind-Value="_userForRegistration.ConfirmPassword" For="@(() => _userForRegistration.ConfirmPassword)" InputType="InputType.Password" />
|
|
||||||
<MudRadioGroup T="UserRoles" Label="Roles" @bind-Value="_userForRegistration.Roles">
|
|
||||||
@foreach (UserRoles item in Enum.GetValues(typeof(UserRoles)))
|
|
||||||
{
|
|
||||||
if (item != UserRoles.Administrator)
|
|
||||||
{
|
|
||||||
<MudRadio Value="@item">@item.ToString()</MudRadio>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</MudRadioGroup>
|
|
||||||
<MudStack Row="true">
|
|
||||||
|
|
||||||
<MudTextField Label="Class"
|
<MudItem xs="12" sm="8" style="background-color:transparent">
|
||||||
HelperText="Enter a class number between 1 and 14."
|
|
||||||
Class="mt-3"
|
|
||||||
@bind-Value="_userForRegistration.Class"
|
|
||||||
For="@(() => _userForRegistration.Class)"
|
|
||||||
InputType="InputType.Number"
|
|
||||||
Required="true"
|
|
||||||
RequiredError="Class is required." />
|
|
||||||
|
|
||||||
<MudTextField Label="Grade"
|
<MudPaper Class="d-flex flex-row flex-grow-1 justify-center rounded-xl px-20 mud-height-full">
|
||||||
HelperText="Enter a grade number between 1 and 6."
|
<EditForm Model="@_userForRegistration" OnValidSubmit="Register" FormName="RegistrationForm" class="w-100">
|
||||||
Class="mt-3"
|
<DataAnnotationsValidator />
|
||||||
@bind-Value="_userForRegistration.Grade"
|
<MudPaper Class="d-flex flex-column flex-grow-1 rounded-xl px-5 justify-content-center pt-15 w-100 " Elevation="0" Outlined="false">
|
||||||
For="@(() => _userForRegistration.Grade)"
|
<MudText Typo="Typo.h5"> <b>注册账户</b> </MudText>
|
||||||
InputType="InputType.Number"
|
<MudTextField Label="Name" HelperText="Max. 8 characters"
|
||||||
Required="true"
|
@bind-Value="_userForRegistration.Name" For="@(() => _userForRegistration.Email)" />
|
||||||
RequiredError="Grade is required." />
|
<MudTextField Label="Email" Class="mt-3"
|
||||||
</MudStack>
|
@bind-Value="_userForRegistration.Email" For="@(() => _userForRegistration.Email)" />
|
||||||
|
<MudTextField Label="Password" HelperText="Choose a strong password" Class="mt-3"
|
||||||
|
@bind-Value="_userForRegistration.Password" For="@(() => _userForRegistration.Password)" InputType="InputType.Password" />
|
||||||
|
<MudTextField Label="Password" HelperText="Repeat the password" Class="mt-3"
|
||||||
|
@bind-Value="_userForRegistration.ConfirmPassword" For="@(() => _userForRegistration.ConfirmPassword)" InputType="InputType.Password" />
|
||||||
|
<MudChipSet T="UserRoles" @bind-SelectedValue="_userForRegistration.Roles" CheckMark SelectionMode="SelectionMode.SingleSelection" Class="w-100">
|
||||||
|
<MudChip Text="Student" Color="Color.Primary" Value="@UserRoles.Student">Student</MudChip>
|
||||||
|
<MudChip Text="Teacher" Color="Color.Secondary" Value="@UserRoles.Teacher">Teacher</MudChip>
|
||||||
|
</MudChipSet>
|
||||||
|
|
||||||
<MudTextField Label="Phone Number"
|
<MudStack Row="true">
|
||||||
HelperText="Enter your phone number (optional, 7-20 digits)."
|
<MudSelect T="GradeEnum" Value="grade" Label="Select Grade" AdornmentColor="Color.Secondary" ValueChanged="HandleSelectedValuesChanged">
|
||||||
Class="mt-3"
|
@foreach (GradeEnum item in Enum.GetValues(typeof(GradeEnum)))
|
||||||
@bind-Value="_userForRegistration.PhoneNumber"
|
{
|
||||||
For="@(() => _userForRegistration.PhoneNumber)"
|
<MudSelectItem Value="@item">@item</MudSelectItem>
|
||||||
InputType="InputType.Telephone" /> <MudTextField Label="Home Address"
|
}
|
||||||
HelperText="Enter your home address (optional, max 200 characters)."
|
</MudSelect>
|
||||||
Class="mt-3"
|
|
||||||
@bind-Value="_userForRegistration.HomeAddress"
|
|
||||||
For="@(() => _userForRegistration.HomeAddress)"
|
|
||||||
Lines="3" />
|
|
||||||
</MudCardContent>
|
|
||||||
<MudCardActions>
|
|
||||||
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary" Class="ml-auto">Register</MudButton>
|
|
||||||
</MudCardActions>
|
|
||||||
</MudCard>
|
|
||||||
|
|
||||||
</MudItem>
|
|
||||||
<MudItem xs="12" sm="5">
|
|
||||||
<MudPaper Class="pa-4 mud-height-full">
|
|
||||||
<MudText Typo="Typo.subtitle2">Validation Summary</MudText>
|
|
||||||
@if (success)
|
|
||||||
{
|
|
||||||
<MudText Color="Color.Success">Success</MudText>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<MudText Color="@Color.Error">
|
|
||||||
<ValidationSummary />
|
|
||||||
</MudText>
|
|
||||||
}
|
|
||||||
</MudPaper>
|
|
||||||
</MudItem>
|
|
||||||
<MudItem xs="12">
|
|
||||||
<MudText Typo="Typo.body2" Align="Align.Center">
|
|
||||||
Fill out the form correctly to see the success message.
|
|
||||||
</MudText>
|
|
||||||
</MudItem>
|
|
||||||
</MudGrid>
|
|
||||||
|
|
||||||
</EditForm>
|
|
||||||
|
|
||||||
|
|
||||||
|
<MudSelect T="byte" Value="selectclass" Label="Select Class" AdornmentColor="Color.Secondary" ValueChanged="HandleListSelectedValuesChanged">
|
||||||
|
@foreach (byte item in Classes)
|
||||||
|
{
|
||||||
|
<MudSelectItem Value="@item">@item</MudSelectItem>
|
||||||
|
}
|
||||||
|
</MudSelect>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
|
||||||
|
<MudStack Row=true Class="align-content-center justify-content-start my-3">
|
||||||
|
<MudCheckBox @bind-Value="Basic_CheckBox2" Color="Color.Primary"></MudCheckBox>
|
||||||
|
<MudText Typo="Typo.body2" Align=Align.Center Class="align-content-center"> 点击注册,即表示你同意我们的服务条款和隐私政策。 </MudText>
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary" Class="w-100 mx-0 my-3 justify-content-center rounded-pill">LOGIN</MudButton>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<MudText Typo="Typo.body2" Class="justify-content-center mx-auto mt-5" Color="Color.Dark">
|
||||||
|
已有账户?
|
||||||
|
<a href="/login" style="color: blue;">Sign up</a>
|
||||||
|
</MudText>
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
</EditForm>
|
||||||
|
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@@ -5,6 +5,7 @@ using MudBlazor;
|
|||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using TechHelper.Features;
|
using TechHelper.Features;
|
||||||
using Entities.Contracts;
|
using Entities.Contracts;
|
||||||
|
using TechHelper.Client.Services;
|
||||||
|
|
||||||
namespace TechHelper.Client.Pages.Author
|
namespace TechHelper.Client.Pages.Author
|
||||||
{
|
{
|
||||||
@@ -12,14 +13,19 @@ namespace TechHelper.Client.Pages.Author
|
|||||||
{
|
{
|
||||||
|
|
||||||
private UserForRegistrationDto _userForRegistration = new UserForRegistrationDto();
|
private UserForRegistrationDto _userForRegistration = new UserForRegistrationDto();
|
||||||
|
private GradeEnum grade = GradeEnum.Unknown;
|
||||||
[Inject]
|
[Inject]
|
||||||
public IAuthenticationClientService AuthenticationService { get; set; }
|
public IAuthenticationClientService AuthenticationService { get; set; }
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
public NavigationManager NavigationManager { get; set; }
|
public NavigationManager NavigationManager { get; set; }
|
||||||
|
[Inject]
|
||||||
|
public IClassServices ClassServices { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public bool Basic_CheckBox2 { get; set; } = true;
|
||||||
|
public byte selectclass { get; set; } = 0;
|
||||||
|
public List<byte> Classes { get; set; } = new List<byte>();
|
||||||
|
|
||||||
public bool ShowRegistrationErrors { get; set; }
|
public bool ShowRegistrationErrors { get; set; }
|
||||||
public string[] Errors { get; set; }
|
public string[] Errors { get; set; }
|
||||||
@@ -51,6 +57,25 @@ namespace TechHelper.Client.Pages.Author
|
|||||||
[Inject]
|
[Inject]
|
||||||
public IEmailSender emailSender { get; set; }
|
public IEmailSender emailSender { get; set; }
|
||||||
|
|
||||||
|
private async void HandleSelectedValuesChanged(GradeEnum selectedValues)
|
||||||
|
{
|
||||||
|
grade = selectedValues;
|
||||||
|
Snackbar.Add("<22><><EFBFBD>ȴ<EFBFBD>,<2C><><EFBFBD>ڻ<EFBFBD>ȡ<EFBFBD><C8A1><EFBFBD>꼶<EFBFBD>༶", Severity.Info);
|
||||||
|
var result = await ClassServices.GetGradeClasses((byte)selectedValues);
|
||||||
|
if (result.Status)
|
||||||
|
{
|
||||||
|
Classes = result.Result as List<byte> ?? new List<byte>();
|
||||||
|
Snackbar.Add("<22><>ȡ<EFBFBD>ɹ<EFBFBD>", Severity.Success);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Snackbar.Add("<22><>ȡʧ<C8A1><CAA7>", Severity.Error);
|
||||||
|
}
|
||||||
|
private void HandleListSelectedValuesChanged(byte selectedValues)
|
||||||
|
{
|
||||||
|
selectclass = selectedValues;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public async void SendEmail()
|
public async void SendEmail()
|
||||||
{
|
{
|
||||||
string eamilTo = "1928360026@qq.com";
|
string eamilTo = "1928360026@qq.com";
|
||||||
|
29
TechHelper.Client/Pages/Author/Registration.razor.css
Normal file
29
TechHelper.Client/Pages/Author/Registration.razor.css
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
.page-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%; /* <20>ӿڸ߶ȣ<DFB6>ȷ<EFBFBD><C8B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>㹻<EFBFBD>ĸ߶<C4B8><DFB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ԫ<EFBFBD><D4AA> */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column; /* <20><>ֱ<EFBFBD><D6B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ԫ<EFBFBD><D4AA> */
|
||||||
|
align-items: center; /* ˮƽ<CBAE><C6BD><EFBFBD><EFBFBD> */
|
||||||
|
justify-content: center; /* <20><>ֱ<EFBFBD><D6B1><EFBFBD><EFBFBD> */
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mud-paper-full-width {
|
||||||
|
width: 80%; /* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><D2BB>paper<65>Ŀ<EFBFBD><C4BF><EFBFBD> */
|
||||||
|
height: auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mud-paper-80-percent-centered {
|
||||||
|
width: 80%;
|
||||||
|
min-height: 80%;
|
||||||
|
padding: 0px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background-color: #959dff;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 40px;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
34
TechHelper.Client/Pages/Author/RestoreUserRole.razor
Normal file
34
TechHelper.Client/Pages/Author/RestoreUserRole.razor
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
@using TechHelper.Client.Services
|
||||||
|
<MudPaper Height="50" Width="50" Outlined="true">
|
||||||
|
|
||||||
|
<MudButton Variant="Variant.Filled" OnClick="Restore"> Restore Role </MudButton>
|
||||||
|
|
||||||
|
</MudPaper>
|
||||||
|
@code {
|
||||||
|
[Inject]
|
||||||
|
public IAuthenticationClientService Authentication { get; set; }
|
||||||
|
[Inject]
|
||||||
|
public IUserServices UserServices { get; set; }
|
||||||
|
[Inject]
|
||||||
|
public ISnackbar Snackbar { get; set; }
|
||||||
|
private async Task Restore()
|
||||||
|
{
|
||||||
|
var result = await UserServices.RestoreUserInfo();
|
||||||
|
if (result.Status)
|
||||||
|
{
|
||||||
|
|
||||||
|
Snackbar.Add("更新成功", Severity.Success);
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Snackbar.Add("更新失败", Severity.Error);
|
||||||
|
var token = await Authentication.RefreshTokenAsync();
|
||||||
|
if (token != null)
|
||||||
|
Snackbar.Add("刷新令牌成功", Severity.Success);
|
||||||
|
else
|
||||||
|
Snackbar.Add("刷新令牌失败,你可以手动刷新", Severity.Warning);
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
137
TechHelper.Client/Pages/Common/Exam/AssignmentInfoCard.razor
Normal file
137
TechHelper.Client/Pages/Common/Exam/AssignmentInfoCard.razor
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
<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> 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 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>
|
||||||
|
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,6 @@
|
|||||||
|
<MudPaper Class="rounded-xl w-100 px-10 ma-3 pt-5">
|
||||||
|
|
||||||
|
<MudText> Name </MudText>
|
||||||
|
<MudText> 错误数量 </MudText>
|
||||||
|
<MudText> 分数 </MudText>
|
||||||
|
</MudPaper>
|
191
TechHelper.Client/Pages/Common/Exam/SubmissionInfoCard.razor
Normal file
191
TechHelper.Client/Pages/Common/Exam/SubmissionInfoCard.razor
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,6 @@
|
|||||||
|
<MudPaper Class="rounded-xl w-100 px-10 ma-3 pt-5">
|
||||||
|
|
||||||
|
<MudText> Name </MudText>
|
||||||
|
<MudText> 平均数 </MudText>
|
||||||
|
<MudText> 中为数 </MudText>
|
||||||
|
</MudPaper>
|
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
TechHelper.Client/Pages/Common/ExamGlobalInfoDialog.razor
Normal file
34
TechHelper.Client/Pages/Common/ExamGlobalInfoDialog.razor
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
37
TechHelper.Client/Pages/Common/GlobalInfoCard.razor
Normal file
37
TechHelper.Client/Pages/Common/GlobalInfoCard.razor
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
67
TechHelper.Client/Pages/Common/PublishExamDialog.razor
Normal file
67
TechHelper.Client/Pages/Common/PublishExamDialog.razor
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
@using Entities.DTO
|
||||||
|
@inject ISnackbar Snackbar
|
||||||
|
@using Entities.Contracts
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<MudDialog Class="rounded-xl pa-2" 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>
|
||||||
|
<MudPaper Elevation="0" Class="rounded-xl pa-1 " Style="background-color: transparent">
|
||||||
|
<MudPaper Elevation="0" Class="rounded-xl pa-2 ma-2">
|
||||||
|
<MudTextField @bind-Value="Exam.Name" Label="Title" Variant="Variant.Text" Margin="Margin.Dense" AutoFocus="true" />
|
||||||
|
<MudTextField @bind-Value="Exam.TotalQuestions" Label="TotalQuestions" Variant="Variant.Text" Adornment="Adornment.End" AdornmentText="." Margin="Margin.Dense" AutoFocus="true" />
|
||||||
|
<MudTextField @bind-Value="Exam.Score" Label="Score" Variant="Variant.Text" Adornment="Adornment.End" AdornmentText="." Margin="Margin.Dense" AutoFocus="true" />
|
||||||
|
</MudPaper>
|
||||||
|
<MudPaper Elevation="0" Class="rounded-xl pa-2 ma-2">
|
||||||
|
|
||||||
|
<MudChipSet T="SubjectAreaEnum" @bind-SelectedValue="@Exam.SubjectArea" CheckMark=true SelectionMode="SelectionMode.SingleSelection" Size="Size.Small">
|
||||||
|
<MudChip Text="@SubjectAreaEnum.Literature.ToString()" Color="Color.Primary" Value="@SubjectAreaEnum.Literature"> @SubjectAreaEnum.Literature</MudChip>
|
||||||
|
<MudChip Text="@SubjectAreaEnum.Mathematics.ToString()" Color="Color.Secondary" Value="@SubjectAreaEnum.Mathematics"> @SubjectAreaEnum.Mathematics</MudChip>
|
||||||
|
<MudChip Text="@SubjectAreaEnum.English.ToString()" Color="Color.Info" Value="@SubjectAreaEnum.English"> @SubjectAreaEnum.English</MudChip>
|
||||||
|
<MudChip Text="@SubjectAreaEnum.ComputerScience.ToString()" Color="Color.Success" Value="@SubjectAreaEnum.ComputerScience"> @SubjectAreaEnum.ComputerScience</MudChip>
|
||||||
|
</MudChipSet>
|
||||||
|
|
||||||
|
|
||||||
|
</MudPaper>
|
||||||
|
<MudPaper Elevation="0" Class="rounded-xl pa-2 ma-2">
|
||||||
|
|
||||||
|
<MudChipSet T="ExamType" @bind-SelectedValue="@Exam.ExamType" CheckMark=true SelectionMode="SelectionMode.SingleSelection" Size="Size.Small">
|
||||||
|
<MudChip Text="@ExamType.DailyTest.ToString()" Color="Color.Primary" Value="@ExamType.DailyTest"> @ExamType.DailyTest</MudChip>
|
||||||
|
<MudChip Text="@ExamType.WeeklyExam.ToString()" Color="Color.Secondary" Value="@ExamType.WeeklyExam"> @ExamType.WeeklyExam</MudChip>
|
||||||
|
<MudChip Text="@ExamType.MonthlyExam.ToString()" Color="Color.Info" Value="@ExamType.MonthlyExam"> @ExamType.MonthlyExam</MudChip>
|
||||||
|
<MudChip Text="@ExamType.MidtermExam.ToString()" Color="Color.Success" Value="@ExamType.MidtermExam"> @ExamType.MidtermExam</MudChip>
|
||||||
|
<MudChip Text="@ExamType.FinalExam.ToString()" Color="Color.Warning" Value="@ExamType.FinalExam"> @ExamType.FinalExam</MudChip>
|
||||||
|
<MudChip Text="@ExamType.AITest.ToString()" Color="Color.Error" Value="@ExamType.AITest"> @ExamType.AITest</MudChip>
|
||||||
|
</MudChipSet>
|
||||||
|
</MudPaper>
|
||||||
|
</MudPaper>
|
||||||
|
</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 Exam { get; set; } = new AssignmentDto();
|
||||||
|
|
||||||
|
|
||||||
|
public SubjectAreaEnum SubjectArea { get; set; }
|
||||||
|
private void Cancel() => MudDialog.Cancel();
|
||||||
|
|
||||||
|
private void Confirm()
|
||||||
|
{
|
||||||
|
Snackbar.Add("属性已更新", Severity.Success);
|
||||||
|
MudDialog.Close(DialogResult.Ok(Exam));
|
||||||
|
}
|
||||||
|
}
|
34
TechHelper.Client/Pages/Common/QuestionCardDialog.razor
Normal file
34
TechHelper.Client/Pages/Common/QuestionCardDialog.razor
Normal 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>
|
||||||
|
<TechHelper.Client.Pages.Exam.AssignmentQuestionEdit AssignmentQuestion="Questions"/>
|
||||||
|
</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 AssignmentQuestionDto Questions { get; set; } = new AssignmentQuestionDto();
|
||||||
|
|
||||||
|
private void Cancel() => MudDialog.Cancel();
|
||||||
|
|
||||||
|
private void Confirm()
|
||||||
|
{
|
||||||
|
Snackbar.Add("属性已更新", Severity.Success);
|
||||||
|
MudDialog.Close(DialogResult.Ok(Questions));
|
||||||
|
}
|
||||||
|
}
|
22
TechHelper.Client/Pages/Common/SimpleCard.razor
Normal file
22
TechHelper.Client/Pages/Common/SimpleCard.razor
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<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>
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Style { get; set; }
|
||||||
|
[Parameter]
|
||||||
|
public string Height { get; set; } = "200px";
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public RenderFragment TitleContent { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public RenderFragment BodyContent { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public RenderFragment FooterContent { get; set; }
|
||||||
|
}
|
86
TechHelper.Client/Pages/Common/TextEditorDialog.razor
Normal file
86
TechHelper.Client/Pages/Common/TextEditorDialog.razor
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
@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>
|
||||||
|
<BlazoredTextEditor @ref="@TextEditor">
|
||||||
|
<ToolbarContent>
|
||||||
|
<select class="ql-header">
|
||||||
|
<option selected=""></option>
|
||||||
|
<option value="1"></option>
|
||||||
|
<option value="2"></option>
|
||||||
|
<option value="3"></option>
|
||||||
|
<option value="4"></option>
|
||||||
|
<option value="5"></option>
|
||||||
|
</select>
|
||||||
|
<span class="ql-formats">
|
||||||
|
<button class="ql-bold"></button>
|
||||||
|
<button class="ql-italic"></button>
|
||||||
|
<button class="ql-underline"></button>
|
||||||
|
<button class="ql-strike"></button>
|
||||||
|
</span>
|
||||||
|
<span class="ql-formats">
|
||||||
|
<select class="ql-color"></select>
|
||||||
|
<select class="ql-background"></select>
|
||||||
|
</span>
|
||||||
|
<span class="ql-formats">
|
||||||
|
<button class="ql-list" value="ordered"></button>
|
||||||
|
<button class="ql-list" value="bullet"></button>
|
||||||
|
</span>
|
||||||
|
<span class="ql-formats">
|
||||||
|
<button class="ql-link"></button>
|
||||||
|
</span>
|
||||||
|
</ToolbarContent>
|
||||||
|
<EditorContent>
|
||||||
|
</EditorContent>
|
||||||
|
</BlazoredTextEditor>
|
||||||
|
</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 BlazoredTextEditor TextEditor { get; set; } = new BlazoredTextEditor();
|
||||||
|
[Parameter]
|
||||||
|
public string EditorText { get; set; } = "{}";
|
||||||
|
|
||||||
|
private void HandleClick()
|
||||||
|
{
|
||||||
|
TextEditor.InsertText(EditorText);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Cancel() => MudDialog.Cancel();
|
||||||
|
|
||||||
|
|
||||||
|
protected async override Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await DelayInsert();
|
||||||
|
await base.OnInitializedAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private async Task DelayInsert()
|
||||||
|
{
|
||||||
|
await Task.Delay(100);
|
||||||
|
HandleClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Confirm()
|
||||||
|
{
|
||||||
|
Snackbar.Add("属性已更新", Severity.Success);
|
||||||
|
EditorText = await TextEditor.GetText();
|
||||||
|
MudDialog.Close(DialogResult.Ok(TextEditor));
|
||||||
|
}
|
||||||
|
}
|
10
TechHelper.Client/Pages/Exam/AssignmentDetailView.razor
Normal file
10
TechHelper.Client/Pages/Exam/AssignmentDetailView.razor
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<MudText> QuestionNum </MudText>
|
||||||
|
<MudText> QuestionTypeDis </MudText>
|
||||||
|
<MudText> ErrorQuestionTypeDis </MudText>
|
||||||
|
<MudText> ErrorLessonDis </MudText>
|
||||||
|
<MudText> ErrorIndexDis </MudText>
|
||||||
|
<MudText> ErrorNum </MudText>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
|
||||||
|
}
|
7
TechHelper.Client/Pages/Exam/AssignmentManagerCard.razor
Normal file
7
TechHelper.Client/Pages/Exam/AssignmentManagerCard.razor
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<MudPaper>
|
||||||
|
<MudText> ExamName </MudText>
|
||||||
|
<MudText> 已经指派人数 </MudText>
|
||||||
|
<MudText> 总人数 </MudText>
|
||||||
|
<MudText> 平均S </MudText>
|
||||||
|
<MudText> 指派 </MudText>
|
||||||
|
</MudPaper>
|
113
TechHelper.Client/Pages/Exam/AssignmentQuestionEdit.razor
Normal file
113
TechHelper.Client/Pages/Exam/AssignmentQuestionEdit.razor
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
@using Entities.DTO
|
||||||
|
@using Entities.Contracts
|
||||||
|
@using Newtonsoft.Json
|
||||||
|
@using TechHelper.Client.Exam
|
||||||
|
@using TechHelper.Client.Pages.Exam.QuestionCard
|
||||||
|
|
||||||
|
|
||||||
|
<MudPaper Elevation="0" Class="rounded-xl" Style="background-color: transparent">
|
||||||
|
@* <MudText>@AssignmentQuestion.Id</MudText> *@
|
||||||
|
<MudPaper Class="ma-4 pa-5 rounded-xl">
|
||||||
|
<MudText Class="mt-3" Typo="Typo.button"><b>包裹器属性</b></MudText>
|
||||||
|
<MudTextField @bind-Value="AssignmentQuestion.Title" Label="Title" Variant="Variant.Text" Margin="Margin.Dense" AutoFocus="true" />
|
||||||
|
<MudTextField @bind-Value="AssignmentQuestion.Index" Label="Index" Variant="Variant.Text" Adornment="Adornment.End" AdornmentText="." Margin="Margin.Dense" AutoFocus="true" />
|
||||||
|
<MudTextField @bind-Value="AssignmentQuestion.Score" Label="Score" Variant="Variant.Text" Adornment="Adornment.End" AdornmentText="." Margin="Margin.Dense" AutoFocus="true" />
|
||||||
|
<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)
|
||||||
|
{
|
||||||
|
<QuestionEdit Question="AssignmentQuestion.Question" />
|
||||||
|
}
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[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();
|
||||||
|
if (AssignmentQuestion.Question != null)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
AssignmentQuestion.StructType = type;
|
||||||
|
if (type != AssignmentStructType.Question && AssignmentQuestion.Question != null)
|
||||||
|
{
|
||||||
|
AssignmentQuestion.Title = AssignmentQuestion.Question.Title;
|
||||||
|
AssignmentQuestion.Question = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == AssignmentStructType.Question && AssignmentQuestion.Question == null)
|
||||||
|
{
|
||||||
|
if (TempQuesdto != null)
|
||||||
|
{
|
||||||
|
AssignmentQuestion.Question = TempQuesdto;
|
||||||
|
if (AssignmentQuestion.Title == AssignmentQuestion.Question.Title)
|
||||||
|
{
|
||||||
|
AssignmentQuestion.Title = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
AssignmentQuestion.Question = new QuestionDto { };
|
||||||
|
}
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
@@ -1,223 +1,239 @@
|
|||||||
@using Entities.DTO
|
@using Entities.DTO
|
||||||
@using TechHelper.Client.Exam
|
@using TechHelper.Client.Exam
|
||||||
|
@using TechHelper.Client.Services
|
||||||
@page "/exam/check/{ExamID}"
|
@page "/exam/check/{ExamID}"
|
||||||
|
|
||||||
|
|
||||||
<MudText Typo="Typo.h4" Class="mb-4">试卷批改预览: @ExamDto.AssignmentTitle</MudText>
|
<MudText Typo="Typo.h4" Class="mb-4">试卷批改预览: @Assignment.Title</MudText>
|
||||||
<MudDivider Class="my-4" />
|
<MudDivider Class="my-4" />
|
||||||
|
|
||||||
@if (_isLoading)
|
@if (_isLoading)
|
||||||
{
|
{
|
||||||
<MudProgressCircular Indeterminate="true" Color="Color.Primary" Class="d-flex justify-center my-8" />
|
<MudProgressCircular Indeterminate="true" Color="Color.Primary" Class="d-flex justify-center my-8" />
|
||||||
<MudText Class="text-center">正在加载试卷和学生数据...</MudText>
|
<MudText Class="text-center">正在加载试卷和学生数据...</MudText>
|
||||||
}
|
}
|
||||||
else if (_questionsForTable.Any() && _students.Any())
|
else if (_questionsForTable.Any() && _students.Any())
|
||||||
{
|
{
|
||||||
<MudTable @ref="_table" T="QuestionRowData" Items="@_questionsForTable" Hover="true" Breakpoint="Breakpoint.Sm" Class="mud-elevation-2" Dense="true">
|
<MudTable @ref="_table" T="QuestionRowData" Items="@_questionsForTable" Hover="true" Breakpoint="Breakpoint.Sm" Striped="true" Class="mud-elevation-2" Dense="true">
|
||||||
<HeaderContent>
|
<HeaderContent>
|
||||||
<MudTh Style="width:100px;">序号</MudTh>
|
<MudTh Style="width:100px;">序号</MudTh>
|
||||||
<MudTh Style="width:80px; text-align:center;">分值</MudTh>
|
<MudTh Style="width:80px; text-align:center;">分值</MudTh>
|
||||||
@foreach (var student in _students)
|
@foreach (var student in _students)
|
||||||
{
|
{
|
||||||
<MudTh Style="width:120px; text-align:center;">
|
<MudTh Style="width:120px; text-align:center;">
|
||||||
@student.Name
|
@student.DisplayName
|
||||||
<MudTooltip Text="点击以切换此学生所有题目的对错">
|
<MudTooltip Text="点击以切换此学生所有题目的对错">
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Info" Size="Size.Small" Class="ml-1"
|
<MudIconButton Icon="@Icons.Material.Filled.Info" Size="Size.Small" Class="ml-1"
|
||||||
@onclick="() => ToggleStudentAllAnswers(student.Id)" />
|
@onclick="() => ToggleStudentAllAnswers(student.Id)" />
|
||||||
</MudTooltip>
|
</MudTooltip>
|
||||||
</MudTh>
|
</MudTh>
|
||||||
}
|
}
|
||||||
</HeaderContent>
|
</HeaderContent>
|
||||||
<RowTemplate>
|
<RowTemplate>
|
||||||
<MudTd DataLabel="序号">@context.QuestionItem.Sequence</MudTd>
|
<MudTd DataLabel="序号">@context.QuestionItem.Sequence</MudTd>
|
||||||
<MudTd DataLabel="分值" Style="text-align:center;">@context.QuestionItem.Score</MudTd>
|
<MudTd DataLabel="分值" Style="text-align:center;">@context.QuestionItem.Score</MudTd>
|
||||||
@foreach (var student in _students)
|
@foreach (var student in _students)
|
||||||
{
|
{
|
||||||
<MudTd DataLabel="@student.Name" Style="text-align:center;">
|
<MudTd DataLabel="@student.DisplayName" Style="text-align:center;">
|
||||||
@if (context.StudentAnswers.ContainsKey(student.Id))
|
@if (context.StudentAnswers.ContainsKey(student.Id))
|
||||||
{
|
{
|
||||||
<MudCheckBox @bind-Value="context.StudentAnswers[student.Id]" Size="Size.Small" Color="Color.Primary"></MudCheckBox>
|
<MudCheckBox @bind-Value="context.StudentAnswers[student.Id]" Size="Size.Small" Color="Color.Primary"></MudCheckBox>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<MudText Color="Color.Warning">N/A</MudText>
|
<MudText Color="Color.Warning">N/A</MudText>
|
||||||
}
|
}
|
||||||
</MudTd>
|
</MudTd>
|
||||||
}
|
}
|
||||||
</RowTemplate>
|
</RowTemplate>
|
||||||
<PagerContent>
|
<PagerContent>
|
||||||
<MudTablePager />
|
<MudTablePager />
|
||||||
</PagerContent>
|
</PagerContent>
|
||||||
</MudTable>
|
</MudTable>
|
||||||
|
|
||||||
<MudPaper Class="pa-4 mt-4 mud-elevation-2 d-flex flex-column align-end">
|
<MudPaper Class="pa-4 mt-4 mud-elevation-2 d-flex flex-column align-end">
|
||||||
<MudText Typo="Typo.h6">学生总分预览:</MudText>
|
<MudText Typo="Typo.h6">学生总分预览:</MudText>
|
||||||
@foreach (var student in _students)
|
@foreach (var student in _students)
|
||||||
{
|
{
|
||||||
<MudText Typo="Typo.subtitle1">
|
<MudText Typo="Typo.subtitle1">
|
||||||
@student.Name: <MudText Typo="Typo.h5" Color="Color.Primary" Class="d-inline-block ml-2">@GetStudentTotalScore(student.Id)</MudText>
|
@student.DisplayName: <MudText Typo="Typo.h5" Color="Color.Primary" Class="d-inline-block ml-2">@GetStudentTotalScore(student.Id)</MudText>
|
||||||
</MudText>
|
</MudText>
|
||||||
}
|
}
|
||||||
<MudButton Variant="Variant.Filled" Color="Color.Success" Class="mt-4" @onclick="SubmitGrading">
|
<MudButton Variant="Variant.Filled" Color="Color.Success" Class="mt-4" @onclick="SubmitGrading">
|
||||||
提交批改结果 (模拟)
|
提交批改结果 (模拟)
|
||||||
</MudButton>
|
</MudButton>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<MudAlert Severity="Severity.Info" Class="mt-4">无法加载试卷或题目信息。</MudAlert>
|
<MudAlert Severity="Severity.Info" Class="mt-4">无法加载试卷或题目信息。</MudAlert>
|
||||||
<MudButton Variant="Variant.Text" Color="Color.Primary" Class="mt-4" >返回试卷列表</MudButton>
|
<MudButton Variant="Variant.Text" Color="Color.Primary" Class="mt-4">返回试卷列表</MudButton>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string ExamId { get; set; } // 从路由获取的试卷ID
|
public string ExamId { get; set; }
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
public IExamService ExamService { get; set; } // 注入试卷服务
|
public IExamService ExamService { get; set; }
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
private ISnackbar Snackbar { get; set; } // 注入 Snackbar 用于消息提示
|
private ISnackbar Snackbar { get; set; }
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
private NavigationManager Navigation { get; set; } // 注入导航管理器
|
private NavigationManager Navigation { get; set; }
|
||||||
|
|
||||||
private MudTable<QuestionRowData> _table = new(); // MudTable 实例引用
|
private MudTable<QuestionRowData> _table = new();
|
||||||
private ExamDto ExamDto { get; set; } = new ExamDto(); // 原始试卷数据
|
private AssignmentDto Assignment { get; set; } = new AssignmentDto();
|
||||||
private ExamStruct _examStruct = new ExamStruct(); // 处理后的试卷结构,包含带序号的题目
|
private AssignmentCheckData _examStruct = new AssignmentCheckData();
|
||||||
|
|
||||||
private List<Student> _students = new List<Student>(); // 临时生成的学生列表
|
private List<StudentDto> _students = new List<StudentDto>();
|
||||||
private List<QuestionRowData> _questionsForTable = new List<QuestionRowData>(); // 用于 MudTable 的数据源
|
private List<QuestionRowData> _questionsForTable = new List<QuestionRowData>();
|
||||||
|
|
||||||
private bool _isLoading = true; // 加载状态
|
private bool _isLoading = true;
|
||||||
|
|
||||||
// 在组件初始化时加载数据
|
[Inject]
|
||||||
protected override async Task OnInitializedAsync()
|
public IClassServices ClassServices { get; set; }
|
||||||
{
|
|
||||||
_isLoading = true;
|
|
||||||
await LoadExamData();
|
|
||||||
GenerateTemporaryStudentsAndAnswers(); // 生成学生和初始作答数据
|
|
||||||
_isLoading = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载试卷数据的方法
|
protected override async Task OnInitializedAsync()
|
||||||
private async Task LoadExamData()
|
{
|
||||||
{
|
_isLoading = true;
|
||||||
if (Guid.TryParse(ExamId, out Guid parsedExamId))
|
await LoadExamData();
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var result = await ExamService.GetExam(parsedExamId);
|
|
||||||
if (result.Status)
|
|
||||||
{
|
|
||||||
ExamDto = result.Result as ExamDto ?? new ExamDto();
|
|
||||||
_examStruct = ExamDto.GetStruct(); // 将 ExamDto 转换为 ExamStruct
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Snackbar?.Add($"获取试卷失败: {result.Message}", Severity.Error);
|
|
||||||
Navigation.NavigateTo("/exam/manager"); // 导航回管理页
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.Error.WriteLine($"获取试卷时发生错误: {ex.Message}");
|
|
||||||
Snackbar?.Add($"获取试卷失败: {ex.Message}", Severity.Error);
|
|
||||||
Navigation.NavigateTo("/exam/manager");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.Error.WriteLine($"错误:路由参数 ExamId '{ExamId}' 不是一个有效的 GUID 格式。");
|
|
||||||
Snackbar?.Add("无效的试卷ID,无法加载。", Severity.Error);
|
|
||||||
Navigation.NavigateTo("/exam/manager");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成临时学生和作答数据
|
var result = await ClassServices.GetClassStudents();
|
||||||
private void GenerateTemporaryStudentsAndAnswers()
|
if (!result.Status) Snackbar.Add($"获取学生失败, {result.Message}", Severity.Error);
|
||||||
{
|
_students = result.Result as List<StudentDto> ?? new List<StudentDto>();
|
||||||
_students = new List<Student>();
|
BuildTable();
|
||||||
// 生成 40 个学生
|
_isLoading = false;
|
||||||
for (int i = 1; i <= 40; i++)
|
}
|
||||||
{
|
|
||||||
_students.Add(new Student { Name = $"学生{i}" });
|
|
||||||
}
|
|
||||||
|
|
||||||
_questionsForTable = _examStruct.Questions.Select(qItem =>
|
private void BuildTable()
|
||||||
{
|
{
|
||||||
var rowData = new QuestionRowData
|
_questionsForTable = _examStruct.Questions.Select(q =>
|
||||||
{
|
{
|
||||||
QuestionItem = qItem,
|
var rowData = new QuestionRowData
|
||||||
StudentAnswers = new Dictionary<Guid, bool>()
|
{
|
||||||
};
|
QuestionItem = q,
|
||||||
|
StudentAnswers = new Dictionary<Guid, bool>()
|
||||||
|
};
|
||||||
|
foreach (var student in _students)
|
||||||
|
{
|
||||||
|
rowData.StudentAnswers[student.Id] = false;
|
||||||
|
}
|
||||||
|
return rowData;
|
||||||
|
}).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
// 为每个学生随机生成初始的对错状态
|
private async Task LoadExamData()
|
||||||
var random = new Random();
|
{
|
||||||
foreach (var student in _students)
|
if (Guid.TryParse(ExamId, out Guid parsedExamId))
|
||||||
{
|
{
|
||||||
// 模拟随机对错,50%的概率
|
try
|
||||||
rowData.StudentAnswers[student.Id] = random.Next(0, 2) == 1;
|
{
|
||||||
}
|
var result = await ExamService.GetExam(parsedExamId);
|
||||||
return rowData;
|
if (result.Status)
|
||||||
}).ToList();
|
{
|
||||||
}
|
Assignment = result.Result as AssignmentDto ?? new AssignmentDto();
|
||||||
|
_examStruct = Assignment.GetStruct();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Snackbar?.Add($"获取试卷失败: {result.Message}", Severity.Error);
|
||||||
|
Navigation.NavigateTo("/exam/manager");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"获取试卷时发生错误: {ex.Message}");
|
||||||
|
Snackbar?.Add($"获取试卷失败: {ex.Message}", Severity.Error);
|
||||||
|
Navigation.NavigateTo("/exam/manager");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"错误:路由参数 ExamId '{ExamId}' 不是一个有效的 GUID 格式。");
|
||||||
|
Snackbar?.Add("无效的试卷ID,无法加载。", Severity.Error);
|
||||||
|
Navigation.NavigateTo("/exam/manager");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 当某个学生的某个题目的作答状态改变时触发
|
|
||||||
private void OnAnswerChanged(string questionSequence, Guid studentId, bool isCorrect)
|
|
||||||
{
|
|
||||||
// 可以在这里添加额外的逻辑,例如记录更改
|
|
||||||
Console.WriteLine($"题目 {questionSequence}, 学生 {studentId} 的答案变为: {isCorrect}");
|
|
||||||
// 由于是 @bind-Checked,数据模型已经自动更新,这里只是日志
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算某个学生的总分
|
|
||||||
private float GetStudentTotalScore(Guid studentId)
|
|
||||||
{
|
|
||||||
float totalScore = 0;
|
|
||||||
foreach (var row in _questionsForTable)
|
|
||||||
{
|
|
||||||
if (row.StudentAnswers.TryGetValue(studentId, out bool isCorrect) && isCorrect)
|
|
||||||
{
|
|
||||||
totalScore += row.QuestionItem.Score;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return totalScore;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 切换某个学生所有题目的对错状态 (用于快速批改)
|
private float GetStudentTotalScore(Guid studentId)
|
||||||
private void ToggleStudentAllAnswers(Guid studentId)
|
{
|
||||||
{
|
float totalScore = 0;
|
||||||
bool allCorrect = _questionsForTable.All(row => row.StudentAnswers.ContainsKey(studentId) && row.StudentAnswers[studentId]);
|
foreach (var row in _questionsForTable)
|
||||||
|
{
|
||||||
|
if (row.StudentAnswers.TryGetValue(studentId, out bool isCorrect) && isCorrect)
|
||||||
|
{
|
||||||
|
totalScore += row.QuestionItem.Score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return totalScore;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var row in _questionsForTable)
|
private void ToggleStudentAllAnswers(Guid studentId)
|
||||||
{
|
{
|
||||||
if (row.StudentAnswers.ContainsKey(studentId))
|
bool allCorrect = _questionsForTable.All(row => row.StudentAnswers.ContainsKey(studentId) && row.StudentAnswers[studentId]);
|
||||||
{
|
|
||||||
row.StudentAnswers[studentId] = !allCorrect; // 全部取反
|
|
||||||
}
|
|
||||||
}
|
|
||||||
StateHasChanged(); // 手动通知 Blazor 刷新 UI
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交批改结果(模拟)
|
foreach (var row in _questionsForTable)
|
||||||
private void SubmitGrading()
|
{
|
||||||
{
|
if (row.StudentAnswers.ContainsKey(studentId))
|
||||||
Console.WriteLine("--- 提交批改结果 ---");
|
{
|
||||||
foreach (var student in _students)
|
row.StudentAnswers[studentId] = !allCorrect;
|
||||||
{
|
}
|
||||||
Console.WriteLine($"学生: {student.Name}, 总分: {GetStudentTotalScore(student.Id)}");
|
}
|
||||||
foreach (var row in _questionsForTable)
|
StateHasChanged();
|
||||||
{
|
}
|
||||||
if (row.StudentAnswers.TryGetValue(student.Id, out bool isCorrect))
|
|
||||||
{
|
private void SubmitGrading()
|
||||||
Console.WriteLine($" - 题目 {row.QuestionItem.Sequence}: {(isCorrect ? "正确" : "错误")}");
|
{
|
||||||
}
|
|
||||||
}
|
List<SubmissionDto> submissionDto = new List<SubmissionDto>();
|
||||||
}
|
|
||||||
Snackbar?.Add("批改结果已提交(模拟)", Severity.Success);
|
|
||||||
// 实际应用中,这里会将 _questionsForTable 和 _students 的数据发送到后端API
|
foreach (var student in _students)
|
||||||
}
|
{
|
||||||
|
var newSubmission = new SubmissionDto();
|
||||||
|
newSubmission.StudentId = student.Id;
|
||||||
|
newSubmission.AssignmentId = Assignment.Id;
|
||||||
|
newSubmission.SubmissionTime = DateTime.Now;
|
||||||
|
newSubmission.Status = Entities.Contracts.SubmissionStatus.Graded;
|
||||||
|
|
||||||
|
|
||||||
|
foreach (var row in _questionsForTable)
|
||||||
|
{
|
||||||
|
if (row.QuestionItem.AssignmentQuestionDto.StructType == Entities.Contracts.AssignmentStructType.Struct) continue;
|
||||||
|
if (row.StudentAnswers.TryGetValue(student.Id, out bool isCorrect))
|
||||||
|
{
|
||||||
|
newSubmission.SubmissionDetails.Add(new SubmissionDetailDto
|
||||||
|
{
|
||||||
|
IsCorrect = isCorrect,
|
||||||
|
StudentId = student.Id,
|
||||||
|
AssignmentQuestionId = row.QuestionItem.AssignmentQuestionDto.Id,
|
||||||
|
PointsAwarded = isCorrect ? row.QuestionItem.AssignmentQuestionDto.Score : 0
|
||||||
|
});
|
||||||
|
|
||||||
|
newSubmission.OverallGrade += isCorrect ? row.QuestionItem.AssignmentQuestionDto.Score : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
submissionDto.Add(newSubmission);
|
||||||
|
}
|
||||||
|
|
||||||
|
submissionDto.ForEach(async s =>
|
||||||
|
{
|
||||||
|
Snackbar?.Add($"正在提交: {_students.FirstOrDefault(std => std.Id == s.StudentId)?.DisplayName} 的试卷", Severity.Info);
|
||||||
|
var submidResult = await ExamService.SubmissionAssignment(s);
|
||||||
|
if (submidResult.Status)
|
||||||
|
Snackbar?.Add($"批改结果已提交 {_students.FirstOrDefault(st => st.Id == s.StudentId)?.DisplayName}", Severity.Success);
|
||||||
|
else
|
||||||
|
Snackbar?.Add("批改结果提交失败", Severity.Error);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,10 @@
|
|||||||
@page "/exam/create"
|
@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
|
||||||
@using Blazored.TextEditor
|
@using Blazored.TextEditor
|
||||||
@using Entities.DTO
|
@using Entities.DTO
|
||||||
@using TechHelper.Client.Exam
|
@using TechHelper.Client.Exam
|
||||||
@@ -7,64 +13,55 @@
|
|||||||
@using Microsoft.AspNetCore.Components
|
@using Microsoft.AspNetCore.Components
|
||||||
@using System.Globalization;
|
@using System.Globalization;
|
||||||
@using TechHelper.Client.Pages.Editor
|
@using TechHelper.Client.Pages.Editor
|
||||||
|
@inject IDialogService DialogService
|
||||||
|
|
||||||
<MudPaper Elevation="5" Class="d-flex overflow-hidden flex-grow-1" Style="overflow:hidden; position:relative;height:100%">
|
<MudPaper Elevation="5" Class="d-flex overflow-hidden flex-grow-1 pa-1 rounded-xl " Style="overflow:hidden; position:relative;height:100%">
|
||||||
<MudDrawerContainer Class="mud-height-full flex-grow-1" Style="height:100%">
|
<MudDrawerContainer Class="mud-height-full flex-grow-1" Style="height:100%">
|
||||||
<MudDrawer @bind-Open="@_open" Elevation="0" Variant="@DrawerVariant.Persistent" Color="Color.Primary" Anchor="Anchor.End" OverlayAutoClose="true">
|
<MudDrawer @bind-Open="@_open" Elevation="0" Variant="@DrawerVariant.Persistent" Color="Color.Primary" Anchor="Anchor.End" OverlayAutoClose="true">
|
||||||
<MudDrawerHeader>
|
|
||||||
<MudText Typo="Typo.h6"> 配置 </MudText>
|
|
||||||
</MudDrawerHeader>
|
|
||||||
|
|
||||||
<MudStack Class="overflow-auto">
|
@if (_edit)
|
||||||
<ParseRoleConfig />
|
{
|
||||||
<MudButton Color="Color.Success"> ParseExam </MudButton>
|
<AssignmentQuestionEdit AssignmentQuestion="selectedAssignmentQuestion" />
|
||||||
</MudStack>
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
<MudDrawerHeader>
|
||||||
|
<MudText Typo="Typo.h6"> 配置 </MudText>
|
||||||
|
</MudDrawerHeader>
|
||||||
|
|
||||||
|
<MudStack Class="overflow-auto">
|
||||||
|
<ParseRoleConfig />
|
||||||
|
<MudButton Color="Color.Success"> ParseExam </MudButton>
|
||||||
|
</MudStack>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</MudDrawer>
|
</MudDrawer>
|
||||||
<MudStack Row="true" Class="flex-grow-1" Style="height:100%">
|
<MudStack Class="flex-grow-1 h-100">
|
||||||
<ExamView Class="overflow-auto" ParsedExam="ExamContent"></ExamView>
|
|
||||||
|
|
||||||
<MudPaper Class="ma-2">
|
<MudPaper Class="rounded-xl ma-1 pa-2" Style="background-color:#fefefefe">
|
||||||
<MudPaper Elevation="0" Style="height:calc(100% - 80px)">
|
<MudButton Variant="Variant.Filled" Color="Color.Secondary" Class="rounded-xl" Size="Size.Small" OnClick="OpenEditor">文本编辑器</MudButton>
|
||||||
<BlazoredTextEditor @ref="@_textEditor">
|
<MudButton Variant="Variant.Filled" Color="Color.Secondary" Class="rounded-xl" Size="Size.Small" OnClick="ParseExam">载入</MudButton>
|
||||||
<ToolbarContent>
|
<MudButton Variant="Variant.Filled" Color="Color.Secondary" Class="rounded-xl" Size="Size.Small" OnClick="OpenPublish">发布</MudButton>
|
||||||
<select class="ql-header">
|
<MudButton Variant="Variant.Filled" Color="Color.Secondary" Class="rounded-xl" Size="Size.Small" OnClick="OpenPublish">指派</MudButton>
|
||||||
<option selected=""></option>
|
<MudButton Variant="Variant.Filled" Color="Color.Secondary" Class="rounded-xl" Size="Size.Small" OnClick="OpenTest">Test</MudButton>
|
||||||
<option value="1"></option>
|
<MudButton Variant="Variant.Filled" Color="Color.Secondary" Class="rounded-xl" Size="Size.Small" OnClick="HandleGlobalInfo">GlobalExamInfo</MudButton>
|
||||||
<option value="2"></option>
|
|
||||||
<option value="3"></option>
|
|
||||||
<option value="4"></option>
|
|
||||||
<option value="5"></option>
|
|
||||||
</select>
|
|
||||||
<span class="ql-formats">
|
|
||||||
<button class="ql-bold"></button>
|
|
||||||
<button class="ql-italic"></button>
|
|
||||||
<button class="ql-underline"></button>
|
|
||||||
<button class="ql-strike"></button>
|
|
||||||
</span>
|
|
||||||
<span class="ql-formats">
|
|
||||||
<select class="ql-color"></select>
|
|
||||||
<select class="ql-background"></select>
|
|
||||||
</span>
|
|
||||||
<span class="ql-formats">
|
|
||||||
<button class="ql-list" value="ordered"></button>
|
|
||||||
<button class="ql-list" value="bullet"></button>
|
|
||||||
</span>
|
|
||||||
<span class="ql-formats">
|
|
||||||
<button class="ql-link"></button>
|
|
||||||
</span>
|
|
||||||
</ToolbarContent>
|
|
||||||
<EditorContent>
|
|
||||||
</EditorContent>
|
|
||||||
</BlazoredTextEditor>
|
|
||||||
</MudPaper>
|
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
|
|
||||||
<MudButtonGroup Vertical="true" Color="Color.Primary" Variant="Variant.Filled">
|
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Settings" OnClick="@ToggleDrawer" Color="Color.Secondary" />
|
<ExamView Class="overflow-auto ma-1 pa-2 rounded-xl" ClickedStruct="HandleClickedStruct" ParsedExam="ExamContent"></ExamView>
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.TransitEnterexit" OnClick="@ParseExam" Color="Color.Secondary" />
|
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Save" OnClick="@ToggleDrawer" Color="Color.Secondary" />
|
<MudPaper MaxWidth="300">
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Publish" OnClick="@Publish" Color="Color.Secondary" />
|
|
||||||
</MudButtonGroup>
|
@if (_parsedExam.Errors.Any())
|
||||||
|
{
|
||||||
|
foreach (var item in _parsedExam.Errors)
|
||||||
|
{
|
||||||
|
<MudText> @item.Message </MudText>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</MudPaper>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudDrawerContainer>
|
</MudDrawerContainer>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
@@ -74,23 +71,97 @@
|
|||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
private Task<AuthenticationState> authenticationStateTask { get; set; }
|
private Task<AuthenticationState> authenticationStateTask { get; set; }
|
||||||
|
|
||||||
|
private AssignmentQuestionDto selectedAssignmentQuestion = new AssignmentQuestionDto();
|
||||||
|
private IReadOnlyCollection<string> _selected;
|
||||||
private bool _open = false;
|
private bool _open = false;
|
||||||
|
private bool _edit = false;
|
||||||
|
|
||||||
private void ToggleDrawer()
|
private void ToggleDrawer()
|
||||||
{
|
{
|
||||||
_open = !_open;
|
_open = !_open;
|
||||||
|
_edit = false;
|
||||||
}
|
}
|
||||||
private BlazoredTextEditor _textEditor = new BlazoredTextEditor();
|
private BlazoredTextEditor _textEditor = new BlazoredTextEditor();
|
||||||
private ExamPaper _parsedExam = new ExamPaper();
|
private AssignmentEx _parsedExam = new AssignmentEx();
|
||||||
private ExamDto ExamContent = new ExamDto();
|
private AssignmentDto ExamContent = new AssignmentDto();
|
||||||
|
|
||||||
private ExamParserConfig _examParserConfig { get; set; } = new ExamParserConfig();
|
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 } };
|
||||||
|
parameters.Add("EditorText", EditorText);
|
||||||
|
|
||||||
|
var dialog = await DialogService.ShowAsync<TextEditorDialog>("TextEditor", parameters);
|
||||||
|
var result = await dialog.Result;
|
||||||
|
if (!result.Canceled)
|
||||||
|
{
|
||||||
|
_textEditor = result.Data as BlazoredTextEditor ?? new BlazoredTextEditor();
|
||||||
|
await ParseExam();
|
||||||
|
}
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
private async void OpenPublish()
|
||||||
|
{
|
||||||
|
var parameters = new DialogParameters<PublishExamDialog> { { x => x.Exam, ExamContent } };
|
||||||
|
|
||||||
|
var dialog = await DialogService.ShowAsync<PublishExamDialog>("PublishExam", parameters);
|
||||||
|
var result = await dialog.Result;
|
||||||
|
if (!result.Canceled)
|
||||||
|
{
|
||||||
|
await Publish();
|
||||||
|
}
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void HandleClickedStruct(AssignmentQuestionDto dto)
|
||||||
|
{
|
||||||
|
// _open = true;
|
||||||
|
// _edit = true;
|
||||||
|
// StateHasChanged();
|
||||||
|
|
||||||
|
var parameters = new DialogParameters<QuestionCardDialog> { { x => x.Questions, dto } };
|
||||||
|
|
||||||
|
var dialog = await DialogService.ShowAsync<QuestionCardDialog>("Edit_Question", parameters);
|
||||||
|
var result = await dialog.Result;
|
||||||
|
if (!result.Canceled)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ParseExam()
|
private async Task ParseExam()
|
||||||
{
|
{
|
||||||
|
|
||||||
var plainText = await _textEditor.GetText();
|
var plainText = await _textEditor.GetText();
|
||||||
|
EditorText = plainText;
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(plainText))
|
if (!string.IsNullOrWhiteSpace(plainText))
|
||||||
{
|
{
|
||||||
@@ -100,7 +171,8 @@
|
|||||||
_parsedExam = exampar.ParseExamPaper(plainText);
|
_parsedExam = exampar.ParseExamPaper(plainText);
|
||||||
Snackbar.Add("试卷解析成功。", Severity.Success);
|
Snackbar.Add("试卷解析成功。", Severity.Success);
|
||||||
Snackbar.Add($"{_parsedExam.Errors}。", Severity.Success);
|
Snackbar.Add($"{_parsedExam.Errors}。", Severity.Success);
|
||||||
ExamContent = _parsedExam.ConvertToExamDTO();
|
StateHasChanged();
|
||||||
|
ExamContent = Mapper.Map<AssignmentDto>(_parsedExam);
|
||||||
ExamContent.SeqIndex();
|
ExamContent.SeqIndex();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -122,12 +194,91 @@
|
|||||||
[Inject]
|
[Inject]
|
||||||
public IExamService examService { get; set; }
|
public IExamService examService { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
public INoteService NoteService { get; set; }
|
||||||
|
|
||||||
|
|
||||||
public async Task Publish()
|
public async Task Publish()
|
||||||
{
|
{
|
||||||
ExamContent.CreaterEmail = authenticationStateTask.Result.User.Identity.Name;
|
|
||||||
var apiRespon = await examService.SaveParsedExam(ExamContent);
|
var apiRespon = await examService.SaveParsedExam(ExamContent);
|
||||||
Snackbar.Add(apiRespon.Message);
|
Snackbar.Add(apiRespon.Message);
|
||||||
}
|
}
|
||||||
|
public async Task OpenTest()
|
||||||
|
{
|
||||||
|
Dictionary<string, (Color, string)> Note = new Dictionary<string, (Color, string)> { { "Hello", (Color.Surface, "World") }, { "My", (Color.Surface, "App") }, };
|
||||||
|
var json = JsonConvert.SerializeObject(Note);
|
||||||
|
var result = await NoteService.AddNote(new GlobalDto { SubjectArea = Entities.Contracts.SubjectAreaEnum.Physics, Data = json });
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
Console.WriteLine(json);
|
||||||
|
var res = JsonConvert.DeserializeObject<Dictionary<string, (Color, string)>>(json);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void HandleGlobalInfo()
|
||||||
|
{
|
||||||
|
// _open = true;
|
||||||
|
// _edit = true;
|
||||||
|
// StateHasChanged();
|
||||||
|
|
||||||
|
var parameters = new DialogParameters<ExamGlobalInfoDialog> { { x => x.Assignment, ExamContent } };
|
||||||
|
|
||||||
|
var dialog = await DialogService.ShowAsync<ExamGlobalInfoDialog>("Exam_GlobalInfo", parameters);
|
||||||
|
var result = await dialog.Result;
|
||||||
|
if (!result.Canceled)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- #region name -->
|
||||||
|
@* <MudButtonGroup Vertical="true" Color="Color.Primary" Variant="Variant.Filled">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Settings" OnClick="@ToggleDrawer" Color="Color.Secondary" />
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.TransitEnterexit" OnClick="@ParseExam" Color="Color.Secondary" />
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Save" OnClick="@ToggleDrawer" Color="Color.Secondary" />
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Publish" OnClick="@Publish" Color="Color.Secondary" />
|
||||||
|
</MudButtonGroup> *@
|
||||||
|
@* <MudPaper Class="ma-2 h-100">
|
||||||
|
<MudPaper Elevation="0" Style="height:calc(100% - 80px)">
|
||||||
|
<BlazoredTextEditor @ref="@_textEditor">
|
||||||
|
<ToolbarContent>
|
||||||
|
<select class="ql-header">
|
||||||
|
<option selected=""></option>
|
||||||
|
<option value="1"></option>
|
||||||
|
<option value="2"></option>
|
||||||
|
<option value="3"></option>
|
||||||
|
<option value="4"></option>
|
||||||
|
<option value="5"></option>
|
||||||
|
</select>
|
||||||
|
<span class="ql-formats">
|
||||||
|
<button class="ql-bold"></button>
|
||||||
|
<button class="ql-italic"></button>
|
||||||
|
<button class="ql-underline"></button>
|
||||||
|
<button class="ql-strike"></button>
|
||||||
|
</span>
|
||||||
|
<span class="ql-formats">
|
||||||
|
<select class="ql-color"></select>
|
||||||
|
<select class="ql-background"></select>
|
||||||
|
</span>
|
||||||
|
<span class="ql-formats">
|
||||||
|
<button class="ql-list" value="ordered"></button>
|
||||||
|
<button class="ql-list" value="bullet"></button>
|
||||||
|
</span>
|
||||||
|
<span class="ql-formats">
|
||||||
|
<button class="ql-link"></button>
|
||||||
|
</span>
|
||||||
|
</ToolbarContent>
|
||||||
|
<EditorContent>
|
||||||
|
</EditorContent>
|
||||||
|
</BlazoredTextEditor>
|
||||||
|
</MudPaper>
|
||||||
|
</MudPaper> *@
|
||||||
|
|
||||||
|
<!-- #endregion -->
|
@@ -1,5 +1,8 @@
|
|||||||
@page "/exam/edit/{ExamId}"
|
@page "/exam/edit/{ExamId}"
|
||||||
@using Entities.DTO
|
@using Entities.DTO
|
||||||
|
@using TechHelper.Client.Pages.Exam.ExamView
|
||||||
|
@using TechHelper.Client.Services
|
||||||
|
@using Entities.DTO
|
||||||
@using TechHelper.Client.Exam
|
@using TechHelper.Client.Exam
|
||||||
|
|
||||||
<ExamView ParsedExam="@ExamDto"/>
|
<ExamView ParsedExam="@ExamDto"/>
|
||||||
@@ -17,7 +20,7 @@
|
|||||||
[Inject]
|
[Inject]
|
||||||
private ISnackbar Snackbar { get; set; }
|
private ISnackbar Snackbar { get; set; }
|
||||||
|
|
||||||
private ExamDto ExamDto { get; set; }
|
private AssignmentDto ExamDto { get; set; }
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
@@ -28,7 +31,7 @@
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = await ExamService.GetExam(parsedExamId);
|
var result = await ExamService.GetExam(parsedExamId);
|
||||||
if (result.Status) ExamDto = result.Result as ExamDto ?? new ExamDto();
|
if (result.Status) ExamDto = result.Result as AssignmentDto ?? new AssignmentDto();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@@ -1,48 +0,0 @@
|
|||||||
@using Entities.DTO
|
|
||||||
@using TechHelper.Client.Exam
|
|
||||||
|
|
||||||
|
|
||||||
<MudPaper Elevation=@Elevation Class=@Class>
|
|
||||||
|
|
||||||
@foreach (var majorQG in MajorQGList)
|
|
||||||
{
|
|
||||||
<MudStack Row="true">
|
|
||||||
<MudText Typo="Typo.h6">@majorQG.Title</MudText>
|
|
||||||
@if (majorQG.Score > 0)
|
|
||||||
{
|
|
||||||
<MudText Typo="Typo.body2"><b>总分:</b> @majorQG.Score 分</MudText>
|
|
||||||
}
|
|
||||||
</MudStack>
|
|
||||||
|
|
||||||
|
|
||||||
@if (!string.IsNullOrWhiteSpace(majorQG.Descript))
|
|
||||||
{
|
|
||||||
<MudText Typo="Typo.body2">@((MarkupString)majorQG.Descript.Replace("\n", "<br />"))</MudText>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (majorQG.SubQuestions.Any())
|
|
||||||
{
|
|
||||||
@foreach (var question in majorQG.SubQuestions)
|
|
||||||
{
|
|
||||||
<QuestionCard Question="question" Elevation=@Elevation Class="my-2 pa-1" />
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (majorQG.SubQuestionGroups.Any())
|
|
||||||
{
|
|
||||||
<ExamGroupView MajorQGList="majorQG.SubQuestionGroups" Elevation="1" />
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</MudPaper>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
[Parameter]
|
|
||||||
public List<QuestionGroupDto> MajorQGList { get; set; } = new List<QuestionGroupDto>();
|
|
||||||
|
|
||||||
[Parameter]
|
|
||||||
public string Class { get; set; } = "my-2 pa-1";
|
|
||||||
|
|
||||||
[Parameter]
|
|
||||||
public int Elevation { get; set; } = 0;
|
|
||||||
}
|
|
@@ -1,9 +1,11 @@
|
|||||||
@using Entities.DTO
|
@using Entities.DTO
|
||||||
@using Microsoft.AspNetCore.Authorization
|
@using Microsoft.AspNetCore.Authorization
|
||||||
@using TechHelper.Client.Exam
|
@using TechHelper.Client.Exam
|
||||||
|
@using TechHelper.Client.Pages.Common.Exam
|
||||||
|
|
||||||
@page "/exam/manage"
|
@page "/exam/manage"
|
||||||
|
@using Entities.DTO
|
||||||
|
@using TechHelper.Client.Services
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
|
|
||||||
|
|
||||||
@@ -19,7 +21,6 @@ else
|
|||||||
<MudPaper Class="d-flex flex-wrap flex-grow-0 gap-4" Height="100%" Width="100%">
|
<MudPaper Class="d-flex flex-wrap flex-grow-0 gap-4" Height="100%" Width="100%">
|
||||||
@foreach (var item in examDtos)
|
@foreach (var item in examDtos)
|
||||||
{
|
{
|
||||||
<ExamPreview examDto="item" Width="256px" Height="256px"> </ExamPreview>
|
|
||||||
}
|
}
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
|
|
||||||
@@ -33,7 +34,7 @@ else
|
|||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
private Task<AuthenticationState> authenticationStateTask { get; set; }
|
private Task<AuthenticationState> authenticationStateTask { get; set; }
|
||||||
|
|
||||||
private List<ExamDto> examDtos = new List<ExamDto>();
|
private List<AssignmentDto> examDtos = new List<AssignmentDto>();
|
||||||
|
|
||||||
private bool isloding = true;
|
private bool isloding = true;
|
||||||
|
|
||||||
@@ -47,10 +48,17 @@ else
|
|||||||
{
|
{
|
||||||
isloding = true;
|
isloding = true;
|
||||||
Snackbar.Add("正在加载", Severity.Info);
|
Snackbar.Add("正在加载", Severity.Info);
|
||||||
var result = await ExamService.GetAllExam(authenticationStateTask.Result.User.Identity.Name);
|
var result = await ExamService.GetAllExam();
|
||||||
examDtos = result.Result as List<ExamDto> ?? new List<ExamDto>();
|
if (result.Status)
|
||||||
|
{
|
||||||
|
examDtos = result.Result as List<AssignmentDto> ?? new List<AssignmentDto>();
|
||||||
|
Snackbar.Add("加载成功", Severity.Info);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Snackbar.Add($"加载失败 {result.Message}", Severity.Error);
|
||||||
|
}
|
||||||
isloding = false;
|
isloding = false;
|
||||||
Snackbar.Add("加载成功", Severity.Info);
|
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
<MudPaper Class="overflow-hidden " Style="background-color:pink" Width="@Width" Height="@Height" MaxHeight="@MaxHeight" MaxWidth="@MaxWidth">
|
<MudPaper Class="overflow-hidden " Style="background-color:pink" Width="@Width" Height="@Height" MaxHeight="@MaxHeight" MaxWidth="@MaxWidth">
|
||||||
<MudPaper Class="d-flex flex-column flex-grow-1 justify-content-between" Height="100%" Style="background-color:green">
|
<MudPaper Class="d-flex flex-column flex-grow-1 justify-content-between" Height="100%" Style="background-color:green">
|
||||||
|
|
||||||
<MudText Typo="Typo.body2"> @examDto.AssignmentTitle </MudText>
|
<MudText Typo="Typo.body2"> @AssignmentDto.Title </MudText>
|
||||||
|
|
||||||
|
|
||||||
<MudPaper>
|
<MudPaper>
|
||||||
@@ -12,7 +12,11 @@
|
|||||||
|
|
||||||
<MudButton OnClick="ExamClick"> 详情 </MudButton>
|
<MudButton OnClick="ExamClick"> 详情 </MudButton>
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Delete" aria-label="delete" />
|
<MudIconButton Icon="@Icons.Material.Filled.Delete" aria-label="delete" />
|
||||||
<MudIconButton Icon="@Icons.Custom.Brands.GitHub" OnClick="CheckExam" Color="Color.Primary" aria-label="github" />
|
@if (bteacher)
|
||||||
|
{
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Check" OnClick="CheckExam" Color="Color.Primary" aria-label="github" />
|
||||||
|
}
|
||||||
|
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Favorite" Color="Color.Secondary" aria-label="add to favorite" />
|
<MudIconButton Icon="@Icons.Material.Filled.Favorite" Color="Color.Secondary" aria-label="add to favorite" />
|
||||||
</MudButtonGroup>
|
</MudButtonGroup>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
@@ -21,12 +25,16 @@
|
|||||||
</MudPaper>
|
</MudPaper>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
[CascadingParameter]
|
||||||
|
private Task<AuthenticationState> authenticationStateTask { get; set; }
|
||||||
|
|
||||||
|
private bool bteacher = false;
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
public NavigationManager navigationManager { get; set; }
|
public NavigationManager navigationManager { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public ExamDto examDto { get; set; }
|
public AssignmentDto AssignmentDto { get; set; }
|
||||||
|
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
@@ -45,13 +53,19 @@
|
|||||||
public string? MaxHeight { get; set; } = "64";
|
public string? MaxHeight { get; set; } = "64";
|
||||||
|
|
||||||
|
|
||||||
|
protected override Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
bteacher = authenticationStateTask.Result.User.IsInRole("Teacher");
|
||||||
|
return base.OnInitializedAsync();
|
||||||
|
}
|
||||||
|
|
||||||
private void ExamClick()
|
private void ExamClick()
|
||||||
{
|
{
|
||||||
navigationManager.NavigateTo($"exam/edit/{examDto.AssignmentId}");
|
navigationManager.NavigateTo($"exam/edit/{AssignmentDto.Id}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CheckExam()
|
private void CheckExam()
|
||||||
{
|
{
|
||||||
navigationManager.NavigateTo($"exam/check/{examDto.AssignmentId}");
|
navigationManager.NavigateTo($"exam/check/{AssignmentDto.Id}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
115
TechHelper.Client/Pages/Exam/ExamView/ExamStructView.razor
Normal file
115
TechHelper.Client/Pages/Exam/ExamView/ExamStructView.razor
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
@using Entities.Contracts
|
||||||
|
@using Entities.DTO
|
||||||
|
@using Newtonsoft.Json
|
||||||
|
@using TechHelper.Client.Exam
|
||||||
|
@using TechHelper.Client.Pages.Exam.QuestionCard
|
||||||
|
|
||||||
|
<MudPaper @onclick:stopPropagation Style="background-color:transparent" Elevation="0">
|
||||||
|
<MudPaper Elevation=@Elevation Class=@Class @onclick="HandleClick" Style="@Style">
|
||||||
|
|
||||||
|
<MudStack Row="true" Class="justify-content-between align-content-center" Style="background-color: transparent">
|
||||||
|
<MudText Class="justify-content-lg-start" Typo="Typo.h6">@ExamStruct.Title</MudText>
|
||||||
|
<MudStack Row="true" Class="align-content-center">
|
||||||
|
<MudText Class="ma-auto" Align="Align.Center" Typo="Typo.body2"> Num: @ExamStruct.ChildrenAssignmentQuestion.Count</MudText>
|
||||||
|
<MudText Class="ma-auto" Align="Align.Center" Typo="Typo.body2"><b>总分:</b> @ExamStruct.Score 分</MudText>
|
||||||
|
<MudToggleIconButton @bind-Toggled="ExamStruct.BCorrect"
|
||||||
|
Icon="@Icons.Material.Filled.Close"
|
||||||
|
Color="@Color.Error"
|
||||||
|
ToggledIcon="@Icons.Material.Filled.Check"
|
||||||
|
ToggledColor="@Color.Success"
|
||||||
|
title="@(ExamStruct.BCorrect ? "On" : "Off")" />
|
||||||
|
|
||||||
|
<MudIconButton Color="Color.Tertiary" Icon="@Icons.Material.Filled.ExpandLess" Size="Size.Small" />
|
||||||
|
<MudIconButton Color="Color.Tertiary" Icon="@Icons.Material.Filled.ExpandMore" Size="Size.Small" />
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Delete" aria-label="delete" Size="Size.Small" />
|
||||||
|
<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>
|
||||||
|
|
||||||
|
@if (ExamStruct.Question != null)
|
||||||
|
{
|
||||||
|
<QuestionCard Question="ExamStruct.Question" Index="ExamStruct.Index" Elevation=0 Class="my-2 pa-1 rounded-xl" />
|
||||||
|
}
|
||||||
|
|
||||||
|
@foreach (var examStruct in ExamStruct.ChildrenAssignmentQuestion)
|
||||||
|
{
|
||||||
|
<ExamStructView ExamStruct="examStruct" ClickedStruct="HandleChildStructClick" Elevation=@(examStruct.Question != null
|
||||||
|
&& examStruct.ChildrenAssignmentQuestion.Count == 0 ? 0 : 0) Class="@($"my-2 pa-1 rounded-xl {(examStruct.StructType != AssignmentStructType.Question ? "my-5" : "my-1")}")"
|
||||||
|
Style=@(examStruct.StructType switch
|
||||||
|
{
|
||||||
|
AssignmentStructType.Question => "background-color: #ececec",
|
||||||
|
AssignmentStructType.Group => "background-color: #ffffff",
|
||||||
|
AssignmentStructType.Struct => "background-color: #cccccccc",
|
||||||
|
AssignmentStructType.SubQuestion => "background-color: #ffffff",
|
||||||
|
AssignmentStructType.Option => "background-color: #ffffff",
|
||||||
|
_ => "background-color: transparent"
|
||||||
|
}) />
|
||||||
|
}
|
||||||
|
|
||||||
|
</MudPaper>
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
|
||||||
|
@* Style=@(examStruct.StructType switch
|
||||||
|
{
|
||||||
|
AssignmentStructType.Question => "background-color: #ffffff",
|
||||||
|
AssignmentStructType.Composite => "background-color: #ececec",
|
||||||
|
AssignmentStructType.Struct => "background-color: #dcdcdc",
|
||||||
|
AssignmentStructType.SubQuestion => "background-color: #ffffff",
|
||||||
|
AssignmentStructType.Option => "background-color: #dddddd",
|
||||||
|
_ => "background-color: transparent"
|
||||||
|
}) *@
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public AssignmentQuestionDto ExamStruct { get; set; } = new AssignmentQuestionDto();
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public EventCallback<AssignmentQuestionDto> ClickedStruct { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Class { get; set; } = "my-2 pa-1";
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public int Elevation { get; set; } = 0;
|
||||||
|
|
||||||
|
[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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void HandleChildStructClick(AssignmentQuestionDto clickedChildExamStruct)
|
||||||
|
{
|
||||||
|
await ClickedStruct.InvokeAsync(clickedChildExamStruct);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleSelected(int num)
|
||||||
|
{
|
||||||
|
ExamStruct.Question.DifficultyLevel = (DifficultyLevel)num;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -4,11 +4,11 @@
|
|||||||
@if (ParsedExam != null)
|
@if (ParsedExam != null)
|
||||||
{
|
{
|
||||||
|
|
||||||
<MudPaper Height="@Height" Class="@Class" Style="@Style" Width="@Width">
|
<MudPaper Height="@Height" Class="@Class" Style="@Style" Width="@Width" Elevation="5">
|
||||||
<MudText Class="d-flex justify-content-center" Typo="Typo.h6"> @ParsedExam.AssignmentTitle </MudText>
|
<MudText Class="d-flex justify-content-center" Typo="Typo.button"> <b> @ParsedExam.Title </b></MudText>
|
||||||
<MudText Typo="Typo.body1"> @ParsedExam.Description </MudText>
|
<MudText Typo="Typo.body1"> @ParsedExam.Description </MudText>
|
||||||
|
|
||||||
<ExamGroupView MajorQGList="@ParsedExam.QuestionGroups.SubQuestionGroups" Elevation="1" Class="ma-0 pa-2" />
|
<ExamStructView ExamStruct="@ParsedExam.ExamStruct" Elevation="0" ClickedStruct="HandleClickedStruct" Class="ma-0 pa-2 rounded-xl" />
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,11 @@ else
|
|||||||
@code {
|
@code {
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public ExamDto ParsedExam { get; set; } = new ExamDto();
|
public AssignmentDto ParsedExam { get; set; } = new AssignmentDto();
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public EventCallback<AssignmentQuestionDto> ClickedStruct { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Height { get; set; } = "100%";
|
public string Height { get; set; } = "100%";
|
||||||
[Parameter]
|
[Parameter]
|
||||||
@@ -33,4 +37,10 @@ else
|
|||||||
public string Class { get; set; } = "";
|
public string Class { get; set; } = "";
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Style { get; set; } = "";
|
public string Style { get; set; } = "";
|
||||||
|
|
||||||
|
|
||||||
|
private void HandleClickedStruct(AssignmentQuestionDto dto)
|
||||||
|
{
|
||||||
|
ClickedStruct.InvokeAsync(dto);
|
||||||
|
}
|
||||||
}
|
}
|
@@ -1,7 +1,34 @@
|
|||||||
@page "/exam"
|
@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 {
|
@code {
|
||||||
|
|
||||||
|
|
||||||
|
[CascadingParameter]
|
||||||
|
private Task<AuthenticationState> authenticationStateTask { get; set; }
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
if (authenticationStateTask is null)
|
||||||
|
{
|
||||||
|
NavigationManager.Refresh(forceReload: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,127 +1,62 @@
|
|||||||
@using TechHelper.Client.Exam
|
@using TechHelper.Client.Exam
|
||||||
|
@using Entities.Contracts // Assuming SubjectAreaEnum is defined here, adjust if not
|
||||||
|
|
||||||
<MudPaper Outlined="true" Class="mt-2">
|
<MudPaper Outlined="true" Class="mt-2">
|
||||||
|
|
||||||
<MudRadioGroup @bind-Value="_examParser">
|
<MudText Typo="Typo.h6" Class="mb-4">Current Parsing Rules</MudText>
|
||||||
@foreach (ExamParserEnum item in Enum.GetValues(typeof(ExamParserEnum)))
|
|
||||||
{
|
|
||||||
<MudRadio T="ExamParserEnum" Value="@item">@item</MudRadio>
|
|
||||||
}
|
|
||||||
</MudRadioGroup>
|
|
||||||
|
|
||||||
<MudTextField @bind-Value="_ParserConfig" Label="正则表达式模式" Variant="Variant.Outlined" FullWidth="true" Class="mb-2" />
|
@* Display Question Patterns *@
|
||||||
<MudNumericField Label="优先级" @bind-Value="_Priority" Variant="Variant.Outlined" Min="1" Max="100" />
|
@if (ExamParserConfig.QuestionPatterns.Any())
|
||||||
<MudButton OnClick="AddPattern" Variant="Variant.Filled" Color="Color.Primary" Class="mt-2">添加模式</MudButton>
|
{
|
||||||
|
<MudExpansionPanel Text="Question Patterns" Class="mb-2" IsInitiallyExpanded="true">
|
||||||
|
<MudStack Spacing="1">
|
||||||
|
@foreach (var config in ExamParserConfig.QuestionPatterns)
|
||||||
|
{
|
||||||
|
<MudChip T="string" Color="Color.Info" Variant="Variant.Outlined" Class="d-flex justify-content-between align-items-center">
|
||||||
|
<div class="d-flex flex-column align-items-start">
|
||||||
|
<MudText Typo="Typo.body2">**Type:** @config.Type</MudText>
|
||||||
|
<MudText Typo="Typo.body2">**Pattern:** <code>@config.Pattern</code></MudText>
|
||||||
|
</div>
|
||||||
|
<MudText Typo="Typo.body2">**Priority:** @config.Priority</MudText>
|
||||||
|
</MudChip>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
</MudExpansionPanel>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.body2" Class="mb-2">No question patterns configured.</MudText>
|
||||||
|
}
|
||||||
|
|
||||||
|
@* Display Option Patterns *@
|
||||||
<MudText Typo="Typo.subtitle1" Class="mb-2">所有已配置模式:</MudText>
|
@if (ExamParserConfig.OptionPatterns.Any())
|
||||||
|
{
|
||||||
|
<MudExpansionPanel Text="Option Patterns" Class="mb-2" IsInitiallyExpanded="true">
|
||||||
@if (ExamParserConfig.MajorQuestionGroupPatterns.Any())
|
<MudStack Spacing="1">
|
||||||
{
|
@foreach (var config in ExamParserConfig.OptionPatterns)
|
||||||
<MudExpansionPanel Text="大题组模式详情" Class="mb-2">
|
{
|
||||||
<MudStack>
|
<MudChip T="string" Color="Color.Warning" Variant="Variant.Outlined" Class="d-flex justify-content-between align-items-center">
|
||||||
@foreach (var config in ExamParserConfig.MajorQuestionGroupPatterns)
|
<div class="d-flex flex-column align-items-start">
|
||||||
{
|
<MudText Typo="Typo.body2">**Type:** @config.Type</MudText>
|
||||||
<MudChip T="string">
|
<MudText Typo="Typo.body2">**Pattern:** <code>@config.Pattern</code></MudText>
|
||||||
**模式:** <code>@config.Pattern</code>, **优先级:** @config.Priority
|
</div>
|
||||||
</MudChip>
|
<MudText Typo="Typo.body2">**Priority:** @config.Priority</MudText>
|
||||||
}
|
</MudChip>
|
||||||
</MudStack>
|
}
|
||||||
</MudExpansionPanel>
|
</MudStack>
|
||||||
}
|
</MudExpansionPanel>
|
||||||
else
|
}
|
||||||
{
|
else
|
||||||
<MudText Typo="Typo.body2" Class="mb-2">暂无大题组模式。</MudText>
|
{
|
||||||
}
|
<MudText Typo="Typo.body2" Class="mb-2">No option patterns configured.</MudText>
|
||||||
|
}
|
||||||
@* 题目模式详情 *@
|
|
||||||
@if (ExamParserConfig.QuestionPatterns.Any())
|
|
||||||
{
|
|
||||||
<MudExpansionPanel Text="题目模式详情" Class="mb-2">
|
|
||||||
<MudStack>
|
|
||||||
@foreach (var config in ExamParserConfig.QuestionPatterns)
|
|
||||||
{
|
|
||||||
<MudChip T="string">
|
|
||||||
**模式:** <code>@config.Pattern</code>, **优先级:** @config.Priority
|
|
||||||
</MudChip>
|
|
||||||
}
|
|
||||||
</MudStack>
|
|
||||||
</MudExpansionPanel>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<MudText Typo="Typo.body2" Class="mb-2">暂无题目模式。</MudText>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (ExamParserConfig.OptionPatterns.Any())
|
|
||||||
{
|
|
||||||
<MudExpansionPanel Text="选项模式详情" Class="mb-2">
|
|
||||||
<MudStack>
|
|
||||||
@foreach (var config in ExamParserConfig.OptionPatterns)
|
|
||||||
{
|
|
||||||
<MudChip T="string">
|
|
||||||
**模式:** <code>@config.Pattern</code>, **优先级:** @config.Priority
|
|
||||||
</MudChip>
|
|
||||||
}
|
|
||||||
</MudStack>
|
|
||||||
</MudExpansionPanel>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<MudText Typo="Typo.body2" Class="mb-2">暂无选项模式。</MudText>
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
<MudButton Variant="Variant.Filled" Color="Color.Secondary" OnClick="ResetPatterns">重置默认规则</MudButton>
|
|
||||||
|
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public ExamParserConfig ExamParserConfig { get; set; } = new ExamParserConfig();
|
||||||
|
|
||||||
public ExamParserEnum _examParser { get; set; } = ExamParserEnum.MajorQuestionGroupPatterns;
|
// No other properties or methods are needed as the component is now purely for display.
|
||||||
private string _ParserConfig;
|
|
||||||
private int _Priority = 1;
|
|
||||||
|
|
||||||
[Parameter]
|
|
||||||
public ExamParserConfig ExamParserConfig { get; set; } = new ExamParserConfig();
|
|
||||||
|
|
||||||
[Inject]
|
|
||||||
public ISnackbar Snackbar { get; set; }
|
|
||||||
|
|
||||||
private void AddPattern()
|
|
||||||
{
|
|
||||||
|
|
||||||
switch ((ExamParserEnum)_examParser)
|
|
||||||
{
|
|
||||||
case ExamParserEnum.MajorQuestionGroupPatterns:
|
|
||||||
ExamParserConfig.MajorQuestionGroupPatterns.Add(new RegexPatternConfig(_ParserConfig, _Priority));
|
|
||||||
Snackbar.Add($"已添加大题组模式: {_ParserConfig}, 优先级: {_Priority}", Severity.Success);
|
|
||||||
break;
|
|
||||||
case ExamParserEnum.QuestionPatterns:
|
|
||||||
ExamParserConfig.QuestionPatterns.Add(new RegexPatternConfig(_ParserConfig, _Priority));
|
|
||||||
Snackbar.Add($"已添加题目模式: {_ParserConfig}, 优先级: {_Priority}", Severity.Success);
|
|
||||||
break;
|
|
||||||
case ExamParserEnum.OptionPatterns:
|
|
||||||
ExamParserConfig.OptionPatterns.Add(new RegexPatternConfig(_ParserConfig, _Priority));
|
|
||||||
Snackbar.Add($"已添加选项模式: {_ParserConfig}, 优先级: {_Priority}", Severity.Success);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Snackbar.Add("请选择要添加的模式类型。");
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
StateHasChanged();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void ResetPatterns()
|
|
||||||
{
|
|
||||||
ExamParserConfig = new ExamParserConfig();
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,36 +0,0 @@
|
|||||||
@using Entities.DTO
|
|
||||||
@using TechHelper.Client.Exam
|
|
||||||
|
|
||||||
<MudPaper Class=@Class Elevation=@Elevation Outlined="false">
|
|
||||||
|
|
||||||
<MudText Typo="Typo.subtitle1">
|
|
||||||
<b>@Question.Index</b> @((MarkupString)Question.Stem.Replace("\n", "<br />"))
|
|
||||||
@if (Question.Score > 0)
|
|
||||||
{
|
|
||||||
<MudText Typo="Typo.body2" Class="d-inline ml-2">(@Question.Score 分)</MudText>
|
|
||||||
}
|
|
||||||
</MudText>
|
|
||||||
|
|
||||||
@if (Question.Options.Any())
|
|
||||||
{
|
|
||||||
<div class="mt-2">
|
|
||||||
@foreach (var option in Question.Options)
|
|
||||||
{
|
|
||||||
var tempOption = option;
|
|
||||||
<p>@((MarkupString)(tempOption.Value.Replace("\n", "<br />")))</p>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
</MudPaper>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
[Parameter]
|
|
||||||
public SubQuestionDto Question { get; set; } = new SubQuestionDto();
|
|
||||||
|
|
||||||
[Parameter]
|
|
||||||
public string Class { get; set; }
|
|
||||||
|
|
||||||
[Parameter]
|
|
||||||
public int Elevation { get; set; }
|
|
||||||
}
|
|
46
TechHelper.Client/Pages/Exam/QuestionCard/QuestionCard.razor
Normal file
46
TechHelper.Client/Pages/Exam/QuestionCard/QuestionCard.razor
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
@using Entities.Contracts
|
||||||
|
@using Entities.DTO
|
||||||
|
@using TechHelper.Client.Exam
|
||||||
|
|
||||||
|
<MudPaper Class=@Class Elevation=@Elevation Outlined="false">
|
||||||
|
|
||||||
|
<MudText Typo="Typo.subtitle1">
|
||||||
|
<b>@Index</b> @((MarkupString)Question.Title.Replace("\n", "<br />"))
|
||||||
|
@if (Score > 0)
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.body2" Class="d-inline ml-2">(@Score 分)</MudText>
|
||||||
|
}
|
||||||
|
</MudText>
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(Question.Options))
|
||||||
|
{
|
||||||
|
<div class="mt-2">
|
||||||
|
@foreach (var option in Question.Options.ParseOptionsFromText())
|
||||||
|
{
|
||||||
|
var tempOption = option;
|
||||||
|
<p>@((MarkupString)(tempOption.Replace("\n", "<br />")))</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public QuestionDto Question { get; set; } = new QuestionDto();
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public AssignmentStructType Type { get; set; } = AssignmentStructType.Question;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public byte Index { get; set; } = 0;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public byte Score { get; set; } = 0;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Class { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public int Elevation { get; set; }
|
||||||
|
}
|
68
TechHelper.Client/Pages/Exam/QuestionCard/QuestionEdit.razor
Normal file
68
TechHelper.Client/Pages/Exam/QuestionCard/QuestionEdit.razor
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
@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" />
|
||||||
|
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
|
||||||
|
@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();
|
||||||
|
}
|
||||||
|
}
|
9
TechHelper.Client/Pages/Exam/QuestionDetailView.razor
Normal file
9
TechHelper.Client/Pages/Exam/QuestionDetailView.razor
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<MudText> Title </MudText>
|
||||||
|
<MudText> Error Person Num </MudText>
|
||||||
|
<MudText> List Error Person </MudText>
|
||||||
|
<MudText> Lesson </MudText>
|
||||||
|
<MudText> Keypoint </MudText>
|
||||||
|
<MudText> Answer </MudText>
|
||||||
|
@code {
|
||||||
|
|
||||||
|
}
|
4
TechHelper.Client/Pages/Exam/StudentExamView.razor
Normal file
4
TechHelper.Client/Pages/Exam/StudentExamView.razor
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
@code {
|
||||||
|
|
||||||
|
}
|
32
TechHelper.Client/Pages/Global/LoginInOut/LoginInOut.razor
Normal file
32
TechHelper.Client/Pages/Global/LoginInOut/LoginInOut.razor
Normal 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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%";
|
||||||
|
}
|
@@ -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; }
|
||||||
|
}
|
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,118 +1,19 @@
|
|||||||
@page "/"
|
@page "/"
|
||||||
@using Microsoft.AspNetCore.Authorization
|
@using Microsoft.AspNetCore.Authorization
|
||||||
|
@using TechHelper.Client.Pages.Common.Exam
|
||||||
|
|
||||||
<AuthorizeView Roles="Administrator">
|
<AuthorizeView Roles="Student">
|
||||||
|
<Authorized>
|
||||||
<MudText> Hello @context.User.Identity.Name</MudText>
|
<TechHelper.Client.Pages.Student.HomePage />
|
||||||
@foreach (var item in context.User.Claims)
|
</Authorized>
|
||||||
{
|
|
||||||
<MudPaper class="ma-2 pa-2">
|
|
||||||
<MudText> @item.Value </MudText>
|
|
||||||
<MudText> @item.Issuer </MudText>
|
|
||||||
<MudText> @item.Subject </MudText>
|
|
||||||
<MudText> @item.Properties </MudText>
|
|
||||||
<MudText> @item.ValueType </MudText>
|
|
||||||
</MudPaper>
|
|
||||||
}
|
|
||||||
|
|
||||||
Welcome to your new app.
|
|
||||||
</AuthorizeView>
|
</AuthorizeView>
|
||||||
|
@code {
|
||||||
|
[CascadingParameter]
|
||||||
|
private Task<AuthenticationState> authenticationStateTask { get; set; }
|
||||||
|
|
||||||
<MudText>Hello </MudText>
|
protected override Task OnInitializedAsync()
|
||||||
<MudText>Hello </MudText>
|
{
|
||||||
<MudText>Hello </MudText>
|
return base.OnInitializedAsync();
|
||||||
<MudText>Hello </MudText>
|
Console.WriteLine(authenticationStateTask.Result.User.IsInRole("Student"));
|
||||||
<MudText>Hello </MudText>
|
}
|
||||||
<MudText>Hello </MudText>
|
}
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
||||||
<MudText>Hello </MudText>
|
|
@@ -55,4 +55,5 @@ else
|
|||||||
<MudText Class="ma-3 pa-3"> 年级 : @authenticationStateTask.Result.User.FindFirst("Grade")?.Value.ToString() </MudText>
|
<MudText Class="ma-3 pa-3"> 年级 : @authenticationStateTask.Result.User.FindFirst("Grade")?.Value.ToString() </MudText>
|
||||||
<MudText Class="ma-3 pa-3"> 班级 : @authenticationStateTask.Result.User.FindFirst("Class")?.Value.ToString() </MudText>
|
<MudText Class="ma-3 pa-3"> 班级 : @authenticationStateTask.Result.User.FindFirst("Class")?.Value.ToString() </MudText>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
}
|
}
|
||||||
|
<RestoreUserRole></RestoreUserRole>
|
@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Components.Authorization;
|
|||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
using System.Collections.Frozen;
|
using System.Collections.Frozen;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Security.Claims;
|
||||||
using TechHelper.Client.HttpRepository;
|
using TechHelper.Client.HttpRepository;
|
||||||
using TechHelper.Client.Services;
|
using TechHelper.Client.Services;
|
||||||
|
|
||||||
|
@@ -3,58 +3,56 @@
|
|||||||
@using System.ComponentModel.DataAnnotations
|
@using System.ComponentModel.DataAnnotations
|
||||||
@using Microsoft.AspNetCore.Identity
|
@using Microsoft.AspNetCore.Identity
|
||||||
|
|
||||||
|
<MudPaper Class="flex-grow-1">
|
||||||
|
|
||||||
<PageTitle>Profile</PageTitle>
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
<h3>Profile</h3>
|
<EditForm Model="Input" FormName="profile" OnValidSubmit="OnValidSubmitAsync" method="post">
|
||||||
|
<DataAnnotationsValidator />
|
||||||
<div class="row">
|
<ValidationSummary class="text-danger" role="alert" />
|
||||||
<div class="col-md-6">
|
<div class="form-floating mb-3">
|
||||||
<EditForm Model="Input" FormName="profile" OnValidSubmit="OnValidSubmitAsync" method="post">
|
<input type="text" value="@username" class="form-control" placeholder="Please choose your username." disabled />
|
||||||
<DataAnnotationsValidator />
|
<label for="username" class="form-label">Username</label>
|
||||||
<ValidationSummary class="text-danger" role="alert" />
|
</div>
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
<input type="text" value="@username" class="form-control" placeholder="Please choose your username." disabled />
|
<InputText @bind-Value="Input.PhoneNumber" class="form-control" placeholder="Please enter your phone number." />
|
||||||
<label for="username" class="form-label">Username</label>
|
<label for="phone-number" class="form-label">Phone number</label>
|
||||||
</div>
|
<ValidationMessage For="() => Input.PhoneNumber" class="text-danger" />
|
||||||
<div class="form-floating mb-3">
|
</div>
|
||||||
<InputText @bind-Value="Input.PhoneNumber" class="form-control" placeholder="Please enter your phone number." />
|
<button type="submit" class="w-100 btn btn-lg btn-primary">Save</button>
|
||||||
<label for="phone-number" class="form-label">Phone number</label>
|
</EditForm>
|
||||||
<ValidationMessage For="() => Input.PhoneNumber" class="text-danger" />
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="w-100 btn btn-lg btn-primary">Save</button>
|
|
||||||
</EditForm>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
</MudPaper>
|
||||||
@code {
|
@code {
|
||||||
|
|
||||||
private string? username;
|
private string? username;
|
||||||
private string? phoneNumber;
|
private string? phoneNumber;
|
||||||
|
|
||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
private Task<AuthenticationState> authenticationStateTask { get; set; }
|
private Task<AuthenticationState> authenticationStateTask { get; set; }
|
||||||
|
|
||||||
[SupplyParameterFromForm]
|
[SupplyParameterFromForm]
|
||||||
private InputModel Input { get; set; } = new();
|
private InputModel Input { get; set; } = new();
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
username = authenticationStateTask.Result.User.Identity.Name;
|
username = authenticationStateTask.Result.User.Identity.Name;
|
||||||
phoneNumber = authenticationStateTask.Result.User.Identity.IsAuthenticated.ToString();
|
phoneNumber = authenticationStateTask.Result.User.Identity.IsAuthenticated.ToString();
|
||||||
|
|
||||||
Input.PhoneNumber ??= phoneNumber;
|
Input.PhoneNumber ??= phoneNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnValidSubmitAsync()
|
private async Task OnValidSubmitAsync()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class InputModel
|
private sealed class InputModel
|
||||||
{
|
{
|
||||||
[Phone]
|
[Phone]
|
||||||
[Display(Name = "Phone number")]
|
[Display(Name = "Phone number")]
|
||||||
public string? PhoneNumber { get; set; }
|
public string? PhoneNumber { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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%";
|
||||||
|
}
|
@@ -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; }
|
||||||
|
}
|
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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;
|
||||||
|
};
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user