Как я уже говорил выше, это решение представляет собой комбинацию частей из многих других потоков, но наиболее основательно вдохновлено: Скачать файл jQuery. Ajax
Что он делает
Позволяет пользователю взаимодействовать с динамическими c или stati c ссылками / кнопками загрузки файла и дает обратную связь, когда сервер flask начинает генерировать файл, и обслуживает файл. Другими словами, он отображает счетчик после запуска запроса на файл и скрывает счетчик после начала загрузки. На этом этапе загрузка все еще должна быть запущена, но большинство пользователей ясно знают, как браузер дает отзыв об этом. Пользователь никогда не покидает страницу запроса, что полезно в том случае, если запросы генерируются динамически с помощью форм (формы не меняют состояние).
Сторона flask
@downloads.route('/downloads/report_1/<year>/<month>/<person>')
def report_1(year, month, person):
#create an output stream
output = BytesIO()
writer = pd.ExcelWriter(output, engine='xlsxwriter') # for example. another great package
#prepare your file using arguments
#the writer has done its job
writer.close()
#go back to the beginning of the stream
output.seek(0)
response = make_response(send_file(output, attachment_filename=fname, as_attachment=True))
response.headers['my_filename'] = fname # I had problems with how the native "filename" key
return response
Javascript
<script type="text/javascript">
function downloadReport(url) {
showspinner();
fetch(url) // for instance: /downloads/report_1/2020/2/Robert
.then(
function(response) {
if (response.status !== 200) {
console.log('Looks like there was a problem. Status Code: '+response.status);
return;
}
var fname = response.headers.get('my_filename'); // this was necessary because the native filename key was oddly concatinated with another
// Examine the response
response.blob().then(function(blob) {
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
// the filename you want
a.download = fname;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
hidespinner();
});
}
)
.catch(function(err) {
console.log('Fetch Error :-S', err);
});
}
function showspinner(){
document.getElementsByClassName("loading_spinner")[0].style.display = "block";
}
function hidespinner(){
document.getElementsByClassName("loading_spinner")[0].style.display = "none";
}
</script>
Веб-страница
На моей странице есть поля формы, заполненные flask, значения которых используются для генерации url
выше, когда кнопка нажата (downloadReport(url)
вызывается атрибутом onclick на кнопке).
CSS Spinner
Для полноты, я поделюсь используемым прядильщиком (который можно найти на нескольких сайтах через inte rnet как lds-spinner):
.loading_spinner {
/* display: inline-block; */
/* transform: translateZ(1px); */
position: absolute;
left:50%;
top:30%;
display:none;
}
.loading_spinner > div {
display: inline-block;
width: 64px;
height: 64px;
margin: 8px;
border-radius: 50%;
background: #25aae1;
animation: lds-circle 2.4s cubic-bezier(0, 0.2, 0.8, 1) infinite;
}
@keyframes loading_spinner {
0%, 100% {
animation-timing-function: cubic-bezier(0.5, 0, 1, 0.5);
}
0% {
transform: rotateY(0deg);
}
50% {
transform: rotateY(1800deg);
animation-timing-function: cubic-bezier(0, 0.5, 0.5, 1);
}
100% {
transform: rotateY(3600deg);
}
}
со следующим на веб-странице
<div class="loading_spinner"><div></div></div>