MarkHeader.vue 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. <template>
  2. <div v-if="store.setting && store.setting.subject.name" class="mark-header">
  3. <div class="mark-header-part">
  4. <div
  5. :class="['header-menu', { 'is-toggled': store.historyOpen }]"
  6. @click="store.toggleHistory"
  7. >
  8. <img class="header-icon" src="@/assets/icons/icon-left-menu.svg" />回评
  9. </div>
  10. <div class="header-subject" :title="store.setting.subject.name">
  11. {{
  12. `${store.setting.subject.code ?? ""}-${
  13. store.setting.subject.name ?? ""
  14. }`
  15. }}
  16. </div>
  17. <a-tooltip overlayClassName="mark-tooltip">
  18. <template #title>
  19. 问题卷 {{ store.status.problemCount }}
  20. <br />
  21. 待仲裁 {{ store.status.arbitrateCount }}
  22. </template>
  23. <div class="header-programs">
  24. <img
  25. class="header-icon"
  26. src="@/assets/icons/icon-problems.svg"
  27. @mouseover="questionMarkShouldChange = false"
  28. />
  29. </div>
  30. </a-tooltip>
  31. <div class="header-secret">
  32. <div class="header-noun">
  33. <span>编号:</span>
  34. <span>
  35. {{ store.currentTask?.secretNumber ?? "-" }}
  36. </span>
  37. </div>
  38. <div
  39. v-if="
  40. store.currentTask &&
  41. store.currentTask.objectiveScore !== null &&
  42. !!store.setting?.showObjectiveScore
  43. "
  44. class="header-noun"
  45. >
  46. <span>客观分:</span>
  47. <span>
  48. {{ store.currentTask.objectiveScore }}
  49. </span>
  50. </div>
  51. <div
  52. v-if="
  53. props.showTotalScore &&
  54. store.currentTask &&
  55. store.currentTask.objectiveScore !== null
  56. "
  57. class="header-noun"
  58. >
  59. <span>成绩:</span>
  60. <span> {{ totalScore }} </span>
  61. </div>
  62. </div>
  63. <div v-show="store.status.totalCount" class="header-total">
  64. <span class="header-noun">
  65. <span>已评:</span>
  66. <transition-group name="count-animation" tag="span">
  67. <span :key="store.status.personCount || 0">
  68. {{ store.status.personCount }}
  69. </span>
  70. </transition-group>
  71. </span>
  72. <span v-if="store.setting.topCount" class="header-noun">
  73. <span>分配:</span>
  74. <span>{{ store.setting.topCount ?? "-" }}</span>
  75. </span>
  76. <span class="header-noun">
  77. <span>未评:</span>
  78. <transition-group name="count-animation" tag="span">
  79. <span :key="todoCount || 0">
  80. {{ todoCount }}
  81. </span>
  82. </transition-group>
  83. </span>
  84. <span class="header-noun">
  85. <span>进度:</span>
  86. <transition-group name="count-animation" tag="span">
  87. <span :key="progress || '-'"> {{ progress }}% </span>
  88. </transition-group>
  89. </span>
  90. </div>
  91. </div>
  92. <div class="mark-header-part">
  93. <a-tooltip overlayClassName="mark-tooltip">
  94. <template #title>
  95. {{
  96. store.setting.startTime > 0
  97. ? $filters.datetimeFilter(store.setting.startTime)
  98. : "-"
  99. }}
  100. <div style="text-align: center">~</div>
  101. {{
  102. store.setting.endTime > 0
  103. ? $filters.datetimeFilter(store.setting.endTime)
  104. : "-"
  105. }}
  106. </template>
  107. <div class="header-text-btn">
  108. <img
  109. class="header-icon"
  110. src="@/assets/icons/icon-times.svg"
  111. />评卷时间段
  112. </div>
  113. </a-tooltip>
  114. <a-dropdown>
  115. <template v-if="!store.setting.forceMode" #overlay>
  116. <a-menu>
  117. <a-menu-item key="1" @click="toggleSettingMode">
  118. {{ exchangeModeName }}
  119. </a-menu-item>
  120. </a-menu>
  121. </template>
  122. <div class="header-text-btn">
  123. <img src="@/assets/icons/icon-track-mode.svg" class="header-icon" />
  124. {{ modeName }}
  125. <CaretDownOutlined v-if="!store.setting.forceMode" class="a-icon" />
  126. </div>
  127. </a-dropdown>
  128. <div
  129. class="header-text-btn"
  130. :title="store.setting.groupTitle + '-' + store.setting.groupNumber"
  131. @click="openSwitchGroupModal"
  132. >
  133. <img class="header-icon" src="@/assets/icons/icon-group.svg" />{{
  134. "分组:" + store.setting.groupNumber
  135. }}
  136. <CaretDownOutlined v-if="store.groups.length > 1" class="a-icon" />
  137. </div>
  138. <div class="header-text-btn header-logout" @click="logout">
  139. <img class="header-icon" src="@/assets/icons/icon-return.svg" />返回
  140. </div>
  141. <a-tooltip placement="bottomRight">
  142. <template #title>弹出给分板</template>
  143. <div
  144. :class="[
  145. 'header-menu',
  146. { 'is-toggled': store.isScoreBoardVisible && store.currentTask },
  147. ]"
  148. @click="store.toggleScoreBoard"
  149. >
  150. <img src="@/assets/icons/icon-right-menu.svg" class="header-icon" />
  151. </div>
  152. </a-tooltip>
  153. </div>
  154. </div>
  155. <MarkChangeProfile ref="changeProfileRef" />
  156. <MarkSwitchGroupDialog ref="switchGroupRef" />
  157. </template>
  158. <script setup lang="ts">
  159. import { doLogout, updateUISetting, clearMarkTask } from "@/api/markPage";
  160. import { watch, watchEffect } from "vue";
  161. import { store } from "@/store/store";
  162. import MarkChangeProfile from "./MarkChangeProfile.vue";
  163. import MarkSwitchGroupDialog from "./MarkSwitchGroupDialog.vue";
  164. import { isNumber } from "lodash-es";
  165. import { Modal } from "ant-design-vue";
  166. import { CaretDownOutlined } from "@ant-design/icons-vue";
  167. const props = defineProps<{ showTotalScore?: boolean }>();
  168. const modeName = $computed(() =>
  169. store.setting.mode === "TRACK" ? "轨迹模式" : "普通模式"
  170. );
  171. const exchangeModeName = $computed(() =>
  172. store.setting.mode === "TRACK" ? "普通模式" : "轨迹模式"
  173. );
  174. async function toggleSettingMode() {
  175. if (store.isTrackMode) {
  176. store.setting.mode = "COMMON";
  177. } else {
  178. store.setting.mode = "TRACK";
  179. }
  180. await updateUISetting(store.setting.mode, store.setting.uiSetting);
  181. const body = document.querySelector("body");
  182. if (body) body.innerHTML = "重新加载中...";
  183. // 等待一秒后,重新加载页面
  184. await new Promise((resolve) => setTimeout(resolve, 1000));
  185. window.location.reload();
  186. }
  187. const progress = $computed(() => {
  188. const { totalCount, markedCount } = store.status;
  189. if (typeof totalCount !== "number" || totalCount === 0) return 0;
  190. let p = markedCount / totalCount;
  191. if (p < 0.01 && markedCount >= 1) p = 0.01;
  192. p = Math.floor(p * 100);
  193. return p;
  194. });
  195. const totalScore = $computed(() => {
  196. return parseFloat(
  197. (
  198. ((Math.max(store.currentTask.objectiveScore || 0, 0) * 100 +
  199. Math.max(store.currentTask.markResult?.markerScore || 0, 0) * 100) |
  200. 0) /
  201. 100
  202. ).toFixed(2)
  203. );
  204. });
  205. const logout = async () => {
  206. await clearMarkTask();
  207. doLogout();
  208. };
  209. let changeProfileRef = $ref<InstanceType<typeof MarkChangeProfile>>();
  210. // 应该是@typescript-eslint/no-unsafe-call不完美的搞法,导致了下面的调用会出错
  211. type ShowModalFunc = () => void;
  212. // const openProfileModal = () => {
  213. // (changeProfileRef.showModal as ShowModalFunc)();
  214. // };
  215. let switchGroupRef = $ref<InstanceType<typeof MarkSwitchGroupDialog>>();
  216. const openSwitchGroupModal = () => {
  217. (switchGroupRef.showModal as ShowModalFunc)();
  218. };
  219. watchEffect(() => {
  220. if (
  221. isNumber(store.setting.topCount) &&
  222. store.setting.topCount > 0 &&
  223. store.setting.topCount === store.status.personCount
  224. ) {
  225. Modal.confirm({
  226. centered: true,
  227. mask: true,
  228. zIndex: 6000,
  229. maskStyle: { opacity: 0.97 },
  230. content: `分配任务份已完成,是否继续?`,
  231. okText: "继续",
  232. cancelText: "退出",
  233. onCancel: logout,
  234. });
  235. }
  236. });
  237. const todoCount = $computed(() =>
  238. typeof store.status.totalCount === "number"
  239. ? store.status.totalCount -
  240. store.status.markedCount -
  241. store.status.problemCount -
  242. store.status.arbitrateCount
  243. : "-"
  244. );
  245. let questionMarkShouldChange = $ref(false);
  246. watch(
  247. () => [store.status.problemCount, store.status.arbitrateCount],
  248. () => {
  249. if (!store.status.problemCount && !store.status.arbitrateCount) return;
  250. questionMarkShouldChange = true;
  251. setTimeout(() => {
  252. questionMarkShouldChange = false;
  253. }, 11 * 1000);
  254. }
  255. );
  256. </script>