线上答题小程序: 微信答题小程序开发学习与实践入门记录整理分享
WeChatAnswerSmallProgram
开发答题类微信小程序学习实践过程流水账式详细记录
前言:这是一个简单的考试类微信小程序,基本上可以满足在线考试的需求。 纯前端,数据写好在data.json文件里,每一次考试结果利用缓存存储。
一、 试题数据
新建小程序项目时,我们看到已经有index和logs页,先不要管他。我们新增一个和pages文件夹同级的data文件夹,新建json.js文件存放我们的数据。

1. 数据格式:
// data/json.js
var json = {
"001": [
{
"question": "爸爸的爸爸叫什么?",
"option": {
"A": "爷爷",
"B": "姥爷",
"C": "叔叔",
"D": "伯伯",
"E": "阿姨",
"F": "老舅"
},
"true": "A", // 正确答案
"type": 1, // 类型 1 单选 2 多选
"scores": 10, // 分值
"checked": false // 默认没有选中
},
{
"question": "妈妈的姐妹叫什么?",
"option": {
"A": "姥姥",
"B": "奶奶",
"C": "叔叔",
"D": "大姨",
"E": "小姨",
"F": "老舅"
},
"true": ["D", "E"], // 正确答案
"type": 2, // 类型 1 单选 2 多选
"scores": 10, // 分值
"checked": false // 默认没有选中
},
... ...
],
"002": [
// ...数据格式同上
]
}
2. 导出数据
// data/json.js
var json = {...}
module.exports = {
questionList: json
}
定义完数据后,要在json.js最后面使用module.exports导出。
3. 导入数据
// app.js
// 导入数据
var jsonList = require('data/json.js');
App({
globalData: {
questionList: jsonList.questionList // 拿到答题数据
}
})
在app.js里使用require导入数据,并且定义在全局变量globalData里。将来使用的时候:
首先 var app = getApp();
然后 app.globalData.questionList 就可以拿到导出的json数据。
因为我们不只有一套试卷,前面在json里定义了两个数组:001和002。之后可以通过
app.globalData.questionList["001"]选择性导入试题数据。
二、 home页面(考试入口)
在pages文件夹里新增home页面。首页授权登录后点击跳转到该页面,页面上有两个模块:001和002。点击模块进行对应的考试。
1. 主要代码
home.wxml
<view class="page">
<view class="page-title">请选择试题:</view>
<view class="flex-box">
<view class="flex-item"><view class="item bc_green" bindtap="toTestPage" data-testId="001">001</view></view>
<view class="flex-item"><view class="item bc_red" bindtap="toTestPage" data-testId="002">002</view></view>
</view>
</view>
home.js
Page({
data: {
},
onLoad: function (options) {
},
toTestPage: function (e) {
let testId = e.currentTarget.dataset['testid'];
wx.navigateTo({
url: '../test/test?testId=' + testId
})
}
})
2. 页面

三、 答题页 和 答题结束页
不管是001还是002试题,都是共用一套页面模板。在这个页面要实现答题(含:每次进入试卷试题都要乱序排列)、评分、查看错题、记录答题数据(时间/试题id/得分)的功能。
1. 实现简单的问答页面 (test页面)
首先新建一个test页面文件夹,在test.wxml文件里编写我们的答题模板。第一步先不要考虑乱序排列和记录答题功能,只先实现简单的选择答案和下一题、评分的功能。
test.wxml 解析

答题模板很简单,主要由题目、答案(单选/多选)、下一题(提交)、退出答题组成。
test.wxml 代码
<!--pages/test/test.wxml-->
<view class="page">
<!--标题-->
<view class='page__hd'>
<view class="page__title">
{{index+1}}、{{questionList[index].question}}
{{questionList[index].type==1?"【单选】":"【多选】"}}
({{questionList[index].scores}}分)
</view>
</view>
<!--内容-->
<view class="page__bd">
<radio-group class="radio-group" bindchange="radioChange" wx:if="{{questionList[index].type == 1}}">
<label class="radio my-choosebox" wx:for="{{questionList[index].option}}" wx:for-index="key" wx:for-item="value">
<radio value="{{key}}" checked="{{questionList[index].checked}}"/>{{key}}、{{value}}
</label>
</radio-group>
<checkbox-group bindchange="checkboxChange" wx:else>
<label class="checkbox my-choosebox" wx:for="{{questionList[index].option}}" wx:for-index="key" wx:for-item="value">
<checkbox value="{{key}}" checked="{{questionList[index].checked}}"/>{{key}}、{{value}}
</label>
</checkbox-group>
</view>
<!--按钮-->
<view class='page_ft'>
<view class='mybutton'>
<button bindtap='nextSubmit' wx:if="{{index == questionList.length-1}}">提交</button>
<button bindtap='nextSubmit' wx:else>下一题</button>
<text bindtap='outTest' class="toindex-btn">退出答题</text>
</view>
</view>
</view>
test.wxss 样式
/* pages/test/test.wxss */
.page {
padding: 20rpx;
}
.page__bd {
padding: 20rpx;
}
.my-choosebox {
display: block;
margin-bottom: 20rpx;
}
.toindex-btn {
margin-top: 20rpx;
display:inline-block;
line-height:2.3;
font-size:13px;
padding:0 1.34em;
color:#576b95;
text-decoration:underline;
float: right;
}
test.js 解析


test.js
var app = getApp();
Page({
data: {
index: 0, // 题目序列
chooseValue: [], // 选择的答案序列
totalScore: 100, // 总分
wrongList: [], // 错误的题目集合
},
onLoad: function (options) {
console.log(options);
wx.setNavigationBarTitle({ title: options.testId }) // 动态设置导航条标题
this.setData({
questionList: app.globalData.questionList[options.testId], // 拿到答题数据
testId: options.testId // 课程ID
})
},
/*
* 单选事件
*/
radioChange: function (e) {
console.log('checkbox发生change事件,携带value值为:', e.detail.value)
this.data.chooseValue[this.data.index] = e.detail.value;
console.log(this.data.chooseValue);
},
/*
* 多选事件
*/
checkboxChange: function (e) {
console.log('checkbox发生change事件,携带value值为:', e.detail.value)
this.data.chooseValue[this.data.index] = e.detail.value.sort();
console.log(this.data.chooseValue);
},
/*
* 下一题/提交 按钮
*/
nextSubmit: function () {
// 如果没有选择
if (this.data.chooseValue[this.data.index] == undefined || this.data.chooseValue[this.data.index].length == 0) {
wx.showToast({
title: '请选择至少一个答案!',
icon: 'none',
duration: 2000,
success: function () {
return;
}
})
return;
}
// 判断答案是否正确
this.chooseError();
// 判断是不是最后一题
if (this.data.index < this.data.questionList.length - 1) {
// 渲染下一题
this.setData({
index: this.data.index + 1
})
} else {
// 跳转到结果页
}
},
/*
* 错题处理
*/
chooseError: function () {
var trueValue = this.data.questionList[this.data.index]['true'];
var chooseVal = this.data.chooseValue[this.data.index];
console.log('选择了' + chooseVal + '答案是' + trueValue);
if (chooseVal.toString() != trueValue.toString()) {
this.data.wrongList.push(this.data.index);
this.setData({
totalScore: this.data.totalScore - this.data.questionList[this.data.index]['scores'] // 扣分操作
})
}
}
})
至此,一个简单的答题、下一题的页面就完成了。
2. 答题结束页(results页面)
前面我们实现了题目的展示和下一题的操作,那到最后一题的提交按钮应该发生什么呢?通常是告诉用户一个答题结果。下面我们新建一个results页面来专门展示用户答题结果(包括得分、评价、查看错题按钮和返回首页按钮)
test.js 跳转传参

// 跳转到结果页
let wrongList = JSON.stringify(this.data.wrongList);
let chooseValue = JSON.stringify(this.data.chooseValue);
wx.navigateTo({
url: '../results/results?totalScore=' + this.data.totalScore + '&wrongList=' + wrongList + '&chooseValue=' + chooseValue
})
首先我们要完善test.js里的nextSubmit函数,使点击提交按钮的时候跳转到results结果页。这里我们传入了totalScore得分、wrongList错题集合、chooseValue用户选择的答案集合三个数据。
results.wxml
我们先来看结果页的页面结构

results.wxml 代码
<view class="page">
<!--标题-->
<view class='page-head'>
<view class="page-title">
答题结束!您的得分为:
</view>
<!--分数-->
<view class='page-score'>
<text class="score-num">{{totalScore}}</text>
<text class="score-text">分</text>
</view>
<text class="score-remark">{{totalScore==100?remark[0]:(totalScore>=80?remark[1]:remark[2])}}</text> <!-- 评价 -->
</view>
<!--查询错误-->
<view class='page-footer'>
<view class="wrong-view" wx:if="{{wrongList.length > 0}}">
<text>错误的题数:</text>
<text wx:for="{{wrongList}}">[{{item-0+1}}]</text> 题
</view>
<view class="wrong-btns">
<button type="default" bindtap="toView" hover-class="other-button-hover" class="wrong-btn" wx:if="{{wrongList.length > 0}}"> 点击查看 </button>
<button type="default" bindtap="toIndex" hover-class="other-button-hover" class="wrong-btn"> 返回首页 </button>
</view>
</view>
</view>
results.js
// pages/results/results.js
var app = getApp();
Page({
data: {
totalScore: null, // 分数
wrongList: [], // 错误的题数
chooseValue: [], // 选择的答案
remark: ["好极了!你很棒棒哦", "哎哟不错哦", "别灰心,继续努力哦!"], // 评语
},
onLoad: function (options) {
console.log(options);
wx.setNavigationBarTitle({ title: options.testId }) // 动态设置导航条标题
let wrongList = JSON.parse(options.wrongList);
let chooseValue = JSON.parse(options.chooseValue);
this.setData({
totalScore: options.totalScore,
wrongList: wrongList,
chooseValue: chooseValue
})
console.log(this.data.chooseValue);
},
// 查看错题
toView: function () {
},
// 返回首页
toIndex: function () {
wx.switchTab({
url: '../home/home'
})
}
})
- 解析: 我们可以看到在onLoad函数里,拿到了我们需要的数据:totalScore 用户得分、 wrongList 错题集合、 chooseValue 用户选择的答案。接下来会自动渲染到页面上。
- 数组类型的数据要经过JSON.parse()转换。
四、 查看错题弹层组件
查看错题弹窗(wrongModal弹窗组件)
我们看到results页面有一个点击查看的按钮,当点击它的时候,会弹出一个层,展示用户的错题信息。包括错误的题目,用户选择的答案和该题正确的答案。

