scoreDetail.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. <!-- 答题详情 -->
  2. <template>
  3. <view class="safetyEducationExamination-scoreDetail">
  4. <scroll-view scroll-y class="scroll-content" @scrolltolower="onScrollToLower">
  5. <!-- 考试概览 -->
  6. <view class="card overview-card">
  7. <view class="row-between mb8">
  8. <text class="tag tag-blue text-xs">{{ detail.examTypeName }}</text>
  9. <text :class="detail.isPass ? 'score-result-pass' : 'score-result-fail'">
  10. {{ detail.isPass ? '通过' : '未通过' }}
  11. </text>
  12. </view>
  13. <view class="exam-name">{{ detail.examName }}</view>
  14. <view class="overview-grid">
  15. <view><text class="text-gray">考试时间:</text>{{ detail.examTime }}</view>
  16. <view><text class="text-gray">用时:</text>{{ detail.usedTime }}</view>
  17. <view><text class="text-gray">满分/及格:</text>{{ detail.totalScore }} / {{ detail.passScore }}</view>
  18. <view><text class="text-gray">考次:</text>{{ detail.attemptCount }} / {{ detail.attemptLimit === 0 ? '不限' : detail.attemptLimit }}</view>
  19. <view>
  20. <text class="text-gray">最高成绩:</text>
  21. <text class="text-blue fw500">{{ detail.bestScore }} 分</text>
  22. </view>
  23. <view>
  24. <text class="text-gray">考试结果:</text>
  25. <text class="fw500" :class="detail.isPass ? 'text-green' : 'text-red'">
  26. {{ detail.isPass ? '通过' : '未通过' }}
  27. </text>
  28. </view>
  29. </view>
  30. <!-- 答题统计 -->
  31. <view class="stat-box">
  32. <view class="text-sm fw500 mb8">答题统计</view>
  33. <view class="stat-grid">
  34. <view class="stat-item">
  35. <view class="stat-num text-blue">{{ detail.totalQuestionNum || 0 }}</view>
  36. <view class="text-xs text-gray">总题数</view>
  37. </view>
  38. <view class="stat-item">
  39. <view class="stat-num text-green">{{ detail.correctCount || 0 }}</view>
  40. <view class="text-xs text-gray">正确</view>
  41. </view>
  42. <view class="stat-item">
  43. <view class="stat-num text-red">{{ detail.wrongCount || 0 }}</view>
  44. <view class="text-xs text-gray">错误</view>
  45. </view>
  46. <view class="stat-item">
  47. <view class="stat-num text-blue">{{ detail.accuracy }}%</view>
  48. <view class="text-xs text-gray">正确率</view>
  49. </view>
  50. </view>
  51. </view>
  52. </view>
  53. <!-- 答题详情 -->
  54. <view class="detail-panel">
  55. <view class="detail-title">
  56. 答题详情
  57. <text class="text-gray detail-title-sub">(共{{ total || 0 }}题)</text>
  58. </view>
  59. <view v-for="(item, index) in answerList" :key="index" class="question-card">
  60. <view class="row-between mb8">
  61. <view class="q-head">
  62. <text class="q-type-badge">{{ questionTypeName(item.questionType) }}</text>
  63. <text class="text-xs text-gray q-no">第 {{ questionNo(index) }} 题</text>
  64. <text class="diff-badge" :class="difficultyClass(item.difficulty)">{{ difficultyName(item.difficulty) }}</text>
  65. </view>
  66. <text class="tag text-xs" :class="isCorrect(item) ? 'tag-green' : 'tag-orange'">
  67. {{ isCorrect(item) ? '答对' : '答错' }}
  68. </text>
  69. </view>
  70. <view class="q-content">
  71. <rich-text :nodes="item.questionContent || ''"></rich-text>
  72. </view>
  73. <view class="answer-box" :class="isCorrect(item) ? 'answer-box-correct' : 'answer-box-wrong'">
  74. <view class="mb4">
  75. <text class="text-xs text-gray">我的答案:</text>
  76. <text v-if="item.answerTags[0]" class="text-xs fw500" :class="isCorrect(item) ? 'text-green' : 'text-red'">
  77. <!-- {{ item.answerTags }}{{ answerSuffix(item) }} -->
  78. {{ item.answerTags }}
  79. </text>
  80. <text v-if="!item.answerTags[0]" class="text-xs fw500" :class="isCorrect(item) ? 'text-green' : 'text-red'">
  81. 未作答
  82. </text>
  83. </view>
  84. <view>
  85. <text class="text-xs text-gray">正确答案:</text>
  86. <text class="text-xs text-green fw500">{{ item.correctAnswerTags }}</text>
  87. </view>
  88. </view>
  89. <view v-if="item.analysis" class="analysis-box">解析:{{ item.analysis }}</view>
  90. </view>
  91. <view v-if="!answerList[0] && !listLoading" class="empty-tip">暂无答题记录</view>
  92. <view v-if="listLoading" class="loading-tip">加载中...</view>
  93. <view v-if="noMore && answerList[0]" class="loading-tip">没有更多了</view>
  94. </view>
  95. </scroll-view>
  96. </view>
  97. </template>
  98. <script>
  99. import {
  100. parseTime
  101. } from '@/component/public.js'
  102. import {
  103. examElExamUserResultMyScoreDetail,
  104. examElExamAttemptPaperQuestionPage
  105. } from '@/pages_safetyEducationExamination/api/index.js'
  106. // 题型映射:1-单选题,2-多选题,3-判断题
  107. const QUESTION_TYPE_MAP = {
  108. 1: '单选题',
  109. 2: '多选题',
  110. 3: '判断题',
  111. };
  112. // 难度映射:1-简单,2-一般,3-较难,4-极难
  113. const DIFFICULTY_MAP = {
  114. 1: { name: '简单', cls: 'diff-easy' },
  115. 2: { name: '一般', cls: 'diff-normal' },
  116. 3: { name: '较难', cls: 'diff-hard' },
  117. 4: { name: '极难', cls: 'diff-extreme' },
  118. };
  119. export default {
  120. data() {
  121. return {
  122. attemptId: '',
  123. examId: '',
  124. // 上部分:考试概况
  125. detail: {
  126. examTypeName: '',
  127. examName: '',
  128. isPass: false,
  129. examTime: '',
  130. usedTime: '',
  131. totalScore: 0,
  132. passScore: 0,
  133. attemptCount: 0,
  134. attemptLimit: 0,
  135. bestScore: 0,
  136. totalQuestionNum: 0,
  137. correctCount: 0,
  138. wrongCount: 0,
  139. accuracy: '-',
  140. },
  141. // 下部分:答题详情列表
  142. answerList: [],
  143. listLoading: false,
  144. page: 1,
  145. pageSize: 10,
  146. total: 0,
  147. noMore: false,
  148. }
  149. },
  150. onLoad(options) {
  151. this.attemptId = options.attemptId || '';
  152. this.examId = options.examId || '';
  153. if (!this.attemptId) {
  154. uni.showToast({ title: '考试信息无效', icon: 'none' });
  155. setTimeout(() => uni.navigateBack(), 1500);
  156. return;
  157. }
  158. this.loadExamDetail();
  159. this.loadAnswerList();
  160. },
  161. methods: {
  162. questionTypeName(type) {
  163. return QUESTION_TYPE_MAP[type] || '';
  164. },
  165. difficultyName(difficulty) {
  166. const d = DIFFICULTY_MAP[difficulty];
  167. return d ? d.name : '';
  168. },
  169. difficultyClass(difficulty) {
  170. const d = DIFFICULTY_MAP[difficulty];
  171. return d ? d.cls : '';
  172. },
  173. isCorrect(item) {
  174. return item.isCorrect === 1 || item.isCorrect === true;
  175. },
  176. answerSuffix(item) {
  177. if (item.answerStatusText) return `(${item.answerStatusText})`;
  178. if (item.isPartial) return '(部分正确)';
  179. return this.isCorrect(item) ? '(正确)' : '(错误)';
  180. },
  181. questionNo(index) {
  182. const item = this.answerList[index];
  183. if (item && item.questionNo) return item.questionNo;
  184. if (item && item.sortNo) return item.sortNo;
  185. return index + 1;
  186. },
  187. // 上部分:考试概况(独立详情接口)
  188. async loadExamDetail() {
  189. try {
  190. let obj = {
  191. examId:this.examId,
  192. examScene:1,
  193. }
  194. const { data } = await examElExamUserResultMyScoreDetail(obj);
  195. if (data.code === 200 && data.data) {
  196. const d = data.data;
  197. this.detail = {
  198. examTypeName: d.examTypeName || '',
  199. examName: d.examName || '',
  200. isPass: d.examStatus === 1,
  201. examTime: parseTime(d.startTime, '{y}-{m}-{d}'),
  202. usedTime: d.myDuration || '-',
  203. totalScore: d.totalScore || 0,
  204. passScore: d.passScore || 0,
  205. attemptCount: d.attemptCount || 0,
  206. attemptLimit: d.attemptLimit || 0,
  207. bestScore: d.bestScore || 0,
  208. totalQuestionNum: d.totalQuestionNum || 0,
  209. correctCount: d.correctCount || 0,
  210. wrongCount: d.wrongCount || 0,
  211. accuracy: d.accuracy || '-',
  212. };
  213. }
  214. } catch (e) {
  215. console.error('获取考试详情失败', e);
  216. }
  217. },
  218. resetList() {
  219. this.page = 1;
  220. this.noMore = false;
  221. this.$set(this, 'answerList', []);
  222. this.loadAnswerList();
  223. },
  224. // 下部分:答题详情列表(分页滚动加载)
  225. async loadAnswerList() {
  226. if (this.listLoading || this.noMore) return;
  227. this.listLoading = true;
  228. try {
  229. const { data } = await examElExamAttemptPaperQuestionPage({
  230. page: this.page,
  231. pageSize: this.pageSize,
  232. attemptId: this.attemptId,
  233. });
  234. if (data.code === 200) {
  235. const records = data.data.records || [];
  236. const newList = this.page === 1 ? records : [...this.answerList, ...records];
  237. this.$set(this, 'answerList', newList);
  238. this.total = data.data.total;
  239. if (newList.length >= this.total) {
  240. this.noMore = true;
  241. } else {
  242. this.page++;
  243. }
  244. }
  245. } catch (e) {
  246. console.error('获取答题详情失败', e);
  247. } finally {
  248. this.listLoading = false;
  249. }
  250. },
  251. onScrollToLower() {
  252. this.loadAnswerList();
  253. },
  254. },
  255. }
  256. </script>
  257. <style lang="stylus" scoped>
  258. .safetyEducationExamination-scoreDetail
  259. height 100%
  260. display flex
  261. flex-direction column
  262. overflow hidden
  263. background-color #f0f4fb
  264. .scroll-content
  265. flex 1
  266. overflow hidden
  267. .card
  268. background #fff
  269. border 1rpx solid #dde3ed
  270. border-radius 24rpx
  271. padding 32rpx
  272. margin 24rpx 24rpx 24rpx
  273. box-shadow 0 4rpx 24rpx rgba(24,87,212,0.08)
  274. .exam-name
  275. font-size 32rpx
  276. font-weight 700
  277. color #1a2233
  278. margin-bottom 20rpx
  279. .overview-grid
  280. display grid
  281. grid-template-columns 1fr 1fr
  282. gap 12rpx
  283. font-size 26rpx
  284. color #1a2233
  285. margin-bottom 28rpx
  286. // 答题统计
  287. .stat-box
  288. background #f4f6fa
  289. border-radius 16rpx
  290. padding 24rpx
  291. margin-bottom 28rpx
  292. .stat-grid
  293. display grid
  294. grid-template-columns 1fr 1fr 1fr 1fr
  295. gap 16rpx
  296. text-align center
  297. .stat-num
  298. font-size 40rpx
  299. font-weight 700
  300. // 答题详情区
  301. .detail-panel
  302. padding 0 24rpx 24rpx
  303. .detail-title
  304. font-size 24rpx
  305. font-weight 500
  306. padding 28rpx 0 20rpx
  307. .detail-title-sub
  308. font-weight 400
  309. // 题目卡片
  310. .question-card
  311. background #fff
  312. border 1rpx solid #dde3ed
  313. border-radius 20rpx
  314. padding 28rpx
  315. margin-bottom 20rpx
  316. .row-between
  317. display flex
  318. align-items center
  319. justify-content space-between
  320. .q-head
  321. display flex
  322. align-items center
  323. .q-type-badge
  324. background #1857d4
  325. color #fff
  326. font-size 24rpx
  327. padding 4rpx 16rpx
  328. border-radius 8rpx
  329. .q-no
  330. margin-left 12rpx
  331. .diff-badge
  332. margin-left 12rpx
  333. font-size 20rpx
  334. padding 2rpx 12rpx
  335. border-radius 8rpx
  336. border 1rpx solid transparent
  337. .diff-easy
  338. background #f6ffed
  339. color #52c41a
  340. border-color #b7eb8f
  341. .diff-normal
  342. background #fff7e6
  343. color #fa8c16
  344. border-color #ffd591
  345. .diff-hard
  346. background #fff2f0
  347. color #f5222d
  348. border-color #ffccc7
  349. .diff-extreme
  350. background #f9f0ff
  351. color #722ed1
  352. border-color #d3adf7
  353. .q-content
  354. font-size 24rpx
  355. line-height 1.7
  356. margin-bottom 20rpx
  357. // 答案区
  358. .answer-box
  359. border-radius 16rpx
  360. padding 20rpx
  361. margin-bottom 20rpx
  362. .answer-box-correct
  363. background #f6ffed
  364. border 1rpx solid #b7eb8f
  365. .answer-box-wrong
  366. background #f4f6fa
  367. // 解析区
  368. .analysis-box
  369. background #f6ffed
  370. border-radius 16rpx
  371. padding 20rpx
  372. font-size 26rpx
  373. color #4a5568
  374. line-height 1.7
  375. // 标签
  376. .tag
  377. display inline-flex
  378. align-items center
  379. padding 4rpx 16rpx
  380. border-radius 40rpx
  381. font-weight 500
  382. .tag-blue
  383. background #eef3fd
  384. color #1857d4
  385. .tag-green
  386. background #edfaf6
  387. color #0d8f6f
  388. .tag-orange
  389. background #fef6ec
  390. color #e67e22
  391. .score-result-pass
  392. color #10a37f
  393. font-weight 600
  394. .score-result-fail
  395. color #e53e3e
  396. font-weight 600
  397. .text-xs
  398. font-size 22rpx
  399. .text-sm
  400. font-size 24rpx
  401. .text-gray
  402. color #8896a7
  403. .text-blue
  404. color #1857d4
  405. .text-green
  406. color #10a37f
  407. .text-red
  408. color #e53e3e
  409. .fw500
  410. font-weight 500
  411. .mb4
  412. margin-bottom 8rpx
  413. .mb8
  414. margin-bottom 16rpx
  415. // 提示
  416. .empty-tip
  417. text-align center
  418. line-height 80rpx
  419. color #aaa
  420. font-size 24rpx
  421. .loading-tip
  422. text-align center
  423. line-height 60rpx
  424. color #aaa
  425. font-size 22rpx
  426. </style>