Files
xin-procurement/web/api/common/download.ts
T
2026-07-23 20:41:25 +08:00

101 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import axios from 'axios';
/**
* 公共文件下载工具(blob 请求 + 触发浏览器保存)
*
* - 成功:解析响应头 Content-Disposition 的 filenameRFC 5987 `filename*=UTF-8''` 优先),兜底用 fallbackName
* - 失败兜底:后端业务错误也以 blob 返回(JSON),blob.type 为 application/json 时解析 msg 提示
* - 401:清 token 跳登录页(与 createAxios 行为一致)
*/
export async function downloadBlob(
url: string,
params: Record<string, unknown>,
fallbackName: string
): Promise<void> {
const token = localStorage.getItem('token');
let blob: Blob;
let disposition = '';
try {
const response = await axios.get(url, {
baseURL: import.meta.env.VITE_BASE_URL || '',
params,
responseType: 'blob',
headers: token ? { Authorization: `Bearer ${token}` } : {},
});
blob = response.data as Blob;
disposition = (response.headers['content-disposition'] as string) || '';
} catch (err: any) {
const status = err?.response?.status;
const errBlob: Blob | undefined = err?.response?.data;
if (status === 401) {
window.$message?.error('您未登录,或者登录已经超时,请先登录!');
localStorage.removeItem('token');
localStorage.removeItem('auth-storage');
window.location.href = '/login';
return;
}
if (errBlob instanceof Blob) {
await showBlobError(errBlob);
return;
}
window.$message?.error('下载失败,请稍后重试');
return;
}
// 后端业务错误以 JSON blob 返回
if (blob.type.includes('application/json')) {
await showBlobError(blob);
return;
}
const filename = parseFilename(disposition) || fallbackName;
const objectUrl = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = objectUrl;
link.download = filename;
document.body.appendChild(link);
link.click();
link.remove();
URL.revokeObjectURL(objectUrl);
}
/**
* 解析 blob 形式的错误响应并提示
*/
async function showBlobError(blob: Blob): Promise<void> {
try {
const body = JSON.parse(await blob.text());
window.$message?.error(body?.msg || '导出失败');
} catch {
window.$message?.error('导出失败');
}
}
/**
* 从 Content-Disposition 解析文件名:RFC 5987 filename*=UTF-8'' 优先,其次 filename="..."
*/
function parseFilename(disposition: string): string | null {
if (!disposition) {
return null;
}
const rfc5987 = disposition.match(/filename\*\s*=\s*(?:UTF-8|utf-8)''([^;]+)/i);
if (rfc5987?.[1]) {
try {
return decodeURIComponent(rfc5987[1].trim());
} catch {
return null;
}
}
const plain = disposition.match(/filename\s*=\s*"?([^";]+)"?/i);
if (plain?.[1]) {
try {
return decodeURIComponent(plain[1].trim());
} catch {
return plain[1].trim();
}
}
return null;
}