Debug-026-el-upload照片上传-编辑页回显照片并且支持再上传的实现方案

news2024/11/26 9:30:04

前言:

       在之前写的一篇文章《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不论是上传图片还是文件,其实大同小异(不论新增或编辑),思想是一致的。以后实现上传文件和文件的回显也可以借鉴,当然,文件只需要准备一个文件名字去回显,存在后端也无所谓了。

备注:这里我其实有点偷懒了,没有详细拆解分析代码,对于新手同学可能不太友好,这对我来讲已经很清晰了,因为我已经写过好几遍了。不过可能对正在实现这个功能的朋友不太友好,不过如果你明白我的思路可以直接去尝试一下,如果有时间的话。

(有问题可以留言回复我!!!)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2100490.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java项目: 基于SpringBoot+mysql大学生入学审核系统(含源码+数据库+开题报告+答辩PPT+毕业论文)

一、项目简介 本项目是一套基于SpringBootmysql大学生入学审核系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单、…

PAT (Advanced Level) Practice——1008,1009

1008&#xff1a; 难度&#xff1a;简单 题意&#xff1a; 我们城市最高的建筑只有一部电梯。N个数字表示电梯将按指定顺序停在哪些楼层。电梯上一层需要 6 秒&#xff0c;下一层需要 4 秒。电梯将在每个站点停留 5 秒。对于给定的N个数字&#xff0c;您将计算完成这些请求所…

前后端分离项目遇到的跨域问题解决方案(后端为主)

文章目录 什么是跨域问题&#xff1f;第一种方式 ⇒ 注解解决方案&#xff1a;第二种方式 ⇒ 使用 CorsFilter 方法解决&#xff1a;第三种方式 ⇒ 实现 WebMvcConfigure 接口&#xff0c;添加映射&#xff08;个人推荐&#xff09; 什么是跨域问题&#xff1f; 先说问题&#…

【软件流程】项目开发管理制度(Doc文件)

项目开发管理总体流程 一、总则 二、阶段成果 三、岗位设置 四、项目立项 五、项目计划与监控 六、需求分析 七、总体设计 八、详细设计 九、项目实现 十、项目测试 十一、用户培训 十二、系统上线 十三、系统验收 十四、产品维护 十五、源码和文档 十六、质量检…

Gitee镜像关联GitHub仓库

申请 GitHub 私人令牌 GitHub 私人令牌用于授予 Gitee 读写 Github 仓库的权限。 1&#xff09;登录GitHub&#xff0c;通过 个人头像 > Settings > 下拉左侧菜单栏进入 Developer settings。 2&#xff09;Personal access tokens > Tokens(classic) > Generate …

[数据集][目标检测]街道乱堆垃圾检测数据集VOC+YOLO格式94张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;94 标注数量(xml文件个数)&#xff1a;94 标注数量(txt文件个数)&#xff1a;94 标注类别数…

Spring Security 原理、源码解析及进阶

