소스 검색

feat: 评卷UI修改

zhangjie 21 시간 전
부모
커밋
b03387b4d5

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "stmms-web",
-  "version": "1.6.0",
+  "version": "2.0.0",
   "private": "true",
   "scripts": {
     "start": "vite --host 0.0.0.0",

+ 1 - 1
src/App.vue

@@ -1,5 +1,5 @@
 <template>
-  <a-config-provider :locale="zhCN">
+  <a-config-provider :locale="zhCN" :autoInsertSpaceInButton="false">
     <router-view> </router-view>
     <a-spin
       v-if="spinning"

BIN
src/assets/bg-question-active.png


+ 89 - 49
src/components/ZoomPaper.vue

@@ -1,37 +1,24 @@
 <template>
-  <div
-    class="tw-flex tw-flex-col tw-gap-2 zoom-container tw-place-content-center"
-    :class="{ 'tw-fixed': props.fixed }"
-  >
-    <RotateRightOutlined
-      v-if="props.showRotate"
-      class="icon-font-size-20 tw-cursor-pointer"
-      style="color: white"
-      title="向右旋转"
-      @click="$emit('rotateRight')"
-    />
-    <ZoomInOutlined
-      class="icon-font-size-20 tw-cursor-pointer"
-      :style="{
-        color: greaterThanOneScale ? 'red' : 'white',
-      }"
-      title="放大"
-      @click="upScale"
-    />
-    <ZoomOutOutlined
-      class="icon-font-size-20 tw-cursor-pointer"
-      :style="{
-        color: lessThanOneScale ? 'red' : 'white',
-      }"
-      title="缩小"
-      @click="downScale"
-    />
-    <FullscreenOutlined
-      class="icon-font-size-20 tw-cursor-pointer"
-      style="color: white"
-      title="适应"
-      @click="normalScale"
-    />
+  <div class="zoom-paper" :style="bodyStyle">
+    <div class="zoom-paper-body" :class="props.fixed ? 'fixed' : ''">
+      <RotateRightOutlined
+        v-if="props.showRotate"
+        title="向右旋转"
+        @click="$emit('rotateRight')"
+      />
+      <ZoomInOutlined
+        :class="{ active: greaterThanOneScale }"
+        title="放大"
+        @click="upScale"
+      />
+      <ZoomOutOutlined
+        :class="{ active: lessThanOneScale }"
+        title="缩小"
+        @click="downScale"
+      />
+      <FullscreenOutlined title="适应" @click="normalScale" />
+    </div>
+    <div class="zoom-paper-tag"></div>
   </div>
 </template>
 
@@ -75,6 +62,13 @@ function keyListener(event: KeyboardEvent) {
     downScale();
   }
 }
+
+const bodyStyle = computed(() => {
+  return {
+    width: props.showRotate ? "155px" : "120px",
+  };
+});
+
 onMounted(() => {
   document.addEventListener("keydown", keyListener);
 });
@@ -83,24 +77,70 @@ onUnmounted(() => {
 });
 </script>
 
-<style scoped>
-.zoom-container {
+<style scoped lang="less">
+.zoom-paper {
   z-index: 1001;
   position: sticky;
-  background-color: rgba(0, 0, 0, 0.9);
+  left: calc(100% - 120px);
   bottom: 10px;
-  left: calc(100% - 30px);
-  width: 40px;
-  /* height: 100px; */
-  padding: 10px 0;
-  border-radius: 10px;
-}
-.zoom-container.tw-fixed {
-  position: fixed;
-  right: 30px;
-  left: unset;
-}
-.icon-font-size-20 {
-  font-size: 24px;
+  right: 8px;
+  width: 120px;
+  height: 40px;
+  background: var(--color-text-dark);
+  border-radius: 8px;
+
+  display: flex;
+  justify-content: center;
+  align-items: center;
+
+  &.fixed {
+    position: fixed;
+    right: 30px;
+    left: unset;
+  }
+
+  &-tag {
+    flex-grow: 0;
+    width: 15px;
+    height: 100%;
+    background: #bfbfbf;
+    border-radius: 0px 6px 6px 0px;
+    position: relative;
+    z-index: 9;
+
+    &::after {
+      content: "";
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      width: 6px;
+      height: 18px;
+      background: #8c8c8c;
+      border-radius: 3px;
+      box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.3) inset;
+    }
+  }
+
+  &-body {
+    padding: 12px;
+    flex-grow: 2;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+  }
+
+  .anticon {
+    font-size: 16px;
+    color: #fff;
+
+    &:hover {
+      color: var(--color-text-primary);
+    }
+
+    &.active {
+      color: var(--color-text-danger);
+    }
+  }
 }
 </style>

+ 15 - 17
src/features/mark/CommonMarkBody.vue

@@ -43,9 +43,8 @@
       <MultiMediaMarkBody />
     </div>
     <div v-else>impossible</div>
-    <div v-if="markStatus" class="status-container">
+    <div v-if="markStatus" class="status-container warning">
       {{ markStatus }}
-      <div class="double-triangle"></div>
     </div>
 
     <ZoomPaper
