<!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>乘法交换律与结合律(奥数挑战版)</title>
<style>
/* ================= 基础重置与全局变量 ================= */
:root {
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--accent-pink: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%);
--accent-blue: linear-gradient(135deg, #a18cd1 0%, #fbc2eb 100%);
/* 升级微投影:更柔和深邃 */
--card-shadow: 0 10px 25px -5px rgba(50, 50, 93, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
--card-radius: 24px;
--bg-color: #f8f9fa;
}
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
::-webkit-scrollbar { display: none; width: 0 !important; height: 0 !important; }
* { -ms-overflow-style: none; scrollbar-width: none; }
html, body {
margin: 0; padding: 0;
background: var(--bg-color);
/* 升级背景:柔和的噪点+渐变,更有质感 */
background-image:
radial-gradient(at 0% 0%, hsla(253,16%,7%,0) 0, hsla(253,16%,7%,0) 50%),
radial-gradient(at 50% 0%, hsla(225,39%,30%,0) 0, hsla(225,39%,30%,0) 50%),
radial-gradient(at 100% 0%, hsla(339,49%,30%,0) 0, hsla(339,49%,30%,0) 50%);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
overflow: hidden; height: 100vh;
}
#app { height: 100vh; max-width: 480px; margin: 0 auto; background: transparent; display: flex; flex-direction: column; position: relative; }
.content-area { flex: 1; overflow-y: auto; padding-bottom: 90px; scroll-behavior: smooth; padding-top: 10px; }
/* ================= 页面通用动画与样式 ================= */
.page {
padding: 20px;
opacity: 1;
transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
}
.page-enter { opacity: 0; transform: translateY(20px); }
.page-title {
font-size: 26px; font-weight: 800;
margin-bottom: 8px; text-align: center;
background: var(--primary-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
letter-spacing: -0.5px;
}
.page-subtitle {
font-size: 16px; color: #8898aa; margin-bottom: 25px; text-align: center; font-weight: 500;
}
/* ================= 升级版卡片样式 ================= */
.intro-card, .concept-card, .question-card, .expand-card {
border-radius: var(--card-radius);
box-shadow: var(--card-shadow);
border: 1px solid rgba(255,255,255,0.8);
backdrop-filter: blur(10px);
margin-bottom: 24px;
overflow: hidden;
background: #ffffff;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
/* 引入页卡片 */
.intro-card {
background: linear-gradient(135deg, #e0c3fc 0%, #8ec5fc 100%);
padding: 30px 25px;
text-align: center;
}
.intro-emoji { font-size: 64px; margin-bottom: 20px; filter: drop-shadow(0 4px 8px rgba(0,0,0,0.1)); }
.intro-story { font-size: 17px; line-height: 1.8; color: #435466; text-align: left; margin-bottom: 25px; font-weight: 500; }
.teacher-btn {
background: #fff; color: #667eea; border: none; padding: 14px 32px;
border-radius: 50px; font-size: 16px; font-weight: 700;
cursor: pointer; box-shadow: 0 4px 15px rgba(0,0,0,0.1);
transition: all 0.2s; display: inline-flex; align-items: center; gap: 8px;
}
.teacher-btn:active { transform: scale(0.95); box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
/* ================= 导航栏 ================= */
.bottom-nav {
position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);
width: 90%; max-width: 440px; height: 65px;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(20px);
border-radius: 20px;
box-shadow: 0 15px 35px rgba(0,0,0,0.1);
display: flex; justify-content: space-around; align-items: center; z-index: 9999;
padding: 0 10px;
}
.nav-item { flex: 1; display: flex; flex-direction: column; align-items: center; cursor: pointer; padding: 5px 0; transition: all 0.3s; color: #b0bdd1; transform: scale(0.9); }
.nav-item.active { color: #667eea; transform: scale(1.1) translateY(-5px); }
.nav-icon { font-size: 22px; margin-bottom: 2px; }
.nav-text { font-size: 10px; font-weight: 600; }
/* ================= Tab 导航 ================= */
.tab-nav {
display: flex; background: #eef2f7; border-radius: 16px; padding: 4px; margin-bottom: 24px;
}
.tab-item {
flex: 1; text-align: center; padding: 10px 0; border-radius: 12px;
cursor: pointer; font-size: 15px; color: #7f8c8d; transition: all 0.3s; font-weight: 600;
}
.tab-item.active {
background: white; color: #667eea;
box-shadow: 0 4px 10px rgba(0,0,0,0.05);
}
/* ================= 动画与演示区域 ================= */
.animation-area {
background: radial-gradient(circle at center, #ffffff 0%, #f0f4f8 100%);
border-radius: var(--card-radius);
box-shadow: inset 0 2px 10px rgba(0,0,0,0.05);
padding: 10px; height: 320px; position: relative;
margin-bottom: 24px; border: 1px solid #edf2f7;
}
.svg-container { width: 100%; height: 100%; }
.step-btn {
position: absolute; bottom: 20px; z-index: 100;
background: white; color: #667eea; border: none;
width: 44px; height: 44px; border-radius: 50%; font-size: 20px;
cursor: pointer; display: flex; align-items: center; justify-content: center;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
transition: all 0.2s;
}
.step-btn:active { transform: scale(0.9); }
.step-btn:disabled { background: #f1f3f5; color: #ccc; box-shadow: none; }
.step-btn.prev { left: 20px; }
.step-btn.next { right: 20px; background: var(--primary-gradient); color: white; }
/* ================= 知识点卡片 ================= */
.concept-header {
padding: 18px 25px; color: white; font-weight: 700; font-size: 17px;
display: flex; justify-content: space-between; align-items: center;
}
.bg-pink { background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%); }
.bg-purple { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
.bg-blue { background: linear-gradient(135deg, #89f7fe 0%, #66a6ff 100%); }
.concept-body { padding: 25px; }
.highlight-box {
background: #fff8e1; border-left: 5px solid #ffc107;
padding: 15px 20px; border-radius: 0 12px 12px 0; margin: 15px 0;
color: #d35400; font-size: 15px; line-height: 1.6;
}
.formula-text { font-family: "Courier New", monospace; font-weight: 700; color: #e67e22; margin-top: 8px; display: block; }
/* ================= 秘籍卡片 (左右滑动升级版) ================= */
.secret-container {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
gap: 20px;
padding: 10px 0 40px 0; /* 底部留空间防止阴影被切 */
width: 100%;
}
/* 隐藏滚动条 */
.secret-container::-webkit-scrollbar { display: none; }
.secret-card {
flex: 0 0 100%; /* 强制占满宽度 */
scroll-snap-align: center; /* 关键:滚动对齐 */
min-height: 500px; /* 增加高度 */
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
border-radius: 20px;
padding: 30px;
margin-bottom: 20px; /* 在这里保持margin,但在flex布局下由 gap 控制间距更佳,此处保留以防万一 */
box-shadow: 0 8px 20px rgba(0,0,0,0.15);
display: flex; flex-direction: column; justify-content: center; align-items: center;
position: relative;
}
/* 秘籍卡片内部装饰 */
.secret-card::after {
content: "SECRET";
position: absolute; top: 20px; right: 20px;
font-size: 60px; font-weight: 900; color: rgba(255,255,255,0.2);
transform: rotate(-15deg); pointer-events: none;
}
.secret-card h3 {
font-size: 32px; margin-bottom: 25px; color: #fff;
text-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.secret-card p {
font-size: 20px; line-height: 1.8; text-align: center; color: #fff;
padding: 0 20px; font-weight: 500;
}
.swipe-hint {
position: absolute; bottom: 20px;
font-size: 12px; color: rgba(255,255,255,0.8);
animation: bounceLeftRight 1.5s infinite;
background: rgba(0,0,0,0.1);
padding: 4px 10px;
border-radius: 20px;
}
@keyframes bounceLeftRight {
0%, 100% { transform: translateX(0); }
50% { transform: translateX(10px); }
}
/* ================= 练习题通用 ================= */
.question-card { padding: 30px 25px; }
.question-header {
border-bottom: 2px dashed #edf2f7; margin-bottom: 25px; padding-bottom: 15px;
display: flex; justify-content: space-between; align-items: center;
}
.score-display { color: #f1c40f; font-weight: 800; font-size: 20px; text-shadow: 0 2px 4px rgba(241, 196, 15, 0.2); }
.question-text { font-size: 22px; font-weight: 700; color: #2d3436; text-align: center; margin-bottom: 30px; }
.option-btn {
background: white; border: 2px solid #edf2f7; color: #555;
padding: 16px; border-radius: 16px; margin-bottom: 12px;
font-size: 18px; font-weight: 600; text-align: center;
transition: all 0.2s; position: relative; overflow: hidden;
}
.option-btn:hover { border-color: #cbd5e0; }
.option-btn.correct { background: #d4edda; border-color: #2ecc71; color: #155724; }
.option-btn.wrong { background: #f8d7da; border-color: #e74c3c; color: #721c24; }
.next-btn, .restart-btn {
width: 100%; padding: 18px; margin-top: 25px;
background: var(--primary-gradient);
color: white; border: none; border-radius: 16px;
font-size: 18px; font-weight: 700; cursor: pointer;
box-shadow: 0 5px 15px rgba(118, 75, 162, 0.3);
transition: transform 0.2s;
}
.next-btn:active, .restart-btn:active { transform: scale(0.98); }
/* 反馈区 */
.feedback-area {
background: #f8f9fa; border-radius: 12px; padding: 20px; margin-top: 20px;
font-size: 15px; line-height: 1.6;
}
/* 动画关键帧 */
@keyframes vibrate {
0%, 100% { transform: translate(0); }
20% { transform: translate(-2px, 2px); }
40% { transform: translate(-2px, -2px); }
60% { transform: translate(2px, 2px); }
80% { transform: translate(2px, -2px); }
}
.vibrate { animation: vibrate 0.3s linear; }
/* 完成页 */
.completion-page { text-align: center; padding-top: 40px; }
.completion-emoji { font-size: 80px; margin-bottom: 20px; display: inline-block; animation: popIn 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275); }
@keyframes popIn { from { transform: scale(0); } to { transform: scale(1); } }
.final-score { font-size: 48px; color: #f39c12; font-weight: 800; margin: 20px 0; }
/* 扩展卡片(折叠) */
.expand-card .card-header { padding: 20px; background: transparent; }
.expand-card .card-content { display: none; padding: 0 20px 20px; border-top: 1px dashed #eee; animation: slideDown 0.3s ease; }
.expand-card.active .card-content { display: block; }
@keyframes slideDown { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } }
</style>
</head>
<body>
<div id="app">
<div class="content-area">
<div v-show="currentPage === 1" class="page intro-page">
<h1 class="page-title">乘法交换律与结合律</h1>
<div class="page-subtitle">凑整思想 · 巧算魔法</div>
<div class="intro-card">
<div class="intro-emoji">📦</div>
<div class="intro-story">
超市新到了一批货,有 <strong>4</strong> 箱牛奶,每箱 <strong>25</strong> 瓶,还有 13 箱果汁。<br><br>
管理员叔叔想快速算出有多少瓶饮料。如果你先算 4x25=100,这道题是不是瞬间变简单了?<br><br>
这就是数学里的<strong>“搬家”</strong>魔法!让我们一起来学习如何让数字找朋友,凑成整十整百!
</div>
<button class="teacher-btn" @click="speakIntro">
<span>🎧</span> 听老师讲解
</button>
</div>
</div>
<div v-show="currentPage === 2" class="page demo-page">
<h2 class="page-title">概念讲解</h2>
<div class="tab-nav">
<div class="tab-item" :class="{active: demoTab === 'direct'}" @click="switchDemoTab('direct')">直接判断</div>
<div class="tab-item" :class="{active: demoTab === 'theory'}" @click="switchDemoTab('theory')">核心原理</div>
</div>
<div v-show="demoTab === 'direct'">
<div class="concept-card">
<div class="concept-header bg-pink">
<span>💡 判断1:找朋友</span>
</div>
<div class="concept-body">
<div class="sub-title" style="color:#e74c3c; font-weight:bold; margin-bottom:10px;">黄金搭档口诀:</div>
<div class="highlight-box">
<div>👀 看到 <strong>25</strong> -> 马上找 <strong>4</strong></div>
<div>👀 看到 <strong>125</strong> -> 马上找 <strong>8</strong></div>
<span class="formula-text">凑整:25×4=100 | 125×8=1000</span>
</div>
</div>
</div>
<div class="concept-card">
<div class="concept-header bg-blue">
<span>💡 判断2:带尾巴</span>
</div>
<div class="concept-body">
<div class="highlight-box">
<div>看到 <strong>50</strong> -> 找 2</div>
<div>看到 <strong>250</strong> -> 找 4</div>
<span class="formula-text">目标:凑成整百、整千!</span>
</div>
</div>
</div>
</div>
<div v-show="demoTab === 'theory'">
<div class="animation-area">
<svg class="svg-container" viewBox="0 0 400 320" id="numberAnimation">
<defs>
<radialGradient id="gradBlue" cx="30%" cy="30%" r="70%">
<stop offset="0%" style="stop-color:#bde6ff;stop-opacity:1" />
<stop offset="100%" style="stop-color:#2980b9;stop-opacity:1" />
</radialGradient>
<radialGradient id="gradPink" cx="30%" cy="30%" r="70%">
<stop offset="0%" style="stop-color:#ffd1d5;stop-opacity:1" />
<stop offset="100%" style="stop-color:#d63031;stop-opacity:1" />
</radialGradient>
<radialGradient id="gradGold" cx="30%" cy="30%" r="70%">
<stop offset="0%" style="stop-color:#ffeaa7;stop-opacity:1" />
<stop offset="100%" style="stop-color:#fdcb6e;stop-opacity:1" />
</radialGradient>
<filter id="dropShadow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur in="SourceAlpha" stdDeviation="3"/>
<feOffset dx="2" dy="2" result="offsetblur"/>
<feComponentTransfer>
<feFuncA type="linear" slope="0.3"/>
</feComponentTransfer>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<g id="numberGroup"></g>
</svg>
<button class="step-btn prev" @click="prevStep" :disabled="directStep === 0">❮</button>
<button class="step-btn next" @click="nextStep" :disabled="directStep === 2">❯</button>
</div>
<div class="concept-card">
<div class="concept-header bg-purple">
<span>🤝 核心原理:握手原理</span>
</div>
<div class="concept-body">
<div class="highlight-box">
<strong>数字搬家特权:</strong><br>
在连乘算式里,数字可以带着前面的符号随便换座位,就像好朋友握手,谁先握都一样。
</div>
</div>
</div>
</div>
</div>
<div v-show="currentPage === 3" class="page explain-page">
<h2 class="page-title">典型例题</h2>
<div class="tab-nav">
<div class="tab-item" :class="{active: explainTab === 'basic'}" @click="explainTab = 'basic'">基础题</div>
<div class="tab-item" :class="{active: explainTab === 'advanced'}" @click="explainTab = 'advanced'">进阶题</div>
<div class="tab-item" :class="{active: explainTab === 'mistake'}" @click="explainTab = 'mistake'">易错题</div>
</div>
<div class="example-list">
<div class="tab-content" v-show="explainTab === 'basic'">
<div class="expand-card" :class="{active: expandedCard === 0}" @click="toggleCard(0)">
<div class="card-header">
<span class="card-title" style="color: #4a90e2; font-weight:800;">📝 例题1:基础搬家</span>
<span class="toggle-icon">▼</span>
</div>
<div class="card-content" @click.stop>
<div><strong>题目:</strong>$25 \times 13 \times 4$</div>
<div><strong>解析:</strong><br>
1. 发现 25 和 4 是好朋友。<br>
2. 交换位置:$25 \times 4 \times 13$。<br>
3. $100 \times 13 = 1300$。
</div>
</div>
</div>
</div>
<div class="tab-content" v-show="explainTab === 'advanced'">
<div class="expand-card" :class="{active: expandedCard === 1}" @click="toggleCard(1)">
<div class="card-header">
<span class="card-title" style="color: #9b59b6; font-weight:800;">🚀 例题2:拆分大法</span>
<span class="toggle-icon">▼</span>
</div>
<div class="card-content" @click.stop>
<div><strong>题目:</strong>$125 \times 32 \times 25$</div>
<div><strong>解析:</strong><br>
1. 32 里面藏着宝贝!拆成 $8 \times 4$。<br>
2. 分配好朋友:$(125 \times 8) \times (4 \times 25)$。<br>
3. $1000 \times 100 = 100000$。
</div>
</div>
</div>
</div>
<div class="tab-content" v-show="explainTab === 'mistake'">
<div class="expand-card" :class="{active: expandedCard === 2}" @click="toggleCard(2)">
<div class="card-header">
<span class="card-title" style="color: #e74c3c; font-weight:800;">⚠️ 例题3:陷阱题</span>
<span class="toggle-icon">▼</span>
</div>
<div class="card-content" @click.stop>
<div><strong>题目:</strong>$25 \times 8$</div>
<div><strong>错解:</strong>1000 ❌</div>
<div><strong>正解:</strong>200 ✅<br>
记住了!$25 \times 4 = 100$,那 8 是两个 4,所以是 200。别背混了!
</div>
</div>
</div>
</div>
</div>
</div>
<div v-show="currentPage === 4" class="page practice-page">
<h2 class="page-title">课内练习</h2>
<div v-if="!practiceCompleted">
<div class="question-card">
<div class="question-header">
<div style="font-weight:bold; color:#7f8c8d;">第 {{currentQuestion + 1}}/{{practiceQuestions.length}} 题</div>
<div class="score-display">{{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,
'vibrate': answered && selectedOption === index && !option.correct
}"
@click="selectOption(index, option.correct)"
:disabled="answered">
{{option.text}}
</button>
</div>
<div v-if="answered" class="feedback-area">
<div v-if="isCorrect" style="color:#27ae60;"><strong>🎉 太棒了!</strong> {{practiceQuestions[currentQuestion].explanation}}</div>
<div v-else style="color:#c0392b;"><strong>💡 解析:</strong> {{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 class="completion-emoji">🎉</div>
<div class="completion-title">练习完成!</div>
<div class="final-score">{{score}}分</div>
<button class="restart-btn" @click="switchPage(5)">挑战奥数题 →</button>
</div>
</div>
<div v-show="currentPage === 5" class="page practice-page">
<h2 class="page-title">奥数挑战 (8题)</h2>
<div v-if="!olympiadCompleted">
<div class="question-card">
<div class="question-header">
<div style="font-weight:bold; color:#7f8c8d;">
第 {{currentOlympiad + 1}}/{{olympiadQuestions.length}} 题
<span class="difficulty-badge" style="background:#e74c3c; color:white; padding:2px 6px; border-radius:4px; font-size:10px; margin-left:5px;">{{olympiadQuestions[currentOlympiad].difficultyText}}</span>
</div>
<div class="score-display">{{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': olympiadAnswered && selectedOlympiadOption === index && !option.correct,
'vibrate': olympiadAnswered && selectedOlympiadOption === index && !option.correct
}"
@click="selectOlympiadOption(index, option.correct)"
:disabled="olympiadAnswered">
{{option.text}}
</button>
</div>
<div v-if="wrongAttempts > 0 && !olympiadAnswered" class="feedback-area" style="background:#fff3cd; color:#856404;">
<strong>💡 提示:</strong> {{ wrongAttempts === 1 ? olympiadQuestions[currentOlympiad].hint1 : olympiadQuestions[currentOlympiad].hint2 }}
</div>
<div v-if="olympiadAnswered" class="feedback-area">
<div v-if="isOlympiadCorrect" style="color:#27ae60;"><strong>🏆 厉害!</strong> {{olympiadQuestions[currentOlympiad].explanation}}</div>
<div v-else style="color:#c0392b;"><strong>📖 解析:</strong> {{olympiadQuestions[currentOlympiad].explanation}}</div>
</div>
<button v-if="olympiadAnswered || wrongAttempts >= 3" class="next-btn" @click="nextOlympiad">
{{currentOlympiad < olympiadQuestions.length - 1 ? '下一题 →' : '查看秘籍 ✓'}}
</button>
</div>
</div>
<div v-else class="completion-page">
<div class="completion-emoji">🏆</div>
<div class="completion-title">奥数挑战完成!</div>
<div class="final-score">{{olympiadScore}}分</div>
<button class="restart-btn" @click="switchPage(6)">领取通关秘籍 →</button>
</div>
</div>
<div v-show="currentPage === 6" class="page secret-page">
<h2 class="page-title">通关秘籍</h2>
<div class="page-subtitle">左右滑动查看更多 👉</div>
<div class="secret-container">
<div class="secret-card">
<h3>✨ 秘籍一</h3>
<p>见 <strong>25</strong> 想 <strong>4</strong></p>
<p>见 <strong>125</strong> 想 <strong>8</strong></p>
<p>见 <strong>5</strong> 想 <strong>2</strong></p>
<div class="swipe-hint">← 滑动查看下一招 →</div>
</div>
<div class="secret-card" style="background: linear-gradient(135deg, #89f7fe 0%, #66a6ff 100%);">
<h3>✨ 秘籍二</h3>
<p>如果好朋友没出现...</p>
<p>就去隔壁的大数里</p>
<p style="font-size:24px; font-weight:bold;">“拆”</p>
<p>一个出来!</p>
<p style="font-size:14px; opacity:0.8;">(例如: 32 = 4 x 8)</p>
<div class="swipe-hint">← 滑动查看下一招 →</div>
</div>
<div class="secret-card" style="background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%);">
<h3>✨ 秘籍三</h3>
<p>数字可以带着符号</p>
<p><strong>随便搬家</strong></p>
<p>谁好算,就先算谁!</p>
<div class="swipe-hint">← 滑动完成学习 →</div>
</div>
</div>
<button class="restart-btn" @click="switchPage(1)">再学一遍 ↺</button>
</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-text">引入</div>
</div>
<div class="nav-item" :class="{active: currentPage === 2}" @click="switchPage(2)">
<div class="nav-icon">📚</div><div class="nav-text">讲解</div>
</div>
<div class="nav-item" :class="{active: currentPage === 3}" @click="switchPage(3)">
<div class="nav-icon">✍️</div><div class="nav-text">例题</div>
</div>
<div class="nav-item" :class="{active: currentPage === 4}" @click="switchPage(4)">
<div class="nav-icon">📝</div><div class="nav-text">练习</div>
</div>
<div class="nav-item" :class="{active: currentPage === 5}" @click="switchPage(5)">
<div class="nav-icon">🚀</div><div class="nav-text">奥数</div>
</div>
<div class="nav-item" :class="{active: currentPage === 6}" @click="switchPage(6)">
<div class="nav-icon">🔑</div><div class="nav-text">秘籍</div>
</div>
</div>
</div>
<script src="https://www.xinghuo.tv/wp-content/themes/xinghuo-tv/assets/js/vue.global.prod.js"></script>
<script src="https://www.xinghuo.tv/wp-content/themes/xinghuo-tv/assets/js/gsap.min.js"></script>
<script src="https://www.xinghuo.tv/wp-content/themes/xinghuo-tv/assets/js/confetti.browser.min.js"></script>
<script>
const { createApp, ref, nextTick } = Vue;
createApp({
setup() {
const currentPage = ref(1);
const demoTab = ref('direct');
const directStep = ref(0);
const explainTab = ref('basic');
const expandedCard = ref(-1);
// 课内练习数据
const practiceQuestions = ref([
{ text: "$25 \\times 19 \\times 4 = ?$", options: [{text: "A. 190", correct: false}, {text: "B. 1900", correct: true}, {text: "C. 7600", correct: false}, {text: "D. 1000", correct: false}], explanation: "25 和 4 是好朋友,先算 25 × 4 = 100,再算 100 × 19 = 1900。" },
{ text: "$125 \\times 7 \\times 8 = ?$", options: [{text: "A. 5600", correct: false}, {text: "B. 7000", correct: true}, {text: "C. 1000", correct: false}, {text: "D. 875", correct: false}], explanation: "125 找 8,先算 125 × 8 = 1000,再算 1000 × 7 = 7000。" },
{ text: "$50 \\times 37 \\times 2 = ?$", options: [{text: "A. 370", correct: false}, {text: "B. 7400", correct: false}, {text: "C. 3700", correct: true}, {text: "D. 3750", correct: false}], explanation: "50 找 2 凑成 100,先算 50 × 2 = 100,再算 100 × 37 = 3700。" },
{ text: "$25 \\times 32 = ?$", options: [{text: "A. 800", correct: true}, {text: "B. 600", correct: false}, {text: "C. 750", correct: false}, {text: "D. 1000", correct: false}], explanation: "把 32 拆成 4 × 8,先算 25 × 4 = 100,再算 100 × 8 = 800。" },
{ text: "$125 \\times 24 = ?$", options: [{text: "A. 2400", correct: false}, {text: "B. 3000", correct: true}, {text: "C. 2000", correct: false}, {text: "D. 1250", correct: false}], explanation: "把 24 拆成 8 × 3,先算 125 × 8 = 1000,再算 1000 × 3 = 3000。" },
{ text: "$5 \\times 17 \\times 4 = ?$", options: [{text: "A. 340", correct: true}, {text: "B. 3400", correct: false}, {text: "C. 85", correct: false}, {text: "D. 170", correct: false}], explanation: "5 和 4 凑成 20,先算 5 × 4 = 20,再算 20 × 17 = 340。" },
{ text: "$125 \\times 16 = ?$", options: [{text: "A. 1600", correct: false}, {text: "B. 2000", correct: true}, {text: "C. 1000", correct: false}, {text: "D. 1250", correct: false}], explanation: "把 16 拆成 8 × 2,先算 125 × 8 = 1000,再算 1000 × 2 = 2000。" },
{ text: "$250 \\times 13 \\times 4 = ?$", options: [{text: "A. 1300", correct: false}, {text: "B. 5200", correct: false}, {text: "C. 13000", correct: true}, {text: "D. 1000", correct: false}], explanation: "250 乘以 4 等于 1000,先算 250 × 4 = 1000,再算 1000 × 13 = 13000。" }
]);
const currentQuestion = ref(0);
const score = ref(0);
const answered = ref(false);
const selectedOption = ref(null);
const isCorrect = ref(false);
const practiceCompleted = ref(false);
// 升级后的奥数题数据 (8题)
const olympiadQuestions = ref([
{
text: "1. 计算 $125 \\times 32 \\times 5$",
options: [{text: "A. 20000", correct: true}, {text: "B. 16000", correct: false}, {text: "C. 10000", correct: false}, {text: "D. 5000", correct: false}],
difficulty: 3, difficultyText: "3星",
explanation: "拆分大法!把 32 拆成 $8 \\times 4$。$125 \\times 8 = 1000$。$4 \\times 5 = 20$。$1000 \\times 20 = 20000$。",
hint1: "32 可以给两边分。", hint2: "给 125 一个 8,给 5 一个 4。"
},
{
text: "2. 计算 $88 \\times 125$",
options: [{text: "A. 10000", correct: false}, {text: "B. 11000", correct: true}, {text: "C. 12500", correct: false}, {text: "D. 8800", correct: false}],
difficulty: 3, difficultyText: "3星",
explanation: "看到 125 找 8!把 88 拆成 $11 \\times 8$。然后 $125 \\times 8 = 1000$,最后 $11 \\times 1000 = 11000$。",
hint1: "125 的好朋友是谁?", hint2: "把 88 拆成 11 乘以几?"
},
{
text: "3. 计算 $35 \\times 18$",
options: [{text: "A. 630", correct: true}, {text: "B. 540", correct: false}, {text: "C. 600", correct: false}, {text: "D. 700", correct: false}],
difficulty: 4, difficultyText: "4星",
explanation: "凑整变形!把 35 看作 $5 \\times 7$,把 18 看作 $2 \\times 9$。$5 \\times 2 = 10$。剩下的 $7 \\times 9 = 63$。$63 \\times 10 = 630$。",
hint1: "35 里面有 5,18 里面有 2。", hint2: "先算 5 × 2 = 10,再算剩下的。"
},
{
text: "4. 计算 $4800 \\div 25 \\div 4$",
options: [{text: "A. 48", correct: true}, {text: "B. 480", correct: false}, {text: "C. 1200", correct: false}, {text: "D. 240", correct: false}],
difficulty: 4, difficultyText: "4星",
explanation: "连续除以两个数,等于除以这两个数的积。$4800 \\div (25 \\times 4) = 4800 \\div 100 = 48$。",
hint1: "不要一个一个除,太慢了。", hint2: "先把 25 和 4 乘起来。"
},
{
text: "5. 计算 $72 \\times 125$",
options: [{text: "A. 900", correct: false}, {text: "B. 8000", correct: false}, {text: "C. 9000", correct: true}, {text: "D. 7200", correct: false}],
difficulty: 4, difficultyText: "4星",
explanation: "把 72 拆成 $9 \\times 8$。$125 \\times 8 = 1000$。$1000 \\times 9 = 9000$。",
hint1: "72 是 8 的几倍?", hint2: "把 8 送给 125。"
},
{
text: `6. 如图,一个长方形花坛长125米,宽8米。求它的面积。<br>
<svg viewBox="0 0 300 120" style="width: 100%; max-width: 260px; margin: 10px auto; display: block; filter: drop-shadow(2px 2px 2px rgba(0,0,0,0.1));">
<defs>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto" markerUnits="strokeWidth"><path d="M0,0 L0,6 L9,3 z" fill="#666" /></marker>
</defs>
<rect x="50" y="30" width="200" height="60" fill="#e3f2fd" stroke="#4a90e2" stroke-width="2"/>
<text x="150" y="25" font-size="14" fill="#666" text-anchor="middle">125 米</text>
<text x="260" y="65" font-size="14" fill="#666" text-anchor="middle">8 米</text>
</svg>`,
options: [{text: "A. 100 平方米", correct: false}, {text: "B. 1000 平方米", correct: true}, {text: "C. 1258 平方米", correct: false}, {text: "D. 800 平方米", correct: false}],
difficulty: 3, difficultyText: "3星",
explanation: "长方形面积 = 长 × 宽。$125 \\times 8 = 1000$ 平方米。看到图形也要想到凑整哦!",
hint1: "面积公式是长乘以宽。", hint2: "125 × 8 是多少?"
},
{
text: "7. 计算 $99 \\times 38 + 38$",
options: [{text: "A. 380", correct: false}, {text: "B. 3800", correct: true}, {text: "C. 3900", correct: false}, {text: "D. 4000", correct: false}],
difficulty: 5, difficultyText: "5星",
explanation: "乘法分配律的逆运算。这里有一个隐藏的 '×1'。原式 = $38 \\times (99 + 1) = 38 \\times 100 = 3800$。",
hint1: "后面的 38 可以看作 38 × 1。", hint2: "把 38 提取出来。"
},
{
text: "8. 计算 $(40 + 4) \\times 25$",
options: [{text: "A. 1100", correct: true}, {text: "B. 1000", correct: false}, {text: "C. 1200", correct: false}, {text: "D. 10000", correct: false}],
difficulty: 5, difficultyText: "5星",
explanation: "乘法分配律。$40 \\times 25 + 4 \\times 25$。$1000 + 100 = 1100$。",
hint1: "25 要分别和括号里的数相乘。", hint2: "40 个 25 加上 4 个 25。"
}
]);
const currentOlympiad = ref(0);
const olympiadScore = ref(0);
const olympiadAnswered = ref(false);
const selectedOlympiadOption = ref(null);
const isOlympiadCorrect = ref(false);
const wrongAttempts = ref(0);
const olympiadCompleted = ref(false);
// 语音逻辑
const speak = (text) => {
const isWeChat = /MicroMessenger/i.test(navigator.userAgent);
if (isWeChat) {
let audio = document.getElementById('tts-audio');
if (!audio) {
audio = document.createElement('audio');
audio.id = 'tts-audio';
audio.style.display = 'none';
document.body.appendChild(audio);
}
const url = `https://www.xinghuo.tv/wp-content/themes/xinghuo-tv/tts.php?text=${encodeURIComponent(text)}&t=${Date.now()}`;
audio.src = url;
audio.play().catch(err => console.log('语音播放失败:', err));
} else {
if (window.speechSynthesis) {
window.speechSynthesis.cancel();
const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = 'zh-CN';
utterance.rate = 0.9;
window.speechSynthesis.speak(utterance);
}
}
};
const stopSpeak = () => {
const isWeChat = /MicroMessenger/i.test(navigator.userAgent);
if (isWeChat) {
const audio = document.getElementById('tts-audio');
if (audio) { audio.pause(); audio.currentTime = 0; }
} else {
if (window.speechSynthesis) window.speechSynthesis.cancel();
}
};
const speakIntro = () => {
const introText = "超市新到了一批货,有4箱牛奶,每箱25瓶,还有13箱果汁。管理员叔叔想计算一共有多少瓶饮料。他可以先把牛奶和果汁加起来再乘吗?不对,它们的单位不一样!但是他灵机一动:先把4箱牛奶算出来正好是100瓶,再算100瓶牛奶加13箱果汁?好像还是不对。别急!其实可以换个思路:让数字搬家找朋友,凑成整十整百再算!这就是我们今天要学的乘法交换律和结合律的凑整思想。";
speak(introText);
};
const switchPage = (page) => {
stopSpeak();
currentPage.value = page;
nextTick(() => {
const pageEl = document.querySelector(`[v-show="currentPage === ${page}"]`);
if (pageEl) {
pageEl.classList.add('page-enter');
setTimeout(() => pageEl.classList.remove('page-enter'), 500);
}
});
if (page === 2) { directStep.value = 0; runDirectAnimation(); }
if (page === 4 && !practiceCompleted.value) { answered.value = false; selectedOption.value = null; }
if (page === 5 && !olympiadCompleted.value) { olympiadAnswered.value = false; selectedOlympiadOption.value = null; wrongAttempts.value = 0; }
};
const switchDemoTab = (tab) => {
stopSpeak();
demoTab.value = tab;
if (tab === 'theory') {
directStep.value = 0;
nextTick(() => { runDirectAnimation(); });
}
};
const prevStep = () => { if (directStep.value > 0) { directStep.value--; runDirectAnimation(); } };
const nextStep = () => { if (directStep.value < 2) { directStep.value++; runDirectAnimation(); } };
// === SVG 动画渲染 ===
const runDirectAnimation = () => {
const svgNS = "http://www.w3.org/2000/svg";
const container = document.getElementById('numberGroup');
if (!container) return;
while (container.firstChild) container.removeChild(container.firstChild);
const tl = gsap.timeline();
// 辅助函数:创建 3D 球体
const create3DBall = (x, y, r, fillId, textVal, fontSize = "24") => {
const g = document.createElementNS(svgNS, "g");
// 阴影
const shadow = document.createElementNS(svgNS, "circle");
shadow.setAttribute("cx", x); shadow.setAttribute("cy", y); shadow.setAttribute("r", r);
shadow.setAttribute("fill", `url(#${fillId})`);
shadow.setAttribute("filter", "url(#dropShadow)");
g.appendChild(shadow);
// 文字
const text = document.createElementNS(svgNS, "text");
text.setAttribute("x", x); text.setAttribute("y", y);
text.setAttribute("text-anchor", "middle");
text.setAttribute("dominant-baseline", "central");
text.setAttribute("font-size", fontSize);
text.setAttribute("font-weight", "800");
text.setAttribute("fill", "white");
text.setAttribute("style", "text-shadow: 1px 1px 2px rgba(0,0,0,0.3);");
text.textContent = textVal;
g.appendChild(text);
return g;
};
if (directStep.value === 0) {
const b4 = create3DBall(100, 160, 40, "gradBlue", "4");
const b13 = create3DBall(200, 160, 40, "gradPink", "13");
const b25 = create3DBall(300, 160, 40, "gradBlue", "25");
container.appendChild(b4);
container.appendChild(b13);
container.appendChild(b25);
gsap.from(b4, { scale: 0, y: -50, duration: 0.6, ease: "back.out(1.7)", delay: 0 });
gsap.from(b13, { scale: 0, y: -50, duration: 0.6, ease: "back.out(1.7)", delay: 0.2 });
gsap.from(b25, { scale: 0, y: -50, duration: 0.6, ease: "back.out(1.7)", delay: 0.4 });
}
else if (directStep.value === 1) {
const b4 = create3DBall(100, 160, 40, "gradBlue", "4");
const b25 = create3DBall(300, 160, 40, "gradBlue", "25");
const b13 = create3DBall(350, 160, 35, "gradPink", "13");
const b100 = create3DBall(200, 160, 50, "gradGold", "100", "30");
container.appendChild(b4);
container.appendChild(b25);
container.appendChild(b13);
tl.to(b4, { x: 100, duration: 0.8, ease: "power2.inOut" })
.to(b25, { x: -100, duration: 0.8, ease: "power2.inOut" }, "<")
.to([b4, b25], { scale: 0, opacity: 0, duration: 0.3 })
.add(() => container.appendChild(b100))
.from(b100, { scale: 0, rotation: 180, duration: 0.6, ease: "elastic.out(1, 0.5)" });
}
else if (directStep.value === 2) {
const b100 = create3DBall(100, 160, 45, "gradGold", "100");
const b13 = create3DBall(300, 160, 45, "gradPink", "13");
const bFinal = create3DBall(200, 160, 60, "gradBlue", "1300", "34");
container.appendChild(b100);
container.appendChild(b13);
tl.to(b100, { x: 100, duration: 0.8, ease: "power2.inOut" })
.to(b13, { x: -100, duration: 0.8, ease: "power2.inOut" }, "<")
.to([b100, b13], { scale: 0, opacity: 0, duration: 0.3 })
.add(() => container.appendChild(bFinal))
.from(bFinal, { scale: 0, y: 20, duration: 0.6, ease: "elastic.out(1, 0.5)" });
}
};
const toggleCard = (index) => { expandedCard.value = expandedCard.value === index ? -1 : index; };
const selectOption = (index, correct) => {
if (answered.value) return;
answered.value = true; selectedOption.value = index; isCorrect.value = correct;
if (correct) { score.value += 10; confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); }
};
const nextQuestion = () => {
if (currentQuestion.value < practiceQuestions.value.length - 1) { currentQuestion.value++; answered.value = false; selectedOption.value = null; }
else { practiceCompleted.value = true; }
};
const selectOlympiadOption = (index, correct) => {
if (olympiadAnswered.value) return;
selectedOlympiadOption.value = index;
if (correct) {
isOlympiadCorrect.value = true; olympiadAnswered.value = true; olympiadScore.value += 30;
confetti({ particleCount: 150, spread: 80, origin: { y: 0.6 } });
} else {
wrongAttempts.value++;
if (wrongAttempts.value >= 3) { olympiadAnswered.value = true; isOlympiadCorrect.value = false; }
}
};
const nextOlympiad = () => {
if (currentOlympiad.value < olympiadQuestions.value.length - 1) { currentOlympiad.value++; olympiadAnswered.value = false; selectedOlympiadOption.value = null; wrongAttempts.value = 0; }
else { olympiadCompleted.value = true; }
};
nextTick(() => { if (demoTab.value === 'theory') runDirectAnimation(); });
return {
currentPage, demoTab, directStep, explainTab, expandedCard,
practiceQuestions, currentQuestion, score, answered, selectedOption, isCorrect, practiceCompleted,
olympiadQuestions, currentOlympiad, olympiadScore, olympiadAnswered, selectedOlympiadOption, isOlympiadCorrect, wrongAttempts, olympiadCompleted,
speakIntro, switchPage, switchDemoTab, prevStep, nextStep,
toggleCard, selectOption, nextQuestion, selectOlympiadOption, nextOlympiad
};
}
}).mount('#app');
</script>
</body>
</html>
💡 这段代码完全由 AI 生成。
登录后可复制完整代码