|  | @@ -1,43 +1,33 @@
 | 
	
		
			
				|  |  |  <template>
 | 
	
		
			
				|  |  |    <div class="webuploader-container">
 | 
	
		
			
				|  |  | -    <el-upload
 | 
	
		
			
				|  |  | -      :action="uploadUrl"
 | 
	
		
			
				|  |  | -      :before-upload="handleBeforeUpload"
 | 
	
		
			
				|  |  | -      :on-success="handleSuccess"
 | 
	
		
			
				|  |  | -      :on-error="handleError"
 | 
	
		
			
				|  |  | -      :on-progress="handleProgress"
 | 
	
		
			
				|  |  | -      :show-file-list="false"
 | 
	
		
			
				|  |  | -      :headers="uploadHeaders"
 | 
	
		
			
				|  |  | -      :data="uploadData"
 | 
	
		
			
				|  |  | -      :accept="props.accept"
 | 
	
		
			
				|  |  | -      :auto-upload="true"
 | 
	
		
			
				|  |  | -      class="upload-box"
 | 
	
		
			
				|  |  | -      :disabled="props.disabled"
 | 
	
		
			
				|  |  | +    <!-- 上传按钮 -->
 | 
	
		
			
				|  |  | +    <div 
 | 
	
		
			
				|  |  | +      class="btntext" 
 | 
	
		
			
				|  |  | +      :style="{ width: '24px', cursor: props.disabled ? 'not-allowed' : 'pointer', opacity: props.disabled ? 0.5 : 1 }"
 | 
	
		
			
				|  |  | +      @click="selectFile"
 | 
	
		
			
				|  |  |      >
 | 
	
		
			
				|  |  | -      <!-- 自定义上传按钮 -->
 | 
	
		
			
				|  |  | -      <div 
 | 
	
		
			
				|  |  | -        class="btntext upname" 
 | 
	
		
			
				|  |  | -        :style="{ width: '24px', cursor: props.disabled ? 'not-allowed' : 'pointer', opacity: props.disabled ? 0.5 : 1 }"
 | 
	
		
			
				|  |  | -      >
 | 
	
		
			
				|  |  | -        <img :src="props.imgSrc" alt="upload icon" class="custom-icon" />
 | 
	
		
			
				|  |  | -      </div>
 | 
	
		
			
				|  |  | -    </el-upload>
 | 
	
		
			
				|  |  | +      <img :src="props.imgSrc" alt="upload icon" class="custom-icon" />
 | 
	
		
			
				|  |  | +    </div>
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    <!-- 隐藏文件选择框 -->
 | 
	
		
			
				|  |  | +    <input
 | 
	
		
			
				|  |  | +      ref="fileInput"
 | 
	
		
			
				|  |  | +      type="file"
 | 
	
		
			
				|  |  | +      :accept="props.accept"
 | 
	
		
			
				|  |  | +      style="display: none"
 | 
	
		
			
				|  |  | +      @change="handleFileChange"
 | 
	
		
			
				|  |  | +    />
 | 
	
		
			
				|  |  |    </div>
 | 
	
		
			
				|  |  |  </template>
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  <script setup>
 | 
	
		
			
				|  |  | +import { ref } from 'vue'
 | 
	
		
			
				|  |  |  import { ElMessage } from 'element-plus'
 | 
	
		
			
				|  |  |  import SparkMD5 from 'spark-md5'
 | 
	
		
			
				|  |  | -import { request } from '@/utils/request'
 | 
	
		
			
				|  |  | +import { uploadFile } from '@/utils/request'
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  const props = defineProps({
 | 
	
		
			
				|  |  | -  projectId: Number,
 | 
	
		
			
				|  |  | -  solverType: String,
 | 
	
		
			
				|  |  |    accept: String,
 | 
	
		
			
				|  |  | -  upId: String,
 | 
	
		
			
				|  |  | -  name: String,
 | 
	
		
			
				|  |  | -  namelist: Array,
 | 
	
		
			
				|  |  | -  gfname: String,
 | 
	
		
			
				|  |  |    imgSrc: String,
 | 
	
		
			
				|  |  |    disabled: { type: Boolean, default: false }
 | 
	
		
			
				|  |  |  })
 | 
	
	
		
			
				|  | @@ -48,85 +38,86 @@ const emit = defineEmits([
 | 
	
		
			
				|  |  |    'upload-status',
 | 
	
		
			
				|  |  |  ])
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -let fileMd5 = ref('')
 | 
	
		
			
				|  |  | +const fileInput = ref(null)
 | 
	
		
			
				|  |  |  let fileName = ref('')
 | 
	
		
			
				|  |  | -let uuid = ref(generateUUID())
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -const uploadUrl = import.meta.env.VITE_BASE_URL + '/TransServlet'
 | 
	
		
			
				|  |  | +let uuid = ref('')
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -const uploadHeaders = {
 | 
	
		
			
				|  |  | -  Content: 'multipart',
 | 
	
		
			
				|  |  | -  Type: 'form-data',
 | 
	
		
			
				|  |  | +// 点击按钮触发文件选择
 | 
	
		
			
				|  |  | +function selectFile() {
 | 
	
		
			
				|  |  | +  if (props.disabled) return
 | 
	
		
			
				|  |  | +  fileInput.value.click()
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -const uploadData = computed(() => ({
 | 
	
		
			
				|  |  | -  transCode: 'B00028',
 | 
	
		
			
				|  |  | -  channelNo: 'service',
 | 
	
		
			
				|  |  | -  clientToken: 'e47b87eec69545559d1e81e56626da68',
 | 
	
		
			
				|  |  | -  userId: '5f06c8bc77234f969d13e160b54c27e3',
 | 
	
		
			
				|  |  | -  fileName: fileName.value,
 | 
	
		
			
				|  |  | -  bfid: uuid.value,
 | 
	
		
			
				|  |  | -}))
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -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)
 | 
	
		
			
				|  |  | -  })
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | +// 文件选择后触发
 | 
	
		
			
				|  |  | +async function handleFileChange(event) {
 | 
	
		
			
				|  |  | +  const file = event.target.files[0]
 | 
	
		
			
				|  |  | +  if (!file) return
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -async function handleBeforeUpload(rawFile) {
 | 
	
		
			
				|  |  | -  if (props.disabled) return false
 | 
	
		
			
				|  |  | -  fileName.value = rawFile.name
 | 
	
		
			
				|  |  | +  fileName.value = file.name
 | 
	
		
			
				|  |  |    emit('update-fileName', fileName.value)
 | 
	
		
			
				|  |  | +  emit('upload-status', '上传中')
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  const ext = fileName.value.split('.').pop().toLowerCase()
 | 
	
		
			
				|  |  | -  const acceptList = props.accept
 | 
	
		
			
				|  |  | -    ? props.accept.split(',').map(e => e.trim().replace(/^\./, '').toLowerCase())
 | 
	
		
			
				|  |  | -    : []
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -  if (acceptList.length > 0 && !acceptList.includes(ext)) {
 | 
	
		
			
				|  |  | -    ElMessage.error(`只支持 ${props.accept} 格式文件!`)
 | 
	
		
			
				|  |  | -    return false
 | 
	
		
			
				|  |  | -  }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -  fileMd5.value = await calculateMD5(rawFile)
 | 
	
		
			
				|  |  |    uuid.value = generateUUID()
 | 
	
		
			
				|  |  | -  emit('upload-status', '上传中')
 | 
	
		
			
				|  |  | -  return true
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -function handleSuccess(response, file) {
 | 
	
		
			
				|  |  | -  if (response.returnCode !== '000000000') {
 | 
	
		
			
				|  |  | -    ElMessage.error(response.returnMsg || '上传失败')
 | 
	
		
			
				|  |  | +  try {
 | 
	
		
			
				|  |  | +    await uploadInChunks(file)
 | 
	
		
			
				|  |  | +    await mergeChunks()
 | 
	
		
			
				|  |  | +    emit('upload-success', { bfid: uuid.value, fname: fileName.value })
 | 
	
		
			
				|  |  | +    emit('upload-status', '上传成功')
 | 
	
		
			
				|  |  | +  } catch (err) {
 | 
	
		
			
				|  |  | +    ElMessage.error(err.message || '上传失败')
 | 
	
		
			
				|  |  |      emit('upload-status', '上传失败')
 | 
	
		
			
				|  |  | -    return
 | 
	
		
			
				|  |  | +    emit('update-percentage', 0) 
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  emit('upload-success', {
 | 
	
		
			
				|  |  | -    bfid: response.bfid,
 | 
	
		
			
				|  |  | -    fname: file.name,
 | 
	
		
			
				|  |  | -  })
 | 
	
		
			
				|  |  | -  emit('upload-status', '上传成功')
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -  uuid.value = generateUUID()
 | 
	
		
			
				|  |  | +// 分片上传
 | 
	
		
			
				|  |  | +async function uploadInChunks(file) {
 | 
	
		
			
				|  |  | +  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.value)
 | 
	
		
			
				|  |  | +    formData.append('chunk', i + 1)
 | 
	
		
			
				|  |  | +    formData.append('chunks', totalChunks)
 | 
	
		
			
				|  |  | +    formData.append('file', chunkFile)
 | 
	
		
			
				|  |  | +    formData.append('fileName', fileName.value)
 | 
	
		
			
				|  |  | +    formData.append('transCode', 'B00028') // 分片接口固定字段
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    await uploadFile(formData, 'service', (progress) => {
 | 
	
		
			
				|  |  | +      const chunkPercent = (1 / totalChunks) * progress
 | 
	
		
			
				|  |  | +      const percent = Math.min(Math.floor((i / totalChunks) * 100 + chunkPercent), 100)
 | 
	
		
			
				|  |  | +      emit('update-percentage', percent)
 | 
	
		
			
				|  |  | +    })
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -function handleError() {
 | 
	
		
			
				|  |  | -  emit('upload-status', '上传失败')
 | 
	
		
			
				|  |  | -  emit('update-fileName', '')
 | 
	
		
			
				|  |  | -  uuid.value = generateUUID()
 | 
	
		
			
				|  |  | +// 合并接口
 | 
	
		
			
				|  |  | +async function mergeChunks() {
 | 
	
		
			
				|  |  | +  const formData = new FormData()
 | 
	
		
			
				|  |  | +  formData.append('bfid', uuid.value)
 | 
	
		
			
				|  |  | +  formData.append('transCode', 'B00029') // 合并接口固定字段
 | 
	
		
			
				|  |  | +  await uploadFile(formData, 'service')
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -function handleProgress(event, file, fileList) {
 | 
	
		
			
				|  |  | -  const percentage = Math.floor(event.percent)
 | 
	
		
			
				|  |  | -  emit('update-percentage', percentage)
 | 
	
		
			
				|  |  | +// 生成 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 = 2097152 // 2MB
 | 
	
		
			
				|  |  | +    const chunkSize = 2 * 1024 * 1024
 | 
	
		
			
				|  |  |      const chunks = Math.ceil(file.size / chunkSize)
 | 
	
		
			
				|  |  |      let currentChunk = 0
 | 
	
		
			
				|  |  |      const spark = new SparkMD5.ArrayBuffer()
 | 
	
	
		
			
				|  | @@ -148,7 +139,7 @@ function calculateMD5(file) {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      function loadNext() {
 | 
	
		
			
				|  |  |        const start = currentChunk * chunkSize
 | 
	
		
			
				|  |  | -      const end = Math.min(start + chunkSize, file.size)
 | 
	
		
			
				|  |  | +      const end = Math.min(file.size, start + chunkSize)
 | 
	
		
			
				|  |  |        fileReader.readAsArrayBuffer(file.slice(start, end))
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -161,7 +152,9 @@ function calculateMD5(file) {
 | 
	
		
			
				|  |  |  .webuploader-container {
 | 
	
		
			
				|  |  |    position: relative;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  | -.upload-box {
 | 
	
		
			
				|  |  | +.btntext {
 | 
	
		
			
				|  |  | +  width: 100%;
 | 
	
		
			
				|  |  | +  height: 100%;
 | 
	
		
			
				|  |  |    display: flex;
 | 
	
		
			
				|  |  |    align-items: center;
 | 
	
		
			
				|  |  |  }
 |