开源一个自然拼读小程序

238 阅读16分钟

一个完整的 28 天自然拼读学习路径,从「规则 → 单词 → 故事 → 练习 → 复习 → 统计」,全部落在一个微信小程序里,并基于微信云开发和一套可复用的数据/云函数体系实现。

GitHub 仓库地址:https://github.com/erDuo10/duoEnglish

效果展示

学习 拼读规则 单词 故事 小测验 统计

近30天访问量

近30天访问量

开源说明

开源文件说明
  • cloudfunctions:全部云函数
  • cursor:完整的开发过程,包括初期设计以及后期调试优化
  • data:数据初始化
  • miniprogram:全部代码

一、项目背景与设计初衷

详情可查看文档 cursor/erDuoEng/000核心理念.md

  • 一切的源头是自然拼读规则,通过拼读规则学习单词,每次单词学习后用故事进行总结巩固,再基于已经学过的内容做复习

  • 学习节奏按「天」组织,目标是「28 天掌握自然拼读规则」。

  • 目标用户:处在启蒙阶段的小学生及家长,希望通过系统化自然拼读,在 28 天内建立起字母–发音之间的联系。

  • 学习内容规模:101+ 条拼读规则、500+ 精选单词、28 篇原创故事,以及围绕这些内容设计的练习与小测验。

  • 产品目标

    • 把抽象的 phonics 规则拆成可落地的「规则 + 单词 + 故事」日程。
    • 提供可视化的进度与连续学习反馈,降低孩子和家长的使用门槛。
    • 在微信生态内完成从学习、练习、复习到统计的闭环。

额外目标:

  • 完整演示「用 Cursor 辅助,从零搭建一套微信小程序 + 云开发应用

二、核心理念与学习路径设计

1. 从规则到单词,再到故事

可以参考云函数设计文档 data/new/data-union/plan-items-tasks.md

  • 每天有一个 duo_abc_learning_plan_items 记录,包含多个 tasks
  • tasks 中至少包含三类:
    • type: "phonics":拼读规则任务,content_ids 指向 duo_abc_phonics_rules 的若干规则 _id(如 phonics_consonant_basic_b_001)。
    • type: "words":基于当日规则生成的单词任务(例如「a-/æ/ 和 b-/b/ 拼读单词」)。
    • type: "story":使用当日单词生成的故事,content_ids 指向 duo_abc_stories 中对应的故事 _id
  • duo_abc_learning_plan_items 中拿到当天 phonics 任务的 content_ids
  • 再查 duo_abc_phonics_rules 得到字母 letter、音标 phoneticexamples 单词。
  • 同时查 duo_abc_stories 得到对应 day 的故事标题、摘要和 _id
  • 最终将这些信息「拼装回」 tasks 字段,形成包含规则、单词、故事的完整任务列表。
  • 通过云函数 duo_abc_plan_get_daily 拉取某个 planId + day 的计划项。
  • 将云端返回的 tasks 映射成前端使用的结构:
    • type 赋予不同的 colortypeText(如 phonics / words / story / quiz)。
    • 每个任务包含 content_idsdurationpoints 和是否完成标记 is_completed 等。

这一整条链路确保了「拼读规则 – 单词 – 故事」在数据库、云函数和小程序前端之间是打通的。

2. 28 天学习计划与日历视图

学习首页 pages/study/index/index.js 负责把这些计划可视化:

  • 使用 weeksdays 列表组织 4 周 * 7 天的视图。
  • userOverallProgressDaycalendarTargetPlanDaycompleted_days 等字段用来标记当前学习日和完成情况。
  • processStreakGroupsupdateVisibleStreakGroups 通过学习日期和完成天数生成「连续学习分组」,并用 HSL 颜色渲染在日历背景上,以视觉化地展示打卡连续性。
  • 顶部显示「28 天自然拼读入门」标题和总进度条。
  • 中间是按周切换的日历视图,每天的圆点会根据当前日、完成状态和 streak 背景来显示不同样式。
  • 下方分为「预览学习内容」和「今日学习」两个模块,让家长和孩子可以同时看到今天应该学什么以及任意一天包含哪些任务。

三、系统架构与代码结构概览

1. 小程序前端结构

