|
|
@@ -0,0 +1,294 @@
|
|
|
+<template>
|
|
|
+ <ie-page :fix-height="true" :safe-area-inset-bottom="false">
|
|
|
+ <ie-navbar :title="pageTitle" custom-back @left-click="handleLeftClick">
|
|
|
+ <template #headerRight>
|
|
|
+ <view v-if="isExamMode" class="countdown-text" :class="{ 'text-red-500': examDuration < 30 }">
|
|
|
+ {{ formatExamDuration }}
|
|
|
+ </view>
|
|
|
+ <view v-else class="">{{ formatPracticeDuration }}</view>
|
|
|
+ </template>
|
|
|
+ </ie-navbar>
|
|
|
+ <view class="px-20 py-14 bg-back flex justify-between items-center gap-x-20">
|
|
|
+ <text class="flex-1 min-w-1 text-26 ellipsis-1">{{ pageSubtitle }}</text>
|
|
|
+ <view class="flex items-baseline">
|
|
|
+ <text class="text-34 text-primary font-bold">{{ currentIndex + 1 }}</text>/
|
|
|
+ <text class="text-28 text-fore-subtitle">{{ totalCount }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="flex-1 min-h-1 relative">
|
|
|
+ <view class="absolute inset-0 ">
|
|
|
+ <swiper class="h-full" :disable-touch="false" :current="currentIndex" :duration="swiperDuration"
|
|
|
+ @change="handleSwiperChange" @transition="handleSwiperTransition"
|
|
|
+ @animationfinish="handleSwiperAnimationFinish">
|
|
|
+ <swiper-item class="h-full" v-for="(item, index) in questionList" :key="index">
|
|
|
+ <question-item :question="item" />
|
|
|
+ </swiper-item>
|
|
|
+ </swiper>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <ie-safe-toolbar :height="64" :shadow="false">
|
|
|
+ <view class="px-18 h-full flex items-center justify-around border-0 border-t border-solid border-[#EFEFEF]">
|
|
|
+ <view class="w-48 h-48 flex items-center justify-center" @click="handleFavorite">
|
|
|
+ <uv-icon v-if="currentQuestion.isFavorite" name="star-fill" color="#FF9A18" size="27" />
|
|
|
+ <uv-icon v-else name="star" size="27" />
|
|
|
+ </view>
|
|
|
+ <view class="w-48 h-48 flex items-center justify-center" @click="handleMark">
|
|
|
+ <ie-image
|
|
|
+ :src="currentQuestion.isMark ? '/pagesStudy/static/image/icon-mark-active.png' : '/pagesStudy/static/image/icon-mark.png'"
|
|
|
+ custom-class="w-38 h-38" mode="aspectFill" />
|
|
|
+ </view>
|
|
|
+ <view class="w-48 h-48 flex items-center justify-center" @click="handleCalendar">
|
|
|
+ <uv-icon name="calendar" size="28" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </ie-safe-toolbar>
|
|
|
+ </ie-page>
|
|
|
+ <question-stats-popup ref="questionStatsPopupRef">
|
|
|
+ <template #title>
|
|
|
+ <view class="ml-20">
|
|
|
+ <text class="text-30 text-primary">{{ doneCount }}</text>
|
|
|
+ <text>/</text>
|
|
|
+ <text class="text-30 text-fore-light">{{ totalCount }}</text>
|
|
|
+ </view>
|
|
|
+ </template>
|
|
|
+ <view class="popup-content">
|
|
|
+ <view class="flex-1 min-h-1">
|
|
|
+ <scroll-view class="h-full" scroll-y>
|
|
|
+ <view v-for="(item, i) in stateQuestionList" :key="i" class="">
|
|
|
+ <template v-if="item.list.length > 0">
|
|
|
+ <view class="h-60 bg-back px-20 leading-60 text-fore-subcontent">{{ questionTypeDesc[item.type] }}</view>
|
|
|
+ <view class="grid grid-cols-5 place-items-center gap-x-20 gap-y-20">
|
|
|
+ <view v-for="(qs, j) in item.list" :key="j" class="py-20 flex items-center justify-center">
|
|
|
+ <view
|
|
|
+ class="w-52 h-52 rounded-full flex items-center justify-center bg-white border border-solid border-border"
|
|
|
+ :class="{
|
|
|
+ 'is-done': qs.question.isDone,
|
|
|
+ 'is-not-know': qs.question.isNotKnow,
|
|
|
+ 'is-mark': qs.question.isMark
|
|
|
+ }">
|
|
|
+ {{ qs.index + 1 }}
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ </view>
|
|
|
+
|
|
|
+ </template>
|
|
|
+ </view>
|
|
|
+ </scroll-view>
|
|
|
+ </view>
|
|
|
+ <view class="h-150 bg-white flex items-center gap-x-120 px-40">
|
|
|
+ <view class="flex flex-col items-center gap-x-10">
|
|
|
+ <uv-icon name="reload" size="20" />
|
|
|
+ <text class="mt-4 text-20 text-subcontent">重新作答</text>
|
|
|
+ </view>
|
|
|
+ <view class="flex-1 py-20 text-center rounded-full bg-primary text-white">交卷</view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </question-stats-popup>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script lang="ts" setup>
|
|
|
+import QuestionItem from './components/question-item.vue';
|
|
|
+import QuestionStatsPopup from './components/question-stats-popup.vue';
|
|
|
+import { useTransferPage } from '@/hooks/useTransferPage';
|
|
|
+import { EnumExamMode, EnumQuestionType } from '@/common/enum';
|
|
|
+import { getOpenExaminee } from '@/api/modules/study';
|
|
|
+import { useExam } from '@/composables/useExam';
|
|
|
+import { Study } from '@/types';
|
|
|
+import { NEXT_QUESTION, PREV_QUESTION, NEXT_QUESTION_QUICKLY, PREV_QUESTION_QUICKLY } from '@/types/injectionSymbols';
|
|
|
+const { prevData } = useTransferPage();
|
|
|
+const { questionList, stateQuestionList, questionTypeDesc, favoriteList, notKnowList, markList, currentIndex,
|
|
|
+ totalCount, doneCount, notDoneCount, notKnowCount, markCount,
|
|
|
+ loadExamData, nextQuestion, prevQuestion, nextQuestionQuickly, prevQuestionQuickly, swiperDuration,
|
|
|
+ formatPracticeDuration, formatExamDuration, practiceDuration, startPracticeDuration, stopPracticeDuration,
|
|
|
+ examDuration, startExamDuration, stopExamDuration, setExamDuration, setCountDownCallback } = useExam();
|
|
|
+
|
|
|
+provide(NEXT_QUESTION, nextQuestion);
|
|
|
+provide(PREV_QUESTION, prevQuestion);
|
|
|
+provide(NEXT_QUESTION_QUICKLY, nextQuestionQuickly);
|
|
|
+provide(PREV_QUESTION_QUICKLY, prevQuestionQuickly);
|
|
|
+
|
|
|
+const isAnimationFinish = ref(false);
|
|
|
+const transitionStartX = ref(null);
|
|
|
+const transitionEndX = ref(null);
|
|
|
+const pageTitle = computed(() => {
|
|
|
+ const { mode } = prevData.value;
|
|
|
+ return mode === EnumExamMode.PRACTICE ? '练习' : '考试';
|
|
|
+});
|
|
|
+const pageSubtitle = computed(() => {
|
|
|
+ const { mode, name } = prevData.value;
|
|
|
+ return (mode === EnumExamMode.PRACTICE ? '知识点练习' : '考试') + '-' + name;
|
|
|
+});
|
|
|
+const isExamMode = computed(() => {
|
|
|
+ return prevData.value.mode === EnumExamMode.EXAM;
|
|
|
+});
|
|
|
+const handleLeftClick = () => {
|
|
|
+ beforeQuit();
|
|
|
+};
|
|
|
+const handleSwiperChange = (e: any) => {
|
|
|
+ console.log(e)
|
|
|
+ currentIndex.value = e.detail.current;
|
|
|
+};
|
|
|
+const beforeQuit = () => {
|
|
|
+ const { mode } = prevData.value;
|
|
|
+ if (mode === EnumExamMode.PRACTICE) {
|
|
|
+ beforeQuitPractice();
|
|
|
+ } else {
|
|
|
+ beforeQuitExam();
|
|
|
+ }
|
|
|
+};
|
|
|
+const beforeQuitPractice = () => {
|
|
|
+ uni.$ie.showModal({
|
|
|
+ title: '提示',
|
|
|
+ content: '当前练习未完成,确认退出?',
|
|
|
+ }).then(confirm => {
|
|
|
+ if (confirm) {
|
|
|
+ uni.$ie.showLoading('保存中...');
|
|
|
+ setTimeout(() => {
|
|
|
+ uni.$ie.hideLoading();
|
|
|
+ uni.navigateBack();
|
|
|
+ }, 1000);
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+const beforeQuitExam = () => {
|
|
|
+ uni.$ie.showModal({
|
|
|
+ title: '提示',
|
|
|
+ content: '当前考试未完成,确认退出?',
|
|
|
+ }).then(confirm => {
|
|
|
+ if (confirm) {
|
|
|
+ uni.navigateBack();
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+const currentQuestion = computed(() => {
|
|
|
+ console.log(questionList.value[currentIndex.value])
|
|
|
+ return questionList.value[currentIndex.value];
|
|
|
+});
|
|
|
+const handleFavorite = () => {
|
|
|
+ console.log('handleFavorite')
|
|
|
+ currentQuestion.value.isFavorite = !currentQuestion.value.isFavorite;
|
|
|
+};
|
|
|
+const handleMark = () => {
|
|
|
+ console.log('handleMark')
|
|
|
+ currentQuestion.value.isMark = !currentQuestion.value.isMark;
|
|
|
+};
|
|
|
+const questionStatsPopupRef = ref();
|
|
|
+const handleCalendar = () => {
|
|
|
+ console.log('handleCalendar')
|
|
|
+ questionStatsPopupRef.value.open();
|
|
|
+};
|
|
|
+
|
|
|
+const handleSwiperTransition = (e: any) => {
|
|
|
+ if (currentIndex.value === questionList.value.length - 1) {
|
|
|
+ if (!transitionStartX.value) {
|
|
|
+ transitionStartX.value = e.detail.dx;
|
|
|
+ } else {
|
|
|
+ transitionEndX.value = e.detail.dx;
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+};
|
|
|
+const startTime = () => {
|
|
|
+ if (isExamMode.value) {
|
|
|
+ startExamDuration();
|
|
|
+ } else {
|
|
|
+ startPracticeDuration();
|
|
|
+ }
|
|
|
+}
|
|
|
+const stopTime = () => {
|
|
|
+ if (isExamMode.value) {
|
|
|
+ stopExamDuration();
|
|
|
+ } else {
|
|
|
+ stopPracticeDuration();
|
|
|
+ }
|
|
|
+}
|
|
|
+const handleSwiperAnimationFinish = (e: any) => {
|
|
|
+ if (transitionStartX.value == null || transitionEndX.value == null || currentIndex.value !== questionList.value.length - 1) {
|
|
|
+ isAnimationFinish.value = true;
|
|
|
+ transitionStartX.value = null;
|
|
|
+ transitionEndX.value = null;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const offsetX = transitionEndX.value - transitionStartX.value;
|
|
|
+ if (offsetX < 0 && offsetX > -150) {
|
|
|
+ const text = notDoneCount.value > 0 ? `还有${notDoneCount.value}题未做,确认交卷?` : '是否确认交卷?';
|
|
|
+ stopTime();
|
|
|
+ uni.$ie.showModal({
|
|
|
+ title: '提示',
|
|
|
+ content: text,
|
|
|
+ }).then(confirm => {
|
|
|
+ if (confirm) {
|
|
|
+ // uni.navigateBack();
|
|
|
+ handleSubmit();
|
|
|
+ } else {
|
|
|
+ startTime();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ isAnimationFinish.value = true;
|
|
|
+ transitionStartX.value = null;
|
|
|
+ transitionEndX.value = null;
|
|
|
+};
|
|
|
+const handleSubmit = () => {
|
|
|
+ uni.$ie.showLoading('保存中...');
|
|
|
+ setTimeout(() => {
|
|
|
+ uni.$ie.hideLoading();
|
|
|
+ uni.navigateBack();
|
|
|
+ }, 1000);
|
|
|
+ console.log('handleSubmit')
|
|
|
+}
|
|
|
+const loadData = async () => {
|
|
|
+ // const { data } = await getOpenExaminee({
|
|
|
+ // paperType: prevData.value.paperType,
|
|
|
+ // relateId: prevData.value.relateId
|
|
|
+ // });
|
|
|
+ // console.log(data)
|
|
|
+ // 构造模拟数据
|
|
|
+ console.log(questionList.value)
|
|
|
+ loadExamData();
|
|
|
+ // setExamDuration(35)
|
|
|
+ setCountDownCallback(() => {
|
|
|
+ uni.$ie.showToast('考试结束');
|
|
|
+ // uni.navigateBack();
|
|
|
+ });
|
|
|
+ startTime();
|
|
|
+ // startExamDuration();
|
|
|
+};
|
|
|
+onMounted(() => {
|
|
|
+ setTimeout(() => {
|
|
|
+ console.log(stateQuestionList.value)
|
|
|
+ handleCalendar();
|
|
|
+ }, 500);
|
|
|
+});
|
|
|
+onLoad(() => {
|
|
|
+ console.log(prevData.value)
|
|
|
+ loadData();
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.countdown-text {
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ font-weight: bold;
|
|
|
+ font-family: 'Courier New', Courier, monospace;
|
|
|
+}
|
|
|
+
|
|
|
+.popup-content {
|
|
|
+ @apply h-[42vh] flex flex-col;
|
|
|
+}
|
|
|
+
|
|
|
+.scroll-view {
|
|
|
+ @apply h-full;
|
|
|
+}
|
|
|
+
|
|
|
+.is-done {
|
|
|
+ @apply text-primary border-[#EBF9FF] bg-[#EBF9FF];
|
|
|
+}
|
|
|
+
|
|
|
+.is-not-know {
|
|
|
+ @apply border-[#F2F2F2] bg-[#F2F2F2];
|
|
|
+}
|
|
|
+</style>
|