소스 검색

feat: 开始培训逻辑修改

lvkun996 10 달 전
부모
커밋
1c49a9b834
10개의 변경된 파일335개의 추가작업 그리고 82개의 파일을 삭제
  1. 6 2
      src/App.vue
  2. 3 3
      src/api/common.js
  3. 1 1
      src/store/useCommonStore.js
  4. 6 3
      src/store/useTrainStore.js
  5. 192 0
      src/utils/qiniu.js
  6. 73 26
      src/utils/wx.js
  7. 14 7
      src/views/create-train.vue
  8. 21 21
      src/views/sign-code.vue
  9. 1 1
      src/views/trained.vue
  10. 18 18
      src/views/training.vue

+ 6 - 2
src/App.vue

@@ -6,9 +6,9 @@
 </van-tabbar>
 </template>
 <script setup >
-import { ref } from 'vue'
+import { ref, onMounted } from 'vue'
 import { useRouter, useRoute } from 'vue-router'
-
+import { registerWxConfig } from '@/utils/wx.js'
 const router = useRouter()
 
 const route = useRoute()
@@ -27,6 +27,10 @@ const pushHomePage = () => {
   })
 }
 
+onMounted(() => {
+  registerWxConfig()
+})
+
 </script>
 <style lang="less">
 #app {

+ 3 - 3
src/api/common.js

@@ -24,10 +24,10 @@ export const getContentList = (params) => {
   })
 }
 
