上线项目演示

微信搜索[放马来答]或扫以下二维码体验:

项目源码

项目源码

其他版本

Vue答题App实战教程

Hello小程序

1.注册微信小程序

点击立即注册,选择微信小程序,按照要求填写信息

2.登录小程序并完善信息

填写小程序信息,完善信息。

3.下载小程序开发工具

完善信息后点击文档,工具,下载,选择稳定版的对应平台的安装包下载,下载完后点击安装即可



4.建立小程序项目

扫码登录,选择小程序,并点击加号,填写相关信息,APPID位置于下方截图所示。

5.小程序代码结构介绍

如下图所示的四个文件,主要用于注册和配置微信小程序,其包含的是全局配置信息。

  • app.js:用于注册微信小程序应用。

  • app.json:小程序的全局配置,比如网络请求的超时时间,以及窗口的属性

  • app.wxss:小程序全局样式

  • project.config.json:包含了小程序的整体配置信息,即使是换了开发设备,亦或是换了项目,只要将该文件保留,每个开发者的个性化设置就都将保留。

如下图所示,还有两个目录,

  • pages:每一个子文件夹代表了小程序的一个页面,比如index,和logs分别代表了两个页面。每个页面又由四个文件组成:

    index.js:处理页面逻辑和数据交互。

    index.json:对应页面的配置信息。

    index.wxml:展示页面的内容和元素。

    index.wxss:设置用wxml展示元素的样式。

  • utils:存放的是一些工具代码,实现代码复用的目的。

6.小程序helloworld

开发试题分类页面

新增home页面

pages目录下新建home目录,并添加4个文件,如图所示:

其中:

home.js

// pages/home/home.js
Page({
data: { },
onLoad: function (options) { },
toTestPage: function(e){
let testId = e.currentTarget.dataset['testid'];
wx.navigateTo({
url: '../test/test?testId='+testId
})
}
})

home.wxml

<!--pages/home/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="18">IT知识</view></view>
<view class="flex-item"><view class="item bc_red" bindtap="toTestPage" data-testId="15">游戏知识</view></view>
<view class="flex-item"><view class="item bc_yellow" bindtap="toTestPage" data-testId="21">体育知识</view></view>
<view class="flex-item"><view class="item bc_blue" bindtap="toTestPage" data-testId="27">动物知识</view></view>
<view class="flex-item item-last"><view class="item bc_green" bindtap="toTestPage" data-testId="0">综合知识</view></view>
</view>
</view>

home.json

{
"navigationBarTitleText": "试题分类",
"usingComponents": {}
}

home.wxss

/* pages/home/home.wxss */
.page-title {
padding-top: 20rpx;
padding-left: 40rpx;
font-size: 16px;
}
.flex-box {
display: flex;
align-items:center;
flex-wrap: wrap;
justify-content: space-between;
padding: 20rpx;
box-sizing:border-box;
}
.flex-item {
width: 50%;
height: 200rpx;
padding: 20rpx;
box-sizing:border-box;
}
.item {
width:100%;
height:100%;
border-radius:8rpx;
display: flex;
align-items:center;
justify-content: center;
color: #fff;
}
.item-last {
flex: 1;
}

修改app.json,注意:pages/home/home一定要放到pages数组的最前,以成为首页。

{
"pages": [
"pages/home/home",
"pages/index/index",
"pages/logs/logs",
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "WeChat",
"navigationBarTextStyle": "black"
},
"style": "v2",
"sitemapLocation": "sitemap.json"
}

修改app.wxss,定义全局样式

/**app.wxss**/
.container {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: 200rpx 0;
box-sizing: border-box;
} .bc_green{
background-color: #09BB07;
}
.bc_red{
background-color: #F76260;
}
.bc_blue{
background-color: #10AEFF;
}
.bc_yellow{
background-color: #FFBE00;
}
.bc_gray{
background-color: #C9C9C9;
}

运行结果

开发试题展示功能

新增test页面

pages目录下新建test目录,并添加4个文件,如图所示:

修改test.js

