Files
exchange-2.0/resources/assets/vue/components/general/elements/PasswordProtectedDownloadComponent.vue
T
2025-06-13 00:23:22 +08:00

156 lines
5.1 KiB
Vue

<template>
<span>
<a :class="[customClass, { 'link-disabled': isDownloading }]" @click.prevent="showPasswordModal">
<slot></slot>
<span v-if="isDownloading" class="spinner"></span>
</a>
<modal-component styleType="fill-in" type="passwordPrompt" @close="closeModal">
<div class="row bg-white p-t-45 p-b-45 p-l-45 p-r-45">
<div class="col">
<h4 class="m-b-20 text-center">Enter Password</h4>
<div class="form-group">
<input type="password" class="form-control" v-model="password" placeholder="Enter password" @keyup.enter="handleDownload">
</div>
<div class="text-danger m-b-10" v-if="error">{{ error }}</div>
<div class="row">
<div class="col-6">
<button class="btn btn-default btn-block" @click="closeModal">Cancel</button>
</div>
<div class="col-6">
<button class="btn btn-primary btn-block" @click="handleDownload" :disabled="isDownloading">
Download
</button>
</div>
</div>
</div>
</div>
</modal-component>
</span>
</template>
<script>
export default {
props: {
url: {
type: String,
required: true,
},
customClass: {
type: String,
default: ''
}
},
data() {
return {
isDownloading: false,
password: '',
error: '',
modalVisible: false
};
},
methods: {
showPasswordModal() {
this.password = '';
this.error = '';
this.$nextTick(() => {
$('.modalContainer[data-type="passwordPrompt"]').modal('show');
});
},
closeModal() {
$('.modalContainer[data-type="passwordPrompt"]').modal('hide');
},
async handleDownload() {
if (!this.password) {
this.error = 'Please enter a password';
return;
}
this.isDownloading = true;
this.error = '';
try {
const response = await fetch(this.url + '?password=' + this.password, {
method: 'GET',
headers: {
'Authorization': 'Bearer ' + this.$store.getters.getAccessToken,
},
});
if (response.status === 403) {
this.error = 'Invalid password';
this.isDownloading = false;
return;
}
if (response.status === 200) {
// Check if response is JSON
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
const data = await response.json();
if (data.src) {
// If JSON contains a file URL, trigger download
window.location.href = data.src;
this.closeModal();
return;
}
}
// Handle direct file download
const contentDisposition = response.headers.get('Content-Disposition');
const filename = contentDisposition ? contentDisposition.split('filename=')[1] : 'downloaded_file';
const blob = await response.blob();
const blobUrl = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = blobUrl;
a.download = filename;
a.style.display = 'none';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(blobUrl);
this.closeModal();
} else {
this.error = 'Download failed';
}
} catch (error) {
console.error('Network error:', error);
this.error = 'Network error occurred';
} finally {
this.isDownloading = false;
}
}
}
};
</script>
<style scoped>
.spinner {
border: 2px solid rgba(0, 0, 0, 0.1);
border-left-color: #000;
border-radius: 50%;
width: 20px;
height: 20px;
animation: spin 1s linear infinite;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
@keyframes spin {
0% {
transform: translate(-50%, -50%) rotate(0deg);
}
100% {
transform: translate(-50%, -50%) rotate(360deg);
}
}
.link-disabled {
pointer-events: none;
opacity: 0.6;
border: none;
}
</style>