Exam-Marking.vue 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. <template>
  2. <div class="exam-train-exam-marking-edit-container layout-padding">
  3. <div class="exam-train-exam-marking-edit-container-box pd20 h100">
  4. <splitpanes :horizontal="false">
  5. <pane min-size="16" max-size="25" size="16" class="left-container">
  6. <p class="border-title mb20">考试人员</p>
  7. <el-scrollbar ref="scrollBarRef" style="height: calc(100% - 55px)" always>
  8. <el-auto-resizer class="table">
  9. <template #default="{ height }">
  10. <el-skeleton :loading="state.userLoading" animated :rows="10">
  11. <template #default>
  12. <el-tree-v2
  13. :data="state.userList"
  14. highlight-current
  15. :expand-on-click-node="false"
  16. node-key="userId"
  17. :props="{ children: 'children', label: 'userName' }"
  18. @node-click="handleNodeClick"
  19. :current-node-key="state.userList[0]?.id"
  20. ref="treeRef"
  21. :item-size="36"
  22. empty-text="暂无数据"
  23. :height="height"
  24. >
  25. <template #default="{ node }">
  26. <span>{{ node.label }}</span>
  27. </template>
  28. </el-tree-v2>
  29. </template>
  30. </el-skeleton>
  31. </template>
  32. </el-auto-resizer>
  33. </el-scrollbar>
  34. </pane>
  35. <pane class="right-container">
  36. <p class="border-title mb20">阅卷试卷</p>
  37. <div style="overflow: hidden; width: 100%; height: 100%; flex: 1">
  38. <el-scrollbar style="height: calc(100% - 32px)" always v-loading="state.loading">
  39. <div class="h100 w100 exam-container">
  40. <template v-if="state.examList.length">
  41. <div v-for="item in state.examList" :key="item.id" class="exam-item">
  42. <p class="exam-item-questionName">
  43. 题目:{{ item.title }} <span v-if="item.questionScore">({{ item.questionScore }}分)</span>
  44. </p>
  45. <p class="exam-item-answer">回答答案:{{ item.answer }}</p>
  46. <el-divider></el-divider>
  47. <p class="exam-item-referenceAnswer">参考答案:{{ item.correctAnswer }}</p>
  48. <div class="exam-item-score">
  49. 分数:
  50. <span v-if="isView">{{ item.score }}</span>
  51. <el-input-number
  52. v-model="item.score"
  53. :step="1"
  54. :precision="0"
  55. :min="0"
  56. :max="item.questionScore"
  57. placeholder="请填写分数"
  58. style="width: 200px"
  59. v-else
  60. ></el-input-number>
  61. </div>
  62. <el-divider></el-divider>
  63. </div>
  64. </template>
  65. <el-empty description="该考试人员暂未考试完成" v-else></el-empty>
  66. </div>
  67. </el-scrollbar>
  68. </div>
  69. <div class="flex-center-between flex-center-align">
  70. <el-text type="danger" tag="b" class="mr20" v-if="!isView">温馨提示:保存仅保存当前用户的试卷分数</el-text>
  71. <span v-else></span>
  72. <div>
  73. <el-button type="primary" @click="onSave(ruleFormRef)" :loading="state.loading" v-if="!isView && state.examList.length">保存</el-button>
  74. <!-- <el-button type="primary" @click="onSubmit" :loading="state.loading">提交</el-button>-->
  75. <el-button class="default-button" @click="onCancel" :loading="state.loading">取消 </el-button>
  76. </div>
  77. </div>
  78. </pane>
  79. </splitpanes>
  80. </div>
  81. </div>
  82. </template>
  83. <script setup lang="ts" name="examTrainExamMarkingEdit">
  84. import { computed, onMounted, reactive, ref } from 'vue';
  85. import { ElMessage } from 'element-plus';
  86. import mittBus from '@/utils/mitt';
  87. import { useRoute, useRouter } from 'vue-router';
  88. import { Splitpanes, Pane } from 'splitpanes';
  89. import 'splitpanes/dist/splitpanes.css';
  90. import { throttle } from '@/utils/tools';
  91. import { batchMarking, getMarkingDetailByUser, getMarkingDetailUser } from '@/api/examTrain/marking';
  92. // 定义变量内容
  93. const state = reactive<any>({
  94. loading: false,
  95. userList: [], // 用户列表
  96. queryParams: {},
  97. userLoading: false,
  98. UserId: null, // 用户ID
  99. examList: [], // 试卷列表
  100. });
  101. const route = useRoute(); // 获取路由
  102. const router = useRouter(); // 路由跳转
  103. const ruleFormRef = ref<any>(); // 表单ref
  104. // 点击人员查询试卷
  105. const handleNodeClick = (data: any) => {
  106. state.UserId = data.userId;
  107. getExamManageDetailData();
  108. };
  109. // 获取用户列表
  110. const routeParams = route.params;
  111. const getUserList = async () => {
  112. state.userLoading = true;
  113. try {
  114. const { result } = await getMarkingDetailUser({ ExamId: route.params.examId });
  115. state.userList = result ?? [];
  116. state.userLoading = false;
  117. state.UserId = result[0]?.userId;
  118. await getExamManageDetailData();
  119. } catch (error) {
  120. console.error(error);
  121. state.userLoading = false;
  122. }
  123. };
  124. // 根据用户获取试卷内容
  125. const getExamManageDetailData = async () => {
  126. state.loading = true;
  127. try {
  128. const { result } = await getMarkingDetailByUser({ ExamId: route.params.examId, UserId: state.UserId });
  129. state.examList = result ?? [];
  130. state.loading = false;
  131. } catch (error) {
  132. console.error(error);
  133. state.loading = false;
  134. }
  135. };
  136. // 保存提交
  137. const onSave = throttle(() => {
  138. // 校验阅卷是否完成
  139. for (const item of state.examList) {
  140. if (item.score === undefined || item.score === null) {
  141. ElMessage.warning(`请填写【${item.title}】的分数`);
  142. return;
  143. }
  144. }
  145. const request = state.examList.map((item: any) => {
  146. return {
  147. userExamItemId: item.id,
  148. score: item.score,
  149. };
  150. });
  151. state.loading = true;
  152. batchMarking({ items: request })
  153. .then(() => {
  154. state.loading = false;
  155. handleSuccess();
  156. })
  157. .catch((error) => {
  158. console.error(error);
  159. state.loading = false;
  160. });
  161. }, 300);
  162. // 取消
  163. const onCancel = () => {
  164. mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 1, ...route }));
  165. mittBus.emit('clearCache', 'examTrainExamMarking');
  166. router.push({
  167. path: '/examTrain/exam/marking',
  168. });
  169. };
  170. // 是否查看
  171. const isView = computed(() => {
  172. return route.params.isView === 'true';
  173. });
  174. const handleSuccess = () => {
  175. state.loading = false;
  176. ElMessage.success('保存成功');
  177. };
  178. onMounted(() => {
  179. state.queryParams.ExamId = routeParams.examId;
  180. getUserList();
  181. });
  182. </script>
  183. <style lang="scss" scoped>
  184. .exam-train-exam-marking-edit-container {
  185. .exam-train-exam-marking-edit-container-box {
  186. background-color: var(--el-color-white);
  187. overflow: hidden;
  188. }
  189. .right-container {
  190. height: 100%;
  191. display: flex;
  192. flex-direction: column;
  193. }
  194. }
  195. :deep(.el-tree-node__content) {
  196. height: 40px;
  197. }
  198. .exam-container {
  199. .exam-item {
  200. margin-bottom: 20px;
  201. &-questionName {
  202. font-size: 16px;
  203. margin-bottom: 10px;
  204. }
  205. &-answer {
  206. text-indent: 1em;
  207. margin-bottom: 10px;
  208. }
  209. &-referenceAnswer {
  210. display: block;
  211. background-color: var(--hotline-bg-color);
  212. margin: 0 10px 10px 10px;
  213. padding: 10px 5px;
  214. text-indent: 1em;
  215. }
  216. }
  217. }
  218. </style>