// pages/test/test.js
Page({ /**
* 页面的初始数据
*/
data: {
test_id:0
}, /**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
this.setData({test_id:options.testId})
}
})

修改test.wxml

<!--pages/test/test.wxml-->
<text>我是{{test_id}}</text>

运行结果

在试题分类页点击某一分类,跳转到试题页,试题页显示分类id

Mock试题数据

项目目录下新增data目录,data目录新增json.js,存放我们的试题模拟数据。

var json = {
"18": [
{
"question": "This mobile OS held the largest market share in 2012.?",
"option": {
"A": "iOS",
"B": "Android",
"C": "BlackBerry",
"D": "Symbian"
},
"true": "A",
"scores": 10,
"checked": false
},
{
"question": "This mobile OS held the largest market share in 2012.?",
"option": {
"A": "iOS",
"B": "Android",
"C": "BlackBerry",
"D": "Symbian"
},
"true": "A",
"scores": 10,
"checked": false
}
],
"0": [
{
"question": "This mobile OS held the largest market share in 2012.?",
"option": {
"A": "iOS",
"B": "Android",
"C": "BlackBerry",
"D": "Symbian"
},
"true": "A",
"scores": 10,
"checked": false
},
{
"question": "This mobile OS held the largest market share in 2012.?",
"option": {
"A": "iOS",
"B": "Android",
"C": "BlackBerry",
"D": "Symbian"
},
"true": "A",
"scores": 10,
"checked": false
}
]
} module.exports = {
questionList: json
}

修改app.js

var jsonList = require('data/json.js');

App({
...
},
globalData: {
questionList: jsonList.questionList, // 拿到答题数据
// questionList:{},
quizCategory:{
"0": "综合知识",
"18": "IT知识",
"21": "体育知识",
"15": "游戏知识",
"27":"动物知识",
}
}
})

修改test.js

// import he from "he";
var app = getApp(); Page({
data: {
bIsReady: false, // 页面是否准备就绪
index: 0, // 题目序列
chooseValue: [], // 选择的答案序列
}, shuffle(array) {
return array.sort(() => Math.random() - 0.5);
},
/**
* 生成一个从 start 到 end 的连续数组
* @param start
* @param end
*/
generateArray: function (start, end) {
return Array.from(new Array(end + 1).keys()).slice(start)
}, onLoad: function (options) {
wx.setNavigationBarTitle({ title: options.testId }) // 动态设置导航条标题
this.setData({
questionList: app.globalData.questionList[options.testId], // 拿到答题数据
testId: options.testId // 课程ID
})
let countArr = this.generateArray(0, this.data.questionList.length - 1); // 生成题序
this.setData({
bIsReady: true,
shuffleIndex: this.shuffle(countArr).slice(0, countArr.length) // 生成随机题序 [2,0,3] 并截取num道题
})
},
/*
* 单选事件
*/
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);
},
/*
* 退出答题 按钮
*/
outTest: function () {
wx.showModal({
title: '提示',
content: '你真的要退出答题吗?',
success(res) {
if (res.confirm) {
console.log('用户点击确定')
wx.switchTab({
url: '../home/home'
})
} else if (res.cancel) {
console.log('用户点击取消')
}
}
})
},
/*
* 下一题/提交 按钮
*/
nextSubmit: function () {
// 如果没有选择
if (this.data.chooseValue[this.data.index] == undefined) {
wx.showToast({
title: '请选择至少一个答案!',
icon: 'none',
duration: 2000,
success: function () {
return;
}
})
return;
} // 判断是不是最后一题
if (this.data.index < this.data.shuffleIndex.length - 1) {
// 渲染下一题
this.setData({
index: this.data.index + 1
})
} else {
console.log('ok')
}
}
})

test.wxml

