uniapp考试答题小程序源码解析:从题库加载到自动判分

简介:这是一份基于uniapp框架开发的考试答题类小程序源码,面向希望学习跨平台应用开发、了解前端答题逻辑的开发者与在校学生。项目实现了单选、多选、判断三种题型的答题界面,并支持上一题、下一题的自由切换,题目与答案均以静态数据形式预设在前端,未接入后台交互,因此更适合作为学习参考而非实际在线考试平台。压缩包为zip格式,大小约1.47MB,文件总数暂未提供明细,主要包含uniapp项目源码与配置文件,可借助HBuilderX直接导入运行与调试。目前已有2372人学习下载,热度较高。通过阅读源码,读者可以掌握uniapp的组件化开发方式、页面跳转、状态管理与数据绑定等核心用法,理解前端如何维护题目索引与答题交互逻辑,并在此基础上自行扩展用户登录、后台题库管理、分数计算与答案校验等功能,为构建更复杂的在线教育应用打下实践基础。

1. 考场之外:为什么一个答题小程序值得拆开看

去年帮一个做职业培训的朋友救火,他花两千块找人做了个答题小程序,上线三天崩了两次——一次是交卷时答案没存上,一次是排行榜刷不出来。我接手翻源码,发现是 uniapp 写的,结构乱得像个杂物间,但底子其实不差。这件事让我意识到,考试答题类小程序看着简单,真要做好,交卷、判分、防重复提交、排行榜这几块全是坑。这份 uniapp 开发的考试答题类小程序源码,正好覆盖了从题库加载、倒计时答题、自动判分到成绩展示的完整链路,适合想入门 uniapp 实战的开发者,也适合需要快速搭一套答题系统的团队做二次开发。下面我按自己拆项目的习惯,从目录结构一路讲到怎么改造成自己的题库系统。

2. 项目骨架与运行链路:从目录结构到第一道题渲染

2.1 目录结构:页面、组件、状态各归各位

拿到源码先别急着跑,花五分钟看目录,后面改起来能省两小时。这个项目的结构是典型的 uniapp 多端项目布局,核心目录如下:

├── pages/ │ ├── index/ # 首页,考试入口与历史成绩 │ ├── exam/ # 答题页,核心逻辑所在 │ ├── result/ # 成绩页,展示得分与错题 │ └── rank/ # 排行榜页 ├── components/ │ ├── question-card/ # 题目卡片组件,支持单选多选 │ ├── count-down/ # 倒计时组件 │ └── option-item/ # 选项组件,处理选中态 ├── static/ │ └── data/ # 本地题库 JSON ├── store/ │ └── exam.js # 考试状态管理 ├── utils/ │ ├── request.js # 请求封装 │ └── format.js # 时间与分数格式化 ├── App.vue ├── main.js ├── manifest.json # 多端配置 └── pages.json # 路由与窗口样式

pages/exam/是整个项目的心脏,答题、计时、交卷都在这。store/exam.js管状态,我见过不少人把状态直接塞在页面 data 里,结果从答题页跳成绩页时数据丢了,又回头补,血泪经验就是状态该抽就抽。static/data/放本地题库,方便没后端时先跑通流程。

2.2 跑起来:三条命令与一个容易忽略的配置

跑这个项目不需要复杂环境,Node 版本建议 16 以上,HBuilderX 或 CLI 都行。我用 CLI 的方式,因为改起来顺手:

# 安装依赖,建议用 npm,yarn 有时会漏装 uni 的插件 npm install # 启动到微信小程序,需要先在 manifest.json 里填 appid npm run dev:mp-weixin # 如果想先在浏览器看效果,跑 H5 npm run dev:h5

跑之前有个坑:manifest.json里的appid如果是空的,微信开发者工具会报错,但报错信息不明显,容易以为是代码问题。填上自己申请的测试号 appid 就行。H5 端不需要 appid,适合先看界面。

2.3 第一道题怎么渲染出来的

首页点「开始考试」后,跳转到pages/exam/exam.vue,核心逻辑在onLoad里。我把它简化成关键几行:

// pages/exam/exam.vue onLoad() { // 从 store 取题库,没有就去本地 JSON 加载 const questions = this.$store.state.exam.questions if (!questions.length) { this.loadLocalQuestions() } else { this.currentQuestion = questions[0] } }, methods: { async loadLocalQuestions() { // 本地题库用 require 引入,注意路径别写错 const data = require('@/static/data/questions.json') // 提交到 store,后续判分和错题都从这里取 this.$store.commit('exam/SET_QUESTIONS', data.list) this.currentQuestion = data.list[0] } }

