ExamEdit&Check
This commit is contained in:
@@ -1,24 +1,21 @@
|
|||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization; // 用于 XML 序列化/反序列化
|
||||||
using TechHelper.Client.AI;
|
using TechHelper.Client.AI;
|
||||||
using TechHelper.Services;
|
using TechHelper.Services;
|
||||||
using Entities.DTO;
|
using Entities.DTO;
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json; // 用于 PostAsJsonAsync
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json; // 用于 JSON 反序列化
|
||||||
using TechHelper.Client.Pages.Exam;
|
|
||||||
|
|
||||||
|
|
||||||
namespace TechHelper.Client.Exam
|
namespace TechHelper.Client.Exam
|
||||||
{
|
{
|
||||||
public class ExamService : IExamService
|
public class ExamService : IExamService
|
||||||
{
|
{
|
||||||
private IAIService aIService;
|
private readonly IAIService _aIService; // 遵循命名规范,字段前加下划线
|
||||||
private IHttpClientFactory httpClientFactory;
|
private readonly HttpClient _client; // 直接注入 HttpClient
|
||||||
|
|
||||||
public ExamService(IAIService aIService,
|
public ExamService(IAIService aIService, HttpClient client) // 修正点:直接注入 HttpClient
|
||||||
IHttpClientFactory httpClientFactory)
|
|
||||||
{
|
{
|
||||||
this.aIService = aIService;
|
_aIService = aIService;
|
||||||
this.httpClientFactory = httpClientFactory;
|
_client = client; // 赋值注入的 HttpClient 实例
|
||||||
}
|
}
|
||||||
|
|
||||||
public ApiResponse ConvertToXML<T>(string xmlContent)
|
public ApiResponse ConvertToXML<T>(string xmlContent)
|
||||||
@@ -31,32 +28,16 @@ namespace TechHelper.Client.Exam
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
T deserializedObject = (T)serializer.Deserialize(reader);
|
T deserializedObject = (T)serializer.Deserialize(reader);
|
||||||
|
return ApiResponse.Success(result: deserializedObject, message: "XML 反序列化成功。");
|
||||||
// 成功时返回 ApiResponse
|
|
||||||
return new ApiResponse
|
|
||||||
{
|
|
||||||
Status = true,
|
|
||||||
Result = deserializedObject,
|
|
||||||
Message = "XML 反序列化成功。"
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
catch (InvalidOperationException ex)
|
catch (InvalidOperationException ex)
|
||||||
{
|
{
|
||||||
return new ApiResponse
|
return ApiResponse.Error(
|
||||||
{
|
message: $"XML 反序列化操作错误: {ex.Message}. 内部异常: {ex.InnerException?.Message ?? "无"}");
|
||||||
Status = false,
|
|
||||||
Result = null,
|
|
||||||
Message = $"XML 反序列化操作错误: {ex.Message}. 内部异常: {ex.InnerException?.Message ?? "无"}"
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return new ApiResponse
|
return ApiResponse.Error(message: $"处理 XML 反序列化时发生未知错误: {ex.Message}");
|
||||||
{
|
|
||||||
Status = false,
|
|
||||||
Result = null,
|
|
||||||
Message = $"处理 XML 反序列化时发生未知错误: {ex.Message}"
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,35 +46,21 @@ namespace TechHelper.Client.Exam
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string respon = await aIService.CallGLM(examContent, AIConfiguration.BreakQuestions);
|
string? response = await _aIService.CallGLM(examContent, AIConfiguration.BreakQuestions);
|
||||||
|
|
||||||
if (respon != null)
|
if (response != null)
|
||||||
{
|
{
|
||||||
return new ApiResponse
|
return ApiResponse.Success(result: response, message: "试题分割成功。");
|
||||||
{
|
|
||||||
Status = true,
|
|
||||||
Result = respon,
|
|
||||||
Message = "试题分割成功。"
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return new ApiResponse
|
return ApiResponse.Error(message: "AI 服务未能返回有效内容,或返回内容为空。");
|
||||||
{
|
|
||||||
Status = false,
|
|
||||||
Result = null,
|
|
||||||
Message = "AI 服务未能返回有效内容,或返回内容为空。"
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return new ApiResponse
|
// 实际应用中,这里应该加入日志记录
|
||||||
{
|
return ApiResponse.Error(message: $"处理试题分割时发生内部错误: {ex.Message}");
|
||||||
Status = false,
|
|
||||||
Result = null,
|
|
||||||
Message = $"处理试题分割时发生内部错误: {ex.Message}"
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,111 +68,95 @@ namespace TechHelper.Client.Exam
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string respon = await aIService.CallGLM(examContent, AIConfiguration.Format);
|
string? response = await _aIService.CallGLM(examContent, AIConfiguration.Format);
|
||||||
|
|
||||||
if (respon != null)
|
if (response != null)
|
||||||
{
|
{
|
||||||
return new ApiResponse
|
return ApiResponse.Success(result: response, message: "试题格式化成功。");
|
||||||
{
|
|
||||||
Status = true,
|
|
||||||
Result = respon,
|
|
||||||
Message = "试题格式化成功。"
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return new ApiResponse
|
return ApiResponse.Error(message: "AI 服务未能返回有效内容,或返回内容为空。");
|
||||||
{
|
|
||||||
Status = false,
|
|
||||||
Result = null,
|
|
||||||
Message = "AI 服务未能返回有效内容,或返回内容为空。"
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return new ApiResponse
|
// 实际应用中,这里应该加入日志记录
|
||||||
{
|
return ApiResponse.Error(message: $"处理试题格式化时发生内部错误: {ex.Message}");
|
||||||
Status = false,
|
|
||||||
Result = null,
|
|
||||||
Message = $"处理试题格式化时发生内部错误: {ex.Message}"
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ApiResponse> GetAllExam(string user)
|
public async Task<ApiResponse> GetAllExam(string user)
|
||||||
{
|
{
|
||||||
using (var client = httpClientFactory.CreateClient("Default"))
|
// 直接使用注入的 _client 实例
|
||||||
|
var response = await _client.GetAsync($"exam/getAllPreview?user={user}");
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
var response = await client.GetAsync($"exam/getAllPreview?user={user}");
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<List<ExamDto>>(content);
|
||||||
|
return ApiResponse.Success(result: result);
|
||||||
if (response.IsSuccessStatusCode)
|
}
|
||||||
{
|
else
|
||||||
var content = await response.Content.ReadAsStringAsync();
|
{
|
||||||
var result = JsonConvert.DeserializeObject<List<ExamDto>>(content);
|
// 读取错误信息,并返回 ApiResponse
|
||||||
return ApiResponse.Success(result: result);
|
var errorContent = await response.Content.ReadAsStringAsync();
|
||||||
}
|
return ApiResponse.Error(message: $"获取所有试题失败: {response.StatusCode} - {errorContent}");
|
||||||
else
|
|
||||||
{
|
|
||||||
return ApiResponse.Error(await response.Content.ReadAsStringAsync());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ApiResponse> GetExam(Guid guid)
|
public async Task<ApiResponse> GetExam(Guid guid)
|
||||||
{
|
{
|
||||||
return ApiResponse.Success("HELLO");
|
|
||||||
|
var response = await _client.GetAsync($"exam/get?id={guid}");
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
var exam = JsonConvert.DeserializeObject<ExamDto>(content);
|
||||||
|
return ApiResponse.Success(result: exam);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var errorContent = await response.Content.ReadAsStringAsync();
|
||||||
|
return ApiResponse.Error(message: $"获取试题失败: {response.StatusCode} - {errorContent}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ApiResponse> ParseSingleQuestionGroup(string examContent)
|
public async Task<ApiResponse> ParseSingleQuestionGroup(string examContent)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string respon = await aIService.CallGLM(examContent, AIConfiguration.ParseSignelQuestion2);
|
string? response = await _aIService.CallGLM(examContent, AIConfiguration.ParseSignelQuestion2);
|
||||||
|
|
||||||
if (respon != null)
|
if (response != null)
|
||||||
{
|
{
|
||||||
return new ApiResponse
|
return ApiResponse.Success(result: response, message: "试题解析成功。");
|
||||||
{
|
|
||||||
Status = true,
|
|
||||||
Result = respon,
|
|
||||||
Message = "试题解析成功。"
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return new ApiResponse
|
return ApiResponse.Error(message: "AI 服务未能返回有效内容,或返回内容为空。");
|
||||||
{
|
|
||||||
Status = false,
|
|
||||||
Result = null,
|
|
||||||
Message = "AI 服务未能返回有效内容,或返回内容为空。"
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return new ApiResponse
|
// 实际应用中,这里应该加入日志记录
|
||||||
{
|
return ApiResponse.Error(message: $"处理试题解析时发生内部错误: {ex.Message}");
|
||||||
Status = false,
|
|
||||||
Result = null,
|
|
||||||
Message = $"处理试题解析时发生内部错误: {ex.Message}"
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ApiResponse> SaveParsedExam(ExamDto examDto)
|
public async Task<ApiResponse> SaveParsedExam(ExamDto examDto)
|
||||||
{
|
{
|
||||||
using (var client = httpClientFactory.CreateClient("Default"))
|
// 直接使用注入的 _client 实例
|
||||||
{
|
var response = await _client.PostAsJsonAsync("exam/add", examDto);
|
||||||
var respont = await client.PostAsJsonAsync("exam/add",
|
|
||||||
examDto);
|
|
||||||
|
|
||||||
if (respont.StatusCode == System.Net.HttpStatusCode.OK)
|
if (response.IsSuccessStatusCode) // 检查是否是成功的状态码,例如 200 OK, 201 Created 等
|
||||||
{
|
{
|
||||||
return new ApiResponse(true, "ok");
|
return ApiResponse.Success(message: "试题保存成功。");
|
||||||
}
|
}
|
||||||
return new ApiResponse("false");
|
else
|
||||||
|
{
|
||||||
|
var errorContent = await response.Content.ReadAsStringAsync();
|
||||||
|
return ApiResponse.Error(message: $"保存试题失败: {response.StatusCode} - {errorContent}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
92
TechHelper.Client/Exam/ExamStruct.cs
Normal file
92
TechHelper.Client/Exam/ExamStruct.cs
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
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,30 +1,22 @@
|
|||||||
using TechHelper.Client.HttpRepository;
|
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 MudBlazor;
|
using TechHelper.Client.HttpRepository;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
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 RefreshTokenService _refreshTokenService;
|
|
||||||
|
|
||||||
private readonly ISnackbar _serviceProvider;
|
|
||||||
private ISnackbar toastService = null;
|
|
||||||
|
|
||||||
public HttpInterceptorHandlerService(
|
public HttpInterceptorHandlerService(
|
||||||
NavigationManager navManager,
|
NavigationManager navManager,
|
||||||
RefreshTokenService refreshTokenService,
|
RefreshTokenService2 refreshTokenService)
|
||||||
ISnackbar serviceProvider)
|
|
||||||
{
|
{
|
||||||
_navManager = navManager;
|
_navManager = navManager;
|
||||||
_refreshTokenService = refreshTokenService;
|
_refreshTokenService = refreshTokenService;
|
||||||
_serviceProvider = serviceProvider;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||||
@@ -36,50 +28,55 @@ namespace BlazorProducts.Client.HttpInterceptor
|
|||||||
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);
|
||||||
|
|
||||||
HandleResponse(response);
|
await HandleResponse(response);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleResponse(HttpResponseMessage response)
|
private async Task HandleResponse(HttpResponseMessage response)
|
||||||
{
|
{
|
||||||
if (response is null)
|
if (response is null)
|
||||||
{
|
{
|
||||||
|
|
||||||
_navManager.NavigateTo("/error");
|
_navManager.NavigateTo("/error");
|
||||||
throw new HttpResponseException("Server not available.");
|
throw new HttpResponseException("服务器不可用。");
|
||||||
}
|
}
|
||||||
|
|
||||||
var message = "";
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
var errorContent = await response.Content.ReadAsStringAsync();
|
||||||
|
Console.WriteLine($"HTTP 错误: {response.StatusCode}. 详情: {errorContent}");
|
||||||
|
|
||||||
switch (response.StatusCode)
|
switch (response.StatusCode)
|
||||||
{
|
{
|
||||||
case HttpStatusCode.NotFound:
|
case HttpStatusCode.NotFound:
|
||||||
|
// 404 Not Found error, navigate to a 404 page.
|
||||||
_navManager.NavigateTo("/404");
|
_navManager.NavigateTo("/404");
|
||||||
message = "Resource not found.";
|
|
||||||
break;
|
break;
|
||||||
case HttpStatusCode.BadRequest:
|
case HttpStatusCode.BadRequest:
|
||||||
message = "Invalid request. Please try again.";
|
// 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:
|
||||||
_navManager.NavigateTo("/unauthorized");
|
// 401 Unauthorized error. Navigate to an unauthorized page or login page.
|
||||||
message = "Unauthorized access";
|
_navManager.NavigateTo("/unauthorized"); // Or: _navManager.NavigateTo("/authentication/login");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
// For all other errors, navigate to a general error page.
|
||||||
_navManager.NavigateTo("/error");
|
_navManager.NavigateTo("/error");
|
||||||
message = "Something went wrong. Please contact the administrator.";
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -13,21 +13,20 @@ namespace TechHelper.Client.HttpRepository
|
|||||||
{
|
{
|
||||||
public class AuthenticationClientService : IAuthenticationClientService
|
public class AuthenticationClientService : IAuthenticationClientService
|
||||||
{
|
{
|
||||||
private HttpClient _client;
|
private readonly HttpClient _client;
|
||||||
private readonly IHttpClientFactory _clientFactory;
|
|
||||||
private readonly JsonSerializerOptions _options =
|
private readonly JsonSerializerOptions _options =
|
||||||
new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
|
new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
|
||||||
private readonly AuthenticationStateProvider _stateProvider;
|
private readonly AuthenticationStateProvider _stateProvider;
|
||||||
private readonly ILocalStorageService _localStorageService;
|
private readonly ILocalStorageService _localStorageService;
|
||||||
private readonly NavigationManager _navigationManager;
|
private readonly NavigationManager _navigationManager;
|
||||||
|
|
||||||
public AuthenticationClientService(IHttpClientFactory httpClientFactory,
|
// 构造函数现在直接接收 HttpClient
|
||||||
|
public AuthenticationClientService(HttpClient client, // <-- 修正点:直接注入 HttpClient
|
||||||
AuthenticationStateProvider authenticationStateProvider,
|
AuthenticationStateProvider authenticationStateProvider,
|
||||||
ILocalStorageService localStorageService,
|
ILocalStorageService localStorageService,
|
||||||
NavigationManager navigationManager)
|
NavigationManager navigationManager)
|
||||||
{
|
{
|
||||||
_clientFactory = httpClientFactory;
|
_client = client; // <-- 修正点:直接赋值
|
||||||
//_client = httpClientFactory.CreateClient("Default");
|
|
||||||
_localStorageService = localStorageService;
|
_localStorageService = localStorageService;
|
||||||
_stateProvider = authenticationStateProvider;
|
_stateProvider = authenticationStateProvider;
|
||||||
_navigationManager = navigationManager;
|
_navigationManager = navigationManager;
|
||||||
@@ -35,121 +34,105 @@ namespace TechHelper.Client.HttpRepository
|
|||||||
|
|
||||||
public async Task<AuthResponseDto> LoginAsync(UserForAuthenticationDto userForAuthenticationDto)
|
public async Task<AuthResponseDto> LoginAsync(UserForAuthenticationDto userForAuthenticationDto)
|
||||||
{
|
{
|
||||||
using (_client = _clientFactory.CreateClient("Default"))
|
// 移除 using (_client = _clientFactory.CreateClient("Default"))
|
||||||
{
|
// _client 已经是注入的实例,直接使用它
|
||||||
|
var reponse = await _client.PostAsJsonAsync("account/login",
|
||||||
|
userForAuthenticationDto);
|
||||||
|
|
||||||
var reponse = await _client.PostAsJsonAsync("account/login",
|
var content = await reponse.Content.ReadAsStringAsync();
|
||||||
userForAuthenticationDto);
|
|
||||||
|
|
||||||
var content = await reponse.Content.ReadAsStringAsync();
|
var result = JsonSerializer.Deserialize<AuthResponseDto>(content, _options);
|
||||||
|
|
||||||
var result = JsonSerializer.Deserialize<AuthResponseDto>(content, _options);
|
if (!reponse.IsSuccessStatusCode || result.Is2StepVerificationRequired)
|
||||||
|
return result;
|
||||||
|
|
||||||
if (!reponse.IsSuccessStatusCode || result.Is2StepVerificationRequired)
|
_localStorageService.SetItem("authToken", result.Token);
|
||||||
return result;
|
_localStorageService.SetItem("refreshToken", result.RefreshToken);
|
||||||
|
((AuthStateProvider)_stateProvider).NotifyUserAuthentication(
|
||||||
|
result.Token);
|
||||||
|
|
||||||
_localStorageService.SetItem("authToken", result.Token);
|
// 直接在注入的 _client 实例上设置默认请求头
|
||||||
_localStorageService.SetItem("refreshToken", result.RefreshToken);
|
_client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(
|
||||||
((AuthStateProvider)_stateProvider).NotifyUserAuthentication(
|
"bearer", result.Token);
|
||||||
result.Token);
|
|
||||||
|
|
||||||
_client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(
|
return new AuthResponseDto { IsAuthSuccessful = true };
|
||||||
"bearer", result.Token);
|
|
||||||
|
|
||||||
return new AuthResponseDto { IsAuthSuccessful = true };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task LogoutAsync()
|
public async Task LogoutAsync()
|
||||||
{
|
{
|
||||||
using (_client = _clientFactory.CreateClient("Default"))
|
// 移除 using (_client = _clientFactory.CreateClient("Default"))
|
||||||
{
|
_localStorageService.RemoveItem("authToken");
|
||||||
_localStorageService.RemoveItem("authToken");
|
_localStorageService.RemoveItem("refreshToken");
|
||||||
_localStorageService.RemoveItem("refreshToken");
|
((AuthStateProvider)_stateProvider).NotifyUserLogout();
|
||||||
((AuthStateProvider)_stateProvider).NotifyUserLogout();
|
|
||||||
|
|
||||||
_client.DefaultRequestHeaders.Authorization = null;
|
// 直接在注入的 _client 实例上清除默认请求头
|
||||||
}
|
_client.DefaultRequestHeaders.Authorization = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> RefreshTokenAsync()
|
public async Task<string> RefreshTokenAsync()
|
||||||
{
|
{
|
||||||
using (_client = _clientFactory.CreateClient("Default"))
|
// 移除 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");
|
|
||||||
|
|
||||||
var response = await _client.PostAsJsonAsync("token/refresh",
|
var response = await _client.PostAsJsonAsync("token/refresh",
|
||||||
new RefreshTokenDto
|
new RefreshTokenDto
|
||||||
{
|
{
|
||||||
Token = token,
|
Token = token,
|
||||||
RefreshToken = refreshToken
|
RefreshToken = refreshToken
|
||||||
});
|
});
|
||||||
|
|
||||||
var content = await response.Content.ReadAsStringAsync();
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
var result = JsonSerializer.Deserialize<AuthResponseDto>(content, _options);
|
var result = JsonSerializer.Deserialize<AuthResponseDto>(content, _options);
|
||||||
|
|
||||||
_localStorageService.SetItem("authToken", result.Token);
|
_localStorageService.SetItem("authToken", result.Token);
|
||||||
_localStorageService.SetItem("refreshToken", result.RefreshToken);
|
_localStorageService.SetItem("refreshToken", result.RefreshToken);
|
||||||
|
|
||||||
_client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", result.Token);
|
// 直接在注入的 _client 实例上设置默认请求头
|
||||||
|
_client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", result.Token);
|
||||||
|
|
||||||
return result.Token;
|
return result.Token;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ResponseDto> RegisterUserAsync(UserForRegistrationDto userForRegistrationDto)
|
public async Task<ResponseDto> RegisterUserAsync(UserForRegistrationDto userForRegistrationDto)
|
||||||
{
|
{
|
||||||
using (_client = _clientFactory.CreateClient("Default"))
|
// 移除 using (_client = _clientFactory.CreateClient("Default"))
|
||||||
|
userForRegistrationDto.ClientURI = Path.Combine(
|
||||||
|
_navigationManager.BaseUri, "emailconfirmation");
|
||||||
|
|
||||||
|
var reponse = await _client.PostAsJsonAsync("account/register",
|
||||||
|
userForRegistrationDto);
|
||||||
|
|
||||||
|
if (!reponse.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
|
var content = await reponse.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonSerializer.Deserialize<ResponseDto>(content, _options);
|
||||||
userForRegistrationDto.ClientURI = Path.Combine(
|
return result;
|
||||||
_navigationManager.BaseUri, "emailconfirmation");
|
|
||||||
|
|
||||||
var reponse = await _client.PostAsJsonAsync("account/register",
|
|
||||||
userForRegistrationDto);
|
|
||||||
|
|
||||||
if (!reponse.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
var content = await reponse.Content.ReadAsStringAsync();
|
|
||||||
|
|
||||||
var result = JsonSerializer.Deserialize<ResponseDto>(content, _options);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ResponseDto { IsSuccessfulRegistration = true };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return new ResponseDto { IsSuccessfulRegistration = true };
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<HttpStatusCode> ForgotPasswordAsync(ForgotPasswordDto forgotPasswordDto)
|
public async Task<HttpStatusCode> ForgotPasswordAsync(ForgotPasswordDto forgotPasswordDto)
|
||||||
{
|
{
|
||||||
using (_client = _clientFactory.CreateClient("Default"))
|
// 移除 using (_client = _clientFactory.CreateClient("Default"))
|
||||||
{
|
forgotPasswordDto.ClientURI = Path.Combine(_navigationManager.BaseUri, "resetpassword");
|
||||||
|
var result = await _client.PostAsJsonAsync("account/forgotpassword",
|
||||||
|
forgotPasswordDto);
|
||||||
|
|
||||||
|
return result.StatusCode;
|
||||||
forgotPasswordDto.ClientURI = Path.Combine(_navigationManager.BaseUri, "resetpassword");
|
|
||||||
var result = await _client.PostAsJsonAsync("account/forgotpassword",
|
|
||||||
forgotPasswordDto);
|
|
||||||
|
|
||||||
return result.StatusCode;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ResetPasswordResponseDto> ResetPasswordAsync(ResetPasswordDto resetPasswordDto)
|
public async Task<ResetPasswordResponseDto> ResetPasswordAsync(ResetPasswordDto resetPasswordDto)
|
||||||
{
|
{
|
||||||
using (_client = _clientFactory.CreateClient("Default"))
|
// 移除 using (_client = _clientFactory.CreateClient("Default"))
|
||||||
{
|
var resetresult = await _client.PostAsJsonAsync("account/resetpassword",
|
||||||
var resetresult = await _client.PostAsJsonAsync("account/resetpassword",
|
resetPasswordDto);
|
||||||
resetPasswordDto);
|
|
||||||
|
|
||||||
var resetContent = await resetresult.Content.ReadAsStringAsync();
|
var resetContent = await resetresult.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonSerializer.Deserialize<ResetPasswordResponseDto>(resetContent, _options);
|
||||||
|
|
||||||
var result = JsonSerializer.Deserialize<ResetPasswordResponseDto>(resetContent, _options);
|
return result;
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<HttpStatusCode> EmailConfirmationAsync(string email, string token)
|
public async Task<HttpStatusCode> EmailConfirmationAsync(string email, string token)
|
||||||
@@ -159,40 +142,35 @@ namespace TechHelper.Client.HttpRepository
|
|||||||
["email"] = email,
|
["email"] = email,
|
||||||
["token"] = token
|
["token"] = token
|
||||||
};
|
};
|
||||||
using (_client = _clientFactory.CreateClient("Default"))
|
// 移除 using (_client = _clientFactory.CreateClient("Default"))
|
||||||
{
|
var response = await _client.GetAsync(QueryHelpers.AddQueryString(
|
||||||
var response = await _client.GetAsync(QueryHelpers.AddQueryString(
|
"account/emailconfirmation", queryStringParam));
|
||||||
"account/emailconfirmation", queryStringParam));
|
|
||||||
|
|
||||||
return response.StatusCode;
|
return response.StatusCode;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<AuthResponseDto> LoginVerfication(TwoFactorVerificationDto twoFactorVerificationDto)
|
public async Task<AuthResponseDto> LoginVerfication(TwoFactorVerificationDto twoFactorVerificationDto)
|
||||||
{
|
{
|
||||||
using (_client = _clientFactory.CreateClient("Default"))
|
// 移除 using (_client = _clientFactory.CreateClient("Default"))
|
||||||
{
|
var reponse = await _client.PostAsJsonAsync("account/twostepverification",
|
||||||
|
twoFactorVerificationDto);
|
||||||
|
|
||||||
var reponse = await _client.PostAsJsonAsync("account/twostepverification",
|
var content = await reponse.Content.ReadAsStringAsync();
|
||||||
twoFactorVerificationDto);
|
|
||||||
|
|
||||||
var content = await reponse.Content.ReadAsStringAsync();
|
var result = JsonSerializer.Deserialize<AuthResponseDto>(content, _options);
|
||||||
|
|
||||||
var result = JsonSerializer.Deserialize<AuthResponseDto>(content, _options);
|
if (!reponse.IsSuccessStatusCode)
|
||||||
|
return result;
|
||||||
|
|
||||||
if (!reponse.IsSuccessStatusCode)
|
_localStorageService.SetItem("authToken", result.Token);
|
||||||
return result;
|
_localStorageService.SetItem("refreshToken", result.RefreshToken);
|
||||||
|
((AuthStateProvider)_stateProvider).NotifyUserAuthentication(
|
||||||
|
result.Token);
|
||||||
|
|
||||||
_localStorageService.SetItem("authToken", result.Token);
|
_client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(
|
||||||
_localStorageService.SetItem("refreshToken", result.RefreshToken);
|
"bearer", result.Token);
|
||||||
((AuthStateProvider)_stateProvider).NotifyUserAuthentication(
|
|
||||||
result.Token);
|
|
||||||
|
|
||||||
_client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(
|
return new AuthResponseDto { IsAuthSuccessful = true };
|
||||||
"bearer", result.Token);
|
|
||||||
|
|
||||||
return new AuthResponseDto { IsAuthSuccessful = true };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
7
TechHelper.Client/HttpRepository/IRefreshTokenService.cs
Normal file
7
TechHelper.Client/HttpRepository/IRefreshTokenService.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace TechHelper.Client.HttpRepository
|
||||||
|
{
|
||||||
|
public interface IRefreshTokenService
|
||||||
|
{
|
||||||
|
public Task<string> TryRefreshToken();
|
||||||
|
}
|
||||||
|
}
|
@@ -1,34 +1,51 @@
|
|||||||
using Microsoft.AspNetCore.Components.Authorization;
|
using Microsoft.AspNetCore.Components.Authorization;
|
||||||
|
using Microsoft.Extensions.DependencyInjection; // 需要这个命名空间来获取 IServiceProvider
|
||||||
|
using System; // 需要这个命名空间来使用 Lazy<T>
|
||||||
|
|
||||||
namespace TechHelper.Client.HttpRepository
|
namespace TechHelper.Client.HttpRepository
|
||||||
{
|
{
|
||||||
public class RefreshTokenService
|
public class RefreshTokenService : IRefreshTokenService
|
||||||
{
|
{
|
||||||
private readonly AuthenticationStateProvider _authenticationStateProvider;
|
|
||||||
private readonly IAuthenticationClientService _authenticationClientService;
|
|
||||||
|
|
||||||
public RefreshTokenService(AuthenticationStateProvider authenticationStateProvider, IAuthenticationClientService authenticationClientService)
|
private readonly Lazy<AuthenticationStateProvider> _authenticationStateProvider;
|
||||||
|
private readonly Lazy<IAuthenticationClientService> _authenticationClientService;
|
||||||
|
|
||||||
|
public RefreshTokenService(IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
_authenticationStateProvider = authenticationStateProvider;
|
_authenticationStateProvider = new Lazy<AuthenticationStateProvider>(
|
||||||
_authenticationClientService = authenticationClientService;
|
() => serviceProvider.GetRequiredService<AuthenticationStateProvider>());
|
||||||
|
|
||||||
|
_authenticationClientService = new Lazy<IAuthenticationClientService>(
|
||||||
|
() => serviceProvider.GetRequiredService<IAuthenticationClientService>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> TryRefreshToken()
|
public async Task<string> TryRefreshToken()
|
||||||
{
|
{
|
||||||
var authState = await _authenticationStateProvider.GetAuthenticationStateAsync();
|
var authState = await _authenticationStateProvider.Value.GetAuthenticationStateAsync();
|
||||||
var user = authState.User;
|
var user = authState.User;
|
||||||
var expClaim = user.FindFirst(c => c.Type.Equals("exp")).Value;
|
|
||||||
|
// 如果 user 或 claims 为空,表示用户未认证,直接返回空字符串
|
||||||
|
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(
|
var expTime = DateTimeOffset.FromUnixTimeSeconds(
|
||||||
Convert.ToInt64(expClaim));
|
Convert.ToInt64(expClaim));
|
||||||
|
|
||||||
var diff = expTime - DateTime.UtcNow;
|
var diff = expTime - DateTime.UtcNow;
|
||||||
|
|
||||||
|
// 只有当令牌即将过期时才尝试刷新
|
||||||
if (diff.TotalMinutes <= 2)
|
if (diff.TotalMinutes <= 2)
|
||||||
return await _authenticationClientService.RefreshTokenAsync();
|
return await _authenticationClientService.Value.RefreshTokenAsync(); // 访问 .Value 来调用方法
|
||||||
|
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
46
TechHelper.Client/HttpRepository/RefreshTokenService2.cs
Normal file
46
TechHelper.Client/HttpRepository/RefreshTokenService2.cs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
223
TechHelper.Client/Pages/Exam/ExamCheck.razor
Normal file
223
TechHelper.Client/Pages/Exam/ExamCheck.razor
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
@using Entities.DTO
|
||||||
|
@using TechHelper.Client.Exam
|
||||||
|
@page "/exam/check/{ExamID}"
|
||||||
|
|
||||||
|
|
||||||
|
<MudText Typo="Typo.h4" Class="mb-4">试卷批改预览: @ExamDto.AssignmentTitle</MudText>
|
||||||
|
<MudDivider Class="my-4" />
|
||||||
|
|
||||||
|
@if (_isLoading)
|
||||||
|
{
|
||||||
|
<MudProgressCircular Indeterminate="true" Color="Color.Primary" Class="d-flex justify-center my-8" />
|
||||||
|
<MudText Class="text-center">正在加载试卷和学生数据...</MudText>
|
||||||
|
}
|
||||||
|
else if (_questionsForTable.Any() && _students.Any())
|
||||||
|
{
|
||||||
|
<MudTable @ref="_table" T="QuestionRowData" Items="@_questionsForTable" Hover="true" Breakpoint="Breakpoint.Sm" Class="mud-elevation-2" Dense="true">
|
||||||
|
<HeaderContent>
|
||||||
|
<MudTh Style="width:100px;">序号</MudTh>
|
||||||
|
<MudTh Style="width:80px; text-align:center;">分值</MudTh>
|
||||||
|
@foreach (var student in _students)
|
||||||
|
{
|
||||||
|
<MudTh Style="width:120px; text-align:center;">
|
||||||
|
@student.Name
|
||||||
|
<MudTooltip Text="点击以切换此学生所有题目的对错">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Info" Size="Size.Small" Class="ml-1"
|
||||||
|
@onclick="() => ToggleStudentAllAnswers(student.Id)" />
|
||||||
|
</MudTooltip>
|
||||||
|
</MudTh>
|
||||||
|
}
|
||||||
|
</HeaderContent>
|
||||||
|
<RowTemplate>
|
||||||
|
<MudTd DataLabel="序号">@context.QuestionItem.Sequence</MudTd>
|
||||||
|
<MudTd DataLabel="分值" Style="text-align:center;">@context.QuestionItem.Score</MudTd>
|
||||||
|
@foreach (var student in _students)
|
||||||
|
{
|
||||||
|
<MudTd DataLabel="@student.Name" Style="text-align:center;">
|
||||||
|
@if (context.StudentAnswers.ContainsKey(student.Id))
|
||||||
|
{
|
||||||
|
<MudCheckBox @bind-Value="context.StudentAnswers[student.Id]" Size="Size.Small" Color="Color.Primary"></MudCheckBox>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudText Color="Color.Warning">N/A</MudText>
|
||||||
|
}
|
||||||
|
</MudTd>
|
||||||
|
}
|
||||||
|
</RowTemplate>
|
||||||
|
<PagerContent>
|
||||||
|
<MudTablePager />
|
||||||
|
</PagerContent>
|
||||||
|
</MudTable>
|
||||||
|
|
||||||
|
<MudPaper Class="pa-4 mt-4 mud-elevation-2 d-flex flex-column align-end">
|
||||||
|
<MudText Typo="Typo.h6">学生总分预览:</MudText>
|
||||||
|
@foreach (var student in _students)
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.subtitle1">
|
||||||
|
@student.Name: <MudText Typo="Typo.h5" Color="Color.Primary" Class="d-inline-block ml-2">@GetStudentTotalScore(student.Id)</MudText>
|
||||||
|
</MudText>
|
||||||
|
}
|
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Success" Class="mt-4" @onclick="SubmitGrading">
|
||||||
|
提交批改结果 (模拟)
|
||||||
|
</MudButton>
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Info" Class="mt-4">无法加载试卷或题目信息。</MudAlert>
|
||||||
|
<MudButton Variant="Variant.Text" Color="Color.Primary" Class="mt-4" >返回试卷列表</MudButton>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string ExamId { get; set; } // 从路由获取的试卷ID
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
public IExamService ExamService { get; set; } // 注入试卷服务
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
private ISnackbar Snackbar { get; set; } // 注入 Snackbar 用于消息提示
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
private NavigationManager Navigation { get; set; } // 注入导航管理器
|
||||||
|
|
||||||
|
private MudTable<QuestionRowData> _table = new(); // MudTable 实例引用
|
||||||
|
private ExamDto ExamDto { get; set; } = new ExamDto(); // 原始试卷数据
|
||||||
|
private ExamStruct _examStruct = new ExamStruct(); // 处理后的试卷结构,包含带序号的题目
|
||||||
|
|
||||||
|
private List<Student> _students = new List<Student>(); // 临时生成的学生列表
|
||||||
|
private List<QuestionRowData> _questionsForTable = new List<QuestionRowData>(); // 用于 MudTable 的数据源
|
||||||
|
|
||||||
|
private bool _isLoading = true; // 加载状态
|
||||||
|
|
||||||
|
// 在组件初始化时加载数据
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
_isLoading = true;
|
||||||
|
await LoadExamData();
|
||||||
|
GenerateTemporaryStudentsAndAnswers(); // 生成学生和初始作答数据
|
||||||
|
_isLoading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载试卷数据的方法
|
||||||
|
private async Task LoadExamData()
|
||||||
|
{
|
||||||
|
if (Guid.TryParse(ExamId, out Guid parsedExamId))
|
||||||
|
{
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成临时学生和作答数据
|
||||||
|
private void GenerateTemporaryStudentsAndAnswers()
|
||||||
|
{
|
||||||
|
_students = new List<Student>();
|
||||||
|
// 生成 40 个学生
|
||||||
|
for (int i = 1; i <= 40; i++)
|
||||||
|
{
|
||||||
|
_students.Add(new Student { Name = $"学生{i}" });
|
||||||
|
}
|
||||||
|
|
||||||
|
_questionsForTable = _examStruct.Questions.Select(qItem =>
|
||||||
|
{
|
||||||
|
var rowData = new QuestionRowData
|
||||||
|
{
|
||||||
|
QuestionItem = qItem,
|
||||||
|
StudentAnswers = new Dictionary<Guid, bool>()
|
||||||
|
};
|
||||||
|
|
||||||
|
// 为每个学生随机生成初始的对错状态
|
||||||
|
var random = new Random();
|
||||||
|
foreach (var student in _students)
|
||||||
|
{
|
||||||
|
// 模拟随机对错,50%的概率
|
||||||
|
rowData.StudentAnswers[student.Id] = random.Next(0, 2) == 1;
|
||||||
|
}
|
||||||
|
return rowData;
|
||||||
|
}).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当某个学生的某个题目的作答状态改变时触发
|
||||||
|
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 void ToggleStudentAllAnswers(Guid studentId)
|
||||||
|
{
|
||||||
|
bool allCorrect = _questionsForTable.All(row => row.StudentAnswers.ContainsKey(studentId) && row.StudentAnswers[studentId]);
|
||||||
|
|
||||||
|
foreach (var row in _questionsForTable)
|
||||||
|
{
|
||||||
|
if (row.StudentAnswers.ContainsKey(studentId))
|
||||||
|
{
|
||||||
|
row.StudentAnswers[studentId] = !allCorrect; // 全部取反
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StateHasChanged(); // 手动通知 Blazor 刷新 UI
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交批改结果(模拟)
|
||||||
|
private void SubmitGrading()
|
||||||
|
{
|
||||||
|
Console.WriteLine("--- 提交批改结果 ---");
|
||||||
|
foreach (var student in _students)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"学生: {student.Name}, 总分: {GetStudentTotalScore(student.Id)}");
|
||||||
|
foreach (var row in _questionsForTable)
|
||||||
|
{
|
||||||
|
if (row.StudentAnswers.TryGetValue(student.Id, out bool isCorrect))
|
||||||
|
{
|
||||||
|
Console.WriteLine($" - 题目 {row.QuestionItem.Sequence}: {(isCorrect ? "正确" : "错误")}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Snackbar?.Add("批改结果已提交(模拟)", Severity.Success);
|
||||||
|
// 实际应用中,这里会将 _questionsForTable 和 _students 的数据发送到后端API
|
||||||
|
}
|
||||||
|
}
|
@@ -1,15 +1,45 @@
|
|||||||
@page "/exam/edit/{ExamId:Guid}"
|
@page "/exam/edit/{ExamId}"
|
||||||
|
@using Entities.DTO
|
||||||
@using TechHelper.Client.Exam
|
@using TechHelper.Client.Exam
|
||||||
|
|
||||||
|
<ExamView ParsedExam="@ExamDto"/>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public Guid ExamId { get; set; }
|
public string ExamId { get; set; }
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
public IExamService ExamService { get; set; }
|
public IExamService ExamService { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
private NavigationManager Navigation { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
private ISnackbar Snackbar { get; set; }
|
||||||
|
|
||||||
|
private ExamDto ExamDto { get; set; }
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
await ExamService.GetExam(Guid.NewGuid());
|
|
||||||
|
if (Guid.TryParse(ExamId, out Guid parsedExamId))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"ExamId 字符串成功解析为 Guid: {parsedExamId}");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await ExamService.GetExam(parsedExamId);
|
||||||
|
if (result.Status) ExamDto = result.Result as ExamDto ?? new ExamDto();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar?.Add($"获取试卷失败: {ex.Message}", Severity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"错误:路由参数 ExamId '{ExamId}' 不是一个有效的 GUID 格式。无法获取试卷信息。");
|
||||||
|
Navigation.NavigateTo("/exam/manager");
|
||||||
|
Snackbar?.Add("无效的试卷ID,无法加载。", Severity.Error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -38,7 +38,7 @@
|
|||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public List<QuestionGroupDto> MajorQGList { get; set; }
|
public List<QuestionGroupDto> MajorQGList { get; set; } = new List<QuestionGroupDto>();
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Class { get; set; } = "my-2 pa-1";
|
public string Class { get; set; } = "my-2 pa-1";
|
||||||
|
@@ -16,11 +16,12 @@ else
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@foreach (var item in examDtos)
|
<MudPaper Class="d-flex flex-wrap flex-grow-0 gap-4" Height="100%" Width="100%">
|
||||||
{
|
@foreach (var item in examDtos)
|
||||||
<ExamPreview examDto="item"> </ExamPreview>
|
{
|
||||||
}
|
<ExamPreview examDto="item" Width="256px" Height="256px"> </ExamPreview>
|
||||||
|
}
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Inject]
|
[Inject]
|
||||||
|
@@ -1,18 +1,25 @@
|
|||||||
@using Entities.DTO
|
@using Entities.DTO
|
||||||
|
|
||||||
<MudPaper Width="@Width" Height="@Height" @onclick="ExamClick">
|
<MudPaper Class="overflow-hidden " Style="background-color:pink" Width="@Width" Height="@Height" MaxHeight="@MaxHeight" MaxWidth="@MaxWidth">
|
||||||
<MudCard>
|
<MudPaper Class="d-flex flex-column flex-grow-1 justify-content-between" Height="100%" Style="background-color:green">
|
||||||
<MudCardHeader>
|
|
||||||
<MudText> @examDto.AssignmentTitle </MudText>
|
|
||||||
</MudCardHeader>
|
|
||||||
|
|
||||||
<MudCardContent>
|
<MudText Typo="Typo.body2"> @examDto.AssignmentTitle </MudText>
|
||||||
<MudText> @examDto.Description </MudText>
|
|
||||||
</MudCardContent>
|
|
||||||
</MudCard>
|
<MudPaper>
|
||||||
|
|
||||||
|
<MudButtonGroup Size="Size.Small" Color="Color.Primary" Variant="Variant.Filled">
|
||||||
|
|
||||||
|
<MudButton OnClick="ExamClick"> 详情 </MudButton>
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Delete" aria-label="delete" />
|
||||||
|
<MudIconButton Icon="@Icons.Custom.Brands.GitHub" OnClick="CheckExam" Color="Color.Primary" aria-label="github" />
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Favorite" Color="Color.Secondary" aria-label="add to favorite" />
|
||||||
|
</MudButtonGroup>
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
</MudPaper>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
@@ -23,15 +30,28 @@
|
|||||||
|
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string? Width { get; set; } = "200";
|
public string? Width { get; set; } = "256";
|
||||||
|
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string? Height { get; set; } = "400";
|
public string? Height { get; set; } = "64";
|
||||||
|
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string? MaxWidth { get; set; } = "256";
|
||||||
|
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string? MaxHeight { get; set; } = "64";
|
||||||
|
|
||||||
|
|
||||||
private void ExamClick()
|
private void ExamClick()
|
||||||
{
|
{
|
||||||
navigationManager.NavigateTo($"exam/Edit/{examDto.AssignmentId}");
|
navigationManager.NavigateTo($"exam/edit/{examDto.AssignmentId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckExam()
|
||||||
|
{
|
||||||
|
navigationManager.NavigateTo($"exam/check/{examDto.AssignmentId}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
16
TechHelper.Client/Pages/Exam/ExamPreview.razor.css
Normal file
16
TechHelper.Client/Pages/Exam/ExamPreview.razor.css
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
.hover-effect-paper {
|
||||||
|
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out; /* 添加平滑过渡效果 */
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover-effect-paper:hover {
|
||||||
|
transform: translateY(-5px) scale(1.02); /* 向上轻微移动并放大 */
|
||||||
|
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); /* 增加阴影,使其看起来浮起 */
|
||||||
|
/* 您还可以尝试改变背景色,例如: */
|
||||||
|
/* background-color: var(--mud-palette-action-hover); */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.test{
|
||||||
|
background-color: gray;
|
||||||
|
}
|
@@ -1,12 +1,25 @@
|
|||||||
@using Entities.DTO
|
@using Entities.DTO
|
||||||
@using TechHelper.Client.Exam
|
@using TechHelper.Client.Exam
|
||||||
<MudPaper Height="@Height" Class="@Class" Style="@Style" Width="@Width">
|
|
||||||
<MudText Class="d-flex justify-content-center" Typo="Typo.h6"> @ParsedExam.AssignmentTitle </MudText>
|
|
||||||
<MudText Typo="Typo.body1"> @ParsedExam.Description </MudText>
|
|
||||||
|
|
||||||
<ExamGroupView MajorQGList="@ParsedExam.QuestionGroups.SubQuestionGroups" Elevation="1" Class="ma-0 pa-2" />
|
@if (ParsedExam != null)
|
||||||
</MudPaper>
|
{
|
||||||
|
|
||||||
|
<MudPaper Height="@Height" Class="@Class" Style="@Style" Width="@Width">
|
||||||
|
<MudText Class="d-flex justify-content-center" Typo="Typo.h6"> @ParsedExam.AssignmentTitle </MudText>
|
||||||
|
<MudText Typo="Typo.body1"> @ParsedExam.Description </MudText>
|
||||||
|
|
||||||
|
<ExamGroupView MajorQGList="@ParsedExam.QuestionGroups.SubQuestionGroups" Elevation="1" Class="ma-0 pa-2" />
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudPaper Class="justify-content-center">
|
||||||
|
|
||||||
|
<MudProgressCircular Indeterminate="true" Color="Color.Primary" />
|
||||||
|
<MudText Class="ml-4">加载试卷中...</MudText>
|
||||||
|
</MudPaper>
|
||||||
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
|
||||||
|
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public SubQuestionDto Question { get; set; }
|
public SubQuestionDto Question { get; set; } = new SubQuestionDto();
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Class { get; set; }
|
public string Class { get; set; }
|
||||||
|
@@ -14,6 +14,7 @@ using Microsoft.Extensions.Options;
|
|||||||
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
|
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
|
||||||
using TechHelper.Client.AI;
|
using TechHelper.Client.AI;
|
||||||
using TechHelper.Client.Exam;
|
using TechHelper.Client.Exam;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
|
||||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||||
@@ -27,36 +28,46 @@ builder.Services.AddOidcAuthentication(options =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
builder.Services.Configure<ApiConfiguration>(builder.Configuration.GetSection("ApiConfiguration"));
|
||||||
builder.Services.AddAuthorizationCore();
|
builder.Services.AddAuthorizationCore();
|
||||||
builder.Services.AddCascadingAuthenticationState();
|
builder.Services.AddCascadingAuthenticationState();
|
||||||
builder.Services.AddLocalStorageServices();
|
builder.Services.AddLocalStorageServices();
|
||||||
|
|
||||||
builder.Services.AddScoped<IAuthenticationClientService, AuthenticationClientService>();
|
builder.Services.AddScoped<IAuthenticationClientService, AuthenticationClientService>();
|
||||||
builder.Services.AddScoped<IExamService, ExamService>();
|
builder.Services.AddScoped<IExamService, ExamService>();
|
||||||
builder.Services.AddScoped<AuthenticationStateProvider, AuthStateProvider>();
|
builder.Services.AddScoped<AuthenticationStateProvider, AuthStateProvider>();
|
||||||
builder.Services.Configure<ApiConfiguration>(builder.Configuration.GetSection("ApiConfiguration"));
|
builder.Services.AddScoped<IRefreshTokenService, RefreshTokenService>();
|
||||||
builder.Services.AddScoped<RefreshTokenService>();
|
|
||||||
builder.Services.AddScoped<IClassServices, ClasssServices>();
|
builder.Services.AddScoped<IClassServices, ClasssServices>();
|
||||||
builder.Services.AddScoped<IEmailSender, QEmailSender>();
|
builder.Services.AddScoped<IEmailSender, QEmailSender>();
|
||||||
builder.Services.AddTransient<HttpInterceptorHandlerService>();
|
builder.Services.AddScoped<HttpInterceptorHandlerService>();
|
||||||
|
builder.Services.AddScoped<RefreshTokenService2>();
|
||||||
|
builder.Services.AddScoped<IAIService, AiService>();
|
||||||
|
|
||||||
builder.Services.AddHttpClient("WebApiClient", client =>
|
builder.Services.AddHttpClient("WebApiClient", client =>
|
||||||
{
|
{
|
||||||
var baseAddress = builder.Configuration.GetSection("ApiConfiguration:BaseAddress").Value;
|
var baseAddress = builder.Configuration.GetSection("ApiConfiguration:BaseAddress").Value;
|
||||||
client.BaseAddress = new Uri(baseAddress);
|
client.BaseAddress = new Uri(baseAddress+"/api/");
|
||||||
client.Timeout = TimeSpan.FromSeconds(30);
|
client.Timeout = TimeSpan.FromSeconds(30);
|
||||||
client.DefaultRequestHeaders.Add("Accept", "application/json");
|
client.DefaultRequestHeaders.Add("Accept", "application/json");
|
||||||
}).AddHttpMessageHandler<HttpInterceptorHandlerService>();
|
}).AddHttpMessageHandler<HttpInterceptorHandlerService>();
|
||||||
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("WebApiClient"));
|
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("WebApiClient"));
|
||||||
|
|
||||||
|
|
||||||
builder.Services.AddHttpClient("Default", (sp, cl) =>
|
builder.Services.AddHttpClient("AuthClient", client =>
|
||||||
{
|
{
|
||||||
var apiConfiguration = sp.GetRequiredService<IOptions<ApiConfiguration>>().Value;
|
var baseAddress = builder.Configuration.GetSection("ApiConfiguration:BaseAddress").Value;
|
||||||
cl.BaseAddress = new Uri(apiConfiguration.BaseAddress + "/api/");
|
client.BaseAddress = new Uri(baseAddress);
|
||||||
})
|
client.Timeout = TimeSpan.FromSeconds(30);
|
||||||
.AddHttpMessageHandler<HttpInterceptorHandlerService>();
|
client.DefaultRequestHeaders.Add("Accept", "application/json");
|
||||||
|
});
|
||||||
|
|
||||||
|
//builder.Services.AddHttpClient("Default", (sp, cl) =>
|
||||||
|
//{
|
||||||
|
// var apiConfiguration = sp.GetRequiredService<IOptions<ApiConfiguration>>().Value;
|
||||||
|
// cl.BaseAddress = new Uri(apiConfiguration.BaseAddress + "/api/");
|
||||||
|
//})
|
||||||
|
// .AddHttpMessageHandler<HttpInterceptorHandlerService>();
|
||||||
|
|
||||||
builder.Services.AddScoped<IAIService, AiService>();
|
|
||||||
|
|
||||||
//builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
//builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
||||||
//builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("Default"));
|
//builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("Default"));
|
||||||
|
@@ -1,19 +1,15 @@
|
|||||||
using Entities.DTO;
|
using Entities.DTO;
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
using System.Text.Json;
|
|
||||||
|
|
||||||
namespace TechHelper.Client.Services
|
namespace TechHelper.Client.Services
|
||||||
{
|
{
|
||||||
public class ClasssServices : IClassServices
|
public class ClasssServices : IClassServices
|
||||||
{
|
{
|
||||||
private HttpClient _client;
|
private readonly HttpClient _client;
|
||||||
private IHttpClientFactory _httpClientFactory;
|
|
||||||
|
|
||||||
public ClasssServices(HttpClient client, IHttpClientFactory httpClientFactory)
|
public ClasssServices(HttpClient client)
|
||||||
{
|
{
|
||||||
_client = client;
|
_client = client;
|
||||||
_httpClientFactory = httpClientFactory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<ResponseDto> CreateClass(UserRegistrationToClassDto userClass)
|
public Task<ResponseDto> CreateClass(UserRegistrationToClassDto userClass)
|
||||||
@@ -23,23 +19,24 @@ namespace TechHelper.Client.Services
|
|||||||
|
|
||||||
public async Task<ResponseDto> UserRegister(UserRegistrationToClassDto userRegistrationToClassDto)
|
public async Task<ResponseDto> UserRegister(UserRegistrationToClassDto userRegistrationToClassDto)
|
||||||
{
|
{
|
||||||
using (_client = _httpClientFactory.CreateClient("Default"))
|
try
|
||||||
{
|
{
|
||||||
|
var result = await _client.PostAsJsonAsync("class/userRegiste",
|
||||||
|
userRegistrationToClassDto);
|
||||||
|
var data = await result.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
try
|
return new ResponseDto
|
||||||
{
|
{
|
||||||
|
IsSuccessfulRegistration = result.IsSuccessStatusCode,
|
||||||
var result = await _client.PostAsJsonAsync("class/userRegiste",
|
Errors = result.IsSuccessStatusCode ? null : new string[] { data }
|
||||||
userRegistrationToClassDto);
|
};
|
||||||
var data = await result.Content.ReadAsStringAsync();
|
}
|
||||||
return new ResponseDto { IsSuccessfulRegistration = result.IsSuccessStatusCode, Errors = new string[] { data } };
|
catch (Exception ex)
|
||||||
}
|
{
|
||||||
catch (Exception ex)
|
// 实际应用中,这里应该加入日志记录
|
||||||
{
|
Console.WriteLine($"Error in UserRegister: {ex.Message}");
|
||||||
return new ResponseDto { IsSuccessfulRegistration = false, Errors = new string[] { ex.Message } };
|
return new ResponseDto { IsSuccessfulRegistration = false, Errors = new string[] { ex.Message } };
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -46,8 +46,10 @@ namespace TechHelper.Server.Controllers
|
|||||||
{
|
{
|
||||||
|
|
||||||
var result = await _examService.GetAsync(id);
|
var result = await _examService.GetAsync(id);
|
||||||
|
if (result.Status)
|
||||||
return Ok(result);
|
return Ok(result.Result);
|
||||||
|
else
|
||||||
|
return BadRequest("查找失败");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user