feat: 实现可编辑已有的实验
This commit is contained in:
@@ -1,14 +1,13 @@
|
||||
<template>
|
||||
<div v-if="show" class="modal modal-open overflow-hidden">
|
||||
<div v-if="isShowModal" class="modal modal-open overflow-hidden">
|
||||
<div class="modal-box w-full max-w-7xl max-h-[90vh] p-0 overflow-hidden">
|
||||
<div
|
||||
class="flex justify-between items-center p-6 border-b border-base-300"
|
||||
>
|
||||
<h2 class="text-2xl font-bold text-base-content">创建新实验</h2>
|
||||
<button
|
||||
@click="closeCreateModal"
|
||||
class="btn btn-sm btn-circle btn-ghost"
|
||||
>
|
||||
<h2 class="text-2xl font-bold text-base-content">
|
||||
{{ mode === "create" ? "新建实验" : "编辑实验" }}
|
||||
</h2>
|
||||
<button @click="close" class="btn btn-sm btn-circle btn-ghost">
|
||||
<svg
|
||||
class="w-6 h-6"
|
||||
fill="none"
|
||||
@@ -40,7 +39,7 @@
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
v-model="newExam.id"
|
||||
v-model="editExamInfo.id"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="例如: EXP001"
|
||||
required
|
||||
@@ -54,7 +53,7 @@
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
v-model="newExam.name"
|
||||
v-model="editExamInfo.name"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="实验名称"
|
||||
required
|
||||
@@ -67,7 +66,7 @@
|
||||
<span class="label-text font-medium">实验描述 *</span>
|
||||
</label>
|
||||
<textarea
|
||||
v-model="newExam.description"
|
||||
v-model="editExamInfo.description"
|
||||
class="textarea textarea-bordered w-full h-32"
|
||||
placeholder="详细描述实验内容、目标和要求..."
|
||||
required
|
||||
@@ -78,7 +77,7 @@
|
||||
<div class="form-control">
|
||||
<div class="flex flex-wrap gap-2 mb-3 min-h-[2rem]">
|
||||
<span
|
||||
v-for="(tag, index) in newExam.tags"
|
||||
v-for="(tag, index) in editExamInfo.tags"
|
||||
:key="index"
|
||||
class="badge badge-primary gap-2"
|
||||
>
|
||||
@@ -126,12 +125,12 @@
|
||||
:key="i"
|
||||
type="radio"
|
||||
:value="i"
|
||||
v-model="newExam.difficulty"
|
||||
v-model="editExamInfo.difficulty"
|
||||
class="mask mask-star-2 bg-orange-400"
|
||||
/>
|
||||
</div>
|
||||
<span class="text-lg font-medium text-base-content"
|
||||
>({{ newExam.difficulty }}/5)</span
|
||||
>({{ editExamInfo.difficulty }}/5)</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
@@ -143,7 +142,7 @@
|
||||
<label class="label cursor-pointer justify-start gap-4">
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="newExam.isVisibleToUsers"
|
||||
v-model="editExamInfo.isVisibleToUsers"
|
||||
class="checkbox checkbox-primary"
|
||||
/>
|
||||
<div>
|
||||
@@ -161,14 +160,22 @@
|
||||
<div class="space-y-3">
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="isCreating || !canCreateExam"
|
||||
:disabled="isUpdating || !canCreateExam"
|
||||
class="btn btn-primary w-full"
|
||||
>
|
||||
<span
|
||||
v-if="isCreating"
|
||||
v-if="isUpdating"
|
||||
class="loading loading-spinner loading-sm mr-2"
|
||||
></span>
|
||||
{{ isCreating ? "创建中..." : "创建实验" }}
|
||||
{{
|
||||
mode === "create"
|
||||
? isUpdating
|
||||
? "创建中..."
|
||||
: "创建实验"
|
||||
: isUpdating
|
||||
? "更新中..."
|
||||
: "更新实验"
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -194,44 +201,22 @@
|
||||
@click="mdFileInput?.click()"
|
||||
@dragover.prevent
|
||||
@dragenter.prevent
|
||||
@drop.prevent="handleMdFileDrop"
|
||||
@drop.prevent="(e) => handleFileDrop(e, 'md')"
|
||||
>
|
||||
<div
|
||||
v-if="!uploadFiles.mdFile"
|
||||
class="flex flex-col items-center gap-3"
|
||||
>
|
||||
<svg
|
||||
class="w-12 h-12 text-base-content/40"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
||||
/>
|
||||
</svg>
|
||||
<FileTextIcon
|
||||
class="w-12 h-12 text-base-content opacity-40"
|
||||
/>
|
||||
<div class="text-sm text-base-content/70 text-center">
|
||||
<div class="font-medium mb-1">点击或拖拽上传</div>
|
||||
<div class="text-xs">支持 .md 文件</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex flex-col items-center gap-2">
|
||||
<svg
|
||||
class="w-8 h-8 text-success"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<FileTextIcon class="w-8 h-8 text-success" />
|
||||
<div class="text-xs font-medium text-success text-center">
|
||||
{{ uploadFiles.mdFile.name }}
|
||||
</div>
|
||||
@@ -241,7 +226,7 @@
|
||||
<input
|
||||
type="file"
|
||||
ref="mdFileInput"
|
||||
@change="handleMdFileChange"
|
||||
@change="(e) => handleFileChange(e, 'md')"
|
||||
accept=".md"
|
||||
class="hidden"
|
||||
/>
|
||||
@@ -257,44 +242,20 @@
|
||||
@click="imageFilesInput?.click()"
|
||||
@dragover.prevent
|
||||
@dragenter.prevent
|
||||
@drop.prevent="handleImageFilesDrop"
|
||||
@drop.prevent="(e) => handleFileDrop(e, 'image')"
|
||||
>
|
||||
<div
|
||||
v-if="uploadFiles.imageFiles.length === 0"
|
||||
class="flex flex-col items-center gap-3"
|
||||
>
|
||||
<svg
|
||||
class="w-12 h-12 text-base-content/40"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
<ImageIcon class="w-12 h-12 text-base-content opacity-40" />
|
||||
<div class="text-sm text-base-content/70 text-center">
|
||||
<div class="font-medium mb-1">点击或拖拽上传</div>
|
||||
<div class="text-xs">支持 PNG, JPG, GIF 等图片格式</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex flex-col items-center gap-2">
|
||||
<svg
|
||||
class="w-8 h-8 text-success"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
<ImageIcon class="w-8 h-8 text-success" />
|
||||
<div class="text-xs font-medium text-success">
|
||||
{{ uploadFiles.imageFiles.length }} 个文件
|
||||
</div>
|
||||
@@ -304,7 +265,7 @@
|
||||
<input
|
||||
type="file"
|
||||
ref="imageFilesInput"
|
||||
@change="handleImageFilesChange"
|
||||
@change="(e) => handleFileChange(e, 'image')"
|
||||
accept="image/*"
|
||||
multiple
|
||||
class="hidden"
|
||||
@@ -324,44 +285,22 @@
|
||||
@click="bitstreamFilesInput?.click()"
|
||||
@dragover.prevent
|
||||
@dragenter.prevent
|
||||
@drop.prevent="handleBitstreamFilesDrop"
|
||||
@drop.prevent="(e) => handleFileDrop(e, 'bitstream')"
|
||||
>
|
||||
<div
|
||||
v-if="uploadFiles.bitstreamFiles.length === 0"
|
||||
class="flex flex-col items-center gap-3"
|
||||
>
|
||||
<svg
|
||||
class="w-12 h-12 text-base-content/40"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 10V3L4 14h7v7l9-11h-7z"
|
||||
/>
|
||||
</svg>
|
||||
<BinaryIcon
|
||||
class="w-12 h-12 text-base-content opacity-40"
|
||||
/>
|
||||
<div class="text-sm text-base-content/70 text-center">
|
||||
<div class="font-medium mb-1">点击或拖拽上传</div>
|
||||
<div class="text-xs">支持 .sbit, .bit, .bin 文件</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex flex-col items-center gap-2">
|
||||
<svg
|
||||
class="w-8 h-8 text-success"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 10V3L4 14h7v7l9-11h-7z"
|
||||
/>
|
||||
</svg>
|
||||
<BinaryIcon class="w-8 h-8 text-success" />
|
||||
<div class="text-xs font-medium text-success">
|
||||
{{ uploadFiles.bitstreamFiles.length }} 个文件
|
||||
</div>
|
||||
@@ -371,7 +310,7 @@
|
||||
<input
|
||||
type="file"
|
||||
ref="bitstreamFilesInput"
|
||||
@change="handleBitstreamFilesChange"
|
||||
@change="(e) => handleFileChange(e, 'bitstream')"
|
||||
accept=".sbit,.bit,.bin"
|
||||
multiple
|
||||
class="hidden"
|
||||
@@ -388,44 +327,22 @@
|
||||
@click="canvasFilesInput?.click()"
|
||||
@dragover.prevent
|
||||
@dragenter.prevent
|
||||
@drop.prevent="handleCanvasFilesDrop"
|
||||
@drop.prevent="(e) => handleFileDrop(e, 'canvas')"
|
||||
>
|
||||
<div
|
||||
v-if="uploadFiles.canvasFiles.length === 0"
|
||||
class="flex flex-col items-center gap-3"
|
||||
>
|
||||
<svg
|
||||
class="w-12 h-12 text-base-content/40"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z"
|
||||
/>
|
||||
</svg>
|
||||
<FileJsonIcon
|
||||
class="w-12 h-12 text-base-content opacity-40"
|
||||
/>
|
||||
<div class="text-sm text-base-content/70 text-center">
|
||||
<div class="font-medium mb-1">点击或拖拽上传</div>
|
||||
<div class="text-xs">支持 .json 文件</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex flex-col items-center gap-2">
|
||||
<svg
|
||||
class="w-8 h-8 text-success"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z"
|
||||
/>
|
||||
</svg>
|
||||
<FileJsonIcon class="w-8 h-8 text-success" />
|
||||
<div class="text-xs font-medium text-success">
|
||||
{{ uploadFiles.canvasFiles.length }} 个文件
|
||||
</div>
|
||||
@@ -435,7 +352,7 @@
|
||||
<input
|
||||
type="file"
|
||||
ref="canvasFilesInput"
|
||||
@change="handleCanvasFilesChange"
|
||||
@change="(e) => handleFileChange(e, 'canvas')"
|
||||
accept=".json"
|
||||
multiple
|
||||
class="hidden"
|
||||
@@ -454,44 +371,22 @@
|
||||
@click="resourceFileInput?.click()"
|
||||
@dragover.prevent
|
||||
@dragenter.prevent
|
||||
@drop.prevent="handleResourceFileDrop"
|
||||
@drop.prevent="(e) => handleFileDrop(e, 'resource')"
|
||||
>
|
||||
<div
|
||||
v-if="!uploadFiles.resourceFile"
|
||||
class="flex flex-col items-center gap-3"
|
||||
>
|
||||
<svg
|
||||
class="w-12 h-12 text-base-content/40"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
||||
/>
|
||||
</svg>
|
||||
<FileArchiveIcon
|
||||
class="w-12 h-12 text-base-content opacity-40"
|
||||
/>
|
||||
<div class="text-sm text-base-content/70 text-center">
|
||||
<div class="font-medium mb-1">点击或拖拽上传</div>
|
||||
<div class="text-xs">支持 .zip, .rar, .7z 文件</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex flex-col items-center gap-2">
|
||||
<svg
|
||||
class="w-8 h-8 text-success"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
||||
/>
|
||||
</svg>
|
||||
<FileArchiveIcon class="w-8 h-8 text-success" />
|
||||
<div class="text-xs font-medium text-success text-center">
|
||||
{{ uploadFiles.resourceFile.name }}
|
||||
</div>
|
||||
@@ -501,7 +396,7 @@
|
||||
<input
|
||||
type="file"
|
||||
ref="resourceFileInput"
|
||||
@change="handleResourceFileChange"
|
||||
@change="(e) => handleFileChange(e, 'resource')"
|
||||
accept=".zip,.rar,.7z"
|
||||
class="hidden"
|
||||
/>
|
||||
@@ -511,28 +406,39 @@
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-backdrop" @click="closeCreateModal"></div>
|
||||
<div class="modal-backdrop" @click="close"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { CreateExamRequest, type FileParameter } from "@/APIClient";
|
||||
import {
|
||||
FileTextIcon,
|
||||
ImageIcon,
|
||||
BinaryIcon,
|
||||
FileArchiveIcon,
|
||||
FileJsonIcon,
|
||||
} from "lucide-vue-next";
|
||||
import { ExamDto, type FileParameter } from "@/APIClient";
|
||||
import { useAlertStore } from "@/components/Alert";
|
||||
import { AuthManager } from "@/utils/AuthManager";
|
||||
import { useRequiredInjection } from "@/utils/Common";
|
||||
import { defineModel, ref, computed } from "vue";
|
||||
import { mod } from "mathjs";
|
||||
import type { ExamInfo } from "@/APIClient";
|
||||
|
||||
const show = defineModel<boolean>("show", {
|
||||
type Mode = "create" | "edit";
|
||||
|
||||
const isShowModal = defineModel<boolean>("isShowModal", {
|
||||
default: false,
|
||||
});
|
||||
|
||||
const emits = defineEmits<{
|
||||
createFinished: [examId: string];
|
||||
editFinished: [examId: string];
|
||||
}>();
|
||||
|
||||
const alertStore = useRequiredInjection(useAlertStore);
|
||||
const alert = useRequiredInjection(useAlertStore);
|
||||
|
||||
const newExam = ref({
|
||||
const editExamInfo = ref({
|
||||
id: "",
|
||||
name: "",
|
||||
description: "",
|
||||
@@ -541,7 +447,8 @@ const newExam = ref({
|
||||
isVisibleToUsers: true,
|
||||
});
|
||||
|
||||
const isCreating = ref(false);
|
||||
const isUpdating = ref(false);
|
||||
const mode = ref<Mode>("create");
|
||||
const newTagInput = ref("");
|
||||
|
||||
// 文件上传相关
|
||||
@@ -563,89 +470,86 @@ const resourceFileInput = ref<HTMLInputElement>();
|
||||
// 计算属性
|
||||
const canCreateExam = computed(() => {
|
||||
return (
|
||||
newExam.value.id.trim() !== "" &&
|
||||
newExam.value.name.trim() !== "" &&
|
||||
newExam.value.description.trim() !== "" &&
|
||||
editExamInfo.value.id.trim() !== "" &&
|
||||
editExamInfo.value.name.trim() !== "" &&
|
||||
editExamInfo.value.description.trim() !== "" &&
|
||||
uploadFiles.value.mdFile !== null
|
||||
);
|
||||
});
|
||||
|
||||
const handleResourceFileChange = (event: Event) => {
|
||||
// 文件类型定义
|
||||
type FileType = "md" | "image" | "bitstream" | "canvas" | "resource";
|
||||
|
||||
// 统一文件处理方法
|
||||
const handleFileChange = (event: Event, fileType: FileType) => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
if (target.files && target.files.length > 0) {
|
||||
uploadFiles.value.resourceFile = target.files[0];
|
||||
if (!target.files) return;
|
||||
|
||||
switch (fileType) {
|
||||
case "md":
|
||||
if (target.files.length > 0) {
|
||||
uploadFiles.value.mdFile = target.files[0];
|
||||
}
|
||||
break;
|
||||
case "image":
|
||||
uploadFiles.value.imageFiles = Array.from(target.files);
|
||||
break;
|
||||
case "bitstream":
|
||||
uploadFiles.value.bitstreamFiles = Array.from(target.files);
|
||||
break;
|
||||
case "canvas":
|
||||
uploadFiles.value.canvasFiles = Array.from(target.files);
|
||||
break;
|
||||
case "resource":
|
||||
if (target.files.length > 0) {
|
||||
uploadFiles.value.resourceFile = target.files[0];
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// 文件处理方法
|
||||
const handleMdFileChange = (event: Event) => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
if (target.files && target.files.length > 0) {
|
||||
uploadFiles.value.mdFile = target.files[0];
|
||||
}
|
||||
};
|
||||
|
||||
const handleMdFileDrop = (event: DragEvent) => {
|
||||
const handleFileDrop = (event: DragEvent, fileType: FileType) => {
|
||||
const files = event.dataTransfer?.files;
|
||||
if (files && files.length > 0) {
|
||||
const file = files[0];
|
||||
if (file.name.endsWith(".md")) {
|
||||
uploadFiles.value.mdFile = file;
|
||||
}
|
||||
}
|
||||
};
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
const handleImageFilesChange = (event: Event) => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
if (target.files) {
|
||||
uploadFiles.value.imageFiles = Array.from(target.files);
|
||||
}
|
||||
};
|
||||
|
||||
const handleImageFilesDrop = (event: DragEvent) => {
|
||||
const files = event.dataTransfer?.files;
|
||||
if (files && files.length > 0) {
|
||||
const imageFiles = Array.from(files).filter((file) =>
|
||||
file.type.startsWith("image/"),
|
||||
);
|
||||
uploadFiles.value.imageFiles = imageFiles;
|
||||
}
|
||||
};
|
||||
|
||||
const handleBitstreamFilesChange = (event: Event) => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
if (target.files) {
|
||||
uploadFiles.value.bitstreamFiles = Array.from(target.files);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBitstreamFilesDrop = (event: DragEvent) => {
|
||||
const files = event.dataTransfer?.files;
|
||||
if (files && files.length > 0) {
|
||||
const bitstreamFiles = Array.from(files).filter(
|
||||
(file) =>
|
||||
file.name.endsWith(".sbit") ||
|
||||
file.name.endsWith(".bit") ||
|
||||
file.name.endsWith(".bin"),
|
||||
);
|
||||
uploadFiles.value.bitstreamFiles = bitstreamFiles;
|
||||
}
|
||||
};
|
||||
|
||||
const handleCanvasFilesChange = (event: Event) => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
if (target.files) {
|
||||
uploadFiles.value.canvasFiles = Array.from(target.files);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCanvasFilesDrop = (event: DragEvent) => {
|
||||
const files = event.dataTransfer?.files;
|
||||
if (files && files.length > 0) {
|
||||
const canvasFiles = Array.from(files).filter((file) =>
|
||||
file.name.endsWith(".json"),
|
||||
);
|
||||
uploadFiles.value.canvasFiles = canvasFiles;
|
||||
switch (fileType) {
|
||||
case "md":
|
||||
const mdFile = files[0];
|
||||
if (mdFile.name.endsWith(".md")) {
|
||||
uploadFiles.value.mdFile = mdFile;
|
||||
}
|
||||
break;
|
||||
case "image":
|
||||
const imageFiles = Array.from(files).filter((file) =>
|
||||
file.type.startsWith("image/"),
|
||||
);
|
||||
uploadFiles.value.imageFiles = imageFiles;
|
||||
break;
|
||||
case "bitstream":
|
||||
const bitstreamFiles = Array.from(files).filter(
|
||||
(file) =>
|
||||
file.name.endsWith(".sbit") ||
|
||||
file.name.endsWith(".bit") ||
|
||||
file.name.endsWith(".bin"),
|
||||
);
|
||||
uploadFiles.value.bitstreamFiles = bitstreamFiles;
|
||||
break;
|
||||
case "canvas":
|
||||
const canvasFiles = Array.from(files).filter((file) =>
|
||||
file.name.endsWith(".json"),
|
||||
);
|
||||
uploadFiles.value.canvasFiles = canvasFiles;
|
||||
break;
|
||||
case "resource":
|
||||
const resourceFile = files[0];
|
||||
if (
|
||||
resourceFile.name.endsWith(".zip") ||
|
||||
resourceFile.name.endsWith(".rar") ||
|
||||
resourceFile.name.endsWith(".7z")
|
||||
) {
|
||||
uploadFiles.value.resourceFile = resourceFile;
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -656,18 +560,18 @@ const addTag = (event?: Event) => {
|
||||
event.stopPropagation();
|
||||
}
|
||||
const tag = newTagInput.value.trim();
|
||||
if (tag && !newExam.value.tags.includes(tag)) {
|
||||
newExam.value.tags.push(tag);
|
||||
if (tag && !editExamInfo.value.tags.includes(tag)) {
|
||||
editExamInfo.value.tags.push(tag);
|
||||
newTagInput.value = "";
|
||||
}
|
||||
};
|
||||
|
||||
const removeTag = (index: number) => {
|
||||
newExam.value.tags.splice(index, 1);
|
||||
editExamInfo.value.tags.splice(index, 1);
|
||||
};
|
||||
|
||||
const resetCreateForm = () => {
|
||||
newExam.value = {
|
||||
editExamInfo.value = {
|
||||
id: "",
|
||||
name: "",
|
||||
description: "",
|
||||
@@ -692,75 +596,81 @@ const resetCreateForm = () => {
|
||||
if (resourceFileInput.value) resourceFileInput.value.value = "";
|
||||
};
|
||||
|
||||
const closeCreateModal = () => {
|
||||
show.value = false;
|
||||
resetCreateForm();
|
||||
};
|
||||
|
||||
const handleResourceFileDrop = (event: DragEvent) => {
|
||||
const files = event.dataTransfer?.files;
|
||||
if (files && files.length > 0) {
|
||||
const file = files[0];
|
||||
if (
|
||||
file.name.endsWith(".zip") ||
|
||||
file.name.endsWith(".rar") ||
|
||||
file.name.endsWith(".7z")
|
||||
) {
|
||||
uploadFiles.value.resourceFile = file;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 提交创建实验
|
||||
const submitCreateExam = async () => {
|
||||
if (isCreating.value) return;
|
||||
if (isUpdating.value) return;
|
||||
|
||||
// 验证必填字段
|
||||
if (!newExam.value.id || !newExam.value.name || !newExam.value.description) {
|
||||
alertStore?.error("请填写所有必填字段");
|
||||
if (
|
||||
!editExamInfo.value.id ||
|
||||
!editExamInfo.value.name ||
|
||||
!editExamInfo.value.description
|
||||
) {
|
||||
alert?.error("请填写所有必填字段");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!uploadFiles.value.mdFile) {
|
||||
alertStore?.error("请上传MD文档");
|
||||
alert.error("请上传MD文档");
|
||||
return;
|
||||
}
|
||||
|
||||
isCreating.value = true;
|
||||
isUpdating.value = true;
|
||||
|
||||
try {
|
||||
const client = AuthManager.createAuthenticatedExamClient();
|
||||
|
||||
// 创建实验请求
|
||||
const createRequest = new CreateExamRequest({
|
||||
id: newExam.value.id,
|
||||
name: newExam.value.name,
|
||||
description: newExam.value.description,
|
||||
tags: newExam.value.tags,
|
||||
difficulty: newExam.value.difficulty,
|
||||
isVisibleToUsers: newExam.value.isVisibleToUsers,
|
||||
});
|
||||
let exam: ExamInfo;
|
||||
if (mode.value === "create") {
|
||||
// 创建实验请求
|
||||
const createRequest = new ExamDto({
|
||||
id: editExamInfo.value.id,
|
||||
name: editExamInfo.value.name,
|
||||
description: editExamInfo.value.description,
|
||||
tags: editExamInfo.value.tags,
|
||||
difficulty: editExamInfo.value.difficulty,
|
||||
isVisibleToUsers: editExamInfo.value.isVisibleToUsers,
|
||||
});
|
||||
|
||||
// 创建实验
|
||||
const createdExam = await client.createExam(createRequest);
|
||||
console.log("实验创建成功:", createdExam);
|
||||
// 创建实验
|
||||
exam = await client.createExam(createRequest);
|
||||
console.log("实验创建成功:", exam);
|
||||
} else if (mode.value === "edit") {
|
||||
// 编辑实验请求
|
||||
const editRequest = new ExamDto({
|
||||
id: editExamInfo.value.id,
|
||||
name: editExamInfo.value.name,
|
||||
description: editExamInfo.value.description,
|
||||
tags: editExamInfo.value.tags,
|
||||
difficulty: editExamInfo.value.difficulty,
|
||||
isVisibleToUsers: editExamInfo.value.isVisibleToUsers,
|
||||
});
|
||||
|
||||
// 编辑实验
|
||||
exam = await client.updateExam(editRequest);
|
||||
console.log("实验编辑成功:", exam);
|
||||
} else {
|
||||
// 处理其他模式
|
||||
console.error("未知的模式:", mode.value);
|
||||
throw new Error("未知的模式");
|
||||
}
|
||||
|
||||
// 上传文件
|
||||
await uploadExamResources(createdExam.id);
|
||||
await uploadExamResources(exam.id);
|
||||
|
||||
alertStore?.success("实验创建成功");
|
||||
closeCreateModal();
|
||||
emits("createFinished", createdExam.id);
|
||||
alert.success("实验创建成功");
|
||||
close();
|
||||
emits("editFinished", exam.id);
|
||||
} catch (err: any) {
|
||||
console.error("创建实验失败:", err);
|
||||
alertStore?.error(err.message || "创建实验失败");
|
||||
alert.error(err.message || "创建实验失败");
|
||||
} finally {
|
||||
isCreating.value = false;
|
||||
isUpdating.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 上传实验资源
|
||||
const uploadExamResources = async (examId: string) => {
|
||||
async function uploadExamResources(examId: string) {
|
||||
const client = AuthManager.createAuthenticatedResourceClient();
|
||||
|
||||
try {
|
||||
@@ -825,9 +735,42 @@ const uploadExamResources = async (examId: string) => {
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error("资源上传失败:", err);
|
||||
alertStore?.error("部分资源上传失败: " + (err.message || "未知错误"));
|
||||
alert?.error("部分资源上传失败: " + (err.message || "未知错误"));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function show() {
|
||||
isShowModal.value = true;
|
||||
}
|
||||
|
||||
function close() {
|
||||
isShowModal.value = false;
|
||||
mode.value = "create";
|
||||
resetCreateForm();
|
||||
}
|
||||
|
||||
async function editExam(examId: string) {
|
||||
const client = AuthManager.createAuthenticatedExamClient();
|
||||
const examInfo = await client.getExam(examId);
|
||||
|
||||
editExamInfo.value = {
|
||||
id: examInfo.id,
|
||||
name: examInfo.name,
|
||||
description: examInfo.description,
|
||||
tags: examInfo.tags,
|
||||
difficulty: examInfo.difficulty,
|
||||
isVisibleToUsers: examInfo.isVisibleToUsers,
|
||||
};
|
||||
|
||||
mode.value = "edit";
|
||||
show();
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
show,
|
||||
close,
|
||||
editExam,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped></style>
|
||||
|
||||
Reference in New Issue
Block a user