瀏覽代碼

成绩检查与阅卷管理

zhangjie 1 年之前
父節點
當前提交
b836a3193b

+ 70 - 2
src/features/check/ObjectiveAnswer.vue

@@ -70,10 +70,11 @@
           <div
             v-else
             class="single-image-container"
-            :style="{ width: answerPaperScale }"
+            :style="{ width: answerPaperScale, fontSize: answerPaperFontSize }"
           >
             <img
               draggable="false"
+              id="mark-body-paper"
               :src="curImageUrl"
               :style="{
                 transform:
@@ -82,7 +83,15 @@
               }"
               @click="switchImage"
               @contextmenu="showBigImage"
+              @load="paperLoad"
             />
+            <div
+              v-for="(tag, tindex) in answerTags"
+              :key="tindex"
+              :style="tag.style"
+            >
+              {{ tag.answer }}
+            </div>
           </div>
         </div>
         <ZoomPaper v-if="student" showRotate fixed @rotateRight="rotateRight" />
@@ -182,8 +191,9 @@ 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";
+import { StudentObjectiveInfo, PaperRecogData } from "@/types";
 import { doLogout } from "@/api/markPage";
+import { maxNum } from "@/utils/utils";
 
 const { addTimeout } = useTimers();
 
@@ -215,6 +225,15 @@ const curImageUrl = $computed(() =>
 let student: StudentObjectiveInfo | null = $ref(null);
 /** 后台数据错误,停止整个页面的流程 */
 let dataError = $ref(false);
+let answerMap: Record<string, string> = {};
+
+interface AnswerTagType {
+  mainNumber: number;
+  subNumber: number;
+  answer: string;
+  style: Record<string, string>;
+}
+let answerTags = $ref([]);
 
 const answersComputed = $computed(() => {
   let mains = student?.answers.map((v) => ({
@@ -276,6 +295,11 @@ async function getStudent(studentId: string) {
   currentImage = 0;
   browsedImageIndexes = [0];
 
+  answerMap = {};
+  stu.answers.forEach((item) => {
+    answerMap[`${item.mainNumber}_${item.subNumber}`] = item.answer;
+  });
+
   return stu;
 }
 
@@ -346,6 +370,46 @@ async function saveStudentAnswer() {
   }
 }
 
+function paperLoad() {
+  if (!student.sheetUrls[currentImage]?.recogData) {
+    answerTags = [];
+    return;
+  }
+  const imgDom = document.getElementById("mark-body-paper");
+  const { naturalWidth, naturalHeight } = imgDom;
+  const recogData: PaperRecogData = JSON.parse(
+    window.atob(student.sheetUrls[currentImage].recogData)
+  );
+
+  answerTags = [];
+  recogData.question.forEach((question) => {
+    question.fill_result.forEach((result) => {
+      const tagSize = result.fill_size[1];
+      const fillPositions = result.fill_position.map((pos) => {
+        return pos.split(",").map((n) => n * 1);
+      });
+      const tagLeft =
+        maxNum(fillPositions.map((pos) => pos[0])) + result.fill_size[0] + 2;
+
+      answerTags.push({
+        mainNumber: result.main_number,
+        subNumber: result.sub_number,
+        answer: answerMap[`${result.main_number}_${result.sub_number}`],
+        style: {
+          height: ((100 * tagSize) / naturalHeight).toFixed(2) + "%",
+          fontSize: ((100 * 20) / tagSize).toFixed(2) + "%",
+          left: ((100 * tagLeft) / naturalWidth).toFixed(2) + "%",
+          top: ((100 * fillPositions[0][1]) / naturalHeight).toFixed(2) + "%",
+          position: "absolute",
+          color: "#f53f3f",
+          lineHeight: 1,
+          zIndex: 9,
+        },
+      });
+    });
+  });
+}
+
 //#region : 显示大图,供查看和翻转
 let currentImage = $ref(0);
 let browsedImageIndexes = $ref([0]);
@@ -437,6 +501,10 @@ const answerPaperScale = $computed(() => {
   const scale = store.setting.uiSetting["answer.paper.scale"];
   return scale * 100 + "%";
 });
+const answerPaperFontSize = $computed(() => {
+  const scale = store.setting.uiSetting["answer.paper.scale"];
+  return scale * 14 + "px";
+});
 //#endregion : 放大缩小和之后的滚动
 
 //#region rotateRight

文件差異過大導致無法顯示
+ 1 - 0
src/features/check/objAnswer.js


+ 1 - 4
src/features/mark/MarkBody.vue

@@ -180,10 +180,7 @@ const makeTrack = (
   maxSliceWidth: number,
   theFinalHeight: number
 ) => {
-  if (
-    store.setting.uiSetting["specialTag.modal"] &&
-    store.currentSpecialTagType
-  ) {
+  if (store.currentSpecialTagType) {
     makeSpecialTagTrack(event, item, maxSliceWidth, theFinalHeight);
     if (store.currentSpecialTagType === "TEXT") {
       store.currentSpecialTag = undefined;

+ 97 - 14
src/features/mark/MarkTool.vue

@@ -62,16 +62,77 @@
         <img src="@/assets/icons/icon-shortcut.svg" />
         <p>快捷键</p>
       </div>
-      <div
-        v-if="checkValid('specialTag')"
-        :class="[
-          'mark-tool-item',
-          { 'is-active': store.setting.uiSetting['specialTag.modal'] },
-        ]"
-        @click="toSpecialTag"
-      >
-        <img src="@/assets/icons/icon-special-tag.svg" />
-        <p>特殊标记</p>
+      <div v-if="checkValid('specialTag')" class="tool-special">
+        <div
+          :class="[
+            'tag-item',
+            { 'is-current': store.currentSpecialTag === '√' },
+          ]"
+          @click="chooseSpecialTag('√', 'RIGHT')"
+        >
+          <img src="@/assets/icons/icon-right.svg" />
+        </div>
+        <div
+          :class="[
+            'tag-item',
+            {
+              'is-current': store.currentSpecialTag === '乄',
+            },
+          ]"
+          @click="chooseSpecialTag('乄', 'HALF_RIGTH')"
+        >
+          乄
+        </div>
+        <div
+          :class="[
+            'tag-item',
+            { 'is-current': store.currentSpecialTag === 'X' },
+          ]"
+          @click="chooseSpecialTag('X', 'WRONG')"
+        >
+          <img src="@/assets/icons/icon-wrong.svg" />
+        </div>
+        <div
+          :class="[
+            'tag-item',
+            { 'is-current': store.currentSpecialTag === '○' },
+          ]"
+          title="标记圆圈"
+          @click="chooseSpecialTag('○', 'CIRCLE')"
+        >
+          <img src="@/assets/icons/icon-circle.svg" />
+        </div>
+        <div
+          :class="[
+            'tag-item',
+            { 'is-current': store.currentSpecialTag === '-' },
+          ]"
+          title="标记圆圈"
+          @click="chooseSpecialTag('-', 'LINE')"
+        >
+          -
+        </div>
+        <div
+          :class="[
+            'tag-item',
+            { 'is-current': store.currentSpecialTagType === 'TEXT' },
+          ]"
+          title="标记文本"
+          @click="chooseSpecialTag('', 'TEXT')"
+        >
+          <img src="@/assets/icons/icon-text.svg" />
+        </div>
+        <div class="tag-item tag-splite"></div>
+        <div class="tag-item" title="回退" @click="clearLatestTagOfCurrentTask">
+          <img src="@/assets/icons/icon-goback.svg" />
+        </div>
+        <div
+          class="tag-item tag-danger"
+          title="清空"
+          @click="clearAllTagsOfCurrentTask"
+        >
+          <img src="@/assets/icons/icon-clear.svg" />
+        </div>
       </div>
     </div>
     <div>
@@ -160,10 +221,10 @@ const toShortcut = () => {
   store.setting.uiSetting["shortCut.modal"] =
     !store.setting.uiSetting["shortCut.modal"];
 };
-const toSpecialTag = () => {
-  store.setting.uiSetting["specialTag.modal"] =
-    !store.setting.uiSetting["specialTag.modal"];
-};
+// const toSpecialTag = () => {
+//   store.setting.uiSetting["specialTag.modal"] =
+//     !store.setting.uiSetting["specialTag.modal"];
+// };
 
 const toAllZero = () => {
   Modal.confirm({
@@ -196,6 +257,28 @@ const lessThanOneScale = computed(() => {
 const equalOneScale = computed(() => {
   return store.setting.uiSetting["answer.paper.scale"] === 1;
 });
+
+function clearLatestTagOfCurrentTask() {
+  if (!store.currentTask?.markResult) return;
+  store.currentTask.markResult.specialTagList.splice(-1);
+}
+
+function clearAllTagsOfCurrentTask() {
+  if (!store.currentTask?.markResult) return;
+  store.currentTask.markResult.specialTagList = [];
+}
+
+function chooseSpecialTag(tagName: string, tagType: string) {
+  if (store.currentSpecialTag === tagName) {
+    store.currentSpecialTag = undefined;
+    store.currentSpecialTagType = undefined;
+  } else {
+    store.currentSpecialTag = tagName;
+    store.currentSpecialTagType = tagType;
+    store.currentScore = undefined;
+  }
+}
+
 function keyListener(event: KeyboardEvent) {
   if (event.key === "+") {
     toMagnify();

+ 55 - 1
src/styles/page.less

@@ -223,7 +223,7 @@
       > div {
         color: #262626;
         font-weight: 600;
-        line-height: 20px;
+        line-height: 24px;
         font-size: 0;
 
         > span {
@@ -249,6 +249,60 @@
       }
     }
   }
+
+  .tool-special {
+    display: inline-block;
+    vertical-align: top;
+    padding: 8px 10px;
+    border: 1px solid #e5e5e5;
+    border-radius: 4px;
+    margin-left: 20px;
+
+    .tag-item {
+      display: inline-block;
+      vertical-align: top;
+      width: 24px;
+      height: 24px;
+      padding: 2px;
+      margin: 0 3px;
+      border-radius: 2px;
+      line-height: 18px;
+      text-align: center;
+      font-weight: 600;
+      cursor: pointer;
+
+      &.tag-splite {
+        width: 0;
+        padding: 0;
+        margin: 0 6px;
+        border-left: 1px solid #e5e5e5;
+      }
+
+      &:hover {
+        background-color: #e7e7e7;
+      }
+
+      > img {
+        display: block;
+        width: 100%;
+        height: 100%;
+      }
+
+      &.is-current {
+        color: #fff;
+        background-color: #165dff;
+
+        img {
+          filter: brightness(1000%);
+        }
+      }
+      &.tag-danger {
+        &:hover {
+          background-color: #fff2f0;
+        }
+      }
+    }
+  }
 }
 // mark-history
 .mark-history {

+ 17 - 1
src/types/index.ts

@@ -519,7 +519,7 @@ export type StudentObjectiveInfo = {
   upload: boolean;
   absent: boolean;
   paperType: string;
-  sheetUrls: Array<{ index: number; url: string }>;
+  sheetUrls: Array<{ index: number; url: string; recogData: string }>;
   answers: Array<{
     mainNumber: number;
     subNumber: string;
@@ -554,3 +554,19 @@ export type StudentSubjectiveInfo = {
   titles: { [index: number]: string };
   success: boolean;
 };
+
+export interface PaperRecogData {
+  page_index: number;
+  question: Array<{
+    index: number;
+    fill_result: Array<{
+      main_number: number;
+      sub_number: number;
+      single: number;
+      fill_option: number[];
+      suspect_flag: number;
+      fill_position: string[];
+      fill_size: number[];
+    }>;
+  }>;
+}

+ 7 - 1
src/utils/utils.ts

@@ -443,9 +443,15 @@ export function parseHrefParam(
  * 计算总数
  * @param {Array} dataList 需要统计的数组
  */
-export function calcSum(dataList) {
+export function calcSum(dataList: number[]): number {
   if (!dataList.length) return 0;
   return dataList.reduce(function (total, item) {
     return total + item;
   }, 0);
 }
+
+/** 获取数组最大数 */
+export function maxNum(dataList: number[]): number {
+  if (!dataList.length) return 0;
+  return Math.max.apply(null, dataList);
+}

部分文件因文件數量過多而無法顯示