<!--pages/test/test.wxml-->
<view wx:if="{{bIsReady}}" class="page">
<!--标题-->
<view class='page__hd'>
<view class="page__title">
{{index+1}}、{{questionList[shuffleIndex[index]].question}}
({{questionList[shuffleIndex[index]].scores}}分)
</view>
</view>
<!--内容-->
<view class="page__bd"> <radio-group class="radio-group" bindchange="radioChange">
<label class="radio my-choosebox" wx:for="{{questionList[shuffleIndex[index]].option}}" wx:for-index="key" wx:for-item="value">
<radio value="{{key}}" checked="{{questionList[shuffleIndex[index]].checked}}"/>{{key}}、{{value}}
</label>
</radio-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

  getQuestions(testId) {
// 显示标题栏加载效果
wx.showNavigationBarLoading();
wx.request({
// url: 'https://opentdb.com/api.php?amount=10&difficulty=easy&type=multiple&category=' + testId,
url: 'https://opentdb.com/api.php?amount=10&difficulty=easy&type=multiple&category=' + testId,
method: "GET",
success: res => {
if (res.data.response_code === 0) {
this.setData({
questionList: this.parseQuestion(res.data.results), // 拿到答题数据
testId: testId // 课程ID
})
console.log(this.data.questionList);
app.globalData.questionList[testId] = this.data.questionList
let count = this.generateArray(0, this.data.questionList.length - 1); // 生成题序 this.setData({
bIsReady: true,
shuffleIndex: this.shuffle(count).slice(0, 10) // 生成随机题序 [2,0,3] 并截取num道题
})
} else {
;
}
// 停止加载效果
wx.stopPullDownRefresh();
wx.hideNavigationBarLoading();
},
fail: err => {
// 停止加载效果
wx.stopPullDownRefresh();
wx.hideNavigationBarLoading();
}
}); }, onLoad: function (options) {
this.getQuestions(options.testId)
console.log(options); wx.setNavigationBarTitle({ title: app.globalData.quizCategory[options.testId] }) // 动态设置导航条标题
},

解析返回的数据:

   // 主题列表数据模型
parseQuestion(aList) {
let aTopicList = [];
if (!aList || (aList && !Array.isArray(aList))) {
aList = [];
} aTopicList = aList.map(oItem => { const answers = [oItem.correct_answer, oItem.incorrect_answers].flat()
let optionArr = ['A', 'B', 'C', 'D']
let options = {}
let optionArrShuffle = this.shuffle(optionArr)
for (let i = 0; i < answers.length; i++) {
options[optionArr[i]] = String(answers[i]);
}
const ordered_options = {};
Object.keys(options).sort().forEach(function (key) {
ordered_options[key] = options[key];
});
return {
"question": String(oItem.question), // id
"scores": 10,
"checked": false,
"option": ordered_options,
"true": optionArr[0]
};
});
return aTopicList; },

这里解析的原因是,接口返回的json数据和我们自己设计的数据格式略有不同,我们要转换成自己的数据格式:

接口返回的数据格式:

{
"response_code": 0,
"results": [{
"category": "Science: Computers",
"type": "multiple",
"difficulty": "easy",
"question": "The numbering system with a radix of 16 is more commonly referred to as ",
"correct_answer": "Hexidecimal",
"incorrect_answers": ["Binary", "Duodecimal", "Octal"]
}, {
"category": "Science: Computers",
"type": "multiple",
"difficulty": "easy",
"question": "This mobile OS held the largest market share in 2012.",
"correct_answer": "iOS",
"incorrect_answers": ["Android", "BlackBerry", "Symbian"]
}, {
"category": "Science: Computers",
"type": "multiple",
"difficulty": "easy",
"question": "How many values can a single byte represent?",
"correct_answer": "256",
"incorrect_answers": ["8", "1", "1024"]
}, {
"category": "Science: Computers",
"type": "multiple",
"difficulty": "easy",
"question": "In computing, what does MIDI stand for?",
"correct_answer": "Musical Instrument Digital Interface",
"incorrect_answers": ["Musical Interface of Digital Instruments", "Modular Interface of Digital Instruments", "Musical Instrument Data Interface"]
}, {
"category": "Science: Computers",
"type": "multiple",
"difficulty": "easy",
"question": "In computing, what does LAN stand for?",
"correct_answer": "Local Area Network",
"incorrect_answers": ["Long Antenna Node", "Light Access Node", "Land Address Navigation"]
}]
}

我们自己的数据格式:

var json = {
"18": [
{
"question": "This mobile OS held the largest market share in 2012.?",
"option": {
"A": "iOS",
"B": "Android",
"C": "BlackBerry",
"D": "Symbian"
},
"true": "A",
"scores": 10,
"checked": false
},
{
"question": "This mobile OS held the largest market share in 2012.?",
"option": {
"A": "iOS",
"B": "Android",
"C": "BlackBerry",
"D": "Symbian"
},
"true": "A",
"scores": 10,
"checked": false
}
],
"0": [
{
"question": "This mobile OS held the largest market share in 2012.?",
"option": {
"A": "iOS",
"B": "Android",
"C": "BlackBerry",
"D": "Symbian"
},
"true": "A",
"scores": 10,
"checked": false
},
{
"question": "This mobile OS held the largest market share in 2012.?",
"option": {
"A": "iOS",
"B": "Android",
"C": "BlackBerry",
"D": "Symbian"
},
"true": "A",
"scores": 10,
"checked": false
}
]
}