这里SET_QUESTIONS是 Vuex 的 mutation,把题库存进全局状态。为什么不在页面里直接存?因为交卷后成绩页要拿同一份数据算分,页面间传参容易丢,状态管理更稳。questions.json的结构一般是{ list: [{ id, type, stem, options, answer }] },type区分单选多选,answer是正确答案的索引或数组。

提示:本地题库适合演示,真上线还是得走接口,后面改造章节会讲怎么换成后端拉题。

3. 答题核心逻辑:倒计时、选项切换与交卷判分

3.1 倒计时组件:别用 setInterval 硬扛

倒计时看着简单,但用setInterval每秒减一,页面切后台再回来时间就对不上了。这个项目用的是「结束时间戳 + 定时刷新」的方式,我把它抽出来讲:

// components/count-down/count-down.vue data() { return { remain: 0, // 剩余秒数 endTime: 0 // 考试结束的绝对时间戳 } }, methods: { start(duration) { // duration 是考试总时长,单位秒 this.endTime = Date.now() + duration * 1000 this.tick() this.timer = setInterval(this.tick, 1000) }, tick() { // 每次都用结束时间减当前时间,避免累计误差 const left = Math.max(0, Math.floor((this.endTime - Date.now()) / 1000)) this.remain = left if (left <= 0) { this.stop() this.$emit('timeup') // 通知父组件自动交卷 } }, stop() { clearInterval(this.timer) } }

关键在tick里每次重新算endTime - Date.now(),而不是remain--。这样即使定时器被节流,时间也不会漂。timeup事件抛给答题页,触发自动交卷。参数duration从考试配置里来,一般设 600 到 3600 秒。

3.2 选项切换:单选多选的状态处理

选项组件要处理两种模式,单选直接替换,多选要增删。核心方法:

// components/option-item/option-item.vue methods: { handleSelect(optionIndex) { if (this.type === 'single') { // 单选:直接覆盖,同时清掉其他选中 this.$emit('change', [optionIndex]) } else { // 多选:已选则移除,未选则加入 const selected = [...this.selected] const pos = selected.indexOf(optionIndex) if (pos > -1) { selected.splice(pos, 1) } else { selected.push(optionIndex) } this.$emit('change', selected) } } }

selected是父组件传下来的已选索引数组,change事件把新数组抛回去。多选这里用扩展运算符复制一份再改,避免直接改 props 触发警告。type从题目数据里读,单选是single,多选是multiple。

3.3 交卷判分:一次遍历搞定分数与错题

交卷时把用户答案和题库答案比对,算出分数并收集错题。这段逻辑在store/exam.js的 action 里:

// store/exam.js actions: { submitExam({ state, commit }) { const { questions, userAnswers } = state let score = 0 const wrongList = [] questions.forEach((q, index) => { const userAns = userAnswers[index] || [] // 排序后转字符串比对,兼容多选顺序不同 const isRight = JSON.stringify([...userAns].sort()) === JSON.stringify([...q.answer].sort()) if (isRight) { score += q.score || 1 } else { wrongList.push({ ...q, userAnswer: userAns }) } }) commit('SET_RESULT', { score, wrongList }) } }

userAnswers是个对象或数组,键是题目索引,值是选中项数组。比对前先排序再转字符串,这样多选答案顺序不同也能判对。q.score是每题分值,没配就默认 1 分。错题列表带上用户答案,成绩页可以展示「你选了什么、正确答案是什么」。

注意:如果题目有填空或简答,这套判分逻辑要扩展,不能直接套。

4. 数据流与状态管理:Vuex 在答题场景下的取舍

4.1 为什么这个项目用 Vuex 而不是 Pinia

uniapp 对 Pinia 的支持要看版本,老项目用 Vuex 更稳。这个项目用 Vuex 管三样东西:题库、用户答案、考试结果。结构如下:

// store/exam.js const state = { questions: [], // 当前考试题库 userAnswers: {}, // { 题目索引: [选中项] } result: null, // { score, wrongList } examConfig: { duration: 1800, // 考试时长,秒 passScore: 60 // 及格分 } }

userAnswers用对象而不是数组,因为用户可能跳题作答,用索引当键更灵活。examConfig放时长和及格线,改配置不用翻页面代码。

4.2 答案暂存:切题不丢答案的关键

答题页每次切换题目,要把当前题的选中项写回 store。做法是在watch里监听当前题索引变化:

watch: { currentIndex(newVal, oldVal) { // 离开旧题时,把选中项存进 store if (oldVal !== undefined) { this.$store.commit('exam/SAVE_ANSWER', { index: oldVal, answer: this.selectedOptions }) } // 进入新题时,从 store 恢复之前的选择 this.selectedOptions = this.$store.state.exam.userAnswers[newVal] || [] } }

这样用户来回切题,答案不会丢。SAVE_ANSWER就是给userAnswers对象赋值。我见过有人把答案存在页面 data 里,结果从答题页返回首页再进来,全没了,用户直接骂人。

4.3 成绩页取数:从 store 读还是从路由参数读

成绩页pages/result/result.vue有两种取数方式:从 store 读,或从路由参数读。这个项目用 store,因为错题列表数据量大,塞 URL 不现实。成绩页onLoad里:

onLoad() { const result = this.$store.state.exam.result if (!result) { // 直接刷新成绩页会丢数据,给个兜底 uni.showToast({ title: '请先参加考试', icon: 'none' }) setTimeout(() => uni.navigateBack(), 1500) return } this.score = result.score this.wrongList = result.wrongList }

兜底逻辑很重要,用户如果从历史记录直接点进成绩页,store 里没数据,得给提示而不是白屏。

5. 避坑与排查:答题小程序上线前必须过的五道坎

5.1 交卷后答案没存上

现象:用户点交卷,成绩页显示 0 分,但明明答了题。原因:交卷时userAnswers还是空的,因为最后一题的答案没触发SAVE_ANSWER,或者交卷按钮直接调了submitExam但没先保存当前题。解决:交卷前强制保存当前题答案,再调判分。在交卷方法开头加一句this.saveCurrentAnswer(),确保最后一题也进 store。

5.2 倒计时归零后还能继续答题

现象:时间到了,弹了提示,但用户还能点选项。原因:timeup事件只弹了 toast,没禁用交互。解决:在 data 里加isLocked标志,timeup时设为true,选项组件的点击事件里判断if (this.isLocked) return。同时自动交卷。

5.3 排行榜数据不刷新

现象:交卷后回排行榜,自己的成绩没出现。原因:排行榜页onShow里没重新拉数据,或者拉的是缓存。解决:在onShow生命周期里重新请求排行榜接口,别只在onLoad里拉。如果用了本地缓存,交卷后清掉排行榜缓存。

5.4 多选判分把对的判成错的

现象:用户选了 A、B,正确答案是 B、A,判错。原因:比对时没排序,数组顺序不同直接===就挂了。解决:如 3.3 节所示,比对前先sort()再转字符串。注意sort()默认按字符串排,数字索引要传比较函数(a, b) => a - b。

5.5 微信小程序端题库加载失败

现象:H5 正常,微信小程序里题目出不来。原因:require('@/static/data/questions.json')在小程序端路径解析有问题,或者 JSON 文件太大超过包体积限制。解决:改用import静态引入,或者把题库放后端接口。如果必须本地,检查manifest.json里有没有配optimization分包。

6. 二次开发:把本地题库换成后端接口的完整改造

6.1 改造思路:从静态到动态的三步走

这份源码默认用本地 JSON,真要用起来得换后端。我一般分三步:先加请求层,再改 store 的加载逻辑,最后处理加载态和错误。请求层用utils/request.js封装:

// utils/request.js const BASE_URL = 'https://your-api.com' // 换成自己的接口地址 export function request(options) { return new Promise((resolve, reject) => { uni.request({ url: BASE_URL + options.url, method: options.method || 'GET', data: options.data || {}, header: { 'Content-Type': 'application/json' }, success: (res) => { if (res.statusCode === 200) { resolve(res.data) } else { reject(res) } }, fail: reject }) }) }

BASE_URL换成自己的域名,微信小程序里要在后台配合法域名,否则请求被拦。uni.request是 uniapp 跨端请求 API,H5 和小程序都能用。

6.2 改 store:从 require 到异步 action

把loadLocalQuestions换成异步 action:

// store/exam.js actions: { async fetchQuestions({ commit }, examId) { commit('SET_LOADING', true) try { const res = await request({ url: `/api/exam/${examId}/questions` }) commit('SET_QUESTIONS', res.data.list) return res.data } catch (e) { uni.showToast({ title: '题库加载失败', icon: 'none' }) throw e } finally { commit('SET_LOADING', false) } } }

页面里onLoad改成await this.$store.dispatch('exam/fetchQuestions', this.examId)。加loading状态是为了在请求期间显示骨架屏或转圈,不然用户看着白屏会以为卡死。

6.3 交卷接口:判分放前端还是后端

本地判分适合演示,真考试得后端判,防止改前端代码作弊。改造时把submitExam改成调接口:

async submitExam({ state, commit }) { const res = await request({ url: '/api/exam/submit', method: 'POST', data: { examId: state.examId, answers: state.userAnswers } }) commit('SET_RESULT', res.data) }

后端返回{ score, wrongList },前端只负责展示。这样答案不落前端,安全得多。改造时注意接口返回的错题结构要和成绩页对齐,字段名不一致就在 store 里做一层映射。

6.4 验证改造是否成功

改完别急着上线,按这个顺序验一遍:先跑 H5 看题库能不能拉下来,再跑微信小程序看域名有没有配错,然后模拟交卷看成绩页数据对不对,最后断网测一下错误提示。我自己的习惯是每次改完数据流,都强制走一遍「加载 → 答题 → 交卷 → 看成绩」全链路,少一步都可能漏。希望帮到你。

本文还有配套的精品资源,点击获取

相关新闻

扣子工作流从零到上手:核心节点、搭建方法与避坑指南

扣子工作流从零到上手:核心节点、搭建方法与避坑指南

1. 扣子的工作流到底是什么&#xff0c;能解决什么问题先说结论&#xff1a;扣子&#xff08;Coze&#xff09;里的工作流&#xff0c;就是把“机器人回答问题”这件事&#xff0c;从一句简单的提示词变成一条可视化的流水线。以前你做一个AI机器人&#xff0c;基本套路就是写一…

2026/11/14 18:16:46 阅读更多 →
手动搭建Kubernetes集群并集成KubeSphere:从裸机到可用平台的完整指南

手动搭建Kubernetes集群并集成KubeSphere:从裸机到可用平台的完整指南

手动搭一套 Kubernetes 集群&#xff0c;很多人第一反应就是难。尤其当你还要在它上面叠一个 KubeSphere&#xff0c;这个集可视化、多集群、监控、日志、DevOps 于一体的全家桶平台&#xff0c;光是版本匹配就够折腾一阵子。这篇文章写给想自己动手从裸机开始搭一套完整 k8s 环…

2026/11/14 19:14:06 阅读更多 →
K8s Pod运维实战:状态排查、探针配置与资源限制

K8s Pod运维实战:状态排查、探针配置与资源限制

先聊点实际的。如果你已经会用kubectl apply -f把 Pod 拉起来&#xff0c;会用kubectl get pods看状态&#xff0c;那这篇就是给你准备的。这是 K8s 操作系列里 Pod 部分的第二篇&#xff0c;前一篇我们把 Pod 的创建、查看、删除、标签这些基础操作过了一遍&#xff0c;这一篇…

2026/11/12 2:01:21 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年&#xff0c;我大部分时间都在跟浏览器标签页打交道&#xff0c;直到第一次把桌面应用跑起来&#xff0c;才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词&#xff0c;最开始是在某段时间频繁出现在同事的讨论里&#xff0c;一个用HTML…

2026/11/14 5:13:29 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介&#xff1a;亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档&#xff0c;主要面向智能锁安装人员、终端用户及售后维护者&#xff0c;帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件&#xff0c;大小4.37MB&#xff0c;内容结构清晰&#xff…

2026/11/14 5:13:29 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统&#xff0c;整车要求空载跑到8m/s&#xff0c;电机转速已经拉到额定点&#xff0c;驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令&#xff0c;转速纹丝不动&#xff0c;三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/11/14 5:13:29 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年&#xff0c;用它写过嵌入式驱动、做过游戏服务端&#xff0c;也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词&#xff0c;我其实挺感慨的——这门语言的难点从来不是语法多&#xff0c;而是知识点之间的耦合太深…

2026/11/13 22:08:41 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介&#xff1a;亚太天能T109滑盖智能锁产品说明书以PDF格式发布&#xff0c;适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开&#xff0c;先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能&#xff0c;再给出完整安装步骤&…

2026/11/14 2:41:37 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历&#xff1a;翻开源码注释、论文摘要或者一份专业报告&#xff0c;每个字都认识&#xff0c;连在一起却像天书。想找人聊聊&#xff0c;身边要么没人懂&#xff0c;要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型&#xff0c;几秒钟之后&a…

2026/11/14 5:13:29 阅读更多 →

日新闻

周新闻

月新闻