-export const uploadFile = (params) => {
+export const uploadFile = (data) => {
   return request({
     url: '/parent//file/uploadFile',
-    method: 'GET',
-    params
+    method: 'POST',
+    data
   })
 }

+ 1 - 1
src/store/useCommonStore.js

@@ -21,7 +21,7 @@ export const useCommonStore = defineStore('useCommonStore', () => {
   }
   const _getContentList = async () => {
     const { data } = await getContentList()
-    contentList.value.push(...data)
+    contentList.value = data
   }
 
   return {

+ 6 - 3
src/store/useTrainStore.js

@@ -28,7 +28,7 @@ export const useTrainStore = defineStore('useTrainStore', () => {
     if (status === 200) {
       // eslint-disable-next-line no-undef
       showToast('培训开始')
-      router.push({
+      router.replace({
         path: '/training'
       })
     }
@@ -42,7 +42,7 @@ export const useTrainStore = defineStore('useTrainStore', () => {
     if (status === 200) {
       // eslint-disable-next-line no-undef
       showToast('培训结束')
-      router.push({
+      router.replace({
         path: '/trained'
       })
     }
@@ -52,8 +52,11 @@ export const useTrainStore = defineStore('useTrainStore', () => {
     const { status, data } = await addTrain(_data)
     if (status === 200) {
       // eslint-disable-next-line no-undef
-      showToast('新增成功')
+      closeToast()
       current.value = { id: data }
+    } else {
+      // eslint-disable-next-line no-undef
+      closeToast()
     }
   }
   const del = async (id) => {

+ 192 - 0
src/utils/qiniu.js

@@ -0,0 +1,192 @@
+import axios from 'axios'
+
+/**
+ * 获取上传地址
+ * bucket 所在区域。ECN, SCN, NCN, NA, ASG,分别对应七牛云的:华东,华南,华北,北美,新加坡 5 个区域
+ */
+function uploadURLFromRegionCode (regionCode) {
+  switch (regionCode) {
+    case 'ECN':
+      return 'https://up.qiniup.com'
+    case 'NCN':
+      return 'https://up-z1.qiniup.com'
+    case 'SCN':
+      return 'https://up-z2.qiniup.com'
+    case 'NA':
+      return 'https://up-na0.qiniup.com'
+    case 'ASG':
+      return 'https://up-as0.qiniup.com'
+    default:
+      console.error('please make the region is with one of [ECN, SCN, NCN, NA, ASG]')
+      return ''
+  }
+}
+
+/**
+ * 获取上传token
+ * @param bucket{string} 七牛云某个区域的空间名称
+ */
+function getToken (bucket = 'img') {
+  return new Promise(async resolve => {
+    const res = await axios({
+      url: `/zd-api/teacher-lib/utils/file/token/${bucket}`,
+      method: 'POST',
+      headers: {
+        token: window.localStorage.getItem('token')
+      }
+    })
+    if (res.data.status === 200) {
+      resolve(res.data)
+    }
+  })
+}
+
+function fileToBuffer (file) {
+  return new Promise(async resolve => {
+    const blob = file.slice(0)
+    const arraybuffer = await blob.arrayBuffer()
+    const buf = new Buffer(arraybuffer.byteLength)
+    const view = new Uint8Array(arraybuffer)
+    for (let i = 0; i < buf.length; ++i) {
+      buf[i] = view[i]
+    }
+    resolve(buf)
+  })
+}
+
+function getEtag (buffer, callback) {
+  var mode = 'buffer'
+
+  if (typeof buffer === 'string') {
+    buffer = require('fs').createReadStream(buffer)
+    mode = 'stream'
+  } else if (buffer instanceof require('stream')) {
+    mode = 'stream'
+  }
+  var sha1 = function (content) {
+    var crypto = require('crypto')
+    var sha1 = crypto.createHash('sha1')
+    sha1.update(content)
+    return sha1.digest()
+  }
+
+  var blockSize = 4 * 1024 * 1024
+  var sha1String = []
+  var prefix = 0x16
+  var blockCount = 0
+
+  switch (mode) {
+    case 'buffer':
+      var bufferSize = buffer.length
+      blockCount = Math.ceil(bufferSize / blockSize)
+      // console.log(blockCount, "blockCount");
+      for (var i = 0; i < blockCount; i++) {
+        sha1String.push(sha1(buffer.slice(i * blockSize, (i + 1) * blockSize)))
+      }
+      process.nextTick(function () {
+        callback(calcEtag())
+      })
+      break
+    case 'stream':
+      var stream = buffer
+      stream.on('readable', function () {
+        var chunk
+        while ((chunk = stream.read(blockSize))) {
+          sha1String.push(sha1(chunk))
+          blockCount++
+        }
+      })
+      stream.on('end', function () {
+        callback(calcEtag())
+      })
+      break
+  }
+
+  function calcEtag () {
+    if (!sha1String.length) {
+      return 'Fto5o-5ea0sNMlW_75VgGJCv2AcJ'
+    }
+    var sha1Buffer = Buffer.concat(sha1String, blockCount * 20)
+
+    if (blockCount > 1) {
+      prefix = 0x96
+      sha1Buffer = sha1(sha1Buffer)
+    }
+
+    sha1Buffer = Buffer.concat([new Buffer([prefix]), sha1Buffer], sha1Buffer.length + 1)
+
+    return sha1Buffer
+      .toString('base64')
+      .replace(/\//g, '_')
+      .replace(/\+/g, '-')
+  }
+}
+
+/**
+ * 七牛云直传
+ * @param file{File} 要上传的文件对象
+ * @param directory{string} 所在空间的目录名称 最后要以 / 结尾
+ * @param bucket{string} 所在空间的名称
+ * @param regionCode{string} 所在空间七牛云区域代码
+ * @param domainName{string} 所在空间的七牛云域名
+ */
+export function qiniuUploadRequest (
+  file,
+  directory = '',
+  bucket = 'img',
+  regionCode = 'SCN',
+  domainName = 'https://img.luojigou.vip/'
+) {
+  return new Promise(async resolve => {
+    const { data: token } = await getToken(bucket)
+
+    const formData = new FormData()
+    const buffer = await fileToBuffer(file)
+
+    // 获取文件唯一标识作为key,可以防止上传多次重复文件
+    getEtag(buffer, async val => {
+      // 默认
+      let type = file.type.split('/')[1]
+      // console.log(type, "type");
+
+      // 文件后缀需要特殊处理
+      if (file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') {
+        type = 'xlsx'
+      } else if (file.type.includes('audio')) {
+        type = 'mp3'
+      }
+      const key = directory + val + '.' + type
+      console.log(key, 'key')
+
+      formData.append('file', file)
+      formData.append('token', token)
+      formData.append('key', key)
+
+      const options = {
+        url: uploadURLFromRegionCode(regionCode),
+        data: formData,
+        method: 'POST'
+      }
+
+      const { status } = await axios(options)
+      if (status === 200) {
+        const url = domainName + key
+        console.log(url, 'url')
+        resolve(url)
+      }
+    })
+  })
+}
+
+export const BUCKET_LIST = [
+  {
+    bucket: 'img',
+    regionCode: 'SCN',
+    domainName: 'https://img.luojigou.vip/'
+  },
+  {
+    bucket: 'luojigou-game',
+    regionCode: 'SCN',
+    domainName: 'https://res-game.luojigou.vip/'
+  }
+]

+ 73 - 26
src/utils/wx.js

@@ -8,7 +8,7 @@ import axios from 'axios'
 
 export const WxLogin = (url = '/') => {
   const appid = 'wxe87236d542cd0f94'
-  const URL = 'https://luojigou.vip/training/#' + url
+  const URL = 'https://luojigou.vip/training-test//#' + url
   const redirect_uri = encodeURIComponent(URL)
   const state = 1
   window.location.replace(`https://open.weixin.qq.com/connect/oauth2/authorize?&appid=${appid}&redirect_uri=${redirect_uri}&response_type=code&scope=snsapi_userinfo&state=${state}#wechat_redirect`)
@@ -31,42 +31,89 @@ export function getQueryString (name) {
 //   axios.get(`https://api.weixin.qq.com/sns/oauth2/access_token?appid=${wxe87236d542cd0f94}&secret=SECRET&code=CODE&grant_type=authorization_code`)
 // }
 
-/**
- *
- * @description 获取用户地理位置
- *
- */
-
-export function getLocation () {
+// 注册使用wx jsapi的接口
+export function registerWxConfig () {
   return new Promise(async (resolve, reject) => {
     const r = await axios.get(`/zd-api//user-training/h5/visitor/wechat/location?url=${encodeURIComponent(window.location.href.split('#')[0])}`)
-
     const data = r.data.data
-    // console.log(data, 'data');
     wx.config({
       debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
       appId: data.appId, // 必填,公众号的唯一标识
       timestamp: data.timestamp, // 必填,生成签名的时间戳
       nonceStr: data.nonceStr, // 必填,生成签名的随机串
       signature: data.signature, // 必填,签名
-      jsApiList: ['getLocation'] // 必填,需要使用的JS接口列表
+      jsApiList: ['getLocation', 'chooseImage', 'uploadImage'] // 必填,需要使用的JS接口列表
     })
-
     wx.ready(() => {
-      wx.getLocation({
-        type: 'wgs84', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
-        success: function (res) {
-          console.log(res, 'wgs84')
-          // var latitude = res.latitude // 纬度,浮点数,范围为90 ~ -90
-          // var longitude = res.longitude // 经度,浮点数,范围为180 ~ -180。
-          // var speed = res.speed // 速度,以米/每秒计
-          // var accuracy = res.accuracy // 位置精度
-          resolve(res)
-        },
-        fail: (err) => {
-          reject(err)
-        }
-      })
+      resolve()
+    })
+  })
+}
+
+// 本地图片置换 server localId -> serverId
+export const getServerIdByLocalId = async (localId) => {
+  return new Promise((resolve) => {
+    wx.uploadImage({
+      localId: localId, // 需要上传的图片的本地ID,由chooseImage接口获得
+      isShowProgressTips: 1, // 默认为1,显示进度提示
+      success: (res) => {
+        const { serverId } = res // 返回图片的服务器端ID
+        resolve(serverId)
+      }
+    })
+  })
+}
+
+/**
+ *
+ * @description 获取用户地理位置
+ *
+ */
+
+// export function getLocation () {
+//   return new Promise(async (resolve, reject) => {
+//     const r = await axios.get(`/zd-api//user-training/h5/visitor/wechat/location?url=${encodeURIComponent(window.location.href.split('#')[0])}`)
+
+//     const data = r.data.data
+//     // console.log(data, 'data');
+//     wx.config({
+//       debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
+//       appId: data.appId, // 必填,公众号的唯一标识
+//       timestamp: data.timestamp, // 必填,生成签名的时间戳
+//       nonceStr: data.nonceStr, // 必填,生成签名的随机串
+//       signature: data.signature, // 必填,签名
+//       jsApiList: ['getLocation'] // 必填,需要使用的JS接口列表
+//     })
+
+//     wx.ready(() => {
+//       wx.getLocation({
+//         type: 'wgs84', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
+//         success: function (res) {
+//           console.log(res, 'wgs84')
+//           // var latitude = res.latitude // 纬度,浮点数,范围为90 ~ -90
+//           // var longitude = res.longitude // 经度,浮点数,范围为180 ~ -180。
+//           // var speed = res.speed // 速度,以米/每秒计
+//           // var accuracy = res.accuracy // 位置精度
+//           resolve(res)
+//         },
+//         fail: (err) => {
+//           reject(err)
+//         }
+//       })
+//     })
+//   })
+// }
+
+export function getLocation () {
+  return new Promise(async (resolve, reject) => {
+    wx.getLocation({
+      type: 'wgs84', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
+      success: function (res) {
+        resolve(res)
+      },
+      fail: (err) => {
+        reject(err)
+      }
     })
   })
 }

+ 14 - 7
src/views/create-train.vue

@@ -45,8 +45,8 @@
         <div class="check-in-item-title" >培训内容</div>
         <van-checkbox-group class="check-in-item-content"  v-model="formSate.contents" direction="horizontal" shape="square">
           <van-checkbox :name="item.name" style="margin-bottom: 12px"  v-for="item in contentList" :key="item.id" >{{item.name}}</van-checkbox>
-          <van-checkbox :name="formSate.cusContent" style="margin-bottom: 12px"  >
-            <van-field style="border: 1px solid #cdc8c8;height: 40px;" v-model="formSate.cusContent" label="" placeholder="自定义培训内容" />
+          <van-checkbox label-disabled :name="formSate.cusContent" style="margin-bottom: 12px"  >
+            <van-field  style="border: 1px solid #cdc8c8;height: 40px;" v-model="formSate.cusContent" label="" placeholder="自定义培训内容" />
           </van-checkbox>
         </van-checkbox-group>
         <!-- <div :class="['check-in-item-input',formRequireState.contentId ? 'check-in-item-input-warn' : '' ]" @click="openModal('content')">
@@ -104,7 +104,7 @@
         <div class="info-modal-item" >  <div class="title"> 园所名称 </div> <div class="value"> {{formSate.schoolName}} </div></div>
         <div class="info-modal-item" >  <div class="title"> 代理商名称 </div> <div class="value"> {{formSate.agentName}} </div></div>
       </div>
-      <div class="submit-btn" @click="run" >确认提交</div>
+      <div class="submit-btn" @click="submit" >确认提交</div>
     </div>
   </van-popup>
 
@@ -201,11 +201,18 @@ watch(
 )
 
 const submit = () => {
-  trainStore.add(formSate.value).then(() => {
-    router.push({
-      path: '/sign-code'
-    })
+  // eslint-disable-next-line no-undef
+  showLoadingToast({
+    message: '创建中...',
+    forbidClick: true
   })
+  setTimeout(() => {
+    trainStore.add(formSate.value).then(() => {
+      router.replace({
+        path: '/sign-code'
+      })
+    })
+  }, 1000)
 }
 
 const submitInfoModal = () => {

+ 21 - 21
src/views/sign-code.vue

@@ -20,8 +20,8 @@
         <div class="label" >上传照片</div>
         <div class="content" >
           <div class="photos" >
-            <div class="photo" v-for="(item, index) in photos" :key="index" >
-              <img :src="item" alt="">
+            <div class="photo" v-for="(item, index) in photos" :key="item.localId" >
+              <img :src="item.localData" alt="">
               <van-icon name="close" class="close-icon" @click="delPhoto(index)" />
             </div>
           </div>
@@ -50,7 +50,8 @@ import QRCode from 'qrcodejs2'
 import { useTrainStore } from '@/store'
 import { storeToRefs } from 'pinia'
 import wx from 'weixin-js-sdk'
-import { uploadFile } from '@/api/common'
+import { qiniuUploadRequest } from '@/utils/qiniu.js'
+import { getServerIdByLocalId } from '@/utils/wx.js'
 
 const trainStore = useTrainStore()
 
@@ -64,7 +65,7 @@ const urlLink = ref()
 
 const maxUploadCount = 3
 
-const photos = ref([require('@/assets/logo.png'), require('@/assets/logo.png'), require('@/assets/logo.png')])
+const photos = ref([])
 
 const checkInCount = ref('')
 const createQrecode = () => {
@@ -84,7 +85,7 @@ const createQrecode = () => {
   urlLink.value = base64Text
 }
 
-const startTrain = () => {
+const startTrain = async () => {
   if (photos.value.length === 0) {
     // eslint-disable-next-line no-undef
     showToast('请上传照片')
@@ -95,7 +96,11 @@ const startTrain = () => {
     showToast('请输入参与人数')
     return
   }
-  start(current.value.id, photos.value, checkInCount.value)
+  const serverIds = []
+  for (let index = 0; index < photos.value.length; index++) {
+    serverIds.push(await getServerIdByLocalId(photos.value[index].localId))
+  }
+  start(current.value.id, serverIds, checkInCount.value)
 }
 
 const delPhoto = (index) => {
@@ -108,25 +113,20 @@ const chooseImage = () => {
     sizeType: ['original', 'compressed'],
     sourceType: ['album'],
     success: function (res) {
-      handleUploadFile(res.tempFilePaths)
+      res.localIds.forEach(localId => {
+        wx.getLocalImgData({
+          localId, // 图片的localID
+          success: function (res) {
+            var localData = res.localData // localData是图片的base64数据,可以用img标签显示
+            photos.value.push({ localId, localData })
+          }
+        })
+      })
     }
   })
 }
-
-const handleUploadFile = async (data) => {
-  const filePath = data.path || data[0]
-  // eslint-disable-next-line no-undef
-  showLoadingToast({
-    message: '上传中...',
-    forbidClick: true
-  })
-  await uploadFile({ filePath, name: 'file' })
-  // eslint-disable-next-line no-undef
-  closeToast(true)
-}
-
 onMounted(() => {
-  // byId(current.value.id)
+  byId(current.value.id)
 })
 </script>
 <style lang='less' scoped >

+ 1 - 1
src/views/trained.vue

@@ -4,7 +4,7 @@
       class="photo-cantainer"
     >
       <div class="photos" >
-        <div class="photo" v-for="(item, index) in [1,2,3,4,5,6]" :key="index" >
+        <div class="photo" v-for="(item, index) in [...current.startPhotos, ...current.endPhotos]" :key="index" >
            <img :src="item" alt="">
          </div>
       </div>

+ 18 - 18
src/views/training.vue

@@ -6,8 +6,8 @@
         <div class="label" >上传照片(结束培训)</div>
         <div class="content" >
           <div class="photos" >
-            <div class="photo" v-for="(item, index) in photos" :key="index" >
-              <img :src="item" alt="">
+            <div class="photo" v-for="(item, index) in photos" :key="item.localId" >
+              <img :src="item.localData" alt="">
               <van-icon name="close" class="close-icon" @click="delPhoto(index)" />
             </div>
           </div>
@@ -64,7 +64,7 @@ import QRCode from 'qrcodejs2'
 import { useTrainStore } from '@/store'
 import { storeToRefs } from 'pinia'
 import wx from 'weixin-js-sdk'
-import { uploadFile } from '@/api/common'
+import { getServerIdByLocalId } from '@/utils/wx.js'
 
 const trainStore = useTrainStore()
 
@@ -123,29 +123,29 @@ const chooseImage = () => {
     sizeType: ['original', 'compressed'],
     sourceType: ['album'],
     success: function (res) {
-      handleUploadFile(res.tempFilePaths)
+      res.localIds.forEach(localId => {
+        wx.getLocalImgData({
+          localId, // 图片的localID
+          success: function (res) {
+            var localData = res.localData // localData是图片的base64数据,可以用img标签显示
+            photos.value.push({ localId, localData })
+          }
+        })
+      })
     }
   })
 }
 
-const handleUploadFile = async (data) => {
-  const filePath = data.path || data[0]
-  // eslint-disable-next-line no-undef
-  showLoadingToast({
-    message: '上传中...',
-    forbidClick: true
-  })
-  await uploadFile({ filePath, name: 'file' })
-  // eslint-disable-next-line no-undef
-  closeToast(true)
-}
-
 const reload = () => {
   byId(current.value.id)
 }
-const confirmEnd = () => {
+const confirmEnd = async () => {
   show.value = false
-  finish(current.value.id)
+  const serverIds = []
+  for (let index = 0; index < photos.value.length; index++) {
+    serverIds.push(await getServerIdByLocalId(photos.value[index].localId))
+  }
+  finish(current.value.id, serverIds, checkInCount)
 }
 
 const cancelEnd = () => {