目标:类似 message.success() (调用函数)得方式,调起一个弹窗。弹窗功能:拿到上传excel文件得File,用于外部封装为FormData,传给后端。
重点是createRoot和unmount两个api。
/**
* 下载文件
* @param {any} data
* @param {string} type 文件类型
* @param {string} filename 文件名
*/
export function downloadFile(
data: any,
filename: string = '文件.xlsx',
type: string = 'text/plain;charset=utf-8'
) {
let blob = new Blob([data], { type: type });
let URL = window.URL || window.webkitURL;
let src = URL.createObjectURL(blob);
// 下载文件
if ('download' in document.createElement('a')) {
// 非IE下载
if (src) {
let link = document.createElement('a');
link.style.display = 'none';
link.href = src;
link.setAttribute('download', filename);
document.body.appendChild(link);
link.click();
link.remove();
URL.revokeObjectURL(src);
}
} else {
(navigator as any).msSaveBlob(blob, filename);
}
}
// 生成dom
function getContainer() {
let _container: any = document.querySelector('#_container');
if (!_container) {
_container = document.createElement('div');
_container.style.opacity = '0';
_container.id = '_container';
document.body.appendChild(_container);
}
return _container;
}
// 删除dom
function removeContainer(ele: HTMLElement) {
ele.remove();
}
// function
interface IUploadFileObjProps extends ModalProps {
/**
* 处理弹窗确定按钮额外得操作
*@param {RcFile[]} data 选中得文件或文件列表
*/
onSubmit?: (data: RcFile[]) => Promise<boolean>;
/** 透传antd modal属性 */
antdModalProps?: ModalProps;
/** 透传antd upload属性 */
antdUploadProps?: UploadProps;
/** 覆盖upload.dragger内容区得样式 */
content?: ReactNode;
}
// tsx
interface IUploadModalProps extends IUploadFileObjProps {
/** 关闭弹窗时额外需要操作得内容 */
hanldeClose?: () => void;
}
// tsx
export const UploadModal = memo((props: IUploadModalProps) => {
const { hanldeClose, onSubmit, antdModalProps, antdUploadProps, content } =
props;
const [state, { toggle }] = useToggle(true);
const onOK = useMemoizedFn(() => {
if (fileList.length) {
onSubmit?.(fileList as RcFile[]).then((res: boolean) => {
if (res) {
toggle();
hanldeClose?.();
}
});
}
});
const [fileList, setFileList] = useState<UploadFile[]>([]);
const beforeUpload = useMemoizedFn(
(file: UploadFile, fileList: UploadFile[]) => {
setFileList(fileList);
return Promise.reject();
}
);
const onRemove = useMemoizedFn((file: UploadFile) => {
setFileList(fileList.filter((i) => i.uid !== file.uid));
});
const onPreview = useMemoizedFn((file: UploadFile) => {
downloadFile(file, file.name);
});
const modalProps = useMemo(
() => ({
visible: state,
onCancel() {
toggle();
hanldeClose?.();
},
onOk: onOK,
...antdModalProps,
}),
[antdModalProps, hanldeClose, onOK, state, toggle]
);
const uploadProps = useMemo(
() => ({
accept: '.xls,.xlsx',
maxCount: 1,
beforeUpload,
fileList,
showUploadList: false,
...antdUploadProps,
}),
[antdUploadProps, beforeUpload, fileList]
);
return (
<Modal {...modalProps}>
<Upload.Dragger {...uploadProps}>
{content ?? (
<div className="py-5">
<p className="pb-3">
<img src={BatchImportImage} className="w-9 h-9" />
</p>
<p className="text-gray-400 pb-6">支持xls、xlsx文件</p>
<div
className="inline-block px-5 py-3 text-xs text-white"
style={{
background: '#193C58',
borderRadius: '4px',
border: '1px solid rgba(25,60,88,0.24)',
}}
>
<PlusOutlined
style={{
fontSize: '14px',
marginRight: '3px',
color: '#ffffff',
}}
/>
添加附件
</div>
</div>
)}
</Upload.Dragger>
{fileList.length ? (
<div
className="py-3 pl-3 pr-6 text-xs mt-3"
style={{ background: '#F2F2F6' }}
>
{fileList.map((file, i) => (
<div className="flex items-center justify-between" key={i}>
<div
className="flex items-center font-bold"
style={{ color: '#111F2C' }}
>
<FileTextOutlined className="mr-2" />
{file.name}
</div>
<div>
<a
className="mr-4"
style={{ color: '#0096FF' }}
onClick={() => onRemove(file)}
>
删除
</a>
<a
className="mr-4"
style={{ color: '#0096FF' }}
onClick={() => onPreview(file)}
>
预览
</a>
</div>
</div>
))}
</div>
) : null}
</Modal>
);
});
export function uploadFileObj(props: IUploadFileObjProps) {
const container = getContainer();
const _dom = createRoot(container);
const hanldeClose = () => {
_dom.unmount();
removeContainer(container);
};
const _props = {
...props,
hanldeClose,
};
// 这里就可以渲染任意得Node,我在这里就包了一个二次封装得modal
_dom.render(<UploadModal {..._props} />);
}
使用
uploadFileObj({
onSubmit(file) {
const formData = new FormData();
formData.append('file', file[0]);
return new Promise((resolve) => {
boxImportBoxExcel(formData).then(({ success }) => {
resolve(success);
if (success) {
message.success('导入成功');
} else {
message.error('导入失败');
}
});
});
},
antdModalProps: {
title: (
<div className="w-5/6 flex justify-between items-center">
<span>批量导入</span>
<a
className="text-xs underline"
style={{ color: '#0096FF' }}
// TODO
href=""
>
下载模板
</a>
</div>
),
},
});