|
|
@@ -1,194 +1,132 @@
|
|
|
<template>
|
|
|
<view class="container">
|
|
|
- <!-- 顶部导航 -->
|
|
|
- <view class="nav-bar">
|
|
|
- <view class="nav-back" @click="confirmQuit">
|
|
|
- <text class="nav-back-icon">←</text>
|
|
|
+ <view class="resume-prompt" v-if="showResume">
|
|
|
+ <text class="resume-text">检测到上次未完成的测评</text>
|
|
|
+ <view class="resume-btns">
|
|
|
+ <view class="btn btn--outline" @tap="resumeSession">继续答题</view>
|
|
|
+ <view class="btn btn--ghost" @tap="clearSession">重新开始</view>
|
|
|
</view>
|
|
|
- <text class="nav-title">数学闯关</text>
|
|
|
- <view class="nav-placeholder"></view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- ================================================================== -->
|
|
|
- <!-- Step 1: 设定挑战 -->
|
|
|
- <!-- ================================================================== -->
|
|
|
- <view class="step-content" v-if="currentStep === 1">
|
|
|
- <view class="level-card" v-if="sessionInfo">
|
|
|
- <text class="level-title">当前等级</text>
|
|
|
- <text class="level-name">{{ sessionInfo.levelName }}</text>
|
|
|
- <text class="level-desc" v-if="sessionInfo.totalPassed > 0">
|
|
|
- 已通关 {{ sessionInfo.totalPassed }} 次
|
|
|
- <text v-if="sessionInfo.bestAccuracy">
|
|
|
- · 最佳正确率 {{ sessionInfo.bestAccuracy }}%
|
|
|
- </text>
|
|
|
- </text>
|
|
|
- </view>
|
|
|
-
|
|
|
- <view class="age-hint" v-if="sessionInfo">
|
|
|
- <text class="hint-text">匹配年龄 {{ sessionInfo.age }} 岁 · 建议目标 {{ sessionInfo.suggestedTargetN }} 题</text>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 目标设定 -->
|
|
|
- <view class="content-card">
|
|
|
- <text class="content-title">设定挑战目标</text>
|
|
|
- <text class="content-desc">选择连续答对多少题算通关</text>
|
|
|
-
|
|
|
- <view class="target-row">
|
|
|
- <view class="target-btn" @click="adjustTarget(-1)">−</view>
|
|
|
- <view class="target-display">
|
|
|
- <text class="target-num">{{ targetN }}</text>
|
|
|
- <text class="target-unit">题</text>
|
|
|
+ <view class="step-wrap" v-if="!showResume">
|
|
|
+ <view class="step-row">
|
|
|
+ <view class="step-item" v-for="i in 4" :key="i" @tap="goStep(i)">
|
|
|
+ <view class="step-dot" :class="{
|
|
|
+ 'step-dot--active': step === i,
|
|
|
+ 'step-dot--done': step > i,
|
|
|
+ 'step-dot--pending': step < i
|
|
|
+ }">
|
|
|
+ <text v-if="step > i" class="step-check">✓</text>
|
|
|
+ <text v-else class="step-num">{{ i }}</text>
|
|
|
</view>
|
|
|
- <view class="target-btn" @click="adjustTarget(1)">+</view>
|
|
|
+ <text class="step-label" :class="{
|
|
|
+ 'step-label--active': step >= i,
|
|
|
+ 'step-label--muted': step < i
|
|
|
+ }">{{ stepLabels[i - 1] }}</text>
|
|
|
</view>
|
|
|
- <text class="range-hint">范围 2 ~ 15 题</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
|
|
|
- <!-- 等级选择 -->
|
|
|
- <view class="level-selector" v-if="sessionInfo">
|
|
|
- <text class="level-select-label">难度</text>
|
|
|
- <view class="level-options">
|
|
|
- <view class="level-option"
|
|
|
- :class="{ active: selectedLevel === 1 }"
|
|
|
- @click="selectedLevel = 1">一位数<br>加减</view>
|
|
|
- <view class="level-option"
|
|
|
- :class="{ active: selectedLevel === 2 }"
|
|
|
- @click="selectedLevel = 2">两位数<br>加减</view>
|
|
|
- <view class="level-option"
|
|
|
- :class="{ active: selectedLevel === 3 }"
|
|
|
- @click="selectedLevel = 3">一位数<br>四则</view>
|
|
|
- <view class="level-option"
|
|
|
- :class="{ active: selectedLevel === 4 }"
|
|
|
- @click="selectedLevel = 4">两位数<br>四则</view>
|
|
|
+ <view class="step-content" v-if="step === 1 && !showResume">
|
|
|
+ <view class="content-header">
|
|
|
+ <text class="content-title">选择测评目标</text>
|
|
|
+ <text class="content-sub">选择你想提升的能力维度</text>
|
|
|
+ </view>
|
|
|
+ <view class="goal-grid">
|
|
|
+ <view class="goal-card" v-for="g in goals" :key="g.id" :class="{ 'goal-card--selected': selectedGoal && selectedGoal.id === g.id }" @tap="selectGoal(g)">
|
|
|
+ <view class="goal-icon-wrap">
|
|
|
+ <text class="goal-icon">{{ g.icon }}</text>
|
|
|
</view>
|
|
|
+ <text class="goal-title">{{ g.title }}</text>
|
|
|
+ <text class="goal-desc">{{ g.desc }}</text>
|
|
|
</view>
|
|
|
-
|
|
|
- <view class="start-btn" @click="startChallenge">
|
|
|
- <text class="start-btn-text">开始挑战</text>
|
|
|
- </view>
|
|
|
+ </view>
|
|
|
+ <view class="action-bar">
|
|
|
+ <view class="btn btn--primary" :class="{ 'btn--disabled': !selectedGoal }" @tap="goStep2">下一步</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- ================================================================== -->
|
|
|
- <!-- Step 2: 出题答题 -->
|
|
|
- <!-- ================================================================== -->
|
|
|
- <view class="step-content" v-if="currentStep === 2">
|
|
|
- <!-- 进度条 -->
|
|
|
- <view class="progress-bar">
|
|
|
- <view class="progress-fill" :style="{ width: progressPercent + '%' }"></view>
|
|
|
+ <view class="step-content" v-if="step === 2 && !showResume">
|
|
|
+ <view class="content-header">
|
|
|
+ <text class="content-title">选择题目类型</text>
|
|
|
+ <text class="content-sub">共5道题,选择适合的答题方式</text>
|
|
|
</view>
|
|
|
- <view class="progress-info">
|
|
|
- <text class="progress-text">连续答对 {{ correctCount }} / {{ targetN }}</text>
|
|
|
- <text class="timer-text">{{ formattedTime }}</text>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 已出题目列表 -->
|
|
|
- <view class="expr-list" v-if="askedExpressions.length > 0">
|
|
|
- <view class="expr-item" v-for="(item, i) in askedExpressions" :key="i">
|
|
|
- <text class="expr-index">{{ i + 1 }}.</text>
|
|
|
- <text class="expr-text">{{ item.expr }}</text>
|
|
|
- <text class="expr-result" :class="item.correct ? 'correct' : 'wrong'">
|
|
|
- {{ item.correct ? '✓' : '✗' }}
|
|
|
- </text>
|
|
|
+ <view class="type-list">
|
|
|
+ <view class="type-card" v-for="t in questionTypes" :key="t.id" :class="{ 'type-card--selected': questionType === t.id }" @tap="selectType(t.id)">
|
|
|
+ <view class="type-icon-wrap">
|
|
|
+ <text class="type-icon">{{ t.icon }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="type-info">
|
|
|
+ <text class="type-name">{{ t.name }}</text>
|
|
|
+ <text class="type-desc">{{ t.desc }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="type-radio" :class="{ 'type-radio--checked': questionType === t.id }">
|
|
|
+ <view v-if="questionType === t.id" class="type-radio-dot"></view>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
</view>
|
|
|
+ <view class="action-bar action-bar--double">
|
|
|
+ <view class="btn btn--outline" @tap="goStep1">上一步</view>
|
|
|
+ <view class="btn btn--primary" :class="{ 'btn--disabled': !questionType }" @tap="startQuiz">开始答题</view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
|
|
|
- <!-- 出题输入区 -->
|
|
|
- <view class="quiz-card">
|
|
|
- <view class="input-group">
|
|
|
- <text class="input-label">写出算式</text>
|
|
|
- <input class="expr-input" v-model="currentExpression"
|
|
|
- placeholder="例如:12+38" maxlength="100"
|
|
|
- :disabled="waitingVerify" />
|
|
|
- </view>
|
|
|
- <view class="input-group">
|
|
|
- <text class="input-label">你的答案</text>
|
|
|
- <input class="answer-input" v-model="currentAnswer" type="text"
|
|
|
- placeholder="输入答案" maxlength="50"
|
|
|
- :disabled="waitingVerify" />
|
|
|
+ <view class="step-content" v-if="step === 3 && !showResume">
|
|
|
+ <view class="progress-header">
|
|
|
+ <text class="progress-label">第 {{ currentQ + 1 }}/{{ questions.length }} 题</text>
|
|
|
+ <view class="progress-bar">
|
|
|
+ <view class="progress-fill" :style="'width:' + progressPct + '%'"></view>
|
|
|
</view>
|
|
|
-
|
|
|
- <view class="submit-row">
|
|
|
- <view class="submit-btn" @click="submitAnswer"
|
|
|
- v-if="!waitingVerify && currentExpression.trim() && currentAnswer.trim()">
|
|
|
- <text class="submit-btn-text">提交验证</text>
|
|
|
- </view>
|
|
|
- <view class="submit-btn disabled" v-else-if="waitingVerify">
|
|
|
- <text class="submit-btn-text">AI 验证中...</text>
|
|
|
+ </view>
|
|
|
+ <view class="question-card" v-if="currentQuestion">
|
|
|
+ <view class="q-tag">{{ tagLabel }}</view>
|
|
|
+ <text class="q-text">{{ currentQuestion.question }}</text>
|
|
|
+ <view class="q-options" v-if="currentQuestion.isTF">
|
|
|
+ <view class="q-option q-option--tf" :class="{ 'q-option--selected': currentAnswer === '正确' }" @tap="currentAnswer = '正确'">
|
|
|
+ <text class="q-option-tf-text">正确</text>
|
|
|
</view>
|
|
|
- <view class="submit-btn disabled" v-else>
|
|
|
- <text class="submit-btn-text">请填写算式和答案</text>
|
|
|
+ <view class="q-option q-option--tf" :class="{ 'q-option--selected': currentAnswer === '错误' }" @tap="currentAnswer = '错误'">
|
|
|
+ <text class="q-option-tf-text">错误</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
-
|
|
|
- <!-- AI 反馈 -->
|
|
|
- <view class="feedback" v-if="feedbackText">
|
|
|
- <view class="feedback-card" :class="lastCorrect ? 'fc-correct' : 'fc-wrong'">
|
|
|
- <text class="fb-icon">{{ lastCorrect ? '🎉' : '💪' }}</text>
|
|
|
- <view class="fb-content">
|
|
|
- <text class="fb-title">{{ lastCorrect ? '正确!' : '再想想' }}</text>
|
|
|
- <text class="fb-desc">{{ feedbackText }}</text>
|
|
|
+ <view class="q-options" v-else>
|
|
|
+ <view class="q-option" v-for="(opt, oi) in currentQuestion.options" :key="oi" :class="{ 'q-option--selected': currentAnswer === opt }" @tap="currentAnswer = opt">
|
|
|
+ <view class="q-radio" :class="{ 'q-radio--checked': currentAnswer === opt }">
|
|
|
+ <view v-if="currentAnswer === opt" class="q-radio-dot"></view>
|
|
|
</view>
|
|
|
- </view>
|
|
|
- <view class="continue-btn" @click="continueQuiz" v-if="!waitingVerify">
|
|
|
- <text class="continue-btn-text">{{ correctCount >= targetN ? '查看结果' : '下一题' }}</text>
|
|
|
+ <text class="q-option-text">{{ opt }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
-
|
|
|
- <!-- 放弃按钮 -->
|
|
|
- <view class="quit-row" v-if="!waitingVerify">
|
|
|
- <view class="quit-btn" @click="confirmQuit">
|
|
|
- <text class="quit-btn-text">放弃挑战</text>
|
|
|
- </view>
|
|
|
+ <view class="action-bar action-bar--double">
|
|
|
+ <view class="btn btn--outline" :class="{ 'btn--disabled': currentQ === 0 }" @tap="prevQuestion">上一题</view>
|
|
|
+ <view class="btn btn--primary" :class="{ 'btn--disabled': !currentAnswer }" @tap="nextQuestion">{{ currentQ < questions.length - 1 ? '下一题' : '查看结果' }}</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- ================================================================== -->
|
|
|
- <!-- Step 3: 过关统计 -->
|
|
|
- <!-- ================================================================== -->
|
|
|
- <view class="step-content" v-if="currentStep === 3">
|
|
|
- <view class="result-card">
|
|
|
- <text class="result-icon">🏆</text>
|
|
|
- <text class="result-title" v-if="passResult.passed">恭喜通关!</text>
|
|
|
- <text class="result-title" v-else>挑战结束</text>
|
|
|
-
|
|
|
- <view class="stats-row">
|
|
|
- <view class="stat-item">
|
|
|
- <text class="stat-value">{{ passResult.accuracy }}%</text>
|
|
|
- <text class="stat-label">正确率</text>
|
|
|
- </view>
|
|
|
- <view class="stat-item">
|
|
|
- <text class="stat-value">{{ passResult.totalAttempts }}</text>
|
|
|
- <text class="stat-label">总答题</text>
|
|
|
- </view>
|
|
|
- <view class="stat-item">
|
|
|
- <text class="stat-value">{{ formatDuration(passResult.totalTimeMs) }}</text>
|
|
|
- <text class="stat-label">用时</text>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 升级/降级提示 -->
|
|
|
- <view class="level-change" v-if="passResult.levelUp">
|
|
|
- <text class="change-icon">⬆</text>
|
|
|
- <text class="change-text">{{ passResult.reason }}</text>
|
|
|
- </view>
|
|
|
- <view class="level-change down" v-if="passResult.levelDown">
|
|
|
- <text class="change-icon">⬇</text>
|
|
|
- <text class="change-text">{{ passResult.reason }}</text>
|
|
|
- </view>
|
|
|
- <view class="level-change stay" v-if="!passResult.levelUp && !passResult.levelDown">
|
|
|
- <text class="change-text">{{ passResult.reason }}</text>
|
|
|
+ <view class="step-content" v-if="step === 4 && !showResume">
|
|
|
+ <view class="result-header">
|
|
|
+ <view class="score-circle">
|
|
|
+ <text class="score-value">{{ score }}/5</text>
|
|
|
</view>
|
|
|
-
|
|
|
- <view class="result-actions">
|
|
|
- <view class="action-btn primary" @click="playAgain">
|
|
|
- <text class="action-btn-text">再来一轮</text>
|
|
|
+ <text class="score-label">{{ resultMessage }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="section-title">维度分析</view>
|
|
|
+ <view class="result-dims">
|
|
|
+ <view class="dim-bar-item" v-for="(dim, di) in dimBreakdown" :key="di">
|
|
|
+ <view class="dim-bar-header">
|
|
|
+ <text class="dim-bar-name">{{ dim.name }}</text>
|
|
|
+ <text class="dim-bar-count">{{ dim.correct }}/{{ dim.total }}</text>
|
|
|
</view>
|
|
|
- <view class="action-btn secondary" @click="goBack">
|
|
|
- <text class="action-btn-text">返回</text>
|
|
|
+ <view class="dim-bar-track">
|
|
|
+ <view class="dim-bar-fill" :style="'width:' + dim.pct + '%'"></view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
+ <view class="action-bar">
|
|
|
+ <view class="btn btn--primary" @tap="resetQuiz">再测一次</view>
|
|
|
+ <view class="btn btn--outline" @tap="goHome">返回首页</view>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
|
|
|
<view class="bottom-spacer"></view>
|
|
|
@@ -196,219 +134,330 @@
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
-import { getMathQuizStart, verifyMathAnswer, completeMathQuiz } from '../../utils/api.js'
|
|
|
+var QUESTION_BANK = [
|
|
|
+ { dimension: 'focus', question: '以下哪个因素最容易影响注意力集中?', options: ['噪音', '温度', '光照', '以上都是'], answer: '以上都是' },
|
|
|
+ { dimension: 'focus', question: '持续专注工作建议多久休息一次?', options: ['15分钟', '30分钟', '45分钟', '60分钟'], answer: '45分钟' },
|
|
|
+ { dimension: 'focus', question: '舒尔特方格的训练目标是提升什么?', options: ['记忆力', '专注力', '逻辑思维', '创造力'], answer: '专注力' },
|
|
|
+ { dimension: 'memory', question: '短期记忆通常能记住几个信息组块?', options: ['3-4个', '5-9个', '10-15个', '20个以上'], answer: '5-9个' },
|
|
|
+ { dimension: 'memory', question: '以下哪个方法有助于增强记忆?', options: ['死记硬背', '联想记忆', '反复抄写', '一次性学习'], answer: '联想记忆' },
|
|
|
+ { dimension: 'memory', question: '睡眠对记忆的影响是什么?', options: ['削弱记忆', '巩固记忆', '无影响', '干扰记忆'], answer: '巩固记忆' },
|
|
|
+ { dimension: 'logic', question: '如果A>B且B>C,那么?', options: ['A=C', 'A>C', 'A<C', '不确定'], answer: 'A>C' },
|
|
|
+ { dimension: 'logic', question: '1, 1, 2, 3, 5, 8, ? 下一个数是多少?', options: ['10', '11', '12', '13'], answer: '13' },
|
|
|
+ { dimension: 'logic', question: '以下哪个不是有效推理方式?', options: ['归纳推理', '演绎推理', '循环论证', '类比推理'], answer: '循环论证' },
|
|
|
+ { dimension: 'perception', question: '人眼能分辨约多少种颜色?', options: ['几百种', '几千种', '几万种', '数百万种'], answer: '数百万种' },
|
|
|
+ { dimension: 'perception', question: '以下哪种错觉与视觉感知有关?', options: ['缪勒-莱尔错觉', '德布罗意错觉', '薛定谔错觉', '迈克尔逊错觉'], answer: '缪勒-莱尔错觉' },
|
|
|
+ { dimension: 'perception', question: '感觉适应是指什么?', options: ['敏感度降低', '敏感度升高', '感知消失', '感知增强'], answer: '敏感度降低' },
|
|
|
+ { dimension: 'spatial', question: '正方体有多少个面?', options: ['4', '6', '8', '12'], answer: '6' },
|
|
|
+ { dimension: 'spatial', question: '以下哪个是立体几何的基本要素?', options: ['点线面', '正反合', '红黄蓝', '上下左右'], answer: '点线面' },
|
|
|
+ { dimension: 'spatial', question: '一个立方体展开图有几个正方形?', options: ['4', '6', '8', '12'], answer: '6' },
|
|
|
+ { dimension: 'speed', question: '反应时实验测量的是什么?', options: ['思考速度', '反应速度', '运动速度', '阅读速度'], answer: '反应速度' },
|
|
|
+ { dimension: 'speed', question: '以下哪个因素会影响加工速度?', options: ['年龄', '情绪', '疲劳', '以上都是'], answer: '以上都是' },
|
|
|
+ { dimension: 'speed', question: '信息加工速度最快的年龄段是?', options: ['儿童期', '青春期', '成年早期', '中老年期'], answer: '成年早期' }
|
|
|
+]
|
|
|
|
|
|
export default {
|
|
|
- data: function() {
|
|
|
+ data() {
|
|
|
return {
|
|
|
- currentStep: 1,
|
|
|
- sessionInfo: null,
|
|
|
- childId: null,
|
|
|
- age: null,
|
|
|
- targetN: 5,
|
|
|
- selectedLevel: 1,
|
|
|
-
|
|
|
- correctCount: 0,
|
|
|
- totalAttempts: 0,
|
|
|
- startTime: null,
|
|
|
- timerHandle: null,
|
|
|
- elapsedSeconds: 0,
|
|
|
- askedExpressions: [],
|
|
|
- askedSet: {},
|
|
|
-
|
|
|
- currentExpression: '',
|
|
|
- currentAnswer: '',
|
|
|
- waitingVerify: false,
|
|
|
- lastCorrect: false,
|
|
|
- feedbackText: '',
|
|
|
-
|
|
|
- passResult: {}
|
|
|
+ step: 1,
|
|
|
+ selectedGoal: null,
|
|
|
+ questionType: null,
|
|
|
+ questions: [],
|
|
|
+ currentQ: 0,
|
|
|
+ answers: [],
|
|
|
+ showResume: false,
|
|
|
+ savedSession: null,
|
|
|
+ goals: [
|
|
|
+ { id: 'focus', title: '专注力', desc: '提升注意力集中能力', icon: '集' },
|
|
|
+ { id: 'memory', title: '记忆力', desc: '增强信息记忆与回忆能力', icon: '忆' },
|
|
|
+ { id: 'logic', title: '逻辑思维', desc: '锻炼逻辑推理与判断能力', icon: '逻' },
|
|
|
+ { id: 'perception', title: '感知觉', desc: '提高感官信息处理能力', icon: '感' },
|
|
|
+ { id: 'spatial', title: '空间思维', desc: '培养空间想象与构建能力', icon: '间' },
|
|
|
+ { id: 'speed', title: '加工速度', desc: '加快信息处理与反应速度', icon: '速' }
|
|
|
+ ],
|
|
|
+ questionTypes: [
|
|
|
+ { id: 'choice', name: '选择题', desc: '四选一标准选择题', icon: '选' },
|
|
|
+ { id: 'truefalse', name: '判断题', desc: '判断陈述正误', icon: '判' },
|
|
|
+ { id: 'mix', name: '混合题型', desc: '选择题与判断题混合', icon: '混' }
|
|
|
+ ],
|
|
|
+ stepLabels: ['目标', '题型', '答题', '结果']
|
|
|
}
|
|
|
},
|
|
|
computed: {
|
|
|
- progressPercent: function() {
|
|
|
- if (this.targetN <= 0) return 0
|
|
|
- return Math.min(100, (this.correctCount / this.targetN) * 100)
|
|
|
+ currentQuestion: function() {
|
|
|
+ if (this.questions.length === 0) {
|
|
|
+ return null
|
|
|
+ }
|
|
|
+ return this.questions[this.currentQ]
|
|
|
},
|
|
|
- formattedTime: function() {
|
|
|
- var m = Math.floor(this.elapsedSeconds / 60)
|
|
|
- var s = this.elapsedSeconds % 60
|
|
|
- return (m > 0 ? m + '分' : '') + s + '秒'
|
|
|
+ currentAnswer: {
|
|
|
+ get: function() {
|
|
|
+ return this.answers[this.currentQ] || null
|
|
|
+ },
|
|
|
+ set: function(val) {
|
|
|
+ this.$set(this.answers, this.currentQ, val)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ progressPct: function() {
|
|
|
+ if (this.questions.length === 0) {
|
|
|
+ return 0
|
|
|
+ }
|
|
|
+ return ((this.currentQ + 1) / this.questions.length) * 100
|
|
|
+ },
|
|
|
+ score: function() {
|
|
|
+ var correct = 0
|
|
|
+ for (var i = 0; i < this.questions.length; i++) {
|
|
|
+ var q = this.questions[i]
|
|
|
+ var a = this.answers[i]
|
|
|
+ if (q && a && q.answer === a) {
|
|
|
+ correct++
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return correct
|
|
|
+ },
|
|
|
+ resultMessage: function() {
|
|
|
+ var s = this.score
|
|
|
+ if (s >= 5) return '太棒了!满分通过!'
|
|
|
+ if (s >= 4) return '非常优秀!继续保持!'
|
|
|
+ if (s >= 3) return '表现不错,还有提升空间!'
|
|
|
+ return '继续加油,多练习会更好!'
|
|
|
+ },
|
|
|
+ tagLabel: function() {
|
|
|
+ if (!this.currentQuestion) return ''
|
|
|
+ var dimMap = {
|
|
|
+ focus: '专注力',
|
|
|
+ memory: '记忆力',
|
|
|
+ logic: '逻辑思维',
|
|
|
+ perception: '感知觉',
|
|
|
+ spatial: '空间思维',
|
|
|
+ speed: '加工速度'
|
|
|
+ }
|
|
|
+ return dimMap[this.currentQuestion.dimension] || ''
|
|
|
+ },
|
|
|
+ dimBreakdown: function() {
|
|
|
+ var dimNames = {
|
|
|
+ focus: '专注力',
|
|
|
+ memory: '记忆力',
|
|
|
+ logic: '逻辑思维',
|
|
|
+ perception: '感知觉',
|
|
|
+ spatial: '空间思维',
|
|
|
+ speed: '加工速度'
|
|
|
+ }
|
|
|
+ var dims = {}
|
|
|
+ for (var i = 0; i < this.questions.length; i++) {
|
|
|
+ var q = this.questions[i]
|
|
|
+ if (!q) continue
|
|
|
+ if (!dims[q.dimension]) {
|
|
|
+ dims[q.dimension] = { total: 0, correct: 0 }
|
|
|
+ }
|
|
|
+ dims[q.dimension].total++
|
|
|
+ if (this.answers[i] && q.answer === this.answers[i]) {
|
|
|
+ dims[q.dimension].correct++
|
|
|
+ }
|
|
|
+ }
|
|
|
+ var result = []
|
|
|
+ var keys = Object.keys(dims)
|
|
|
+ for (var k = 0; k < keys.length; k++) {
|
|
|
+ var key = keys[k]
|
|
|
+ var item = dims[key]
|
|
|
+ result.push({
|
|
|
+ name: dimNames[key] || key,
|
|
|
+ total: item.total,
|
|
|
+ correct: item.correct,
|
|
|
+ pct: item.total > 0 ? Math.round((item.correct / item.total) * 100) : 0
|
|
|
+ })
|
|
|
+ }
|
|
|
+ return result
|
|
|
}
|
|
|
},
|
|
|
- onLoad: function(options) {
|
|
|
- if (options && options.childId) {
|
|
|
- this.childId = options.childId
|
|
|
- }
|
|
|
- this.loadSession()
|
|
|
+ onLoad: function() {
|
|
|
+ this.checkSession()
|
|
|
},
|
|
|
onUnload: function() {
|
|
|
- this.stopTimer()
|
|
|
+ this.saveSession()
|
|
|
},
|
|
|
methods: {
|
|
|
- loadSession: function() {
|
|
|
- var self = this
|
|
|
- var childId = this.childId || uni.getStorageSync('currentChildId')
|
|
|
- this.childId = childId
|
|
|
- getMathQuizStart(childId, this.age).then(function(res) {
|
|
|
- if (res.code === 200 && res.data) {
|
|
|
- self.sessionInfo = res.data
|
|
|
- self.selectedLevel = res.data.level || 1
|
|
|
- self.targetN = res.data.suggestedTargetN || 5
|
|
|
- self.age = res.data.age
|
|
|
+ checkSession: function() {
|
|
|
+ var saved = uni.getStorageSync('selfQuizSession')
|
|
|
+ if (saved) {
|
|
|
+ try {
|
|
|
+ var session = JSON.parse(saved)
|
|
|
+ if (session && session.step && session.step >= 2 && session.step <= 3) {
|
|
|
+ this.showResume = true
|
|
|
+ this.savedSession = session
|
|
|
+ } else {
|
|
|
+ uni.removeStorageSync('selfQuizSession')
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ uni.removeStorageSync('selfQuizSession')
|
|
|
}
|
|
|
- }).catch(function() {})
|
|
|
+ }
|
|
|
},
|
|
|
- adjustTarget: function(delta) {
|
|
|
- var n = this.targetN + delta
|
|
|
- if (n >= 2 && n <= 15) {
|
|
|
- this.targetN = n
|
|
|
+ saveSession: function() {
|
|
|
+ var session = {
|
|
|
+ step: this.step,
|
|
|
+ selectedGoal: this.selectedGoal,
|
|
|
+ questionType: this.questionType,
|
|
|
+ questions: this.questions,
|
|
|
+ currentQ: this.currentQ,
|
|
|
+ answers: this.answers
|
|
|
}
|
|
|
+ uni.setStorageSync('selfQuizSession', JSON.stringify(session))
|
|
|
},
|
|
|
- startChallenge: function() {
|
|
|
- this.correctCount = 0
|
|
|
- this.totalAttempts = 0
|
|
|
- this.askedExpressions = []
|
|
|
- this.askedSet = {}
|
|
|
- this.currentExpression = ''
|
|
|
- this.currentAnswer = ''
|
|
|
- this.feedbackText = ''
|
|
|
- this.lastCorrect = false
|
|
|
- this.waitingVerify = false
|
|
|
- this.startTime = Date.now()
|
|
|
- this.elapsedSeconds = 0
|
|
|
- this.currentStep = 2
|
|
|
- this.startTimer()
|
|
|
+ resumeSession: function() {
|
|
|
+ if (!this.savedSession) return
|
|
|
+ var s = this.savedSession
|
|
|
+ this.step = s.step
|
|
|
+ this.selectedGoal = s.selectedGoal
|
|
|
+ this.questionType = s.questionType
|
|
|
+ this.questions = s.questions || []
|
|
|
+ this.currentQ = s.currentQ || 0
|
|
|
+ this.answers = s.answers || []
|
|
|
+ this.showResume = false
|
|
|
+ this.savedSession = null
|
|
|
},
|
|
|
- startTimer: function() {
|
|
|
- var self = this
|
|
|
- this.stopTimer()
|
|
|
- this.timerHandle = setInterval(function() {
|
|
|
- self.elapsedSeconds = Math.floor((Date.now() - self.startTime) / 1000)
|
|
|
- }, 1000)
|
|
|
+ clearSession: function() {
|
|
|
+ uni.removeStorageSync('selfQuizSession')
|
|
|
+ this.showResume = false
|
|
|
+ this.savedSession = null
|
|
|
},
|
|
|
- stopTimer: function() {
|
|
|
- if (this.timerHandle) {
|
|
|
- clearInterval(this.timerHandle)
|
|
|
- this.timerHandle = null
|
|
|
+ selectGoal: function(g) {
|
|
|
+ this.selectedGoal = g
|
|
|
+ },
|
|
|
+ goStep2: function() {
|
|
|
+ if (this.selectedGoal) {
|
|
|
+ this.step = 2
|
|
|
+ this.saveSession()
|
|
|
}
|
|
|
},
|
|
|
- submitAnswer: function() {
|
|
|
- var expr = this.currentExpression.trim()
|
|
|
- var ans = this.currentAnswer.trim()
|
|
|
- if (!expr || !ans) return
|
|
|
-
|
|
|
- var exprKey = expr.replace(/\s+/g, '').toLowerCase()
|
|
|
- if (this.askedSet[exprKey]) {
|
|
|
- uni.showToast({ title: '这题出过了,换一题吧', icon: 'none' })
|
|
|
- return
|
|
|
+ goStep1: function() {
|
|
|
+ this.step = 1
|
|
|
+ this.saveSession()
|
|
|
+ },
|
|
|
+ selectType: function(id) {
|
|
|
+ this.questionType = id
|
|
|
+ },
|
|
|
+ startQuiz: function() {
|
|
|
+ if (!this.questionType) return
|
|
|
+ this.generateQuestions()
|
|
|
+ this.currentQ = 0
|
|
|
+ this.answers = []
|
|
|
+ this.step = 3
|
|
|
+ this.saveSession()
|
|
|
+ },
|
|
|
+ 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
|
|
|
}
|
|
|
-
|
|
|
- this.waitingVerify = true
|
|
|
- this.feedbackText = ''
|
|
|
- var self = this
|
|
|
- var childId = this.childId
|
|
|
-
|
|
|
- verifyMathAnswer(childId, expr, ans).then(function(res) {
|
|
|
- self.waitingVerify = false
|
|
|
- if (res.code === 200 && res.data) {
|
|
|
- self.totalAttempts++
|
|
|
- var correct = res.data.correct === true
|
|
|
- self.lastCorrect = correct
|
|
|
- self.feedbackText = res.data.feedback || (correct ? '答案正确!' : '再想想哦')
|
|
|
-
|
|
|
- self.askedSet[exprKey] = true
|
|
|
- self.askedExpressions.push({
|
|
|
- expr: expr + ' = ' + ans,
|
|
|
- correct: correct
|
|
|
- })
|
|
|
-
|
|
|
- if (correct) {
|
|
|
- self.correctCount++
|
|
|
- } else {
|
|
|
- self.correctCount = 0
|
|
|
- }
|
|
|
- }
|
|
|
- }).catch(function() {
|
|
|
- self.waitingVerify = false
|
|
|
- self.feedbackText = '验证失败,请重试'
|
|
|
- })
|
|
|
+ return a
|
|
|
},
|
|
|
- continueQuiz: function() {
|
|
|
- if (this.correctCount >= this.targetN) {
|
|
|
- this.finishChallenge()
|
|
|
- return
|
|
|
+ convertToTF: function(q) {
|
|
|
+ var opts = q.options
|
|
|
+ var idx = opts.indexOf(q.answer)
|
|
|
+ var wrongOpts = []
|
|
|
+ for (var w = 0; w < opts.length; w++) {
|
|
|
+ if (opts[w] !== q.answer) {
|
|
|
+ wrongOpts.push(opts[w])
|
|
|
+ }
|
|
|
+ }
|
|
|
+ var isTrue = Math.random() > 0.4
|
|
|
+ var claimIdx
|
|
|
+ if (isTrue) {
|
|
|
+ claimIdx = idx
|
|
|
+ } else {
|
|
|
+ var randomWrong = wrongOpts[Math.floor(Math.random() * wrongOpts.length)]
|
|
|
+ claimIdx = opts.indexOf(randomWrong)
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ dimension: q.dimension,
|
|
|
+ question: q.question + ' 正确答案是"' + opts[claimIdx] + '"',
|
|
|
+ options: ['正确', '错误'],
|
|
|
+ answer: isTrue ? '正确' : '错误',
|
|
|
+ isTF: true
|
|
|
}
|
|
|
- this.currentExpression = ''
|
|
|
- this.currentAnswer = ''
|
|
|
- this.feedbackText = ''
|
|
|
- this.lastCorrect = false
|
|
|
},
|
|
|
- finishChallenge: function() {
|
|
|
- this.stopTimer()
|
|
|
- var totalTimeMs = Date.now() - this.startTime
|
|
|
- var self = this
|
|
|
- var exprs = this.askedExpressions.map(function(e) { return e.expr })
|
|
|
-
|
|
|
- completeMathQuiz({
|
|
|
- childId: this.childId,
|
|
|
- level: this.selectedLevel,
|
|
|
- targetN: this.targetN,
|
|
|
- correctCount: this.correctCount,
|
|
|
- totalAttempts: this.totalAttempts,
|
|
|
- totalTimeMs: totalTimeMs,
|
|
|
- expressions: exprs
|
|
|
- }).then(function(res) {
|
|
|
- if (res.code === 200 && res.data) {
|
|
|
- self.passResult = res.data
|
|
|
- self.currentStep = 3
|
|
|
+ generateQuestions: function() {
|
|
|
+ var goalId = this.selectedGoal && this.selectedGoal.id
|
|
|
+ if (!goalId) return
|
|
|
+ var pool = []
|
|
|
+ for (var p = 0; p < QUESTION_BANK.length; p++) {
|
|
|
+ if (QUESTION_BANK[p].dimension === goalId) {
|
|
|
+ pool.push(QUESTION_BANK[p])
|
|
|
}
|
|
|
- }).catch(function() {
|
|
|
- self.passResult = {
|
|
|
- passed: self.correctCount >= self.targetN,
|
|
|
- accuracy: self.totalAttempts > 0 ? Math.round(self.correctCount / self.totalAttempts * 100) : 0,
|
|
|
- totalAttempts: self.totalAttempts,
|
|
|
- totalTimeMs: totalTimeMs,
|
|
|
- levelUp: false,
|
|
|
- levelDown: false,
|
|
|
- newLevel: self.selectedLevel,
|
|
|
- reason: '结果保存失败'
|
|
|
+ }
|
|
|
+ var shuffled = this.shuffle(pool)
|
|
|
+ var type = this.questionType
|
|
|
+ if (type === 'truefalse') {
|
|
|
+ var tfQuestions = []
|
|
|
+ var count = Math.min(5, shuffled.length)
|
|
|
+ for (var ti = 0; ti < count; ti++) {
|
|
|
+ tfQuestions.push(this.convertToTF(shuffled[ti]))
|
|
|
}
|
|
|
- self.currentStep = 3
|
|
|
- })
|
|
|
- },
|
|
|
- playAgain: function() {
|
|
|
- this.loadSession()
|
|
|
- this.startChallenge()
|
|
|
- },
|
|
|
- confirmQuit: function() {
|
|
|
- if (this.currentStep === 2) {
|
|
|
- var self = this
|
|
|
- uni.showModal({
|
|
|
- title: '提示',
|
|
|
- content: '确定要放弃当前挑战吗?',
|
|
|
- success: function(r) {
|
|
|
- if (r.confirm) {
|
|
|
- self.stopTimer()
|
|
|
- self.currentStep = 1
|
|
|
- self.loadSession()
|
|
|
+ this.questions = tfQuestions
|
|
|
+ } else if (type === 'mix') {
|
|
|
+ var allDims = ['focus', 'memory', 'logic', 'perception', 'spatial', 'speed']
|
|
|
+ var otherDims = []
|
|
|
+ for (var od = 0; od < allDims.length; od++) {
|
|
|
+ if (allDims[od] !== goalId) {
|
|
|
+ otherDims.push(allDims[od])
|
|
|
+ }
|
|
|
+ }
|
|
|
+ otherDims = this.shuffle(otherDims)
|
|
|
+ var selectedDims = [goalId].concat(otherDims.slice(0, 4))
|
|
|
+ var mixedQuestions = []
|
|
|
+ for (var di = 0; di < selectedDims.length; di++) {
|
|
|
+ var dimPool = []
|
|
|
+ for (var dq = 0; dq < QUESTION_BANK.length; dq++) {
|
|
|
+ if (QUESTION_BANK[dq].dimension === selectedDims[di]) {
|
|
|
+ dimPool.push(QUESTION_BANK[dq])
|
|
|
}
|
|
|
}
|
|
|
- })
|
|
|
+ if (dimPool.length > 0) {
|
|
|
+ var pick = this.shuffle(dimPool)
|
|
|
+ mixedQuestions.push(pick[0])
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.questions = this.shuffle(mixedQuestions).slice(0, 5)
|
|
|
} else {
|
|
|
- this.goBack()
|
|
|
+ this.questions = shuffled.slice(0, 5)
|
|
|
}
|
|
|
},
|
|
|
- goBack: function() {
|
|
|
- uni.navigateBack()
|
|
|
+ nextQuestion: function() {
|
|
|
+ if (!this.currentAnswer) return
|
|
|
+ if (this.currentQ < this.questions.length - 1) {
|
|
|
+ this.currentQ++
|
|
|
+ this.saveSession()
|
|
|
+ } else {
|
|
|
+ this.step = 4
|
|
|
+ this.saveSession()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ prevQuestion: function() {
|
|
|
+ if (this.currentQ > 0) {
|
|
|
+ this.currentQ--
|
|
|
+ }
|
|
|
+ },
|
|
|
+ resetQuiz: function() {
|
|
|
+ uni.removeStorageSync('selfQuizSession')
|
|
|
+ this.step = 1
|
|
|
+ this.selectedGoal = null
|
|
|
+ this.questionType = null
|
|
|
+ this.questions = []
|
|
|
+ this.currentQ = 0
|
|
|
+ this.answers = []
|
|
|
+ },
|
|
|
+ goHome: function() {
|
|
|
+ uni.switchTab({ url: '/pages/wisdom/index' })
|
|
|
},
|
|
|
- formatDuration: function(ms) {
|
|
|
- if (!ms) return '0秒'
|
|
|
- var s = Math.floor(ms / 1000)
|
|
|
- if (s < 60) return s + '秒'
|
|
|
- var m = Math.floor(s / 60)
|
|
|
- var r = s % 60
|
|
|
- return m + '分' + r + '秒'
|
|
|
+ goStep: function(n) {
|
|
|
+ if (n === this.step) return
|
|
|
+ if (n < this.step) {
|
|
|
+ this.step = n
|
|
|
+ this.saveSession()
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -417,288 +466,456 @@ export default {
|
|
|
<style scoped>
|
|
|
.container {
|
|
|
min-height: 100vh;
|
|
|
- background: linear-gradient(180deg, #FFF8E1 0%, #f5f7fa 100%);
|
|
|
- padding-bottom: 100rpx;
|
|
|
+ background: #f5f7fa;
|
|
|
+ padding: 30rpx;
|
|
|
+ padding-bottom: 120rpx;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.resume-prompt {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 20rpx;
|
|
|
+ padding: 50rpx 30rpx;
|
|
|
+ text-align: center;
|
|
|
+ margin-top: 160rpx;
|
|
|
+ box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
|
|
|
}
|
|
|
-.nav-bar {
|
|
|
+.resume-text {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #333;
|
|
|
+ display: block;
|
|
|
+ margin-bottom: 30rpx;
|
|
|
+}
|
|
|
+.resume-btns {
|
|
|
display: flex;
|
|
|
- flex-direction: row;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 20rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.step-wrap {
|
|
|
+ margin-bottom: 10rpx;
|
|
|
+}
|
|
|
+.step-row {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: flex-start;
|
|
|
+ padding: 20rpx 0;
|
|
|
+}
|
|
|
+.step-item {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
align-items: center;
|
|
|
- padding: 80rpx 30rpx 20rpx;
|
|
|
- background: linear-gradient(135deg, #FFD700, #FFA500);
|
|
|
+ margin: 0 24rpx;
|
|
|
}
|
|
|
-.nav-back {
|
|
|
- width: 60rpx; height: 60rpx;
|
|
|
+.step-dot {
|
|
|
+ width: 48rpx;
|
|
|
+ height: 48rpx;
|
|
|
+ border-radius: 50%;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
+ border: 3rpx solid #ddd;
|
|
|
+ background: #fff;
|
|
|
+ transition: all 0.3s;
|
|
|
}
|
|
|
-.nav-back-icon { font-size: 36rpx; color: #fff; }
|
|
|
-.nav-title {
|
|
|
- flex: 1; text-align: center;
|
|
|
- font-size: 34rpx; font-weight: bold; color: #fff;
|
|
|
- margin-right: 60rpx;
|
|
|
+.step-dot--active {
|
|
|
+ background: #FFD700;
|
|
|
+ border-color: #FFD700;
|
|
|
+}
|
|
|
+.step-dot--done {
|
|
|
+ border-color: #FFD700;
|
|
|
+ background: #FFD700;
|
|
|
+}
|
|
|
+.step-dot--pending {
|
|
|
+ border-color: #ddd;
|
|
|
+ background: #fff;
|
|
|
+}
|
|
|
+.step-check {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #fff;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+.step-num {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+.step-dot--active .step-num {
|
|
|
+ color: #fff;
|
|
|
+}
|
|
|
+.step-label {
|
|
|
+ font-size: 20rpx;
|
|
|
+ margin-top: 8rpx;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+.step-label--active {
|
|
|
+ color: #FFD700;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+.step-label--muted {
|
|
|
+ color: #ccc;
|
|
|
}
|
|
|
-.nav-placeholder { width: 60rpx; }
|
|
|
|
|
|
-/* ===== Step 1 ===== */
|
|
|
-.step-content { padding: 30rpx; }
|
|
|
-.level-card {
|
|
|
- background: linear-gradient(135deg, #FFD700, #FFA500);
|
|
|
- border-radius: 20rpx;
|
|
|
- padding: 40rpx;
|
|
|
- text-align: center;
|
|
|
- margin-bottom: 30rpx;
|
|
|
+.step-content {
|
|
|
+ animation: fadeIn 0.3s ease;
|
|
|
}
|
|
|
-.level-title { font-size: 26rpx; color: rgba(255,255,255,0.85); display: block; }
|
|
|
-.level-name {
|
|
|
- font-size: 48rpx; font-weight: bold; color: #fff;
|
|
|
- display: block; margin: 10rpx 0;
|
|
|
+@keyframes fadeIn {
|
|
|
+ from { opacity: 0; transform: translateY(20rpx); }
|
|
|
+ to { opacity: 1; transform: translateY(0); }
|
|
|
}
|
|
|
-.level-desc { font-size: 24rpx; color: rgba(255,255,255,0.8); display: block; }
|
|
|
-.age-hint { text-align: center; margin-bottom: 20rpx; }
|
|
|
-.hint-text { font-size: 24rpx; color: #999; }
|
|
|
|
|
|
-.content-card {
|
|
|
- background: #fff;
|
|
|
- border-radius: 24rpx;
|
|
|
- padding: 40rpx;
|
|
|
- box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
|
|
|
+.content-header {
|
|
|
+ text-align: center;
|
|
|
+ margin-bottom: 30rpx;
|
|
|
}
|
|
|
.content-title {
|
|
|
- font-size: 34rpx; font-weight: bold; color: #333;
|
|
|
- display: block; text-align: center; margin-bottom: 10rpx;
|
|
|
+ font-size: 36rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+.content-sub {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #999;
|
|
|
+ margin-top: 10rpx;
|
|
|
+ display: block;
|
|
|
}
|
|
|
-.content-desc {
|
|
|
- font-size: 26rpx; color: #999;
|
|
|
- display: block; text-align: center; margin-bottom: 40rpx;
|
|
|
+.section-title {
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
}
|
|
|
|
|
|
-.target-row {
|
|
|
+.goal-grid {
|
|
|
display: flex;
|
|
|
- flex-direction: row;
|
|
|
+ flex-wrap: wrap;
|
|
|
+}
|
|
|
+.goal-card {
|
|
|
+ width: calc(50% - 12rpx);
|
|
|
+ margin: 6rpx;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 20rpx;
|
|
|
+ padding: 30rpx 16rpx;
|
|
|
+ box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
align-items: center;
|
|
|
- justify-content: center;
|
|
|
- margin-bottom: 10rpx;
|
|
|
+ border: 3rpx solid transparent;
|
|
|
+ transition: all 0.3s;
|
|
|
+ box-sizing: border-box;
|
|
|
}
|
|
|
-.target-btn {
|
|
|
- width: 60rpx; height: 60rpx;
|
|
|
- border-radius: 50%;
|
|
|
+.goal-card--selected {
|
|
|
+ border-color: #FFD700;
|
|
|
background: #FFF8E1;
|
|
|
- border: 2rpx solid #FFD700;
|
|
|
+}
|
|
|
+.goal-icon-wrap {
|
|
|
+ width: 80rpx;
|
|
|
+ height: 80rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: linear-gradient(135deg, #FFD700, #FFA500);
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
- font-size: 36rpx; color: #B8860B; font-weight: bold;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
+}
|
|
|
+.goal-icon {
|
|
|
+ font-size: 32rpx;
|
|
|
+ color: #fff;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+.goal-title {
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 6rpx;
|
|
|
+}
|
|
|
+.goal-desc {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #999;
|
|
|
+ text-align: center;
|
|
|
+ line-height: 1.4;
|
|
|
}
|
|
|
-.target-btn:active { opacity: 0.7; }
|
|
|
-.target-display { margin: 0 40rpx; text-align: center; }
|
|
|
-.target-num { font-size: 72rpx; font-weight: bold; color: #B8860B; display: block; }
|
|
|
-.target-unit { font-size: 28rpx; color: #999; display: block; text-align: center; }
|
|
|
-.range-hint { font-size: 24rpx; color: #ccc; display: block; text-align: center; margin-bottom: 40rpx; }
|
|
|
|
|
|
-.level-selector {
|
|
|
- margin-bottom: 40rpx;
|
|
|
- border-top: 2rpx solid #f5f5f5;
|
|
|
- padding-top: 30rpx;
|
|
|
+.type-list {
|
|
|
+ margin-top: 10rpx;
|
|
|
}
|
|
|
-.level-select-label {
|
|
|
- font-size: 26rpx; color: #666;
|
|
|
- display: block; margin-bottom: 16rpx;
|
|
|
+.type-card {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 20rpx;
|
|
|
+ padding: 28rpx 24rpx;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+ box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
|
|
|
+ border: 3rpx solid transparent;
|
|
|
+ transition: all 0.3s;
|
|
|
}
|
|
|
-.level-options {
|
|
|
+.type-card--selected {
|
|
|
+ border-color: #FFD700;
|
|
|
+ background: #FFF8E1;
|
|
|
+}
|
|
|
+.type-icon-wrap {
|
|
|
+ width: 72rpx;
|
|
|
+ height: 72rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: linear-gradient(135deg, #FFD700, #FFA500);
|
|
|
display: flex;
|
|
|
- flex-direction: row;
|
|
|
- justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ margin-right: 20rpx;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+.type-icon {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #fff;
|
|
|
+ font-weight: bold;
|
|
|
}
|
|
|
-.level-option {
|
|
|
+.type-info {
|
|
|
flex: 1;
|
|
|
- text-align: center;
|
|
|
- padding: 16rpx 8rpx;
|
|
|
- margin: 0 6rpx;
|
|
|
- border-radius: 12rpx;
|
|
|
- background: #f5f5f5;
|
|
|
+}
|
|
|
+.type-name {
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+.type-desc {
|
|
|
font-size: 22rpx;
|
|
|
color: #999;
|
|
|
- line-height: 1.4;
|
|
|
+ margin-top: 4rpx;
|
|
|
+ display: block;
|
|
|
}
|
|
|
-.level-option.active {
|
|
|
- background: #FFF8E1;
|
|
|
- color: #B8860B;
|
|
|
- font-weight: bold;
|
|
|
- border: 2rpx solid #FFD700;
|
|
|
+.type-radio {
|
|
|
+ width: 36rpx;
|
|
|
+ height: 36rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ border: 3rpx solid #ddd;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ flex-shrink: 0;
|
|
|
+ margin-left: 16rpx;
|
|
|
}
|
|
|
-
|
|
|
-.start-btn {
|
|
|
- background: linear-gradient(135deg, #FFD700, #FFA500);
|
|
|
- padding: 24rpx 0;
|
|
|
- border-radius: 44rpx;
|
|
|
- text-align: center;
|
|
|
- box-shadow: 0 4rpx 20rpx rgba(255,215,0,0.4);
|
|
|
+.type-radio--checked {
|
|
|
+ border-color: #FFD700;
|
|
|
+}
|
|
|
+.type-radio-dot {
|
|
|
+ width: 20rpx;
|
|
|
+ height: 20rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: #FFD700;
|
|
|
}
|
|
|
-.start-btn:active { opacity: 0.8; }
|
|
|
-.start-btn-text { font-size: 30rpx; color: #fff; font-weight: bold; }
|
|
|
|
|
|
-/* ===== Step 2 ===== */
|
|
|
+.progress-header {
|
|
|
+ margin-bottom: 30rpx;
|
|
|
+}
|
|
|
+.progress-label {
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #666;
|
|
|
+ display: block;
|
|
|
+ margin-bottom: 12rpx;
|
|
|
+}
|
|
|
.progress-bar {
|
|
|
- height: 12rpx;
|
|
|
- background: #e0e0e0;
|
|
|
- border-radius: 6rpx;
|
|
|
+ height: 8rpx;
|
|
|
+ background: #eee;
|
|
|
+ border-radius: 4rpx;
|
|
|
overflow: hidden;
|
|
|
- margin-bottom: 10rpx;
|
|
|
}
|
|
|
.progress-fill {
|
|
|
height: 100%;
|
|
|
- background: linear-gradient(90deg, #FFD700, #FFA500);
|
|
|
- border-radius: 6rpx;
|
|
|
- transition: width 0.3s;
|
|
|
-}
|
|
|
-.progress-info {
|
|
|
- display: flex;
|
|
|
- flex-direction: row;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- margin-bottom: 20rpx;
|
|
|
+ background: linear-gradient(135deg, #FFD700, #FFA500);
|
|
|
+ border-radius: 4rpx;
|
|
|
+ transition: width 0.3s ease;
|
|
|
}
|
|
|
-.progress-text { font-size: 26rpx; color: #666; }
|
|
|
-.timer-text { font-size: 26rpx; color: #B8860B; font-weight: bold; }
|
|
|
|
|
|
-.expr-list {
|
|
|
+.question-card {
|
|
|
background: #fff;
|
|
|
- border-radius: 16rpx;
|
|
|
- padding: 16rpx 20rpx;
|
|
|
+ border-radius: 20rpx;
|
|
|
+ padding: 36rpx 28rpx;
|
|
|
+ box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
|
|
|
+ margin-bottom: 30rpx;
|
|
|
+}
|
|
|
+.q-tag {
|
|
|
+ display: inline-block;
|
|
|
+ padding: 6rpx 20rpx;
|
|
|
+ background: #FFF8E1;
|
|
|
+ color: #B8860B;
|
|
|
+ font-size: 20rpx;
|
|
|
+ border-radius: 20rpx;
|
|
|
margin-bottom: 20rpx;
|
|
|
- box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
|
|
|
- max-height: 300rpx;
|
|
|
- overflow-y: auto;
|
|
|
}
|
|
|
-.expr-item {
|
|
|
+.q-text {
|
|
|
+ font-size: 30rpx;
|
|
|
+ color: #333;
|
|
|
+ line-height: 1.6;
|
|
|
+ display: block;
|
|
|
+ margin-bottom: 30rpx;
|
|
|
+}
|
|
|
+.q-options {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 16rpx;
|
|
|
+}
|
|
|
+.q-option {
|
|
|
display: flex;
|
|
|
- flex-direction: row;
|
|
|
align-items: center;
|
|
|
- padding: 8rpx 0;
|
|
|
- border-bottom: 1rpx solid #f5f5f5;
|
|
|
- font-size: 24rpx;
|
|
|
+ padding: 24rpx 20rpx;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ border: 2rpx solid #eee;
|
|
|
+ transition: all 0.3s;
|
|
|
}
|
|
|
-.expr-item:last-child { border-bottom: none; }
|
|
|
-.expr-index { color: #999; width: 40rpx; }
|
|
|
-.expr-text { flex: 1; color: #333; }
|
|
|
-.expr-result { width: 36rpx; text-align: center; font-weight: bold; }
|
|
|
-.expr-result.correct { color: #52c41a; }
|
|
|
-.expr-result.wrong { color: #ff4d4f; }
|
|
|
-
|
|
|
-.quiz-card {
|
|
|
- background: #fff;
|
|
|
- border-radius: 24rpx;
|
|
|
- padding: 30rpx;
|
|
|
- box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
|
|
|
+.q-option--selected {
|
|
|
+ border-color: #FFD700;
|
|
|
+ background: #FFF8E1;
|
|
|
}
|
|
|
-.input-group { margin-bottom: 24rpx; }
|
|
|
-.input-label {
|
|
|
- font-size: 28rpx; color: #333; font-weight: 500;
|
|
|
- display: block; margin-bottom: 10rpx;
|
|
|
+.q-option--tf {
|
|
|
+ justify-content: center;
|
|
|
+ padding: 28rpx;
|
|
|
}
|
|
|
-.expr-input, .answer-input {
|
|
|
- width: 100%;
|
|
|
- height: 72rpx;
|
|
|
- background: #f5f7fa;
|
|
|
- border-radius: 12rpx;
|
|
|
- padding: 0 20rpx;
|
|
|
+.q-option-tf-text {
|
|
|
font-size: 28rpx;
|
|
|
- color: #333;
|
|
|
- box-sizing: border-box;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #666;
|
|
|
}
|
|
|
-
|
|
|
-.submit-row { margin-top: 20rpx; }
|
|
|
-.submit-btn {
|
|
|
- background: linear-gradient(135deg, #FFD700, #FFA500);
|
|
|
- padding: 20rpx 0;
|
|
|
- border-radius: 44rpx;
|
|
|
- text-align: center;
|
|
|
- box-shadow: 0 4rpx 20rpx rgba(255,215,0,0.4);
|
|
|
+.q-option--selected .q-option-tf-text {
|
|
|
+ color: #B8860B;
|
|
|
+}
|
|
|
+.q-radio {
|
|
|
+ width: 32rpx;
|
|
|
+ height: 32rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ border: 3rpx solid #ddd;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ flex-shrink: 0;
|
|
|
+ margin-right: 16rpx;
|
|
|
+}
|
|
|
+.q-radio--checked {
|
|
|
+ border-color: #FFD700;
|
|
|
+}
|
|
|
+.q-radio-dot {
|
|
|
+ width: 18rpx;
|
|
|
+ height: 18rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: #FFD700;
|
|
|
+}
|
|
|
+.q-option-text {
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #333;
|
|
|
+ flex: 1;
|
|
|
}
|
|
|
-.submit-btn.disabled { opacity: 0.5; }
|
|
|
-.submit-btn:active { opacity: 0.8; }
|
|
|
-.submit-btn-text { font-size: 28rpx; color: #fff; font-weight: bold; }
|
|
|
|
|
|
-.feedback { margin-top: 24rpx; }
|
|
|
-.feedback-card {
|
|
|
+.action-bar {
|
|
|
+ margin-top: 40rpx;
|
|
|
display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 16rpx;
|
|
|
+}
|
|
|
+.action-bar--double {
|
|
|
flex-direction: row;
|
|
|
- align-items: center;
|
|
|
- padding: 20rpx;
|
|
|
+ gap: 20rpx;
|
|
|
+}
|
|
|
+.action-bar--double .btn {
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.btn {
|
|
|
+ padding: 24rpx 0;
|
|
|
border-radius: 16rpx;
|
|
|
- margin-bottom: 16rpx;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ transition: all 0.3s;
|
|
|
+ box-sizing: border-box;
|
|
|
}
|
|
|
-.fc-correct { background: #f0fff4; border: 2rpx solid #52c41a; }
|
|
|
-.fc-wrong { background: #fff5f5; border: 2rpx solid #ff4d4f; }
|
|
|
-.fb-icon { font-size: 40rpx; margin-right: 16rpx; }
|
|
|
-.fb-content { flex: 1; }
|
|
|
-.fb-title { font-size: 28rpx; font-weight: bold; color: #333; display: block; }
|
|
|
-.fb-desc { font-size: 24rpx; color: #999; margin-top: 4rpx; display: block; }
|
|
|
-.continue-btn {
|
|
|
+.btn--primary {
|
|
|
background: linear-gradient(135deg, #FFD700, #FFA500);
|
|
|
- padding: 16rpx 0;
|
|
|
- border-radius: 40rpx;
|
|
|
- text-align: center;
|
|
|
+ color: #fff;
|
|
|
}
|
|
|
-.continue-btn:active { opacity: 0.8; }
|
|
|
-.continue-btn-text { font-size: 28rpx; color: #fff; font-weight: bold; }
|
|
|
-
|
|
|
-.quit-row { margin-top: 30rpx; text-align: center; }
|
|
|
-.quit-btn { display: inline-block; padding: 16rpx 60rpx; }
|
|
|
-.quit-btn-text { font-size: 26rpx; color: #ccc; }
|
|
|
-
|
|
|
-/* ===== Step 3 ===== */
|
|
|
-.result-card {
|
|
|
+.btn--primary:active {
|
|
|
+ opacity: 0.9;
|
|
|
+ transform: scale(0.98);
|
|
|
+}
|
|
|
+.btn--disabled {
|
|
|
+ opacity: 0.4;
|
|
|
+ pointer-events: none;
|
|
|
+}
|
|
|
+.btn--outline {
|
|
|
+ border: 3rpx solid #FFD700;
|
|
|
+ color: #B8860B;
|
|
|
background: #fff;
|
|
|
- border-radius: 24rpx;
|
|
|
- padding: 60rpx 40rpx;
|
|
|
- text-align: center;
|
|
|
- box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
|
|
|
}
|
|
|
-.result-icon { font-size: 120rpx; display: block; margin-bottom: 20rpx; }
|
|
|
-.result-title {
|
|
|
- font-size: 40rpx; font-weight: bold; color: #B8860B;
|
|
|
- display: block; margin-bottom: 40rpx;
|
|
|
+.btn--outline:active {
|
|
|
+ background: #FFF8E1;
|
|
|
+}
|
|
|
+.btn--ghost {
|
|
|
+ color: #999;
|
|
|
+ background: transparent;
|
|
|
}
|
|
|
-.stats-row {
|
|
|
+
|
|
|
+.result-header {
|
|
|
display: flex;
|
|
|
- flex-direction: row;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ margin: 40rpx 0;
|
|
|
+}
|
|
|
+.score-circle {
|
|
|
+ width: 160rpx;
|
|
|
+ height: 160rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: linear-gradient(135deg, #FFD700, #FFA500);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
justify-content: center;
|
|
|
- margin-bottom: 40rpx;
|
|
|
+ box-shadow: 0 8rpx 30rpx rgba(255,215,0,0.3);
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+}
|
|
|
+.score-value {
|
|
|
+ font-size: 40rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #fff;
|
|
|
+}
|
|
|
+.score-label {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #666;
|
|
|
+ display: block;
|
|
|
}
|
|
|
-.stat-item { text-align: center; margin: 0 24rpx; }
|
|
|
-.stat-value { font-size: 44rpx; font-weight: bold; color: #B8860B; display: block; }
|
|
|
-.stat-label { font-size: 22rpx; color: #999; margin-top: 6rpx; display: block; }
|
|
|
-
|
|
|
-.level-change {
|
|
|
- padding: 20rpx;
|
|
|
- border-radius: 12rpx;
|
|
|
- margin-bottom: 40rpx;
|
|
|
- background: #f0fff4;
|
|
|
-}
|
|
|
-.level-change.down { background: #fff5f5; }
|
|
|
-.level-change.stay { background: #f5f5f5; }
|
|
|
-.change-icon { font-size: 32rpx; font-weight: bold; display: block; margin-bottom: 6rpx; }
|
|
|
-.change-text { font-size: 26rpx; color: #333; line-height: 1.5; }
|
|
|
|
|
|
-.result-actions { display: flex; flex-direction: column; align-items: center; }
|
|
|
-.action-btn {
|
|
|
- width: 60%;
|
|
|
- padding: 20rpx 0;
|
|
|
- border-radius: 44rpx;
|
|
|
- margin-bottom: 20rpx;
|
|
|
- text-align: center;
|
|
|
+.result-dims {
|
|
|
+ margin: 20rpx 0 30rpx;
|
|
|
+}
|
|
|
+.dim-bar-item {
|
|
|
+ margin-bottom: 24rpx;
|
|
|
+}
|
|
|
+.dim-bar-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 8rpx;
|
|
|
+}
|
|
|
+.dim-bar-name {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #333;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+.dim-bar-count {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+.dim-bar-track {
|
|
|
+ height: 8rpx;
|
|
|
+ background: #eee;
|
|
|
+ border-radius: 4rpx;
|
|
|
+ overflow: hidden;
|
|
|
}
|
|
|
-.action-btn.primary {
|
|
|
+.dim-bar-fill {
|
|
|
+ height: 100%;
|
|
|
background: linear-gradient(135deg, #FFD700, #FFA500);
|
|
|
- box-shadow: 0 4rpx 20rpx rgba(255,215,0,0.4);
|
|
|
+ border-radius: 4rpx;
|
|
|
+ transition: width 0.6s ease;
|
|
|
}
|
|
|
-.action-btn.secondary { background: #fff; border: 2rpx solid #ddd; }
|
|
|
-.action-btn-text { font-size: 28rpx; font-weight: bold; }
|
|
|
-.action-btn.primary .action-btn-text { color: #fff; }
|
|
|
-.action-btn.secondary .action-btn-text { color: #666; }
|
|
|
|
|
|
-.bottom-spacer { height: 40rpx; }
|
|
|
+.bottom-spacer {
|
|
|
+ height: 120rpx;
|
|
|
+}
|
|
|
</style>
|