注意:

开发期间:不校验合法域名,web-view.....这里不要勾选。

引入第三方库

细心的朋友可能会发现,有些题目中有乱码,如下图所示'

有一个很好的第三方库He可以处理这个问题。

我们需要使用npm导入一个第三方库处理这个问题,大家会学习到在小程序开发中如何使用npm引入第三方库。

项目根目录下新建package.json文件

{
"name": "wechatanswer-master",
"version": "1.0.0",
"description": "答题类微信小程序",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://gitee.com/kamiba/my_quiz_wechat_app.git"
},
"author": "",
"license": "ISC",
"dependencies": {
"he": "^1.2.0"
}
}

2、执行npm install --production xxx,这个xxx就是你想使用的npm 包。此时在当前文件夹下会生成一个node_modules文件夹。PS:此处请务必使用--production选项,可以减少安装一些业务无关的 npm 包,从而减少整个小程序包的大小。

npm install --production he

3、在微信开发者工具-->工具-->构建npm,此时会生成一个miniprogram_npm文件夹。

4、构建完成后就可以使用 npm 包了。首先把使用npm模块勾起来,然后在js文件中引入即可。

然后修改test.js

import he from "he";

  // 主题列表数据模型
parseQuestion(aList) {
let aTopicList = [];
if (!aList || (aList && !Array.isArray(aList))) {
aList = [];
} aTopicList = aList.map(oItem => { const answers = [oItem.correct_answer, oItem.incorrect_answers].flat()
let optionArr = ['A', 'B', 'C', 'D']
let options = {}
let optionArrShuffle = this.shuffle(optionArr)
for (let i = 0; i < answers.length; i++) {
options[optionArr[i]] = he.decode(String(answers[i]));
}
const ordered_options = {};
Object.keys(options).sort().forEach(function (key) {
ordered_options[key] = options[key];
});
return {
"question": he.decode(String(oItem.question)), // id
"scores": 10,
"checked": false,
"option": ordered_options,
"true": optionArr[0]
};
});
return aTopicList; },

上面和以前有三处修改

import he from "he";
options[optionArr[i]] = he.decode(String(answers[i]));
"question": he.decode(String(oItem.question)),

计算用户得分,记录错题

修改test.js

Page({
data: {
bIsReady: false, // 页面是否准备就绪
index: 0, // 题目序列
chooseValue: [], // 选择的答案序列
totalScore: 100, // 总分
wrong: 0, // 错误的题目数量
wrongListSort: [], // 错误的题目集合
},
 /*
* 下一题/提交 按钮
*/
nextSubmit: function () {
// 如果没有选择
if (this.data.chooseValue[this.data.index] == undefined) {
wx.showToast({
title: '请选择至少一个答案!',
icon: 'none',
duration: 2000,
success: function () {
return;
}
})
return;
}
// 判断答案是否正确
this.chooseError(); // 判断是不是最后一题
if (this.data.index < this.data.shuffleIndex.length - 1) {
// 渲染下一题
this.setData({
index: this.data.index + 1
})
} else {
let wrongListSort = JSON.stringify(this.data.wrongListSort);
let chooseValue = JSON.stringify(this.data.chooseValue);
let shuffleIndex = JSON.stringify(this.data.shuffleIndex);
console.log('wrongListSort:' + wrongListSort)
wx.navigateTo({
url: '../results/results?totalScore=' + this.data.totalScore + '&shuffleIndex=' + shuffleIndex + '&chooseValue=' + chooseValue + '&wrongListSort=' + wrongListSort + '&testId=' + this.data.testId
})
// 设置缓存 var logs = wx.getStorageSync('logs') || [] let logsList = { "date": Date.now(), "testId": app.globalData.quizCategory[this.data.testId], "score": this.data.totalScore }
logs.unshift(logsList);
wx.setStorageSync('logs', logs);
}
},
/*
* 错题处理
*/
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()) {
console.log('错了');
this.data.wrong++;
this.data.wrongListSort.push(this.data.index);
this.setData({
totalScore: this.data.totalScore - this.data.questionList[this.data.shuffleIndex[this.data.index]]['scores'] // 扣分操作
})
}
},