@@ -791,27 +790,26 @@ function scrollToFirstScore() {
 .status-container {
   position: sticky;
   /* top: 56px; */
-  bottom: calc(100% - 50px);
+  bottom: calc(100% - 68px);
   /* right: 340px; */
-  left: calc(100% - 20px);
-  color: white;
+  left: calc(100% - 30px);
+  color: #f53f3f;
+  width: 36px;
+  min-height: 60px;
+  padding: 8px;
+  background: rgba(245, 63, 63, 0.2);
+  border: 1px solid #f53f3f;
+  border-radius: 0px 0px 6px 6px;
   pointer-events: none;
   font-size: var(--app-title-font-size);
-  background-color: #ef7c78;
-
-  width: 30px;
-  height: 50px;
   text-align: center;
+  font-weight: 600;
   z-index: 1000;
 }
-.double-triangle {
-  background-color: #ef7c78;
-  width: 30px;
-  height: 6px;
-  clip-path: polygon(0 0, 0 6px, 50% 0, 100% 0, 100% 6px, 50% 0);
-
-  position: absolute;
-  bottom: -5px;
+.status-container.warning {
+  background: rgba(251, 232, 66, 0.5);
+  border-color: #cfaf0f;
+  color: #cd8800;
 }
 
 @keyframes rotate {

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

@@ -1,7 +1,7 @@
 <template>
-  <div class="my-container">
+  <div class="my-container mark">
     <mark-header :showTotalScore="store.isMultiMedia" />
-    <div class="tw-flex tw-gap-1">
+    <div class="tw-flex">
       <mark-history showSearch showOrder hasJump :getHistory="getHistoryTask" />
       <mark-body @error="removeBrokenTask" />
       <mark-board-track

+ 36 - 178
src/features/mark/MarkBoardTrack.vue

@@ -1,12 +1,11 @@
 <template>
   <div
     v-if="store.currentTask"
-    class="mark-board-track-container tw-flex tw-flex-col"
+    class="mark-board-track tw-flex tw-flex-col"
     :class="[
       {
         hide: store.isScoreBoardCollapsed && !props.modal,
-        'mark-board-track-container-in-dialog':
-          store.isScoreBoardCollapsed && !props.modal,
+        'in-dialog': store.isScoreBoardCollapsed && !props.modal,
         show: !store.isScoreBoardCollapsed,
       },
     ]"
@@ -14,76 +13,37 @@
       height: props.modal ? '100%' : 'auto',
     }"
   >
-    <div
-      class="tw-flex tw-rounded tw-justify-between tw-p-2 tw-pl-5 top-container tw-mb-4"
-    >
-      <div class="tw-flex tw-flex-col">
-        <div class="tw-flex tw-items-center tw-gap-2">
-          <img
-            src="./images/totalscore.png"
-            style="width: 13px; height: 16px"
-          />
-          总分
-        </div>
-        <div class="total-score tw-ml-5 tw-font-bold" style="height: 50px">
+    <!-- header -->
+    <div class="board-header">
+      <div class="header-score">
+        <h5>试卷总分</h5>
+        <div class="score-detail">
+          <img src="./images/icon-score.png" />
           <transition-group name="score-number-animation" tag="span">
             <span
               :key="store.currentTask.markResult?.markerScore || 0"
-              class="tw-inline-block"
+              class="score-number"
               >{{ store.currentTask.markResult?.markerScore }}</span
             >
           </transition-group>
         </div>
       </div>
 
-      <div class="tw-flex tw-place-content-center tw-items-center tw-gap-2">
-        <!-- <div class="tw-flex tw-flex-col tw-gap-1">
-          <a-popconfirm
-            v-if="store.setting.enableAllZero && !store.setting.forceSpecialTag"
-            title="确定给全零分?"
-            :overlayStyle="{ width: '200px' }"
-            @confirm="$emit('allZeroSubmit')"
-          >
-            <a-button
-              type="primary"
-              size="middle"
-              class="all-zero-unselective-button"
-            >
-              <span>全零分</span>
-            </a-button>
-          </a-popconfirm>
-        </div> -->
-
+      <div class="header-action">
         <qm-button
           v-if="canAllSelective && !disabledArbitrateType"
           type="primary"
-          shape="round"
-          size="middle"
-          style="height: 76px; border-radius: 10px; padding: 12px"
           @click="setAllUnselective"
         >
           未选做
         </qm-button>
-        <qm-button
-          type="primary"
-          shape="round"
-          size="middle"
-          style="height: 76px; border-radius: 10px; padding: 12px"
-          @click="submit"
-        >
-          提交
-        </qm-button>
+        <qm-button type="primary" @click="submit">提交</qm-button>
+        <!-- <qm-button type="primary" @click="submit">阿提交</qm-button> -->
       </div>
     </div>
 
-    <div
-      style="
-        height: calc(100% - 56px);
-        overflow: hidden;
-        user-select: none;
-        position: relative;
-      "
-    >
+    <div class="board-questions">
+      <!-- question list -->
       <div
         v-if="store.currentTask && store.currentTask.questionList"
         class="tw-flex tw-gap-2 tw-flex-wrap tw-justify-between tw-overflow-auto tw-content-start"
@@ -166,51 +126,25 @@
         </template>
       </div>
 
-      <div
-        ref="dragSpliter"
-        style="
-          width: 100%;
-          height: 4px;
-          border: 2px solid grey;
-          background-color: grey;
-          cursor: row-resize;
-        "
-        class="split-pane tw-flex tw-justify-evenly"
-      >
-        <div
-          style="
-            margin-top: -14px;
-            width: 20px;
-            height: 16px;
-            text-align: center;
-            clip-path: polygon(0 100%, 100% 100%, 50% 0);
-            background-color: lightskyblue;
-            cursor: pointer;
-          "
-          @click="topPercent = 20"
-        ></div>
-        <div
-          style="
-            margin-top: -3px;
-            width: 20px;
-            height: 16px;
-            text-align: center;
-            clip-path: polygon(0 0, 100% 0, 50% 100%);
-            background-color: lightskyblue;
-            cursor: pointer;
-          "
-          @click="topPercent = 90"
-        ></div>
+      <!-- split pane -->
+      <div ref="dragSpliter" class="split-pane">
+        <div class="split-pane-btn" @click="topPercent = 20">
+          <caret-up-outlined />
+        </div>
+        <div class="split-pane-btn" @click="topPercent = 80">
+          <caret-down-outlined />
+        </div>
       </div>
+      <!-- score list -->
       <div
-        class="tw-flex tw-flex-wrap tw-mt-5 tw-overflow-auto tw-content-start"
-        style="padding-bottom: 40px; gap: 8px"
+        class="tw-flex tw-flex-wrap tw-overflow-auto tw-content-start"
+        style="padding-bottom: 40px; gap: 8px; margin-top: 8px"
         :style="{ height: `${100 - topPercent}%` }"
       >
         <div style="width: 100%">
           <div
             v-if="store.currentTask.questionList[curQuestionIndex].selective"
-            class="single-score tw-cursor-pointer tw-font-bold unselective"
+            class="single-score unselective"
             :class="{
               'current-score':
                 store.currentTask.questionList[curQuestionIndex]
@@ -229,7 +163,7 @@
         <div
           v-for="(s, i) in questionScoreSteps.slice(1)"
           :key="i"
-          class="single-score tw-cursor-pointer tw-font-bold"
+          class="single-score"
           :class="{
             'current-score': isCurrentScore(s),
             'limit-disable': limitDisable(),
@@ -239,10 +173,12 @@
           {{ s }}
         </div>
 
+        <div style="width: 100%"></div>
+
         <a-tooltip placement="bottom" :destroyTooltipOnHide="true">
           <template #title>作答错误,或只书写题号等情况</template>
           <div
-            class="single-score tw-cursor-pointer tw-font-bold"
+            class="single-score"
             :class="{
               'current-score': Object.is(store.currentScore, 0),
               'limit-disable': limitDisable(),
@@ -255,7 +191,7 @@
         <a-tooltip placement="bottom" :destroyTooltipOnHide="true">
           <template #title>作答为空、未作答、或完全没找到作答</template>
           <div
-            class="single-score tw-cursor-pointer tw-font-bold"
+            class="single-score danger"
             :class="{
               'current-score': Object.is(store.currentScore, -0),
               'limit-disable': limitDisable(),
@@ -267,13 +203,10 @@
         </a-tooltip>
       </div>
     </div>
-    <div
-      class="tw-flex tw-justify-between tw-mt-4"
-      style="position: relative; bottom: 0px; right: 0px; width: 230px"
-    >
+    <!-- footer -->
+    <div class="board-footer">
       <qm-button
         type="primary"
-        shape="round"
         size="large"
         style="
           background-color: var(--app-undo-button-bg-color);
@@ -287,7 +220,6 @@
 
       <qm-button
         type="primary"
-        shape="round"
         size="large"
         :clickTimeout="300"
         data-test="clear-score"
@@ -311,6 +243,7 @@ import EventBus from "@/plugins/eventBus";
 import { cloneDeep } from "lodash-es";
 import { useRoute } from "vue-router";
 import { message, Modal } from "ant-design-vue";
+import { CaretDownOutlined, CaretUpOutlined } from "@ant-design/icons-vue";
 
 const route = useRoute();
 // const curQuestionIndex = ref<number>(0);
@@ -396,8 +329,8 @@ watch(topPercent, () => {
   if (topPercent.value < 10) {
     topPercent.value = 10;
   }
-  if (topPercent.value > 90) {
-    topPercent.value = 90;
+  if (topPercent.value > 80) {
+    topPercent.value = 80;
   }
 });
 
@@ -756,82 +689,7 @@ const buttonHeightForSelective = $computed(() =>
     }
   }
 }
-.mark-board-track-container {
-  max-width: 290px;
-  min-width: 290px;
-  padding: 20px;
-  max-height: calc(100vh - 56px - 0px);
-  overflow: auto;
-  z-index: 1001;
-  transition: margin-right 0.5s;
-  color: var(--app-small-header-text-color);
-  background-color: var(--app-main-bg-color);
-}
-
-.mark-board-track-container-in-dialog {
-  max-width: 100%;
-  min-width: 100%;
-  height: 100%;
-}
-
-.mark-board-track-container.show {
-  margin-right: 0;
-}
-
-.mark-board-track-container.hide {
-  margin-right: -100%;
-}
-
-.top-container {
-  background-color: var(--app-container-bg-color);
-}
-
-.total-score {
-  color: var(--app-main-text-color);
-  font-size: 32px;
-}
-
-.question {
-  min-width: 110px;
-  max-width: 110px;
-  min-height: 72px;
-  padding: 10px;
-  background-color: var(--app-container-bg-color);
-  &.disabled {
-    background-color: #f5f5f5 !important;
-    // pointer-events: none;
-    cursor: not-allowed;
-    * {
-      color: rgba(0, 0, 0, 0.25) !important;
-    }
-  }
-  position: relative;
-}
-
-.current-question {
-  color: white;
-  background-color: var(--app-score-color);
-}
 
