简介:这是一套面向高校学生与开发者的评教系统完整源码,采用微信小程序与Web应用双端架构,适合用作毕业设计、课程设计或全栈练手项目。资源包共528个文件,约9.43MB,以JavaScript业务逻辑、JSON配置、WXSS样式与WXML页面结构为主,辅以Less预处理样式、WXS脚本及少量图片资源,前后端代码组织清晰,便于按模块阅读与二次开发。项目围绕学生评教、教师管理与数据统计等典型场景展开,涵盖小程序端页面渲染、接口调用与后台管理功能,代码经过测试可直接运行,省去从零搭建的繁琐。目前已有186人学习下载,适合希望快速获得可运行项目、理解小程序与Web协同开发流程的读者参考借鉴。
1. 评教系统做成微信小程序:一份 zip 源码背后真正要落地的四件事
每学期期末,教务群里最热闹的话题永远是评教——学生嫌入口难找,老师盯着参评率发愁,教务处的老师则要手动把几千条记录从后台导出来做统计。把评教系统做成微信小程序,本质上是把「登录—选课—打分—提交—统计」这条链路塞进一个不用装 App、扫码即用的入口里。你手上如果正好有一份「评教系统(微信小程序).zip」,它大概率包含小程序前端页面、后端接口和一份数据库脚本,但真正决定它能不能上线的,不是代码写得多漂亮,而是四件事:微信登录怎么和学号绑定、评教数据怎么保证一人一课一评、主观题和客观题怎么存、以及教务端怎么把结果导出来。这篇笔记面向的是拿到源码想跑通、想改成自己学校能用的开发者,也适合正在评估「微信小程序项目实例」能不能直接套用的教务信息化同学。下面按「先跑起来、再改对、最后防坑」的顺序讲。
2. 把 zip 跑起来:环境、目录与最小可运行链路
2.1 先看清 zip 里通常有什么,再决定改哪里
拿到一份评教系统源码,别急着点「编译」。先解压,用编辑器打开根目录,按下面的方式快速判断它的技术栈。常见的评教小程序项目结构大致是这几类文件:
| 目录/文件 | 作用 | 你要关注的点 |
|---|---|---|
miniprogram/或pages/ | 小程序前端页面 | 登录页、课程列表、评教表单、提交结果页 |
app.js/app.json | 全局逻辑与页面注册 | app.json里的pages数组决定入口页 |
utils/request.js | 请求封装 | 看 baseURL 指向哪个后端 |
server/或backend/ | 后端服务 | Node/Java/PHP 都有可能,看package.json或pom.xml |
*.sql | 数据库脚本 | 建表语句里能看出评教维度设计 |
判断技术栈的命令很简单,在解压目录执行:
# 看后端语言和依赖 ls -la cat package.json 2>/dev/null || cat pom.xml 2>/dev/null || echo "可能是 PHP 或其它" # 找数据库脚本 find . -name "*.sql" -maxdepth 3 # 找请求基地址 grep -rn "baseUrl\|baseURL\|request(" --include="*.js" miniprogram/ 2>/dev/null | head -20逻辑说明:package.json存在说明后端是 Node,pom.xml是 Java,两者都没有就要看有没有index.php或application.properties。grep那一条是为了定位前端请求打到哪个域名,这决定了你后面要不要改request合法域名。参数上,--include="*.js"限定只搜 JS,head -20防止输出刷屏。
提示:如果
app.json里pages第一项是pages/login/login,说明强制先登录;如果是pages/index/index,可能允许游客浏览,评教系统一般应该是前者。
2.2 微信登录换学号:wx.login到后端openid的完整链路
评教系统最关键的一步是「这个人是谁」。微信小程序不能直接拿学号,标准做法是前端wx.login拿code,后端用code换openid,再把openid和学号做绑定。前端代码通常长这样:
// pages/login/login.js Page({ onLoad() { // 页面加载时先静默登录,拿到 code wx.login({ success: (res) => { if (res.code) { // 把 code 发给自己的后端,由后端去换 openid wx.request({ url: 'https://your-domain.com/api/login', method: 'POST', data: { code: res.code }, success: (r) => { // 后端返回 token 和是否已绑定学号 wx.setStorageSync('token', r.data.token); if (!r.data.bound) { wx.navigateTo({ url: '/pages/bind/bind' }); } } }); } } }); } });逻辑说明:wx.login的code只能用一次、五分钟内有效,所以必须立刻发给后端。后端拿到code后调用微信的code2Session接口换openid和session_key,这一步涉及appid和secret,绝对不能放在前端。参数上,wx.request的url必须是 HTTPS 且在小程序后台「request 合法域名」里配置过,否则真机直接报「不在以下 request 合法域名列表中」。
后端换openid的伪代码逻辑是:接收code→ 请求微信接口 → 拿到openid→ 查数据库里这个openid有没有绑学号 → 有就发 token,没有就返回bound: false。绑定页让学生输入学号和姓名,后端拿学号去教务库比对,比对成功就把openid和学号写进绑定表。这里有个血泪经验:很多学校教务库不开放,只能导入一份学生名单做比对,那就把名单表建好,绑定接口查这张表。
2.3 评教表单:客观题用单选、主观题用文本,别混着存
评教表单一般分两类题:客观题(打分、单选)和主观题(建议、评语)。微信小程序里客观题用<radio-group>,主观题用<textarea>。数据结构建议前端提交时统一成数组:
// 提交评教结果 const answers = [ { questionId: 1, type: 'radio', value: 'A' }, // 客观题 { questionId: 2, type: 'radio', value: 'B' }, { questionId: 3, type: 'text', value: '老师讲课很清晰' } // 主观题 ]; wx.request({ url: 'https://your-domain.com/api/evaluate', method: 'POST', header: { 'Authorization': wx.getStorageSync('token') }, data: { courseId: 101, teacherId: 5, answers }, success: (r) => { if (r.data.code === 0) { wx.showToast({ title: '评教成功' }); } } });逻辑说明:questionId对应题库表主键,type区分题型,value存选项或文本。后端收到后按type分别写入客观题结果表和主观题结果表,或者统一存一张 JSON 字段表。参数上,courseId和teacherId是评教的维度,必须和选课表关联,否则统计时对不上。注意header里带 token 做鉴权,防止别人替你评教。
注意:微信小程序的
<radio>组件在部分安卓机型上点击区域偏小,建议给label包一层并设置padding,否则学生会觉得「点不动」。
3. 评教系统的数据设计:一人一课一评怎么保证
3.1 三张核心表:学生、课程、评教记录
评教系统的数据库设计决定了后面统计能不能做。最小可用方案是三张表加一张题库表:
-- 学生绑定表 CREATE TABLE student_bind ( id INT PRIMARY KEY AUTO_INCREMENT, openid VARCHAR(64) UNIQUE NOT NULL, student_no VARCHAR(20) NOT NULL, real_name VARCHAR(20), created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 评教记录表 CREATE TABLE evaluation ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL, course_id INT NOT NULL, teacher_id INT NOT NULL, submit_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_stu_course (student_no, course_id) ); -- 评教答案表 CREATE TABLE evaluation_answer ( id INT PRIMARY KEY AUTO_INCREMENT, evaluation_id INT NOT NULL, question_id INT NOT NULL, answer_value VARCHAR(500), INDEX idx_eval (evaluation_id) );逻辑说明:student_bind的openid加唯一索引,防止一个微信绑多个学号。evaluation表的UNIQUE KEY uk_stu_course是核心,它保证同一个学生对同一门课只能提交一次,这就是「一人一课一评」的数据库级约束。evaluation_answer用evaluation_id关联,主观题的长文本也放得下(VARCHAR(500)够用,不够改TEXT)。
参数上,student_no用字符串而不是数字,因为有些学号带字母或前导零。submit_time默认当前时间,方便后面按学期筛选。如果学校要求匿名评教,那evaluation表里就不要存student_no,改成存一个随机token,但这样就没法做「一人一课一评」的约束了——这是匿名和防重复之间的取舍,常见做法是存student_no但统计时只输出聚合结果,不暴露个人。
3.2 提交接口的幂等处理:重复提交怎么拦
前端可能因为网络卡顿重复点提交,后端必须做幂等。最直接的方式是利用UNIQUE KEY,插入冲突时返回友好提示:
// Node.js 示例:提交评教 app.post('/api/evaluate', async (req, res) => { const { courseId, teacherId, answers } = req.body; const studentNo = req.user.studentNo; // 从 token 解析 try { const [result] = await db.execute( 'INSERT INTO evaluation (student_no, course_id, teacher_id) VALUES (?, ?, ?)', [studentNo, courseId, teacherId] ); const evalId = result.insertId; // 批量插入答案 const values = answers.map(a => [evalId, a.questionId, a.value]); await db.query('INSERT INTO evaluation_answer (evaluation_id, question_id, answer_value) VALUES ?', [values]); res.json({ code: 0, msg: 'ok' }); } catch (e) { if (e.code === 'ER_DUP_ENTRY') { return res.json({ code: 1001, msg: '你已经评过这门课了' }); } res.json({ code: 500, msg: '提交失败' }); } });逻辑说明:先插evaluation主表拿到evalId,再用批量插入写答案,减少数据库往返。ER_DUP_ENTRY是 MySQL 唯一键冲突的错误码,捕获后返回「已评过」而不是报 500。参数上,answers.map把前端数组转成二维数组,db.query的?占位批量插入比循环单条快很多。
提示:如果学校要求「提交后不可修改」,那就在提交成功后把该学生的评教入口置灰;如果允许修改,就用
INSERT ... ON DUPLICATE KEY UPDATE,但评教一般不允许改,防止事后改分。
3.3 教务端统计:从评教记录到教师得分
统计是评教系统的出口。常见需求是「每个老师每门课的平均分」和「主观题原文导出」。平均分用 SQL 聚合:
SELECT e.teacher_id, e.course_id, AVG(CASE WHEN q.type = 'radio' THEN CAST(a.answer_value AS UNSIGNED) END) AS avg_score, COUNT(DISTINCT e.student_no) AS participant_count FROM evaluation e JOIN evaluation_answer a ON a.evaluation_id = e.id JOIN question q ON q.id = a.question_id GROUP BY e.teacher_id, e.course_id;逻辑说明:CASE WHEN只对客观题求平均,主观题不参与。CAST把选项值转成数字,前提是选项设计成 A=5、B=4 这种分值。COUNT(DISTINCT e.student_no)统计参评人数,用来算参评率。参数上,如果选项是字母,需要在题库表里存一个score字段做映射,别在 SQL 里硬编码。
主观题导出一般做成 CSV,教务老师用 Excel 打开。注意 CSV 要加 BOM 头,否则 Excel 打开中文乱码:
const csv = '\uFEFF' + rows.map(r => `${r.teacher_name},${r.course_name},"${r.answer_value}"`).join('\n');\uFEFF就是 BOM,"${r.answer_value}"用引号包住防止评语里有逗号。
4. 微信小程序评教系统的避坑与排查
4.1 真机请求失败:合法域名和 HTTPS 证书
现象:开发者工具里一切正常,真机预览时所有接口报「request:fail」。原因:小程序真机强制要求 HTTPS,且域名必须在小程序后台「开发—开发设置—服务器域名」里配置。解决:把后端域名加进 request 合法域名,证书用受信任的 CA 签发,自签名证书不行。如果只是内网测试,可以在开发者工具「详情—本地设置」勾选「不校验合法域名」,但真机不行。
4.2 登录态过期:token 失效后页面白屏
现象:学生评教到一半,提交时提示未登录,返回登录页后数据丢了。原因:token 设了过期时间,或者后端重启导致 session 丢失。解决:前端在request封装里统一拦截 401,跳转登录前把当前表单数据存进wx.setStorageSync,登录后回填。token 建议用 JWT,后端无状态,重启不影响。
4.3 单选组件取值取不到:radio-group的bindchange
现象:学生选了选项,提交时value是空。原因:<radio>必须包在<radio-group>里,且bindchange绑在radio-group上,不是radio上。解决:
<radio-group bindchange="onRadioChange" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />