start-exam.vue 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. <template>
  2. <ie-page :fix-height="true" :safe-area-inset-bottom="false">
  3. <ie-navbar :title="pageTitle" custom-back @left-click="handleLeftClick">
  4. <template #headerRight>
  5. <view v-if="isExamMode" class="countdown-text" :class="{ 'text-red-500': examDuration < 30 }">
  6. {{ formatExamDuration }}
  7. </view>
  8. <view v-else class="">{{ formatPracticeDuration }}</view>
  9. </template>
  10. </ie-navbar>
  11. <view class="px-20 py-14 bg-back flex justify-between items-center gap-x-20">
  12. <text class="flex-1 min-w-1 text-26 ellipsis-1">{{ pageSubtitle }}</text>
  13. <view class="flex items-baseline">
  14. <text class="text-34 text-primary font-bold">{{ currentIndex + 1 }}</text>/
  15. <text class="text-28 text-fore-subtitle">{{ totalCount }}</text>
  16. </view>
  17. </view>
  18. <view class="flex-1 min-h-1 relative">
  19. <view class="absolute inset-0 ">
  20. <swiper class="h-full" :disable-touch="false" :current="currentIndex" :duration="swiperDuration"
  21. @change="handleSwiperChange" @transition="handleSwiperTransition"
  22. @animationfinish="handleSwiperAnimationFinish">
  23. <swiper-item class="h-full" v-for="(item, index) in questionList" :key="index">
  24. <question-item :question="item" />
  25. </swiper-item>
  26. </swiper>
  27. </view>
  28. </view>
  29. <ie-safe-toolbar :height="64" :shadow="false">
  30. <view class="px-18 h-full flex items-center justify-around border-0 border-t border-solid border-[#EFEFEF]">
  31. <view class="w-48 h-48 flex items-center justify-center" @click="handleFavorite">
  32. <uv-icon v-if="currentQuestion.isFavorite" name="star-fill" color="#FF9A18" size="27" />
  33. <uv-icon v-else name="star" size="27" />
  34. </view>
  35. <view class="w-48 h-48 flex items-center justify-center" @click="handleMark">
  36. <ie-image
  37. :src="currentQuestion.isMark ? '/pagesStudy/static/image/icon-mark-active.png' : '/pagesStudy/static/image/icon-mark.png'"
  38. custom-class="w-38 h-38" mode="aspectFill" />
  39. </view>
  40. <view class="w-48 h-48 flex items-center justify-center" @click="handleCalendar">
  41. <uv-icon name="calendar" size="28" />
  42. </view>
  43. </view>
  44. </ie-safe-toolbar>
  45. </ie-page>
  46. <question-stats-popup ref="questionStatsPopupRef">
  47. <template #title>
  48. <view class="ml-20">
  49. <text class="text-30 text-primary">{{ doneCount }}</text>
  50. <text>/</text>
  51. <text class="text-30 text-fore-light">{{ totalCount }}</text>
  52. </view>
  53. </template>
  54. <view class="popup-content">
  55. <view class="flex-1 min-h-1">
  56. <scroll-view class="h-full" scroll-y>
  57. <view v-for="(item, i) in stateQuestionList" :key="i" class="">
  58. <template v-if="item.list.length > 0">
  59. <view class="h-60 bg-back px-20 leading-60 text-fore-subcontent">{{ questionTypeDesc[item.type] }}</view>
  60. <view class="grid grid-cols-5 place-items-center gap-x-20 gap-y-20">
  61. <view v-for="(qs, j) in item.list" :key="j" class="py-20 flex items-center justify-center">
  62. <view
  63. class="w-52 h-52 rounded-full flex items-center justify-center bg-white border border-solid border-border"
  64. :class="{
  65. 'is-done': qs.question.isDone,
  66. 'is-not-know': qs.question.isNotKnow,
  67. 'is-mark': qs.question.isMark
  68. }">
  69. {{ qs.index + 1 }}
  70. </view>
  71. </view>
  72. </view>
  73. </template>
  74. </view>
  75. </scroll-view>
  76. </view>
  77. <view class="h-150 bg-white flex items-center gap-x-120 px-40">
  78. <view class="flex flex-col items-center gap-x-10">
  79. <uv-icon name="reload" size="20" />
  80. <text class="mt-4 text-20 text-subcontent">重新作答</text>
  81. </view>
  82. <view class="flex-1 py-20 text-center rounded-full bg-primary text-white">交卷</view>
  83. </view>
  84. </view>
  85. </question-stats-popup>
  86. </template>
  87. <script lang="ts" setup>
  88. import QuestionItem from './components/question-item.vue';
  89. import QuestionStatsPopup from './components/question-stats-popup.vue';
  90. import { useTransferPage } from '@/hooks/useTransferPage';
  91. import { EnumExamMode, EnumQuestionType } from '@/common/enum';
  92. import { getOpenExaminee } from '@/api/modules/study';
  93. import { useExam } from '@/composables/useExam';
  94. import { Study } from '@/types';
  95. import { NEXT_QUESTION, PREV_QUESTION, NEXT_QUESTION_QUICKLY, PREV_QUESTION_QUICKLY } from '@/types/injectionSymbols';
  96. const { prevData } = useTransferPage();
  97. const { questionList, stateQuestionList, questionTypeDesc, favoriteList, notKnowList, markList, currentIndex,
  98. totalCount, doneCount, notDoneCount, notKnowCount, markCount,
  99. loadExamData, nextQuestion, prevQuestion, nextQuestionQuickly, prevQuestionQuickly, swiperDuration,
  100. formatPracticeDuration, formatExamDuration, practiceDuration, startPracticeDuration, stopPracticeDuration,
  101. examDuration, startExamDuration, stopExamDuration, setExamDuration, setCountDownCallback } = useExam();
  102. provide(NEXT_QUESTION, nextQuestion);
  103. provide(PREV_QUESTION, prevQuestion);
  104. provide(NEXT_QUESTION_QUICKLY, nextQuestionQuickly);
  105. provide(PREV_QUESTION_QUICKLY, prevQuestionQuickly);
  106. const isAnimationFinish = ref(false);
  107. const transitionStartX = ref(null);
  108. const transitionEndX = ref(null);
  109. const pageTitle = computed(() => {
  110. const { mode } = prevData.value;
  111. return mode === EnumExamMode.PRACTICE ? '练习' : '考试';
  112. });
  113. const pageSubtitle = computed(() => {
  114. const { mode, name } = prevData.value;
  115. return (mode === EnumExamMode.PRACTICE ? '知识点练习' : '考试') + '-' + name;
  116. });
  117. const isExamMode = computed(() => {
  118. return prevData.value.mode === EnumExamMode.EXAM;
  119. });
  120. const handleLeftClick = () => {
  121. beforeQuit();
  122. };
  123. const handleSwiperChange = (e: any) => {
  124. console.log(e)
  125. currentIndex.value = e.detail.current;
  126. };
  127. const beforeQuit = () => {
  128. const { mode } = prevData.value;
  129. if (mode === EnumExamMode.PRACTICE) {
  130. beforeQuitPractice();
  131. } else {
  132. beforeQuitExam();
  133. }
  134. };
  135. const beforeQuitPractice = () => {
  136. uni.$ie.showModal({
  137. title: '提示',
  138. content: '当前练习未完成,确认退出?',
  139. }).then(confirm => {
  140. if (confirm) {
  141. uni.$ie.showLoading('保存中...');
  142. setTimeout(() => {
  143. uni.$ie.hideLoading();
  144. uni.navigateBack();
  145. }, 1000);
  146. }
  147. });
  148. };
  149. const beforeQuitExam = () => {
  150. uni.$ie.showModal({
  151. title: '提示',
  152. content: '当前考试未完成,确认退出?',
  153. }).then(confirm => {
  154. if (confirm) {
  155. uni.navigateBack();
  156. }
  157. });
  158. };
  159. const currentQuestion = computed(() => {
  160. console.log(questionList.value[currentIndex.value])
  161. return questionList.value[currentIndex.value];
  162. });
  163. const handleFavorite = () => {
  164. console.log('handleFavorite')
  165. currentQuestion.value.isFavorite = !currentQuestion.value.isFavorite;
  166. };
  167. const handleMark = () => {
  168. console.log('handleMark')
  169. currentQuestion.value.isMark = !currentQuestion.value.isMark;
  170. };
  171. const questionStatsPopupRef = ref();
  172. const handleCalendar = () => {
  173. console.log('handleCalendar')
  174. questionStatsPopupRef.value.open();
  175. };
  176. const handleSwiperTransition = (e: any) => {
  177. if (currentIndex.value === questionList.value.length - 1) {
  178. if (!transitionStartX.value) {
  179. transitionStartX.value = e.detail.dx;
  180. } else {
  181. transitionEndX.value = e.detail.dx;
  182. }
  183. return;
  184. }
  185. };
  186. const startTime = () => {
  187. if (isExamMode.value) {
  188. startExamDuration();
  189. } else {
  190. startPracticeDuration();
  191. }
  192. }
  193. const stopTime = () => {
  194. if (isExamMode.value) {
  195. stopExamDuration();
  196. } else {
  197. stopPracticeDuration();
  198. }
  199. }
  200. const handleSwiperAnimationFinish = (e: any) => {
  201. if (transitionStartX.value == null || transitionEndX.value == null || currentIndex.value !== questionList.value.length - 1) {
  202. isAnimationFinish.value = true;
  203. transitionStartX.value = null;
  204. transitionEndX.value = null;
  205. return;
  206. }
  207. const offsetX = transitionEndX.value - transitionStartX.value;
  208. if (offsetX < 0 && offsetX > -150) {
  209. const text = notDoneCount.value > 0 ? `还有${notDoneCount.value}题未做,确认交卷?` : '是否确认交卷?';
  210. stopTime();
  211. uni.$ie.showModal({
  212. title: '提示',
  213. content: text,
  214. }).then(confirm => {
  215. if (confirm) {
  216. // uni.navigateBack();
  217. handleSubmit();
  218. } else {
  219. startTime();
  220. }
  221. });
  222. }
  223. isAnimationFinish.value = true;
  224. transitionStartX.value = null;
  225. transitionEndX.value = null;
  226. };
  227. const handleSubmit = () => {
  228. uni.$ie.showLoading('保存中...');
  229. setTimeout(() => {
  230. uni.$ie.hideLoading();
  231. uni.navigateBack();
  232. }, 1000);
  233. console.log('handleSubmit')
  234. }
  235. const loadData = async () => {
  236. // const { data } = await getOpenExaminee({
  237. // paperType: prevData.value.paperType,
  238. // relateId: prevData.value.relateId
  239. // });
  240. // console.log(data)
  241. // 构造模拟数据
  242. console.log(questionList.value)
  243. loadExamData();
  244. // setExamDuration(35)
  245. setCountDownCallback(() => {
  246. uni.$ie.showToast('考试结束');
  247. // uni.navigateBack();
  248. });
  249. startTime();
  250. // startExamDuration();
  251. };
  252. onMounted(() => {
  253. setTimeout(() => {
  254. console.log(stateQuestionList.value)
  255. handleCalendar();
  256. }, 500);
  257. });
  258. onLoad(() => {
  259. console.log(prevData.value)
  260. loadData();
  261. });
  262. </script>
  263. <style lang="scss" scoped>
  264. .countdown-text {
  265. height: 100%;
  266. display: flex;
  267. align-items: center;
  268. font-weight: bold;
  269. font-family: 'Courier New', Courier, monospace;
  270. }
  271. .popup-content {
  272. @apply h-[42vh] flex flex-col;
  273. }
  274. .scroll-view {
  275. @apply h-full;
  276. }
  277. .is-done {
  278. @apply text-primary border-[#EBF9FF] bg-[#EBF9FF];
  279. }
  280. .is-not-know {
  281. @apply border-[#F2F2F2] bg-[#F2F2F2];
  282. }
  283. </style>