简介:这是一套面向教育培训行业开发者的微信小程序与公众号双端源码解决方案,专为中小型培训机构、在线教育机构及教育类创业团队设计,解决课程管理、营销转化与用户运营一体化难题。资源包为77.27MB的ZIP压缩文件,含完整前后端代码、数据库脚本及配套安装文档,涵盖小程序端与公众号H5双版本,支持课程体系搭建、团购分销、直播互动、积分商城、优惠券发放等核心业务模块。已有354人学习下载,说明其在实战场景中具备较高复用价值。用户可直接部署上线,快速获得包含后台管理系统的全功能教育平台——后台集成课件上传、练习题库、活动报名、屏显广告配置、三级分发关系图谱等可视化操作界面,配合视频+文字双教程,大幅降低二次开发门槛与部署试错成本。
1. 教育培训类微信小程序源码不是“拿来即用”的压缩包,而是需要理解课件渲染逻辑、活动生命周期和双端(小程序+公众号)路由映射的可维护工程
很多刚接触教育培训数字化的运营者或小机构开发者,看到“超强大”“自带课件/练习/活动插件”这类宣传语,第一反应是下载解压、填入AppID、上传发布——结果卡在首页白屏、课件图片404、公众号菜单点击无响应。根本原因在于:这类源码本质是基于uni-app 框架构建的跨端教育业务中台,其“强大”体现在对教学场景的抽象能力(如课件分页渲染引擎、练习题实时判分状态机、活动倒计时与参与校验耦合逻辑),而非零配置部署。它面向的是具备基础前端工程能力的教育技术实施者:能读懂pages/course/detail.vue中onLoad钩子如何拉取课件结构树,能修改utils/activity.js里checkActivityStatus()的时间戳比对规则,能在manifest.json中正确配置公众号 JS-SDK 的jsApiList。如果你正为机构搭建线上课程体系、需快速验证教学流程闭环、或希望复用成熟插件避免从零实现题库/签到/直播跳转等模块,这套源码提供的是可调试、可裁剪、可对接自有教务系统的骨架,而非黑盒式SaaS界面。
2. 基于 uni-app 的双端架构解析:为什么必须同时处理小程序和公众号的环境差异
2.1 小程序与公众号的本质区别决定代码分支策略
微信小程序运行在独立沙箱环境,支持wx.navigateTo、wx.getStorageSync等原生 API;而公众号内嵌 H5 页面依赖微信 JS-SDK(wx.config初始化后调用wx.openProductView等接口),且受制于浏览器同源策略与 iOS WKWebView 缓存机制。本套源码通过uni.getSystemInfoSync().platform判断运行环境,并在main.js中注入统一的适配层:
// utils/platform.js export const getPlatform = () => { const sys = uni.getSystemInfoSync() if (sys.platform === 'ios' || sys.platform === 'android') { return 'mp-weixin' // 小程序环境 } // 公众号环境需额外检测 URL 参数或 User-Agent const ua = navigator.userAgent.toLowerCase() if (ua.includes('micromessenger') && !ua.includes('miniprogram')) { return 'web-wechat' } return 'h5' }提示:直接使用
uni.getProvider或uni.getSystemInfo判断公众号环境不可靠——部分安卓微信版本会错误返回platform: android。必须结合location.href是否含&from=singlemessage或__biz参数进行二次校验。
2.2 课件插件的核心:富文本课件的双端渲染一致性保障
课件模块(components/courseware-renderer.vue)并非简单v-html渲染 HTML,而是将课件 JSON 结构(含文字、图片、音频、交互题型)转换为平台兼容的 DOM 树。关键点在于:
- 小程序端:使用
<rich-text>组件渲染基础格式,但需自定义nodes解析器处理<audio>标签(小程序不支持直接播放外链音频,需转为wx.createInnerAudioContext()) - 公众号端:使用
document.createElement动态插入<audio>并绑定wx.ready后的播放事件,同时为图片添加wx.previewImage事件代理
// components/courseware-renderer.vue 中的节点处理逻辑 const renderNode = (node) => { if (node.name === 'audio') { const src = node.attrs.src if (platform === 'mp-weixin') { const audioCtx = uni.createInnerAudioContext() audioCtx.src = src return { type: 'audio', context: audioCtx } } else { // 公众号端创建 audio 元素并绑定 wx.playVoice const audioEl = document.createElement('audio') audioEl.src = src audioEl.controls = true return audioEl } } // 其他节点处理... }2.2.1 课件资源路径的双端映射规则
课件中的图片/附件路径在courseware.json中存储为相对路径(如./assets/img/001.png),但实际部署时需映射为:
| 环境 | 资源请求路径 | 配置位置 |
|---|---|---|
| 小程序 | https://cdn.example.com/mp/assets/img/001.png | vue.config.js中configureWebpack.externals配置 CDN 域名 |
| 公众号 | https://www.example.com/web/assets/img/001.png | manifest.json的h5.publicPath设置为/web/ |
若未配置,小程序会因跨域被拦截,公众号则出现 404。验证方法:在课件页面console.log(uni.getRealPathSync('./assets/img/001.png'))(小程序)或console.log(location.origin + '/web/assets/img/001.png')(公众号)。
2.3 练习插件的状态同步机制:本地缓存与服务端校验的协同
练习模块(pages/exercise/index.vue)采用“本地预提交 + 服务端终审”模式:
- 用户作答后,答案暂存
uni.setStorageSync('exercise_temp_' + exerciseId, answerData) - 提交时,先校验本地缓存有效性(检查
timestamp是否超 30 分钟),再调用uni.request发送至/api/exercise/submit - 公众号环境因 localStorage 容量限制,改用
sessionStorage并增加wx.setStorage备份(需在wx.ready后调用)
// pages/exercise/index.vue 提交逻辑 const submitExercise = async () => { const cacheKey = `exercise_temp_${exerciseId}` const cached = uni.getStorageSync(cacheKey) if (!cached || Date.now() - cached.timestamp > 1800000) { uni.showToast({ title: '答题数据已过期,请重新作答', icon: 'none' }) return } try { const res = await uni.request({ url: '/api/exercise/submit', method: 'POST', data: { ...cached, platform: getPlatform() }, // 显式传递平台标识 header: { 'X-Platform': getPlatform() } // 服务端据此选择判分规则 }) // 处理响应... } catch (e) { // 公众号环境降级:尝试 wx.uploadFile 上传答案快照 if (getPlatform() === 'web-wechat') { await uploadAnswerSnapshot(cached) } } }注意:服务端接口
/api/exercise/submit必须根据X-Platform头区分处理逻辑——小程序端可信任wx.login获取的openid,公众号端需解析code换取unionid并校验用户关注状态。
3. 活动插件的落地配置:从创建活动到用户参与的全链路参数控制
3.1 活动配置项的三层生效机制
活动插件(components/activity-card.vue)的配置不只存在于后台管理界面,而是由数据库配置 → 前端环境变量 → 运行时动态计算三级共同决定:
| 层级 | 配置位置 | 示例值 | 修改影响 |
|---|---|---|---|
| 数据库层 | activity_config表config_json字段 | {"countdown": true, "share_reward": 5} | 控制活动是否开启倒计时、分享奖励积分 |
| 环境变量层 | .env.production中VUE_APP_ACTIVITY_BASE_URL | https://api.edu.example.com/v1/activity | 决定活动接口请求域名,小程序与公众号必须指向同一网关 |
| 运行时层 | pages/activity/detail.vue中computed属性 | isStarted() { return Date.now() > this.startTime } | 根据客户端时间动态判断活动状态,避免服务端时间偏差导致误判 |
3.1.1 活动倒计时的双端时间同步方案
小程序端可直接使用Date.now(),但公众号端因页面可能被微信后台销毁,需每次进入页面时向服务端请求当前时间:
// pages/activity/detail.vue data() { return { serverTimeOffset: 0 // 服务端时间与客户端时间差(毫秒) } }, async onLoad() { // 公众号环境强制校准时间 if (getPlatform() === 'web-wechat') { const res = await uni.request({ url: '/api/time' }) // 返回 { timestamp: 1717023456789 } this.serverTimeOffset = res.data.timestamp - Date.now() } }, computed: { remainingTime() { const now = getPlatform() === 'web-wechat' ? Date.now() + this.serverTimeOffset : Date.now() return Math.max(0, this.endTime - now) } }3.2 公众号菜单与小程序页面的精准跳转映射
公众号自定义菜单需指向https://www.example.com/web/activity?id=123,而该 URL 必须能正确启动 H5 页面并透传参数。关键配置在manifest.json:
{ "name": "教育培训学校", "h5": { "template": "index.html", "publicPath": "/web/", "devServer": { "port": 8080, "proxy": { "/api": { "target": "https://api.edu.example.com", "changeOrigin": true } } } } }同时,在index.html中添加 URL 参数解析逻辑:
<!-- index.html --> <script> // 解析 URL 参数并挂载到 Vue 实例 const urlParams = new URLSearchParams(window.location.search) window.__INITIAL_DATA__ = { activityId: urlParams.get('id'), platform: 'web-wechat' } </script>然后在main.js中读取:
// main.js const app = new Vue({ ...App, created() { // 从全局变量获取初始参数 if (window.__INITIAL_DATA__) { this.$store.commit('SET_INITIAL_PARAMS', window.__INITIAL_DATA__) } } })3.2.1 小程序跳转公众号文章的合规实现
源码中pages/course/detail.vue的“查看配套资料”按钮,需跳转至公众号历史文章。严禁使用weixin://dl/business等未公开协议(已失效且违反微信规范),正确方式是:
- 后台生成带
?from=miniprogram参数的公众号文章链接(如https://mp.weixin.qq.com/s/xxx?from=miniprogram) - 小程序端调用
uni.navigateToMiniProgram跳转至公众号主页(需提前在公众号后台配置JSAPI域名) - 或更稳妥方案:在公众号文章末尾添加“小程序卡片”,引导用户从公众号回流
// pages/course/detail.vue goToOfficialAccount() { // 方案一:跳转公众号主页(需公众号已开通“公众号关注组件”) uni.navigateToMiniProgram({ appId: 'wx1234567890abcdef', // 公众号对应的 AppID(非服务号) path: 'pages/index/index', // 公众号主页路径 envVersion: 'release' }) // 方案二:打开公众号文章链接(需公众号后台设置业务域名) uni.openURL('https://mp.weixin.qq.com/s/xxx?from=miniprogram') }提示:
uni.openURL在 iOS 微信内会自动唤起内置浏览器,Android 则可能跳转失败——必须在公众号后台【公众号设置】→【功能设置】→【业务域名】中添加https://mp.weixin.qq.com,否则提示“不支持打开此链接”。
4. 修改刚进入的加载页面:从 splash screen 到首屏课件的无缝衔接
4.1 小程序端启动页(Splash Screen)的定制化覆盖
uni-app 默认启动页为白色背景,但教育类应用需在首屏展示品牌 Logo 与课程分类。修改路径:
- 替换
static/splash.png:尺寸需严格匹配 iPhone X 及以上机型(1125×2436px),否则 iOS 会出现拉伸 - 配置
manifest.json的mp-weixin字段:
"mp-weixin": { "usingComponents": true, "splash": { "backgroundColor": "#2c3e50", "image": "/static/splash.png", "delay": 2000 } }- 隐藏默认 loading,启用自定义骨架屏:在
App.vue的onLaunch中延迟显示主页面
// App.vue onLaunch() { // 首次启动时显示自定义加载动画 uni.showLoading({ title: '加载中...', mask: true }) // 模拟资源加载(实际应替换为真实异步操作) setTimeout(() => { uni.hideLoading() // 此处可触发骨架屏消失动画 this.$nextTick(() => { this.isLoading = false }) }, 1500) }4.2 公众号端首屏优化:解决白屏与资源加载阻塞
公众号 H5 首屏常因vendor.js过大(>1MB)导致白屏超 3 秒。优化措施:
- 代码分割:在
vue.config.js中配置optimization.splitChunks
configureWebpack: { optimization: { splitChunks: { chunks: 'all', cacheGroups: { courseware: { name: 'chunk-courseware', test: /[\\/]components[\\/].*courseware.*[\\/]/, priority: 20 }, activity: { name: 'chunk-activity', test: /[\\/]components[\\/].*activity.*[\\/]/, priority: 15 } } } } }- 预加载关键资源:在
index.html<head>中添加
<link rel="preload" href="/js/chunk-courseware.js" as="script"> <link rel="preload" href="/fonts/iconfont.woff2" as="font" type="font/woff2" crossorigin>- 服务端渲染首屏课件列表:通过
nuxt.js或vue-server-renderer生成静态 HTML,减少客户端 JavaScript 执行时间
4.2.1 加载状态与用户感知的匹配设计
教育用户对“等待”敏感度高,需将技术加载过程转化为教学语言:
| 加载阶段 | 小程序表现 | 公众号表现 | 设计逻辑 |
|---|---|---|---|
| 资源加载中 | 骨架屏 + “正在为您准备今日课程…” | 静态课件封面图 + “加载中,知识即将送达” | 避免纯技术术语,用教学场景话术降低焦虑 |
| 接口请求中 | 进度条 + “连接教务系统…” | 图标旋转动画 + “查询您的学习记录…” | 明确告知用户系统在做什么,而非“加载” |
| 首屏渲染后 | 自动播放课件导学音频 | 高亮显示“您有3个未完成练习” | 首屏即提供价值,而非空白页面 |
验证方法:使用 Chrome DevTools 的Network → Disable cache模拟弱网,观察首屏内容出现时间是否 ≤1.5s(小程序)或 ≤2.5s(公众号)。
5. 关键排错清单:当课件不显示、活动无法参与、公众号跳转失败时的定位路径
5.1 课件图片/音频 404 的五层排查法
| 层级 | 检查项 | 命令/操作 | 预期结果 | 常见修复 |
|---|---|---|---|---|
| 1. 资源路径 | courseware.json中图片路径是否以./assets/开头 | grep -r '"src":"\./assets/' src/ | 存在且格式统一 | 统一改为@/assets/(alias 配置) |
| 2. 构建输出 | dist/build/mp-weixin/static/assets/是否存在对应文件 | ls -l dist/build/mp-weixin/static/assets/img/ | 文件存在且大小 >0 | 检查vue.config.js中copy-webpack-plugin配置 |
| 3. CDN 配置 | 小程序manifest.json的mp-weixin.splash.image路径是否带/static/ | cat manifest.json | grep splash | 路径为/static/splash.png | 改为绝对路径https://cdn.example.com/static/splash.png |
| 4. 网络请求 | 浏览器 Network 面板查看图片请求 Response Headers | curl -I https://cdn.example.com/static/assets/img/001.png | HTTP/2 200+Content-Type: image/png | 检查 CDN 缓存规则是否拦截了*.png |
| 5. 权限控制 | 公众号业务域名是否包含图片 CDN 域名 | 微信公众平台 → 设置与开发 → 公众号设置 → 业务域名 | 已添加且 SSL 证书有效 | 添加https://cdn.example.com并验证 |
5.2 活动参与失败的断点调试流程
当用户点击“立即参与”无响应时,按顺序执行:
- 确认活动状态:访问
https://api.edu.example.com/v1/activity/status?id=123,检查status字段是否为active - 检查用户权限:在小程序控制台执行
uni.getStorageSync('user_info'),确认openid存在且非空字符串 - 验证签名参数:抓包
POST /api/activity/join请求,检查sign字段是否由activityId + openid + timestamp + secretKey生成 - 公众号环境特殊处理:在
pages/activity/detail.vue的onLoad中添加console.log('platform:', getPlatform(), 'url:', location.href),确认是否误判为小程序 - 服务端日志追踪:在 Nginx access log 中搜索
activity/join,确认请求是否到达服务器(排除前端未发送)
5.3 公众号跳转失败的三类根因与修复
| 现象 | 根因 | 修复命令/配置 |
|---|---|---|
| 点击菜单无反应 | 公众号未配置 JSAPI 安全域名 | 登录 mp.weixin.qq.com → 公众号设置 → 功能设置 → 业务域名 → 添加www.example.com并上传验证文件 |
| 跳转后显示“网页不存在” | index.html中routermode 为history但服务器未配置 fallback | Nginx 配置location / { try_files $uri $uri/ /index.html; } |
| iOS 微信内白屏 | manifest.json的h5.router.base未设置为/web/ | "h5": { "router": { "base": "/web/" } },且所有路由path以/web/开头 |
提示:所有修复后必须执行
npm run build:mp-weixin和npm run build:h5重新构建,并清除微信开发者工具缓存(菜单栏 → 工具 → 清除缓存 → 全部清除)。
本文还有配套的精品资源,点击获取