Browse Source

客观题检查

zhangjie 1 năm trước cách đây
mục cha
commit
b53ed27371

+ 19 - 0
src/api/confirmPage.ts

@@ -0,0 +1,19 @@
+import { httpApp } from "@/plugins/axiosApp";
+import { StudentObjectiveInfo } from "@/types";
+
+/** 获取学生客观题数据 */
+export async function studentObjectiveConfirmData(studentId) {
+  return httpApp.post<StudentObjectiveInfo>(
+    "/api/admin/mark/inspected/objective/getTask",
+    {},
+    { params: { studentId } }
+  );
+}
+/** 保存考生客观题数据 */
+export async function saveStudentObjectiveConfirmData({ studentId, answers }) {
+  return httpApp.post<StudentObjectiveInfo>(
+    "/api/admin/mark/inspected/objective/saveTask",
+    {},
+    { params: { studentId, answers } }
+  );
+}

+ 0 - 6
src/components/QmButton.vue

@@ -8,12 +8,6 @@
   </a-button>
 </template>
 
-<script lang="ts">
-export default {
-  inheritAttrs: false,
-};
-</script>
-
 <script setup lang="ts">
 import { reactive, useAttrs, watch } from "vue";
 

+ 6 - 3
src/devLogin.ts

@@ -4,8 +4,7 @@ import { getBase64 } from "./utils/crypto";
 import { httpApp } from "@/plugins/axiosApp";
 import vls from "./utils/storage";
 
