123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712 |
- import { ActionSheet, Button, Image, Popup, Swipe, SwipeItem } from 'vant';
- import {
- computed,
- defineComponent,
- nextTick,
- onMounted,
- onUnmounted,
- reactive,
- watch,
- ref
- } from 'vue';
- import { useRoute, useRouter } from 'vue-router';
- import styles from './index.module.less';
- import iconButtonList from '../images/icon-button-list.png';
- import MSticky from '@/components/m-sticky';
- import ChoiceQuestion from '../model/choice-question';
- import AnswerList from '../model/answer-list';
- 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 MHeader from '@/components/m-header';
- import { useEventListener, useInterval, useWindowScroll } from '@vueuse/core';
- export default defineComponent({
- name: 'unit-detail',
- setup() {
- const route = useRoute();
- const router = useRouter();
- const swipeRef = ref();
- const state = reactive({
- background: 'transparent',
- color: '#fff',
- visiableError: false,
- visiableAnswer: false,
- id: route.query.id,
- currentIndex: 0,
- questionList: [] as any,
- page: 1,
- rows: 10,
- total: 0,
- isFinish: false, // 是否完成加载
- visiableInfo: {
- show: false,
- operationType: 'RESULT' as 'RESULT' | 'BACK' | 'CONTINUE' | 'GRASP',
- type: 'DEFAULT' as 'DEFAULT' | 'FAIL' | 'PASS' | 'GOOD' | 'COUNTDOWN',
- content: '',
- showCancelButton: false,
- confirmButtonText: '',
- cancelButtonText: '',
- title: '',
- graspItem: {} as any
- },
- nextStatus: false,
- swipeHeight: 'auto' as any,
- answerAnalysis: '',
- questionTypeCode: '',
- overResult: {
- time: '00:00', // 时长
- questionLength: 0, // 答题数
- errorLength: 0, // 错题数
- rate: 0 // 正确率
- }
- });
- // 计时
- const { counter, resume, pause } = useInterval(1000, { controls: true });
- const getExamDetails = async () => {
- try {
- const { data } = await request.post(
- '/edu-app/studentUnitExamination/errorEdition',
- {
- data: {
- page: state.page,
- rows: state.rows
- }
- }
- );
- const temp = data || {};
- state.total = temp.total || 0;
- state.isFinish = temp.current < temp.pages ? false : true;
- temp.records.forEach((item: any) => {
- item.showAnalysis = false; // 默认不显示解析
- item.grasp = false; // 是否掌握题目
- item.analysis = {
- message: item.answerAnalysis,
- topic: true, // 是否显示结果
- userResult: false // 用户答题对错
- };
- item.userAnswer = []; // 用户答题
- });
- state.questionList.push(...(temp.records || []));
- } catch {
- //
- }
- };
- // 监听索引
- watch(
- () => state.currentIndex,
- () => {
- console.log(state.currentIndex, 'index');
- // 判断是否在倒数第三题,并且没有加载完
- if (
- state.currentIndex + 3 >= state.questionList.length &&
- !state.isFinish
- ) {
- state.page = state.page + 1;
- getExamDetails();
- }
- }
- );
- /** 已掌握此题 */
- const onGraspQuestion = async (item: any) => {
- // 判断是否掌握此题
- if (item.grasp) return;
- state.visiableInfo.show = true;
- state.visiableInfo.title = '确定掌握此题?';
- state.visiableInfo.showCancelButton = true;
- state.visiableInfo.operationType = 'GRASP';
- state.visiableInfo.cancelButtonText = '取消';
- state.visiableInfo.confirmButtonText = '确定';
- state.visiableInfo.content = `你确定已掌握该题知识要点,此题将移除你的错题集。`;
- state.visiableInfo.graspItem = item;
- };
- /** 已掌握此题确认 */
- const onGraspQuestionConfirm = async () => {
- try {
- state.visiableInfo.show = false;
- await request.get('/edu-app/studentExaminationErrorEdition/del', {
- hideLoading: false,
- params: {
- studentExaminationErrorEditionId:
- state.visiableInfo.graspItem.studentExaminationErrorEditionId
- }
- });
- state.visiableInfo.graspItem.grasp = true;
- // 只有一道题
- if (state.questionList.length === 1) {
- onAfter();
- router.back();
- return;
- }
- // 后面还有题
- if (state.questionList.length > state.currentIndex + 1) {
- const index = state.questionList.findIndex(
- (item: any) =>
- item.studentExaminationErrorEditionId ===
- state.visiableInfo.graspItem.studentExaminationErrorEditionId
- );
- state.questionList.splice(index, 1);
- state.total -= 1;
- resizeSwipeItemHeight();
- // swipeRef.value?.next();
- return;
- }
- // 后面没有题
- if (state.questionList.length === state.currentIndex + 1) {
- swipeRef.value?.prev();
- return;
- }
- } 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(
- '/edu-app/studentUnitExamination/submitTrainingAnswer',
- {
- hideLoading: true,
- data: result
- }
- );
- // 初始化是否显示解析
- questionList.forEach((question: any, index: number) => {
- // 格式化所有题目的答案
- if (index === state.currentIndex) {
- state.answerAnalysis = question.answerAnalysis;
- state.questionTypeCode = question.questionTypeCode;
- question.showAnalysis = true;
- question.analysis.userResult = data;
- }
- });
- // 判断是否是最后一题
- if (state.questionList.length === state.currentIndex + 1) {
- eventUnit.emit('unitAudioStop');
- state.visiableInfo.show = true;
- state.visiableInfo.title = '练习完成';
- state.visiableInfo.showCancelButton = true;
- state.visiableInfo.operationType = 'CONTINUE';
- state.visiableInfo.cancelButtonText = '再等等';
- state.visiableInfo.confirmButtonText = '确认完成';
- state.visiableInfo.content = `确认本次练习的题目都完成了吗?`;
- 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');
- const allImg = currentItemDom?.querySelectorAll(
- '.answerTitleImg img'
- );
- let status = true;
- // console.log(allImg)
- allImg?.forEach((img: any) => {
- 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 onConfirmResult = () => {
- if (state.visiableInfo.operationType === 'RESULT') {
- state.visiableInfo.show = false;
- onAfter();
- } else if (state.visiableInfo.operationType === 'BACK') {
- state.visiableInfo.show = false;
- onAfter();
- } else if (state.visiableInfo.operationType === 'CONTINUE') {
- onResultPopup();
- } else if (state.visiableInfo.operationType === 'GRASP') {
- onGraspQuestionConfirm();
- }
- };
- const onCloseResult = async () => {
- const operationType = state.visiableInfo.operationType;
- if (operationType === 'RESULT') {
- } else if (operationType === 'BACK') {
- state.visiableInfo.show = false;
- window.history.pushState(null, '', document.URL);
- window.addEventListener('popstate', onBack, false);
- } else if (operationType === 'CONTINUE' || operationType === 'GRASP') {
- state.visiableInfo.show = false;
- }
- };
- /** 结果页面弹窗 */
- const onResultPopup = () => {
- 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;
- // 60 及格
- // 85 及以上优秀
- state.visiableInfo.show = true;
- state.visiableInfo.title = '已完成';
- state.visiableInfo.showCancelButton = false;
- state.visiableInfo.operationType = 'RESULT';
- state.visiableInfo.confirmButtonText = '确认';
- state.visiableInfo.content = `<div>您已完成本次测试,答对<span class='${
- styles.right
- }'>${answerResult.passCount}</span>,答错<span class='${styles.error}'>${
- answerResult.count - answerResult.passCount
- }</span>,正确率${rate}%~</div>`;
- };
- // 拦截
- const onBack = () => {
- const answerResult = getAnswerResult.value;
- state.visiableInfo.show = true;
- state.visiableInfo.title = '确认退出吗?';
- state.visiableInfo.showCancelButton = true;
- state.visiableInfo.operationType = 'BACK';
- state.visiableInfo.cancelButtonText = '取消';
- state.visiableInfo.confirmButtonText = '确定';
- state.visiableInfo.content = `您已经完成${
- answerResult.passCount + answerResult.noPassCount
- }道题了,继续做题可以巩固所学知识哦~`;
- eventUnit.emit('unitAudioStop');
- };
- const onAfter = () => {
- window.removeEventListener('popstate', onBack, false);
- router.back();
- };
- onMounted(async () => {
- useEventListener(document, 'scroll', () => {
- const { y } = useWindowScroll();
- if (y.value > 52) {
- state.background = '#fff';
- state.color = '#323333';
- } else {
- state.background = 'transparent';
- state.color = '#fff';
- }
- });
- await getExamDetails();
- resizeSwipeItemHeight();
- window.history.pushState(null, '', document.URL);
- window.addEventListener('popstate', onBack, false);
- });
- onUnmounted(() => {
- // 关闭所有音频
- eventUnit.emit('unitAudioStop');
- });
- return () => (
- <div class={styles.unitDetail}>
- <MSticky position="top">
- <MHeader
- border={false}
- background={state.background}
- color={state.color}
- />
- </MSticky>
- <Swipe
- loop={false}
- showIndicators={false}
- ref={swipeRef}
- duration={300}
- touchable={false}
- class={styles.unitSwipe}
- 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}>
- {{
- title: () => (
- <div class={styles.questionTitle}>
- <div class={styles.questionNum}>
- <p class={styles.pointName}>
- {item.knowledgePointName}
- </p>
- <span>{state.currentIndex + 1}</span>/{state.total}
- </div>
- <Button
- round
- plain
- size="mini"
- color={item.grasp ? '#FF5A56' : '#1CACF1'}
- class={styles.controlBtn}
- disabled={item.grasp}
- onClick={() => onGraspQuestion(item)}>
- {item.grasp ? '已掌握此题' : '掌握此题'}
- </Button>
- </div>
- )
- }}
- </ChoiceQuestion>
- )}
- {item.questionTypeCode === QuestionType.CHECKBOX && (
- <ChoiceQuestion
- v-model:value={item.userAnswer}
- index={index + 1}
- data={item}
- type="checkbox"
- showAnalysis={item.showAnalysis}
- analysis={item.analysis}>
- {{
- title: () => (
- <div class={styles.questionTitle}>
- <div class={styles.questionNum}>
- <p class={styles.pointName}>
- {item.knowledgePointName}
- </p>
- <span>{state.currentIndex + 1}</span>/{state.total}
- </div>
- <Button
- round
- plain
- size="mini"
- color={item.grasp ? '#FF5A56' : '#1CACF1'}
- class={styles.controlBtn}
- disabled={item.grasp}
- onClick={() => onGraspQuestion(item)}>
- {item.grasp ? '已掌握此题' : '掌握此题'}
- </Button>
- </div>
- )
- }}
- </ChoiceQuestion>
- )}
- {item.questionTypeCode === QuestionType.SORT && (
- <DragQuestion
- v-model:value={item.userAnswer}
- onUpdate:value={() => {
- // 如果是空则滑动到顶部
- const status =
- item.userAnswer && item.userAnswer.length > 0
- ? false
- : true;
- resizeSwipeItemHeight(status);
- }}
- data={item}
- index={index + 1}
- showAnalysis={item.showAnalysis}
- analysis={item.analysis}>
- {{
- title: () => (
- <div class={styles.questionTitle}>
- <div class={styles.questionNum}>
- <p class={styles.pointName}>
- {item.knowledgePointName}
- </p>
- <span>{state.currentIndex + 1}</span>/{state.total}
- </div>
- <Button
- round
- plain
- size="mini"
- color={item.grasp ? '#FF5A56' : '#1CACF1'}
- class={styles.controlBtn}
- disabled={item.grasp}
- onClick={() => onGraspQuestion(item)}>
- {item.grasp ? '已掌握此题' : '掌握此题'}
- </Button>
- </div>
- )
- }}
- </DragQuestion>
- )}
- {item.questionTypeCode === QuestionType.LINK && (
- <KeepLookQuestion
- v-model:value={item.userAnswer}
- data={item}
- index={index + 1}
- showAnalysis={item.showAnalysis}
- analysis={item.analysis}>
- {{
- title: () => (
- <div class={styles.questionTitle}>
- <div class={styles.questionNum}>
- <p class={styles.pointName}>
- {item.knowledgePointName}
- </p>
- <span>{state.currentIndex + 1}</span>/{state.total}
- </div>
- <Button
- round
- plain
- size="mini"
- color={item.grasp ? '#FF5A56' : '#1CACF1'}
- class={styles.controlBtn}
- disabled={item.grasp}
- onClick={() => onGraspQuestion(item)}>
- {item.grasp ? '已掌握此题' : '掌握此题'}
- </Button>
- </div>
- )
- }}
- </KeepLookQuestion>
- )}
- {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}>
- {{
- title: () => (
- <div class={styles.questionTitle}>
- <div class={styles.questionNum}>
- <span>{state.currentIndex + 1}</span>/{state.total}
- </div>
- {/* <div class={styles.questionType}>
- <i></i>
- <span>{item.knowledgePointName}</span>
- </div> */}
- <Button
- round
- plain
- size="mini"
- color={item.grasp ? '#FF5A56' : '#1CACF1'}
- disabled={item.grasp}
- class={styles.controlBtn}
- onClick={() => onGraspQuestion(item)}>
- {item.grasp ? '已掌握此题' : '掌握此题'}
- </Button>
- </div>
- )
- }}
- </PlayQuestion>
- )}
- </div>
- </SwipeItem>
- ))}
- </Swipe>
- <MSticky position="bottom">
- <div class={['btnGroup btnMore', styles.btnSection]}>
- <Button
- round
- block
- class={
- state.currentIndex > 0 ? styles.activePrevBtn : styles.prevBtn
- }
- disabled={state.currentIndex > 0 ? false : true}
- onClick={() => {
- swipeRef.value?.prev();
- }}>
- 上一题
- </Button>
- <Button
- block
- round
- class={styles.nextBtn}
- onClick={onNextQuestion}
- loading={state.nextStatus}
- disabled={state.nextStatus}>
- {state.questionList.length === state.currentIndex + 1
- ? '提交'
- : '下一题'}
- </Button>
- <Image
- src={iconButtonList}
- class={[styles.wapList, 'van-haptics-feedback']}
- onClick={() => (state.visiableAnswer = true)}
- />
- </div>
- </MSticky>
- {/* 题目集合 */}
- <ActionSheet
- v-model:show={state.visiableAnswer}
- title="题目列表"
- safeAreaInsetBottom>
- <AnswerList
- value={state.questionList}
- lookType={'PRACTICE'}
- statusList={[
- {
- text: '答对',
- color: '#1CACF1'
- },
- {
- text: '答错',
- color: '#FF8486'
- },
- {
- text: '未答',
- color: '#EAEAEA'
- }
- ]}
- 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}
- questionTypeCode={state.questionTypeCode}
- onConform={() => {
- swipeRef.value?.next();
- state.answerAnalysis = '';
- }}
- />
- </Popup>
- <Popup
- v-model:show={state.visiableInfo.show}
- closeOnClickOverlay={false}
- style={{
- background: 'transparent',
- width: '100%',
- maxWidth: '100%',
- transform: 'translateY(-55%)'
- }}>
- <ResultFinish
- title={state.visiableInfo.title}
- showCancelButton={state.visiableInfo.showCancelButton}
- cancelButtonText={state.visiableInfo.cancelButtonText}
- confirmButtonText={state.visiableInfo.confirmButtonText}
- status={state.visiableInfo.type}
- content={state.visiableInfo.content}
- contentHtml
- onConform={onConfirmResult}
- onClose={onCloseResult}
- />
- </Popup>
- </div>
- );
- }
- });
|