ソースを参照

1024多文件上传

tangjunhao 3 日 前
コミット
ed31652899

+ 54 - 25
src/utils/request.ts

@@ -177,37 +177,66 @@ const requestblobfile = (
 
 
 // 文件上传
-const uploadFile = (params, channelNo = 'service', callback1) => {
-  let config = {
-    timeout: 60000,
-    headers: { "Content-Type": "multipart/form-data" },
-    onUploadProgress: progressEvent => {
-      //属性lengthComputable主要表明总共需要完成的工作量和已经完成的工作是否可以被测量,如果lengthComputable为false,就获取不到progress.total和progress.loaded
-      if (progressEvent.lengthComputable) {
-        let loaded = progressEvent.loaded
-        let total = progressEvent.total
-        // let progress = Math.floor((loaded / total) * 100) > 1 ? Math.floor((loaded / total) * 100) : 1;
-        // console.log('上传进度 ' + progress + '%');
-        // callback1(progress)
+// const uploadFile = (params, channelNo = 'service', callback1) => {
+//   let config = {
+//     timeout: 60000,
+//     headers: { "Content-Type": "multipart/form-data" },
+//     onUploadProgress: progressEvent => {
+//       //属性lengthComputable主要表明总共需要完成的工作量和已经完成的工作是否可以被测量,如果lengthComputable为false,就获取不到progress.total和progress.loaded
+//       if (progressEvent.lengthComputable) {
+//         let loaded = progressEvent.loaded
+//         let total = progressEvent.total
+//         let progress = Math.floor((loaded / total) * 100) > 1 ? Math.floor((loaded / total) * 100) : 1;
+//         // console.log('上传进度 ' + progress + '%');
+//         callback1(progress)
         
+//       }
+//     }
+//   }
+//   let url = getUrl(channelNo);//完善请求url
+//   params.append("clientToken", getToken());
+//   params.append("userId", getUserId());
+//   params.append("channelNo", channelNo);//配置上行报文公共包头
+//   console.log(params);
+//   return new Promise((resolve, reject) => {
+//     axios.post(url, params, config).then(res => {
+//       resolve(res)
+//     }).catch(err => {
+//       reject(err)
+//     })
+//   })
+// }
+/**
+ * 上传文件(支持 FormData)
+ * @param {FormData} formData 
+ * @param {string} channelNo 
+ * @param {Function} onProgress 上传进度回调,可选
+ */
+const uploadFile = (formData, channelNo = 'service', onProgress) => {
+  // 自动加固定字段
+  formData.append('clientToken', getToken() || 'e47b87eec69545559d1e81e56626da68')
+  formData.append('userId', getUserId() || '5f06c8bc77234f969d13e160b54c27e3')
+  formData.append('channelNo', channelNo)
+
+  return axios.post(getUrl(channelNo), formData, {
+    timeout: 60000,
+    headers: { 'Content-Type': 'multipart/form-data' },
+    onUploadProgress: e => {
+      if (typeof onProgress === 'function') {
+        const percent = e.total ? Math.floor((e.loaded / e.total) * 100) : 0
+        onProgress(percent)
       }
     }
-  }
-  let url = getUrl(channelNo);//完善请求url
-  params.append("clientToken", getToken());
-  params.append("userId", getUserId());
-  params.append("channelNo", channelNo);//配置上行报文公共包头
-  console.log(params);
-  return new Promise((resolve, reject) => {
-    axios.post(url, params, config).then(res => {
-      resolve(res)
-    }).catch(err => {
-      reject(err)
-    })
+  })
+  .then(res => res)
+  .catch(err => {
+    if (err !== 'cancel') {
+      console.error(err.returnMsg || '上传失败')
+    }
+    return Promise.reject(err)
   })
 }
 
-
 const downloadFile = (id, channelNo = 'service') => {
   const token = getToken();
   const userId = getUserId();

+ 180 - 0
src/views/components/mulfileuploads.vue

@@ -0,0 +1,180 @@
+<template>
+  <div class="multi-uploader">
+    <!-- 上传按钮 -->
+    <div
+      class="btntext"
+      :style="{
+        width: '24px',
+        cursor: props.disabled ? 'not-allowed' : 'pointer',
+        opacity: props.disabled ? 0.5 : 1
+      }"
+      @click="selectFiles"
+    >
+      <img :src="props.imgSrc" alt="upload icon" class="custom-icon" />
+    </div>
+
+    <!-- 隐藏文件选择框 -->
+    <input
+      ref="fileInput"
+      type="file"
+      multiple
+      :accept="props.accept"
+      style="display: none"
+      @change="handleFilesChange"
+    />
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import { ElMessage } from 'element-plus'
+import SparkMD5 from 'spark-md5'
+import { uploadFile } from '@/utils/request'
+
+const props = defineProps({
+  accept: String,
+  imgSrc: String,
+  disabled: { type: Boolean, default: false }
+})
+
+const emit = defineEmits([
+  'upload-success',
+  'update-fileName',
+  'update-percentage',
+  'upload-status'
+])
+
+const fileInput = ref(null)
+
+// 点击按钮触发文件选择
+function selectFiles() {
+  if (props.disabled) return
+  fileInput.value.click()
+}
+
+// 多文件选择后触发
+async function handleFilesChange(event) {
+  const files = Array.from(event.target.files)
+  if (files.length === 0) return
+
+  emit('upload-status', '上传中')
+
+  // 并发上传所有文件
+  await Promise.all(
+    files.map(async (file) => {
+      const fileName = file.name
+      const uuid = generateUUID()
+      emit('update-fileName', fileName)
+
+      try {
+        await uploadInChunks(file, uuid, fileName)
+        await mergeChunks(uuid)
+        emit('upload-success', { bfid: uuid, fname: fileName })
+        ElMessage.success(`${fileName} 上传成功`)
+      } catch (err) {
+        ElMessage.error(`${fileName} 上传失败: ${err.message}`)
+        emit('update-percentage', { fileName, percent: 0 })
+      }
+    })
+  )
+
+  emit('upload-status', '全部上传完成')
+  event.target.value = ''
+}
+
+// 分片上传
+async function uploadInChunks(file, uuid, fileName) {
+  const chunkSize = 2 * 1024 * 1024 // 2MB
+  const totalChunks = Math.ceil(file.size / chunkSize)
+
+  for (let i = 0; i < totalChunks; i++) {
+    const start = i * chunkSize
+    const end = Math.min(file.size, start + chunkSize)
+    const chunkFile = file.slice(start, end)
+
+    const formData = new FormData()
+    formData.append('bfid', uuid)
+    formData.append('chunk', i + 1)
+    formData.append('chunks', totalChunks)
+    formData.append('file', chunkFile)
+    formData.append('fileName', fileName)
+    formData.append('transCode', 'B00028')
+
+    await uploadFile(formData, 'service', (progress) => {
+      const percent = Math.min(
+        Math.floor(((i + progress / 100) / totalChunks) * 100),
+        100
+      )
+      console.log('上传进度:', percent)
+      emit('update-percentage', { fileName, percent })
+    })
+  }
+}
+
+// 合并分片
+async function mergeChunks(uuid) {
+  const formData = new FormData()
+  formData.append('bfid', uuid)
+  formData.append('transCode', 'B00029')
+  await uploadFile(formData, 'service')
+}
+
+// 生成 UUID
+function generateUUID() {
+  return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
+    const r = (Math.random() * 16) | 0
+    const v = c === 'x' ? r : (r & 0x3) | 0x8
+    return v.toString(16)
+  })
+}
+
+// 计算文件 MD5(可选,用于秒传验证)
+function calculateMD5(file) {
+  return new Promise((resolve, reject) => {
+    const chunkSize = 2 * 1024 * 1024
+    const chunks = Math.ceil(file.size / chunkSize)
+    let currentChunk = 0
+    const spark = new SparkMD5.ArrayBuffer()
+    const fileReader = new FileReader()
+
+    fileReader.onload = function (e) {
+      spark.append(e.target.result)
+      currentChunk++
+      if (currentChunk < chunks) {
+        loadNext()
+      } else {
+        resolve(spark.end())
+      }
+    }
+
+    fileReader.onerror = function () {
+      reject(new Error('文件读取失败'))
+    }
+
+    function loadNext() {
+      const start = currentChunk * chunkSize
+      const end = Math.min(file.size, start + chunkSize)
+      fileReader.readAsArrayBuffer(file.slice(start, end))
+    }
+
+    loadNext()
+  })
+}
+</script>
+
+<style scoped>
+.multi-uploader {
+  position: relative;
+}
+.btntext {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+}
+.custom-icon {
+  width: 100%;
+  height: auto;
+  display: block;
+}
+</style>

+ 57 - 35
src/views/titlecomponent/PropNoise.vue

@@ -53,23 +53,25 @@
             </el-col>
             <!-- 文件上传按钮部分 -->
             <el-col :span="1" style="display: flex; align-items: center; margin-left: -35px">
-              <fileUploads :projectId="136" solverType="exampleSolver" accept="" upId="airfoil_polars" name="点击选择文件"
-                :imgSrc="meshFileImgSrc" @upload-success="handleFileUploadSuccess1" @update-fileName="updateFileName1"
-                @update-percentage="updatePercentage1" @upload-status="getUploadStatus1" />
+              <mulfileuploads :imgSrc="meshFileImgSrc" accept="" @upload-success="handleSuccess"
+                @update-fileName="handleFileName" @update-percentage="handleProgress" @upload-status="handleStatus" />
             </el-col>
           </el-row>
           <!-- 进度条 -->
-          <el-row v-if="showProgress1" style="width: 100%; margin-top: 10px;">
-            <el-col :span="20">
-              <el-progress :percentage="percentage1"></el-progress>
-            </el-col>
-            <el-col :span="4">
-              <div style="line-height: 15px">{{uploadStatus1}}</div>
-            </el-col>
-          </el-row>
+          <div v-if="showProgress1" v-for="(progress, name) in progressMap" :key="name" style="width: 100%;">
+            <el-row style="width: 100%; margin-top: 10px;">
+              <el-col :span="18">
+                <el-progress :percentage="progress"></el-progress>
+              </el-col>
+              <el-col :span="6">
+                <div style="line-height: 15px" class="ellipsis">{{ name }}</div>
+              </el-col>
+            </el-row>
+          </div>
         </el-form-item>
       </el-form>
-      <el-table :data="gxcsTable" border style="width: 100%;max-height: 450px;" :header-cell-class-name="headerCellClassName1">
+      <el-table :data="gxcsTable" border style="width: 100%;max-height: 450px;"
+        :header-cell-class-name="headerCellClassName1">
         <el-table-column label="启用">
           <el-table-column type="index" width="70" label="编号">
           </el-table-column>
@@ -209,6 +211,7 @@ import propnoise3 from "@/assets/img/propnoise3.png";
 import propnoise4 from "@/assets/img/propnoise4.png";
 
 import fileUploads from "../components/fileuploads.vue";
+import mulfileuploads from "../components/mulfileuploads.vue";
 
 const meshFileImgSrc = new URL("@/assets/img/open.png", import.meta.url).href;
 
@@ -256,12 +259,10 @@ const selectTab1 = (index) => {
 }
 
 let percentage = ref(0);
-let percentage1 = ref(0);
 let uploadStatus = ref('');
-let uploadStatus1 = ref('');
+
 // 控制进度条显隐
 const showProgress = computed(() => percentage.value > 0 && percentage.value <= 100);
-const showProgress1 = computed(() => percentage1.value > 0 && percentage1.value <= 100);
 
 // 处理上传成功后的逻辑
 const handleFileUploadSuccess = (newValue) => {
@@ -288,29 +289,46 @@ const getUploadStatus = (newValue) => {
   uploadStatus.value = newValue
 }
 
-// 处理上传成功后的逻辑
-const handleFileUploadSuccess1 = (newValue) => {
-  //隐藏进度条
-  setTimeout(() => {
-    percentage1.value = 0;
-  }, 1000);
-  propnoise.value.airfoil_polars_fids = newValue.bfid;
-  console.log("文件上传成功,bfid:", newValue.bfid, "fname:", newValue.fname)
-}
+// 多文件上传
+const progressMap = reactive({});
 
-// 更新文件名
-const updateFileName1 = (newValue) => {
-  propnoise.value.airfoil_polars = newValue
-}
+const showProgress1 = ref(false);
 
-// 更新进度条
-const updatePercentage1 = (newValue) => {
-  percentage1.value = newValue
+function handleFileName(name) {
+  progressMap[name] = 0
+}
+function handleProgress({ fileName, percent }) {
+  console.log('文件:', fileName, '进度:', percent)
+  progressMap[fileName] = percent
+}
+function handleStatus(status) {
+  console.log('状态:', status)
+  showProgress1.value = status === '上传中';
+  // 全部上传完成
+  if (status === '全部上传完成') {
+    setTimeout(() => {
+      showProgress1.value = false
+      for (const key in progressMap) {
+        delete progressMap[key]
+      }
+    }, 1000)
+  }
 }
+function handleSuccess(data) {
+  console.log('上传成功:', data)
+  
+  // 如果原来有值,就用分号追加;否则直接赋值
+  if (propnoise.value.airfoil_polars_fids) {
+    propnoise.value.airfoil_polars_fids += ';' + data.bfid;
+  } else {
+    propnoise.value.airfoil_polars_fids = data.bfid;
+  }
 
-// 更新上传状态
-const getUploadStatus1 = (newValue) => {
-  uploadStatus1.value = newValue
+  if (propnoise.value.airfoil_polars) {
+    propnoise.value.airfoil_polars += ';' + data.fname;
+  } else {
+    propnoise.value.airfoil_polars = data.fname;
+  }
 }
 
 let writesolution = ref(1);
@@ -497,6 +515,10 @@ defineExpose({
   order: -1;
 }
 
-
+.ellipsis {
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
 
 </style>