From 7a59c29e06725052934cefba0461fa8f7f40e000 Mon Sep 17 00:00:00 2001 From: SikongJueluo Date: Wed, 13 Aug 2025 16:11:06 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E5=8F=AF=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E5=B7=B2=E6=9C=89=E7=9A=84=E5=AE=9E=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/Controllers/ExamController.cs | 310 +++++++------- server/src/Database/ResourceManager.cs | 2 +- src/APIClient.ts | 248 +++++------ src/views/AuthView.vue | 22 +- src/views/Exam/ExamCard.vue | 0 src/views/Exam/ExamEditModal.vue | 513 ++++++++++------------- src/views/Exam/Index.vue | 76 ++-- 7 files changed, 578 insertions(+), 593 deletions(-) create mode 100644 src/views/Exam/ExamCard.vue diff --git a/server/src/Controllers/ExamController.cs b/server/src/Controllers/ExamController.cs index 03fba6a..49cecf8 100644 --- a/server/src/Controllers/ExamController.cs +++ b/server/src/Controllers/ExamController.cs @@ -28,7 +28,7 @@ public class ExamController : ControllerBase [Authorize] [HttpGet("list")] [EnableCors("Users")] - [ProducesResponseType(typeof(ExamSummary[]), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ExamInfo[]), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public IActionResult GetExamList() @@ -37,19 +37,10 @@ public class ExamController : ControllerBase { var exams = _examManager.GetAllExams(); - var examSummaries = exams.Select(exam => new ExamSummary - { - ID = exam.ID, - Name = exam.Name, - CreatedTime = exam.CreatedTime, - UpdatedTime = exam.UpdatedTime, - Tags = exam.GetTagsList(), - Difficulty = exam.Difficulty, - IsVisibleToUsers = exam.IsVisibleToUsers - }).ToArray(); + var examInfos = exams.Select(exam => new ExamInfo(exam)).ToArray(); - logger.Info($"成功获取实验列表,共 {examSummaries.Length} 个实验"); - return Ok(examSummaries); + logger.Info($"成功获取实验列表,共 {examInfos.Length} 个实验"); + return Ok(examInfos); } catch (Exception ex) { @@ -93,17 +84,7 @@ public class ExamController : ControllerBase } var exam = result.Value.Value; - var examInfo = new ExamInfo - { - ID = exam.ID, - Name = exam.Name, - Description = exam.Description, - CreatedTime = exam.CreatedTime, - UpdatedTime = exam.UpdatedTime, - Tags = exam.GetTagsList(), - Difficulty = exam.Difficulty, - IsVisibleToUsers = exam.IsVisibleToUsers - }; + var examInfo = new ExamInfo(exam); logger.Info($"成功获取实验信息: {examId}"); return Ok(examInfo); @@ -121,7 +102,7 @@ public class ExamController : ControllerBase /// 创建实验请求 /// 创建结果 [Authorize("Admin")] - [HttpPost] + [HttpPost("create")] [EnableCors("Users")] [ProducesResponseType(typeof(ExamInfo), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] @@ -129,7 +110,7 @@ public class ExamController : ControllerBase [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status409Conflict)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public IActionResult CreateExam([FromBody] CreateExamRequest request) + public IActionResult CreateExam([FromBody] ExamDto request) { if (string.IsNullOrWhiteSpace(request.ID) || string.IsNullOrWhiteSpace(request.Name) || string.IsNullOrWhiteSpace(request.Description)) return BadRequest("实验ID、名称和描述不能为空"); @@ -148,17 +129,7 @@ public class ExamController : ControllerBase } var exam = result.Value; - var examInfo = new ExamInfo - { - ID = exam.ID, - Name = exam.Name, - Description = exam.Description, - CreatedTime = exam.CreatedTime, - UpdatedTime = exam.UpdatedTime, - Tags = exam.GetTagsList(), - Difficulty = exam.Difficulty, - IsVisibleToUsers = exam.IsVisibleToUsers - }; + var examInfo = new ExamInfo(exam); logger.Info($"成功创建实验: {request.ID}"); return CreatedAtAction(nameof(GetExam), new { examId = request.ID }, examInfo); @@ -170,127 +141,168 @@ public class ExamController : ControllerBase } } - /// - /// 实验信息类 + /// 更新实验信息 /// - public class ExamInfo + /// 更新实验请求 + /// 更新结果 + [Authorize("Admin")] + [HttpPost("update")] + [EnableCors("Users")] + [ProducesResponseType(typeof(ExamInfo), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public IActionResult UpdateExam([FromBody] ExamDto request) { - /// - /// 实验的唯一标识符 - /// - public required string ID { get; set; } + var examId = request.ID; - /// - /// 实验名称 - /// - public required string Name { get; set; } + try + { + // 首先检查实验是否存在 + var existingExamResult = _examManager.GetExamByID(examId); + if (!existingExamResult.IsSuccessful) + { + logger.Error($"检查实验是否存在时出错: {existingExamResult.Error.Message}"); + return StatusCode(StatusCodes.Status500InternalServerError, $"检查实验失败: {existingExamResult.Error.Message}"); + } - /// - /// 实验描述 - /// - public required string Description { get; set; } + if (!existingExamResult.Value.HasValue) + { + logger.Warn($"要更新的实验不存在: {examId}"); + return NotFound($"实验 {examId} 不存在"); + } - /// - /// 实验创建时间 - /// - public DateTime CreatedTime { get; set; } + // 执行更新 + var updateResult = _examManager.UpdateExam( + examId, + request.Name, + request.Description, + request.Tags, + request.Difficulty, + request.IsVisibleToUsers + ); - /// - /// 实验最后更新时间 - /// - public DateTime UpdatedTime { get; set; } + if (!updateResult.IsSuccessful) + { + logger.Error($"更新实验时出错: {updateResult.Error.Message}"); + return StatusCode(StatusCodes.Status500InternalServerError, $"更新实验失败: {updateResult.Error.Message}"); + } - /// - /// 实验标签 - /// - public string[] Tags { get; set; } = Array.Empty(); + // 获取更新后的实验信息并返回 + var updatedExamResult = _examManager.GetExamByID(examId); + if (!updatedExamResult.IsSuccessful || !updatedExamResult.Value.HasValue) + { + logger.Error($"获取更新后的实验信息失败: {examId}"); + return StatusCode(StatusCodes.Status500InternalServerError, "更新成功但获取更新后信息失败"); + } - /// - /// 实验难度(1-5) - /// - public int Difficulty { get; set; } = 1; + var updatedExam = updatedExamResult.Value.Value; + var examInfo = new ExamInfo(updatedExam); - /// - /// 普通用户是否可见 - /// - public bool IsVisibleToUsers { get; set; } = true; - } - - /// - /// 实验简要信息类(用于列表显示) - /// - public class ExamSummary - { - /// - /// 实验的唯一标识符 - /// - public required string ID { get; set; } - - /// - /// 实验名称 - /// - public required string Name { get; set; } - - /// - /// 实验创建时间 - /// - public DateTime CreatedTime { get; set; } - - /// - /// 实验最后更新时间 - /// - public DateTime UpdatedTime { get; set; } - - /// - /// 实验标签 - /// - public string[] Tags { get; set; } = Array.Empty(); - - /// - /// 实验难度(1-5) - /// - public int Difficulty { get; set; } = 1; - - /// - /// 普通用户是否可见 - /// - public bool IsVisibleToUsers { get; set; } = true; - } - - /// - /// 创建实验请求类 - /// - public class CreateExamRequest - { - /// - /// 实验ID - /// - public required string ID { get; set; } - - /// - /// 实验名称 - /// - public required string Name { get; set; } - - /// - /// 实验描述 - /// - public required string Description { get; set; } - - /// - /// 实验标签 - /// - public string[] Tags { get; set; } = Array.Empty(); - - /// - /// 实验难度(1-5) - /// - public int Difficulty { get; set; } = 1; - - /// - /// 普通用户是否可见 - /// - public bool IsVisibleToUsers { get; set; } = true; + logger.Info($"成功更新实验: {examId},更新记录数: {updateResult.Value}"); + return Ok(examInfo); + } + catch (Exception ex) + { + logger.Error($"更新实验 {examId} 时出错: {ex.Message}"); + return StatusCode(StatusCodes.Status500InternalServerError, $"更新实验失败: {ex.Message}"); + } } } + +/// +/// 实验信息 +/// +public class ExamInfo +{ + /// + /// 实验的唯一标识符 + /// + public string ID { get; set; } + + /// + /// 实验名称 + /// + public string Name { get; set; } + + /// + /// 实验描述 + /// + public string Description { get; set; } + + /// + /// 实验创建时间 + /// + public DateTime CreatedTime { get; set; } + + /// + /// 实验最后更新时间 + /// + public DateTime UpdatedTime { get; set; } + + /// + /// 实验标签 + /// + public string[] Tags { get; set; } = Array.Empty(); + + /// + /// 实验难度(1-5) + /// + public int Difficulty { get; set; } = 1; + + /// + /// 普通用户是否可见 + /// + public bool IsVisibleToUsers { get; set; } = true; + + public ExamInfo(Database.Exam exam) + { + ID = exam.ID; + Name = exam.Name; + Description = exam.Description; + CreatedTime = exam.CreatedTime; + UpdatedTime = exam.UpdatedTime; + Tags = exam.GetTagsList(); + Difficulty = exam.Difficulty; + IsVisibleToUsers = exam.IsVisibleToUsers; + } +} + +/// +/// 统一的实验数据传输对象 +/// +public class ExamDto +{ + /// + /// 实验的唯一标识符 + /// + public required string ID { get; set; } + + /// + /// 实验名称 + /// + public required string Name { get; set; } + + /// + /// 实验描述 + /// + public required string Description { get; set; } + + /// + /// 实验标签 + /// + public string[] Tags { get; set; } = Array.Empty(); + + /// + /// 实验难度(1-5) + /// + public int Difficulty { get; set; } = 1; + + /// + /// 普通用户是否可见 + /// + public bool IsVisibleToUsers { get; set; } = true; +} diff --git a/server/src/Database/ResourceManager.cs b/server/src/Database/ResourceManager.cs index 0680c99..f540678 100644 --- a/server/src/Database/ResourceManager.cs +++ b/server/src/Database/ResourceManager.cs @@ -165,7 +165,7 @@ public class ResourceManager if (duplicateResource != null && duplicateResource.ResourceName == resourceName) { logger.Info($"资源已存在: {resourceName}"); - return new(new Exception($"资源已存在: {resourceName}")); + return duplicateResource; } var nowTime = DateTime.Now; diff --git a/src/APIClient.ts b/src/APIClient.ts index 1c3764b..a61fe6b 100644 --- a/src/APIClient.ts +++ b/src/APIClient.ts @@ -299,7 +299,7 @@ export class VideoStreamClient { return Promise.resolve(null as any); } - setVideoStreamEnable(enable: boolean | undefined, cancelToken?: CancelToken): Promise { + setVideoStreamEnable(enable: boolean | undefined, cancelToken?: CancelToken): Promise { let url_ = this.baseUrl + "/api/VideoStream/SetVideoStreamEnable?"; if (enable === null) throw new Error("The parameter 'enable' cannot be null."); @@ -308,11 +308,10 @@ export class VideoStreamClient { url_ = url_.replace(/[?&]$/, ""); let options_: AxiosRequestConfig = { - responseType: "blob", method: "POST", url: url_, headers: { - "Accept": "application/octet-stream" + "Accept": "application/json" }, cancelToken }; @@ -328,7 +327,7 @@ export class VideoStreamClient { }); } - protected processSetVideoStreamEnable(response: AxiosResponse): Promise { + protected processSetVideoStreamEnable(response: AxiosResponse): Promise { const status = response.status; let _headers: any = {}; if (response.headers && typeof response.headers === "object") { @@ -338,22 +337,27 @@ export class VideoStreamClient { } } } - if (status === 200 || status === 206) { - const contentDisposition = response.headers ? response.headers["content-disposition"] : undefined; - let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined; - let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined; - if (fileName) { - fileName = decodeURIComponent(fileName); - } else { - fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined; - fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined; - } - return Promise.resolve({ fileName: fileName, status: status, data: new Blob([response.data], { type: response.headers["content-type"] }), headers: _headers }); + if (status === 200) { + const _responseText = response.data; + let result200: any = null; + let resultData200 = _responseText; + result200 = resultData200 !== undefined ? resultData200 : null; + + return Promise.resolve(result200); + + } else if (status === 500) { + const _responseText = response.data; + let result500: any = null; + let resultData500 = _responseText; + result500 = resultData500 !== undefined ? resultData500 : null; + + return throwException("A server side error occurred.", status, _responseText, _headers, result500); + } else if (status !== 200 && status !== 204) { const _responseText = response.data; return throwException("An unexpected server error occurred.", status, _responseText, _headers); } - return Promise.resolve(null as any); + return Promise.resolve(null as any); } /** @@ -2505,7 +2509,7 @@ export class ExamClient { * 获取所有实验列表 * @return 实验列表 */ - getExamList( cancelToken?: CancelToken): Promise { + getExamList( cancelToken?: CancelToken): Promise { let url_ = this.baseUrl + "/api/Exam/list"; url_ = url_.replace(/[?&]$/, ""); @@ -2529,7 +2533,7 @@ export class ExamClient { }); } - protected processGetExamList(response: AxiosResponse): Promise { + protected processGetExamList(response: AxiosResponse): Promise { const status = response.status; let _headers: any = {}; if (response.headers && typeof response.headers === "object") { @@ -2546,12 +2550,12 @@ export class ExamClient { if (Array.isArray(resultData200)) { result200 = [] as any; for (let item of resultData200) - result200!.push(ExamSummary.fromJS(item)); + result200!.push(ExamInfo.fromJS(item)); } else { result200 = null; } - return Promise.resolve(result200); + return Promise.resolve(result200); } else if (status === 401) { const _responseText = response.data; @@ -2568,7 +2572,7 @@ export class ExamClient { const _responseText = response.data; return throwException("An unexpected server error occurred.", status, _responseText, _headers); } - return Promise.resolve(null as any); + return Promise.resolve(null as any); } /** @@ -2657,8 +2661,8 @@ export class ExamClient { * @param request 创建实验请求 * @return 创建结果 */ - createExam(request: CreateExamRequest, cancelToken?: CancelToken): Promise { - let url_ = this.baseUrl + "/api/Exam"; + createExam(request: ExamDto, cancelToken?: CancelToken): Promise { + let url_ = this.baseUrl + "/api/Exam/create"; url_ = url_.replace(/[?&]$/, ""); const content_ = JSON.stringify(request); @@ -2740,6 +2744,95 @@ export class ExamClient { } return Promise.resolve(null as any); } + + /** + * 更新实验信息 + * @param request 更新实验请求 + * @return 更新结果 + */ + updateExam(request: ExamDto, cancelToken?: CancelToken): Promise { + let url_ = this.baseUrl + "/api/Exam/update"; + url_ = url_.replace(/[?&]$/, ""); + + const content_ = JSON.stringify(request); + + let options_: AxiosRequestConfig = { + data: content_, + method: "POST", + url: url_, + headers: { + "Content-Type": "application/json", + "Accept": "application/json" + }, + cancelToken + }; + + return this.instance.request(options_).catch((_error: any) => { + if (isAxiosError(_error) && _error.response) { + return _error.response; + } else { + throw _error; + } + }).then((_response: AxiosResponse) => { + return this.processUpdateExam(_response); + }); + } + + protected processUpdateExam(response: AxiosResponse): Promise { + const status = response.status; + let _headers: any = {}; + if (response.headers && typeof response.headers === "object") { + for (const k in response.headers) { + if (response.headers.hasOwnProperty(k)) { + _headers[k] = response.headers[k]; + } + } + } + if (status === 200) { + const _responseText = response.data; + let result200: any = null; + let resultData200 = _responseText; + result200 = ExamInfo.fromJS(resultData200); + return Promise.resolve(result200); + + } else if (status === 400) { + const _responseText = response.data; + let result400: any = null; + let resultData400 = _responseText; + result400 = ProblemDetails.fromJS(resultData400); + return throwException("A server side error occurred.", status, _responseText, _headers, result400); + + } else if (status === 401) { + const _responseText = response.data; + let result401: any = null; + let resultData401 = _responseText; + result401 = ProblemDetails.fromJS(resultData401); + return throwException("A server side error occurred.", status, _responseText, _headers, result401); + + } else if (status === 403) { + const _responseText = response.data; + let result403: any = null; + let resultData403 = _responseText; + result403 = ProblemDetails.fromJS(resultData403); + return throwException("A server side error occurred.", status, _responseText, _headers, result403); + + } else if (status === 404) { + const _responseText = response.data; + let result404: any = null; + let resultData404 = _responseText; + result404 = ProblemDetails.fromJS(resultData404); + return throwException("A server side error occurred.", status, _responseText, _headers, result404); + + } else if (status === 500) { + const _responseText = response.data; + return throwException("A server side error occurred.", status, _responseText, _headers); + + } else if (status !== 200 && status !== 204) { + const _responseText = response.data; + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + } + return Promise.resolve(null as any); + } } export class HdmiVideoStreamClient { @@ -7802,94 +7895,7 @@ export interface IChannelCaptureData { data: string; } -/** 实验简要信息类(用于列表显示) */ -export class ExamSummary implements IExamSummary { - /** 实验的唯一标识符 */ - id!: string; - /** 实验名称 */ - name!: string; - /** 实验创建时间 */ - createdTime!: Date; - /** 实验最后更新时间 */ - updatedTime!: Date; - /** 实验标签 */ - tags!: string[]; - /** 实验难度(1-5) */ - difficulty!: number; - /** 普通用户是否可见 */ - isVisibleToUsers!: boolean; - - constructor(data?: IExamSummary) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (this)[property] = (data)[property]; - } - } - if (!data) { - this.tags = []; - } - } - - init(_data?: any) { - if (_data) { - this.id = _data["id"]; - this.name = _data["name"]; - this.createdTime = _data["createdTime"] ? new Date(_data["createdTime"].toString()) : undefined; - this.updatedTime = _data["updatedTime"] ? new Date(_data["updatedTime"].toString()) : undefined; - if (Array.isArray(_data["tags"])) { - this.tags = [] as any; - for (let item of _data["tags"]) - this.tags!.push(item); - } - this.difficulty = _data["difficulty"]; - this.isVisibleToUsers = _data["isVisibleToUsers"]; - } - } - - static fromJS(data: any): ExamSummary { - data = typeof data === 'object' ? data : {}; - let result = new ExamSummary(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["id"] = this.id; - data["name"] = this.name; - data["createdTime"] = this.createdTime ? this.createdTime.toISOString() : undefined; - data["updatedTime"] = this.updatedTime ? this.updatedTime.toISOString() : undefined; - if (Array.isArray(this.tags)) { - data["tags"] = []; - for (let item of this.tags) - data["tags"].push(item); - } - data["difficulty"] = this.difficulty; - data["isVisibleToUsers"] = this.isVisibleToUsers; - return data; - } -} - -/** 实验简要信息类(用于列表显示) */ -export interface IExamSummary { - /** 实验的唯一标识符 */ - id: string; - /** 实验名称 */ - name: string; - /** 实验创建时间 */ - createdTime: Date; - /** 实验最后更新时间 */ - updatedTime: Date; - /** 实验标签 */ - tags: string[]; - /** 实验难度(1-5) */ - difficulty: number; - /** 普通用户是否可见 */ - isVisibleToUsers: boolean; -} - -/** 实验信息类 */ +/** 实验信息 */ export class ExamInfo implements IExamInfo { /** 实验的唯一标识符 */ id!: string; @@ -7962,7 +7968,7 @@ export class ExamInfo implements IExamInfo { } } -/** 实验信息类 */ +/** 实验信息 */ export interface IExamInfo { /** 实验的唯一标识符 */ id: string; @@ -7982,9 +7988,9 @@ export interface IExamInfo { isVisibleToUsers: boolean; } -/** 创建实验请求类 */ -export class CreateExamRequest implements ICreateExamRequest { - /** 实验ID */ +/** 统一的实验数据传输对象 */ +export class ExamDto implements IExamDto { + /** 实验的唯一标识符 */ id!: string; /** 实验名称 */ name!: string; @@ -7997,7 +8003,7 @@ export class CreateExamRequest implements ICreateExamRequest { /** 普通用户是否可见 */ isVisibleToUsers!: boolean; - constructor(data?: ICreateExamRequest) { + constructor(data?: IExamDto) { if (data) { for (var property in data) { if (data.hasOwnProperty(property)) @@ -8024,9 +8030,9 @@ export class CreateExamRequest implements ICreateExamRequest { } } - static fromJS(data: any): CreateExamRequest { + static fromJS(data: any): ExamDto { data = typeof data === 'object' ? data : {}; - let result = new CreateExamRequest(); + let result = new ExamDto(); result.init(data); return result; } @@ -8047,9 +8053,9 @@ export class CreateExamRequest implements ICreateExamRequest { } } -/** 创建实验请求类 */ -export interface ICreateExamRequest { - /** 实验ID */ +/** 统一的实验数据传输对象 */ +export interface IExamDto { + /** 实验的唯一标识符 */ id: string; /** 实验名称 */ name: string; diff --git a/src/views/AuthView.vue b/src/views/AuthView.vue index 69f024c..0c64764 100644 --- a/src/views/AuthView.vue +++ b/src/views/AuthView.vue @@ -2,7 +2,10 @@
-
+

用户登录

@@ -44,7 +47,10 @@
-
+

用户注册

@@ -122,7 +128,7 @@ const isSignUpLoading = ref(false); const signUpData = ref({ username: "", email: "", - password: "" + password: "", }); // 登录处理函数 @@ -149,7 +155,7 @@ const handleLogin = async () => { // 短暂延迟后跳转到project页面 setTimeout(async () => { - await router.push("/project"); + router.go(-1); }, 1000); } catch (error: any) { console.error("Login error:", error); @@ -180,7 +186,7 @@ const handleRegister = () => { signUpData.value = { username: "", email: "", - password: "" + password: "", }; }; @@ -227,13 +233,13 @@ const handleSignUp = async () => { const result = await dataClient.signUpUser( signUpData.value.username.trim(), signUpData.value.email.trim(), - signUpData.value.password.trim() + signUpData.value.password.trim(), ); if (result) { // 注册成功 alertStore?.show("注册成功!请登录", "success", 2000); - + // 延迟后返回登录页面 setTimeout(() => { backToLogin(); @@ -271,7 +277,7 @@ const checkExistingToken = async () => { const isValid = await AuthManager.verifyToken(); if (isValid) { // 如果token仍然有效,直接跳转到project页面 - await router.push("/project"); + router.go(-1); } } catch (error) { // token无效或验证失败,继续显示登录页面 diff --git a/src/views/Exam/ExamCard.vue b/src/views/Exam/ExamCard.vue new file mode 100644 index 0000000..e69de29 diff --git a/src/views/Exam/ExamEditModal.vue b/src/views/Exam/ExamEditModal.vue index cdb470c..0d50765 100644 --- a/src/views/Exam/ExamEditModal.vue +++ b/src/views/Exam/ExamEditModal.vue @@ -1,14 +1,13 @@ diff --git a/src/views/Exam/Index.vue b/src/views/Exam/Index.vue index 20a598d..93142bd 100644 --- a/src/views/Exam/Index.vue +++ b/src/views/Exam/Index.vue @@ -62,7 +62,7 @@
+
@@ -75,15 +75,26 @@ v-for="exam in exams" :key="exam.id" class="card bg-base-200 shadow-lg hover:shadow-xl transition-all duration-200 cursor-pointer hover:scale-[1.02] relative overflow-hidden" - @click="viewExam(exam.id)" + @click="handleCardClicked($event, exam.id)" >

{{ exam.name }}

- {{ exam.id }} +
+ + {{ exam.id }} +
@@ -160,8 +171,8 @@
@@ -170,36 +181,27 @@ import { ref, onMounted, computed } from "vue"; import { useRoute } from "vue-router"; import { AuthManager } from "@/utils/AuthManager"; -import { type ExamSummary, type ExamInfo } from "@/APIClient"; +import { type ExamInfo } from "@/APIClient"; import { formatDate } from "@/utils/Common"; import ExamInfoModal from "./ExamInfoModal.vue"; import ExamEditModal from "./ExamEditModal.vue"; +import router from "@/router"; +import { EditIcon } from "lucide-vue-next"; +import { templateRef } from "@vueuse/core"; // 响应式数据 const route = useRoute(); -const exams = ref([]); +const exams = ref([]); const selectedExam = ref(null); const loading = ref(false); const error = ref(""); const isAdmin = ref(false); // Modal -const showCreateModal = ref(false); +const examEditModalRef = templateRef("examEditModalRef"); const showInfoModal = ref(false); -// 方法 -const checkAdminStatus = async () => { - console.log("检查管理员权限..."); - try { - isAdmin.value = await AuthManager.verifyAdminAuth(); - console.log("管理员权限:", isAdmin.value); - } catch (err) { - console.warn("无法验证管理员权限:", err); - isAdmin.value = false; - } -}; - -const refreshExams = async () => { +async function refreshExams() { loading.value = true; error.value = ""; @@ -212,9 +214,9 @@ const refreshExams = async () => { } finally { loading.value = false; } -}; +} -const viewExam = async (examId: string) => { +async function viewExam(examId: string) { try { const client = AuthManager.createAuthenticatedExamClient(); selectedExam.value = await client.getExam(examId); @@ -222,16 +224,32 @@ const viewExam = async (examId: string) => { } catch (err: any) { error.value = err.message || "获取实验详情失败"; console.error("获取实验详情失败:", err); + showInfoModal.value = false; } -}; +} -async function handleCreateExamFinished() { +async function handleEditExamFinished() { await refreshExams(); } +async function handleCardClicked(event: MouseEvent, examId: string) { + if (event.target instanceof HTMLButtonElement) return; + await viewExam(examId); +} + +async function handleEditExamClicked(event: MouseEvent, examId: string) { + examEditModalRef?.value?.editExam(examId); +} + // 生命周期 onMounted(async () => { - await checkAdminStatus(); + const isAuthenticated = await AuthManager.isAuthenticated(); + if (!isAuthenticated) { + router.push("/login"); + } + + isAdmin.value = await AuthManager.verifyAdminAuth(); + await refreshExams(); // 处理路由参数,如果有examId则自动打开该实验的详情模态框