practicePlay.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784
  1. <!-- 刷题练习页 -->
  2. <template>
  3. <view class="practice-screen">
  4. <!-- 练习用时顶栏(绿色) -->
  5. <view class="timer-bar">
  6. <text class="timer-label">练习用时</text>
  7. <text class="timer-text">{{ timerDisplay }}</text>
  8. <text class="timer-mode">刷题练习模式</text>
  9. </view>
  10. <!-- 答题统计行 -->
  11. <view class="stat-row">
  12. <view class="stat-item">
  13. <text class="stat-num">{{ answered }}</text>
  14. <text class="stat-label">已答</text>
  15. </view>
  16. <view class="stat-divider"></view>
  17. <view class="stat-item">
  18. <text class="stat-num stat-correct">{{ correct }}</text>
  19. <text class="stat-label">正确</text>
  20. </view>
  21. <view class="stat-divider"></view>
  22. <view class="stat-item">
  23. <text class="stat-num stat-wrong">{{ incorrect }}</text>
  24. <text class="stat-label">错误</text>
  25. </view>
  26. <view class="stat-divider"></view>
  27. <view class="stat-item">
  28. <text class="stat-num">{{ accuracyText }}</text>
  29. <text class="stat-label">正确率</text>
  30. </view>
  31. </view>
  32. <!-- 题号导航面板(底部弹出,仿 mockExamPlay) -->
  33. <view v-if="showQuestionNav" class="q-nav-panel" @tap="closeNav">
  34. <view class="q-nav-scroll-wrapper" @tap.stop>
  35. <view class="q-nav-header">
  36. <text class="q-nav-title">题目列表</text>
  37. <text class="q-nav-close" @tap="closeNav">✕</text>
  38. </view>
  39. <scroll-view scroll-y class="q-nav-scroll" :style="'height:' + navScrollHeight + 'px'">
  40. <view v-for="(group, gi) in questionTypeGroups" :key="gi" class="q-group">
  41. <view class="q-group-title">{{ group.questionTypeName }}({{ (group.questions || []).length }}题)</view>
  42. <view class="q-num-row">
  43. <view
  44. v-for="(item, qi) in group.questions"
  45. :key="qi"
  46. class="q-num"
  47. :class="getNavNumClass(item, gi, qi)"
  48. :data-gi="gi"
  49. :data-qi="qi"
  50. @tap="onNavNumTap"
  51. >{{ item.seq }}</view>
  52. </view>
  53. </view>
  54. <!-- 图例 -->
  55. <view class="nav-legend">
  56. <view class="legend-item"><view class="q-num legend-dot answered"></view><text>已答对</text></view>
  57. <view class="legend-item"><view class="q-num legend-dot wrong"></view><text>答错</text></view>
  58. <view class="legend-item"><view class="q-num legend-dot current"></view><text>当前</text></view>
  59. </view>
  60. </scroll-view>
  61. </view>
  62. </view>
  63. <!-- 题目内容 -->
  64. <scroll-view scroll-y class="q-scroll-content" :style="scrollStyle">
  65. <view v-if="currentQuestion && currentQuestion.id" class="q-card">
  66. <!-- 题目头部 -->
  67. <view class="q-header">
  68. <view class="q-header-left">
  69. <text class="q-type-badge">{{ currentQuestion.questionTypeName }}</text>
  70. <text class="q-num-text">第 {{ currentSeq }} 题</text>
  71. <text v-if="currentQuestion.difficulty" class="diff-badge" :class="diffClass(currentQuestion.difficulty)">{{ diffName(currentQuestion.difficulty) }}</text>
  72. </view>
  73. <view class="view-answer-btn" @tap="toggleAnswer">
  74. {{ viewAnswerShowType ? '关闭答案' : '查看答案' }}
  75. </view>
  76. </view>
  77. <!-- 题干 -->
  78. <view class="q-content">
  79. <rich-text :nodes="currentQuestion.questionContent || ''"></rich-text>
  80. </view>
  81. <!-- 选项 -->
  82. <view class="q-options">
  83. <view
  84. v-for="(opt, oi) in (currentQuestion.options || [])"
  85. :key="oi"
  86. class="q-option"
  87. :class="{ selected: isSelected(opt.optionTag) }"
  88. :data-tag="opt.optionTag"
  89. @tap="onOptionTap"
  90. >
  91. <view class="q-option-label">{{ opt.optionTag }}</view>
  92. <view class="q-option-content">
  93. <rich-text :nodes="opt.optionContent || ''"></rich-text>
  94. </view>
  95. </view>
  96. </view>
  97. <!-- 查看答案区域 -->
  98. <view v-if="viewAnswerShowType" class="answer-result-box">
  99. <view class="answer-row">
  100. <text class="answer-label">答案:</text>
  101. <text class="answer-value">{{ formatAnswer(currentQuestion.correctAnswer) }}</text>
  102. </view>
  103. <view class="analysis-title">试题解析</view>
  104. <view class="analysis-content">
  105. <rich-text v-if="currentQuestion.analysis" :nodes="currentQuestion.analysis"></rich-text>
  106. <text v-else class="analysis-empty">无</text>
  107. </view>
  108. </view>
  109. </view>
  110. <view v-else-if="loading" class="empty-tip">加载中...</view>
  111. <view v-else class="empty-tip">暂无题目</view>
  112. <view class="scroll-bottom-space"></view>
  113. </scroll-view>
  114. <!-- 上一题/下一题 -->
  115. <view class="q-nav">
  116. <view class="q-nav-btn" :class="{ disabled: isFirstQuestion }" @tap="prevQuestion">上一题</view>
  117. <view class="q-nav-btn q-nav-center" @tap="toggleQuestionNav">选题</view>
  118. <view class="q-nav-btn q-nav-btn-next" :class="{ disabled: isLastQuestion }" @tap="nextQuestion">下一题</view>
  119. </view>
  120. </view>
  121. </template>
  122. <script>
  123. import {
  124. examUserPracticePanel,
  125. examUserPracticeQuestionDetail,
  126. examUserPracticeSubmit,
  127. examUserExamStart,
  128. } from '../../api/index.js';
  129. const DIFFICULTY_MAP = {
  130. 1: { name: '简单', cls: 'diff-easy' },
  131. 2: { name: '一般', cls: 'diff-normal' },
  132. 3: { name: '较难', cls: 'diff-hard' },
  133. 4: { name: '极难', cls: 'diff-extreme' },
  134. };
  135. export default {
  136. data() {
  137. return {
  138. newData: {},
  139. examInfo: {
  140. knowledgePointName: '',
  141. totalCount: 0,
  142. },
  143. questionTypeGroups: [],
  144. currentQuestion: null,
  145. groupIndex: 0,
  146. questionIndex: 0,
  147. viewAnswerShowType: false,
  148. showQuestionNav: false,
  149. navScrollHeight: 300,
  150. loading: false,
  151. // 计时器
  152. elapsedSeconds: 0,
  153. timerInterval: null,
  154. }
  155. },
  156. computed: {
  157. answered() {
  158. let n = 0;
  159. this.questionTypeGroups.forEach(g => {
  160. if (g.questions) g.questions.forEach(q => { if (q.answerStatus === 1 || q.answerStatus === 2) n++; });
  161. });
  162. return n;
  163. },
  164. correct() {
  165. let n = 0;
  166. this.questionTypeGroups.forEach(g => {
  167. if (g.questions) g.questions.forEach(q => { if (q.answerStatus === 1) n++; });
  168. });
  169. return n;
  170. },
  171. incorrect() {
  172. let n = 0;
  173. this.questionTypeGroups.forEach(g => {
  174. if (g.questions) g.questions.forEach(q => { if (q.answerStatus === 2) n++; });
  175. });
  176. return n;
  177. },
  178. accuracyText() {
  179. let total = 0;
  180. this.questionTypeGroups.forEach(g => { if (g.questions) total += g.questions.length; });
  181. if (total === 0 || this.correct === 0) return '0%';
  182. return (this.correct / total * 100).toFixed(0) + '%';
  183. },
  184. timerDisplay() {
  185. const m = Math.floor(this.elapsedSeconds / 60);
  186. const s = this.elapsedSeconds % 60;
  187. return String(m).padStart(2, '0') + ':' + String(s).padStart(2, '0');
  188. },
  189. isFirstQuestion() {
  190. return this.groupIndex === 0 && this.questionIndex === 0;
  191. },
  192. isLastQuestion() {
  193. const last = this.questionTypeGroups.length - 1;
  194. if (last < 0) return true;
  195. const lastQ = this.questionTypeGroups[last].questions.length - 1;
  196. return this.groupIndex === last && this.questionIndex === lastQ;
  197. },
  198. currentSeq() {
  199. const g = this.questionTypeGroups[this.groupIndex];
  200. if (!g || !g.questions[this.questionIndex]) return '-';
  201. return g.questions[this.questionIndex].seq;
  202. },
  203. scrollStyle() {
  204. // timer-bar ~48px + stat-row ~52px + bottom-nav ~44px
  205. const sys = uni.getSystemInfoSync();
  206. const h = sys.windowHeight - 48 - 52 - 44;
  207. return 'height:' + (h > 200 ? h : 400) + 'px;';
  208. },
  209. },
  210. onLoad(options) {
  211. this.newData = {
  212. knowledgePointId: options.knowledgePointId || '',
  213. attemptId: options.attemptId || '',
  214. examId: options.examId || '',
  215. };
  216. uni.setNavigationBarTitle({ title: '刷题练习' });
  217. this.startTimer();
  218. // 计算题号面板可滚动高度
  219. const sys = uni.getSystemInfoSync();
  220. this.navScrollHeight = Math.floor(sys.windowHeight * 0.5);
  221. this.loadPanel();
  222. },
  223. onUnload() {
  224. this.stopTimer();
  225. },
  226. // 支持导航栏"题号"按钮(pages.json 中配置 navigationBarButtons)
  227. onNavigationBarButtonTap(e) {
  228. if (e.index === 0) {
  229. this.showQuestionNav = !this.showQuestionNav;
  230. }
  231. },
  232. methods: {
  233. closeNav() {
  234. this.showQuestionNav = false;
  235. },
  236. toggleQuestionNav() {
  237. this.showQuestionNav = !this.showQuestionNav;
  238. },
  239. toggleAnswer() {
  240. this.viewAnswerShowType = !this.viewAnswerShowType;
  241. },
  242. onOptionTap(e) {
  243. const tag = e.currentTarget.dataset.tag;
  244. if (tag !== undefined) this.selectOption(tag);
  245. },
  246. onNavNumTap(e) {
  247. const gi = Number(e.currentTarget.dataset.gi);
  248. const qi = Number(e.currentTarget.dataset.qi);
  249. this.jumpToQuestion(gi, qi);
  250. },
  251. startTimer() { this.timerInterval = setInterval(() => {
  252. this.elapsedSeconds++;
  253. }, 1000);
  254. },
  255. stopTimer() {
  256. if (this.timerInterval) clearInterval(this.timerInterval);
  257. },
  258. diffName(d) { return DIFFICULTY_MAP[d] ? DIFFICULTY_MAP[d].name : ''; },
  259. diffClass(d) { return DIFFICULTY_MAP[d] ? DIFFICULTY_MAP[d].cls : ''; },
  260. isSelected(tag) {
  261. if (!this.currentQuestion || !this.currentQuestion.userAnswer) return false;
  262. return this.currentQuestion.userAnswer.indexOf(tag) !== -1;
  263. },
  264. formatAnswer(ans) {
  265. if (!ans) return '';
  266. return ans.split(',').map(a => {
  267. if (a === 'Y') return '对';
  268. if (a === 'N') return '错';
  269. return a;
  270. }).join('、');
  271. },
  272. getNavNumClass(item, gi, qi) {
  273. const isCurrent = this.groupIndex === gi && this.questionIndex === qi;
  274. if (isCurrent) return 'current';
  275. if (item.answerStatus === 1) return 'answered';
  276. if (item.answerStatus === 2) return 'wrong';
  277. return '';
  278. },
  279. // 加载左侧面板数据
  280. loadPanel() {
  281. if (this.loading) return;
  282. this.loading = true;
  283. examUserPracticePanel({ knowledgePointId: this.newData.knowledgePointId }).then(res => {
  284. const resp = res.data || {};
  285. if (resp.code !== 200) {
  286. uni.showToast({ title: resp.message || '加载失败', icon: 'none' });
  287. return;
  288. }
  289. const d = resp.data || {};
  290. this.examInfo = {
  291. knowledgePointName: d.knowledgePointName || '',
  292. totalCount: d.totalCount || 0,
  293. };
  294. this.$set(this, 'questionTypeGroups', d.questionTypeGroups || []);
  295. this.groupIndex = 0;
  296. this.questionIndex = 0;
  297. const first = (d.questionTypeGroups || [])[0];
  298. if (first && first.questions && first.questions[0]) {
  299. this.loadQuestionDetail(first.questions[0].questionId, first.questions[0].userAnswer);
  300. }
  301. }).catch(e => {
  302. console.error('loadPanel error', e);
  303. uni.showToast({ title: '加载失败', icon: 'none' });
  304. }).finally(() => {
  305. this.loading = false;
  306. });
  307. },
  308. // 加载单题详情
  309. loadQuestionDetail(questionId, userAnswer) {
  310. examUserPracticeQuestionDetail({ questionId }).then(res => {
  311. const resp = res.data || {};
  312. if (resp.code !== 200) return;
  313. const q = resp.data || {};
  314. // 判断题补充选项
  315. if (q.questionType == 3) {
  316. q.options = [
  317. { optionContent: '正确', optionTag: 'Y', sortOrder: 1 },
  318. { optionContent: '错误', optionTag: 'N', sortOrder: 2 },
  319. ];
  320. }
  321. q.userAnswer = Array.isArray(userAnswer)
  322. ? userAnswer
  323. : (userAnswer ? userAnswer.split(',') : []);
  324. this.$set(this, 'currentQuestion', q);
  325. this.$set(this, 'viewAnswerShowType', false);
  326. }).catch(e => {
  327. console.error('loadQuestionDetail error', e);
  328. });
  329. },
  330. // 选择选项
  331. selectOption(tag) {
  332. if (!this.currentQuestion) return;
  333. const type = this.currentQuestion.questionType;
  334. let ans = [...(this.currentQuestion.userAnswer || [])];
  335. if (type == 1 || type == 3) {
  336. // 单选 / 判断:单选
  337. ans = [tag];
  338. } else if (type == 2) {
  339. // 多选
  340. const idx = ans.indexOf(tag);
  341. if (idx === -1) {
  342. ans.push(tag);
  343. } else {
  344. ans.splice(idx, 1);
  345. }
  346. }
  347. this.$set(this.currentQuestion, 'userAnswer', ans);
  348. },
  349. // 提交当前题答案并切换题目
  350. _submitThenGo(nextGroupIndex, nextQuestionIndex) {
  351. const q = this.currentQuestion;
  352. const hasAnswer = q && q.userAnswer && q.userAnswer[0];
  353. if (!hasAnswer) {
  354. this._goTo(nextGroupIndex, nextQuestionIndex);
  355. return;
  356. }
  357. const userAnswerStr = q.userAnswer.join(',');
  358. examUserPracticeSubmit({
  359. attemptId: this.newData.attemptId,
  360. questionId: q.id,
  361. userAnswer: userAnswerStr,
  362. }).then(res => {
  363. const data = (res.data || {}).data || {};
  364. // 回写 answerStatus 和 userAnswer 到面板 item
  365. const answerStatus = data.answerStatus;
  366. const g = this.questionTypeGroups[this.groupIndex];
  367. if (g && g.questions[this.questionIndex]) {
  368. this.$set(g.questions[this.questionIndex], 'answerStatus', answerStatus);
  369. this.$set(g.questions[this.questionIndex], 'userAnswer', userAnswerStr);
  370. }
  371. this._goTo(nextGroupIndex, nextQuestionIndex);
  372. }).catch(() => {
  373. this._goTo(nextGroupIndex, nextQuestionIndex);
  374. });
  375. },
  376. _goTo(gi, qi) {
  377. this.groupIndex = gi;
  378. this.questionIndex = qi;
  379. const g = this.questionTypeGroups[gi];
  380. if (!g) return;
  381. const item = g.questions[qi];
  382. if (!item) return;
  383. this.loadQuestionDetail(item.questionId, item.userAnswer);
  384. },
  385. prevQuestion() {
  386. if (this.isFirstQuestion) return;
  387. let gi = this.groupIndex;
  388. let qi = this.questionIndex;
  389. if (qi > 0) {
  390. qi--;
  391. } else {
  392. gi--;
  393. qi = (this.questionTypeGroups[gi] || { questions: [] }).questions.length - 1;
  394. }
  395. this._submitThenGo(gi, qi);
  396. },
  397. nextQuestion() {
  398. if (this.isLastQuestion) return;
  399. let gi = this.groupIndex;
  400. let qi = this.questionIndex;
  401. const curGroup = this.questionTypeGroups[gi];
  402. if (qi < curGroup.questions.length - 1) {
  403. qi++;
  404. } else {
  405. gi++;
  406. qi = 0;
  407. }
  408. this._submitThenGo(gi, qi);
  409. },
  410. // 从题号面板跳题
  411. jumpToQuestion(gi, qi) {
  412. this.showQuestionNav = false;
  413. if (gi === this.groupIndex && qi === this.questionIndex) return;
  414. this._submitThenGo(gi, qi);
  415. },
  416. },
  417. }
  418. </script>
  419. <style lang="stylus" scoped>
  420. .practice-screen
  421. height 100%
  422. display flex
  423. flex-direction column
  424. background #f0f4fb
  425. overflow hidden
  426. // 顶部绿色计时器
  427. .timer-bar
  428. display flex
  429. align-items center
  430. justify-content center
  431. gap 16rpx
  432. background linear-gradient(90deg, #52c41a, #389e0d)
  433. padding 12rpx 32rpx
  434. flex-shrink 0
  435. .timer-label
  436. color rgba(255,255,255,.8)
  437. font-size 24rpx
  438. .timer-text
  439. color #fff
  440. font-size 44rpx
  441. font-weight 700
  442. letter-spacing 4rpx
  443. .timer-mode
  444. color rgba(255,255,255,.6)
  445. font-size 22rpx
  446. // 统计行
  447. .stat-row
  448. display flex
  449. align-items center
  450. background #fff
  451. padding 12rpx 0
  452. flex-shrink 0
  453. border-bottom 1rpx solid #e8edf3
  454. .stat-item
  455. flex 1
  456. display flex
  457. flex-direction column
  458. align-items center
  459. .stat-num
  460. font-size 36rpx
  461. font-weight 700
  462. color #333
  463. .stat-correct
  464. color #52c41a
  465. .stat-wrong
  466. color #ff4d4f
  467. .stat-label
  468. font-size 22rpx
  469. color #999
  470. margin-top 2rpx
  471. .stat-divider
  472. width 1rpx
  473. height 48rpx
  474. background #e8edf3
  475. // 题目内容滚动区
  476. .q-scroll-content
  477. flex 1
  478. overflow hidden
  479. .q-card
  480. margin 24rpx
  481. padding 28rpx
  482. background #fff
  483. border-radius 20rpx
  484. border 1rpx solid #dde3ed
  485. // 题头
  486. .q-header
  487. display flex
  488. align-items center
  489. justify-content space-between
  490. margin-bottom 20rpx
  491. .q-header-left
  492. display flex
  493. align-items center
  494. flex-wrap wrap
  495. gap 10rpx
  496. .q-type-badge
  497. background #1677ff
  498. color #fff
  499. font-size 22rpx
  500. padding 4rpx 14rpx
  501. border-radius 8rpx
  502. .q-num-text
  503. font-size 26rpx
  504. font-weight 600
  505. color #333
  506. .diff-badge
  507. font-size 20rpx
  508. padding 2rpx 12rpx
  509. border-radius 8rpx
  510. border 1rpx solid transparent
  511. .diff-easy
  512. background #f6ffed
  513. color #52c41a
  514. border-color #b7eb8f
  515. .diff-normal
  516. background #e6f4ff
  517. color #1677ff
  518. border-color #91caff
  519. .diff-hard
  520. background #fff7e6
  521. color #fa8c16
  522. border-color #ffd591
  523. .diff-extreme
  524. background #f9f0ff
  525. color #722ed1
  526. border-color #d3adf7
  527. .view-answer-btn
  528. background #1677ff
  529. color #fff
  530. font-size 24rpx
  531. padding 8rpx 24rpx
  532. border-radius 10rpx
  533. flex-shrink 0
  534. // 题干
  535. .q-content
  536. font-size 28rpx
  537. color #333
  538. line-height 1.7
  539. margin-bottom 24rpx
  540. // 选项
  541. .q-options
  542. display flex
  543. flex-direction column
  544. gap 16rpx
  545. margin-bottom 16rpx
  546. .q-option
  547. display flex
  548. align-items center
  549. border 1rpx solid #e8edf3
  550. border-radius 12rpx
  551. padding 20rpx 24rpx
  552. .q-option.selected
  553. border-color #1677ff
  554. background #e6f7ff
  555. .q-option-label
  556. background #1677ff
  557. color #fff
  558. .q-option-label
  559. width 48rpx
  560. height 48rpx
  561. line-height 48rpx
  562. text-align center
  563. border-radius 50%
  564. background #f0f0f0
  565. color #666
  566. font-size 24rpx
  567. font-weight 600
  568. flex-shrink 0
  569. margin-right 20rpx
  570. .q-option-content
  571. flex 1
  572. font-size 28rpx
  573. color #333
  574. // 答案区
  575. .answer-result-box
  576. background #f5f5f5
  577. border-radius 12rpx
  578. padding 24rpx
  579. margin-top 16rpx
  580. .answer-row
  581. font-size 26rpx
  582. color #333
  583. margin-bottom 12rpx
  584. .answer-label
  585. font-weight 600
  586. .answer-value
  587. color #52c41a
  588. font-weight 600
  589. .analysis-title
  590. font-size 26rpx
  591. font-weight 600
  592. color #333
  593. margin-bottom 8rpx
  594. .analysis-content
  595. font-size 26rpx
  596. color #666
  597. line-height 1.7
  598. margin 0 20rpx
  599. .analysis-empty
  600. color #999
  601. .scroll-bottom-space
  602. height 40rpx
  603. // 底部上一题/选题/下一题
  604. .q-nav
  605. display flex
  606. background #fff
  607. border-top 1rpx solid #e8edf3
  608. flex-shrink 0
  609. .q-nav-btn
  610. flex 1
  611. height 88rpx
  612. line-height 88rpx
  613. text-align center
  614. font-size 28rpx
  615. color #1677ff
  616. .q-nav-btn.q-nav-btn-next
  617. border-left 1rpx solid #e8edf3
  618. .q-nav-center
  619. border-left 1rpx solid #e8edf3
  620. border-right 1rpx solid #e8edf3
  621. .q-nav-btn.disabled
  622. color #ccc
  623. // 题号导航底部弹层
  624. .q-nav-panel
  625. position fixed
  626. top 0
  627. left 0
  628. right 0
  629. bottom 0
  630. background rgba(0,0,0,0.45)
  631. z-index 100
  632. display flex
  633. flex-direction column
  634. justify-content flex-end
  635. .q-nav-scroll-wrapper
  636. background #fff
  637. border-radius 32rpx 32rpx 0 0
  638. max-height 70vh
  639. display flex
  640. flex-direction column
  641. .q-nav-header
  642. display flex
  643. align-items center
  644. justify-content space-between
  645. padding 28rpx 28rpx 16rpx
  646. flex-shrink 0
  647. .q-nav-title
  648. font-size 30rpx
  649. font-weight 600
  650. color #222
  651. .q-nav-close
  652. font-size 32rpx
  653. color #999
  654. padding 10rpx
  655. .q-nav-scroll
  656. overflow hidden
  657. padding-bottom 40rpx
  658. .q-group
  659. padding 0 28rpx 16rpx
  660. .q-group-title
  661. font-size 24rpx
  662. color #666
  663. line-height 60rpx
  664. .q-num-row
  665. display flex
  666. flex-wrap wrap
  667. .q-num
  668. width 72rpx
  669. height 72rpx
  670. line-height 72rpx
  671. text-align center
  672. font-size 24rpx
  673. border-radius 12rpx
  674. margin 8rpx
  675. background #f4f6fa
  676. color #333
  677. border 1rpx solid transparent
  678. .q-num.answered
  679. background #f6ffed
  680. color #52c41a
  681. border-color #b7eb8f
  682. .q-num.wrong
  683. background #fff2f0
  684. color #ff4d4f
  685. border-color #ffa39e
  686. .q-num.current
  687. background #1677ff
  688. color #fff
  689. // 图例
  690. .nav-legend
  691. display flex
  692. gap 24rpx
  693. flex-wrap wrap
  694. padding 12rpx 28rpx 24rpx
  695. .legend-item
  696. display flex
  697. align-items center
  698. gap 8rpx
  699. font-size 22rpx
  700. color #666
  701. .legend-dot
  702. width 40rpx
  703. height 40rpx
  704. margin 0
  705. font-size 0
  706. // 空态
  707. .empty-tip
  708. text-align center
  709. line-height 120rpx
  710. color #aaa
  711. font-size 24rpx
  712. </style>