实现结果展示页面

pages目录下新增page---results

results.js

// pages/results/results.js
var app = getApp();
Page({
data: {
totalScore: null, // 分数
shuffleIndex: [], // 错误的题数-乱序
wrongListSort: [], // 错误的题数-正序
chooseValue: [], // 选择的答案
remark: ["好极了!你很棒棒哦", "哎哟不错哦", "别灰心,继续努力哦!"], // 评语
modalShow: false
},
onLoad: function (options) {
console.log(options);
wx.setNavigationBarTitle({ title: app.globalData.quizCategory[options.testId] }) // 动态设置导航条标题 let shuffleIndex = JSON.parse(options.shuffleIndex);
let wrongListSort = JSON.parse(options.wrongListSort);
let chooseValue = JSON.parse(options.chooseValue);
this.setData({
totalScore: options.totalScore != "" ? options.totalScore : "无",
shuffleIndex: shuffleIndex,
wrongListSort: wrongListSort,
chooseValue: chooseValue,
questionList: app.globalData.questionList[options.testId], // 拿到答题数据
testId: options.testId // 课程ID
})
console.log(this.data.chooseValue);
},
// 查看错题
toView: function () {
// 显示弹窗
this.setData({
modalShow: true
})
},
// 返回首页
toIndex: function () {
wx.switchTab({
url: '../home/home'
})
}
})

results.json

{
"navigationBarTitleText": "WeChatTest",
"usingComponents": {
"wrong-modal":"/components/wrongModal/wrongModal"
}
}

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="{{wrongListSort.length > 0}}">
<text>错误的题数:</text>
<text wx:for="{{wrongListSort}}">[{{item-0+1}}]</text> 题
</view>
<view class="wrong-btns">
<button type="default" bindtap="toView" hover-class="other-button-hover" class="wrong-btn" wx:if="{{wrongListSort.length > 0}}"> 点击查看 </button>
<button type="default" bindtap="toIndex" hover-class="other-button-hover" class="wrong-btn"> 返回首页 </button>
</view>
</view>
<wrong-modal modalShow="{{modalShow}}" shuffleIndex="{{shuffleIndex}}" wrongListSort="{{wrongListSort}}" chooseValue="{{chooseValue}}" questionList="{{questionList}}" testId="{{testId}}"
></wrong-modal>
</view>

results.wxss

/* pages/results/results.wxss */
.page {
padding: 20rpx;
}
.page-head {
text-align: center;
}
.page-title { }
.page-score {
display: flex;
justify-content: center;
align-items: flex-end;
padding-top:40rpx;
padding-bottom:40rpx;
}
.score-num {
font-size:100rpx;
}
.page-footer {
margin-top:60rpx;
text-align: center;
}
.wrong-btns {
display:flex;
align-items:center;
justify-content:center;
margin-top: 60rpx;
}
.wrong-btn {
margin-left:20rpx;
margin-right:20rpx;
height:70rpx;
line-height:70rpx;
font-size:14px;
}

实现错题查看页面

项目目录下新增components目录。components目录下新增wrongModal目录,wrongModal目录下新增page---wrongModal

wrongModal.js

// components/wrongModal/wrongModal.js
Component({
/**
* 组件的属性列表
*/
properties: {
// 是否显示
modalShow: {
type: Boolean,
value: false
},
// 题库
questionList: {
type: Array,
value: []
},
// 课程ID
testId: {
type: String,
value: '101-1'
},
// 题库乱序index
shuffleIndex: {
type: Array,
value: []
}, // 错题题数-正序
wrongListSort: {
type: Array,
value: []
},
// 选择的答案
chooseValue: {
type: Array,
value: []
}
},
/**
* 组件的初始数据
*/
data: {
index: 0 // wrongList的index
},
/**
* 组件所在页面的生命周期
*/
pageLifetimes: {
show: function () {
// 页面被展示
console.log('show')
console.log(this.data.questionList)
// console.log(this.data.wrongList)
},
hide: function () {
// 页面被隐藏
},
resize: function (size) {
// 页面尺寸变化
}
},
/**
* 组件的方法列表
*/
methods: {
// 下一题
next: function(){
if (this.data.index < this.data.wrongListSort.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'
})
},
}
})

