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

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.mfbz.cn/a/653687.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Pytorch环境配置2.0.1+ Cuda11.7

查找cuda、cudnn、Pytorch(GPU)及cuda和NVIDIA显卡驱动对应关系 查询可支持的最高cuda版本 nvidia-smi查看支持的cuda的版本 CUDA版本对应表 我的显卡驱动是Driver Version&#xff1a;535.40.&#xff0c;那么左边对应的CUDA都可以兼容 右上角为CUDA 版本&#xff0c;可以看…

gitLab 使用tortoiseGit 克隆新项目 一直提示tortoiseGitPlink输入密码 输完也不生效

问题描述&#xff1a;准备用TortoiseGit拉取gitlab上一个新项目代码&#xff0c;出现tortoiseGitPlink提示让输入密码&#xff0c;输入后又弹出&#xff0c;反复几次&#xff0c;无法down下来代码。 解决方案&#xff1a; 1.找到PuTTYgen工具&#xff0c;打开 2. 点击load 按钮…

基于 Milvus Cloud + LlamaIndex 实现初级 RAG

初级 RAG 初级 RAG 的定义 初级 RAG 研究范式代表了最早的方法论,在 ChatGPT 广泛采用后不久就取得了重要地位。初级 RAG 遵循传统的流程,包括索引创建(Indexing)、检索(Retrieval)和生成(Generation),常常被描绘成一个“检索—读取”框架,其工作流包括三个关键步…

CSS学习笔记:Less

什么是Less&#xff1f; Less是一个CSS预处理器&#xff0c; Less文件后缀是.less 扩充了CSS 语言&#xff0c;使CSS具备一定的逻辑性、计算能力 可以通俗地理解&#xff1a;Less是一种更好用的CSS 注释 运算 嵌套 Less嵌套的作用&#xff1a;快速生成后代选择器 变量 问…

开源远程协助:分享屏幕,隔空协助!

&#x1f5a5;️ 星控远程协助系统 &#x1f5b1;️ 一个使用Java GUI技术实现的远程控制软件&#xff0c;你现在就可以远程查看和控制你的伙伴的桌面&#xff0c;接受星星的指引吧&#xff01; 支持系统&#xff1a;Windows / Mac / Linux &#x1f31f; 功能导览 &#x1f…

解密Prompt系列15. LLM Agent之数据库应用设计:DIN C3 SQL-Palm BIRD

上一章我们主要讲搜索引擎和LLM的应用设计&#xff0c;这一章我们来唠唠大模型和DB数据库之间的交互方案。有很多数据平台已经接入&#xff0c;可以先去玩玩再来看下面的实现方案&#xff0c;推荐 [sql translate]&#xff1a;简单&#xff0c;文本到SQL&#xff0c;SQL到文本…

AI架构设计7:TGI

这个专栏主要关注围绕着AI运用于实际的业务场景所需的系统架构设计。整体基于云原生技术&#xff0c;结合开源领域的LLMOps或者MLOps技术&#xff0c;充分运用低代码构建高性能、高效率和敏捷响应的AI中台。该专栏需要具备一定的计算机基础。 若在某个环节出现卡点&#xff0c;…

(2023|EMNLP,RWKV,Transformer,RNN,AFT,时间依赖 Softmax,线性复杂度)

RWKV: Reinventing RNNs for the Transformer Era 公众号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 2. 背景 2.1 循环神经网络 (RNN) 2.2 Transformer 和 AFT 3. RWKV 3.1 架构 …

零拷贝(Zero Copy)

目录 零拷贝&#xff08;Zero Copy&#xff09; 1.什么是Zero Copy? 2.物理内存和虚拟内存 3.内核空间和用户空间 4.Linux的I/O读写方式 4.1 I/O中断原理 4.2 DMA传输原理 5.传统I/O方式 5.1传统读操作 5.2传统写操作 6.零拷贝 6.1.用户态直接IO 6.2.mmapwrite …

