Aupload + vuedraggable实现 上传的文件可以拖拽排序

一、问题

1.以前的需求已经通过Aupload封装了 文件上传组件,现在希望添加 文件拖拽排序功能

2.首先想到可以使用 vuedraggable来实现。但是 vuedraggable需要传递数组并且在插槽里面指定单个元素的渲染方式,但是原本的Aupload组件是直接渲染数组的,要怎么兼容呢?

二、解决方法

1.方法一:不用Aupload了,自己实现单个文件的渲染fileCard以及文件上传逻辑,在vueDraggable中复用fileCard组件。之前的很多逻辑都不能用了需要重写,工作量比较大,还有可能有性能问题

2.方法二:在当前的基础上直接添加拖拽功能。本文采用方案二,具体实现如下

  1. 实现拖拽的功能:拿到上传的文件数据就行=> 拖拽区域的数据和上传区域的数据共用,拖拽结束后同步修改父组件传入的数据
html 复制代码
<Draggable
      v-model="innerFileList"
      item-key="uid"
      :animation="animation"
      tag="div"
      class="drag-list"
      @end="handleDragEnd"
    >
      <!-- 拖拽区域 -->
      <template #item="{ element }">
        <div class="drag-item">
          <AUpload
            :file-list="[element]"
            :list-type="listType"
            @remove="handleRemove"
            @preview="handlePreview"
          >
          </AUpload>
        </div>
      </template>
    </Draggable>
    <div>
        <template v-if="!maxCount || innerFileList.length < maxCount">
              <AUpload
                v-bind="bind"
                v-model:file-list="innerFileList"
                :beforeUpload="handleBeforeUpload"
                :showUploadList="false"
                @remove="handleRemove"
                @change="handleChange"
              >
                <!-- 添加图片 -->
                <PlusOutlined />
                <div class="ml-1">{{ uploadText }}</div>
              </AUpload>
        </template>


    </div>
  1. 现在上传图片后会显示两份数据:Draggable和Aupload各一份,如何只显示拖拽区域的数据呢?

AUpload添加配置不显示上传数据 : :showUploadList="false"

  1. 当前的样式是:拖拽区域和上传区域 上下布局,如何让上传区域紧跟在 Draggable渲染的列表后面呢?

把Aupload那部分放在Draggable组件的 footer插槽中

  1. 对于图片希望可以直接预览,不打开新窗口

使用AImage组件自定义预览渲染时机,默认样式设置为不显示

7.支持批量上传,但是超过最大上传数量时,本次上传失败

用当前所有的文件数量和最大上传数量比较。

注意:beforeUpload中取到的只是本次上传的数量,需要加上 之前的数量,才是总的上传数量

javascript 复制代码
async function handleBeforeUpload(file: UploadFile, fileList: UploadFile[]) {
  if (!checkMaxCount(fileList)) {
    return false
  }

}

// 检查文件数量是否超过最大限制
function checkMaxCount(fileList: UploadFile[]) {
  const totalCount = fileList.length + innerFileList.value.length
  if (props.maxCount && totalCount > props.maxCount) {
    message.destroy()
    message.warning(props.checkTips?.maxCount || `最多上传${props.maxCount}张图片`)
    return false
  }
  return true
}

8.完整代码

1)DragSort.vue

javascript 复制代码
<template>
  <div class="flex flex-wrap gap-4">
    <!-- Aupload上传支持拖拽排序 -->
    <Draggable
      v-model="innerFileList"
      item-key="uid"
      :animation="animation"
      tag="div"
      class="drag-list"
      @end="handleDragEnd"
    >
      <template #item="{ element }">
        <div class="inline-flex">
          <div class="drag-item">
            <AUpload :file-list="[element]" :list-type="listType" @remove="handleRemove"> </AUpload>
          </div>
        </div>
      </template>
      <template #footer>
        <slot name="footer">
          <ImgUpload
            v-if="innerFileList.length < maxCount"
            v-bind="bind"
            :show-upload-list="false"
            @update:file-list="handleUpdateFileList"
          />
        </slot>
      </template>
    </Draggable>
  </div>
</template>
<script setup lang="ts">
import ImgUpload from '@/components/ImgUpload.vue'
import type { UploadFile } from 'ant-design-vue'
import type { UploadListType } from 'ant-design-vue/es/upload/interface'
import Draggable from 'vuedraggable'
const props = withDefaults(
  defineProps<{
    fileList?: UploadFile[]
    urlList?: string[]
    maxCount?: number
    [key: string]: any
    size?: number
    listType?: UploadListType
    animation?: number
  }>(),
  {
    size: 100,
    animation: 200,
    listType: 'picture-card',
    maxCount: 0
  }
)
const emit = defineEmits(['update:fileList', 'update:urlList'])
const attrs: any = useAttrs()
const bind = computed(() => {
  return {
    ...attrs,
    ...props
  }
})

const innerFileList = ref<UploadFile[]>([])

function handleUpdateFileList(fileList: UploadFile[]) {
  innerFileList.value = fileList
}

// 删除文件
function handleRemove(file: UploadFile) {
  innerFileList.value = innerFileList.value.filter((item) => item.uid !== file.uid)
  emit('update:fileList', innerFileList.value)
  emit(
    'update:urlList',
    innerFileList.value.map((item) => item.thumbUrl)
  )
}

