文件上传方式

173 阅读3分钟

单一文件上传[formData 格式]

主要核心
预览方式 bold 格式
const src = URL.createObjectURL(文件对象)
img.src = src
上传方式
const fd = new FormData()
fd.append('属性名', 值)
axios.post('/xx', fd)

单一文件上传(只适合图片)()[base64 格式]

主要核心
const fileReader = new fileReader()
fileReader.readAsDataURL(文件对象) // 读取文件对象转换为 base64格式
fileReader.onload(e => { // 读取文件是异步操作
 console.log(e.target.result) // base 64格式
 img.src = e.target.result
 axios.post('/xx', e.target.result)
})

单一文件上传(自定义唯一文件名)

主要核心
自定义文件名核心(利用spark MD5包)
"spark-md5": "^3.0.1",
const fileReader = new fileReader()
fileReader.readAsArrayBuffer(文件对象)
fileReader.onload((e) => {
  const spark = new sparkMD5.ArrayBuffer() // 读取buffer 格式数据
  spark.append(e.target.result) 
  const HASH = spark.end() // 获取得到唯一hash 名文件
})

完整代码

;(function (){
  const upload3 = document.querySelector('#upload3')
  const up_file = upload3.querySelector('.upload_inp')
  const uploadSer = upload3.querySelector('.upload_button.upload')
  const upload_select1 = upload3.querySelector('.upload_button.select')
  const upload_abbre = upload3.querySelector('.upload_abbre')
  let _file = null // 文件对象
  // 判断文件是否读取中
  const isDisable = (e) => {
    return e.classList.contains('loadig') || e.classList.contains('disable')
  } 
  
  // 文件点击
  function changeDisable(flag) {
    if (flag) {
      upload_select1.classList.add('disable')
      uploadSer.classList.add('loading')
    } else {
      upload_select1.classList.remove('disable')
      uploadSer.classList.remove('loading')
    }
  }


  // 文件读取成base64格式
  const getBase64 = (file) => {
    return new Promise(reslove => {
      const readFile = new FileReader()
      readFile.readAsDataURL(file)
      readFile.onload = (e) => {
        reslove(e.target.result)
      }
    })
  }

  // 文件生成唯一hash 值
  const changeBuffer = (file) => {
    return new Promise(reslove => {
      const readFile = new FileReader()
      readFile.readAsArrayBuffer(file)
      readFile.onload = (e) => {
        const buffer = e.target.result
        const spark = new SparkMD5.ArrayBuffer()
        spark.append(buffer) // 开始 生成 唯一的hash
        const HASH = spark.end() // 结束后拿到 hash
        // console.log(file.name)
        // console.log(HASH)
        const suffix = /\.([A-Za-z0-9]+)$/.exec(file.name)[1] // 获取文件后缀名
        reslove({
          buffer,
          suffix,
          HASH,
          filename: `${HASH}.${suffix}`
        })
      }
    })
  }

  // 点击上传服务器
  uploadSer.addEventListener('click', async function () {
    // 判断是否有文件对象
    if (!_file) return
    const {filename} = await changeBuffer(_file) // 生成唯一 的hash 文件名 
    const fd = new FormData()
    fd.append('file', _file)
    fd.append('filename', filename) 
    try {
      changeDisable(true)
      const res = await instance.post('upload_single_name', fd)
      if (res.code !== 0) throw res.codeText
      alert('上传成功')
    } catch (err) {
      alert(err)
    }
    changeDisable(false)
    _file = null
    upload_abbre.style.display = 'none'
    upload_abbre.children[0] = ''
  })


  // 监听文件状态
  up_file.addEventListener('change', async function (e) {
    const file = e.target.files
    // 判断是否有文件
    if (file.length <= 0) return alert('没有文件')
    // 限制文件大小
    // console.log(file[0])
    if (file[0].size > 1024 * 1024 * 2) return alert('文件过大')
    // 获取base64 格式字符串
    changeDisable(true)
    const res = await getBase64(file[0])
    upload_abbre.style.display = 'block'
    upload_abbre.children[0].src = res
    changeDisable(false)
    _file = file[0]
  })
  
  // 触发点击文件的操作
  upload_select1.addEventListener('click', function() {
    // 判断文件是否在读取中进行防抖操作
    if(isDisable(this)) return
    up_file.click()
  })
})();

文件上传进度条显示

主要核心
axios 监听文件上传进度事件
onUploadProgress(e) { // 基于原生 XMLHttpRequest().upload.onprogress事件
  e.loaded // 文件上传过程大小
  e.total // 文件总大小
}

axios.post('/xx', data, {
  onUploadProgress(e) {
    e.loaded
    e.total
  }
})

