|
|
@@ -0,0 +1,513 @@
|
|
|
+<template>
|
|
|
+ <view class="container">
|
|
|
+ <!-- 引导页 -->
|
|
|
+ <view class="step-intro" v-if="step === 'intro'">
|
|
|
+ <view class="intro-icon">
|
|
|
+ <text class="intro-icon-text">🧠</text>
|
|
|
+ </view>
|
|
|
+ <text class="intro-title">逻辑推理测试</text>
|
|
|
+ <text class="intro-desc">观察数列规律,从选项中选出缺失的数字</text>
|
|
|
+ <view class="intro-info">
|
|
|
+ <view class="info-row">
|
|
|
+ <text class="info-label">题目数量</text>
|
|
|
+ <text class="info-value">8 题</text>
|
|
|
+ </view>
|
|
|
+ <view class="info-row">
|
|
|
+ <text class="info-label">每题限时</text>
|
|
|
+ <text class="info-value">15 秒</text>
|
|
|
+ </view>
|
|
|
+ <view class="info-row">
|
|
|
+ <text class="info-label">测评维度</text>
|
|
|
+ <text class="info-value">逻辑思维</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="btn btn--primary" @tap="startTest">开始测试</view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 答题页 -->
|
|
|
+ <view class="step-playing" v-if="step === 'playing'">
|
|
|
+ <view class="quiz-header">
|
|
|
+ <text class="quiz-progress">第 {{ currentQ + 1 }}/{{ questions.length }} 题</text>
|
|
|
+ <text class="quiz-timer">{{ timerSeconds }}s</text>
|
|
|
+ </view>
|
|
|
+ <view class="timer-track">
|
|
|
+ <view class="timer-fill" :style="'width:' + timerPct + '%'"></view>
|
|
|
+ </view>
|
|
|
+ <view class="question-card" v-if="currentQuestion">
|
|
|
+ <text class="q-text">{{ currentQuestion.seq }}</text>
|
|
|
+ <view class="q-options">
|
|
|
+ <view
|
|
|
+ class="q-option"
|
|
|
+ v-for="(opt, oi) in currentQuestion.options"
|
|
|
+ :key="oi"
|
|
|
+ :class="{ 'q-option--selected': selectedIndex === oi }"
|
|
|
+ @tap="selectOption(oi)"
|
|
|
+ >
|
|
|
+ <text class="q-option-text">{{ opt }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 结果页 -->
|
|
|
+ <view class="step-result" v-if="step === 'result'">
|
|
|
+ <view class="score-circle">
|
|
|
+ <text class="score-value">{{ score }}</text>
|
|
|
+ </view>
|
|
|
+ <text class="score-label">{{ scoreText }}</text>
|
|
|
+ <view class="result-info">
|
|
|
+ <view class="result-row">
|
|
|
+ <text class="result-label">答对题数</text>
|
|
|
+ <text class="result-value">{{ correctCount }}/{{ questions.length }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="result-row">
|
|
|
+ <text class="result-label">测评维度</text>
|
|
|
+ <view class="dim-tag">
|
|
|
+ <text class="dim-tag-text">逻辑思维</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="result-row">
|
|
|
+ <text class="result-label">得分</text>
|
|
|
+ <text class="result-value">{{ score }} 分</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="btn btn--primary" @tap="submitScore">提交成绩</view>
|
|
|
+ <view class="btn btn--outline" @tap="restartTest">再测一次</view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { submitCognitiveSelfTest } from '../../utils/api.js'
|
|
|
+
|
|
|
+var TIMER_SECONDS = 15
|
|
|
+var ROUND_COUNT = 8
|
|
|
+
|
|
|
+var QUESTION_BANK = [
|
|
|
+ { seq: '2, 4, 6, 8, ?', options: ['9', '10', '11', '12'], answer: '10' },
|
|
|
+ { seq: '21, 18, 15, 12, ?', options: ['10', '9', '8', '7'], answer: '9' },
|
|
|
+ { seq: '1, 2, 4, 8, ?', options: ['12', '14', '16', '20'], answer: '16' },
|
|
|
+ { seq: '1, 3, 9, 27, ?', options: ['54', '72', '81', '90'], answer: '81' },
|
|
|
+ { seq: '1, 1, 2, 3, 5, 8, ?', options: ['10', '12', '13', '15'], answer: '13' },
|
|
|
+ { seq: '2, 3, 5, 8, 13, ?', options: ['18', '20', '21', '24'], answer: '21' },
|
|
|
+ { seq: '1, 4, 9, 16, 25, ?', options: ['30', '32', '36', '49'], answer: '36' },
|
|
|
+ { seq: '1, 8, 27, 64, ?', options: ['100', '121', '125', '144'], answer: '125' },
|
|
|
+ { seq: '5, 7, 6, 8, 7, ?', options: ['8', '9', '10', '11'], answer: '9' },
|
|
|
+ { seq: '2, 5, 6, 9, 10, ?', options: ['11', '12', '13', '14'], answer: '13' },
|
|
|
+ { seq: '1, 3, 5, 7, 9, ?', options: ['10', '11', '12', '13'], answer: '11' },
|
|
|
+ { seq: '2, 5, 11, 23, ?', options: ['35', '41', '46', '47'], answer: '47' },
|
|
|
+ { seq: '1, 3, 7, 13, 21, ?', options: ['27', '29', '31', '33'], answer: '31' },
|
|
|
+ { seq: '3, 8, 6, 11, 9, ?', options: ['12', '13', '14', '15'], answer: '14' },
|
|
|
+ { seq: '2, 3, 5, 7, 11, ?', options: ['12', '13', '15', '17'], answer: '13' },
|
|
|
+ { seq: '64, 32, 16, 8, ?', options: ['2', '4', '6', '8'], answer: '4' },
|
|
|
+ { seq: '4, 7, 10, 13, 16, ?', options: ['17', '18', '19', '21'], answer: '19' },
|
|
|
+ { seq: '50, 45, 40, 35, ?', options: ['25', '28', '30', '32'], answer: '30' },
|
|
|
+ { seq: '1, 5, 9, 13, 17, ?', options: ['19', '20', '21', '23'], answer: '21' },
|
|
|
+ { seq: '3, 5, 9, 17, 33, ?', options: ['49', '57', '63', '65'], answer: '65' },
|
|
|
+ { seq: '4, 8, 7, 14, 13, ?', options: ['20', '24', '25', '26'], answer: '26' },
|
|
|
+ { seq: '1, 3, 6, 10, 15, ?', options: ['18', '20', '21', '24'], answer: '21' },
|
|
|
+ { seq: '30, 28, 24, 18, 10, ?', options: ['0', '2', '4', '6'], answer: '0' },
|
|
|
+ { seq: '7, 14, 28, 56, ?', options: ['84', '96', '108', '112'], answer: '112' }
|
|
|
+]
|
|
|
+
|
|
|
+export default {
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ step: 'intro',
|
|
|
+ questions: [],
|
|
|
+ currentQ: 0,
|
|
|
+ selectedIndex: -1,
|
|
|
+ correctCount: 0,
|
|
|
+ timeLeft: TIMER_SECONDS,
|
|
|
+ timer: null,
|
|
|
+ answered: false,
|
|
|
+ score: 0,
|
|
|
+ memberId: ''
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ currentQuestion: function() {
|
|
|
+ if (this.questions.length === 0) {
|
|
|
+ return null
|
|
|
+ }
|
|
|
+ return this.questions[this.currentQ]
|
|
|
+ },
|
|
|
+ timerSeconds: function() {
|
|
|
+ return Math.ceil(this.timeLeft)
|
|
|
+ },
|
|
|
+ timerPct: function() {
|
|
|
+ var pct = (this.timeLeft / TIMER_SECONDS) * 100
|
|
|
+ if (pct < 0) {
|
|
|
+ pct = 0
|
|
|
+ }
|
|
|
+ return pct
|
|
|
+ },
|
|
|
+ scoreText: function() {
|
|
|
+ var s = this.score
|
|
|
+ if (s >= 90) return '太棒了,逻辑满分!'
|
|
|
+ if (s >= 70) return '逻辑思维很出色!'
|
|
|
+ if (s >= 50) return '表现不错,继续加油!'
|
|
|
+ return '多练习数列规律会更好!'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onLoad(options) {
|
|
|
+ if (options && options.memberId) {
|
|
|
+ this.memberId = options.memberId
|
|
|
+ }
|
|
|
+ this.memberId = this.memberId || uni.getStorageSync('currentChildId') || ''
|
|
|
+ },
|
|
|
+ onUnload() {
|
|
|
+ this.stopTimer()
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ shuffle: function(arr) {
|
|
|
+ var a = arr.slice()
|
|
|
+ for (var i = a.length - 1; i > 0; i--) {
|
|
|
+ var j = Math.floor(Math.random() * (i + 1))
|
|
|
+ var tmp = a[i]
|
|
|
+ a[i] = a[j]
|
|
|
+ a[j] = tmp
|
|
|
+ }
|
|
|
+ return a
|
|
|
+ },
|
|
|
+ startTest: function() {
|
|
|
+ var shuffled = this.shuffle(QUESTION_BANK)
|
|
|
+ this.questions = shuffled.slice(0, ROUND_COUNT)
|
|
|
+ this.currentQ = 0
|
|
|
+ this.correctCount = 0
|
|
|
+ this.score = 0
|
|
|
+ this.answered = false
|
|
|
+ this.selectedIndex = -1
|
|
|
+ this.step = 'playing'
|
|
|
+ this.startTimer()
|
|
|
+ },
|
|
|
+ startTimer: function() {
|
|
|
+ this.stopTimer()
|
|
|
+ this.timeLeft = TIMER_SECONDS
|
|
|
+ var self = this
|
|
|
+ this.timer = setInterval(function() {
|
|
|
+ self.timeLeft -= 0.1
|
|
|
+ if (self.timeLeft <= 0.01) {
|
|
|
+ self.timeLeft = 0
|
|
|
+ self.onTimeout()
|
|
|
+ }
|
|
|
+ }, 100)
|
|
|
+ },
|
|
|
+ stopTimer: function() {
|
|
|
+ if (this.timer) {
|
|
|
+ clearInterval(this.timer)
|
|
|
+ this.timer = null
|
|
|
+ }
|
|
|
+ },
|
|
|
+ selectOption: function(idx) {
|
|
|
+ if (this.answered) return
|
|
|
+ this.answered = true
|
|
|
+ this.selectedIndex = idx
|
|
|
+ this.stopTimer()
|
|
|
+ var q = this.currentQuestion
|
|
|
+ if (q && q.options[idx] === q.answer) {
|
|
|
+ this.correctCount++
|
|
|
+ }
|
|
|
+ var self = this
|
|
|
+ setTimeout(function() {
|
|
|
+ self.advance()
|
|
|
+ }, 400)
|
|
|
+ },
|
|
|
+ onTimeout: function() {
|
|
|
+ if (this.answered) return
|
|
|
+ this.answered = true
|
|
|
+ this.stopTimer()
|
|
|
+ this.selectedIndex = -1
|
|
|
+ var self = this
|
|
|
+ setTimeout(function() {
|
|
|
+ self.advance()
|
|
|
+ }, 400)
|
|
|
+ },
|
|
|
+ advance: function() {
|
|
|
+ if (this.currentQ < this.questions.length - 1) {
|
|
|
+ this.currentQ++
|
|
|
+ this.answered = false
|
|
|
+ this.selectedIndex = -1
|
|
|
+ this.startTimer()
|
|
|
+ } else {
|
|
|
+ this.finishTest()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ finishTest: function() {
|
|
|
+ this.stopTimer()
|
|
|
+ this.score = Math.round((this.correctCount / ROUND_COUNT) * 100)
|
|
|
+ this.step = 'result'
|
|
|
+ },
|
|
|
+ restartTest: function() {
|
|
|
+ this.stopTimer()
|
|
|
+ this.startTest()
|
|
|
+ },
|
|
|
+ submitScore: function() {
|
|
|
+ if (!this.memberId) {
|
|
|
+ uni.showToast({ title: '未获取到成员信息', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ submitCognitiveSelfTest(this.memberId, 'logicScore', this.score)
|
|
|
+ .then((res) => {
|
|
|
+ if (res && res.code === 200) {
|
|
|
+ uni.showToast({ title: '提交成功', icon: 'success' })
|
|
|
+ var self = this
|
|
|
+ setTimeout(function() {
|
|
|
+ uni.navigateBack()
|
|
|
+ }, 800)
|
|
|
+ } else {
|
|
|
+ uni.showToast({ title: '提交失败', icon: 'none' })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .catch(function() {
|
|
|
+ uni.showToast({ title: '提交失败', icon: 'none' })
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.container {
|
|
|
+ min-height: 100vh;
|
|
|
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
|
|
+ padding: 60rpx 40rpx;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 引导页 ===== */
|
|
|
+.step-intro {
|
|
|
+ padding-top: 80rpx;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.intro-icon {
|
|
|
+ width: 160rpx;
|
|
|
+ height: 160rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: rgba(255, 215, 0, 0.15);
|
|
|
+ border: 3rpx solid #FFD700;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ margin-bottom: 40rpx;
|
|
|
+}
|
|
|
+.intro-icon-text {
|
|
|
+ font-size: 72rpx;
|
|
|
+}
|
|
|
+.intro-title {
|
|
|
+ font-size: 44rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #FFD700;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+.intro-desc {
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: rgba(255, 255, 255, 0.7);
|
|
|
+ margin-bottom: 50rpx;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+.intro-info {
|
|
|
+ width: 100%;
|
|
|
+ background: rgba(255, 255, 255, 0.06);
|
|
|
+ border-radius: 20rpx;
|
|
|
+ padding: 20rpx 30rpx;
|
|
|
+ margin-bottom: 60rpx;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+.info-row {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 16rpx 0;
|
|
|
+}
|
|
|
+.info-row + .info-row {
|
|
|
+ border-top: 1rpx solid rgba(255, 255, 255, 0.08);
|
|
|
+}
|
|
|
+.info-label {
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: rgba(255, 255, 255, 0.7);
|
|
|
+}
|
|
|
+.info-value {
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #FFD700;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 答题页 ===== */
|
|
|
+.quiz-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+}
|
|
|
+.quiz-progress {
|
|
|
+ font-size: 30rpx;
|
|
|
+ color: #fff;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+.quiz-timer {
|
|
|
+ font-size: 30rpx;
|
|
|
+ color: #FFD700;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+.timer-track {
|
|
|
+ height: 12rpx;
|
|
|
+ background: rgba(255, 255, 255, 0.12);
|
|
|
+ border-radius: 6rpx;
|
|
|
+ overflow: hidden;
|
|
|
+ margin-bottom: 50rpx;
|
|
|
+}
|
|
|
+.timer-fill {
|
|
|
+ height: 100%;
|
|
|
+ background: linear-gradient(90deg, #FFD700, #FFA500);
|
|
|
+ border-radius: 6rpx;
|
|
|
+ transition: width 0.1s linear;
|
|
|
+}
|
|
|
+.question-card {
|
|
|
+ background: rgba(255, 255, 255, 0.06);
|
|
|
+ border-radius: 24rpx;
|
|
|
+ padding: 40rpx 30rpx;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+.q-text {
|
|
|
+ font-size: 44rpx;
|
|
|
+ color: #fff;
|
|
|
+ font-weight: bold;
|
|
|
+ text-align: center;
|
|
|
+ display: block;
|
|
|
+ margin-bottom: 50rpx;
|
|
|
+ letter-spacing: 4rpx;
|
|
|
+}
|
|
|
+.q-options {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+.q-option {
|
|
|
+ padding: 26rpx 0;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ border: 3rpx solid rgba(255, 255, 255, 0.2);
|
|
|
+ background: #24243e;
|
|
|
+ text-align: center;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+ transition: all 0.2s;
|
|
|
+}
|
|
|
+.q-option:last-child {
|
|
|
+ margin-bottom: 0;
|
|
|
+}
|
|
|
+.q-option--selected {
|
|
|
+ border-color: #FFD700;
|
|
|
+ background: rgba(255, 215, 0, 0.12);
|
|
|
+}
|
|
|
+.q-option:active {
|
|
|
+ transform: scale(0.98);
|
|
|
+}
|
|
|
+.q-option-text {
|
|
|
+ font-size: 34rpx;
|
|
|
+ color: #fff;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+.q-option--selected .q-option-text {
|
|
|
+ color: #FFD700;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 结果页 ===== */
|
|
|
+.step-result {
|
|
|
+ padding-top: 100rpx;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.score-circle {
|
|
|
+ width: 240rpx;
|
|
|
+ height: 240rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: linear-gradient(135deg, #FFD700, #FFA500);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ box-shadow: 0 8rpx 40rpx rgba(255, 215, 0, 0.35);
|
|
|
+ margin-bottom: 30rpx;
|
|
|
+}
|
|
|
+.score-value {
|
|
|
+ font-size: 80rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #1a1a2e;
|
|
|
+}
|
|
|
+.score-label {
|
|
|
+ font-size: 30rpx;
|
|
|
+ color: #FFD700;
|
|
|
+ margin-bottom: 50rpx;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+.result-info {
|
|
|
+ width: 100%;
|
|
|
+ background: rgba(255, 255, 255, 0.06);
|
|
|
+ border-radius: 20rpx;
|
|
|
+ padding: 20rpx 30rpx;
|
|
|
+ margin-bottom: 60rpx;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+.result-row {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 16rpx 0;
|
|
|
+}
|
|
|
+.result-row + .result-row {
|
|
|
+ border-top: 1rpx solid rgba(255, 255, 255, 0.08);
|
|
|
+}
|
|
|
+.result-label {
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: rgba(255, 255, 255, 0.7);
|
|
|
+}
|
|
|
+.result-value {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #fff;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+.dim-tag {
|
|
|
+ padding: 6rpx 24rpx;
|
|
|
+ border-radius: 24rpx;
|
|
|
+ border: 3rpx solid #FFD700;
|
|
|
+ background: rgba(255, 215, 0, 0.12);
|
|
|
+}
|
|
|
+.dim-tag-text {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #FFD700;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 按钮 ===== */
|
|
|
+.btn {
|
|
|
+ width: 100%;
|
|
|
+ padding: 26rpx 0;
|
|
|
+ border-radius: 50rpx;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 30rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ box-sizing: border-box;
|
|
|
+ margin-bottom: 24rpx;
|
|
|
+}
|
|
|
+.btn:last-child {
|
|
|
+ margin-bottom: 0;
|
|
|
+}
|
|
|
+.btn--primary {
|
|
|
+ background: linear-gradient(135deg, #FFD700, #FFA500);
|
|
|
+ color: #1a1a2e;
|
|
|
+}
|
|
|
+.btn--primary:active {
|
|
|
+ opacity: 0.9;
|
|
|
+ transform: scale(0.98);
|
|
|
+}
|
|
|
+.btn--outline {
|
|
|
+ border: 3rpx solid #FFD700;
|
|
|
+ color: #FFD700;
|
|
|
+ background: transparent;
|
|
|
+}
|
|
|
+.btn--outline:active {
|
|
|
+ background: rgba(255, 215, 0, 0.12);
|
|
|
+}
|
|
|
+</style>
|