一、背景
最近项目中用到图片上传(含预览、删除)功能。于是前端vue3采用饿了么团队的element-plus的upload文件上传组件。按照官网的教程,刚开始都很顺利。多说无益,直接进入重点,项目需求:组卷系统的题目修改功能,包含试题内容、答案、解析、插图的编辑,其中的图片编辑功能功能比较麻烦。
难点:试题原先可能有插图,在修改时,需要删除、插入新图片功能,界面如下
图1 父组件点击编辑按钮,调用子组件
图2 子组件上传图片(可多张)
本文主要用到图2,图2的页面是图1点击编辑按钮后,打开图2的对话框(用到dialoge组件从隐藏切换到显示,即visiable=true)
二、踩过的坑
先看下官网的demo示范
实现关键代码如下(省略部分代码,详情自己看官网):
<template>
<el-upload
v-model:file-list="fileList"
list-type="picture-card"
……
>
</template>
<script lang="ts" setup>
const fileList = ref<UploadUserFile[]>([
{
name: 'food.jpeg',
url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100',
},
{
name: 'plant-1.png',
url: '/images/plant-1.png',
},
……
])
……
</script>
关于fileList的解释,官网很少。其中发现fileList的内容是写死的。是一个特殊的数组,其元素至少由name和url两部分构成的字典。 刚开始按照官网demo非常顺利实现图片预览、插入、删除功能,因为在测试的时候的case中试题原先没有插图。
卡脖子事件
当遇到试题的插图原先就有图片,则需要先把原先的图片加载进来预览,并作为后面的删除、插入依据。
于是尝试方案1(失败)
我在vue组件的全局变量fileList直接赋值,关键代码如下:
<script setup>
const props = defineProps({
type: String,
father_method : Function,
question: Object,
});
// 把图片images列表中的URL,转换成字典{name,url}
const convertImage=(images)=>{
// console.log("待转换的图片列表",images)
if(images.length>0){
var pattern=/\d+\.(\w+){3,4}/g
let images_url_temp =[]
for(let item of images){
var image_name=item.match(pattern).toString()
images_url_temp.push({name:image_name,url:item})
}
return images_url_temp
}else{
return Array()
}
}
let fileList=convertImage(props.question.images) #关键代码
</script>
其中question是上个页面传递过来的试题内容,从上个页面传过来的images数组只包含图片的url,如http://xx/123.jpg,关键语句是,经过自定义函数convertImage,转换成upload要求的格式。
let fileList=convertImage(props.question.images)
其作用是模仿官网的demo,给fileList赋初始值。
结果报错误, fileList中虽然已经赋值,但是界面中不显示原先的图片(还是没搞清楚原因,知道原因的请在评论区告诉我,非常感谢)
方案2(失败)
在onMouted中加载fileList的值
onMounted(() => {
fileList=convertImage(props.question.images)
console.log("初始化:",fileList)
})
结果还是没成功,忘记错误提示是什么了。 尝试了其他所有的已知能力范围内方案都不行,废了2天时间。(此时我快气到爆炸) 最后去百度上疯狂的学习upload的文章,最后终于在一篇文章中得到启发。(具体内容略过,自己点链接去看) 标题:封装el-upload
方案3(失败)
方法就是把file-list直接绑定到defineProps中传递的值fileList
<template>
<el-upload
v-model:file-list="fileList"
list-type="picture-card"
……
>
</template>
<script setup>
const props = defineProps({
type: String,
father_method : Function,
question: Object,
});
……
</script>
方案4:封装upload组件并浅拷贝(成功了)
先把子组件中的文件上传组件(el-upload)封装起来,再引入该封装的组件,文件名叫UploadImage.vue: 其中关键代码是:
const initFileList=computed(()=>{
return props.fileList
})
watch(initFileList,(newVal)=>{
console.log("uploadimage中监视",newVal)
fileLists.splice(0,fileLists.length)
for(let item of newVal){
fileLists.push(item)
}
})
大致思路就是把props.fileList的内容浅拷贝给绑定upload组件图片的fileLists 封装文件上传的UploadImage.vue完整代码如下,
<template>
<div>
<el-upload
ref="upload"
name="images"
:action="props.action"
list-type="picture-card"
accept=".jpg,.jpeg,.png"
:on-success="handleSuccess"
:file-list="fileLists"
:headers="{token:token}"
:limit="props.limit"
:on-remove="handleRemove"
:on-preview="handlePictureCardPreview"
>
<el-icon><Plus /></el-icon>
</el-upload>
<el-dialog v-model="zoomInDialogVisible">
<img w-full :src="dialogImageUrl" alt="Preview Image" />
</el-dialog>
</div>
</template>
<script setup>
import { localGet } from '@/utils'
import { reactive, ref, watch, computed } from 'vue'
import { Delete, Download, Plus, ZoomIn, EditPen } from '@element-plus/icons-vue'
import { genFileId } from 'element-plus'
const props = defineProps({
limit:{
type:Number,
default:5,
},
action:{
type:String,
default:'#'
},
fileList:{
type:Array,
default: ()=>{
return []
}
},
father_method:Function
});
const upload = ref(null)
const disabled = ref(false)
const emit = defineEmits(['father_method']);
let dialogImageUrl = ref('https://element-plus.org/images/element-plus-logo.svg');
let zoomInDialogVisible = ref(false);
let editURLDialogVisible=ref(false)
let fileLists=ref(props.fileList);
fileLists=props.fileList
const dialogVisible = ref(false)
let editForm=reactive({
url:'',
uid:null
})
const editUrlDialogVisible=ref(false)
const token=ref(localGet('token') || '')
const initFileList=computed(()=>{
return props.fileList
})
watch(initFileList,(newVal)=>{
console.log("uploadimage中监视",newVal)
fileLists.splice(0,fileLists.length)
for(let item of newVal){
fileLists.push(item)
}
})
// 移除图片
const handleRemove=(file,fileList)=> {
console.log("被删除的文件是:",file)
console.log("所有文件:",fileList)
submitFileToFather()
}
const handlePictureCardPreview=(uploadFile)=>{
console.log("我要放大预览啦", uploadFile.url)
dialogImageUrl.value = uploadFile.url;
zoomInDialogVisible.value = true;
}
const handleSuccess=(response,file,fileList)=>{
// let obj = {
// name: file.name,
// status: "success",
// uid: file.uid,
// url: file.url,
// }
// fileLists.value.push(obj)
fileLists=fileList
submitFileToFather()
}
// 将图片文件传回给父组件
const submitFileToFather=()=>{
// console.log("我要发送给父组件啦",fileLists)
if(props.father_method)props.father_method(fileLists)
}
// 用户选择超过规定数量的图片
const handleExceed = (files) => {
alert("文件超出最大数量")
}
const modifyFileList=(fileList)=>{
fileLists = fileList
}
// 将修改fileList的方法暴露出去
defineExpose({ modifyFileList })
</script>
<style scoped>
</style>
子组件调用该封装文件,关键代码如下:
<el-form label-width="100px">
<el-form-item label="图片:">
<UploadImage :limit="5" :fileList="state.fileList" :action="state.uploadImgServer" :father_method="getImageList"></UploadImage>
</el-form-item>
</el-form>
最后还是有个bug,每次上传、删除图片后,下一次打开其他的试题(图1),编辑图片时,初始状态图片还是上次的编辑后的值。哪位大哥知道原因的请告诉我,非常感谢。 第1次写文章,不知道在哪里上传源代码文件,知道的大哥请告诉我,栓Q