完整代码

  // 3. 监听 file 文件对象
  up_file.addEventListener('change', async function(e) {
    const target = e.target 
    // console.log(target.files)
    const file = target.files[0]
    // 3.1 判断是否有文件对象
    if (file.length <= 0) return
    // 3.2 判断文件类型
    // console.log(file.type)
    if (!/(PNG|JPG|JPEG)/i.test(file.type)) return alert('文件类型不对')
    // 3.3 校验文件大小
    if (file.size > 2 * 1024 * 1024) return alert('文件大小不超过2M')

    // 上传 formate 格式
    const fd = await changeFormData(file)
    try {
      const res = await instance.post('/upload_single', fd, {
        onUploadProgress: (ev) => { // 利用onUploadProgress 监听事件
          // ev.loaded 文件加载进度
          // ev.total 文件总大小
          upload_progress.style.display = 'block'
          upload_value.style.width = `${ev.loaded / ev.total * 100}%`
        }
      })
      upload_value.style.width = `100%`
      await delay(1000)
      alert('上传成功')
      if (res.code !== 0) throw res.codeText
    } catch (err) {
      alert(err)
    }

    // 无论成功还是失败 将盒子隐藏
    upload_progress.style.display = 'none'
    upload_value.style.width = `0%`
  })

多文件上传[文件进度管控]

核心思路
file.addEventListener('click', async function (){
  const fileList = e.target.files
  if(fileList.length <= 0) return
  const list = fileList.map(item => {
    return axios.post('/xx', data, {
      onUploadProgress(e) {
         // 做操作
      }
    })
  })
  
  try {
    await Promise.all(list) // list 返回时promise 数组
  } catch(err) {
  }
  // 无论成功还是失败 做一些操作
  xxx
})

拖拽上传

核心思路
利用H5 新增事件 
 1. drogenter // 目标元素 进入区域
 2. dorgleave // 目标元素 离开区域
 3. dropover // 目标元素 经过区域
 4. drog // 目标元素放入区域
 
注意:
 1. 文件拖拽到浏览器默认是打开这个文件 需要阻止事件默认行为 e.preventDefault()
 2. 通过 e.dataTransfer.files 拿到文件对象
 代码
 upload_drag.addEventListener('drop', function (e) {
    e.preventDefault()
    // console.log(e)
    const file = e.dataTransfer.files[0] // 拿到文件对象
    进行文件上传操作 ...
  }

大文件切片上传

核心思路:
 1. 文件change 事件触发 优先获取已经上传完成的切片,
 2. 进行切片上传 利用文件对象.slice() 方法 进行切片
 3.切片完成 发起合并切片请求
 
 代码
 // 根据文件大小获取切片数量和 每个切片大小
 // file: 文件对象
 const getSliceCount = (file) => {
   let max = 100 * 1024 // 默认切片数量为 100kb
   let count = Math.ceil(file.size / max) // file.size 获取b 向上取整
   if (count > 100) { // 超过100 按 100 算
       max = file.size / 100
       count = 100
   }
   return {max, count}
 }
 
 // 拿到上传对象的文件HASH 名
 const getHASH = (file) => {
     return new Promise(reslove => {
         const fileRead = new FileReader()
         fileRead.readASArrayBuffer(file)
         fileRead.onload = (e) => {
             const spark = sparkMD5.ArrayBuffer()
             spark.append(e.target.result)
             const HASH = spark.end() // 获取文件唯一 hash名
             const suffix = file.name.slice(file.name.indexOf('.'), ) // 获取文件后缀名 .jpg .png
             reslove({
                HASH,
                suffix,
             })
         }
     })
     
 }
 
 // 获取每一个切片文件的大小 组合
 const chunkFile = (count, max, HASH, file, suffix) => {

        // slice(0, 1024) 1
        // slice(1024, 2048) 2
        // slice(2048, xx)
   
     const = chunkList = []
     for(let i = 0; i < count; i++) {
         chunkList.push({
            file: file.slice(i*max, (i+1)*max)
            HASH: `${HASH}_${i+1)${suffix)` // 文件名 xxx_1.jpg xxx_2.jpg
         })
     }
     return chunkList
 }
 
 // 管控进度显示  index 从0 开始 的 执行到最后会比数量小1
 const complect = (index, count, HASH) => {
    if(index < count - 1) {
       upload_value.style.width = `((index+1) / count)*100` + '%'
       return
    }
    // 走到这一步 说明文件 切片 上传完成 (执行合并切片)
    try {
        const res = await instance.post('/upload_merge', {
          HASH: HASH,
          count
        },{
          headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
          } 
        })
        if (res.code !== 0) throw res.codeText
        alert('上传切片成功')
      } catch (err) {
        alert('合并切片失败')
    }
 }
 

部分核心代码

