Răsfoiți Sursa

更换图片音频上传服务

zhoupeng 5 ani în urmă
părinte
comite
2b9f5b61e1
42 a modificat fișierele cu 1743 adăugiri și 1010 ștergeri
  1. 6 2
      app.js
  2. 4 4
      app.json
  3. BIN
      assets/add.png
  4. BIN
      assets/del.png
  5. BIN
      assets/icon-del.png
  6. BIN
      assets/icon-history.png
  7. BIN
      assets/icon-pause.png
  8. BIN
      assets/icon-play.png
  9. BIN
      assets/icon-scan.png
  10. BIN
      assets/icon-state-play.png
  11. BIN
      assets/icon-state-stop.png
  12. BIN
      assets/icon-stop.png
  13. BIN
      assets/icon-upload.png
  14. BIN
      assets/picture.png
  15. BIN
      assets/reload.png
  16. BIN
      assets/scan.png
  17. BIN
      assets/upload.png
  18. 280 0
      pages/index/component/AudioPanel/index.js
  19. 1 1
      pages/index/component/AudioPanel/index.json
  20. 33 0
      pages/index/component/AudioPanel/index.wxml
  21. 125 0
      pages/index/component/AudioPanel/index.wxss
  22. 19 15
      pages/index/component/AudioPlayer/index.js
  23. 0 0
      pages/index/component/AudioPlayer/index.json
  24. 11 0
      pages/index/component/AudioPlayer/index.wxml
  25. 46 0
      pages/index/component/AudioPlayer/index.wxss
  26. 486 0
      pages/index/component/PicturePanel/index.js
  27. 4 0
      pages/index/component/PicturePanel/index.json
  28. 32 0
      pages/index/component/PicturePanel/index.wxml
  29. 161 0
      pages/index/component/PicturePanel/index.wxss
  30. 0 56
      pages/index/components/history/HistoryList.js
  31. 0 18
      pages/index/components/history/HistoryList.wxml
  32. 0 63
      pages/index/components/history/HistoryList.wxss
  33. 0 6
      pages/index/components/player/AudioPlayer.wxml
  34. 0 49
      pages/index/components/player/AudioPlayer.wxss
  35. 214 448
      pages/index/index.js
  36. 2 2
      pages/index/index.json
  37. 17 51
      pages/index/index.wxml
  38. 35 204
      pages/index/index.wxss
  39. 13 3
      project.config.json
  40. 7 0
      sitemap.json
  41. 119 39
      utils/api.js
  42. 128 49
      utils/api.ts

+ 6 - 2
app.js

@@ -1,9 +1,13 @@
 //app.js
+import {
+  Api
+} from "utils/api";
+
 App({
   onLaunch: function () {
     
   },
   globalData: {
-    userInfo: null
-  }
+    api: new Api()
+  },
 })

+ 4 - 4
app.json

@@ -1,12 +1,12 @@
 {
   "pages": [
-    "pages/index/index",
-    "pages/index/components/history/HistoryList"
+    "pages/index/index"
   ],
   "window": {
     "backgroundTextStyle": "light",
     "navigationBarBackgroundColor": "#fff",
-    "navigationBarTitleText": "语音答题",
+    "navigationBarTitleText": "考试作答小程序",
     "navigationBarTextStyle": "black"
-  }
+  },
+  "sitemapLocation": "sitemap.json"
 }

BIN
assets/add.png


BIN
assets/del.png


BIN
assets/icon-del.png


BIN
assets/icon-history.png


BIN
assets/icon-pause.png


BIN
assets/icon-play.png


BIN
assets/icon-scan.png


BIN
assets/icon-state-play.png


BIN
assets/icon-state-stop.png


BIN
assets/icon-stop.png


BIN
assets/icon-upload.png


BIN
assets/picture.png


BIN
assets/reload.png


BIN
assets/scan.png


BIN
assets/upload.png


+ 280 - 0
pages/index/component/AudioPanel/index.js

@@ -0,0 +1,280 @@
+// pages/index/component/AudioPanel/index.js
+const RECORD_CONFIG = {
+  duration: 120000,
+  sampleRate: 44100,
+  numberOfChannels: 1,
+  encodeBitRate: 128000,
+  format: 'mp3',
+}
+
+const app = getApp()
+const API = app.globalData.api
+Component({
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    paper: {
+      type: Object
+    }
+  },
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+    audio: '',
+    isRecord: false,
+    recordTime: 0,
+    duration: '00:00'
+  },
+
+  lockRecord: false,
+
+  recordHandler: 0,
+
+  recordManager: null,
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    audioAction: function (e) {
+      if (this.lockRecord) {
+        return false
+      }
+
+      if (!this.data.paper || !this.data.paper.courseId > 0) {
+        wx.showToast({
+          title: '请重新扫描题目二维码',
+          icon: 'none'
+        })
+        return false
+      }
+
+      this.lockRecord = true
+      this.triggerEvent('enableScan', { enabled: false })
+      this.startRecord()
+    },
+
+    uploadAudio(e) {
+      // 正在录音 禁止操作
+      if (this.data.isRecord) {
+        return
+      }
+
+      const filepath = this.data.audio
+      const that = this
+      wx.showLoading({
+        title: '正在上传录音',
+        mask: true
+      })
+
+      const duration = filepath
+      wx.getFileInfo({
+        filePath: filepath,
+        success: function (res) {
+          console.log(res)
+          var accessUrl = ''
+          // 获取上传签名
+          API.getSign(that.data.paper.examRecordDataId, that.data.paper.examStudentId,
+            that.data.paper.questionOrder, res.digest, 'mp3')
+            .then(res => {
+              accessUrl = res.accessUrl
+              // 上传文件
+              console.log(res)
+              return API.uploadFile(filepath, res.formParams, res.formUrl)
+            }).then(res => {
+              console.log(res)
+              //文件上传成功后通知服务端
+              API.notifyServer(that.data.paper.examRecordDataId, that.data.paper.examStudentId, that.data.paper.questionOrder, accessUrl, 'AUDIO').then(res => {
+                // 轮询答案处理结果
+                const _startTime = new Date().getTime()
+                var times = 10
+                const handler = setInterval(function () {
+                  API.getNotifyResult(res)
+                    .then(res => {
+                      console.log(res)
+                      if (res == 'CONFIRMED') {
+                        clearInterval(handler)
+                        wx.hideLoading()
+                        
+                        wx.showToast({
+                          title: '上传成功,继续扫描下一题',
+                          icon: 'none',
+                          duration: 2500
+                        })
+
+                        wx.reLaunch({
+                          url: '/pages/index/index'
+                        })
+                      } else {
+                        throw '答案未同步'
+                      }
+                    }).catch(exceptions => {
+                      times--
+                      if (new Date().getTime() - _startTime >= 1000 * 10 || times < 0) {
+                        clearInterval(handler)
+                        wx.hideLoading()
+                        wx.showToast({
+                          title: '上传失败,请重新上传',
+                          icon: 'none'
+                        })
+                      }
+                    })
+                }, 1000)
+              }).catch(exceptions => {
+                console.log('catch', exceptions)
+                var error = '上传失败,请重新上传'
+                try {
+                  if (exceptions.statusCode == 500) {
+                    error = exceptions.data.desc
+                  }
+                } catch (e) {
+
+                }
+                wx.hideLoading()
+                wx.showToast({
+                  title: error,
+                  icon: 'none'
+                })
+              })
+            }).catch(exceptions => {
+              console.log('catch', exceptions)
+              var error = '上传失败,请重新上传'
+              try {
+                if (exceptions.statusCode == 500) {
+                  error = exceptions.data.desc
+                }
+              } catch (e) {
+
+              }
+              wx.hideLoading()
+              wx.showToast({
+                title: error,
+                icon: 'none'
+              })
+            })
+        }
+      })
+    },
+
+    startRecord: function () {
+      const that = this
+      if (!this.data.isRecord) {
+        if (this.data.audio) {
+          wx.showModal({
+            title: '提示',
+            content: '已存在未上传的录音,是否开始重新录音',
+            confirmText: '开始录音',
+            success(res) {
+              if (res.confirm) {
+                that.recordManager.start(RECORD_CONFIG)
+              } else {
+                that.lockRecord = false
+              }
+            }
+          })
+        } else {
+          wx.showLoading({
+            title: '',
+            mask: true
+          })
+          that.recordManager.start(RECORD_CONFIG)
+        }
+      } else {
+        that.recordManager.stop()
+      }
+    },
+
+    onRecordStart() {
+      this.lockRecord = false
+      console.log("record start")
+      const that = this
+
+      this.setData({
+        audio: '',
+        isRecord: true
+      })
+
+      this.recordHandler = setInterval(function () {
+        var recordTime = that.data.recordTime + 0.1
+        var second = parseInt(recordTime)
+        var msc = parseInt((recordTime - second) * 10) * 10
+        var duration = (second > 9 ? second : ('0' + second)) + ":" + (msc > 0 ? msc : "00")
+        that.setData({
+          isRecord: true,
+          recordTime: recordTime,
+          duration: duration
+        })
+      }, 100)
+    },
+    onRecordStop(audioPath) {
+      this.triggerEvent('enableScan', {enabled: true})
+      this.lockRecord = false
+      console.log("record stop", audioPath)
+      clearInterval(this.recordHandler)
+      this.recordHandler = 0
+
+      this.setData({
+        audio: audioPath,
+        recordTime: 0,
+        duration: "00:00",
+        isRecord: false,
+      })
+    },
+  },
+
+  attached() {
+    const that = this
+    this.recordManager = wx.getRecorderManager()
+    this.recordManager.onError(function (res) {
+      console.log(res)
+      that.lockRecord = false
+      wx.showToast({
+        title: '录音出错',
+        icon: 'none'
+      })
+    })
+
+    this.recordManager.onStart(function () {
+      //保持屏幕常亮
+      wx.setKeepScreenOn({ keepScreenOn: true })
+      wx.hideLoading()
+      that.onRecordStart()
+    })
+    this.recordManager.onStop(function (res) {
+      wx.setKeepScreenOn({ keepScreenOn: false })
+      wx.hideLoading()
+      that.onRecordStop(res.tempFilePath)
+    })
+    this.recordManager.onInterruptionBegin(function () {
+      wx.hideLoading()
+      console.log("onInterruptionBegin")
+      that.lockRecord = false
+      that.recordManager.stop()
+    })
+    this.recordManager.onInterruptionEnd(function () {
+      wx.hideLoading()
+      that.lockRecord = false
+      console.log("onInterruptionEnd")
+    })
+
+    wx.onAppHide(() => {
+      if (that.data.isRecord) {
+        wx.hideLoading()
+        that.recordManager.stop()
+
+        that.lockRecord = false
+      }
+    })
+  },
+  detached() {
+    const that = this
+    wx.offAppHide(() => {
+      if (that.data.isRecord) {
+        that.recordManager.stop()
+      }
+    })
+  }
+})

+ 1 - 1
pages/index/components/history/HistoryList.json → pages/index/component/AudioPanel/index.json

@@ -1,6 +1,6 @@
 {
   "component": true,
   "usingComponents": {
-    "AudioPlayer": "../player/AudioPlayer"
+    "Player": "../AudioPlayer/index"
   }
 }

+ 33 - 0
pages/index/component/AudioPanel/index.wxml

@@ -0,0 +1,33 @@
+<!--pages/main/component/AudioPanel/index.wxml-->
+<view class="audio-container">
+  <block wx:if="{{audio == ''}}">
+    <text class="second">{{duration}}</text>
+    <text class="tips">(最长2分钟)</text>
+  </block>
+  <view class="audio-bottom">
+    <view class="btn-container" bindtap="audioAction" wx:if="{{audio == ''}}">
+      <view class="btn">
+        <view class="record-{{isRecord?'ing':'normal'}}"></view>
+      </view>
+      <text class="btn-label">{{isRecord?'点击结束录音':'点击开始录音'}}</text>
+    </view>
+    <view class="actions-container" wx:if="{{audio != ''}}">
+      <view class="btn-container" bindtap="audioAction">
+        <view class='btn'>
+          <image mode="aspectFit" src='/assets/reload.png' style='width:32px;height:32px;'></image>
+        </view>
+        <text class="btn-label">重新录音</text>
+      </view>
+      <view class="btn-container">
+        <view class="upload-container" bindtap="uploadAudio">
+          <image mode="aspectFit" src='/assets/upload.png' style='width:32px;height:32px;'></image>
+        </view>
+        <text class="btn-label">上传录音</text>
+      </view>
+    </view>
+  </view>
+
+  <view class="player-container" wx:if="{{isRecord == false && audio != ''}}">
+    <Player id='player' src="{{audio}}" duration="{{recordTime}}"></Player>
+  </view>
+</view>

+ 125 - 0
pages/index/component/AudioPanel/index.wxss

