多张图片上传、图片回显、url路径转成File文件

news2024/12/23 9:15:44

1. 实现

背景:在表单中使用element-plus实现多张图片上传(限制最多10张),因为还要与其他参数一起上传,所以使用formData格式。
编辑表单回显时得到的是图片路径数组,上传的格式是File,所以要进行一次转换。
在这里插入图片描述

<template>
    <el-dialog v-model="visible" :title="`${props.type === 'add' ? '新增' : '编辑'}`" direction="rtl" @close="handleDialogClose"
        :close-on-click-modal="false" class="auto-dialog" :center="true" destroy-on-close>
        <el-form ref="ruleFormRef" :model="ruleForm" label-position="right" label-width="auto">
            <!-- 省略表单项... -->
            
            <!-- 上传多张图片 -->
            <el-upload v-model:file-list="pictureList" accept=".png,.jpg,.jpeg" :auto-upload="false"
                list-type="picture-card" :class="{ 'upload-hide': pictureList?.length === 10 }"  :on-change="handleChanges" :on-preview="handlePictureCardPreview">
                <el-icon>
                    <Plus />
                </el-icon>
            </el-upload>
            <el-dialog v-model="previewVisible">
                <img w-full :src="dialogImageUrl" alt="Preview Image" />
            </el-dialog>

            <el-button type="primary" @click="handleSubmit">提交</el-button>
        </el-form>
    </el-dialog>
</template>
<script setup lang="ts">
import type { UploadProps, UploadFile, UploadFiles } from 'element-plus';
import _ from '@lodash';

const visible = defineModel<boolean>({ default: false })
const props = defineProps<{
    type: 'add' | 'mod',
    id?: string
}>()
// 图片列表
const pictureList = ref<any[]>([])
// 图片预览显示
const previewVisible = ref(false)
// 图片预览url
const dialogImageUrl = ref('')
// 除图片外上传的其他参数
const ruleForm = reactive<Record<string, string>>({
    code: '',
    // 省略..
})

// 编辑时数据回显
watch(() => visible.value, async (val) => {
    if (val && props.type === 'mod' && props.id) {
        await getEditData(props.id)
    }
}, {
    deep: true
})
// 上传图片
const handleChanges: UploadProps['onChange'] = (file: UploadFile, fileList: UploadFiles) => {
    // 文件格式
    const isPngOrJpg = ['image/png', 'image/jpeg'].includes(file.raw.type)
    if (!isPngOrJpg) {
        ElMessage.warning('上传文件格式错误!');
        return false;
    }
    // 文件名重复
    const isDuplicate = pictureList.value?.some(item => item.name === file.name);
    if (isDuplicate) {
        ElMessage.warning('该文件已存在,请重新选择!');
        // 移除新添加的重复文件
        fileList.pop();
        pictureList.value = fileList;
    } else {
        pictureList.value = fileList;
    }
};

