1. 效果
选择文件后:
2. 代码
<template>
<div
class="drop-zone c-normal"
:class="{
borderOutline: outline,
}"
@dragover.prevent
@drop.prevent="handleDrop"
@click="chooseFiles"
>
<div v-if="files.length < 1" class="flex items-center justify-center">
<slot name="blank">
<div class="f-6">
<svg-icon icon-class="upload-fill"></svg-icon>
</div>
</slot>
</div>
<div v-if="files.length < 1">
<slot name="title" :is-dragging="isDragging">
<div v-if="!isDragging" class="upload-instructions f6">点击或将文件拖到这里上传</div>
<div v-else class="dragging-feedback">释放以上传文件</div>
</slot>
</div>
<div class="flex items-center">
<div v-for="(file, index) in files" :key="index" class="flex file-item pa2 flex-column">
<div class="f2 pa2">
<svg-icon icon-class="file" />
</div>
<div class="f6 truncate">{{ file.name }}</div>
<div class="delete-icon" @click.stop="removeFile(index)">
<svg-icon icon-class="delete" />
</div>
</div>
</div>
<input ref="fileInput" type="file" multiple style="display: none" @change="inputChange" />
</div>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue';
import SvgIcon from '@/components/SvgIcon/SvgIcon.vue';
type Prop = {
outline?: boolean;
maxFiles?: number;
data: any;
fileChange?: (data: any) => boolean | Promise<boolean>;
};
const props = withDefaults(defineProps<Prop>(), {
outline: true,
maxFiles: -1,
data: [],
fileChange: () => true,
});
const emits = defineEmits(['update:data']);
const isDragging = ref(false);
const files = computed({
get() {
return props.data;
},
set: async (val) => {
const flag = await props.fileChange(val);
if (val.length > props.maxFiles){
//:TODO 这里需要自定义一哈
console.log('最多只能上传'+props.maxFiles+'个文件');
return
}
if (flag === false) {
return;
}
emits('update:data', val);
},
});
const fileInput = ref<HTMLInputElement>();
const handleDrop = (e: DragEvent) => {
isDragging.value = false;
const newFiles: File[] | any = e.dataTransfer?.files || [];
files.value = [...files.value, ...newFiles];
};
const chooseFiles = () => {
if (fileInput.value) {
fileInput.value.click();
}
};
const inputChange = (e: Event) => {
const target = e.target as HTMLInputElement;
const newFiles = target.files || [];
files.value = [...files.value, ...newFiles];
};
const removeFile = (index: number) => {
files.value.splice(index, 1);
};
</script>
<style lang="scss" scoped>
/*这里的我用的是外部的公共样式, 实际并没有在组件内, 这里将用到的样式复制在下面了*/
:root{
--c-normal: #a6a5ad;
}
.flex {
display: flex;
}
.items-center {
align-items: center;
}
.justify-center {
justify-content: center;
}
.c{
&-normal{
color: var(--c-normal);
}
}
.f2 {
font-size: 2.25rem;
}
.pa2 {
padding: 0.5rem;
}
.f-6,
.f-headline {
font-size: 6rem;
}
.truncate {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* ↑↑↑ 上面都是引用的公共样式 */
.drop-zone {
box-sizing: border-box;
width: 100%;
height: 100%;
padding: 20px;
text-align: center;
min-height: 150px;
overflow-y: auto;
&.borderOutline {
border: 2px dashed #aaa;
}
.dragging-feedback {
background-color: var(--c-normal);
}
.upload-instructions {
margin-bottom: 10px;
}
.file-item {
position: relative;
width: 6rem;
.delete-icon {
position: absolute;
top: 5px;
right: 5px;
cursor: pointer;
color: var(--c-normal);
&:hover {
color: #1a1a1a;
}
}
}
}
</style>
这个组件引入了
<svg-icon/>
这个组件(这里可以替换为自己需要的组件),svg-icon
这个组件的封装可以参考这篇文章: vue3 + vite +ts 封住昂svg-icon组件这篇文章距离现在时间比较长了, 可能会有一些版本问题, 若遇问题可参考其他相似的文章
如果有什么问题或建议欢迎评论或留言
3. 组件说明
prop参数
name | 说明 | 默认值 |
---|---|---|
outline | 外边框 | true |
maxFiles | 最大文件数(大于-1时生效) | -1 |
data | 文件数据(双向绑定的) | [] |
fileChange | 当文件变化时生效需返回一个boolen或Promise<boolean> 类型 | ()=>true |
slot
name | 说明 | 参数 |
---|---|---|
blank | 文件时显示的图标 | - |
title | 显示的文字 | isDragging是否正在拖拽 |