@@ -0,0 +1,125 @@
+/* pages/main/component/AudioPanel/index.wxss */
+.audio-container {
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  margin-bottom: 134px;
+}
+
+.audio-container .second {
+  font-size:48px;
+  font-family:PingFangSC-Regular;
+  font-weight:400;
+  color:rgba(44,58,81,1);
+  line-height:68px;
+}
+
+.audio-container .tips {
+  font-size:14px;
+  font-family:PingFangSC-Regular;
+  font-weight:400;
+  color:rgba(122,124,145,1);
+  line-height:20px;
+}
+
+.audio-container .audio-bottom {
+  width:100%;
+  height:134px;
+  background:rgba(249,249,251,1);
+  position: fixed;
+  bottom: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding-bottom: 20px;
+  z-index: 1000;
+}
+
+.audio-bottom .btn-container {
+  padding: 10px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-direction: column;
+}
+
+.btn-container .btn {
+  width:64px;
+  height:64px;
+  background:rgba(255,255,255,1);
+  border-radius: 50%;
+  border:2px solid #F98183;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.btn .record-normal {
+  width:20px;
+  height:20px;
+  background:linear-gradient(180deg,rgba(249,129,131,1) 0%,rgba(241,74,76,1) 100%);
+  border-radius:10px;
+}
+
+.btn .record-ing {
+  width:20px;
+  height:20px;
+  background:linear-gradient(180deg,rgba(249,129,131,1) 0%,rgba(241,74,76,1) 100%);
+  border-radius:4px;
+}
+
+.audio-bottom .actions-container {
+  width: 100%;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-around;
+}
+
+.actions-container .upload-container {
+  width:64px;
+  height:64px;
+  background:linear-gradient(180deg,rgba(125,218,153,1) 0%,rgba(71,179,96,1) 100%);
+  border-radius:32px;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+}
+
+.btn-label {
+  height:20px;
+  font-size:14px;
+  font-family:PingFangSC-Regular;
+  font-weight:400;
+  color:rgba(44,58,81,1);
+  line-height:20px;
+  margin-top: 10px;
+}
+
+.player-container {
+  padding: 20px 16px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+
+.player-container Player {
+  margin-bottom: 20px;
+}
+
+.upload-btn {
+  width:90px;
+  height:32px;
+  background:rgba(255,255,255,1);
+  border-radius:10px;
+  border:1px solid rgba(80,182,104,0.85);
+  font-size:14px;
+  font-family:PingFangSC-Medium;
+  font-weight:500;
+  color:rgba(71,179,96,1);
+  line-height:32px;
+  text-align: center;
+}

+ 19 - 15
pages/index/components/player/AudioPlayer.js → pages/index/component/AudioPlayer/index.js

@@ -1,4 +1,4 @@
-// pages/index/components/player/AudioPlayer.js
+// pages/main/component/Player/index.js
 var AudioContext = wx.createInnerAudioContext()
 Component({
   /**
@@ -9,7 +9,7 @@ Component({
       type: String,
       observer: function (src) {
         console.log(this.data.auto)
-        console.log("audio src changed",src)
+        console.log("audio src changed", src)
 
         this.stop()
         if (this.data.auto) {
@@ -17,10 +17,12 @@ Component({
         }
       }
     },
-    updated: {
-      type: String,
-      observer: function(v) {
-        console.log(v)
+    duration: {
+      type: Number,
+      observer: function(d) {
+        this.setData({
+          formatDuration: this.formatTimer(d)
+        })
       }
     },
     auto: {
@@ -45,7 +47,7 @@ Component({
    * 组件的方法列表
    */
   methods: {
-    stop: function() {
+    stop: function () {
       AudioContext.stop()
 
       this.setData({
@@ -61,10 +63,11 @@ Component({
       if (!this.data.src) {
         return
       }
-      if (AudioContext.src != this.data.src) {
+      /*if (AudioContext.src != this.data.src) {
         this.resetAudioContext(this.data.src)
-      }
-      
+      }*/
+
+      console.log('playAction',this.data.isPlay)
       if (this.data.isPlay) {
         this.stop()
       } else {
@@ -120,7 +123,7 @@ Component({
         console.log("onCanPlay", AudioContext, AudioContext.duration)
         that.canPlay = true
 
-        setTimeout(function() {
+        setTimeout(function () {
           const formatDuration = that.formatTimer(AudioContext.duration)
           that.setData({
             formatCurrent: '00:00',
@@ -189,7 +192,8 @@ Component({
         console.log(errMessage)
         wx.showToast({
           title: errMessage,
-          icon: 'none'
+          icon: 'warn',
+          duration: 2500
         })
         that.setData({
           isPlay: false,
@@ -208,7 +212,7 @@ Component({
       AudioContext.autoplay = this.data.auto
     },
 
-    audioCallback: function(callback) {
+    audioCallback: function (callback) {
       this.audioCallback = callback
     }
   },
@@ -224,14 +228,14 @@ Component({
       }
     })
 
-    wx.onAppHide(function() {
+    wx.onAppHide(function () {
       console.log('player onAppHide')
       console.log(that.data.isPlay)
       if (that.data.isPlay) {
         that.stop()
       }
     })
-    wx.onAppShow(function() {
+    wx.onAppShow(function () {
       console.log("player onAppShow")
       if (that.data.src && !that.canPlay) {
         that.resetAudioContext(that.data.src)

+ 0 - 0
pages/index/components/player/AudioPlayer.json → pages/index/component/AudioPlayer/index.json


+ 11 - 0
pages/index/component/AudioPlayer/index.wxml

@@ -0,0 +1,11 @@
+<!--pages/main/component/Player/index.wxml-->
+<!--pages/index/components/player/AudioPlayer.wxml-->
+<view class="player-container" bindtap='playAction'>
+  <view class="play-btn">
+    <image mode="aspectFit" src="/assets/icon-state-{{isPlay ? 'play' : 'stop'}}.png" style='height: 14px;width: 14px;'></image>
+  </view>
+  <progress percent="{{progress}}" stroke-width="8" border-radius="4" backgroundColor="rgba(227,227,230,1)" activeColor="rgba(71,179,96,1)" border-radius="6" />
+  <view class='timer'>{{formatCurrent}}/{{formatDuration}}</view>
+
+  <view class="tips">点击试听录音</view>
+</view>

+ 46 - 0
pages/index/component/AudioPlayer/index.wxss

@@ -0,0 +1,46 @@
+/* pages/main/component/Player/index.wxss */
+.player-container {
+  width: calc(100vw - 32px - 16px);
+  display: flex;
+  flex-direction: row;
+  padding-left: 16px;
+  background: gainsboro;
+  align-items: center;
+  height:64px;
+  background:rgba(249,249,251,1);
+  border-radius:10px;
+  position: relative;
+}
+
+.play-btn {
+  width: 32px;
+  height: 32px;
+  background:linear-gradient(180deg,rgba(125,218,153,1) 0%,rgba(71,179,96,1) 100%);
+  border-radius: 16px;
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+}
+
+progress {
+  width: calc(100% - 16px - 32px - 32px - 80px);
+  margin-left: 16px;
+  margin-right: 16px;
+}
+
+.timer {
+  color: #2C3A51;
+  font-size: 14px;
+  text-align: right;
+  vertical-align: middle;
+  padding-right: 10px;
+  width: 80px;
+}
+
+.tips {
+  color: #7A7C91;
+  font-size: 10px;
+  position: absolute;
+  left: 65px;
+  top: 40px;
+}

+ 486 - 0
pages/index/component/PicturePanel/index.js

@@ -0,0 +1,486 @@
+// pages/index/component/PicturePanel/index.js
+const MAX_PICTURE = 6
+const app = getApp()
+const API = app.globalData.api
+const SUFFIX = ["jpg", "jpeg", "png"]
+const MASK = false
+Component({
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    paper: {
+      type: Object
+    },
+    cancelAll: {
+      type: Boolean,
+      observer: function(isCancelAll) {
+        console.log("isCancelAll",isCancelAll)
+        if (this.data.willSubmit && isCancelAll) {
+          wx.hideLoading()
+        }
+      }
+    }
+  },
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+    size: 0,
+    maxPicture: MAX_PICTURE,
+    pictures: [],
+    enableChoose: true,
+    gridLoading: false,
+    willSubmit: false, // 确定按钮是否已点击
+  },
+
+  timeoutHandler: null,
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    reset: function() {
+      this.setData({
+        pictures: [],
+        willSubmit: false
+      })
+    },
+    del: function(e) {
+      if (this.data.willSubmit) {
+        return false
+      }
+      var index = e.currentTarget.dataset.index;
+      console.log(index);
+      var pictures = this.data.pictures
+      pictures.splice(index, 1)
+      this.setData({
+        pictures: pictures
+      })
+    },
+    choosePic: function(e) {
+      if (this.data.willSubmit) {
+        return false
+      }
+      
+      if (this.data.pictures.length >= MAX_PICTURE) {
+        return false
+      }
+
+      console.log('choosePic', this.data.enableChoose)
+      if (!this.data.enableChoose) {
+        return false
+      }
+
+      this.setData({
+        enableChoose: false,
+      })
+
+      var that = this
+      wx.showActionSheet({
+        itemList: ['拍照', '从手机相册选择'],
+        success: chooseResult => {
+          console.log('showActionSheet',this.data.pictures.length)
+          if (this.data.pictures.length >= MAX_PICTURE) {
+            wx.showToast({
+              title: '一次最多只能选择6张',
+              icon: 'none',
+              mask: MASK
+            })
+            return
+          }
+          that.setData({
+            gridLoading: true
+          })
+
+          
+          switch (chooseResult.tapIndex) {
+            case 0:
+              wx.chooseImage({
+                sourceType: ['camera'],
+                sizeType: ['original'],
+                success: res => {
+                  console.log(res);
+                  var pictures = that.data.pictures
+
+                  res.tempFiles.forEach(function (item, idx) {
+                    console.log(idx, item)
+                    item['suffix'] = item.path.substring(item.path.lastIndexOf(".") + 1, item.path.length)
+                    item['err'] = SUFFIX.indexOf(item['suffix'].toLowerCase()) > -1 ? '' : '图片格式错误'
+                    if (item['err'] == '') {
+                      item['err'] = item.size > 1024 * 1024 * 10 ? '图片超过10M' : ''
+                    }
+                    console.log(item)
+                  })
+                  console.log(res.tempFiles)
+                  Array.prototype.push.apply(pictures, res.tempFiles);
+                  that.setData({
+                    pictures: pictures,
+                  })
+                },
+                complete: () => {
+                  that.setData({
+                    gridLoading: false
+                  })
+                }
+              })
+              break;
+            case 1:
+              wx.chooseImage({
+                count: MAX_PICTURE - that.data.pictures.length,
+                sizeType: ['original'],
+                sourceType: ['album'],
+                success: res => {
+                  console.log(res);
+                  var pictures = that.data.pictures
+                
+                  var pic = []
+                  res.tempFiles.forEach(function (item, idx) {
+                    console.log(idx, item)
+                    if (that.data.pictures.length + idx + 1 <= MAX_PICTURE) {
+                      item['suffix'] = item.path.substring(item.path.lastIndexOf(".") + 1, item.path.length)
+                      item['err'] = SUFFIX.indexOf(item['suffix'].toLowerCase()) > -1 ? '' : '图片格式错误'
+                      if (item['err'] == '') {
+                        item['err'] = item.size > 1024 * 1024 * 10 ? '图片超过10M' : ''
+                      }
+                      pic.push(item)
+                      console.log(item)
+                    }
+                  })
+                  console.log(pic)
+                  Array.prototype.push.apply(pictures, pic);
+                  that.setData({
+                    pictures: pictures,
+                  })
+                },
+                complete: () => {
+                  that.setData({
+                    gridLoading: false
+                  })
+                }
+              })
+              break;
+          }
+        },
+        complete: () => {
+          that.setData({
+            enableChoose: true
+          })
+        }
+      })
+    },
+    submit: function(e) {
+      const pictures = this.data.pictures
+      var valid = true
+      pictures.forEach(function(item, index) {
+        pictures[index]['retry'] = 0
+        if (item.err) {
+          valid = false
+          return
+        }
+      })
+
+      if (!valid) {
+        wx.showToast({
+          title: '仅支持10M以下的jpg、png、jpeg格式图片',
+          icon: 'none',
+          duration: 3000,
+          mask: MASK
+        })
+        return
+      }
+
+      if (this.data.willSubmit) {
+        return false
+      }
+
+      this.setData({
+        willSubmit: true
+      })
+
+      wx.showLoading({
+        title: '正在上传(0/' + this.data.pictures.length + ')',
+        mask: MASK
+      })
+
+      var that = this
+      this.timeoutHandler = setTimeout(function() {
+        console.log('timeout', new Date())
+        wx.hideLoading()
+        that.setData({
+          willSubmit: false
+        })
+        if (that.data.pictures.length > 0) {
+          wx.showToast({
+            title: '上传失败',
+            icon: 'none',
+            duration: 2500,
+            mask: MASK
+          })
+        }
+      }, 60000)
+
+      this.checkUploadComplete()
+
+      //TODO 断点上传
+      for (var index = 0; index < pictures.length; index++) {
+        console.log('uploadImage:' + index)
+        var retryTimes = 3
+        this.uploadImage(index, retryTimes)
+      }
+    },
+
+    checkFailed() {
+      var _pics = this.data.pictures
+      var retryFailedCount = _pics.filter(function (item) {
+        return item != null && item.status == 'FAIL' && item.retry <= 0
+      }).length
+      var failedCount = _pics.filter(function (item) {
+        return item != null && item.status == 'FAIL'
+      }).length
+      console.log('checkFailed', new Date, retryFailedCount)
+      var that = this
+      if (failedCount > 0 && retryFailedCount == failedCount) {
+        clearTimeout(this.timeoutHandler)
+        wx.hideLoading()
+        wx.showToast({
+          title: '上传失败',
+          icon: 'none',
+          duration: 2500,
+          mask: MASK
+        })
+        that.setData({
+          willSubmit: false
+        })
+      }
+    },
+    checkUploadComplete() {
+      if (this.data.willSubmit && !this.data.cancelAll && !this.checkFailed()) {
+        var _pics = this.data.pictures
+        var uploadCount = _pics.filter(function(item) {
+          return item != null && item.status == 'SUCCESS'
+        }).length
+
+        console.log('checkUploadComplete',_pics)
+        if (uploadCount == this.data.pictures.length) {
+          clearTimeout(this.timeoutHandler)
+          this.timeoutHandler = setTimeout(function () {
+            console.log('timeout', new Date())
+            wx.hideLoading()
+            that.setData({
+              willSubmit: false
+            })
+            if (that.data.pictures.length > 0) {
+              wx.showToast({
+                title: '更新答案超时',
+                icon: 'none',
+                duration: 2500,
+                mask: MASK
+              })
+            }
+          }, 30000)
+          wx.showLoading({
+            title: '正在更新答案',
+            mask: MASK
+          })
+          // TODO 合并图片地址,保存地址到服务端,轮询获取保存状态
+          const imagePaths = this.data.pictures.map(function(item) {
+            return item.url
+          }).join(',')
+          const that = this
+          console.log('notifyServer')
+          API.notifyServer(that.data.paper.examRecordDataId, that.data.paper.examStudentId,
+              that.data.paper.questionOrder, imagePaths, 'PIC')
+            .then(notifyResult => {
+              console.log(notifyResult)
+              const _startTime = new Date().getTime()
+              var times = 10 // 轮询10次
+              const handler = setInterval(function() { // 10秒超时
+                API.getNotifyResult(notifyResult)
+                  .then(res => {
+                    console.log(res)
+                    if (res == 'CONFIRMED') {
+                      clearTimeout(that.timeoutHandler)
+                      clearInterval(handler)
+                      wx.hideLoading()
+                      wx.showToast({
+                        title: '上传成功\n继续扫描下一题',
+                        icon: 'success',
+                        duration: 2500,
+                        mask: MASK
+                      })
+                      setTimeout(function () {
+                        wx.reLaunch({
+                          url: '/pages/index/index'
+                        })
+                      }, 2500)
+                    } else if (res == 'DISCARDED') {
+                      clearTimeout(that.timeoutHandler)
+                      clearInterval(handler)
+                      wx.hideLoading()
+                      wx.showToast({
+                        title: '题目已切换,请重新扫码',
+                        icon: 'none',
+                        duration: 2500,
+                        mask: MASK
+                      })
+                      setTimeout(function () {
+                        wx.reLaunch({
+                          url: '/pages/index/index'
+                        })
+                      }, 2500)
+                    } else {
+                      throw '答案未同步'
+                    }
+                  }).catch(exceptions => {
+                    console.log('getNotifyResult:catch:', exceptions)
+                    times--
+                    if (new Date().getTime() - _startTime >= 1000 * 10 || times < 0) {
+                      clearInterval(handler)
+                      clearTimeout(that.timeoutHandler)
+                      wx.hideLoading()
+                      wx.showToast({
+                        title: '答案上传失败,请点击重试',
+                        icon: 'none',
+                        duration: 2500,
+                        mask: MASK
+                      })
+                      that.setData({
+                        willSubmit:false
+                      })
+                    }
+                  })
+              }, 1000)
+            }).catch(function(error) {
+              clearTimeout(that.timeoutHandler)
+              wx.hideLoading()
+              wx.showToast({
+                title: error,
+                icon: 'none',
+                duration: 2500,
+                mask: MASK
+              })
+              that.setData({
+                willSubmit: false
+              })
+            })
+          return true
+        } else {
+          console.log('正在上传(' + uploadCount + '/' + this.data.pictures.length + ')')
+          wx.showLoading({
+            title: '正在上传(' + uploadCount + '/' + this.data.pictures.length + ')',
+            mask: MASK
+          })
+          return false
+        }
+      }
+    },
+
+    //上传文件,支持配置重试次数
+    uploadFile(filepath, formParams, formUrl, accessUrl, index, retry) {
+      console.log('uploadFile:cancelAll', this.data.cancelAll)
+      if (this.data.cancelAll) {
+        return
+      }
+      if (retry <= 0) {
+        return
+      }
+      const that = this
+      API.uploadFile(filepath, formParams, formUrl).then(res => {
+        var pic = that.data.pictures
+        pic[index]['url'] = accessUrl
+        pic[index]['status'] = 'SUCCESS'
+        that.setData({
+          pictures: pic
+        })
+        that.checkUploadComplete()
+      }).catch(exceptions => {
+        console.log('uploadFile:uploadFile:catch', exceptions)
+        var pic = that.data.pictures
+        pic[index]['status'] = 'FAIL'
+        pic[index]['retry'] = 3 - retry
+        that.setData({
+          pictures: pic
+        })
+        that.checkFailed()
+        retry--
+        if (retry > 0) {
+          that.uploadFile(filepath, formParams, formUrl, accessUrl, index, retry)
+        }
+      })
+    },
+    uploadImage(imageIndex, retry) {
+      if (this.data.cancelAll) {
+        return;
+      }
+      const img = this.data.pictures[imageIndex]
+      console.log(img)
+      if (img.status == 'SUCCESS') { // 已上传
+        return
+      }
+      const that = this
+      if (retry > 0) {
+        wx.compressImage({
+          src: img.path,
+          quality: 80,
+          success: function(cf) {
+            console.log('uploadImage:compressImage:success',cf)
+            wx.getFileInfo({
+              filePath: cf.tempFilePath,
+              success: function (res) {
+                console.log('uploadImage:compressImage:success>>getFileInfo:success', res)
+                // 获取上传签名
+                API.getSign(that.data.paper.examRecordDataId, that.data.paper.examStudentId,
+                  that.data.paper.questionOrder, res.digest, 'jpeg')
+                  .then(signResult => {
+                    // 上传文件
+                    var _retry = 3
+                    that.uploadFile(cf.tempFilePath, signResult.formParams, signResult.formUrl, signResult.accessUrl, imageIndex, _retry)
+                  }).catch(exceptions => {
+                    var pic = that.data.pictures
+                    pic[imageIndex]['status'] = 'FAIL'
+                    pic[imageIndex]['retry'] = 3 - retry
+                    that.setData({
+                      pictures: pic
+                    })
+                    that.checkFailed()
+                    console.log('uploadImage:exceptions:', new Date(), pic)
+                    that.uploadImage(imageIndex, --retry)
+                  })
+              }
+            })
+          },
+          error: function() {
+            var pic = that.data.pictures
+            pic[imageIndex]['status'] = 'FAIL'
+            pic[imageIndex]['retry'] = 3 - retry
+            that.setData({
+              pictures: pic
+            })
+            that.checkFailed()
+            console.log('压缩失败')
+          },
+          complete: function () {
+            console.log('压缩complete')
+          }
+        })
+      
+      } else {
+        var pic = that.data.pictures
+        pic[imageIndex]['status'] = 'FAIL'
+        pic[imageIndex]['retry'] = 3 - retry
+        that.setData({
+          pictures: pic
+        })
+        that.checkFailed()
+      }
+    },
+  },
+
+  attached() {
+    var result = wx.getSystemInfoSync();
+    this.setData({
+      size: (result.screenWidth - 20 * 2 - 10 * 4) / 3
+    })
+  }
+})

+ 4 - 0
pages/index/component/PicturePanel/index.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 32 - 0
pages/index/component/PicturePanel/index.wxml

@@ -0,0 +1,32 @@
+<!--pages/main/component/PicturePanel/index.wxml-->
+<view class="picture-container">
+  <view class="picture-bottom" wx:if="{{pictures.length == 0 && !gridLoading}}" bindtap="choosePic">
+    <view class="btn-container">
+      <view class="btn"><image mode="aspectFit" src='/assets/picture.png' style='width:24px;height:22px;'></image></view>
+      <text class="btn-label">点击开始拍照</text>
+    </view>
+  </view>
+</view>
+
+<view class="grid-loading" wx:if="{{gridLoading}}">正在加载...</view>
+
+<view class="grid-container" wx:if="{{pictures.length>0}}">
+    <view class="label">最多可以上传{{maxPicture}}张照片</view>
+    <view class="grid">
+      <block wx:for="{{pictures}}">
+        <view class="image-container">
+        <view class="del" bindtap="del" data-index="{{index}}"><image mode="center" src='/assets/del.png' style='width:28px;height:28px;'></image></view>
+        <view class="err" wx:if="{{item.err || (item.status == 'FAIL' && item.retry == 0)}}">{{item.err ? item.err : '请删除或点击确认上传重试'}}</view>
+        <!--<view class="success" wx:if="{{item.status == 'SUCCESS'}}">上传成功</view>-->
+        <image mode="scaleToFill" src='{{item.path}}' style="width: {{size}}px;height: {{size}}px;border-radius:10px;"></image>
+        </view>
+      </block>
+      <block wx:if="{{pictures.length < maxPicture}}">
+      <view class="add" style="width: {{size}}px;height: {{size}}px;" bindtap="choosePic">
+        <image mode="aspectFit" src='/assets/add.png' style='width:24px;height:24px;'></image>
+      </view>
+      </block>
+    </view>
+    <view class="submit {{willSubmit ? 'disable' : ''}}" bindtap="submit">确认上传</view>
+
+  </view>

+ 161 - 0
pages/index/component/PicturePanel/index.wxss

@@ -0,0 +1,161 @@
+/* pages/main/component/PicturePanel/index.wxss */
+.picture-container {
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+
+.picture-container .picture-bottom {
+  width:100%;
+  height:134px;
+  background:rgba(249,249,251,1);
+  position: absolute;
+  bottom: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding-bottom: 20px;
+}
+
+.picture-bottom .btn-container {
+  padding: 10px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-direction: column;
+}
+
+.picture-bottom .btn-container .btn {
+  width:64px;
+  height:64px;
+  background:rgba(255,255,255,1);
+  border-radius: 50%;
+  border:2px solid #F98183;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.btn-label {
+  height:20px;
+  font-size:14px;
+  font-family:PingFangSC-Regular;
+  font-weight:400;
+  color:rgba(44,58,81,1);
+  line-height:20px;
+  margin-top: 10px;
+}
+
+.grid-container {
+  padding: 20px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+
+.grid-loading {
+  height: 120px;
+  padding: 20px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  text-align: center;
+  color:rgba(122,124,145,1);
+}
+
+.grid-container .submit {
+  width:215px;
+  height:48px;
+  background:linear-gradient(180deg,rgba(125,218,153,1) 0%,rgba(71,179,96,1) 100%);
+  border-radius:24px;
+  font-size:18px;
+  font-family:PingFangSC-Semibold;
+  font-weight:600;
+  color:rgba(255,255,255,1);
+  line-height:48px;
+  text-align: center;
+  margin-top: 20px;
+}
+
+.disable {
+  background:linear-gradient(180deg,rgba(125,218,153,0.4) 0%,rgba(71,179,96,0.4) 100%);
+}
+
+.grid-container .label {
+  width: 100%;
+  height:17px;
+  font-size:12px;
+  font-family:PingFangSC-Regular;
+  font-weight:400;
+  color:rgba(122,124,145,1);
+  line-height:17px;
+  margin-bottom: 10px;
+}
+
+.grid-container .grid {
+  display: grid;
+  background:rgba(249,249,251,1);
+  border-radius:10px;
+  padding: 10px;
+  grid-template-columns: 1fr 1fr 1fr;
+  grid-row-gap: 10px;
+  grid-column-gap: 10px;
+}
+
+.grid .image-container {
+  border-radius: 10px;
+  position: relative;
+}
+
+.grid .image-container image {
+  border-radius: 10px;
+}
+
+.grid .image-container .del {
+  position: absolute;
+  right: 0;
+  top: 0;
+  z-index: 99;
+}
+
+.grid .image-container .err {
+  position: absolute;
+  width: 100%;
+  height: 36px;
+  text-align: center;
+  bottom: 2px;
+  z-index: 99;
+  font-size: 12px;
+  background: crimson;
+  line-height: 18px;
+  color: white;
+  border-bottom-left-radius: 10px;
+  border-bottom-right-radius: 10px;
+}
+
+.grid .image-container .success {
+  position: absolute;
+  width: 100%;
+  height: 20px;
+  text-align: center;
+  bottom: 2px;
+  z-index: 99;
+  font-size: 12px;
+  background: green;
+  line-height: 20px;
+  color: white;
+  border-bottom-left-radius: 10px;
+  border-bottom-right-radius: 10px;
+}
+
+.grid .add {
+  background:rgba(249,249,251,1);
+  border-radius:10px;
+  border:1px dashed rgba(202,202,204,1);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}

+ 0 - 56
pages/index/components/history/HistoryList.js

@@ -1,56 +0,0 @@
-// pages/index/components/history/HistoryList.js
-Component({
-
-  properties: {
-    histories: {
-      type: Array,
-      observer: function (list) {
-        console.log('histories')
-        console.log(list)
-      }
-    }
-  },
-
-  data: {
-    currentPidx: -1,
-    currentIdx: -1,
-    src: ""
-  },
-
-  methods: {
-    selectAudio: function(e) {
-      const dataset = e.currentTarget.dataset
-      const src = this.data.histories[dataset.pid]['answers'][dataset.id]['studentAnswer'] + "?t=" + new Date().getTime()
-      this.setData({
-        currentPidx: dataset.pid,
-        currentIdx: dataset.id,
-        src: src
-      })
-      console.log(this.data)
-    }
-  },
-
-  attached() {
-    const that = this
-    this.setData({
-      src: ""
-    })
-    console.log("history attached")
-
-    const player = this.selectComponent('.player')
-    if (player) {
-      player.audioCallback(function (src) {
-        that.setData({
-          currentPidx: 0,
-          currentIdx: 0,
-          src: ''
-        })
-        console.log('player bind failed', src)
-      })
-    }
-  },
-
-  detached() {
-    console.log("detached")
-  }
-})

+ 0 - 18
pages/index/components/history/HistoryList.wxml

@@ -1,18 +0,0 @@
-<!--pages/index/components/history/HistoryList.wxml-->
-<view style='padding-top:2px;padding-bottom:8px;padding-left:16px;padding-right:16px;' wx:if="{{histories.length > 0}}">
-      <AudioPlayer class="player" src="{{src}}" auto="{{true}}"></AudioPlayer>
-    </view>
-<scroll-view scroll-y style="max-height:calc(100vh - 240px);">
-<view class='list-container'>
-  <view class='group' wx:for="{{histories}}" wx:key="{{idx}}" wx:for-index="pidx" wx:if="{{item}}">
-    <view class='title' wx:if="{{item.title}}">{{item.title}}</view>
-    <view class='item-container'>
-      <view class='item' wx:for="{{item.answers}}" wx:key="{{order}}" wx:for-index="idx" data-pid="{{pidx}}" data-id="{{idx}}" bindtap='selectAudio'>
-        <view class="content {{currentPidx === pidx && currentIdx == idx ? 'item-selected' : ''}}">{{item.subNumber}}</view>
-      </view>
-    </view>
-  </view>
-
-  <view class='empty' wx:if="{{!histories || histories.length == 0}}">暂无上传记录</view>
-</view>
-</scroll-view>

+ 0 - 63
pages/index/components/history/HistoryList.wxss

@@ -1,63 +0,0 @@
-/* pages/index/components/history/HistoryList.wxss */
-/*列表*/
-.list-container {
-  padding-left: 16px;
-  padding-right: 16px;
-  padding-bottom: 48px;
-  background: #f8f8f8;
-}
-
-.list-container .group .title{
-  font-size: 16px;
-  color:black;
-  padding-top:10px;
-  padding-bottom: 4px;
-}
-
-.list-container .group .item-container {
-  display: flex;
-  flex-direction: row;
-  flex-wrap: wrap;
-  align-content: flex-start;
-}
-
-.list-container .group .item {
-  width: 14.285714285714286%;
-  height: calc((100vw - 32px) / 7);
-  flex-direction: row;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
-
-.list-container .group .item .content{
-  width: 35px;
-  height: 35px;
-  font-size: 18px;
-  box-sizing: border-box;
-  border-radius: 50%;
-  border: 1px solid #ccc;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  color: #2d8cf0;
-  background: 0 0;
-  text-decoration: none;
-  outline: 0;
-  transition: color .2s ease;
-}
-
-.item-selected {
-    box-shadow: 0 0 15px gold;
-    background-color: #a8bcf7!important;
-    transform: scale(1.2);
-}
-
-.empty {
-  display: flex;
-  min-height: 120px;
-  align-items: center;
-  justify-content: center;
-  font-size: 18px;
-  color: #ccc;
-}

+ 0 - 6
pages/index/components/player/AudioPlayer.wxml

@@ -1,6 +0,0 @@
-<!--pages/index/components/player/AudioPlayer.wxml-->
-<view class="player-container {{updated == 'true' ? 'updated' : ''}}">
-  <image mode="aspectFit" src="/assets/icon-{{!isPlay ? 'play' : 'stop'}}.png" bindtap='playAction' style='height: 40px;width: 40px;'></image>
-  <progress percent="{{progress}}" stroke-width="6" color="rgba(56, 121, 217, .8)" border-radius="6" />
-  <view class='timer'>{{formatCurrent}}/{{formatDuration}}</view>
-</view>

+ 0 - 49
pages/index/components/player/AudioPlayer.wxss

@@ -1,49 +0,0 @@
-/* pages/index/components/player/AudioPlayer.wxss */
-@import "/iconfont.wxss";
-
-.player-container {
-  display: flex;
-  flex-direction: row;
-  padding: 10px 0px 10px 16px;
-  border-radius: 6px;
-  border: 1rpx solid gainsboro;
-  background: gainsboro;
-  align-items: center;
-}
-
-.player-container.updated {
-  border: 1rpx solid rgba(93, 167, 228, 0.6)!important;
-  background: rgba(93, 167, 228, 0.6)!important;
-}
-
-.updated .timer {
-  color:aliceblue;
-}
-
-.play-btn {
-  width: 60rpx;
-  height: 60rpx;
-  color:rgba(56, 121, 217, .8);
-  font-size: 60rpx;
-  line-height: 60rpx;
-}
-
-.act-btn {
-  color:rgba(128, 128, 128, .8);
-  font-size: 40rpx;
-}
-
-progress {
-  width: calc(100% - 16px - 16px - 80px);
-  margin-left: 16px;
-  margin-right: 16px;
-}
-
-.timer {
-  color: #aaa;
-  font-size: 14px;
-  text-align: right;
-  vertical-align: middle;
-  padding-right: 10px;
-  width: 80px;
-}

+ 214 - 448
pages/index/index.js

@@ -1,35 +1,20 @@
-//index.js
-//获取应用实例
+// pages/index/index.js
 const WX_REG = "http://wxapp2.qmth.com.cn/"
 const ZH_CN = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十',
   '十一', '十二', '十三', '十四', '十五', '十六', '十七', '十八', '十九', '二十',
   '三十一', '三十二', '三十三', '三十四', '三十五', '三十六', '三十七', '三十八', '三十九', '四十',
-  '四十一', '四十二', '四十三', '四十四', '四十五', '四十六', '四十七', '四十八', '四十九', '五十']
-const II = ['①', '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨', '⑩',
-  '⑪', '⑫', '⑬', '⑭', '⑮', '⑯', '⑰', '⑱', '⑲', '⑳']
-import {
-  Api
-} from "../../utils/api";
-const API = new Api()
+  '四十一', '四十二', '四十三', '四十四', '四十五', '四十六', '四十七', '四十八', '四十九', '五十'
+]
+
 const app = getApp()
-const RECORD_CONFIG = {
-  duration: 120000,
-  sampleRate: 44100,
-  numberOfChannels: 1,
-  encodeBitRate: 128000,
-  format: 'mp3',
-}
+const API = app.globalData.api
 
 Page({
-  data: {
-    audio: '',
-    updated: false,
-    isRecord: false,
-    recordTime: 0.0,
-    duration: "00:00",
-    windowWidth: 0,
-    maskOpacity: 0,
 
+  /**
+   * 页面的初始数据
+   */
+  data: {
     paper: {
       courseId: 0,
       courseName: "",
@@ -37,380 +22,161 @@ Page({
       examStudentId: 0,
       questionMainNumber: 0,
       questionOrder: 0,
-      subNumber: 0
+      subNumber: 0,
+      isTokenInValid: false
     },
-    currentQuestionTitle: '请扫描题目',
-
-    audioList: [],
-    histories: [],
+    subTitle: '',
+    questionType: '', //AUDIO PIC
+    enableScan: true, //录音时不允许扫描
   },
 
   secret: '',
 
-  recordHandler: 0,
-
-  recordManager: null,
-
-  paperStruct: [],
-  
-  resetData: function() {
+  reset: function() {
     this.secret = ''
     this.setData({
-      audio: '',
-      updated: false,
-      isRecord: false,
-      recordTime: 0.0,
-      duration: "00:00",
-      windowWidth: 0,
-      maskOpacity: 0,
-
-      paper: {
-        courseId: 0,
-        courseName: "",
-        examRecordDataId: 0,
-        examStudentId: 0,
-        questionMainNumber: 0,
-        questionOrder: 0,
-        subNumber: 0
-      },
-      currentQuestionTitle: '请扫描题目',
-
-      audioList: [],
-      histories: [],
+      paper: null,
+      subTitle: '',
+      questionType: '',
+      enableScan: true,
+      isTokenInValid: false
     })
   },
+  scan: function (e) {
+    console.log(e)
+    if (!this.data.enableScan) {
+      return false
+    }
 
-  scanQuestion: function(e) {
-    // 正在录音 禁止操作
-    if (this.data.isRecord) {
-      return
+    var hasAnswer = false //是否有未提交的答案
+    var component = this.selectComponent('#audio')
+    if (component != null && component.data.audio != '') {
+      hasAnswer = true
     }
 
+    if (!hasAnswer) {
+      component = this.selectComponent('#pic')
+      if (component != null && component.data.pictures.length > 0) {
+        hasAnswer = true
+      }
+    }
+
+    if (hasAnswer) {
+      wx.showModal({
+        title: '提示',
+        content: '有未提交的答案,是否放弃',
+        showCancel: true,
+        cancelText: '放弃',
+        cancelColor: '#FF5252',
+        confirmText: '取消',
+        success(res) {
+          if (!res.confirm) {
+            wx.reLaunch({
+              url: '/pages/index/index',
+            })
+          }
+        }
+      })
+      return
+    }
     const that = this
     wx.scanCode({
       onlyFromCamera: true,
       scanType: ['qrCode'],
-      success: function(res) {
+      success(res) {
         console.log(res)
-        if (res) {
+        var type = that.getQueryVariable(decodeURIComponent(res.result), 'transferFileType')
+        if (type == 'PIC' || type == 'AUDIO') {
+          wx.setNavigationBarTitle({
+            title: type == 'PIC' ? '图片上传' : '音频上传'
+          })
+          that.setData({
+            questionType: type
+          })
+          // 获取试题信息
           that.secret = res.result.replace(WX_REG, '')
           that.checkQrcode(that.secret, true)
         } else {
           wx.showToast({
-            title: '扫码失败',
-            icon: 'none'
+            title: '二维码有误!',
+            icon: 'none',
+            duration: 2500,
+            mask: true
           })
         }
-      }
-    })
-  },
-
-  lockRecord: false,
-
-  audioAction: function(e) {
-    if (this.lockRecord) {
-      return false
-    }
-
-    if (!this.data.paper || !this.data.paper.questionOrder) {
-      wx.showToast({
-        title: '请重新扫描题目二维码',
-        icon: 'none'
-      })
-      return false
-    }
-
-    this.lockRecord = true
-    this.startRecord()
-  },
-
-  startRecord: function() {
-    const that = this
-    if (!this.data.isRecord) {
-      if (this.data.audio) {
-        wx.showModal({
-          title: '提示',
-          content: '已存在未上传的录音,是否开始重新录音',
-          confirmText: '开始录音',
-          success(res) {
-            if (res.confirm) {
-              that.recordManager.start(RECORD_CONFIG)
-            } else {
-              that.lockRecord = false
-            }
-          }
-        })
-      } else {
-        wx.showLoading({
-          title: '',
-          mask: true
-        })
-        that.recordManager.start(RECORD_CONFIG)
-      }
-    } else {
-      that.recordManager.stop()
-    }
-  },
-
-  /**
-   * 显示题目列表
-   */
-  showListPanel() {
-    const player = this.selectComponent("#player")
-    if (player != null) {
-      console.log(player)
-      player.stop()
-    }
-
-    if (!this.secret) {
-      wx.showToast({
-        title: '请先扫描题目',
-        icon: 'none'
-      })
-      return
-    }
-
-    // 正在录音 禁止操作
-    if (this.data.isRecord) {
-      return
-    }
-
-    wx.showLoading({
-      title: '正在加载',
-      mask: true
-    })
-    const that = this
-
-    /*if (this.paperStruct && this.paperStruct.length > 0 && this.data.paperthis.paperStruct[0].examRecordDataId == this.data.paper.examRecordDataId) {
-      API.getUploadedAudioAnswerList(this.data.paper.examRecordDataId).then(list => {
-        that.__inline_getAnswerListSuccess(list)
-      }).catch(e => {
-        console.log(e)
-        wx.hideLoading()
+      },
+      fail(res) {
+        console.log(res)
+        console.log(res != null && res.errMsg == 'scanCode:fail cancel')
+        if (res != null && res.errMsg == 'scanCode:fail cancel') {
+          return
+        }
         wx.showToast({
-          title: '加载失败',
-          icon: 'none'
-        })
-        this.hideActionPanel()
-      })
-    } else {
-      
-    }*/
-
-    API.getPaperStruct(this.data.paper.examRecordDataId).then(list => {
-      var struct = []
-      if (list.defaultPaper.questionGroupList && Array.isArray(list.defaultPaper.questionGroupList) && list.defaultPaper.questionGroupList.length > 0) {
-        list.defaultPaper.questionGroupList.forEach(item => {
-          struct.push(item.groupName)
+          title: '二维码读取异常!',
+          icon: 'none',
+          duration: 2500,
+          mask: true
         })
       }
-      this.paperStruct = struct
-      return API.getUploadedAudioAnswerList(this.data.paper.examRecordDataId)
-    }).then(list => {
-      that.__inline_getAnswerListSuccess(list)
-    }).catch(e => {
-      console.log(e)
-      wx.hideLoading()
-      wx.showToast({
-        title: '加载失败',
-        icon: 'none'
-      })
-      this.hideActionPanel()
-    })
-  },
-
-  __inline_getAnswerListSuccess: function(list) {
-    //list = TEST_LIST
-
-    var histories = []
-    if (list && Array.isArray(list) && list.length > 0) {
-      list.forEach(item => {
-        if (item.mainNumber <= this.paperStruct.length && item.mainNumber > 0) {
-          let index = histories.findIndex((v) => {
-            return v && v['idx'] === item.mainNumber
-          })
-          if (index > -1) {
-            histories[index]['answers'].push(item)
-          } else {
-            histories[item.mainNumber] = { 
-              idx: item.mainNumber,
-              title: this.paperStruct[item.mainNumber - 1], 
-              answers: new Array(item)
-            }
-          }
-        } else {
-          throw new Error('试题结构异常')
-        }
-      })
-    }
-
-    this.setData({
-      histories: histories,
-      maskOpacity: 0.6
     })
-    wx.hideLoading()
   },
 
-  /**
-   * 关闭题目列表
-   */
-  hideActionPanel() {
+  onScanEnable: function(event) {
+    console.log('event::onScanEnable')
+    console.log(event)
     this.setData({
-      maskOpacity: 0
+      enableScan: event.detail.enabled
     })
   },
 
-  uploadAudio(e) {
-    // 正在录音 禁止操作
-    if (this.data.isRecord) {
-      return
+  checkQrcode: function (code, fromScan) {
+    if (fromScan) {
+      wx.showLoading({
+        title: '正在加载',
+        mask: true
+      })
     }
 
-    const filepath = this.data.audio
     const that = this
-    wx.showLoading({
-      title: '正在上传录音',
-      mask: true
-    })
-
-    const duration = filepath
-    wx.getFileInfo({
-      filePath: filepath,
-      success: function(res) {
-        console.log(res)
-        // 获取上传签名
-        API.getSign(that.data.paper.examRecordDataId, that.data.paper.examStudentId,
-          that.data.paper.questionOrder, res.digest)
-          .then(res => {
-            // 上传文件
-            console.log(res)
-            return API.uploadFile(filepath, res.policy, res.signature, res.uploadUrl)
-          }).then(res => {
-            console.log(res)
-            //文件上传成功后通知服务端
-            return API.notifyServer(that.data.paper.examRecordDataId, that.data.paper.examStudentId, that.data.paper.questionOrder, res.url)
-          }).then(res => {
-            // 轮询答案处理结果
-            const _startTime = new Date().getTime()
-            var times = 10
-            const handler = setInterval(function() {
-              API.getNotifyResult(that.data.paper.examRecordDataId, that.data.paper.examStudentId, that.data.paper.questionOrder, res)
-                .then(res => {
-                  console.log(res)
-                  if (res) {
-                    clearInterval(handler)
-                    wx.hideLoading()
-                    wx.showToast({
-                      title: '上传成功,继续扫描下一题',
-                      icon: 'none'
-                    })
-                    var paper = that.data.paper
-                    paper['questionMainNumber'] = 0
-                    paper['questionOrder'] = 0
-                    paper['subNumber'] = 0
-                    that.setData({
-                      audio: '',
-                      updated: false,
-                      paper: paper,
-                      currentQuestionTitle: '请扫描题目'
-                    })
-                  } else {
-                    throw '答案未同步'
-                  }
-                }).catch(exceptions => {
-                  times--
-                  if (new Date().getTime() - _startTime >= 1000 * 10 || times < 0) {
-                    clearInterval(handler)
-                    wx.hideLoading()
-                    wx.showToast({
-                      title: '上传失败,请重新上传',
-                      icon: 'none'
-                    })
-                  }
-                })
-            }, 1000)
-          }).catch(exceptions => {
-            console.log('catch', exceptions)
-            var error = '上传失败,请重新上传'
-            try {
-              if (exceptions.statusCode == 500) {
-                error = exceptions.data.desc
-              }
-            } catch (e) {
-
-            }
+    try {
+      API.checkQrcode(code)
+        .then(function (res) {
+          console.log(res)
+          const title = `第${ZH_CN.length >= res.questionMainNumber ? ZH_CN[res.questionMainNumber - 1] : res.questionMainNumber}大题 第${res.subNumber}小题 `
+          that.setData({
+            paper: res,
+            subTitle: title
+          })
+          if (fromScan) {
             wx.hideLoading()
             wx.showToast({
-              title: error,
-              icon: 'none'
+              title: '扫码成功',
+              icon: 'success',
+              mask: true
+            })
+          }
+        })
+        .catch(function (error) {
+          console.log(error)
+          wx.hideLoading()
+          var errorMsg = error
+          if ((errorMsg && errorMsg.indexOf('request:fail') == 0) || errorMsg == undefined) {
+            errorMsg = "网络请求失败,请稍后重试"
+          }
+          that.showConfirmDialog(`${errorMsg}`, '确定', function () {
+            console.log('callback')
+            wx.reLaunch({
+              url: '/pages/index/index',
             })
           })
-      }
-    })
-  },
-
-  removeAudio(e) {
-    const that = this
-    wx.showModal({
-      title: '提示',
-      content: '删除后无法恢复,是否直接删除',
-      confirmText: '直接删除',
-      confirmColor: '#ff0000',
-      success(res) {
-        if (res.confirm) {
-          that.setData({
-            audio: ''
-          })
-        }
-      }
-    })
-  },
-
-  onRecordStart() {
-    this.lockRecord = false
-    console.log("record start")
-    const that = this
-
-    this.setData({
-      audio: '',
-      updated: false,
-      isRecord: true
-    })
-
-    this.recordHandler = setInterval(function() {
-      var recordTime = that.data.recordTime + 0.1
-      var second = parseInt(recordTime)
-      var msc = parseInt((recordTime - second) * 10) * 10
-      var duration = (second > 9 ? second : ('0' + second)) + ":" + (msc > 0 ? msc : "00")
-      that.setData({
-        isRecord: true,
-        recordTime: recordTime,
-        duration: duration
-      })
-    }, 100)
-  },
-  onRecordStop(audioPath) {
-    this.lockRecord = false
-    console.log("record stop", audioPath)
-    clearInterval(this.recordHandler)
-    this.recordHandler = 0
-
-    this.setData({
-      audio: audioPath,
-      recordTime: 0,
-      duration: "00:00",
-      isRecord: false,
-      updated: false
-    })
-  },
-
-  onShow: function() {
-    /*if (this.secret) {
-      this.checkQrcode(this.secret, false)
-    }*/
+        })
+    } catch(e) {
+      console.log('========+++++++++++++')
+    }
+    
   },
 
-  showConfirmDialog: function(msg, confirm, callback) {
+  showConfirmDialog: function (msg, confirm, callback) {
     const confirmText = confirm ? confirm : '确定'
     wx.showModal({
       title: '提示',
@@ -428,114 +194,114 @@ Page({
     })
   },
 
-  onLoad: function(options) {
-    console.log(options)
-    const encodeReg = encodeURIComponent(WX_REG)
-    if (options && options.q && options.q.indexOf(encodeReg) == 0) {
-      this.secret = options.q.replace(encodeReg, '')
+  getQueryVariable: function(url, variable) {
+    var vars = url.split("&")
+    for (var i = 0; i < vars.length; i++) {
+      var pair = vars[i].split("=")
+      if (pair[0] == variable) {
+        return pair[1]
+      }
     }
+    return (false)
+  },
 
-    const that = this
-
-    this.recordManager = wx.getRecorderManager()
-    this.recordManager.onError(function(res) {
-      console.log(res)
-      that.lockRecord = false
-      wx.showToast({
-        title: '录音出错',
-        icon: 'none'
+  tokenValidCallback: function () {
+    if (this.data.isTokenInValid) {
+      return;
+    }
+    this.setData({
+      isTokenInValid: true
+    })
+    this.showConfirmDialog('二维码已过期,请重新扫描题目', '确定', function () {
+      console.log('callback')
+      wx.reLaunch({
+        url: '/pages/index/index'
       })
     })
+  },
 
-    this.recordManager.onStart(function () {
-      wx.hideLoading()
-      that.onRecordStart()
-    })
-    this.recordManager.onStop(function (res) {
-      wx.hideLoading()
-      that.onRecordStop(res.tempFilePath)
-    })
-    this.recordManager.onInterruptionBegin(function () {
-      wx.hideLoading()
-      console.log("onInterruptionBegin")
-      that.lockRecord = false
-      that.recordManager.stop()
-    })
-    this.recordManager.onInterruptionEnd(function () {
-      wx.hideLoading()
-      that.lockRecord = false
-      console.log("onInterruptionEnd")
-    })
+  /**
+   * 生命周期函数--监听页面加载
+   */
+  onLoad: function(options) {
+    console.log(options)
+    if (options && options.q) {
+      const query = decodeURIComponent(options.q)
+      console.log(query)
+      if (query.indexOf(WX_REG) == 0) {
+        this.secret = query.replace(WX_REG, '')
+      }
+      var type = this.getQueryVariable(decodeURIComponent(query), 'transferFileType')
+      console.log(type)
+      if (type == 'PIC' || type == 'AUDIO') {
+        wx.setNavigationBarTitle({
+          title: type == 'PIC' ? '图片上传' : '音频上传'
+        })
+        this.setData({
+          questionType: type
+        })
+      } else {
+        wx.showToast({
+          title: '二维码有误!',
+          icon: 'none',
+          duration: 2500,
+          mask: true
+        })
+      }
+    }
+    if (this.secret) {
+      this.checkQrcode(this.secret, false)
+    }
 
     API.setTokenValidCallback(this.tokenValidCallback)
+  },
 
-    wx.onAppHide(() => {
-      console.log("index onAppHide")
-      if (that.data.isRecord) {
-        wx.hideLoading()
-        that.recordManager.stop()
+  /**
+   * 生命周期函数--监听页面初次渲染完成
+   */
+  onReady: function() {
 
-        that.lockRecord = false
-      }
-    })
   },
 
-  checkQrcode: function(code, fromScan) {
-    if (fromScan) {
-      wx.showLoading({
-        title: '正在加载',
-        mask: true
-      })
-    }
+  /**
+   * 生命周期函数--监听页面显示
+   */
+  onShow: function() {
 
-    const that = this
-    API.checkQrcode(code)
-      .then(function(res) {
-        const title = `第${ZH_CN.length >= res.questionMainNumber ? ZH_CN[res.questionMainNumber - 1] : res.questionMainNumber}大题 第${II.length >= res.subNumber ? II[res.subNumber-1] : res.subNumber}小题 `
-        that.setData({
-          audio: '',
-          isRecord: false,
-          updated: false,
-          paper: res,
-          currentQuestionTitle: title,
-        })
-        if (fromScan) {
-          wx.hideLoading()
-          wx.showToast({
-            title: '扫码成功',
-            icon: 'none'
-          })
-        }
-      })
-      .catch(function(error) {
-        wx.hideLoading()
-        var errorMsg = error
-        if (errorMsg.indexOf('request:fail') == 0) {
-          errorMsg = "网络请求失败,请稍后重试"
-        }
-        that.showConfirmDialog(`${errorMsg}`, '确定', function() {
-          console.log('callback')
-          wx.reLaunch({
-            url: '/pages/index/index',
-          })
-        })
-      })
   },
 
-  tokenValidCallback: function() {
-    this.showConfirmDialog('二维码已过期,请重新扫描题目', '立即扫描', function() {
-      console.log('callback')
-      this.scanQuestion()
-    })
+  /**
+   * 生命周期函数--监听页面隐藏
+   */
+  onHide: function() {
+
   },
 
+  /**
+   * 生命周期函数--监听页面卸载
+   */
   onUnload: function() {
-    const that = this
-    wx.offAppHide(() => {
-      console.log("index offAppHide")
-      if (that.data.isRecord) {
-        that.recordManager.stop()
-      }
-    })
+
+  },
+
+  /**
+   * 页面相关事件处理函数--监听用户下拉动作
+   */
+  onPullDownRefresh: function() {
+
+  },
+
+  /**
+   * 页面上拉触底事件的处理函数
+   */
+  onReachBottom: function() {
+
+  },
+
+  /**
+   * 用户点击右上角分享
+   */
+  onShareAppMessage: function() {
+
   }
 })

+ 2 - 2
pages/index/index.json

@@ -1,7 +1,7 @@
 {
   "usingComponents": {
-    "AudioPlayer":"components/player/AudioPlayer",
-    "HistoryList": "components/history/HistoryList"
+    "AudioPanel": "component/AudioPanel/index",
+    "PicturePanel": "component/PicturePanel/index"
   },
   "disableScroll": true
 }

+ 17 - 51
pages/index/index.wxml

@@ -1,54 +1,20 @@
-<!--index.wxml-->
-<view class="container">
-  <view class="action-container">
-    <view class='action-btn' bindtap='scanQuestion'>
-      <image mode="aspectFit" src='/assets/icon-scan.png' style='width:50px;height:50px;'></image>
-      <view class='label'>扫描题目</view>
+<!--pages/index/index.wxml-->
+<scroll-view scroll-y='true' style="height:100vh">
+  <view class="container">
+    <view class="action-container">
+      <view class="btn-container" bindtap="scan" style="opacity: {{enableScan ? 1.0 : 0.3}}">
+        <view class="action-btn">
+          <image mode="aspectFit" src='/assets/scan.png' style='width:32px;height:32px;'></image>
+        </view>
+        <view class="action-label">请扫描题目二维码</view>
+      </view>
     </view>
-    <view class='action-btn' bindtap='showListPanel'>
-      <image mode="aspectFit" src='/assets/icon-history.png' style='width:50px;height:50px;'></image>
-      <view class='label'>上传记录</view>
-    </view>
-  </view>
-
-  <view class='recording-header'>
-    <view class='q-name'>{{paper && paper.courseName ? paper.courseName : ''}}</view>
-    <view class='recording-title'>{{currentQuestionTitle}}</view>
-    <view class='recording-tips'>(最长120秒)</view>
-    <view class='recording-duration'>
-      <text>{{duration}}</text>
-    </view>
-  </view>
-
-  <view style='padding-top:15px;padding-bottom:15px;width:100%;' wx:if="{{paper && audio}}">
-    <view class='question-group-header'>{{currentQuestionTitle}}</view>
-    <view style='padding-top:2px;padding-bottom:8px;'>
-      <AudioPlayer id="player" class="player" src="{{audio}}" updated="{{updated}}" wx:if="{{audio}}"></AudioPlayer>
-    </view>
-  </view>
-</view>
-
-<view class='bottom-action-container' wx:if="{{paper && paper.courseId > 0}}">
-  <view class='act-btn' wx:if="{{audio}}" bindtap='removeAudio'>
-    <view class='btn cancel-btn'>
-      <image mode="aspectFit" src='/assets/icon-del.png' style='width:30px;height:30px;'></image>
-    </view>
-  </view>
 
-  <view class='record-btn' bindtap='audioAction'>
-    <view class='recording' wx:if="{{isRecord}}"></view>
-  </view>
-  <view class='act-btn' wx:if="{{audio}}" bindtap='uploadAudio'>
-    <view class='btn upload-btn {{updated ? "updated":""}}'>
-      <image mode="aspectFit" src='/assets/icon-upload.png' style='width:30px;height:30px;'></image>
-    </view>
-  </view>
-</view>
-<view class="mask" style="opacity: {{maskOpacity}}" ontap="hideActionPanel" wx:if="{{maskOpacity > 0}}"></view>
-<view class='panel-container' wx:if="{{maskOpacity > 0}}">
-  <view class="head">
-    <view class='title'>上传记录</view>
-    <view class='panel-close' bindtap='hideActionPanel'>关闭</view>
+    <block wx:if="{{paper != null && paper.courseId > 0}}">
+      <text class="question-title">{{paper.courseName}}</text>
+      <text class="question-info">{{subTitle}}</text>
+    </block>
+    <AudioPanel id='audio' bind:enableScan="onScanEnable" paper="{{paper}}" wx:if="{{questionType == 'AUDIO'}}"></AudioPanel>
+    <PicturePanel id='pic' paper="{{paper}}" cancelAll="{{isTokenInValid}}" wx:if="{{questionType == 'PIC'}}"></PicturePanel>
   </view>
-  <HistoryList histories="{{histories}}"></HistoryList>
-</view>
+</scroll-view>

+ 35 - 204
pages/index/index.wxss

@@ -1,237 +1,68 @@
-/**index.wxss**/
-@import "/iconfont.wxss";
-
-.recording-header {
-  display: flex;
+/* pages/index/index.wxss */
+.container {
   width: 100%;
+  display: flex;
   flex-direction: column;
   align-items: center;
-  justify-content: center;
-  margin-top: 10px;
-}
-
-.recording-duration {
-  font-size: 38px;
-  color: black;
-}
-
-.recording-tips {
-  font-size: 14px;
-  padding: 2px;
-  color:#666;
-}
-
-.q-name {
-  color:#666;
-  font-size: 18px!important;
-  padding-bottom: 4px;
+  padding: 0;
+  margin: 0;
 }
-
-.recording-title {
-  color:#666;
-  font-size: 16px!important;
-  border-bottom: 1px solid #666;
-  padding-bottom: 2px;
-}
-
-.n {
-  border: 1px #666 solid;
-  width: 15px;
-  height: 15px;
-  display: inline-flex;
-  justify-content: center;
-  font-size: 11px;
-  border-radius: 50%;
-  margin-bottom: 4px;
-}
-
-.player {
-  width: 100%;
-}
-
 .action-container {
   width: 100%;
-  height: 90px;
   display: flex;
   flex-direction: row;
   align-items: center;
-  justify-content: space-around;
+  justify-content: center;
+  padding-top: 30px;
+  padding-bottom: 22px;
 }
 
-.action-container .action-btn {
+.action-container .btn-container {
+  height: 100px;
   display: flex;
   flex-direction: column;
   justify-content: center;
   align-items: center;
 }
-.scan-btn {
-  width: 100rpx;
-  height: 100rpx;
-  color: green;
-  font-size: 100rpx;
-}
-.action-btn .label {
-  font-size: 16px;
-  color: #999;
-  margin-top: 6px;
-}
 
-.record-btn {
-  width: 130rpx;
-  height: 130rpx;
-  border-radius: 50%;
-  background-color: red;
-  box-shadow: 0 0 10px #f00;
+.btn-container .action-btn {
+  width:72px;
+  height:72px;
+  background:linear-gradient(180deg,rgba(125,218,153,1) 0%,rgba(71,179,96,1) 100%);
+  border-radius:36px;
   display: flex;
   flex-direction: column;
-  align-items: center;
   justify-content: center;
-}
-
-.record-btn .recording {
-  width: 40rpx;
-  height: 40rpx;
-  background-color: white;
-}
-
-.panel-container {
-  background-color: transparent;
-  width: 100%;
-  position:absolute;
-  bottom: 0px;
-  color: rgba(56, 121, 217, .8);
-  background: #f8f8f8;
-}
-
-.panel-container .head {
-  display: flex;
   align-items: center;
-  justify-content: space-between;
-  height: 50px;
-  background: #fafafa;
-}
-
-.panel-container .head .title {
-  font-size:17px;
-  color:rgb(53, 54, 56);
-  padding-left: 16px;
 }
 
-.panel-container .head .panel-close {
-  font-size:14px;
-  color:rgba(139,142,156,1);
-  padding-left: 16px;
-  padding-right: 16px;  
-  height: 50px;
-  line-height: 50px;
-}
-
-.panel-list {
-  background: white;
-  display: flex;
-  flex-direction: column;
-  align-items: stretch;
-  justify-content: space-between;
-  border-radius: 8px;
-}
-
-.mask {
-  position: fixed;
-  height: 100vh;
-  width: 100vw;
-  transition: all 0.3s;
-  background:rgb(7,24,54);
-  top:0;
-}
-
-.panel-list .item {
-  padding: 8px;
-  text-align: center;
-  font-size: 20px;
-  border-bottom: 1rpx solid gainsboro;
-}
-
-.panel-list .item:last-child {
-  border: none;
-}
-
-.panel-container .close {
-  color: rgba(56, 121, 217, .8);
-  border-radius: 8px;
-  background: white;
-  display: flex;
-  flex-direction: column;
-  align-items: stretch;
-  justify-content: space-between;
-  padding: 8px;
+.btn-container .action-label {
+  font-size:16px;
+  font-family:PingFangSC-Regular;
+  font-weight:400;
+  color:rgba(122,124,145,1);
+  line-height:20px;
   text-align: center;
   margin-top: 8px;
-  font-size: 20px;
-}
-
-.question-group-header {
-  font-size: 14px;
-  padding: 8px;
-  color:#666;
 }
 
-.bottom-action-container {
-  display: flex;
-  width: 100%;
-  flex-direction: row;
-  justify-content: space-around;
-  align-items: center;
-  height: 120px;
-  position: fixed;
-  bottom: 0;
-  background: #f8f8f8;
-  padding-bottom: 20px;
+.question-title {
+  font-size:24px;
+  font-family:PingFangSC-Semibold;
+  font-weight:600;
+  color:rgba(44,58,81,1);
+  line-height:34px;
 }
 
-.act-btn {
-  width: 100rpx;
-  height: 100rpx;
-  padding: 20rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
+.question-info {
+  font-size:14px;
+  font-family:PingFangSC-Regular;
+  font-weight:400;
+  color:rgba(122,124,145,1);
+  line-height:20px;
+  margin-top: 8px;
 }
 
-.act-btn .btn {
+AudioPanel {
   width: 100%;
-  height:100%;
-  border-radius: 50%;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
-
-.act-btn .btn.cancel-btn {
-  box-shadow: 0 0 10px #d8d8d8;
-  border: 1rpx solid #d8d8d8;
-  border-style: none;
-  background: #d8d8d8;
-}
-
-.act-btn .btn.upload-btn {
-  box-shadow: 0 0 10px #d8d8d8;
-  border: 1rpx solid #d8d8d8;
-  border-style: none;
-  background: #d8d8d8;
-}
-
-.cancel {
-  color: rgb(255, 255, 255)!important;
-  font-size: 50rpx!important;
-}
-
-.upload {
-  color: rgb(255, 255, 255)!important;
-  font-size: 50rpx!important;
-}
-
-.act-btn .updated {
-  box-shadow: 0 0 10rpx green!important;
-  border: none!important;
-  background: green!important;
 }

+ 13 - 3
project.config.json

@@ -4,22 +4,32 @@
 		"ignore": []
 	},
 	"setting": {
-		"urlCheck": true,
+		"urlCheck": false,
 		"es6": true,
 		"postcss": true,
 		"minified": true,
 		"newFeature": true,
 		"nodeModules": false,
-		"autoAudits": false
+		"autoAudits": false,
+		"checkInvalidKey": true,
+		"checkSiteMap": true,
+		"uploadWithSourceMap": true,
+		"babelSetting": {
+			"ignore": [],
+			"disablePlugins": [],
+			"outputPath": ""
+		}
 	},
 	"compileType": "miniprogram",
-	"libVersion": "2.7.1",
+	"libVersion": "2.8.3",
 	"appid": "wx4263754944d4d184",
 	"projectname": "tools",
 	"debugOptions": {
 		"hidedInDevtools": []
 	},
 	"isGameTourist": false,
+	"simulatorType": "wechat",
+	"simulatorPluginLibVersion": {},
 	"condition": {
 		"search": {
 			"current": -1,

+ 7 - 0
sitemap.json

@@ -0,0 +1,7 @@
+{
+  "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
+  "rules": [{
+  "action": "allow",
+  "page": "*"
+  }]
+}

Fișier diff suprimat deoarece este prea mare
+ 119 - 39
utils/api.js


+ 128 - 49
utils/api.ts

@@ -6,7 +6,7 @@ declare namespace tools {
     }
 
     export interface Result<T extends Entity> {
-        code?: number,
+        code?: number | string,
         desc?: string,
         data?: T | T[]
     }
@@ -56,10 +56,12 @@ declare namespace tools {
     }
 
     export interface SignResult extends Entity {
-        filePath: string,
-        policy: string,
-        signature: string,
-        uploadUrl: string
+        accessUrl: string,
+        formUrl: string,
+        fsType: string,
+        uploadUrl: string,
+        formParams: object,
+        signIdentifier: string
     }
 
     export interface NotifyResult extends Entity {
@@ -98,13 +100,13 @@ interface API {
 
     getPaperStruct(examRecordDataId: number) : Promise<any>
 
-    getSign(examRecordDataId: number, examStudentId: number, order: number, fileMd5: string) : Promise<tools.SignResult>
+    getSign(examRecordDataId: number, examStudentId: number, order: number, fileMd5: string, suffix: string) : Promise<tools.SignResult>
 
-    uploadFile(filePath: string, policy: string, signature: string, uploadUrl: string) : Promise<tools.UpyunResult>
+    uploadFile(filePath: string, formParams: object, uploadUrl: string) : Promise<tools.UpyunResult>
 
-    notifyServer(examRecordDataId: number, examStudentId: number, order: number, filePath: string) : Promise<string>
+    notifyServer(examRecordDataId: number, examStudentId: number, order: number, filePath: string, transferFileType: string) : Promise<string>
 
-    getNotifyResult(examRecordDataId: number, examStudentId: number, order: number, filePath: string) : Promise<boolean>
+    getNotifyResult(acknowledgeId: string) : Promise<boolean>
 
     getUploadedAudioAnswerList(examRecordDataId: number) : Promise<tools.AudioAnswer[]>
 
@@ -114,7 +116,11 @@ interface API {
 }
 
 export class Api implements API {
-    configOption: ApiConfigOption = { ver: 1, versionName: 'v1.0.0', baseUrl: 'https://ecs.qmth.com.cn:8878' }
+    //ecs.qmth.com.cn:8878
+    //http://192.168.10.39:8003
+    //https://ecs.qmth.com.cn:8878
+    //ecs-test.qmth.com.cn
+  configOption: ApiConfigOption = { ver: 1, versionName: 'v1.0.0', baseUrl: 'http://ecs-test.qmth.com.cn' }
     private key: string = ''
     private token: string = ''
     private tokenValidCallback: TokenValidCallback = () => {}
@@ -138,7 +144,7 @@ export class Api implements API {
         this.tokenValidCallback = callback
     }
 
-    checkQrcode(secret: string): Promise<tools.CheckQrResult> {
+    checkQrcode(secret: string): Promise<any> {
         const that = this
         return new Promise<tools.CheckQrResult>((resolve, reject) => {
             return this.postRequest({
@@ -147,6 +153,7 @@ export class Api implements API {
                     qrCode: secret
                 }
             }, false).then(res => {
+                console.log('then')
                 if (res.key && res.token) {
                     that.configAuthorize(res.key, res.token)
 
@@ -162,65 +169,69 @@ export class Api implements API {
                     }
                     resolve((result as tools.CheckQrResult))
                 } else {
-                    reject(new Error('二维码已失效'))
+                    reject('二维码已失效')
                 }
             }, error => {
                 reject(error)
+            }).catch( exceptions => {
+                reject(exceptions)
             })
         })
     }
 
     getPaperStruct(examRecordDataId: number) : Promise<any> {
-        return this.getRequest({
-            url: '/api/ecs_oe_student/examRecordPaperStruct/getExamRecordPaperStruct',
-            parameter: {
-                examRecordDataId: examRecordDataId
-            }
+        return new Promise((resovle, reject) => {
+            return this.getRequest({
+                url: '/api/ecs_oe_student/examRecordPaperStruct/getExamRecordPaperStruct',
+                parameter: {
+                    examRecordDataId: examRecordDataId
+                }
+            }).then( res => {
+                resovle(res)
+            }, error => {
+                reject(error)
+            }).catch( exceptions => {
+                reject(exceptions)
+            })
         })
     }
 
-    getSign(examRecordDataId: number, examStudentId: number, order: number, fileMd5: string) : Promise<tools.SignResult> {
+    getSign(examRecordDataId: number, examStudentId: number, order: number, fileMd5: string, suffix: string) : Promise<tools.SignResult> {
         return new Promise<tools.SignResult>((resolve, reject) => {
             return this.postRequest({
-                url: '/api/ecs_oe_student/examControl/upyunSignature',
+                url: '/api/ecs_oe_student/examControl/yunSignature',
                 parameter: {
                     examRecordDataId: examRecordDataId,
                     examStudentId: examStudentId,
                     order: order,
                     fileMd5: fileMd5,
-                    fileSuffix: 'mp3'
+                    fileSuffix: suffix
                 }
             }).then(res => {
                 var result = {
-                    filePath: res.filePath,
-                    policy: res.policy,
-                    signature: res.signature,
+                    accessUrl: res.accessUrl,
+                    formUrl: res.formUrl,
+                    fsType: res.fsType,
                     uploadUrl: res.uploadUrl,
+                    formParams: res.formParams,
+                    signIdentifier: res.signIdentifier,
                 }
-                resolve((result as tools.SignResult))
+                resolve(result)
             }, error => {
                 reject(error)
             })
         })
     }
 
-    uploadFile(filePath: string, policy: string, signature: string, uploadUrl: string) : Promise<tools.UpyunResult> {
+    uploadFile(filePath: string, formParams: object, uploadUrl: string) : Promise<any> {
         return new Promise(function (resolve, reject) {
             wx.uploadFile({
                 url: uploadUrl,
                 filePath: filePath,
                 name: 'file',
-                formData: {
-                  authorization: signature,
-                  policy: policy
-                },
+                formData: formParams,
                 success: res => {
-                    const result = JSON.parse(res.data)
-                    if (res.statusCode !== 200) {
-                        reject(result.message)
-                    } else {
-                        resolve(result)
-                    }
+                    resolve(res)
                 },
                 fail: error => {
                     if (error.errMsg == 'uploadFile:fail url not in domain list') {
@@ -234,33 +245,43 @@ export class Api implements API {
     }
 
     notifyServer(examRecordDataId: number, examStudentId: number, 
-        order: number, filePath: string) : Promise<string> {
+        order: number, filePath: string, transferFileType: string) : Promise<string> {
         return new Promise((resovle, reject) => {
             return this.postRequest({
-                url: '/api/ecs_oe_student/examControl/audioUploadSave',
+                url: '/api/ecs_oe_student/examControl/saveUploadedFile',
                 parameter: {
                     examRecordDataId: examRecordDataId,
                     examStudentId: examStudentId,
                     order: order,
-                    filePath: filePath
+                    filePath: filePath,
+                    transferFileType: transferFileType
                 }
-            }).then( _ => {
-                resovle(filePath)
+            }).then( res => {
+                resovle(res)
+            }, error => {
+                console.log('===================')
+                reject(error)
             }).catch( exceptions => {
+                console.log('===================')
                 reject(exceptions)
             })
         })
     }
 
-    getNotifyResult(examRecordDataId: number, examStudentId: number, order: number, filePath: string) : Promise<any> {
-        return this.postRequest({
-            url: '/api/ecs_oe_student/examControl/getAudioUploadSaveStatus',
-            parameter: {
-                examRecordDataId: examRecordDataId,
-                examStudentId: examStudentId,
-                order: order,
-                filePath: filePath
-            }
+    getNotifyResult(acknowledgeId: string) : Promise<any> {
+        return new Promise((resovle, reject) => {
+            return this.postRequest({
+                url: '/api/ecs_oe_student/examControl/getUploadedFileAcknowledgeStatus',
+                parameter: {
+                    acknowledgeId: acknowledgeId
+                }
+            }).then( res => {
+                resovle(res)
+            }, error => {
+                reject(error)
+            }).catch( exceptions => {
+                reject(exceptions)
+            })
         })
     }
 
@@ -333,6 +354,7 @@ export class Api implements API {
         } : {
             'Content-Type': 'application/x-www-form-urlencoded',
         }
+        console.log(option)
         return new Promise(function (resolve, reject) {
             wx.request({
                 url: that.configOption.baseUrl + option.url,
@@ -341,12 +363,15 @@ export class Api implements API {
                 data: option.parameter,
                 success: res => {
                     console.log("request success", res);
+                    
                     if (that.isTokenInValidCode(res.statusCode)) {
                         reject('token失效')
                         // token失效
                         if (that.tokenValidCallback) {
                             that.tokenValidCallback()
                         }
+                    } else if (res.statusCode == 503) { //接口限流,自动重试
+                        that._postRequest(option, auth, 0, resolve, reject)
                     } else if (res.statusCode !== 200) {
                         reject((res.data as tools.Result<any>).desc)
                     } else {
@@ -367,6 +392,60 @@ export class Api implements API {
         })
     }
 
+    _postRequest(option: RequestOption, auth: boolean = true, retry: number = 0, resolve: (value?: any | PromiseLike<any>) => void, reject: (reason?: any) => void) {
+        const that = this
+        const header = auth ? {
+            'Content-Type': 'application/x-www-form-urlencoded',
+            key: that.key,
+            token: that.token
+        } : {
+            'Content-Type': 'application/x-www-form-urlencoded',
+        }
+        console.log(option)
+        if (retry > 0) {
+            console.log('正在重试'+retry)
+        }
+        wx.request({
+            url: that.configOption.baseUrl + option.url,
+            method: 'POST',
+            header: header,
+            data: option.parameter,
+            success: res => {
+                console.log("request success", res);
+                if (that.isTokenInValidCode(res.statusCode)) {
+                    reject('token失效')
+                    // token失效
+                    if (that.tokenValidCallback) {
+                        that.tokenValidCallback()
+                    }
+                } else if (res.statusCode == 503) { //接口限流,自动重试
+                    if (retry < 10) {
+                        console.log('200ms重试')
+                        setTimeout(() => {
+                            that._postRequest(option, auth, retry+1, resolve, reject)
+                        }, 200);
+                    } else {
+                        reject('服务器繁忙,请稍后重试!')
+                    }
+                } else if (res.statusCode !== 200) {
+                    reject((res.data as tools.Result<any>).desc)
+                } else {
+                    resolve(res.data)
+                }
+            },
+            fail: function (res) {
+                console.log("request fail", res);
+                if (res.errMsg == 'request:fail url not in domain list') {
+                    reject('请点击右上角"•••"按钮开启调试模式')
+                } else if (res.errMsg == 'request:fail ') {
+                    reject("网络请求失败");
+                } else {
+                    reject(res.errMsg);
+                }
+            }
+        })
+    }
+
     private isTokenInValidCode(code: number) : boolean {
         return code === 401 || code === 403
     }

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff