feat: 前端增加提交功能
This commit is contained in:
		@@ -1,6 +1,7 @@
 | 
			
		||||
using DotNext;
 | 
			
		||||
using LinqToDB;
 | 
			
		||||
using LinqToDB.Mapping;
 | 
			
		||||
using Tapper;
 | 
			
		||||
 | 
			
		||||
namespace Database;
 | 
			
		||||
 | 
			
		||||
@@ -231,6 +232,7 @@ public class Exam
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// 资源类型枚举
 | 
			
		||||
/// </summary>
 | 
			
		||||
[TranspilationSource]
 | 
			
		||||
public static class ResourceTypes
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										117
									
								
								src/components/UploadModal.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								src/components/UploadModal.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,117 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { templateRef } from "@vueuse/core";
 | 
			
		||||
import { File, UploadIcon, XIcon } from "lucide-vue-next";
 | 
			
		||||
import { isNull } from "mathjs";
 | 
			
		||||
import { useSlots } from "vue";
 | 
			
		||||
 | 
			
		||||
const slots = useSlots();
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
  autoUpload?: boolean;
 | 
			
		||||
  closeAfterUpload?: boolean;
 | 
			
		||||
  callback: (files: File[]) => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<Props>(), {
 | 
			
		||||
  autoUpload: false,
 | 
			
		||||
  closeAfterUpload: false,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const inputFiles = defineModel<File[] | null>("inputFiles", { default: null });
 | 
			
		||||
const isShowModal = defineModel<boolean>("isShowModal", { default: false });
 | 
			
		||||
 | 
			
		||||
const fileInputRef = templateRef("fileInputRef");
 | 
			
		||||
 | 
			
		||||
function handleFileChange(event: Event) {
 | 
			
		||||
  const files = (event.target as HTMLInputElement).files;
 | 
			
		||||
  if (!files) return;
 | 
			
		||||
  inputFiles.value = Array.from(files);
 | 
			
		||||
 | 
			
		||||
  if (props.autoUpload) handleUpload();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleFileDrop(event: DragEvent) {
 | 
			
		||||
  const files = event.dataTransfer?.files;
 | 
			
		||||
  if (!files) return;
 | 
			
		||||
  inputFiles.value = Array.from(files);
 | 
			
		||||
 | 
			
		||||
  if (props.autoUpload) handleUpload();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleUpload() {
 | 
			
		||||
  if (!inputFiles.value) return;
 | 
			
		||||
  props.callback(inputFiles.value);
 | 
			
		||||
  if (props.closeAfterUpload) close();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function show() {
 | 
			
		||||
  isShowModal.value = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function close() {
 | 
			
		||||
  isShowModal.value = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({
 | 
			
		||||
  show,
 | 
			
		||||
  close,
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div v-if="isShowModal" class="modal modal-open overflow-hidden">
 | 
			
		||||
    <div class="modal-box overflow-hidden flex flex-col gap-3">
 | 
			
		||||
      <div
 | 
			
		||||
        class="flex justify-between items-center pb-3 border-b border-base-300"
 | 
			
		||||
      >
 | 
			
		||||
        <h2 class="text-2xl font-bold text-base-content">文件上传</h2>
 | 
			
		||||
        <button @click="close" class="btn btn-sm btn-circle btn-ghost">
 | 
			
		||||
          <XIcon class="w-6 h-6" />
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div
 | 
			
		||||
        class="border-2 border-dashed border-base-300 rounded-lg text-center cursor-pointer hover:border-primary hover:bg-primary/5 transition-colors aspect-4/2 flex items-center justify-center"
 | 
			
		||||
        @click="fileInputRef.click()"
 | 
			
		||||
        @dragover.prevent
 | 
			
		||||
        @dragenter.prevent
 | 
			
		||||
        @drop.prevent="handleFileDrop"
 | 
			
		||||
      >
 | 
			
		||||
        <div v-if="slots.content">
 | 
			
		||||
          <slot name="content"></slot>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div v-else class="flex flex-col items-center gap-3">
 | 
			
		||||
          <File 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>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div v-else class="flex flex-col items-center gap-2">
 | 
			
		||||
          <File class="w-8 h-8 text-success" />
 | 
			
		||||
          <div class="text-xs font-medium text-success text-center">
 | 
			
		||||
            {{ inputFiles?.[0]?.name }}
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="text-xs text-base-content/50">点击重新选择</div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <input
 | 
			
		||||
        type="file"
 | 
			
		||||
        ref="fileInputRef"
 | 
			
		||||
        @change="handleFileChange"
 | 
			
		||||
        accept=""
 | 
			
		||||
        class="hidden"
 | 
			
		||||
      />
 | 
			
		||||
      <button
 | 
			
		||||
        v-if="!autoUpload"
 | 
			
		||||
        class="btn btn-primary btn-sm w-full h-10"
 | 
			
		||||
        @click="handleUpload"
 | 
			
		||||
        :disabled="isNull(inputFiles) || inputFiles.length === 0"
 | 
			
		||||
      >
 | 
			
		||||
        <UploadIcon class="w-6 h-6" />
 | 
			
		||||
        上传
 | 
			
		||||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="modal-backdrop" @click="close"></div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style lang="postcss" scoped></style>
 | 
			
		||||
@@ -14,9 +14,8 @@ const router = createRouter({
 | 
			
		||||
    { path: "/login", name: "login", component: AuthView },
 | 
			
		||||
    { path: "/project", name: "project", component: ProjectView },
 | 
			
		||||
    { path: "/test", name: "test", component: TestView },
 | 
			
		||||
    { path: "/user/:page?", name: "user", component: UserView },
 | 
			
		||||
    { path: "/exam/:examId?", name: "exam", component: ExamView },
 | 
			
		||||
    { path: "/exam", redirect: "/exam/" },
 | 
			
		||||
    { path: "/user/:page*", name: "user", component: UserView },
 | 
			
		||||
    { path: "/exam/:page*", name: "exam", component: ExamView },
 | 
			
		||||
    { path: "/markdown", name: "markdown", component: MarkdownEditor },
 | 
			
		||||
  ],
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -8,19 +8,7 @@
 | 
			
		||||
          {{ mode === "create" ? "新建实验" : "编辑实验" }}
 | 
			
		||||
        </h2>
 | 
			
		||||
        <button @click="close" class="btn btn-sm btn-circle btn-ghost">
 | 
			
		||||
          <svg
 | 
			
		||||
            class="w-6 h-6"
 | 
			
		||||
            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>
 | 
			
		||||
          <XIcon class="w-6 h-6" />
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@@ -417,11 +405,13 @@ import {
 | 
			
		||||
  BinaryIcon,
 | 
			
		||||
  FileArchiveIcon,
 | 
			
		||||
  FileJsonIcon,
 | 
			
		||||
  XIcon,
 | 
			
		||||
} from "lucide-vue-next";
 | 
			
		||||
import {
 | 
			
		||||
  ExamClient,
 | 
			
		||||
  ExamDto,
 | 
			
		||||
  ResourceClient,
 | 
			
		||||
  ResourcePurpose,
 | 
			
		||||
  type FileParameter,
 | 
			
		||||
} from "@/APIClient";
 | 
			
		||||
import { useAlertStore } from "@/components/Alert";
 | 
			
		||||
@@ -685,7 +675,12 @@ async function uploadExamResources(examId: string) {
 | 
			
		||||
        data: uploadFiles.value.mdFile,
 | 
			
		||||
        fileName: uploadFiles.value.mdFile.name,
 | 
			
		||||
      };
 | 
			
		||||
      await client.addResource("doc", "template", examId, mdFileParam);
 | 
			
		||||
      await client.addResource(
 | 
			
		||||
        "doc",
 | 
			
		||||
        ResourcePurpose.Template,
 | 
			
		||||
        examId,
 | 
			
		||||
        mdFileParam,
 | 
			
		||||
      );
 | 
			
		||||
      console.log("MD文档上传成功");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -695,7 +690,12 @@ async function uploadExamResources(examId: string) {
 | 
			
		||||
        data: imageFile,
 | 
			
		||||
        fileName: imageFile.name,
 | 
			
		||||
      };
 | 
			
		||||
      await client.addResource("image", "template", examId, imageFileParam);
 | 
			
		||||
      await client.addResource(
 | 
			
		||||
        "image",
 | 
			
		||||
        ResourcePurpose.Template,
 | 
			
		||||
        examId,
 | 
			
		||||
        imageFileParam,
 | 
			
		||||
      );
 | 
			
		||||
      console.log("图片上传成功:", imageFile.name);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -707,7 +707,7 @@ async function uploadExamResources(examId: string) {
 | 
			
		||||
      };
 | 
			
		||||
      await client.addResource(
 | 
			
		||||
        "bitstream",
 | 
			
		||||
        "template",
 | 
			
		||||
        ResourcePurpose.Template,
 | 
			
		||||
        examId,
 | 
			
		||||
        bitstreamFileParam,
 | 
			
		||||
      );
 | 
			
		||||
@@ -720,7 +720,12 @@ async function uploadExamResources(examId: string) {
 | 
			
		||||
        data: canvasFile,
 | 
			
		||||
        fileName: canvasFile.name,
 | 
			
		||||
      };
 | 
			
		||||
      await client.addResource("canvas", "template", examId, canvasFileParam);
 | 
			
		||||
      await client.addResource(
 | 
			
		||||
        "canvas",
 | 
			
		||||
        ResourcePurpose.Template,
 | 
			
		||||
        examId,
 | 
			
		||||
        canvasFileParam,
 | 
			
		||||
      );
 | 
			
		||||
      console.log("画布模板上传成功:", canvasFile.name);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -732,7 +737,7 @@ async function uploadExamResources(examId: string) {
 | 
			
		||||
      };
 | 
			
		||||
      await client.addResource(
 | 
			
		||||
        "resource",
 | 
			
		||||
        "template",
 | 
			
		||||
        ResourcePurpose.Template,
 | 
			
		||||
        examId,
 | 
			
		||||
        resourceFileParam,
 | 
			
		||||
      );
 | 
			
		||||
@@ -775,6 +780,7 @@ defineExpose({
 | 
			
		||||
  show,
 | 
			
		||||
  close,
 | 
			
		||||
  editExam,
 | 
			
		||||
  editExamInfo,
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -13,19 +13,7 @@
 | 
			
		||||
          @click="closeExamDetail"
 | 
			
		||||
          class="btn btn-sm btn-circle btn-ghost"
 | 
			
		||||
        >
 | 
			
		||||
          <svg
 | 
			
		||||
            class="w-6 h-6"
 | 
			
		||||
            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>
 | 
			
		||||
          <XIcon class="w-6 h-6" />
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@@ -147,17 +135,18 @@
 | 
			
		||||
                <div class="space-y-4">
 | 
			
		||||
                  <div class="flex justify-between items-center">
 | 
			
		||||
                    <span class="text-base-content/70">当前状态</span>
 | 
			
		||||
                    <div class="badge badge-error">未完成</div>
 | 
			
		||||
                  </div>
 | 
			
		||||
 | 
			
		||||
                  <div class="flex justify-between items-center">
 | 
			
		||||
                    <span class="text-base-content/70">批阅状态</span>
 | 
			
		||||
                    <div class="badge badge-ghost">待提交</div>
 | 
			
		||||
                    <div class="badge badge-error">
 | 
			
		||||
                      {{
 | 
			
		||||
                        isUndefined(commitsList) || commitsList.length === 0
 | 
			
		||||
                          ? "未提交"
 | 
			
		||||
                          : "已提交"
 | 
			
		||||
                      }}
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
 | 
			
		||||
                  <div class="flex justify-between items-center">
 | 
			
		||||
                    <span class="text-base-content/70">成绩</span>
 | 
			
		||||
                    <span class="text-base-content/50">未评分</span>
 | 
			
		||||
                    <div class="badge badge-ghost">未评分</div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
@@ -167,17 +156,22 @@
 | 
			
		||||
                <div class="space-y-3">
 | 
			
		||||
                  <h4 class="font-medium text-base-content">提交历史</h4>
 | 
			
		||||
                  <div
 | 
			
		||||
                    v-if="isUndefined(commitsList)"
 | 
			
		||||
                    v-if="isUndefined(commitsList) || commitsList.length === 0"
 | 
			
		||||
                    class="text-sm text-base-content/50 text-center py-4"
 | 
			
		||||
                  >
 | 
			
		||||
                    暂无提交记录
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div v-else class="overflow-y-auto">
 | 
			
		||||
                    <ul class="steps steps-vertical">
 | 
			
		||||
                      <li class="step step-primary">Register</li>
 | 
			
		||||
                      <li class="step step-primary">Choose plan</li>
 | 
			
		||||
                      <li class="step">Purchase</li>
 | 
			
		||||
                      <li class="step">Receive Product</li>
 | 
			
		||||
                      <li
 | 
			
		||||
                        class="step"
 | 
			
		||||
                        :class="{ 'step-primary': _idx === 1 }"
 | 
			
		||||
                        v-for="(commit, _idx) in commitsList.slice(0, 3)"
 | 
			
		||||
                      >
 | 
			
		||||
                        {{ commit.id }} ---
 | 
			
		||||
                        {{ commit.uploadTime.toDateString() }}
 | 
			
		||||
                      </li>
 | 
			
		||||
                      <li class="step" v-if="commitsList.length > 3">......</li>
 | 
			
		||||
                    </ul>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
@@ -187,58 +181,27 @@
 | 
			
		||||
            <!-- 操作按钮 -->
 | 
			
		||||
            <div class="space-y-3">
 | 
			
		||||
              <button @click="startExam" class="btn btn-primary w-full">
 | 
			
		||||
                <svg
 | 
			
		||||
                  class="w-5 h-5 mr-2"
 | 
			
		||||
                  fill="none"
 | 
			
		||||
                  stroke="currentColor"
 | 
			
		||||
                  viewBox="0 0 24 24"
 | 
			
		||||
                >
 | 
			
		||||
                  <path
 | 
			
		||||
                    stroke-linecap="round"
 | 
			
		||||
                    stroke-linejoin="round"
 | 
			
		||||
                    stroke-width="2"
 | 
			
		||||
                    d="M14.828 14.828a4 4 0 01-5.656 0M9 10h1m4 0h1m-6 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
 | 
			
		||||
                  />
 | 
			
		||||
                </svg>
 | 
			
		||||
                <Smile class="w-5 h-5" />
 | 
			
		||||
                开始实验
 | 
			
		||||
              </button>
 | 
			
		||||
 | 
			
		||||
              <button @click="uploadModal?.show" class="btn btn-info w-full">
 | 
			
		||||
                <Upload class="w-5 h-5" />
 | 
			
		||||
                提交实验
 | 
			
		||||
              </button>
 | 
			
		||||
 | 
			
		||||
              <button
 | 
			
		||||
                @click="downloadResources"
 | 
			
		||||
                class="btn btn-outline w-full"
 | 
			
		||||
                :disabled="downloadingResources"
 | 
			
		||||
              >
 | 
			
		||||
                <svg
 | 
			
		||||
                  class="w-5 h-5 mr-2"
 | 
			
		||||
                  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>
 | 
			
		||||
                <Download class="w-5 h-5" />
 | 
			
		||||
                <span v-if="downloadingResources">下载中...</span>
 | 
			
		||||
                <span v-else>下载资源包</span>
 | 
			
		||||
              </button>
 | 
			
		||||
 | 
			
		||||
              <button class="btn btn-outline w-full">
 | 
			
		||||
                <svg
 | 
			
		||||
                  class="w-5 h-5 mr-2"
 | 
			
		||||
                  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>
 | 
			
		||||
                <GitGraph class="w-5 h-5" />
 | 
			
		||||
                查看记录
 | 
			
		||||
              </button>
 | 
			
		||||
            </div>
 | 
			
		||||
@@ -247,6 +210,12 @@
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="modal-backdrop" @click="closeExamDetail"></div>
 | 
			
		||||
    <UploadModal
 | 
			
		||||
      ref="uploadModal"
 | 
			
		||||
      class="fixed z-auto"
 | 
			
		||||
      :auto-upload="true"
 | 
			
		||||
      :callback="submitExam"
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
@@ -266,10 +235,16 @@ import { formatDate } from "@/utils/Common";
 | 
			
		||||
import { computed } from "vue";
 | 
			
		||||
import { watch } from "vue";
 | 
			
		||||
import { isNull, isUndefined } from "lodash";
 | 
			
		||||
import { Download, GitGraph, Smile, Upload, XIcon } from "lucide-vue-next";
 | 
			
		||||
import UploadModal from "@/components/UploadModal.vue";
 | 
			
		||||
import { templateRef } from "@vueuse/core";
 | 
			
		||||
import { toFileParameter } from "@/utils/Common";
 | 
			
		||||
 | 
			
		||||
const alertStore = useRequiredInjection(useAlertStore);
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
 | 
			
		||||
const uploadModal = templateRef("uploadModal");
 | 
			
		||||
 | 
			
		||||
const show = defineModel<boolean>("show", {
 | 
			
		||||
  default: false,
 | 
			
		||||
});
 | 
			
		||||
@@ -350,6 +325,24 @@ const startExam = () => {
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const submitExam = (files: File[]) => {
 | 
			
		||||
  try {
 | 
			
		||||
    const client = AuthManager.createClient(ResourceClient);
 | 
			
		||||
 | 
			
		||||
    for (const file of files) {
 | 
			
		||||
      client.addResource(
 | 
			
		||||
        "compression",
 | 
			
		||||
        ResourcePurpose.Homework,
 | 
			
		||||
        props.selectedExam.id,
 | 
			
		||||
        toFileParameter(file),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  } catch (err: any) {
 | 
			
		||||
    alertStore.error(err.message || "上传资料失败");
 | 
			
		||||
    console.error("上传资料失败:", err);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const closeExamDetail = () => {
 | 
			
		||||
  show.value = false;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -191,6 +191,7 @@ import { templateRef } from "@vueuse/core";
 | 
			
		||||
import { isArray, isNull } from "lodash";
 | 
			
		||||
import { watch } from "vue";
 | 
			
		||||
import { watchEffect } from "vue";
 | 
			
		||||
import { nextTick } from "vue";
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const route = useRoute();
 | 
			
		||||
@@ -218,13 +219,18 @@ watch(
 | 
			
		||||
  },
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
watchEffect(() => {
 | 
			
		||||
  if (showEditModal.value) {
 | 
			
		||||
    router.replace({ path: `/exam/edit` });
 | 
			
		||||
  } else {
 | 
			
		||||
    router.replace({ path: `/exam` });
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
watch(
 | 
			
		||||
  () => showEditModal.value,
 | 
			
		||||
  () => {
 | 
			
		||||
    if (showEditModal.value) {
 | 
			
		||||
      router.replace({
 | 
			
		||||
        path: `/exam/edit/${examEditModalRef.value?.editExamInfo.id}`,
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
      router.replace({ path: `/exam` });
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
async function refreshExams() {
 | 
			
		||||
  loading.value = true;
 | 
			
		||||
@@ -263,7 +269,8 @@ async function handleCardClicked(event: MouseEvent, examId: string) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function handleEditExamClicked(event: MouseEvent, examId: string) {
 | 
			
		||||
  examEditModalRef?.value?.editExam(examId);
 | 
			
		||||
  await examEditModalRef?.value?.editExam(examId);
 | 
			
		||||
  router.replace(`/exam/edit/${examId}`);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 生命周期
 | 
			
		||||
@@ -278,17 +285,28 @@ onMounted(async () => {
 | 
			
		||||
  await refreshExams();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
async function loadBasicPage(page: string) {
 | 
			
		||||
  if (page === "") return;
 | 
			
		||||
  else if (page === "edit") showEditModal.value = true;
 | 
			
		||||
  else if (page) await viewExam(page);
 | 
			
		||||
  else router.push("/exam");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
  // 处理路由参数,如果有examId则自动打开该实验的详情模态框
 | 
			
		||||
  const examId = route.params.examId as string;
 | 
			
		||||
  if (examId === "") return;
 | 
			
		||||
  const page = route.params.page;
 | 
			
		||||
 | 
			
		||||
  if (isArray(examId)) return;
 | 
			
		||||
 | 
			
		||||
  if (examId === "edit") showEditModal.value = true;
 | 
			
		||||
 | 
			
		||||
  if (examId) {
 | 
			
		||||
    await viewExam(examId);
 | 
			
		||||
  if (Array.isArray(page)) {
 | 
			
		||||
    if (page.length == 1) await loadBasicPage(page[0]);
 | 
			
		||||
    else if (page.length == 2) {
 | 
			
		||||
      if (page[0] === "edit") {
 | 
			
		||||
        await examEditModalRef.value?.editExam(page[1]);
 | 
			
		||||
      } else {
 | 
			
		||||
        router.push("/exam");
 | 
			
		||||
      }
 | 
			
		||||
    } else router.push("/exam");
 | 
			
		||||
  } else {
 | 
			
		||||
    await loadBasicPage(page);
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user