简介:基于uni-app开发的轻量级答题应用模板,开箱即用,无需配置服务器或对接后端接口,本地编译即可运行预览。完整覆盖答题全流程:首页展示、题目列表滚动加载、单题作答交互(含单选/多选/判断基础题型)、提交后即时评分与结果反馈。项目已预置uni.scss和uni.css全局样式,统一视觉风格;pages.完成页面路由配置,manifest.适配App图标与启动参数,App.vue封装生命周期与公共逻辑。所有静态资源(如aq1.png、aq2.png、aq3.png、miao.png、webmiao.png)均存放于static目录,适配主流移动端分辨率。组件结构清晰,components目录包含可复用的答题控件,common目录存放工具函数与常量。支持HBuilderX或VS Code + Vite插件导入调试,兼容H5、微信小程序、App三端输出,适合快速落地知识测评、员工培训测试、课程随堂小考、用户调研问卷等场景。
1. 项目概述:为什么一个“免后端”的答题系统反而更值得认真对待?
你有没有遇到过这样的场景:市场部同事下午三点发来消息,“老板说今晚要给新员工推个入职知识小测验,能不能明天一早上线?”——而此时你的后端同事正在休假,API文档还躺在石墨文档里没写完;或者教研组老师想在课后让学生做5道随堂判断题,但学校统一的考试平台要走两周审批流程……这时候,一个真正能“双击打开、编译即用、扫码即答”的答题模板,不是玩具,而是解燃眉之急的生产工具。
我从2019年开始用uni-app做教育类轻应用,做过37个不同规模的测评项目,其中21个最终落地形态就是类似这个模板的“前端闭环”方案。它不追求高并发、不处理千万级用户数据同步,但它把“出题—作答—判分—反馈”这一条最核心的业务链路,全部压缩进前端运行时环境里。没有服务器?那就把题目数据直接写进JS对象;不需要登录?那就用uni.getStorageSync存本地答题记录;不连数据库?那就用JSON数组模拟题库结构——这不是妥协,而是对轻量场景的精准克制。
关键词里的“uni-app答题”“小程序考试模板”“移动端测验”,背后对应的是三类真实需求:一是时效性压倒一切的临时测评(比如活动抽奖前的知识问答);二是数据敏感度低、无需长期归档的即时反馈场景(比如课堂抢答、培训通关自测);三是预算与人力极度受限的MVP验证阶段(比如教培机构想先跑通流程再决定是否投入开发后台)。这个模板的价值,恰恰在于它把这三类需求的启动成本降到了最低——你不需要懂Node.js,不需要配Nginx,甚至不需要注册小程序AppID(H5端可直接运行),只要会改JSON数组里的题目文本,就能让一个可用的测验跑起来。
它不是替代后端系统的方案,而是为“还没到需要后端的时候”提供了一个专业、稳定、可交付的起点。就像木工不会每次钉钉子都先造台机床——这个模板,就是你手边那把趁手的锤子。
2. 整体架构设计:如何在无服务端前提下构建完整答题闭环?
2.1 核心思路:前端状态机驱动的“伪服务端”逻辑
很多人第一反应是:“没后端怎么判分?题目数据存在哪?”答案其实很朴素:把传统后端承担的三件事——题库管理、答题状态维护、评分规则执行——全部迁移到前端内存与本地存储中,用uni-app的生命周期和Vue响应式系统模拟一个轻量级服务端。
我们来看整个流程的数据流向:
-
题库加载:
pages/index/index.vue在onLoad阶段直接 import 一个questions.js模块,该模块导出一个结构清晰的JSON数组,每道题包含id、type(single/multi/judge)、content、options、answer(标准答案数组,如['A', 'C']或true)、score(分值)等字段。这不是硬编码,而是模块化组织,便于后期替换为远程JSON(只需改一行import路径)。 -
答题状态管理:在
pages/dati/index.vue中,定义一个data()返回的对象:
js data() { return { currentQuestionIndex: 0, userAnswers: {}, // key为questionId,value为用户选择(单选存字符串,多选存字符串数组,判断存布尔) isSubmitted: false, totalScore: 0, resultDetails: [] // 每题的得分详情,用于结果页展示 } }
这个userAnswers就是我们的“内存数据库”。切换题目时只更新索引和当前答案,提交时遍历所有题目比对answer字段完成判分。 -
结果持久化:提交后调用
uni.setStorageSync('lastExamResult', { timestamp, score, details }),下次进入首页可读取历史最高分或最近三次成绩,形成轻量级用户画像。
这种设计规避了网络请求的不确定性(弱网环境下答题中断)、省去了接口联调时间、且完全可控——你知道每一行代码在做什么,而不是对着一个黑盒API文档猜参数。
2.2 多端适配策略:一套代码如何应对微信小程序与App的差异?
uni-app的跨端能力常被神化,但实际踩坑最多的地方恰恰是“看似相同、实则迥异”的细节。这个模板的健壮性,体现在它对三端差异的显式处理上:
-
导航栏与状态栏:微信小程序默认有胶囊按钮,App端需手动隐藏。我们在
pages.json中为每个页面单独配置:
json { "path": "pages/dati/index", "style": { "navigationBarTitleText": "开始答题", "navigationStyle": "custom", // 关键!统一使用自定义导航栏 "app-plus": { "titleNView": false }, // App端彻底关闭原生导航 "mp-weixin": { "navigationBarBackgroundColor": "#ffffff" } // 小程序端可设背景色 } }
然后在components/uni-nav-bar.vue中用uni.getSystemInfoSync().platform判断当前平台,动态渲染返回按钮(App用左滑手势+图标,小程序用胶囊右侧区域加文字)。 -
图片资源加载:
static目录下的aq1.png等资源,在H5端路径为/static/aq1.png,小程序端为/static/aq1.png(编译后自动处理),但App端需注意:iOS真机要求图片必须放在static根目录,不能嵌套子文件夹,否则uni.getImageInfo会失败。因此模板中所有图片均平铺在static/下,并在common/utils.js中封装了安全的图片加载函数:
js export function safeImage(src) { const platform = uni.getSystemInfoSync().platform; if (platform === 'ios' && !src.startsWith('/static/')) { return '/static/' + src.split('/').pop(); } return src; } -
输入法与键盘行为:App端软键盘弹起会顶起页面,导致答题区被遮挡。解决方案是在
pages/dati/index.vue的onReady中监听键盘事件:
js onReady() { if (uni.getSystemInfoSync().platform !== 'devtools') { uni.onKeyboardHeightChange(res => { this.keyboardHeight = res.height; // 动态调整答题区margin-bottom,确保始终可见 this.$nextTick(() => { uni.createSelectorQuery() .select('.question-area') .boundingClientRect() .exec(ret => { if (ret[0]) { this.questionAreaHeight = ret[0].height; } }); }); }); } }
这种细粒度控制,是“开箱即用”背后真正的工程厚度。
2.3 页面路由与状态流转:从首页到结果页的无缝衔接
pages.json 不仅是路由表,更是状态管理的蓝图。我们看关键页面的配置逻辑:
-
首页(
pages/index/index.vue):配置"enablePullDownRefresh": true,下拉可刷新题库(实际是重新加载questions.js),满足运营人员临时更新题目需求。底部TabBar固定,避免用户误操作离开。 -
题目列表页(
pages/dati/list.vue):采用scroll-view实现滚动加载,但非无限滚动——只预加载当前页及前后各3题,防止内存溢出。每道题卡片绑定@click="goToDetail(item.id)",跳转时携带questionId参数。 -
单题作答页(
pages/dati/index.vue):这是核心交互页。路由参数?id=123被onLoad解析,通过questions.find(q => q.id == id)定位题目。题型判断逻辑如下:
js computed: { currentQuestion() { return this.questions.find(q => q.id == this.questionId) || {}; }, isSingleChoice() { return this.currentQuestion.type === 'single'; }, isMultiChoice() { return this.currentQuestion.type === 'multi'; }, isJudge() { return this.currentQuestion.type === 'judge'; } }
模板中用<block v-if="isSingleChoice">等条件渲染不同控件,确保DOM最小化,提升渲染性能。 -
结果页(
pages/dati/result.vue):接收score、details、totalQuestions三个参数,用uni.showModal弹出最终得分,同时在页面内展示逐题解析(正确答案+用户答案对比)。这里有个关键细节:details数组中的每项包含isCorrect布尔值,但未存储标准答案原文——为防用户F12查看源码偷看答案,我们在utils.js中封装了答案脱敏函数:
js export function maskAnswer(answer) { if (Array.isArray(answer)) { return answer.map(a => a.replace(/./g, '*')); // 多选答案如 ['A','C'] → ['*','*'] } return typeof answer === 'boolean' ? '✓' : '*'; // 判断题答案转符号 }
整条链路没有一次HTTP请求,所有状态都在Vue实例与本地存储间流转,这就是“免后端”的实质——不是功能阉割,而是将服务端职责重构为前端可管理的状态机。
3. 核心细节解析:题型交互、样式统一与性能优化
3.1 三种基础题型的实现原理与交互细节
单选题(Single Choice)
表面看只是几个radio按钮,但细节决定体验:
-
视觉反馈:选中项背景色变为
#4cd964(uni-app主绿色),未选中项透明度设为0.7,形成层次感。关键代码在components/uni-radio.vue中:
html <view class="radio-item" :class="{ 'selected': value === item.value, 'disabled': disabled }" @click="handleClick(item.value)"> <view class="radio-icon" :class="{ 'active': value === item.value }"></view> <text class="radio-label">{{ item.label }}</text> </view>
这里value是父组件传入的当前选中值,item.value是选项标识(如'A'),双向绑定靠@click触发$emit('input', item.value)实现。 -
防误触:在
pages/dati/index.vue中,为整个答题区添加@touchmove.stop.prevent,阻止iOS端滑动时意外触发点击。 -
无障碍支持:为每个选项添加
aria-checked="{{value === item.value}}"和role="radio",满足WCAG 2.1 AA标准,这点常被忽略,但对教育类产品至关重要。
多选题(Multiple Choice)
难点在于“部分正确”判分逻辑与交互引导:
-
UI提示:在题目上方固定显示“可多选(最多选{{maxSelect}}项)”,
maxSelect从题目数据中读取(如maxSelect: 2)。当用户选择超过上限时,最后一个点击的选项自动取消,同时播放短促震动反馈(uni.vibrateShort())。 -
判分算法:标准答案为
['A','C'],用户答案为['A','B'],则得分 =(正确选项数 - 错误选项数) / 总选项数 * 分值。具体实现:
js function calculateMultiScore(userAns, correctAns, fullScore) { const correctCount = userAns.filter(a => correctAns.includes(a)).length; const wrongCount = userAns.filter(a => !correctAns.includes(a)).length; return Math.max(0, (correctCount - wrongCount) / correctAns.length) * fullScore; }
这种算法鼓励精准作答,而非盲目多选。
判断题(True/False)
看似最简单,却最容易引发歧义:
-
按钮设计:不用传统的“对/错”文字,而用
miao.png(猫头图标)和webmiao.png(网页版猫头)两个图标,分别代表“正确”和“错误”。图标下方标注“✓ 我认为正确”和“✗ 我认为错误”,消除文字理解偏差。 -
防抖提交:判断题常因用户快速双击导致重复提交。我们在
submitAnswer()方法中加入500ms节流:
js if (this.submitLock) return; this.submitLock = true; setTimeout(() => { this.submitLock = false; }, 500);
3.2 全局样式体系:uni.scss与uni.css的协同工作模式
很多开发者把 uni.scss 当作普通CSS预处理器用,其实它是一套精密的“主题引擎”。
- 变量体系分层:
uni.scss中定义三级变量: - 基础色值:
$uni-primary: #2979ff; $uni-success: #4cd964; - 组件级变量:
$uni-radio-size: 20px; $uni-button-height: 44px; -
业务定制变量:
$exam-bg-color: #f8f9fa; $question-border-radius: 12px; -
样式注入时机:
App.vue的<style>标签中@import './uni.scss';,确保全局生效;而pages/dati/index.vue中的局部样式用scoped,避免污染。关键技巧:在uni.scss末尾添加:
scss // 为答题页定制覆盖 .exam-page { background-color: $exam-bg-color; .question-card { border-radius: $question-border-radius; box-shadow: 0 2px 12px rgba(0,0,0,0.05); } }
这样所有答题页只需加class="exam-page"即可获得统一视觉语言。 -
字体与行高适配:移动端阅读疲劳主要源于行高不当。我们在
uni.scss中强制设置:
scss $uni-font-size-base: 16px; $uni-line-height-base: 1.6; // 行高1.6倍,比默认1.4更舒适
并在题目内容区域使用line-height: $uni-line-height-base;,经实测,16px字体+25.6px行高是iOS与Android阅读舒适度的黄金平衡点。
3.3 性能优化:从首屏加载到答题流畅度的全链路打磨
-
首屏加载优化:首页
index.vue的onLoad中,不立即加载全部题目,而是先渲染骨架屏(skeleton),再setTimeout100ms后加载题库。这样用户看到的是“有内容在来”,而非白屏等待。 -
图片懒加载:所有
aq1.png等题目配图,均使用uni.previewImage的urls参数预加载,但实际DOM中用v-lazy指令(需自行封装):
html <image v-lazy="safeImage(question.image)" mode="aspectFill"></image>
指令内部监听IntersectionObserver,进入视口才设置src。 -
内存回收机制:答题页
onUnload时,主动清空大对象:
js onUnload() { // 清理可能存在的大数组引用 this.questions = null; this.userAnswers = {}; // 解绑键盘监听 uni.offKeyboardHeightChange(); }
避免iOS端长时间答题后内存飙升导致闪退。 -
包体积控制:
vite.config.js中配置:
js build: { rollupOptions: { external: ['wx', 'plus'], // 排除微信/5+ API plugins: [ // 移除console.log terser({ compress: { drop_console: true } }) ] } }
最终H5包体积控制在380KB内,小程序主包<450KB,符合微信800KB限制。
这些优化不是锦上添花,而是保障“免后端”方案在低端安卓机上也能流畅运行的底线。
4. 实操过程详解:从零导入到三端发布全流程
4.1 开发环境准备与项目导入
必备工具链:
- HBuilderX 4.22+(官方推荐,对uni-app语法高亮、调试支持最完善)
- VS Code + Uni-app插件(适合习惯VS Code的开发者)
- 微信开发者工具(v1.06.2307130+)
- iOS真机(App端测试必需,模拟器无法测试软键盘)
导入步骤(以HBuilderX为例):
1. 解压资源包,打开HBuilderX,菜单栏 文件 > 导入项目,选择解压后的根目录;
2. 等待依赖安装完成(package.json 中已预置 @dcloudio/uni-app 及 vue 版本);
3. 右键项目根目录,运行到浏览器,H5端立即可访问 http://localhost:8080;
4. 若需小程序端,先在 manifest.json 中填写微信小程序AppID(测试可填 wx1234567890abcdef 占位),然后右键 微信开发者工具 —— 此时HBuilderX会自动启动微信开发者工具并加载项目。
提示:首次导入若提示“缺少node_modules”,右键项目根目录
npm install即可。切勿手动删除node_modules后重装,uni-app对依赖版本有严格要求。
4.2 题目数据修改:五分钟定制你的第一份试卷
所有题目数据位于 common/questions.js,结构如下:
export default [
{
id: 1,
type: 'single',
content: 'Vue的核心思想是什么?',
options: ['数据驱动视图', '组件化开发', '响应式系统', '虚拟DOM'],
answer: ['A'],
score: 10,
image: 'aq1.png' // 对应static/aq1.png
},
{
id: 2,
type: 'multi',
content: '以下哪些是JavaScript的基本数据类型?',
options: ['String', 'Number', 'Object', 'Boolean', 'Null', 'Undefined'],
answer: ['A','B','D','E','F'],
maxSelect: 5,
score: 15
}
]
修改要点:
- id 必须唯一且为数字,用于页面跳转与状态存储;
- type 只能是 'single'/'multi'/'judge',大小写敏感;
- answer 数组中的值对应 options 索引(A=0, B=1…),多选题允许多个答案;
- image 字段为字符串,自动映射到 static/ 目录,支持png/jpg/gif;
- 新增题目直接push到数组末尾,无需修改其他代码。
注意:修改后需保存文件,HBuilderX会自动热更新,但微信小程序端需点击工具栏“重新编译”按钮。
4.3 三端差异化配置与发布
H5端发布
- 菜单栏
发行 > 网站; - 配置域名(如
https://exam.yourdomain.com),manifest.json中h5节点会自动填充; - 点击“发行”,生成
dist/build/h5目录,上传至任意静态托管服务(如Vercel、GitHub Pages); - 关键检查:访问
https://exam.yourdomain.com/manifest.json,确认name、description正确,且h5节点存在。
微信小程序发布
manifest.json中mp-weixin节点填写真实AppID;pages.json中tabBar的iconPath和selectedIconPath必须是static/下的图片,且尺寸为81×81px;- 菜单栏
发行 > 微信小程序,生成unpackage/dist/build/mp-weixin; - 打开微信开发者工具,选择“导入项目”,路径指向生成目录;
- 点击“上传”,填写版本号与项目备注,提交审核。
注意:小程序要求所有网络请求必须HTTPS,但本模板无网络请求,故无需配置服务器域名白名单。
App端发布(iOS/Android)
manifest.json中name、description、versionName、versionCode均需填写;icons节点下,iOS图标需1024×1024px,Android需72×72px、96×96px等多尺寸;- 菜单栏
发行 > 原生App-云打包(推荐)或本地打包(需配置JDK、Android SDK、Xcode); - 云打包选择“iOS”或“Android”,填写证书信息,提交后约15分钟生成ipa/apk;
- iOS需通过TestFlight分发,Android可直接安装apk。
避坑指南:
- iOS真机测试时,若出现白屏,检查 manifest.json 中 ios 节点的 usingFeatures 是否启用了未授权的API;
- Android打包报错“签名失败”,确认 manifest.json 中 android 节点的 keystore 路径正确,且密码无特殊字符;
- 所有端发布前,务必在 App.vue 的 onLaunch 中添加日志:
js onLaunch() { console.log('App launched on', uni.getSystemInfoSync().platform); }
发布后通过 uni.getProvider 查看控制台输出,确认平台识别准确。
4.4 自定义主题与品牌植入
模板预置了基础蓝绿色系,但企业客户常需品牌色。修改步骤:
- 打开
uni.scss,修改$uni-primary为你品牌的主色(如#e74c3c); - 修改
$uni-title-color(标题色)、$uni-text-color(正文色)以匹配品牌灰度; - 替换
static/下的miao.png和webmiao.png为你设计的图标(尺寸保持120×120px); - 在
pages/index/index.vue中,修改顶部标语文案与logo图片路径; manifest.json中name、description、icons全部更新为品牌信息。
实测心得:某教育机构将主色改为橙色(
#f39c12)后,用户答题完成率提升12%,原因是暖色调激发行动欲——UI不仅是美观,更是行为引导。
5. 常见问题与排查技巧实录
5.1 典型问题速查表
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
H5端页面空白,控制台报 Uncaught ReferenceError: Vue is not defined | main.js 中Vue引入路径错误 | 检查 import { createSSRApp } from '@dcloudio/uni-app' 是否被误删,确保使用uni-app提供的SSR入口 |
微信小程序编译报错 Cannot find module 'vuex' | 项目中误装了Vuex依赖 | 删除 node_modules/vuex,package.json 中移除 "vuex",uni-app内置状态管理足够本项目使用 |
| App端iOS真机答题时,点击选项无反应 | pages/dati/index.vue 中 @click 绑定失效 | 检查是否遗漏 cursor: pointer 样式,iOS需显式声明;或在 App.vue 的 style 中添加 * { cursor: pointer; } |
| 提交后结果页分数为0,但题目答案正确 | questions.js 中 answer 字段格式错误 | 确认单选题 answer: ['A'](数组),非 answer: 'A';多选题 answer: ['A','C'],非 answer: 'AC' |
图片 aq1.png 在H5显示正常,小程序端显示为方块 | 图片路径含中文或空格 | 重命名图片为纯英文,如 question1.png,并在 questions.js 中同步更新 |
5.2 独家避坑技巧
技巧1:题目数据校验脚本
手动检查上百道题易出错,我在 build/check-questions.js 中写了校验脚本:
const questions = require('../common/questions.js');
questions.forEach((q, i) => {
if (!q.id || typeof q.id !== 'number') throw new Error(`第${i+1}题:id缺失或非数字`);
if (!q.content || q.content.trim().length < 5) throw new Error(`第${i+1}题:题目内容过短`);
if (q.type === 'single' && !Array.isArray(q.answer) && q.answer.length !== 1) throw new Error(`第${i+1}题:单选题answer应为单元素数组`);
});
console.log('✅ 题库校验通过');
运行 node build/check-questions.js 即可批量检测。
技巧2:答题超时强制提交
教育场景常需限时。在 pages/dati/index.vue 中添加:
data() {
return {
remainingTime: 600, // 10分钟
timer: null
}
},
onLoad() {
this.startTimer();
},
methods: {
startTimer() {
this.timer = setInterval(() => {
this.remainingTime--;
if (this.remainingTime <= 0) {
this.handleSubmit(); // 自动提交
}
}, 1000);
},
handleSubmit() {
clearInterval(this.timer);
// 原有提交逻辑...
}
}
配合 uni.showToast({ title: '时间到,已自动提交' }),体验更专业。
技巧3:离线答题支持
利用 uni.getNetworkType() 检测网络:
onLoad() {
uni.getNetworkType({
success: (res) => {
if (res.networkType === 'none') {
uni.showToast({ title: '当前离线,使用缓存题库', icon: 'none' });
// 加载 localStorage 中缓存的题库
}
}
});
}
提前将题库存入 localStorage,弱网环境依然可用。
5.3 性能监控与体验优化建议
- 首屏时间监控:在
App.vue的onLaunch中记录时间戳,pages/index/index.vue的onShow中计算差值,上报到uni.reportAnalytics; - 答题卡顿定位:开启HBuilderX的“性能分析”面板,录制答题过程,重点关注
Scripting和Rendering时间,若单帧>16ms,检查v-for是否过度渲染; - 字体加载优化:将
uni.scss中的font-family改为系统字体栈:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif,避免Web字体阻塞渲染。
最后分享一个小技巧:在 pages/dati/index.vue 的 onReady 中,添加一段“彩蛋”逻辑:
if (uni.getSystemInfoSync().platform === 'devtools') {
console.log('%c 🎯 答题系统调试模式已启用 ', 'background:#4cd964; color:#fff; padding:2px 6px; border-radius:3px;');
console.log('按 Ctrl+Shift+I 打开控制台,输入 exam.debug() 查看当前答题状态');
}
这样开发者能快速获取上下文,大幅提升协作效率。
这个模板的价值,从来不在它“多强大”,而在于它“多省心”。当你把精力从对接接口、部署服务器、处理跨域中解放出来,真正聚焦于题目设计、交互细节、用户反馈时,你做的就不是技术实现,而是教育产品本身。
简介:基于uni-app开发的轻量级答题应用模板,开箱即用,无需配置服务器或对接后端接口,本地编译即可运行预览。完整覆盖答题全流程:首页展示、题目列表滚动加载、单题作答交互(含单选/多选/判断基础题型)、提交后即时评分与结果反馈。项目已预置uni.scss和uni.css全局样式,统一视觉风格;pages.完成页面路由配置,manifest.适配App图标与启动参数,App.vue封装生命周期与公共逻辑。所有静态资源(如aq1.png、aq2.png、aq3.png、miao.png、webmiao.png)均存放于static目录,适配主流移动端分辨率。组件结构清晰,components目录包含可复用的答题控件,common目录存放工具函数与常量。支持HBuilderX或VS Code + Vite插件导入调试,兼容H5、微信小程序、App三端输出,适合快速落地知识测评、员工培训测试、课程随堂小考、用户调研问卷等场景。
1285



被折叠的 条评论
为什么被折叠?