-.single-score {
-  position: relative;
-  width: 32px;
-  height: 32px;
-  font-size: var(--app-secondary-font-size);
-  display: grid;
-  place-content: center;
-  background-color: var(--app-container-bg-color);
-
-  border-radius: 30px;
-  &.unselective {
-    width: 72px;
-  }
-}
-
-.current-score {
-  background-color: var(--app-score-color);
-  color: white;
-}
 .limit-disable {
   cursor: not-allowed;
   background-color: #ddd !important;

+ 306 - 455
src/features/mark/MarkHeader.vue

@@ -1,371 +1,337 @@
 <template>
-  <div
-    v-if="store.setting && store.setting.subject.name"
-    class="tw-flex tw-gap-2 tw-justify-between tw-items-center header-container"
-  >
+  <div v-if="store.setting && store.setting.subject.name" class="mark-header">
     <a-tooltip>
       <template #title>回评</template>
       <div
-        class="tw-flex tw-place-content-center tw-cursor-pointer tw-relative menu"
-        :class="[store.historyOpen && 'menu-toggled']"
+        class="header-history"
+        :class="[store.historyOpen && 'active']"
         @click="store.toggleHistory"
       >
-        <span class="tw-inline-flex tw-place-content-center">
-          <img
-            src="./images/left-menu.svg"
-            :class="[store.historyOpen && 'svg-red']"
-          />
-        </span>
-        <div v-if="store.historyOpen" class="triangle"></div>
+        <unordered-list-outlined />
       </div>
     </a-tooltip>
-    <div style="max-width: 12%; margin-left: -20px">
-      <a
-        class="tw-text-white tw-block tw-overflow-ellipsis tw-overflow-hidden tw-whitespace-nowrap header-big-text"
-        :title="store.setting.subject.name"
-        href="/mark/subject-select"
-        @dragstart.prevent
-      >
-        {{
-          `${store.setting.subject.code ?? ""}-${
-            store.setting.subject.name ?? ""
-          }`
-        }}
-      </a>
-    </div>
-    <div class="tw-flex" style="margin: 0 -20px 0 -40px">
-      <a-tooltip>
-        <template #title>
-          问题卷{{ store.status.problemCount }}
-          <br />
-          待仲裁{{ store.status.arbitrateCount }}
-        </template>
-        <img
-          src="./images/problems.svg"
-          :class="questionMarkShouldChange && 'question-mark-animation'"
-          @mouseover="questionMarkShouldChange = false"
-        />
-      </a-tooltip>
-    </div>
-    <!-- <div v-if="store.setting.statusValue === 'TRIAL'">试评</div> -->
-    <div class="tw-flex tw-gap-1">
-      <div style="min-width: 105px">
-        <span class="header-small-text">编号</span>
-        <span class="highlight-text">
-          {{ store.currentTask?.secretNumber ?? "-" }}
-        </span>
-      </div>
-      <div
-        v-if="
-          store.currentTask &&
-          store.currentTask.objectiveScore !== null &&
-          !!store.setting?.showObjectiveScore
-        "
-      >
-        <span class="header-small-text">客观分</span>
-        <span class="highlight-text">
-          {{ store.currentTask.objectiveScore }}
-        </span>
-      </div>
-      <div
-        v-if="
-          props.showTotalScore &&
-          store.currentTask &&
-          store.currentTask.objectiveScore !== null
-        "
-        style="margin-left: 0.5em"
-      >
-        <span class="header-small-text">成绩</span>
-        <span
-          class="highlight-text"
-          style="margin-left: 0.2em; margin-top: 0.1em"
+    <div class="header-info">
+      <!-- 科目信息 -->
+      <div class="head-subjuect">
+        <a
+          :title="store.setting.subject.name"
+          href="/mark/subject-select"
+          @dragstart.prevent
         >
           {{
-            parseFloat(
-              (
-                ((Math.max(store.currentTask.objectiveScore || 0, 0) * 100 +
-                  Math.max(store.currentTask.markResult?.markerScore || 0, 0) *
-                    100) |
-                  0) /
-                100
-              ).toFixed(2)
-            )
+            `${store.setting.subject.code ?? ""}-${
+              store.setting.subject.name ?? ""
+            }`
           }}
-        </span>
+        </a>
       </div>
-    </div>
-    <div
-      v-show="store.status.totalCount"
-      class="tw-flex tw-gap-2 tw-items-center"
-    >
-      <span style="display: inline-flex; height: 16px; min-width: 55px">
-        <span class="header-small-text">已评</span>
-        <transition-group name="count-animation" tag="span">
-          <span
-            :key="store.status.personCount || 0"
-            class="highlight-text"
-            style="display: block"
-          >
-            {{ store.status.personCount }}
-          </span>
-        </transition-group>
-      </span>
-      <span v-if="store.setting.topCount">
-        <span class="header-small-text">分配</span>
-        <span class="highlight-text">{{ store.setting.topCount ?? "-" }}</span>
-      </span>
-      <span style="display: inline-flex; height: 16px; min-width: 55px">
-        <span class="header-small-text">未评</span>
-        <transition-group name="count-animation" tag="span">
-          <span
-            :key="todoCount || 0"
-            class="highlight-text"
-            style="display: block"
-          >
-            {{ todoCount }}
+      <!-- 问题卷数据 -->
+      <div class="head-issue">
+        <a-tooltip>
+          <template #title>
+            问题卷:{{ store.status.problemCount }}
+            <br />
+            待仲裁:{{ store.status.arbitrateCount }}
+          </template>
+          <img
+            src="./images/problems.svg"
+            :class="questionMarkShouldChange && 'question-mark-animation'"
+            @mouseover="questionMarkShouldChange = false"
+          />
+        </a-tooltip>
+      </div>
+      <!-- 编号 客观分 成绩 -->
+      <div class="head-part">
+        <div class="head-part-info">
+          <span class="head-part-label">编号</span>
+          <span class="head-part-value">
+            {{ store.currentTask?.secretNumber ?? "-" }}
           </span>
-        </transition-group>
-      </span>
-      <span style="display: inline-flex; height: 16px; min-width: 60px">
-        <span class="header-small-text">进度</span>
-        <transition-group name="count-animation" tag="span">
-          <span
-            :key="progress || '-'"
-            class="highlight-text"
-            style="display: block"
-          >
-            {{ progress }}%
+        </div>
+        <div
+          v-if="
+            store.currentTask &&
+            store.currentTask.objectiveScore !== null &&
+            !!store.setting?.showObjectiveScore
+          "
+          class="head-part-info"
+        >
+          <span class="head-part-label">客观分</span>
+          <span class="head-part-value">
+            {{ store.currentTask.objectiveScore }}
           </span>
-        </transition-group>
-      </span>
-    </div>
-    <div class="tw-flex tw-place-items-center">
-      <a-tooltip>
-        <template #title>
-          评卷时间段
-          <br />
-          {{
-            store.setting.startTime > 0
-              ? $filters.datetimeFilter(store.setting.startTime)
-              : "-"
-          }}
-          <br />~<br />
-          {{
-            store.setting.endTime > 0
-              ? $filters.datetimeFilter(store.setting.endTime)
-              : "-"
-          }}
-        </template>
-        <img
-          src="./images/time.png"
-          style="width: 16px; height: 16px"
-          class="svg-red-hover"
-        />
-      </a-tooltip>
-    </div>
-    <div class="tw-flex">
-      <a-dropdown class="header-bg-color">
-        <template v-if="!store.setting.forceMode" #overlay>
-          <a-menu>
-            <a-menu-item key="1" @click="toggleSettingMode">
-              {{ exchangeModeName }}
-            </a-menu-item>
-          </a-menu>
-        </template>
-        <a-button
-          style="
-            color: rgba(255, 255, 255, 0.5);
-            border: none;
-            display: flex;
-            align-items: center;
+        </div>
+        <div
+          v-if="
+            props.showTotalScore &&
+            store.currentTask &&
+            store.currentTask.objectiveScore !== null
           "
+          class="head-part-info"
         >
-          <img
-            src="./images/trackmode.png"
-            style="
-              width: 11px;
-              height: 12px;
-              display: inline;
-              margin-right: 2px;
-            "
-          />
-          {{ modeName }}
-          <div v-if="!store.setting.forceMode" class="dropdown-triangle"></div>
-        </a-button>
-      </a-dropdown>
-    </div>
-    <a-popover
-      v-if="!store.isScanImage"
-      title="小助手"
-      trigger="hover"
-      class="tw-cursor-pointer"
-    >
-      <template #content>
-        <table class="assistant-table">
-          <tr v-if="store.setting.statusValue !== 'TRIAL'">
-            <td>问题卷</td>
-            <td>
-              <a-button @click="openProblemModal">选择问题类型</a-button>
-            </td>
-          </tr>
-        </table>
-      </template>
-      <div class="tw-flex tw-items-center">
-        <img
-          src="./images/assistant.png"
-          style="width: 10px; height: 12px; margin-right: 2px"
-        />
-        <span>小助手</span>
-        <div class="dropdown-triangle"></div>
-      </div>
-    </a-popover>
-    <a-popover
-      v-if="store.isScanImage"
-      trigger="hover"
-      class="tw-cursor-pointer"
-    >
-      <template #content>
-        <table class="assistant-table">
-          <tr v-if="store.setting.subject.paperUrl">
-            <td>试卷</td>
-            <td>
-              <a-switch
-                v-model:checked="store.setting.uiSetting['paper.modal']"
-              />
-            </td>
-          </tr>
-          <tr v-if="store.setting.subject.answerUrl">
-            <td>答案</td>
-            <td>
-              <a-switch
-                v-model:checked="store.setting.uiSetting['answer.modal']"
-              />
-            </td>
-          </tr>
-          <tr>
-            <td>全卷</td>
-            <td>
-              <a-switch v-model:checked="store.allPaperModal" />
-            </td>
-          </tr>
-          <tr v-if="store.setting.sheetView">
-            <td>原图</td>
-            <td>
-              <a-switch v-model:checked="store.sheetViewModal" />
-            </td>
-          </tr>
-          <tr>
-            <td>缩略图</td>
-            <td>
-              <a-switch
-                v-model:checked="store.setting.uiSetting['minimap.modal']"
-              />
-            </td>
-          </tr>
-          <tr>
-            <td>特殊标记</td>
-            <td>
-              <a-switch
-                v-model:checked="store.setting.uiSetting['specialTag.modal']"
-              />
-            </td>
-          </tr>
-          <tr v-if="store.setting.statusValue !== 'TRIAL'">
-            <td>问题卷</td>
-            <td>
-              <a-button
-                type="text"
-                style="
-                  color: var(--app-primary-button-bg-color);
-                  margin-right: -15px;
-                  height: 25px;
-                "
-                @click="openProblemModal"
-              >
-                选择问题类型
-              </a-button>
-            </td>
-          </tr>
-          <tr v-if="store.isScanImage">
-            <td>分数/标记大小</td>
-            <td>
-              <a-slider
-                v-model:value="store.setting.uiSetting['score.fontSize.scale']"
-                :min="0.5"
-                :step="0.1"
-                :max="2"
-                style="margin: 0"
-              />
-            </td>
-          </tr>
-          <tr v-if="store.isScanImage">
-            <td>快捷键</td>
-            <td>
-              <a-switch
-                v-model:checked="store.setting.uiSetting['shortCut.modal']"
-              />
-            </td>
-          </tr>
-        </table>
-      </template>
-      <div class="tw-flex tw-items-center assistant-text">
-        <img
-          src="./images/assistant.png"
-          style="width: 10px; height: 12px; margin-right: 2px"
-        />
-        <span>小助手</span>
-        <div class="dropdown-triangle"></div>
+          <span class="head-part-label">成绩</span>
+          <span class="head-part-value">
+            {{
+              parseFloat(
+                (
+                  ((Math.max(store.currentTask.objectiveScore || 0, 0) * 100 +
+                    Math.max(
+                      store.currentTask.markResult?.markerScore || 0,
+                      0
+                    ) *
+                      100) |
+                    0) /
+                  100
+                ).toFixed(2)
+              )
+            }}
+          </span>
+        </div>
       </div>
-    </a-popover>
-    <div
-      class="tw-flex tw-place-content-center tw-cursor-pointer tw-items-center"
-      style="max-width: 8%"
-      :title="store.setting.groupTitle + '-' + store.setting.groupNumber"
-      @click="openSwitchGroupModal"
-    >
-      <img
-        src="./images/group.png"
-        style="width: 10px; height: 12px; margin-right: 2px"
-      />
-      <div class="tw-overflow-ellipsis tw-overflow-hidden tw-whitespace-nowrap">
-        {{ "分组:" + store.setting.groupNumber }}
+      <!-- 已评 分配 未评 进度 -->
+      <div v-show="store.status.totalCount" class="head-part">
+        <div class="head-part-info">
+          <span class="head-part-label">已评</span>
+          <transition-group name="count-animation" tag="span">
+            <span :key="store.status.personCount || 0" class="head-part-value">
+              {{ store.status.personCount }}
+            </span>
+          </transition-group>
+        </div>
+        <div v-if="store.setting.topCount" class="head-part-info">
+          <span class="head-part-label">已分配</span>
+          <span class="head-part-value">{{
+            store.setting.topCount ?? "-"
+          }}</span>
+        </div>
+        <div class="head-part-info">
+          <span class="head-part-label">未评</span>
+          <transition-group name="count-animation" tag="span">
+            <span
+              :key="store.status.topCount - store.status.personCount || 0"
+              class="head-part-value"
+            >
+              {{ store.status.topCount - store.status.personCount || 0 }}
+            </span>
+          </transition-group>
+        </div>
+        <div class="head-part-info">
+          <span class="head-part-label">进度</span>
+          <transition-group name="count-animation" tag="span">
+            <span :key="progress || '-'" class="head-part-value">
+              {{ progress }}%
+            </span>
+          </transition-group>
+        </div>
       </div>
-      <div v-if="store.groups.length > 1" class="dropdown-triangle"></div>
-    </div>
-    <div class="tw-flex tw-gap-4">
+      <!-- 评卷时段 -->
       <div
-        class="tw-flex tw-place-items-center tw-cursor-pointer"
-        @click="openProfileModal"
+        v-if="store.setting.startTime && store.setting.endTime"
+        class="head-part"
       >
-        <!-- <UserOutlined /> -->
-        {{ store.setting.userName }}
+        <a-tooltip>
+          <template #title>
+            {{
+              store.setting.startTime > 0
+                ? $filters.datetimeFilter(store.setting.startTime)
+                : "-"
+            }}
+            ~
+            {{
+              store.setting.endTime > 0
+                ? $filters.datetimeFilter(store.setting.endTime)
+                : "-"
+            }}
+          </template>
+          <div class="head-part-info">
+            <span class="head-part-label">评卷时段</span>
+            <span class="dropdown-triangle"></span>
+          </div>
+        </a-tooltip>
+      </div>
+      <!-- 评卷模式 -->
+      <div class="head-part">
+        <a-dropdown class="header-bg-color">
+          <template v-if="!store.setting.forceMode" #overlay>
+            <a-menu>
+              <a-menu-item key="1" @click="toggleSettingMode">
+                {{ exchangeModeName }}
+              </a-menu-item>
+            </a-menu>
+          </template>
+          <div class="head-part-info cursor">
+            <span class="head-part-label">模式</span>
+            <span class="head-part-value">
+              {{ modeName }}
+            </span>
+            <span
+              v-if="!store.setting.forceMode"
+              class="dropdown-triangle"
+            ></span>
+          </div>
+        </a-dropdown>
       </div>
+      <!-- 分组 -->
       <div
-        class="tw-flex tw-place-items-center tw-cursor-pointer"
-        @click="logout"
+        class="head-part"
+        :title="store.setting.groupTitle + '-' + store.setting.groupNumber"
+        @click="openSwitchGroupModal"
       >
-        <!-- <PoweroffOutlined /> -->
-        退出
+        <div class="head-part-info cursor">
+          <span class="head-part-label">分组</span>
+          <span class="head-part-value">
+            {{ store.setting.groupNumber }}
+          </span>
+          <span class="dropdown-triangle"></span>
+        </div>
+      </div>
+      <!-- 用户 -->
+      <div class="head-part" @click="openProfileModal">
+        <div class="head-part-info cursor">
+          <span class="head-part-label">用户</span>
+          <span class="head-part-value">{{ store.setting.userName }}</span>
+          <span class="dropdown-triangle"></span>
+        </div>
       </div>
-      <a-tooltip placement="bottomRight" :overlayStyle="{ width: '58px' }">
+    </div>
+    <div class="header-user">
+      <!-- 小助手 -->
+      <a-popover
+        v-if="!store.isScanImage"
+        title="小助手"
+        trigger="hover"
+        class="tw-cursor-pointer"
+      >
+        <template #content>
+          <table class="assistant-table">
+            <tr v-if="store.setting.statusValue !== 'TRIAL'">
+              <td>问题卷</td>
+              <td>
+                <a-button @click="openProblemModal">选择问题类型</a-button>
+              </td>
+            </tr>
+          </table>
+        </template>
+        <div class="head-handle">
+          <img src="./images/icon-handle.png" alt="handle" />
+          <span>小助手</span>
+        </div>
+      </a-popover>
+      <a-popover
+        v-if="store.isScanImage"
+        trigger="hover"
+        class="tw-cursor-pointer"
+      >
+        <template #content>
+          <table class="assistant-table">
+            <tr v-if="store.setting.subject.paperUrl">
+              <td>试卷</td>
+              <td>
+                <a-switch
+                  v-model:checked="store.setting.uiSetting['paper.modal']"
+                />
+              </td>
+            </tr>
+            <tr v-if="store.setting.subject.answerUrl">
+              <td>答案</td>
+              <td>
+                <a-switch
+                  v-model:checked="store.setting.uiSetting['answer.modal']"
+                />
+              </td>
+            </tr>
+            <tr>
+              <td>全卷</td>
+              <td>
+                <a-switch v-model:checked="store.allPaperModal" />
+              </td>
+            </tr>
+            <tr v-if="store.setting.sheetView">
+              <td>原图</td>
+              <td>
+                <a-switch v-model:checked="store.sheetViewModal" />
+              </td>
+            </tr>
+            <tr>
+              <td>缩略图</td>
+              <td>
+                <a-switch
+                  v-model:checked="store.setting.uiSetting['minimap.modal']"
+                />
+              </td>
+            </tr>
+            <tr>
+              <td>特殊标记</td>
+              <td>
+                <a-switch
+                  v-model:checked="store.setting.uiSetting['specialTag.modal']"
+                />
+              </td>
+            </tr>
+            <tr v-if="store.setting.statusValue !== 'TRIAL'">
+              <td>问题卷</td>
+              <td>
+                <a-button
+                  type="text"
+                  style="
+                    color: var(--app-primary-button-bg-color);
+                    margin-right: -15px;
+                    height: 25px;
+                  "
+                  @click="openProblemModal"
+                >
+                  选择问题类型
+                </a-button>
+              </td>
+            </tr>
+            <tr v-if="store.isScanImage">
+              <td>分数/标记大小</td>
+              <td>
+                <a-slider
+                  v-model:value="
+                    store.setting.uiSetting['score.fontSize.scale']
+                  "
+                  :min="0.5"
+                  :step="0.1"
+                  :max="2"
+                  style="margin: 0"
+                />
+              </td>
+            </tr>
+            <tr v-if="store.isScanImage">
+              <td>快捷键</td>
+              <td>
+                <a-switch
+                  v-model:checked="store.setting.uiSetting['shortCut.modal']"
+                />
+              </td>
+            </tr>
+          </table>
+        </template>
+        <div class="head-handle">
+          <img src="./images/icon-handle.png" alt="handle" />
+          <span>小助手</span>
+        </div>
+      </a-popover>
+      <!-- 退出 -->
+      <div class="head-logout" @click="logout">
+        <close-circle-outlined />
+      </div>
+      <!-- 弹出给分板 -->
+      <a-tooltip placement="bottomRight" :align="{ offset: [-10, 0] }">
         <template #title>弹出给分板</template>
         <div
-          class="tw-flex tw-place-content-center tw-cursor-pointer menu"
           :class="[
+            'head-board',
             store.isScoreBoardVisible && store.currentTask && 'menu-toggled',
           ]"
           @click="store.toggleScoreBoard"
         >
-          <span class="tw-inline-flex tw-place-content-center tw-relative">
-            <img
-              src="./images/right-menu.svg"
-              :class="[store.isScoreBoardVisible && 'svg-red']"
-            />
-          </span>
-          <div
+          <img src="./images/icon-right-menu.png" />
+          <!-- <div
             v-if="store.isScoreBoardVisible && store.currentTask"
             class="triangle"
-          ></div>
+          ></div> -->
         </div>
       </a-tooltip>
     </div>
@@ -384,11 +350,15 @@ import MarkSwitchGroupDialog from "./MarkSwitchGroupDialog.vue";
 import MarkProblemDialog from "./MarkProblemDialog.vue";
 import { isNumber } from "lodash-es";
 import { Modal } from "ant-design-vue";
+import {
+  CloseCircleOutlined,
+  UnorderedListOutlined,
+} from "@ant-design/icons-vue";
 
 const props = defineProps<{ showTotalScore?: boolean }>();
 
 const modeName = $computed(() =>
-  store.setting.mode === "TRACK" ? "轨迹模式" : "普通模式"
+  store.setting.mode === "TRACK" ? "轨迹" : "普通"
 );
 
 const exchangeModeName = $computed(() =>
@@ -473,12 +443,6 @@ watchEffect(() => {
   }
 });
 
