前端兼容处理接口返回的文件流或json数据

news2025/2/25 19:40:26

参考文档:JavaScript | MDN

参考链接:Blob格式转json格式,拿到后端返回的json数据_blob转json-CSDN博客

参考链接:https://juejin.cn/post/7117939029567340557

场景:导入上传文件,导入成功,返回json数据显示列表,导入失败后端返回二进制文件流。

一、代码实现

1、接口请求

/**
 * @description 导入
 */
export const postCustGroupImport = (params?: any) => {
  return request(`${prefixPath}/custGroupManages/custGroupListImport`, {
    method: 'POST',
    data: params,
    responseType: 'blob',
    // headers: {
    //   'Content-Type': 'multipart/form-data',
    // },
  });
};

2、操作步骤

(1)点击要上传的文件

(2)上传文件

(3)导入正确的文件

请求参数:file为二进制文件流

返回格式

打印结果:

(4)导入失败的文件,返回结果

(5)代码:

    const handleUpload = async () => {
      // 请求接口中去除,直接用下面的
      // headers: {
      //   'Content-Type': 'multipart/form-data',
      // },

      // 设置'multipart/form-data'
      const formData = new FormData();
      formData.append('channelCode', channelCode);
      formData.append('file', fileList[0]);

      setUploading(true);

      // 1、导入文件上传
      const res = await postCustGroupImport(formData);
      console.log('res', res);

      setUploading(false);
      if (res.failed) {
        message.error(res.message, undefined, undefined, 'top');
        return;
      }

      /** 检查返回的 Blob 是否是 JSON 格式 */
      if (res.type === 'application/json') {
        const text = await res.text(); // 将 Blob 转换为文本
        const json = JSON.parse(text); // 将文本解析为 JSON
        console.log('JSON response:', json);

        // 上传成功
        if (Array.isArray(json) && json?.length > 0) {
          setStatus('success');
          message.success('上传成功', undefined, undefined, 'top');
          tableDs.loadData(json);
          return;
        }
      } else {
        // 上传失败
        console.error('返回的 Blob 类型不是 JSON:(错误文件)', res.type);
        setStatus('error');
        setErrorFile(res); // 把错误文件存储到本地,手动点击下载
      }
    };

    /** 下载错误文件 */
    const handleDown = () => {
      // 确保 errorFile 是一个有效的 Blob 对象
      if (!(errorFile instanceof Blob)) {
        console.error('errorFile 不是一个有效的 Blob 对象');
        return;
      }

      // 创建 Blob 对象
      const blob = new Blob([errorFile], {
        type:
          'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
      });

      // 创建一个可访问的 URL
      const downloadUrl = URL.createObjectURL(blob);

      // 使用 window.open 触发下载
      window.open(downloadUrl, '_blank');

      // 释放资源
      URL.revokeObjectURL(downloadUrl);
    };

三、完整代码

1、引用模块代码

2、导入模块代码

import { Button, Icon, message, Modal, Table } from 'choerodon-ui/pro';
import DataSet from 'choerodon-ui/dataset/data-set/DataSet';
import React, { useEffect, useMemo, useState } from 'react';
import formatterCollections from 'hzero-front/lib/utils/intl/formatterCollections';
import { ColumnProps } from 'choerodon-ui/pro/lib/table/Column';
import {
  commonModelPrompt,
  languageConfig,
  prmPrTemCode,
  TABLE_COLUMN_NUM,
} from '@/language/language';
import { CoverModelChooseProps } from '@/interface/customerBase/main';
import { handleSerialNum } from '@/utils/utils';
import { ColumnAlign } from 'choerodon-ui/dataset/enum';
import { ButtonColor, FuncType } from 'choerodon-ui/pro/lib/button/enum';
import { Upload } from 'choerodon-ui';
import { postCustGroupImport } from '@/api/customerBase/main';
import { importTableList } from './store';
import {
  SelectionMode,
  TableAutoHeightType,
} from 'choerodon-ui/pro/lib/table/enum';
import { Title } from '@ino/ltc-component-paas';
import moment from 'moment';
import { ErrorMessage, handleTotal } from '../../../hook';