文章目录 参考文献 1、信息安全基础1. CIA 三要素2. Authentication Vs. Authorization3. RBAC模型 2、Spring Security1. Spring Security 简介2. Spring Security 架构核心2.1 FilterChain (Servlet)2.2 [DelegatingFilterProxy](https://docs.spring.io/spring-security/ref…

基于DPU与SmartNIC的K8s Service解决方案

1. 方案背景 1.1. Kubernetes Service介绍 Kubernetes Service是Kubernetes中的一个核心概念&#xff0c;它定义了一种抽象&#xff0c;用于表示一组提供相同功能的Pods&#xff08;容器组&#xff09;的逻辑集合&#xff0c;并提供了一种方式让这些Pods能够被系统内的其他组…

5、Django Admin后台移除“删除所选”操作

默认情况下&#xff0c;Django Admin后台的listview模型列表页&#xff0c;会有一个Delete Selected删除所选操作。假设你需要再从Hero管理模型中移除该删除操作。 ModelAdmin.get_actions方法可以返回所有的操作方法。通过覆盖此方法&#xff0c;移除其中delete_selected方法…

uni-app 获取当前位置的经纬度以及地址信息

文章目录 uni.getLocation(objc)获取经纬度和地址调试结果问题 uni-app 获取当前位置的经纬度以及地址信息 uni.getLocation(objc) uni-app官方文档定位API: uni.getLocation(OBJECT) uni.getLocation({type: wgs84,success: function (res) {console.log(当前位置的经度&…

Easy-Paas 功能演示

1.登录 http://192.168.11.10:8100/web 账号&#xff1a;admin 密码&#xff1a;admin 2.主页

阿里巴巴商品搜索API返回值中的图片与详情链接

在使用阿里巴巴&#xff08;Alibaba&#xff09;的商品搜索API时&#xff0c;通常返回的JSON或其他格式的数据中会包含商品的相关信息&#xff0c;如标题、价格、图片链接、详情页链接等。这些链接&#xff08;特别是图片和详情页的链接&#xff09;通常设计为直接可用的URL&am…

buildroot构建根文件系统

目录 嵌入式系统1. Bootloader&#xff08;引导程序&#xff0c;如U-Boot&#xff09;2. Linux内核3. 根文件系统 根文件系统使用buildroot编译文件系统Buildroot目录简介选择配置文件Buildroot其他分析 嵌入式系统 嵌入式系统的三大部分&#xff1a;bootloader&#xff08;如…

Ceph RBD使用

CephRBD使用 一、RBD架构说明二、RBD相关操作1、创建存储池2、创建img镜像2.1 创建镜像2.1.2 查看镜像详细信息2.1.3 镜像其他特性2.1.4 镜像特性的启用和禁用 3、配置客户端使用RBD3.1 客户端配置yum源3.2 客户端使用admin用户挂载并使用RBD3.2.1 同步admin账号认证文件3.2.2 …

安数云助力某省移动部署全省云安全资源池

随着云计算技术的拓展&#xff0c;安全运维的兴起以及5G新技术的试点应用&#xff0c;传统烟囱式产品堆砌的安全解决方案已难以满足各新兴业务的安全保障需求。用户大部分业务逐步迁移到了云上&#xff0c;所有资源都以虚拟化的形态存在&#xff0c;以达到集中式的管理。 云安全…

MIT6.S081最详解析与归纳——lab11:network driver

Lab11主题&#xff1a;network driver &#xff08;一&#xff09;xv6网络协议栈&#xff08;二&#xff09;networking&#xff08;1&#xff09;e1000_transmit&#xff08;2&#xff09;e1000_recv &#xff08;三&#xff09;完结感想 &#xff08;一&#xff09;xv6网络协…

股指期货交割手续费怎么算?

股指期货交割手续费是投资者在股指期货合约到期进行交割时必须支付的费用&#xff0c;主要用于覆盖交易所和期货公司的运营成本。其计算方式与开仓、平仓手续费相似&#xff0c;但标准有所不同。#股指期货# 要熟悉股指期货的保证金和手续费计算&#xff0c;可以遵循以下公式&a…

浏览器百科:网页存储篇-Cookie应用实例(三)

1.引言 在前面的章节中&#xff0c;我们详细介绍了 Cookie 的基本概念、属性以及如何在 Chrome 浏览器中管理和调试 Cookie。理解这些理论知识之后&#xff0c;下一步是将其应用于实际开发中。在本篇文章中&#xff0c;我们将通过具体的代码示例&#xff0c;演示如何在网页中设…

并发集合:ConcurrentHashMap解析

1、ConcurrentHashMap 介绍 1.1、ConcurrentHashMap 概述 ConcurrentHashMap 是线程安全的HashMap&#xff0c;但最早的线程安全的HashMap 是 HashTable &#xff0c;HashTable 现在已经弃用&#xff0c;因为它是使用synchronized 来保证线程安全&#xff0c;性能比较低&#…

无人机地理测绘技术详解

无人机地理测绘技术&#xff0c;作为现代测绘领域的一项重要革新&#xff0c;融合了无人机技术、遥感技术、地理信息系统&#xff08;GIS&#xff09;及全球定位系统&#xff08;GPS&#xff09;等多学科技术。该技术通过无人机搭载的高精度传感器&#xff0c;如相机、激光雷达…