Vue+ElementUI实现复制粘贴上传图片功能
背景
前后端分离框架
前端:Vue-element-admin
后端:Spring框架
场景:在保留原有组件的上传功能时,新增一个通过复制粘贴上传图片的功能。并且该按钮是在列表中,每一个列都会有一个上传按钮。
在查阅博客后未找到直接实现该功能的代码,踩了一些坑,记录一下。
实现过程
现有项目使用的是Element-ui最简单的通过按钮上传图片,支持基础的预览、删除等功能。
原始代码:
<el-table-column label="上传附件" width="130">
<template slot-scope="scope">
<el-upload
:action="uploadUrl"
:on-preview="handlePreview"
:before-remove="beforeRemove"
:on-remove="(res) => { return onRemove(res, scope.row) }"
:data="uploadData"
:multiple="true"
:limit="9"
:on-exceed="handleExceed"
:file-list="scope.row.compensateFiles"
:before-upload="handleUpload"
:name="'uploadFiles'"
:on-success="(res) => { return uploadSuccess(res, scope.row) }"
auto-upload
>
<el-button size="small" type="primary">上传</el-button>
</el-upload>
<el-dialog :visible.sync="picVisible" :append-to-body="true" @close="closePicView()">
<img width="80%" :src="dialogImageUrl" alt="">
</el-dialog>
</template>
</el-table-column>
改动后代码:
Vue代码:
<el-table-column label="上传附件" width="130">
<template v-slot="scope">
<el-upload
:ref="'upload' + scope.$index"
:action="uploadUrl"
:on-preview="handlePreview"
:before-remove="beforeRemove"
:on-remove="(res) => { return onRemove(res, scope.row) }"
:data="uploadData"
:multiple="true"
:limit="9"
:on-exceed="handleExceed"
:file-list="scope.row.compensateFiles"
:before-upload="handleUpload"
:name="'uploadFiles'"
:on-success="(res) => { return uploadSuccess(res, scope.row) }"
auto-upload
>
<el-button size="small" type="primary">上传</el-button>
<!-- 每个上传按钮对应一个隐藏的输入框 -->
<input
ref="pasteInputs"
type="text"
style="position: absolute; left: -9999px;"
@paste="(event) => handlePaste(event, scope.row, scope.$index)"
>
</el-upload>
<el-dialog :visible.sync="picVisible" :append-to-body="true" @close="closePicView()">
<img width="80%" :src="dialogImageUrl" alt="">
</el-dialog>
</template>
</el-table-column>
JS代码:
handlePaste(event, row, index) {
const me = this
const items = (event.clipboardData || window.clipboardData).items
for (const item of items) {
if (item.type.indexOf('image') !== -1) {
const file = item.getAsFile()
// 使用动态 ref 获取对应的 el-upload 组件实例
const uploadComponent = this.$refs['upload' + index]
// 手动触发上传
if (uploadComponent) {
const formData = new FormData()
formData.append('uploadFiles', file)
formData.append('traceId', this.form.refundSerialNo)
multipartUpload(formData).then(
(response) => {
if (response.success) {
if (Array.isArray(response.data.successFiles) && response.data.successFiles.length > 0) {
let rowCompensateFile
response.data.successFiles.forEach(file => {
var compensateFile = {
fileId: file.fileId,
name: file.fileName,
url: file.fileUrl,
passengerGuid: row.passengerGuid,
passengerName: row.passengerName
}
if (!Array.isArray(me.form.showCompensateItems[index].compensateFiles)) {
rowCompensateFile = []
} else {
rowCompensateFile = [...me.form.showCompensateItems[index].compensateFiles]
}
rowCompensateFile.push(compensateFile)
Vue.set(me.form.showCompensateItems[index], 'compensateFiles', rowCompensateFile)
})
}
if (Array.isArray(response.data.failFiles) && response.data.failFiles.length > 0) {
var failMsg = ''
// 失败提示
response.data.failFiles.forEach(failFile => {
failMsg += failFile.fileName
})
this.$message.warning(`上传失败,乘客姓名 ${row.passengerName} 失败文件 ${failMsg} `)
}
}
},
() => {
}
)
}
}
}
}
开发过程中遇到的问题
1.如何监听粘贴事件
在button后面新增一个隐藏的输入框,通过输入框的@paste事件监听粘贴
<input
ref="pasteInputs"
type="text"
style="position: absolute; left: -9999px;"
@paste="(event) => handlePaste(event, scope.row, scope.$index)"
>
2.如何手动触发上传
因为现在要求需要兼容原有的逻辑,而后端代码的接口协议无法改变,手动触发上传时需要以form-data的形式调用接口。
后端接口协议:
@PostMapping(value = "/multipartUpload")
public SingleResponse multipartUpload(FileUploadParam fileUploadParam)
Js代码:需要创建一个FormData对象通过append进行处理后进行调用,才能调通,否则会获取不到传参的值。
const formData = new FormData()
formData.append('uploadFiles', file)
formData.append('traceId', this.form.refundSerialNo)
multipartUpload(formData).then()
3.在列表中如何获取对应的组件实例,然后进行处理
在组件中的ref指定时使用index进行处理
<el-upload :ref="'upload' + scope.$index" />
获取实例
const uploadComponent = this.$refs['upload' + index]
4.改变了:file-list对应的值,但是没有在页面上渲染出来。
原始代码:直接修改compensateFiles的值时发现没有渲染到页面,一开始一直怀疑是值没改变,通过debug发现值是改变了,但是一直没有触发渲染。
if (!Array.isArray(row.compensateFiles)) {
row.compensateFiles = []
}
row.compensateFiles.push(compensateFile)
vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。对于已经创建的实例,Vue允许动态添加根级别的响应式 property。
改动后代码:通过Vue.set方法,能够实现实时渲染。
if (!Array.isArray(me.form.showCompensateItems[index].compensateFiles)) {
rowCompensateFile = []
} else {
rowCompensateFile = [...me.form.showCompensateItems[index].compensateFiles]
}
rowCompensateFile.push(compensateFile)
Vue.set(me.form.showCompensateItems[index], 'compensateFiles', rowCompensateFile)
5.数组的复制问题
在开发的过程中,发现对于js中数组的创建不太熟悉
此处我想实现的效果是原有没值时,就创建一个空的数组,如果有值,则直接进行赋值。忘记了compensateFiles本来就是一个数组,通过new Array是创建了嵌套数组。
原有代码:
if (!Array.isArray(me.form.showCompensateItems[index].compensateFiles)) {
rowCompensateFile = []
}else {
rowCompensateFile = new Array(me.form.showCompensateItems[index].compensateFiles)
}
改进后代码:
if (!Array.isArray(me.form.showCompensateItems[index].compensateFiles)) {
rowCompensateFile = []
} else {
rowCompensateFile = [...me.form.showCompensateItems[index].compensateFiles]
}