wrongModal.json

{
"component": true,
"usingComponents": {}
}

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[shuffleIndex[wrongListSort[index]]].question}}
</view>
<view class="modal-body">
<radio-group class="radio-group" bindchange="radioChange">
<label class="radio my-choosebox" wx:for="{{questionList[shuffleIndex[wrongListSort[index]]].option}}" wx:for-index="key" wx:for-item="value">
<radio disabled="{{true}}" value="{{key}}" checked="{{questionList[shuffleIndex[wrongListSort[index]]].checked}}"/>{{key}}、{{value}}
</label>
</radio-group>
</view>
<!-- 答案解析 -->
<view class="modal-answer">
<text class="answer-text wrong-answer">
您的答案为 {{chooseValue[wrongListSort[index]]}}
</text>
<text class="answer-text true-answer">
正确答案为 {{questionList[shuffleIndex[wrongListSort[index]]]['true']}}
</text>
</view>
<!-- 操作按钮 -->
<view class="modal-button">
<view wx:if="{{index == wrongListSort.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.wxss

/* components/wrongModal/wrongModal.wxss */
.modal-mask {
position:fixed;
width:100%;
height:100%;
top:0;
left:0;
z-index:10;
background: #000;
opacity: 0.5;
overflow: hidden;
}
.modal-page {
display:flex;
align-items:center;
justify-content:center;
width:100%;
height:100%;
top:0;
position:absolute;
left:0;
}
.modal-content {
width: 80%;
min-height: 80%;
background: #fff;
border-radius: 8rpx;
z-index:11;
padding: 20rpx;
}
.modal-title {
font-size: 14px;
}
.modal-body {
padding: 20rpx;
}
.my-choosebox {
display: block;
margin-bottom: 20rpx;
font-size: 14px;
}
.modal-answer {
display: flex;
}
.answer-text {
font-size: 14px;
margin-right: 20rpx;
}
.modal-button {
display: flex;
align-items:center;
justify-content:center;
margin-top:60rpx;
}
.modal-btns {
display: flex;
align-items:center;
justify-content:center;
}
.modal-btn {
margin-left:20rpx;
margin-right:20rpx;
height:70rpx;
line-height:70rpx;
font-size:12px;
}

实现成绩记录页面

修改项目目录下的app.json,增加底部导航栏

{
"pages": [
"pages/home/home",
"pages/logs/logs",
"pages/test/test",
"pages/results/results",
"pages/index/index"
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "WeChat",
"navigationBarTextStyle": "black"
},
"tabBar": {
"color": "#666666",
"selectedColor": "#3cc51f",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"list": [
{
"pagePath": "pages/home/home",
"iconPath": "image/icon_component.png",
"selectedIconPath": "image/icon_component_HL.png",
"text": "答题"
},
{
"pagePath": "pages/logs/logs",
"iconPath": "image/icon_API.png",
"selectedIconPath": "image/icon_API_HL.png",
"text": "记录"
}
]
},
"sitemapLocation": "sitemap.json"
}

项目目录下新增image文件夹,并添加以下文件,具体文件请下载源码获取:

修改pages目录下logs页面

logs.js

//logs.js
const util = require('../../utils/util.js') Page({
data: {
logs: [],
},
onShow: function() {
this.setData({
logs: this.formatLogs()
})
},
// 拿到缓存并格式化日期数据
formatLogs: function(){
let newList = [];
(wx.getStorageSync('logs') || []).forEach(log => {
if(log.date){
log['date'] = util.formatTime(new Date(log.date));
newList.push(log);
}
})
return newList;
}
})

logs.json

{
"navigationBarTitleText": "查看日志",
"usingComponents": {}
}

logs.wxml

