Element-Puls中el-upload组件结合vue-draggable-plus实现上传支持拖拽排序(并保留el-upload原有样式、预览、删除)等功能

2024-01-09

展示效果

需求

需求想要一个可拖拽排序的图片列表,但是发现el-upload虽然可以实现照片墙,但是没办法拖拽

实现思路

使用 vue-draggable-plus 拖拽插件,隐藏Upload原有的已上传文件列表,自定义上传后文件列表的样式并绑定预览和删除功能

安装VueDraggablePlus库 (尤雨溪推荐)

功能齐全 :全面继承 Sortable.js 的所有功能

无缝迁移 :适用于 Vue 3 和 Vue2

灵活使用 :支持组件、指令、函数式调用

类型强 :用 TypeScript 编写,带有完整的 TS 文档

数据绑定 :支持 v-model 双向绑定,不需要单独维护排序数据

自定义容器 :可以自定某个容器作为拖拽容器,比 Sortable.js 更灵活

安装指令

npm install vue-draggable-plus

yarn add vue-draggable-plus

pnpm add vue-draggable-plus

使用(组件方式)

封装成公共组件 /src/components/DraggableImageUpload/index.vue

HTML代码

<template>
  <div class="draggable_image_upload">
    <VueDraggable class="box-uploader" ref="draggableRef" v-model="curList" :animation="600" easing="ease-out"
      ghostClass="ghost" draggable="ul" @start="onStart" @update="onUpdate">
      <!-- 使用element-ui el-upload自带样式 -->
      <ul v-for="(item, index) in curList" :key="index" class="el-upload-list el-upload-list--picture-card">
        <li class="el-upload-list__item is-success animated">
          <el-image class="originalImg" :src="item.url" :preview-src-list="[item.url]" />
          <!--           <el-icon>
            <Close />
          </el-icon> -->
          <label class="el-upload-list__item-status-label">
            <el-icon class="el-icon--upload-success el-icon--check">
              <Check />
            </el-icon>
          </label>
          <span class="el-upload-list__item-actions">
            <!-- 预览    -->
            <span class="el-upload-list__item-preview" @click="handleImgPreview('originalImg', index)">
              <el-icon>
                <ZoomIn />
              </el-icon>
            </span>
            <!-- 删除    -->
            <span class="el-upload-list__item-delete" @click="onImageRemove(item.url)">
              <el-icon>
                <Delete />
              </el-icon>
            </span>
          </span>
        </li>
      </ul>
      <!-- 上传组件 -->
      <el-upload ref="uploadImages" :show-file-list="false" :multiple="multiple" :auto-upload="autoUpload" action="#"
        :on-change="onSkuImgSelect" :on-exceed="(file: any, fileList: any) => ElMessage.warning($t('reg.tip.imgLimit'))"
        :file-list="curList" list-type="picture-card" accept=".jpg,.jpeg,.png,.gif" :limit="limit"
        style="display: inline;">
        <ElImage class="circle-plus" :src="$getIconUrl('circle-plus', 'svg')" fit="scale-down" />
      </el-upload>
    </VueDraggable>
  </div>
</template>

JavaScript代码

<script name="DraggableImageUpload" setup lang="ts">
import { Upload } from '@/api/business/product';
import utils from '@/utils/utils';
import { ComponentInternalInstance, getCurrentInstance } from 'vue';

import { type UseDraggableReturn, VueDraggable } from 'vue-draggable-plus'
const draggableRef = ref<UseDraggableReturn>()

const { proxy } = getCurrentInstance() as ComponentInternalInstance;

const props = defineProps({
  //接受上传的文件类型(thumbnail-mode 模式下此参数无效)
  accept: {
    type: String,
    default: ".jpg,.jpeg,.png"
  },
  //允许上传文件的最大数量
  limit: {
    type: Number,
    default: 9999
  },
  //是否支持多选文件
  multiple: {
    type: Boolean,
    default: true
  },
  //是否自动上传文件
  autoUpload: {
    type: Boolean,
    default: false
  },
  modelValue: {
    type: Array,
    default: () => []
  },
});

const data = reactive<any>({
  maxImgsLen: 0,
  imgList: [],
});

const { maxImgsLen, imgList} = toRefs(data)

const emit = defineEmits(["update:modelValue"]);
const curList: any = computed({
  get() {
    return props.modelValue;
  },
  set(newValue) {
    emit("update:modelValue", newValue);
  }
});

//元素开始拖拽
const onStart = () => {
  console.log('start')
}

//元素顺序更新时触发
const onUpdate = () => {
  console.log(curList.value, 'update++++++++++++++')
}

// 图片预览
const handleImgPreview = (domClass: string, index: number) => {
  /*   showViewer.value = true
    previewList.value = [url] */
  const dom = document.getElementsByClassName(domClass);
  (dom[index].firstElementChild as any).click(); //调用 el-image 的预览方法 
}

// 删除图片
const onImageRemove = (url: string) => {
  let list = utils.deepClone(curList.value)
  list.forEach((item: any, index: number) => {
    if (url === item.url) {
      list.splice(index, 1)
    }
  })
  curList.value = list
}

// 监听图片选择后回调
const onSkuImgSelect = (file: any, fileList: any[]) => {
  let curLen = fileList.length
  maxImgsLen.value = Math.max(curLen, maxImgsLen.value)
  setTimeout(() => {
    if (curLen !== maxImgsLen.value) return
    validatePic(fileList)
  });
}


// 校验批量上传图片集合规格合法性
const validatePic = async (fileList: any[]) => {
  let tip = ''
  for (let i = 0; i < fileList.length; i++) {
    const file = fileList[i]
    if (!file.id && file.raw) {
      const type = file.raw.type,
        size = file.raw.size
      const isImg =
        type === 'image/jpeg' ||
        type === 'image/jpg' ||
        type === 'image/png' ||
        //type === 'image/bmp' ||
        type === 'image/gif',
        isLt5M = size / 1024 / 1024 < 5
      //console.log('是否为图片', type, isImg)

      isImg
        ? isLt5M
          ? null
          : (tip = '上传图片大小不能超过5MB')
        : (tip = '上传图片格式不正确')

      if (!isImg || !isLt5M) {
        setTimeout(() => {
          ElMessage.error(tip);
        })
        fileList.splice(i, 1)
        i--
        continue
      }
    }
  }
  // console.log('处理结果列表------list', fileList)
  let defaultCount = 0
  fileList.forEach(item => {
    item.id ? defaultCount++ : null
  })
  // console.log('已有图片数量', defaultCount)
  if (fileList.length > defaultCount) {
    imgList.value.length = 0;
    imgList.value.push(...fileList);
    uploadImg()
  }
  maxImgsLen.value = 0
}

// 图集上传方法
const uploadImg = async () => {
  const fd = new FormData()
  //console.log('二级制对象长度', fd.forEach)
  imgList.value.forEach((file: any) => {
    if (file.raw) {
      console.log('文件后缀', file.raw.type)
      fd.append('file', file.raw)
    }
  })
  if (!fd.has('file')) return;

  proxy?.$loading({
    lock: true,
    text: '正在上传,请稍候...',
    background: 'rgba(0, 0, 0, 0.7)'
  })

  //将图片上传至服务器
  const { data } = await Upload(fd).finally(() => proxy?.$loading.closeLoading());
  if (!data || JSON.stringify(data) === '{}') return;
  let curListNew = utils.deepClone(curList.value) //深拷贝
  data.forEach((file: any) => {
    const { ossId, fileName, savePath, url } = file,
      imgObj = {
        ossId,
        fileName,
        savePath,
        url
      }
    curListNew.push(imgObj)
  })
  curList.value = curListNew
}
</script>

Style

<style lang="scss" scoped>
.draggable_image_upload {

  .box-uploader {
    display: inline-block;
    vertical-align: middle;

    :deep(.el-upload) {
      border-radius: 4px;

      /* display flex
              align-items center
              justify-content center */
      .circle-plus {
        width: 24px;
        height: 24px;
      }

      &.el-upload-list {
        &.el-upload__item {
          width: 100px;
          height: 100px;
          margin: 0 17px 17px 0;
          border-color: #e7e7e7;
          padding: 3px;
        }
      }

      &.el-upload--picture-card {
        width: 100px;
        height: 100px;
        line-height: 100px;
        border-style: dashed;
        margin-right: 17px;
      }
    }

    .el-upload-list__item {
      width: 100px;
      height: 100px;
      margin: 0 17px 17px 0;
      border-color: #e7e7e7;
      padding: 3px;

      .originalImg {
        :deep(.el-image__preview) {
          width: 100%;
          height: 100%;
          -o-object-fit: contain;
          object-fit: contain;
        }
      }
    }

    ul:nth-child(6n+6) {
      li {
        margin-right: 0;
      }
    }
  }
}
</style>

在 xxx.vue 文件中使用

<template>
  <el-dialog title="上传商品图片" v-model="infoVisible" :close-on-click-modal="false">
    <!-- 图片拖拽组件 -->
    <DraggableImageUpload accept=".jpg,.jpeg,.png,.gif" v-model="curList" />
  </el-dialog>
</template>

<script name="" setup lang="ts">
import DraggableImageUpload from '@/components/DraggableImageUpload/index.vue'
import { ComponentInternalInstance, getCurrentInstance } from 'vue';

const { proxy } = getCurrentInstance() as ComponentInternalInstance;

const data = reactive<any>({
  curList: [], // 需亚预览的数组
  infoVisible: false, //预览组件可见性
});

const { infoVisible, curList } = toRefs(data)
</script>

图片预览第二种实现方式

将 li 中 el-image 组件更改为 img标签,引入 el-image-viewer组件,并修改图片预览方法,下面只给出要调整的部分代码

<template>
  <li class="el-upload-list__item is-success animated">
    <!-- class里的类名不能遗弃,否则将无法使用el-upload样式 -->
    <img :src="item.url" alt="Upload Image" class="el-upload-list__item-thumbnail">
    <!-- <el-image class="originalImg" :src="item.url" :preview-src-list="[item.url]" /> -->
  </li>
  <!-- 预览 -->
  <span class="el-upload-list__item-preview" @click="handleImgPreview(item.url, index)">
    <el-icon>
      <ZoomIn />
    </el-icon>
  </span>
  <!-- element-plus图片预览组件 -->
  <el-image-viewer @close="()=>{showViewer = false}" v-if="showViewer" :url-list="previewList" />
</template>

<script name="" setup lang="ts">
import { ComponentInternalInstance, getCurrentInstance } from 'vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;

const data = reactive<any>({
  previewList: [],
  showViewer: false,
});

const { previewList, showViewer } = toRefs(data)


// 图片预览
const handleImgPreview = (url: string, index: number) => {
    showViewer.value = true
    previewList.value = [url]
}
</script>
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Element-Puls中el-upload组件结合vue-draggable-plus实现上传支持拖拽排序(并保留el-upload原有样式、预览、删除)等功能 的相关文章

