<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>第07讲:简单的立体图形计数(堆积木计数)</title>
<style>
/* 基础重置 & 字体 */
:root {
--primary-color: #667eea;
--secondary-color: #764ba2;
--accent-color: #ffc107;
--success-color: #28a745;
--error-color: #dc3545;
--bg-color: #F5F7FA;
}
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; font-family: "PingFang SC", "Microsoft YaHei", "Comic Sans MS", sans-serif; }
::-webkit-scrollbar { display: none; }
html, body { margin: 0; padding: 0; background: var(--bg-color); overflow: hidden; height: 100vh; }
/* 主容器 */
#app { height: 100vh; max-width: 480px; margin: 0 auto; background: #fff; display: flex; flex-direction: column; position: relative; box-shadow: 0 0 20px rgba(0,0,0,0.05); }
.content-area { flex: 1; overflow-y: auto; padding-bottom: 90px; scroll-behavior: smooth; }
/* 底部导航 */
.bottom-nav {
position: fixed; bottom: 0; left: 50%; transform: translateX(-50%);
width: 100%; max-width: 480px; height: 75px;
background: white; border-top: 1px solid #eee;
box-shadow: 0 -5px 15px rgba(0,0,0,0.03);
display: flex; justify-content: space-around; align-items: center; z-index: 9999;
padding-bottom: 10px; /* 适配 iPhone X 底部 */
}
.nav-item { flex: 1; display: flex; flex-direction: column; align-items: center; cursor: pointer; color: #999; transition: all 0.3s; }
.nav-item.active { color: var(--primary-color); transform: translateY(-5px); }
.nav-icon { font-size: 24px; margin-bottom: 4px; }
.nav-label { font-size: 10px; font-weight: 500; }
/* 通用 Tab 导航 */
.tab-nav { display: flex; background: white; padding: 10px 10px 0; position: sticky; top: 0; z-index: 10; }
.tab-item {
flex: 1; padding: 12px; text-align: center; cursor: pointer;
font-weight: bold; color: #666; position: relative;
transition: color 0.3s;
}
.tab-item.active { color: var(--primary-color); }
.tab-item.active::after {
content: ''; position: absolute; bottom: 0; left: 20%; width: 60%;
height: 3px; background: var(--primary-color); border-radius: 3px;
}
/* 动画演示区域 */
.demo-page { padding: 10px; }
.animation-area {
background: radial-gradient(circle at center, #ffffff 0%, #f0f4f8 100%);
border-radius: 20px; padding: 20px;
height: 340px; position: relative;
box-shadow: inset 0 0 20px rgba(0,0,0,0.03);
border: 1px solid #e1e8ed;
overflow: hidden;
}
.svg-container { width: 100%; height: 100%; }
/* 立方体样式 (关键修改) */
.cube-face { stroke: #5e6d82; stroke-width: 1.5; stroke-linejoin: round; }
.cube-top { fill-opacity: 0.9; }
.cube-side { fill-opacity: 0.7; }
.cube-front { fill-opacity: 1; }
/* 控制区 */
.math-area {
background: white; border-radius: 16px; padding: 15px; margin: 15px 0;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.1); text-align: center;
}
.math-formula { font-size: 20px; font-weight: 800; color: #333; margin-bottom: 8px; }
.step-explanation { font-size: 15px; color: #666; line-height: 1.4; min-height: 42px; }
.control-bar {
display: flex; justify-content: space-between; gap: 15px; margin-top: 10px;
}
.step-btn {
flex: 1; background: var(--primary-color); color: white; border: none;
padding: 12px; border-radius: 12px; font-size: 15px; font-weight: bold;
cursor: pointer; transition: all 0.2s; box-shadow: 0 4px 6px rgba(102, 126, 234, 0.3);
}
.step-btn:disabled { background: #e0e0e0; color: #999; box-shadow: none; transform: none; }
.step-btn:active:not(:disabled) { transform: scale(0.96); }
/* 页面1: 概念引入 */
.intro-page { padding: 30px 20px; text-align: center; }
.intro-header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 30px; border-radius: 20px; color: white; margin-bottom: 30px; box-shadow: 0 10px 20px rgba(118, 75, 162, 0.3); }
.intro-emoji { font-size: 70px; margin-bottom: 10px; display: block; filter: drop-shadow(0 4px 0 rgba(0,0,0,0.1)); }
.concept-card { background: white; border-radius: 16px; padding: 20px; margin: 15px 0; text-align: left; box-shadow: 0 4px 12px rgba(0,0,0,0.05); border-left: 5px solid var(--primary-color); }
.concept-title { font-size: 18px; font-weight: bold; margin-bottom: 8px; color: #333; }
.speak-btn { background: #ffc107; color: #744d00; border: none; padding: 15px 40px; border-radius: 30px; font-size: 18px; font-weight: bold; margin-top: 20px; cursor: pointer; box-shadow: 0 6px 0 #e0a800; transition: transform 0.1s; }
.speak-btn:active { transform: translateY(4px); box-shadow: 0 2px 0 #e0a800; }
/* 页面3: 讲解页 */
.explain-page { padding: 15px; }
.example-card { background: white; border-radius: 16px; padding: 20px; margin-bottom: 15px; box-shadow: 0 4px 10px rgba(0,0,0,0.03); }
.example-card h4 { margin: 0 0 12px 0; color: var(--primary-color); border-bottom: 1px dashed #eee; padding-bottom: 8px; }
.tag-pill { display: inline-block; background: #eef2ff; color: var(--primary-color); padding: 4px 10px; border-radius: 20px; font-size: 12px; margin-right: 5px; font-weight: bold; }
/* 练习与奥数通用 */
.practice-page { padding: 20px; }
.question-card { background: white; border-radius: 24px; padding: 24px; box-shadow: 0 8px 30px rgba(0,0,0,0.06); }
.question-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.score-pill { background: #fff8e1; color: #f59e0b; padding: 5px 15px; border-radius: 20px; font-weight: bold; display: flex; align-items: center; gap: 5px; }
.question-text { font-size: 18px; margin-bottom: 25px; line-height: 1.6; color: #2d3748; font-weight: 500; }
.option-btn {
width: 100%; background: #f8f9fa; border: 2px solid #edf2f7; border-radius: 16px;
padding: 18px; margin-bottom: 12px; text-align: left; font-size: 16px;
cursor: pointer; transition: all 0.2s; position: relative; overflow: hidden;
display: flex; align-items: center;
}
.option-btn:hover:not(:disabled) { border-color: var(--primary-color); background: #f0f4ff; }
.option-btn .opt-icon { margin-right: 10px; font-weight: bold; width: 24px; }
.option-btn.correct { background: #d1fae5; border-color: #34d399; color: #065f46; }
.option-btn.wrong { background: #fee2e2; border-color: #f87171; color: #991b1b; }
.option-btn.shake { animation: shake 0.5s cubic-bezier(.36,.07,.19,.97) both; }
@keyframes shake {
10%, 90% { transform: translate3d(-1px, 0, 0); }
20%, 80% { transform: translate3d(2px, 0, 0); }
30%, 50%, 70% { transform: translate3d(-4px, 0, 0); }
40%, 60% { transform: translate3d(4px, 0, 0); }
}
.feedback-area { margin-top: 20px; padding: 15px; border-radius: 12px; font-size: 15px; animation: slideDown 0.3s ease; }
.feedback-correct { background: #d1fae5; color: #065f46; border: 1px solid #a7f3d0; }
.feedback-wrong { background: #fee2e2; color: #991b1b; border: 1px solid #fecaca; }
@keyframes slideDown { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } }
.next-btn { width: 100%; padding: 16px; background: var(--primary-color); color: white; border: none; border-radius: 16px; font-size: 18px; font-weight: bold; margin-top: 20px; box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4); }
/* 通关秘籍 */
.secrets-container { padding: 20px; white-space: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch; padding-bottom: 40px; }
.secret-card {
display: inline-block; width: 280px; height: 360px; margin-right: 20px;
background: white; border-radius: 24px; padding: 30px 20px; vertical-align: top;
white-space: normal; text-align: center; position: relative;
box-shadow: 0 10px 25px rgba(0,0,0,0.1);
border: 1px solid #eee; transition: transform 0.3s;
}
.secret-card-bg1 { background: linear-gradient(135deg, #a18cd1 0%, #fbc2eb 100%); color: white; border: none; }
.secret-card-bg2 { background: linear-gradient(135deg, #84fab0 0%, #8fd3f4 100%); color: #05485e; border: none; }
.secret-card-bg3 { background: linear-gradient(135deg, #e0c3fc 0%, #8ec5fc 100%); color: #3c2a6f; border: none; }
/* 完成页 */
.completion-page { text-align: center; padding-top: 40px; }
.completion-score { font-size: 48px; font-weight: 800; color: var(--accent-color); margin: 20px 0; text-shadow: 2px 2px 0px #ffe0b2; }
</style>
</head>
<body>
<div id="app">
<div class="content-area" ref="contentArea">
<div v-show="currentPage === 1" class="intro-page">
<div class="intro-header">
<span class="intro-emoji">📦</span>
<h1>堆积木计数</h1>
<p style="opacity: 0.9;">奥数思维训练 · 第07讲</p>
</div>
<div class="concept-card">
<div class="concept-title">👀 什么是“立体图形”?</div>
<p>就是那些不是平平的、有厚度的、可以拿在手里玩的图形,比如积木、魔方。</p>
</div>
<div class="concept-card">
<div class="concept-title">🔄 为什么要转着看?</div>
<p>因为从前面、侧面、上面看,积木的样子可能不一样哦!有时候还会藏起来呢。</p>
</div>
<div class="concept-card">
<div class="concept-title">✨ 数数小秘诀</div>
<p>按顺序数!一层一层数,或者一排一排数,千万别漏掉藏在后面的积木。</p>
</div>
<button class="speak-btn" @click="speak('小朋友们好!今天我们来学习堆积木计数。立体图形就是有厚度的图形。记得哦,从不同角度看,积木的样子可能不一样,数的时候要有顺序,不要漏掉藏起来的积木!')">
🔊 听老师讲解
</button>
</div>
<div v-show="currentPage === 2" class="demo-page">
<div class="tab-nav">
<div class="tab-item" :class="{active: demoTab === 'simple'}" @click="switchDemoTab('simple')">简单堆积</div>
<div class="tab-item" :class="{active: demoTab === 'lshape'}" @click="switchDemoTab('lshape')">L形堆积</div>
</div>
<div v-show="demoTab === 'simple'">
<div class="animation-area">
<svg class="svg-container" viewBox="0 0 400 340">
<path d="M50 280 L350 280 L300 240 L100 240 Z" fill="#e0e0e0" opacity="0.5"/>
<g id="cube-red" opacity="0">
<path d="M170 240 L230 240 L230 190 L170 190 Z" fill="#FF6B6B" class="cube-face cube-front"/>
<path d="M170 190 L230 190 L250 170 L190 170 Z" fill="#FF6B6B" class="cube-face cube-top"/>
<path d="M230 240 L250 220 L250 170 L230 190 Z" fill="#FF6B6B" class="cube-face cube-side"/>
</g>
<g id="cube-yellow" opacity="0">
<path d="M170 190 L230 190 L230 140 L170 140 Z" fill="#FFD93D" class="cube-face cube-front"/>
<path d="M170 140 L230 140 L250 120 L190 120 Z" fill="#FFD93D" class="cube-face cube-top"/>
<path d="M230 190 L250 170 L250 120 L230 140 Z" fill="#FFD93D" class="cube-face cube-side"/>
</g>
<g id="view-indicator" opacity="0">
<text x="200" y="80" text-anchor="middle" font-weight="bold" fill="#333">👀 不同角度</text>
<path d="M120 220 Q100 220 160 215" stroke="#333" stroke-width="2" marker-end="url(#arrow)" stroke-dasharray="5,5"/>
<text x="80" y="225" font-size="12" fill="#666">前面</text>
<path d="M300 200 Q320 200 260 190" stroke="#333" stroke-width="2" marker-end="url(#arrow)" stroke-dasharray="5,5"/>
<text x="310" y="200" font-size="12" fill="#666">侧面</text>
</g>
<defs>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="0" refY="3" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L0,6 L9,3 z" fill="#333" />
</marker>
</defs>
</svg>
</div>
<div class="math-area">
<div class="math-formula">{{simpleFormulas[simpleStep]}}</div>
<div class="step-explanation">{{simpleSteps[simpleStep]}}</div>
<div class="control-bar">
<button class="step-btn" @click="prevSimpleStep" :disabled="simpleStep === 0">◀ 上一步</button>
<button class="step-btn" @click="nextSimpleStep" :disabled="simpleStep === 3">下一步 ▶</button>
</div>
</div>
</div>
<div v-show="demoTab === 'lshape'">
<div class="animation-area">
<svg class="svg-container" viewBox="0 0 400 340">
<path d="M50 280 L350 280 L300 240 L100 240 Z" fill="#e0e0e0" opacity="0.5"/>
<g id="l-red" opacity="0">
<path d="M140 240 L200 240 L200 190 L140 190 Z" fill="#FF6B6B" class="cube-face cube-front"/>
<path d="M140 190 L200 190 L220 170 L160 170 Z" fill="#FF6B6B" class="cube-face cube-top"/>
<path d="M200 240 L220 220 L220 170 L200 190 Z" fill="#FF6B6B" class="cube-face cube-side"/>
</g>
<g id="l-yellow" opacity="0">
<path d="M200 240 L260 240 L260 190 L200 190 Z" fill="#FFD93D" class="cube-face cube-front"/>
<path d="M200 190 L260 190 L280 170 L220 170 Z" fill="#FFD93D" class="cube-face cube-top"/>
<path d="M260 240 L280 220 L280 170 L260 190 Z" fill="#FFD93D" class="cube-face cube-side"/>
</g>
<g id="l-blue" opacity="0">
<path d="M140 190 L200 190 L200 140 L140 140 Z" fill="#4D96FF" class="cube-face cube-front"/>
<path d="M140 140 L200 140 L220 120 L160 120 Z" fill="#4D96FF" class="cube-face cube-top"/>
<path d="M200 190 L220 170 L220 120 L200 140 Z" fill="#4D96FF" class="cube-face cube-side"/>
</g>
<text id="l-text" x="200" y="80" text-anchor="middle" font-size="16" fill="#333" opacity="0" font-weight="bold">L形完成!一共3块</text>
</svg>
</div>
<div class="math-area">
<div class="math-formula">{{lshapeFormulas[lshapeStep]}}</div>
<div class="step-explanation">{{lshapeSteps[lshapeStep]}}</div>
<div class="control-bar">
<button class="step-btn" @click="prevLshapeStep" :disabled="lshapeStep === 0">◀ 上一步</button>
<button class="step-btn" @click="nextLshapeStep" :disabled="lshapeStep === 3">下一步 ▶</button>
</div>
</div>
</div>
</div>
<div v-show="currentPage === 3" class="explain-page">
<div class="tab-nav" style="margin-bottom: 20px;">
<div class="tab-item" :class="{active: explainTab === 'basic'}" @click="explainTab = 'basic'">核心知识</div>
<div class="tab-item" :class="{active: explainTab === 'example'}" @click="explainTab = 'example'">典型例题</div>
</div>
<div v-show="explainTab === 'basic'">
<div class="example-card">
<h4><span class="tag-pill">知识点1</span>什么是立体图形?</h4>
<p>平时我们画在纸上的正方形是平的。但是积木、骰子是立体的,它们有长、宽、高。</p>
<div style="text-align: center; margin-top: 10px;">📦 🎲 🧱</div>
</div>
<div class="example-card">
<h4><span class="tag-pill">知识点2</span>观察的角度</h4>
<p>同一个物体,从不同角度看,形状可能完全不同。数积木的时候,最好想象自己在绕着它转圈。</p>
</div>
<div class="example-card">
<h4><span class="tag-pill">重点</span>隐藏的积木</h4>
<p style="color: #dc3545; font-weight: bold;">这是最容易错的地方!</p>
<p>如果一个积木在第二层,那它下面一定还藏着一块积木支撑着它,千万别漏数了下面那一块!</p>
</div>
</div>
<div v-show="explainTab === 'example'">
<div class="example-card">
<h4>例题1:基础堆叠</h4>
<p><strong>题目</strong>:3块积木摞成一个塔,从前面看是几块?</p>
<div style="background:#f9f9f9; padding:10px; border-radius:8px; margin:10px 0;">
✅ <strong>解析</strong>:
<br>第1步:想象3块积木垂直叠放。
<br>第2步:从前面看,它们没有被遮挡,所以能看到全部3块。
</div>
</div>
<div class="example-card">
<h4>例题2:隐藏积木</h4>
<p><strong>题目</strong>:如右图(假设),楼梯形状,最高处是2层。如果是2层高,它下面有积木吗?</p>
<div style="background:#f9f9f9; padding:10px; border-radius:8px; margin:10px 0;">
✅ <strong>解析</strong>:
<br>积木不会飞!如果看到第2层有积木,那么第1层对应位置肯定也有一块支撑着它。所以数的时候要算上下面看不见的那一块。
</div>
</div>
</div>
</div>
<div v-show="currentPage === 4" class="practice-page">
<div v-if="!practiceCompleted">
<div class="question-card">
<div class="question-header">
<span style="font-weight: bold; color: #999;">练习 {{currentQuestion + 1}} / {{practiceQuestions.length}}</span>
<div class="score-pill">⭐ {{score}}</div>
</div>
<div class="question-text" v-html="practiceQuestions[currentQuestion].text"></div>
<div class="options-container">
<button
v-for="(option, index) in practiceQuestions[currentQuestion].options"
:key="'opt'+index"
class="option-btn"
:class="{
'correct': answered && option.correct,
'wrong': answered && selectedOption === index && !option.correct
}"
@click="selectOption(index, option.correct)"
:disabled="answered"
>
<span class="opt-icon">{{['A','B','C','D'][index]}}.</span>
{{option.text}}
</button>
</div>
<div v-if="answered" class="feedback-area" :class="isCorrect ? 'feedback-correct' : 'feedback-wrong'">
<div v-if="isCorrect"><strong>🎉 回答正确!</strong></div>
<div v-else><strong>💡 还要加油:</strong></div>
<div style="margin-top: 5px; opacity: 0.9;">{{practiceQuestions[currentQuestion].explanation}}</div>
</div>
<button v-if="answered" class="next-btn" @click="nextQuestion">
{{currentQuestion < practiceQuestions.length - 1 ? '下一题' : '查看成绩'}}
</button>
</div>
</div>
<div v-else class="completion-page">
<div style="font-size: 80px;">🎊</div>
<h2>练习完成!</h2>
<div class="completion-score">{{score}}分</div>
<p style="color: #666; margin-bottom: 30px;">基础知识掌握得不错!准备好挑战奥数题了吗?</p>
<button class="next-btn" style="background: linear-gradient(135deg, #f6d365 0%, #fda085 100%); box-shadow: 0 5px 15px rgba(253, 160, 133, 0.4);" @click="switchPage(5)">🚀 挑战奥数题</button>
</div>
</div>
<div v-show="currentPage === 5" class="practice-page">
<div v-if="!olympiadCompleted">
<div class="question-card" style="border: 2px solid #ffd700;">
<div class="question-header">
<div>
<span style="font-weight: bold; color: #333;">奥数挑战 {{currentOlympiad + 1}} / {{olympiadQuestions.length}}</span>
<span style="margin-left: 10px; font-size: 12px; background: #fff3cd; padding: 2px 6px; border-radius: 4px; color: #856404;">
{{olympiadQuestions[currentOlympiad].difficultyText}}
</span>
</div>
<div class="score-pill">🏆 {{olympiadScore}}</div>
</div>
<div class="question-text" v-html="olympiadQuestions[currentOlympiad].text"></div>
<div class="options-container">
<button
v-for="(option, index) in olympiadQuestions[currentOlympiad].options"
:key="'olopt'+index"
class="option-btn"
:class="{
'correct': olympiadAnswered && option.correct,
'wrong': badAttemptIndex === index,
'shake': badAttemptIndex === index
}"
@click="handleOlympiadChoice(index, option.correct)"
:disabled="olympiadAnswered || (badAttempts.includes(index))"
>
<span class="opt-icon">{{['A','B','C','D'][index]}}.</span>
{{option.text}}
</button>
</div>
<div v-if="currentWrongCount > 0 && !olympiadAnswered" class="feedback-area feedback-wrong" style="background: #fff3cd; border-color: #ffeeba; color: #856404;">
<strong>🤔 再想想...</strong>
<div style="margin-top:5px;">
提示:{{ currentWrongCount === 1 ? olympiadQuestions[currentOlympiad].hint1 : olympiadQuestions[currentOlympiad].hint2 }}
</div>
</div>
<div v-if="olympiadAnswered" class="feedback-area feedback-correct">
<strong>🏆 答对了!</strong>
<div style="margin-top: 5px; opacity: 0.9;" v-html="olympiadQuestions[currentOlympiad].explanation"></div>
</div>
<button v-if="olympiadAnswered" class="next-btn" @click="nextOlympiad">
{{currentOlympiad < olympiadQuestions.length - 1 ? '下一关' : '领取奖励'}}
</button>
</div>
</div>
<div v-else class="completion-page">
<div style="font-size: 80px;">🏆</div>
<h2>挑战通关!</h2>
<div class="completion-score">{{olympiadScore}}分</div>
<p style="color: #666; margin-bottom: 30px;">你真是个空间思维小天才!快去看看通关秘籍吧。</p>
<button class="next-btn" style="background: linear-gradient(135deg, #84fab0 0%, #8fd3f4 100%); color: #05485e;" @click="switchPage(6)">🎁 查看通关秘籍</button>
</div>
</div>
<div v-show="currentPage === 6" class="secrets-container">
<div class="secret-card secret-card-bg1">
<div style="font-size: 50px; margin-bottom: 20px;">🔄</div>
<div style="font-size: 24px; font-weight: bold; margin-bottom: 15px;">秘籍1:多角度观察</div>
<p>不要只看一个面。想象自己变成了小鸟,从上面飞过去看,就能发现谁被挡住了!</p>
</div>
<div class="secret-card secret-card-bg2">
<div style="font-size: 50px; margin-bottom: 20px;">🏢</div>
<div style="font-size: 24px; font-weight: bold; margin-bottom: 15px;">秘籍2:分层计数法</div>
<p>面对复杂的图形,先数第一层有几个,再数第二层有几个...最后加起来,这样绝对不会乱!</p>
</div>
<div class="secret-card secret-card-bg3">
<div style="font-size: 50px; margin-bottom: 20px;">🧩</div>
<div style="font-size: 24px; font-weight: bold; margin-bottom: 15px;">秘籍3:标记法</div>
<p>如果在纸上做题,可以在每一个看到的积木面上标上数字,标一个数一个,这样就不会重复啦。</p>
</div>
</div>
</div>
<div class="bottom-nav">
<div class="nav-item" :class="{active: currentPage === 1}" @click="switchPage(1)">
<div class="nav-icon">💡</div><div class="nav-label">概念</div>
</div>
<div class="nav-item" :class="{active: currentPage === 2}" @click="switchPage(2)">
<div class="nav-icon">🎬</div><div class="nav-label">演示</div>
</div>
<div class="nav-item" :class="{active: currentPage === 3}" @click="switchPage(3)">
<div class="nav-icon">📝</div><div class="nav-label">讲解</div>
</div>
<div class="nav-item" :class="{active: currentPage === 4}" @click="switchPage(4)">
<div class="nav-icon">✏️</div><div class="nav-label">练习</div>
</div>
<div class="nav-item" :class="{active: currentPage === 5}" @click="switchPage(5)">
<div class="nav-icon">🏆</div><div class="nav-label">奥数</div>
</div>
<div class="nav-item" :class="{active: currentPage === 6}" @click="switchPage(6)">
<div class="nav-icon">🎁</div><div class="nav-label">秘籍</div>
</div>
</div>
</div>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
currentPage: 1,
demoTab: 'simple',
explainTab: 'basic',
// 动画状态
simpleStep: 0,
lshapeStep: 0,
simpleSteps: ['准备开始...这里是一片空地', '放置底层红色积木', '在红色上面放置黄色积木', '从不同角度观察,看到的形状不同哦'],
simpleFormulas: ['准备', '1块', '1+1=2块', '多角度观察'],
lshapeSteps: ['准备开始...', '底层横着放两块(红+黄)', '左边红块上面叠加蓝块', 'L形完成!看着像2块,实际是3块'],
lshapeFormulas: ['准备', '底层2块', '2+1=3块', 'L形: 3块'],
// 练习题
practiceQuestions: [
{ text: "2块积木摞在一起,从上往下看能看到几块?", options: [{text: "1块", correct: true}, {text: "2块", correct: false}, {text: "3块", correct: false}, {text: "0块", correct: false}], explanation: "俯视的时候,上面的积木挡住了下面的,所以只能看到最顶上的1块。" },
{ text: "4块积木摆成“田”字形(2×2),从上往下看能看到几块?", options: [{text: "2块", correct: false}, {text: "3块", correct: false}, {text: "4块", correct: true}, {text: "1块", correct: false}], explanation: "平铺在地上,没有重叠,所以4块都能看到。" },
{ text: "5块积木搭成小金字塔(下3上2),从前面看能看到几块?", options: [{text: "3块", correct: true}, {text: "4块", correct: false}, {text: "5块", correct: false}, {text: "2块", correct: false}], explanation: "从前面看,上面的2块挡住了后面或者下面的部分,但最主要是看投影面,通常能看到底层的3个面宽。" },
// 为了演示精简了题目数量,可自行添加
],
currentQuestion: 0,
answered: false,
selectedOption: null,
isCorrect: false,
score: 0,
practiceCompleted: false,
// 奥数题 (增强逻辑)
olympiadQuestions: [
{
text: "小明用10块积木搭房子。从前面看是6块,从后面看是5块。这可能吗?",
options: [{text: "完全可能", correct: true}, {text: "绝不可能", correct: false}, {text: "一定是数错了", correct: false}, {text: "不知道", correct: false}],
hint1: "积木可以前后遮挡哦。",
hint2: "只要总数不超过10块,不同面看到的数量是可以不一样的。",
explanation: "这是完全合理的!立体图形中间可能是空心的,或者排列不规则,导致不同角度看到的数量不同。",
difficultyText: "⭐⭐⭐"
},
{
text: "一个正方体魔方由27个小块组成。如果拿掉最中间那一块(看不见的那块),还剩几块?",
options: [{text: "25块", correct: false}, {text: "26块", correct: true}, {text: "27块", correct: false}, {text: "20块", correct: false}],
hint1: "原来一共有27块。",
hint2: "只拿掉了里面的1块。",
explanation: "27 - 1 = 26块。虽然外表看不出来变化,但重量变轻啦!",
difficultyText: "⭐⭐"
},
{
text: "积木楼梯:第1层1块,第2层2块,第3层3块...第5层有多少块?",
options: [{text: "4块", correct: false}, {text: "5块", correct: true}, {text: "6块", correct: false}, {text: "15块", correct: false}],
hint1: "第几层就是几块。",
hint2: "第1层1,第2层2...第5层呢?",
explanation: "规律就是:层数 = 块数。所以第5层有5块。(如果要算总数才是15块哦)",
difficultyText: "⭐"
}
],
currentOlympiad: 0,
olympiadAnswered: false,
olympiadScore: 0,
olympiadCompleted: false,
// 奥数题新增状态
badAttemptIndex: null, // 当前错误的索引,用于触发抖动
badAttempts: [], // 已经试过的错误选项索引
currentWrongCount: 0 // 当前题目的错误次数
};
},
methods: {
switchPage(page) {
this.currentPage = page;
window.scrollTo(0, 0);
if(page === 2) {
this.simpleStep = 0;
this.lshapeStep = 0;
this.$nextTick(() => { this.runSimpleAnimation(); });
}
},
switchDemoTab(tab) {
this.demoTab = tab;
if(tab === 'simple') {
this.simpleStep = 0;
this.$nextTick(() => this.runSimpleAnimation());
} else {
this.lshapeStep = 0;
this.$nextTick(() => this.runLshapeAnimation());
}
},
// 简单的 TTS 实现
speak(text) {
if ('speechSynthesis' in window) {
window.speechSynthesis.cancel();
const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = 'zh-CN';
utterance.rate = 1;
window.speechSynthesis.speak(utterance);
} else {
alert("您的浏览器不支持语音播放");
}
},
// 动画逻辑 GSAP
runSimpleAnimation() {
const tl = gsap.timeline();
tl.set(['#cube-red', '#cube-yellow', '#view-indicator'], { opacity: 0 });
if (this.simpleStep >= 1) tl.to('#cube-red', { opacity: 1, duration: 0.5, y: 0 });
if (this.simpleStep >= 2) tl.to('#cube-yellow', { opacity: 1, duration: 0.5, y: 0 });
if (this.simpleStep >= 3) tl.to('#view-indicator', { opacity: 1, duration: 0.5 });
},
runLshapeAnimation() {
const tl = gsap.timeline();
tl.set(['#l-red', '#l-yellow', '#l-blue', '#l-text'], { opacity: 0 });
if (this.lshapeStep >= 1) tl.to(['#l-red', '#l-yellow'], { opacity: 1, duration: 0.5, stagger: 0.2 });
if (this.lshapeStep >= 2) tl.to('#l-blue', { opacity: 1, duration: 0.5 });
if (this.lshapeStep >= 3) tl.to('#l-text', { opacity: 1, duration: 0.5 });
},
nextSimpleStep() { if(this.simpleStep < 3) { this.simpleStep++; this.runSimpleAnimation(); } },
prevSimpleStep() { if(this.simpleStep > 0) { this.simpleStep--; this.runSimpleAnimation(); } },
nextLshapeStep() { if(this.lshapeStep < 3) { this.lshapeStep++; this.runLshapeAnimation(); } },
prevLshapeStep() { if(this.lshapeStep > 0) { this.lshapeStep--; this.runLshapeAnimation(); } },
// 普通练习逻辑
selectOption(index, correct) {
if (this.answered) return;
this.answered = true;
this.selectedOption = index;
this.isCorrect = correct;
if (correct) {
this.score += 20;
confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 }, colors: ['#667eea', '#764ba2'] });
}
},
nextQuestion() {
if (this.currentQuestion < this.practiceQuestions.length - 1) {
this.currentQuestion++;
this.answered = false;
this.selectedOption = null;
this.isCorrect = false;
} else {
this.practiceCompleted = true;
}
},
// 奥数题逻辑 (优化版:允许重试)
handleOlympiadChoice(index, correct) {
if (this.olympiadAnswered || this.badAttempts.includes(index)) return;
if (correct) {
// 答对了
this.olympiadAnswered = true;
this.olympiadScore += Math.max(10, 30 - (this.currentWrongCount * 10)); // 每次错误扣10分,最低10分
confetti({ particleCount: 150, spread: 100, origin: { y: 0.6 } });
} else {
// 答错了
this.badAttemptIndex = index;
this.currentWrongCount++;
// 动画结束后添加进错误列表
setTimeout(() => {
this.badAttempts.push(index);
this.badAttemptIndex = null;
}, 500);
}
},
nextOlympiad() {
if (this.currentOlympiad < this.olympiadQuestions.length - 1) {
this.currentOlympiad++;
this.olympiadAnswered = false;
this.badAttempts = [];
this.currentWrongCount = 0;
} else {
this.olympiadCompleted = true;
}
}
}
}).mount('#app');
</script>
</body>
</html>
💡 这段代码完全由 AI 生成。
登录后可复制完整代码