2025-07-14 10:47:35 +08:00
|
|
|
|
import StreamSaver from 'streamsaver';
|
|
|
|
|
import { errorCodeRecord } from '@/constants/common';
|
|
|
|
|
import { localStg } from '@/utils/storage';
|
|
|
|
|
import { getServiceBaseURL } from '@/utils/service';
|
|
|
|
|
import { transformToURLSearchParams } from '@/utils/common';
|
2025-09-01 11:16:18 +08:00
|
|
|
|
import { WritableStream } from 'web-streams-polyfill/ponyfill';
|
2025-07-14 10:47:35 +08:00
|
|
|
|
|
|
|
|
|
interface RequestConfig {
|
|
|
|
|
method: 'GET' | 'POST';
|
|
|
|
|
url: string;
|
|
|
|
|
params?: Record<string, any>;
|
|
|
|
|
filename?: string;
|
|
|
|
|
contentType?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useDownload() {
|
|
|
|
|
const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
|
|
|
|
|
const { baseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
|
|
|
|
|
|
|
|
|
|
/** 获取通用请求头 */
|
|
|
|
|
const getCommonHeaders = (contentType = 'application/octet-stream') => ({
|
|
|
|
|
Authorization: `Bearer ${localStg.get('token')}`,
|
|
|
|
|
Clientid: import.meta.env.VITE_APP_CLIENT_ID!,
|
|
|
|
|
'Content-Type': contentType
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/** 通用下载方法 */
|
|
|
|
|
function downloadByData(data: BlobPart, filename: string, type = 'application/octet-stream') {
|
|
|
|
|
const blob = new Blob([data], { type });
|
|
|
|
|
const blobURL = window.URL.createObjectURL(blob);
|
|
|
|
|
|
|
|
|
|
const tempLink = Object.assign(document.createElement('a'), {
|
|
|
|
|
style: { display: 'none' },
|
|
|
|
|
href: blobURL,
|
|
|
|
|
download: filename
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (typeof tempLink.download === 'undefined') {
|
|
|
|
|
tempLink.setAttribute('target', '_blank');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
document.body.appendChild(tempLink);
|
|
|
|
|
tempLink.click();
|
|
|
|
|
document.body.removeChild(tempLink);
|
|
|
|
|
window.URL.revokeObjectURL(blobURL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 流式下载 */
|
|
|
|
|
async function downloadByStream(
|
|
|
|
|
readableStream: ReadableStream<Uint8Array>,
|
|
|
|
|
filename: string,
|
|
|
|
|
contentLength?: number
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
window.$loading?.endLoading();
|
2025-09-01 11:16:18 +08:00
|
|
|
|
|
|
|
|
|
// 检查浏览器是否原生支持 WritableStream,若不支持则使用polyfill
|
|
|
|
|
if (typeof window.WritableStream === 'undefined') {
|
|
|
|
|
window.WritableStream = WritableStream;
|
|
|
|
|
// 确保StreamSaver也使用相同的Polyfill
|
|
|
|
|
StreamSaver.WritableStream = WritableStream;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
StreamSaver.mitm = '/mitm.html' // 增加此行代码
|
2025-07-14 10:47:35 +08:00
|
|
|
|
const fileStream = StreamSaver.createWriteStream(filename, { size: contentLength });
|
|
|
|
|
|
|
|
|
|
if (window.WritableStream && readableStream?.pipeTo) {
|
|
|
|
|
await readableStream.pipeTo(fileStream);
|
|
|
|
|
window.$message?.success('下载完成');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 降级处理
|
|
|
|
|
const writer = fileStream.getWriter();
|
|
|
|
|
const reader = readableStream.getReader();
|
|
|
|
|
|
|
|
|
|
const pump = async (): Promise<void> => {
|
|
|
|
|
const { done, value } = await reader.read();
|
|
|
|
|
if (done) return writer.close();
|
|
|
|
|
await writer.write(value);
|
|
|
|
|
return pump();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
await pump();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 处理响应 */
|
|
|
|
|
async function handleResponse(response: Response) {
|
|
|
|
|
if (response.headers.get('Content-Type')?.includes('application/json')) {
|
|
|
|
|
const res = await response.json();
|
|
|
|
|
const code = res.code as CommonType.ErrorCode;
|
|
|
|
|
throw new Error(errorCodeRecord[code] || res.msg || errorCodeRecord.default);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 核心下载逻辑 */
|
|
|
|
|
async function executeDownload(config: RequestConfig): Promise<void> {
|
|
|
|
|
const { method, url, params, filename, contentType } = config;
|
|
|
|
|
const timestamp = Date.now();
|
|
|
|
|
const fullUrl = `${baseURL}${url}${url.includes('?') ? '&' : '?'}t=${timestamp}`;
|
|
|
|
|
|
|
|
|
|
window.$loading?.startLoading('正在下载数据,请稍候...');
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const requestOptions: RequestInit = {
|
|
|
|
|
method,
|
|
|
|
|
headers: getCommonHeaders(contentType)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (method === 'POST' && params) {
|
|
|
|
|
requestOptions.body = transformToURLSearchParams(params);
|
|
|
|
|
requestOptions.headers = {
|
|
|
|
|
...requestOptions.headers,
|
|
|
|
|
'Content-Type': 'application/x-www-form-urlencoded'
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const response = await fetch(fullUrl, requestOptions);
|
|
|
|
|
|
|
|
|
|
await handleResponse(response);
|
|
|
|
|
|
|
|
|
|
const finalFilename = filename || response.headers.get('Download-Filename') || `download-${timestamp}`;
|
|
|
|
|
|
2025-09-02 14:13:24 +08:00
|
|
|
|
/*老旧浏览器还是存在流式传输兼容问题,暂时屏蔽,只使用普通传输*/
|
|
|
|
|
/*if (response.body) {
|
2025-07-14 10:47:35 +08:00
|
|
|
|
const contentLength = Number(response.headers.get('Content-Length'));
|
|
|
|
|
await downloadByStream(response.body, finalFilename, contentLength);
|
|
|
|
|
return;
|
2025-09-02 14:13:24 +08:00
|
|
|
|
}*/
|
2025-07-14 10:47:35 +08:00
|
|
|
|
|
|
|
|
|
const responseContentType = response.headers.get('Content-Type');
|
|
|
|
|
const mainType = responseContentType?.split(';')[0]?.trim() || 'application/octet-stream';
|
|
|
|
|
downloadByData(await response.blob(), finalFilename, mainType);
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
window.$message?.error(error.message);
|
|
|
|
|
} finally {
|
|
|
|
|
window.$loading?.endLoading();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 公共下载接口 */
|
|
|
|
|
const download = (url: string, params: Record<string, any>, filename: string) =>
|
|
|
|
|
executeDownload({ method: 'POST', url, params, filename });
|
|
|
|
|
|
|
|
|
|
/** OSS文件下载 */
|
|
|
|
|
const oss = (ossId: CommonType.IdType) =>
|
|
|
|
|
executeDownload({
|
|
|
|
|
method: 'GET',
|
|
|
|
|
url: `/resource/oss/download/${ossId}`
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/** ZIP文件下载 */
|
|
|
|
|
const zip = (url: string, filename: string) =>
|
|
|
|
|
executeDownload({
|
|
|
|
|
method: 'GET',
|
|
|
|
|
url,
|
|
|
|
|
filename,
|
|
|
|
|
contentType: 'application/octet-stream'
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
oss,
|
|
|
|
|
zip,
|
|
|
|
|
download
|
|
|
|
|
};
|
|
|
|
|
}
|