前言:
在之前写的一篇文章《Debug-022-el-upload照片上传-整体实现回顾》中回顾了整体的借助el-upload实现了照片上传的功能。现在业务中增加了一项需求,我们的表单一般是分为“新增页”和“编辑页”的,这里新需求希望可以在编辑页中实现对“新增页”上传过的图片进行二次编辑,也就是这个图片不再只是“一次性上传”就OK了。这个功能很快就实现了,在原来的基础上,等于就是在编辑页在实现一遍新增的功能,基本毫无差别。但是有一个致命的问题折腾了一天。
回显问题:
首先新增图片时,我们的图片是上传存储在阿里云的OSS上的,我们的图片信息不保存在后端,这一点没问题,毕竟需要占用很多内存。既然这样,前端进入编辑页就只能拿到最开始保存到后端的上传OSS之后拿到的imageUrl链接。
方案一:
思路就是:只要能让el-upload支持图片链接的回显就行,这是一开始就能想到的常规思路。这个也就造成了这一块的一个最大的难点,我想了很久,看了很多文档,没有找到合适的方案,就是el-upload其实是不支持回显照片链接的。
我模拟实现了一下,验证了我的直觉,直觉告诉我,upload要的是下面这段格式:
我通过父传子props的形式给编辑页传了这个文件对象file,果然在el-upload上回显成功。
可是我们不能在新增的时候把照片的文件流通过请求存到后端,之前说过了。所以这个方案是不成立的,只能pass。
方案二:
这个方案是我自己想的一种办法,就是我现在是能拿到图片链接的,思路就是:只要能实现通过这个图片链接,拿到这个图片的文件流形式,理论上就可以回显图片在el-upload上。我首先是去阿里云的OSS上查找,希望能找到相关的API方法。很遗憾,不行。然后是百度和请教AI工具,尝试了一些,还是没成功。好吧,果断放弃。
方案三:(成功方案)
这个思路其实是比方案二想到的要早,并且我其实知道这个思路是必然能行的,但是我还是最后没有办法了之后再去选择实现它,原因很简单,就是麻烦呗,如果能让el-upload直接回显上传过的图片,或者说方案一或二能实现,那我就不用走这条路了。先说思路吧
思路:(1)前端在对应的表单位置上准备两个元素,一个是el-image,另一个才是el-upload。
(2)二者的显示互斥,通过v-show="isHasImageUrl"字段控制(这里不能用v-if,因为这里是需要把它隐藏,但不能把它销毁)
(3)el-image负责回显使用照片链接回显照片,el-upload则负责上传图片,实现方式和新增的时候上传方式完全一样。
(4)有一点需要做的,也是比较麻烦的就是保证el-image显示上要和el-upload上传过图片的样式完全一致,这样就能实现以假乱真的效果。利用CSS,之后就是前端工程师的基本功了。
这样的话,就有两种情况:上传照片这一项在表单中不是必填项,新增的时候可以不必填,进入编辑页时,可能有照片,也可能没有照片。代码逻辑中我们只需要调用接口根据其中是否有图片链接判断即可,实际中使用定义过的isHasImageUrl字段。
(1)上传过照片,显示el-image(但是样式上会跟el-upload有文件的时候一样,不会被用户察觉出来)
(2)未上传过照片,直接显示el-upload即可,这种情况就跟“新增”本质没有任何区别了。
html部分代码截取:
<el-form-item label="安装照片:" prop="img">
<!-- 回显照片,判断详情接口中的ext字段中是否有照片链接 -->
<div v-show="isHasImageUrl" class="image-container">
<el-image
class="image-edit"
:src="props.data.ext?.imageUrl"
fit="cover"
/>
<div class="overlay">
<div
class="el-upload-list__item-preview"
@click="handleImgEnlarge"
>
<!-- 放大 -->
<svg-icon style="width: 22px;height: 22px;" name="device_img_enlarge" />
</div>
<div
class="el-upload-list__item-preview"
@click="handleImgDelete"
>
<!-- 删除 -->
<svg-icon style="width: 22px;height: 22px;" name="device_img_delete" />
</div>
<div
class="el-upload-list__item-preview"
@click="handleImgReplace"
>
<!-- 替换 -->
<svg-icon style="width: 22px;height: 22px;" name="device_img_replace" />
</div>
</div>
</div>
<el-upload
v-show="!isHasImageUrl"
ref="imgREF"
v-model:file-list="imgList"
action="#"
accept=".jpg, .jpeg, .png"
:limit="1"
list-type="picture-card"
:auto-upload="false"
:on-change="handleChangeImg"
:on-exceed="handleExceed"
class="image_upload"
>
<el-icon class="plusIcon">
<plus />
</el-icon>
<template #file="{ file }">
<div style="width: 100%;">
<el-image class="el-upload-list__item-thumbnail" :src="file.url" fit="cover" />
<div class="el-upload-list__item-actions image-flex">
<div
class="el-upload-list__item-preview"
@click="handleImgEnlarge"
>
<!-- 放大 -->
<svg-icon style="width: 22px;height: 22px;" name="device_img_enlarge" />
</div>
<div
class="el-upload-list__item-preview"
@click="handleImgDelete"
>
<!-- 删除 -->
<svg-icon style="width: 22px;height: 22px;" name="device_img_delete" />
</div>
<div
class="el-upload-list__item-preview"
@click="handleImgReplace"
>
<!-- 替换 -->
<svg-icon style="width: 22px;height: 22px;" name="device_img_replace" />
</div>
</div>
</div>
</template>
</el-upload>
</el-form-item>
CSS部分:
.image-flex{
display: flex;
gap: 5px;
align-items: center;
justify-content: center;
}
.image-container {
position: relative;
display: inline-block;
width: 120px; /* 和图片宽度一致 */
height: 120px; /* 和图片高度一致 */
border-radius: 5px;
img {
width: 120px;
height: 120px;
border: 1px solid #ccc;
border-radius: 5px;
object-fit: cover;
}
.overlay {
position: absolute;
top: 0;
left: 0;
display: flex;
gap: 5px;
align-items: center; /* 垂直居中 */
justify-content: center; /* 水平居中 */
width: 100%;
height: 100%;
background: rgba(0, 0, 0, .5); /* 黑色遮罩 */
border-radius: 6px;
opacity: 0;
transition: opacity .3s ease-in-out;
}
}
.image-container:hover .overlay {
opacity: 1;
}
.image_upload:hover .plusIcon {
color: #4b88ff;
}
//上传图片的方框大小
.el-upload--picture-card{
--el-upload-picture-card-size: 120px;
}
.el-upload-list--picture-card{
--el-upload-list-picture-card-size: 120px;
}
js部分:
基本和新增时候差不多,因为在编辑的时候用户是必然可以重新上传,删除和替换图片的。 这里只是要稍微调整一下代码,和新增有一点点区别,要控制好数据(变量)的更新和销毁,并且考虑更新isHasImageUrl的状态(有一点比较确定的是只有初次进来编辑页,如果有照片才显示el-image,一旦在编辑过程中删除或者替换过照片,之后就是显示的就全部是el-upload了,你可以细品一下,这里不赘述)
/// ///安装照片
const imgREF = ref<UploadInstance>()
const showViewer = ref(false)
const imgList = ref<any>([]) // 图片列表
const urlList = ref<UploadUserFile[]>([]) // 放大查看列表
const isHasImageUrl = ref<any>(false) // 判断详情的ext中是否存在照片链接,回显时显示el-image还是el-upload
onMounted(() => {
console.log('onMounted', 222222, props)
isHasImageUrl.value = !!props.data?.ext?.imageUrl
})
// 这里监听imgList图片的个数,看是否隐藏上传图片的默认入口
watchEffect(() => {
const elements = document.querySelectorAll('.el-upload--picture-card')
if (imgList.value.length) {
// 遍历所有匹配的元素并设置 display 为 none
elements.forEach((element) => {
element.style.display = 'none'
})
} else {
elements.forEach((element) => {
element.style.display = ''
})
}
})
// 上传图片-文件状态改变 用于判断图片类型和大小
function handleChangeImg(uploadFile:any) {
console.log('handleChangeImg', uploadFile, imgList.value)
// 图片内容,上传OSS
form.imgRaw = uploadFile.raw
const uuid = uuidv4()
// 存一份图片名称
form.imageName = `${uuid}-${uploadFile.name}`
const FILE_TYPES = [
'image/png',
'image/jpeg',
]
const rawFile = uploadFile.raw!
if (!FILE_TYPES.includes(rawFile.type)) {
imgREF.value!.clearFiles()
return $message.error('请上传jpg、png、jepg格式文件')
} else if (rawFile.size / 1024 / 1024 > 5) {
imgREF.value!.clearFiles()
return $message.error('图片不超过5MB')
} else {
imgList.value = [uploadFile]
}
console.log('finally', imgList.value)
}
// 当超出限制时,执行的钩子函数 这里用以替换照片的功能
function handleExceed(files:any) {
console.log('handleExceed', files)
const FILE_TYPES = [
'image/png',
'image/jpeg',
]
const rawFile = files[0]!
// 替换之前需要校验照片格式,但是这里先不删除原照片
if (!FILE_TYPES.includes(rawFile.type)) {
// imgREF.value!.clearFiles()
return $message.error('请上传jpg、png、jepg格式文件')
} else if (rawFile.size / 1024 / 1024 > 5) {
// imgREF.value!.clearFiles()
return $message.error('图片不超过5MB')
} else {
// 满足前两个条件,则删除原照片,上传新照片
imgREF.value!.clearFiles()
isHasImageUrl.value = false
const file = files[0] as UploadRawFile
file.uid = genFileId()
imgREF.value!.handleStart(file)
urlList.value = [rawFile]
console.log('finallyhandleExceed', imgList.value)
}
}
// 放大查看图片
function handleImgEnlarge() {
console.log('handleImgEnlarge', isHasImageUrl.value)
// ext中上传过照片,就取ext中的imageUrl,否则取新上传的图片链接
urlList.value = isHasImageUrl.value ? [props.data.ext?.imageUrl] : [imgList.value[0].url]
showViewer.value = true
}
// 删除图片
function handleImgDelete() {
isHasImageUrl.value = false
urlList.value = []
imgREF.value!.clearFiles()
}
// 替换图片
function handleImgReplace() {
onUploadImgLocal()
}
// 手动调取图片本地上传入口
function onUploadImgLocal() {
console.log('onUploadImgLocal')
const input = document.createElement('input')
input.type = 'file'
input.accept = '.jpeg, .png, .jpg' // 限制选择的文件类型为 .jpg, .png, .jpg
input.style.display = 'none'
document.body.appendChild(input)
input.click()
input.onchange = (e:any) => {
const file = e.target.files[0] // 获取文件对象
console.log('eeeeee', e, file)
handleExceed([file])
}
}
点击确认:还有就是多考虑一下用户确认的时候是重新上传了图片还是没编辑过图片,还是删除了之前的图片,记得认真考虑判断一下经过用户编辑过后“图片的状态”到底是如何??
function handleConfirm() {
matchEXT()
formRef.value!.validate(async (isValid) => {
if (!isValid) return
try {
// 先判断用户是否上传了图片
if (imgList.value.length) {
drawerRef.value.confirmIsLoading = true // 确认按钮loading
// OSS
const result = await client.put(`/device/install/imge_${form.imageName}`, form.imgRaw)
console.log('handleConfirm-oss111111111', result, params_ext.value)
if (params_ext.value) { // 有拓展字段的设备
params_ext.value.imageUrl = result.url
} else { // 没有拓展字段的设备
params_ext.value = {}
params_ext.value.imageUrl = result.url
}
} else {
// 回显后直接确定,那么不删除原来图片链接,直接使用ext,全部返回
if (isHasImageUrl.value) { // el-image
params_ext.value = props.data.ext
} else { // el-upload
// 编辑时,将图片置为空就删除以前的链接
if (params_ext.value) { // 有拓展字段的设备,这里只处理图片链接,别的字段不动
params_ext.value.imageUrl = ''
} else { // 没有拓展字段的设备置空
params_ext.value = {}
// params_ext.value.imageUrl = ''
}
}
}
const data = {
iotId: props.data.iotId,
nickName: form.nickName,
ext: params_ext.value,
}
drawerRef.value.confirmIsLoading = true // 确认按钮loading
await updateDeviceDetail(data).then(() => {
$message.success('操作成功')
emits('update')
drawerRef.value.confirmIsLoading = false
})
} catch (error) {
console.log('OSS-error', error)
// $message.error('新增失败,请重新上传至OSS')
} finally {
drawerRef.value.confirmIsLoading = false
// $message.error('配置失败,请重新上传至阿里云')
}
})
}
小结:
使用el-upload不论是上传图片还是文件,其实大同小异(不论新增或编辑),思想是一致的。以后实现上传文件和文件的回显也可以借鉴,当然,文件只需要准备一个文件名字去回显,存在后端也无所谓了。
备注:这里我其实有点偷懒了,没有详细拆解分析代码,对于新手同学可能不太友好,这对我来讲已经很清晰了,因为我已经写过好几遍了。不过可能对正在实现这个功能的朋友不太友好,不过如果你明白我的思路可以直接去尝试一下,如果有时间的话。
(有问题可以留言回复我!!!)