-const todoCount = $computed(() =>
-  typeof store.status.totalCount === "number"
-    ? store.status.totalCount - store.status.markedCount
-    : "-"
-);
-
 let questionMarkShouldChange = $ref(false);
 watch(
   () => [store.status.problemCount, store.status.arbitrateCount],
@@ -491,116 +455,3 @@ watch(
   }
 );
 </script>
-
-<style scoped>
-.header-bg-color {
-  background-color: var(--header-bg-color);
-}
-
-.header-container {
-  position: relative;
-  height: 56px;
-  line-height: 16px;
-
-  background-color: var(--header-bg-color);
-  color: rgba(255, 255, 255, 0.5);
-}
-
-.menu {
-  width: 56px;
-  height: 56px;
-  padding: 20px;
-}
-
-.menu:hover,
-.menu-toggled {
-  background-color: rgba(255, 255, 255, 0.2);
-}
-
-.header-container span {
-  vertical-align: middle;
-}
-
-.header-big-text {
-  font-size: 20px;
-  line-height: 30px;
-}
-
-.header-small-text {
-  font-size: var(--app-secondary-font-size);
-}
-
-.highlight-text {
-  color: white;
-  font-size: var(--app-title-font-size);
-}
-
-.header-bg-color.ant-btn:hover {
-  background-color: var(--app-ant-select-bg-override-color) !important;
-}
-
-.assistant-table {
-  z-index: 5500;
-  border-collapse: separate;
-  border-spacing: 0 1em;
-  color: var(--app-bold-text-color);
-  width: 240px;
-}
-
-.assistant-table tr td:nth-child(2) {
-  text-align: right;
-}
-
-.svg-red {
-  filter: invert(27%) sepia(51%) saturate(2878%) hue-rotate(346deg)
-    brightness(104%) contrast(97%);
-}
-
-.svg-red-hover:hover {
-  filter: invert(27%) sepia(51%) saturate(2878%) hue-rotate(346deg)
-    brightness(104%) contrast(97%);
-}
-
-.triangle {
-  background-color: white;
-  width: 10px;
-  height: 10px;
-  clip-path: polygon(0 100%, 100% 100%, 50% 0);
-
-  position: absolute;
-  bottom: -2px;
-}
-
-.dropdown-triangle {
-  background-color: #8c8d9b;
-  width: 7px;
-  height: 5px;
-  clip-path: polygon(0 0, 100% 0, 50% 100%);
-  margin-left: 4px;
-}
-
-.count-animation-enter-active,
-.count-animation-leave-active {
-  transition: all 1.2s ease-in-out;
-}
-
-.count-animation-enter-from,
-.count-animation-leave-to {
-  opacity: 0;
-  transform: translateY(18px);
-}
-
-.question-mark-animation {
-  animation: pluse 2s ease-in-out infinite alternate;
-}
-
-@keyframes pluse {
-  0% {
-    scale: 0.7;
-  }
-
-  100% {
-    scale: 1.3;
-  }
-}
-</style>

+ 73 - 195
src/features/mark/MarkHistory.vue

@@ -1,20 +1,16 @@
 <template>
-  <div
-    class="history-container tw-px-1"
-    :class="[store.historyOpen ? 'show' : 'hide']"
-  >
-    <div class="tw-mt-1 tw-mb-1 tw-flex tw-place-items-center">
-      <div class="tw-text-lg main-text-color tw-mr-3 tw-font-bold">
+  <div class="mark-history" :class="[store.historyOpen ? 'show' : 'hide']">
+    <div class="history-header">
+      <h2>
         {{ title }}
-      </div>
+      </h2>
       <div
         v-if="showSearch && store.getStatusValueName !== '试评'"
-        class="tw-flex-1 tw-flex"
+        class="header-select"
       >
         <a-select
           ref="select"
           v-model:value="searchType"
-          style="width: 75px; margin-right: 4px; font-size: 12px"
           @change="searchTypeChange"
         >
           <a-select-option value="1">编号</a-select-option>
@@ -22,8 +18,7 @@
         </a-select>
         <a-input-search
           v-model:value="secretNumberInput"
