index.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. import { ActionSheet, Button, Cell, Icon, Image, Swipe, SwipeItem } from 'vant'
  2. import { defineComponent, onMounted, reactive, ref, nextTick, onUnmounted } from 'vue'
  3. import { useRoute, useRouter } from 'vue-router'
  4. import styles from './index.module.less'
  5. import iconQuestionNums from '../images/icon-question-nums.png'
  6. import iconButtonList from '../images/icon-button-list.png'
  7. import OSticky from '@/components/o-sticky'
  8. import ChoiceQuestion from '../model/choice-question'
  9. import AnswerList from '../model/answer-list'
  10. import DragQuestion from '../model/drag-question'
  11. import KeepLookQuestion from '../model/keep-look-question'
  12. import PlayQuestion from '../model/play-question'
  13. import request from '@/helpers/request'
  14. import { eventUnit, QuestionType } from '../unit'
  15. import { useRect } from '@vant/use'
  16. import { state as baseState } from '@/state'
  17. import OHeader from '@/components/o-header'
  18. export default defineComponent({
  19. name: 'unit-detail',
  20. setup() {
  21. const route = useRoute()
  22. const router = useRouter()
  23. const swipeRef = ref()
  24. const state = reactive({
  25. id: route.query.id,
  26. examDetail: {} as any,
  27. visiableAnswer: false,
  28. currentIndex: 0,
  29. questionList: [],
  30. time: 0,
  31. resultInfo: {} as any,
  32. answerResult: [] as any,
  33. nextStatus: false,
  34. swipeHeight: 'auto' as any
  35. })
  36. // 学员端查看详情
  37. const getExamDetails = async () => {
  38. try {
  39. const { data } = await request.post('/api-student/studentUnitExamination/detail', {
  40. requestType: 'form',
  41. data: {
  42. studentUnitExaminationId: state.id
  43. }
  44. })
  45. const { questionJson, studentAnswerJson, answerResult, ...res } = data
  46. const temp = questionJson || []
  47. // 正确答案
  48. state.answerResult = answerResult ? JSON.parse(answerResult) : []
  49. temp.forEach((item: any) => {
  50. item.userAnswer = formatUserAnswers(item, studentAnswerJson)
  51. item.showAnalysis = true
  52. item.showScore = true
  53. item.analysis = {
  54. message: item.answerAnalysis,
  55. topic: true, // 是否显示结果
  56. userResult: formatUserResult(item.id) // 用户答题对错
  57. }
  58. })
  59. // 问题列表
  60. state.questionList = temp
  61. // 详情
  62. state.examDetail = { ...res } || {}
  63. } catch {
  64. //
  65. }
  66. }
  67. const getExamTeaherDetails = async () => {
  68. try {
  69. const { data } = await request.post(
  70. baseState.platformApi + '/classGroupUnitExamination/report',
  71. {
  72. requestType: 'form',
  73. data: {
  74. classGroupUnitExaminationId: state.id,
  75. level: route.query.level
  76. }
  77. }
  78. )
  79. // console.log(data)
  80. // const { questionJson, studentAnswerJson, answerResult, ...res } = data
  81. state.examDetail = {
  82. unitExaminationName: data.unitExaminationName,
  83. questionNum: data.questionNum || 0
  84. }
  85. // 问题列表
  86. const temp = data.examinationQuestionAdds || []
  87. temp.forEach((item: any) => {
  88. const rightAnswers = item.rightAnswers || []
  89. // const answers = item.answers || []
  90. // answers.forEach((answer: any) => {
  91. // const child = rightAnswers.find(
  92. // (right: any) =>
  93. // right.examinationQuestionAnswerId === answer.examinationQuestionAnswerId
  94. // )
  95. // if (child) {
  96. // answer.selectRate = child.selectRate
  97. // }
  98. // })
  99. // item.answers = answers
  100. item.userAnswer = formatTeacherAnswer(rightAnswers)
  101. item.showAnalysis = true
  102. item.showRate = true
  103. item.showScore = false
  104. item.analysis = {
  105. message: item.answerAnalysis,
  106. topic: false // 是否显示结果
  107. }
  108. })
  109. // 问题列表
  110. state.questionList = temp
  111. // 正确答案
  112. // console.log(state.questionList, 'state.questionList')
  113. } catch {
  114. //
  115. }
  116. }
  117. /**
  118. * @description 初始化正确答案
  119. */
  120. const formatTeacherAnswer = (answers: any) => {
  121. const result: any = []
  122. answers.forEach((answer: any) => {
  123. // rightAnswerFlag 说明是正确的
  124. if (answer.rightAnswerFlag) {
  125. const rightOption = answers.find(
  126. (item: any) => item.questionExtra === answer.questionExtra
  127. )
  128. result.push({
  129. answer: answer.questionAnswer,
  130. answerId: answer.examinationQuestionAnswerId,
  131. answerExtra: rightOption ? rightOption.questionExtra : null
  132. })
  133. }
  134. })
  135. return result || []
  136. }
  137. /**
  138. * @description 初始化用户答案
  139. */
  140. const formatUserAnswers = (item: any, userAnswer: any) => {
  141. // 判断是否有结果
  142. if (!userAnswer) return []
  143. const answers = userAnswer || []
  144. return answers[item.id] ? answers[item.id] : []
  145. }
  146. /**
  147. * @description 检查用户是否答对
  148. * @returns Boolean
  149. */
  150. const formatUserResult = (id: string) => {
  151. let result = false
  152. state.answerResult.forEach((item: any) => {
  153. if (item.questionId === id) {
  154. result = item.rightFlag
  155. }
  156. })
  157. return result
  158. }
  159. /**
  160. * @description 重置当前的题目高度
  161. */
  162. let size = 0
  163. const resizeSwipeItemHeight = (scroll = true) => {
  164. nextTick(() => {
  165. scroll && window.scrollTo(0, 0)
  166. setTimeout(() => {
  167. const currentItemDom: any = document
  168. .querySelectorAll('.van-swipe-item')
  169. [state.currentIndex]?.querySelector('.swipe-item-question')
  170. const allImg = currentItemDom.querySelectorAll('.answerTitleImg img')
  171. let status = true
  172. // console.log(allImg)
  173. allImg.forEach((img: any) => {
  174. // console.log(img.complete)
  175. if (!img.complete) {
  176. status = false
  177. }
  178. })
  179. // 判断图片是否加载完了
  180. if (!status && size < 3) {
  181. setTimeout(() => {
  182. size += 1
  183. resizeSwipeItemHeight(scroll)
  184. }, 300)
  185. }
  186. if (status) {
  187. size = 0
  188. }
  189. const rect = useRect(currentItemDom)
  190. // console.log('🚀 ~ setTimeout ~ currentItemDom', currentItemDom)
  191. // console.log('🚀 ~ setTimeout ~ rect', rect, state.currentIndex)
  192. state.swipeHeight = rect.height
  193. }, 100)
  194. })
  195. }
  196. /**
  197. * @description 下一题 | 测试完成
  198. */
  199. const onNextQuestion = async () => {
  200. try {
  201. state.nextStatus = true
  202. if (state.questionList.length === state.currentIndex + 1) {
  203. // 关闭所有音频
  204. eventUnit.emit('unitAudioStop')
  205. router.back()
  206. }
  207. swipeRef.value?.next()
  208. state.nextStatus = false
  209. } catch {
  210. //
  211. state.nextStatus = false
  212. }
  213. }
  214. onMounted(async () => {
  215. if (baseState.platformType === 'TEACHER' || baseState.platformType === 'SCHOOL') {
  216. await getExamTeaherDetails()
  217. } else {
  218. await getExamDetails()
  219. }
  220. // 初始化高度
  221. resizeSwipeItemHeight()
  222. })
  223. onUnmounted(() => {
  224. // 关闭所有音频
  225. eventUnit.emit('unitAudioStop')
  226. })
  227. return () => (
  228. <div class={styles.unitDetail}>
  229. <OHeader />
  230. <Cell center class={styles.unitSection} border={false}>
  231. {{
  232. title: () => <div class={styles.unitTitle}>{state.examDetail.unitExaminationName}</div>,
  233. label: () => (
  234. <div class={styles.unitCount}>
  235. <div class={styles.qNums}>
  236. <Icon class={styles.icon} name={iconQuestionNums} />
  237. 题目数量{' '}
  238. <span class={styles.num} style={{ paddingLeft: '6px' }}>
  239. {state.currentIndex + 1}
  240. </span>
  241. /{state.examDetail.questionNum || 0}
  242. </div>
  243. </div>
  244. )
  245. }}
  246. </Cell>
  247. <Swipe
  248. loop={false}
  249. showIndicators={false}
  250. ref={swipeRef}
  251. duration={300}
  252. touchable={false}
  253. lazyRender
  254. style={{ paddingBottom: '12px' }}
  255. height={state.swipeHeight}
  256. onChange={(index: number) => {
  257. eventUnit.emit('unitAudioStop')
  258. state.currentIndex = index
  259. // .swipe-item-question
  260. // const t = setInterval(() => {
  261. // console.log(document.querySelectorAll('.van-swipe-item')[state.currentIndex])
  262. // console.log(
  263. // document
  264. // .querySelectorAll('.van-swipe-item')
  265. // [state.currentIndex].querySelector('.swipe-item-question')
  266. // )
  267. // if (
  268. // document
  269. // .querySelectorAll('.van-swipe-item')
  270. // [state.currentIndex].querySelector('.swipe-item-question')
  271. // ) {
  272. // clearInterval(t)
  273. // }
  274. // }, 100)
  275. resizeSwipeItemHeight()
  276. }}
  277. >
  278. {state.questionList.map((item: any, index: number) => (
  279. // item.questionTypeCode === QuestionType.CHECKBOX && (
  280. // <SwipeItem>
  281. // <ChoiceQuestion
  282. // v-model:value={item.userAnswer}
  283. // index={index + 1}
  284. // data={item}
  285. // readOnly
  286. // type="checkbox"
  287. // showRate={item.showRate}
  288. // showAnalysis={item.showAnalysis}
  289. // analysis={item.analysis}
  290. // />
  291. // </SwipeItem>
  292. // )
  293. <SwipeItem>
  294. <div class="swipe-item-question">
  295. {item.questionTypeCode === QuestionType.RADIO && (
  296. <ChoiceQuestion
  297. v-model:value={item.userAnswer}
  298. index={index + 1}
  299. data={item}
  300. readOnly
  301. type="radio"
  302. showRate={item.showRate}
  303. showAnalysis={item.showAnalysis}
  304. analysis={item.analysis}
  305. />
  306. )}
  307. {item.questionTypeCode === QuestionType.CHECKBOX && (
  308. <ChoiceQuestion
  309. v-model:value={item.userAnswer}
  310. index={index + 1}
  311. data={item}
  312. readOnly
  313. type="checkbox"
  314. showRate={item.showRate}
  315. showAnalysis={item.showAnalysis}
  316. analysis={item.analysis}
  317. />
  318. )}
  319. {item.questionTypeCode === QuestionType.SORT && (
  320. <DragQuestion
  321. readOnly
  322. v-model:value={item.userAnswer}
  323. data={item}
  324. index={index + 1}
  325. showRate={item.showRate}
  326. showAnalysis={item.showAnalysis}
  327. analysis={item.analysis}
  328. />
  329. )}
  330. {item.questionTypeCode === QuestionType.LINK && (
  331. <KeepLookQuestion
  332. readOnly
  333. v-model:value={item.userAnswer}
  334. data={item}
  335. index={index + 1}
  336. showRate={item.showRate}
  337. showAnalysis={item.showAnalysis}
  338. analysis={item.analysis}
  339. />
  340. )}
  341. {item.questionTypeCode === QuestionType.PLAY && (
  342. <PlayQuestion
  343. readOnly
  344. v-model:value={item.userAnswer}
  345. data={item}
  346. index={index + 1}
  347. unitId={state.id as any}
  348. showScore={item.showScore}
  349. showRate={item.showRate}
  350. showAnalysis={item.showAnalysis}
  351. analysis={item.analysis}
  352. />
  353. )}
  354. </div>
  355. </SwipeItem>
  356. ))}
  357. </Swipe>
  358. <OSticky position="bottom" background="white">
  359. <div class={['btnGroup btnMore']}>
  360. {state.currentIndex > 0 && (
  361. <Button
  362. round
  363. block
  364. type="primary"
  365. plain
  366. onClick={() => {
  367. swipeRef.value?.prev()
  368. }}
  369. >
  370. 上一题
  371. </Button>
  372. )}
  373. <Button
  374. block
  375. round
  376. type="primary"
  377. onClick={onNextQuestion}
  378. loading={state.nextStatus}
  379. disabled={state.nextStatus}
  380. >
  381. {state.questionList.length === state.currentIndex + 1 ? '确定' : '下一题'}
  382. </Button>
  383. <Image
  384. src={iconButtonList}
  385. class={[styles.wapList, 'van-haptics-feedback']}
  386. onClick={() => (state.visiableAnswer = true)}
  387. />
  388. </div>
  389. </OSticky>
  390. {/* 题目集合 */}
  391. <ActionSheet v-model:show={state.visiableAnswer} title="题目列表" safeAreaInsetBottom>
  392. <AnswerList
  393. value={state.questionList}
  394. answerResult={state.answerResult}
  395. index={state.currentIndex}
  396. lookType={baseState.platformType === 'STUDENT' ? 'RESULT' : 'CLICK'}
  397. statusList={
  398. baseState.platformType === 'STUDENT'
  399. ? [
  400. {
  401. text: '答对',
  402. color: '#71B0FF'
  403. },
  404. {
  405. text: '答错',
  406. color: '#FF8486'
  407. }
  408. ]
  409. : []
  410. }
  411. onSelect={(item: any) => {
  412. // 跳转,并且跳过动画
  413. swipeRef.value?.swipeTo(item, {
  414. immediate: true
  415. })
  416. state.visiableAnswer = false
  417. }}
  418. />
  419. </ActionSheet>
  420. </div>
  421. )
  422. }
  423. })