| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675 |
- <template>
- <view class="page">
- <!-- ============ 1. 说明页 ============ -->
- <view class="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-rule">
- <text class="rule-item">起始位数:3 位</text>
- <text class="rule-item">最高位数:8 位</text>
- <text class="rule-item">共 6 关</text>
- </view>
- <button class="primary-btn" @click="startTest">开始测试</button>
- </view>
- <!-- ============ 2. 测试中 ============ -->
- <view class="playing" v-if="step === 'playing'">
- <view class="progress-header">
- <text class="progress-level">第 {{ levelIndex }} 关</text>
- <text class="progress-digit">{{ digitCount }} 位数</text>
- </view>
- <view class="progress-track">
- <view class="progress-fill" :style="{ width: progressPct + '%' }"></view>
- </view>
- <!-- 展示阶段:数字闪现 + 计时条 -->
- <view class="show-area" v-if="phase === 'show'">
- <text class="show-hint">记住这串数字</text>
- <view class="digit-row">
- <view class="show-digit" v-for="(d, i) in showDigits" :key="getKey(d, i)">
- <text>{{ d }}</text>
- </view>
- </view>
- <view class="timer-bar-wrap">
- <view class="timer-bar" :style="{ width: timerProgress * 100 + '%' }"></view>
- </view>
- </view>
- <!-- 输入阶段:数字键盘 -->
- <view class="input-area" v-if="phase === 'input'">
- <text class="input-hint">请输入刚才的数字</text>
- <view class="input-display">
- <view
- class="input-cell"
- v-for="(c, i) in inputCells"
- :key="getCellKey(i)"
- :class="{ filled: i < userInput.length }"
- ></view>
- </view>
- <view class="pad">
- <view class="pad-row" v-for="(row, ri) in padRows" :key="getRowKey(ri)">
- <view
- class="pad-key"
- :class="{ 'pad-key-ctrl': key.ctrl }"
- v-for="(key, ci) in row"
- :key="getPadKey(key, ri, ci)"
- @click="onPadPress(key)"
- >
- <text>{{ key.v }}</text>
- </view>
- </view>
- </view>
- </view>
- <!-- 反馈动画 -->
- <view class="feedback feedback-correct" v-if="feedback === 'correct'">
- <text class="feedback-text">回答正确</text>
- </view>
- <view class="feedback feedback-wrong" v-if="feedback === 'wrong'">
- <text class="feedback-text">回答错误</text>
- </view>
- </view>
- <!-- ============ 3. 结果页 ============ -->
- <view class="result" v-if="step === 'result'">
- <view class="score-circle">
- <text class="score-num">{{ score }}</text>
- <text class="score-unit">分</text>
- </view>
- <view class="max-level">
- <text class="max-level-label">最高记忆</text>
- <text class="max-level-value">{{ maxLevel === 0 ? 3 : maxLevel }}位数</text>
- </view>
- <view class="dimension-tag">
- <text>记忆力</text>
- </view>
- <button class="primary-btn submit-btn" :disabled="submitting" @click="submitScore">
- {{ submitting ? '提交中...' : '提交成绩' }}
- </button>
- <button class="ghost-btn" @click="startTest">再测一次</button>
- </view>
- </view>
- </template>
- <script>
- import { submitCognitiveSelfTest } from '../../utils/api.js'
- export default {
- data() {
- return {
- step: 'intro', // intro | playing | result
- phase: 'show', // show | input
- digitCount: 3, // 当前关卡位数 3-8
- currentNumber: '', // 需要记忆的数字串
- userInput: [], // 用户已输入的数字
- inputLocked: false, // 反馈期间锁定键盘
- feedback: '', // '' | correct | wrong
- showStart: 0, // 数字展示开始时间戳
- showDuration: 0, // 数字展示时长(ms)
- timerProgress: 1, // 计时条剩余比例 0-1
- showTimer: null,
- correctTimer: null,
- wrongTimer: null,
- submitTimer: null,
- maxLevel: 0, // 成功记住的最高位数
- score: 0,
- submitting: false,
- memberId: '',
- padRows: [
- [{ v: 1 }, { v: 2 }, { v: 3 }],
- [{ v: 4 }, { v: 5 }, { v: 6 }],
- [{ v: 7 }, { v: 8 }, { v: 9 }],
- [{ v: '清除', ctrl: true }, { v: 0 }, { v: '确认', ctrl: true }]
- ]
- }
- },
- computed: {
- levelIndex() {
- return this.digitCount - 2
- },
- progressPct() {
- return (this.levelIndex / 6) * 100
- },
- showDigits() {
- return (this.currentNumber || '').split('')
- },
- inputCells() {
- var arr = []
- for (var i = 0; i < this.digitCount; i++) {
- arr.push(i)
- }
- return arr
- }
- },
- onLoad(options) {
- var id = (options && options.memberId) || uni.getStorageSync('currentChildId') || ''
- this.memberId = id
- },
- onUnload() {
- this.clearAllTimers()
- },
- methods: {
- // key 一律用方法调用,禁止模板内表达式
- getKey(d, i) {
- return 'digit-' + i
- },
- getCellKey(i) {
- return 'cell-' + i
- },
- getRowKey(ri) {
- return 'row-' + ri
- },
- getPadKey(key, ri, ci) {
- return 'pad-' + ri + '-' + ci
- },
- clearAllTimers() {
- if (this.showTimer) {
- clearInterval(this.showTimer)
- this.showTimer = null
- }
- if (this.correctTimer) {
- clearTimeout(this.correctTimer)
- this.correctTimer = null
- }
- if (this.wrongTimer) {
- clearTimeout(this.wrongTimer)
- this.wrongTimer = null
- }
- if (this.submitTimer) {
- clearTimeout(this.submitTimer)
- this.submitTimer = null
- }
- },
- startTest() {
- this.clearAllTimers()
- this.digitCount = 3
- this.maxLevel = 0
- this.score = 0
- this.userInput = []
- this.inputLocked = false
- this.feedback = ''
- this.step = 'playing'
- this.startRound()
- },
- startRound() {
- this.phase = 'show'
- this.userInput = []
- this.inputLocked = false
- this.feedback = ''
- // 生成随机数字串
- var num = ''
- for (var i = 0; i < this.digitCount; i++) {
- num += Math.floor(Math.random() * 10)
- }
- this.currentNumber = num
- this.showStart = Date.now()
- this.showDuration = Math.max(1.5, this.digitCount * 0.5) * 1000
- this.timerProgress = 1
- if (this.showTimer) {
- clearInterval(this.showTimer)
- this.showTimer = null
- }
- this.showTimer = setInterval(() => {
- var remaining = this.showDuration - (Date.now() - this.showStart)
- if (remaining <= 0) {
- if (this.showTimer) {
- clearInterval(this.showTimer)
- this.showTimer = null
- }
- this.timerProgress = 0
- this.phase = 'input'
- return
- }
- this.timerProgress = remaining / this.showDuration
- }, 50)
- },
- onPadPress(key) {
- if (this.feedback || this.inputLocked) return
- var v = key.v
- if (typeof v === 'number') {
- if (this.userInput.length < this.digitCount) {
- this.userInput.push(v)
- }
- } else if (v === '清除') {
- this.userInput.pop()
- } else if (v === '确认') {
- this.confirmInput()
- }
- },
- confirmInput() {
- if (this.userInput.length < this.digitCount) return
- this.inputLocked = true
- var entered = this.userInput.join('')
- if (entered === this.currentNumber) {
- this.feedback = 'correct'
- this.correctTimer = setTimeout(() => {
- this.correctTimer = null
- this.feedback = ''
- this.maxLevel = this.digitCount
- if (this.digitCount >= 8) {
- // 最高关卡已通过,测试结束
- this.endGame()
- } else {
- this.digitCount++
- this.startRound()
- }
- }, 700)
- } else {
- this.feedback = 'wrong'
- this.wrongTimer = setTimeout(() => {
- this.wrongTimer = null
- this.feedback = ''
- this.endGame()
- }, 900)
- }
- },
- endGame() {
- this.clearAllTimers()
- // 按最高成功记住的位数映射得分
- var score = 100
- if (this.maxLevel === 0) {
- score = 40
- } else if (this.maxLevel === 3) {
- score = 55
- } else if (this.maxLevel === 4) {
- score = 70
- } else if (this.maxLevel === 5) {
- score = 82
- } else if (this.maxLevel === 6) {
- score = 92
- }
- this.score = score
- this.step = 'result'
- },
- submitScore() {
- if (this.submitting) return
- var memberId = this.memberId
- if (!memberId) {
- uni.showToast({ title: '未找到测评成员', icon: 'none' })
- return
- }
- this.submitting = true
- submitCognitiveSelfTest(memberId, 'memoryScore', this.score)
- .then(() => {
- uni.showToast({ title: '提交成功', icon: 'success' })
- this.submitTimer = setTimeout(() => {
- var pages = getCurrentPages()
- if (pages.length > 1) {
- uni.navigateBack()
- } else {
- uni.reLaunch({ url: '/pages/index/index' })
- }
- }, 800)
- })
- .catch(() => {
- // request() 已自动弹出失败提示,这里只需恢复按钮状态
- this.submitting = false
- })
- }
- }
- }
- </script>
- <style scoped>
- .page {
- min-height: 100vh;
- background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 60rpx 40rpx 80rpx;
- box-sizing: border-box;
- }
- /* ---------- 通用按钮 ---------- */
- .primary-btn {
- margin-top: 60rpx;
- width: 480rpx;
- height: 96rpx;
- line-height: 96rpx;
- border-radius: 48rpx;
- background: linear-gradient(135deg, #6366f1 0%, #818cf8 100%);
- color: #ffffff;
- font-size: 34rpx;
- font-weight: 600;
- border: none;
- box-shadow: 0 8rpx 24rpx rgba(99, 102, 241, 0.35);
- }
- .primary-btn::after {
- border: none;
- }
- .primary-btn[disabled] {
- opacity: 0.6;
- color: #ffffff;
- background: linear-gradient(135deg, #6366f1 0%, #818cf8 100%);
- }
- .ghost-btn {
- margin-top: 30rpx;
- width: 480rpx;
- height: 96rpx;
- line-height: 96rpx;
- border-radius: 48rpx;
- background: transparent;
- color: #a5b4fc;
- font-size: 32rpx;
- border: 2rpx solid rgba(99, 102, 241, 0.5);
- }
- .ghost-btn::after {
- border: none;
- }
- /* ---------- 说明页 ---------- */
- .intro {
- display: flex;
- flex-direction: column;
- align-items: center;
- width: 100%;
- padding-top: 80rpx;
- }
- .intro-icon {
- width: 160rpx;
- height: 160rpx;
- border-radius: 50%;
- background: rgba(99, 102, 241, 0.15);
- display: flex;
- align-items: center;
- justify-content: center;
- margin-bottom: 40rpx;
- }
- .intro-icon-text {
- font-size: 80rpx;
- }
- .intro-title {
- font-size: 48rpx;
- font-weight: bold;
- color: #ffffff;
- margin-bottom: 30rpx;
- }
- .intro-desc {
- font-size: 30rpx;
- color: rgba(255, 255, 255, 0.75);
- line-height: 1.7;
- text-align: center;
- padding: 0 20rpx;
- }
- .intro-rule {
- display: flex;
- flex-direction: row;
- flex-wrap: wrap;
- justify-content: center;
- margin-top: 40rpx;
- }
- .rule-item {
- font-size: 24rpx;
- color: #a5b4fc;
- background: rgba(99, 102, 241, 0.12);
- border-radius: 24rpx;
- padding: 10rpx 24rpx;
- margin: 8rpx 12rpx;
- }
- /* ---------- 测试中 ---------- */
- .playing {
- width: 100%;
- display: flex;
- flex-direction: column;
- align-items: center;
- }
- .progress-header {
- display: flex;
- flex-direction: column;
- align-items: center;
- margin-top: 20rpx;
- }
- .progress-level {
- font-size: 44rpx;
- font-weight: bold;
- color: #ffffff;
- }
- .progress-digit {
- font-size: 26rpx;
- color: #a5b4fc;
- margin-top: 8rpx;
- }
- .progress-track {
- width: 480rpx;
- height: 8rpx;
- border-radius: 4rpx;
- background: rgba(255, 255, 255, 0.1);
- margin-top: 24rpx;
- overflow: hidden;
- }
- .progress-fill {
- height: 100%;
- border-radius: 4rpx;
- background: linear-gradient(90deg, #6366f1, #818cf8);
- transition: width 0.3s ease;
- }
- /* 展示阶段:数字闪现 */
- .show-area {
- display: flex;
- flex-direction: column;
- align-items: center;
- width: 100%;
- padding-top: 120rpx;
- }
- .show-hint {
- font-size: 26rpx;
- color: rgba(255, 255, 255, 0.5);
- margin-bottom: 60rpx;
- }
- .digit-row {
- display: flex;
- flex-direction: row;
- justify-content: center;
- align-items: center;
- }
- .show-digit {
- width: 76rpx;
- height: 140rpx;
- margin: 0 4rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- background: rgba(255, 255, 255, 0.06);
- border-radius: 16rpx;
- }
- .show-digit text {
- font-size: 120rpx;
- font-weight: bold;
- color: #ffffff;
- }
- .timer-bar-wrap {
- width: 480rpx;
- height: 12rpx;
- border-radius: 6rpx;
- background: rgba(255, 255, 255, 0.1);
- overflow: hidden;
- margin-top: 60rpx;
- }
- .timer-bar {
- height: 100%;
- border-radius: 6rpx;
- background: linear-gradient(90deg, #6366f1, #818cf8);
- transition: width 0.05s linear;
- }
- /* 输入阶段:数字键盘 */
- .input-area {
- display: flex;
- flex-direction: column;
- align-items: center;
- width: 100%;
- padding-top: 60rpx;
- }
- .input-hint {
- font-size: 26rpx;
- color: rgba(255, 255, 255, 0.5);
- margin-bottom: 50rpx;
- }
- .input-display {
- display: flex;
- flex-direction: row;
- justify-content: center;
- min-height: 100rpx;
- margin-bottom: 40rpx;
- }
- .input-cell {
- width: 76rpx;
- height: 100rpx;
- margin: 0 4rpx;
- border-radius: 16rpx;
- background: rgba(255, 255, 255, 0.04);
- border: 2rpx solid rgba(255, 255, 255, 0.15);
- box-sizing: border-box;
- }
- .input-cell.filled {
- background: rgba(99, 102, 241, 0.5);
- border-color: #6366f1;
- }
- .pad {
- width: 100%;
- max-width: 620rpx;
- }
- .pad-row {
- display: flex;
- flex-direction: row;
- justify-content: space-between;
- margin-bottom: 20rpx;
- }
- .pad-key {
- width: 180rpx;
- height: 108rpx;
- border-radius: 20rpx;
- background: rgba(255, 255, 255, 0.06);
- border: 2rpx solid rgba(255, 255, 255, 0.08);
- display: flex;
- align-items: center;
- justify-content: center;
- box-sizing: border-box;
- }
- .pad-key text {
- font-size: 44rpx;
- color: #ffffff;
- font-weight: 500;
- }
- .pad-key:active {
- border-color: #6366f1;
- background: rgba(99, 102, 241, 0.2);
- }
- .pad-key-ctrl {
- background: rgba(255, 255, 255, 0.03);
- }
- .pad-key-ctrl text {
- font-size: 32rpx;
- color: #a5b4fc;
- }
- /* 反馈动画 */
- .feedback {
- position: fixed;
- left: 0;
- right: 0;
- top: 0;
- bottom: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 99;
- animation: feedbackFade 0.7s ease both;
- }
- .feedback-correct {
- background: rgba(16, 185, 129, 0.3);
- }
- .feedback-wrong {
- background: rgba(239, 68, 68, 0.3);
- }
- .feedback-text {
- font-size: 56rpx;
- font-weight: bold;
- color: #ffffff;
- padding: 24rpx 60rpx;
- border-radius: 24rpx;
- background: rgba(0, 0, 0, 0.35);
- }
- @keyframes feedbackFade {
- 0% {
- opacity: 0;
- }
- 20% {
- opacity: 1;
- }
- 80% {
- opacity: 1;
- }
- 100% {
- opacity: 0;
- }
- }
- /* ---------- 结果页 ---------- */
- .result {
- display: flex;
- flex-direction: column;
- align-items: center;
- width: 100%;
- padding-top: 100rpx;
- }
- .score-circle {
- width: 280rpx;
- height: 280rpx;
- border-radius: 50%;
- background: linear-gradient(135deg, #6366f1 0%, #818cf8 100%);
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- box-shadow: 0 12rpx 40rpx rgba(99, 102, 241, 0.45);
- }
- .score-num {
- font-size: 96rpx;
- font-weight: bold;
- color: #ffffff;
- line-height: 1;
- }
- .score-unit {
- font-size: 28rpx;
- color: rgba(255, 255, 255, 0.8);
- margin-top: 8rpx;
- }
- .max-level {
- display: flex;
- flex-direction: row;
- align-items: center;
- margin-top: 60rpx;
- }
- .max-level-label {
- font-size: 30rpx;
- color: rgba(255, 255, 255, 0.6);
- margin-right: 16rpx;
- }
- .max-level-value {
- font-size: 40rpx;
- font-weight: bold;
- color: #ffffff;
- }
- .dimension-tag {
- margin-top: 24rpx;
- font-size: 26rpx;
- color: #a5b4fc;
- background: rgba(99, 102, 241, 0.15);
- border: 2rpx solid rgba(99, 102, 241, 0.4);
- border-radius: 24rpx;
- padding: 8rpx 28rpx;
- }
- .submit-btn {
- margin-top: 80rpx;
- }
- </style>
|