简介:微教育全能版是一套面向学校及培训机构的微信小程序源码,集社会报名、食谱查看、微信绑定登录、在线报课与微分享于一体,适合教育机构快速搭建移动端服务入口,也适合开发者学习小程序全栈结构并进行二次开发。资源包共12339个文件,以php后端逻辑、html页面、svg与png图形资源为主,辅以js/css实现前端交互与样式,并包含wxapp相关配置,整体约193.24MB。目前已有89人学习下载。源码采用全开源形式,版本标识为3.15.27全能版,附带fm_jiaoyu_plugin_bigdata大数据插件,可采集分析用户行为以优化课程推荐;目录中同时提供config、functions等配置文件与多类型素材,便于开发者快速定位功能模块、调整报名流程或对接微信平台。无论是用于学校介绍展示,还是扩展在线教育业务,这套源码都提供了较为完整的基础能力和较高的定制灵活度。
1. 学校介绍小程序源码要解决的真实问题
每年招生季,学校官网在手机上的体验几乎都会崩掉:校园风光图变成一长条,家长最关心的招生计划、师资介绍散落在不同栏目,招生老师只能反复往群里扔图文链接。学校介绍类小程序就是把学校的信息架构收拢成一个入口,让家长在一屏内看完校园环境、师资队伍、升学路径和报名信息。「微教育全能版」这类叫法在教育信息化供应商里很普遍,重点不是某个功能多炫,而是一套源码给多个学校独立部署,改配置不改代码。最常见的落地形态是 uniapp 写前端,一套代码编译成微信小程序和 App,后端用 PHP 或 Node 提供接口,数据按学校 ID 隔离。适合教育信息化服务商做标准化产品,也适合外包团队接学校官网改造项目。
2. uni-app 搭学校介绍小程序的优势与最小实现
2.1 双端开发的成本怎么看
学校介绍类内容的交互并不复杂:轮播图、图文混排、列表、地图定位、报名表单,都是常规组件。只做微信小程序的话,微信原生开发和 uniapp 差别不大;但项目标题里的「app 小程序」意味着要同时交付小程序和 App 两个形态,原生方案等于把每个页面写两遍。uniapp 用 Vue 语法编译到微信小程序、H5 和 App 平台,学校介绍这种以展示为主的页面,一次开发就能覆盖主要渠道,后续改版也只动一套代码。
外包交付场景更看重这一点。教育行业甲方验收时经常要求「小程序和 App 都要能装」,uniapp 的 App 端可以打包成 Android 的 APK 直接交付,iOS 走标准签名流程,不需要养两支开发团队。H5 版本还能嵌进学校官网,或者作为二维码落地页,海报扫码后直接拉起小程序,招生季的物料分发链路会短很多。
2.2 最小目录结构与页面路由配置
我一般用 HBuilderX 创建 uni-app 默认模板,然后按「页面、组件、配置、接口」四层整理代码,目录结构如下:
project-root/ ├── pages/ │ ├── index/index.vue # 学校首页:轮播+公告+快捷入口 │ ├── school/intro.vue # 学校概况 │ ├── teachers/list.vue # 师资团队 │ └── admission/plan.vue # 招生计划与报名 ├── components/ │ └── school-card.vue # 通用信息卡组件 ├── config/ │ └── school.js # 当前部署学校的配置 ├── api/ │ └── index.js # 请求封装与数据源切换 ├── static/ # 图片等静态资源 ├── manifest.json # 多端配置 └── pages.json # 页面路由与导航栏pages.json 是 uni-app 的多端入口文件,页面路由、导航栏标题、全局主题色都在这里定义。下面的配置里,globalStyle 的 navigationBarBackgroundColor 决定导航栏底色,navigationBarTextStyle 只允许 white 和 black 两个值,要和底色保持对比度:
{ "pages": [ { "path": "pages/index/index", "style": { "navigationBarTitleText": "学校首页" } } ], "globalStyle": { "navigationBarTextStyle": "white", "navigationBarBackgroundColor": "#2979ff", "backgroundColor": "#f5f6f8" } }这个文件里最需要留意的是 navigationStyle 字段。如果某个页面开启 custom,原生导航栏会被隐藏,setNavigationBarTitle 也就不再生效,标题必须自己在页面上渲染。学校介绍类项目的标题需要按学校动态切换,用原生导航栏加 setNavigationBarTitle 更省事,所以我不建议为追求视觉效果开 custom。
2.3 首页轮播、公告与信息卡的实现
首页是家长打开小程序看到的第一屏,我一般放三块内容:轮播图展示校园风光,公告栏显示最新招生动态,快捷入口把师资、招生计划、联系方式收拢成四个格子。核心代码如下:
<template> <view class="page"> <swiper :indicator-dots="banners.length > 1" :autoplay="true" :interval="4000" circular> <swiper-item v-for="img in banners" :key="img.url"> <image :src="img.url" mode="aspectFill" class="banner-img" /> </swiper-item> </swiper> <view class="notice-bar" @tap="goAdmission"> <text class="notice-text">{{ notices[0]?.title || '暂无公告' }}</text> </view> <view class="quick-grid"> <view v-for="item in shortcuts" :key="item.path" class="grid-item" @tap="navigateTo(item.path)"> <text>{{ item.name }}</text> </view> </view> </view> </template> <script> import { getSchoolConfig } from '@/config/school.js' import { fetchSchoolData } from '@/api/index.js' export default { data() { return { schoolId: 'demo001', banners: [], notices: [], shortcuts: [] } }, onLoad(options) { this.schoolId = options.schoolId || 'demo001' const cfg = getSchoolConfig(this.schoolId) uni.setNavigationBarTitle({ title: cfg.name }) this.loadData() }, methods: { async loadData() { const data = await fetchSchoolData(this.schoolId) this.banners = data.banners this.notices = data.notices this.shortcuts = [ { name: '学校概况', path: '/pages/school/intro' }, { name: '师资团队', path: '/pages/teachers/list' }, { name: '招生计划', path: '/pages/admission/plan' }, { name: '联系我们', path: '/pages/school/contact' } ] } } } </script>这段代码的几个参数值得说明。indicator-dots 我绑定成 banners.length > 1,只有一张图时隐藏圆点,避免露出空状态。mode="aspectFill" 让图片等比裁剪铺满容器,不同尺寸的校园照片不会把轮播高度顶得忽高忽低。notices[0]?.title 用可选链,后端公告为空时页面不至于报错。onLoad 里先设置导航栏标题再请求数据,保证家长无论从分享链接还是扫码进入,第一眼看到的都是当前学校的名字。
3. 学校介绍的数据建模:把图文、师资、招生信息结构化
学校介绍的页面可以千变万化,但数据模型是稳定的。不管几百人的小学,还是上万人的中学,家长关注的信息就那几类。我按六个对象建模:schoolInfo(基础信息)、banners(轮播图)、notices(公告)、teachers(师资)、admission(招生计划)、contact(地址与地图坐标),所有对象统一挂在 schoolId 下。教研、班级等扩展内容可以通过 sections 对象动态扩展,结构上不要锁死,否则后续每个学校提一个定制需求,前端就要跟着加一个页面。
3.1 学校基础信息的字段定义
schoolInfo 是其他内容块的根,它的字段设计决定了后续页面要不要写死逻辑。一个在多个学校项目里验证过的字段表如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| schoolId | string | 学校唯一标识,接口隔离的依据 |
| name | string | 学校全称,用于导航栏、分享标题 |
| shortName | string | 简称,用于信息卡和列表页 |
| campus | array | 校区数组,每项含 name、address、latitude、longitude |
| banners | array | 轮播图,每项含 url、link |
| themeColor | string | 主题色,十六进制,运行时设置导航栏颜色 |
campus 用数组而不是单个字符串,因为不少学校有多个校区,每个校区要在地图上单独打点。banners 里每个元素带 link 字段,可以指向站内页面路径,也可以留空表示纯展示。themeColor 放在配置里而不是数据里,是因为微信小程序的导航栏颜色必须在运行时通过 uni.setNavigationBarColor 设置,它依赖配置的加载时机,静态页面里无法提前生效。
3.2 本地 JSON 与远程接口的快速切换
开发阶段没有后端接口时,我习惯把模拟数据放 static 目录下,用 uni.request 直接读本地文件,联调时再切到线上地址。切换逻辑写在 api/index.js 里,方便多个页面共用:
// api/index.js const ENV = { baseURL: 'https://edu-api.example.com', mockURL: '/static/school-mock.json', useMock: true // 联调时改为 false } export function fetchSchoolData(schoolId) { const url = ENV.useMock ? ENV.mockURL : `${ENV.baseURL}/school/${schoolId}` return new Promise((resolve, reject) => { uni.request({ url, method: 'GET', success: (res) => { if (res.statusCode === 200) { // 后端接口统一返回 { code, data },这里直接取 data resolve(res.data.data || res.data) } else { reject(new Error(`请求失败,状态码 ${res.statusCode}`)) } }, fail: reject }) }) }真机调试本地 mock 时有一个容易踩的坑:mock 文件必须放在 static 目录下,因为 uni-app 编译时只原样拷贝 static 里的文件到产物目录。放在项目根目录或 src 下,开发工具里能读到,但编译到真机或发布后就会 404。useMock 建议只用于开发环境,发布前手动确认已改为 false,否则上线后所有请求都会打到本地文件上。
3.3 师资列表与招生简章的渲染细节
师资列表是信息密度最高的页面,头像、姓名、职称、荣誉缺一不可。渲染时我给 image 加 lazy-load 属性,师资几十人时头像能按滚动位置按需加载,首屏渲染压力小很多。招生简章比师资列表更讲究版本管理,去年的招生计划和今年的字段结构可能完全不同,所以我不把批次、专业、学费写死在页面里,而是让后台返回半结构化的 planData,页面做通用渲染:
<template> <view class="teacher-grid"> <view v-for="teacher in teachers" :key="teacher.id" class="teacher-card"> <image :src="teacher.avatar" mode="aspectFill" class="avatar" lazy-load /> <text class="name">{{ teacher.name }}</text> <text class="title">{{ teacher.title }}</text> <text class="honor">{{ teacher.honor }}</text> </view> </view> </template>这段模板里,avatar 是教师的证件照地址,title 是职称,honor 是荣誉或教学成果的短句。如果一位教师有多项荣誉,honor 建议存逗号分隔的字符串,页面按需要截断显示,而不是在数据层把数组展开,这样接口和页面都简单。招生计划页同理,年份和批次用嵌套数组返回,前端用两层循环渲染,新增一个批次不需要改代码。
4. 全能版的关键:多学校配置、动态标题与后台发布
「全能版」和普通单校项目最大的差异在配置与发布。单校项目把学校名写死也能跑,但交付第二个学校时就得改页面代码。多学校方案的核心,是把所有学校相关的差异收敛到配置中心和内容接口两个位置。页面代码里不出现任何学校名、主题色、接口地址,全部从运行时配置读取,这样新增一所学校只加一条配置记录和一套内容数据。
4.1 一套源码多学校部署的配置中心
编译期做法是在 config/school.js 里维护一个学校数组,用构建参数选中当前学校;运行时做法是启动时用 schoolId 请求远程配置。我推荐运行时,学校运营方改文案和主题色不需要重新发版。配置文件的骨架如下:
// config/school.js const SCHOOLS = { 'demo001': { name: '第一实验中学', shortName: '实验中学', themeColor: '#2979ff', apiPrefix: '/school/demo001', remoteConfig: true }, 'demo002': { name: '阳光小学', shortName: '阳光小学', themeColor: '#07c160', apiPrefix: '/school/demo002', remoteConfig: true } } export function getSchoolConfig(schoolId) { return SCHOOLS[schoolId] || SCHOOLS['demo001'] }这里 remoteConfig 为 true 时,页面 onLoad 后会额外请求一次远程配置,用返回的 JSON 覆盖本地配置里的文案和颜色。覆盖的字段优先级要提前约定,否则运营改完不生效还不知道问题出在哪。参数表如下:
| 配置项 | 类型 | 说明 | 默认值 |
|---|---|---|---|
| name | string | 学校全称,用于导航栏标题和分享卡片 | 学校名称 |
| themeColor | string | 导航栏与主题色(十六进制) | #2979ff |
| apiPrefix | string | 该校内容接口前缀 | /school/demo001 |
| remoteConfig | boolean | 是否启动时拉取远程配置覆盖本地 | false |
4.2 动态设置小程序标题与分享链接
多学校复用时最容易被吐槽的就是标题写死。页面的导航栏标题要用当前学校的名字,分享卡片也要带上 schoolId,家长从聊天里点开才知道是哪所学校。实现代码:
// pages/index/index.vue onLoad(options) { this.schoolId = options.schoolId || 'demo001' const cfg = getSchoolConfig(this.schoolId) uni.setNavigationBarTitle({ title: cfg.name }) uni.setNavigationBarColor({ frontColor: '#ffffff', backgroundColor: cfg.themeColor }) }, onShareAppMessage() { return { title: `${this.school.name} - 招生简章`, path: `/pages/index/index?schoolId=${this.schoolId}` } }有一个容易被忽略的坑:如果首页是 tab 页,onLoad 只在冷启动时执行一次,onShow 才是每次进入都触发。家长从小程序列表切回时,onShow 会重新执行,但 setNavigationBarTitle 放在 onLoad 里就不会再更新。所以动态标题的逻辑我会同时挂在 onLoad 和 onShow 里。招生老师往短信渠道发落地页时,可以用微信公众号后台生成的 URL Scheme 把 H5 用户拉到小程序指定页面,链接里的 schoolId 参数同样被 onLoad 读取,和分享卡片共用一套入口逻辑,不额外写代码。
4.3 后台发布接口:一个学校信息更新只改内容
后端接口的设计目标是运营人员改完内容,小程序端下次进入就能看到新数据。我按「学校 ID + 内容类型」路由,用一张内容表存不同区块的 JSON 字符串。PHP 的写法在教育行业外包项目里最常见,如下:
<?php // api/school_info.php header('Content-Type: application/json; charset=utf-8'); $schoolId = $_GET['schoolId'] ?? 'demo001'; // PDO 预编译,school_id 作为过滤条件 $stmt = $pdo->prepare( 'SELECT type, content FROM school_content WHERE school_id = ? ORDER BY sort_order ASC' ); $stmt->execute([$schoolId]); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); $sections = []; foreach ($rows as $row) { // content 字段存 JSON 字符串,直接解析成数组 $sections[$row['type']] = json_decode($row['content'], true); } echo json_encode(['code' => 0, 'data' => $sections]);这个接口把所有区块一次性返回,前端拿到后按 type 分发到页面。content 字段存 JSON 的好处是,banners 里多一张图或 teachers 里多一个字段,都不需要改表结构,运营后台把新 JSON 写进去就行。sort_order 控制区块显示顺序,首页展示的顺序调整也不用发版。注意 PHP 接口要加跨域头才能在 H5 端联调,小程序端因为 uni.request 走合法域名校验,反而不存在跨域问题。
5. 上线前验证清单与两个最常见的问题
5.1 真机与开发者工具的双端验证顺序
提审之前,我会按顺序走一遍:先在微信开发者工具里跑通页面跳转和接口请求,再把项目编译到 HBuilderX 内置浏览器验证 H5,最后运行到手机同步调试 App 端。小程序端的业务域名校验要等到发布体验版前才打开,开发阶段先勾选小程序后台的「不校验合法域名」可以省掉大量调试时间。
5.2 图片域名与业务域名的配置
学校介绍页面几乎全是图片,这是最容易漏配置的地方。接口域名和图片 CDN 域名经常是两套,微信小程序要求两者都加进后台合法域名,漏掉图片域名,真机上就会报url not in domain list。如果学校自己的服务器没有备案域名,提审前基本躲不掉,提前把图片迁到已备案的 OSS 或 CDN 上,比临时换图省事得多。
5.3 用体验版二维码复现分享链路
模拟器验证不了两件事:分享卡片的实际文案,以及从分享链接进入后标题是否还是默认值。我一般发一版体验版,用真机微信扫码,把小程序分享到文件传输助手,再从聊天记录里点开,检查标题、主题色、schoolId 参数是否一致。最后再用学校信息后台改一条公告文字,回到小程序下拉刷新,确认远程配置生效。
注意:验证地图打点时,模拟器的定位是写死的,必须用真机确认坐标。多校区学校建议在 campus 数组里把每个分校区单独打点并逐一点击测试,如果标记偏离实际位置超过 50 米,优先检查 latitude 和 longitude 的传参顺序是否对调。
本文还有配套的精品资源,点击获取