index.tsx 12 KB

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