logic-test.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. <template>
  2. <view class="container">
  3. <!-- 引导页 -->
  4. <view class="step-intro" v-if="step === 'intro'">
  5. <view class="intro-icon">
  6. <text class="intro-icon-text">🧠</text>
  7. </view>
  8. <text class="intro-title">逻辑推理测试</text>
  9. <text class="intro-desc">观察数列规律,从选项中选出缺失的数字</text>
  10. <view class="intro-info">
  11. <view class="info-row">
  12. <text class="info-label">题目数量</text>
  13. <text class="info-value">8 题</text>
  14. </view>
  15. <view class="info-row">
  16. <text class="info-label">每题限时</text>
  17. <text class="info-value">15 秒</text>
  18. </view>
  19. <view class="info-row">
  20. <text class="info-label">测评维度</text>
  21. <text class="info-value">逻辑思维</text>
  22. </view>
  23. </view>
  24. <view class="btn btn--primary" @tap="startTest">开始测试</view>
  25. </view>
  26. <!-- 答题页 -->
  27. <view class="step-playing" v-if="step === 'playing'">
  28. <view class="quiz-header">
  29. <text class="quiz-progress">第 {{ currentQ + 1 }}/{{ questions.length }} 题</text>
  30. <text class="quiz-timer">{{ timerSeconds }}s</text>
  31. </view>
  32. <view class="timer-track">
  33. <view class="timer-fill" :style="'width:' + timerPct + '%'"></view>
  34. </view>
  35. <view class="question-card" v-if="currentQuestion">
  36. <text class="q-text">{{ currentQuestion.seq }}</text>
  37. <view class="q-options">
  38. <view
  39. class="q-option"
  40. v-for="(opt, oi) in currentQuestion.options"
  41. :key="oi"
  42. :class="{ 'q-option--selected': selectedIndex === oi }"
  43. @tap="selectOption(oi)"
  44. >
  45. <text class="q-option-text">{{ opt }}</text>
  46. </view>
  47. </view>
  48. </view>
  49. </view>
  50. <!-- 结果页 -->
  51. <view class="step-result" v-if="step === 'result'">
  52. <view class="score-circle">
  53. <text class="score-value">{{ score }}</text>
  54. </view>
  55. <text class="score-label">{{ scoreText }}</text>
  56. <view class="result-info">
  57. <view class="result-row">
  58. <text class="result-label">答对题数</text>
  59. <text class="result-value">{{ correctCount }}/{{ questions.length }}</text>
  60. </view>
  61. <view class="result-row">
  62. <text class="result-label">测评维度</text>
  63. <view class="dim-tag">
  64. <text class="dim-tag-text">逻辑思维</text>
  65. </view>
  66. </view>
  67. <view class="result-row">
  68. <text class="result-label">得分</text>
  69. <text class="result-value">{{ score }} 分</text>
  70. </view>
  71. </view>
  72. <view class="btn btn--primary" @tap="submitScore">提交成绩</view>
  73. <view class="btn btn--outline" @tap="restartTest">再测一次</view>
  74. </view>
  75. </view>
  76. </template>
  77. <script>
  78. import { submitCognitiveSelfTest } from '../../utils/api.js'
  79. var TIMER_SECONDS = 15
  80. var ROUND_COUNT = 8
  81. var QUESTION_BANK = [
  82. { seq: '2, 4, 6, 8, ?', options: ['9', '10', '11', '12'], answer: '10' },
  83. { seq: '21, 18, 15, 12, ?', options: ['10', '9', '8', '7'], answer: '9' },
  84. { seq: '1, 2, 4, 8, ?', options: ['12', '14', '16', '20'], answer: '16' },
  85. { seq: '1, 3, 9, 27, ?', options: ['54', '72', '81', '90'], answer: '81' },
  86. { seq: '1, 1, 2, 3, 5, 8, ?', options: ['10', '12', '13', '15'], answer: '13' },
  87. { seq: '2, 3, 5, 8, 13, ?', options: ['18', '20', '21', '24'], answer: '21' },
  88. { seq: '1, 4, 9, 16, 25, ?', options: ['30', '32', '36', '49'], answer: '36' },
  89. { seq: '1, 8, 27, 64, ?', options: ['100', '121', '125', '144'], answer: '125' },
  90. { seq: '5, 7, 6, 8, 7, ?', options: ['8', '9', '10', '11'], answer: '9' },
  91. { seq: '2, 5, 6, 9, 10, ?', options: ['11', '12', '13', '14'], answer: '13' },
  92. { seq: '1, 3, 5, 7, 9, ?', options: ['10', '11', '12', '13'], answer: '11' },
  93. { seq: '2, 5, 11, 23, ?', options: ['35', '41', '46', '47'], answer: '47' },
  94. { seq: '1, 3, 7, 13, 21, ?', options: ['27', '29', '31', '33'], answer: '31' },
  95. { seq: '3, 8, 6, 11, 9, ?', options: ['12', '13', '14', '15'], answer: '14' },
  96. { seq: '2, 3, 5, 7, 11, ?', options: ['12', '13', '15', '17'], answer: '13' },
  97. { seq: '64, 32, 16, 8, ?', options: ['2', '4', '6', '8'], answer: '4' },
  98. { seq: '4, 7, 10, 13, 16, ?', options: ['17', '18', '19', '21'], answer: '19' },
  99. { seq: '50, 45, 40, 35, ?', options: ['25', '28', '30', '32'], answer: '30' },
  100. { seq: '1, 5, 9, 13, 17, ?', options: ['19', '20', '21', '23'], answer: '21' },
  101. { seq: '3, 5, 9, 17, 33, ?', options: ['49', '57', '63', '65'], answer: '65' },
  102. { seq: '4, 8, 7, 14, 13, ?', options: ['20', '24', '25', '26'], answer: '26' },
  103. { seq: '1, 3, 6, 10, 15, ?', options: ['18', '20', '21', '24'], answer: '21' },
  104. { seq: '30, 28, 24, 18, 10, ?', options: ['0', '2', '4', '6'], answer: '0' },
  105. { seq: '7, 14, 28, 56, ?', options: ['84', '96', '108', '112'], answer: '112' }
  106. ]
  107. export default {
  108. data() {
  109. return {
  110. step: 'intro',
  111. questions: [],
  112. currentQ: 0,
  113. selectedIndex: -1,
  114. correctCount: 0,
  115. timeLeft: TIMER_SECONDS,
  116. timer: null,
  117. answered: false,
  118. score: 0,
  119. memberId: ''
  120. }
  121. },
  122. computed: {
  123. currentQuestion: function() {
  124. if (this.questions.length === 0) {
  125. return null
  126. }
  127. return this.questions[this.currentQ]
  128. },
  129. timerSeconds: function() {
  130. return Math.ceil(this.timeLeft)
  131. },
  132. timerPct: function() {
  133. var pct = (this.timeLeft / TIMER_SECONDS) * 100
  134. if (pct < 0) {
  135. pct = 0
  136. }
  137. return pct
  138. },
  139. scoreText: function() {
  140. var s = this.score
  141. if (s >= 90) return '太棒了,逻辑满分!'
  142. if (s >= 70) return '逻辑思维很出色!'
  143. if (s >= 50) return '表现不错,继续加油!'
  144. return '多练习数列规律会更好!'
  145. }
  146. },
  147. onLoad(options) {
  148. if (options && options.memberId) {
  149. this.memberId = options.memberId
  150. }
  151. this.memberId = this.memberId || uni.getStorageSync('currentChildId') || ''
  152. },
  153. onUnload() {
  154. this.stopTimer()
  155. },
  156. methods: {
  157. shuffle: function(arr) {
  158. var a = arr.slice()
  159. for (var i = a.length - 1; i > 0; i--) {
  160. var j = Math.floor(Math.random() * (i + 1))
  161. var tmp = a[i]
  162. a[i] = a[j]
  163. a[j] = tmp
  164. }
  165. return a
  166. },
  167. startTest: function() {
  168. var shuffled = this.shuffle(QUESTION_BANK)
  169. this.questions = shuffled.slice(0, ROUND_COUNT)
  170. this.currentQ = 0
  171. this.correctCount = 0
  172. this.score = 0
  173. this.answered = false
  174. this.selectedIndex = -1
  175. this.step = 'playing'
  176. this.startTimer()
  177. },
  178. startTimer: function() {
  179. this.stopTimer()
  180. this.timeLeft = TIMER_SECONDS
  181. var self = this
  182. this.timer = setInterval(function() {
  183. self.timeLeft -= 0.1
  184. if (self.timeLeft <= 0.01) {
  185. self.timeLeft = 0
  186. self.onTimeout()
  187. }
  188. }, 100)
  189. },
  190. stopTimer: function() {
  191. if (this.timer) {
  192. clearInterval(this.timer)
  193. this.timer = null
  194. }
  195. },
  196. selectOption: function(idx) {
  197. if (this.answered) return
  198. this.answered = true
  199. this.selectedIndex = idx
  200. this.stopTimer()
  201. var q = this.currentQuestion
  202. if (q && q.options[idx] === q.answer) {
  203. this.correctCount++
  204. }
  205. var self = this
  206. setTimeout(function() {
  207. self.advance()
  208. }, 400)
  209. },
  210. onTimeout: function() {
  211. if (this.answered) return
  212. this.answered = true
  213. this.stopTimer()
  214. this.selectedIndex = -1
  215. var self = this
  216. setTimeout(function() {
  217. self.advance()
  218. }, 400)
  219. },
  220. advance: function() {
  221. if (this.currentQ < this.questions.length - 1) {
  222. this.currentQ++
  223. this.answered = false
  224. this.selectedIndex = -1
  225. this.startTimer()
  226. } else {
  227. this.finishTest()
  228. }
  229. },
  230. finishTest: function() {
  231. this.stopTimer()
  232. this.score = Math.round((this.correctCount / ROUND_COUNT) * 100)
  233. this.step = 'result'
  234. },
  235. restartTest: function() {
  236. this.stopTimer()
  237. this.startTest()
  238. },
  239. submitScore: function() {
  240. if (!this.memberId) {
  241. uni.showToast({ title: '未获取到成员信息', icon: 'none' })
  242. return
  243. }
  244. submitCognitiveSelfTest(this.memberId, 'logicScore', this.score)
  245. .then((res) => {
  246. if (res && res.code === 200) {
  247. uni.showToast({ title: '提交成功', icon: 'success' })
  248. var self = this
  249. setTimeout(function() {
  250. uni.navigateBack()
  251. }, 800)
  252. } else {
  253. uni.showToast({ title: '提交失败', icon: 'none' })
  254. }
  255. })
  256. .catch(function() {
  257. uni.showToast({ title: '提交失败', icon: 'none' })
  258. })
  259. }
  260. }
  261. }
  262. </script>
  263. <style scoped>
  264. .container {
  265. min-height: 100vh;
  266. background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
  267. padding: 60rpx 40rpx;
  268. box-sizing: border-box;
  269. }
  270. /* ===== 引导页 ===== */
  271. .step-intro {
  272. padding-top: 80rpx;
  273. display: flex;
  274. flex-direction: column;
  275. align-items: center;
  276. }
  277. .intro-icon {
  278. width: 160rpx;
  279. height: 160rpx;
  280. border-radius: 50%;
  281. background: rgba(255, 215, 0, 0.15);
  282. border: 3rpx solid #FFD700;
  283. display: flex;
  284. align-items: center;
  285. justify-content: center;
  286. margin-bottom: 40rpx;
  287. }
  288. .intro-icon-text {
  289. font-size: 72rpx;
  290. }
  291. .intro-title {
  292. font-size: 44rpx;
  293. font-weight: bold;
  294. color: #FFD700;
  295. margin-bottom: 16rpx;
  296. display: block;
  297. }
  298. .intro-desc {
  299. font-size: 26rpx;
  300. color: rgba(255, 255, 255, 0.7);
  301. margin-bottom: 50rpx;
  302. display: block;
  303. }
  304. .intro-info {
  305. width: 100%;
  306. background: rgba(255, 255, 255, 0.06);
  307. border-radius: 20rpx;
  308. padding: 20rpx 30rpx;
  309. margin-bottom: 60rpx;
  310. box-sizing: border-box;
  311. }
  312. .info-row {
  313. display: flex;
  314. justify-content: space-between;
  315. align-items: center;
  316. padding: 16rpx 0;
  317. }
  318. .info-row + .info-row {
  319. border-top: 1rpx solid rgba(255, 255, 255, 0.08);
  320. }
  321. .info-label {
  322. font-size: 26rpx;
  323. color: rgba(255, 255, 255, 0.7);
  324. }
  325. .info-value {
  326. font-size: 26rpx;
  327. color: #FFD700;
  328. font-weight: bold;
  329. }
  330. /* ===== 答题页 ===== */
  331. .quiz-header {
  332. display: flex;
  333. justify-content: space-between;
  334. align-items: center;
  335. margin-bottom: 20rpx;
  336. }
  337. .quiz-progress {
  338. font-size: 30rpx;
  339. color: #fff;
  340. font-weight: bold;
  341. }
  342. .quiz-timer {
  343. font-size: 30rpx;
  344. color: #FFD700;
  345. font-weight: bold;
  346. }
  347. .timer-track {
  348. height: 12rpx;
  349. background: rgba(255, 255, 255, 0.12);
  350. border-radius: 6rpx;
  351. overflow: hidden;
  352. margin-bottom: 50rpx;
  353. }
  354. .timer-fill {
  355. height: 100%;
  356. background: linear-gradient(90deg, #FFD700, #FFA500);
  357. border-radius: 6rpx;
  358. transition: width 0.1s linear;
  359. }
  360. .question-card {
  361. background: rgba(255, 255, 255, 0.06);
  362. border-radius: 24rpx;
  363. padding: 40rpx 30rpx;
  364. box-sizing: border-box;
  365. }
  366. .q-text {
  367. font-size: 44rpx;
  368. color: #fff;
  369. font-weight: bold;
  370. text-align: center;
  371. display: block;
  372. margin-bottom: 50rpx;
  373. letter-spacing: 4rpx;
  374. }
  375. .q-options {
  376. display: flex;
  377. flex-direction: column;
  378. }
  379. .q-option {
  380. padding: 26rpx 0;
  381. border-radius: 16rpx;
  382. border: 3rpx solid rgba(255, 255, 255, 0.2);
  383. background: #24243e;
  384. text-align: center;
  385. margin-bottom: 20rpx;
  386. transition: all 0.2s;
  387. }
  388. .q-option:last-child {
  389. margin-bottom: 0;
  390. }
  391. .q-option--selected {
  392. border-color: #FFD700;
  393. background: rgba(255, 215, 0, 0.12);
  394. }
  395. .q-option:active {
  396. transform: scale(0.98);
  397. }
  398. .q-option-text {
  399. font-size: 34rpx;
  400. color: #fff;
  401. font-weight: bold;
  402. }
  403. .q-option--selected .q-option-text {
  404. color: #FFD700;
  405. }
  406. /* ===== 结果页 ===== */
  407. .step-result {
  408. padding-top: 100rpx;
  409. display: flex;
  410. flex-direction: column;
  411. align-items: center;
  412. }
  413. .score-circle {
  414. width: 240rpx;
  415. height: 240rpx;
  416. border-radius: 50%;
  417. background: linear-gradient(135deg, #FFD700, #FFA500);
  418. display: flex;
  419. align-items: center;
  420. justify-content: center;
  421. box-shadow: 0 8rpx 40rpx rgba(255, 215, 0, 0.35);
  422. margin-bottom: 30rpx;
  423. }
  424. .score-value {
  425. font-size: 80rpx;
  426. font-weight: bold;
  427. color: #1a1a2e;
  428. }
  429. .score-label {
  430. font-size: 30rpx;
  431. color: #FFD700;
  432. margin-bottom: 50rpx;
  433. display: block;
  434. }
  435. .result-info {
  436. width: 100%;
  437. background: rgba(255, 255, 255, 0.06);
  438. border-radius: 20rpx;
  439. padding: 20rpx 30rpx;
  440. margin-bottom: 60rpx;
  441. box-sizing: border-box;
  442. }
  443. .result-row {
  444. display: flex;
  445. justify-content: space-between;
  446. align-items: center;
  447. padding: 16rpx 0;
  448. }
  449. .result-row + .result-row {
  450. border-top: 1rpx solid rgba(255, 255, 255, 0.08);
  451. }
  452. .result-label {
  453. font-size: 26rpx;
  454. color: rgba(255, 255, 255, 0.7);
  455. }
  456. .result-value {
  457. font-size: 28rpx;
  458. color: #fff;
  459. font-weight: bold;
  460. }
  461. .dim-tag {
  462. padding: 6rpx 24rpx;
  463. border-radius: 24rpx;
  464. border: 3rpx solid #FFD700;
  465. background: rgba(255, 215, 0, 0.12);
  466. }
  467. .dim-tag-text {
  468. font-size: 24rpx;
  469. color: #FFD700;
  470. font-weight: bold;
  471. }
  472. /* ===== 按钮 ===== */
  473. .btn {
  474. width: 100%;
  475. padding: 26rpx 0;
  476. border-radius: 50rpx;
  477. text-align: center;
  478. font-size: 30rpx;
  479. font-weight: bold;
  480. box-sizing: border-box;
  481. margin-bottom: 24rpx;
  482. }
  483. .btn:last-child {
  484. margin-bottom: 0;
  485. }
  486. .btn--primary {
  487. background: linear-gradient(135deg, #FFD700, #FFA500);
  488. color: #1a1a2e;
  489. }
  490. .btn--primary:active {
  491. opacity: 0.9;
  492. transform: scale(0.98);
  493. }
  494. .btn--outline {
  495. border: 3rpx solid #FFD700;
  496. color: #FFD700;
  497. background: transparent;
  498. }
  499. .btn--outline:active {
  500. background: rgba(255, 215, 0, 0.12);
  501. }
  502. </style>