Browse Source

更新分数的数据结构和算法,不要修改 question.score

Michael Wang 3 years ago
parent
commit
63d51bc137

+ 17 - 4
README.md

@@ -31,9 +31,22 @@ src/features/student/inspect/MarkBody.vue 整卷渲染的组件,凡是不用
 1. 轨迹模式下,
    1. 分数轨迹发生变化,MarkBoardTrack.vue 改变 store.removeScoreTracks ,CommonMarkBody.vue 监听 store.removeScoreTracks 改变 sliceImagesWithTrackList 的 trackList
    1. 特殊标记轨迹发生变化,SpecialTagModal.vue 改变 store.currentMarkResult.specialTagList ,CommonMarkBody.vue 监听 store.currentMarkResult.specialTagList 改变 sliceImagesWithTrackList 的 tagList
-1. 分数之间的关系,(理想设计,现在不是这种情况)
+1. 分数之间的关系,(理想设计)
    1. 来源: question.score 和 track.score, history.task.markerScore 评卷时不参与给分板的计算和显示
-   1. 初始收集: markResult.scoreList markResult.trackList.score markResult.markerScore
-   1. 轨迹改变:track.score 动态计算 scoreList,再显示到给分板上(单题和总分自动计算),单题根据 questionList 的 index 去找 score,要区分 0 分和 null
+   1. 初始收集: markResult.scoreList markResult.trackList.score markResult.markerScore 要区分 0 分和 null
+   1. 轨迹改变:track.score 动态计算 scoreList,再显示到给分板上(单题和总分自动计算),只有轨迹改变才去计算 scoreList 对应的题的分数,这样可以尽可能长的显示旧分数
    1. 普通改变:scoreList\[index\]的值
-   1. 普通模式评分的记录在轨迹模式下如何保持?先将 task 中的 question.score 转移到 markResult.scoreList,如果 trackList 有变化,则更新对应 scoreList,不然则保持原样。
+   1. 回评时普通模式评分的记录在轨迹模式下如何保持?先将 task 中的 question.score 转移到 markResult.scoreList,如果 trackList 有变化,则更新对应 scoreList,不然则保持原样。
+   1. markResult 放到 task 里面? 回评清理它的 markResult . DONE!
+   1. 使用 pinia ?
+   1. eslint 找出未使用的 import
+
+### watch 的层次(列出有外部影响的状态):
+
+1. Top component watch currentTask
+   1.1 body watch special: track
+   1.1 openSide
+2. header watch setting / status
+3. history watch open , change currentTask
+4. body watch trackList, computed score? emit 'trackListChange'('add', questionIndex, trackList)/('removeLast', questionIndex)/('removeAll',questionIndex)
+5. markboard watch scoreList / markerScore, emit 'updateScore'

+ 0 - 1
src/features/mark/CommonMarkBody.vue

@@ -60,7 +60,6 @@ import {
   onMounted,
   onUnmounted,
   reactive,
-  ref,
   watch,
   watchEffect,
 } from "vue";

+ 23 - 9
src/features/mark/MarkBoardKeyBoard.vue

@@ -121,7 +121,11 @@
                 class="tw-text-center tw-text-3xl"
                 :class="isCurrentQuestion(question) && 'current-score'"
               >
-                {{ isCurrentQuestion(question) ? scoreStr : question.score }}
+                {{
+                  isCurrentQuestion(question)
+                    ? scoreStr
+                    : store.currentTask.markResult.scoreList[index]
+                }}
               </div>
             </div>
             <div>
@@ -159,7 +163,7 @@ const { toggleKeyMouse } = keyMouse();
 const { chooseQuestion } = autoChooseFirstQuestion();
 
 /**
- * 当前题的输入串,初次是question.score,然后接收输入字符,回车时判断是否合法,合法则赋值给question.score
+ * 当前题的输入串,初次是markResult.scoreList中score,然后接收输入字符,回车时判断是否合法,合法则赋值给markResult.scoreList
  * 切换到下一题,则重新开始
  *  */
 let scoreStr = $ref("");
