vue+Minio實(shí)現(xiàn)多文件進(jìn)度上傳的詳細(xì)步驟
背景
最近突然接到了一個(gè)產(chǎn)品的需求,有點(diǎn)特別,在這里給大家分享一下,需求如下
- 提交表單,同時(shí)要上傳模型資源
- 模型文件是大文件,要顯示上傳進(jìn)度,同時(shí)可以刪除
- 模型文件要上傳到服務(wù)器,表單數(shù)據(jù)同步到數(shù)據(jù)庫
- 同時(shí)要同步上傳后的模型地址到數(shù)據(jù)庫
- 后端使用Minio做文件管理
設(shè)計(jì)圖如下

一開始以為是一個(gè)簡(jiǎn)單的表單上傳,發(fā)現(xiàn)并不是,這是大文件上傳啊,但很快又發(fā)現(xiàn),不單單是上傳大文件,還有將文件信息關(guān)聯(lián)到表單。
基于這個(gè)奇葩的情況,我和后端兄弟商量了一下,決定使用如下方案
實(shí)現(xiàn)方案
分2步走
- 點(diǎn)擊上傳時(shí),先提交表單信息到數(shù)據(jù)庫,接著后端返回一個(gè)表單的id給我
- 當(dāng)所有文件上傳完成后,再調(diào)用另外一個(gè)服務(wù),將上傳完成后的地址和表單id發(fā)送給后端
如此,便完成了上面的需求
了解一下Mino
這里大家先了解一下Minio的js SDK文檔
里面有2個(gè)很重要的接口,今天要用到
| 一個(gè)是給文件生成用于put方法上傳的地址 |
|---|
![]() |
| 一個(gè)是獲取已經(jīng)上傳完成后的文件的get下載地址 |
|---|

