123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576 |
- import {
- ActionSheet,
- Button,
- Cell,
- CountDown,
- Grid,
- GridItem,
- Icon,
- Image,
- Popup,
- showDialog,
- Swipe,
- SwipeItem,
- Tag
- } from 'vant'
- import { computed, defineComponent, nextTick, onMounted, reactive, ref } from 'vue'
- import { useRoute, useRouter } from 'vue-router'
- import styles from './index.module.less'
- import iconButtonList from '../images/icon-button-list.png'
- import OSticky from '@/components/o-sticky'
- import ChoiceQuestion from '../model/choice-question'
- import AnswerList from '../model/answer-list'
- import ODialog from '@/components/o-dialog'
- import DragQuestion from '../model/drag-question'
- import KeepLookQuestion from '../model/keep-look-question'
- import PlayQuestion from '../model/play-question'
- import ErrorMode from '../model/error-mode'
- import ResultFinish from '../model/result-finish'
- import { eventUnit, QuestionType } from '../unit'
- import request from '@/helpers/request'
- import { useRect } from '@vant/use'
- import OHeader from '@/components/o-header'
- import { useInterval } from '@vueuse/core'
- export default defineComponent({
- name: 'unit-detail',
- setup() {
- const route = useRoute()
- const router = useRouter()
- const countDownRef = ref()
- const swipeRef = ref()
- const state = reactive({
- examId: route.query.examId,
- name: route.query.name,
- visiableError: false,
- visiableAnswer: false,
- visiableResult: false,
- id: route.query.id,
- currentIndex: 0,
- questionList: [],
- visiableSure: false,
- resultInfo: {} as any,
- resultStatusType: 'SUCCESS', // 'SUCCESS' | 'FAIL'
- visiableExam: false, // 考试已结束
- nextStatus: false,
- swipeHeight: 'auto' as any,
- answerAnalysis: '',
- overResult: {
- time: '00:00', // 时长
- questionLength: 0, // 答题数
- errorLength: 0, // 错题数
- rate: 0 // 正确率
- },
- knowledgelist: [] as any, // 知识点列表
- quitStatus: false
- })
- // 计时
- const { counter, resume, pause } = useInterval(1000, { controls: true })
- const getExamDetails = async () => {
- try {
- const { data } = await request.post('/api-student/examinationQuestion/randomPage', {
- data: {
- page: 1,
- row: 50,
- categoryId: state.id
- }
- })
- const temp = data || []
- temp.forEach((item: any) => {
- item.showAnalysis = false // 默认不显示解析
- item.analysis = {
- message: item.answerAnalysis,
- topic: true, // 是否显示结果
- userResult: false // 用户答题对错
- }
- item.userAnswer = [] // 用户答题
- })
- state.questionList = temp
- } catch {
- //
- }
- }
- // 获取所在知识点
- const getDetails = async () => {
- try {
- const { data } = await request.post('/api-student/unitExamination/queryKnowledgePoint', {
- requestType: 'form',
- data: {
- unitExaminationId: state.examId
- }
- })
- state.knowledgelist = data.lists || []
- } catch {
- //
- }
- }
- /**
- * @description 下一题 | 测试完成
- */
- const onNextQuestion = async () => {
- try {
- const questionList = state.questionList || []
- let result: any = {}
- questionList.forEach((question: any, index: number) => {
- // 格式化所有题目的答案
- if (index === state.currentIndex) {
- result = {
- questionId: question.id,
- details: question.userAnswer || []
- }
- }
- })
- const { data } = await request.post(
- '/api-student/studentUnitExamination/submitTrainingAnswer',
- {
- hideLoading: true,
- data: result
- }
- )
- // 初始化是否显示解析
- questionList.forEach((question: any, index: number) => {
- // 格式化所有题目的答案
- if (index === state.currentIndex) {
- state.answerAnalysis = question.answerAnalysis
- question.showAnalysis = true
- question.analysis.userResult = data
- }
- })
- // 判断是否是最后一题
- if (state.questionList.length === state.currentIndex + 1) {
- state.visiableSure = true
- return
- }
- if (data) {
- swipeRef.value?.next()
- } else {
- state.visiableError = true
- }
- } catch {
- //
- }
- }
- //
- const getAnswerResult = computed(() => {
- const questionList = state.questionList || []
- let count = 0
- let passCount = 0
- let noPassCount = 0
- questionList.forEach((item: any) => {
- if (item.showAnalysis) {
- count += 1
- if (item.analysis.userResult) {
- passCount += 1
- } else {
- noPassCount += 1
- }
- }
- })
- return {
- count,
- passCount,
- noPassCount
- }
- })
- /**
- * @description 重置当前的题目高度
- * @param {any} scroll 是否滚动到顶部
- */
- let size = 0
- const resizeSwipeItemHeight = (scroll = true) => {
- nextTick(() => {
- scroll && window.scrollTo(0, 0)
- setTimeout(() => {
- const currentItemDom: any = document
- .querySelectorAll('.van-swipe-item')
- [state.currentIndex]?.querySelector('.swipe-item-question')
- console.log('🚀 ~ setTimeout ~ currentItemDom', currentItemDom)
- const allImg = currentItemDom.querySelectorAll('.answerTitleImg img')
- let status = true
- // console.log(allImg)
- allImg.forEach((img: any) => {
- console.log(img.complete)
- if (!img.complete) {
- status = false
- }
- })
- // 判断图片是否加载完了
- if (!status && size < 3) {
- setTimeout(() => {
- size += 1
- resizeSwipeItemHeight(scroll)
- }, 300)
- }
- if (status) {
- size = 0
- }
- const rect = useRect(currentItemDom)
- state.swipeHeight = rect.height
- }, 100)
- })
- }
- const onConfirmExam = () => {
- const answerResult = getAnswerResult.value
- let rate = 0
- if (answerResult.count > 0) {
- rate = Math.floor((answerResult.passCount / answerResult.count) * 100)
- }
- const times = counter.value
- const minute =
- Math.floor(times / 60) >= 10 ? Math.floor(times / 60) : '0' + Math.floor(times / 60)
- const seconds = times % 60 >= 10 ? times % 60 : '0' + (times % 60)
- state.overResult = {
- time: minute + ':' + seconds, // 时长
- questionLength: answerResult.count, // 答题数
- errorLength: answerResult.noPassCount, // 错题数
- rate // 正确率
- }
- // 重置计时
- pause()
- counter.value = 0
- state.visiableResult = true
- }
- // 重新练习
- const onCloseResult = async () => {
- state.questionList = []
- await getExamDetails()
- setTimeout(async () => {
- swipeRef.value?.swipeTo(0, {
- immediate: true
- })
- state.swipeHeight = 'auto'
- state.answerAnalysis = ''
- state.overResult = {
- time: '00:00', // 时长
- questionLength: 0, // 答题数
- errorLength: 0, // 错题数
- rate: 0 // 正确率
- }
- state.visiableResult = false
- // 恢复计时
- resume()
- resizeSwipeItemHeight()
- }, 100)
- }
- // 下一个考点
- const onConfirmResult = () => {
- const knowledgelist = state.knowledgelist || []
- console.log('🚀 ~ file: index.tsx:246 ~ onConfirmResult ~ knowledgelist', knowledgelist)
- // 当前正在考试的节点
- const knownleIndex = knowledgelist.findIndex((item: any) => item.id === state.id)
- console.log('🚀 ~ file: index.tsx:249 ~ onConfirmResult ~ knownleIndex', knownleIndex)
- let currentKnowle: any = {}
- if (knownleIndex + 1 >= knowledgelist.length || knownleIndex < 0) {
- currentKnowle = knowledgelist[0]
- } else {
- currentKnowle = knowledgelist[knownleIndex + 1]
- }
- state.id = currentKnowle.id
- state.visiableResult = false
- state.currentIndex = 0
- // 重置
- onCloseResult()
- }
- // 拦截
- const onBack = () => {
- // showDialog({
- // title: '提示',
- // message: '您是否退出?',
- // theme: 'round-button',
- // confirmButtonColor: '#ff8057'
- // }).then(() => {
- // onAfter()
- // })
- state.quitStatus = true
- }
- const onAfter = () => {
- window.removeEventListener('popstate', onBack, false)
- router.back()
- }
- onMounted(async () => {
- await getExamDetails()
- await getDetails()
- resizeSwipeItemHeight()
- // window.history.pushState(null, '', document.URL)
- // window.addEventListener('popstate', onBack, false)
- })
- return () => (
- <div class={styles.unitDetail}>
- <OSticky position="top">
- <OHeader
- v-slots={{
- right: () => (
- <span
- style="color: var(--van-primary-color)"
- onClick={() => (state.visiableSure = true)}
- >
- 结束练习
- </span>
- )
- }}
- />
- </OSticky>
- <Cell center class={styles.unitSection} border={false}>
- {{
- title: () => <div class={[styles.unitTitle]}>{state.name}</div>,
- value: () => (
- <div class={styles.unitCount}>
- <div class={styles.countSection}>
- <span class={styles.nums}>{getAnswerResult.value.passCount}</span>
- <span>答对</span>
- </div>
- <div class={styles.countSection}>
- <span class={styles.nums} style={{ color: '#F44541' }}>
- {getAnswerResult.value.noPassCount}
- </span>
- <span>答错</span>
- </div>
- </div>
- )
- }}
- </Cell>
- <Swipe
- loop={false}
- showIndicators={false}
- ref={swipeRef}
- duration={300}
- touchable={false}
- style={{ paddingBottom: '12px' }}
- lazyRender
- height={state.swipeHeight}
- onChange={(index: number) => {
- eventUnit.emit('unitAudioStop')
- state.currentIndex = index
- resizeSwipeItemHeight()
- }}
- >
- {state.questionList.map((item: any, index: number) => (
- <SwipeItem>
- <div class="swipe-item-question">
- {item.questionTypeCode === QuestionType.RADIO && (
- <ChoiceQuestion
- v-model:value={item.userAnswer}
- index={index + 1}
- data={item}
- type="radio"
- showAnalysis={item.showAnalysis}
- analysis={item.analysis}
- />
- )}
- {item.questionTypeCode === QuestionType.CHECKBOX && (
- <ChoiceQuestion
- v-model:value={item.userAnswer}
- index={index + 1}
- data={item}
- type="checkbox"
- showAnalysis={item.showAnalysis}
- analysis={item.analysis}
- />
- )}
- {item.questionTypeCode === QuestionType.SORT && (
- <DragQuestion
- v-model:value={item.userAnswer}
- onUpdate:value={() => {
- resizeSwipeItemHeight(false)
- }}
- data={item}
- index={index + 1}
- showAnalysis={item.showAnalysis}
- analysis={item.analysis}
- />
- )}
- {item.questionTypeCode === QuestionType.LINK && (
- <KeepLookQuestion
- v-model:value={item.userAnswer}
- data={item}
- index={index + 1}
- showAnalysis={item.showAnalysis}
- analysis={item.analysis}
- />
- )}
- {item.questionTypeCode === QuestionType.PLAY && (
- <PlayQuestion
- v-model:value={item.userAnswer}
- data={item}
- index={index + 1}
- unitId={state.id as any}
- showAnalysis={item.showAnalysis}
- analysis={item.analysis}
- />
- )}
- </div>
- </SwipeItem>
- ))}
- </Swipe>
- <OSticky position="bottom" background="white">
- <div class={['btnGroup btnMore']}>
- {state.currentIndex > 0 && (
- <Button
- round
- block
- type="primary"
- plain
- onClick={() => {
- swipeRef.value?.prev()
- }}
- >
- 上一题
- </Button>
- )}
- <Button
- block
- round
- type="primary"
- onClick={onNextQuestion}
- loading={state.nextStatus}
- disabled={state.nextStatus}
- >
- 提交
- </Button>
- <Image
- src={iconButtonList}
- class={[styles.wapList, 'van-haptics-feedback']}
- onClick={() => (state.visiableAnswer = true)}
- />
- </div>
- </OSticky>
- {/* 题目集合 */}
- <ActionSheet v-model:show={state.visiableAnswer} title="题目列表" safeAreaInsetBottom>
- <AnswerList
- value={state.questionList}
- lookType={'PRACTICE'}
- statusList={[
- {
- text: '答对',
- color: '#71B0FF'
- },
- {
- text: '答错',
- color: '#FF8486'
- }
- ]}
- onSelect={(item: any) => {
- // 跳转,并且跳过动画
- swipeRef.value?.swipeTo(item, {
- immediate: true
- })
- state.visiableAnswer = false
- }}
- />
- </ActionSheet>
- <Popup
- v-model:show={state.visiableError}
- style={{ width: '90%' }}
- round
- closeOnClickOverlay={false}
- >
- <ErrorMode
- onClose={() => (state.visiableError = false)}
- answerAnalysis={state.answerAnalysis}
- onConform={() => {
- swipeRef.value?.next()
- state.answerAnalysis = ''
- }}
- />
- </Popup>
- <Popup
- v-model:show={state.visiableResult}
- closeOnClickOverlay={false}
- style={{ background: 'transparent', width: '96%' }}
- >
- <ResultFinish
- status="PRACTICE"
- confirmButtonText="下一个考点"
- cancelButtonText="继续练习本考点"
- onClose={onCloseResult}
- onConform={onConfirmResult}
- v-slots={{
- content: () => (
- <div class={styles.practiceResult}>
- <div class={styles.practiceTitle}>本次练习正确率</div>
- <div class={styles.practiceRate}>{state.overResult.rate}%</div>
- <Grid border={false} columnNum={3}>
- <GridItem>
- <p class={styles.title}>{state.overResult.time}</p>
- <p class={styles.name}>练习时长</p>
- </GridItem>
- <GridItem>
- <p class={[styles.title]}>{state.overResult.questionLength | 0}</p>
- <p class={styles.name}>答题数</p>
- </GridItem>
- <GridItem>
- <p class={styles.title}>{state.overResult.errorLength | 0}</p>
- <p class={styles.name}>错题数</p>
- </GridItem>
- </Grid>
- <div class={styles.practiceTips}>
- 继续努力!
- <br />
- 争取在测验中获得高分!
- </div>
- </div>
- )
- }}
- />
- </Popup>
- <ODialog
- v-model:show={state.visiableSure}
- title="练习完成"
- message="确认本次练习的题目都完成了吗?"
- messageAlign="left"
- showCancelButton
- cancelButtonText="再等等"
- confirmButtonText="确认完成"
- onConfirm={onConfirmExam}
- />
- <ODialog
- v-model:show={state.quitStatus}
- title="提示"
- message="您是否退出本次练习?"
- showCancelButton
- cancelButtonText="取消"
- onCancel={() => {
- window.history.pushState(null, '', document.URL)
- window.addEventListener('popstate', onBack, false)
- }}
- confirmButtonText="确认完成"
- onConfirm={onAfter}
- />
- </div>
- )
- }
- })
|