@@ -205,6 +209,13 @@ function isCurrentQuestion(question: Question) {
   );
 }
 
+let questionScore = $computed(
+  () =>
+    store.currentTask &&
+    store.currentQuestion &&
+    store.currentTask.markResult.scoreList[store.currentQuestion.__index]
+);
+
 function numberKeyListener(event: KeyboardEvent) {
   // console.log(event);
   if (!store.currentQuestion || !store.currentTask) return;
@@ -219,11 +230,11 @@ function numberKeyListener(event: KeyboardEvent) {
   }
   // 处理Enter跳下一题或submit
   if (event.key === "Enter") {
-    const allScoreMarked = store.currentTask?.questionList.every((q) =>
-      isNumber(q.score)
+    const allScoreMarked = store.currentTask?.markResult.scoreList.every((s) =>
+      isNumber(s)
     );
     // 如果所有题已赋分,并且当前题赋分和输入串和当前题分数一致,则可以在任意题提交
-    if (allScoreMarked && scoreStr === "" + store.currentQuestion.score) {
+    if (allScoreMarked && scoreStr === "" + questionScore) {
       submit();
       return;
     }
@@ -260,12 +271,13 @@ function numberKeyListener(event: KeyboardEvent) {
       message.error({ content: "输入的分数不在有效间隔内", duration: 5 });
       return;
     }
-    store.currentQuestion.score = score;
+    const { __index } = store.currentQuestion;
+    store.currentTask.markResult.scoreList[__index] = score;
     //
     // scoreStr = "";
     // console.log("give score", score);
     const idx = indexOfCurrentQuestion() as number;
-    if (idx + 1 < store.currentTask?.questionList.length) {
+    if (idx + 1 < store.currentTask.questionList.length) {
       chooseQuestion(store.currentTask.questionList[idx + 1]);
     }
     return;
@@ -343,9 +355,11 @@ watch(
 );
 
 function submit() {
+  if (!store.currentTask) return;
   const errors: any = [];
-  store.currentTask?.questionList.forEach((question, index) => {
-    if (!isNumber(question.score)) {
+  store.currentTask.markResult.scoreList.forEach((s, index) => {
+    if (!isNumber(s) && store.currentTask) {
+      const question = store.currentTask.questionList[index];
       errors.push({
         question,
         index,

+ 11 - 4
src/features/mark/MarkBoardMouse.vue

@@ -144,10 +144,15 @@ const { toggleKeyMouse } = keyMouse();
 
 function chooseScore(question: Question, score: number) {
   store.currentQuestion = question;
-  store.currentQuestion.score = score;
+  const { __index } = store.currentQuestion;
+  store.currentTask &&
+    (store.currentTask.markResult.scoreList[__index] = score);
 }
 function isCurrentScore(question: Question, score: number) {
-  return question.score === score;
+  const { __index } = question;
+  const questionScore =
+    store.currentTask && store.currentTask.markResult.scoreList[__index];
+  return questionScore === score;
 }
 
 function questionScoreSteps(question: Question) {
@@ -173,9 +178,11 @@ function questionScoreSteps(question: Question) {
 }
 
 function submit() {
+  if (!store.currentTask) return;
   const errors: any = [];
-  store.currentTask?.questionList.forEach((question, index) => {
-    if (!isNumber(question.score)) {
+  store.currentTask.markResult.scoreList.forEach((s, index) => {
+    if (!isNumber(s) && store.currentTask) {
+      const question = store.currentTask.questionList[index];
       errors.push({
         question,
         index,

+ 25 - 11
src/features/mark/MarkBoardTrack.vue

@@ -109,7 +109,7 @@
             </div>
             <div class="tw-font-medium tw-text-2xl score">
               <!-- 特殊的空格符号 -->
-              {{ question.score ?? " " }}
+              {{ store.currentTask.markResult.scoreList[index] ?? " " }}
             </div>
             <div
               v-if="isCurrentQuestion(question)"
@@ -191,12 +191,19 @@ const { dragSpliter, topPercent } = dragSplitPane();
 
 const { chooseQuestion } = autoChooseFirstQuestion();
 
-const questionScoreSteps = computed(() => {
+let questionScore = $computed(
+  () =>
+    store.currentTask &&
+    store.currentQuestion &&
+    store.currentTask.markResult.scoreList[store.currentQuestion.__index]
+);
+
+let questionScoreSteps = $computed(() => {
   const question = store.currentQuestion;
   if (!question) return [];
 
   const remainScore =
-    Math.round(question.maxScore * 100 - (question.score || 0) * 100) / 100;
+    Math.round(question.maxScore * 100 - (questionScore || 0) * 100) / 100;
   const steps = [];
   for (
     let i = 0;
@@ -280,7 +287,7 @@ function numberKeyListener(event: KeyboardEvent) {
     return;
   }
   const score = parseFloat(keys.join(""));
-  if (isNumber(score) && questionScoreSteps.value.includes(score)) {
+  if (isNumber(score) && questionScoreSteps.includes(score)) {
     chooseScore(score);
   }
 }
@@ -329,10 +336,6 @@ function clearLatestMarkOfCurrentQuetion() {
   if (ts.length === 0) {
     return;
   }
-  if (ts.length === 1) {
-    // 即将清除最后一条记录,置为0,因为watch trackList 算分要考虑不同模式的回评,无法自动
-    store.currentQuestion.score = null;
-  }
   const maxNumber = Math.max(...ts.map((q) => q.number));
   const idx = markResult.trackList.findIndex(
     (q) =>
@@ -342,6 +345,14 @@ function clearLatestMarkOfCurrentQuetion() {
   );
   if (idx >= 0) {
     store.removeScoreTracks = markResult.trackList.splice(idx, 1);
+    const { __index, mainNumber, subNumber } = store.currentQuestion;
+    const trackList = markResult.trackList
+      .filter((t) => t.mainNumber === mainNumber && t.subNumber === subNumber)
+      .map((t) => t.score);
+    markResult.scoreList[__index] =
+      trackList.length === 0
+        ? null
+        : trackList.reduce((acc, v) => (acc += Math.round(v * 100)), 0) / 100;
   }
 }
 
@@ -361,13 +372,16 @@ function clearAllMarksOfCurrentQuetion() {
         q.subNumber === store.currentQuestion?.subNumber
       )
   );
-  store.currentQuestion.score = null;
+  const { __index } = store.currentQuestion;
+  markResult.scoreList[__index] = null;
 }
 
 function submit() {
+  if (!store.currentTask) return;
   const errors: any = [];
-  store.currentTask?.questionList.forEach((question, index) => {
-    if (!isNumber(question.score)) {
+  store.currentTask.markResult.scoreList.forEach((s, index) => {
+    if (!isNumber(s) && store.currentTask) {
+      const question = store.currentTask.questionList[index];
       errors.push({
         question,
         index,

+ 12 - 52
src/features/mark/MarkBody.vue

@@ -15,11 +15,10 @@
 </template>
 
 <script setup lang="ts">
-import { onMounted, onUnmounted, watch, watchEffect } from "vue";
+import { onMounted, onUnmounted, watch } from "vue";
 import { store } from "./store";
 import { ModeEnum } from "@/types";
 import type { SliceImage, SpecialTag, Track } from "@/types";
-import { isNumber } from "lodash";
 // @ts-ignore
 import CustomCursor from "custom-cursor.js";
 import CommonMarkBody from "./CommonMarkBody.vue";
@@ -67,10 +66,14 @@ const makeScoreTrack = (
     return;
   }
   // 是否保留当前的轨迹分
+  const questionScore =
+    store.currentTask &&
+    store.currentQuestion &&
+    store.currentTask.markResult.scoreList[store.currentQuestion.__index];
   const ifKeepScore =
     Math.round(
       store.currentQuestion.maxScore * 100 -
-        (store.currentQuestion.score || 0) * 100 -
+        (questionScore || 0) * 100 -
         store.currentScore * 2 * 100
     ) / 100;
   if (ifKeepScore < 0 && store.currentScore > 0) {
@@ -90,6 +93,12 @@ const makeScoreTrack = (
     //   Math.max(...markResult.trackList.map((t) => t.number))
     // );
     markResult.trackList = [...markResult.trackList, track];
+    const { __index, mainNumber, subNumber } = store.currentQuestion;
+    markResult.scoreList[__index] =
+      markResult.trackList
+        .filter((t) => t.mainNumber === mainNumber && t.subNumber === subNumber)
+        .map((t) => t.score)
+        .reduce((acc, v) => (acc += Math.round(v * 100)), 0) / 100;
   }
   item.trackList.push(track);
 };
@@ -149,55 +158,6 @@ const makeTrack = (
   }
 };
 
-// 轨迹模式下,添加轨迹,更新分数
-watch(
-  () => store.currentTask?.markResult.trackList,
-  () => {
-    if (store.setting.mode !== ModeEnum.TRACK) return;
-    const markResult = store.currentTask?.markResult;
-    if (markResult) {
-      const cq = store.currentQuestion;
-      // 当无轨迹时,不更新;无轨迹时,将分数置null
-      if (cq) {
-        if (markResult.trackList.length > 0) {
-          const cqTrackList = markResult.trackList.filter(
-            (v) =>
-              v.mainNumber === cq.mainNumber && v.subNumber === cq.subNumber
-          );
-          if (cqTrackList.length > 0) {
-            cq.score =
-              cqTrackList
-                .map((v) => v.score)
-                .reduce((acc, v) => (acc += Math.round(v * 100)), 0) / 100;
-          } else {
-            cq.score = null;
-          }
-        } else {
-          // TODO: 不需要?如果此行代码生效,则无法清除最后一道题的分数 此时的场景是回评普通模式评的分,需要看见
-          // cq.score = cq.__origScore;
-        }
-      }
-      // renderPaperAndMark();
-    }
-  },
-  { deep: true }
-);
-
-// question.score更新后,自动关联markResult.scoreList和markResult.markerScore
-watchEffect(() => {
-  const markResult = store.currentTask?.markResult;
-
-  if (markResult && store.currentTask) {
-    const scoreList = store.currentTask.questionList.map((q) => q.score);
-    markResult.scoreList = [...(scoreList as number[])];
-    markResult.markerScore =
-      (markResult.scoreList.filter((s) => isNumber(s)) as number[]).reduce(
-        (acc, v) => (acc += Math.round(v * 100)),
-        0
-      ) / 100;
-  }
-});
-
 watch(
   () => store.setting.mode,
   () => {

+ 0 - 17
src/features/mark/MarkHistory.vue

@@ -209,10 +209,6 @@ EventBus.on("should-reload-history", async () => {
     if (res.data) {
       let data = cloneDeep(res.data) as Array<Task>;
       data = data.map((t) => {
-        t.questionList.map((q) => {
-          q.__origScore = q.score;
-          return q;
-        });
         t.sliceUrls = t.sliceUrls?.map((s) => store.setting.fileServer + s);
         t.sheetUrls = t.sheetUrls?.map((s) => store.setting.fileServer + s);
         t.jsonUrl = store.setting.fileServer + t.jsonUrl;
@@ -259,10 +255,6 @@ async function updateHistoryTask({
   if (res.data) {
     let data = cloneDeep(res.data) as Array<Task>;
     data = data.map((t) => {
-      t.questionList.map((q) => {
-        q.__origScore = q.score;
-        return q;
-      });
       t.sliceUrls = t.sliceUrls?.map((s) => store.setting.fileServer + s);
       t.sheetUrls = t.sheetUrls?.map((s) => store.setting.fileServer + s);
       t.jsonUrl = store.setting.fileServer + t.jsonUrl;
@@ -275,15 +267,6 @@ async function updateHistoryTask({
 }
 
 function replaceCurrentTask(task: Task | undefined) {
-  if (task) {
-    task.questionList = task.questionList.map((q) => {
-      if (typeof q.__origScore !== "undefined") {
-        // 如果是回评的任务,则将旧分数还原
-        q.score = q.__origScore;
-      }
-      return q;
-    });
-  }
   store.currentTask = task;
 }
 

+ 24 - 2
src/features/mark/store.ts

@@ -89,9 +89,12 @@ watch(
       task.markResult = undefined;
     }
     if (!task.markResult) {
+      // 初始化 __index
+      task.questionList.forEach((q, i, ar) => (ar[i].__index = i));
+
       task.__markStartTime = Date.now();
       const statusValue = store.setting.statusValue;
-      const { libraryId, studentId, previous } = task;
+      const { libraryId, studentId } = task;
       task.markResult = {
         statusValue: statusValue,
         libraryId: libraryId,
@@ -104,7 +107,7 @@ watch(
         ),
         specialTagList: [...(task.specialTagList ?? [])],
         scoreList: task.questionList.map((q) => q.score),
-        markerScore: 0, // 后期通过 scoreList 自动更新
+        markerScore: null, // 后期通过 scoreList 自动更新
 
         problem: false,
         problemTypeId: 0,
@@ -113,3 +116,22 @@ watch(
     }
   }
 );
+
+// 唯一根据 scoreList 自动更新 markerScore
+watch(
+  () => store.currentTask?.markResult.scoreList,
+  () => {
+    if (!store.currentTask) return;
+    const scoreList = store.currentTask.markResult.scoreList.filter(
+      (v) => v !== null
+    ) as number[];
+    const result =
+      scoreList.length === 0
+        ? null
+        : scoreList.reduce((acc, v) => (acc += Math.round(v * 100)), 0) / 100;
+    store.currentTask.markResult.markerScore = result;
+  },
+  { deep: true }
+);
+
+// scoreList 被 trackList 和用户手动更新

+ 3 - 4
src/types/index.ts

@@ -93,7 +93,6 @@ export interface Task {
   sliceConfig: Array<PictureSlice>; //最高显示优先级
   jsonUrl: string; // sliceUrls为空,则是多媒体阅卷,显示JSON
   questionList: Array<Question>;
-  currentQuestion: number; // 前端添加和使用的属性; 通过index去questionList去定位question,然后找到大题号、小题号
   specialTagList: Array<SpecialTag>;
 
   sheetUrls: Array<string>; //原图url
@@ -123,7 +122,7 @@ export interface Question {
   title: string; // 题目名称
   trackList: Array<Track>; // 轨迹列表
   score: number | null; //得分;null的值时是为打回时可以被评卷修改的;null也是从未评分过的情况,要通过rejected来判断
-  __origScore: number | null; // 在回评时restore score
+  __index: number; // question 在 task 里面的 index ,用来对应 scoreList 的 score
 }
 
 export interface Track {
@@ -148,7 +147,7 @@ export interface SpecialTag {
 }
 
 export interface PictureSlice {
-  i: number;
+  i: number; // 从1开始
   w: number;
   h: number;
   x: number;
@@ -173,7 +172,7 @@ export interface MarkResult {
   spent: number; // 毫秒单位
 
   // 轨迹 or 键盘
-  markerScore: number;
+  markerScore: number | null;
   trackList: Array<Track>;
   scoreList: Array<number | null>;
   specialTagList: Array<SpecialTag>; // 轨迹和键盘都需要