first version

This commit is contained in:
liu
2026-07-23 20:41:25 +08:00
parent 00a0938a1b
commit 10cff754a4
211 changed files with 11577 additions and 842 deletions
+100
View File
@@ -0,0 +1,100 @@
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;
}