<!--logs.wxml-->
<view class="page">
<view class="table" wx:if="{{logs.length>0}}">
<view class="tr bg-w">
<view class="th first">时间</view>
<view class="th">试题</view>
<view class="th ">得分</view>
</view>
<block wx:for="{{logs}}" wx:for-item="item">
<view class="tr">
<view class="td first">{{item.date}}</view>
<view class="td">{{item.testId}}</view>
<view class="td">{{item.score}}</view>
</view>
</block>
</view>
<view class="no-record" wx:else>
<image src="/image/wechat.png" class="no-image"></image>
<text class="no-text">没有数据哦~</text>
</view>
</view>

logs.wxss

.table {
border: 0px solid darkgray;
font-size: 12px;
}
.tr {
display: flex;
width: 100%;
justify-content: center;
height: 2rem;
align-items: center;
}
.td {
width:40%;
justify-content: center;
text-align: center;
}
.bg-w{
background: snow;
}
.th {
width: 40%;
justify-content: center;
background: #3366FF;
color: #fff;
display: flex;
height: 2rem;
align-items: center;
}
.first {
flex:1 0 auto;
}
.no-record {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.no-image {
width: 200rpx;
height: 200rpx;
margin-top: 200rpx;
margin-bottom: 40rpx;
}
.no-text {
font-size: 16px;
color: #ccc;
display: block;
}

修改test.js

  /*
* 下一题/提交 按钮
*/
nextSubmit: function () {
// 如果没有选择
.....
// 设置缓存 var logs = wx.getStorageSync('logs') || [] let logsList = { "date": Date.now(), "testId": app.globalData.quizCategory[this.data.testId], "score": this.data.totalScore }
logs.unshift(logsList);
wx.setStorageSync('logs', logs);
}
},

最终项目结构

小程序部署上线

租用服务器,申请域名、搭建Linux环境并配置https服务

发布体验版

这里的体验版发布阶段有点类似于我们日常的灰度发布前的内部灰度测试,可以指定白名单用户进行生产测试体验。发布的动作其实很简单,就是在微信的开发IDE中上面工具栏上点击上传按钮即可发布到微信服务器,提交后就可以在mp管理端查看到新的开发版本,可以发布二维码白名单用户扫码后进行体验。

上线审核

体验版本验证没问题后就可以发布,点击开发版本右边的提交审核按钮就可以发布到腾讯进行小程序审核,第一次发布审核时间会比较长,大约3-5个工作日左右,日后的升级版本审核就很快了,基本上可以做到半天就审核通过。

上线

审核通过后就会出现在审核版本的栏位,点击右边的发布即可,发布后监控一段后台服务情况,如果发现问题可以紧急回退版本,小程序mp管理端也提供了前端版本回退的功能。

总体来说小程序在版本管理、发布回退的体验方面做得还是很好的,能够满足基本需求,不需要额外的开发。

项目总结

很感谢您和豆约翰走到了这里,至此我们这个知识测验微信小程序,全部开发完毕。如果对于代码或小程序上线有任何疑问,可以加我微信[tiantiancode]一起讨论。

最后

如果您觉得豆约翰的文章对您有所帮助,另外不想错过豆约翰未来更多更好的技术实战教程,还请