// 点击图片预览
const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile: UploadFile) => {
    dialogImageUrl.value = uploadFile.url!
    previewVisible.value = true
}
// 编辑时数据回显
async function getEditData(id?: number) {
    try {
        if (!id) return;
        await nextTick()
        const res = await getEditData({ id });
        if (res.code || _.isEmpty(res?.data)) throw new Error(res?.message);
        ruleForm.value = _.cloneDeep(res?.data);//表单项回显
        
        // 图片列表数据格式要以{url: '', name: ''}格式,才能正确回显
        pictureList.value = ruleForm.value.pictures?.map((item: any) => {
            return {
                url: item,
                name: item?.url?.split('/').pop()
            }
        })
    } catch (error) {
        if (error?.code === RESPONSE_CODE.CANCEL) return;
        ElMessage.error(error?.message);
        console.log(`[log] - getEditData - error:`, error);
    }
};
// 路径url转成file文件格式
async function convertUrlToFile(imageUrl: string, fileName: string) {
    try {
        // 发起GET请求获取资源,设置responseType为blob
        const response = await fetch(imageUrl, { method: 'GET', mode: 'cors' });
        // 检查请求是否成功
        if (!response.ok) {
            throw new Error('图片加载失败!');
        }
        // 获取Blob数据
        const blob = await response.blob();
        // 创建File对象
        const file = new File([blob], fileName, { type: blob.type });
        return file;
    } catch (error) {
        console.error('图片url转换Blob失败!', error);
        return null;
    }
}
// 提交
async function handleSubmit() {
    try {
        // 表单校验省略...

        const fd = new FormData();
        // 除图片外的其他参数 (只上传图片,这步跳过)
        Object.keys(ruleForm).forEach(key => {
            fd.append(key, ruleForm[key]);
        });

        if (!_.isEmpty(pictureList.value)) {
            return ElMessage.warning('请先选择图片!');
        } else {
            const pictures = [] as File[]
            // 图片列表处理:
            for (let item of pictureList.value) {
                // 1. 图片url,需要先将url转换为文件格式,再上传
                if (!item?.raw) {
                    const fileName = item?.url?.split('/').pop()
                    const res = await convertUrlToFile(item.url, fileName)
                    if (!res) return
                    pictures.push(res)
                } else {
                    // 2. 图片文件,直接上传
                    pictures.push(item?.raw)
                }
            }
            pictures.forEach((item) => {
                fd.append('pictures', item);
            });
        }

        const res = await updateData(fd);
        if (res?.code) throw new Error(res?.message);
        ElMessage.success(res?.message );
        visible.value = false;
    } catch (error) {
        console.log(`[log] - handleSubmit - error:`, error);
        ElMessage.error(error?.message );
    }
}
</script>
<style scoped>
:deep(.el-upload-list--picture-card) {
    --el-upload-list-picture-card-size: 94px;
    width: 100%;
    max-height: 210px;
    overflow: auto;
}

:deep(.el-upload--picture-card) {
    --el-upload-picture-card-size: 94px
}

.upload-hide {
    :deep(.el-upload--picture-card) {
        display: none;
    }
}
</style>

2. 踩坑记录

问题:在对图片列表遍历后处理时,一开始在forEach中进行文件格式转换操作,数据项无法插入formData中,但控制台打印有值。
原错误写法:

        if (!_.isEmpty(pictureList.value)) {
            const pictures = [] as File[]
            pictureList.value.forEach(async(item) => {
                if (!item?.raw) {
                    const fileName = item?.url?.split('/').pop()
                    const res = await convertUrlToFile(item.url, fileName)
                    if(!res) return
                    pictures.push(res)
                } else {
                    pictures.push(item?.raw)
                }
            })
            console.log(pictures,'pictures');// 这里能打印
            pictures.forEach((item) => {
                fd.append('pictures', item);
            });
        }

原因
forEach并发执行,在每次迭代时会立即执行指定的回调函数,并且不会等待上一次迭代的结果,所以并不能保证每次convertUrlToFile操作都已完成。

解决方法: 使用promise.all() 确保遍历执行的所有操作都完成后,再执行append操作。
另外,也可以使用for...of 循环,因为它是用迭代器实现的,每次迭代都会等待 next() 返回,所以可以保证执行的顺序。

if (!_.isEmpty(pictureList.value)) {
  const promises = pictureList.value.map(async (item) => {
    if (!item?.raw) {
      const fileName = item?.url?.split('/').pop();
      const res = await convertUrlToFile(item.url, fileName);
      if (!res) return;
      return res;
    } else {
      return item?.raw;
    }
  });

  Promise.all(promises)
    .then((filledPictures) => {
      const pictures = filledPictures.filter(Boolean) as File[];
      pictures.forEach((item) => {
        fd.append('pictures', item);
      });
    })
    .catch((error) => {
      console.error('Error:', error);
    });
}

JavaScript 中的 BLOB 数据结构的使用介绍
谈谈JS二进制:File、Blob、FileReader、ArrayBuffer、Base64
Base64、Blob、File 三种类型的相互转换 最详细

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1707396.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IDEA提示Untrusted Server‘s certificate

如果你用的是Intellij系列IDE&#xff08;GoLand, PHPStorm, WebStorm, IDEA&#xff09;&#xff0c;突然弹出个提示『Untrusted Servers certificate 』 莫慌&#xff0c;这是因为你用了破解版的 IDE&#xff0c;破解过程中有个hosts绑定的操作&#xff1a; 0.0.0.0 account.…

智慧农田视频监控技术应用:智能监管引领农业新时代

据新闻报道&#xff0c;5月24日合肥市公安局接到群众报警&#xff0c;反映自己辛苦种植的小麦有几十亩地被人偷偷用收割机盗割。公安机关迅速出警并立案侦查&#xff0c;通过查看监控视频得知&#xff0c;用户所在的公司租用了几千亩土地进行农业种植&#xff0c;因公司与村民之…

nginx的常用配置与命令相关硬核干货

今天小晨跟大家分享Nginx常用配置与命令相关的硬核干货&#xff0c;可以说运维工作中基本都会用到这些&#xff0c;掌握它&#xff0c;你可以不用求人&#xff01; Nginx特点 高并发、高性能&#xff1b; 模块化架构使得它的扩展性非常好&#xff1b; 异步非阻塞的事件驱动模…

adb 启动app并查看启动时间

启动app adb shell am start -n 包名/界面名 获取app的启动时长 adb shell am start -W 包名/界面名 要启动一个app 就需要知道其包名与界面名,提前打开一个程序&#xff0c;然后执行以下程序 C:\Users\i5ba0>adb shell dumpsys window windows | findstr mFocusedAppm…

CentOS8搭载正反向解析dns服务器

1.介绍&#xff08;是什么&#xff09; DNS&#xff08;Domain Name System&#xff09;&#xff0c;即域名系统&#xff0c;是一个将域名和 IP 地址相互映射的分布式数据库&#xff0c;它可以将用户输入的域名转换成对应的 IP 地址。DNS 由多个服务器组成&#xff0c;分为多个…

The Sandbox DAO:投票决定元宇宙的未来!

赋予用户治理权&#xff0c;打造由社群运营的开放式数码国度 随着The Sandbox DAO的启动&#xff0c;我们邀请全球社群——这个新数字国度的公民们——提出建议并参与治理&#xff0c;共同塑造开放元宇宙的未来。 介绍 在The Sandbox&#xff0c;我们正在建立一个开放的元宇宙…

LVM和配额管理

文章目录 一、LVM1.1 LVM概述1.2 LVM的管理命令1.3 创建LVM的过程第一步&#xff1a;先创建物理卷第二步&#xff1a;创建逻辑卷组 / 扩容第三步&#xff1a;创建逻辑卷 / 扩容对ext4文件系统的管理 1.4 删除LVM 二、磁盘配额2.1 磁盘配额概述2.2 磁盘配额命令2.3 磁盘配额设置…

UE5 使用外置摄像头进行拍照并保存到本地

连接外置摄像头功能&#xff1a;https://docs.unrealengine.com/4.27/zh-CN/WorkingWithMedia/IntegratingMedia/MediaFramework/HowTo/UsingWebCams/ 核心功能&#xff1a;UE4 相机拍照功能&#xff08;图片保存&#xff09;_ue 移动端保存图片-CSDN博客 思路是&#xff1a; …

亿道三防8寸安卓三防加固工业平板电脑M81T|加固手持终端pad

在现代信息化时代&#xff0c;随着科技的飞速发展&#xff0c;移动终端设备在各行各业中发挥着越来越重要的作用。为了应对复杂多变的使用环境和严苛的工作需求&#xff0c;亿道三防推出了一款加固型8寸安卓系统三防平板电脑M81T。这款设备不仅具备强大的硬件配置和先进的软件功…

如何使用 Monte Carlo 模拟作为项目管理工具

Monte Carlo 模拟是一种预测不确定事件可能结果的数学技术。我们之前曾撰写过有关其为研发专业人员带来的益处的文章&#xff0c;并主持过有关 Monte Carlo 模拟功能的网络讲座&#xff0c;以帮助产品满足预期规格、预测过程能力并确定最佳过程设置。然而&#xff0c;Monte …

手机里装上好用的实用工具,生活办公更轻松

手机里装上好用的实用工具&#xff0c;生活办公才更轻松&#xff01;~ 1.一个木函 安卓手机里的“工具百宝箱”&#xff0c;应用集成了超过100种实用工具&#xff0c;包括单位和汇率换算、查询、OCR图片文字识别、自动文章摘要、图片表格识别和表情制作等等。它提供了全面的多…

LeetCode刷题之HOT100之盛最多水的容器

狂风大作&#xff0c;降温了。下午提前把题目做了&#xff0c;上午做的最长回文子串还不是很能理解&#xff0c;慢慢理解吧&#xff0c;且看题 1、题目描述 2、逻辑分析 题目要求很清晰&#xff0c;我的思路就是暴力解法&#xff1a;枚举全部可能的情况&#xff08;两数不相等…

I.MX6ULL模仿 STM32 驱动开发格式实验

系列文章目录 I.MX6ULL模仿 STM32 驱动开发格式实验 I.MX6ULL模仿 STM32 驱动开发格式实验 系列文章目录一、前言二、模仿 STM32 寄存器定义2.1 STM32 寄存器定义简介2.2 I.MX6Ul 寄存器定义2.3硬件原理图2.4实验程序编写 三、编译下载验证 一、前言 使用 C 语言编写 LED 灯驱…

解决vue版本不一致导致不能正常编译

解决vue版本不一致导致不能正常编译 异常现象分析原因解决方案 异常现象 项目原本运行无异常&#xff0c;但安装了一个el-table-infinite-scroll的插件后&#xff0c;编译报错&#xff0c;截图如下 分析原因 vue版本与compile版本不一致&#xff0c;应该统一起来&#xff0…

crossover玩游戏缺少文件怎么办 为什么游戏打开说缺失文件 crossover支持的游戏列表 CrossOver 提示 X 11 缺失怎么办?

CrossOver是一款类虚拟机软件&#xff0c;可以实现在Mac电脑上运行exe程序。不少Mac用户为了玩游戏&#xff0c;选择使用CrossOver这款软件玩Windows平台的游戏。 一、CrossOver支持的软件多吗 CrossOver是一款基于Wine的兼容工具&#xff0c;它可以让你在Mac或Linux上运行许多…

深入分析C#中的“编写器”概念——代码修改、注解与重构

文章目录 1. 编写器&#xff08;Writer&#xff09;的概念2. 编写器的作用和工作原理3. 编写器的重要性4. 写入器常用方法5. 写入器示例6. 编写器示例——使用Fody进行代码注解和重构7. 总结 在软件开发过程中&#xff0c;代码的维护和更新是至关重要的。C#作为一种流行的编程语…

【问题解决】huggingface 离线模型下载

问题 因业务需要在本机测试embedding分词模型&#xff0c;使用 huggingface上的transformers 加载模型时&#xff0c;因为网络无法访问&#xff0c;不能从 huggingface 平台下载模型并加载出现如下错误。 下面提供几种模型下载办法 解决 有三种方式下载模型&#xff0c;一种是通…

分布式事务解决方案(最终一致性【可靠消息解决方案】)

可靠消息最终一致性解决方案 可靠消息最终一致性分布式事务解决方案指的是事务的发起方执行完本地事务之后&#xff0c;发出一条消息&#xff0c;事务的参与方&#xff0c;也就是消息的消费者一定能够接收到这条消息并且处理完成&#xff0c;这个方案强调的是只要事务发起方将消…

Mybatis入门——其他查询操作和数据库连接池(4)

目录 一、多表查询 二、#{} 和 ${} 1、#{} 和 ${} 的使用 &#xff08;1&#xff09;Integer类型的参数 #{} 的使用 ${} 的使用 &#xff08;2&#xff09;使用String类型的参数 #{} 的使用 ${} 的使用 小结&#xff1a; 2、#{} 和 ${} 的区别 &#xff08;1&#…

AI预测福彩3D采取888=3策略+和值012路一缩定乾坤测试5月28日预测第4弹

昨天的第二套方案已命中&#xff0c;第一套方案由于杀了对子&#xff0c;导致最终出错。 今天继续基于8883的大底&#xff0c;使用尽可能少的条件进行缩号&#xff0c;同时&#xff0c;同样准备两套方案&#xff0c;一套是我自己的条件进行缩号&#xff0c;另外一套是8883的大底…