README.mdminiprogram/app.json 一致给出了前端架构:

  • app.json 中的 pagestabBar 定义了四个核心入口:
    • pages/study/index/index:学习首页(28 天计划 + 日历)。
    • pages/practice/index/index:练习(单词发音 / 故事朗读)。
    • pages/review/index/index:复习(单词卡片 / 发音复习 / 小测验)。
    • pages/profile/index/index:我的。
  • miniprogram/components 中包含一系列可复用教学组件,如
    • phonics-learning:拼读规则学习组件。
    • word-card / word-learning / word-review:不同场景下的单词展示与复习。
    • story-reading:故事阅读组件。
    • pronunciation-practice / pronunciation-review:发音练习和发音复习组件。
    • 若干进度条组件,如 progress-barsegmented-progress-barlearning-progress-circle 等。
  • 全局 app.globalData + dataHelper 统一处理用户信息和学习计划(见 dataHelper.getUserInfo, L246–277;getLearningPlan, L285–307)。
  • 单个页面通过 setData 管理本地 UI 状态(如 study/indexpractice/indexreview/index 中的实现)。

2. 云函数与后端服务

cloudfunctions 目录下,可以看到本项目将后端能力拆成多个按业务命名的云函数):

  • 内容获取:duo_abc_content_get_phonicsduo_abc_content_get_wordsduo_abc_content_get_storiesduo_abc_content_get_quizzes
  • 学习计划:duo_abc_plan_get_allduo_abc_plan_get_itemsduo_abc_plan_get_dailyduo_abc_plan_items_tasks_update
  • 记录与进度:duo_abc_record_saveduo_abc_record_get_historyduo_abc_record_get_progressduo_abc_record_get_progress_all_days
  • 专项能力:
    • duo_abc_union_words_highlightsduo_abc_update_rule_examples:用于批量加工拼读规则与单词高亮数据。
    • duo_abc_review_gen:根据已学内容生成复习任务。
    • duo_abc_generate_asr_signatureduo_abc_get_soe_token:为 ASR/SOE 提供安全签名和临时凭证。
    • duo_abc_get_temp_links:为前端解析 cloud:// 文件路径为临时 URL。

duo_abc_content_get_phonics 为例(cloudfunctions/duo_abc_content_get_phonics/index.js):

  • 云函数支持按 ids 批量获取拼读规则,或者按 type / subtype / letter 过滤查询(L21–71)。
  • 默认按 order 字段升序返回,并携带 totalskiplimit 等分页信息(L73–92)。

所有这些云函数的命名和使用方式都遵循 cursor/erDuoEng/000云开发数据库云函数设计规范.md 中的规范:

  • 集合名使用复数英文+下划线,如 duo_abc_phonics_rulesduo_abc_stories
  • 云函数名以业务前缀 duo_abc_ 加动词/功能名组合,如 duo_abc_plan_get_dailyduo_abc_record_get_history

3. 数据层与缓存体系

数据访问统一封装在 miniprogram/utils

  • cloudHelper.js:负责所有云函数调用,并按模块划分 API(userApi, planApi, contentApi, recordApi, asrApi, soeApi, reviewApi, practiceApi),例如 planApi.getDailyTask 直接封装 duo_abc_plan_get_daily
  • cacheHelper.js:提供带过期时间的本地缓存封装,并统一维护一组 CACHE_KEYS 常量,例如 USER_INFO, LEARNING_PLAN, DAILY_TASK_PREFIX 等。
  • dataHelper.js:在 cloudHelpercacheHelper 之上实现了一个通用的 fetchDataWithCache(L166–237),并在此基础上提供:
    • getUserInfogetLearningPlangetDailyTasks
    • getPhonicsContentgetWordsByPhonicsRuleIdsgetStoriesContent
    • 练习和复习相关的 getAllPracticeItemsgetLearningProgress*getQuizzesContentgetQuizHistory 等。

媒体资源处理:

  • resolveCloudFileURLbatchRefreshTempLinksInItems会:
    • 识别数据中的 cloud:// 路径。
    • 在登录用户场景下使用 env.getTempFileURL,在游客场景下使用代理云函数 duo_abc_get_temp_links 生成可访问 URL。
    • 在缓存对象中维护 expiresAt 字段,并在过期前重刷临时链接。

这套机制保证了音频、图片等资源在微信云存储临时 URL 过期的前提下,仍然能在前端缓存层面做到「缓存命中 90%+」的效果。

四、核心功能与关键技术实现

1. 学习首页:计划、日历与进度可视化

学习首页 pages/study/index/index.js 主要承担三件事:

  • 加载用户信息与学习计划
    • fetchAllDataFromServerAndRender并行调用 dataHelper.getUserInfodataHelper.getLearningPlan,将结果写入 userInfoplanData
    • 同时根据 userInfo.progress.current_daycompleted_days 计算当前周、当前日与 streak 分组。
  • 计算日历目标日
    • determineAndSetCalendarTargetPlanDay根据当天日期与用户进度,确定「今天在日历上对应的学习日」,并支持通过本地缓存 calendarTargetPlanDayInfo 提速。
    • 同时会根据是否在 completed_days 中,设置 isCalendarTargetDayCompleted,影响「今日学习」区块的显示逻辑。
  • 渲染周视图与连续学习背景
    • processStreakGroupsgenerateStreakGroupsByStudyDatesupdateVisibleStreakGroupsstudy_dates + completed_days 推导出每一段连续学习天数,对应到日历上的彩色背景条。

在 WXML 中,visibleStreakGroups 被渲染成覆盖在 day-selector 背后的背景块,同时结合 userOverallProgressDayselectedDaycalendarTargetPlanDay 三个状态,呈现出当前日、已完成日和预览日的不同视觉效果(pages/study/index/index.wxml)。

2. 练习页:发音练习与故事朗读

练习页 pages/practice/index/index.js 重点实现「和内容强绑定的发音练习」:

  • 首次进入时:
    • dataHelper.getUserInfo 加载用户信息,并基于 userInfo.progress.completed_days 选择一个「已完成最近一天」作为默认练习日。
    • 通过 dataHelper.getAllPracticeItems 获取所有可练习的计划项。
    • 根据当前 Tab(单词 or 故事)调用 loadWordPracticeloadStoryPractice
  • loadWordPractice
    • selectedTask.tasks 中抽取所有 type === 'phonics'content_ids,得到一组拼读规则 ID。
    • 调用 dataHelper.getWordsByPhonicsRuleIds 拉取与这些拼读规则相关的单词(内部会自动完成 cloud:// 链接解析)。
    • 将得到的单词列表存入 wordList,驱动 pronunciation-practice 组件展开逐词评测。
  • loadStoryPractice
    • selectedTask.tasks 中找到 type === 'story' 的任务,并取第一个 content_ids 作为故事 ID。
    • 调用 dataHelper.getStoriesContent([storyId]) 获取完整故事内容、音频地址和已分好的句子段落 segments
    • 将结果写入 storySegmentsstoryAudioUrl,驱动 pronunciation-story-practice 组件进行逐句朗读练习。
  • 页面还内建了一套练习计时器 practiceTimer,在练习完成时配合 cloudHelper.recordApi.saveRecord 记录本次练习时长)。

语音评测相关技术

  • miniprogram/utils/soeManager.js 提供了一个较为复杂的 SOE 插件管理器:
    • 为每个需要语音评测的组件创建独立的插件实例和 manager。
    • 通过 sessionToComponent 映射和 validateSessionOwnershiphandleSessionConflict 等方法,保证同一条 Session 不会被多个组件抢占。
    • 支持全局录音状态 globalRecordingState 和最小录音间隔 MIN_RECORDING_INTERVAL,避免用户频繁点击造成的异常。
  • miniprogram/utils/microphonePermission.js 则统一处理麦克风权限:
    • 缓存权限状态 30 秒。
    • 提供 checkAndRequestPermissionshowPermissionGuidecheckBeforeRecording 等方法,既兼顾体验又避免重复弹窗。

练习页在 onShowonHide 生命周期中显式调用 soeManager 的重建与清理方法,确保在页面切换和小程序前后台切换时不会残留无效的录音实例。

3. 复习页:单词卡片、发音复习和小测验

