first version
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
import axios from 'axios';
|
||||
|
||||
/**
|
||||
* 公共文件下载工具(blob 请求 + 触发浏览器保存)
|
||||
*
|
||||
* - 成功:解析响应头 Content-Disposition 的 filename(RFC 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;
|
||||
}
|
||||
Reference in New Issue
Block a user