The First项目报告:解读去中心化衍生品交易所AVEO

2023 年12月8日凌晨&#xff0c;Solana 生态 MEV 基础设施开发商 Jito Labs 开放了 JTO 空投申领窗口&#xff0c;JTO 的价格在开盘短暂震荡后迅速攀高&#xff0c;一度触及 4.94 美元。 JTO 是加密社区这两日关注的热门标的&#xff0c;而在这场讨论中&#xff0c;除 Solana …

unity接入live2d

在bilibili上找到一个教程&#xff0c;首先注意一点&#xff0c;你直接导入那个sdk&#xff0c;并且打开示例&#xff0c;显示的模型是有问题的&#xff0c;你需要调整模型上脚本的一个枚举值&#xff0c;调整它的渲染顺序是front z to我看教程时候&#xff0c;很多老师都没有提…

python max_min标准化

python max_min标准化 max_min标准化sklearn实现max_min标准化手动实现max_min标准化 max_min标准化 Max-Min标准化&#xff08;也称为归一化或Min-Max Scaling&#xff09;是一种将数据缩放到特定范围&#xff08;通常是0到1&#xff09;的标准化方法。这种方法通过线性变换将…

【软考】下篇 第14章 云原生架构设计与理论实践

目录 一、云原生架构定义二、云原生架构原则三、云原生架构主要架构模式3.1 服务化架构模式3.2 Mesh化架构模式3.3 Serverless模式3.4 存储计算分离模式3.5 分布式事务模式4.6 可观测架构3.7 事件驱动架构 四、云原生架构反模式五、云原生架构技术5.1 容器技术容器编排K8S 5.2 …

Elasticsearch 分析器的高级用法二(停用词,拼音搜索)

Elasticsearch 分析器的高级用法二&#xff08;停用词&#xff0c;拼音搜索&#xff09; 停用词简介停用词分词过滤器自定义停用词分词过滤器内置分析器的停用词过滤器注意&#xff0c;有一个细节 拼音搜索安装使用相关配置 停用词 简介 停用词是指&#xff0c;在被分词后的词…

【umi-max】初识 antd pro

修改端口号 根目录下的 .env 文件&#xff1a; PORT8888目录结构 (umijs.org) 新增页面 在 umirc.ts 中进行配置。 新增页面 - Ant Design Pro 这里有一个配置 icon:string&#xff0c;可以在菜单加 icon 图标&#xff0c;默认使用 antd 的 icon 名&#xff0c;默认不适用二…

Yourpassword does not satisfy the current policyrequirements

mysql 新增数据库用户失败 解决方法&#xff1a; 修改校验密码策略等级 set global validate_password.policyLOW;

【K8s】专题四(1):Kubernetes 控制器简介

以下内容均来自个人笔记并重新梳理&#xff0c;如有错误欢迎指正&#xff01;如果对您有帮助&#xff0c;烦请点赞、关注、转发&#xff01;欢迎扫码关注个人公众号&#xff01; 目录 一、基本概念 二、工作原理 三、常见类型 四、相关特性 一、基本概念 Kubernetes 控制器…

js中金额进行千分以及toFixed()保留两位小数丢失精度的问题

1、金额进行千分 function commafy(num) { if ((num "").trim() "") { return ""; } if (isNaN(num)) { return ""; } num num ""; if (/^.*\..*$/.test(num)) { const pointIndex num.lastIndexOf("."); co…

像素匹配+均值homograph+结果

1. 像素匹配 2. 均值homography 转换前转换后 3. 比较 基准图转换图

Kibana创建ElasticSearch 用户角色

文章目录 1, ES 权限参考2, 某应用的管理员权限&#xff1a;可以open/close/delete/cat/read/write 索引3, 某应用的读写权限&#xff1a;可以cat/read/write 索引 &#xff08;不能删除索引或数据&#xff09;4, 某应用的只读权限 1, ES 权限参考 https://www.elastic.co/gui…