复习页 pages/review/index/index.js 在交互与数据流上和练习页保持了高度一致,但功能更丰富:

  • 页面初始化:
    • 同样先获取 userInfogetAllPracticeItems,并根据 completed_days 选出默认 selectedPlanItem
    • 初始 Tab 可以通过页面参数指定,支持从其他页直接跳到「小测验」等场景。
    • 调用 loadContentForSelectedPlanItem 按当前 Tab 类型加载内容)。
  • loadContentForSelectedPlanItem 根据 activeTab 分三种情况:
    • Tab0 / Tab1:和练习页类似,调用 dataHelper.getWordsByPhonicsRuleIds 加载单词列表。
    • Tab2(小测验):并行调用 dataHelper.getQuizzesContentgetPhonicsContent
      • getQuizzesContent 返回的是某组拼读规则对应的一套测验题目。
      • 结合 SUBTYPE_MAP 和规则详情,为小测验生成更具描述性的标题,例如「短元音:a /æ/, e /e/…」。
      • 将题目列表和标题传给 quiz-review 组件进行出题和批改。
  • 页面内同样维护了一个复习计时器 reviewTimer,并在完成单词复习、发音复习或测验时通过 saveReviewRecordhandleSubmitQuiz 统一写入 duo_abc_learning_records
  • loadQuizHistoryhandleViewQuizDetailResult 通过 dataHelper.getQuizHistorygetQuizRecordDetail 对接历史记录,支持从历史列表中点击查看某次测验的详细结果。

整体来看,复习模块把「基于当天内容生成复习任务 – 记录结果 – 再回看历史」这一闭环打通了。

4. 统计页与可视化

pages/statistics/index/index.js 当前版本主要是一个 UI 骨架和静态示例:

  • 内置了一个 stats 对象)和一个 progress 对象,用来展示总天数、总时长、掌握单词数以及规则/单词/故事/对话的完成百分比。
  • generateHeatmapData 生成一个 7×4 的简易热力图数据),配合 WXML 中的栅格实现学习活跃度可视化。
  • fetchStatisticsData 目前使用的是模拟数据,并在 onLoadonShow 时刷新),为未来接入真实云函数统计接口预留了空间。

结合 README.md 中对「学习进度系统」的描述以及 dataHelper.getLearningProgress* 的实现,可以判断统计页未来会以这两个接口为基础,展示更真实的累计学习情况。

五、开发难点与经验总结

1. 数据设计与任务自动生成

data/new/data-union/plan-items-tasks.mddata/new/gen-new/000生成单词和故事的提示.md 可以看出,项目在内容侧做了大量「规则驱动内容生成」的设计:

  • duo_abc_phonics_rules 中的 examples 列表为基础,自动为每天的规则生成覆盖所有字母组合的单词(后者在 data/new/words/999生成单词和故事的提示.md 中有 Prompt 级别的要求)。
  • 为每组单词生成对应的英语故事和中文故事,要求逻辑通顺、难度适中、适合儿童。
  • 再将这些故事与计划任务绑定到 duo_abc_learning_plan_items.tasks 中。

这一系列操作,并不是在前端完成,而是通过多组云函数和数据脚本(如 duo_abc_union_words_highlightsduo_abc_update_rule_examples 等)离线完成,然后再通过 duo_abc_plan_* 系列云函数提供给小程序使用。代码中对这些流程有明确的 SQL/JSON 示例和步骤说明(plan-items-tasks.md )。

2. 云函数与缓存的平衡

在微信云开发环境下,频繁调用云函数和获取临时文件 URL 都是有成本的。为此本项目做了几层优化:

  • 所有云函数调用统一封装在 cloudHelper.callFunction 中,并允许按需控制 Loading、错误提示等行为(cloudHelper.js )。
  • dataHelper.fetchDataWithCache 负责:
    • 使用 cacheHelper 在本地存储任意类型的数据(学习计划、每日任务、单词、故事、测验、历史记录等)。
    • 对包含 cloud:// 链接的对象,添加 expiresAt 字段,并在过期前统一刷新临时 URL。
  • 对于公共内容(如基础单词、规则和故事),缓存有效期普遍设置为 1–7 天(getPhonicsContent, getStoriesContent, getQuizzesContent 等的 expirySeconds)。

这一套设计在不牺牲「数据一致性」的前提下,尽量减少了云函数和云存储调用次数,对应 README.md 中提到的「缓存命中率 90%+」和「云函数平均响应时间 < 500ms」。

3. 语音评测 Session 冲突与权限体验