-const { loginName, password, examId, paperNumber, groupNumber, schoolCode } =
-  LOGIN_CONFIG;
+const { loginName, password, schoolCode, studentIds, mark } = LOGIN_CONFIG;
 
 export async function initLogin() {
   const res = await httpApp.post("/api/admin/common/login", {
@@ -25,6 +24,10 @@ export async function initLogin() {
     }
   }
   vls.set("user", data);
-  vls.set("mark", { examId, paperNumber, groupNumber });
   vls.set("token", data.accessToken);
+
+  studentIds
+    ? vls.set("check-students", studentIds)
+    : vls.remove("check-students");
+  mark ? vls.set("mark", mark) : vls.remove("mark");
 }

+ 19 - 118
src/devLoginParams.ts

@@ -1,124 +1,25 @@
-/** 224 评卷员 */
+// 192.168.11.167:7001
+// 评卷员
 // export const LOGIN_CONFIG = {
+//   isAdmin: false,
+//   loginName: "java",
+//   password: "12345678",
+//   mark: {
+//     examId: "437537682336260096",
+//     paperNumber: "bh102101",
+//     groupNumber: 1,
+//   },
+//   schoolCode: "test-school-1",
 // };
-// export const isAdmin= false;
-// export const forceChange=true;
-// export const loginName="1-431-5-1";
-// export const password="123456";
-// export const examId="1";
-// export const markerId="419";
-
-// 192.168.11.167:7001
 export const LOGIN_CONFIG = {
-  isAdmin: false,
-  loginName: "java",
+  isAdmin: true,
+  loginName: "admin",
   password: "12345678",
-  examId: "437537682336260096",
-  paperNumber: "bh102101",
-  groupNumber: 1,
+  // mark: {
+  //   examId: "437537682336260096",
+  //   paperNumber: "bh102101",
+  //   groupNumber: 1,
+  // },
   schoolCode: "test-school-1",
+  studentIds: ["448443226152513536"],
 };
-/** 244 评卷员 */
-// export const LOGIN_CONFIG = {
-//   isAdmin: false,
-//   forceChange: true,
-//   loginName: "1-339-5-1",
-//   // loginName: "liuyang",
-//   password: "123456",
-//   examId: "1",
-//   markerId: "147",
-//   // markerId: "482",
-//   // markerId: "483",
-// };
-// export const LOGIN_CONFIG = {
-//   isAdmin: false,
-//   forceChange: true,
-//   loginName: "spj111-1",
-//   // loginName: "liuyang",
-//   password: "654321",
-//   examId: "341",
-//   markerId: "3487",
-//   // markerId: "482",
-//   // markerId: "483",
-// };
-// export const LOGIN_CONFIG = {
-//   isAdmin: false,
-//   forceChange: true,
-//   loginName: "spj111-01",
-//   // loginName: "liuyang",
-//   password: "123456",
-//   examId: "295",
-//   markerId: "3132",
-//   // markerId: "482",
-//   // markerId: "483",
-// };
-// export const LOGIN_CONFIG = {
-//   isAdmin: false,
-//   forceChange: true,
-//   loginName: "6-X-M_pj01",
-//   // loginName: "liuyang",
-//   password: "123456",
-//   examId: "432",
-//   markerId: "3123",
-//   // markerId: "482",
-//   // markerId: "483",
-// };
-// export const LOGIN_CONFIG = {
-//   isAdmin: false,
-//   forceChange: true,
-//   loginName: "spj111-01",
-//   // loginName: "spj432-01",
-//   // loginName: "liuyang",
-//   password: "123456",
-//   examId: "295",
-//   markerId: "3397",
-//   // markerId: "2692",
-//   // markerId: "482",
-//   // markerId: "483",
-// };
-/** 224 管理员 */
-// export const LOGIN_CONFIG = {
-//   isAdmin: true,
-//   forceChange: true,
-//   loginName: "admin-test",
-//   password: "123456",
-//   examId: "1",
-//   markerId: null,
-// };
-// export const LOGIN_CONFIG = {
-//   isAdmin: true,
-//   forceChange: true,
-//   // loginName: "fh161301",
-//   loginName: "24-FH-111-1",
-//   password: "654321",
-//   examId: "341",
-//   markerId: null,
-// };
-
-/** 255 评卷员 */
-// export const LOGIN_CONFIG = {
-//   isAdmin: false,
-//   forceChange: true,
-//   loginName: "spj111-02",
-//   password: "123456",
-//   examId: "348",
-//   markerId: "3717",
-// };
-
-/** 225 管理员 */
-
-// export const LOGIN_CONFIG = {
-//   isAdmin: true,
-//   forceChange: true,
-//   loginName: "admin031",
-//   password: "123456",
-//   examId: "347",
-//   markerId: "null",
-// };
-
-// export const loginName = "admin-ch";
-// export const password = "123456";
-// export const examId = "1";
-// export const markerId = "null";
-// export const isAdmin = true;
-// export const forceChange = true;

+ 177 - 377
src/features/admin/confirmPaper/ConfirmPaper.vue

@@ -1,272 +1,200 @@
 <template>
-  <div v-if="!dataError" class="tw-h-screen confirm-paper">
-    <header
-      class="tw-flex tw-gap-2 tw-justify-between tw-items-center header-container"
-    >
-      <div class="tw-ml-2">
-        进度:<span class="highlight-text">
-          {{ currentIndex }}/{{ allIds.length }}
-        </span>
-      </div>
-      <div>
-        姓名:<span class="highlight-text">{{ student?.name }}</span>
-      </div>
-      <div>
-        准考证号:<span class="highlight-text">{{ student?.examNumber }}</span>
-      </div>
-      <div>
-        学号:<span class="highlight-text">{{ student?.studentCode }}</span>
-      </div>
-      <div>
-        科目:<span class="highlight-text">
-          {{ student?.subjectCode }}-{{ student?.subjectName }}
-        </span>
-      </div>
-      <div>
-        客观分:<span class="highlight-text">{{
-          student?.objectiveScore
-        }}</span>
-      </div>
-      <div>
-        主观分:<span class="highlight-text">{{
-          student?.subjectiveScore
-        }}</span>
+  <div class="mark confirm-paper">
+    <div class="mark-header">
+      <div v-if="student" class="mark-header-part">
+        <div class="header-noun">
+          <span>课程名称:</span>
+          <span> {{ student.courseName }}({{ student.courseCode }})</span>
+        </div>
+        <div class="header-noun">
+          <span>试卷编号:</span>
+          <span>{{ student.paperNumber }}</span>
+        </div>
+        <div class="header-noun">
+          <span>姓名:</span>
+          <span>{{ student.studentName }}</span>
+        </div>
+        <div class="header-noun">
+          <span>学号:</span>
+          <span>{{ student?.studentCode }}</span>
+        </div>
+        <div v-if="studentIds.length > 1" class="header-noun">
+          <span>进度:</span>
+          <span> {{ currentIndex }}/{{ studentIds.length }} </span>
+        </div>
       </div>
-      <div class="tw-flex tw-items-center tw-gap-2 tw-mx-8">
-        <span
-          v-for="(u, index) in student?.sheetUrls"
-          :key="index"
-          class="tw-cursor-pointer"
-          :class="currentImage === index && 'highlight-text'"
-          @click="currentImage = index"
-        >
-          {{ index + 1 }}
-        </span>
+      <div class="mark-header-part">
+        <div class="paper-menu">
+          <span
+            v-for="(u, index) in student?.sheetUrls"
+            :key="index"
+            :class="{ 'is-active': currentImage === index }"
+            @click="currentImage = index"
+          >
+            {{ index + 1 }}
+          </span>
+        </div>
       </div>
-    </header>
+    </div>
 
-    <div class="tw-flex" style="height: calc(100% - 56px)">
-      <div
-        style="flex: 0 1 420px; overflow: auto"
-        class="tw-flex tw-flex-col tw-justify-between"
-      >
-        <div class="tw-m-2 tw-flex-1 tw-overflow-auto">
-          <div v-if="pageType === 'DATA_CHECK'" class="tw-my-2 top-block">
-            是否缺考:
-            <a-radio-group v-if="student" v-model:value="student.absent">
-              <a-radio :value="true">是</a-radio>
-              <a-radio :value="false">否</a-radio>
-            </a-radio-group>
+    <div class="mark-main">
+      <div class="mark-body">
+        <div
+          v-if="student && currentImage !== 0"
+          class="page-action page-prev"
+          title="上一张"
+          @click="switchImageArrow({ left: true })"
+        >
+          <ArrowLeftOutlined />
+        </div>
+        <div
+          v-if="student && currentImage !== student.sheetUrls.length - 1"
+          class="page-action page-next"
+          title="上一张"
+          @click="switchImageArrow({ right: true })"
+        >
+          <ArrowRightOutlined />
+        </div>
+        <div class="mark-body-container">
+          <div v-if="!student" class="mark-body-none">
+            <div>
+              <img src="@/assets/image-none-task.png" />
+              <p>暂无数据</p>
+            </div>
           </div>
-          <div v-if="pageType === 'DATA_CHECK'" class="tw-my-2 top-block">
-            试卷类型:
-            <a-input
-              v-if="student"
-              v-model:value="student.paperType"
-              class="normal-input"
-              :maxlength="1"
+          <div
+            v-else
+            class="single-image-container"
+            :style="{ width: answerPaperScale }"
+          >
+            <img
+              draggable="false"
+              :src="curImageUrl"
+              :style="{
+                transform:
+                  (rotateDegree ? 'translate( 0,  calc(30vh))' : '') +
+                  `rotate(${rotateDegree}deg)`,
+              }"
+              @click="switchImage"
+              @contextmenu="showBigImage"
             />
           </div>
+        </div>
+        <ZoomPaper v-if="student" showRotate fixed @rotateRight="rotateRight" />
+      </div>
 
-          <div v-if="student?.answers" class="tw-mt-4">
-            <div
-              v-for="group in answersComputed"
-              :key="group.mainNumber"
-              class="tw-mt-2"
-              :class="{ hide: !group.subs?.filter((s) => s.exist).length }"
-            >
-              <h2>
-                {{ group.mainNumber }}、{{ group.mainTitle }} ({{
-                  group.subs.length
-                }})
-              </h2>
-              <div class="tw-flex tw-flex-wrap tw-gap-2">
-                <div
-                  v-for="question in group.subs"
-                  :key="question.subNumber"
-                  :class="{ hide: !question.exist }"
-                >
-                  <span class="question-number">{{ question.subNumber }} </span>
-                  <!-- <a-input
-                    class="normal-input"
-                    :class="{
-                      'long-input': question.type
-                        ? ['SINGLE', 'MULTIPLE'].includes(question.type)
-                        : group.mainTitle.match(/单选|多选|多项|不定项/),
-                      'middle-input': question.type
-                        ? ['TRUE_OR_FALSE'].includes(question.type)
-                        : group.mainTitle.match(/判断/),
-                    }"
-                    :value="question.answer"
-                    :maxLength="
-                      (
-                        question.type
-                          ? ['MULTIPLE'].includes(question.type)
-                          : group.mainTitle.match(/多选|多项|不定项/)
-                      )
-                        ? 100
-                        : 1
-                    "
-                    @keydown="onPreventAnswerKey"
-                    @input="changeAnswer($event, question)"
-                    @blur="changeAnswer($event, question, '#')"
-                  /> -->
-                  <a-input
-                    class="normal-input"
-                    :class="{
-                      'long-input': question.type
-                        ? !['SINGLE', 'TRUE_OR_FALSE'].includes(question.type)
-                        : !group.mainTitle.match(/单选|单项|判断/),
-                    }"
-                    :value="question.answer"
-                    :maxLength="
-                      (
-                        question.type
-                          ? ['MULTIPLE'].includes(question.type)
-                          : group.mainTitle.match(/多选|多项|不定项/)
-                      )
-                        ? 100
-                        : 1
-                    "
-                    @keydown="onPreventAnswerKey"
-                    @input="changeAnswer($event, question)"
-                    @blur="changeAnswer($event, question, '#')"
-                  />
-                </div>
+      <div class="mark-board-track">
+        <div class="paper-topics">
+          <div
+            v-for="group in answersComputed"
+            :key="group.mainNumber"
+            class="paper-topic"
+          >
+            <h2 class="paper-topic-title">
+              {{ group.mainNumber }}、{{ group.mainTitle }} ({{
+                group.subs.length
+              }})
+            </h2>
+            <div class="paper-topic-body">
+              <div
+                v-for="question in group.subs"
+                :key="question.subNumber"
+                class="paper-topic-question"
+              >
+                <span class="question-number">{{ question.subNumber }} </span>
+                <a-input
+                  class="normal-input"
+                  :class="{
+                    'long-input': question.type
+                      ? !['SINGLE', 'TRUE_OR_FALSE'].includes(question.type)
+                      : !group.mainTitle.match(/单选|单项|判断/),
+                  }"
+                  :value="question.answer"
+                  :maxLength="
+                    (
+                      question.type
+                        ? ['MULTIPLE'].includes(question.type)
+                        : group.mainTitle.match(/多选|多项|不定项/)
+                    )
+                      ? 100
+                      : 1
+                  "
+                  @keydown="onPreventAnswerKey"
+                  @input="changeAnswer($event, question)"
+                  @blur="changeAnswer($event, question, '#')"
+                />
               </div>
             </div>
           </div>
         </div>
 
-        <div class="tw-flex tw-justify-between tw-bg-white tw-p-4">
-          <a-tooltip v-if="!allViewed" placement="top">
-            <template #title>请先浏览完该学生的所有试卷</template>
-            <a-button
-              :disabled="!allViewed"
-              type="primary"
-              shape="round"
-              @click="saveStudentAnswer"
-            >
-              保存
-            </a-button>
-          </a-tooltip>
+        <div class="board-tips">
+          <p v-if="!allViewed">请先浏览完该学生的所有试卷</p>
+        </div>
+        <div :class="['board-footer', { 'is-simple': !isMultiStudent }]">
           <a-button
-            v-else
-            :disabled="!student?.upload"
+            class="board-submit"
+            size="medium"
             type="primary"
-            shape="round"
+            :disabled="!allViewed || !student?.upload"
             @click="saveStudentAnswer"
           >
             保存
           </a-button>
-          <div>
-            <a-button
-              shape="round"
-              :disabled="currentIndex <= 1"
-              class="tw-mr-4"
-              @click="getPreviousStudent"
-            >
+          <div v-if="isMultiStudent" class="student-switch">
+            <a-button :disabled="isFirst" @click="getPreviousStudent">
               上一份
             </a-button>
-            <a-button
-              shape="round"
-              :disabled="currentIndex === allIds.length"
-              @click="getNextStudent"
-            >
+            <a-button :disabled="isLast" @click="getNextStudent">
               下一份
             </a-button>
           </div>
         </div>
       </div>
-
-      <div style="flex: 1" class="mark-body-container tw-relative">
-        <ArrowLeftOutlined
-          v-if="student && currentImage !== 0"
-          class="tw-cursor-pointer tw-absolute"
-          style="top: 45%; left: 20px; z-index: 1; font-size: 40px"
-          title="上一张"
-          @click="switchImageArrow({ left: true })"
-        />
-        <ArrowRightOutlined
-          v-if="student && currentImage !== student.sheetUrls.length - 1"
-          class="tw-cursor-pointer tw-absolute"
-          style="top: 45%; right: 20px; z-index: 1; font-size: 40px"
-          title="上一张"
-          @click="switchImageArrow({ right: true })"
-        />
-        <div :style="{ width: answerPaperScale }">
-          <img
-            v-for="(item, index) in student?.sheetUrls"
-            :key="item"
-            class="tw-object-cover"
-            :src="item"
-            :style="{
-              display: index === currentImage ? 'block' : 'none',
-              transform:
-                (rotateDegree ? 'translate( 0,  calc(30vh))' : '') +
-                `rotate(${rotateDegree}deg)`,
-            }"
-            @click="switchImage"
-            @contextmenu="showBigImage"
-          />
-        </div>
-        <ZoomPaper v-if="student" fixed showRotate @rotateRight="rotateRight" />
-      </div>
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { httpApp } from "@/plugins/axiosApp";
+import {
+  studentObjectiveConfirmData,
+  saveStudentObjectiveConfirmData,
+} from "@/api/confirmPage";
 import { message } from "ant-design-vue";
