|
@@ -0,0 +1,245 @@
|
|
|
|
+<template>
|
|
|
|
+ <div>
|
|
|
|
+ <header class="tw-flex">
|
|
|
|
+ <div>进度:{{ currentIndex }}/{{ allIds.length }}</div>
|
|
|
|
+ <div>姓名:{{ student?.name }}</div>
|
|
|
|
+ <div>准考证号:{{ student?.examNumber }}</div>
|
|
|
|
+ <div>学号:{{ student?.studentCode }}</div>
|
|
|
|
+ <div>科目:{{ student?.subjectCode }}-{{ student?.subjectName }}</div>
|
|
|
|
+ <div>客观分:{{ student?.objectiveScore }}</div>
|
|
|
|
+ <div>主观分:{{ student?.subjectiveScore }}</div>
|
|
|
|
+ <div></div>
|
|
|
|
+ </header>
|
|
|
|
+
|
|
|
|
+ <div>
|
|
|
|
+ <div>
|
|
|
|
+ <div>
|
|
|
|
+ <div>
|
|
|
|
+ 是否缺考:
|
|
|
|
+ <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>
|
|
|
|
+ <div>
|
|
|
|
+ 试卷类型:
|
|
|
|
+ <a-input
|
|
|
|
+ v-if="student"
|
|
|
|
+ v-model:value="student.paperType"
|
|
|
|
+ :maxlength="1"
|
|
|
|
+ style="width: 40px"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <div v-if="student?.answers">
|
|
|
|
+ <div v-for="group in answersComputed" :key="group.mainNumber">
|
|
|
|
+ <h2>
|
|
|
|
+ {{ group.mainNumber }}、{{ group.mainTitle }} ({{
|
|
|
|
+ group.subs.length
|
|
|
|
+ }})
|
|
|
|
+ </h2>
|
|
|
|
+ <div class="tw-flex tw-gap-4">
|
|
|
|
+ <div v-for="question in group.subs" :key="question.subNumber">
|
|
|
|
+ <span>{{ question.subNumber }}</span>
|
|
|
|
+ <a-input
|
|
|
|
+ :value="question.answer"
|
|
|
|
+ style="width: 40px"
|
|
|
|
+ :maxLength="
|
|
|
|
+ group.mainTitle.match(/多选|多项|不定项/) ? 100 : 1
|
|
|
|
+ "
|
|
|
|
+ @input="($event) => changeAnswer($event, question)"
|
|
|
|
+ @blur="($event) => changeAnswer($event, question, '#')"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <div>
|
|
|
|
+ <a-button @click="saveStudentAnswer">保存</a-button>
|
|
|
|
+ <a-button :disabled="currentIndex === 1" @click="getPreviousStudent"
|
|
|
|
+ >上一份</a-button
|
|
|
|
+ >
|
|
|
|
+ <a-button
|
|
|
|
+ :disabled="currentIndex === allIds.length"
|
|
|
|
+ @click="getNextStudent"
|
|
|
|
+ >下一份</a-button
|
|
|
|
+ >
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <div>
|
|
|
|
+ <div>
|
|
|
|
+ <img v-for="item in student?.sheetUrls" :key="item" :src="item" />
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+</template>
|
|
|
|
+
|
|
|
|
+<script lang="ts" setup>
|
|
|
|
+import { httpApp } from "@/plugins/axiosApp";
|
|
|
|
+import { message } from "ant-design-vue";
|
|
|
|
+import { onMounted, reactive } from "vue";
|
|
|
|
+import { useRoute } from "vue-router";
|
|
|
|
+import { CheckSetting, StudentInfo } from "./check";
|
|
|
|
+
|
|
|
|
+const route = useRoute();
|
|
|
|
+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";
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+onMounted(async () => {
|
|
|
|
+ await getSetting();
|
|
|
|
+ if (setting.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(-1);
|
|
|
|
+
|
|
|
|
+const currentIndex = $computed(() => allIds.indexOf(currentStudentId) + 1);
|
|
|
|
+
|
|
|
|
+let student: StudentInfo | null = $ref(null);
|
|
|
|
+
|
|
|
|
+const answersComputed = $computed(() => {
|
|
|
|
+ let mains = student?.answers.map((v) => ({
|
|
|
|
+ mainTitle: "",
|
|
|
|
+ mainNumber: v.mainNumber,
|
|
|
|
+ subs: [v],
|
|
|
|
+ }));
|
|
|
|
+ const mSet = new Set();
|
|
|
|
+ mains = mains?.filter((v) => {
|
|
|
|
+ if (!mSet.has(v.mainNumber)) {
|
|
|
|
+ mSet.add(v.mainNumber);
|
|
|
|
+ v.subs = [];
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ mains?.forEach((v) => {
|
|
|
|
+ v.mainTitle = student?.titles[v.mainNumber] ?? "";
|
|
|
|
+ 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 q: Record<string, string> = JSON.parse(
|
|
|
|
+ sessionStorage.getItem(queryId) || "{}"
|
|
|
|
+ );
|
|
|
|
+ const form = new FormData();
|
|
|
|
+ for (const [k, v] of Object.entries(q)) {
|
|
|
|
+ form.append(k, v + "");
|
|
|
|
+ }
|
|
|
|
+ res = await httpApp.post("/admin/exam/check/answer/getSetting", form);
|
|
|
|
+ } else {
|
|
|
|
+ res = await httpApp.get(
|
|
|
|
+ `/admin/exam/check/student/getSetting?checkType=${checkType}`
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ setting.fileServer = res.data.fileServer;
|
|
|
|
+ setting.studentIds = res.data.studentIds;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+async function getNextStudent() {
|
|
|
|
+ const wantedIndex = allIds.indexOf(currentStudentId);
|
|
|
|
+ student = await getStudent(allIds[wantedIndex + 1]);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+async function getPreviousStudent() {
|
|
|
|
+ const wantedIndex = allIds.indexOf(currentStudentId);
|
|
|
|
+ student = await getStudent(allIds[wantedIndex - 1]);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+async function getStudent(studentId: number) {
|
|
|
|
+ const stu: StudentInfo = await (
|
|
|
|
+ await httpApp.get(`/admin/exam/check/answer/info?studentId=${studentId}`)
|
|
|
|
+ ).data;
|
|
|
|
+ stu?.sheetUrls.forEach((v, i, a) => (a[i] = setting.fileServer + v));
|
|
|
|
+ currentStudentId = stu.id;
|
|
|
|
+
|
|
|
|
+ // 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: "多选题" };
|
|
|
|
+ return stu;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function changeAnswer(
|
|
|
|
+ event: Event,
|
|
|
|
+ question: StudentInfo["answers"][0],
|
|
|
|
+ defaultValue?: string
|
|
|
|
+) {
|
|
|
|
+ // console.log(question, event.target.value);
|
|
|
|
+ student!.answers = student!.answers.map((v) => {
|
|
|
|
+ if (
|
|
|
|
+ v.mainNumber === question.mainNumber &&
|
|
|
|
+ v.subNumber === question.subNumber
|
|
|
|
+ ) {
|
|
|
|
+ v.answer =
|
|
|
|
+ (<HTMLInputElement>event.target!).value.toUpperCase() ||
|
|
|
|
+ defaultValue ||
|
|
|
|
+ "";
|
|
|
|
+ }
|
|
|
|
+ return v;
|
|
|
|
+ });
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+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 (!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
|
|
|
|
+ ? // 数据检查
|
|
|
|
+ "/admin/exam/check/answer/save"
|
|
|
|
+ : `/admin/exam/check/student/save`;
|
|
|
|
+ await httpApp.post(url, form).catch(() => message.error("保存失败"));
|
|
|
|
+ void message.success("保存成功");
|
|
|
|
+
|
|
|
|
+ if (setting.studentIds.length === 0) {
|
|
|
|
+ void message.success("所有考生已处理完毕。");
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+</script>
|