const Index: React.FC<CoverModelChooseProps> = ({
  /** 控制弹框显示/隐藏 */
  visible,
  /** 设置弹框显示/隐藏的回调函数 */
  setVisible,
  /** 弹框关闭后回调函数 */
  onSelect,
  /** 渠道编码 */
  channelCode = '',
  infoData,
}) => {
  const { chooseList = [] } = infoData;

  /** ds */
  const tableDs = useMemo(() => new DataSet(importTableList(chooseList)), []);
  const columns: ColumnProps[] = useMemo(() => {
    return [
      {
        header: TABLE_COLUMN_NUM,
        width: 60,
        align: ColumnAlign.left,
        renderer: ({ record }) => {
          return handleSerialNum(record);
        },
      },
      { name: 'custCode' },
      { name: 'custName' },
      { name: 'productAuthCategoryId' },
      { name: 'categoryCapacity' },
      { name: 'custManager' },
      {
        name: 'disableMessage',
        renderer: ({ value, record }) => {
          const { custCode, productAuthCategoryId } = record?.toData();
          const arr = chooseList.filter(
            (item: any) =>
              item.custCode === custCode &&
              productAuthCategoryId === item.productAuthCategoryId,
          );

          /** 列表中添加过的数据,手动设置文案:'列表中已添加' */
          return (
            <div style={{ color: 'red' }}>
              {arr.length === 0
                ? value
                : languageConfig(
                    'customerBase.relevantInfo.tip.hasDisableMessage',
                    '列表中已添加该条数据。',
                  )}
            </div>
          );
        },
      },
    ];
  }, []);

  useEffect(() => {
    if (visible) {
      openModal();
    }
  }, [visible]);

  /** 弹框打开 */
  const openModal = () => {
    Modal.open({
      title: languageConfig(
        'btn.add.importReleaseCustToList',
        '导入关联客户列表',
      ),
      style: { width: '70vw' },
      closable: true,
      maskClosable: false,
      keyboardClosable: false,
      onClose: () => {
        setVisible(false);
      },
      children: <Box />,
      onOk: async () => {
        if (tableDs?.selected.length === 0) {
          message.error(
            languageConfig(
              'customerBase.relevantInfo.tip.pleaseChooseOne',
              '请至少选择一条数据',
            ),
            undefined,
            undefined,
            'top',
          );
          return false;
        }

        /** 1、导入的数据:处理 */
        const choose = tableDs.selected?.map((item: Record<any, any>) => {
          return {
            ...item.toData(),
            joinDate: moment().format('YYYY-MM-DD HH:mm:ss'), // 入团时间(默认'当前')
            status: 'TO_BE_ACTIVE', // 状态(默认'待生效')
          };
        });

        /** 2、'已选数据'中存在的提示 */
        const matchingItems = choose.filter(itemChoose =>
          chooseList.some(
            itemChooseList => itemChooseList.custCode === itemChoose.custCode,
          ),
        );
        if (matchingItems.length > 0) {
          message.error(
            languageConfig(
              'customerBase.coverModel.tip.alreadySelected',
              '已选数据中存在重复数据',
            ),
            undefined,
            undefined,
            'top',
          );
          return false;
        }

        /** 3、总价超5k 校验 */
        const list = chooseList.concat(choose);
        if (handleTotal(list, 'categoryCapacity') > 5000) {
          ErrorMessage(
            languageConfig(
              'customerBase.relevantInfo.tips.categoryCapacityPass',
              '客户团总容量已超上限5000万,不可提交!',
            ),
          );
          return false;
        }

        onSelect(choose);
      },
    });
  };

  /** 内容 */
  const Box = () => {
    const [fileList, setFileList] = useState<any>([]); // 文件列表
    const [uploading, setUploading] = useState(false); // 是否正在上传
    const [status, setStatus] = useState<any>(''); // 导入状态
    const [errorFile, setErrorFile] = useState<any>(''); // 导入失败,错误文件存储

    /** 上传文件 */
    const handleUpload = async () => {
      // 请求接口中去除,直接用下面的
      // headers: {
      //   'Content-Type': 'multipart/form-data',
      // },

      // 设置'multipart/form-data'
      const formData = new FormData();
      formData.append('channelCode', channelCode);
      formData.append('file', fileList[0]);

      setUploading(true);

      // 1、导入文件上传
      const res = await postCustGroupImport(formData);
      console.log('res', res);

      setUploading(false);
      if (res.failed) {
        message.error(res.message, undefined, undefined, 'top');
        return;
      }

      /** 检查返回的 Blob 是否是 JSON 格式,是json格式为'上传成功',否则为'上传失败' */
      if (res.type === 'application/json') {
        const text = await res.text(); // 将 Blob 转换为文本
        const json = JSON.parse(text); // 将文本解析为 JSON
        console.log('JSON response:', json);

        // 上传成功
        if (Array.isArray(json) && json?.length > 0) {
          setStatus('success');
          message.success('上传成功', undefined, undefined, 'top');
          tableDs.loadData(json);
          return;
        }
      } else {
        // 上传失败
        console.error('返回的 Blob 类型不是 JSON:(错误文件)', res.type);
        setStatus('error');
        setErrorFile(res);
      }
    };

    /** 下载错误文件 */
    const handleDown = () => {
      // 确保 errorFile 是一个有效的 Blob 对象
      if (!(errorFile instanceof Blob)) {
        console.error('errorFile 不是一个有效的 Blob 对象');
        return;
      }

      // 创建 Blob 对象
      const blob = new Blob([errorFile], {
        type:
          'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
      });

      // 创建一个可访问的 URL
      const downloadUrl = URL.createObjectURL(blob);

      // 使用 window.open 触发下载
      window.open(downloadUrl, '_blank');

      // 释放资源
      URL.revokeObjectURL(downloadUrl);
    };

    const handleRemove = file => {
      const index = fileList.indexOf(file);
      const newFileList = [...fileList];
      newFileList.splice(index, 1);
      setFileList(newFileList);
    };

    const beforeUpload = file => {
      setFileList([...fileList, file]);
      return false; // 返回 false 阻止自动上传
    };

    return (
      <>
        {/* 导入文件 */}
        <div style={{ display: 'flex' }}>
          <Upload beforeUpload={beforeUpload} onRemove={handleRemove}>
            <Button>
              <Icon type="file_upload" />
              {languageConfig(
                'customerBase.btn.chooseTheFileToImport',
                '选择要导入的文件',
              )}
            </Button>
          </Upload>
          <Button
            funcType={FuncType.raised}
            color={ButtonColor.primary}
            onClick={handleUpload}
            disabled={fileList.length === 0}
            loading={uploading}
            style={{ marginLeft: '12px' }}
          >
            {uploading
              ? languageConfig('customerBase.label.Importing', '导入中')
              : languageConfig('customerBase.label.startImport', '开始导入')}
          </Button>
        </div>

        {/* 导入成功的数据 */}
        {status === 'success' && (
          <>
            <div style={{ marginTop: '12px' }}>
              <Title
                title={languageConfig(
                  'customerBase.title.importSuccessData',
                  '导入成功的数据',
                )}
              />
              <Table
                dataSet={tableDs}
                columns={columns}
                pagination={false}
                alwaysShowRowBox
                selectionMode={SelectionMode.click}
                selectedHighLightRow
                // autoHeight={{ type: TableAutoHeightType.maxHeight, diff: 100 }}
                renderEmpty={() => {
                  return <div>暂无数据</div>;
                }}
              />
            </div>
          </>
        )}

        {/* 导入失败 */}
        {status === 'error' && (
          <>
            <div style={{ marginTop: '12px' }}>
              <Title
                title={languageConfig(
                  'customerBase.title.importFailedData',
                  '导入失败的数据',
                )}
              />
            </div>

            <div>
              <a onClick={handleDown}>
                <Icon type="file_download_black-o" />
                {languageConfig(
                  'customerBase.download.errorFile',
                  '下载错误文件',
                )}
              </a>
            </div>
          </>
        )}
      </>
    );
  };

  return <></>;
};