-import { onMounted, reactive, watch } from "vue";
-import { useRoute } from "vue-router";
-import { CheckSetting, StudentInfo } from "./check";
+import { onMounted, watch } from "vue";
 import "viewerjs/dist/viewer.css";
 import Viewer from "viewerjs";
 import { store } from "@/store/store";
 import ZoomPaper from "@/components/ZoomPaper.vue";
 import { useTimers } from "@/setups/useTimers";
 import { ArrowLeftOutlined, ArrowRightOutlined } from "@ant-design/icons-vue";
+import vls from "@/utils/storage";
+import { StudentObjectiveInfo } from "@/types";
 
 const { addTimeout } = useTimers();
 
-const route = useRoute();
-// 使用 location.search 替代
-// const checkType = route.query.checkType;
-const queryId = route.query.queryId as string;
-let pageType: "DATA_CHECK" | "HAND_CHECK" = "HAND_CHECK";
-if (queryId) {
-  pageType = "DATA_CHECK";
-  sessionStorage.setItem(queryId, localStorage.getItem(queryId) || "[]");
-  localStorage.removeItem(queryId);
-}
+const studentIds = $ref(vls.get("check-students", []));
 
 onMounted(async () => {
-  await getSetting();
-  if (setting.studentIds.length === 0) {
+  if (studentIds.length === 0) {
     void message.info("没有需要处理的考生,请返回。");
     return;
   }
   await getNextStudent();
 });
 
-let setting: CheckSetting = reactive({
-  fileServer: "",
-  studentIds: [],
-  studentIdsDone: [],
-});
-
-const allIds = $computed(() => [
-  ...setting.studentIdsDone,
-  ...setting.studentIds,
-]);
+let currentStudentId = $ref("");
 
-let currentStudentId = $ref(-1);
+const currentIndex = $computed(() => studentIds.indexOf(currentStudentId));
+const isFirst = $computed(() => currentIndex === 0);
+const isLast = $computed(() => currentIndex === studentIds.length - 1);
+const isMultiStudent = $computed(() => studentIds.length > 1);
 
-const currentIndex = $computed(() => allIds.indexOf(currentStudentId) + 1);
+const curImageUrl = $computed(() =>
+  student ? student.sheetUrls[currentImage].url : ""
+);
 
-let student: StudentInfo | null = $ref(null);
+let student: StudentObjectiveInfo | null = $ref(null);
 /** 后台数据错误,停止整个页面的流程 */
 let dataError = $ref(false);
 
@@ -289,69 +217,37 @@ const answersComputed = $computed(() => {
     v.subs =
       student?.answers.filter((v2) => v2.mainNumber === v.mainNumber) ?? [];
   });
-  // console.log(mains);
   return mains;
 });
 
-async function getSetting() {
-  let res: any;
-  if (pageType === "DATA_CHECK") {
-    const query: Array<{ name: string; value: string }> = JSON.parse(
-      sessionStorage.getItem(queryId) || "[]"
-    );
-    const form = new FormData();
-    for (const v of query) {
-      form.append(v.name, v.value + "");
-    }
-    res = await httpApp.post("/api/admin/exam/check/answer/getSetting", form);
-  } else {
-    const form = new FormData();
-    form.append("checkType", route.query.checkType as string);
-    form.append("subjectCode", route.query.subjectCode as string);
-    form.append("examSite", route.query.examSite as string);
-    res = await httpApp.post(`/api/admin/exam/check/student/getSetting`, form);
-  }
-
-  setting.fileServer = res.data.fileServer;
-  setting.studentIds = res.data.studentIds || [];
-}
-
 async function getNextStudent() {
-  const wantedIndex = allIds.indexOf(currentStudentId);
-  if (allIds[wantedIndex + 1]) {
-    student = await getStudent(allIds[wantedIndex + 1]);
-  }
+  if (isLast) return;
+  student = await getStudent(studentIds[currentIndex + 1]);
 }
 
 async function getPreviousStudent() {
-  const wantedIndex = allIds.indexOf(currentStudentId);
-  student = await getStudent(allIds[wantedIndex - 1]);
+  if (isFirst) return;
+  student = await getStudent(studentIds[currentIndex - 1]);
 }
 
