import { isBlob, isPromiseLike } from 'ut2';
import dataURLToBlob from './dataURLToBlob';
import isUrl from './isUrl';
import ajax from './ajax';
import { createObjectURL, nativeUndefined, revokeObjectURL } from './utils/native';
// 下载文件到本地
function saver(blobUrl, fileName = '') {
const anchor = document.createElement('a');
// anchor.href = decodeURIComponent(blobUrl);
anchor.href = blobUrl;
anchor.style.display = 'none';
anchor.setAttribute('download', fileName);
// 处理点击事件,防止事件冒泡到 body/html 的点击事件。
function handleClick(e) {
e.stopPropagation();
anchor.removeEventListener('click', handleClick);
}
anchor.addEventListener('click', handleClick);
document.body.appendChild(anchor);
anchor.click();
document.body.removeChild(anchor);
}
/**
* 下载
*
* <em style="font-weight: bold;">注意:该方法仅适用于浏览器端,兼容 IE10+ 和现代浏览器。</em>
*
* <em style="font-weight: bold;">注意:微信浏览器不支持H5下载文件。</em>
*
* <em>响应头中有 "Content-Disposition" 字段,客户端获取不到?</em> 请参考查阅 [Access-Control-Expose-Headers](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Access-Control-Expose-Headers) 。
*
* @alias module:Browser.download
* @since 4.16.0
* @see {@link https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Access-Control-Expose-Headers Access-Control-Expose-Headers}
* @see {@link https://zh.wikipedia.org/wiki/多用途互聯網郵件擴展 MIME}
* @see {@link https://9ykc9s.csb.app/ 在线示例}
* @param {string|Blob|ArrayBuffer|TypedArray} data 字符串、blob数据或url地址
* @param {string|Object} [options] 文件名称 或 配置项。
* @param {string} [options.fileName] 文件名称。
* @param {string} [options.type] MIME 类型。
* @param {'url'|'text'} [options.dataType] 手动设置数据类型,主要是为了区分 `url` 和 `text`,默认会根据传入的数据判断类型。<br/>如果你要下载的文本是 `url` ,请设置 `'text'` ;如果你要下载的 url 是绝对/相对路径,请设置 'url' 。
* @param {Function} [options.transformRequest] 请求前触发,XHR 对象或配置调整。
* @param {Function} [options.transformResponse] 请求成功后触发,在传递给 then/catch 前,允许修改响应数据。
* @returns {Promise<void>}
* @example
* // 文本
* download('hello world', 'text.txt');
*
* // 远程文件1
* // 不带协议的绝对地址,需要通过 dataType 指定为 url 类型
* download('/xxx.jpg', { dataType: 'url', fileName: 'test.jpg' });
*
* // 远程文件2
* download('https://example.com/xxx.jpg');
*
* // base64
* download('data:image/png;base64,PGEgaWQ9ImEiPjxiIGlkPSJiIj5oZXkhPC9iPjwvYT4=', 'test.png');
*
* // blob文件
* download(new Blob(['hello world']), 'text.txt');
*
* // 本地文件
* download(File, 'filename.ext');
*
*/
async function download(data, options) {
const config = typeof options === 'object' ? options : {};
if (typeof options === 'string') {
config.fileName = options;
}
const { fileName, type, dataType, transformRequest, transformResponse } = config;
let payload;
// dataURLs、blob url、url、string
if (typeof data === 'string') {
if (!dataType && /^blob:.*?\/.*/.test(data)) {
// blob url
saver(data, fileName);
return Promise.resolve();
}
else if (!dataType && /^data:([\w+-]+\/[\w+.-]+)?[,;]/.test(data)) {
// dataURLs
payload = dataURLToBlob(data);
}
else if (dataType === 'url' || (!dataType && isUrl(data))) {
// url
// 包装为异步方法
const asyncTransformRequest = (opts) => {
// 请求前配置调整
const tempOptions = typeof transformRequest === 'function' ? transformRequest(opts) : opts;
return isPromiseLike(tempOptions) ? tempOptions : Promise.resolve(tempOptions);
};
const asyncTransformResponse = (res) => {
const tempRes = typeof transformResponse === 'function' ? transformResponse(res) : res;
return isPromiseLike(tempRes) ? tempRes : Promise.resolve(tempRes);
};
const ajaxOptions = await asyncTransformRequest({ responseType: 'blob' });
const ev = await ajax(data, ajaxOptions);
// @ts-ignore
const res = await asyncTransformResponse(ev.target.response);
const currentFileName = fileName || data.split('?')[0].split('#')[0].split('/').pop();
return download(res, { fileName: currentFileName, type: type || (isBlob(res) ? res.type : nativeUndefined) });
}
else {
// string
payload = new Blob([data], { type: type || 'text/plain' });
}
}
else if (data instanceof Blob) {
// File or Blob
payload = data;
}
// html、TypedArray
if (!payload) {
payload = new Blob([data], { type });
}
// @ts-ignore
if (navigator.msSaveBlob) {
// @ts-ignore
navigator.msSaveBlob(payload, fileName || 'download');
}
else {
const url = createObjectURL(payload);
saver(url, fileName);
revokeObjectURL(url);
}
return Promise.resolve();
}
export default download;