Aupload + vuedraggable实现 上传的文件可以拖拽排序
一、问题
1.以前的需求已经通过Aupload封装了 文件上传组件,现在希望添加 文件拖拽排序功能。
2.首先想到可以使用 vuedraggable来实现。但是 vuedraggable需要传递数组并且在插槽里面指定单个元素的渲染方式,但是原本的Aupload组件是直接渲染数组的,要怎么兼容呢?
二、解决方法
1.方法一:不用Aupload了,自己实现单个文件的渲染fileCard以及文件上传逻辑,在vueDraggable中复用fileCard组件。之前的很多逻辑都不能用了需要重写,工作量比较大,还有可能有性能问题
2.方法二:在当前的基础上直接添加拖拽功能。本文采用方案二,具体实现如下
3. 实现拖拽的功能:拿到上传的文件数据就行=> 拖拽区域的数据和上传区域的数据共用,拖拽结束后同步修改父组件传入的数据
<Draggablev-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"><AUploadv-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>
4. 现在上传图片后会显示两份数据:Draggable和Aupload各一份,如何只显示拖拽区域的数据呢?
AUpload添加配置不显示上传数据: :showUploadList="false"
5. 当前的样式是:拖拽区域和上传区域 上下布局,如何让 上传区域紧跟在 Draggable渲染的列表后面呢?
把Aupload那部分放在 Draggable组件的 footer插槽中
6. 对于图片希望可以直接预览,不打开新窗口
使用AImage组件自定义预览渲染时机,默认样式设置为不显示
7.支持批量上传,但是超过最大上传数量时,本次上传失败
用当前所有的文件数量和最大上传数量比较。
注意:beforeUpload中取到的只是本次上传的数量,需要加上 之前的数量,才是总的上传数量
async function handleBeforeUpload(file: UploadFile, fileList: UploadFile[]) {if (!checkMaxCount(fileList)) {return false}}// 检查文件数量是否超过最大限制
function checkMaxCount(fileList: UploadFile[]) {const totalCount = fileList.length + innerFileList.value.lengthif (props.maxCount && totalCount > props.maxCount) {message.destroy()message.warning(props.checkTips?.maxCount || `最多上传${props.maxCount}张图片`)return false}return true
}
8.完整代码
1)DragSort.vue
<template><div class="flex flex-wrap gap-4"><!-- Aupload上传支持拖拽排序 --><Draggablev-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"><ImgUploadv-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]: anysize?: numberlistType?: UploadListTypeanimation?: 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
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 数据转换为 Blobfunction dataURLtoBlob(dataURL: string): Blob {const arr = dataURL.split(',')const mime = arr[0].match(/:(.*?);/)?.[1] || 'application/pdf'const bstr = atob(arr[1])let n = bstr.lengthconst 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 URLconst 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.其实实现的过程并不复杂,但是一开始没有思路想得脑子疼。还是要先行动,一步一步的想办法接近最终结果,很有可能就完美实现了!
/*
希望对你有帮助!
如有错误,欢迎指正,谢谢!
*/