results 使用组件弹层
首先要在results.json文件里进行配置
// results.json
{
"navigationBarTitleText": "WeChatTest",
"usingComponents": {
"wrong-modal": "/components/wrongModal/wrongModal"
}
}
然后在results.wxml里引入组件
<wrong-modal modalShow="{{modalShow}}" wrongList="{{wrongList}}" wrongListSort="{{wrongListSort}}" chooseValue="{{chooseValue}}" questionList="{{questionList}}" testId="{{testId}}"></wrong-modal>
wrongModal.wxml
<!--components/wrongModal/wrongModal.wxml-->
<view class="modal-page" wx:if="{{modalShow}}">
<view class="modal-mask" bindtap="closeModal"></view>
<!-- 内容 -->
<view class="modal-content">
<view class="modal-title">
题目: {{questionList[wrongList[index]].question}}
{{questionList[wrongList[index]].type==1?"【单选】":"【多选】"}}
({{questionList[wrongList[index]].scores}}分)
</view>
<view class="modal-body">
<radio-group class="radio-group" bindchange="radioChange" wx:if="{{questionList[wrongList[index]].type == 1}}">
<label class="radio my-choosebox" wx:for="{{questionList[wrongList[index]].option}}" wx:for-index="key" wx:for-item="value">
<radio disabled="{{true}}" value="{{key}}" checked="{{questionList[wrongList[index]].checked}}"/>{{key}}、{{value}}
</label>
</radio-group>
<checkbox-group bindchange="checkboxChange" wx:else>
<label class="checkbox my-choosebox" wx:for="{{questionList[wrongList[index]].option}}" wx:for-index="key" wx:for-item="value">
<checkbox disabled="{{true}}" value="{{key}}" checked="{{questionList[wrongList[index]].checked}}"/>{{key}}、{{value}}
</label>
</checkbox-group>
</view>
<!-- 答案解析 -->
<view class="modal-answer">
<text class="answer-text wrong-answer">
您的答案为 {{chooseValue[wrongList[index]]}}
</text>
<text class="answer-text true-answer">
正确答案为 {{questionList[wrongList[index]]['true']}}
</text>
</view>
<!-- 操作按钮 -->
<view class="modal-button">
<view wx:if="{{index == wrongList.length-1}}" class="modal-btns">
<button bindtap='again' class="modal-btn">再来一次</button>
<button bindtap='toIndex' class="modal-btn">返回首页</button>
</view>
<button bindtap='next' wx:else class="modal-btn">下一题</button>
</view>
</view>
</view>
wrongModal.js
// components/wrongModal/wrongModal.js
Component({
/**
* 组件的属性列表
*/
properties: {
// 是否显示
modalShow: {
type: Boolean,
value: false
},
// 题库
questionList: {
type: Array,
value: []
},
// 课程ID
testId: {
type: String,
value: '101-1'
},
// 错题题序集合
wrongList: {
type: Array,
value: []
},
// 选择的答案集合
chooseValue: {
type: Array,
value: []
}
},
/**
* 组件的初始数据
*/
data: {
index: 0 // wrongList的index
},
/**
* 组件的方法列表
*/
methods: {
// 下一题
next: function () {
if (this.data.index < this.data.wrongList.length - 1) {
// 渲染下一题
this.setData({
index: this.data.index + 1
})
}
},
// 关闭弹窗
closeModal: function () {
this.setData({
modalShow: false
})
},
// 再来一次
again: function () {
wx.reLaunch({
url: '../test/test?testId=' + this.data.testId
})
},
// 返回首页
toIndex: function () {
wx.reLaunch({
url: '../home/home'
})
}
}
})
看代码很容易理解,主要是在Component组件的properties定义组件要接收的数据,methods里定义方法。不管是文件结构还是事件都和test页面很像。区别主要是wrongModal页面展示是筛选过的用户答错的题。
- 解析
questionList[wrongList[index]] // 试题[错题集合[当前index]]
例如用户第2、3题答题错误(index从0开始) 错题集合=[2,3] 当前index=0 下一题index+1
那么依次展示的就是questionList[2]、questionList[3]题
现在,一个简单的答题小程序就实现了。 但是现在每次出现的题都是固定的,假如我们001题库里有20道题,要求每次随机抽选10道题考核,并且这10道题乱序排列。应该怎么做呢?只要加一个乱序的步骤就可以了。
五、 乱序抽题
1. 实现乱序抽题