-async function getStudent(studentId: number) {
-  const stu: StudentInfo = await (
-    await httpApp.get(
-      `/api/admin/exam/check/answer/info?studentId=${studentId}`
-    )
-  ).data;
-  stu?.sheetUrls.forEach((v, i, a) => (a[i] = setting.fileServer + v));
-  currentStudentId = stu.id;
-  currentImage = 0;
-  browsedImageIndexes = [0];
-  if (!stu.success) {
-    void message.error(stu.message, 24 * 60 * 60);
+async function getStudent(studentId: string) {
+  const res = await studentObjectiveConfirmData(studentId).catch(() => {
     dataError = true;
-
-    throw new Error("取学生信息出错: " + stu.message);
+  });
+  if (dataError) {
+    void message.error(res.message, 24 * 60 * 60);
+    throw new Error("取学生信息出错: " + res.message);
   }
 
-  // for dev
-  // stu.answers = [
-  //   { mainNumber: 1, subNumber: "1", answer: "A" },
-  //   { mainNumber: 1, subNumber: "2", answer: "B" },
-  //   { mainNumber: 2, subNumber: "1", answer: "#" },
-  // ];
-  // stu.titles = { 1: "单选题", 2: "多选题" };
+  const stu = res.data;
+  stu.sheetUrls = [
+    { index: 1, url: "/1-1.jpg" },
+    { index: 2, url: "/1-2.jpg" },
+  ];
+  currentStudentId = stu.studentId;
+  currentImage = 0;
+  browsedImageIndexes = [0];
+
   return stu;
 }
 
@@ -373,11 +269,7 @@ function onPreventAnswerKey(e: KeyboardEvent) {
   }
 }
 
-function changeAnswer(
-  event: Event,
-  question: StudentInfo["answers"][0],
-  defaultValue?: string
-) {
+function changeAnswer(event: Event, question: string, defaultValue?: string) {
   const target = event.target as HTMLInputElement;
   student.answers = student.answers.map((v) => {
     if (
@@ -390,45 +282,30 @@ function changeAnswer(
   });
 }
 
+let loading = false;
 async function saveStudentAnswer() {
   if (!student) return;
-  const form = new FormData();
-  form.append("studentId", student.id + "");
-  const answers = student.answers.map((v) => v.answer || "#").join(",");
+
+  if (loading) return;
+  loading = true;
+
+  const data = {
+    studentId: student.studentId,
+    answers: student.answers.map((v) => v.answer || "#").join(","),
+  };
   // if (!answers.match(/^(#*,*[A-Z]*)+$/g)) {
   //   void message.error("答案只能是#和大写英文字母");
   //   return;
   // }
 
-  form.append("answers", answers);
-
-  const extra = pageType === "DATA_CHECK";
-  extra && form.append("absent", student.absent + "");
-  extra && form.append("paperType", student.paperType);
-  if (extra) {
-    if (!student.paperType.match(/^#|[A-Z]$/)) {
-      void message.error("试卷类型只能是#和大写英文字母");
-      return;
-    }
-  }
-
-  const url = extra
-    ? // 数据检查
-      "/api/admin/exam/check/answer/save"
-    : `/api/admin/exam/check/student/save`;
-  const res = await httpApp
-    .post(url, form)
-    .catch(() => message.error("保存失败-接口调用失败"));
-  if (!res.data) {
+  const res = await saveStudentObjectiveConfirmData(data).catch(() => false);
+  loading = false;
+  if (!res) {
     void message.error("保存失败,请刷新页面。");
   } else {
     void message.success("保存成功");
     await getNextStudent();
   }
-
-  if (setting.studentIds.length === 0) {
-    void message.success("所有考生已处理完毕。");
-  }
 }
 
 //#region : 显示大图,供查看和翻转
@@ -442,7 +319,6 @@ watch(
   () => currentImage,
   () => {
     browsedImageIndexes.push(currentImage);
-    console.log(browsedImageIndexes);
   }
 );
 function switchImageArrow({
@@ -532,79 +408,3 @@ function rotateRight() {
 }
 //#endregion
 </script>
-
-<style scoped>
-.question-number {
-  display: inline-block;
-  width: 22px;
-  text-align: right;
-  font-size: 12px;
-  font-weight: normal;
-  color: #474966;
-  line-height: 14px;
-  margin-right: 0.5em;
-}
-.normal-input {
-  width: 36px;
-  height: 32px;
-  background: #edf2fa;
-  border-radius: 6px;
-  border: 1px solid #c8ced8;
-  font-size: 14px;
-  font-weight: normal;
-  color: #474966;
-  text-align: center;
-}
-.normal-input.long-input {
-  width: 94px;
-  padding: 4px;
-}
-.normal-input.middle-input {
-  width: 61px !important;
-  padding: 4px;
-}
-.header-container {
-  position: relative;
-  height: 56px;
-  line-height: 16px;
-
-  background-color: var(--header-bg-color);
-  color: rgba(255, 255, 255, 0.5);
-}
-.highlight-text {
-  color: white;
-  font-size: var(--app-title-font-size);
-}
-
-.mark-body-container {
-  position: relative;
-  min-height: calc(100vh - 56px);
-  height: calc(100vh - 56px);
-  overflow: auto;
-  /* background-size: 8px 8px;
-  background-image: linear-gradient(to right, #e7e7e7 4px, transparent 4px),
-    linear-gradient(to bottom, transparent 4px, #e7e7e7 4px); */
-  background-color: var(--app-container-bg-color);
-  background-image: linear-gradient(45deg, #e0e0e0 25%, transparent 25%),
-    linear-gradient(-45deg, #e0e0e0 25%, transparent 25%),
-    linear-gradient(45deg, transparent 75%, #e0e0e0 75%),
-    linear-gradient(-45deg, transparent 75%, #e0e0e0 75%);
-  background-size: 20px 20px;
-  background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
-  transform: inherit;
-}
-
-.hide {
-  display: none;
-  visibility: hidden;
-  opacity: 0;
-}
-.confirm-paper .top-block {
-  background-color: #fff;
-  height: 50px;
-  padding: 0 20px;
-  display: flex;
-  align-items: center;
-  border-radius: 6px;
-}
-</style>

+ 1 - 1
src/features/mark/Mark.vue

@@ -196,7 +196,7 @@ function nextTask() {
 }
 
 // 5秒更新一次tasks
-// addInterval(nextTask, 5 * 1000);
+addInterval(nextTask, 5 * 1000);
 
 onMounted(async () => {
   await updateMarkTask();

+ 1 - 2
src/main.ts

@@ -20,7 +20,6 @@ import "ant-design-vue/es/message/style/css.js";
 
 import QmButton from "@/components/QmButton.vue";
 import QmDialog from "@/components/QmDialog.vue";
-import vls from "./utils/storage";
 
 // if(process.env.NODE_ENV)
 // console.log(import.meta.env.DEV);
@@ -39,7 +38,7 @@ app.component("QmDialog", QmDialog);
  */
 initMarkStore();
 
-if (import.meta.env.DEV && !vls.get("user")) {
+if (import.meta.env.DEV) {
   await import("./devLogin")
     .then((m) => {
       return m.initLogin();

+ 5 - 5
src/router/index.ts

@@ -2,7 +2,7 @@ import { createRouter, createWebHistory } from "vue-router";
 import Mark from "@/features/mark/Mark.vue";
 
 const routes = [
-  { path: "/", redirect: { name: "Mark" } },
+  { path: "/", redirect: { name: "ConfirmData" } },
   { path: "/mark", component: Mark, name: "Mark" },
   {
     // 整卷批量复核
@@ -17,7 +17,7 @@ const routes = [
       import("@/features/student/importInspect/ImportInspect.vue"),
   },
   {
-    //成绩校验
+    // 成绩校验
     path: "/admin/exam/score/verify/start",
     component: () => import("@/features/student/scoreVerify/ScoreVerify.vue"),
   },
@@ -27,12 +27,12 @@ const routes = [
     component: () => import("@/features/library/inspect/LibraryInspect.vue"),
   },
   {
-    // 仲裁
+    // 仲裁:TODO:
     path: "/admin/exam/arbitrate/start",
     component: () => import("@/features/arbitrate/Arbitrate.vue"),
   },
   {
-    // 成绩查询-试卷轨迹
+    // 成绩查询-试卷轨迹 TODO:
     path: "/admin/exam/track/student",
     name: "StudentTrack",
     component: () => import("@/features/student/studentTrack/StudentTrack.vue"),
@@ -54,7 +54,7 @@ const routes = [
     component: () => import("@/features/library/libraryTrack/LibraryTrack.vue"),
   },
   {
-    // 数据检查
+    // 数据检查-客观题复核 TODO:
     path: "/admin/exam/check/answer/start",
     name: "ConfirmData",
     component: () => import("@/features/admin/confirmPaper/ConfirmPaper.vue"),

+ 153 - 0
src/styles/page.less

@@ -846,6 +846,159 @@
   }
 }
 
+// confirm-paper
+.confirm-paper {
+  .mark-main {
+    height: calc(100vh - 56px);
+  }
+  .mark-body {
+    position: relative;
+  }
+  .paper-menu {
+    padding-right: 15px;
+    span {
+      display: inline-block;
+      vertical-align: middle;
+      width: 32px;
+      height: 32px;
+      border-radius: 4px;
+      font-size: 14px;
+      font-weight: 400;
+      border: 1px solid #3e4057;
+      padding: 4px;
+      color: #8c8d9b;
+      line-height: 22px;
+      text-align: center;
+      cursor: pointer;
+
+      &:not(:first-child) {
+        margin-left: 8px;
+      }
+
+      &:hover {
+        color: #fff;
+      }
+      &.is-active {
+        color: #000;
+        border-color: #fff;
+        background-color: #fff;
+      }
+    }
+  }
+
+  .page-action {
+    position: absolute;
+    width: 80px;
+    height: 80px;
+    top: 50%;
+    margin-top: -40px;
+    font-size: 40px;
+    line-height: 70px;
+    padding: 15px 0;
+    text-align: center;
+    border: 5px solid transparent;
+    border-radius: 50%;
+    color: #8c8c8c;
+    z-index: 9;
+    opacity: 0.3;
+    cursor: pointer;
+
+    .anticon {
+      display: block;
+      margin: 0 auto;
+    }
+
+    &:hover {
+      opacity: 1;
+      border-color: #8c8c8c;
+      background-color: #e0e0e0;
+      color: #8c8c8c;
+    }
+
+    &.page-prev {
+      left: 10px;
+    }
+    &.page-next {
+      right: 10px;
+    }
+  }
+  .zoom-container.tw-fixed {
+    left: auto !important;
+    right: 370px !important;
+  }
+  .paper-topics {
+    flex-grow: 2;
+    overflow-x: hidden;
+    overflow-y: auto;
+
+    .paper-topic {
+      &:not(:first-child) {
+        margin-top: 16px;
+      }
+
+      &-title {
+        font-weight: 600;
+        line-height: 20px;
+        margin-bottom: 5px;
+      }
+
+      &-body {
+        margin-right: -5px;
+        font-size: 0;
+      }
+
+      &-question {
+        display: inline-block;
+        vertical-align: middle;
+        font-size: 14px;
+        margin-bottom: 5px;
+        margin-right: 5px;
+      }
+    }
+
+    .question-number {
+      display: inline-block;
+      vertical-align: middle;
+      width: 20px;
+      margin-right: 8px;
+      text-align: right;
+    }
+    .normal-input {
+      display: inline-block;
+      vertical-align: middle;
+      width: 48px;
+
+      &.long-input {
+        width: 128px;
+      }
+    }
+  }
+
+  .board-tips {
+    .flex-static;
+    p {
+      margin-bottom: 5px;
+      color: #ff4d4f;
+    }
+  }
+  .board-footer {
+    &.is-simple {
+      .ant-btn {
+        width: 100%;
+      }
+    }
+  }
+
+  .student-switch {
+    .ant-btn {
+      width: 75px;
+      &:not(:first-child) {
+        margin-left: 6px;
+      }
+    }
+  }
+}
+
 // common
 .mark-tooltip {
   padding-top: 0px !important;

+ 25 - 0
src/types/index.ts

@@ -496,3 +496,28 @@ interface OExamPaperJSONQuestion {
   structType: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
 }
 //#endregion
+
+export type StudentObjectiveInfo = {
+  studentId: string;
+  studentName: string;
+  studentCode: string;
+  campusName: string;
+  courseCode: string;
+  courseName: string;
+  paperNumber: string;
+  objectiveScore: number;
+  subjectiveScore: number;
+  upload: boolean;
+  absent: boolean;
+  paperType: string;
+  sheetUrls: Array<{ index: number; url: string }>;
+  answers: Array<{
+    mainNumber: number;
+    subNumber: string;
+    answer: string;
+    exist: boolean;
+    questionType: string;
+  }>;
+  titles: { [index: number]: string };
+  success: boolean;
+};