index.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. <template>
  2. <div v-loading="loading" class="teacher-task-detail">
  3. <TaskTop :item-info="itemInfo" :is-exercises="isExercises" type="teacher" @viewFile="showFileVisible" />
  4. <div class="teacher-task-detail-main">
  5. <div class="student-finish-situation">
  6. <div>{{ $t('Key308') }}</div>
  7. <div class="student-list" :style="{ height: student_list_height + 'px' }">
  8. <ul>
  9. <li
  10. v-for="item in student_list"
  11. :key="item.student_id"
  12. :class="['student-item', { active: item.student_id === curStudentId }]"
  13. @click="getTaskStudentExecuteInfo(item.student_id)"
  14. >
  15. <span>{{ item.student_name }}</span>
  16. <svg-icon v-if="!isExercises && item.is_finished === 'true'" icon-class="check-mark" />
  17. <span v-if="isExercises && item.exercise_info.is_finish === 'true'" class="submitted">已提交</span>
  18. </li>
  19. </ul>
  20. </div>
  21. </div>
  22. <div class="finish-detail">
  23. <template v-if="isExercises">
  24. <div ref="situation" class="exercise">
  25. <div class="title">测试报告</div>
  26. <div class="color-list">
  27. <div v-for="{ label, color } in questionColorList" :key="label" class="color-item">
  28. <span class="color" :style="{ backgroundColor: color }"></span>
  29. <span>{{ label }}</span>
  30. </div>
  31. </div>
  32. <div class="exercise-details">
  33. <div class="info-item">
  34. <span class="label">完成时间</span>
  35. <span class="exercise-info">{{ exercise_info.answer_record?.finish_time }}</span>
  36. </div>
  37. <div class="info-item">
  38. <span class="label">耗时</span>
  39. <span class="exercise-info">{{
  40. secondFormatConversion(exercise_info.answer_record?.answer_duration, 'chinese')
  41. }}</span>
  42. </div>
  43. <div class="info-item">
  44. <span class="label">正确</span>
  45. <span class="exercise-info">{{ exercise_info.answer_record?.right_count }}</span>
  46. </div>
  47. <div class="info-item">
  48. <span class="label">错误</span>
  49. <span class="exercise-info">{{ exercise_info.answer_record?.error_count }}</span>
  50. </div>
  51. <div class="info-item">
  52. <span class="label">正确率</span>
  53. <span class="exercise-info">{{ exercise_info.answer_record?.right_percent }}%</span>
  54. </div>
  55. </div>
  56. <!-- 问题列表 -->
  57. <div class="question-list">
  58. <span
  59. v-for="({ question_id, is_objective, answer_status }, i) in exercise_info.question_list"
  60. :key="question_id"
  61. :class="['question-list-item', { subjectivity: is_objective === 'false', error: answer_status === 2 }]"
  62. @click="
  63. exerciseLink(
  64. exercise_info.answer_record.exercise_share_record_id,
  65. exercise_info.answer_record.exercise_answer_record_id,
  66. i
  67. )
  68. "
  69. >
  70. {{ i + 1 }}
  71. </span>
  72. </div>
  73. <div class="total-score">
  74. <div>总得分</div>
  75. <div>{{ exercise_info.answer_record?.total_score }}</div>
  76. </div>
  77. <div class="footer">
  78. <el-button type="primary" @click="remarkTaskStudentExecuteInfo_Teacher">批改完成</el-button>
  79. <el-button>重发</el-button>
  80. <el-button v-if="student_list.length > 1" @click="next">
  81. {{ buttonName }} <i class="el-icon-right"></i>
  82. </el-button>
  83. </div>
  84. </div>
  85. </template>
  86. <template v-else>
  87. <div class="student-info">
  88. <div>
  89. <el-avatar :src="curFinishDetail.student_image_url" :size="32" icon="el-icon-user-solid" />
  90. <span class="student-info-name">{{ curFinishDetail.student_name }}</span>
  91. </div>
  92. <span class="finish-time">{{ curFinishDetail.finish_time_view_txt }}</span>
  93. </div>
  94. <div ref="situation" class="finish-situation">
  95. <div class="title">
  96. {{ $t('Key312') }}
  97. </div>
  98. <div class="courseware-list">
  99. <el-tag
  100. v-for="item in curFinishDetail.courseware_list"
  101. :key="item.courseware_id"
  102. color="#fff"
  103. :title="item.courseware_name"
  104. @click="showCompletionView(item.courseware_id, item.is_finished, item.group_id_selected_info)"
  105. >
  106. <div class="courseware">
  107. <svg-icon icon-class="courseware" />
  108. <span class="courseware_name nowrap-ellipsis">{{ item.courseware_name }}</span>
  109. <svg-icon v-if="item.is_finished === 'true'" class="check-mark" icon-class="check-mark-circle" />
  110. </div>
  111. </el-tag>
  112. </div>
  113. <div class="title">
  114. {{ $t('Key313') }}
  115. </div>
  116. <div>
  117. <el-tag v-for="item in accessory_list" :key="item.file_id" color="#fff" :title="item.file_name">
  118. <span @click="showFileVisible(item.file_name, item.file_id)">{{ item.file_name }}</span>
  119. </el-tag>
  120. </div>
  121. <!-- 作业列表 -->
  122. <template v-if="is_enable_homework">
  123. <div class="title">
  124. {{ $t('Key314') }}
  125. </div>
  126. <div>
  127. <el-tag
  128. v-for="item in curFinishDetail.homework_list"
  129. :key="item.file_id"
  130. color="#fff"
  131. :title="item.file_name"
  132. >
  133. <span @click="showFileVisible(item.file_name, item.file_id)">{{ item.file_name }}</span>
  134. </el-tag>
  135. </div>
  136. </template>
  137. <!-- 学员留言 -->
  138. <template v-if="is_enable_message">
  139. <div class="title">
  140. {{ $t('Key315') }}
  141. </div>
  142. <div>{{ curFinishDetail.student_message }}</div>
  143. </template>
  144. <template v-if="teachingType === 10 && is_enable_KHPJ">
  145. <div class="title">
  146. {{ $t('Key316') }}
  147. </div>
  148. <div>{{ student_remark }}</div>
  149. <div class="title">
  150. <span>{{ $t('Key317') }}</span>
  151. <el-rate v-model="student_score" disabled />
  152. </div>
  153. </template>
  154. <div class="title">
  155. <span>{{ $t('Key318') }}</span>
  156. <el-rate v-model="teacher_score" />
  157. </div>
  158. <div>
  159. <el-input v-model="teacher_remark" type="textarea" resize="none" :rows="6" maxlength="3000" />
  160. </div>
  161. <div class="confirm">
  162. <el-button type="primary" @click="remarkTaskStudentExecuteInfo_Teacher">
  163. {{ $t('Key319') }}
  164. </el-button>
  165. <el-button v-if="student_list.length > 1" @click="next">
  166. {{ buttonName }} <i class="el-icon-right"></i>
  167. </el-button>
  168. </div>
  169. </div>
  170. </template>
  171. </div>
  172. </div>
  173. <CompletionView
  174. :task-id="id"
  175. :cur-student-id="curStudentId"
  176. :cur-courseware-id="curCoursewareId"
  177. :dialog-visible="dialogVisible"
  178. :preview-group-id="previewGroupId"
  179. @dialogClose="dialogClose"
  180. />
  181. <ShowFile :visible="visible" :file-name="curFileName" :file-id="curFileId" @close="dialogShowFileClose" />
  182. </div>
  183. </template>
  184. <script>
  185. export default {
  186. name: 'TaskDetailsTeacher'
  187. };
  188. </script>
  189. <script setup>
  190. import { ref, inject, nextTick, computed } from 'vue';
  191. import { GetTaskInfo, GetTaskStudentExecuteInfo, RemarkTaskStudentExecuteInfo_Teacher } from '@/api/course';
  192. import { useRoute } from 'vue-router/composables';
  193. import { Message } from 'element-ui';
  194. import { useShowFile } from '@/common/show_file/index';
  195. import { useExerciseTeacher, questionColorList } from './exercise';
  196. import { secondFormatConversion } from '@/utils';
  197. import CompletionView from '@/components/course/CompletionView.vue';
  198. import ShowFile from '@/common/show_file/index.vue';
  199. import TaskTop from '../TaskTop.vue';
  200. const $t = inject('$t');
  201. const route = useRoute();
  202. let id = route.params.id;
  203. const { exerciseLink } = useExerciseTeacher();
  204. // 任务详情
  205. let itemInfo = ref({});
  206. let teachingType = ref('');
  207. let isExercises = computed(() => {
  208. return teachingType.value === 15;
  209. });
  210. let accessory_list = ref([]);
  211. let student_list = ref([]);
  212. let loading = ref(true);
  213. // 开启课后评价
  214. let is_enable_KHPJ = ref(false);
  215. let is_enable_homework = ref(false);
  216. let is_enable_message = ref(false);
  217. GetTaskInfo({
  218. id,
  219. is_contain_cs_item_learning_material: true,
  220. is_contain_student: true
  221. })
  222. .then(
  223. ({
  224. name,
  225. teaching_type,
  226. course_name,
  227. courseware_list,
  228. cs_item_name,
  229. accessory_list: accList,
  230. cs_item_learning_material_list,
  231. time_space_view_txt,
  232. student_list: stuList,
  233. is_enable_KHPJ: isKHPJ,
  234. is_enable_homework: isHomework,
  235. is_enable_message: isMessage,
  236. content,
  237. cs_item_begin_time,
  238. cs_item_end_time
  239. }) => {
  240. itemInfo.value = {
  241. name,
  242. time_space_view_txt,
  243. course_name,
  244. cs_item_name,
  245. cs_item_learning_material_list,
  246. courseware_list,
  247. content,
  248. cs_item_time: `${cs_item_begin_time} ~ ${cs_item_end_time}`
  249. };
  250. teachingType.value = teaching_type;
  251. accessory_list.value = accList;
  252. is_enable_KHPJ.value = isKHPJ === 'true';
  253. is_enable_homework.value = isHomework === 'true';
  254. is_enable_message.value = isMessage === 'true';
  255. student_list.value = stuList;
  256. if (student_list.value.length > 0) getTaskStudentExecuteInfo(student_list.value[0].student_id);
  257. }
  258. )
  259. .finally(() => {
  260. loading.value = false;
  261. });
  262. const situation = ref();
  263. // 当前学生完成详情
  264. let curFinishDetail = ref({
  265. student_name: '',
  266. student_image_url: '',
  267. courseware_list: [],
  268. homework_list: [],
  269. student_message: '',
  270. finish_time_view_txt: ''
  271. });
  272. let curStudentId = ref('');
  273. let teacher_remark = ref('');
  274. let teacher_score = ref(0);
  275. let student_remark = ref('');
  276. let student_score = ref(0);
  277. let student_list_height = ref(490);
  278. let exercise_info = ref({}); // 练习题信息
  279. function getTaskStudentExecuteInfo(student_id) {
  280. GetTaskStudentExecuteInfo({
  281. task_id: id,
  282. student_id
  283. }).then(
  284. ({
  285. courseware_list,
  286. homework_list,
  287. student_message,
  288. student_name,
  289. student_image_url,
  290. finish_time_view_txt,
  291. teacher_remark: tRemake,
  292. teacher_score: tScore,
  293. student_remark: sRemake,
  294. student_score: stuScore,
  295. exercise_info: exerciseInfo
  296. }) => {
  297. curStudentId.value = student_id;
  298. teacher_remark.value = tRemake;
  299. teacher_score.value = tScore;
  300. student_remark.value = sRemake;
  301. student_score.value = stuScore;
  302. curFinishDetail.value = {
  303. courseware_list,
  304. homework_list,
  305. student_message,
  306. student_name,
  307. student_image_url,
  308. finish_time_view_txt
  309. };
  310. exercise_info.value = exerciseInfo;
  311. nextTick(() => {
  312. student_list_height.value = situation.value.clientHeight;
  313. });
  314. }
  315. );
  316. }
  317. let previewGroupId = ref('[]');
  318. let dialogVisible = ref(false);
  319. let curCoursewareId = ref('');
  320. function showCompletionView(id, is_finished, group_id_selected_info) {
  321. if (is_finished === 'false') return Message.warning($t('Key338'));
  322. previewGroupId.value = group_id_selected_info.length <= 0 ? '[]' : group_id_selected_info;
  323. curCoursewareId.value = id;
  324. dialogVisible.value = true;
  325. }
  326. function dialogClose() {
  327. curCoursewareId.value = '';
  328. dialogVisible.value = false;
  329. }
  330. let { visible, curFileId, curFileName, dialogShowFileClose, showFileVisible } = useShowFile();
  331. function remarkTaskStudentExecuteInfo_Teacher() {
  332. RemarkTaskStudentExecuteInfo_Teacher({
  333. task_id: id,
  334. student_id: curStudentId.value,
  335. teacher_score: teacher_score.value,
  336. teacher_remark: teacher_remark.value
  337. }).then(() => {
  338. Message.success(isExercises.value ? '批改成功' : $t('Key324'));
  339. });
  340. }
  341. let buttonName = computed(() => {
  342. const list = student_list.value;
  343. if (list.length <= 0) return '';
  344. return list[list.length - 1].student_id === curStudentId.value ? $t('Key618') : $t('Key619');
  345. });
  346. function next() {
  347. const list = student_list.value;
  348. if (list.length <= 0) return Message.warning($t('Key325'));
  349. const curIndex = list.findIndex(({ student_id }) => student_id === curStudentId.value);
  350. const nextStudentId = (list.length - 1 === curIndex ? list[curIndex - 1] : list[curIndex + 1]).student_id;
  351. if (nextStudentId) getTaskStudentExecuteInfo(nextStudentId);
  352. }
  353. </script>
  354. <style lang="scss">
  355. @import '~@/styles/mixin';
  356. $bor-color: #d9d9d9;
  357. .teacher-task-detail {
  358. @include container;
  359. @include dialog;
  360. min-height: calc(100vh - 130px);
  361. .el-tag {
  362. @include el-tag;
  363. margin: 0 8px 6px 0;
  364. border-color: $bor-color;
  365. border-radius: 4px;
  366. > span {
  367. cursor: pointer;
  368. }
  369. }
  370. &-main {
  371. display: flex;
  372. min-height: calc(100vh - 390px);
  373. margin-top: 16px;
  374. background-color: #fff;
  375. border-radius: 8px;
  376. .student-finish-situation {
  377. flex: 3;
  378. padding: 24px 0;
  379. > div:first-child {
  380. padding: 0 32px;
  381. margin-bottom: 24px;
  382. font-weight: 700;
  383. }
  384. // 学员列表
  385. .student-list {
  386. overflow: auto;
  387. ul {
  388. cursor: pointer;
  389. .student-item {
  390. display: flex;
  391. justify-content: space-between;
  392. padding: 8px 32px;
  393. &.active {
  394. background-color: #f2f2f2;
  395. }
  396. .submitted {
  397. position: relative;
  398. left: 16px;
  399. color: #00c264;
  400. }
  401. }
  402. }
  403. }
  404. }
  405. // 完成详情
  406. .finish-detail {
  407. flex: 7;
  408. border-left: 1px solid #dbdbdb;
  409. // 练习题
  410. .exercise {
  411. display: flex;
  412. flex-direction: column;
  413. row-gap: 24px;
  414. padding: 24px 40px;
  415. .color-list {
  416. display: flex;
  417. column-gap: 24px;
  418. .color-item {
  419. display: flex;
  420. column-gap: 8px;
  421. align-items: center;
  422. .color {
  423. width: 16px;
  424. height: 16px;
  425. border-radius: 50%;
  426. }
  427. }
  428. }
  429. // 练习题详情
  430. .exercise-details {
  431. display: flex;
  432. column-gap: 80px;
  433. padding: 16px 40px;
  434. background-color: #f7f7f7;
  435. .info-item {
  436. display: flex;
  437. flex-direction: column;
  438. row-gap: 12px;
  439. .label {
  440. font-size: 14px;
  441. color: #949494;
  442. white-space: nowrap;
  443. }
  444. .exercise-info {
  445. font-size: 20px;
  446. font-weight: bold;
  447. color: #333;
  448. }
  449. }
  450. }
  451. // 题目列表
  452. .question-list {
  453. display: flex;
  454. flex-wrap: wrap;
  455. gap: 24px;
  456. width: 770px;
  457. margin-bottom: 40px;
  458. &-item {
  459. width: 56px;
  460. height: 40px;
  461. padding: 8px;
  462. line-height: 24px;
  463. text-align: center;
  464. cursor: pointer;
  465. background-color: #f0f0f0;
  466. border-radius: 20px;
  467. &.error {
  468. color: #fff;
  469. background-color: #f2555a;
  470. }
  471. &.subjectivity {
  472. background-color: #fef2a4;
  473. }
  474. }
  475. }
  476. .total-score {
  477. display: flex;
  478. flex-direction: column;
  479. row-gap: 8px;
  480. font-size: 14px;
  481. :first-child {
  482. color: #949494;
  483. }
  484. :last-child {
  485. font-weight: bold;
  486. color: #333;
  487. }
  488. }
  489. .footer {
  490. .el-button {
  491. font-weight: bold;
  492. }
  493. }
  494. }
  495. .student-info {
  496. display: flex;
  497. justify-content: space-between;
  498. height: 89px;
  499. padding: 28px 32px;
  500. line-height: 32px;
  501. border-bottom: 1px solid #dbdbdb;
  502. &-name {
  503. display: inline-block;
  504. height: 32px;
  505. margin-left: 24px;
  506. line-height: 32px;
  507. vertical-align: text-bottom;
  508. }
  509. .finish-time {
  510. color: #999;
  511. }
  512. }
  513. // 完成情况
  514. .finish-situation {
  515. width: 100%;
  516. padding: 24px 32px;
  517. .el-rate {
  518. display: inline-block;
  519. margin-left: 42px;
  520. }
  521. .title {
  522. font-size: 18px;
  523. font-weight: bold;
  524. + div {
  525. padding: 16px 0;
  526. }
  527. }
  528. .courseware-list {
  529. .el-tag {
  530. cursor: pointer;
  531. .courseware {
  532. overflow: hidden;
  533. .svg-icon {
  534. margin-right: 12px;
  535. font-size: 18px;
  536. vertical-align: super;
  537. }
  538. .check-mark {
  539. position: relative;
  540. margin: 0 0 0 12px;
  541. }
  542. &_name {
  543. display: inline-block;
  544. max-width: 120px;
  545. }
  546. }
  547. }
  548. }
  549. // 学员详情按钮
  550. .confirm {
  551. display: flex;
  552. justify-content: space-between;
  553. .el-button {
  554. width: 110px;
  555. }
  556. }
  557. }
  558. }
  559. }
  560. }
  561. </style>