index.tsx 13 KB

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