kz-question.vue 60 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177
  1. <template>
  2. <view class="questions">
  3. <!-- 头部信息 -->
  4. <view class="test-header" v-if="showCountDown">
  5. <tui-countdown :time="limit_time" borderColor="#FFF" color="#080808" :size="36" :colonSize="36"
  6. @end="endOfTime"></tui-countdown>
  7. </view>
  8. <!-- 题目列表 -->
  9. <view class="card-shadow">
  10. <view class="topic-title">
  11. <view class="topic-title_left">
  12. <!-- 题型 -->
  13. <view class="text-kind">
  14. {{ kindText() }}
  15. </view>
  16. <!-- 收藏 -->
  17. <view class="test-favor" v-if="canCollect">
  18. <tui-icon name="like-fill" color="#f74d54" :size="18" @click="collectDel()"
  19. v-if="list[swiperIndex-1] && list[swiperIndex-1].collected"></tui-icon>
  20. <tui-icon name="like" color="#aaa" :size="18" @click="collectAdd()" v-else></tui-icon>
  21. <view class="tui-fabulous"
  22. :class="{ 'tui-fabulous__active': list[swiperIndex-1] && list[swiperIndex-1].collected }">
  23. <tui-icon name="like-fill" color="#f74d54"></tui-icon>
  24. </view>
  25. </view>
  26. </view>
  27. <!-- 题标 -->
  28. <view class="topic-title_right">
  29. <text class="title-index">{{ swiperIndex }}</text>
  30. /{{ total }}
  31. </view>
  32. </view>
  33. <!-- 防切屏提示 -->
  34. <view class="topic-title" style="font-size: 24rpx;" v-if="isPreventSwitchScreen">
  35. 注意:当前考试开启了防切屏,切屏次数超过{{ switchScreenCount }}次考试将自动结束
  36. </view>
  37. <swiper :current="swiperIndex - 1" @change="swiperChange" class="questions-cont">
  38. <swiper-item v-for="(item, index) in list" :key="index" class="swiper-item">
  39. <block v-if="index == swiperIndex - 1">
  40. <!-- 材料题标题 -->
  41. <view class="material-title" v-if="item.material_title != undefined">
  42. <view class="material-title-tip">材料:</view>
  43. <view v-if="!item.show_full">
  44. <rich-text :nodes="questionTitle(swiperIndex, item, 'material_title')"></rich-text>
  45. <button @click="showFullMaterialTitle(swiperIndex, item, true)">展开</button>
  46. </view>
  47. <view v-else>
  48. <rich-text :nodes="questionTitle(swiperIndex, item, 'material_title')"></rich-text>
  49. <button @click="showFullMaterialTitle(swiperIndex, item, false)">收起</button>
  50. </view>
  51. </view>
  52. <!-- 题目视频 -->
  53. <view v-if="item.title_video_url != undefined && item.title_video" class="title-video">
  54. <video id="title-video" class="title-video" :src="item.title_video_url" :controls="true"></video>
  55. </view>
  56. <!-- 判断、单选、多选题 -->
  57. <view v-if="item.kind === 'JUDGE' || item.kind === 'SINGLE' || item.kind === 'MULTI'">
  58. <view class="test-main">
  59. <view class="test-title">
  60. <rich-text :nodes="questionTitle(swiperIndex, item)"
  61. style="font-size: 36rpx; font-weight: bold;"></rich-text>
  62. </view>
  63. <view class="test-cont">
  64. <!-- optionItem.click_index ? 'active_true' : '' optionChooseClass(index, optionIndex, item, optionItem) -->
  65. <view class="test-cont-item"
  66. v-for="(optionItem, optionIndex) in item.options_json" :key="optionIndex"
  67. @tap.stop="chooseItem(index, optionIndex, item.kind)"
  68. :class="optionItem.classes ? optionItem.classes : ''">
  69. <view class="key text-cut">
  70. {{ optionItem.key }}
  71. </view>
  72. <view class="cont">
  73. <view class="cont-text">
  74. <block v-if="item.options_img" v-for="(optionImg, optionImgIndex) in item.options_img"
  75. :key="optionImgIndex">
  76. <image class="image" v-if="optionImg.key == optionItem.key"
  77. @tap.stop="previewImage(optionImg.value)" :src="optionImg.value"
  78. mode="widthFix"></image>
  79. </block>
  80. <view>{{ optionItem.value ? optionItem.value : '' }}</view>
  81. </view>
  82. <view class="cont-icon">
  83. <view v-if="optionItem.classes">
  84. <text v-if="optionItem.classes.indexOf('true') != -1" class="cuIcon-check"></text>
  85. <text
  86. v-else-if="optionItem.classes.indexOf('true') == -1 && optionItem.classes.indexOf('active') != -1"
  87. class="cuIcon-close"></text>
  88. </view>
  89. </view>
  90. </view>
  91. </view>
  92. </view>
  93. </view>
  94. <view class="test-describe" v-if="showAnswer || item.show_answer">
  95. <view class="describe-cont">
  96. <view>
  97. 正确答案:
  98. <text style="color: #5677fc;">{{ item.answer }}</text>
  99. </view>
  100. <view v-if="mode == 'VIEW' && item.user_answer">
  101. 用户答案:
  102. <text>{{ item.user_answer }}</text>
  103. </view>
  104. <view>
  105. <view>答案解析:</view>
  106. <view>
  107. <rich-text :nodes="questionExplain(swiperIndex, item)"
  108. style="border-top: solid 1px #ccc;width: 100%;display: block;"
  109. v-if="item.explain"></rich-text>
  110. <text v-else>无</text>
  111. </view>
  112. </view>
  113. </view>
  114. </view>
  115. </view>
  116. <!-- 填空题 -->
  117. <view v-else-if="item.kind === 'FILL'">
  118. <view class="test-main">
  119. <view class="test-title test-title-fill">
  120. <block v-for="(titleText, titleIndex) in item.title_data" :key="titleIndex">
  121. <view class="test-title-fill-item">{{ titleText }}</view>
  122. <view class="test-title-fill-item">
  123. <!-- #ifdef MP-WEIXIN -->
  124. <input type="text" placeholder="请输入答案" class="fill-input"
  125. v-if="item.title_data && item.title_data.length - 1 != titleIndex"
  126. :class="[item.answer && item.answer[titleIndex] && item.answer[titleIndex].class ? item.answer[titleIndex].class : '']"
  127. :value="setFillInputValue(titleIndex)"
  128. :disabled="mode === 'VIEW'"
  129. @change="(e) => changeFillInput(e, titleIndex, swiperIndex)"
  130. />
  131. <!-- #endif -->
  132. <!-- #ifdef H5 -->
  133. <input type="text" placeholder="请输入答案" class="fill-input"
  134. v-if="item.title_data && item.title_data.length - 1 != titleIndex"
  135. :class="[item.answer && item.answer[titleIndex] && item.answer[titleIndex].class ? item.answer[titleIndex].class : '']"
  136. :value="setFillInputValue(titleIndex)"
  137. :disabled="mode === 'VIEW'"
  138. @blur="(e) => changeFillInput(e, titleIndex, swiperIndex)"
  139. />
  140. <!-- #endif -->
  141. </view>
  142. </block>
  143. <view class="test-title-fill-item">{{ getQuestionTitleScore(index, item) }}</view>
  144. </view>
  145. <view class="test-cont">
  146. <!-- 确认答案按钮 -->
  147. <view class="btn-confirm" v-if="mode == 'TRAINING' && item.is_answered === false">
  148. <tui-button type="primary" shape="circle" @click="confirmFillAnswer" width="360rpx" height="60rpx"
  149. :size="26" margin="0 auto">确认答案
  150. </tui-button>
  151. </view>
  152. </view>
  153. </view>
  154. <view class="test-describe" v-if="showAnswer || item.show_answer">
  155. <view class="describe-cont">
  156. <view>
  157. 正确答案:
  158. <view v-for="(answer, answerIndex) in item.answer" :key="answerIndex">
  159. 填空位{{ answerIndex + 1 }}答案:{{ answer.answers.join(',') }}
  160. </view>
  161. </view>
  162. <view v-if="mode == 'VIEW' && item.user_answer">
  163. 用户答案:
  164. <view v-for="(userAnswer, userAnswerIndex) in item.user_answer" :key="userAnswerIndex">
  165. 填空位{{ userAnswerIndex + 1 }}答案:{{ userAnswer }}
  166. </view>
  167. </view>
  168. <view>
  169. <view>答案解析:</view>
  170. <view>
  171. <rich-text :nodes="questionExplain(swiperIndex, item)"
  172. style="border-top: solid 1px #ccc;width: 100%;display: block;"
  173. v-if="item.explain"></rich-text>
  174. <text v-else>无</text>
  175. </view>
  176. </view>
  177. </view>
  178. </view>
  179. </view>
  180. <!-- 简答题 -->
  181. <view v-else-if="item.kind === 'SHORT'">
  182. <view class="test-main">
  183. <view class="test-title">
  184. <rich-text :nodes="questionTitle(swiperIndex, item)"
  185. style="font-size: 36rpx; font-weight: bold;"></rich-text>
  186. </view>
  187. <view class="test-cont">
  188. <!-- 答案输入框 -->
  189. <textarea placeholder="在此输入答案" class="short-input"
  190. :maxlength="-1"
  191. :value="setShortInputValue(swiperIndex)"
  192. :auto-blur="true"
  193. @blur="(e) => changeShortInput(e, swiperIndex)"
  194. @input="(e) => changeShortInput(e, swiperIndex)"
  195. />
  196. <!-- 确认答案按钮 -->
  197. <view class="btn-confirm" v-if="mode == 'TRAINING'">
  198. <tui-button type="primary" shape="circle" @click="confirmShortAnswer" width="360rpx" height="60rpx"
  199. :size="26" margin="0 auto">确认答案
  200. </tui-button>
  201. </view>
  202. </view>
  203. </view>
  204. <view class="test-describe" v-if="showAnswer || item.show_answer">
  205. <view class="describe-cont">
  206. <view>
  207. <view>
  208. 正确答案:
  209. <text style="color: #5677fc;" v-if="item.answer && item.answer.answer">{{
  210. item.answer.answer
  211. }}
  212. </text>
  213. </view>
  214. <view v-for="(keyword, keywordIndex) in item.answer.config" :key="keywordIndex">
  215. <view :class="[keyword.class]">
  216. 关键词{{ keywordIndex + 1 }}:{{ keyword.answer }}({{ keyword.score }}分)
  217. </view>
  218. </view>
  219. </view>
  220. <view>
  221. <view>答案解析:</view>
  222. <view>
  223. <rich-text :nodes="questionExplain(swiperIndex, item)"
  224. style="border-top: solid 1px #ccc;width: 100%;display: block;"
  225. v-if="item.explain"></rich-text>
  226. <text v-else>无</text>
  227. </view>
  228. </view>
  229. </view>
  230. </view>
  231. </view>
  232. <!-- 解析视频 -->
  233. <view class="explain-video-view"
  234. v-if="(showAnswer || item.show_answer) && item.explain_video_url != undefined && item.explain_video">
  235. <video id="explain-video" class="explain-video" :src="item.explain_video_url" controls></video>
  236. </view>
  237. </block>
  238. <view style="height: 100rpx;"></view>
  239. </swiper-item>
  240. </swiper>
  241. </view>
  242. <!-- 底部栏 -->
  243. <!-- :class="mode == 'EXAM' || mode == 'VIEW' ? (canDeleteWrong ? ['col-5'] : ['col-4']) : ['col-3']" -->
  244. <view class="fix-bottom grid text-center bg-white cu-list" :class="[getBottomBarClass()]">
  245. <view url="/pages/index/index" class="cu-item" @click="handleNumberPanel"><!-- v-if="mode != 'TRAINING'" -->
  246. <image src="/static/img/caidan.png"></image>
  247. <text>{{ swiperIndex }}/{{ total }}</text>
  248. </view>
  249. <view url="/pages/index/index" class="cu-item" @tap="prev">
  250. <image src="/static/img/left.png"></image>
  251. <text>上一题</text>
  252. </view>
  253. <view url="/pages/index/index" class="cu-item" @tap="next">
  254. <image src="/static/img/right.png"></image>
  255. <text>下一题</text>
  256. </view>
  257. <view url="/pages/index/index" class="cu-item" @tap="buttonClicked==true?submitShowModal():''"
  258. v-if="mode == 'EXAM'">
  259. <image src="/static/img/jiaojuan.png"></image>
  260. <text>交卷</text>
  261. </view>
  262. <view url="/pages/index/index" class="cu-item" @tap="buttonClicked==true?endTrainShowModal():''"
  263. v-if="mode == 'TRAINING'">
  264. <image src="/static/img/jiaojuan.png"></image>
  265. <text>结束练习</text>
  266. </view>
  267. <view url="/pages/index/index" class="cu-item" @tap="wrongDel()" v-if="mode == 'VIEW' && canDeleteWrong">
  268. <image src="/static/img/delete.png"></image>
  269. <text>删除</text>
  270. </view>
  271. <!-- <view url="/pages/index/index" class="cu-item" @tap="wrongClear()" v-if="mode == 'VIEW' && canDeleteWrong">
  272. <image src="/static/img/round_close.png"></image>
  273. <text>清空</text>
  274. </view> -->
  275. </view>
  276. <!-- 题标 -->
  277. <!-- <view class="fixed-bottom" :class="{ active: showNumberPanel }" @click.stop="handleNumberPanel">
  278. <view class="tibiao" @click.stop>
  279. <scroll-view scroll-y="true" class="tibiao-scroll">
  280. <view class="tibiao-item" v-for="(item, index) in total" :key="index" :class="swiperIndex - 1 == index ? 'selected' : ''"
  281. @click="changeQuestion(index)">
  282. {{ index + 1 }}
  283. </view>
  284. </scroll-view>
  285. </view>
  286. </view> -->
  287. <!-- 题标 -->
  288. <view class="cu-modal bottom-modal fixed-bottom" :class="showNumberPanel?'show':''" @click.stop="handleNumberPanel">
  289. <view class="cu-dialog tibiao" @click.stop>
  290. <scroll-view scroll-y="true" class="tibiao-scroll">
  291. <view class="tibiao-scroll-list">
  292. <!-- class="tibiao-item" -->
  293. <!-- :class="[getNumberPanelClass(index)]" -->
  294. <!-- :class="swiperIndex - 1 == index ? 'selected' : (list[index] && (list[index].check || list[index].user_answers) ? 'right' : '')" -->
  295. <view
  296. :class="['tibiao-item', getNumberPanelClass(index)]"
  297. v-for="(item, index) in total" :key="index" @click="changeQuestion(index)">
  298. {{ index + 1 }}
  299. </view>
  300. </view>
  301. </scroll-view>
  302. </view>
  303. </view>
  304. <!-- 删除错题对话框 -->
  305. <view class="cu-modal" :class="showDeleteDialog?'show':''">
  306. <view class="cu-dialog">
  307. <view class="cu-bar bg-white justify-end">
  308. <view class="content">提示</view>
  309. <view class="action" @tap="hideModal">
  310. <text class="cuIcon-close text-red"></text>
  311. </view>
  312. </view>
  313. <view class="padding-xl">
  314. {{ wrongDeleteType == 'single' ? '确定删除该错题吗?' : '确定清空所有错题吗?' }}
  315. </view>
  316. <view class="cu-bar bg-white justify-end">
  317. <view class="action">
  318. <button class="cu-btn line-primary text-primary" @tap="hideModal">取消</button>
  319. <button class="cu-btn bg-primary margin-left" @tap="confirmDelWrong">确定</button>
  320. </view>
  321. </view>
  322. </view>
  323. </view>
  324. <!-- 结束练习对话框 -->
  325. <tui-modal :show="showEndTrainDialog" @cancel="hideModal" :custom="true">
  326. <view class="tui-modal-custom">
  327. <view class="tui-prompt-title">练习结果</view>
  328. <view class="tui-modal-custom-text">
  329. <view class="">
  330. 正确题数:
  331. <text class="text-green">{{ trainResult.right }}</text>
  332. </view>
  333. <view class="">
  334. 错误题数:
  335. <text class="text-red">{{ trainResult.error }}</text>
  336. </view>
  337. <view class="">
  338. 未答题数:
  339. <text class="text-yellow">{{ trainResult.unchecked }}</text>
  340. </view>
  341. </view>
  342. <view class="tui-flex-box">
  343. <view class="tui-flex-botton-view">
  344. <tui-button margin="0 20rpx 0 0" height="72rpx" :size="28" shape="circle" @click="endTrain">确定退出
  345. </tui-button>
  346. </view>
  347. <view class="tui-flex-botton-view">
  348. <tui-button margin="0 20rpx 0 0" height="72rpx" :size="28" shape="circle" @click="hideModal" plain>取消
  349. </tui-button>
  350. </view>
  351. </view>
  352. </view>
  353. </tui-modal>
  354. <!-- 悬浮按钮 -->
  355. <tn-fab
  356. :btnList="fabBtnList"
  357. left="auto"
  358. :right="40"
  359. :bottom="180"
  360. :width="88"
  361. :height="88"
  362. :iconSize="45"
  363. :zIndex="998"
  364. backgroundColor="fab-bg-color"
  365. fontColor="#aaa"
  366. icon="up"
  367. animationType="up"
  368. :showMask="false"
  369. :customBtn="true"
  370. @click="clickFabItem"
  371. >
  372. </tn-fab>
  373. <!-- 纠错反馈弹窗 -->
  374. <tn-popup
  375. v-model="showCorrection"
  376. length="50%"
  377. mode="bottom"
  378. backgroundColor="#fff"
  379. :zIndex="999"
  380. :borderRadius="23"
  381. :closeBtn="true"
  382. :maskCloseable="false"
  383. >
  384. <view class="popup-content" :class="{'popup-content--center': mode === 'center'}">
  385. <view class="tn-border-solid-bottom margin-top padding-bottom text-center text-bold" style="font-size: 30rpx;">
  386. 纠错反馈
  387. </view>
  388. <view class="padding">
  389. <tn-checkbox-group @change="correctionTypeChange" activeColor="#5677fc" :size="28">
  390. <tn-checkbox :labelSize="28" v-model="item.checked" v-for="(item, index) in correctionTypeslist"
  391. :key="index" :name="item.name">{{ item.name }}
  392. </tn-checkbox>
  393. </tn-checkbox-group>
  394. </view>
  395. <view style="padding: 0rpx 20rpx;">
  396. <textarea v-model="correctionRemark"
  397. :maxlength="500"
  398. placeholder="其他错误,请描述您遇到的问题"
  399. style="background-color: #f3f3f3; padding: 20rpx; width: 100%; height: 150rpx; font-size: 28rpx;">
  400. </textarea>
  401. </view>
  402. <view class="btn-confirm">
  403. <tui-button type="primary" shape="circle" @click="submitCorrection" width="600rpx" height="70rpx" :size="26"
  404. margin="0 auto">提交
  405. </tui-button>
  406. </view>
  407. </view>
  408. </tn-popup>
  409. </view>
  410. </template>
  411. <script>
  412. import correctionApi from "@/common/api/correction.js"
  413. import { cons_log } from "../../common/js/utils";
  414. export default {
  415. name: "kz-question",
  416. props: {
  417. /**
  418. * 模式
  419. * EXAM:考试模式
  420. * VIEW:看题模式
  421. * TRAINING:练习模式
  422. */
  423. mode: {
  424. type: String,
  425. default: 'EXAM'
  426. },
  427. // 试卷标题
  428. title: {
  429. type: String,
  430. default: '标题'
  431. },
  432. // 试题集
  433. questions: {
  434. type: Array,
  435. default: () => []
  436. },
  437. // 试题集总数(不传默认获取试题集长度)
  438. questionCount: {
  439. type: Number,
  440. default: 0
  441. },
  442. // 试题集每页条数
  443. pageCount: {
  444. type: Number,
  445. default: 20
  446. },
  447. // 试题集当前页
  448. currentPage: {
  449. type: Number,
  450. default: 1
  451. },
  452. // 考试时间(倒计时)
  453. limit_time: {
  454. type: Number,
  455. default: 0
  456. },
  457. // 考试配置
  458. configs: {
  459. type: Object,
  460. default: () => {
  461. }
  462. },
  463. // 允许收藏试题
  464. canCollect: {
  465. type: Boolean,
  466. default: true
  467. },
  468. // 允许删除错题
  469. canDeleteWrong: {
  470. type: Boolean,
  471. default: false
  472. },
  473. // 看题/练题的模式:normal=普通模式,memory=记忆模式,random=随机查询
  474. viewMode: {
  475. type: String,
  476. default: 'normal'
  477. },
  478. /**
  479. * 试卷选题模式
  480. * RANDOM:随机模式
  481. * FIX:固定模式
  482. */
  483. paperMode: {
  484. type: String,
  485. default: 'RANDOM'
  486. },
  487. // 防切屏
  488. isPreventSwitchScreen: {
  489. type: Boolean,
  490. default: false
  491. },
  492. // 切屏限制次数
  493. switchScreenCount: {
  494. type: Number,
  495. default: 0
  496. },
  497. },
  498. data() {
  499. return {
  500. // swiper当前下标
  501. swiperIndex: 1,
  502. // 总题数
  503. total: 0,
  504. // 试题集合
  505. list: [],
  506. // 题目面板
  507. showNumberPanel: false,
  508. // 防止交卷按钮点击多次
  509. buttonClicked: true,
  510. // 显示倒计时
  511. showCountDown: false,
  512. // 显示试题答案
  513. showAnswer: false,
  514. // 显示试题分数
  515. showQuestionScore: false,
  516. // 显示正确选择
  517. showRightChoose: false,
  518. // 显示错误选择
  519. showErrorChoose: false,
  520. // 显示结束练习对话框
  521. showEndTrainDialog: false,
  522. // 显示删除错题对话框
  523. showDeleteDialog: false,
  524. // 错题删除类型
  525. wrongDeleteType: 'single',
  526. // 答题开始时间
  527. startTime: 0,
  528. // 即将删除的错题ID
  529. wrongDelId: 0,
  530. // 练习结果
  531. trainResult: {
  532. right: 0,
  533. error: 0,
  534. unchecked: 0,
  535. rightIds: [],
  536. errorIds: [],
  537. },
  538. // 记忆模式相关数据
  539. memoryData: {},
  540. // 加载试题次数(以此来判断是否是第一次进入)
  541. loadQuestionCount: 0,
  542. // 延迟加载已加载页码
  543. loadQuestionPage: [1,],
  544. // 悬浮按钮列表
  545. fabBtnList: [
  546. {
  547. text: '纠错反馈',
  548. bgColor: '#fff',
  549. textColor: '#aaa',
  550. iconColor: '#aaa',
  551. icon: 'edit-form',
  552. iconSize: 45,
  553. },
  554. {
  555. text: '清空错题',
  556. bgColor: '#fff',
  557. textColor: '#aaa',
  558. iconColor: '#aaa',
  559. icon: 'clear',
  560. iconSize: 45,
  561. },
  562. ],
  563. // 纠错反馈弹窗
  564. showCorrection: false,
  565. correctionTypeslist: [],
  566. checkCorrectionTypeslist: [],
  567. correctionRemark: '',
  568. };
  569. },
  570. watch: {
  571. // 加载试题时
  572. questions: function () {
  573. let questionCount = this.questionCount ? this.questionCount : this.questions.length
  574. this.total = questionCount < this.questions.length ? this.questions.length : questionCount
  575. this.list = [...this.questions]
  576. // 材料题处理
  577. this.showFullMaterialTitle(0)
  578. // 填空题处理
  579. this.splitFillTitle(0)
  580. // 初始化模式配置
  581. this.initMode()
  582. // 记忆模式 - 跳转上次题标
  583. this.jumpMemoryQuestion()
  584. },
  585. },
  586. computed: {
  587. // 试题类型
  588. kindText() {
  589. return function () {
  590. return this.list[this.swiperIndex - 1] ? this.list[this.swiperIndex - 1].kind_text : '未知'
  591. }
  592. },
  593. // 试题标题
  594. questionTitle() {
  595. return function (index, item, field = 'title') {
  596. let html = item[field]
  597. html = this.utils.formatRichText(html)
  598. html += this.getQuestionTitleScore(index, item)
  599. if (field == 'material_title') {
  600. // console.log('material_title show_full', item.show_full)
  601. if (this.list[index] && !this.list[index].show_full) {
  602. // if (!item.show_full) {
  603. // if (!show_full) {
  604. html = this.truncatedText(html)
  605. // console.log('material_title', html)
  606. }
  607. }
  608. return html
  609. }
  610. },
  611. // 试题解析
  612. questionExplain() {
  613. return function (index, item) {
  614. return this.utils.formatRichText(item.explain)
  615. }
  616. },
  617. // 获取试题分数
  618. getQuestionTitleScore() {
  619. return function (index, item) {
  620. if (this.showQuestionScore) {
  621. return '(' + item.score + '分)'
  622. // return '(' + this.getSingleScore(item.kind, item.difficulty, item) + '分)'
  623. }
  624. return ''
  625. }
  626. },
  627. // 截取文本
  628. truncatedText() {
  629. return function (text, maxLength = 100) {
  630. if (text.length > maxLength) {
  631. return text.slice(0, maxLength) + '...';
  632. }
  633. return text;
  634. }
  635. },
  636. // 设置填空题答案
  637. setFillInputValue() {
  638. return (titleIndex) => {
  639. let index = this.swiperIndex - 1
  640. if (this.mode == 'VIEW') {
  641. if (this.list[index].answer) {
  642. if (this.list[index].answer[titleIndex].answers != undefined) {
  643. return this.list[index].answer[titleIndex].answers[0]
  644. }
  645. }
  646. } else {
  647. if (this.list[index].user_answers) {
  648. return this.list[index].user_answers[titleIndex]
  649. }
  650. }
  651. return ''
  652. }
  653. },
  654. // 设置简答题答案
  655. setShortInputValue() {
  656. return () => {
  657. let index = this.swiperIndex - 1
  658. if (this.mode == 'VIEW') {
  659. if (this.list[index].answer) {
  660. if (this.list[index].answer.answer != undefined) {
  661. return this.list[index].answer.answer
  662. }
  663. }
  664. } else {
  665. if (this.list[index].user_answers) {
  666. return this.list[index].user_answers
  667. }
  668. }
  669. return ''
  670. }
  671. },
  672. // 设置底部栏样式
  673. getBottomBarClass() {
  674. return () => {
  675. // console.log('getBottomBarClass', this.mode)
  676. let classes = []
  677. if (this.mode == 'EXAM') {
  678. if (this.canDeleteWrong) {
  679. classes = ['col-5']
  680. } else {
  681. classes = ['col-4']
  682. }
  683. } else if (this.mode == 'TRAINING') {
  684. if (this.canDeleteWrong) {
  685. classes = ['col-5']
  686. } else {
  687. classes = ['col-4']
  688. }
  689. } else if (this.mode == 'VIEW') {
  690. if (this.canDeleteWrong) {
  691. classes = ['col-4']
  692. } else {
  693. classes = ['col-3']
  694. }
  695. } else {
  696. classes = ['col-3']
  697. }
  698. return classes
  699. }
  700. },
  701. // 设置答题卡样式
  702. getNumberPanelClass() {
  703. return (index) => {
  704. if (this.swiperIndex - 1 == index) {
  705. return ['selected']
  706. }
  707. let classes = []
  708. let question = this.list[index]
  709. // console.log('getNumberPanelClass question', question)
  710. if (question && question.kind) {
  711. switch (question.kind) {
  712. case 'JUDGE':
  713. case 'SINGLE':
  714. case 'MULTI':
  715. if (question.is_right) {
  716. if (question.is_right == 'right') {
  717. return ['tibiao-right']
  718. } else {
  719. return ['tibiao-error']
  720. }
  721. }
  722. if (question.check) {
  723. return ['tibiao-right']
  724. }
  725. break
  726. case 'FILL':
  727. case 'SHORT':
  728. if (question.user_answers) {
  729. if (question.is_right) {
  730. if (question.is_right == 'right') {
  731. return ['tibiao-right']
  732. } else {
  733. return ['tibiao-error']
  734. }
  735. }
  736. return ['tibiao-right']
  737. }
  738. break
  739. default:
  740. return classes
  741. }
  742. }
  743. return classes
  744. }
  745. },
  746. },
  747. methods: {
  748. // 初始化模式配置
  749. initMode(reset = false) {
  750. switch (this.mode) {
  751. // 考试模式
  752. case 'EXAM':
  753. this.showCountDown = true
  754. this.showAnswer = false
  755. this.showQuestionScore = true
  756. this.showRightChoose = false
  757. this.showErrorChoose = false
  758. this.startTime = this.utils.timestamp()
  759. break
  760. // 练习模式
  761. case 'TRAINING':
  762. this.showCountDown = false
  763. this.showAnswer = false
  764. this.showQuestionScore = false
  765. this.showRightChoose = false
  766. this.showErrorChoose = true
  767. break
  768. // 看题模式
  769. case 'VIEW':
  770. this.showCountDown = false
  771. this.showAnswer = true
  772. this.showQuestionScore = false
  773. this.showRightChoose = true
  774. this.showErrorChoose = false
  775. // 显示试题正确的选项
  776. this.list.forEach((item, index) => {
  777. if (item.options_json) {
  778. // console.log('item.options_json', item.options_json)
  779. Array.from(item.options_json).forEach((optionItem, optionIndex) => {
  780. item.options_json[optionIndex].click_index = true
  781. item.options_json[optionIndex].classes = item.answer.indexOf(optionItem.key) > -1 ? 'active_true' : ''
  782. })
  783. this.list[index] = item
  784. }
  785. })
  786. break
  787. }
  788. },
  789. // 计算试题积分
  790. getSingleScore(kind, difficulty, question) {
  791. if (this.paperde == 'FIX') {
  792. return question.score
  793. }
  794. const configs = this.configs[kind.toLowerCase()];
  795. if (configs && configs['use_difficulty']) {
  796. return configs['difficulty'][difficulty.toLowerCase()]['score'];
  797. }
  798. return configs['score'];
  799. },
  800. // 延迟加载试题
  801. loadQuestion() {
  802. if (this.mode != 'TRAINING' && this.mode != 'VIEW') {
  803. return
  804. }
  805. if (this.viewMode != 'normal') {
  806. return
  807. }
  808. // 取下一页数据
  809. let page = Math.round(this.swiperIndex / this.pageCount) + 1
  810. if (!this.loadQuestionPage.includes(page)) {
  811. this.loadQuestionPage.push(page)
  812. this.$emit('loadQuestion', page)
  813. }
  814. },
  815. // 滑动切题
  816. swiperChange(e, type = '') {
  817. // console.log('swiperChange', this.list.length, this.swiperIndex, e.detail.current)
  818. // 当前题标
  819. this.swiperIndex = e.detail.current + 1
  820. // 材料题处理
  821. this.showFullMaterialTitle(e.detail.current)
  822. // 填空题处理
  823. this.splitFillTitle(e.detail.current)
  824. // 加载题目
  825. this.loadQuestion()
  826. // 记忆模式
  827. this.memoryQuestion()
  828. },
  829. // 上一题
  830. prev() {
  831. if (this.swiperIndex > 1) {
  832. this.swiperIndex--
  833. }
  834. },
  835. // 下一题
  836. next() {
  837. // console.log('next', this.list.length, this.swiperIndex)
  838. if (this.list.length - this.swiperIndex >= 1) {
  839. // 填空题处理
  840. this.splitFillTitle(this.swiperIndex)
  841. this.swiperIndex++
  842. } else {
  843. let title = '没有更多题了~'
  844. if (this.mode == 'EXAM') {
  845. title = '可以交卷了~'
  846. }
  847. uni.showToast({
  848. title: title,
  849. icon: 'none',
  850. duration: 1500
  851. })
  852. }
  853. },
  854. // 选择
  855. chooseItem(questionIndex, optionIndex, kind) {
  856. // 看题模式不让选
  857. if (this.mode == 'VIEW') {
  858. return
  859. }
  860. let questionItem = this.list[questionIndex]
  861. switch (kind) {
  862. // 多选题
  863. case 'MULTI':
  864. questionItem.options_json[optionIndex].click_index = !questionItem.options_json[optionIndex].click_index;
  865. //多选 确认按钮
  866. let arr = []
  867. questionItem.options_json.forEach((item, index) => {
  868. if (item.click_index == true) {
  869. arr.push(item.key)
  870. }
  871. })
  872. questionItem.check = arr.join(',')
  873. questionItem.selected = true
  874. break
  875. // 单选/判断
  876. default:
  877. questionItem.options_json.forEach((item, index) => {
  878. questionItem.options_json[index].click_index = false
  879. questionItem.options_json[index].classes = ''
  880. })
  881. questionItem.options_json[optionIndex].click_index = true
  882. questionItem.check = questionItem.options_json[optionIndex].key
  883. questionItem.selected = true
  884. break
  885. }
  886. questionItem.options_json[optionIndex].classes = this.optionChooseClass(questionIndex, optionIndex, questionItem, questionItem.options_json[optionIndex])
  887. this.list[questionIndex] = questionItem
  888. // #ifdef H5
  889. this.$forceUpdate()
  890. // #endif
  891. },
  892. // 选项选择样式
  893. optionChooseClass(questionIndex, optionIndex, questionItem, optionItem) {
  894. if (this.showRightChoose) {
  895. let classNames = ''
  896. if ((questionItem.selected && optionItem.code) || (questionItem.kind == 'MULTI' && optionItem.click_index)) {
  897. classNames = 'active_true'
  898. }
  899. if ((questionItem.kind != 'MULTI' && optionItem.click_index && !optionItem.code) || (questionItem.kind == 'MULTI' && optionItem.click_index && !optionItem.code && questionItem.selected)) {
  900. classNames += ' active'
  901. }
  902. return classNames
  903. } else {
  904. // 练习模式,选择后显示错误选项
  905. if (this.showErrorChoose) {
  906. let result = ''
  907. switch (questionItem.kind) {
  908. // 多选题
  909. case 'MULTI':
  910. let isRight = true
  911. let answer_arr = questionItem.answer.split(',')
  912. console.log('questionItem.answer', questionItem.answer, questionItem.check, answer_arr)
  913. let check_arr = questionItem.check.indexOf(',') > 0 ? questionItem.check.split(',') : [questionItem.check]
  914. for (const check_answer of check_arr) {
  915. if (!answer_arr.includes(check_answer)) {
  916. isRight = false
  917. break
  918. }
  919. }
  920. // 选择数量跟答案数量一致
  921. if (questionItem.check && (!isRight || questionItem.check.length == questionItem.answer.length)) {
  922. // 选择后显示答案和解析
  923. questionItem.show_answer = true
  924. this.list[questionIndex] = questionItem
  925. }
  926. result = questionItem.answer.indexOf(optionItem.key) > -1 ? 'active_true' : 'active'
  927. break
  928. // 单选/判断
  929. default:
  930. // 选择后显示答案和解析
  931. questionItem.show_answer = true
  932. this.list[questionIndex] = questionItem
  933. // 选择正确与否
  934. result = optionItem.key == questionItem.answer ? 'active_true' : 'active'
  935. break
  936. }
  937. // 记录练习结果
  938. if (result === 'active_true') {
  939. // this.trainResult.right++
  940. // this.trainResult.rightIds.push(questionItem.id)
  941. this.list[questionIndex]['is_right'] = 'right'
  942. } else {
  943. // this.trainResult.error++
  944. // this.trainResult.errorIds.push(questionItem.id)
  945. this.list[questionIndex]['is_right'] = 'error'
  946. }
  947. return result
  948. } else {
  949. if (optionItem.click_index) {
  950. return 'active_true'
  951. }
  952. }
  953. }
  954. return ''
  955. },
  956. // 获取未答题数量
  957. getUncheckedCount() {
  958. if (this.mode === 'EXAM') {
  959. let unchecked = []
  960. for (let i in this.list) {
  961. let question = this.list[i]
  962. let item = {
  963. id: question.id,
  964. answer: ''
  965. }
  966. switch (question.kind) {
  967. case 'JUDGE':
  968. case 'SINGLE':
  969. case 'MULTI':
  970. if (question.check) {
  971. item.answer = question.check
  972. } else {
  973. unchecked.push({
  974. id: question.id
  975. })
  976. }
  977. break
  978. case 'FILL':
  979. if (question.user_answers) {
  980. item.answer = question.user_answers
  981. } else {
  982. unchecked.push({
  983. id: question.id
  984. })
  985. }
  986. break
  987. case 'SHORT':
  988. if (question.user_answers) {
  989. item.answer = question.user_answers
  990. } else {
  991. unchecked.push({
  992. id: question.id
  993. })
  994. }
  995. break
  996. }
  997. }
  998. return unchecked.length
  999. } else {
  1000. let right = 0
  1001. let error = 0
  1002. let unchecked = 0
  1003. for (let i in this.list) {
  1004. if (this.list[i].is_right) {
  1005. if (this.list[i].is_right == 'right') {
  1006. right++
  1007. } else {
  1008. error++
  1009. }
  1010. } else {
  1011. unchecked++
  1012. }
  1013. }
  1014. // this.trainResult.right = (Array.from(new Set(this.trainResult.rightIds))).length
  1015. // this.trainResult.error = (Array.from(new Set(this.trainResult.errorIds))).length
  1016. this.trainResult.right = right
  1017. this.trainResult.error = error
  1018. let count = this.total - this.trainResult.right - this.trainResult.error
  1019. return count > 0 ? count : 0;
  1020. }
  1021. },
  1022. // 左下角交卷按钮点击弹窗
  1023. submitShowModal() {
  1024. this.buttonClicked = false
  1025. let unchecked_count = this.getUncheckedCount()
  1026. let modal_title = '确认要交卷吗?'
  1027. if (unchecked_count > 0) {
  1028. modal_title = `还有${unchecked_count}道题未答,` + modal_title
  1029. }
  1030. uni.showModal({
  1031. title: '提示',
  1032. content: modal_title,
  1033. success: res => {
  1034. if (res.confirm) {
  1035. this.submit()
  1036. } else {
  1037. this.buttonClicked = true
  1038. }
  1039. }
  1040. })
  1041. },
  1042. // 倒计时结束
  1043. endOfTime() {
  1044. // 非考试模式不处理
  1045. if (this.mode != 'EXAM') {
  1046. return
  1047. }
  1048. // 为0时不处理
  1049. if (this.limit_time == 0) {
  1050. return
  1051. }
  1052. uni.showToast({
  1053. title: '考试时间到,即将自动交卷',
  1054. duration: 2500,
  1055. })
  1056. this.buttonClicked = true
  1057. setTimeout(() => {
  1058. this.submit()
  1059. }, 2500)
  1060. },
  1061. // 交卷
  1062. submit() {
  1063. let data = {}
  1064. let questions = {}
  1065. let list = this.list
  1066. for (let i in list) {
  1067. let item = {}
  1068. switch (list[i].kind) {
  1069. case 'JUDGE':
  1070. case 'SINGLE':
  1071. case 'MULTI':
  1072. item.id = list[i].id
  1073. item.answer = list[i].check ? list[i].check : ''
  1074. break
  1075. case 'FILL':
  1076. item.id = list[i].id
  1077. item.answer = list[i].user_answers ? list[i].user_answers : []
  1078. break
  1079. case 'SHORT':
  1080. item.id = list[i].id
  1081. item.answer = list[i].user_answers ? list[i].user_answers : ''
  1082. break
  1083. default:
  1084. break
  1085. }
  1086. // 材料题主题ID
  1087. item.material_id = list[i].material_id ? list[i].material_id : 0
  1088. questions[i] = item
  1089. }
  1090. data.start_time = this.startTime
  1091. data.questions = questions
  1092. this.$emit('submitQuestion', data)
  1093. },
  1094. // 控制题目面板显示隐藏
  1095. handleNumberPanel() {
  1096. // if (this.mode == 'TRAINING') {
  1097. // return
  1098. // }
  1099. this.showNumberPanel = !this.showNumberPanel;
  1100. },
  1101. // 题目面板跳题
  1102. async changeQuestion(e, type = '') {
  1103. // 题已加载,直接跳
  1104. if (e + 1 <= this.list.length) {
  1105. // this.swiperIndex = e + 1
  1106. this.showNumberPanel = !this.showNumberPanel
  1107. this.swiperChange({detail: {current: e}},)
  1108. } else {
  1109. // 题未加载,一页页加载
  1110. let currLastPage = this.loadQuestionPage.at(-1)
  1111. let willGetPage = Math.round(e / this.pageCount) + 1
  1112. for (var page = currLastPage; page <= willGetPage; page++) {
  1113. if (this.loadQuestionPage.includes(page)) {
  1114. continue;
  1115. }
  1116. await new Promise((resolve, reject) => {
  1117. this.$emit('loadQuestion', page, () => {
  1118. this.loadQuestionPage.push(page)
  1119. if (page >= willGetPage) {
  1120. setTimeout(() => {
  1121. this.changeQuestion(e, 'digui')
  1122. }, 1000)
  1123. }
  1124. resolve()
  1125. })
  1126. })
  1127. }
  1128. }
  1129. },
  1130. // 记录错题
  1131. wrong(id) {
  1132. this.http('question/wrongAdd', {
  1133. question_id: id,
  1134. }, 'get').then(res => {
  1135. })
  1136. },
  1137. // 删除错题
  1138. wrongDel(showDialog = true) {
  1139. // 弹窗提示
  1140. if (showDialog) {
  1141. this.showDeleteDialog = true
  1142. this.wrongDeleteType = 'single'
  1143. return
  1144. }
  1145. this.hideModal()
  1146. // 执行删除
  1147. this.http('question/wrongDelete', {
  1148. question_id: this.list[this.swiperIndex - 1].id
  1149. }, 'get').then(res => {
  1150. uni.showToast({
  1151. icon: 'none',
  1152. title: res.msg
  1153. })
  1154. if (res.code == 1) {
  1155. setTimeout(() => {
  1156. this.$emit('refresh')
  1157. }, 1200)
  1158. }
  1159. });
  1160. },
  1161. // 清空错题
  1162. wrongClear(showDialog = true) {
  1163. // 弹窗提示
  1164. if (showDialog) {
  1165. this.showDeleteDialog = true
  1166. this.wrongDeleteType = 'all'
  1167. return
  1168. }
  1169. this.hideModal()
  1170. // 执行清空
  1171. this.http('question/wrongClear', {}).then(res => {
  1172. uni.showToast({
  1173. icon: 'none',
  1174. title: res.msg
  1175. })
  1176. if (res.code == 1) {
  1177. setTimeout(() => {
  1178. this.$emit('refresh')
  1179. }, 1200)
  1180. }
  1181. });
  1182. },
  1183. // 确认删除错题
  1184. confirmDelWrong() {
  1185. if (this.wrongDeleteType == 'single') {
  1186. this.wrongDel(false)
  1187. } else if (this.wrongDeleteType == 'all') {
  1188. this.wrongClear(false)
  1189. }
  1190. },
  1191. // 隐藏弹窗
  1192. hideModal(e) {
  1193. this.showDeleteDialog = false
  1194. this.showEndTrainDialog = false
  1195. this.buttonClicked = true
  1196. },
  1197. // 添加收藏
  1198. collectAdd() {
  1199. let id = this.list[this.swiperIndex - 1].id
  1200. let index = this.swiperIndex - 1
  1201. this.http('question/collectAdd', {
  1202. question_id: id,
  1203. }, 'get').then(res => {
  1204. uni.showToast({
  1205. icon: 'none',
  1206. title: res.msg
  1207. })
  1208. if (res.code == 1) {
  1209. this.list[index]['collected'] = true
  1210. this.$forceUpdate()
  1211. }
  1212. });
  1213. },
  1214. // 取消收藏
  1215. collectDel() {
  1216. let id = this.list[this.swiperIndex - 1].id
  1217. let index = this.swiperIndex - 1
  1218. this.http('question/collectCancel', {
  1219. question_id: id
  1220. }, 'get').then(res => {
  1221. uni.showToast({
  1222. icon: 'none',
  1223. title: res.msg
  1224. })
  1225. if (res.code == 1) {
  1226. this.list[index]['collected'] = false
  1227. this.$forceUpdate()
  1228. }
  1229. });
  1230. },
  1231. // 结束练习弹窗
  1232. endTrainShowModal() {
  1233. this.buttonClicked = false
  1234. this.trainResult.unchecked = this.getUncheckedCount()
  1235. this.showEndTrainDialog = true
  1236. },
  1237. // 结束练习
  1238. endTrain() {
  1239. this.utils.goback()
  1240. },
  1241. // 记忆模式 - 缓存key
  1242. getMemoryCacheKey(cate_id) {
  1243. return this.mode.toLowerCase() + '-' + cate_id
  1244. },
  1245. // 记忆当前题目信息
  1246. memoryQuestion() {
  1247. if (this.viewMode != 'memory') {
  1248. return
  1249. }
  1250. let question = this.list[0]
  1251. let data = {
  1252. memory_cate_id: question.cate_id,
  1253. memory_question_id: question.id,
  1254. memory_index: this.swiperIndex,
  1255. }
  1256. this.memoryData = data
  1257. // 缓存当前题目信息
  1258. let cache_key = this.getMemoryCacheKey(question.cate_id) //this.mode.toLowerCase() + '-' + question.cate_id
  1259. this.utils.setData(cache_key, data)
  1260. },
  1261. // 记忆模式 - 跳转上次题标
  1262. jumpMemoryQuestion() {
  1263. // 记忆模式且是第一次加载试题
  1264. if (this.viewMode == 'memory' && this.loadQuestionCount == 0) {
  1265. let cache_key = this.getMemoryCacheKey(this.questions[0].cate_id)
  1266. this.memoryData = this.utils.getData(cache_key)
  1267. if (this.memoryData) {
  1268. // 跳转到上次题标位置
  1269. this.swiperIndex = this.memoryData.memory_index
  1270. }
  1271. }
  1272. },
  1273. // 图片预览
  1274. previewImage(image) {
  1275. uni.previewImage({
  1276. current: 0,
  1277. urls: [image]
  1278. })
  1279. },
  1280. // 填空题 - 处理题目数据
  1281. splitFillTitle(index) {
  1282. if (this.list[index] && this.list[index].kind == 'FILL') {
  1283. if (!this.list[index].title_data) {
  1284. this.list[index]['title_data'] = this.list[index].title.split('______')
  1285. }
  1286. // 未回答标识
  1287. if (!this.list[index].is_answered) {
  1288. this.list[index]['is_answered'] = false
  1289. }
  1290. }
  1291. },
  1292. // 填空题 - 文本框修改
  1293. changeFillInput(e, titleIndex, swiperIndex) {
  1294. if (this.mode == 'VIEW') {
  1295. return
  1296. }
  1297. if (!this.list[swiperIndex - 1]['user_answers']) {
  1298. this.list[swiperIndex - 1]['user_answers'] = []
  1299. }
  1300. this.list[swiperIndex - 1].user_answers[titleIndex] = e.target.value
  1301. },
  1302. // 填空题 - 练习模式 - 确认答案
  1303. confirmFillAnswer() {
  1304. let index = this.swiperIndex - 1
  1305. if (!this.list[index].user_answers || this.list[index].user_answers.length != this.list[index].answer.length) {
  1306. this.utils.toast('请在文本框填写完整的答案')
  1307. return
  1308. }
  1309. let right_count = 0
  1310. for (var i = 0; i < this.list[index].user_answers.length; i++) {
  1311. let user_answer = this.list[index].user_answers[i]
  1312. if (user_answer === '') {
  1313. this.utils.toast('第' + (i + 1) + '个文本框未填写答案')
  1314. return
  1315. }
  1316. let is_right = false
  1317. for (var j = 0; j < this.list[index].answer[i].answers.length; j++) {
  1318. let right_answer = this.list[index].answer[i].answers[j]
  1319. if (user_answer === right_answer) {
  1320. is_right = true
  1321. break
  1322. }
  1323. }
  1324. // 填空题对错 - 文本框样式
  1325. this.list[index].answer[i]['class'] = is_right ? 'fill-input-right' : 'fill-input-error'
  1326. if (is_right) {
  1327. right_count++
  1328. }
  1329. }
  1330. // 练习模式,选择后显示错误选项
  1331. if (this.showErrorChoose) {
  1332. // 记录练题情况
  1333. if (right_count == this.list[index].user_answers.length) {
  1334. // this.trainResult.right++
  1335. this.list[index]['is_right'] = 'right'
  1336. } else {
  1337. // this.trainResult.error++
  1338. this.list[index]['is_right'] = 'error'
  1339. }
  1340. }
  1341. // 显示答案
  1342. this.list[index]['show_answer'] = true
  1343. // 标记为已回答
  1344. this.list[index]['is_answered'] = true
  1345. this.$forceUpdate()
  1346. },
  1347. // 简答题 - 文本框修改
  1348. changeShortInput(e, swiperIndex) {
  1349. if (this.mode == 'VIEW') {
  1350. return
  1351. }
  1352. if (!this.list[swiperIndex - 1]['user_answers']) {
  1353. this.list[swiperIndex - 1]['user_answers'] = ''
  1354. }
  1355. this.list[swiperIndex - 1].user_answers = e.target.value
  1356. },
  1357. // 简答题 - 练习模式 - 确认答案
  1358. confirmShortAnswer() {
  1359. let index = this.swiperIndex - 1
  1360. if (!this.list[index].user_answers) {
  1361. this.utils.toast('请在文本框填写完整的答案')
  1362. return
  1363. }
  1364. let right_count = 0
  1365. // let right_indexes = []
  1366. for (var i = 0; i < this.list[index].answer.config.length; i++) {
  1367. this.list[index].answer.config[i]['class'] = ''
  1368. if (this.list[index].user_answers.indexOf(this.list[index].answer.config[i].answer) > -1) {
  1369. right_count++
  1370. this.list[index].answer.config[i]['class'] = 'short-input-right'
  1371. // right_indexes.push(i)
  1372. }
  1373. }
  1374. // 练习模式,选择后显示错误选项
  1375. if (this.showErrorChoose) {
  1376. // 记录练题情况
  1377. if (right_count > 0) {
  1378. // this.trainResult.right++
  1379. this.list[index]['is_right'] = 'right'
  1380. } else {
  1381. // this.trainResult.error++
  1382. this.list[index]['is_right'] = 'error'
  1383. }
  1384. }
  1385. // 显示答案
  1386. this.list[index]['show_answer'] = true
  1387. // 标记为已回答
  1388. this.list[index]['is_answered'] = true
  1389. this.$forceUpdate()
  1390. },
  1391. // 材料题 - 处理材料题目数据
  1392. showFullMaterialTitle(index, item, status = false) {
  1393. if (item && item.material_title != undefined) {// item.kind == 'MATERIAL'
  1394. // 未回答标识
  1395. if (!item.show_full) {
  1396. item['show_full'] = false
  1397. if (this.list[index]) {
  1398. this.list[index]['show_full'] = false
  1399. }
  1400. }
  1401. item.show_full = status
  1402. if (this.list[index]) {
  1403. this.list[index].show_full = status
  1404. }
  1405. // console.log('showFullMaterialTitle', item.show_full)
  1406. this.$forceUpdate()
  1407. }
  1408. },
  1409. // 点击悬浮按钮的内容
  1410. clickFabItem(e) {
  1411. console.log('clickFabItem', e)
  1412. // 错题反馈
  1413. if (e.index === 0) {
  1414. correctionApi.getCorrectionTypes(this, {}).then(res => {
  1415. this.correctionTypeslist = res.data.types
  1416. this.showCorrection = true
  1417. })
  1418. } else if (e.index === 1 && this.canDeleteWrong) {
  1419. // 清空错题
  1420. this.wrongClear()
  1421. }
  1422. },
  1423. // 纠错反馈类型选择
  1424. correctionTypeChange(e) {
  1425. console.log('correctionTypeChange', e)
  1426. this.checkCorrectionTypeslist = e
  1427. },
  1428. // 提交纠错反馈
  1429. submitCorrection() {
  1430. if (this.checkCorrectionTypeslist.length == 0) {
  1431. this.utils.toast('请选择纠错类型')
  1432. return
  1433. }
  1434. let data = {
  1435. question_id: this.list[this.swiperIndex - 1].id,
  1436. type_names: this.checkCorrectionTypeslist,
  1437. remark: this.correctionRemark
  1438. }
  1439. correctionApi.submitCorrection(this, data).then(res => {
  1440. if (res.code) {
  1441. this.checkCorrectionTypeslist = []
  1442. this.correctionRemark = ''
  1443. this.utils.toast(res.msg)
  1444. }
  1445. this.showCorrection = false
  1446. })
  1447. },
  1448. }
  1449. }
  1450. </script>
  1451. <style lang="less">
  1452. page {
  1453. height: 100%;
  1454. }
  1455. .questions {
  1456. height: 100%;
  1457. position: relative;
  1458. .test-header {
  1459. width: 100%;
  1460. padding: 0 30rpx;
  1461. display: flex;
  1462. align-items: center;
  1463. justify-content: center;
  1464. height: 80rpx;
  1465. background: #fff;
  1466. position: relative;
  1467. font-size: 34rpx;
  1468. }
  1469. .card-shadow {
  1470. margin-top: 20rpx;
  1471. .topic-title {
  1472. font-size: 34rpx;
  1473. padding: 30rpx 20rpx;
  1474. background: #fff;
  1475. border-bottom: 1px solid #f0f0f0;
  1476. display: flex;
  1477. align-items: center;
  1478. justify-content: space-between;
  1479. .topic-title_left {
  1480. display: flex;
  1481. align-items: center;
  1482. .text-kind {
  1483. font-size: 24rpx;
  1484. color: #fff;
  1485. background: linear-gradient(135deg, #7892fd, #5677fc);
  1486. padding: 8rpx 10rpx;
  1487. border-radius: 15rpx 15rpx 15rpx 0;
  1488. margin-right: 20rpx;
  1489. }
  1490. }
  1491. .title-index {
  1492. color: #5677fc;
  1493. }
  1494. }
  1495. .questions-cont {
  1496. height: 100vh;
  1497. display: flex;
  1498. flex-wrap: nowrap;
  1499. transition: all 0.5s;
  1500. .swiper-item {
  1501. width: 100vw;
  1502. min-width: 100vw;
  1503. max-width: 100vw;
  1504. height: 100%;
  1505. overflow: auto;
  1506. .test-main {
  1507. padding: 0 20rpx;
  1508. // margin: 40rpx 30rpx;
  1509. // margin-bottom: 40rpx;
  1510. // border-radius: 8px;
  1511. background: #fff;
  1512. .test-title {
  1513. color: #333;
  1514. padding: 20rpx 0;
  1515. .text-kind {
  1516. font-size: 24rpx;
  1517. color: #fff;
  1518. background: linear-gradient(135deg, #7892fd, #5677fc);
  1519. padding: 8rpx 10rpx;
  1520. border-radius: 15rpx 15rpx 15rpx 0;
  1521. }
  1522. .test-favor {
  1523. position: relative;
  1524. color: #aaa;
  1525. float: right;
  1526. }
  1527. .test-favor-fill {
  1528. background: #fff;
  1529. color: #fbbd08;
  1530. float: right;
  1531. }
  1532. }
  1533. .test-title-fill {
  1534. width: 100%;
  1535. word-wrap: break-word;
  1536. word-break: break-all;
  1537. display: flex;
  1538. flex-wrap: wrap;
  1539. }
  1540. .test-title-fill-item {
  1541. margin: 5px;
  1542. }
  1543. .test-cont {
  1544. display: flex;
  1545. flex-direction: column;
  1546. padding-bottom: 20rpx;
  1547. color: #333333;
  1548. .test-cont-item {
  1549. padding: 20rpx;
  1550. display: flex;
  1551. background-color: #f6f6f6;
  1552. margin-bottom: 20rpx;
  1553. border-radius: 10rpx;
  1554. align-items: center;
  1555. justify-content: center;
  1556. position: relative;
  1557. &::after {
  1558. background: #333;
  1559. content: "";
  1560. width: 100%;
  1561. height: 100%;
  1562. position: absolute;
  1563. opacity: 0;
  1564. transition: all 0.35s;
  1565. }
  1566. &:active::after {
  1567. opacity: .3;
  1568. width: 0%;
  1569. transition: 0s;
  1570. }
  1571. .cont {
  1572. flex: 1;
  1573. height: 100%;
  1574. padding-left: 20rpx;
  1575. display: flex;
  1576. align-items: center;
  1577. justify-content: space-between;
  1578. .cont-text {
  1579. display: flex;
  1580. align-items: center;
  1581. flex-wrap: wrap;
  1582. flex: 1;
  1583. height: 100%;
  1584. font-p: 32rpx;
  1585. .image {
  1586. width: 50%;
  1587. height: auto;
  1588. margin-right: 20rpx;
  1589. }
  1590. }
  1591. .cont-icon {
  1592. width: 40rpx;
  1593. margin-left: 20rpx;
  1594. color: #5677fc;
  1595. font-size: 36rpx;
  1596. }
  1597. }
  1598. .key {
  1599. width: 50rpx;
  1600. height: 50rpx;
  1601. background-color: #d0d0d0;
  1602. border-radius: 50%;
  1603. color: #FFFFFF;
  1604. display: flex;
  1605. align-items: center;
  1606. justify-content: center;
  1607. }
  1608. &.active_true {
  1609. background-color: rgba(86, 119, 252, 0.2);
  1610. .key {
  1611. background-color: #5677fc;
  1612. }
  1613. .cont {
  1614. .cont-icon {
  1615. font-weight: bold;
  1616. color: #5677fc;
  1617. }
  1618. }
  1619. }
  1620. &.active {
  1621. background-color: rgba(255, 68, 0, 0.2);
  1622. .key {
  1623. background-color: #ff4400;
  1624. }
  1625. .cont {
  1626. .cont-icon {
  1627. font-weight: bold;
  1628. color: #ff4400;
  1629. }
  1630. }
  1631. }
  1632. }
  1633. }
  1634. }
  1635. .test-describe {
  1636. // padding: 0 20rpx;
  1637. margin-bottom: 20px;
  1638. .describe-title {
  1639. height: 48px;
  1640. line-height: 48px;
  1641. display: flex;
  1642. text {
  1643. color: #666;
  1644. font-size: 12px;
  1645. }
  1646. image {
  1647. width: 14px;
  1648. height: 14px;
  1649. margin-top: 17px;
  1650. margin-left: 3px;
  1651. }
  1652. }
  1653. .describe-cont {
  1654. // background: #f5f5f5;
  1655. // padding: 12rpx;
  1656. display: flex;
  1657. flex-direction: column;
  1658. font-size: 34rpx;
  1659. & > view {
  1660. color: #666;
  1661. font-size: 15px;
  1662. line-height: 40px;
  1663. background-color: #fff;
  1664. margin-bottom: 10px;
  1665. text-indent: 15px;
  1666. // border-radius: 10rpx;
  1667. &:nth-child(3) {
  1668. font-size: 12px;
  1669. line-height: 20px;
  1670. }
  1671. }
  1672. }
  1673. }
  1674. }
  1675. }
  1676. }
  1677. .fixed-bottom {
  1678. .tibiao {
  1679. background: #fff;
  1680. width: 100%;
  1681. height: 50vh;
  1682. padding: 35rpx;
  1683. padding-bottom: calc(constant(safe-area-inset-bottom) + 35rpx);
  1684. padding-bottom: calc(constant(safe-area-inset-bottom) + 35rpx);
  1685. border-radius: 20rpx 20rpx 0 0;
  1686. .tibiao-scroll {
  1687. height: 100%;
  1688. .tibiao-scroll-list {
  1689. display: flex;
  1690. align-items: center;
  1691. flex-wrap: wrap;
  1692. .tibiao-item {
  1693. height: 100rpx;
  1694. width: 100rpx;
  1695. border-radius: 50%;
  1696. margin-bottom: 30rpx;
  1697. border: 1rpx solid #d0d0d0;
  1698. display: flex;
  1699. align-items: center;
  1700. justify-content: center;
  1701. margin-right: 45rpx;
  1702. &:nth-child(5n) {
  1703. margin-right: 0;
  1704. }
  1705. &.tibiao-right {
  1706. background: #4caf50;
  1707. color: #fff;
  1708. }
  1709. &.tibiao-error {
  1710. background: #ff4400;
  1711. color: #fff;
  1712. }
  1713. &.selected {
  1714. background: #5677fc;
  1715. color: #fff;
  1716. }
  1717. }
  1718. }
  1719. }
  1720. }
  1721. }
  1722. }
  1723. .cu-list {
  1724. width: 100%;
  1725. height: 100rpx;
  1726. position: fixed;
  1727. left: 0;
  1728. bottom: 0;
  1729. text-align: center;
  1730. border-radius: 8px;
  1731. }
  1732. .cu-list.grid > .cu-item {
  1733. padding-top: 5px;
  1734. }
  1735. .cu-list image {
  1736. width: 25px;
  1737. height: 25px;
  1738. display: inline-block;
  1739. margin: 0 auto;
  1740. }
  1741. // 弹窗
  1742. .result {
  1743. width: 100%;
  1744. height: 100vh;
  1745. background: #fff;
  1746. padding-top: 10px;
  1747. }
  1748. .progress_box {
  1749. position: relative;
  1750. width: 100%;
  1751. display: flex;
  1752. align-items: center;
  1753. justify-content: center;
  1754. text-align: center;
  1755. }
  1756. .progress_bg {
  1757. position: absolute;
  1758. width: 220px;
  1759. height: 220px;
  1760. }
  1761. .progress_bar {
  1762. width: 220px;
  1763. height: 220px;
  1764. }
  1765. .progress_txt {
  1766. position: absolute;
  1767. font-size: 28upx;
  1768. color: #999999;
  1769. }
  1770. .progress_info {
  1771. font-size: 36upx;
  1772. padding-left: 16upx;
  1773. letter-spacing: 2upx;
  1774. font-size: 52upx;
  1775. color: #333333;
  1776. }
  1777. .progress_dot {
  1778. width: 16upx;
  1779. height: 16upx;
  1780. border-radius: 50%;
  1781. background-color: #fb9126;
  1782. }
  1783. .table {
  1784. width: 90%;
  1785. margin: 0 auto;
  1786. overflow: hidden;
  1787. }
  1788. .flex {
  1789. height: 50px;
  1790. line-height: 50px;
  1791. border-bottom: 1px solid #ddd;
  1792. }
  1793. .flex_1 {
  1794. text-align: left;
  1795. }
  1796. .flex_2 {
  1797. text-align: right;
  1798. }
  1799. .red {
  1800. color: #f00;
  1801. }
  1802. .error {
  1803. display: inline-block;
  1804. height: 30px;
  1805. line-height: 30px;
  1806. border-radius: 5px;
  1807. padding: 0 10px;
  1808. margin-left: 15px;
  1809. }
  1810. /*收藏 */
  1811. .tui-fabulous__box {
  1812. position: relative;
  1813. }
  1814. .tui-fabulous {
  1815. position: absolute;
  1816. left: 60px;
  1817. top: 0;
  1818. visibility: hidden;
  1819. }
  1820. .tui-fabulous__active {
  1821. animation: fabulousAni 2s linear;
  1822. }
  1823. @keyframes fabulousAni {
  1824. 0% {
  1825. transform: translateY(0) scale(0.8);
  1826. visibility: visible;
  1827. opacity: 1;
  1828. }
  1829. 15% {
  1830. transform: translateY(-40px) scale(1.25);
  1831. opacity: 1;
  1832. }
  1833. 100% {
  1834. transform: translateY(-240px) scale(0.5);
  1835. visibility: hidden;
  1836. opacity: 0;
  1837. }
  1838. }
  1839. /* 红心收藏效果 */
  1840. .cu-list.grid > .cu-item text {
  1841. margin-top: 0;
  1842. }
  1843. .cu-list.grid > .cu-item:after {
  1844. border: 0px;
  1845. }
  1846. .fix-bottom {
  1847. bottom: calc(constant(safe-area-inset-bottom) + 30rpx);
  1848. bottom: calc(env(safe-area-inset-bottom) + 30rpx);
  1849. width: 95%;
  1850. position: fixed;
  1851. margin: 0 auto;
  1852. left: 0;
  1853. right: 0;
  1854. }
  1855. .tui-prompt-title {
  1856. padding-bottom: 20rpx;
  1857. font-size: 34rpx;
  1858. font-weight: bold;
  1859. text-align: center;
  1860. }
  1861. .tui-flex-box {
  1862. width: 100%;
  1863. display: flex;
  1864. align-items: center;
  1865. flex-wrap: wrap;
  1866. margin-top: 40rpx;
  1867. }
  1868. .tui-flex-botton-view {
  1869. width: 45%;
  1870. margin: 0 auto;
  1871. }
  1872. @keyframes anime {
  1873. 0% {
  1874. background-size: 0% 0%;
  1875. }
  1876. 100% {
  1877. background-position: 100% 100%;
  1878. }
  1879. }
  1880. /** 填空题输入框 */
  1881. .fill-input {
  1882. border: 0px;
  1883. border-bottom: 2px solid #5677fc;
  1884. width: 200rpx;
  1885. margin: 0 10rpx;
  1886. }
  1887. .fill-input-right {
  1888. border-bottom: 2px solid #4caf50;
  1889. }
  1890. .fill-input-error {
  1891. border-bottom: 2px solid #ff4400;
  1892. }
  1893. .btn-confirm {
  1894. margin-top: 60rpx;
  1895. margin-bottom: 20rpx;
  1896. }
  1897. /** 简答题输入框 */
  1898. .short-input {
  1899. border: 0px;
  1900. border-bottom: 2px solid #5677fc;
  1901. width: 100%;
  1902. min-height: 500rpx;
  1903. margin: 10rpx;
  1904. }
  1905. .short-input-right {
  1906. color: #4caf50;
  1907. }
  1908. .short-input-error {
  1909. color: #ff4400;
  1910. }
  1911. .material-title {
  1912. background-color: #fff;
  1913. padding: 10px;
  1914. }
  1915. .material-title-tip {
  1916. font-size: 36rpx;
  1917. font-weight: bold;
  1918. }
  1919. .title-video {
  1920. width: 100%;
  1921. min-height: 200px;
  1922. pointer-events: auto !important;
  1923. }
  1924. .explain-video-view {
  1925. width: 100%;
  1926. min-height: 240px;
  1927. background-color: #fff;
  1928. padding: 20px 0px;
  1929. }
  1930. .explain-video {
  1931. width: 100%;
  1932. min-height: 200px;
  1933. pointer-events: auto !important;
  1934. }
  1935. .fab-bg-color {
  1936. background-color: #e6e6e6;
  1937. }
  1938. textarea::-webkit-input-placeholder {
  1939. font-size: 24rpx;
  1940. }
  1941. textarea:-moz-placeholder {
  1942. font-size: 24rpx;
  1943. }
  1944. textarea::-moz-placeholder {
  1945. font-size: 24rpx;
  1946. }
  1947. textarea::-ms-input-placeholder {
  1948. font-size: 24rpx;
  1949. }
  1950. </style>