file_upload.addEventListener('change', async function (){
    const file = e.target.files[0]
    if(!file) return
    
    // 拿到文件对象的HASH名
    const { HASH, suffix} = await getHASH(file)
    let readList = [] // 断点续传的文件
    
    // 1. 优先获取已经上传完成的切片
    try {
      const res = await instance.get('/upload_already', {
         params: {
           HASH
     }
     if(res.code !== 0) throw xx
     readList = res.fileList // res.fileList: 数组 包含缓存的文件名[xx_1.xx, xx_2.xx]
    } catch(err) {
    }
    
    // 2. 切片上传文件
    
    // 获取切片数量 大小
    const = {max, count} = getSliceCount(file)
    
    // 获取 切片组合
    const list = chunkFile = (count, max, HASH, file, suffix)
    list.forEach((item, index) => {
      // 2.1 判断 缓存切片是否缓存
      if(readList.length > 0 && readList.includes(item.HASH)) return complete(index, count, HASH)
        const fd = new FormData()
        fd.append('file', item.file)
        fd.append('filename', item.filename)
         try {
           const res = await instance.post('/upload_chunk', fd)
           if (res.code !== 0) throw '错误'
           // 管控进度
           compolete(index, count, HASH)
         } catch (err) {
         }
    })
   })
})

文件切片完整代码

// 大文件切片上传, 和断点续传
(function () {
const upload = document.querySelector('#upload7')
const upload_inp = upload.querySelector('.upload_inp')
const upload_select = upload.querySelector('.select')
const upload_progress = upload.querySelector('.upload_progress')
const upload_value = upload.querySelector('.value')

// 1. 点击上传图片
upload_select.addEventListener('click', function () {
 upload_inp.click()
})

// 文件切片上传 max: 最大文件体积 count: 文件数量
const uploadFile = (file) => {
 let max = 2 * 1024
 let count =  Math.ceil(file.size / max)
 if (count >= 100) { 
   max = file.size / 100
   count = 100
 }
 return {max, count}
}

// 将文件进行切片
const chunk = ({file, max, count, HASH, suffix}) => {
 const chunk = []
 for (let i = 0; i < count; i++) {
   chunk.push({
     file: file.slice(max * i, max * (i + 1)),
     filename: `${HASH}_${i + 1}.${suffix}`
   })
 }
 return chunk
}

// 拿到hash 值文件名
const getHASH = (file) => {
 return new Promise(reslove => {
   const fileReader = new FileReader()
   fileReader.readAsArrayBuffer(file)
   fileReader.onload = (e) => {
     const spark = new SparkMD5.ArrayBuffer()
     spark.append(e.target.result)
     const HASH = spark.end()
     const suffix = file.name.slice(file.name.indexOf('.'))
     reslove({
       HASH,
       suffix,
       file
     })
   }
 })
}

// 进度管控
const compolete = async (i, count, HASH) => {
 if (i < count - 1) {
   upload_value.style.width = `${i / count * 100}%`
   return
 }
 // 走到这一步 说明 文件上传完成 发送合并切片请求
 upload_value.style.width = `100%`
 try {
   const res = await instance.post('/upload_merge', {
     HASH: HASH,
     count
   },{
     headers: {
       'Content-Type': 'application/x-www-form-urlencoded'
     } 
   })
   if (res.code !== 0) throw res.codeText
   alert('上传切片成功')
 } catch (err) {
   alert('合并切片失败')
 }
 clear()
}

const clear = () => {
 upload_progress.style.display = 'none'
 upload_value.style.width = '0'
 upload_select.classList.remove('loading')
}
// 2. 文件上传 change
upload_inp.addEventListener('change', async function (e) {
 const file = e.target.files[0]
 if (!file) return
 upload_select.classList.add('loading')
 let fileList = [] // 断点上传
 upload_progress.style.display = 'block'
 
 // 拿到文件对象的hash 名
 const {HASH, suffix} = await getHASH(file)

 // 2.1 优先获取已经上传的切片 fileList: []
 try {
   const res = await instance.get('/upload_already', {
     params: {
       HASH: HASH
     }
   })
   if (res.code !== 0) throw res.codeText
   console.log(res)
   fileList = res.fileList
 } catch (err) {
   alert('获取切片失败')
 }

 // file 文件对象有一个方法 slice 
 const {max, count} = uploadFile(file)
 // console.log(max, count)
 // 拿到文件切片对象
 const chunkArr = chunk({file, max, suffix, HASH, count})
 // return
 console.log(chunkArr)
 console.log(fileList)
 chunkArr.forEach(async (item, index) => {
   // 判断是否是断点上传
   if (fileList.length > 0 && fileList.includes(item.filename)) return compolete(index, count, HASH)
   const fd = new FormData()
   fd.append('file', item.file)
   fd.append('filename', item.filename)
   try {
     const res = await instance.post('/upload_chunk', fd)
     if (res.code !== 0) throw '错误'
     // 管控进度
     compolete(index, count, HASH)
   } catch (err) {
     clear()

   }
 })

})

})()