index.tsx 14 KB

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