К сожалению, нет надежных кроссплатформенных методов для запуска поведения загрузки браузера для обычных веб-страниц, которые бы отвечали всем требованиям. Поскольку вы не можете использовать обычный URL с расположением содержимого, перенаправлениями или URI данных в обычном теге привязки DOM, я не вижу другого способа вызвать загрузку без создания скрытого a
и щелчка по нему. Тем не менее, это, кажется, работает хорошо (и это действительно механизм, используемый популярными утилитами, такими как filesaver.js )
Создание сырого DownloadButton
компонента для этого в React довольно просто. Вот рабочий код , который высмеивает ответ Axios и в остальном работает от начала до конца, за исключением любого рефакторинга, который вы хотели бы сделать. Я использую крючки и async/await
для моего здравомыслия / ясности, но ни один из них не является строго необходимым. Он использует атрибут download
для тегов привязки, что хорошо поддерживается в современных браузерах.
function getFileNameFromContentDisposition(contentDisposition) {
if (!contentDisposition) return null;
const match = contentDisposition.match(/filename="?([^"]+)"?/);
return match ? match[1] : null;
}
const DownloadButton = ({ children, fileName, loadingText }) => {
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState(null);
const handleClick = async () => {
setLoading(true);
setError(null);
let res = null;
try {
// add any additional headers, such as authorization, as the second parameter to get below
// also, remember to use responseType: 'blob' if working with blobs instead, and use res.blob() instead of res.data below
res = await axios.get(`/api/files/${fileName}`);
setLoading(false);
} catch (err) {
setLoading(false);
setError(err);
return;
}
const data = res.data; // or res.blob() if using blob responses
const url = window.URL.createObjectURL(
new Blob([data], {
type: res.headers["content-type"]
})
);
const actualFileName = getFileNameFromContentDisposition(
res.headers["content-disposition"]
);
// uses the download attribute on a temporary anchor to trigger the browser
// download behavior. if you need wider compatibility, you can replace this
// part with a library such as filesaver.js
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", actualFileName);
document.body.appendChild(link);
link.click();
link.parentNode.removeChild(link);
};
if (error) {
return (<div>Unable to download file: {error.message}</div>);
}
return (
<button onClick={handleClick} disabled={loading}>
{loading ? loadingText || "Please wait..." : children}
</button>
);
};
Что касается content-disposition
, не отображаемого в заголовках ответа от ExpressJS, яЯ не уверен, в чем проблема. Однако, согласно документам ExpressJS , вторым параметром является имя файла, которое будет автоматически отправлено в виде заголовка content-disposition
, поэтому вам не нужно указывать его самостоятельно в параметре options
. Другие параметры отображаются? Если это так, возможно, существует конфликт в переопределении этого options
. Тем не менее, я не испытываю проблем в любом случае, когда запускаю пример локально с маршрутом, похожим на ваш.
res.download (путь [, имя файла] [, опции] [, fn])
Опциональный аргумент опций поддерживается Express v4.16.0 и далее.
Передает файл по пути как «вложение». Как правило, браузеры предлагают пользователю загрузить файл. По умолчанию параметр «filename =» заголовка Content-Disposition - это путь (обычно он отображается в диалоговом окне браузера). Переопределите это значение по умолчанию с помощью параметра имени файла.
Когда происходит ошибка или передача завершена, метод вызывает дополнительную функцию обратного вызова fn. Этот метод использует res.sendFile () для передачи файла.
Необязательный аргумент параметров передается базовому вызову res.sendFile () и принимает те же параметры.