783 lines
26 KiB
Vue
783 lines
26 KiB
Vue
<template>
|
||
<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">
|
||
{{ mode === "create" ? "新建实验" : "编辑实验" }}
|
||
</h2>
|
||
<button @click="close" class="btn btn-sm btn-circle btn-ghost">
|
||
<XIcon class="w-6 h-6" />
|
||
</button>
|
||
</div>
|
||
|
||
<form @submit.prevent="submitCreateExam" class="flex h-[calc(90vh-5rem)]">
|
||
<!-- 左侧:基本信息 -->
|
||
<div class="w-110 p-6 overflow-y-auto border-r border-base-300">
|
||
<div class="space-y-6">
|
||
<h3 class="text-xl font-semibold text-base-content mb-4">
|
||
基本信息
|
||
</h3>
|
||
|
||
<!-- 实验ID -->
|
||
<div class="form-control">
|
||
<label class="label">
|
||
<span class="label-text font-medium">实验ID *</span>
|
||
</label>
|
||
<input
|
||
type="text"
|
||
v-model="editExamInfo.id"
|
||
class="input input-bordered w-full"
|
||
placeholder="例如: EXP001"
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
<!-- 实验名称 -->
|
||
<div class="form-control">
|
||
<label class="label">
|
||
<span class="label-text font-medium">实验名称 *</span>
|
||
</label>
|
||
<input
|
||
type="text"
|
||
v-model="editExamInfo.name"
|
||
class="input input-bordered w-full"
|
||
placeholder="实验名称"
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
<!-- 实验描述 -->
|
||
<div class="form-control">
|
||
<label class="label">
|
||
<span class="label-text font-medium">实验描述 *</span>
|
||
</label>
|
||
<textarea
|
||
v-model="editExamInfo.description"
|
||
class="textarea textarea-bordered w-full h-32"
|
||
placeholder="详细描述实验内容、目标和要求..."
|
||
required
|
||
></textarea>
|
||
</div>
|
||
|
||
<!-- 标签 -->
|
||
<div class="form-control">
|
||
<div class="flex flex-wrap gap-2 mb-3 min-h-[2rem]">
|
||
<span
|
||
v-for="(tag, index) in editExamInfo.tags"
|
||
:key="index"
|
||
class="badge badge-primary gap-2"
|
||
>
|
||
{{ tag }}
|
||
<button
|
||
type="button"
|
||
@click="removeTag(index)"
|
||
class="text-primary-content hover:text-error"
|
||
>
|
||
<svg
|
||
class="w-3 h-3"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
viewBox="0 0 24 24"
|
||
>
|
||
<path
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round"
|
||
stroke-width="2"
|
||
d="M6 18L18 6M6 6l12 12"
|
||
/>
|
||
</svg>
|
||
</button>
|
||
</span>
|
||
</div>
|
||
<div class="flex gap-2">
|
||
<input
|
||
type="text"
|
||
v-model="newTagInput"
|
||
@keydown.enter.prevent="addTag"
|
||
class="input input-bordered flex-1"
|
||
placeholder="输入标签按回车添加"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 难度等级 -->
|
||
<div class="form-control">
|
||
<div class="flex items-center justify-between p-4 rounded-lg">
|
||
<span class="label-text font-medium">难度等级 *</span>
|
||
<div class="flex items-center gap-4">
|
||
<div class="rating rating-lg">
|
||
<input
|
||
v-for="i in 5"
|
||
:key="i"
|
||
type="radio"
|
||
:value="i"
|
||
v-model="editExamInfo.difficulty"
|
||
class="mask mask-star-2 bg-orange-400"
|
||
/>
|
||
</div>
|
||
<span class="text-lg font-medium text-base-content"
|
||
>({{ editExamInfo.difficulty }}/5)</span
|
||
>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 可见性 -->
|
||
<div class="form-control">
|
||
<div class="p-4 rounded-lg">
|
||
<label class="label cursor-pointer justify-start gap-4">
|
||
<input
|
||
type="checkbox"
|
||
v-model="editExamInfo.isVisibleToUsers"
|
||
class="checkbox checkbox-primary"
|
||
/>
|
||
<div>
|
||
<span class="label-text font-medium">对学生可见</span>
|
||
<div class="text-sm text-base-content/70">
|
||
开启后学生可以在实验列表中看到此实验
|
||
</div>
|
||
</div>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 提交按钮 -->
|
||
<div class="pt-4 border-t border-base-300">
|
||
<div class="space-y-3">
|
||
<button
|
||
type="submit"
|
||
:disabled="isUpdating || !canCreateExam"
|
||
class="btn btn-primary w-full"
|
||
>
|
||
<span
|
||
v-if="isUpdating"
|
||
class="loading loading-spinner loading-sm mr-2"
|
||
></span>
|
||
{{
|
||
mode === "create"
|
||
? isUpdating
|
||
? "创建中..."
|
||
: "创建实验"
|
||
: isUpdating
|
||
? "更新中..."
|
||
: "更新实验"
|
||
}}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 右侧:文件上传 -->
|
||
<div class="flex-1 p-6 overflow-y-auto">
|
||
<div class="space-y-6">
|
||
<h3 class="text-xl font-semibold text-base-content mb-4">
|
||
资源文件
|
||
</h3>
|
||
|
||
<!-- 第一行:MD文档 和 图片资源 -->
|
||
<div class="grid grid-cols-2 gap-4">
|
||
<!-- MD文档 -->
|
||
<div class="space-y-2">
|
||
<label class="text-sm font-medium text-base-content"
|
||
>MD文档 (可选)</label
|
||
>
|
||
<div
|
||
class="border-2 border-dashed border-base-300 rounded-lg p-6 text-center cursor-pointer hover:border-primary hover:bg-primary/5 transition-colors aspect-square flex items-center justify-center"
|
||
@click="mdFileInput?.click()"
|
||
@dragover.prevent
|
||
@dragenter.prevent
|
||
@drop.prevent="(e) => handleFileDrop(e, 'md')"
|
||
>
|
||
<div
|
||
v-if="!uploadFiles.mdFile"
|
||
class="flex flex-col items-center gap-3"
|
||
>
|
||
<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">
|
||
<FileTextIcon class="w-8 h-8 text-success" />
|
||
<div class="text-xs font-medium text-success text-center">
|
||
{{ uploadFiles.mdFile.name }}
|
||
</div>
|
||
<div class="text-xs text-base-content/50">点击重新选择</div>
|
||
</div>
|
||
</div>
|
||
<input
|
||
type="file"
|
||
ref="mdFileInput"
|
||
@change="(e) => handleFileChange(e, 'md')"
|
||
accept=".md"
|
||
class="hidden"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 图片资源 -->
|
||
<div class="space-y-2">
|
||
<label class="text-sm font-medium text-base-content"
|
||
>图片资源 (可选)</label
|
||
>
|
||
<div
|
||
class="border-2 border-dashed border-base-300 rounded-lg p-6 text-center cursor-pointer hover:border-primary hover:bg-primary/5 transition-colors aspect-square flex items-center justify-center"
|
||
@click="imageFilesInput?.click()"
|
||
@dragover.prevent
|
||
@dragenter.prevent
|
||
@drop.prevent="(e) => handleFileDrop(e, 'image')"
|
||
>
|
||
<div
|
||
v-if="uploadFiles.imageFiles.length === 0"
|
||
class="flex flex-col items-center gap-3"
|
||
>
|
||
<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">
|
||
<ImageIcon class="w-8 h-8 text-success" />
|
||
<div class="text-xs font-medium text-success">
|
||
{{ uploadFiles.imageFiles.length }} 个文件
|
||
</div>
|
||
<div class="text-xs text-base-content/50">点击重新选择</div>
|
||
</div>
|
||
</div>
|
||
<input
|
||
type="file"
|
||
ref="imageFilesInput"
|
||
@change="(e) => handleFileChange(e, 'image')"
|
||
accept="image/*"
|
||
multiple
|
||
class="hidden"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 第二行:示例比特流 和 画布模板 -->
|
||
<div class="grid grid-cols-2 gap-4">
|
||
<!-- 示例比特流 -->
|
||
<div class="space-y-2">
|
||
<label class="text-sm font-medium text-base-content"
|
||
>示例比特流 (可选)</label
|
||
>
|
||
<div
|
||
class="border-2 border-dashed border-base-300 rounded-lg p-6 text-center cursor-pointer hover:border-primary hover:bg-primary/5 transition-colors aspect-square flex items-center justify-center"
|
||
@click="bitstreamFilesInput?.click()"
|
||
@dragover.prevent
|
||
@dragenter.prevent
|
||
@drop.prevent="(e) => handleFileDrop(e, 'bitstream')"
|
||
>
|
||
<div
|
||
v-if="uploadFiles.bitstreamFiles.length === 0"
|
||
class="flex flex-col items-center gap-3"
|
||
>
|
||
<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">
|
||
<BinaryIcon class="w-8 h-8 text-success" />
|
||
<div class="text-xs font-medium text-success">
|
||
{{ uploadFiles.bitstreamFiles.length }} 个文件
|
||
</div>
|
||
<div class="text-xs text-base-content/50">点击重新选择</div>
|
||
</div>
|
||
</div>
|
||
<input
|
||
type="file"
|
||
ref="bitstreamFilesInput"
|
||
@change="(e) => handleFileChange(e, 'bitstream')"
|
||
accept=".sbit,.bit,.bin"
|
||
multiple
|
||
class="hidden"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 画布模板 -->
|
||
<div class="space-y-2">
|
||
<label class="text-sm font-medium text-base-content"
|
||
>画布模板 (可选)</label
|
||
>
|
||
<div
|
||
class="border-2 border-dashed border-base-300 rounded-lg p-6 text-center cursor-pointer hover:border-primary hover:bg-primary/5 transition-colors aspect-square flex items-center justify-center"
|
||
@click="canvasFilesInput?.click()"
|
||
@dragover.prevent
|
||
@dragenter.prevent
|
||
@drop.prevent="(e) => handleFileDrop(e, 'canvas')"
|
||
>
|
||
<div
|
||
v-if="uploadFiles.canvasFiles.length === 0"
|
||
class="flex flex-col items-center gap-3"
|
||
>
|
||
<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">
|
||
<FileJsonIcon class="w-8 h-8 text-success" />
|
||
<div class="text-xs font-medium text-success">
|
||
{{ uploadFiles.canvasFiles.length }} 个文件
|
||
</div>
|
||
<div class="text-xs text-base-content/50">点击重新选择</div>
|
||
</div>
|
||
</div>
|
||
<input
|
||
type="file"
|
||
ref="canvasFilesInput"
|
||
@change="(e) => handleFileChange(e, 'canvas')"
|
||
accept=".json"
|
||
multiple
|
||
class="hidden"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 第三行:资源包 (单独一个,居中) -->
|
||
<div class="flex justify-center">
|
||
<div class="w-1/2 space-y-2">
|
||
<label class="text-sm font-medium text-base-content"
|
||
>资源包 (可选)</label
|
||
>
|
||
<div
|
||
class="border-2 border-dashed border-base-300 rounded-lg p-6 text-center cursor-pointer hover:border-primary hover:bg-primary/5 transition-colors aspect-square flex items-center justify-center"
|
||
@click="resourceFileInput?.click()"
|
||
@dragover.prevent
|
||
@dragenter.prevent
|
||
@drop.prevent="(e) => handleFileDrop(e, 'resource')"
|
||
>
|
||
<div
|
||
v-if="!uploadFiles.resourceFile"
|
||
class="flex flex-col items-center gap-3"
|
||
>
|
||
<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">
|
||
<FileArchiveIcon class="w-8 h-8 text-success" />
|
||
<div class="text-xs font-medium text-success text-center">
|
||
{{ uploadFiles.resourceFile.name }}
|
||
</div>
|
||
<div class="text-xs text-base-content/50">点击重新选择</div>
|
||
</div>
|
||
</div>
|
||
<input
|
||
type="file"
|
||
ref="resourceFileInput"
|
||
@change="(e) => handleFileChange(e, 'resource')"
|
||
accept=".zip,.rar,.7z"
|
||
class="hidden"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-backdrop" @click="close"></div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import {
|
||
FileTextIcon,
|
||
ImageIcon,
|
||
BinaryIcon,
|
||
FileArchiveIcon,
|
||
FileJsonIcon,
|
||
XIcon,
|
||
} from "lucide-vue-next";
|
||
import {
|
||
ExamClient,
|
||
ExamDto,
|
||
ResourceClient,
|
||
ResourcePurpose,
|
||
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";
|
||
|
||
type Mode = "create" | "edit";
|
||
|
||
const isShowModal = defineModel<boolean>("isShowModal", {
|
||
default: false,
|
||
});
|
||
|
||
const emits = defineEmits<{
|
||
editFinished: [examId: string];
|
||
}>();
|
||
|
||
const alert = useRequiredInjection(useAlertStore);
|
||
|
||
const editExamInfo = ref({
|
||
id: "",
|
||
name: "",
|
||
description: "",
|
||
tags: [] as string[],
|
||
difficulty: 1,
|
||
isVisibleToUsers: true,
|
||
});
|
||
|
||
const isUpdating = ref(false);
|
||
const mode = ref<Mode>("create");
|
||
const newTagInput = ref("");
|
||
|
||
// 文件上传相关
|
||
const uploadFiles = ref({
|
||
mdFile: null as File | null,
|
||
imageFiles: [] as File[],
|
||
bitstreamFiles: [] as File[],
|
||
canvasFiles: [] as File[],
|
||
resourceFile: null as File | null,
|
||
});
|
||
|
||
// 文件输入引用
|
||
const mdFileInput = ref<HTMLInputElement>();
|
||
const imageFilesInput = ref<HTMLInputElement>();
|
||
const bitstreamFilesInput = ref<HTMLInputElement>();
|
||
const canvasFilesInput = ref<HTMLInputElement>();
|
||
const resourceFileInput = ref<HTMLInputElement>();
|
||
|
||
// 计算属性
|
||
const canCreateExam = computed(() => {
|
||
return (
|
||
editExamInfo.value.id.trim() !== "" &&
|
||
editExamInfo.value.name.trim() !== "" &&
|
||
editExamInfo.value.description.trim() !== "" &&
|
||
(mode.value === "edit")
|
||
);
|
||
});
|
||
|
||
// 文件类型定义
|
||
type FileType = "md" | "image" | "bitstream" | "canvas" | "resource";
|
||
|
||
// 统一文件处理方法
|
||
const handleFileChange = (event: Event, fileType: FileType) => {
|
||
const target = event.target as HTMLInputElement;
|
||
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 handleFileDrop = (event: DragEvent, fileType: FileType) => {
|
||
const files = event.dataTransfer?.files;
|
||
if (!files || files.length === 0) return;
|
||
|
||
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;
|
||
}
|
||
};
|
||
|
||
// 标签管理
|
||
const addTag = (event?: Event) => {
|
||
if (event) {
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
}
|
||
const tag = newTagInput.value.trim();
|
||
if (tag && !editExamInfo.value.tags.includes(tag)) {
|
||
editExamInfo.value.tags.push(tag);
|
||
newTagInput.value = "";
|
||
}
|
||
};
|
||
|
||
const removeTag = (index: number) => {
|
||
editExamInfo.value.tags.splice(index, 1);
|
||
};
|
||
|
||
const resetCreateForm = () => {
|
||
editExamInfo.value = {
|
||
id: "",
|
||
name: "",
|
||
description: "",
|
||
tags: [],
|
||
difficulty: 1,
|
||
isVisibleToUsers: true,
|
||
};
|
||
newTagInput.value = "";
|
||
uploadFiles.value = {
|
||
mdFile: null,
|
||
imageFiles: [],
|
||
bitstreamFiles: [],
|
||
canvasFiles: [],
|
||
resourceFile: null,
|
||
};
|
||
|
||
// 重置文件输入
|
||
if (mdFileInput.value) mdFileInput.value.value = "";
|
||
if (imageFilesInput.value) imageFilesInput.value.value = "";
|
||
if (bitstreamFilesInput.value) bitstreamFilesInput.value.value = "";
|
||
if (canvasFilesInput.value) canvasFilesInput.value.value = "";
|
||
if (resourceFileInput.value) resourceFileInput.value.value = "";
|
||
};
|
||
|
||
// 提交创建实验
|
||
const submitCreateExam = async () => {
|
||
if (isUpdating.value) return;
|
||
|
||
// 验证必填字段
|
||
if (
|
||
!editExamInfo.value.id ||
|
||
!editExamInfo.value.name ||
|
||
!editExamInfo.value.description
|
||
) {
|
||
alert?.error("请填写所有必填字段");
|
||
return;
|
||
}
|
||
|
||
isUpdating.value = true;
|
||
|
||
try {
|
||
const client = AuthManager.createClient(ExamClient);
|
||
|
||
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,
|
||
});
|
||
|
||
// 创建实验
|
||
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(exam.id);
|
||
|
||
alert.success("实验创建成功");
|
||
close();
|
||
emits("editFinished", exam.id);
|
||
} catch (err: any) {
|
||
console.error("创建实验失败:", err);
|
||
alert.error(err.message || "创建实验失败");
|
||
} finally {
|
||
isUpdating.value = false;
|
||
}
|
||
};
|
||
|
||
// 上传实验资源
|
||
async function uploadExamResources(examId: string) {
|
||
const client = AuthManager.createClient(ResourceClient);
|
||
|
||
try {
|
||
// 上传MD文档
|
||
if (uploadFiles.value.mdFile) {
|
||
const mdFileParam: FileParameter = {
|
||
data: uploadFiles.value.mdFile,
|
||
fileName: uploadFiles.value.mdFile.name,
|
||
};
|
||
await client.addResource(
|
||
"doc",
|
||
ResourcePurpose.Template,
|
||
examId,
|
||
mdFileParam,
|
||
);
|
||
console.log("MD文档上传成功");
|
||
}
|
||
|
||
// 上传图片资源
|
||
for (const imageFile of uploadFiles.value.imageFiles) {
|
||
const imageFileParam: FileParameter = {
|
||
data: imageFile,
|
||
fileName: imageFile.name,
|
||
};
|
||
await client.addResource(
|
||
"image",
|
||
ResourcePurpose.Template,
|
||
examId,
|
||
imageFileParam,
|
||
);
|
||
console.log("图片上传成功:", imageFile.name);
|
||
}
|
||
|
||
// 上传比特流文件
|
||
for (const bitstreamFile of uploadFiles.value.bitstreamFiles) {
|
||
const bitstreamFileParam: FileParameter = {
|
||
data: bitstreamFile,
|
||
fileName: bitstreamFile.name,
|
||
};
|
||
await client.addResource(
|
||
"bitstream",
|
||
ResourcePurpose.Template,
|
||
examId,
|
||
bitstreamFileParam,
|
||
);
|
||
console.log("比特流文件上传成功:", bitstreamFile.name);
|
||
}
|
||
|
||
// 上传画布模板
|
||
for (const canvasFile of uploadFiles.value.canvasFiles) {
|
||
const canvasFileParam: FileParameter = {
|
||
data: canvasFile,
|
||
fileName: canvasFile.name,
|
||
};
|
||
await client.addResource(
|
||
"canvas",
|
||
ResourcePurpose.Template,
|
||
examId,
|
||
canvasFileParam,
|
||
);
|
||
console.log("画布模板上传成功:", canvasFile.name);
|
||
}
|
||
|
||
// 上传资源包
|
||
if (uploadFiles.value.resourceFile) {
|
||
const resourceFileParam: FileParameter = {
|
||
data: uploadFiles.value.resourceFile,
|
||
fileName: uploadFiles.value.resourceFile.name,
|
||
};
|
||
await client.addResource(
|
||
"resource",
|
||
ResourcePurpose.Template,
|
||
examId,
|
||
resourceFileParam,
|
||
);
|
||
console.log("资源包上传成功");
|
||
}
|
||
} catch (err: any) {
|
||
console.error("资源上传失败:", err);
|
||
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.createClient(ExamClient);
|
||
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,
|
||
editExamInfo,
|
||
});
|
||
</script>
|
||
|
||
<style lang="postcss" scoped></style>
|