answerTheResult.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  1. <template>
  2. <div class="answerTheResult">
  3. <div class="atr_detail">
  4. <div class="atr_d_btn" @click="lookDetail()">{{isShowDialog ? '查看题目' : '查看结果'}}</div>
  5. <div class="atr_d_msg">
  6. <div>参与人数</div>
  7. <span
  8. >{{ workArrayLength }}/{{
  9. workArrayLength + unsubmittedStudentsLength
  10. }}</span
  11. >
  12. </div>
  13. <div class="atr_d_msg" v-if="workDetail && workDetail.type === '45'">
  14. <div>正确率</div>
  15. <span v-if="choiceQuestionListData[workIndex]">{{choiceQuestionListData[workIndex].accuracyRate}}%({{choiceQuestionListData[workIndex].yes}}/{{choiceQuestionListData[workIndex].all}})</span>
  16. </div>
  17. <div class="atr_d_msg" v-if="choiceQuestionAnswer">
  18. <div>正确答案</div>
  19. <span style="color: #03ae2b">{{ choiceQuestionAnswer }}</span>
  20. </div>
  21. <span class="atr_d_line" v-if="props.unsubmittedStudents && props.unsubmittedStudents?.length > 0"></span>
  22. <div class="no_submit" v-if="props.unsubmittedStudents && props.unsubmittedStudents?.length > 0">
  23. <div>未提交人员</div>
  24. <img
  25. @click="showNoSubmitDetail = !showNoSubmitDetail"
  26. :class="{ no_submit_active: !showNoSubmitDetail }"
  27. src="../../../assets/img/arrow_up.png"
  28. />
  29. </div>
  30. <div
  31. class="no_submitList"
  32. v-if="props.unsubmittedStudents && props.unsubmittedStudents?.length > 0"
  33. :class="{ no_submitList_active: showNoSubmitDetail }"
  34. >
  35. <div
  36. v-for="(student, idx) in props.unsubmittedStudents"
  37. :key="student.id ?? idx"
  38. >
  39. {{ student.name ?? "" }}
  40. </div>
  41. </div>
  42. <span class="atr_d_line" v-if="props.workArray && props.workArray?.length > 0"></span>
  43. <div class="is_submit" v-if="props.workArray && props.workArray?.length > 0">
  44. <div>已提交人员</div>
  45. <img
  46. @click="showIsSubmitDetail = !showIsSubmitDetail"
  47. :class="{ is_submit_active: !showIsSubmitDetail }"
  48. src="../../../assets/img/arrow_up.png"
  49. />
  50. </div>
  51. <div
  52. class="is_submitList"
  53. v-if="props.workArray && props.workArray?.length > 0"
  54. :class="{ is_submitList_active: showIsSubmitDetail }"
  55. >
  56. <div
  57. v-for="(student, idx) in props.workArray"
  58. :key="student.id ?? idx"
  59. @click="emit('openWorkModal', student)"
  60. >
  61. {{ student.name ?? "" }}
  62. </div>
  63. </div>
  64. </div>
  65. <div
  66. class="atr_type45Area"
  67. v-if="
  68. workDetail &&
  69. workDetail.type === '45' &&
  70. choiceQuestionListData[workIndex] &&
  71. choiceQuestionListData[workIndex].choiceUser
  72. "
  73. >
  74. <!-- <div class="atr_t45a_title" >
  75. <span>{{ workIndex+1 }}、{{ choiceQuestionListData[workIndex].type=='1'?'单选题':'多选题' }}:</span>
  76. <span>{{ choiceQuestionListData[workIndex].teststitle }}</span>
  77. <img @click="previewImage(choiceQuestionListData[workIndex].timuList[0].src)" :src="choiceQuestionListData[workIndex].timuList[0].src" v-if="choiceQuestionListData[workIndex].timuList.length>0">
  78. </div> -->
  79. <template
  80. v-for="(op, idx) in choiceQuestionListData[workIndex].choiceUser"
  81. :key="`${workIndex}_${idx}`"
  82. >
  83. <div class="atr_t45a_item">
  84. <div class="atr_t45a_i_top">
  85. <div class="atr_t45a_i_left">
  86. <span>{{ serialNumber[idx] }}</span>
  87. <div>
  88. <img
  89. v-if="op.option.imgType"
  90. :src="op.option.src"
  91. @click="previewImage(op.option.src)"
  92. />
  93. <span v-else>{{ op.option }}</span>
  94. </div>
  95. <span v-if="op.isAnswer">正确</span>
  96. </div>
  97. <img
  98. @click="changeShow(op, idx)"
  99. v-if="op.user.length > 0"
  100. :class="{ show_active: !op.show }"
  101. src="../../../assets/img/arrow_up.png"
  102. />
  103. </div>
  104. <div
  105. class="atr_t45a_i_bottom"
  106. v-if="op.user.length > 0 && op.show"
  107. :class="{ atr_t45a_i_b_active: op.show }"
  108. >
  109. <div
  110. v-for="(name, uIdx) in op.user"
  111. :key="`${workIndex}_${idx}_${uIdx}`"
  112. >
  113. {{ name }}
  114. </div>
  115. </div>
  116. </div>
  117. </template>
  118. <div class="nextAndUpBtn" v-if="choiceQuestionListData.length>1">
  119. <span :class="{no_active:workIndex==0}" @click="changeWorkIndex(0)">上一题</span>
  120. <span :class="{no_active:choiceQuestionListData.length-1<=workIndex}" @click="changeWorkIndex(1)">下一题</span>
  121. </div>
  122. </div>
  123. </div>
  124. <!-- 预览放大(带缩放/拖拽/旋转/工具栏) -->
  125. <previewImageTool ref="previewImageToolRef"/>
  126. </template>
  127. <script lang="ts" setup>
  128. import { ref, computed, watch, inject, type Ref } from 'vue'
  129. import previewImageTool from '../../components/tool/previewImageTool.vue'
  130. import api from '../../../services/course'
  131. interface Props {
  132. workArray?: object[] | null;
  133. unsubmittedStudents?: object[] | null;
  134. workId?: string | null;
  135. slideIndex?:number
  136. toolType?:number
  137. }
  138. const props = withDefaults(defineProps<Props>(), {
  139. workArray: () => [],
  140. unsubmittedStudents: () => [],
  141. workId: '',
  142. toolType: 0,
  143. slideIndex: 0
  144. })
  145. const emit = defineEmits<{
  146. (e: 'openChoiceQuestionDetail', v: number): void,
  147. (e: 'openWorkModal', v:any):void
  148. }>()
  149. const choiceQuestionDetailDialogOpenList = inject<Ref<number[]>>('choiceQuestionDetailDialogOpenList', ref<number[]>([]))
  150. const isShowDialog = computed(() => {
  151. return choiceQuestionDetailDialogOpenList.value.includes(props.slideIndex)
  152. })
  153. // 已提交的作业数量
  154. const workArrayLength = computed(() => {
  155. let _result = 0
  156. if (props.workArray) {
  157. _result = props.workArray.length
  158. }
  159. return _result
  160. })
  161. // 未提交的作业数量
  162. const unsubmittedStudentsLength = computed(() => {
  163. let _result = 0
  164. if (props.unsubmittedStudents) {
  165. _result = props.unsubmittedStudents.length
  166. }
  167. return _result
  168. })
  169. // 选项序号
  170. const serialNumber = ref<string[]>([
  171. 'A',
  172. 'B',
  173. 'C',
  174. 'D',
  175. 'E',
  176. 'F',
  177. 'G',
  178. 'H',
  179. 'I',
  180. 'J',
  181. 'K',
  182. 'L',
  183. 'M',
  184. 'N',
  185. 'O',
  186. 'P',
  187. 'Q',
  188. 'R',
  189. 'S',
  190. 'T',
  191. 'U',
  192. 'V',
  193. 'W',
  194. 'X',
  195. 'Y',
  196. 'Z',
  197. ])
  198. // 第几题
  199. const workIndex = ref<number>(0)
  200. // 是否显示未提交人员
  201. const showNoSubmitDetail = ref<boolean>(false)
  202. const showIsSubmitDetail = ref<boolean>(false)
  203. // 作业详细
  204. const workDetail = ref<any>({})
  205. // 获取作业详细
  206. const getWorkDetail = async () => {
  207. if (props.workId) {
  208. const _res = await api.getWorkDetail({ id: props.workId })
  209. const _data = _res[0][0]
  210. if (_data) {
  211. _data.json = JSON.parse(_data.json)
  212. workDetail.value = _data
  213. choiceQuestionListData.value = choiceQuestionList()
  214. }
  215. }
  216. }
  217. // 选择题题目数组
  218. const choiceQuestionList = () => {
  219. let _result: any[] = []
  220. if (workDetail.value && workDetail.value.type === '45') {
  221. const _workData = workDetail.value.json.testJson
  222. _workData.forEach((item: any, index: number) => {
  223. // 修复 props.workArray 可能为 null 的问题
  224. item.choiceUser = []
  225. item.yes = 0
  226. item.no = 0
  227. item.checkList.forEach((op: any, oidx: number) =>
  228. item.choiceUser.push({
  229. index: serialNumber.value[oidx],
  230. option: op,
  231. user: [],
  232. show: false,
  233. isAnswer: Array.isArray(item.answer)
  234. ? item.answer.includes(oidx)
  235. : item.answer === oidx,
  236. })
  237. )
  238. });
  239. (props.workArray ?? []).forEach(
  240. (studentWork: any, studentIndex: number) => {
  241. const _studentContent: any = JSON.parse(
  242. decodeURIComponent(studentWork.content)
  243. ).testJson
  244. // let _studentContentCorrection = []
  245. // console.log(_workData)
  246. // console.log(_studentContent)
  247. // _studentContentCorrection = _workData.map((i:any) => {
  248. // return _studentContent.find((i2:any) => {
  249. // return (JSON.stringify({testtitle: i2.testtitle, checkList: i2.checkList, timuList: i2.timuList}) === JSON.stringify({testtitle: i.testtitle, checkList: i.checkList, timuList: i.timuList}))
  250. // })
  251. // })
  252. // console.log(_studentContentCorrection)
  253. _studentContent.forEach((test: any, testIndex: number) => {
  254. if (_workData[testIndex] && JSON.stringify({testtitle: test.testtitle, checkList: test.checkList, timuList: test.timuList}) === JSON.stringify({testtitle: _workData[testIndex].testtitle, checkList: _workData[testIndex].checkList, timuList: _workData[testIndex].timuList}) && (test.userAnswer || test.userAnswer === 0)) {
  255. if (Array.isArray(test.userAnswer)) {
  256. test.userAnswer.forEach((ch: number) => {
  257. _workData[testIndex].choiceUser[ch].user.push(studentWork.name)
  258. })
  259. if (
  260. JSON.stringify(
  261. test.userAnswer.sort((a: number, b: number) => a - b)
  262. ) === JSON.stringify(_workData[testIndex].answer.sort((a: number, b: number) => a - b))
  263. ) {
  264. _workData[testIndex].yes += 1
  265. }
  266. else {
  267. _workData[testIndex].no += 1
  268. }
  269. }
  270. else {
  271. _workData[testIndex].choiceUser[test.userAnswer].user.push(
  272. studentWork.name
  273. )
  274. if (test.userAnswer === _workData[testIndex].answer) {
  275. _workData[testIndex].yes += 1
  276. }
  277. else {
  278. _workData[testIndex].no += 1
  279. }
  280. }
  281. }
  282. })
  283. }
  284. )
  285. _workData.forEach((item:any) => {
  286. item.all = item.yes + item.no
  287. if (item.all === 0) {
  288. item.accuracyRate = 0
  289. }
  290. else {
  291. const rate = (item.yes / item.all) * 100
  292. item.accuracyRate = Number.isInteger(rate) ? rate : Number(rate.toFixed(2))
  293. }
  294. })
  295. _result = _workData
  296. }
  297. return _result
  298. }
  299. const choiceQuestionListData = ref<any[]>([])
  300. // 选择题题目正确答案
  301. const choiceQuestionAnswer = computed(() => {
  302. let _result = ''
  303. if (
  304. workDetail.value &&
  305. workDetail.value.type === '45' &&
  306. workDetail.value.json.testJson[workIndex.value]
  307. ) {
  308. const _answer = workDetail.value.json.testJson[workIndex.value].answer
  309. if (Array.isArray(_answer)) {
  310. _result = _answer.map((i: any) => serialNumber.value[i]).join('、')
  311. }
  312. else {
  313. _result = [serialNumber.value[_answer]].join('、')
  314. }
  315. }
  316. return _result
  317. })
  318. // //当前题目正确率
  319. // const choiceQuestionAccuracyRate = computed
  320. // 监听作业Id
  321. watch(
  322. () => props.workId,
  323. (newVal, oldVal) => {
  324. console.log('props.workId变化', { newVal, oldVal })
  325. if (newVal && newVal !== oldVal) {
  326. getWorkDetail()
  327. }
  328. },
  329. { immediate: true }
  330. )
  331. // 监听已提交的作业
  332. watch(() => props.workArray, (newVal, oldVal) => {
  333. if (newVal && newVal !== oldVal) {
  334. choiceQuestionListData.value = choiceQuestionList()
  335. // choiceQuestionAnswerData.value = choiceQuestionAnswer()
  336. }
  337. }, { immediate: true })
  338. // 切换题目
  339. const changeWorkIndex = (type:number) => {
  340. if (type === 0) {
  341. if (workIndex.value === 0) return
  342. workIndex.value -= 1
  343. }
  344. else if (type === 1) {
  345. if (choiceQuestionListData.value.length - 1 <= workIndex.value) return
  346. workIndex.value += 1
  347. }
  348. }
  349. // 切换题目展示所有人选项
  350. const changeShow = (op: any, idx: number) => {
  351. choiceQuestionListData.value[workIndex.value].choiceUser[idx].show = !choiceQuestionListData.value[workIndex.value].choiceUser[idx].show
  352. }
  353. const lookDetail = () => {
  354. console.log(props.toolType)
  355. if ([45, 15, 72, 73].includes(props.toolType)) {
  356. emit('openChoiceQuestionDetail', props.slideIndex)
  357. }
  358. }
  359. const previewImageToolRef = ref<any>(null)
  360. const previewImage = (url:string) => {
  361. previewImageToolRef.value.previewImage(url)
  362. }
  363. defineExpose({
  364. workDetail,
  365. choiceQuestionListData,
  366. workIndex,
  367. workArray: props.workArray,
  368. toolType: props.toolType,
  369. changeWorkIndex
  370. })
  371. </script>
  372. <style lang="scss" scoped>
  373. .answerTheResult {
  374. width: 100%;
  375. height: auto;
  376. max-height: 100%;
  377. overflow: auto;
  378. box-sizing: border-box;
  379. padding: 12px;
  380. .atr_detail {
  381. width: 100%;
  382. height: auto;
  383. border-radius: 4px;
  384. padding: 10px 15px 10px 15px;
  385. box-sizing: border-box;
  386. border: solid 1px rgba(0, 0, 0, 0.1);
  387. .atr_d_btn {
  388. width: 100%;
  389. height: 40px;
  390. display: flex;
  391. align-items: center;
  392. justify-content: center;
  393. background: rgba(0, 0, 0, 0.9);
  394. color: #fff;
  395. font-weight: 500;
  396. font-size: 14px;
  397. border-radius: 4px;
  398. cursor: pointer;
  399. }
  400. .atr_d_msg {
  401. width: 100%;
  402. display: flex;
  403. align-items: center;
  404. margin-top: 15px;
  405. font-size: 14px;
  406. color: rgba(0, 0, 0, 0.9);
  407. & > div {
  408. width: 4em;
  409. margin-right: 35px;
  410. font-weight: 500;
  411. }
  412. }
  413. .atr_d_line {
  414. width: 100%;
  415. height: 2px;
  416. display: block;
  417. background: rgba(242, 243, 245, 1);
  418. margin: 20px 0;
  419. border-radius: 2px;
  420. }
  421. .no_submit {
  422. width: 100%;
  423. display: flex;
  424. align-items: center;
  425. justify-content: space-between;
  426. font-size: 14px;
  427. font-weight: 500;
  428. margin-bottom: 20px;
  429. .no_submit_active {
  430. transform: rotate(180deg);
  431. }
  432. & > img {
  433. cursor: pointer;
  434. }
  435. }
  436. .no_submitList {
  437. width: 100%;
  438. display: flex;
  439. align-items: center;
  440. flex-wrap: nowrap;
  441. overflow: hidden;
  442. gap: 10px;
  443. & > div {
  444. padding: 5px 10px;
  445. background: rgba(255, 236, 232, 1);
  446. color: rgba(245, 63, 63, 1);
  447. font-size: 14px;
  448. font-weight: 500;
  449. border-radius: 10px;
  450. white-space: nowrap;
  451. }
  452. }
  453. .no_submitList_active {
  454. flex-wrap: wrap;
  455. }
  456. .is_submit {
  457. width: 100%;
  458. display: flex;
  459. align-items: center;
  460. justify-content: space-between;
  461. font-size: 14px;
  462. font-weight: 500;
  463. margin-bottom: 20px;
  464. .is_submit_active {
  465. transform: rotate(180deg);
  466. }
  467. & > img {
  468. cursor: pointer;
  469. }
  470. }
  471. .is_submitList {
  472. width: 100%;
  473. display: flex;
  474. align-items: center;
  475. flex-wrap: nowrap;
  476. overflow: hidden;
  477. gap: 10px;
  478. & > div {
  479. padding: 5px 10px;
  480. background: #fff;
  481. color: #2F80ED;
  482. font-size: 14px;
  483. font-weight: 500;
  484. border-radius: 10px;
  485. white-space: nowrap;
  486. box-sizing: border-box;
  487. border: solid 1px #2F80ED;
  488. cursor: pointer;
  489. }
  490. }
  491. .is_submitList_active {
  492. flex-wrap: wrap;
  493. }
  494. }
  495. .atr_type45Area {
  496. width: 100%;
  497. height: auto;
  498. .atr_t45a_title{
  499. width: 100%;
  500. height: auto;
  501. box-sizing: border-box;
  502. padding: 10px 15px 10px 15px;
  503. border: solid 1px #0000001a;
  504. border-radius: 4px;
  505. margin-top: 20px;
  506. font-size: 14px;
  507. font-weight: 600;
  508. img{
  509. width: auto;
  510. max-width: 100%;
  511. height: auto;
  512. object-fit: cover;
  513. cursor: pointer;
  514. display: block;
  515. }
  516. }
  517. .atr_t45a_item {
  518. width: 100%;
  519. height: auto;
  520. box-sizing: border-box;
  521. padding: 10px 15px 10px 15px;
  522. border: solid 1px #0000001a;
  523. border-radius: 4px;
  524. margin-top: 20px;
  525. .atr_t45a_i_top {
  526. width: 100%;
  527. height: auto;
  528. display: flex;
  529. align-items: center;
  530. justify-content: space-between;
  531. .atr_t45a_i_left {
  532. width: calc(100% - 50px);
  533. height: auto;
  534. display: flex;
  535. align-items: center;
  536. & > span:nth-child(1) {
  537. display: block;
  538. width: 20px;
  539. height: 20px;
  540. background: #fccf00;
  541. border-radius: 50%;
  542. padding: auto;
  543. box-sizing: border-box;
  544. display: flex;
  545. align-items: center;
  546. justify-content: center;
  547. font-size: 14px;
  548. font-weight: 500;
  549. color: #fff;
  550. margin-right: 10px;
  551. }
  552. & > div {
  553. font-size: 14px;
  554. font-weight: bold;
  555. max-width: calc(100% - 20px - 10px - 45px);
  556. overflow: hidden;
  557. text-overflow: ellipsis;
  558. white-space: nowrap;
  559. display: block;
  560. position: relative;
  561. &>img{
  562. width: 20px;
  563. height: 20px;
  564. object-fit: cover;
  565. cursor: pointer;
  566. }
  567. }
  568. & > span:nth-of-type(2) {
  569. display: block;
  570. padding: 2px 4px;
  571. border-radius: 2px;
  572. background: #aff0b580;
  573. color: #03ae2b;
  574. font-size: 12px;
  575. font-weight: 400;
  576. margin-left: 10px;
  577. }
  578. }
  579. & > img {
  580. width: 16px;
  581. height: 16px;
  582. margin-left: 20px;
  583. cursor: pointer;
  584. }
  585. .show_active {
  586. transform: rotate(180deg);
  587. }
  588. }
  589. .atr_t45a_i_bottom {
  590. width: 100%;
  591. display: flex;
  592. align-items: center;
  593. flex-wrap: nowrap;
  594. overflow: hidden;
  595. gap: 10px;
  596. margin-top: 10px;
  597. & > div {
  598. padding: 5px 10px;
  599. background: #0000000f;
  600. color: #222222;
  601. font-size: 14px;
  602. font-weight: 500;
  603. border-radius: 10px;
  604. }
  605. }
  606. .atr_t45a_i_b_active {
  607. flex-wrap: wrap !important;
  608. }
  609. }
  610. .nextAndUpBtn{
  611. width: 100%;
  612. height: auto;
  613. display: grid;
  614. grid-template-columns: 1fr 1fr;
  615. margin-top: 20px;
  616. gap: 15px;
  617. &>span{
  618. display: flex;
  619. justify-content: center;
  620. align-items: center;
  621. width: 100%;
  622. height: 40px;
  623. border-radius: 5px;
  624. font-size: 14px;
  625. font-weight: 500;
  626. color: #fff;
  627. background: #3681FC;
  628. cursor: pointer;
  629. }
  630. &>.no_active{
  631. background: #cccccc !important;
  632. color: #999999 !important;
  633. cursor: not-allowed !important;
  634. pointer-events: none !important;
  635. }
  636. }
  637. }
  638. }
  639. </style>