需求:使用element的upload组件,上传文件到阿里云服务器。上传前,先请求自己后台的接口,返回阿里云上传相关的凭证key等信息后才能正确上传。
那如何解决呢?
答案是element上传组件中的 http-request 自定义上传。
<el-upload
ref="upload"
class="upload-demo"
action
:http-request="handleUpload"
:auto-upload="false"
multiple
:on-exceed="handleExceed"
:file-list="fileList"
:on-change="onChangeFile"
:on-remove="handleRemove"
:on-preview="handlePreview"
:before-remove="beforeRemove"
>
<el-button slot="trigger" :disabled="type !== 'view' ? false : true" size="small" type="primary">选取文件</el-button>
<el-button :disabled="type !== 'view' ? false : true" style="margin-left: 10px;" size="small" type="success" @click="submitUpload">上传到服务器</el-button>
</el-upload>
说明一下:
action设置为空,因为我们需要自己来控制上传。
:http-request=“handleUpload” 我们自己定义的上传方法。具体见后面。
:auto-upload=“false” 我关掉了选择文件后自动上传,改为手动触发。
:file-list=“fileList” 上传文件的文件列表
:on-change=“onChangeFile” 选择文件或是上传成功后都会触发,不仅仅是选择文件后触发。
其他看文档。没有什么好说的。
我在data中定义了两个文件列表相关的
data() {
return {
// 真实的附件列表,提交表单的时候,真实数据
realFileList: [],
// 上传文件列表,el-upload使用,临时保存数据。
fileList: [
// { name: 'aaa', url: 'image/196b62fd-fc70-4634-9bca'}
]}
}
fileList
是给upload组件绑定使用的,没有上传的文件都会在里面realFileList
是上传成功后传递后台的文件列表数据。
为啥要分开呢? 因为fileList中,不好控制。选择文件后,没有上传,fileList中是没有的。上传成功后,fileList才会有数据。点击删除的时候,已经上传的fileList还是会保留数据,
这个原因导致我需要维护两个realFileList和fileList属性。
定义的methods中相关的上传函数
methods: {
// 选择上传文件,上传成功后都会出发
onChangeFile(file, fileList) {
console.log('选择文件', file, fileList)
if (file.response) {
// this.fileList.push(file.response)
// 上传成功后,把成功的结果添加到realFileList中
this.realFileList.push(file.response)
}
},
// 自定义上次操作
handleUpload(op) {
// console.log('上传', op)
// op是element返回的上传相关的东西,通过它可以操作很多东西,进度条什么的,可以打印出来看,有很多回调方法,
// 这里的upload是我自己的封装的上传处理函数
myupload(
op.file,
res => {
let temp = {
name: res.attachment,
url: res.aliyunAddress
}
// 成功后的回调,把结果返回,并且把上传列表的状态改变,打上成功的√
op.onSuccess(temp)
},
err => {
console.log(err)
},
res => {
// 处理进度条的东西,把进度数据传进去实现进度条走动,传参res应该是这样的类型{percent: 48}
op.onProgress(res)
}
)
},
// 监听移除文件列表
handleRemove(file, fileList) {
// 删除的是未上传的文件
console.log('删除文件', file, fileList)
if (file.status === 'success') {
this.realFileList.map((v, i, arr) => {
if (v.url === file.url || v.url === file.response.url) {
arr.splice(i, 1)
}
})
}
},
// 预览(下载文件)
handlePreview(file) {
// console.log(file)
// 没有上传成功,不可以预览
if (file.status !== 'success') {
return
}
let fileurl = file.url || file.response.url // 前者是获取详情回来的文件,后者是刚刚新上传成功的文件
window.open(fileurl )
},
handleExceed(files, fileList) {
this.$message.warning(`当前限制选择 3 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`)
},
beforeRemove(file) {
return this.$confirm(`确定移除 ${file.name}?`)
}
upload
函数是我自定义的,使用axios实现的。具体内容如下:
// myupload.js
/*
* @Description: 附件上传
* @Autor: bolingsun
* @Date: 2020-09-09 10:03:43
*/
import { v4 as uuidv4 } from 'uuid'
// import { fetchUploadPara } from '@/api/upload'
import axios from 'axios'
let basePath
if (process.env.NODE_ENV === 'production') {
basePath = window.init_params.basePath
} else {
basePath = '/portal/pages/vue/undefined'
}
/**
* @description: 文件附件上传
* file: 文件raw对象
* successCallback: 成功的回调函数
* errCallBack: 错误的回调函数
* progressCallback: 上传进度的回调函数
*/
const myupload= function(file, successCallback = new Function(), errCallBack = new Function(), progressCallback = new Function()) {
let fileName = file.name
// options = options || {}
axios({
method: 'get',
url: basePath + '/aliyun/get',
params: {
dir: 'image'
}
})
.then(res => {
let obj = res.data.data
let config = {}
config.host = obj['host']
config.policyBase64 = obj['policy']
config.accessid = obj['accessId']
config.signature = obj['signature']
config.expire = parseInt(obj['expire'])
config.callbackbody = obj['callback']
config.dir = obj['dir']
let fd = new FormData(),
uuid = uuidv4(),
key = config.dir + uuid
fd.append('key', key)
fd.append('success_action_status', '200')
fd.append('x-oss-object-acl', 'public-read')
fd.append('x-oss-meta-fullname', fileName)
fd.append('OSSAccessKeyId', config.accessid)
fd.append('policy', config.policyBase64)
fd.append('signature', config.signature)
fd.append('success_action_status', '200')
fd.append('file', file)
if (config.host.indexOf('http:') > -1) {
var protocol = window.location.protocol || 'http:'
var subUrl = config.host.substring(5, config.host.length)
config.host = protocol + subUrl
}
axios({
url: config.host,
method: 'POST',
data: fd,
processData: false,
cache: false,
contentType: false,
onUploadProgress: function(progressEvent) {
if (progressEvent.lengthComputable) {
let percent = ((progressEvent.loaded / progressEvent.total) * 100) | 0
progressCallback({ percent: percent })
}
}
})
.then(() => {
let size = file.size > 1000000 ? parseFloat(file.size / 1000000).toFixed(2) + 'M' : parseFloat(file.size / 1000).toFixed(2) + 'KB'
successCallback({
attachment: fileName,
aliyunAddress: key,
size: size
})
})
.catch(err => {
errCallBack(err)
})
})
.catch(err => {
errCallBack(err)
// console.log(err)
})
}
export default myupload
具体内容大家就根据自己公司的情况去写具体的逻辑了。
我大概解释一下。
myupload= function(file, successCallback = new Function(), errCallBack = new Function(), progressCallback = new Function()) {}
四个参数, file传入的是文件对象,他上面有二进制数据的。successCallback 是成功后的回调,errCallBack 是失败后的回调,progressCallback 是进度条的回调。
上传进度条,相关的是这个内容:
axios({
url: config.host,
method: 'POST',
data: fd,
processData: false,
cache: false,
contentType: false,
// 这个是axios关于上传进度的配置,progressEvent是原生的上传进度对象
onUploadProgress: function(progressEvent) {
if (progressEvent.lengthComputable) {
let percent = ((progressEvent.loaded / progressEvent.total) * 100) || 0
progressCallback({ percent: percent })
}
}
})
在上传的过程中, 会触发progressEvent事件,我们把进度通过progressEvent.loaded / progressEvent.total
计算出来后,通过progressCallback
回调函数,把结果返回回去。
然后在element-ui upload的地方,使用。op.onProgress({ percent: percent })
这样就可以顺利显示进度条啦。
补充说明一下关于element-ui upload的相关属性:
onChangeFile 这个是,你选择了两个文件,就会触发两次,上传成功了,也会触发。
handleUpload 这个是,你选择了两个文件,两个文件会先后依次调用这个方法。选择几个文件,点击上传操作,就会触发调用几次这个函数
handleRemove 这个是,你删除了没有上传成功的文件,就是删除了,但是,你删除上传成功了的文件,列表显示是不见了,但是 绑定fileList内,还是有数据的,没有真的删除掉。
还有一点,这个上传的本质,就是new FormData 实例化一个表单对象,把文件二进制数据,以表单的形式提交而已。就这么回事。