export default formatterCollections({
  code: [prmPrTemCode, commonModelPrompt],
})(Index);

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

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

相关文章

Java基础常见的面试题(易错!!)

面试题一&#xff1a;为什么 Java 不支持多继承 Java 不支持多继承主要是为避免 “菱形继承问题”&#xff08;又称 “钻石问题”&#xff09;&#xff0c;即一个子类从多个父类继承到同名方法或属性时&#xff0c;编译器无法确定该调用哪个父类的成员。同时&#xff0c;多继承…

DPVS-2:单臂负载均衡测试

上一篇编译安装了DPVS&#xff0c;这一篇开启DPVS的负载均衡测试 &#xff1a; 单臂 FULL NAT模式 拓扑-单臂 单臂模式 DPVS 单独物理机 CLINET&#xff0c;和两个RS都是另一个物理机的虚拟机&#xff0c;它们网卡都绑定在一个桥上br0 &#xff0c; 二层互通。 启动DPVS …

Classic Control Theory | 12 Real Poles or Zeros (第12课笔记-中文版)

笔记链接&#xff1a;https://m.tb.cn/h.Tt876SW?tkQaITejKxnFLhttps://m.tb.cn/h.Tt876SW?tkQaITejKxnFL

Kubernetes开发环境minikube | 开发部署MySQL单节点应用

minikube是一个主要用于开发与测试Kubernetes应用的运行环境 本文主要描述在minikube运行环境中部署MySQL单节点应用 minikube start --force kubectl get nodes 如上所示&#xff0c;启动minikube单节点运行环境 minikube ssh docker pull 如上所示&#xff0c;从MySQL官…

安装可视化jar包部署平台JarManage

一、下载 下载地址&#xff1a;JarManage 发行版 - Gitee.com &#x1f692; 下载 最新发行版 下载zip的里面linux和windows版本都有 二、运行 上传到服务器&#xff0c;解压进入目录 &#x1f69a; 执行java -jar jarmanage-depoly.jar 命令运行 java -jar jarmanage-dep…

基于数据可视化+SpringBoot+安卓端的数字化OA公司管理平台设计和实现

博主介绍&#xff1a;硕士研究生&#xff0c;专注于信息化技术领域开发与管理&#xff0c;会使用java、标准c/c等开发语言&#xff0c;以及毕业项目实战✌ 从事基于java BS架构、CS架构、c/c 编程工作近16年&#xff0c;拥有近12年的管理工作经验&#xff0c;拥有较丰富的技术架…

输入搜索、分组展示选项、下拉选取,全局跳转页,el-select 实现 —— 后端数据处理代码,抛砖引玉展思路

详细前端代码写于上一篇&#xff1a;输入搜索、分组展示选项、下拉选取&#xff0c;el-select 实现&#xff1a;即输入关键字检索&#xff0c;返回分组选项&#xff0c;选取跳转到相应内容页 —— VUE项目-全局模糊检索 【效果图】&#xff1a;分组展示选项 >【去界面操作体…

性能巅峰对决:Rust vs C++ —— 速度、安全与权衡的艺术

??关注&#xff0c;带你探索Java的奥秘&#xff01;?? ??超萌技术攻略&#xff0c;轻松晋级编程高手&#xff01;?? ??技术宝库已备好&#xff0c;就等你来挖掘&#xff01;?? ??订阅&#xff0c;智趣学习不孤单&#xff01;?? ??即刻启航&#xff0c;编…

unity学习53:UI的子容器:面板panel

目录 1 UI的最底层容器&#xff1a;canvas 1.1 UI的最底层容器&#xff1a;canvas 1.2 UI的合理结构 2 UI的子容器&#xff1a;面板panel 2.1 创建panel 2.2 面板的本质&#xff1a; image &#xff0c;就是一个透明的图片&#xff0c;1个空容器 3 面板的属性 4 面板的…

4-知识图谱的抽取与构建-4_2实体识别与分类

&#x1f31f; 知识图谱的实体识别与分类&#x1f525; &#x1f50d; 什么是实体识别与分类&#xff1f; 实体识别&#xff08;Entity Recognition&#xff09;是从文本中提取出具体的事物&#xff0c;如人名、地名、组织名等。分类&#xff08;Entity Classification&#x…

elasticsearch在windows上的配置

