diff --git a/web-admin/src/components/business/list-export-dropdown/index.vue b/web-admin/src/components/business/list-export-dropdown/index.vue new file mode 100644 index 000000000..1f97e700f --- /dev/null +++ b/web-admin/src/components/business/list-export-dropdown/index.vue @@ -0,0 +1,144 @@ + + + + + diff --git a/web-admin/src/utils/list-export-download.ts b/web-admin/src/utils/list-export-download.ts new file mode 100644 index 000000000..dc1f0245a --- /dev/null +++ b/web-admin/src/utils/list-export-download.ts @@ -0,0 +1,41 @@ +/** 从 Content-Disposition 解析文件名;失败则用 fallback */ +export function filenameFromContentDisposition( + header: string | undefined, + fallback: string +): string { + if (!header) return fallback + const utf8 = /filename\*\s*=\s*UTF-8''([^;]+)/i.exec(header) + if (utf8?.[1]) { + try { + return decodeURIComponent(utf8[1].trim().replace(/["']/g, '')) + } catch { + /* ignore */ + } + } + const plain = /filename\s*=\s*("?)([^";]+)\1/i.exec(header) + if (plain?.[2]) return plain[2].trim() + return fallback +} + +export function triggerBlobDownload(blob: Blob, filename: string): void { + const url = URL.createObjectURL(blob) + const link = document.createElement('a') + link.href = url + link.download = filename + link.click() + URL.revokeObjectURL(url) +} + +/** Axios 成功时若拦截器已把 data 设为 Blob,直接用;否则兼容 { data: Blob, headers } */ +export function pickBlobFromExportResponse(res: any): { blob: Blob; filenameHeader?: string } { + if (res instanceof Blob) { + return { blob: res } + } + if (res?.data instanceof Blob) { + return { + blob: res.data, + filenameHeader: res.headers?.['content-disposition'] || res.headers?.['Content-Disposition'] + } + } + throw new Error('导出响应不是文件') +}