随机推荐

  • 【技术科普】什么是达芬奇架构?有什么优势?

    芯片架构是指芯片设计的基本结构和组织方式 用于实现各种计算 存储和通信功能 芯片架构通常包括处理器核心 内存 输入输出接口等组成部分 这些部分的设计对芯片性能和功耗有着直接的影响 世界上主流的芯片架构主要包括x86 ARM PowerPC和
  • 2020年认证杯SPSSPRO杯数学建模D题(第二阶段)让电脑桌面飞起来全过程文档及程序

    2020年认证杯SPSSPRO杯数学建模 D题 让电脑桌面飞起来 原题再现 对于一些必须每天使用电脑工作的白领来说 电脑桌面有着非常特殊的意义 通常一些频繁使用或者比较重要的图标会一直保留在桌面上 但是随着时间的推移 桌面上的图标会越来越多
  • JNPF——面向研发使用、全栈开发、前后端分离的低代码平台

    1 背景 JNPF是一个快速开发应用的平台 一款 面向研发开发使用 全栈开发 前后端分离 的低代码工具 拥有强大的 可视化建模 数据库和API集成能力 目前已有将 超千家企业 将JNPF低代码开发工具融入内部研发体系 相较于传统的产研开发
  • CTF之逆向入门

    逆向工程 Reverse Engineering 又称反向工程 是一种技术过程 即对一项目标产品进行逆向分析及研究 从而演绎并得出该产品的处理流程 组织结构 功能性能规格等设计要素 以制作出功能相近 但又不完全一样的产品 逆向工程源于商业及
  • 【Threejs】代码+图文带你快速上手

    前言 大家好 我是南木元元 热衷分享有趣实用的文章 希望大家多多支持 一起进步 个人主页 南木元元
  • Python小说阅读器制作教程

    目录 一 准备阶段 二 创建项目文件夹 三 创建Python文件 四 编写代码 五 运行程序 六 完善功能 可选 七 高级功能 总结 Python是一门强大的编程语言 它被广泛用于各种应用开发 包括小说阅读器的制作 下面我们将一步步教你如何
  • 爬虫数据特殊符号处理(记录+持续补充)

    1 xa0 replace u xa0 2 amp html 的空格 https blog csdn net weixin 43640594 article details 122859029 import html html unesca
  • 一个寒假能学会黑客技术吗?看完你就知道了

    一个寒假能成为黑客吗 资深白帽子来告诉你 如果你想的是学完去美国五角大楼内网随意溜达几圈 想顺走一点机密文件的话 劝你还是趁早放弃 但是成为一名初级黑客还是绰绰有余的 你只需要掌握好渗透测试 Web安全 数据库 搞懂web安全防护 SQL注
  • 新能源预测数据集GEFCom Data,用于光伏发电、风电功率、负荷、电价预测

    引言 新能源在满足世界能源需求方面日益重要 其特点是 发电量在很大程度上取决于天气状况 为了有效地将其整合到电网中 对新能源发电量进行准确的预测是一项不可避免的要求 新能源准确预测成为一项有趣且新颖的挑战 虽然已有大量文献对新能源预测进行了
  • react-native使用动画Animated

    官方网文档 动画 Animated 一些精彩的例子 React Native 动画 Animated 渐变组件的使用 ReactNative 进阶 四十五 渐变组件 react native linear gradient 需要实现如下的动
  • 【面试】 前端竞争压力大?揭秘让你们学后端的真实动机!

    前端开发属于程序员吗 网友是这样回答的 看完前端同学的评论 我悟了 你们让人都去学后端卷 然后减小前端竞争压力是吧 哈哈哈 你们这帮老6 于是我去拿出了我收藏的 某前端招聘JD来盘一盘 那些觉得 是个人都能干前端 的同学们想必已经熟练掌握了
  • 动手学深度学习3 数据操作+数据预处理

    数据操作 数据预处理 1 基础数据结构 N维数组 2 数据操作实现 基础的张量运算 1 张量 创建与赋值 1 数据生成 创建 torch arange 2 张量的属性 shape numel 3 reshape 改变张量的shape但
  • Python库中关于时间的常见操作

    目录 导入所需的库 获取当前时间 格式化日期和时间 解析日期和时间字符串 时间戳操作 获取当前时间戳 将时间戳转换为日期和时间 时间差操作 时间日期的时区处理 时间日期的随机生成 注意事项 总结 在Python中 时间处理是一个重要的主题
  • 从three.js旋转动画,我了解了requestAnimationFrame

    前言 大家好 我是南木元元 热衷分享有趣实用的文章 希望大家多多支持 一起进步 个人主页 南木元元
  • 48V转12V的DC-DC稳压电路推荐

    项目或学习过程中好用且稳定的DC DC的电路将是一个成功硬件项目的基础 主芯片 MP9486 MP9486A 是一款高压降压型开关稳压器 可输 出高达 1A 的持续电流至负载 它集成了一个高 压高端 MOSFET 提供 3 5A 的典型峰值
  • 2024了,我不允许你还不会:Qt查看与调试源码

    一 人人都是大佬 谦 卑 虚 心 长远进步 作为一个Qt的开发者 下面这段代码你已经快到了 相看两不厌 的状态了吧 你有没有好奇过 a exec 到底干了什么 我不允许你再说 这是 Qt 内部干的事情 没办法去看啊 那么 真的没办法去看么
  • 软考考试多少分通过?

    根据 人力资源社会保障部办公厅关于单独划定部分专业技术人员职业资格考试合格标准有关事项的通知 人社厅发 2022 25号 的相关规定 软考考试在一般情况下是需要达到45分才能及格的 但在一些特定地区却有不同的要求 这些地区包括国家乡村振兴重
  • 个性化语音生成:五种基于Python的方法

    引言 随着人工智能技术的不断发展 语音生成已经成为一个热门的研究领域 个性化语音生成技术可以根据用户的需求和特点 生成具有高度相似度的语音 广泛应用于语音助手 虚拟人物 语音合成等领域 本文将介绍五种基于Python的个性化语音生成方法 包
  • vue3 父传参到子(defineProps),父调子方法(defineExpose)

    父页面
  • Element-Puls中el-upload组件结合vue-draggable-plus实现上传支持拖拽排序(并保留el-upload原有样式、预览、删除)等功能

    展示效果 需求 需求想要一个可拖拽排序的图片列表 但是发现el upload虽然可以实现照片墙 但是没办法拖拽 实现思路 使用 vue draggable plus 拖拽插件 隐藏Upload原有的已上传文件列表 自定义上传后文件列表的样式