从0开始,手把手教你开发并部署上线一个知识测验微信小程序的更多相关文章

  1. 【云开发】10分钟零基础学会做一个快递查询微信小程序,快速掌握微信小程序开发技能(轮播图、API请求)

    大家好,我叫小秃僧 这次分享的是10分钟零基础学会做一个快递查询微信小程序,快速掌握开发微信小程序技能. 这篇文章偏基础,特别适合还没有开发过微信小程序的童鞋,一些概念和逻辑我会讲细一点,尽可能用图说 ...

  2. 小程序语音红包开发中 汉字转拼音的问题 微信小程序红包开发遇到的坑

    公司最近在开发微信小程序的红包功能,语音红包需要用到文字转拼音的功能. 之前介绍过怎么将中文的汉字转为拼音的,具体看下面这篇文章. 微信语音红包小程序开发如何提高精准度 红包小程序语音识别精准度 微信 ...

  3. 两天快速开发一个自己的微信小程序

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px "Songti SC" } p.p2 { margin: 0.0px 0. ...

  4. 快速开发一个自己的微信小程序

    一.写在前面 1.为什么要学小程序开发? 对于前端开发而言,微信小程序因为其简单快速.开发成本低.用户流量巨大等特点,也就成了前端开发工程师必会的一个技能. 2.先看看小程序效果 (1)欢迎页 (2) ...

  5. 如何开发一款堪比APP的微信小程序(腾讯内部团队分享)

    一夜之间,微信小程序刷爆了行业网站和朋友圈,小程序真的能如张小龙所说让用户"即用即走"吗? 其功能能和动辄几十兆安装文件的APP相比吗? 开发小程序,是不是意味着移动应用开发的一次 ...

  6. 1-微信小程序开发(安装软件和运行第一个微信小程序)

    https://developers.weixin.qq.com/miniprogram/dev/ 我的 打开 上传成功后

  7. 微信小程序开发环境搭建

    关注,QQ群,微信应用号社区 511389428 微信小程序可谓是今天最火的一个名词了,一经出现真是轰炸了整个开发人员,当然很多App开发人员有了一个担心,微信小程序的到来会不会给移动端App带来一个 ...

  8. 微信小程序开发心得

    微信小程序也已出来有一段时间了,最近写了几款微信小程序项目,今天来说说感受. 首先开发一款微信小程序,最主要的就是针对于公司来运营的,因为,在申请appid(微信小程序ID号)时候,需要填写相关的公司 ...

  9. 微信小程序开发心得--动画机制

    微信小程序也已出来有一段时间了,最近写了几款微信小程序项目,今天来说说感受.首先开发一款微信小程序,最主要的就是针对于公司来运营的,因为,在申请appid(微信小程序ID号)时候,需要填写相关的公司认 ...

随机推荐

  1. TypeScript使用体会(一)

    typescript使用体会 近期接手了一个公司项目是由TS写的,第一次用在这里做一下简单的使用体会 个人觉得TS与JS相差不多,只是多了一些约束(可能自己还没体会到精髓) typescript是Ja ...

  2. 如何通过IAM打造零信任安全架构

    万物互联时代来临,面对越来越严峻的企业网络安全及复杂的(如微服务,容器编排和云计算)开发.生产环境,企业 IT 急需一套全新的身份和访问控制管理方案. 为了满足企业需求,更好的服务企业用户,青云Qin ...

  3. 常见的几种java排序算法

    一.分类: 1)插入排序(直接插入排序.希尔排序) 2)交换排序(冒泡排序.快速排序) 3)选择排序(直接选择排序.堆排序) 4)归并排序 5)分配排序(基数排序) 所需辅助空间最多:归并排序 所需辅 ...

  4. FWT,FST入门

    0.目录 目录 0.目录 1.什么是 FWT 2. FWT 怎么做 2.1. 或卷积 2.2.与卷积 2.3.异或卷积 2.4.例题 3. FST 3.1. FST 怎么做 3.2.例题 1.什么是 ...

  5. Tournament Chart【模拟+vector+map+string】

    Tournament Chart 传送门:链接  来源:UPC10889 题目描述 In 21XX, an annual programming contest, Japan Algorithmist ...

  6. Dotnet core基于ML.net的销售数据预测实践

    ML.net已经进到了1.5版本.作为Microsoft官方的机器学习模型,你不打算用用?   一.前言 ML.net可以让我们很容易地在各种应用场景中将机器学习加入到应用程序中.这是这个框架很重要的 ...

  7. nuget 包是如何还原包的

    nuget 是如何还原包的 Intro 一直以来从来都是用 nuget 包,最近想折腾一个东西,需要自己搞一个 nuget 包的解析,用户指定 nuget 包的名称和版本,然后去解析对应的 nuget ...

  8. 使用git畅游代码的海洋

    如果把互联网上的纷繁代码比作一片海洋,那么git就是在这片海洋上航行的船只,正所谓“水可载舟,亦可覆舟”,git使用恰当可以远征星辰,不然可能会坠入无穷无尽的代码海洋无法自拔.书回正传,我们的征途是星 ...

  9. spring boot actuator扩展httptrace的记录

    SpringBoot记录HTTP请求日志 1.需求解读 需求: 框架需要记录每一个HTTP请求的信息,包括请求路径.请求参数.响应状态.返回参数.请求耗时等信息. 需求解读: Springboot框架 ...

  10. linux环境搭建单机kafka

    准备工作: jdk-8u191-linux-x64.rpm  |   zookeeper-3.4.6.tar.gz  |   kafka_2.11-2.2.0.tgz 对应的地址 zookeeper: ...