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

参考文档: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);

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

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

相关文章

短剧源码部署搭建小程序搭建IAA+IAP混合解锁模式

在当今数字化内容消费迅速增长的时代&#xff0c;短剧作为一种新兴的内容形式&#xff0c;凭借其短小精悍、节奏紧凑的特点&#xff0c;迅速吸引了大量用户。作为一名软件体验测试人员&#xff0c;我有幸体验了一款集创新与实用为一体的短剧小程序。这款小程序不仅在前端用户体…

网络原理---HTTP/HTTPS

通过之前的网络编程&#xff0c;我们已经初步了解UDP和TCP的基本实现方法&#xff0c;接下来我们对其进一步的学习。 在网络编程中&#xff1a; 1.读和写数据通过Socket&#xff0c;通过Socket内置的InputStream和OutputStream(读写的基本单位都是字节&#xff09;。2.当在编…

【Python修仙编程】(二) Python3灵源初探(2)

第一部分&#xff1a;林羽的修仙之旅——字符串与布尔类型的修炼 林羽站在练气期一阶的起点&#xff0c;望着手中的《Python无极心法》秘籍&#xff0c;心中充满了期待。师傅玄天真人在一旁微笑着说道&#xff1a;“林羽&#xff0c;今天我们要修炼的是‘字符串’和‘布尔类型…

【HTML— 快速入门】HTML 基础

准备工作 vscode下载 百度网盘 Subline Text 下载 Sublime Text下载 百度网盘 vscode 下载 Sublime Text 是一款轻量好用的文本编辑器&#xff0c;我们在写前端代码时&#xff0c;使用 Sublime Text 打开比使用记事本打开&#xff0c;得到的代码体验更好&#xff0c;比 vscode…

pipeline 使用git parameter插件实现动态选择分支构造

效果&#xff0c;&#xff0c;点击build with Parameters 就会出现右边的当前仓库的所有的分支&#xff0c;默认最多显示5个&#xff0c;可以修改配置&#xff0c;修改显示的最大分支数量。如果分支太多&#xff0c;可以通过右边的过滤框输入过滤。 安装git params插件 搜索g…

国产OS上完整编译Qt5.15、搭建基本开发环境需要的库

近期有师弟问我国产OS安装Qt5.15编译老是不完整&#xff0c;不是没声音&#xff0c;就是没视频&#xff0c;或者没有xcb。通过QEMU模拟Arm64&#xff0c;闲来20几天摸索&#xff0c;完整编译了Qt5.15&#xff0c;并编译成功了我的SDR玩具taskBus。 1.主要结论&#xff1a; 该O…

数据库 安装initializing database不通过

出现一下情况时&#xff1a; 处理方法&#xff1a; 将自己的电脑名称 中文改成英文 即可通过

【视频2 - 4】初识操作系统,Linux,虚拟机

&#x1f4dd;前言说明&#xff1a; ●本专栏主要记录本人的基础算法学习以及LeetCode刷题记录&#xff0c;主要跟随B站博主灵茶山的视频进行学习&#xff0c;专栏中的每一篇文章对应B站博主灵茶山的一个视频 ●题目主要为B站视频内涉及的题目以及B站视频中提到的“课后作业”。…

AI绘画软件Stable Diffusion详解教程(2):Windows系统本地化部署操作方法(专业版)

一、事前准备 1、一台配置不错的电脑&#xff0c;英伟达显卡&#xff0c;20系列起步&#xff0c;建议显存6G起步&#xff0c;安装win10或以上版本&#xff0c;我的显卡是40系列&#xff0c;16G显存&#xff0c;所以跑大部分的模型都比较快&#xff1b; 2、科学上网&#xff0…

将Ubuntu操作系统的安装源设置为阿里云

在使用Ubuntu操作系统时,默认的软件源通常是国外的仓库,这可能会导致软件安装和更新速度较慢。为了提高下载速度和稳定性,我们可以将Ubuntu的安装源设置为阿里云镜像源。以下是详细步骤: 一、准备工作 在开始之前,请确保您的Ubuntu系统可以正常上网,并且您拥有管理员权…

IP-------GRE和MGRE

4.GRE和MGRE 1.应用场景 现实场景 居家工作&#xff0c;公司工作&#xff0c;分公司工作----------需要传输交换数据--------NAT---在该场景中需要两次NAT&#xff08;不安全&#xff09; 为了安全有两种手段-----1.物理专线---成本高 2.VPN--虚拟专用网---隧道技术--封装技…

Visual Studio Code 跨平台安装与配置指南(附官方下载链接)

一、软件定位与核心功能 Visual Studio Code&#xff08;简称VS Code&#xff09;是微软开发的开源跨平台代码编辑器&#xff0c;支持超过50种编程语言的智能补全、调试和版本控制功能。2025版本新增AI辅助编程模块&#xff0c;可自动生成单元测试代码和API文档注释。 二、下载…

小智AI桌宠机器狗

本文主要介绍如何利用开源小智AI制作桌宠机器狗 1 源码下载 首先下载小智源码,下载地址, 下载源码后,使用vsCode打开,需要在vscode上安装esp-idf,安装方式请自己解决 2 源码修改 2.1添加机器狗控制代码 在目录main/iot/things下添加dog.cc文件,内容如下; #include…

Linux:进程信号(二.信号的保存与处理、递达、volatile关键字、SIGCHLD信号)

目录 1.信号保存 1.1递达、未决、阻塞等概念 1.2再次理解信号产生与保存 1.3信号集操作函数 sigset_t类型 sigemptyset() 函数 sigismember()函数 sigaddset ()函数 sigdelset() 函数 sigprocmask()系统调用 sigpending()系统调用 2.信号的处理/递达 2.1信号处理时…

kotlin 知识点 七 泛型的高级特性

对泛型进行实化 泛型实化这个功能对于绝大多数Java 程序员来讲是非常陌生的&#xff0c;因为Java 中完全没有这个概 念。而如果我们想要深刻地理解泛型实化&#xff0c;就要先解释一下Java 的泛型擦除机制才行。 在JDK 1.5之前&#xff0c;Java 是没有泛型功能的&#xff0c;…

go基本语法

跟Java比较学习。 hello word 示例代码 test1.go文件&#xff1a; // 包路径 package main// 导入模块&#xff0c;下面两种都行 import ("fmt" ) import "log"// main方法 func main() {log.Print("hello word !!!")fmt.Print("hello …

Linux内核,slub分配流程

我们根据上面的流程图&#xff0c;依次看下slub是如何分配的 首先从kmem_cache_cpu中分配&#xff0c;如果没有则从kmem_cache_cpu的partial链表分配&#xff0c;如果还没有则从kmem_cache_node中分配&#xff0c;如果kmem_cache_node中也没有&#xff0c;则需要向伙伴系统申请…

冯诺依曼体系结构 ──── linux第8课

目录 冯诺依曼体系结构 关于冯诺依曼&#xff0c;必须强调几点&#xff1a; 冯诺依曼体系结构 我们常见的计算机&#xff0c;如笔记本。我们不常见的计算机&#xff0c;如服务器&#xff0c;大部分都遵守冯诺依曼体系 输入单元&#xff1a;包括键盘, 鼠标&#xff0c;网卡,扫…

国标28181协议在智联视频超融合平台中的接入方法

一. 国标28181介绍 国标 28181 协议全称是《安全防范视频监控联网系统信息传输、交换、控制技术要求》&#xff0c;是国内视频行业最重要的国家标准&#xff0c;目前有三个版本&#xff1a; 2011 年&#xff1a;推出 GB/T 28181-2011 版本&#xff0c;为安防行业的前端设备、平…

2024-2025 学年广东省职业院校技能大赛 “信息安全管理与评估”赛项 技能测试试卷(四)

2024-2025 学年广东省职业院校技能大赛 “信息安全管理与评估”赛项 技能测试试卷&#xff08;四&#xff09; 第一部分&#xff1a;网络平台搭建与设备安全防护任务书第二部分&#xff1a;网络安全事件响应、数字取证调查、应用程序安全任务书任务 1&#xff1a;应急响应&…