123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500 |
- <template>
- <el-upload ref="uploadRef" class="hc-file-upload-box" :headers="getTokenHeader()" :data="uploadData" :disabled="uploadDisabled" multiple :limit="limit" :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 {isFileSize, deepClone, getObjValue} from "js-fast-way"
- 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
- },
- limit: {
- type: Number,
- default: 10
- }
- })
- //变量
- 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`);
- window?.$message?.warning(`每次请不超过 ${props.limit} 个文件同时上传`);
- }
- //上传中
- 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 = 10,
- 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>
|