index.vue 20 KB

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