實(shí)現(xiàn)步驟
這里是使用原生的 ajax請(qǐng)求進(jìn)行上傳的,至于為什么,后面會(huì)有說到
1.創(chuàng)建存儲(chǔ)桶
創(chuàng)建一個(gè)Minio上傳實(shí)例
var Minio = require('minio')
this.minioClient = new Minio.Client({
endPoint: '192.168.172.162', //后端提供
port: 9000, //端口號(hào)默認(rèn)9000
useSSL: true,
accessKey: 'Q3AM3UQ867SPQQA43P2F', //后端提供
secretKey: 'zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG'
});
this.userBucket = 'yourBucketName' //這里后端需要提供給你,一個(gè)存儲(chǔ)桶名字2.選擇文件
這里使用input標(biāo)簽選擇文件,點(diǎn)擊選擇文件的時(shí)候,調(diào)用一下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">單個(gè)文件的大小限制為128MB</i>
</el-form-item>selectFile() {
let inputDOM = this.$refs.uploadInput
inputDOM.click();
},接著就是對(duì)文件進(jìn)行格式化
//格式化文件并創(chuàng)建上傳隊(duì)列
getAndFormatFile(){
let files = this.$refs.uploadInput.files
const userBucket = this.userBucket
if(files.length > 6) {
this.$message({
message: `最大只能上傳6個(gè)文件`,
type: 'warning'
})
return
}
files.forEach((file, index) => {
if ((file.size / 1024 / 1024).toFixed(2) > 128) { //單個(gè)文件限制大小為128MB
this.$message({
message: `文件大小不能超過128MB`,
type: 'warning'
})
return
}
//創(chuàng)建文件的put方法的url
this.minioClient.presignedPutObject(userBucket, file.name, 24 * 60 * 60, (err, presignedUrl) => {
if (err) {
this.$message({
message: `服務(wù)器連接超時(shí)`,
type: 'error'
})
return err
}
let fileIcon = this.getFileIcon(file)
let fileUploadProgress = '0%' //文件上傳進(jìn)度
this.fileInfoList.push({
file, //文件
fileIcon, //文件對(duì)應(yīng)的圖標(biāo) className
fileUploadProgress, //文件上傳進(jìn)度
filePutUrl: presignedUrl, //文件上傳put方法的url
fileGetUrl: '', //文件下載的url
})
})
})
this.fileList = [...this.fileInfoList]
},3.創(chuàng)建上傳隊(duì)列
這里定義了一個(gè)創(chuàng)建文件上傳請(qǐng)求的方法,使用原生的XMLHttpRequest,它接受以下參數(shù)
- file:要上傳的文件
- filePutUrl:文件上傳的put方法地址
- customHeader: 自定義的頭信息
- onUploadProgress:文件上傳的進(jìn)度監(jiān)聽函數(shù)
- onUploaded:文件上傳完成的監(jiān)聽函數(shù)
- onError:文件上傳出錯(cuò)的監(jiān)聽函數(shù)
//創(chuàng)建上傳文件的http
createUploadHttp(config){
const {file, filePutUrl, customHeader, onUploadProgress, onUploaded, onError} = config
let fileName = file.name
let http = new XMLHttpRequest();
http.upload.addEventListener("progress", (e) => { //監(jiān)聽http的進(jìn)度。并執(zhí)行進(jìn)度監(jiān)聽函數(shù)
onUploadProgress({
progressEvent: e,
uploadingFile: file
})
}, false)
http.onload = () => {
if (http.status === 200 && http.status < 300 || http.status === 304) {
try {
//監(jiān)聽http的完成事件,并執(zhí)行上傳完成的監(jiān)聽函數(shù)
const result = http.responseURL
onUploaded({ result, uploadedFile: file})
} catch(error) {
//監(jiān)聽錯(cuò)誤
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實(shí)例
}4.開始上傳
//上傳文件到存儲(chǔ)桶
async handleUplaod(){
let _this = this
if(this.fileInfoList.length < 1) {
this.$message({
message: `請(qǐng)先選擇文件`,
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: `上傳失敗,請(qǐng)檢查服務(wù)`,
type: 'error'
})
return
}
}
//開始將模型資源上傳到遠(yuǎn)程的存儲(chǔ)桶
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) //創(chuàng)建http請(qǐng)求實(shí)例
this.httpQueue.push(httpInstance) //將http請(qǐng)求保存到隊(duì)列中
})
},
//更新對(duì)應(yīng)文件的上傳進(jìn)度
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里監(jiān)聽文件列表,當(dāng)所有的文件進(jìn)度都是100%時(shí),表示上傳完成,接著就可以同步文件信息
watch:{
fileInfoList: {
handler(val){
//1.3所有文件都上傳到存儲(chǔ)桶后,將上傳完成后的文件地址、文件名字同步后端
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: '上傳失敗,請(qǐng)檢查服務(wù)',
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.刪除文件
刪除文件時(shí)要注意
- 刪除本地的文件緩存
- 刪除存儲(chǔ)桶里面的文件
- 停止當(dāng)前文件對(duì)應(yīng)的http請(qǐng)求
//刪除文件,并取消正在文件的上傳
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)
},
//清空文件并取消上傳隊(duì)列
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 = []
},
//刪除遠(yuǎn)程文件
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.')
})
}
},完整代碼
這里的完整代碼是我直接從工程里拷貝出來的,里面用到了一些自己封裝的服務(wù)和方法 比如 后端的接口、AES解密、獲取Token、表單驗(yàn)證等
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個(gè)步驟
1.先將文件的基本表單信息上傳給后端,獲取文件信息的ID
2.然后將文件上傳存儲(chǔ)桶
3.等所有文件都上傳完成后,再將上傳完成后的文件信息傳遞給后端,注意,此時(shí)的請(qǐng)求頭要戴上第1步獲取的文件信息id
* @Date: 2/28/2022, 1:13:20 PM
* @LastEditDate: 2/28/2022, 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">單個(gè)文件的大小限制為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: '', //服務(wù)名稱
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隊(duì)列
allFileHasUpload: false, //是否完成上傳
modelSourceInfoId: '', //模型資源基本信息的id
}
},
watch:{
fileInfoList: {
handler(val){
//1.3所有文件都上傳到存儲(chǔ)桶后,將上傳完成后的文件地址、文件名字同步后端
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:{
//創(chuàng)建存儲(chǔ)桶
async initMinioClient(){
const { code, result, message } = AES.decryptToJSON(await getMinioConfig({}))
if(!result || code !== 200) {
this.$customMessage.error({message: '獲取存儲(chǔ)桶配置信息出錯(cuò)'})
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只能作為字符串變量傳入,不能作為其他變量的屬性或者函數(shù)返回值,屬于Minio的一個(gè)規(guī)定
this.minioClient.bucketExists(userBucket, (err)=> {
if (err && err.code == 'NoSuchBucket') {
this.minioClient.makeBucket(userBucket, 'us-east-1', (err)=> {
if (err) {
return console.log('創(chuàng)建存儲(chǔ)桶失敗', err)
}
// console.log('Bucket created successfully in "us-east-1".')
})
}else{
//console.log('存儲(chǔ)桶存在')
}
})
},
close(flag = false) {
this.$emit('close', flag)
//關(guān)閉彈窗時(shí),如果文件沒有上傳完成,則清空文件
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)
},
//清空文件并取消上傳隊(duì)列
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 = []
},
//刪除遠(yuǎn)程文件
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.')
})
}
},
//格式化文件并創(chuàng)建上傳隊(duì)列
getAndFormatFile(){
let files = this.$refs.uploadInput.files
const userBucket = this.userBucket
if(files.length > 6) {
this.$message({
message: `最大只能上傳6個(gè)文件`,
type: 'warning'
})
return
}
files.forEach((file, index) => {
if ((file.size / 1024 / 1024).toFixed(2) > 128) { //單個(gè)文件限制大小為128MB
this.$message({
message: `文件大小不能超過128MB`,
type: 'warning'
})
return
}
//創(chuàng)建文件上傳的url并格式化文件信息
this.minioClient.presignedPutObject(userBucket, file.name, 24 * 60 * 60, (err, presignedUrl) => {
if (err) {
this.$message({
message: `服務(wù)器連接超時(shí)`,
type: 'error'
})
return err
}
let fileIcon = this.getFileIcon(file)
let fileUploadProgress = '0%'
this.fileInfoList.push({
file, //文件
fileIcon, //文件對(duì)應(yīng)的圖標(biāo) className
fileUploadProgress, //文件上傳進(jìn)度
filePutUrl: presignedUrl, //文件上傳put方法的url
fileGetUrl: '', //文件下載的url
})
})
})
this.fileList = [...this.fileInfoList]
},
//1.上傳文件到存儲(chǔ)桶
async handleUplaod(){
let _this = this
if(this.fileInfoList.length < 1) {
this.$message({
message: `請(qǐng)先選擇文件`,
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: `上傳失敗,請(qǐng)檢查服務(wù)`,
type: 'error'
})
return
}
}
//1.2開始將模型資源上傳到遠(yuǎn)程的存儲(chǔ)桶
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更新對(duì)應(yīng)文件的上傳進(jìn)度
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: '上傳失敗,請(qǐng)檢查服務(wù)',
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))
},
//獲取文件類型圖標(biāo)class
getFileIcon(file) {
const { type } = file
let icon = ''
return icon
},
//創(chuàng)建上傳文件的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>源碼分享
其實(shí)一開始我是想提供一個(gè)demo的,發(fā)現(xiàn)這個(gè)東西與產(chǎn)品功能強(qiáng)綁定,沒有測(cè)試的服務(wù)地址和存儲(chǔ)桶,也無法做出一個(gè)開放的案例
所以上面的代碼只是給大家提供一種實(shí)現(xiàn)方式和思路,里面具體的細(xì)節(jié)處理我做的比較復(fù)雜
總結(jié)
到此這篇關(guān)于vue+Minio實(shí)現(xiàn)多文件進(jìn)度上傳的文章就介紹到這了,更多相關(guān)vue+Minio多文件進(jìn)度上傳內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue3.x對(duì)echarts的二次封裝之按需加載過程詳解
echarts是我們后臺(tái)系統(tǒng)中最常用的數(shù)據(jù)統(tǒng)計(jì)圖形展示,外界對(duì)它的二次封裝也不計(jì)層數(shù),這篇文章主要介紹了vue3.x對(duì)echarts的二次封裝之按需加載,需要的朋友可以參考下2023-09-09
Vue實(shí)現(xiàn)動(dòng)態(tài)路由導(dǎo)航的示例
本文主要介紹了Vue實(shí)現(xiàn)動(dòng)態(tài)路由導(dǎo)航的示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02
vue使用axios跨域請(qǐng)求數(shù)據(jù)問題詳解
這篇文章主要為大家詳細(xì)介紹了vue使用axios跨域請(qǐng)求數(shù)據(jù)的問題,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10
VueX瀏覽器刷新如何實(shí)現(xiàn)保存數(shù)據(jù)
這篇文章主要介紹了VueX瀏覽器刷新如何實(shí)現(xiàn)保存數(shù)據(jù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07
vue quill editor 使用富文本添加上傳音頻功能
vue-quill-editor 是vue項(xiàng)目中常用的富文本插件,其功能能滿足大部分的項(xiàng)目需求。這篇文章主要介紹了vue-quill-editor 富文本添加上傳音頻功能,需要的朋友可以參考下2020-01-01


