<!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>1.3 提取公因数(基础篇)</title>
<style>
/* ==================== 基础重置与全局样式 ==================== */
* { 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: #fdfbf7; /* 暖白底色 */
background-image:
radial-gradient(#e6e6e6 1.5px, transparent 1.5px),
radial-gradient(#e6e6e6 1.5px, transparent 1.5px);
background-size: 20px 20px;
background-position: 0 0, 10px 10px;
overflow: hidden; height: 100vh;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
#app { height: 100vh; max-width: 480px; margin: 0 auto; background: rgba(255,255,255,0.95); display: flex; flex-direction: column; position: relative; box-shadow: 0 0 30px rgba(0,0,0,0.05); }
.content-area { flex: 1; overflow-y: auto; padding-bottom: 90px; scroll-behavior: smooth; }
/* ==================== 组件样式 ==================== */
/* 1. 顶部标题栏 */
.page-header {
text-align: center; padding: 30px 20px 20px;
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
color: #333; border-radius: 0 0 30px 30px; margin-bottom: 25px;
box-shadow: 0 10px 25px rgba(168, 237, 234, 0.4);
position: relative; z-index: 10;
}
.page-header h1 { margin: 0; font-size: 26px; font-weight: 800; color: #444; letter-spacing: 1px; }
.page-header h2 { margin: 8px 0 0; font-size: 15px; color: #666; font-weight: 500; opacity: 0.8; }
/* 2. 卡片通用样式 (微投影 + 圆润) */
.card-base {
background: white; border-radius: 20px;
box-shadow: 0 8px 24px rgba(149, 157, 165, 0.15); /* 微投影 */
margin-bottom: 20px; overflow: hidden;
transition: transform 0.2s ease;
}
/* 3. 引入页 */
.intro-page { padding: 20px; text-align: center; }
.intro-emoji { font-size: 72px; margin-bottom: 10px; animation: float 3s ease-in-out infinite; }
.intro-story {
padding: 30px; text-align: left; line-height: 1.8; font-size: 17px; color: #555;
}
.intro-story strong { color: #ff6b6b; font-weight: 700; background: linear-gradient(120deg, transparent 60%, #ffd1d1 60%); }
.speak-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white; border: none; padding: 16px 40px; border-radius: 50px;
font-size: 17px; font-weight: bold; cursor: pointer;
margin-top: 10px; box-shadow: 0 10px 20px rgba(118, 75, 162, 0.3);
transition: transform 0.1s;
}
.speak-btn:active { transform: scale(0.96); }
/* 4. Tab 导航 */
.tab-nav {
display: flex; background: #f0f2f5; border-radius: 16px; margin: 15px 20px;
padding: 5px; box-shadow: inset 0 2px 5px rgba(0,0,0,0.03);
}
.tab-item {
flex: 1; text-align: center; padding: 12px; border-radius: 12px;
cursor: pointer; font-weight: bold; color: #888; transition: all 0.3s; font-size: 15px;
}
.tab-item.active {
background: white; color: #667eea;
box-shadow: 0 4px 12px rgba(0,0,0,0.08); transform: scale(1.02);
}
/* 5. 动画演示区 */
.demo-section { padding: 0 20px; }
.animation-area {
background: white;
border-radius: 24px; padding: 10px; height: 320px; position: relative;
box-shadow: inset 0 0 20px rgba(0,0,0,0.02);
border: 1px solid #f0f0f0;
}
.step-label { text-align: center; margin-top: 15px; color: #999; font-size: 14px; font-weight: 500; }
/* 6. 概念卡片 */
.concept-card {
background: white; border-radius: 20px; overflow: hidden;
box-shadow: 0 8px 24px rgba(149, 157, 165, 0.15); margin-bottom: 20px;
}
.concept-header {
padding: 18px 25px; color: white; font-weight: bold; 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, #a18cd1 0%, #fbc2eb 100%); }
.bg-blue { background: linear-gradient(135deg, #89f7fe 0%, #66a6ff 100%); }
.concept-body { padding: 25px; }
.highlight-box {
background: #fffbf0; border-left: 5px solid #ffc107;
padding: 18px; border-radius: 8px; margin-top: 10px;
color: #5d4037; font-size: 16px; line-height: 1.6;
}
.formula-text { font-weight: bold; color: #e67e22; margin-top: 8px; display: block; font-size: 18px;}
/* 7. 例题折叠卡片 */
.example-list { padding: 0 20px; }
.expand-card {
background: white; border-radius: 20px; margin-bottom: 15px;
box-shadow: 0 6px 18px rgba(0,0,0,0.06); overflow: hidden;
border: 1px solid rgba(0,0,0,0.02);
}
.card-header { padding: 20px 25px; display: flex; justify-content: space-between; align-items: center; cursor: pointer; }
.card-content { padding: 0 25px 25px; border-top: 1px dashed #eee; display: none; background: #fafafa; }
.expand-card.active .card-content { display: block; animation: slideDown 0.3s ease-out; }
.expand-card.active .toggle-icon { transform: rotate(180deg); }
.card-content div { margin-top: 12px; line-height: 1.6; font-size: 16px; color: #555; }
/* 8. 练习/挑战页 */
.practice-page { padding: 20px; }
.question-card {
background: white; border-radius: 24px; padding: 30px 25px;
box-shadow: 0 10px 30px rgba(0,0,0,0.08);
position: relative;
}
.question-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 25px; padding-bottom: 15px; border-bottom: 2px solid #f5f5f5; }
.question-number { font-weight: 800; color: #667eea; font-size: 16px; letter-spacing: 0.5px; }
.score-display { font-size: 18px; color: #ff9800; font-weight: 800; }
.question-text { font-size: 22px; line-height: 1.4; color: #333; margin-bottom: 30px; text-align: center; font-weight: bold; font-family: "Georgia", serif; }
.options-container { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; }
.option-btn {
padding: 20px 10px; border: 2px solid #f0f0f0; border-radius: 16px;
background: #fff; font-size: 18px; cursor: pointer;
transition: all 0.2s; font-weight: 600; color: #555;
box-shadow: 0 4px 0 #f0f0f0; /* 3D按钮效果 */
}
.option-btn:active { box-shadow: 0 0 0 #f0f0f0; transform: translateY(4px); }
.option-btn.correct { background: #d4edda; border-color: #28a745; color: #155724; box-shadow: 0 4px 0 #28a745; }
.option-btn.wrong { background: #f8d7da; border-color: #dc3545; color: #721c24; box-shadow: 0 4px 0 #dc3545; }
.feedback-area { margin-top: 25px; padding: 20px; border-radius: 16px; font-size: 16px; line-height: 1.6; }
.feedback-correct { background: #e8f5e9; border: 1px solid #c3e6cb; color: #2e7d32; }
.feedback-wrong { background: #ffebee; border: 1px solid #ffcdd2; color: #c62828; }
.next-btn {
width: 100%; padding: 18px; margin-top: 20px;
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
color: white; border: none; border-radius: 16px; font-size: 18px; font-weight: bold;
box-shadow: 0 8px 20px rgba(79, 172, 254, 0.4);
}
/* 9. 秘籍卡片 (左右滑动容器) */
.secret-page { overflow: hidden; height: 100%; display: flex; flex-direction: column; }
.secret-container {
flex: 1; display: flex;
overflow-x: auto; scroll-snap-type: x mandatory;
padding: 10px 20px 40px; gap: 20px;
align-items: center; /* 垂直居中 */
}
.secret-card {
flex: 0 0 100%; /* 宽度占满容器 */
scroll-snap-align: center;
height: 480px; /* 固定高度确保统一 */
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
border-radius: 30px; padding: 40px 30px;
box-shadow: 0 15px 35px rgba(0,0,0,0.15);
display: flex; flex-direction: column; justify-content: center; align-items: center;
position: relative;
}
.secret-card h3 { font-size: 28px; color: #333; margin: 0 0 30px; text-shadow: 0 2px 0 rgba(255,255,255,0.5); }
.secret-content { font-size: 20px; line-height: 1.8; color: #444; text-align: center; font-weight: 500; }
.swipe-hint { text-align: center; color: #aaa; font-size: 12px; margin-bottom: 10px; }
/* 10. 底部导航 */
.bottom-nav {
position: fixed; bottom: 0; left: 50%; transform: translateX(-50%);
width: 100%; max-width: 480px; height: 80px;
background: rgba(255,255,255,0.98);
border-top: 1px solid rgba(0,0,0,0.05);
display: flex; justify-content: space-around; align-items: center; z-index: 1000;
backdrop-filter: blur(10px);
padding-bottom: 15px; /* 适配 iPhone 底部条 */
box-shadow: 0 -5px 20px rgba(0,0,0,0.03);
}
.nav-item { flex: 1; display: flex; flex-direction: column; align-items: center; cursor: pointer; transition: transform 0.2s; opacity: 0.6; }
.nav-item.active { opacity: 1; transform: translateY(-5px); }
.nav-item.active span:first-child { filter: drop-shadow(0 4px 6px rgba(102, 126, 234, 0.4)); }
/* 动画关键帧 */
@keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-15px); } }
@keyframes slideDown { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } }
.page-enter { animation: pageFadeIn 0.4s cubic-bezier(0.2, 0.8, 0.2, 1); }
@keyframes pageFadeIn { from { opacity: 0; transform: translateX(20px); } to { opacity: 1; transform: translateX(0); } }
/* 汉堡包 */
.hamburger-demo { text-align: center; margin-top: 20px; font-size: 18px; background: #fff; padding: 15px; border-radius: 12px; border: 1px dashed #ddd; }
.difficulty-hard { background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%); color: white; padding: 3px 10px; border-radius: 20px; font-size: 12px; font-weight: bold; margin-left: 10px; box-shadow: 0 2px 5px rgba(255, 154, 158, 0.4); }
</style>
</head>
<body>
<div id="app">
<div class="content-area">
<div v-show="currentPage === 1" class="intro-page">
<div class="intro-emoji">🍬</div>
<div class="intro-story card-base">
<strong>分糖果的小秘密</strong><br><br>
小明有 <strong>5 袋糖</strong>,每袋有 8 颗。<br>
小红有 <strong>3 袋糖</strong>,每袋也是 8 颗。<br><br>
如果他们一袋一袋数:5×8 + 3×8,要算两次乘法,太麻烦啦!<br><br>
聪明的小朋友会发现,他们都有“每袋 8 颗”这个<strong>双胞胎数字</strong>!<br><br>
只要把 8 这个双胞胎“抓”出来,再把袋数抱团(5+3=8袋),一下子就能算出总糖数:<br>
<div style="text-align:center; margin-top:10px; font-size: 20px; color:#667eea; font-weight:bold;">8 × 8 = 64 颗</div><br>
这个巧妙的方法,就叫<strong>提取公因数</strong>!
</div>
<button class="speak-btn" @click="speakIntro">
🔊 听老师讲解
</button>
</div>
<div v-show="currentPage === 2" class="demo-page">
<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 class="demo-section" 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="font-weight:bold; color:#555;">如何判断?</div>
<div class="highlight-box">
算式形如 $A \times \mathbf{C} + B \times \mathbf{C}$<br>
-> C 出现了两次,C 就是公因数。
<span class="formula-text">口诀:双胞胎数字,抓出来只写一次!</span>
</div>
</div>
</div>
<div class="concept-card">
<div class="concept-header bg-blue">
<span>🎯 判断2:检查抱团结果</span>
</div>
<div class="concept-body">
<div class="sub-title" style="font-weight:bold; color:#555;">提出来之后呢?</div>
<div class="highlight-box">
剩下的 A 和 B 加起来是不是整十整百?<br>
-> 如果是,那必须用提取公因数!
<span class="formula-text">目标:凑整,让计算变简单!</span>
</div>
</div>
</div>
</div>
<div class="demo-section" v-show="demoTab === 'theory'">
<div class="concept-card">
<div class="concept-header bg-purple" style="cursor: pointer;" @click="runDirectAnimation">
<span>📺 动画演示:38×5 + 62×5</span>
<span v-if="directStep === 0">▶️</span>
<span v-else>🔄</span>
</div>
<div class="concept-body" style="padding: 10px;">
<div class="animation-area">
<svg class="svg-container" viewBox="0 0 300 250" id="demo-svg" style="width:100%; height:100%;">
<defs>
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="2" dy="4" stdDeviation="3" flood-color="#000" flood-opacity="0.15"/>
</filter>
</defs>
<g id="bubble1">
<rect x="30" y="80" width="90" height="50" rx="10" fill="#a8edea" filter="url(#shadow)"/>
<text x="75" y="112" text-anchor="middle" font-size="20" font-weight="bold" fill="#333">38×5</text>
</g>
<g id="bubble2">
<rect x="180" y="80" width="90" height="50" rx="10" fill="#fed6e3" filter="url(#shadow)"/>
<text x="225" y="112" text-anchor="middle" font-size="20" font-weight="bold" fill="#333">62×5</text>
</g>
<g id="brackets" opacity="0" stroke="#666" stroke-width="3" fill="none">
<path d="M40,160 Q20,160 20,185 Q20,210 40,210" /> <path d="M180,160 Q200,160 200,185 Q200,210 180,210" /> </g>
<g id="jumping5" opacity="0">
<circle cx="250" cy="185" r="25" fill="#ffc107" filter="url(#shadow)"/>
<text x="250" y="192" text-anchor="middle" font-size="28" font-weight="bold" fill="white">× 5</text>
</g>
<g id="group38" opacity="0">
<text x="65" y="192" text-anchor="middle" font-size="24" font-weight="bold" fill="#009688">38</text>
</g>
<g id="plusSign" opacity="0">
<text x="110" y="192" text-anchor="middle" font-size="24" font-weight="bold" fill="#666">+</text>
</g>
<g id="group62" opacity="0">
<text x="155" y="192" text-anchor="middle" font-size="24" font-weight="bold" fill="#E91E63">62</text>
</g>
<g id="merged100" opacity="0">
<rect x="40" y="160" width="140" height="50" rx="10" fill="linear-gradient(135deg, #667eea, #764ba2)" filter="url(#shadow)"/>
<text x="110" y="195" text-anchor="middle" font-size="28" font-weight="bold" fill="white">100</text>
</g>
<g id="finalFormula" opacity="0">
<text x="150" y="240" text-anchor="middle" font-size="22" font-weight="bold" fill="#333">100 × 5 = 500</text>
</g>
</svg>
</div>
<div class="step-label">👆 点击上方标题播放动画</div>
</div>
</div>
<div class="concept-card">
<div class="concept-header bg-purple">
<span>🍔 核心原理:套餐法</span>
</div>
<div class="concept-body">
<div class="highlight-box">
<strong>例子:</strong>买 3 个汉堡,又买 7 个汉堡。<br>
不用算两次钱,一共买了 $3+7=10$ 个汉堡。<br>
<strong>原理:</strong>同样的单价,只算总数量!
</div>
<div class="hamburger-demo">
🍔×3 + 🍔×7 = 🍔×(3+7) = 🍔×10
</div>
</div>
</div>
</div>
</div>
<div v-show="currentPage === 3" class="explain-page">
<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:bold;">📝 例题1:基础提取</span>
<span class="toggle-icon">▼</span>
</div>
<div class="card-content" @click.stop>
<div><strong>题目:</strong>$24 \times 2 + 76 \times 2$</div>
<div><strong>答案:</strong>200</div>
<div><strong>解析:</strong><br>1. 发现双胞胎 2。<br>2. 提出来:$(24 + 76) \times 2$。<br>3. 计算:$100 \times 2 = 200$。</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:bold;">🚀 例题2:位置不同</span>
<span class="toggle-icon">▼</span>
</div>
<div class="card-content" @click.stop>
<div><strong>题目:</strong>$48 \times 7 + 7 \times 52$</div>
<div><strong>答案:</strong>700</div>
<div><strong>解析:</strong>虽然 7 一个在前面,一个在后面,但乘法交换律告诉我们它们是一样的!<br>直接提取:$(48 + 52) \times 7 = 700$。</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:bold;">⚠️ 例题3:隐身衣</span>
<span class="toggle-icon">▼</span>
</div>
<div class="card-content" @click.stop>
<div><strong>题目:</strong>$99 \times 23 + 23$</div>
<div><strong>答案:</strong>2300</div>
<div><strong>解析:</strong>后面的 23 没带乘数?<br>千万别忘了给它穿上隐身衣:$\times 1$。<br>$(99 + 1) \times 23 = 100 \times 23 = 2300$。</div>
</div>
</div>
</div>
</div>
</div>
<div v-show="currentPage === 4" class="practice-page">
<div v-if="!practiceCompleted">
<div class="question-card">
<div class="question-header">
<div class="question-number">第 {{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}" @click="selectOption(index, option.correct)" :disabled="answered">{{option.text}}</button>
</div>
<div v-if="answered" class="feedback-area" :class="isCorrect ? 'feedback-correct' : 'feedback-wrong'">
<div v-if="isCorrect"><strong>🎉 太棒了!</strong><br>{{practiceQuestions[currentQuestion].explanation}}</div>
<div v-else><strong>💡 让我们一起分析:</strong><br>{{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" style="text-align: center; padding-top: 50px;">
<div style="font-size: 80px;">🎊</div>
<h2 style="color:#333; margin:20px 0;">课内练习完成!</h2>
<div style="font-size: 40px; color:#ff9800; font-weight:bold; margin-bottom:40px;">{{score}}分</div>
<button class="speak-btn" @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 #fed6e3;">
<div class="question-header">
<div><span class="question-number">第 {{currentOlympiad + 1}}/{{olympiadQuestions.length}} 题</span><span class="difficulty-badge difficulty-hard">奥数</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}" @click="selectOlympiadOption(index, option.correct)" :disabled="olympiadAnswered || wrongAttempts >= 3">{{option.text}}</button>
</div>
<div v-if="wrongAttempts > 0 && !olympiadAnswered" style="margin-top: 15px;">
<div class="highlight-box" v-if="wrongAttempts === 1"><strong>💡 提示1:</strong> {{olympiadQuestions[currentOlympiad].hint1}}</div>
<div class="highlight-box" v-if="wrongAttempts === 2"><strong>💡 提示2:</strong> {{olympiadQuestions[currentOlympiad].hint2}}</div>
</div>
<div v-if="olympiadAnswered || wrongAttempts >= 3" class="feedback-area" :class="isOlympiadCorrect ? 'feedback-correct' : 'feedback-wrong'">
<div v-if="isOlympiadCorrect"><strong>🏆 厉害!</strong><br>{{olympiadQuestions[currentOlympiad].explanation}}</div>
<div v-else><strong>📖 详细解析:</strong><br>{{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" style="text-align: center; padding-top: 50px;">
<div style="font-size: 80px;">🏆</div>
<h2 style="color:#333; margin:20px 0;">奥数挑战完成!</h2>
<div style="font-size: 40px; color:#ff9800; font-weight:bold; margin-bottom:40px;">{{olympiadScore}}分</div>
<button class="speak-btn" @click="switchPage(6)">查看通关秘籍 →</button>
</div>
</div>
<div v-show="currentPage === 6" class="secret-page">
<div class="swipe-hint">← 左右滑动查看更多秘籍 →</div>
<div class="secret-container">
<div class="secret-card">
<h3>秘籍 1:火眼金睛</h3>
<div class="secret-content">
见到 `+ 23` 这种光杆司令,<br>
马上给它穿上隐身衣 `× 1`。<br><br>
<span style="font-size: 24px;">👀</span>
</div>
</div>
<div class="secret-card">
<h3>秘籍 2:自动检测</h3>
<div class="secret-content">
提取公因数后,<br>
括号里的数通常能<br>
<strong>凑成整十整百</strong>。<br>
如果凑不成,可能提错了!<br><br>
<span style="font-size: 24px;">🤖</span>
</div>
</div>
<div class="secret-card">
<h3>秘籍 3:抓大放小</h3>
<div class="secret-content">
如果有三个数:<br>
$A \times C + B \times C - D \times C$<br>
可以直接一次性把 C 抓出来!<br>
$(A + B - D) \times C$<br><br>
<span style="font-size: 24px;">🖐️</span>
</div>
</div>
<div class="secret-card" style="background: white; border: 2px solid #a8edea;">
<h3 style="color: #667eea;">课程结束</h3>
<div class="secret-content">
恭喜你掌握了提取公因数!<br>
下次计算会更快哦!
</div>
<button class="speak-btn" @click="switchPage(1)">再学一遍 ↺</button>
</div>
</div>
</div>
</div>
<div class="bottom-nav">
<div class="nav-item" :class="{active: currentPage === 1}" @click="switchPage(1)">
<span style="font-size: 22px;">🍬</span><span style="font-size: 11px;">引入</span>
</div>
<div class="nav-item" :class="{active: currentPage === 2}" @click="switchPage(2)">
<span style="font-size: 22px;">📚</span><span style="font-size: 11px;">讲解</span>
</div>
<div class="nav-item" :class="{active: currentPage === 3}" @click="switchPage(3)">
<span style="font-size: 22px;">💡</span><span style="font-size: 11px;">例题</span>
</div>
<div class="nav-item" :class="{active: currentPage === 4}" @click="switchPage(4)">
<span style="font-size: 22px;">✏️</span><span style="font-size: 11px;">练习</span>
</div>
<div class="nav-item" :class="{active: currentPage === 5}" @click="switchPage(5)">
<span style="font-size: 22px;">🏆</span><span style="font-size: 11px;">挑战</span>
</div>
<div class="nav-item" :class="{active: currentPage === 6}" @click="switchPage(6)">
<span style="font-size: 22px;">🗝️</span><span style="font-size: 11px;">秘籍</span>
</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, computed, nextTick } = Vue;
createApp({
setup() {
// --- 核心逻辑不变,保持微信语音兼容性 ---
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 currentPage = ref(1);
const demoTab = ref('direct');
const explainTab = ref('basic');
const expandedCard = ref(null);
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;
resetAnimation();
}
}
const switchDemoTab = (tab) => {
demoTab.value = tab;
directStep.value = 0;
resetAnimation();
}
const speakIntro = () => {
speak("分糖果的小秘密。小明有5袋糖,每袋有8颗。小红有3袋糖,每袋也是8颗。如果他们一袋一袋数,要算两次乘法。聪明的小朋友会发现,他们都有每袋8颗这个双胞胎数字!只要把8这个双胞胎抓出来,再把袋数抱团,一下子就能算出总糖数。这个巧妙的方法,就叫提取公因数!");
}
const toggleCard = (index) => {
expandedCard.value = expandedCard.value === index ? null : index;
}
// --- 升级版 SVG 动画逻辑 ---
const directStep = ref(0);
const resetAnimation = () => {
const tl = gsap.timeline({paused: true});
// 重置所有元素位置和透明度
tl.set('#bubble1', {opacity: 1, x: 0, scale: 1});
tl.set('#bubble2', {opacity: 1, x: 0, scale: 1});
tl.set('#brackets', {opacity: 0, scale: 0.8});
tl.set('#jumping5', {opacity: 0, x: 0, y: 0, scale: 0.5});
tl.set(['#group38', '#plusSign', '#group62'], {opacity: 0});
tl.set('#merged100', {opacity: 0, scale: 0.8});
tl.set('#finalFormula', {opacity: 0, y: 20});
tl.play();
}
const runDirectAnimation = () => {
directStep.value = 0;
resetAnimation();
const tl = gsap.timeline();
// 1. 括号出现 (Visualization of Grouping)
tl.to('#brackets', {duration: 0.5, opacity: 1, scale: 1, ease: "back.out(1.7)"});
// 2. 气泡淡出,文字下沉变换
tl.to(['#bubble1 rect', '#bubble2 rect'], {duration: 0.4, opacity: 0}, "+=0.2")
.to(['#bubble1 text', '#bubble2 text'], {duration: 0.4, opacity: 0}, "<");
// 3. 数字重组:38+62 和 公因数5
tl.set(['#group38', '#plusSign', '#group62'], {opacity: 1, y: -20})
.to(['#group38', '#plusSign', '#group62'], {duration: 0.6, y: 0, ease: "bounce.out"});
tl.set('#jumping5', {opacity: 1, scale: 0.5, x: -50, y: -50}) // 初始位置修正
.to('#jumping5', {duration: 0.8, scale: 1.2, x: 0, y: 0, ease: "elastic.out(1, 0.5)"}, "<");
// 4. 合体变成100
tl.to(['#group38', '#plusSign', '#group62'], {duration: 0.4, opacity: 0, scale: 0.5, delay: 0.5})
.set('#merged100', {opacity: 1, scale: 0.5})
.to('#merged100', {duration: 0.6, scale: 1, ease: "elastic.out(1, 0.6)"});
// 5. 最终算式
tl.to('#finalFormula', {duration: 0.5, opacity: 1, y: 0, ease: "power2.out"});
directStep.value = 1;
}
// --- 课内练习 (8题) ---
const practiceQuestions = ref([
{
text: "$15 \\times 4 + 85 \\times 4 = ?$",
options: [{text: "400", correct: true}, {text: "300", correct: false}, {text: "500", correct: false}, {text: "1000", correct: false}],
explanation: "公因数是4。提出来:(15+85)×4 = 100×4 = 400。"
},
{
text: "$36 \\times 3 + 64 \\times 3 = ?$",
options: [{text: "360", correct: false}, {text: "300", correct: true}, {text: "100", correct: false}, {text: "200", correct: false}],
explanation: "公因数是3。提出来:(36+64)×3 = 100×3 = 300。"
},
{
text: "$7 \\times 128 - 7 \\times 28 = ?$",
options: [{text: "700", correct: true}, {text: "1400", correct: false}, {text: "7000", correct: false}, {text: "100", correct: false}],
explanation: "注意是减法!(128-28)×7 = 100×7 = 700。"
},
{
text: "$19 \\times 5 + 5 = ?$",
options: [{text: "95", correct: false}, {text: "100", correct: true}, {text: "105", correct: false}, {text: "200", correct: false}],
explanation: "隐身衣:5 = 5×1。提出来:(19+1)×5 = 20×5 = 100。"
},
{
text: "$8 \\times 25 + 8 \\times 15 = ?$",
options: [{text: "320", correct: true}, {text: "400", correct: false}, {text: "240", correct: false}, {text: "300", correct: false}],
explanation: "(25+15)×8 = 40×8 = 320。"
},
{
text: "$11 \\times 40 + 40 \\times 89 = ?$",
options: [{text: "4000", correct: true}, {text: "4400", correct: false}, {text: "400", correct: false}, {text: "8900", correct: false}],
explanation: "(11+89)×40 = 100×40 = 4000。"
},
{
text: "$55 \\times 6 + 6 \\times 45 = ?$",
options: [{text: "540", correct: false}, {text: "600", correct: true}, {text: "500", correct: false}, {text: "1000", correct: false}],
explanation: "(55+45)×6 = 100×6 = 600。"
},
{
text: "$2 \\times 9 + 2 = ?$",
options: [{text: "20", correct: true}, {text: "18", correct: false}, {text: "22", correct: false}, {text: "11", correct: false}],
explanation: "隐身衣:(9+1)×2 = 20。"
}
]);
// --- 奥数挑战 (扩充至 8 题) ---
const olympiadQuestions = ref([
{
text: "$46 \\times 28 + 28 \\times 55 - 28$",
options: [{text: "2800", correct: true}, {text: "2828", correct: false}, {text: "2700", correct: false}, {text: "2000", correct: false}],
explanation: "减去1个28:(46 + 55 - 1) × 28 = 100 × 28 = 2800。",
hint1: "三个部分都有28。", hint2: "最后一个28看作减去1个28。"
},
{
text: "$99 \\times 13 + 13$",
options: [{text: "1300", correct: true}, {text: "1287", correct: false}, {text: "1400", correct: false}, {text: "1000", correct: false}],
explanation: "(99+1) × 13 = 100 × 13 = 1300。",
hint1: "给后面的13穿上隐身衣。", hint2: "看作 99个13 加上 1个13。"
},
{
text: "$25 \\times 44$",
options: [{text: "1100", correct: true}, {text: "1000", correct: false}, {text: "1200", correct: false}, {text: "2500", correct: false}],
explanation: "把44拆成4×11。25×4×11 = 100×11 = 1100。",
hint1: "25的好朋友是谁?", hint2: "把44拆开试试。"
},
{
text: "$125 \\times 32 \\times 25$",
options: [{text: "100000", correct: true}, {text: "10000", correct: false}, {text: "32000", correct: false}, {text: "80000", correct: false}],
explanation: "把32拆成8×4。125×8 × (25×4) = 1000 × 100 = 100000。",
hint1: "125找8,25找4。", hint2: "32正好是8乘4。"
},
{
text: "$333 \\times 4 + 111 \\times 8$",
options: [{text: "2220", correct: true}, {text: "2000", correct: false}, {text: "3330", correct: false}, {text: "4440", correct: false}],
explanation: "333是111的3倍。原式 = 111×3×4 + 111×8 = 111×(12+8) = 2220。",
hint1: "333 可以变成 111 × 3。", hint2: "把公因数统一成111。"
},
{
text: "$37 \\times 18 + 63 \\times 18$",
options: [{text: "1800", correct: true}, {text: "18000", correct: false}, {text: "1000", correct: false}, {text: "3700", correct: false}],
explanation: "(37+63) × 18 = 100 × 18 = 1800。",
hint1: "37加63等于多少?", hint2: "直接提取18。"
},
{
text: "$99 \\times 99 + 199$",
options: [{text: "10000", correct: true}, {text: "9900", correct: false}, {text: "9999", correct: false}, {text: "19900", correct: false}],
explanation: "把199拆成99+100。99×99 + 99 + 100 = 99×(99+1)+100 = 9900+100=10000。",
hint1: "199 里面藏着一个 99。", hint2: "拆成 99 + 100 试试。"
},
{
text: "$25 \\times 404$",
options: [{text: "10100", correct: true}, {text: "10000", correct: false}, {text: "11000", correct: false}, {text: "10040", correct: false}],
explanation: "把404拆成400+4。25×(400+4) = 25×400 + 25×4 = 10000 + 100 = 10100。",
hint1: "404 = 400 + 4。", hint2: "用乘法分配律拆开算。"
}
]);
// --- 答题逻辑通用部分 ---
const currentQuestion = ref(0);
const answered = ref(false);
const selectedOption = ref(null);
const isCorrect = ref(false);
const score = ref(0);
const practiceCompleted = ref(false);
const selectOption = (index, correct) => {
if (answered.value) return;
selectedOption.value = index;
answered.value = true;
isCorrect.value = correct;
if (correct) {
score.value += 10;
gsap.to(document.querySelectorAll('.option-btn')[index], {scale: 1.05, duration: 0.1, yoyo: true, repeat: 1});
} else {
gsap.to(document.querySelectorAll('.option-btn')[index], {x: 5, duration: 0.1, yoyo: true, repeat: 3}); // 震动
}
};
const nextQuestion = () => {
answered.value = false;
selectedOption.value = null;
if (currentQuestion.value < practiceQuestions.value.length - 1) {
currentQuestion.value++;
} else {
practiceCompleted.value = true;
confetti({ particleCount: 150, spread: 70, origin: { y: 0.6 } });
}
};
// --- 奥数答题逻辑 ---
const currentOlympiad = ref(0);
const olympiadAnswered = ref(false);
const selectedOlympiadOption = ref(null);
const isOlympiadCorrect = ref(false);
const olympiadScore = ref(0);
const olympiadCompleted = ref(false);
const wrongAttempts = ref(0);
const selectOlympiadOption = (index, correct) => {
if (olympiadAnswered.value || wrongAttempts.value >= 3) return;
selectedOlympiadOption.value = index;
if (correct) {
olympiadAnswered.value = true;
isOlympiadCorrect.value = true;
olympiadScore.value += 30; // 奥数分值高
confetti({ particleCount: 80, spread: 60, origin: { y: 0.6 } });
gsap.to(document.querySelectorAll('.option-btn')[index], {scale: 1.1, duration: 0.2, yoyo: true, repeat: 1});
} else {
wrongAttempts.value++;
gsap.to(document.querySelectorAll('.option-btn')[index], {x: 5, duration: 0.1, yoyo: true, repeat: 3});
if (wrongAttempts.value >= 3) {
olympiadAnswered.value = true;
isOlympiadCorrect.value = false;
}
}
};
const nextOlympiad = () => {
olympiadAnswered.value = false;
selectedOlympiadOption.value = null;
wrongAttempts.value = 0;
if (currentOlympiad.value < olympiadQuestions.value.length - 1) {
currentOlympiad.value++;
} else {
olympiadCompleted.value = true;
confetti({ particleCount: 200, spread: 100, origin: { y: 0.6 } });
}
};
resetAnimation();
return {
currentPage, demoTab, explainTab, expandedCard,
switchPage, switchDemoTab, speakIntro, toggleCard,
directStep, runDirectAnimation,
practiceQuestions, currentQuestion, answered, selectedOption, isCorrect, score, practiceCompleted, selectOption, nextQuestion,
olympiadQuestions, currentOlympiad, olympiadAnswered, selectedOlympiadOption, isOlympiadCorrect, olympiadScore, olympiadCompleted, wrongAttempts, selectOlympiadOption, nextOlympiad
};
}
}).mount('#app');
</script>
</body>
</html>
💡 这段代码完全由 AI 生成。
登录后可复制完整代码