信息发布→ 登录 注册 退出

vue+Minio实现多文件进度上传的详细步骤

发布时间:2026-01-11

点击量:
目录
  • 背景
  • 实现方案
    • 了解一下Mino
  • 实现步骤
    • 1.创建存储桶
    • 2.选择文件
    • 3.创建上传队列
    • 4.开始上传
    • 5 上传完成后,同步文件地址给后端
    • 6.删除文件
  • 完整代码
    • 源码分享
      • 总结

        背景

        最近突然接到了一个产品的需求,有点特别,在这里给大家分享一下,需求如下

        • 提交表单,同时要上传模型资源
        • 模型文件是大文件,要显示上传进度,同时可以删除
        • 模型文件要上传到服务器,表单数据同步到数据库
        • 同时要同步上传后的模型地址到数据库
        • 后端使用Minio做文件管理

        设计图如下

        一开始以为是一个简单的表单上传,发现并不是,这是大文件上传啊,但很快又发现,不单单是上传大文件,还有将文件信息关联到表单。

        基于这个奇葩的情况,我和后端兄弟商量了一下,决定使用如下方案

        实现方案

        分2步走

        • 点击上传时,先提交表单信息到数据库,接着后端返回一个表单的id给我
        • 当所有文件上传完成后,再调用另外一个服务,将上传完成后的地址和表单id发送给后端

        如此,便完成了上面的需求

        了解一下Mino

        这里大家先了解一下Minio的js SDK文档

        里面有2个很重要的接口,今天要用到

        一个是给文件生成用于put方法上传的地址
        一个是获取已经上传完成后的文件的get下载地址

        实现步骤

        这里是使用原生的 ajax请求进行上传的,至于为什么,后面会有说到

        1.创建存储桶

        创建一个Minio上传实例

        var Minio = require('minio')
        
        this.minioClient = new Minio.Client({
            endPoint: '192.168.172.162', //后端提供
            port: 9000, //端口号默认9000
            useSSL: true,
            accessKey: 'Q3AM3UQ867SPQQA43P2F', //后端提供
            secretKey: 'zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG'
        });
        
        this.userBucket = 'yourBucketName' //这里后端需要提供给你,一个存储桶名字

        2.选择文件

        这里使用input标签选择文件,点击选择文件的时候,调用一下input的click方法,就可以打开本地文件夹

        <el-form-item label="资源文件">
          <el-button
            style="marginRight:10px;"
            @click="selectFile()"
            size="mini"
          >选择文件</el-button>
          <input
            :accept="acceptFileType"
            multiple="multiple"
            type="file"
            id="uploadInput"
            ref="uploadInput"
            v-show="false"
            @change="getAndFormatFile()"
          >
          <i class="tip">仅支持.gbl、.gltf、.fbx、.obj、.mtl、.hdr、.png、.jpg格式的文件</i>
          <i class="tip">单个文件的大小限制为128MB</i>
        </el-form-item>
        selectFile() {
            let inputDOM = this.$refs.uploadInput
            inputDOM.click();
        },

        接着就是对文件进行格式化

        //格式化文件并创建上传队列
          getAndFormatFile(){
            let files = this.$refs.uploadInput.files
            const userBucket = this.userBucket
            if(files.length > 6) {
              this.$message({
                message: `最大只能上传6个文件`,
                type: 'warning'
              })
              return
            }
            files.forEach((file, index) => {
              if ((file.size / 1024 / 1024).toFixed(2) > 128) { //单个文件限制大小为128MB
                this.$message({
                  message: `文件大小不能超过128MB`,
                  type: 'warning'
                })
                return
              }
              //创建文件的put方法的url
              this.minioClient.presignedPutObject(userBucket, file.name, 24 * 60 * 60, (err, presignedUrl) => {
                if (err) {
                  this.$message({
                    message: `服务器连接超时`,
                    type: 'error'
                  })
                  return err
                }
                let fileIcon = this.getFileIcon(file)
                let fileUploadProgress = '0%' //文件上传进度
                this.fileInfoList.push({
                  file, //文件
                  fileIcon, //文件对应的图标 className
                  fileUploadProgress, //文件上传进度
                  filePutUrl: presignedUrl, //文件上传put方法的url
                  fileGetUrl: '', //文件下载的url
                })
              })
            })
            this.fileList = [...this.fileInfoList]
          },

        3.创建上传队列

        这里定义了一个创建文件上传请求的方法,使用原生的XMLHttpRequest,它接受以下参数

        • file:要上传的文件
        • filePutUrl:文件上传的put方法地址
        • customHeader: 自定义的头信息
        • onUploadProgress:文件上传的进度监听函数
        • onUploaded:文件上传完成的监听函数
        • onError:文件上传出错的监听函数
         //创建上传文件的http
          createUploadHttp(config){
            const {file, filePutUrl, customHeader, onUploadProgress, onUploaded, onError} = config
            let fileName = file.name
            let http = new XMLHttpRequest();
            http.upload.addEventListener("progress", (e) => { //监听http的进度。并执行进度监听函数
              onUploadProgress({
                progressEvent: e,
                uploadingFile: file
              })
            }, false)
        
            http.onload = () => {
              if (http.status === 200 && http.status < 300 || http.status === 304) {
                try {
                //监听http的完成事件,并执行上传完成的监听函数
                  const result = http.responseURL
                  onUploaded({ result, uploadedFile: file})
                } catch(error) {
                //监听错误
                  onError({ error, errorFile: file})
                }
              }
            }
         
            http.open("PUT", filePutUrl, true);
            //加入头信息
            Object.keys(customHeader).forEach((key, index) =>{
              http.setRequestHeader(key, customHeader[key])
            })
            http.send(file);
            return http //返回该http实例
          }

        4.开始上传

        //上传文件到存储桶
          async handleUplaod(){
            let _this = this
            if(this.fileInfoList.length < 1) {
              this.$message({
                message: `请先选择文件`,
                type: 'warning'
              })
              return
            }
            //先上传文件的基本表单信息,获取表单信息的id
            try{
              const {remark, alias} = _this.uploadFormData
              let res = await uploadModelSourceInfo({remark, serviceName: alias})
              _this.modelSourceInfoId = res.message
            }catch(error){
              if(error) {
                _this.$message({
                  message: `上传失败,请检查服务`,
                  type: 'error'
                })
                return
              }
            }
        
            //开始将模型资源上传到远程的存储桶
            this.fileList.forEach((item, index) => {
              const {file, filePutUrl} = item
              let config = {
                file,
                filePutUrl,
                customHeader:{
                  "X-FILENAME": encodeURIComponent(file.name),
                  "X-Access-Token": getToken()
                },
                onUploadProgress: ({progressEvent, uploadingFile}) => {
                  let progress = (progressEvent.loaded / progressEvent.total).toFixed(2)
                  this.updateFileUploadProgress(uploadingFile, progress)
                },
                onUploaded: ({result, uploadedFile}) => {
                  this.updateFileDownloadUrl(uploadedFile)
                },
                onError: ({error, errorFile}) => {
        
                }
              }
        
              let httpInstance = this.createUploadHttp(config) //创建http请求实例
              this.httpQueue.push(httpInstance) //将http请求保存到队列中
            })
          },
        
        
        //更新对应文件的上传进度
        updateFileUploadProgress(uploadingFile, progress) {
        this.fileInfoList.forEach((item, index) => {
          if(item.file.name === uploadingFile.name){
            item.fileUploadProgress = (Number(progress)*100).toFixed(2) + '%'
          }
        })
        },
        
        //更新上传完成文件的下载地址
        updateFileDownloadUrl(uploadedFile){
        const userBucket = this.userBucket
        this.fileInfoList.forEach((item, index) => {
          if(item.file.name === uploadedFile.name){
            this.minioClient.presignedGetObject(userBucket, uploadedFile.name, 24*60*60, (err, presignedUrl) => {
              if (err) return console.log(err)
              item.fileGetUrl = presignedUrl
            })
          }
        })
        },

        5 上传完成后,同步文件地址给后端

        在watch里监听文件列表,当所有的文件进度都是100%时,表示上传完成,接着就可以同步文件信息

        watch:{
          fileInfoList: {
            handler(val){
              //1.3所有文件都上传到存储桶后,将上传完成后的文件地址、文件名字同步后端
              if(val.length < 1) return
              let allFileHasUpload = val.every((item, index) => {
                return item.fileGetUrl.length > 1
              })
             if(allFileHasUpload) {
               this.allFileHasUpload = allFileHasUpload
               const {modelSourceInfoId} = this
               if(modelSourceInfoId.length < 1) {
                 return
               }
               const url = process.env.VUE_APP_BASE_API + "/vector-map/threeDimensionalModelService/invokeMapService"
               const files = val.map((ite, idx) => {
                 return {
                    fileName: ite.file.name,
                    fileUrl: ite.fileGetUrl
                 }
               })
               this.syncAllUploadedFile(url, files, modelSourceInfoId)
             }
        
            },
            deep: true
          }
        },
        
        
        
        //同步已上传的文件到后端
        syncAllUploadedFile(url, files, modelSourceInfoId){
            let xhr = new XMLHttpRequest()
            xhr.onload = () => {
              if (xhr.status === 200 && xhr.status < 300 || xhr.status === 304) {
                try {
                 const res = JSON.parse(xhr.responseText)
                 if(res && res.code === 200){
                   this.$message({
                     message: '上传完成',
                     type: 'success'
                   })
                     this.$emit('close')
                     this.fileInfoList = []
                     this.fileList = []
                     this.httpQueue = []
                 }
                } catch(error) {
                 this.$message({
                   message: '上传失败,请检查服务',
                   type: 'error'
                 })
                }
              }
            }
            xhr.open("post", url, true)
            xhr.setRequestHeader('Content-Type', 'application/json')
            xhr.setRequestHeader('X-Access-Token', getToken())
            //将前面1.1获取文件信息的id作为头信息传递到后端
            xhr.setRequestHeader('ThreeDimensionalModel-ServiceID', modelSourceInfoId)
            xhr.send(JSON.stringify(files))
        },

        6.删除文件

        删除文件时要注意

        • 删除本地的文件缓存
        • 删除存储桶里面的文件
        • 停止当前文件对应的http请求
        //删除文件,并取消正在文件的上传
          deleteFile(fileInfo, index){
            this.httpQueue[index] && this.httpQueue[index].abort()
            this.httpQueue[index] && this.httpQueue.splice(index, 1)
            this.fileInfoList.splice(index, 1)
            this.fileList.splice(index, 1)
            this.removeRemoteFile(fileInfo)
          },
        
          //清空文件并取消上传队列
          clearFile() {
            this.fileInfoList.forEach((item, index) => {
              this.httpQueue[index] && this.httpQueue[index].abort()
              this.httpQueue[index] && this.httpQueue.splice(index, 1)
              this.removeRemoteFile(item)
            })
            this.fileInfoList = []
            this.httpQueue = []
            this.fileList = []
          },
          //删除远程文件
          removeRemoteFile(fileInfo){
            const userBucket = this.userBucket
            const { fileUploadProgress, file} = fileInfo
            const fileName = file.name
            const complete = fileUploadProgress === '100.00%' ? true : false
            if(complete){
             this. minioClient.removeObject(userBucket, fileName, function(err) {
                if (err) {
                  return console.log('Unable to remove object', err)
                }
                console.log('Removed the object')
              })
            }else{
              this.minioClient.removeIncompleteUpload(userBucket, fileName, function(err) {
                if (err) {
                  return console.log('Unable to remove incomplete object', err)
                }
                console.log('Incomplete object removed successfully.')
              })
            }
          },

        完整代码

        这里的完整代码是我直接从工程里拷贝出来的,里面用到了一些自己封装的服务和方法 比如 后端的接口、AES解密、获取Token、表单验证等

        import{uploadModelSourceInfo, uploadModelSource, getMinioConfig} from '@/api/map' 
        import AES from '@/utils/AES.js' 
        import { getToken } from '@/utils/auth' 
        import * as myValiDate from "@/utils/formValidate";
        /**
         * 文件说明
         * @Author: zhuds
         * @Description: 模型资源上传弹窗
            分为3个步骤
            1.先将文件的基本表单信息上传给后端,获取文件信息的ID
            2.然后将文件上传存储桶
            3.等所有文件都上传完成后,再将上传完成后的文件信息传递给后端,注意,此时的请求头要戴上第1步获取的文件信息id
         * @Date: 2/28/2025, 1:13:20 PM
         * @LastEditDate: 2/28/2025, 1:13:20 PM
         * @LastEditor:
         */
        <template>
          <div class="upload-model">
            <el-dialog
            	:visible.sync="isVisible"
            	@close="close()"
            	:show-close ="false"
            	:close-on-click-modal="false"
            	top="10vh"
              v-if="isVisible"
              :destroy-on-close="true"
            >
            	<div slot="title" class="header-title">
            		<div class="icon"></div>           
            		<span>上传模型资源</span>
            		<i class="el-icon-close"  @click="close()"></i>			       
            	</div>
        
              <el-form
                :label-position="labelPosition"
                label-width="80px"
                :model="uploadFormData"
                ref="form"
                :rules="rules"
              >
                <el-form-item label="别名">
                  <el-input   size="small" v-model="uploadFormData.alias"></el-input>
                </el-form-item>
        
                <el-form-item label="备注">
                  <el-input type="textarea" v-model="uploadFormData.remark"  size="small"></el-input>
                </el-form-item>
        
                <el-form-item label="资源文件">
                  <el-button
                    style="marginRight:10px;"
                    @click="selectFile()"
                    size="mini"
                  >选择文件</el-button>
                  <input
                    :accept="acceptFileType"
                    multiple="multiple"
                    type="file"
                    id="uploadInput"
                    ref="uploadInput"
                    v-show="false"
                    @change="getAndFormatFile()"
                  >
                  <i class="tip">仅支持.gbl、.gltf、.fbx、.obj、.mtl、.hdr、.png、.jpg格式的文件</i>
                  <i class="tip">单个文件的大小限制为128MB</i>
                </el-form-item>
        
              </el-form>
        
              <div class="file-list" v-show="fileInfoList.length > 0">
                <div class="file-item" v-for="(item, index) in fileInfoList" :key="index">
                  <div class="icon"></div>
                  <div class="name">{{item.file.name}}</div>
                  <div class="size">{{(item.file.size/1024/1024).toFixed(2)}}MB  </div>
                  <div class="progress">
                    <div class="bar" :style="{width: item.fileUploadProgress}"></div>
                  </div>
                  <div class="rate">{{item.fileUploadProgress}}</div>
        
        
                  <div class="delete-btn" @click="deleteFile(item, index)">x</div>
                </div>
              </div>
              <div class="custom-footer">
                <button class="info" @click="close()">取 消</button>
                <button class="success" @click="handleUplaod()">上传</button>
              </div>
        
            </el-dialog>
          </div>
        </template>
        
        <script>
        
          import{uploadModelSourceInfo, uploadModelSource, getMinioConfig} from '@/api/map'
          import AES from '@/utils/AES.js'
          import { getToken } from '@/utils/auth'
          import * as myValiDate from "@/utils/formValidate";
          let Minio = require('minio')
        
          export default {
            name: 'UploadModelDialog',
            props: {
              isVisible: {
                type: Boolean,
                default: false
              },
            },
            data(){
              return {
                labelPosition: 'right',
                uploadFormData: {
                  alias: '', //服务名称
                  remark: '', //备注
                },
                rules: {
                  serviceName: [{
                  	validator: myValiDate.validateServiceName,
                  	trigger: "blur",
                  	required: true,
                  }],
                },
                acceptFileType:".glb,.gltf,.fbx,.obj,.mtl,.hdr,.png,.jpg, .mp4",
                fileList:[], //待上传的文件列表
                fileInfoList: [], //格式化后的文件信息列表
                userBucket: null,
                httpQueue: [], //上传文件的http队列
                allFileHasUpload: false, //是否完成上传
                modelSourceInfoId: '', //模型资源基本信息的id
              }
            },
        
            watch:{
              fileInfoList: {
                handler(val){
                  //1.3所有文件都上传到存储桶后,将上传完成后的文件地址、文件名字同步后端
                  if(val.length < 1) return
                  let allFileHasUpload = val.every((item, index) => {
                    return item.fileGetUrl.length > 1
                  })
                 if(allFileHasUpload) {
                   this.allFileHasUpload = allFileHasUpload
                   const {modelSourceInfoId} = this
                   if(modelSourceInfoId.length < 1) {
                     return
                   }
                   const url = process.env.VUE_APP_BASE_API + "/vector-map/threeDimensionalModelService/invokeMapService"
                   const files = val.map((ite, idx) => {
                     return {
                       	fileName: ite.file.name,
                       	fileUrl: ite.fileGetUrl
                     }
                   })
                   this.syncAllUploadedFile(url, files, modelSourceInfoId)
                 }
        
                },
                deep: true
              }
            },
        
            created() {
              this.initMinioClient()
            },
        
            beforeDestroy() {
              if(!this.allFileHasUpload) {
                this.clearFile()
              }
            },
        
            methods:{
               //创建存储桶
              async initMinioClient(){
                const { code, result, message } =  AES.decryptToJSON(await getMinioConfig({}))
                if(!result || code !== 200) {
                	this.$customMessage.error({message: '获取存储桶配置信息出错'})
                	return false
                }
                let {accessKey, bucketName, endPoint, secretKey} = result
                //console.log({accessKey, bucketName, endPoint, secretKey})
        
                let endPointStr = endPoint.split(":")[1]
                let formatPort = Number(endPoint.split(":")[2])
                let formatEndPoint = endPointStr.split('//')[1]
                this.userBucket = bucketName
                this.minioClient = new Minio.Client({
                  useSSL: false,
                  partSize: '20M',
                  port: formatPort,
                  endPoint: formatEndPoint,
                  accessKey,
                  secretKey
                });
                let userBucket = this.userBucket
                //userBucket只能作为字符串变量传入,不能作为其他变量的属性或者函数返回值,属于Minio的一个规定
                this.minioClient.bucketExists(userBucket, (err)=> {
                  if (err && err.code == 'NoSuchBucket') {
                    this.minioClient.makeBucket(userBucket, 'us-east-1', (err)=> {
                      if (err) {
                        return console.log('创建存储桶失败', err)
                      }
                     // console.log('Bucket created successfully in "us-east-1".')
                    })
                  }else{
                    //console.log('存储桶存在')
                  }
                })
              },
        
              close(flag = false) {
                this.$emit('close', flag)
                //关闭弹窗时,如果文件没有上传完成,则清空文件
                if(!this.allFileHasUpload) {
                  this.clearFile()
                }
              },
              selectFile() {
                let inputDOM = this.$refs.uploadInput
                inputDOM.click();
              },
        
              getFileSize(file){
                let fileSize = ''
                if(file.size / 1024 < 1){
                  fileSize = file.size + 'B'
                }else if(file.size / 1024 /1024 < 1){
                  fileSize = file.size + 'KB'
                }else if(file.size / 1024 /1024 >=1){
                   fileSize = file.size + 'MB'
                }else{
                }
        
                return fileSize
        
              },
        
              //删除文件,并取消正在文件的上传
              deleteFile(fileInfo, index){
                this.httpQueue[index] && this.httpQueue[index].abort()
                this.httpQueue[index] && this.httpQueue.splice(index, 1)
                this.fileInfoList.splice(index, 1)
                this.fileList.splice(index, 1)
                this.removeRemoteFile(fileInfo)
              },
        
              //清空文件并取消上传队列
              clearFile() {
                this.fileInfoList.forEach((item, index) => {
                  this.httpQueue[index] && this.httpQueue[index].abort()
                  this.httpQueue[index] && this.httpQueue.splice(index, 1)
                  this.removeRemoteFile(item)
                })
                this.fileInfoList = []
                this.httpQueue = []
                this.fileList = []
              },
              //删除远程文件
              removeRemoteFile(fileInfo){
                const userBucket = this.userBucket
                const { fileUploadProgress, file} = fileInfo
                const fileName = file.name
                const complete = fileUploadProgress === '100.00%' ? true : false
                if(complete){
                 this. minioClient.removeObject(userBucket, fileName, function(err) {
                    if (err) {
                      return console.log('Unable to remove object', err)
                    }
                    console.log('Removed the object')
                  })
                }else{
                  this.minioClient.removeIncompleteUpload(userBucket, fileName, function(err) {
                    if (err) {
                      return console.log('Unable to remove incomplete object', err)
                    }
                    console.log('Incomplete object removed successfully.')
                  })
                }
              },
              //格式化文件并创建上传队列
              getAndFormatFile(){
                let files = this.$refs.uploadInput.files
                const userBucket = this.userBucket
                if(files.length > 6) {
                  this.$message({
                    message: `最大只能上传6个文件`,
                    type: 'warning'
                  })
                  return
                }
                files.forEach((file, index) => {
                  if ((file.size / 1024 / 1024).toFixed(2) > 128) { //单个文件限制大小为128MB
                    this.$message({
                      message: `文件大小不能超过128MB`,
                      type: 'warning'
                    })
                    return
                  }
                  //创建文件上传的url并格式化文件信息
                  this.minioClient.presignedPutObject(userBucket, file.name, 24 * 60 * 60, (err, presignedUrl) => {
                    if (err) {
                      this.$message({
                        message: `服务器连接超时`,
                        type: 'error'
                      })
                      return err
                    }
                    let fileIcon = this.getFileIcon(file)
                    let fileUploadProgress = '0%'
                    this.fileInfoList.push({
                      file, //文件
                      fileIcon, //文件对应的图标 className
                      fileUploadProgress, //文件上传进度
                      filePutUrl: presignedUrl, //文件上传put方法的url
                      fileGetUrl: '', //文件下载的url
                    })
                  })
                })
                this.fileList = [...this.fileInfoList]
              },
        
              //1.上传文件到存储桶
              async handleUplaod(){
                let _this = this
                if(this.fileInfoList.length < 1) {
                  this.$message({
                    message: `请先选择文件`,
                    type: 'warning'
                  })
                  return
                }
                //1.1先上传文件的基本表单信息,获取文件信息的id
                try{
                  const {remark, alias} = _this.uploadFormData
                  let res = await uploadModelSourceInfo({remark, serviceName: alias})
                  _this.modelSourceInfoId = res.message
                }catch(error){
                  if(error) {
                    _this.$message({
                      message: `上传失败,请检查服务`,
                      type: 'error'
                    })
                    return
                  }
                }
        
                //1.2开始将模型资源上传到远程的存储桶
                this.fileList.forEach((item, index) => {
                  const {file, filePutUrl} = item
                  let config = {
                    file,
                    filePutUrl,
                    customHeader:{
                      "X-FILENAME": encodeURIComponent(file.name),
                      "X-Access-Token": getToken()
                    },
                    onUploadProgress: ({progressEvent, uploadingFile}) => {
                      let progress = (progressEvent.loaded / progressEvent.total).toFixed(2)
                      this.updateFileUploadProgress(uploadingFile, progress)
                    },
                    onUploaded: ({result, uploadedFile}) => {
                      this.updateFileDownloadUrl(uploadedFile)
                    },
                    onError: ({error, errorFile}) => {
        
                    }
                  }
        
                  let httpInstance = this.createUploadHttp(config)
                  this.httpQueue.push(httpInstance)
                })
              },
        
              //1更新对应文件的上传进度
              updateFileUploadProgress(uploadingFile, progress) {
                //console.log({uploadingFile, progress})
                this.fileInfoList.forEach((item, index) => {
                  if(item.file.name === uploadingFile.name){
                    item.fileUploadProgress = (Number(progress)*100).toFixed(2) + '%'
                  }
                })
              },
        
              //更新上传完成文件的下载地址
              updateFileDownloadUrl(uploadedFile){
                const userBucket = this.userBucket
                this.fileInfoList.forEach((item, index) => {
                  if(item.file.name === uploadedFile.name){
                    this.minioClient.presignedGetObject(userBucket, uploadedFile.name, 24*60*60, (err, presignedUrl) => {
                      if (err) return console.log(err)
                      item.fileGetUrl = presignedUrl
                     // console.log(presignedUrl)
                    })
                  }
                })
              },
        
              //同步已上传的文件到后端
              syncAllUploadedFile(url, files, modelSourceInfoId){
                let xhr = new XMLHttpRequest()
                xhr.onload = () => {
                  if (xhr.status === 200 && xhr.status < 300 || xhr.status === 304) {
                    try {
                     const res = JSON.parse(xhr.responseText)
                     if(res && res.code === 200){
                       this.$message({
                         message: '上传完成',
                         type: 'success'
                       })
                      // setTimeout(() => {
                         this.$emit('close')
                         this.fileInfoList = []
                         this.fileList = []
                         this.httpQueue = []
                      // }, 1000)
                     }
                    } catch(error) {
                     this.$message({
                       message: '上传失败,请检查服务',
                       type: 'error'
                     })
                    }
                  }
                }
                xhr.open("post", url, true)
                xhr.setRequestHeader('Content-Type', 'application/json')
                xhr.setRequestHeader('X-Access-Token', getToken())
                //将前面1.1获取文件信息的id作为头信息传递到后端
                xhr.setRequestHeader('ThreeDimensionalModel-ServiceID', modelSourceInfoId)
                xhr.send(JSON.stringify(files))
              },
        
              //获取文件类型图标class
              getFileIcon(file) {
                const { type } = file
                let icon = ''
                return icon
              },
        
              //创建上传文件的http
              createUploadHttp(config){
                const {file, filePutUrl, customHeader, onUploadProgress, onUploaded, onError} = config
                let fileName = file.name
                let http = new XMLHttpRequest();
                http.upload.addEventListener("progress", (e) => {
                  onUploadProgress({
                    progressEvent: e,
                    uploadingFile: file
                  })
                }, false)
        
                http.onload = () => {
                  if (http.status === 200 && http.status < 300 || http.status === 304) {
                    try {
                      const result = http.responseURL
                      onUploaded({ result, uploadedFile: file})
                    } catch(error) {
                      onError({ error, errorFile: file})
                    }
                  }
                }
                http.open("PUT", filePutUrl, true);
                Object.keys(customHeader).forEach((key, index) =>{
                  http.setRequestHeader(key, customHeader[key])
                })
                http.send(file);
                return http
              }
        
            }
          }
        </script>
        
        <style scoped lang="scss">
        
          .header-title {
          //  height: 24px;
            border-bottom: 1px solid #EFEFEF;
            padding-bottom: 20px;
            //outline: 1px solid red;
            .icon {
              width: 26px;
              height: 26px;
              background-image: url(../images/icon_upload.png);
              float: left;
              background-size: 100% 100%;
              margin-right: 18px;
              margin-left: 10px;
            }
            span {
              font-size: 23px;
              font-family: Source Han Sans CN;
              font-weight: 500;
              color: #333333;
            }
            i {
              float: right;
              font-size: 16px;
              color: rgba(176, 176, 176, 1);
              cursor: pointer;
              &:hover {
                color: #0069D5 ;
              }
            }
          }
          .tip {
            font-size: 12px;
            display: block;
          }
          .file-list {
            box-sizing: border-box;
            padding: 5px;
           // border: 1px solid red;
           // width: 840px;
            margin: 5px auto;
            margin-left: 80px;
            .file-item {
              min-height: 32px;
              display: flex;
              justify-content: flex-start;
              align-items: center;
              font-size: 12px;
              div {
                margin-right: 15px;
                text-align: left;
              }
              .name {
                width: 200px;
              }
              .size {
                width: 60px;
              }
              .progress {
                width: 180px;
                height: 8px;
                border-radius: 4px;
                background-color: #E2E2E2;
                .bar {
                  width: 50%;
                  height: 8px;
                  border-radius: 4px;
                  background-color: #13A763;
                }
              }
              .rate {
                width: 60px;
               // border: 1px solid red;
        
              }
              .delete-btn {
                cursor: pointer;
                font-size: 16px;
              }
            }
          }
        
          .custom-footer {
           // border: 1px solid red;
            display: flex;
            justify-content: space-evenly;
            align-items: center;
            width: 100%;
            height: 80px;
            background-color: #fff;
            //box-shadow: 0px 1px 0px 0px red;
            border-top: 1px solid #efefef;
            z-index: 10;
            button {
              width: 90px;
              height: 40px;
              border-radius: 4px;
              border: 0;
              font-size: 16px;
              font-family: Source Han Sans CN;
              &:focus {
                border: 0;
                outline: 0;
              }
        
              &.info {
                color: #8f8f8f;
                background: #e2e2e2;
                &:hover{
                  background-color: #A6A9AD;
                  cursor: pointer;
                  color: #fff;
                }
              }
              &.success {
                background: #12a763;
                color: #fff;
                &:hover{
                  cursor: pointer;
                  background-color: #73C132;
                }
              }
            }
          }
        </style>

        源码分享

        其实一开始我是想提供一个demo的,发现这个东西与产品功能强绑定,没有测试的服务地址和存储桶,也无法做出一个开放的案例

        所以上面的代码只是给大家提供一种实现方式和思路,里面具体的细节处理我做的比较复杂

        总结

        在线客服
        服务热线

        服务热线

        4008888355

        微信咨询
        二维码
        返回顶部
        ×二维码

        截屏,微信识别二维码

        打开微信

        微信号已复制,请打开微信添加咨询详情!