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