// 拖拽结束------更新文件顺序
function handleDragEnd() {
  emit('update:fileList', innerFileList.value)
  emit(
    'update:urlList',
    innerFileList.value.map((item) => item.thumbUrl)
  )
}

// 外部修改urlList------更新文件列表
watch(
  () => props.urlList,
  (newVal) => {
    if (newVal && newVal.length > 0) {
      innerFileList.value = newVal?.map((item: string) => {
        let itemInfo = item.split('.')
        return {
          uid: item,
          name: itemInfo[0] || item,
          url: item,
          thumbUrl: item,
          type: itemInfo[1]
        }
      })
    } else {
      innerFileList.value = []
    }
  },
  {
    immediate: true,
    deep: true
  }
)
</script>

<style lang="less" scoped>
.drag-list {
  @apply flex flex-wrap gap-4;
  .drag-item {
    width: v-bind('`${$props.size}px`');
    height: v-bind('`${$props.size}px`');
    cursor: move;
    @apply rounded-lg;
  }
}
</style>

2)useFileDeal.ts

javascript 复制代码
export function useFileDeal() {
  function getBase64(file: any) {
    return new Promise<string>((resolve) => {
      const reader = new FileReader()
      reader.addEventListener('load', () => resolve(reader.result as string))
      reader.readAsDataURL(file)
    })
  }

  // 将 base64 数据转换为 Blob
  function dataURLtoBlob(dataURL: string): Blob {
    const arr = dataURL.split(',')
    const mime = arr[0].match(/:(.*?);/)?.[1] || 'application/pdf'
    const bstr = atob(arr[1])
    let n = bstr.length
    const u8arr = new Uint8Array(n)
    while (n--) {
      u8arr[n] = bstr.charCodeAt(n)
    }
    return new Blob([u8arr], { type: mime })
  }

  function previewFile(fileUrl: string) {
    if (fileUrl.startsWith('data:')) {
      // 如果是 base64 数据,创建 blob URL
      const blob = dataURLtoBlob(fileUrl)
      const blobUrl = URL.createObjectURL(blob)
      const newWindow = window.open(blobUrl, '_blank')
      if (newWindow) {
        // 清理 blob URL 当窗口关闭时
        newWindow.addEventListener('beforeunload', () => {
          URL.revokeObjectURL(blobUrl)
        })
      }
    } else {
      // 如果是普通 URL,直接打开
      window.open(fileUrl, '_blank')
    }
  }

  // 获取文件类型
  enum FileType {
    DOCUMENT = 'DOCUMENT',
    SPREADSHEET = 'SPREADSHEET',
    IMAGE = 'IMAGE',
    PDF = 'PDF',
    PPT = 'PPT',
    UNKNOWN = 'UNKNOWN',
    OTHER = 'OTHER'
  }
  function getFileExt(file: string) {
    const start = file.lastIndexOf('.')
    const fileExt = file.slice(start + 1).toLowerCase()
    if (['pdf', 'ofd'].includes(fileExt)) {
      return FileType.PDF
    } else if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(fileExt)) {
      return FileType.IMAGE
    } else if (['xls', 'xlsx'].includes(fileExt)) {
      return FileType.SPREADSHEET
    } else if (['ppt', 'pptx'].includes(fileExt)) {
      return FileType.PPT
    } else if (['doc', 'docx'].includes(fileExt)) {
      return FileType.DOCUMENT
    } else {
      return FileType.OTHER
    }
  }

  return {
    getBase64,
    dataURLtoBlob,
    previewFile,
    getFileExt,
    FileType
  }
}

三、总结

1.通过AUpload和vuedraggable可以实现 上传文件拖拽功能,但是需要注意了解第三库对应组件的详细属性

2.其实实现的过程并不复杂,但是一开始没有思路想得脑子疼。还是要先行动,一步一步的想办法接近最终结果,很有可能就完美实现了!

/*

希望对你有帮助!

如有错误,欢迎指正,谢谢!

*/

相关推荐
前端 贾公子2 小时前
Vue.js props mutating:反模式如何被视为一种良好实践。
前端·javascript·vue.js
Filotimo_8 小时前
2.CSS3.(2).html
前端·css
yinuo9 小时前
uniapp微信小程序华为鸿蒙定时器熄屏停止
前端
gnip10 小时前
vite中自动根据约定目录生成路由配置
前端·javascript
前端橙一陈11 小时前
LocalStorage Token vs HttpOnly Cookie 认证方案
前端·spring boot
~无忧花开~11 小时前
JavaScript学习笔记(十五):ES6模板字符串使用指南
开发语言·前端·javascript·vue.js·学习·es6·js
泰迪智能科技0111 小时前
图书推荐丨Web数据可视化(ECharts 5)(微课版)
前端·信息可视化·echarts
CodeCraft Studio12 小时前
借助Aspose.Email,使用 Python 读取 Outlook MSG 文件
前端·python·outlook·aspose·email·msg·python读取msg文件
家里有只小肥猫13 小时前
react 初体验2
前端·react.js·前端框架