從0開始,手把手教你開發並部署上線一個知識測驗微信小程序

上線項目演示

微信搜索[放馬來答]或掃如下二維碼體驗:php

項目源碼

項目源碼html

視頻教程

視頻教程前端

其餘版本

Vue答題App實戰教程node

Hello小程序

1.註冊微信小程序

點擊當即註冊,選擇微信小程序,按照要求填寫信息linux

2.登陸小程序並完善信息

填寫小程序信息,完善信息。git

3.下載小程序開發工具

完善信息後點擊文檔,工具,下載,選擇穩定版的對應平臺的安裝包下載,下載完後點擊安裝便可web


4.創建小程序項目

掃碼登陸,選擇小程序,並點擊加號,填寫相關信息,APPID位置於下方截圖所示。npm

5.小程序代碼結構介紹

以下圖所示的四個文件,主要用於註冊和配置微信小程序,其包含的是全局配置信息。json

  • 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"
  }
}

二、執行npm install --production xxx,這個xxx就是你想使用的npm 包。此時在當前文件夾下會生成一個node_modules文件夾。PS:此處請務必使用--production選項,能夠減小安裝一些業務無關的 npm 包,從而減小整個小程序包的大小。

npm install --production he

三、在微信開發者工具-->工具-->構建npm,此時會生成一個miniprogram_npm文件夾。

四、構建完成後就可使用 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']  // 扣分操做
      })
    }
  },

實現結果展現頁面

image-20200709112925606

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;
}

實現錯題查看頁面

image-20200709113002927

項目目錄下新增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;
}

實現成績記錄頁面

image-20200709113214508

修改項目目錄下的app.json,增長底部導航欄

image-20200709113141222

{
  "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文件夾,並添加如下文件,具體文件請下載源碼獲取:

image-20200709113945088

修改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);
    }
  },

最終項目結構

image-20200709113800865

小程序部署上線

租用服務器,申請域名、搭建Linux環境並配置https服務

發佈體驗版

這裏的體驗版發佈階段有點相似於咱們平常的灰度發佈前的內部灰度測試,能夠指定白名單用戶進行生產測試體驗。發佈的動做其實很簡單,就是在微信的開發IDE中上面工具欄上點擊上傳按鈕便可發佈到微信服務器,提交後就能夠在mp管理端查看到新的開發版本,能夠發佈二維碼白名單用戶掃碼後進行體驗。

上線審覈

體驗版本驗證沒問題後就能夠發佈,點擊開發版本右邊的提交審覈按鈕就能夠發佈到騰訊進行小程序審覈,第一次發佈審覈時間會比較長,大約3-5個工做日左右,往後的升級版本審覈就很快了,基本上能夠作到半天就審覈經過。

上線

審覈經過後就會出如今審覈版本的欄位,點擊右邊的發佈便可,發佈後監控一段後臺服務狀況,若是發現問題能夠緊急回退版本,小程序mp管理端也提供了前端版本回退的功能。
整體來講小程序在版本管理、發佈回退的體驗方面作得仍是很好的,可以知足基本需求,不須要額外的開發。

項目總結

很感謝您和豆約翰走到了這裏,至此咱們這個知識測驗微信小程序,所有開發完畢。若是對於代碼或小程序上線有任何疑問,能夠加我微信[tiantiancode]一塊兒討論。

最後

若是您以爲豆約翰的文章對您有所幫助,另外不想錯過豆約翰將來更多更好的技術實戰教程,還請

相關文章
相關標籤/搜索