语音评测部分的难点主要集中在两个方面:

  • Session 冲突和多组件并发
    • 由于页面上可能同时存在多个需要语音评测的组件,如果直接共享一个 SOE 实例,容易出现 Session 冲突和事件错位。
    • SOEManager 通过为每个组件创建独立实例、维护 sessionToComponent 映射并实现冲突检测和自动清理(L110–231),解决了这类隐式 Bug。
  • 权限交互体验
    • 在微信小程序中,麦克风权限一旦被拒绝,后续就不能再弹出系统授权框。
    • microphonePermission 用一个 30 秒的缓存和「智能清除」机制减少不必要的 getSetting 调用,并在权限拒绝时提供引导弹窗,引导用户进入设置页开启权限。

这部分代码的复杂度在整个项目中是比较高的,但也提供了一个可借鉴的「插件 + 权限 + 多组件并发」解决方案。

六、实际效果与适用场景

结合 README.md 中的「项目成果」部分以及 snapshots/ 目录下的页面截图(如 101学习-首页.png, 103学习-单词.png, 201练习-单词发音.png, 303复习-小测验.png 等)可以看出:

  • 学习路径
    • 首页提供 28 天学习计划总览和日历式规划,孩子每天只需要「完成今日任务」即可。
    • 「预览」功能允许家长提前查看未来几天的学习内容。
  • 练习与复习闭环
    • 练习页绑定到已经完成的计划日,从当日规则和故事抽取适合的发音练习任务。
    • 复习页提供单词卡片、发音复习和小测验三种模式,一方面巩固规则,一方面收集可视化的测验结果。
  • 统计与激励
    • 虽然统计页目前使用的是模拟数据,但配套的学习进度接口已在 dataHelper 中实现,为后续接入真实统计打好了基础。
    • 学习首页的「连续学习天数」可视化设计,也在 UI 上给了孩子明确的正向反馈。

整体体验更加适合「家长陪伴下的每日定量学习」,而不是一次性刷完所有内容。

七、如何使用与参与贡献

1. 本地运行与部署

按照 README.md 中的说明,你可以按如下步骤运行项目:

  • 克隆仓库并进入目录:
    • git clone https://github.com/erDuo10/duoEnglish.git
    • cd duoEnglish
  • 配置云环境:
    • miniprogram/config/cloudConfig.example.js 复制为 cloudConfig.js
    • cloudConfig.js 中填入你的云环境 ID 等配置。
  • 使用微信开发者工具打开项目根目录:
    • 确保已开通云开发。
    • 上传并部署所有 cloudfunctions 下的云函数。
    • 按 README 「数据库设计」「部署说明」小节创建并导入初始数据。

2. 适合参考/复用的部分

如果你只是希望借鉴某些子系统,可以重点关注:

  • 数据与计划驱动的教学设计
    • data/cloud/* 中的 JSON 结构。
    • data/new/data-union/gen-new/ 下的梳理与 Prompt 文档。
  • 云函数 + 缓存 + 媒体资源处理
    • cloudfunctions/duo_abc_content_*miniprogram/utils/dataHelper.js
    • miniprogram/utils/cacheHelper.js 与临时 URL 刷新机制。
  • 语音评测与权限管理
    • miniprogram/utils/soeManager.jsmicrophonePermission.js
    • 练习/复习页面中对这些工具的使用方式。

3. 参与贡献

README.md 已给出标准的开源协作流程:

  • Fork 仓库。
  • 基于 main 创建特性分支:git checkout -b feature/your-feature
  • 提交修改并推送到你的 Fork:
    • git commit -m 'feat: xxx'
    • git push origin feature/your-feature
  • 在 GitHub 上发起 Pull Request,简单说明改动内容和动机。

如果你对数据库 Schema、云函数设计或前端交互有改进建议,可以优先提 Issue 进行讨论,避免与现有设计冲突。

结语

duoEnglish 既是一个面向儿童的自然拼读学习产品,也是一个相对完整的「微信小程序 + 云开发 + 语音评测 + 缓存优化」示例工程。希望这篇文章能帮助你更快地理解这个项目的核心理念与技术实现,也欢迎你在 https://github.com/erDuo10/duoEnglish 上提出建议、提交 PR,一起把这套开源教学基础设施打磨得更好。