xhr download file
import { Button, Card, message, Progress, Space } from "antd"; | |
import Axios, { CancelTokenSource } from "axios"; | |
import contentDisposition from "content-disposition"; | |
import React, { useCallback, useRef, useState } from "react"; | |
interface DownloadProps {} | |
const Download: React.FC<DownloadProps> = () => { | |
const cancelSource = useRef<CancelTokenSource | null>(null); | |
const [percentage, setPercentage] = useState<number>(0); | |
const triggerDownload = useCallback((data: any, fileName: string) => { | |
let url = window.URL.createObjectURL(new Blob([data])); | |
let link = document.createElement("a"); | |
link.style.display = "none"; | |
link.href = url; | |
link.setAttribute("download", fileName); | |
document.body.appendChild(link); | |
link.click(); | |
}, []); | |
const onDownload = useCallback( | |
(url: string) => { | |
return new Promise(async (resolve, reject) => { | |
const CancelToken = Axios.CancelToken; | |
cancelSource.current = CancelToken.source(); | |
try { | |
const response = await Axios.get(url, { | |
// 文件类型设置 | |
responseType: "blob", | |
headers: { | |
Authorization: `Bearer Token`, // 授权信息 | |
}, | |
cancelToken: cancelSource.current.token, | |
onDownloadProgress(evt) { | |
setPercentage(Math.ceil((evt.loaded / evt.total) * 100)); | |
}, | |
}); | |
if (response.status === 200) { | |
let fileName = ""; | |
if ("content-disposition" in response.headers) { | |
// 从Header中获取文件名 | |
const dispositionParams = contentDisposition.parse( | |
response.headers["content-disposition"] | |
).parameters; | |
fileName = dispositionParams["filename"]; | |
} | |
triggerDownload(response.data, fileName); | |
resolve("success"); | |
} else { | |
reject("response status" + response.status); | |
} | |
} catch (error) { | |
// Axios的错误类型,读取返回数据中的JSON内容 | |
if (error.isAxiosError) { | |
// 因为数据默认设置为Blob文件类型,因此需要使用fileReader将数据从Blob中读取出来 | |
const fr = new FileReader(); | |
fr.onload = function () { | |
const result = JSON.parse((this.result as string) ?? "{}"); | |
// 与后端协定的数据格式,根据实际情况改动 | |
reject(result.message); | |
}; | |
fr.readAsText(error.response.data); | |
} else { | |
// 其他错误类型,直接返回消息体 | |
reject(error.message); | |
} | |
} | |
}); | |
}, | |
[triggerDownload] | |
); | |
const onClickDownload = useCallback(() => { | |
setPercentage(0); | |
onDownload("/api/download/attachment-image").then( | |
() => { | |
message.success("下载成功"); | |
}, | |
(err) => message.error(err) | |
); | |
}, [onDownload]); | |
const onCancel = useCallback(() => { | |
setPercentage(0); | |
cancelSource.current?.cancel("用户取消下载"); | |
}, []); | |
const onClickUnAuthDownload = useCallback(() => { | |
onDownload("/api/download/download-unauth").then( | |
() => { | |
message.success("下载成功"); | |
}, | |
(err) => message.error(err) | |
); | |
}, [onDownload]); | |
return ( | |
<div className='inner-card'> | |
<Card> | |
<div style={{ display: "flex", justifyContent: "center" }}> | |
<Space> | |
<Button type='primary' disabled={ !!percentage && percentage !== 100 } onClick={onClickDownload}> | |
开始下载 | |
</Button> | |
<Button type='dashed' onClick={onCancel}> | |
取消下载 | |
</Button> | |
<Progress type='circle' percent={percentage} /> | |
</Space> | |
</div> | |
</Card> | |
<Card> | |
<Button onClick={onClickUnAuthDownload} danger> | |
测试下载错误 | |
</Button> | |
</Card> | |
</div> | |
); | |
}; | |
export default Download; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment