介绍
- 目标需求:
- 组件图片flex布局适应 调用父容器宽度 进行换行
- 支持拖拽排序、删除、自定义传入初始值(回显)、点击大图预览
- 支持数量限制、展示图片容器宽度、高度限制、文件大小、文件格式限制
- 支持多图传入并
支持单文件等待上一个上传完毕 至服务器
- 支持自定义
module名称
- 接入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>