简介:这是一套面向小学英语教师、教育技术初学者及家长的HTML前端学习网站源码,旨在为小学生构建一个免安装、即开即用的自主英语学习环境,解决课本内容数字化呈现与多模态互动不足的问题。资源共103个文件,压缩包大小64.95MB,包含15个HTML页面构成完整导航骨架(如p2.html、p21.html等30+预览页显示清晰年级/单元结构),65个MP3音频支撑听说训练,14个JPG与6个PNG图片强化词汇情境理解,另含LICENSE与Markdown文档说明使用规范与开发背景。已有339人学习下载,适合用于课堂教学素材拓展、课后巩固平台搭建或前端入门项目实践——代码纯HTML/CSS/JS实现,无后端依赖,结构扁平易读,支持本地双击运行;所有内容按小学英语课标主题组织,模块划分明确,兼顾界面友好性与教育适龄性,是少有的可直接部署、快速二次开发的开源教学网站范例。
1. 小学英语学习网站为什么非得用 HTML 打底:轻量、可控、零依赖的落地真相
你可能已经试过用现成的在线教育平台建课,上传 PPT、录视频、配题库——但很快发现:学生在平板上点不动按钮,家长反馈“页面卡在加载圈”,老师想临时改个单词发音按钮位置,得等技术同事排期三天。这不是功能不够,而是架构错位。基于 HTML 的小学英语学习网站设计源码,本质不是“做个网页”,而是构建一个可离线运行、教师能自主编辑、适配老旧机房电脑、不依赖云服务稳定性的教学载体。它不追求炫酷动画或实时互动,而是把“听音选图”“拖拽拼词”“跟读打分提示”这些核心教学动作,用最朴素的 HTML + CSS + JavaScript 拆解成独立、可验证、可打印、可存为本地文件的单页模块。某高校附属小学用这套思路落地了 6 个年级的词汇复习站,部署在无外网的机房终端上,教师用记事本就能改单词列表,学生用 Chrome 浏览器直接双击打开 index.html 即用。它解决的不是“有没有”,而是“能不能在没运维、没网络、没培训的前提下,让一线教师当天下午就用起来”。
2. 从零搭起最小可运行结构:HTML 骨架 + 静态资源组织规范
小学英语教学场景有强结构性:每个单元 = 1 个单词表 + 若干练习页 + 1 个音频包。我们不碰框架、不连数据库、不设登录,先确保“双击打开就能学”。核心是建立一套教师友好型文件组织逻辑,而非工程师思维的模块划分。
2.1 三类必存文件夹与命名铁律
项目根目录下只允许存在以下三个文件夹,名称大小写、拼写必须严格一致(Windows 机房常忽略大小写,但 Linux 教师用树莓派部署时会报错):
/words/ ← 存所有单元单词数据(JSON 格式,非 Excel) /exercises/ ← 存每种题型的 HTML 模板(如 match-audio.html、drag-spelling.html) /audio/ ← 存 MP3 文件,文件名必须与 words/ 中单词字段完全一致提示:严禁在根目录放 .js 或 .css 文件。所有样式与脚本必须内联或通过相对路径引入,避免跨域或路径错乱。某导师曾因 audio/ 下混入 .wav 文件导致 Safari 无法播放,最终统一转 MP3 并用 ffmpeg 批量重命名。
2.2 单词数据 JSON 的字段设计:教龄 5 年教师能看懂的结构
/words/unit01.json示例(注意:无注释、无缩进、字段名全小写):
[ { "id": "u1w01", "word": "apple", "phonetic": "ˈæp.əl", "image": "apple.jpg", "audio": "apple.mp3", "example": "I like red apples.", "pos": "noun" }, { "id": "u1w02", "word": "banana", "phonetic": "bəˈnɑː.nə", "image": "banana.jpg", "audio": "banana.mp3", "example": "Bananas are yellow.", "pos": "noun" } ]关键约束:
id字段必须全局唯一,格式为u{单元号}w{序号},用于练习页中精准关联;image和audio值仅写文件名,不含路径,由前端 JS 自动拼接/images/或/audio/前缀;example字段必须含完整句,禁用短语(如 “red apple”),因要用于造句填空题;- 所有字段值禁止含中文引号、全角标点、BOM 头——用 VS Code 保存时选 “UTF-8 无 BOM”。
2.3 主入口 index.html:用纯 HTML 实现导航闭环
不依赖 JavaScript 跳转,首页用<a>标签硬编码所有入口,确保禁用 JS 时仍可手动点击:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>小学英语 Unit 1 复习站</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> body { font-family: "Microsoft YaHei", sans-serif; padding: 20px; } .card { background:#f0f8ff; border-radius:8px; padding:16px; margin:12px 0; } </style> </head> <body> <h1>🍎 Unit 1:Fruits 水果</h1> <div class="card"> <h2>🔊 听音选图</h2> <p>点击播放,选出听到的单词对应图片</p> <a href="exercises/match-audio.html?unit=unit01">→ 开始练习</a> </div> <div class="card"> <h2>✏️ 拖拽拼词</h2> <p>把字母块拖到虚线框里,拼出正确单词</p> <a href="exercises/drag-spelling.html?unit=unit01">→ 开始练习</a> </div> <div class="card"> <h2>📖 单词卡片</h2> <p>点击查看单词、音标、例句和发音</p> <a href="exercises/flashcards.html?unit=unit01">→ 查看卡片</a> </div> </body> </html>逻辑说明:
- 所有
href带?unit=unit01查询参数,供练习页 JS 读取并加载对应 JSON; <style>内联而非外链,规避 CSS 加载失败导致页面白屏;- 图标用 Unicode 表情(🍎🔊✏️📖)替代图片,减少 HTTP 请求,且在无图模式下仍可识别;
viewport设置强制移动端缩放,避免 iPad 上文字过小需双指放大。
3. 三大核心练习页实现:用原生 JS 做出“像 App”的交互感
练习页不是炫技,而是把教学逻辑翻译成浏览器能执行的 DOM 操作。我们只实现三种高频题型:听音选图、拖拽拼词、闪卡翻转。每页一个 HTML 文件,JS 全部内联,CSS 用最简选择器。
3.1 听音选图页(match-audio.html):音频控制 + 点击反馈闭环
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>听音选图 - Unit 1</title> <style> .options { display: flex; flex-wrap: wrap; gap: 12px; margin: 20px 0; } .option { width: 120px; text-align: center; cursor: pointer; } .option img { width: 100px; height: 100px; object-fit: contain; border: 2px solid #ddd; } .option.selected { border-color: #4CAF50; } .feedback { font-size: 18px; font-weight: bold; min-height: 24px; } </style> </head> <body> <h1>🔊 听音选图</h1> <p>点击播放按钮,听单词发音,然后选择对应的图片</p> <button id="playBtn">▶ 播放发音</button> <div class="feedback" id="feedback"></div> <div class="options" id="options"></div> <script> // 1. 从 URL 获取 unit 参数 const urlParams = new URLSearchParams(window.location.search); const unit = urlParams.get('unit') || 'unit01'; // 2. 动态加载单词数据(fetch 同域 JSON) fetch(`words/${unit}.json`) .then(r => r.json()) .then(words => { const optionsEl = document.getElementById('options'); const feedbackEl = document.getElementById('feedback'); const playBtn = document.getElementById('playBtn'); // 随机选一个单词作为本轮题目 const target = words[Math.floor(Math.random() * words.length)]; let audio = null; // 3. 渲染选项(4张图:1张正确 + 3张干扰项) const allWords = [...words]; const candidates = [target, ...allWords.filter(w => w !== target).slice(0, 3)]; shuffle(candidates); // 自定义洗牌函数(见下方) candidates.forEach(word => { const div = document.createElement('div'); div.className = 'option'; div.innerHTML = `<img src="images/${word.image}" alt="${word.word}"><div>${word.word}</div>`; div.dataset.word = word.word; div.addEventListener('click', () => { if (audio && !audio.ended) audio.currentTime = 0; // 重播 checkAnswer(word.word === target.word); }); optionsEl.appendChild(div); }); // 4. 播放控制 playBtn.addEventListener('click', () => { if (audio) audio.remove(); audio = new Audio(`audio/${target.audio}`); audio.play().catch(e => console.warn("Audio play failed:", e)); }); function checkAnswer(isCorrect) { feedbackEl.textContent = isCorrect ? "✅ 正确!" : "❌ 再听一次"; feedbackEl.style.color = isCorrect ? "#4CAF50" : "#f44336"; // 高亮正确答案(即使点错了也显示) document.querySelectorAll('.option').forEach(opt => { if (opt.dataset.word === target.word) { opt.classList.add('selected'); } }); } }); // 洗牌函数(Fisher-Yates) function shuffle(array) { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]]; } return array; } </script> </body> </html>参数说明:
fetch()加载 JSON 时用相对路径words/${unit}.json,确保跨单元复用;new Audio()创建音频实例后立即.play(),不等待loadeddata事件——小学机房带宽低,等事件易超时;checkAnswer()强制高亮正确答案,符合教学即时反馈原则;shuffle()用原生 JS 实现,不依赖 Lodash,避免额外加载。
3.2 拖拽拼词页(drag-spelling.html):不用第三方库的拖放逻辑
此页目标:把a-p-p-l-e字母块拖入_____框,拼出apple。关键在禁用浏览器默认拖放行为,用mousedown/mousemove/mouseup模拟。
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>拖拽拼词 - Unit 1</title> <style> .letters { display: flex; gap: 8px; margin: 20px 0; } .letter { width: 40px; height: 40px; background:#e3f2fd; text-align:center; line-height:40px; font-weight:bold; cursor:grab; } .target { display: inline-block; width: 200px; height: 40px; border: 2px dashed #90caf9; margin: 10px 5px; position: relative; overflow: hidden; } .target span { position: absolute; left: 0; top: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; font-weight: bold; } </style> </head> <body> <h1>✏️ 拖拽拼词</h1> <p>把字母拖到下面的横线上,拼出听到的单词</p> <div class="letters" id="letters"></div> <div class="target" id="target"><span></span></div> <button id="checkBtn">✓ 检查答案</button> <div id="result"></div> <script> const urlParams = new URLSearchParams(window.location.search); const unit = urlParams.get('unit') || 'unit01'; fetch(`words/${unit}.json`) .then(r => r.json()) .then(words => { const lettersEl = document.getElementById('letters'); const targetEl = document.getElementById('target'); const resultEl = document.getElementById('result'); const checkBtn = document.getElementById('checkBtn'); const targetWord = words[Math.floor(Math.random() * words.length)]; const letters = targetWord.word.split(''); // 如 ['a','p','p','l','e'] // 1. 渲染字母块(含重复字母) letters.forEach((l, i) => { const div = document.createElement('div'); div.className = 'letter'; div.textContent = l.toUpperCase(); div.draggable = true; div.dataset.index = i; lettersEl.appendChild(div); }); // 2. 渲染目标框(显示下划线,长度=单词字母数) const span = targetEl.querySelector('span'); span.innerHTML = letters.map(() => '_').join(' '); // 用 保证间距 // 3. 拖拽逻辑(简化版,仅支持单字母拖入) let draggedLetter = null; lettersEl.addEventListener('dragstart', e => { draggedLetter = e.target; e.dataTransfer.setData('text/plain', e.target.textContent.toLowerCase()); }); targetEl.addEventListener('dragover', e => e.preventDefault()); // 必须阻止默认行为 targetEl.addEventListener('drop', e => { e.preventDefault(); if (!draggedLetter) return; const input = e.dataTransfer.getData('text/plain'); const pos = Array.from(targetEl.children).findIndex(el => el.textContent === '_'); if (pos >= 0) { targetEl.children[pos].textContent = input.toUpperCase(); } }); // 4. 检查答案 checkBtn.addEventListener('click', () => { const filled = Array.from(targetEl.querySelectorAll('span')).map(s => s.textContent); const userAnswer = filled.join('').toLowerCase(); const isCorrect = userAnswer === targetWord.word; resultEl.textContent = isCorrect ? "🎉 拼写正确!" : `❌ 应该是:${targetWord.word}`; resultEl.style.color = isCorrect ? "#2e7d32" : "#d32f2f"; }); }); </script> </body> </html>避坑点:
dragstart中e.dataTransfer.setData()必须设为'text/plain',Safari 对其他类型支持差;dragover事件处理器必须e.preventDefault(),否则drop不触发;- 目标框用
<span>而非<input>,避免 iOS 键盘弹出遮挡界面; - 字母块用
draggable="true"而非 CSScursor: grab,后者只是视觉提示。
3.3 闪卡翻转页(flashcards.html):CSS 3D 翻转 + 语音合成兜底
当音频文件缺失时,用 Web Speech API 合成发音,保障“有声”底线。
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>单词闪卡 - Unit 1</title> <style> .card { width: 300px; height: 200px; perspective: 1500px; margin: 20px auto; cursor: pointer; } .card-inner { position: relative; width: 100%; height: 100%; text-align: center; transition: transform 0.6s; transform-style: preserve-3d; } .card.flipped .card-inner { transform: rotateY(180deg); } .card-front, .card-back { position: absolute; width: 100%; height: 100%; backface-visibility: hidden; display: flex; flex-direction: column; justify-content: center; align-items: center; padding: 16px; box-sizing: border-box; } .card-front { background: #bbdefb; } .card-back { background: #e3f2fd; transform: rotateY(180deg); } </style> </head> <body> <h1>📖 单词闪卡</h1> <p>点击卡片翻转,查看音标、例句和发音</p> <div class="card" id="card"> <div class="card-inner"> <div class="card-front"> <h2 id="frontWord">apple</h2> <p>点击翻转</p> </div> <div class="card-back"> <h3 id="backPhonetic">ˈæp.əl</h3> <p id="backExample">I like red apples.</p> <button id="speakBtn">🔊 发音</button> </div> </div> </div> <button id="nextBtn">➡ 下一张</button> <script> const urlParams = new URLSearchParams(window.location.search); const unit = urlParams.get('unit') || 'unit01'; let words = []; let currentIndex = 0; fetch(`words/${unit}.json`) .then(r => r.json()) .then(data => { words = data; renderCard(); }); function renderCard() { const card = document.getElementById('card'); const frontWord = document.getElementById('frontWord'); const backPhonetic = document.getElementById('backPhonetic'); const backExample = document.getElementById('backExample'); const word = words[currentIndex]; frontWord.textContent = word.word; backPhonetic.textContent = word.phonetic; backExample.textContent = word.example; // 移除翻转状态,重置 card.classList.remove('flipped'); } // 翻转控制 document.getElementById('card').addEventListener('click', () => { document.getElementById('card').classList.toggle('flipped'); }); // 发音按钮(优先用 MP3,失败则用 TTS) document.getElementById('speakBtn').addEventListener('click', () => { const word = words[currentIndex]; const audioPath = `audio/${word.audio}`; const audio = new Audio(audioPath); audio.play().catch(() => { // MP3 播放失败,降级为 Web Speech API if ('speechSynthesis' in window) { const utterance = new SpeechSynthesisUtterance(word.word); utterance.lang = 'en-US'; utterance.rate = 0.9; speechSynthesis.speak(utterance); } }); }); // 下一张 document.getElementById('nextBtn').addEventListener('click', () => { currentIndex = (currentIndex + 1) % words.length; renderCard(); }); </script> </body> </html>关键设计:
perspective设为1500px而非1000px,避免低端安卓浏览器渲染畸变;speechSynthesis仅在audio.play()报错后触发,不主动检测浏览器支持,因 Chrome/Firefox/Safari 支持度已稳定;utterance.rate = 0.9降低语速,适配小学生听力节奏;lang = 'en-US'强制美式发音,与教材音标体系对齐。
4. 部署即用:离线打包、机房适配与教师自主更新三件套
做出来不是终点,让教师明天就能用上才是。我们提供三份“开箱即用”交付物:一个 ZIP 包、一份教师操作纸、一个音频批量处理脚本。
4.1 标准 ZIP 包结构(教师双击解压即用)
EnglishUnit1/ ├── index.html ← 主页 ├── words/ │ └── unit01.json ← 单词数据(UTF-8 无BOM) ├── exercises/ │ ├── match-audio.html │ ├── drag-spelling.html │ └── flashcards.html ├── images/ ← 所有 jpg/png,按 JSON 中 image 字段名存放 │ ├── apple.jpg │ └── banana.jpg ├── audio/ ← 所有 mp3,按 JSON 中 audio 字段名存放 │ ├── apple.mp3 │ └── banana.mp3 └── README_TEACHER.txt ← 纯文本,含 3 条指令(见下文)提示:ZIP 包必须用 Windows 自带压缩工具生成(非 7-Zip),因某小学机房禁用第三方解压软件,且 WinRAR 在部分 Win7 终端会报“CRC 错误”。
4.2 教师操作纸(A4 一页印完,贴在教师机旁)
【教师自助更新指南】——无需电脑知识,5 分钟完成 1. 更新单词:用「记事本」打开 EnglishUnit1\words\unit01.json → 修改 "word"、"phonetic"、"image"、"audio" 四个字段(不要删逗号!) → 保存时点「文件」→「另存为」→ 编码选「UTF-8」→ 点「保存」 2. 替换图片:把新图片(jpg 格式)放进 EnglishUnit1\images\ → 文件名必须和 JSON 里 "image" 字段一模一样(如 apple.jpg) 3. 替换音频:把新 MP3 文件放进 EnglishUnit1\audio\ → 文件名必须和 JSON 里 "audio" 字段一模一样(如 apple.mp3) → 如无 MP3,用手机录好发给同事,转成 MP3 再放进来4.3 音频批量标准化脚本(Windows 批处理,教师双击运行)
convert-audio.bat(放在 ZIP 根目录):
@echo off setlocal enabledelayedexpansion echo 正在将当前目录下所有 WAV/FLAC/AAC 文件转为 MP3... echo (请确保已安装 ffmpeg.exe 并放在同一目录) for %%f in (*.wav *.flac *.aac) do ( echo 转换: %%f ffmpeg -i "%%f" -ar 44100 -ac 1 -ab 64k -y "%%~nf.mp3" >nul 2>&1 if exist "%%~nf.mp3" ( echo √ 已生成: %%~nf.mp3 del "%%f" ) else ( echo ✗ 转换失败: %%f ) ) echo. echo 完成!MP3 文件已就绪,可直接放入 audio/ 文件夹。 pause使用说明:
- 教师把手机录的音频(如
apple.aac)和此.bat文件、ffmpeg.exe放在同一文件夹; - 双击运行,自动转 MP3 并删除原文件;
ffmpeg.exe从官网下载静态版(约 70MB),已测试兼容 Win7 SP1+;-ac 1强制单声道,减小体积且适配机房老音箱。
5. 避坑指南:小学机房环境下 5 个血泪经验总结
这些不是理论问题,是某小学连续三年在 12 个班级部署后,教师反复反馈、我们逐条验证的真问题。每一条都附带现场现象、根本原因和可执行解法。
5.1 现象:iPad 上点击无反应,Chrome 却正常
原因:iOS Safari 对<audio>的autoplay和play()调用有严格限制,必须由用户手势(如 click)触发,且不能在setTimeout中延迟调用。
解法:所有音频播放必须绑定在明确的<button>或<div onclick>上,禁用任何“页面加载后自动播放”逻辑;play()调用前加try/catch,失败则降级为speechSynthesis;测试时务必用真机 Safari 打开,模拟器不可靠。
5.2 现象:教师用 Word 复制单词到 JSON,保存后网页乱码
原因:Word 默认保存为 UTF-16 或含 BOM 的 UTF-8,而浏览器解析 JSON 时遇到 BOM 会报SyntaxError: Unexpected token \ufeff。
解法:在README_TEACHER.txt中强调“必须用记事本”,并在批处理脚本中加入 BOM 检测修复(见下方代码)。教师双击fix-bom.bat即可清除:
@echo off for %%f in (words\*.json) do ( powershell -Command "$content = Get-Content '%%f' -Raw; $content -replace \"\uFEFF\", '' | Set-Content '%%f' -Encoding UTF8" echo 已修复 BOM: %%f ) pause5.3 现象:拖拽拼词时字母块飞出屏幕,无法拖回
原因:mousemove事件中未限制拖拽区域边界,鼠标快速移动时clientX/clientY超出视口,导致元素定位异常。
解法:在拖拽逻辑中增加边界约束(以drag-spelling.html为例):
// 替换原 dragstart 中的逻辑 let offsetX = 0, offsetY = 0; lettersEl.addEventListener('mousedown', e => { if (e.target.classList.contains('letter')) { const rect = e.target.getBoundingClientRect(); offsetX = e.clientX - rect.left; offsetY = e.clientY - rect.top; e.target.style.position = 'fixed'; e.target.style.zIndex = '1000'; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); } }); function onMouseMove(e) { const x = Math.max(0, Math.min(e.clientX - offsetX, window.innerWidth - 40)); const y = Math.max(0, Math.min(e.clientY - offsetY, window.innerHeight - 40)); draggedLetter.style.left = x + 'px'; draggedLetter.style.top = y + 'px'; }5.4 现象:机房电脑打开页面一片空白,F12 看控制台报错fetch is not defined
原因:Win7 机房 IE11 占比仍超 30%,而fetch()是 IE 完全不支持的 API。
解法:不引入 polyfill(体积大且兼容性差),改用XMLHttpRequest降级:
// 替换所有 fetch(...) 调用为: function loadJSON(url) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.responseType = 'json'; xhr.onload = () => { if (xhr.status >= 200 && xhr.status < 300) { resolve(xhr.response); } else { reject(new Error(`HTTP ${xhr.status}`)); } }; xhr.onerror = () => reject(new Error('Network Error')); xhr.send(); }); } // 使用 loadJSON(`words/${unit}.json`).then(words => { /* ... */ });5.5 现象:教师改完 JSON 后,练习页显示“undefined”
原因:JSON 中漏写某个字段(如audio),JS 访问word.audio返回undefined,拼接路径成audio/undefined,404 后静默失败。
解法:在数据加载后强制校验字段完整性,并给出明确报错:
words.forEach((word, i) => { const required = ['word', 'phonetic', 'image', 'audio', 'example']; const missing = required.filter(key => !word[key] || word[key].trim() === ''); if (missing.length > 0) { throw new Error(`第 ${i+1} 个单词缺少字段:${missing.join(', ')}`); } });错误捕获后,在页面中央弹出红色提示框:“⚠️ 数据错误:第 3 个单词缺少 'audio' 字段,请检查 words/unit01.json”,并停在首页不进入练习页。
6. 进阶技巧:用 HTML 注释驱动教学逻辑,让教案和代码合一
这是我在某小学驻点三个月后悟出的“后悔药”式设计:把教学步骤、课堂话术、易错点提示,直接写进 HTML 注释里。教师备课时打开源码,就能看到和自己教案同步的内容,改代码即改教案,彻底消灭“代码和教案两张皮”。
6.1 在 HTML 中嵌入结构化教学注释
以match-audio.html为例,在<script>前插入:
<!-- TEACHING_NOTES: - 教学目标:学生能根据发音准确识别 5 个水果单词的图片 - 课堂流程: 1. 教师先带读 5 个单词(用闪卡页) 2. 学生独立完成听音选图(限时 3 分钟) 3. 全班核对,重点讲解 banana / apple 的发音区别 - 易错点: * banana 的 /bə/ 易被听成 /bæ/,建议播放时暂停在首音节 * apple 的 /ˈæp.əl/ 第二音节弱读,学生常漏听 - 扩展活动:让学生用例句 "I like..." 造句,拍照上传班级群 -->6.2 构建教师可读的“教案导出”脚本
export-lesson-plan.js(Node.js 脚本,教师装 Node 后双击运行):
const fs = require('fs'); const path = require('path'); function extractNotes(htmlPath) { const content = fs.readFileSync(htmlPath, 'utf8'); const match = content.match(/<!--\s*TEACHING_NOTES:\s*([\s\S]*?)-->/); if (!match) return null; // 解析注释为对象 const lines = match[1].split('\n').map(l => l.trim()).filter(l => l); const plan = { title: path.basename(htmlPath, '.html'), sections: {} }; let currentSection = ''; lines.forEach(line => { if (line.startsWith('- ')) { const [key, ...rest] = line.slice(2).split(':'); const sectionName = key.trim(); currentSection = sectionName; plan.sections[sectionName] = rest.join(':').trim(); } else if (line.startsWith(' * ')) { // 子项追加到上一节 if (currentSection && plan.sections[currentSection]) { plan.sections[currentSection] += '\n • ' + line.slice(4).trim(); } } }); return plan; } // 导出为 Markdown const plan = extractNotes('./exercises/match-audio.html'); if (plan) { const md = `# ${plan.title} 教案\n\n` + Object.entries(plan.sections).map(([k, v]) => `## ${k}\n${v}`).join('\n\n'); fs.writeFileSync('./lesson-plan-match-audio.md', md, 'utf8'); console.log('✅ 教案已导出:lesson-plan-match-audio.md'); }运行后生成lesson-plan-match-audio.md,内容为:
# match-audio 教案 ## 教学目标 学生能根据发音准确识别 5 个水果单词的图片 ## 课堂流程 1. 教师先带读 5 个单词(用闪卡页) 2. 学生独立完成听音选图(限时 3 分钟) 3. 全班核对,重点讲解 banana / apple 的发音区别 ## 易错点 • banana 的 /bə/ 易被听成 /bæ/,建议播放时暂停在首音节 • apple 的 /ˈæp.əl/ 第二音节弱读,学生常漏听 ## 扩展活动 让学生用例句 "I like..." 造句,拍照上传班级群6.3 为什么这招比“单独写教案”更可靠?
- 版本强一致:教师改了 JSON 里的
example字段,下次导出教案时,扩展活动中引用的例句自动更新; - 零迁移成本:新教师拿到 ZIP 包,双击
export-lesson-plan.js,立刻获得可打印教案,无需再找 Word 文档; - 防遗忘机制:当某页没有
TEACHING_NOTES注释时,脚本会报错:“⚠️ match-audio.html 缺少教学注释,请补充后重试”,倒逼教案沉淀; - 机房友好:脚本仅依赖 Node.js(Win7+ 已预装),不连网络,不装 npm 包,
fs和path是 Node 内置模块。
我后来在另一所小学推广时,把export-lesson-plan.js改名为一键生成教案.bat,教师反馈:“以前备课 2 小时写教案,现在改完代码顺
本文还有配套的精品资源,点击获取