-          style="font-size: 13px"
-          class="tw-flex-1 search-value"
+          class="search-value"
           placeholder="查找试卷"
           allowClear
           @keyup.enter="searchHistoryTask"
@@ -31,112 +26,73 @@
           @keydown.stop=""
           @search="searchHistoryTask"
         >
-          <!-- <template #suffix>
-            <SearchOutlined style="color: rgba(0, 0, 0, 0.45)" />
-          </template> -->
         </a-input-search>
       </div>
-
-      <!-- <input
-        v-if="showSearch"
-        v-model="secretNumberInput"
-        type="text"
-        placeholder="查找试卷"
-        class="tw-flex-1 tw-rounded tw-h-8 tw-pl-1 tw-pr-8"
-        @keydown.stop=""
-        @keypress.stop=""
-        @keyup.enter="searchHistoryTask"
-      />
-      <SearchOutlined
-        v-if="showSearch"
-        style="margin-left: -24px; font-size: 18px; padding: 3px"
-        @click="searchHistoryTask"
-      /> -->
     </div>
-    <div class="tw-flex tw-justify-between tw-mt-5">
-      <div class="tw-flex">编号</div>
-      <!-- <div
-        class="tw-cursor-pointer tw-flex tw-items-center"
-        @click="toggleOrderBy(orderTimeField)"
-      > -->
-      <div
-        class="tw-flex tw-items-center tw-cursor-pointer"
-        @click="toggleOrderBy('markerTime')"
-      >
-        时间
-        <!-- <CaretUpOutlined
-          v-if="showOrder && order === orderTimeField && sort === 'ASC'"
-        />
-        <CaretDownOutlined
-          v-if="showOrder && order === orderTimeField && sort === 'DESC'"
-        /> -->
+    <div class="history-table-head">
+      <div class="table-head-body">
+        <div class="tw-flex">编号</div>
         <div
-          v-if="showOrder"
-          class="order-icons-box tw-flex tw-flex-col tw-justify-center tw-items-center"
+          class="tw-flex tw-items-center tw-cursor-pointer"
+          @click="toggleOrderBy('markerTime')"
         >
-          <CaretUpOutlined
-            :class="[
-              'order-icon',
-              { active: order === 'markerTime' && sort === 'ASC' },
-            ]"
-          />
-          <CaretDownOutlined
-            :class="[
-              'order-icon',
-              { active: order === 'markerTime' && sort === 'DESC' },
-            ]"
-          />
+          <span>时间</span>
+          <div v-if="showOrder" class="order-icons">
+            <CaretUpOutlined
+              :class="[
+                'order-icon',
+                { active: order === 'markerTime' && sort === 'ASC' },
+              ]"
+            />
+            <CaretDownOutlined
+              :class="[
+                'order-icon',
+                { active: order === 'markerTime' && sort === 'DESC' },
+              ]"
+            />
+          </div>
         </div>
-      </div>
-      <!-- <div
-        class="tw-cursor-pointer tw-flex tw-items-center"
-        @click="toggleOrderBy('markerScore')"
-      > -->
-      <div
-        class="tw-flex tw-items-center tw-cursor-pointer"
-        @click="toggleOrderBy('markerScore')"
-      >
-        分数
-        <!-- <CaretUpOutlined
-          v-if="showOrder && order === 'markerScore' && sort === 'ASC'"
-        />
-        <CaretDownOutlined
-          v-if="showOrder && order === 'markerScore' && sort === 'DESC'"
-        /> -->
         <div
-          v-if="showOrder"
-          class="order-icons-box tw-flex tw-flex-col tw-justify-center tw-items-center"
+          class="tw-flex tw-items-center tw-cursor-pointer"
+          @click="toggleOrderBy('markerScore')"
         >
-          <CaretUpOutlined
-            :class="[
-              'order-icon',
-              { active: order === 'markerScore' && sort === 'ASC' },
-            ]"
-          />
-          <CaretDownOutlined
-            :class="[
-              'order-icon',
-              { active: order === 'markerScore' && sort === 'DESC' },
-            ]"
-          />
+          <span>分数</span>
+          <div v-if="showOrder" class="order-icons">
+            <CaretUpOutlined
+              :class="[
+                'order-icon',
+                { active: order === 'markerScore' && sort === 'ASC' },
+              ]"
+            />
+            <CaretDownOutlined
+              :class="[
+                'order-icon',
+                { active: order === 'markerScore' && sort === 'DESC' },
+              ]"
+            />
+          </div>
         </div>
       </div>
     </div>
-    <a-spin
-      v-if="remarkCount !== 0"
-      :spinning="loading"
-      size="large"
-      tip="Loading..."
-      :delay="500"
-    >
-      <div style="margin-bottom: -40px; padding-bottom: 72px">
-        <div v-for="(task, index) of store.historyTasks" :key="index">
+    <div class="history-body">
+      <a-spin
+        v-if="remarkCount !== 0"
+        :spinning="loading"
+        size="large"
+        tip="Loading..."
+        :delay="500"
+      >
+        <div
+          v-for="(task, index) of store.historyTasks"
+          :key="index"
+          class="history-table-body"
+        >
           <div
-            class="tw-flex tw-justify-between tw-place-items-center tw-rounded tw-cursor-pointer tw-font-bold tw-py-2"
+            class="history-table-row"
             :class="store.currentTask === task && 'current-task'"
             @click="replaceCurrentTask(task)"
           >
-            <div class="tw-break-words" style="width: 62px">
+            <div class="tw-break-words" style="width: 72px">
               {{ task.secretNumber }}
             </div>
             <div>
@@ -147,43 +103,31 @@
             </div>
           </div>
         </div>
-      </div>
-    </a-spin>
-    <div
-      class="tw-flex tw-justify-between tw-place-content-center tw-mt-2 pager-container"
-    >
-      <div class="tw-font-bold" style="line-height: 30px">
+      </a-spin>
+    </div>
+
+    <div class="history-footer">
+      <div>
         第{{ currentPage }}页
-        <template v-if="hasJump"
-          >&nbsp;跳至
+        <template v-if="hasJump">
+          &nbsp;跳至
           <a-input
             v-model:value="jumpPage"
             style="width: 40px; font-weight: normal"
-            size="small"
             @keypress.stop=""
             @keydown.stop=""
             @keyup.stop="jumpPageKeyUp"
             @keyup.enter="toJumpPage"
           />
-          &nbsp;页</template
-        >
+          &nbsp;页
+        </template>
       </div>
       <div class="tw-flex tw-gap-2">
-        <a-button
-          shape="circle"
-          type="primary"
-          title="上一页"
-          @click="previousPage"
-        >
-          <div class="left-triangle"></div>
+        <a-button title="上一页" @click="previousPage">
+          <caret-left-outlined />
         </a-button>
-        <a-button
-          shape="circle"
-          type="primary"
-          title="下一页"
-          @click="nextPage"
-        >
-          <div class="right-triangle"></div>
+        <a-button title="下一页" @click="nextPage">
+          <caret-right-outlined />
         </a-button>
       </div>
     </div>
@@ -204,6 +148,8 @@ import {
   // SearchOutlined,
   CaretDownOutlined,
   CaretUpOutlined,
+  CaretLeftOutlined,
+  CaretRightOutlined,
 } from "@ant-design/icons-vue";
 import { cloneDeep } from "lodash-es";
 import EventBus from "@/plugins/eventBus";
@@ -496,71 +442,3 @@ async function searchHistoryTask() {
   }
 }
 </script>
-
-<style lang="less" scoped>
-.history-container {
-  min-width: 290px;
-  width: 290px;
-  padding: 20px;
-  font-size: var(--app-secondary-font-size);
-  overflow-y: auto;
-  height: calc(100vh - 56px);
-  transition: margin-left 0.5s;
-  .order-icons-box {
-    .order-icon {
-      &:first-child {
-        position: relative;
-        bottom: -3px;
-      }
-      color: #999;
-      &.active {
-        color: var(--app-main-text-color);
-      }
-    }
-  }
-}
-.history-container .search-value :deep(.ant-input::-webkit-input-placeholder) {
-  font-size: 12px;
-}
-.history-container :deep(.ant-input-affix-wrapper) {
-  padding: 4px 6px;
-}
-.history-container.show {
-  margin-left: 0;
-}
-.history-container.hide {
-  margin-left: -290px;
-}
-
-.current-task {
-  background-color: var(--app-score-color);
-  color: white;
-  padding-left: 5px;
-  margin-left: -5px;
-  padding-right: 5px;
-  margin-right: -5px;
-}
-
-.left-triangle {
-  width: 12px;
-  height: 12px;
-  background-color: white;
-  clip-path: polygon(0 50%, 100% 0, 100% 100%);
-  transform: translateX(60%);
-}
-
-.right-triangle {
-  width: 12px;
-  height: 12px;
-  background-color: white;
-  clip-path: polygon(100% 50%, 0 100%, 0 0);
-  transform: translateX(90%);
-}
-.pager-container {
-  position: absolute;
-  bottom: 0px;
-  padding-bottom: 20px;
-  width: 250px;
-  background-color: var(--app-main-bg-color);
-}
-</style>

