<!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>7的整除特征 - 完整教学版</title>
<style>
* {
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
html, body {
margin: 0;
padding: 0;
background: #F0F2F5;
overflow: hidden;
height: 100vh;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}
#app {
height: 100vh;
width: 100%;
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;
overflow-x: hidden;
padding-bottom: 80px;
position: relative;
}
/* 底部导航 */
.bottom-nav {
position: fixed;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 100%;
max-width: 480px;
height: 70px;
background: white;
border-top: 1px solid #e0e0e0;
display: flex;
justify-content: space-around;
align-items: center;
z-index: 1000;
box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
}
.nav-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
padding: 8px;
transition: all 0.3s;
color: #888;
}
.nav-item.active {
color: #7C3AED;
transform: translateY(-2px);
}
.nav-icon {
font-size: 24px;
margin-bottom: 4px;
}
.nav-label {
font-size: 12px;
font-weight: 500;
}
/* Page 1: 概念引入 */
.intro-page {
padding: 40px 20px;
text-align: center;
}
.intro-emoji {
font-size: 80px;
margin: 20px 0;
animation: float 3s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
.intro-title {
font-size: 28px;
font-weight: bold;
color: #333;
margin: 20px 0;
}
.intro-story {
font-size: 16px;
line-height: 1.8;
color: #555;
text-align: left;
background: #EDE9FE;
padding: 25px;
border-radius: 16px;
margin: 20px 0;
border-left: 5px solid #7C3AED;
box-shadow: 0 4px 6px rgba(124, 58, 237, 0.1);
}
.highlight {
color: #7C3AED;
font-weight: bold;
background: rgba(124, 58, 237, 0.1);
padding: 0 4px;
border-radius: 4px;
}
.speak-btn {
background: linear-gradient(135deg, #7C3AED 0%, #6D28D9 100%);
color: white;
border: none;
padding: 12px 30px;
border-radius: 25px;
font-size: 16px;
cursor: pointer;
margin-top: 20px;
box-shadow: 0 4px 15px rgba(124, 58, 237, 0.3);
transition: transform 0.2s;
display: inline-flex;
align-items: center;
gap: 8px;
}
.speak-btn:active { transform: scale(0.95); }
/* Page 2: 演示动画 */
.demo-page { padding: 20px; }
.tab-nav {
display: flex;
border-bottom: 2px solid #e0e0e0;
margin-bottom: 20px;
}
.tab-item {
flex: 1;
padding: 12px;
text-align: center;
cursor: pointer;
color: #666;
font-weight: 500;
border-bottom: 3px solid transparent;
transition: all 0.3s;
}
.tab-item.active {
color: #7C3AED;
border-bottom-color: #7C3AED;
background: linear-gradient(to bottom, transparent, rgba(124, 58, 237, 0.05));
}
.animation-area {
background: linear-gradient(135deg, #F5F3FF 0%, #EDE9FE 100%);
border-radius: 16px;
padding: 10px;
height: 340px;
margin-bottom: 20px;
position: relative;
overflow: hidden;
border: 2px solid #e0e0e0;
box-shadow: inset 0 0 20px rgba(0,0,0,0.02);
}
.svg-container { width: 100%; height: 100%; }
.math-area {
background: white;
border: 2px solid #7C3AED;
border-radius: 12px;
padding: 15px;
margin-bottom: 15px;
min-height: 100px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
}
.math-formula {
font-size: 18px;
text-align: center;
color: #333;
line-height: 1.8;
font-weight: 500;
font-family: "Courier New", monospace;
}
.step-explanation {
font-size: 14px;
color: #4B5563;
margin-top: 10px;
line-height: 1.5;
text-align: center;
padding: 10px;
background: #F3F4F6;
border-radius: 8px;
border: 1px solid #E5E7EB;
}
.control-bar {
display: flex;
justify-content: center;
gap: 15px;
margin-top: 20px;
}
.step-btn {
background: #7C3AED;
color: white;
border: none;
padding: 10px 25px;
border-radius: 20px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s;
}
.step-btn:disabled { background: #CBD5E1; cursor: not-allowed; }
.step-btn:hover:not(:disabled) { transform: scale(1.05); box-shadow: 0 4px 12px rgba(124, 58, 237, 0.4); }
/* Page 3: 讲解页 */
.explain-page { padding: 20px; }
.method-card {
background: white;
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
border-left: 4px solid #7C3AED;
}
.method-title {
font-size: 18px;
font-weight: bold;
color: #111827;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 8px;
}
.method-content {
font-size: 15px;
line-height: 1.8;
color: #4B5563;
}
.example-box {
background: #F9FAFB;
padding: 15px;
border-radius: 8px;
margin: 10px 0;
border-left: 3px solid #10b981;
font-family: monospace;
}
.question-item {
background: white;
padding: 15px;
margin: 10px 0;
border-radius: 8px;
border: 2px solid #e0e0e0;
transition: all 0.3s;
cursor: pointer;
}
.question-item.expanded { border-color: #7C3AED; background: #F5F3FF; }
.question-header { display: flex; justify-content: space-between; align-items: center; font-weight: bold; }
.question-answer { margin-top: 15px; padding-top: 15px; border-top: 1px dashed #e0e0e0; display: none; color: #555; }
.question-item.expanded .question-answer { display: block; animation: slideDown 0.3s; }
@keyframes slideDown { from {opacity:0; transform:translateY(-5px);} to {opacity:1; transform:translateY(0);} }
/* Page 4 & 5: 练习与奥数 */
.practice-page { padding: 20px; height: 100%; display: flex; flex-direction: column; }
.question-card {
background: white;
border-radius: 16px;
padding: 25px;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
margin-bottom: 20px;
flex: 1;
}
.question-number {
background: #7C3AED;
color: white;
padding: 5px 15px;
border-radius: 20px;
font-size: 14px;
display: inline-block;
}
.score-display {
background: #FCD34D;
padding: 5px 15px;
border-radius: 20px;
font-weight: bold;
color: #78350F;
float: right;
}
.difficulty-badge {
display: inline-block;
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
font-weight: bold;
margin-left: 10px;
}
.difficulty-bronze { background: #FEE2E2; color: #991B1B; }
.difficulty-silver { background: #E5E7EB; color: #374151; }
.difficulty-gold { background: #FEF3C7; color: #92400E; }
.question-text {
font-size: 18px;
line-height: 1.6;
color: #1F2937;
margin: 20px 0;
font-weight: 500;
}
.option-btn {
display: block;
width: 100%;
background: white;
border: 2px solid #E5E7EB;
border-radius: 12px;
padding: 16px;
text-align: left;
font-size: 16px;
cursor: pointer;
margin-bottom: 12px;
transition: all 0.2s;
position: relative;
}
.option-btn:hover { border-color: #7C3AED; transform: translateX(5px); }
.option-btn.correct { background: #D1FAE5; border-color: #10B981; color: #064E3B; }
.option-btn.wrong { background: #FEE2E2; border-color: #EF4444; color: #7F1D1D; }
.feedback-area {
margin-top: 20px;
padding: 15px;
border-radius: 12px;
font-size: 14px;
line-height: 1.6;
animation: fadeIn 0.5s;
}
.feedback-correct { background: #D1FAE5; color: #065F46; border: 1px solid #34D399; }
.feedback-wrong { background: #FEE2E2; color: #991B1B; border: 1px solid #F87171; }
.next-btn {
background: #7C3AED;
color: white;
border: none;
padding: 15px;
width: 100%;
border-radius: 12px;
font-size: 16px;
font-weight: bold;
margin-top: 15px;
cursor: pointer;
box-shadow: 0 4px 6px rgba(124, 58, 237, 0.2);
}
/* Page 6: 秘籍 */
.secrets-container {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
padding: 20px;
gap: 16px;
height: 100%;
align-items: center;
}
.secrets-container::-webkit-scrollbar { display: none; }
.secret-card {
min-width: 85%;
height: 480px;
scroll-snap-align: center;
background: linear-gradient(135deg, #7C3AED 0%, #6D28D9 100%);
border-radius: 24px;
color: white;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 30px;
box-shadow: 0 10px 30px rgba(124, 58, 237, 0.3);
position: relative;
overflow: hidden;
}
.secret-card::before {
content: '';
position: absolute;
top: -50px; left: -50px;
width: 150px; height: 150px;
background: rgba(255,255,255,0.1);
border-radius: 50%;
}
.secret-emoji { font-size: 80px; margin-bottom: 20px; }
.secret-title { font-size: 26px; font-weight: bold; margin-bottom: 15px; }
.secret-rule {
font-size: 20px;
background: rgba(255,255,255,0.2);
padding: 15px 25px;
border-radius: 15px;
margin: 20px 0;
font-weight: bold;
backdrop-filter: blur(5px);
}
.secret-explanation { font-size: 15px; line-height: 1.8; text-align: center; opacity: 0.95; }
/* 完成页 */
.completion-page {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100%;
text-align: center;
}
.completion-emoji { font-size: 100px; margin-bottom: 20px; animation: bounce 2s infinite; }
.final-score { font-size: 48px; font-weight: bold; color: #7C3AED; margin: 10px 0; }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes bounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-20px); } }
</style>
<!-- 引入必要的库 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.3.4/vue.global.prod.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.6.0/dist/confetti.browser.min.js"></script>
</head>
<body>
<div id="app">
<div class="content-area">
<!-- Page 1: 概念引入 -->
<div v-show="currentPage === 1" class="intro-page">
<div class="intro-emoji">7️⃣</div>
<div class="intro-title">7的整除特征</div>
<div class="intro-story">
<strong>🤔 为什么7这么难?</strong><br><br>
2看末位,3看和,5看0或5...<br>
但是 <strong>7</strong> 是个孤独的数字,没有明显的规律。<br><br>
<span class="highlight">133, 399, 56379...</span><br>
这些数到底能不能被7整除?<br><br>
今天传授你两招必杀技:<br>
1. <strong>🔪 割尾法</strong> (适合小数字)<br>
2. <strong>💉 瘦身法</strong> (适合大数字)
</div>
<button class="speak-btn" @click="speakIntro">
<span>🔊 听老师讲解</span>
</button>
</div>
<!-- Page 2: 演示动画 -->
<div v-show="currentPage === 2" class="demo-page">
<div class="tab-nav">
<div class="tab-item" :class="{active: demoTab === 'cutoff'}" @click="switchDemoTab('cutoff')">🔪 割尾法</div>
<div class="tab-item" :class="{active: demoTab === 'principle'}" @click="switchDemoTab('principle')">🧪 核心原理</div>
</div>
<!-- 割尾法动画 -->
<div v-show="demoTab === 'cutoff'">
<div class="animation-area">
<svg class="svg-container" viewBox="0 0 400 340">
<!-- 标题 -->
<text x="200" y="30" text-anchor="middle" font-weight="bold" fill="#555">判断 133 能否被7整除</text>
<!-- 主数字组 -->
<g id="cutoff-group-main">
<text id="cutoff-head" x="160" y="120" text-anchor="end" font-size="60" font-weight="bold" fill="#7C3AED">13</text>
<text id="cutoff-tail" x="170" y="120" text-anchor="start" font-size="60" font-weight="bold" fill="#EF4444">3</text>
<line id="cutoff-line" x1="165" y1="70" x2="165" y2="130" stroke="#9CA3AF" stroke-width="2" stroke-dasharray="5,5" opacity="0"/>
</g>
<!-- 操作步骤 -->
<g id="cutoff-step1" opacity="0">
<text x="200" y="180" text-anchor="middle" font-size="16" fill="#666">1. 割去末位,乘以2</text>
<text x="200" y="210" text-anchor="middle" font-size="24" font-weight="bold" fill="#EF4444">3 × 2 = 6</text>
</g>
<g id="cutoff-step2" opacity="0">
<text x="200" y="250" text-anchor="middle" font-size="16" fill="#666">2. 剩下的数减去它</text>
<text x="200" y="280" text-anchor="middle" font-size="24" font-weight="bold" fill="#7C3AED">13 - 6 = 7</text>
</g>
<g id="cutoff-result" opacity="0">
<circle cx="200" cy="280" r="30" fill="none" stroke="#10B981" stroke-width="3"/>
<text x="200" y="320" text-anchor="middle" font-size="14" fill="#10B981" font-weight="bold">7能被7整除,所以133也能!</text>
</g>
</svg>
</div>
<div class="math-area">
<div class="math-formula">
<div v-if="demoStep === 0">133 ÷ 7 = ?</div>
<div v-if="demoStep === 1">割去末位: <span style="color:#7C3AED">13</span> | <span style="color:#EF4444">3</span></div>
<div v-if="demoStep === 2">末位翻倍: 3 × 2 = 6</div>
<div v-if="demoStep === 3">作差: 13 - 6 = 7</div>
<div v-if="demoStep === 4"><span style="color:#10B981">7 是 7 的倍数 ✓</span></div>
</div>
</div>
</div>
<!-- 原理推导动画 -->
<div v-show="demoTab === 'principle'">
<div class="animation-area">
<svg class="svg-container" viewBox="0 0 400 340">
<text x="200" y="30" text-anchor="middle" font-weight="bold" fill="#555">为什么是"减去2倍"?</text>
<g id="principle-step1" opacity="1">
<text x="200" y="80" text-anchor="middle" font-size="20">我们以 <tspan fill="#7C3AED" font-weight="bold">39</tspan><tspan fill="#EF4444" font-weight="bold">9</tspan> 为例</text>
</g>
<g id="principle-step2" opacity="0">
<text x="200" y="130" text-anchor="middle" font-size="18">拆分: <tspan fill="#7C3AED">39</tspan>0 + <tspan fill="#EF4444">9</tspan></text>
</g>
<g id="principle-step3" opacity="0">
<rect x="40" y="150" width="320" height="40" rx="5" fill="#FEF3C7" stroke="#F59E0B"/>
<text x="200" y="175" text-anchor="middle" font-size="14" fill="#D97706">关键:减去 21倍的末位 (21是7的倍数)</text>
</g>
<g id="principle-step4" opacity="0">
<text x="200" y="230" text-anchor="middle" font-size="18">
(<tspan fill="#7C3AED">39</tspan>0 + <tspan fill="#EF4444">9</tspan>) - <tspan fill="#F59E0B" font-weight="bold">21</tspan>×<tspan fill="#EF4444">9</tspan>
</text>
</g>
<g id="principle-step5" opacity="0">
<text x="200" y="270" text-anchor="middle" font-size="18">
= <tspan fill="#7C3AED">39</tspan>0 - <tspan fill="#F59E0B" font-weight="bold">20</tspan>×<tspan fill="#EF4444">9</tspan>
</text>
<text x="200" y="310" text-anchor="middle" font-size="20" font-weight="bold" fill="#7C3AED">
= 10 × (<tspan fill="#7C3AED">39</tspan> - <tspan fill="#EF4444">2</tspan>×<tspan fill="#EF4444">9</tspan>)
</text>
</g>
</svg>
</div>
<div class="math-area">
<div class="math-formula">
<div v-if="principleStep === 0">探究 399 的秘密</div>
<div v-if="principleStep === 1">399 = 390 + 9</div>
<div v-if="principleStep === 2">减去21×9 (不影响整除性)</div>
<div v-if="principleStep === 3">合并同类项: 1个9减21个9</div>
<div v-if="principleStep === 4">提取10,剩下: 39 - 2×9</div>
</div>
</div>
</div>
<div class="control-bar">
<button class="step-btn" @click="prevStep" :disabled="currentStepIndex === 0">◀ 上一步</button>
<button class="step-btn" @click="nextStep" :disabled="isStepFinished">下一步 ▶</button>
</div>
</div>
<!-- Page 3: 讲解页 -->
<div v-show="currentPage === 3" class="explain-page">
<div class="tab-nav">
<div class="tab-item" :class="{active: explainTab === 'methods'}" @click="explainTab = 'methods'">判断方法</div>
<div class="tab-item" :class="{active: explainTab === 'examples'}" @click="explainTab = 'examples'">典型例题</div>
</div>
<div v-show="explainTab === 'methods'">
<div class="method-card">
<div class="method-title"><span>🔪</span> 方法一:割尾法</div>
<div class="method-content">
<p><strong>适用:</strong> 三位数或四位数</p>
<p><strong>口诀:</strong> 割去末位,减去双倍。</p>
<div class="example-box">
例:133<br>
1. 割去3 -> 剩 13<br>
2. 末位翻倍 -> 3×2=6<br>
3. 相减 -> 13-6=7 (是7的倍数)
</div>
</div>
</div>
<div class="method-card">
<div class="method-title"><span>💉</span> 方法二:大数瘦身法</div>
<div class="method-content">
<p><strong>适用:</strong> 超大数字</p>
<p><strong>原理:</strong> 减去7的倍数(如700, 2100, 14000等),余数不变。</p>
<div class="example-box">
例:56379<br>
1. 减去 56000 (7的倍数)<br>
-> 剩 379<br>
2. 对379用割尾法<br>
-> 37 - 9×2 = 37-18 = 19<br>
3. 19不是7的倍数,所以原数也不是!
</div>
</div>
</div>
</div>
<div v-show="explainTab === 'examples'">
<div class="question-item" :class="{expanded: expandedQ === 1}" @click="toggleQ(1)">
<div class="question-header"><span>399 能被7整除吗?</span><span>▼</span></div>
<div class="question-answer">
<strong>步骤:</strong><br>
1. 割去末位9,剩下39<br>
2. 9 × 2 = 18<br>
3. 39 - 18 = 21<br>
4. 21 ÷ 7 = 3<br>
<span style="color:#10B981">结论:能整除!</span>
</div>
</div>
<div class="question-item" :class="{expanded: expandedQ === 2}" @click="toggleQ(2)">
<div class="question-header"><span>2401 能被7整除吗?</span><span>▼</span></div>
<div class="question-answer">
<strong>步骤(连续割尾):</strong><br>
1. 240 - 1×2 = 238<br>
2. 23 - 8×2 = 23 - 16 = 7<br>
<span style="color:#10B981">结论:能整除!</span>
</div>
</div>
<div class="question-item" :class="{expanded: expandedQ === 3}" @click="toggleQ(3)">
<div class="question-header"><span>大数 3568 除以7余几?</span><span>▼</span></div>
<div class="question-answer">
<strong>瘦身法:</strong><br>
1. 3568 接近 3500 (7的倍数)<br>
2. 3568 - 3500 = 68<br>
3. 68 ÷ 7 = 9 ... 5<br>
<span style="color:#7C3AED">结论:余数是 5</span>
</div>
</div>
</div>
</div>
<!-- Page 4: 练习 -->
<div v-show="currentPage === 4" class="practice-page">
<div v-if="!practiceFinished">
<div class="question-card">
<div style="margin-bottom:20px; overflow:hidden;">
<div class="question-number">第 {{currentPracIndex + 1}} / {{practiceQuestions.length}} 题</div>
<div class="score-display">⭐ {{score}}</div>
</div>
<div class="question-text">{{ practiceQuestions[currentPracIndex].title }}</div>
<div v-for="(opt, idx) in practiceQuestions[currentPracIndex].options" :key="idx">
<button class="option-btn"
:class="{
'correct': isPracAnswered && opt.val === practiceQuestions[currentPracIndex].correct,
'wrong': isPracAnswered && selectedOption === opt.val && opt.val !== practiceQuestions[currentPracIndex].correct
}"
@click="checkPractice(opt.val)"
:disabled="isPracAnswered">
{{ opt.text }}
</button>
</div>
<div v-if="isPracAnswered" class="feedback-area" :class="isPracCorrect ? 'feedback-correct' : 'feedback-wrong'">
<strong>{{ isPracCorrect ? '🎉 回答正确!' : '💡 再接再厉!' }}</strong><br>
{{ practiceQuestions[currentPracIndex].analysis }}
</div>
<button v-if="isPracAnswered" class="next-btn" @click="nextPractice">
{{ currentPracIndex < 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="speak-btn" @click="switchPage(5)">挑战奥数题 →</button>
</div>
</div>
<!-- Page 5: 奥数 -->
<div v-show="currentPage === 5" class="practice-page">
<div v-if="!olympiadFinished">
<div class="question-card">
<div style="margin-bottom:20px;">
<span class="difficulty-badge" :class="'difficulty-'+olympiadQuestions[currentOlyIndex].level">
{{olympiadQuestions[currentOlyIndex].levelText}}
</span>
<div class="score-display">🏆 {{olyScore}}</div>
</div>
<div style="font-size:12px; color:#999; margin-bottom:5px;">来源: {{olympiadQuestions[currentOlyIndex].source}}</div>
<div class="question-text">{{ olympiadQuestions[currentOlyIndex].title }}</div>
<!-- 选择题模式 -->
<div v-if="olympiadQuestions[currentOlyIndex].type === 'choice'">
<div v-for="(opt, idx) in olympiadQuestions[currentOlyIndex].options" :key="idx">
<button class="option-btn"
:class="{
'correct': isOlyAnswered && opt.val === olympiadQuestions[currentOlyIndex].correct,
'wrong': isOlyAnswered && selectedOlyOption === opt.val && opt.val !== olympiadQuestions[currentOlyIndex].correct
}"
@click="checkOlympiadChoice(opt.val)"
:disabled="isOlyAnswered">
{{ opt.text }}
</button>
</div>
</div>
<!-- 填空题模式 -->
<div v-if="olympiadQuestions[currentOlyIndex].type === 'input'">
<input type="text" v-model="olyInput" placeholder="请输入答案"
style="width:100%; padding:15px; border:2px solid #ddd; border-radius:12px; font-size:16px; margin-bottom:15px;"
:disabled="isOlyAnswered">
<button v-if="!isOlyAnswered" class="next-btn" @click="checkOlympiadInput">提交答案</button>
</div>
<!-- 提示与解析 -->
<div v-if="olyMistakes > 0 && !isOlyAnswered" class="step-explanation" style="text-align:left; background:#FFF7ED; border-color:#FFEDD5; color:#C2410C;">
<strong>💡 提示 {{olyMistakes}}:</strong> {{ olyMistakes === 1 ? olympiadQuestions[currentOlyIndex].hint1 : olympiadQuestions[currentOlyIndex].hint2 }}
</div>
<div v-if="isOlyAnswered" class="feedback-area" :class="isOlyCorrect ? 'feedback-correct' : 'feedback-wrong'">
<strong>解析:</strong> {{ olympiadQuestions[currentOlyIndex].analysis }}
</div>
<button v-if="isOlyAnswered" class="next-btn" @click="nextOlympiad">
{{ currentOlyIndex < 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">{{olyScore}} 分</div>
<button class="speak-btn" @click="switchPage(6)">领取通关秘籍 →</button>
</div>
</div>
<!-- Page 6: 秘籍 -->
<div v-show="currentPage === 6" class="secrets-container">
<div class="secret-card">
<div class="secret-emoji">🔪</div>
<div class="secret-title">割尾法</div>
<div class="secret-rule">割去末位,减去双倍</div>
<div class="secret-explanation">
适用于3-4位数的快速判断。<br>
例如: 133 -> 13 - 3×2 = 7。<br>
如果一次不够,可以多割几次!
</div>
</div>
<div class="secret-card" style="background: linear-gradient(135deg, #10B981 0%, #059669 100%);">
<div class="secret-emoji">💉</div>
<div class="secret-title">瘦身法</div>
<div class="secret-rule">减去 7, 70, 700...</div>
<div class="secret-explanation">
适用于大数字。<br>
减去7的任意倍数,余数不变。<br>
找21, 14, 35, 70这些好算的数去减!
</div>
</div>
<div class="secret-card" style="background: linear-gradient(135deg, #F59E0B 0%, #D97706 100%);">
<div class="secret-emoji">🧪</div>
<div class="secret-title">核心原理</div>
<div class="secret-rule">-21y 不影响整除</div>
<div class="secret-explanation">
10x + y (原数)<br>
- 21y (7的倍数)<br>
= 10(x - 2y)<br>
所以只看 x - 2y 即可!
</div>
</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, ref, computed, nextTick, watch } = Vue;
createApp({
setup() {
const currentPage = ref(1);
// --- 语音功能 ---
const speak = (text) => {
// 停止之前的
if (window.speechSynthesis) window.speechSynthesis.cancel();
const audio = document.getElementById('tts-audio');
if (audio) audio.pause();
// 检测环境
const isWeChat = /MicroMessenger/i.test(navigator.userAgent);
if (isWeChat) {
// 微信:走 PHP 代理
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 themePath = 'https://www.xinghuo.tv/wp-content/themes/xinghuo-tv';
const url = `${themePath}/tts.php?text=${encodeURIComponent(text)}&t=${Date.now()}`;
audio.src = url;
const playPromise = audio.play();
if (playPromise !== undefined) {
playPromise.catch(error => {
console.error("Audio playback failed:", error);
if (window.WeixinJSBridge) {
window.WeixinJSBridge.invoke('getNetworkType', {}, function (e) {
audio.play();
});
}
});
}
} else {
// PC/其他:原生
if (window.speechSynthesis) {
const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = 'zh-CN';
utterance.rate = 0.9;
window.speechSynthesis.speak(utterance);
}
}
};
const speakIntro = () => {
speak("7是一个孤独的数字。判断它能否整除,我们通常用割尾法:把最后一位数字割掉,乘以2,然后用剩下的数字去减它。如果是大数字,我们可以先给它瘦身,减去7的倍数!");
};
// --- 演示动画逻辑 ---
const demoTab = ref('cutoff');
const demoStep = ref(0);
const principleStep = ref(0);
const currentStepIndex = computed(() => demoTab.value === 'cutoff' ? demoStep.value : principleStep.value);
const isStepFinished = computed(() => {
if (demoTab.value === 'cutoff') return demoStep.value >= 4;
return principleStep.value >= 4;
});
const switchDemoTab = (tab) => {
demoTab.value = tab;
demoStep.value = 0;
principleStep.value = 0;
resetAnimations();
if (tab === 'principle') {
speak("让我们来揭开'割尾法'背后的数学秘密。为什么是乘以2呢?我们用399这个数来推导一下。");
} else {
speak("7是一个孤独的数字。判断它能否整除,我们通常用割尾法:把最后一位数字割掉,乘以2,然后用剩下的数字去减它。");
}
};
const resetAnimations = () => {
gsap.killTweensOf("*");
if(demoTab.value === 'cutoff') {
gsap.set("#cutoff-line", {opacity: 0});
gsap.set("#cutoff-tail", {x: 170, y: 120, opacity: 1, scale: 1});
gsap.set("#cutoff-step1", {opacity: 0});
gsap.set("#cutoff-step2", {opacity: 0});
gsap.set("#cutoff-result", {opacity: 0});
} else {
gsap.set("#principle-step1", {opacity: 1});
gsap.set(["#principle-step2", "#principle-step3", "#principle-step4", "#principle-step5"], {opacity: 0});
}
};
const nextStep = () => {
if (demoTab.value === 'cutoff') {
if (demoStep.value >= 4) return;
demoStep.value++;
runCutoffAnimation(demoStep.value);
} else {
if (principleStep.value >= 4) return;
principleStep.value++;
runPrincipleAnimation(principleStep.value);
}
};
const prevStep = () => {
if (demoTab.value === 'cutoff') {
if (demoStep.value <= 0) return;
demoStep.value--;
// 简单回退:重置动画并快速播放到当前步
resetAnimations();
for(let i=1; i<=demoStep.value; i++) runCutoffAnimation(i, true);
} else {
if (principleStep.value <= 0) return;
principleStep.value--;
resetAnimations();
for(let i=1; i<=principleStep.value; i++) runPrincipleAnimation(i, true);
}
};
const runCutoffAnimation = (step, instant = false) => {
const dur = instant ? 0 : 0.5;
if (step === 1) { // 割线出现
gsap.to("#cutoff-line", {opacity: 1, duration: dur});
gsap.to("#cutoff-head", {x: 150, duration: dur}); // Move 13 left
gsap.to("#cutoff-tail", {x: 180, duration: dur}); // Move 3 right
speak("第一步,把末位3割下来");
} else if (step === 2) { // 3 -> 6
gsap.to("#cutoff-step1", {opacity: 1, duration: dur});
gsap.to("#cutoff-tail", {scale: 0, opacity: 0, duration: dur});
speak("第二步,末位3乘以2,变成6");
} else if (step === 3) { // 13 - 6
gsap.to("#cutoff-step2", {opacity: 1, duration: dur});
speak("第三步,用剩下的13减去6,等于7");
} else if (step === 4) { // 结果
gsap.to("#cutoff-result", {opacity: 1, duration: dur});
confetti({ particleCount: 50, spread: 60, origin: { y: 0.6 } });
speak("7能被7整除,所以原数133也能被7整除!");
}
};
const runPrincipleAnimation = (step, instant = false) => {
const dur = instant ? 0 : 0.5;
if (step === 1) {
gsap.to("#principle-step1", {opacity: 0, duration: dur});
gsap.to("#principle-step2", {opacity: 1, duration: dur});
speak("首先,我们把399按位拆开。拆成前面的整十数390,加上个位数9。");
} else if (step === 2) {
gsap.to("#principle-step3", {opacity: 1, duration: dur});
speak("关键一步来了!我们给它减去一个特殊的数:21乘以9。为什么选21?因为21是7的倍数,减去它不影响整除结果!");
} else if (step === 3) {
gsap.to("#principle-step2", {opacity: 0.2, duration: dur});
gsap.to("#principle-step3", {opacity: 0.2, duration: dur});
gsap.to("#principle-step4", {opacity: 1, duration: dur});
speak("现在式子变成了:390加上1个9,再减去21个9。");
} else if (step === 4) {
gsap.to("#principle-step4", {opacity: 0, duration: dur});
gsap.to("#principle-step5", {opacity: 1, duration: dur});
speak("合并一下:1个9减21个9,还剩负20个9。提取公因数10后,括号里就是39减去2乘9。看!这就是为什么我们要'减去末位2倍'的数学原理!");
}
};
// --- 讲解页逻辑 ---
const explainTab = ref('methods');
const expandedQ = ref(null);
const toggleQ = (id) => expandedQ.value = expandedQ.value === id ? null : id;
// --- 练习题逻辑 ---
const currentPracIndex = ref(0);
const score = ref(0);
const isPracAnswered = ref(false);
const isPracCorrect = ref(false);
const selectedOption = ref(null);
const practiceFinished = ref(false);
const practiceQuestions = [
{
title: "161 能否被 7 整除?",
options: [{text:"能", val:true}, {text:"不能", val:false}],
correct: true,
analysis: "割尾法:16 - 1×2 = 14。14是7的倍数,所以161能被7整除。"
},
{
title: "354 是否为 7 的倍数?",
options: [{text:"是", val:true}, {text:"否", val:false}],
correct: false,
analysis: "割尾法:35 - 4×2 = 35 - 8 = 27。27不是7的倍数。"
},
{
title: "判断 7776 除以 7 的余数",
options: [{text:"0", val:0}, {text:"6", val:6}, {text:"1", val:1}],
correct: 6,
analysis: "瘦身法:7776 - 7700 = 76。76 ÷ 7 = 10...6。"
}
];
const checkPractice = (val) => {
isPracAnswered.value = true;
selectedOption.value = val;
isPracCorrect.value = (val === practiceQuestions[currentPracIndex.value].correct);
if(isPracCorrect.value) {
score.value += 30;
confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } });
}
};
const nextPractice = () => {
if (currentPracIndex.value < practiceQuestions.length - 1) {
currentPracIndex.value++;
isPracAnswered.value = false;
selectedOption.value = null;
} else {
practiceFinished.value = true;
}
};
// --- 奥数题逻辑 ---
const currentOlyIndex = ref(0);
const olyScore = ref(0);
const olympiadFinished = ref(false);
const isOlyAnswered = ref(false);
const isOlyCorrect = ref(false);
const selectedOlyOption = ref(null);
const olyInput = ref("");
const olyMistakes = ref(0);
const olympiadQuestions = [
{
level: 'bronze', levelText: '🥉 青铜', source: '希望杯', type: 'choice',
title: '下列哪个数能被7整除?',
options: [{text:'362', val:'B'}, {text:'371', val:'C'}, {text:'383', val:'D'}],
correct: 'C',
hint1: '试试对每个选项用割尾法', hint2: '37-1×2=35',
analysis: 'A. 36-4=32(X); B. 37-2=35(√); C. 38-6=32(X)。选C'
},
{
level: 'silver', levelText: '🥈 白银', source: '华杯赛', type: 'input',
title: '四位数 5□98 能被7整除,求□',
correct: ['6'],
hint1: '先看后三位或者试着用瘦身法', hint2: '5□9 - 8×2 = 5□9 - 16 = 5(□-1)3',
analysis: '用割尾法逆推或瘦身法。5698 ÷ 7 = 814。填6。或者 5698 - 5600 = 98,98是7的倍数。'
},
{
level: 'gold', levelText: '🥇 黄金', source: '亚太奥数', type: 'choice',
title: '既能被7整除又能被9整除的四位数是?',
options: [{text:'1234', val:'A'}, {text:'6174', val:'B'}, {text:'4410', val:'C'}],
correct: 'C',
hint1: '被9整除要求数字和是9的倍数', hint2: '然后再验算7',
analysis: 'C选项:4+4+1=9(9倍数); 441-0=441 -> 44-2=42(7倍数)。选C'
}
];
const checkOlympiadChoice = (val) => {
if(isOlyAnswered.value) return;
if(val === olympiadQuestions[currentOlyIndex.value].correct) {
handleOlyCorrect();
} else {
handleOlyWrong();
}
selectedOlyOption.value = val;
};
const checkOlympiadInput = () => {
if(isOlyAnswered.value) return;
if(olympiadQuestions[currentOlyIndex.value].correct.includes(olyInput.value.trim())) {
handleOlyCorrect();
} else {
handleOlyWrong();
}
};
const handleOlyCorrect = () => {
isOlyAnswered.value = true;
isOlyCorrect.value = true;
const points = olyMistakes.value === 0 ? 20 : (olyMistakes.value === 1 ? 10 : 5);
olyScore.value += points;
speak("回答正确!");
confetti({ particleCount: 150, spread: 100, origin: { y: 0.6 } });
};
const handleOlyWrong = () => {
olyMistakes.value++;
speak("再试一次,看看提示吧");
};
const nextOlympiad = () => {
if (currentOlyIndex.value < olympiadQuestions.length - 1) {
currentOlyIndex.value++;
isOlyAnswered.value = false;
isOlyCorrect.value = false;
selectedOlyOption.value = null;
olyInput.value = "";
olyMistakes.value = 0;
} else {
olympiadFinished.value = true;
}
};
const switchPage = (page) => {
if (window.speechSynthesis) window.speechSynthesis.cancel();
const audio = document.getElementById('tts-audio');
if (audio) audio.pause();
currentPage.value = page;
window.scrollTo(0, 0);
// 页面切换时重置动画状态以防bug
if(page === 2) {
nextTick(() => resetAnimations());
}
};
return {
currentPage, switchPage, speakIntro,
demoTab, switchDemoTab, demoStep, principleStep, currentStepIndex, isStepFinished, prevStep, nextStep,
explainTab, expandedQ, toggleQ,
currentPracIndex, score, isPracAnswered, isPracCorrect, selectedOption, practiceFinished, practiceQuestions, checkPractice, nextPractice,
currentOlyIndex, olyScore, olympiadFinished, isOlyAnswered, isOlyCorrect, selectedOlyOption, olyInput, olyMistakes, olympiadQuestions, checkOlympiadChoice, checkOlympiadInput, nextOlympiad
};
}
}).mount('#app');
</script>
</body>
</html>
💡 这段代码完全由 gemini 生成。
登录后可复制完整代码