从0开始手把手教你开发并部署上线一个知识测验微信小程序

27次阅读

共计 21823 个字符,预计需要花费 55 分钟才能阅读完成。

上线我的项目演示

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

我的项目源码

我的项目源码

视频教程

视频教程

其余版本

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….. 这里不要勾选。

引入第三方库

仔细的敌人可能会发现,有些题目中有乱码,如下图所示&#039;

有一个很好的第三方库 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