<!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>
/* =========================================
1. 基础重置与全局变量
========================================= */
:root {
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--secondary-gradient: linear-gradient(135deg, #ff9a9e 0%, #fecfef 99%, #fecfef 100%);
--success-gradient: linear-gradient(135deg, #84fab0 0%, #8fd3f4 100%);
--card-bg: rgba(255, 255, 255, 0.95);
--shadow-soft: 0 10px 30px -10px rgba(0, 0, 0, 0.1);
--shadow-micro: 0 4px 12px rgba(0, 0, 0, 0.05);
--shadow-inset: inset 0 2px 4px rgba(0,0,0,0.02);
--radius-lg: 24px;
--radius-md: 16px;
--radius-sm: 12px;
--text-main: #2d3748;
--text-sub: #718096;
}
* { 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;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: #fdfbfb;
background-image: linear-gradient(120deg, #fdfbfb 0%, #ebedee 100%);
overflow: hidden; height: 100vh;
color: var(--text-main);
}
#app {
height: 100vh;
max-width: 520px; /* 稍微放宽一点 */
margin: 0 auto;
background: transparent;
display: flex;
flex-direction: column;
position: relative;
}
/* 背景装饰球(纯CSS绘制,增加氛围感) */
#app::before {
content: ''; position: absolute; top: -100px; left: -100px;
width: 300px; height: 300px; background: #a18cd1; opacity: 0.15;
filter: blur(80px); border-radius: 50%; z-index: -1;
}
#app::after {
content: ''; position: absolute; bottom: 50px; right: -50px;
width: 250px; height: 250px; background: #fad0c4; opacity: 0.2;
filter: blur(60px); border-radius: 50%; z-index: -1;
}
.content-area {
flex: 1;
overflow-y: auto;
padding-bottom: 90px; /* 底部留白给导航 */
scroll-behavior: smooth;
}
/* =========================================
2. 通用组件样式
========================================= */
.page-content { padding: 24px; }
.page-title {
font-size: 26px;
font-weight: 800;
margin: 10px 0 20px;
color: var(--text-main);
text-align: center;
letter-spacing: -0.5px;
text-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.sub-title {
font-size: 17px;
font-weight: 700;
margin: 15px 0 10px;
color: #4a5568;
display: flex; align-items: center; gap: 6px;
}
/* 按钮通用 */
button { font-family: inherit; }
/* 底部导航 - 悬浮胶囊风格 */
.bottom-nav {
position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);
width: 92%; max-width: 460px; height: 65px;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(12px);
border-radius: 35px;
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
display: flex; justify-content: space-around; align-items: center;
z-index: 9999;
padding: 0 10px;
border: 1px solid rgba(255,255,255,0.5);
}
.nav-item {
flex: 1; display: flex; flex-direction: column; align-items: center;
cursor: pointer; font-size: 11px; padding-top: 2px;
color: #a0aec0; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.nav-emoji { font-size: 22px; margin-bottom: 2px; filter: grayscale(100%); transition: all 0.3s; }
.nav-item.active { color: #667eea; transform: translateY(-2px); }
.nav-item.active .nav-emoji { filter: grayscale(0%); transform: scale(1.1); }
/* 页面过渡 */
.page-enter { animation: slideUpFade 0.5s cubic-bezier(0.2, 0.8, 0.2, 1); }
@keyframes slideUpFade {
from { opacity: 0; transform: translateY(20px) scale(0.98); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
/* =========================================
3. 页面特定样式
========================================= */
/* --- 首页 (Page 1) --- */
.intro-page {
display: flex; flex-direction: column; align-items: center; justify-content: center;
height: 100%; padding: 30px; text-align: center;
}
.intro-emoji {
font-size: 80px; margin-bottom: 20px;
animation: float 3s ease-in-out infinite;
}
@keyframes float { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-10px)} }
.intro-title {
font-size: 32px; font-weight: 900;
background: var(--primary-gradient);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
margin-bottom: 25px;
}
.intro-text {
font-size: 17px; line-height: 1.7; color: var(--text-sub);
margin-bottom: 30px; text-align: left;
background: #fff; padding: 25px; border-radius: var(--radius-lg);
box-shadow: var(--shadow-soft);
}
.intro-btn {
background: var(--primary-gradient);
color: white; border: none; padding: 18px 50px; border-radius: 40px;
font-size: 18px; font-weight: bold; cursor: pointer;
box-shadow: 0 10px 25px rgba(102, 126, 234, 0.4);
transition: transform 0.2s, box-shadow 0.2s;
}
.intro-btn:active { transform: scale(0.96); box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4); }
/* --- 演示页 (Page 2) --- */
.demo-page { padding: 20px; }
.tab-nav {
display: flex; background: #f1f3f5; border-radius: var(--radius-md);
padding: 5px; margin-bottom: 25px;
}
.tab-item {
flex: 1; text-align: center; padding: 12px; border-radius: var(--radius-sm);
cursor: pointer; font-weight: 700; transition: all 0.3s;
color: #718096; font-size: 15px;
}
.tab-item.active {
background: white; color: #667eea;
box-shadow: 0 4px 10px rgba(0,0,0,0.05);
}
/* 动画容器优化 */
.animation-area {
background: #ffffff;
border-radius: 24px; padding: 20px; margin: 25px 0;
height: 300px; position: relative;
display: flex; align-items: center; justify-content: center;
box-shadow: inset 0 0 20px rgba(0,0,0,0.02), var(--shadow-micro);
border: 1px solid rgba(0,0,0,0.03);
}
.svg-container { width: 100%; height: 100%; filter: drop-shadow(0 5px 5px rgba(0,0,0,0.1)); }
.step-btn {
position: absolute; bottom: 20px; z-index: 100;
background: white; color: #667eea; border: none;
width: 48px; height: 48px; border-radius: 50%; font-size: 20px;
cursor: pointer; display: flex; align-items: center; justify-content: center;
box-shadow: 0 8px 20px rgba(0,0,0,0.1);
transition: all 0.2s;
}
.step-btn:active { transform: scale(0.9); }
.step-btn:disabled { background: #f7fafc; color: #cbd5e0; box-shadow: none; }
.step-btn.prev { left: 20px; }
.step-btn.next { right: 20px; background: var(--primary-gradient); color: white; }
/* 概念卡片 */
.concept-card {
background: white; border-radius: var(--radius-lg); overflow: hidden;
box-shadow: var(--shadow-soft); margin-bottom: 25px;
transition: transform 0.3s;
}
.concept-header {
padding: 18px 24px; color: white; font-weight: 800; font-size: 19px;
display: flex; justify-content: space-between; align-items: center;
}
.bg-pink { background: var(--secondary-gradient); }
.bg-purple { background: var(--primary-gradient); }
.bg-blue { background: var(--success-gradient); }
.concept-body { padding: 24px; }
.highlight-box {
background: #fff8e6; border-left: 5px solid #ffc107;
padding: 18px; border-radius: 0 12px 12px 0; margin: 15px 0;
color: #d97706; font-size: 15px; line-height: 1.6;
}
/* --- 讲解与折叠卡片 (Page 3) --- */
.explain-page { padding: 20px; }
.expand-card {
background: white; border-radius: var(--radius-md); margin-bottom: 15px;
box-shadow: var(--shadow-micro); overflow: hidden;
border: 1px solid rgba(0,0,0,0.03);
transition: all 0.3s ease;
}
.expand-card.active { box-shadow: var(--shadow-soft); transform: translateY(-2px); }
.card-header {
padding: 20px; display: flex; justify-content: space-between; align-items: center;
cursor: pointer; background: #fff;
}
.card-title { font-weight: 700; font-size: 16px; display: flex; align-items: center; gap: 10px; }
.card-content {
padding: 0 20px 24px; border-top: 1px solid #f7fafc;
display: none; animation: fadeIn 0.3s;
}
@keyframes fadeIn { from{opacity:0} to{opacity:1} }
.expand-card.active .card-content { display: block; }
.expand-card.active .toggle-icon { transform: rotate(180deg); color: #667eea; }
/* --- 练习与挑战 (Page 4 & 5) --- */
.practice-page { padding: 20px; }
.question-card {
background: white; border-radius: var(--radius-lg); padding: 25px;
box-shadow: var(--shadow-soft);
}
.question-header {
display: flex; justify-content: space-between; align-items: center;
margin-bottom: 25px; padding-bottom: 15px; border-bottom: 2px dashed #edf2f7;
}
.score-display {
background: #fff3cd; color: #d97706;
padding: 6px 12px; border-radius: 20px; font-weight: 800; font-size: 15px;
}
.question-text {
font-size: 18px; line-height: 1.6; color: #2d3748;
margin-bottom: 30px; font-weight: 700;
}
/* 选项按钮升级 */
.options-container { display: grid; gap: 15px; margin-bottom: 25px; }
.option-btn {
background: #f7fafc; border: 2px solid #edf2f7;
border-radius: var(--radius-md); padding: 18px; text-align: center;
font-size: 16px; font-weight: 600; color: #4a5568;
cursor: pointer; transition: all 0.2s; position: relative; overflow: hidden;
}
.option-btn:active:not(:disabled) { transform: scale(0.97); }
.option-btn.correct { background: #c6f6d5; border-color: #9ae6b4; color: #22543d; }
.option-btn.wrong { background: #fed7d7; border-color: #feb2b2; color: #822727; }
/* 交互反馈动画 */
@keyframes vibrate {
0%, 100% { transform: translate(0, 0); }
10%, 30%, 50%, 70%, 90% { transform: translate(-3px, 0); border-color: #e53e3e; }
20%, 40%, 60%, 80% { transform: translate(3px, 0); }
}
.option-btn.shake { animation: vibrate 0.4s ease-in-out; }
.feedback-area {
padding: 20px; border-radius: var(--radius-md); margin: 25px 0; font-size: 15px; line-height: 1.6;
}
.feedback-correct { background: #f0fff4; border: 1px solid #c6f6d5; color: #276749; }
.feedback-wrong { background: #fff5f5; border: 1px solid #fed7d7; color: #c53030; }
.next-btn {
width: 100%; background: var(--primary-gradient);
color: white; border: none; padding: 18px; border-radius: var(--radius-md);
font-size: 18px; font-weight: 800; cursor: pointer;
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.3);
transition: transform 0.2s;
}
.next-btn:active { transform: scale(0.97); }
/* 完成页 */
.completion-page {
display: flex; flex-direction: column; align-items: center; justify-content: center;
height: 70vh; text-align: center;
}
.completion-emoji { font-size: 100px; margin-bottom: 20px; filter: drop-shadow(0 10px 10px rgba(0,0,0,0.1)); }
.final-score {
font-size: 48px; font-weight: 900;
background: linear-gradient(to right, #f6d365 0%, #fda085 100%);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
margin-bottom: 40px;
}
/* --- 秘籍页 (Page 6 - 横向滑动重构) --- */
.secret-page {
padding: 0;
height: 100%;
display: flex;
flex-direction: column;
}
/* 滚动容器 */
.secret-container {
flex: 1;
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
padding: 30px 20px 60px 20px; /* 增加上下Padding给阴影留空间 */
gap: 20px;
/* 隐藏滚动条 */
-webkit-overflow-scrolling: touch;
}
.secret-card {
/* 关键修改:宽度占满容器,实现单页效果 */
flex: 0 0 100%;
width: 100%;
min-height: 420px;
scroll-snap-align: center;
background: white;
border-radius: 30px;
padding: 40px 30px;
/* 漂亮的渐变背景 */
background-image: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
box-shadow: 0 15px 35px rgba(0,0,0,0.1), inset 0 0 0 1px rgba(255,255,255,0.4);
display: flex; flex-direction: column;
justify-content: center; align-items: center;
text-align: center;
position: relative;
overflow: hidden;
}
/* 玻璃质感叠加 */
.secret-card::before {
content: ''; position: absolute; inset: 0;
background: linear-gradient(to bottom right, rgba(255,255,255,0.4), rgba(255,255,255,0.1));
z-index: 0;
}
.secret-card > * { position: relative; z-index: 1; }
.secret-card h3 {
font-size: 28px; color: #2d3748; margin-bottom: 20px;
font-weight: 900;
}
.secret-card p {
font-size: 19px; line-height: 1.8; color: #4a5568; margin: 10px 0;
}
.secret-emoji {
font-size: 70px; margin-bottom: 25px;
background: white; width: 120px; height: 120px;
display: flex; align-items: center; justify-content: center;
border-radius: 50%;
box-shadow: 0 10px 20px rgba(0,0,0,0.05);
}
/* 滑动提示 */
.swipe-hint {
text-align: center; color: #a0aec0; font-size: 13px; margin-bottom: 10px;
animation: pulse 2s infinite;
}
@keyframes pulse { 0%,100%{opacity:0.6} 50%{opacity:1} }
/* SVG Glow Effects Definition (Hidden but used) */
.svg-defs { position: absolute; width: 0; height: 0; }
</style>
</head>
<body>
<svg class="svg-defs">
<defs>
<filter id="dropShadow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur in="SourceAlpha" stdDeviation="2"/>
<feOffset dx="1" dy="2" result="offsetblur"/>
<feFlood flood-color="rgba(0,0,0,0.2)"/>
<feComposite in2="offsetblur" operator="in"/>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="glow">
<feGaussianBlur stdDeviation="2.5" result="coloredBlur"/>
<feMerge>
<feMergeNode in="coloredBlur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
</svg>
<div id="app">
<div class="content-area">
<div v-show="currentPage === 1" class="intro-page">
<div class="intro-emoji">🖋️</div>
<div class="intro-title">一笔画挑战</div>
<div class="intro-text">
<p>同学们,你们有没有玩过一个游戏:用<strong>一笔</strong>画出一个图形,笔尖不离开纸,线也不重复?</p>
<p>这看起来像魔术,但其实有数学秘密!比如,一个邮递员想把信送到每条街,<strong>不走回头路</strong>,他能做到吗?</p>
<p>今天我们就来解开“一笔画”的密码!学会了它,你就能一眼看出哪些图形可以一笔画成,成为小小数学家!</p>
</div>
<button class="intro-btn" @click="switchPage(2); speak('欢迎来到一笔画的奇妙世界!我们先来认识两个新朋友:奇点和偶点。')">
开启探索之旅
</button>
</div>
<div v-show="currentPage === 2" class="demo-page">
<div class="page-title">一笔画的秘密</div>
<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>判断秘籍(找奇点)</span>
<span>⭐</span>
</div>
<div class="concept-body">
<div class="sub-title">如何判断?</div>
<p style="margin:0; color:#666">数每个点连了几条线。</p>
<div class="highlight-box">
✅ <strong>0个奇点</strong>:起点随便选,是个闭环。<br>
✅ <strong>2个奇点</strong>:必须从奇点开始,到奇点结束。<br>
❌ <strong>其他(4,6...)</strong>:统统不能一笔画!
</div>
</div>
</div>
<div class="animation-area">
<svg class="svg-container" id="demo-svg" viewBox="0 0 200 200">
<path id="grid-path" d="M50,50 L150,50 L150,150 L50,150 Z M50,100 L150,100 M100,50 L100,150" fill="none" stroke="#e2e8f0" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path id="pen-path" d="" fill="none" stroke="url(#gradientStroke)" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" filter="url(#dropShadow)" :opacity="directStep >= 8 ? 1 : 0"/>
<defs>
<linearGradient id="gradientStroke" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#667eea;stop-opacity:1" />
<stop offset="100%" style="stop-color:#764ba2;stop-opacity:1" />
</linearGradient>
</defs>
<g v-for="(point, i) in demoPoints" :key="'p'+i" :opacity="directStep >= point.step ? 1 : 0" style="transition: opacity 0.3s">
<circle :cx="point.x" :cy="point.y" r="14" :fill="point.isOdd ? '#fc8181' : '#68d391'" stroke="white" stroke-width="3" filter="url(#dropShadow)"/>
<text :x="point.x" :y="point.y+5" text-anchor="middle" fill="white" font-size="14" font-weight="bold" style="text-shadow: 0 1px 2px rgba(0,0,0,0.2)">{{point.lines}}</text>
<text v-if="point.isOdd" :x="point.x" :y="point.y-20" text-anchor="middle" fill="#e53e3e" font-size="10" font-weight="bold">奇</text>
</g>
<circle id="pen-tip" cx="0" cy="0" r="8" fill="#f6ad55" stroke="white" stroke-width="3" filter="url(#glow)" :opacity="directStep >= 8 ? 1 : 0"/>
</svg>
<button class="step-btn prev" @click="directStep > 0 ? directStep-- : null" :disabled="directStep <= 0">◀</button>
<button class="step-btn next" @click="directStep < 9 ? directStep++ : null" :disabled="directStep >= 9">▶</button>
</div>
<p style="text-align:center; color:#a0aec0; font-size:13px; margin-top:-15px;">👇 点击右下角按钮,一步步数点</p>
</div>
<div v-show="demoTab === 'theory'">
<div class="concept-card">
<div class="concept-header bg-purple">
<span>进出原理</span>
</div>
<div class="concept-body">
<div class="sub-title">为什么必须是奇点?</div>
<div class="highlight-box">
🚪 <strong>偶点是“过路站”</strong>:有进必有出,成双成对。<br>
🏁 <strong>奇点是“终点站”</strong>:可以只进不出,或者只出不进。<br>
所以,除了开头和结尾,中间的点必须都是“过路站”(偶点)。
</div>
</div>
</div>
<div class="concept-card">
<div class="concept-header bg-blue">
<span>手指小实验</span>
</div>
<div class="concept-body">
<div>伸出你的手,5根手指就是5个点。</div>
<div class="highlight-box" style="border-left-color: #48bb78; background: #f0fff4; color: #2f855a;">
手指缝就是“线”。试着从大拇指滑到小指,是不是每个缝隙都只能滑一次?这就是一笔画!
</div>
</div>
</div>
</div>
</div>
<div v-show="currentPage === 3" class="explain-page">
<div class="page-title">经典例题解析</div>
<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"><span style="color:#4299e1">●</span> 例题1:“口”字</span>
<span class="toggle-icon">▼</span>
</div>
<div class="card-content" @click.stop>
<div><strong>❓ 题目:</strong>汉字“口”能不能一笔画?</div>
<div style="margin-top:5px; color:#38a169; font-weight:bold;">✅ 答案:能</div>
<div style="margin-top:10px; padding-top:10px; border-top:1px dashed #eee;">
<strong>💡 解析:</strong><br>
4个角都是偶点(连2条线)。<br>
0个奇点 → 可以一笔画。
</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"><span style="color:#9f7aea">●</span> 例题2:“田”字</span>
<span class="toggle-icon">▼</span>
</div>
<div class="card-content" @click.stop>
<div><strong>❓ 题目:</strong>汉字“田”能不能一笔画?</div>
<div style="margin-top:5px; color:#e53e3e; font-weight:bold;">❌ 答案:不能</div>
<div style="margin-top:10px; padding-top:10px; border-top:1px dashed #eee;">
<strong>💡 解析:</strong><br>
看看上下左右4个交叉点,它们都连了3条线(奇点)。<br>
4个奇点 > 2个 → 不能一笔画。
</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"><span style="color:#f56565">●</span> 例题3:起点的选择</span>
<span class="toggle-icon">▼</span>
</div>
<div class="card-content" @click.stop>
<div><strong>❓ 题目:</strong>如果有2个奇点,从哪里开始画?</div>
<div style="margin-top:5px; color:#38a169; font-weight:bold;">✅ 答案:必须从奇点开始</div>
<div style="margin-top:10px; padding-top:10px; border-top:1px dashed #eee;">
<strong>💡 诀窍:</strong><br>
记住口诀:<strong>“奇点出发奇点束”</strong>。<br>
随便找个偶点开始是画不出来的哦!
</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,
'shake': 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">
<div class="completion-emoji">🎊</div>
<div class="completion-title">练习完成!</div>
<div class="final-score">{{score}} 分</div>
<button class="intro-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-top: 5px solid #667eea;">
<div class="question-header">
<div>
<span class="question-number">第 {{currentOlympiad + 1}} 关</span>
<span class="difficulty-badge" :style="{
background: olympiadQuestions[currentOlympiad].difficulty === 'hard' ? '#fed7d7' : '#feebc8',
color: olympiadQuestions[currentOlympiad].difficulty === 'hard' ? '#c53030' : '#c05621',
padding: '4px 8px', borderRadius: '12px', fontSize: '12px', marginLeft: '8px'
}">
{{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,
'shake': !olympiadAnswered && selectedOlympiadOption === index && !option.correct
}"
@click="selectOlympiadOption(index, option.correct)"
:disabled="olympiadAnswered || wrongAttempts >= 3"
>
{{option.text}}
</button>
</div>
<div v-if="wrongAttempts > 0 && !olympiadAnswered">
<div class="feedback-area feedback-wrong" style="background:#fffaf0; border-color:#fbd38d; color:#9c4221">
<span v-if="wrongAttempts === 1">💡 提示1: {{olympiadQuestions[currentOlympiad].hint1}}</span>
<span v-if="wrongAttempts === 2">💡 提示2: {{olympiadQuestions[currentOlympiad].hint2}}</span>
</div>
</div>
<div v-if="olympiadAnswered" class="feedback-area" :class="isOlympiadCorrect ? 'feedback-correct' : 'feedback-wrong'">
{{olympiadQuestions[currentOlympiad].explanation}}
</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="intro-btn" @click="switchPage(6)">查看通关秘籍 →</button>
</div>
</div>
<div v-show="currentPage === 6" class="secret-page">
<div class="page-title" style="margin-top: 30px;">通关秘籍</div>
<div class="swipe-hint">← 左右滑动查看秘籍 →</div>
<div class="secret-container">
<div class="secret-card">
<div class="secret-emoji">1️⃣</div>
<h3>第一步:数奇点</h3>
<p>别急着画,先看点。<br>数数每个交叉点连了几条线。</p>
</div>
<div class="secret-card">
<div class="secret-emoji">2️⃣</div>
<h3>第二步:判死活</h3>
<p>0个奇点 = 活的 (闭环)<br>2个奇点 = 活的 (开环)<br>其他情况 = 死的 (画不出)</p>
</div>
<div class="secret-card">
<div class="secret-emoji">3️⃣</div>
<h3>第三步:定起点</h3>
<p>如果是2个奇点,<br>一定要从<strong>奇点出发</strong>,<br>并在<strong>另一个奇点结束</strong>。</p>
</div>
</div>
<div style="padding: 20px;">
<button class="intro-btn" style="width: 100%;" @click="switchPage(1)">↺ 重玩一次</button>
</div>
</div>
</div>
<div class="bottom-nav">
<div class="nav-item" :class="{active: currentPage === 1}" @click="switchPage(1)">
<span class="nav-emoji">🎯</span>
<span>开始</span>
</div>
<div class="nav-item" :class="{active: currentPage === 2}" @click="switchPage(2)">
<span class="nav-emoji">🧠</span>
<span>原理</span>
</div>
<div class="nav-item" :class="{active: currentPage === 3}" @click="switchPage(3)">
<span class="nav-emoji">📖</span>
<span>例题</span>
</div>
<div class="nav-item" :class="{active: currentPage === 4}" @click="switchPage(4)">
<span class="nav-emoji">✏️</span>
<span>练习</span>
</div>
<div class="nav-item" :class="{active: currentPage === 5}" @click="switchPage(5)">
<span class="nav-emoji">⚡</span>
<span>挑战</span>
</div>
<div class="nav-item" :class="{active: currentPage === 6}" @click="switchPage(6)">
<span class="nav-emoji">📜</span>
<span>秘籍</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, onMounted, watch, 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 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();
}
}
// 页面2: 概念讲解
const demoTab = ref('direct');
const switchDemoTab = (tab) => {
demoTab.value = tab;
directStep.value = 0;
runDirectAnimation();
}
// 动画数据与逻辑 (保持ID和逻辑一致,配合新SVG样式)
const directStep = ref(0);
const demoPoints = ref([
{x: 50, y: 50, lines: 2, isOdd: false, step: 1},
{x: 100, y: 50, lines: 3, isOdd: true, step: 2},
{x: 150, y: 50, lines: 2, isOdd: false, step: 3},
{x: 50, y: 100, lines: 3, isOdd: true, step: 4},
{x: 100, y: 100, lines: 4, isOdd: false, step: 5},
{x: 150, y: 100, lines: 3, isOdd: true, step: 6},
{x: 50, y: 150, lines: 2, isOdd: false, step: 7},
{x: 100, y: 150, lines: 3, isOdd: true, step: 8},
{x: 150, y: 150, lines: 2, isOdd: false, step: 9},
]);
const runDirectAnimation = () => {
nextTick(() => {
const tl = gsap.timeline({paused: true});
gsap.set("#pen-path", {attr: {d: ""}});
gsap.set("#pen-tip", {x: 0, y: 0});
// 节点出现动画
for(let i=1; i<=9; i++) {
tl.to({}, {duration: 0.2, onStart: () => {
if(i===2||i===4||i===6||i===8) {
speak('叮,发现奇点');
}
}}, i*0.5);
}
// 路径绘制动画
tl.to({}, {duration: 0.5, onStart: () => {
speak('看,从奇点出发,一笔画完!');
}}, 5);
tl.to("#pen-path", {duration: 3, attr: {d: "M50,50 L150,50 L150,150 L50,150 L50,100 L150,100 M100,50 L100,150"}, ease: "power1.inOut"}, 5);
tl.to("#pen-tip", {duration: 3, motionPath: {
path: "M50,50 L150,50 L150,150 L50,150 L50,100 L150,100 M100,50 L100,150",
align: "self"
}, ease: "power1.inOut"}, 5);
// 修正:上面的 motionPath 需要注册插件,为了简单兼容原逻辑,这里手动模拟终点位移
// 如果没有 motionPath plugin,简单的 x/y 位移可能不对齐路径,但在本例中主要是视觉效果
// 原代码使用的是 x/y 位移,这里保留以防无插件
tl.to("#pen-tip", {duration: 3, x: 100, y: 150, ease: "none", overwrite: "auto"}, 5);
tl.seek(directStep.value * 0.7);
});
}
watch(directStep, (newStep) => {
runDirectAnimation();
});
// 页面3: 讲解页
const explainTab = ref('basic');
const expandedCard = ref(-1);
const toggleCard = (index) => {
expandedCard.value = expandedCard.value === index ? -1 : index;
}
// 页面4: 课内练习 (数据不变)
const practiceCompleted = ref(false);
const currentQuestion = ref(0);
const score = ref(0);
const answered = ref(false);
const selectedOption = ref(null);
const isCorrect = ref(false);
const practiceQuestions = ref([
{
text: `题目1:下列图形中,奇点个数是0的是?`,
options: [
{text: 'A.五角星', correct: true},
{text: 'B.日字', correct: false},
{text: 'C.田字', correct: false},
{text: 'D.山字', correct: false}
],
explanation: `五角星每个点连2条线,全是偶点(0奇点)。`
},
{
text: `题目2:要想一笔画,奇点的个数只能是?`,
options: [
{text: 'A.1个', correct: false},
{text: 'B.2个或0个', correct: true},
{text: 'C.3个', correct: false},
{text: 'D.无数个', correct: false}
],
explanation: `要么没头没尾(0),要么有头有尾(2)。`
},
{
text: `题目3:“中”字能不能一笔画?`,
options: [
{text: 'A.能', correct: true},
{text: 'B.不能', correct: false}
],
explanation: `中字有两个奇点(中间方块的上下交点),所以可以。`
},
{
text: `题目4:奥运五环能一笔画吗?`,
options: [
{text: 'A.能', correct: false},
{text: 'B.不能', correct: true}
],
explanation: `五环相扣极其复杂,奇点远超2个,不能一笔画。`
},
{
text: `题目5:把“田”字改成一笔画,至少要加几条线?`,
options: [
{text: 'A.1条', correct: true},
{text: 'B.2条', correct: false},
{text: 'C.3条', correct: false},
{text: 'D.4条', correct: false}
],
explanation: `加一条线连接两个奇点,消灭它们,这就只剩2个奇点了。`
},
{
text: `题目6:一个图形有4个奇点,至少几笔画成?`,
options: [
{text: 'A.1笔', correct: false},
{text: 'B.2笔', correct: true},
{text: 'C.3笔', correct: false},
{text: 'D.4笔', correct: false}
],
explanation: `笔数 = 奇点数 ÷ 2。4 ÷ 2 = 2笔。`
},
{
text: `题目7:下面哪个汉字可以一笔画?`,
options: [
{text: 'A.木', correct: false},
{text: 'B.大', correct: false},
{text: 'C.土', correct: true},
{text: 'D.十', correct: false}
],
explanation: `土字有两个T形交点(奇点),其余是偶点。`
},
{
text: `题目8:如果有2个奇点,最后停在哪里?`,
options: [
{text: 'A.起点', correct: false},
{text: 'B.另一个奇点', correct: true},
{text: 'C.任意点', correct: false},
{text: 'D.中间', correct: false}
],
explanation: `奇点进出不对等,一个进一个出。`
}
]);
const selectOption = (index, correct) => {
if (answered.value) return;
selectedOption.value = index;
answered.value = true;
isCorrect.value = correct;
if (correct) {
score.value += 10;
speak('太棒了!');
} else {
speak('再想想哦。');
}
}
const nextQuestion = () => {
if (currentQuestion.value < practiceQuestions.value.length - 1) {
currentQuestion.value++;
answered.value = false;
selectedOption.value = null;
} else {
practiceCompleted.value = true;
speak('恭喜完成练习!');
}
}
// 页面5: 奥数挑战 (数据不变)
const olympiadCompleted = ref(false);
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 olympiadQuestions = ref([
{
text: `题目1:两座小岛和河岸之间有7座桥(哥尼斯堡七桥问题),能不能不重复地走完?`,
options: [
{text: 'A.能', correct: false},
{text: 'B.不能', correct: true}
],
explanation: `这是数学史上最著名的问题。因为4个地点全是奇点,所以不可能。`,
hint1: `数数每个地点连接了多少座桥。`,
hint2: `全是奇数吗?`,
difficulty: 'medium',
difficultyText: '经典'
},
{
text: `题目2:邮递员送信,要不重复地走遍“日”字加一条竖线的街道,可能吗?`,
options: [
{text: 'A.可能', correct: false},
{text: 'B.不可能', correct: true}
],
explanation: `加了竖线后,奇点数量变得更多了(超过2个),所以不可能。`,
hint1: `先想“日”字有几个奇点?`,
hint2: `加一根线增加了几个奇点?`,
difficulty: 'medium',
difficultyText: '中等'
},
{
text: `题目3:一只蚂蚁在3x3的方格纸网线上爬,想不重复走完,行吗?`,
options: [
{text: 'A.行', correct: false},
{text: 'B.不行', correct: true}
],
explanation: `3x3网格周围的点大多是奇点,总数远超2个。`,
hint1: `画个井字棋盘扩充一下。`,
hint2: `边缘的点连了几条线?`,
difficulty: 'hard',
difficultyText: '较难'
},
{
text: `题目4:5个小朋友互相连线(五边形加对角线),能一笔画吗?`,
options: [
{text: 'A.能', correct: true},
{text: 'B.不能', correct: false}
],
explanation: `每个人连4条线,4是偶数,所以全是偶点,可以一笔画!`,
hint1: `每个人连几个人?`,
hint2: `4是奇数还是偶数?`,
difficulty: 'easy',
difficultyText: '简单'
},
{
text: `题目5:将一个正方体的骨架一笔画出,行吗?`,
options: [
{text: 'A.行', correct: false},
{text: 'B.不行', correct: true}
],
explanation: `正方体8个角,每个角连3条线,全是奇点。`,
hint1: `正方体有几个角?`,
hint2: `每个角连几条棱?`,
difficulty: 'medium',
difficultyText: '中等'
},
{
text: `题目6:游乐园大门进,后门出,不走回头路。设计图需要几个奇点?`,
options: [
{text: 'A.0个', correct: false},
{text: 'B.1个', correct: false},
{text: 'C.2个', correct: true},
{text: 'D.4个', correct: false}
],
explanation: `起点和终点不同,这就是典型的“2个奇点”模型。`,
hint1: `大门和后门一样吗?`,
hint2: `有头有尾是几个奇点?`,
difficulty: 'easy',
difficultyText: '应用'
},
{
text: `题目7:6个点两两相连,至少去掉几条线才能一笔画?`,
options: [
{text: 'A.1', correct: true},
{text: 'B.2', correct: false},
{text: 'C.3', correct: false},
{text: 'D.0', correct: false}
],
explanation: `原本每点连5条线(奇)。去掉1条线,这条线两端的点变成连4条(偶),所有点变偶点。`,
hint1: `现在每点连几条?`,
hint2: `怎么把奇数变成偶数?`,
difficulty: 'hard',
difficultyText: '较难'
},
{
text: `题目8:如果是0个奇点,起点和终点是什么关系?`,
options: [
{text: 'A.重合', correct: true},
{text: 'B.不重合', correct: false},
{text: 'C.没关系', correct: false}
],
explanation: `0个奇点意味着必须回到原点,形成闭环。`,
hint1: `比如画一个圆圈。`,
hint2: `从哪里开始,到哪里结束?`,
difficulty: 'easy',
difficultyText: '基础'
}
]);
const selectOlympiadOption = (index, correct) => {
if (olympiadAnswered.value || wrongAttempts.value >= 3) return;
selectedOlympiadOption.value = index;
if (correct) {
isOlympiadCorrect.value = true;
olympiadAnswered.value = true;
olympiadScore.value += 20;
confetti({
particleCount: 100,
spread: 70,
origin: { y: 0.6 }
});
speak('太厉害了!');
} else {
wrongAttempts.value++;
isOlympiadCorrect.value = false;
if (wrongAttempts.value >= 3) {
olympiadAnswered.value = true;
speak('三次机会已用完,我们来看看解析。');
} else {
speak('不对哦,再试试看。');
}
}
}
const nextOlympiad = () => {
if (currentOlympiad.value < olympiadQuestions.value.length - 1) {
currentOlympiad.value++;
olympiadAnswered.value = false;
selectedOlympiadOption.value = null;
wrongAttempts.value = 0;
isOlympiadCorrect.value = false;
} else {
olympiadCompleted.value = true;
speak('恭喜完成奥数挑战,你真是一位小小数学家!');
}
}
onMounted(() => {
runDirectAnimation();
});
return {
speak, stopSpeak, currentPage, switchPage, demoTab, switchDemoTab, directStep, demoPoints,
explainTab, expandedCard, toggleCard, practiceCompleted, currentQuestion, score, answered,
selectedOption, isCorrect, practiceQuestions, selectOption, nextQuestion, olympiadCompleted,
currentOlympiad, olympiadScore, olympiadAnswered, selectedOlympiadOption, isOlympiadCorrect,
wrongAttempts, olympiadQuestions, selectOlympiadOption, nextOlympiad
}
}
}).mount('#app');
</script>
</body>
</html>
💡 这段代码完全由 AI 生成。
登录后可复制完整代码