Vue+element实现文件下载功能

前端的文件下载平时不会经常用到,就算用到可能也是前人已经写好的模块或者是第三方库,引入就可以使用了。但是我觉得作为前端开发,文件的下载还是非常有必要了解清楚的。
这里我用Vue + element简单写了一个文件下载,更多的用到的还是原生Js,希望对大家有所帮助
引入Element组件库这里就不多说啦,不知道的小伙伴就去官网看看吧~
先放一下Element 官方文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import { Loading, Message, MessageBox } from "element-ui"

/*
* 方法 downLoadFile
* 用途 文件导出下载
* 示例 downLoadFile({
* url: 'xxx', // 下载地址,必须有
* method: 'get', // 可选:get(默认)或 post
* params: { id: 1 }, // GET 查询参数,会与 url 自带参数合并
* data: { ids: [1, 2] }, // POST JSON 请求体
* loadingText: 'xxxx' // 导出下载文字提示,非必须
* })
*/

interface DownLoadFileParams {
url: string;
method?: 'get' | 'post' | 'GET' | 'POST';
/** GET 查询参数,会与 url 已有查询参数合并。 */
params?: Record<string, unknown>;
/** POST 请求体,将以 application/json 格式发送。 */
data?: unknown;
loadingText?: string;
timeout?: number;
message?: string;
}

const appendQueryParams = (url: string, params?: Record<string, unknown>) => {
if (!params) return url;

const [urlWithoutHash, hash = ''] = url.split('#', 2);
const searchParams = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value === null || value === undefined) return;
const values = Array.isArray(value) ? value : [value];
values.forEach(item => {
if (item !== null && item !== undefined) searchParams.append(key, String(item));
});
});

const query = searchParams.toString();
if (!query) return url;
const separator = urlWithoutHash.includes('?') ? '&' : '?';
return `${urlWithoutHash}${separator}${query}${hash ? `#${hash}` : ''}`;
};

const getDownloadFileName = (contentDisposition: string | null) => {
if (!contentDisposition) return null;

const encodedMatch = contentDisposition.match(/filename\*=UTF-8''([^;]+)/i);
const plainMatch = contentDisposition.match(/filename="?([^";]+)"?/i);
const filename = encodedMatch?.[1] ?? plainMatch?.[1];
if (!filename) return null;

try {
return decodeURIComponent(filename);
} catch {
return filename;
}
};

const getDownloadErrorMessage = async (blob: Blob | null, fallback: string) => {
if (!blob) return fallback;

try {
const result: unknown = JSON.parse(await blob.text());
if (!isRecord(result)) return fallback;
if (hasOwn(result, 'msg') && typeof result.msg === 'string' && !validatenull(result.msg)) {
return result.msg;
}
if (
hasOwn(result, 'message') &&
typeof result.message === 'string' &&
!validatenull(result.message)
) {
return result.message;
}
} catch (error) {
console.error(toError(error));
}

return fallback;
};

export async function downLoadFile(params: DownLoadFileParams) {
if (!params?.url) {
ElMessageBox.confirm('文件地址不存在', '温馨提示', {
confirmButtonText: '确认',
showCancelButton: false,
type: 'warning',
});
return;
}

const method = (params.method || 'get').toLowerCase() as 'get' | 'post';
const loading = ElLoading.service({
fullscreen: true,
text: params.loadingText || '文件数据资源下载中...',
background: 'rgba(0, 0, 0, 0.7)',
lock: true,
});

try {
await new Promise<void>((resolve, reject) => {
const xhr = new XMLHttpRequest();
const requestUrl =
method === 'get' ? appendQueryParams(params.url, params.params) : params.url;
const url = requestUrl.startsWith('http') ? requestUrl : `/api${requestUrl}`;

xhr.open(method, url, true);
xhr.responseType = 'blob';
xhr.timeout = params.timeout || 15 * 60 * 1000;
xhr.setRequestHeader('Content-Type', 'application/json;charset=utf-8');
xhr.setRequestHeader('Authorization', `Bearer ${getStore({ name: 'token' })}`);
xhr.setRequestHeader('Tenant-id', String(getStore({ name: 'tenantId' }) || ''));
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
const filename = getDownloadFileName(xhr.getResponseHeader('content-disposition'));
if (filename && xhr.response instanceof Blob) {
const blobUrl = URL.createObjectURL(xhr.response);
const link = document.createElement('a');
link.href = blobUrl;
link.download = filename;
document.body.appendChild(link);
link.click();
link.remove();
URL.revokeObjectURL(blobUrl);
resolve();
return;
}

getDownloadErrorMessage(xhr.response, params.message || '文件不存在,请确认后重试!')
.then(message => reject(new Error(message)))
.catch(reject);
return;
}

getDownloadErrorMessage(xhr.response, `文件下载失败:${xhr.statusText || xhr.status}`)
.then(message => reject(new Error(message)))
.catch(reject);
};
xhr.onerror = () => reject(new Error('文件下载失败,请检查网络连接后重试!'));
xhr.ontimeout = () => reject(new Error('文件下载超时,请稍后重试!'));
xhr.send(method === 'post' ? JSON.stringify(params.data ?? {}) : null);
});
} catch (error) {
ElMessageBox.confirm(toError(error).message, '温馨提示', {
confirmButtonText: '确认',
showCancelButton: false,
type: 'warning',
});
} finally {
loading.close();
}
}