Bootstrap

Vue3实现图片上传预览以及拖拽

前言

图片上传使用Ant Design Vue中的upload组件,预览使用Ant Design Vue中的Model组件,拖拽则使用vuedraggable。

介绍

vuedraggable为基于Sortable.js的vue组件,用以实现拖拽功能。
官方地址:https://github.com/SortableJS/Vue.Draggable
中文版文档:https://www.itxst.com/vue-draggable/tutorial.html

安装

npm install vuedraggable -S

使用

import Draggable from ‘vuedraggable’

接下来回到主题,实现功能

代码如下:

// 上传部分
	<Upload
      action="自己的上传地址"
      list-type="picture"
      v-model:fileList="fileList1"
      @change="handleChange"
      @preview="handlePreview"
      class="upload-list-inline"
      accept=".png,.jpg"
      :before-upload="beforeUpload"
      multiple="true"
    >
      <div class="upload">
        <plus-outlined />
        <div class="ant-upload-text">Upload</div>
      </div>
    </Upload>
    
//预览
	<Modal :visible="previewVisible" :footer="null" @cancel="handleCancel">
      <img alt="" style="width: 100%" :src="previewImage" />
    </Modal>
    <div class="tip"> 只支持png/jpg格式,最大支持5M </div>
    
//拖拽
	<draggable :list="fileListArr" @dragend="dragComplete">
      <template #item="{ element }">
        <div class="imgList">
          <a>
            <div class="mask">
              <EyeOutlined class="icon-preview" @click="handlePreview(element.imageUrl)" />
              <DeleteOutlined class="icon-delete" @click="deleteImg(element)" />
            </div>
          </a>
          <img :src="element.imageUrl" alt="" class="img" />
        </div>
      </template>
    </draggable>
// typescript
export default defineComponent({
    name: 'InstructionContentItemTemplateImagesDrawer',
    components: {
      PlusOutlined,
      Upload,
      Modal,
      draggable,
      DeleteOutlined,
      EyeOutlined,
    },
    setup(_, { emit }) {
      const isUpdateView = ref(true);
      const previewVisible = ref<boolean>(false);
      const previewImage = ref<string | undefined>('');
      const fileList = ref<FileItem[]>([]);
      const fileList1 = ref([]);
      const fileListArr = ref<any>([]);
      const recordId = ref('');
      let num = ref();

      const handleCancel = () => {
        previewVisible.value = false;
      };
      
	//点击图片预览
      const handlePreview = async (item) => {
        previewImage.value = item;
        previewVisible.value = true;
      };

	//上传文件状态改变时
      const handleChange = ({ file, fileList }) => {
        if (file.status == 'done') {
          if (fileListArr.value.length >= num.value) {
            fileListArr.value.push({ ...file });
            fileListArr.value[fileListArr.value.length - 1].sort = String(
              fileListArr.value.length - 1,
            );
            fileListArr.value[fileListArr.value.length - 1].instructionContentId = recordId.value;
            //下面的删除是因为接口返回的数据有些不需要,所以删掉,根据实际情况选择,后面涉及到的delete同理;imageUrl,instructionContentId 等属性命名根据实际情况修改
            delete fileListArr.value[fileListArr.value.length - 1].lastModified;
            delete fileListArr.value[fileListArr.value.length - 1].lastModifiedDate;
            delete fileListArr.value[fileListArr.value.length - 1].name;
            delete fileListArr.value[fileListArr.value.length - 1].originFileObj;
            delete fileListArr.value[fileListArr.value.length - 1].percent;
            delete fileListArr.value[fileListArr.value.length - 1].response;
            delete fileListArr.value[fileListArr.value.length - 1].size;
            delete fileListArr.value[fileListArr.value.length - 1].status;
            delete fileListArr.value[fileListArr.value.length - 1].thumbUrl;
            delete fileListArr.value[fileListArr.value.length - 1].type;
            delete fileListArr.value[fileListArr.value.length - 1].uid;
            delete fileListArr.value[fileListArr.value.length - 1].xhr;
          } else {
            for (let i in fileList) {
              fileListArr.value.push({ ...fileList[i] });
              fileListArr.value[i].sort = String(i);
              fileListArr.value[i].imageUrl = fileList[i].response.data.url;
              fileListArr.value[i].instructionContentId = recordId.value;
              delete fileListArr.value[i].lastModified;
              delete fileListArr.value[i].lastModifiedDate;
              delete fileListArr.value[i].name;
              delete fileListArr.value[i].originFileObj;
              delete fileListArr.value[i].percent;
              delete fileListArr.value[i].response;
              delete fileListArr.value[i].size;
              delete fileListArr.value[i].status;
              delete fileListArr.value[i].thumbUrl;
              delete fileListArr.value[i].type;
              delete fileListArr.value[i].uid;
              delete fileListArr.value[i].xhr;
            }
          }
        }
      };

	//拖拽时位置计算
      const dragComplete = (event) => {
        let oldIndex = event.oldIndex; //移动初始位置
        let newIndex = event.newIndex; //运动终止位置
        let diff = Math.abs(newIndex - oldIndex); //插值绝对值
        let index = fileListArr.value[oldIndex];
        if (eval(oldIndex) > eval(newIndex)) {
          for (let i = 0; i < diff; i++) {
            fileListArr.value[oldIndex - i] = fileListArr.value[oldIndex - i - 1];
          }
          fileListArr.value[newIndex] = index;
        } else {
          for (let i = 0; i < diff; i++) {
            fileListArr.value[oldIndex + i] = fileListArr.value[oldIndex + i + 1];
          }
          fileListArr.value[newIndex] = index;
        }
        for (let i = 0; i < fileListArr.value.length; i++) {
          fileListArr.value[i].sort = String(i);
        }
      };

      const deleteImg = (value) => {
          fileListArr.value.findIndex((element) => element.imageUrl === value.imageUrl),
        );
        fileListArr.value.splice(
          fileListArr.value.findIndex((element) => element.imageUrl === value.imageUrl),
          1,
        );
        num.value = num.value - 1;
      };

	//这里使用了代码中封装的组件,以及调用了接口,仅供参考
      const [registerDrawer, { closeDrawer }] = useDrawerInner(async (data) => {
        fileListArr.value = [];
        recordId.value = data.record.id;

        const res = await retrieveInstructionContentAttachmentApi(recordId.value);
        if (res) {
          for (let i = 0; i < res.length; i++) {
            fileListArr.value[i] = { ...res[i] };
            delete fileListArr.value[i].createdAt;
            delete fileListArr.value[i].createdBy;
            delete fileListArr.value[i].delFlag;
            delete fileListArr.value[i].id;
            delete fileListArr.value[i].revision;
            delete fileListArr.value[i].tenantId;
            delete fileListArr.value[i].updatedAt;
            delete fileListArr.value[i].updatedBy;
          }
        }
        num.value = fileListArr.value.length;
      });

      const beforeUpload = (file) => {
        const isLt2M = file.size / 1024 / 1024 < 5;
        if (!isLt2M) {
          message.error('Image must smaller than 5MB!');
        }
        return isLt2M;
      };
      
      return {
        previewVisible,
        previewImage,
        fileList,
        fileList1,
        handleCancel,
        handlePreview,
        handleChange,
        fileListArr,
        dragComplete,
        deleteImg,
        beforeUpload,
      };
    },
  });

// css
.upload {
    width: 120px;
    height: 120px;
    border: 1px rgb(217 217 217) dashed;
    background-color: rgb(250 250 250);
    cursor: pointer;
  }

  .anticon {
    margin: 40px 52px 10px;
    color: #999;
  }

  .ant-upload-text {
    color: #666;
    text-align: center;
  }

  .upload-list-inline :deep(.ant-upload-list-item) {
    width: 120px;
    height: 120px;
    margin-right: 8px;
    padding: 0;
    float: left;
  }

  .upload-list-inline :deep(.ant-upload-animate-enter) {
    animation-name: uploadAnimateInlineIn;
  }

  .upload-list-inline :deep(.ant-upload-animate-leave) {
    animation-name: uploadAnimateInlineOut;
  }

  :deep(.ant-upload-list-item-name) {
    display: none;
  }

  :deep(
      .ant-upload-list-picture .ant-upload-list-item-thumbnail,
      .ant-upload-list-picture-card .ant-upload-list-item-thumbnail
    ) {
    width: 118px;
    height: 118px;
  }

  :deep(
      .ant-upload-list-picture .ant-upload-list-item-thumbnail img,
      .ant-upload-list-picture-card .ant-upload-list-item-thumbnail img
    ) {
    width: 118px;
    height: 118px;
  }

  :deep(
      .ant-upload-list-text .ant-upload-list-item-card-actions,
      .ant-upload-list-picture .ant-upload-list-item-card-actions
    ) {
    margin-left: -30px;
  }

  .tip {
    margin-top: 10px;
    margin-bottom: 20px;
  }

  .imgList {
    position: relative;
    float: left;
  }

  .mask {
    position: absolute;
    top: 0;
    left: 0;
    width: 120px;
    height: 120px;
    margin-top: 15px;
    margin-right: 10px;
    opacity: 0;
    background: rgb(101 101 101 / 60%);
    color: #fff;
  }

  .imgList a:hover .mask {
    opacity: 1;
  }

  .icon-delete {
    position: absolute;
    margin-top: 55px;
    margin-left: 65px;
    color: white;
    cursor: pointer;
  }

  .icon-preview {
    position: absolute;
    margin-top: 55px;
    margin-left: 40px;
    color: white;
    cursor: pointer;
  }

  .img {
    width: 120px;
    height: 120px;
    margin-top: 15px;
    margin-right: 10px;
  }

  :deep(.ant-upload-list) {
    display: none;
  }
效果

在这里插入图片描述
在这里插入图片描述

;