js代码
onLoad: function (options) {
// ... ...省略
let count = this.generateArray(0, this.data.questionList.length - 1);
this.setData({
shuffleIndex: this.shuffle(count).slice(0, 10) // 生成随机题序并进行截取
})
console.log(this.data.shuffleIndex); // [2,0,3,1,5,4...]
},
/*
* 数组乱序/洗牌
*/
shuffle: function (arr) {
let i = arr.length;
while (i) {
let j = Math.floor(Math.random() * i--);
[arr[j], arr[i]] = [arr[i], arr[j]];
}
return arr;
},
/**
* 生成一个从 start 到 end 的连续数组
*/
generateArray: function (start, end) {
return Array.from(new Array(end + 1).keys()).slice(start)
},
test.wxml

- 解析:
把页面上所有的questionList[index]替换成questionList[shuffleIndex[index]],
shuffleIndex是一个数组,里面存放乱序以后的题目下标。用index控制依次展示乱序后的题。
2. 完善结果页 和 错题弹层组件
- 做完以上哪些,我们发现:
- results页面错误的题序也乱序了
- 点击查看弹出的wrongModal的错题和用户答错的题不一致(因为wrongModal里的数据依然是正序排列的题目下标)
- 用户选择的答案和wrongModal里展示的选择的答案不一致。(原因同上)
- 解决:
test页面(test.js)
① data
data: {
index: 0, // 题目序列
chooseValue: [], // 选择的答案序列
totalScore: 100, // 总分
wrongList: [], // 错误的题目集合-乱序
wrongListSort: [], // 错误的题目集合-正序
},
data里新增wrongListSort集合
② chooseError方法
// chooseError 错题处理方法更改如下
chooseError: function () {
var trueValue = this.data.questionList[this.data.shuffleIndex[this.data.index]]['true'];
var chooseVal = this.data.chooseValue[this.data.index];
console.log('选择了' + chooseVal + '答案是' + trueValue);
if (chooseVal.toString() != trueValue.toString()) {
this.data.wrongList.push(this.data.shuffleIndex[this.data.index]);
this.data.wrongListSort.push(this.data.index);
this.setData({
totalScore: this.data.totalScore - this.data.questionList[this.data.shuffleIndex[this.data.index]]['scores'] // 扣分操作
})
}
}
- 解析:
var trueValue = this.data.questionList[this.data.shuffleIndex[this.data.index]]['true'];
// 当前正确答案更新为 乱序排列后的当前题的正确答案
this.data.wrongList.push(this.data.shuffleIndex[this.data.index]);
// wrongList错题集合里保存 乱序排列后的错题题序,
如错误的题为: [2,0,3] 相当于 001题库的[2,0,3]
this.data.wrongListSort.push(this.data.index);
// wrongListSort错题集合里保存 当前题目相对于乱序排列后的题的下标
例如 shuffleIndex = [2,0,1,3] 用户做错了第3、4题, wrongListSort保存为[2,3]
this.setData({
totalScore: this.data.totalScore - this.data.questionList[this.data.shuffleIndex[this.data.index]]['scores'] // 扣分操作
})
// 扣分操作,逻辑同上,扣的是乱序后对应的题目分值
③ nextSubmit 方法
// 判断是不是最后一题
if (this.data.index < this.data.questionList.length - 1) {
// ...
} else {
// 跳转到结果页
let wrongList = JSON.stringify(this.data.wrongList);
let wrongListSort = JSON.stringify(this.data.wrongListSort);
let chooseValue = JSON.stringify(this.data.chooseValue);
wx.navigateTo({
url: '../results/results?totalScore=' + this.data.totalScore + '&wrongList=' + wrongList + '&chooseValue=' + chooseValue + '&wrongListSort=' + wrongListSort + '&testId=' + this.data.testId
})
}
把wrongListSort传递给results页面(注:这里的wrongList也已经更新为乱序后的题目集合)
results页面
① results.js
// results.js
data: {
wrongList: [], // 错误的题数-乱序
wrongListSort: [], // 错误的题数-正序
},
onLoad: function (...