BIN
src/features/mark/images/icon-handle.png


BIN
src/features/mark/images/icon-left-menu.png


BIN
src/features/mark/images/icon-right-menu.png


BIN
src/features/mark/images/icon-score.png


+ 1 - 0
src/main.ts

@@ -6,6 +6,7 @@ if (!validUA) {
   location.href = "about:blank";
 }
 import "./styles/global.css";
+import "./styles/mark.less";
 import { createApp } from "vue";
 import { createPinia } from "pinia";
 import { initMarkStore } from "@/store/store";

+ 6 - 3
src/styles/cssvar.css

@@ -1,13 +1,16 @@
 :root {
-  --header-bg-color: #191b37;
+  --color-text-dark: #262626;
+  --header-bg-color: #262626;
+  --color-text-primary: #0673f9;
+  --color-text-danger: #f53f3f;
   --app-container-bg-color: white;
   --app-main-bg-color: #edf2fa;
   --app-main-text-color: #283e76;
   --app-min-width: 1280px;
   --app-bold-text-color: #435488;
   --app-small-header-text-color: #7584ac;
-  --app-score-color: #5d65ff;
-  --app-primary-button-bg-color: #5d65ff;
+  --app-score-color: #0673f9;
+  --app-primary-button-bg-color: #0673f9;
   --app-ant-select-bg-override-color: #5d6d7d;
   --app-undo-button-bg-color: #4ed885;
 

+ 24 - 2
src/styles/global.css

@@ -2,6 +2,29 @@
 @import "./nprogress.css";
 @import "./cssvar.css";
 
+/* browse style */
+::-webkit-scrollbar {
+  width: 8px;
+  height: 8px;
+  background: transparent;
+}
+::-webkit-scrollbar-button {
+  display: none;
+}
+::-webkit-scrollbar-track {
+  background: #f2f3f5;
+}
+::-webkit-scrollbar-thumb {
+  border-radius: 8px;
+  background: #666;
+}
+::-webkit-scrollbar-corner {
+  background: transparent;
+}
+::-webkit-scrollbar-resizer {
+  background: transparent;
+}
+
 body {
   margin: 0;
   font-size: var(--app-main-font-size);
@@ -63,9 +86,8 @@ button.ant-btn-primary {
   bottom: 3px;
   right: 3px; */
   font-size: 12px !important;
-  
 }
 .set-unselect.ant-btn-primary {
   background-color: #faad14 !important;
   border-color: #faad14 !important;
-}
+}

+ 640 - 0
src/styles/mark.less

@@ -0,0 +1,640 @@
+.mark {
+  &-header {
+    position: relative;
+    height: 56px;
+    background-color: var(--header-bg-color);
+    color: #fff;
+
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+
+    .header-bg-color {
+      background-color: var(--header-bg-color);
+    }
+
+    .header-info {
+      flex: 2;
+      overflow-x: auto;
+      overflow-y: hidden;
+      height: 100%;
+      padding-right: 10px;
+      padding-left: 16px;
+
+      display: flex;
+      align-items: center;
+      gap: 16px;
+
+      > div {
+        flex-shrink: 0;
+      }
+    }
+
+    .header-history {
+      width: 56px;
+      height: 56px;
+      background: var(--color-text-dark);
+      font-size: 22px;
+      border-right: 1px solid #595959;
+      color: #fff;
+
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      cursor: pointer;
+
+      &.active {
+        background: var(--color-text-primary);
+      }
+
+      &:hover {
+        opacity: 0.8;
+      }
+    }
+
+    .head-subjuect a {
+      color: #fff;
+      height: 26px;
+      line-height: 26px;
+      font-weight: 500;
+      font-size: 18px;
+      padding-left: 16px;
+      position: relative;
+
+      &::before {
+        content: "";
+        display: block;
+        position: absolute;
+        width: 4px;
+        height: 20px;
+        background-color: var(--color-text-primary);
+        top: 50%;
+        left: 0;
+        transform: translateY(-50%);
+      }
+
+      &:hover {
+        opacity: 0.8;
+      }
+    }
+
+    .head-part {
+      height: 34px;
+      border: 1px solid #595959;
+      border-radius: 6px;
+      padding: 5px 12px;
+      line-height: 22px;
+      color: #fff;
+      font-size: 16px;
+
+      display: flex;
+      align-items: center;
+      gap: 10px;
+
+      .dropdown-triangle {
+        display: inline-block;
+        vertical-align: middle;
+        background-color: #8c8d9b;
+        width: 7px;
+        height: 5px;
+        clip-path: polygon(0 0, 100% 0, 50% 100%);
+        margin-left: 8px;
+      }
+
+      &-info {
+        position: relative;
+        &.cursor {
+          cursor: pointer;
+
+          &:hover {
+            opacity: 0.8;
+          }
+        }
+
+        &:not(:first-child) {
+          padding-left: 10px;
+
+          &::before {
+            content: "";
+            display: block;
+            position: absolute;
+            height: 16px;
+            border: 1px solid #8c8c8c;
+            left: 0;
+            top: 50%;
+            transform: translateY(-50%);
+          }
+        }
+      }
+
+      &-label {
+        color: #8c8c8c;
+
+        &::after {
+          content: ":";
+        }
+      }
+    }
+    .header-user {
+      display: flex;
+      align-items: center;
+    }
+
+    .head-handle {
+      height: 56px;
+      background: var(--color-text-primary);
+      padding: 17px 8px;
+      color: #fff;
+
+      display: flex;
+      align-items: center;
+      gap: 5px;
+      > img {
+        display: block;
+        width: 16px;
+        height: 16px;
+      }
+    }
+    .head-logout {
+      width: 56px;
+      height: 56px;
+      background: #8c8c8c;
+      font-size: 24px;
+      color: #fff;
+      cursor: pointer;
+
+      display: flex;
+      align-items: center;
+      justify-content: center;
+
+      &:hover {
+        opacity: 0.8;
+      }
+    }
+
+    .head-board {
+      width: 56px;
+      height: 56px;
+      background: #595959;
+      cursor: pointer;
+
+      display: flex;
+      align-items: center;
+      justify-content: center;
+
+      > img {
+        display: block;
+        width: 24px;
+        height: 24px;
+      }
+      &:hover {
+        opacity: 0.8;
+      }
+    }
+  }
+
+  .count-animation-enter-active,
+  .count-animation-leave-active {
+    transition: all 1.2s ease-in-out;
+  }
+
+  .count-animation-enter-from,
+  .count-animation-leave-to {
+    opacity: 0;
+    transform: translateY(18px);
+  }
+
+  .question-mark-animation {
+    animation: pluse 2s ease-in-out infinite alternate;
+  }
+
+  @keyframes pluse {
+    0% {
+      scale: 0.7;
+    }
+
+    100% {
+      scale: 1.3;
+    }
+  }
+}
+
+.assistant-table {
+  z-index: 5500;
+  border-collapse: separate;
+  border-spacing: 0 1em;
+  color: var(--app-bold-text-color);
+  width: 240px;
+
+  tr td:nth-child(2) {
+    text-align: right;
+  }
+}
+
+// mark-board-track
+.mark-board-track {
+  flex-grow: 0;
+  flex-shrink: 0;
+  width: 280px;
+  background: #e5f4ff;
+  padding: 16px;
+  max-height: calc(100vh - 56px);
+  overflow: auto;
+  z-index: 1001;
+  transition: margin-right 0.5s;
+  color: var(--color-text-dark);
+  position: relative;
+
+  &:not(.in-dialog)::before {
+    content: "";
+    display: block;
+    height: 100%;
+    border-left: 1px solid #dce3eb;
+    position: absolute;
+    top: 0;
+    left: 0;
+  }
+
+  &.in-dialog {
+    max-width: 100%;
+    min-width: 100%;
+    height: 100%;
+  }
+
+  &.show {
+    margin-right: 0;
+  }
+
+  &.hide {
+    margin-right: -100%;
+  }
+
+  .board-header {
+    width: 248px;
+    height: 100px;
+    background: linear-gradient(180deg, #fbfcfd 0%, #f5f7fb 100%);
+    border: 1px solid;
+    border-color: #dce3eb;
+    border-radius: 8px;
+    padding: 18px;
+    margin-bottom: 12px;
+    flex: 0;
+
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    position: relative;
+  }
+
+  .header-score {
+    z-index: 9;
+    flex-grow: 2;
+    > h5 {
+      height: 24px;
+      line-height: 24px;
+      color: #8c8c8c;
+      font-size: 16px;
+      margin-bottom: 5px;
+    }
+
+    .score-detail {
+      display: flex;
+      align-items: center;
+      gap: 10px;
+      height: 40px;
+
+      > img {
+        display: block;
+        width: 30px;
+        height: 30px;
+      }
+
+      .score-number {
+        line-height: 40px;
+        font-weight: 700;
+        color: var(--color-text-dark);
+        font-size: 32px;
+      }
+    }
+  }
+  .header-action {
+    z-index: 9;
+    flex-grow: 0;
+    flex-shrink: 0;
+    height: 100%;
+    min-width: 60px;
+    max-width: 114px;
+
+    display: flex;
+    gap: 6px;
+
+    .ant-btn {
+      height: 100%;
+      background: linear-gradient(180deg, #00a6fc 0%, #0072ff 100%);
+      border: 1px solid;
+      border-color: var(--color-text-primary);
+      border-radius: 6px;
+      font-size: 16px;
+      font-weight: 500;
+      padding: 4px;
+      text-align: center;
+
+      flex-grow: 2;
+      flex-shrink: 1;
+
+      &:hover {
+        opacity: 0.8;
+      }
+    }
+  }
+
+  // board-questions
+  .board-questions {
+    height: calc(100% - 56px);
+    overflow: hidden;
+    user-select: none;
+    position: relative;
+  }
+  .question {
+    min-width: 110px;
+    max-width: 110px;
+    min-height: 72px;
+    padding: 10px;
+    background-color: var(--app-container-bg-color);
+    &.disabled {
+      background-color: #f5f5f5 !important;
+      // pointer-events: none;
+      cursor: not-allowed;
+      * {
+        color: rgba(0, 0, 0, 0.25) !important;
+      }
+    }
+    position: relative;
+  }
+
+  .current-question {
+    color: white;
+    background-image: url(../assets/bg-question-active.png);
+    background-size: 100% 100%;
+  }
+
+  // split-pane
+  .split-pane {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+
+    height: 20px;
+    background: linear-gradient(180deg, #ffffff 0%, #e5e5e5 100%);
+    border: 1px solid;
+    border-color: #dce3eb;
+    border-radius: 8px;
+    overflow: hidden;
+    cursor: row-resize;
+
+    .split-pane-btn {
+      width: 50%;
+      cursor: pointer;
+      text-align: center;
+
+      &:hover {
+        background: linear-gradient(180deg, #ffffff 0%, #ccc 100%);
+      }
+
+      &:first-child {
+        border-right: 1px solid #dce3eb;
+      }
+
+      .anticon {
+        vertical-align: middle;
+      }
+    }
+  }
+  // score-btn
+  .single-score {
+    position: relative;
+    font-size: 16px;
+    display: grid;
+    place-content: center;
+    width: 34px;
+    height: 34px;
+    background: #ffffff;
+    border: 1px solid;
+    border-color: #dce3eb;
+    border-radius: 17px;
+    font-weight: 500;
+    cursor: pointer;
+
+    &:not(.current-score):hover {
+      color: var(--color-text-primary);
+    }
+
+    &.unselective {
+      width: 72px;
+    }
+
+    &.danger {
+      color: var(--color-text-danger);
+
+      &:hover {
+        color: var(--color-text-danger);
+        opacity: 0.8;
+      }
+    }
+  }
+
+  .current-score {
+    background-color: var(--app-score-color);
+    color: white;
+  }
+
+  // board-footer
+  .board-footer {
+    display: flex;
+    gap: 10px;
+
+    .ant-btn {
+      flex: 1;
+      border-radius: 8px;
+      padding-left: 8px;
+      padding-right: 8px;
+    }
+  }
+}
+
+// mark-history
+.mark-history {
+  min-width: 298px;
+  width: 298px;
+  padding: 16px;
+  font-size: var(--app-secondary-font-size);
+  background-color: #e5f4ff;
+  overflow-y: auto;
+  height: calc(100vh - 56px);
+  transition: margin-left 0.5s;
+
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+
+  &.show {
+    margin-left: 0;
+  }
+  &.hide {
+    margin-left: -290px;
+  }
+
+  .order-icons {
+    position: relative;
+    width: 12px;
+    .order-icon {
+      color: #999;
+      position: absolute;
+      top: 50%;
+      transform: translateY(-15%);
+
+      &:first-child {
+        transform: translateY(-85%);
+      }
+
+      &.active {
+        color: var(--app-main-text-color);
+      }
+    }
+  }
+
+  .history-header {
+    flex-grow: 0;
+    flex-shrink: 0;
+
+    > h2 {
+      height: 24px;
+      line-height: 24px;
+      font-weight: 500;
+      color: var(--color-text-dark);
+      font-size: 16px;
+      margin-bottom: 16px;
+    }
+
+    .header-select {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+
+      .ant-select-selector {
+        background: linear-gradient(180deg, #f4f4f4 0%, #e5e5e5 100%);
+        border: 1px solid;
+        border-color: #d9d9d9;
+        border-radius: 6px 0px 0px 6px;
+      }
+
+      .ant-input-search-button {
+        background: linear-gradient(180deg, #ffffff 0%, #e5e5e5 100%);
+        border: 1px solid;
+        border-color: #d9d9d9;
+        border-radius: 0px 6px 6px 0px !important;
+      }
+      .ant-select-arrow {
+        .anticon {
+          background-color: var(--color-text-dark);
+          clip-path: polygon(50% 50%, 10% 0%, 90% 0%);
+          transform: translateY(35%);
+        }
+      }
+    }
+  }
+  .history-table-head {
+    flex-grow: 0;
+    flex-shrink: 0;
+    padding: 0 8px;
+    margin: 16px -8px 4px;
+  }
+  .table-head-body {
+    display: flex;
+    flex-grow: 0;
+    flex-shrink: 0;
+    align-items: center;
+    justify-content: space-between;
+    border-bottom: 1px solid #b8cade;
+    padding: 0 0 8px 0;
+  }
+  // .history-table-body {
+  // }
+  .history-table-row {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    height: 30px;
+    line-height: 30px;
+    border-radius: 6px;
+    margin: 4px 0;
+    padding: 0 8px;
+    font-weight: 500;
+    cursor: pointer;
+
+    &:hover {
+      color: var(--app-score-color);
+    }
+
+    &.current-task {
+      background-color: var(--app-score-color);
+      color: white;
+    }
+  }
+
+  .history-body {
+    flex-grow: 2;
+    overflow-x: hidden;
+    overflow-y: auto;
+    margin: 0 -8px;
+    font-size: 14px;
+  }
+
+  .history-footer {
+    flex-grow: 0;
+    flex-shrink: 0;
+    font-size: 14px;
+
+    padding-top: 16px;
+    border-top: 1px solid #b8cade;
+
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+
+    .ant-btn {
+      padding: 7px;
+      font-size: 14px;
+      background: #ffffff;
+      border-color: #e5e5e5;
+      border-radius: 6px;
+      min-width: 32px;
+      .anticon {
+        vertical-align: text-top;
+      }
+    }
+    .ant-input {
+      border-radius: 6px;
+    }
+  }
+}
+
+// ant
+.ant-modal {
+  .ant-modal-content {
+    border-radius: 6px;
+  }
+  .ant-modal-header {
+    padding: 16px 20px;
+    border-radius: 6px 6px 0 0;
+  }
+  .ant-modal-body {
+    padding: 16px 20px;
+  }
+  .ant-modal-footer {
+    padding: 16px 20px;
+  }
+}
+button.ant-btn {
+  border-radius: 4px;
+}