赞
踩
vue 分布式上传文件vue-simple-uploader
"vue-simple-uploader": "^0.7.6",
main.js中
import uploader from 'vue-simple-uploader'
Vue.use(uploader)
<template> <!-- 分布式上传文件列表 --> <div class="files-wrapper"> <uploader ref="uploader" :options="options" :auto-start="false" :file-status-text="fileStatusText" class="uploader-example" @catch-all="onCatchAll" @upload-start="onUploadStart" @file-added="onFileAdded" @files-added="onFilesAdded" @file-complete="fileComplete" @files-submitted="filesSubmitted" @file-success="(rootFile, file, message) => onFileSuccess(rootFile, file, message)" @file-removed="onFileRemoved" @file-error="onFileError" @complete="complete"> <uploader-unsupport></uploader-unsupport> <uploader-drop> <div class="upload-title"> <el-breadcrumb separator-class="el-icon-arrow-right"> <el-breadcrumb-item @click.native="breadcrumb(item.path + '/')" class="breadcrumb-item" v-for="(item, index) in menuItem" :key="index"> {{ item.name }} </el-breadcrumb-item> </el-breadcrumb> <div class="upload-btn-group"> <uploader-btn class="uploader-upload-btn">上传文件</uploader-btn> <uploader-btn class="uploader-upload-btn" :directory="true">上传文件夹</uploader-btn> <el-tooltip effect="dark" content="下载文件缓慢时:请更换网络,或分次下载" placement="bottom"> <el-button type="primary" size="small" @click="downloadAll">批量下载</el-button> </el-tooltip> <!-- <el-button type="primary" size="small" @click="downloadAll">批量下载</el-button> --> <el-button type="primary" size="small" @click="deleteAll">批量删除</el-button> </div> </div> <div v-show="isShowDropUploadFileLists" class="drog_list"> <uploader-list> <div slot-scope="props" class="file-panel" :class="{ collapse: collapse }"> <div class="file-title"> <div class="title">文件列表</div> <div class="operate"> <el-button type="text" :title="collapse ? '展开' : '折叠'" @click="collapse = !collapse"> <i :class="collapse ? 'el-icon-full-screen' : 'el-icon-minus'" /> </el-button> <el-button type="text" title="关闭" @click="closeFilesUploadList"> <i class="el-icon-close" /> </el-button> </div> </div> <ul class="file-list"> <!-- <div> --> <div v-for="(file, i) in props.fileList" :key="i" class="file-info"> <uploader-file :list="true" :file="file"> </uploader-file> <span>{{ timeConversion(file._lastProgressCallback) }}</span> </div> <div v-if="!props.fileList.length" class="no-file"> <i class="iconfont icon-empty-file" /> 暂无待上传文件 </div> </ul> </div> </uploader-list> </div> <ul class="file-group"> <li v-for="(item, index) in dataList" :key="index" :class="[item['active'] ? 'file-item-active' : 'file-item']" @dblclick="openFolder(item)"> <img :src="getImageUrl(item.isDir)" :class="item.isDir ? 'folderIcon' : 'fileIcon'" /> <el-tooltip effect="dark" :content="item.fileName" placement="bottom"> <span>{{ item.fileName }}</span> </el-tooltip> <i class="el-icon-check" v-if="item['active']"></i> <el-tooltip effect="dark" content="下载文件缓慢时:请更换网络,或分次下载" placement="right"> <div class="download-btn" v-if="showDownIcon" @click="handleDownload(item)" /> </el-tooltip> <div v-if="item.fileName !== 'coms_01' && item.fileName !== 'coms_02'" class="remove-btn" @click="handleRemove(item.objectName, item.size, item)" /> <div class="file-mask" @click="selectFiles(item, index)"></div> </li> </ul> </uploader-drop> </uploader> </div> </template> <script> import * as api from '@/configs/api' export default { name: 'files', data() { return { modelName: '数据', menuItem: [], dataList: [], // 文件 folderImg: '/static/img/application/folder.png', fileImg: '/static/img/application/file.png', options: { // 目标上传 URL,可以是字符串也可以是函数,如果是函数的话,则会传入 Uploader.File 实例、 // 当前块 Uploader.Chunk 以及是否是测试模式,默认值为 '/' target: function (file, chunkFile, mode) { // 分块上传前每次都会进入到该方法 const key = chunkFile.offset; // 键值 用于获取分块链接URL return file.chunkUrlData[key]; }, // 为每个块向服务器发出 GET 请求,以查看它是否已经存在。如果在服务器端实现, // 这将允许在浏览器崩溃甚至计算机重新启动后继续上传。(默认: true) testChunks: false, // 分块时按照该值来分。最后一个上传块的大小是可能是大于等于1倍的这个值但是小于两倍的这个值大小, // 可见这个 Issue #51,默认 1*1024*1024。 chunkSize: 5 * 1024 * 1024, // 强制所有块小于或等于 chunkSize。否则,最后一个块将大于或等于chunkSize。(默认: false) forceChunkSize: true, // 服务器分片校验函数 秒传及断点续传的基础(true:不用传 false:需要传) // checkChunkUploadedByResponse: (chunk, message) => { // 这里根据实际业务来 用来判断哪些片已经上传过了 不用再重复上传了 [这里可以用来写断点续传!!!] // return false // }, // 包含在带有数据的多部分 POST 中的额外参数。这可以是一个对象或一个函数。如果是一个函数, // 它将被传递一个 Uploader.File、一个 Uploader.Chunk 对象和一个 isTest 布尔值(默认值{}:) query: function (file, chunkFile, mode) { const data = { partNumber: chunkFile.offset + 1 }; return data; }, uploadMethod: "PUT", // 当上传的时候所使用的是方式,可选 multipart、octet,默认 multipart,参考 multipart vs octet。 // MiniO 的分片不能使用表单 method: "octet", // 处理请求参数,默认 function (params) {return params},一般用于修改参数名字或者删除参数。0.5.2版本后, processParams: function (params) { return {}; }, // headers: { // 'Content-Type': 'binary/octet-stream' // } }, fileStatusText: { success: "上传成功", error: "上传失败", uploading: "上传中", paused: "暂停中", waiting: "等待中", }, isShowDropUploadFileLists: false, collapse: false, chunkSize: 5 * 1024 * 1024, // 切片大小(b) fileSetCode: null, // 修改时的数据集编码 // fileListVisible: false, // 分布式文件上传列表是否展示 filePath: '', dataSetCode: '', // 文件路径 showDownIcon: true, isAllCover: -1, // 是否选择全部替换(-1:未选择 0:否 1:是) timeOut: null, } }, props: { // 查询文件路径传参 filePathUrl: { type: String, required: true, default: '' }, // 存储类型:其他、论文 fileType: { type: String, required: true, default: '' }, metadataInfoCnId: { type: String, required: false } }, watch: { fileType: { handler(newVal, oldVal) { if (this.menuItem[0]) this.breadcrumb(this.fileType + '/' + this.menuItem[0]) console.log(1); }, deep: true }, }, mounted() { console.log('filePathUrl', this.filePathUrl); this.showDownIcon = this.$route.query.id ? true : false if (this.filePathUrl) { this.$emit('getCurrentDataSetCode', this.filePathUrl.split("/")[1]) this.breadcrumb(this.filePathUrl); } else { if (this.menuItem[0]) this.breadcrumb(this.fileType + '/' + this.menuItem[0]) } }, methods: { //时间戳转化成格式时间 timeConversion(te) { if (te == '') { return ''; } else if (te.length == 10) { var time = new Date(te * 1000); //时间戳为10位需*1000,时间戳为13位的话不需乘1000 var y = time.getFullYear(); var m = time.getMonth() < 9 ? '0' + (time.getMonth() + 1) : time.getMonth() + 1; var d = time.getDate() < 10 ? '0' + time.getDate() : time.getDate(); var h = time.getHours() < 10 ? '0' + time.getHours() : time.getHours(); var mm = time.getMinutes() < 10 ? '0' + time.getMinutes() : time.getMinutes(); var s = time.getSeconds() < 10 ? '0' + time.getSeconds() : time.getSeconds(); var timedate = y + '-' + m + '-' + d + ' ' + h + ':' + mm + ':' + s; return timedate; } else { var time = new Date(te); var y = time.getFullYear(); var m = time.getMonth() < 9 ? '0' + (time.getMonth() + 1) : time.getMonth() + 1; var d = time.getDate() < 10 ? '0' + time.getDate() : time.getDate(); var h = time.getHours() < 10 ? '0' + time.getHours() : time.getHours(); var mm = time.getMinutes() < 10 ? '0' + time.getMinutes() : time.getMinutes(); var s = time.getSeconds() < 10 ? '0' + time.getSeconds() : time.getSeconds(); var timedate = y + '-' + m + '-' + d + ' ' + h + ':' + mm + ':' + s; return timedate; } }, // 批量下载 downloadAll() { var pathArr = [] this.dataList.forEach(item => { if (item['active']) { pathArr.push(item) } }) if (pathArr.length == 0) { this.$message({ type: 'warning', message: '请先选择文件' }); } else { var formData = new FormData(); formData.append('datasetId', this.metadataInfoCnId) formData.append('downPaths', JSON.stringify(pathArr)) this.axios.post(api['collection'].downloadFiles, formData, { responseType: "blob" } ).then(response => { console.log(response); this.makeDownload(response, '压缩包'); }) } }, // 批量删除 deleteAll() { var deleteArr = [] this.dataList.forEach(item => { if (item['active']) { deleteArr.push(item.objectName) } }) if (deleteArr.length == 0) { this.$message({ type: 'warning', message: '请先选择文件' }); } else { console.log(deleteArr); this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { var formData = new FormData(); formData.append('filePaths', deleteArr) formData.append('type', 0) this.axios.post(api['collection'].deleteDistributedFiles, formData ).then(response => { let res = response.data if (res.code === 200) { this.getCatalog(this.filePath) } else { } }) }).catch(() => { this.$message({ type: 'info', message: '已取消删除' }); }); } }, /** * 执行下载操作 判断是否文件夹 * @param {*} value 下载项数据 */ handleDownload(value) { const query = this.$route.query if (query.id) { if (value.isDir) { this.downloadDirResult(value, this.metadataInfoCnId); } else { this.downloadFileResult(value, this.metadataInfoCnId); } } }, /** * 双击文件夹 * @param {*} value 文件夹参数 */ openFolder(value) { clearTimeout(this.timeOut); if (value.isDir) { this.breadcrumb(value.objectName); } }, /** * 单击文件或文件夹 * @param {*} value 文件参数 */ selectFiles(value, index) { clearTimeout(this.timeOut); this.timeOut = setTimeout(() => { this.dataList[index]['active'] = value.active ? !value.active : true var arr = this.dataList this.dataList = [] this.dataList = arr }, 600) // this.getCatalog(this.filePath,index,value.active ? !value.active : true) }, /** * 删除文件或文件夹 * @param {*} fileName 文件名称 * @param {*} isDir 是否文件夹 */ handleRemove(fileName, size, item) { this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { this.$emit('deleteDistributedFile', fileName) }).catch(() => { this.$message({ type: 'info', message: '已取消删除' }); }); }, /** * 下载文件 * @param {*} pathFile 文件路径 */ downloadFileResult(value, id) { var formData = new FormData(); formData.append('minioPath', value.objectName) formData.append('dataSize', value.size) formData.append('datasetId', id) this.axios.post(api['collection'].downloadSingle, formData ).then(response => { let res = response.data if (res.code === 200) { window.open(res.data); } else { } }) }, /** * 下载文件夹 * @param {*} value */ downloadDirResult(value, id) { //防止用户多次点击 this.dataListLoading = true; var formData = new FormData(); formData.append('minioPath', value.objectName) formData.append('dataSize', value.size) formData.append('datasetId', id) this.axios.post(api['collection'].downloadSingleDir, formData, { responseType: "blob" } ).then(response => { this.makeDownload(response, value.fileName); }) }, /** * 根据类型(文件或文件夹)显示图片 * @param {*} idDir */ getImageUrl(idDir) { return idDir ? this.folderImg : this.fileImg; }, /** * 文件夹路径 * @param {*} path */ breadcrumb(path) { this.filePath = path if (path !== "") { this.menuItem = []; if (path[path.length - 1] != '/') path = path + '/' var arr = path.split("/"); for (var i = 0; i < arr.length - 1; i++) { this.menuItem.push({ name: arr[i], path: arr.slice(0, i + 1).join("/"), }); } this.menuItem.splice(0, 1); } else { this.menuItem = []; } this.getCatalog(path); }, //扁平化数据处理 dataTree(data) { let result = []; for (const item of data) { result.push(item); if (item.children === null || item.children === undefined) { continue; } let getChildren = dataTree(item.children); result = result.concat(getChildren); } return result; }, getCatalog(minioPath) { if (minioPath === '') minioPath = this.menuItem[this.menuItem.length - 1].path if (!minioPath) this.dataList = [] var formData = new FormData(); formData.append('url', minioPath) this.axios.post(api['collection'].getDfsFileList, formData ).then(response => { let res = response.data if (res.code === 200) { this.dataList = res.data.list } else { } }) .catch(function (error) { console.log(error); }); }, makeDownload(response, fileName) { let fileBlob = new Blob([response.data]); if (fileBlob.size <= 0) { return this.$notify({ title: "警告", message: "后台执行中请稍后下载", type: "warning", }); } let url = window.URL.createObjectURL(fileBlob); let link = document.createElement("a"); link.style.display = "none"; link.href = url; link.setAttribute("download", fileName + ".zip"); document.body.appendChild(link); link.click(); }, // 分布式上传文件 //未调用到 待测试用处 onCatchAll(event) { }, //开始上传时进入 onUploadStart(file) { }, onFileAdded(file) { file.minioPath = this.minioPath; if (file.getSize() >= 5 * 1024 * 1024 * 1024) { // 5G file.ignored = true; //文件校验,不符规则的文件过滤掉 return this.$message.error(file.name + "文件过大请处理完成后重新上传"); } this.isShowDropUploadFileLists = true; }, onFilesAdded(file, filelist) { }, // 根下的单个文件(文件夹)上传完成 fileComplete(rootFile) { this.$message({ message: rootFile.name + "上传成功!", type: "success", }); var index = this.filePath.lastIndexOf('/'); // this.breadcrumb(this.filePath.substring(0, index)) this.breadcrumb(this.filePath) if (rootFile.isFolder) { } else { } }, async filesSubmitted(files, fileList) { if (files.length === 0) { return this.$message.error("文件列表存在同名文件,请关闭文件列表后再试。"); } for (let file of files) { const path = this.filePath const name = '/' + file.relativePath const size = file.size var formData = new FormData(); formData.append('concurrentType', this.fileType) formData.append('fileName', path + name) formData.append('partCount', Math.ceil(size / this.chunkSize)) formData.append('fileSize', size) if (this.fileSetCode) { formData.append('datasetCode', this.fileSetCode) } let res = await this.submitFile(formData, file) } this.isAllCover = -1 }, // 文件提交 submitFile(formData, file) { return new Promise((resolve, reject) => { this.axios.post(api['collection'].uploadTestFile, formData ).then(response => { let res = response.data if (res.code === 200) { setTimeout(() => { file.chunkUrlData = res.data.ulist file.chunkUrlData.uploadId = res.data.uploadId file.path = res.data.fileName if (this.menuItem.length == 0) { this.filePath = this.fileType + '/' + res.data.datasetCode } else { this.filePath = this.menuItem[this.menuItem.length - 1].path } this.dataSetCode = res.data.datasetCode this.$emit('getCurrentDataSetCode', res.data.datasetCode) this.$emit('deletePath', res.data.fileName) // 存在重复文件 if (res.data.isExist) { if (this.isAllCover === 1) { // 已选择全部替换 this.startUpload(file, false) resolve() } else if (this.isAllCover === -1) { // 询问是否需要全部替换 this.$confirm('存在重复文件, 是否全部替换?', '提示', { confirmButtonText: '全部替换', cancelButtonText: '逐一替换', type: 'warning' }).then(() => { this.isAllCover = 1 this.startUpload(file, false) resolve() }).catch(() => { this.isAllCover = 0 // 选择逐一替换 this.$confirm(file.name + '已存在, 是否选择替换?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { this.startUpload(file, false) resolve() }).catch(() => { this.startUpload(file, true) resolve() }); }); } else if (this.isAllCover === 0) { this.$confirm(file.name + '已存在, 是否选择替换?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { this.startUpload(file, false) resolve() }).catch(() => { this.startUpload(file, true) resolve() }); } // this.startUpload(file, res.data.isExist) } else { this.startUpload(file, res.data.isExist) resolve() } }, 1000); } else { } }) .catch(function (error) { console.log(error); }); }) }, // 开始上传 startUpload(file, isExist) { if (isExist) { file.cancel(); } else { file.resume(); } }, // 单个文件上传成功 onFileSuccess(rootFile, file, message) { // 调用后台合并文件 if (file.chunks.length != 1) { // const fileName = file.relativePath; // 文件名 const uploadId = file.chunkUrlData.uploadId; // uploadId var formData = new FormData(); formData.append('objectName', file.path) formData.append('uploadId', uploadId) this.axios.post(api['collection'].completeUploadFile, formData ).then(response => { let res = response.data if (res.code === 200) { this.breadcrumb(this.filePath) } else { } }) .catch(function (error) { console.log(error); }); } else { } }, onFileRemoved(file) { }, // 文件上传失败 onFileError(file) { this.$message({ message: "上传失败,请重试!", type: "error", }); }, // 点击关闭按钮 closeFilesUploadList() { this.$confirm( this.$t("确定进行[关闭]操作?", { handle: this.$t("close") }), "待传任务将取消", { confirmButtonText: '确定', cancelButtonText: '取消', type: "warning", } ) .then(() => { this.uploaderRef.cancel(); this.isShowDropUploadFileLists = false; }) .catch(() => { }); }, // 上传完毕 complete() { }, }, computed: { // 获取上传文件实例 uploaderRef() { return this.$refs.uploader.uploader; }, }, } </script> <style scoped> .files-wrapper { width: 100%; height: 690px; position: relative; /* overflow: auto; */ /* background-color: #00bbff; */ } .uploader-example { width: 100%; height: 100%; } .uploader-drop { width: 100%; height: 100%; padding: 0 !important; background: #fff !important; } /* 路径标题 */ .upload-title { display: flex; flex-direction: row; justify-content: space-between; /* margin: 0px 40px 0px 40px; */ height: 40px; align-items: center; } .upload-title>div { font-size: 18px; /* font-weight: bold; */ } .breadcrumb-item { cursor: pointer; } */deep/ .el-breadcrumb__item .el-breadcrumb__inner { color: #25262b5c; font-weight: 500; } */deep/ .el-breadcrumb__item:last-child .el-breadcrumb__inner { color: #000; font-weight: bold; } */deep/ .el-breadcrumb__item { cursor: pointer; } .upload-btn-group { min-width: 200px; } .uploader-upload-btn { color: #fff !important; background-color: #409eff; border-color: #409eff; font-size: 12px; border-radius: 3px; /* padding: 7px 15px; */ margin: 5px 5px 5px 5px; outline: 0; font-weight: 500; } .uploader-upload-btn:hover { background: #66b1ff; border-color: #66b1ff; color: #fff; } /* 文件按钮 */ .btns { position: absolute; right: 0; top: 0; } /* 文件列表 */ .file-group { display: flex; flex-wrap: wrap; overflow-y: auto; max-height: 455px; padding: 10px; /* max-height: calc(100vh - 238px); */ } .file-item { background-color: #fff; cursor: pointer; } .file-item { width: 120px; position: relative; border-radius: 10px; -webkit-transition: background-color 0.3s ease; -o-transition: background-color 0.3s ease; transition: background-color 0.3s ease; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; display: -ms-flexbox; display: flex; -ms-flex-direction: column; flex-direction: column; -ms-flex-pack: start; justify-content: flex-start; -ms-flex-align: stretch; align-items: center; padding: 20px 0 0 0; margin: 5px 16px 10px; max-height: 160px; } .file-item>span:nth-child(2) { width: 100%; text-align: center; font-size: 14px; line-height: 1.5; color: #25262b; max-width: 100%; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; -o-text-overflow: ellipsis; text-overflow: ellipsis; overflow-wrap: break-word; margin-bottom: 2px; -webkit-transition: color 0.3s ease; -o-transition: color 0.3s ease; transition: color 0.3s ease; } .file-item>span:nth-child(3) { width: 100%; text-align: center; font-size: 12px; line-height: 1.6; color: #25262b5c; max-width: 100%; overflow: hidden; white-space: nowrap; -o-text-overflow: ellipsis; text-overflow: ellipsis; } .download-btn { display: block; z-index: 2; } .remove-btn { display: block; z-index: 2; } .file-item:hover { background: #f1f1f1; } .folderIcon { width: 115px; height: 90px; } .fileIcon { width: 90px; height: 90px; } .file-item:hover>.download-btn { position: absolute; right: 10px; top: 10px; width: 24px; height: 24px; background-color: #fff; border-radius: 4px; background: #fff url("/static/img/application/download.png") no-repeat; background-size: 80% 80%; background-position: center center; } .file-item:hover>.download-btn:hover { cursor: pointer; background-color: #eaeaea; } .file-item:hover>.remove-btn { position: absolute; left: 10px; top: 10px; width: 24px; height: 24px; background-color: #fff; border-radius: 4px; background: #fff url("/static/img/application/delete.png") no-repeat; background-size: 80% 80%; background-position: center center; } .file-item:hover>.remove-btn:hover { cursor: pointer; background-color: #eaeaea; } .file-mask { position: absolute; /* background-color: red; */ width: 100%; height: 100%; } /* 批量选择样式 */ .file-item-active { background-color: #eaeaea; cursor: pointer; text-align: center; } .file-item-active { width: 120px; position: relative; border-radius: 10px; -webkit-transition: background-color 0.3s ease; -o-transition: background-color 0.3s ease; transition: background-color 0.3s ease; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; display: -ms-flexbox; display: flex; -ms-flex-direction: column; flex-direction: column; -ms-flex-pack: start; justify-content: flex-start; -ms-flex-align: stretch; align-items: center; padding: 20px 0 0 0; margin: 5px 16px 10px; max-height: 160px; } .drog_list { position: fixed; position: absolute; z-index: 20; right: -25px; bottom: -30px; width: 620px; box-sizing: border-box; } .file-panel { background-color: #fff; border: 1px solid #e2e2e2; border-radius: 7px 7px 0 0; box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); } .file-panel.collapse>.file-title { background-color: #e7ecf2; } .file-panel.collapse>.file-list { height: 0; } .file-title { display: flex; height: 40px; line-height: 40px; padding: 0 15px; border-bottom: 1px solid #ddd; } .operate { flex: 1; text-align: right; } .operate>i { font-size: 18px; } .file-list { position: relative; height: 340px; overflow-x: hidden; overflow-y: auto; background-color: #fff; transition: all 0.3s; list-style: none; padding: 0 2%; font-size: 12px; } /* 文件列表 样式 */ .file-info /deep/ .uploader-file>.uploader-file-info>.uploader-file-name { width: 30%; } .file-info /deep/ .uploader-file>.uploader-file-info>.uploader-file-meta { width: 0%; } .file-info { position: relative; } .file-info>span { position: absolute; right: 10px; top: 15px; } .custom-status { position: absolute; top: 0; left: 0; right: 0; bottom: 0; z-index: 1; } .el-icon-check:before { color: #24ed19; position: absolute; top: 0; /* right: 0; */ left: 0; font-size: 17px; border: 1px solid #24ed19; /* border-radius: 40px; */ padding: 4px; } </style>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。