gangyj 2 rokov pred
rodič
commit
7325e99ad7

+ 9 - 0
src/api/modules/oss.js

@@ -25,4 +25,13 @@ export default {
             data: form
         }, msg);
     },
+
+    //上传文件分片
+    async uploadChunk(form, msg = true) {
+        return httpApi({
+            url: '/api/blade-resource/largeFile/endpoint/upload-file',
+            method: 'post',
+            data: form
+        }, msg);
+    },
 }

+ 1 - 1
src/views/file/components/HcFileUpload.vue

@@ -20,7 +20,7 @@ const props = defineProps({
     },
     action: {
         type: String,
-        default: "upload-file2"
+        default: "upload-file"
     },
     accept: {
         type: String,

+ 495 - 0
src/views/file/components/HcFileUploadLarge.vue

@@ -0,0 +1,495 @@
+<template>
+    <el-upload ref="uploadRef" class="hc-file-upload-box"  :headers="getTokenHeader()" :data="uploadData" :disabled="uploadDisabled" multiple :show-file-list="false" :http-request="uploadFileHandle"
+               :on-success="uploadSuccess" :on-exceed="uploadExceed" :on-error="uploadError" :before-upload="beforeUpload" :on-progress="uploadprogress">
+        <slot></slot>
+    </el-upload>
+</template>
+
+<script setup>
+import {ref,watch,onMounted} from "vue";
+import {getTokenHeader} from '~src/api/request/header';
+import {isSize, deepClone, getObjValue} from "vue-utils-plus"
+import md5 from 'js-md5' //引入MD5加密
+import ossApi from "~api/oss";
+const props = defineProps({
+    datas: {
+        type: Object,
+        default: () => ({})
+    },
+    api: {
+        type: String,
+        default: "/api/blade-resource/oss/endpoint/"
+    },
+    action: {
+        type: String,
+        default: "upload-file2"
+    },
+    accept: {
+        type: String,
+        default: "image/png,image/jpg,image/jpeg,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel,application/pdf,.doc,.docx,application/msword"
+    },
+    size: {
+        type: Number,
+        default: 60
+    }
+})
+
+//变量
+const uploadRef = ref(null)
+const uploadData = ref(props.datas)
+const uploadDisabled = ref(false)
+
+//监听
+watch(() => [
+    props.datas,
+], ([datas]) => {
+    uploadData.value = datas
+})
+
+//渲染完成
+onMounted(()=> {
+    beforeFileNum.value = 0
+    finishFileNum.value = 0
+    errorFileNum.value = 0
+})
+
+//事件
+const emit = defineEmits(['change', 'progress'])
+
+//上传前
+const beforeFileNum = ref(0)
+const beforeUpload = async (file) => {
+    beforeFileNum.value ++;
+    return true;
+}
+
+//超出限制时
+const uploadExceed = () => {
+    window?.$message?.warning(`请上传 ${props.accept} 格式的文件,文件大小不超过${props.size}M`);
+}
+
+//上传中
+const uploadprogress = () => {
+    uploadDisabled.value = true
+    emit('progress', true)
+}
+
+//上传完成
+const finishFileNum = ref(0)
+const uploadSuccess = (response, uploadFile, uploadFiles) => {
+  //console.log(response)
+  //console.log(uploadFile)
+  //console.log(uploadFiles)
+    finishFileNum.value ++;
+    if (beforeFileNum.value === finishFileNum.value) {
+        const fileList = getUploadFile(deepClone(uploadFiles))
+        uploadClearFiles()
+        emit('change', {type: 'success', fileList})
+        emit('progress', false)
+    }
+}
+
+//上传失败
+const errorFileNum = ref(0)
+const uploadError = (error,uploadFile,uploadFiles) => {
+    errorFileNum.value ++;
+    window?.$message?.error('上传失败');
+    console.log(error)
+    const num = finishFileNum.value + errorFileNum.value;
+    if (beforeFileNum.value === num) {
+        const fileList = getUploadFile(deepClone(uploadFiles))
+        uploadClearFiles()
+        emit('change', {type: 'error', fileList})
+        emit('progress', false)
+    }
+}
+
+const uploadClearFiles = () => {
+    finishFileNum.value = 0
+    beforeFileNum.value = 0
+    errorFileNum.value = 0
+    uploadDisabled.value = false
+    uploadRef.value?.clearFiles()
+}
+
+//获取文件
+const getUploadFile = (fileList) => {
+    let fileArr = [];
+    for (let i = 0; i < fileList.length; i++) {
+        const item = getObjValue(fileList[i]?.response?.data)
+        fileArr.push(item)
+    }
+    return fileArr
+}
+
+const uploadFileHandle = (options) =>{
+    console.log(options)
+    uploadByPieces(options)
+}
+
+/**
+ * 文件分片上传
+ * @params file {File} 文件
+ * @params pieceSize {Number} 分片大小 默认3MB
+ * @params concurrent {Number} 并发数量 默认2
+ * @params process {Function} 进度回调函数
+ * @params success {Function} 成功回调函数
+ * @params error {Function} 失败回调函数
+ */
+ const uploadByPieces = ({
+  file,
+  pieceSize = 3,
+  concurrent = 3,
+  onSuccess:success,
+  onProgress:process,
+  onError:error
+}) => {
+  // 如果文件传入为空直接 return 返回
+  if (!file || file.length < 1) {
+    return error('文件不能为空')
+  }
+  let fileMD5 = '' // 总文件列表
+  const chunkSize = pieceSize * 1024 * 1024 // 1MB一片
+  const chunkCount = Math.ceil(file.size / chunkSize) // 总片数
+  const chunkList = [] // 分片列表
+  let uploaded = [] // 已经上传的
+  let fileType = '' // 文件类型
+  // 获取md5
+  /***
+   * 获取md5
+   **/
+  const readFileMD5 = () => {
+    // 读取视频文件的md5
+    fileType = file.name.substring(file.name.lastIndexOf('.') + 1, file.name.length)
+    console.log('获取文件的MD5值')
+    let fileRederInstance = new FileReader()
+    console.log('file', file)
+    fileRederInstance.readAsBinaryString(file)
+    fileRederInstance.addEventListener('load', e => {
+      let fileBolb = e.target.result
+      fileMD5 = md5(fileBolb)
+      var index = file.name.lastIndexOf('.')
+      var tp = file.name.substring(index + 1, file.name.length)
+      let form = new FormData()
+      form.append('filename', file.name)
+      //form.append('file', new File([], 'filename'))
+      form.append('identifier', fileMD5)
+      form.append('objectType', fileType)
+      form.append('chunkNumber', 1)
+      ossApi.uploadChunk(form).then(res => {
+        if (res.skipUpload) {
+          console.log('文件已被上传')
+          success && success(res)
+        } else {
+          // 判断是否是断点续传
+          if (res.uploaded && res.uploaded.length != 0) {
+            uploaded = [].concat(res.uploaded)
+          }
+          console.log('已上传的分片:' + uploaded)
+          // 判断是并发上传或顺序上传
+          if (concurrent == 1 || chunkCount == 1) {
+            console.log('顺序上传')
+            sequentialUplode(0)
+          } else {
+            console.log('并发上传')
+            concurrentUpload()
+          }
+        }
+      }).catch((e) => {
+        console.log('文件合并错误')
+        console.log(e)
+      })
+    })
+  }
+  /***
+   * 获取每一个分片的详情
+   **/
+  const getChunkInfo = (file, currentChunk, chunkSize) => {
+    let start = currentChunk * chunkSize
+    let end = Math.min(file.size, start + chunkSize)
+    let chunk = file.slice(start, end)
+    return {
+      start,
+      end,
+      chunk
+    }
+  }
+  /***
+   * 针对每个文件进行chunk处理
+   **/
+  const readChunkMD5 = () => {
+    // 针对单个文件进行chunk上传
+    for (var i = 0; i < chunkCount; i++) {
+      const {
+        chunk
+      } = getChunkInfo(file, i, chunkSize)
+
+      // 判断已经上传的分片中是否包含当前分片
+      if (uploaded.indexOf(i + '') == -1) {
+        uploadChunk({
+          chunk,
+          currentChunk: i,
+          chunkCount
+        })
+      }
+    }
+  }
+  /***
+   * 原始上传
+   **/
+  const uploadChunk = (chunkInfo) => {
+    var sd = parseInt((chunkInfo.currentChunk / chunkInfo.chunkCount) * 100)
+    console.log(sd, '进度')
+    process(sd)
+    console.log(chunkInfo, '分片大小')
+    let inde = chunkInfo.currentChunk + 1
+    if (uploaded.indexOf(inde + '') > -1) {
+      const {
+        chunk
+      } = getChunkInfo(file, chunkInfo.currentChunk + 1, chunkSize)
+      uploadChunk({
+        chunk,
+        currentChunk: inde,
+        chunkCount
+      })
+    } else {
+      var index = file.name.lastIndexOf('.')
+      var tp = file.name.substring(index + 1, file.name.length)
+      // 构建上传文件的formData
+      let fetchForm = new FormData()
+      fetchForm.append('identifier', fileMD5)
+      fetchForm.append('chunkNumber', chunkInfo.currentChunk + 1)
+      fetchForm.append('chunkSize', chunkSize)
+      fetchForm.append('currentChunkSize', chunkInfo.chunk.size)
+      const chunkfile = new File([chunkInfo.chunk], file.name)
+      fetchForm.append('file', chunkfile)
+      // fetchForm.append('file', chunkInfo.chunk)
+      fetchForm.append('filename', file.name)
+      fetchForm.append('relativePath', file.name)
+      fetchForm.append('totalChunks', chunkInfo.chunkCount)
+      fetchForm.append('totalSize', file.size)
+      fetchForm.append('objectType', tp)
+      // 执行分片上传
+      let config = {
+        headers: {
+          'Content-Type': 'application/json',
+          'Accept': '*/*'
+        }
+      }
+
+      ossApi.uploadChunk(fetchForm, config).then(res => {
+
+        if (res.code == 200) {
+          console.log('分片上传成功')
+          uploaded.push(chunkInfo.currentChunk + 1)
+          // 判断是否全部上传完
+          if (uploaded.length == chunkInfo.chunkCount) {
+            console.log('全部完成')
+            success(res)
+            process(100)
+          } else {
+            const {
+              chunk
+            } = getChunkInfo(file, chunkInfo.currentChunk + 1, chunkSize)
+            uploadChunk({
+              chunk,
+              currentChunk: chunkInfo.currentChunk + 1,
+              chunkCount
+            })
+          }
+
+        } else {
+          console.log(res.msg)
+        }
+
+      }).catch((e) => {
+        error && error(e)
+      })
+      // if (chunkInfo.currentChunk < chunkInfo.chunkCount) {
+      //   setTimeout(() => {
+      //
+      //   }, 1000)
+      // }
+    }
+  }
+  /***
+   * 顺序上传
+   **/
+  const sequentialUplode = (currentChunk) => {
+    const {
+      chunk
+    } = getChunkInfo(file, currentChunk, chunkSize)
+    let chunkInfo = {
+      chunk,
+      currentChunk,
+      chunkCount
+    }
+    var sd = parseInt((chunkInfo.currentChunk / chunkInfo.chunkCount) * 100)
+    process(sd)
+    console.log('当前上传分片:' + currentChunk)
+    let inde = chunkInfo.currentChunk + 1
+    if (uploaded.indexOf(inde + '') > -1) {
+      console.log('分片【' + currentChunk + '】已上传')
+      sequentialUplode(currentChunk + 1)
+    } else {
+      let uploadData = createUploadData(chunkInfo)
+      let config = {
+        headers: {
+          'Content-Type': 'application/json',
+          'Accept': '*/*'
+        }
+      }
+      // 执行分片上传
+      ossApi.uploadChunk(uploadData, config).then(res => {
+        if (res.code == 200) {
+          console.log('分片【' + currentChunk + '】上传成功')
+          uploaded.push(chunkInfo.currentChunk + 1)
+          // 判断是否全部上传完
+          if (uploaded.length == chunkInfo.chunkCount) {
+            console.log('全部完成')
+            success(res)
+            process(100)
+          } else {
+            sequentialUplode(currentChunk + 1)
+          }
+
+        } else {
+          console.log(res.msg)
+        }
+
+      }).catch((e) => {
+        error && error(e)
+      })
+    }
+  }
+  /***
+   * 并发上传
+   **/
+  const concurrentUpload = () => {
+    for (var i = 0; i < chunkCount; i++) {
+      chunkList.push(Number(i))
+    }
+    console.log('需要上传的分片列表:' + chunkList)
+    concurrentExecution(chunkList, concurrent, (curItem) => {
+      return new Promise((resolve, reject) => {
+        const {
+          chunk
+        } = getChunkInfo(file, curItem, chunkSize)
+        let chunkInfo = {
+          chunk,
+          currentChunk: curItem,
+          chunkCount
+        }
+        var sd = parseInt((chunkInfo.currentChunk / chunkInfo.chunkCount) * 100)
+        process(sd)
+        console.log('当前上传分片:' + curItem)
+        let inde = chunkInfo.currentChunk + 1
+        if (uploaded.indexOf(inde + '') == -1) {
+          // 构建上传文件的formData
+          let uploadData = createUploadData(chunkInfo)
+          // 请求头
+          let config = {
+            headers: {
+              'Content-Type': 'application/json',
+              'Accept': '*/*'
+            }
+          }
+          ossApi.uploadChunk(uploadData, config).then(res => {
+            if (res.code == 200) {
+              uploaded.push(chunkInfo.currentChunk + 1)
+              console.log('已经上传完成的分片:' + uploaded)
+              // 判断是否全部上传完
+              // if (uploaded.length == chunkInfo.chunkCount) {
+              //   success(res)
+              //   process(100)
+              // }
+              if(typeof res.data == 'object'){
+                success(res)
+                process(100)
+              }
+              resolve()
+            } else {
+              reject(res)
+              console.log(res.msg)
+            }
+
+          }).catch((e) => {
+            reject(res)
+            error && error(e)
+          })
+        } else {
+          console.log('分片【' + chunkInfo.currentChunk + '】已上传')
+          resolve()
+        }
+      })
+    }).then(res => {
+      console.log('finish', res)
+    }).catch((e)=>{
+      error && error(e)
+    })
+  }
+  /***
+   * 创建文件上传参数
+   **/
+  const createUploadData = (chunkInfo) => {
+    let fetchForm = new FormData()
+    fetchForm.append('identifier', fileMD5)
+    fetchForm.append('chunkNumber', chunkInfo.currentChunk + 1)
+    fetchForm.append('chunkSize', chunkSize)
+    fetchForm.append('currentChunkSize', chunkInfo.chunk.size)
+    const chunkfile = new File([chunkInfo.chunk], file.name)
+    fetchForm.append('file', chunkfile)
+    // fetchForm.append('file', chunkInfo.chunk)
+    fetchForm.append('filename', file.name)
+    fetchForm.append('relativePath', file.name)
+    fetchForm.append('totalChunks', chunkInfo.chunkCount)
+    fetchForm.append('totalSize', file.size)
+    fetchForm.append('objectType', fileType)
+    return fetchForm
+  }
+
+  readFileMD5() // 开始执行代码
+  
+}
+/**
+ * 并发执行
+ * @params list {Array} - 要迭代的数组
+ * @params limit {Number} - 并发数量控制数,最好小于3
+ * @params asyncHandle {Function} - 对`list`的每一个项的处理函数,参数为当前处理项,必须 return 一个Promise来确定是否继续进行迭代
+ * @return {Promise} - 返回一个 Promise 值来确认所有数据是否迭代完成
+ */
+const  concurrentExecution = (list, limit, asyncHandle)=>{
+  // 递归执行
+  let recursion = (arr) => {
+    // 执行方法 arr.shift() 取出并移除第一个数据
+    return asyncHandle(arr.shift()).then(() => {
+      // 数组还未迭代完,递归继续进行迭代
+      if (arr.length !== 0) {
+        return recursion(arr)
+      } else {
+        return 'finish'
+      }
+    })
+  }
+  // 创建新的并发数组
+  let listCopy = [].concat(list)
+  // 正在进行的所有并发异步操作
+  let asyncList = []
+  limit = limit > listCopy.length ? listCopy.length : limit
+  console.log(limit)
+  while (limit--) {
+    asyncList.push(recursion(listCopy))
+  }
+  // 所有并发异步操作都完成后,本次并发控制迭代完成
+  return Promise.all(asyncList)
+}
+
+</script>
+
+<style lang="scss">
+.hc-file-upload-box .el-upload-list .el-upload-list__item {
+    .el-upload-list__item-status-label, .el-icon--close-tip {
+        display: none;
+    }
+}
+</style>

+ 4 - 3
src/views/file/records.vue

@@ -192,7 +192,7 @@
             </div>
             <HcTable ui="hc-form-table" :column="tableUploadColumn" :datas="tableUploadData" :loading="uploadSaveLoading" :isIndex="false" :class="{'set-table': tableUploadType === 'add'}">
                 <template #no="{index}">
-                    {{index}}
+                    {{index+1}}
                     <template v-if="tableUploadType === 'add'">
                     <span class="text-link text-lg">
                         <HcIcon name="arrow-up" @click="upUpSortClick(index)"/>
@@ -259,7 +259,7 @@
 import {ref, watch, onMounted,nextTick } from "vue";
 import {useAppStore} from "~src/store";
 import HcTree from "~src/components/tree/hc-tree.vue"
-import HcFileUpload from "./components/HcFileUpload.vue"
+import HcFileUpload from "./components/HcFileUploadLarge.vue"
 import notableform from '~src/assets/view/notableform.svg';
 import {delMessage, rowsToId, rowsToIdNumArr} from "~uti/tools";
 import {getArrValue, deepClone, downloadBlob} from "vue-utils-plus"
@@ -955,7 +955,7 @@ const tableUploadData = ref([])
 //上传的文件结果
 const uploadsChange = ({fileList}) => {
     let newArr = []
-    //console.log(fileList)
+    console.log(fileList)
     for (let i = 0; i < fileList.length; i++) {
         const item = fileList[i]
         let name = item['originalName'] || ''
@@ -964,6 +964,7 @@ const uploadsChange = ({fileList}) => {
             projectId: projectId.value,
             contractId: contractId.value,
             nodeId: nodeIds.value,
+            name:fileName,
             approvalFileList:[{
                 projectId: projectId.value,
                 contractId: contractId.value,