ant design vue图片拖拽排序及oss上传

463 阅读3分钟

借助vue内置组件 传送门>>>transition-group

介绍

  • 目标需求:
    1. 组件图片flex布局适应 调用父容器宽度 进行换行
    2. 支持拖拽排序、删除、自定义传入初始值(回显)、点击大图预览
    3. 支持数量限制、展示图片容器宽度、高度限制、文件大小、文件格式限制
    4. 支持多图传入并 支持单文件等待上一个上传完毕 至服务器
    5. 支持自定义 module名称
    6. 接入oss 直传静态存储桶
  • transition-group vue内置组件实现拖拽
  • a-upload 组件 chang事件处理图片文件

OSS上传

erDiagram
"文件生成md5串传至后端" ||--o{ "返回oss临时key" : "无"
"返回oss临时key" ||--|{ "oss返回url" : "上传oss"
"文件生成md5串传至后端" }|..|{ "后端返回url" : "有"

使用方法

  • max 文件最大数量
  • itemWidth 图片展示宽度
  • itemHeight 图片展示高度
  • imageList 回显数组{id:number,img_url:string}[]
  • change 数组变化回调
<UploadImages :max="3" :itemWidth="'100px'" :itemHeight="'100px'" :imageList="imageList" @change="change" :upLoadMode="'xxx'"/>
<template>
    <transition-group class="drag-sort-box" name="sort" tag="div" @dragover="dragover($event)">
        <div :class="[itemClass, 'drag-sort-item']"
            :style="`--itemWidth: ${itemWidth};--itemHeight: ${itemHeight};margin-bottom: 20px;`"
            v-for="(item, index) in images" :key="item.id" :draggable="true" @dragstart="dragstart(item, index)"
            @dragenter="dragenter(item, $event)" @dragend="dragend(item, $event)" @dragover="dragover($event)">
            <p class="moxsind">{{ index + 1 }}</p>
            <a-image :src="item.img_url" :class="index == dargIndex ? 'active' : ''"></a-image>
            <div key="del" class="del" @click="delImg(item)">
                <DeleteOutlined />
                <i>删除</i>
            </div>
        </div>
        <a-upload class="uploadBox" key="upload" :show-upload-list="false" :multiple="true" :maxCount="props.max"
            v-model:file-list="uploadList" @change="changeUpload" :class="[itemClass, 'drag-sort-item']"
            :disabled="loading" :style="`--itemWidth: ${itemWidth};--itemHeight: ${itemHeight}`"
            accept=".jpg,.png,.jpeg" action="#" :customRequest="(): any => { }" :before-upload="beforeUpload">
            <LoadingOutlined v-if="loading" class="plus-icon" />
            <PlusOutlined v-else class="plus-icon" />
        </a-upload>
    </transition-group>
</template>

<script lang="ts" setup>
import { PlusOutlined, DeleteOutlined, LoadingOutlined } from '@ant-design/icons-vue';
import { message } from "ant-design-vue";
import { ref } from 'vue';
import { ossUpLoadImage } from '@/utils/oss';
defineOptions({
    name: 'DragSort'
})
const emit = defineEmits(['change'])
const props = defineProps({
    imageList: {
        type: Array,
        default: () => [] as { id: string | number, img_url: string }[]
    },
    sortType: {
        type: String,
        default: 'insert'
    },
    upLoadMode: {
        type: String,
        default: 'goods'
    },
    max: {
        type: Number,
        default: 15
    },
    itemWidth: {
        type: String,
        default: '100px'
    },
    itemHeight: {
        type: String,
        default: '100px'
    },
    itemClass: String,
})
const images = ref<{ sign?: boolean, id: string | number, img_url: string }[]>([]);
const setData = () => {
    images.value = [...props.imageList] as { sign?: boolean, id: string | number, img_url: string }[];
    //不重复key
    images.value = images.value.map((v, i) => {
        const img_url = v.img_url.split('?index=')[0] + '?index=' + i
        return { id: v.id, img_url }
    });
}
setData()
let dargIndex = ref(-1);

let oldData: any = null;
let newData: any = null;
const loading = ref(false)
const uploadList = ref([] as any)
const timr = ref<NodeJS.Timeout | null>(null)
const changeUpload = ({ file, fileList }: any) => {
    clearTimeout(timr.value as any)
    timr.value = setTimeout(() => {
        uploadList.value = fileList
        beforeUpload2()
    }, 1000)
}
const beforeUpload: any = async (rawFile: File) => { }
const beforeUpload2: any = async () => {
    if (images.value.length >= props.max) {
        return message.error('最多上传' + props.max + '张图片')
    }
    if (uploadList.value.length) {
        if (loading.value) return
        loading.value = true
        for (let i = 0; i < uploadList.value.length; i++) {
            const isJPG = uploadList.value[i].originFileObj.type === 'image/jpeg' || uploadList.value[i].originFileObj.type === 'image/png'
            const isLt2M = uploadList.value[i].originFileObj.size / 1024 / 1024 < 2
            if (!isJPG) {
                message.error('上传图片只能是 JPG,png 格式!')
                loading.value = false
                uploadList.value = []
                return
            }
            if (!isLt2M) {
                message.error('上传图片大小不能超过 2MB!')
                loading.value = false
                uploadList.value = []
                return
            }
            // const foremData = new FormData()
            // foremData.append('image', uploadList.value[i].originFileObj)
            // foremData.append('module', props.upLoadMode)
            // console.log(props.upLoadMode);
            if (images.value.length >= props.max) {
                loading.value = false
                message.error('最多上传' + props.max + '张图片')
                return
            }
            const resx = await ossUpLoadImage(uploadList.value[i].originFileObj, 'admin/' + props.upLoadMode)
            images.value.push({ sign: true, id: new Date().getTime(), img_url: resx + '?index=' + new Date().getTime() })
            emit('change', images.value)
        }
        uploadList.value = []
    }
    loading.value = false
}
const delShow = ref(false)
function dragstart(value: any, index: number) {
    delShow.value = true
    oldData = value
    dargIndex.value = index
}
function dragenter(value: any, e: any) {
    newData = value
    e.preventDefault()
}

function dragover(e: any) {
    e.preventDefault()
}
const delImg = (item: any) => {
    images.value.splice(images.value.indexOf(item), 1)
    emit('change', images.value)
}
function dragend(item: any, event: any) {
    if (oldData.img_url !== newData.img_url) {
        let oldIndex = images.value.indexOf(oldData);
        let newIndex = images.value.indexOf(newData);
        if (props.sortType === 'insert') {
            let newItems = [...images.value]
            newItems.splice(oldIndex, 1)
            newItems.splice(newIndex, 0, oldData)
            images.value = [...newItems]
        } else {
            [images.value[oldIndex], images.value[newIndex]] = [images.value[newIndex], images.value[oldIndex]];
        }
        emit('change', images.value)
    }
    dargIndex.value = -1
    delShow.value = false
}
</script>

<style scoped>
.uploadBox {
    width: var(--itemWidth);
    height: var(--itemHeight);
    border: 1px dashed #ccc;
    background: none !important;
    background-color: #eee !important;
    display: flex;
    justify-content: center;
    align-items: center;
}

.plus-icon {
    width: var(--itemWidth);
    height: var(--itemHeight);
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: calc(var(--itemWidth) / 4);
}

* {
    padding: 0;
    margin: 0;
    box-sizing: border-box;
    line-height: 1;
}

.del {
    width: var(--itemWidth);
    display: flex;
    justify-content: center;
    align-items: center;
    margin-top: 5px;
    color: rgb(240, 110, 110);
}

.del i {
    margin-left: 5px;
    font-size: 12px;
    font-style: normal;
}

.drag-sort-box {
    width: 100%;
    display: flex;
    flex-wrap: wrap;
}

.drag-sort-box .drag-sort-item {
    width: var(--itemWidth);
    ;
    height: var(--itemHeight);
    ;
    margin: 2px;
    cursor: pointer;
    transition: all 0.3s;
    background: #ccc;
    position: relative;
}

.drag-sort-box .drag-sort-item img {
    width: 100%;
    height: 100%;
    transition: all 0.3s;
    position: relative;
}

.drag-sort-box .drag-sort-item .active {
    border-radius: 30px;
    box-shadow: 0 0 15px rgba(0, 0, 0, 0.3);
    opacity: 0;
}

.moxsind {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #dfd6d6;
    text-shadow: 0 0 15px rgba(0, 0, 0, 0.3);
    font-size: 50px;
    font-weight: 600;
    position: absolute;
    top: 0;
    left: 0;
}


#uploader>.ant-upload {
    width: var(--itemWidth);
    height: var(--itemHeight);
    border: 1px dashed #ccc;
    background-color: #eee;
    display: flex;
    justify-self: center;
    align-items: center;

    /* .uploader-icon {
        width: var(--itemWidth);
        height: var(--itemHeight);
    }

    &:hover {
        border: 1px dashed #6cb2fb;
        color: #6cb2fb;
    } */
}
</style>