写在最前面&#xff1a; 上资源 第一步 解压&#xff1a; 第二步 配置两个环境变量 第三步 如果是其他资源需要将标蓝的文件中的内容加一句 xpack.security.enabled: false 不同版本的yaml文件可能配置不同&#xff0c;末尾加这个 xpack.security.enabled: true打开bin目…

详解分布式ID实践

引言 分布式ID&#xff0c;所谓的分布式ID&#xff0c;就是针对整个系统而言&#xff0c;任何时刻获取一个ID&#xff0c;无论系统处于何种情况&#xff0c;该值不会与之前产生的值重复&#xff0c;之后获取分布式ID时&#xff0c;也不会再获取到与其相同的值&#xff0c;它是…

【Rust中级教程】2.8. API设计原则之灵活性(flexible) Pt.4:显式析构函数的问题及3种解决方案

喜欢的话别忘了点赞、收藏加关注哦&#xff08;加关注即可阅读全文&#xff09;&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 说句题外话&#xff0c;这篇文章一共5721个字&#xff0c;是我截至目前写的最长的一篇文章&a…

【复习】Redis

数据结构 Redis常见的数据结构 String&#xff1a;缓存对象Hash&#xff1a;缓存对象、购物车List&#xff1a;消息队列Set&#xff1a;点赞、共同关注ZSet&#xff1a;排序 Zset底层&#xff1f; Zset底层的数据结构是由压缩链表或跳表实现的 如果有序集合的元素 < 12…

STM32使用NRF2401进行数据传送

NRF2401是一款由Nordic Semiconductor公司生产的单片射频收发芯片&#xff0c;以下是关于它的详细介绍&#xff1a; 一、主要特点 工作频段&#xff1a;NRF2401工作于2.4~2.5GHz的ISM&#xff08;工业、科学和医疗&#xff09;频段&#xff0c;该频段无需申请即可使用&#xf…

DeepSeek、微信、硅基流动、纳米搜索、秘塔搜索……十种不同方法实现DeepSeek使用自由

为了让大家实现 DeepSeek 使用自由&#xff0c;今天分享 10 个畅用 DeepSeek 的平台。 一、官方满血版&#xff1a;DeepSeek官网与APP 首推&#xff0c;肯定是 DeepSeek 的官网和 APP&#xff0c;可以使用满血版 R1 和 V3 模型&#xff0c;以及联网功能。 网址&#xff1a; htt…

Orange 开源项目 - 集成阿里云大模型

1 阿里云的大模型服务平台百炼 阿里云的大模型服务平台百炼是一站式的大模型开发及应用构建平台。不论是开发者还是业务人员&#xff0c;都能深入参与大模型应用的设计和构建。您可以通过简单的界面操作&#xff0c;在5分钟内开发出一款大模型应用&#xff0c;或在几小时内训练…

公开整理-最新中国城市统计NJExcel+PDF版本(1985-2024年)

数据简介&#xff1a;《中国城市统计NJ》从1985年开始&#xff0c;本NJ内容共分四个部分:第一部分是全国城市行政区划,列有不同区域、不同级别的城市分布情况;第二、三部分分别是地级以上城市统计资料和县级城市统计资料,具体包括人口、劳动力及土地资源、综合经济、工业、交通…

KubeSphere平台安装

KubeSphere简介 KubeSphere 是一款功能强大的容器管理平台&#xff0c;以下是其简介&#xff1a; 1&#xff09;基本信息 开源项目&#xff1a;基于 Apache-2.0 授权协议开源&#xff0c;由 Google Go、Groovy、HTML/CSS 和 Shell 等多种编程语言开发。基础架构&#xff1a;…

Claude 3.7 Sonnet 泄露,Anthropic 最先进 AI 模型即将在 AWS Bedrock 上首次亮相

(图片&#xff1a;AWS) Anthropic 旗下先进的 AI 模型 Claude 3.7 Sonnet 似乎即将发布。业界预计&#xff0c;亚马逊可能会在2025年2月26日的活动中公布相关消息。泄露的信息表明&#xff0c;该模型将托管于 AWS Bedrock 平台&#xff0c;该平台以提供尖端 AI 模型访问而闻名…