马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
一、html 代码:
代码中的表格引入了 vxe-table 插件
<Tag /> 是自己封装的说明组件
表格列表这块我使用了插槽来增长扩展性,可根据自己需求,在组件外部做调解
二、js 代码:
目前支持校验上传的文件类型有三种:
image:图片类型;video:视频类型;excel:表格类型
这块重要思路是:将文件夹判定后举行递归,获取出文件夹中的文件出来,末了雷同单个文件上传,然后将文件流举行遍历 append 进创建的 FormData 对象。具体方法看:readFiles() 和 handleUploadToServer()
- <script lang="ts" setup>
- import { reactive, ref, getCurrentInstance } from 'vue';
- const { proxy }: any = getCurrentInstance();
- const $tool = proxy.$tool;
- const props = defineProps({
- // 组件参数配置
- data: {
- type: Object,
- default: () => ({
- // fileSize: 100, // 文件大小限制
- /*** type 对象为空或者不传,则不限制上传类型 */
- // type: {
- // 自定义上传的文件类型 = image:图片类型;video:视频类型;excel:表格类型
- // image: ['png', 'jpg', 'jpeg'],
- // video: ['mp4', 'avi', 'mov'],
- // excel: ['xlsx', 'xls']
- // },
- /*** 格式错误自定义提示:根据 type 来,但格式需要写成 ${'type中的key'},因组件内部代码采用:查找 ${} 进行替换;不传则使用组件内默认提示 */
- // formatMessage: {
- // 'image': '只支持上传图片:${image} 格式',
- // 'image,video': '支持上传图片:${image} 格式,视频:${video} 格式',
- // 'image,video,excel': '支持上传图片:${image} 格式,视频:${video} 格式,Excel文件:${excel} 格式'
- // }
- // limit: 0, // 允许上传文件的最大数量,0或不传默认无限制
- // fileName: '', // 超出后列表展示的文件名:不传默认为SPU
- }),
- },
- // 是否支持窗口拖拽,默认true
- draggable: {
- type: Boolean,
- default: true
- },
- // 是否自动上传文件,默认true
- autoUpload: {
- type: Boolean,
- default: true
- },
- // 是否支持打开 file 单文件上传,不传默认false
- singleFile: {
- type: Boolean,
- default: false
- },
- //是否需要开启:列表上传失败 和 提交按钮插槽;默认不开启,展示组件内的失败列表 和 提交逻辑
- openSlot: {
- type: Boolean,
- default: false
- }
- });
- /**
- * @param dragUploadAxiosFn 上传参数抛出,外部做处理,不与组件内部逻辑耦合
- * @param dragUploadErrorTable 超出限制后,展示的列表插槽抛出,外部做处理,不与组件内部逻辑耦合
- */
- const emit = defineEmits(['dragUploadAxiosFn', 'dragUploadErrorTable']);
- const fileUploadRef = ref();
- const fileUploadFloderRef = ref();
- const fileData: any = reactive({
- step: 1, // 步骤: 1:文件拖拽上传;2:文件超出提示
- uploadList: [], //上传的文件列表
- waitUploadList: [], //存储待上传的文件列表
- fileSizeList: [], //存储遍历出来文件里面所有的图片路径及大小
- goBeyondTable: [], //超出限制后,将遍历项还原成文件夹项展示的列表
- allSize: 0, //文件总大小 MB
- firstFileName: 'pathName0' //第一列字段:key
- });
- /*文件上传input*/
- const toUploadFile = () => {
- fileUploadRef.value.click();
- };
- /*文件夹上传input*/
- const toUploadFloder = () => {
- fileUploadFloderRef.value.click();
- };
- /*选择文件改变*/
- const handleFileChange = (e: any) => {
- if (e.target.files) {
- let filesList: any = Array.from(e.target.files);
- filesList.forEach((item: any) => {
- let size = item.size / 1024 / 1024;
- fileData.allSize += size;
- let obj: any = getPath(item.name);
- changeFileSizeList(item, obj);
- });
- fileData.allSize = fileData.allSize.toFixed(2); // 文件总大小 MB
- fileData.waitUploadList = filesList;
- if (!fileLimitFn(fileData.fileSizeList)) return; // 校验方法
- handleUploadToServer(); //上传文件到服务器
- }
- };
- /*文件夹目录上传*/
- const handleFloderChange = (e: any) => {
- if (e.target.files) {
- let filesList: any = Array.from(e.target.files);
- filesList.forEach((item: any) => {
- let size = item.size / 1024 / 1024;
- fileData.allSize += size;
- let obj: any = getPath(item.webkitRelativePath); // 通过路径获取名称方法
- changeFileSizeList(item, obj);
- });
- filesList.reverse(); // 反转数组,保证最先选择的文件排在最后面
- fileData.allSize = fileData.allSize.toFixed(2); // 文件总大小 MB
- fileData.waitUploadList = filesList;
- if (!fileLimitFn(fileData.fileSizeList)) return; // 校验方法
- handleUploadToServer(); //上传文件到服务器
- }
- };
- // 拖放进入目标区域
- const handleDragOver = (event) => {
- event.preventDefault();
- };
- // 拖拽放置
- const handleDrop = async (event) => {
- event.preventDefault();
- const files = [];
- const promises: any[] = [];
- for (const item of event.dataTransfer.items) {
- const entry: any = item.webkitGetAsEntry();
-
- if (!entry.isDirectory && !props.singleFile) {
- proxy.$message.error(`只支持文件夹上传,不支持单个文件上传!`);
- return;
- }
- promises.push(readFiles(entry));
- }
- const resultFilesArrays = await Promise.all(promises); // 等待所有文件读取完成
- fileData.waitUploadList = resultFilesArrays.flat();
- if (!fileLimitFn(fileData.fileSizeList)) return; // 校验方法
- handleUploadToServer(); //上传文件到服务器
- };
- //文件各种限制判断方法封装
- const fileLimitFn = (fileSizeList: any) => {
- //文件类型判断和格式限制
- if (props.data.type) {
- if (!testingFileType(fileSizeList)) return false;
- }
- //文件数量超出限制
- if (props.data.limit && props.data.limit !== 0) {
- if (!fileLimit(fileSizeList)) return false;
- }
- //文件大小超出限制
- if (props.data.fileSize) {
- if (!fileSizeLimit(fileSizeList)) {
- fileData.goBeyondTable = getGoBeyondTable(fileSizeList);
- fileData.step = 2;
- return false;
- }
- }
- return true;
- };
- /**
- * 验证文件类型是否为允许的上传的类型
- * @param fileSizeList 文件列表
- * @param type image:图片类型;video:视频类型;excel:表格类型
- * @param formatMessage 外部传入的自定义提示信息,替换默认提示信息或者新增进入提示信息
- * @returns 如果文件类型不符合要求,返回 false;否则返回 true
- */
- const testingFileType = (fileSizeList: any) => {
- let type: any = props.data.type;
- if (Object.keys(type).length === 0) return true; // type 对象为空或者不传,则不限制上传类型
- // 使用 Object.values 获取对象值的数组,然后使用 flatMap 合并所有数组
- const typeList = Object.values(type).flatMap((array: any) => array);
- // 获取所有键的字符串表示,例如 "image,video,excel"
- const keysString = Object.keys(type).join(',');
- const messageTemplates = {
- 'image': '只支持上传图片:${image} 格式',
- 'video': '只支持上传视频:${video} 格式',
- 'excel': '只支持上传Excel文件:${excel} 格式',
- 'image,video': '支持上传图片:${image} 格式,视频:${video} 格式',
- 'image,excel': '支持上传图片:${image} 格式,Excel文件:${excel} 格式',
- 'video,excel': '支持上传视频:${video} 格式,Excel文件:${excel} 格式',
- 'image,video,excel': '支持上传图片:${image} 格式,视频:${video} 格式,Excel文件:${excel} 格式'
- };
- // 外部传入:自定义提示信息替换成外部传入的
- if (props.data.formatMessage && Object.keys(props.data.formatMessage).length) {
- let msg: any = props.data.formatMessage;
- for (const key in msg) {
- if (messageTemplates.hasOwnProperty(key)) {
- // 如果 messageTemplates 中存在该键,则替换其值
- messageTemplates[key] = msg[key];
- } else {
- // 不存在则增加进去
- messageTemplates[key] = msg[key];
- }
- }
- }
- // 正则:查找 ${},后进行替换
- let message: string = '';
- if (messageTemplates[keysString]) {
- message = messageTemplates[keysString].replace(/\$\{(\w+)\}/g, (_, match) => type[match].join(', '));
- } else {
- message = `不支持上传该类型的文件:${keysString},请重新上传!`;
- }
- //过滤不符合要求的文件类型
- let filterList: any = fileSizeList.filter((item: any) => !typeList.includes(item.type));
- if (filterList.length) {
- //错误的文件,不管里面有没有正确格式的,一律清除
- fileData.fileSizeList = [];
- fileData.allSize = 0;
- proxy.$message.error(message);
- return false;
- };
- return true;
- };
- //文件数量超出限制
- const fileLimit = (fileSizeList: any) => {
- if (fileSizeList.length > props.data.limit) {
- proxy.$message.error(`文件数量不能超过 ${props.data.limit} 个,请重新上传!`);
- fileData.fileSizeList = [];
- return false;
- }
- return true;
- };
- //文件超出限制
- const fileSizeLimit = (fileSizeList: any) => {
- let allSize = fileSizeList.reduce((accumulator, currentValue) => {
- if (currentValue) {
- return accumulator + currentValue.size;
- }
- return accumulator;
- }, 0);
- let fileSize = props.data.fileSize * 1024 * 1024;
- fileData.allSize = (allSize / 1024 / 1024).toFixed(2); //存储文件总大小 MB
- if (allSize > fileSize) {
- proxy.$message.error(`文件总大小不能超过 ${props.data.fileSize} MB,请重新上传!`);
- emit('dragUploadErrorTable', fileData);
- return false;
- }
- return true;
- };
- // 操作数据:文件超出后,展示的列表
- const getGoBeyondTable = (fileSizeList: any) => {
- // 遍历,相同第一列为一项,size累加
- let result: any = fileSizeList.reduce((accumulator, current) => {
- if (accumulator[current[fileData.firstFileName]]) { //如果已经存在,则累加size
- accumulator[current[fileData.firstFileName]].size += current.size;
- } else {
- accumulator[current[fileData.firstFileName]] = { ...current };
- }
- return accumulator;
- }, {});
-
- // 将结果对象转换回数组
- result = Object.values(result);
- // 处理size为MB单位
- result.forEach((item: any) => {
- item.size = (item.size / 1024 / 1024).toFixed(2);
- });
- return result;
- };
- //移除超出文件列表的项
- const handleDelete = (rowIndex: number, row: any) => {
- fileData.goBeyondTable.splice(rowIndex, 1);
- fileData.allSize = (fileData.allSize - row.size).toFixed(2); //更新总大小MB
- fileData.fileSizeList = fileData.fileSizeList.filter((item: any) => item[fileData.firstFileName] !== row[fileData.firstFileName]);
- fileData.waitUploadList = fileData.waitUploadList.filter((item: any) => item[fileData.firstFileName] !== row[fileData.firstFileName]);
- };
- /*请求上传到服务器*/
- const handleUploadToServer = (click?: boolean) => {
- let autoUpload: boolean = props.autoUpload;
- if (click && fileData.step === 1) autoUpload = true; //手动上传
- if (!autoUpload && fileData.step === 1) return; //不自动上传,则不执行
- fileData.uploadList = fileData.waitUploadList;
- // 再次提交时验证文件大小是否超出限制
- if (click && fileData.step === 2 && fileData.allSize > props.data.fileSize) {
- proxy.$message.error(`文件总大小不能超过 ${props.data.fileSize} MB`);
- return;
- }
-
- let formData = new FormData();
- fileData.uploadList.forEach((item: any) => {
- formData.append(`${item.filePathName}`, item);
- });
-
- // // 遍历FormData对象并打印其内容:查看FormData对象数据是否正确
- // for (let [key, value] of formData.entries()) {
- // console.log(`${key}: ${value}`);
- // }
- emit('dragUploadAxiosFn', formData); //上传参数抛出,外部做操作,不在组件做耦合
- };
- //此方法:如果是文件夹,则会递归调用自己,所以最后都会走 else 的逻辑
- const readFiles = async (item: any) => {
- if (item.isDirectory) {
- // 是一个文件夹
- const directoryReader = item.createReader();
- // readEntries是一个异步方法
- const entries: any[] = await new Promise((resolve, reject) => {
- directoryReader.readEntries(resolve, reject);
- });
- let files = [];
- for (const entry of entries) {
- const resultFiles: any = await readFiles(entry);
- files = files.concat(resultFiles);
- }
- return files;
- } else {
- // file也是一个异步方法
- const file = await new Promise((resolve, reject) => {
- item.file(resolve, reject);
- });
- let obj: any = getPath(item.fullPath); //通过路径获取名称方法
- changeFileSizeList(file, obj);
- return [file];
- }
- };
- //更改 fileData.fileSizeList 的值:公共
- const changeFileSizeList = (file: any, obj: any) => {
- try {
- file.filePathName = obj.filePathName; //添加路径名称
- file[fileData.firstFileName] = obj.pathObj[fileData.firstFileName]; //添加第一列文件名
- file.pathObj = obj.pathObj;
- let index = file.name.lastIndexOf('.');
- fileData.fileSizeList.push({ //添加图片路径、大小、名称
- filePathName: obj.filePathName,
- size: file.size,
- type: file.name.substring(index + 1),
- ...obj.pathObj
- });
- } catch {
- proxy.$message.error(`文件读取路径失败,请重新上传文件!`);
- }
- };
- //通过路径获取名称方法:公共
- const getPath = (path: string) => {
- try {
- let filePathName: any = path; // 传给后端的全路径
- if (path.startsWith('/')) { // 如果路径以斜杠开头,则删除第一个斜杠
- filePathName = path.slice(1);
- }
- let parts = filePathName.split('/'); // 路径分割成数组
- let pathObj = {}; // 存储每个部分
- for (let i = 0; i < parts.length; i++) {
- if (parts[i] !== '') { // 跳过空字符串(如果路径以 / 开头或结尾)
- pathObj['pathName' + (i)] = parts[i];
- }
- }
- return {
- filePathName: filePathName,
- pathObj: pathObj
- }
- } catch {
- proxy.$message.error(`文件读取路径失败,请重新上传文件!`);
- }
- };
- //关闭事件
- const closeDialogFn = () => {
- if (fileData.step === 1) {
- props.data.visible = false; //关闭弹窗
- return;
- }
- proxy.$messageBox({
- title: '关闭',
- message: '关闭后不会保留您所做的更改,确定关闭吗?',
- callback: (value: string) => {
- //confirm=确认;cancel=取消
- if (value === 'confirm') {
- fileData.step = 1;
- props.data.visible = false; //关闭弹窗
- }
- }
- });
- };
- </script>
复制代码 三、css 代码:
四、vue 页面中使用:
- <!-- 拖拽上传 -->
- <DragUpload v-if="dragUpload.visible"
- :data="dragUpload"
- @dragUploadAxiosFn="dragUploadAxiosFn"
- />
复制代码- const dragUpload: any = reactive({
- visible: false,
- fileSize: 100, // 单位字节 MB
- type: { //定义上传的文件类型 = image:图片类型;video:视频类型;excel:表格类型
- image: ['png', 'jpg', 'jpeg'],
- },
- formatMessage: {
- 'image': '自定义外部传入${image} 格式',
- },
- fileName: 'SPU' // 超出后列表展示的文件名
- });
复制代码 五、上传到后端接口的参数:
六、效果图,如下:
七、额外补充,后端接收文件流的方法:
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |