当前位置: 首页 > article >正文

封装一个vue3的文件上传组件(拖拽或点击选择文件)

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是否正在拖拽

http://www.kler.cn/a/319974.html

相关文章:

  • AAPM:基于大型语言模型代理的资产定价模型,夏普比率提高9.6%
  • 强化学习-蒙特卡洛方法
  • 建造者模式(或者称为生成器(构建器)模式)
  • Git学习笔记
  • Linux查看日志命令
  • 利用爬虫获取某学习软件的考试题库(带源码)
  • C++ | Leetcode C++题解之第437题路径总和III
  • react + antDesign封装图片预览组件(支持多张图片)
  • 服务端的 Session 详解
  • 无人机避障——4D 毫米波雷达 SLAM篇(一)
  • 3D扫描建模能代替传统建模吗?
  • 大觅网之业务部署(Business deployment of Da Mi Network)
  • Python发送邮件教程:如何实现自动化发信?
  • (学习记录)使用HAL库 STM32CubeMX——spi(DMA)配置OLED
  • 抽象类、比较器和接口
  • 大象机器人资料整理
  • /etc/init.d/mysql
  • 将上一篇的feign接口抽取到公共api模块(包含feign接口示例)
  • 如何在 macOS 上恢复未保存的 Excel 文件 – 文件恢复的最佳方法
  • 运维工程师面试整理-安全常见安全漏洞及修复
  • 31214324
  • 【延时队列的实现方式】
  • Leetcode 1396. 设计地铁系统
  • CentOS 7 aarch64制作openssh 9.9p1 rpm包 —— 筑梦之路
  • 如何在数据分析中处理异常?
  • 模块化编程实战:光敏传感器控制蜂鸣器(江科大stm32练习)