<template>
<div class="document-viewer">
<!-- DOCX/XLSX Container -->
<div
v-show="activeCategory === 'word' || activeCategory === 'spreadsheet'"
ref="documentContainer"
class="document-wrapper"
:style="{ height: viewerHeight + 'px' }"
/>
<!-- PDF Viewer -->
<div
v-show="activeCategory === 'pdf'"
v-loading="isLoading"
class="pdf-container"
:style="{ height: viewerHeight + 'px' }"
>
<vue-pdf
v-for="page in pageTotal"
:key="page"
:src="documentSource"
:page="page"
class="pdf-page"
@progress="handlePdfProgress"
@page-loaded="handlePageLoaded"
/>
</div>
<!-- Legacy DOC Format -->
<iframe
v-show="activeCategory === 'legacy'"
ref="legacyFrame"
class="legacy-doc-frame"
frameborder="0"
:src="documentSource"
:style="{ height: viewerHeight + 'px' }"
/>
</div>
</template>
<script>
import vuePdf from 'vue-pdf'
import { renderAsync } from 'docx-preview'
import { saveAs } from 'file-saver'
import { previewExcel, generatePreviewUrl } from '@/utils/documentHelpers'
export default {
name: 'UniversalDocumentViewer',
components: { vuePdf },
data() {
return {
isLoading: false,
viewerHeight: 600,
activeCategory: '',
documentSource: '',
pageTotal: 0,
fileData: {
binary: null,
remoteUrl: '',
name: ''
}
}
},
mounted() {
this.calculateDimensions()
window.addEventListener('resize', this.debounceResize)
},
beforeDestroy() {
window.removeEventListener('resize', this.debounceResize)
},
methods: {
async loadDocument(metadata) {
this.clearViewer()
this.fileData = metadata
const extension = metadata.name.split('.').pop().toLowerCase()
switch(extension) {
case 'docx':
this.activeCategory = 'word'
await this.renderWordDocument(metadata.binary)
break
case 'pdf':
this.activeCategory = 'pdf'
this.renderPDF(metadata.remoteUrl)
break
case 'xlsx':
this.activeCategory = 'spreadsheet'
this.renderSpreadsheet(metadata.remoteUrl)
break
case 'doc':
this.activeCategory = 'legacy'
this.documentSource = generatePreviewUrl(metadata.remoteUrl)
break
}
},
async renderWordDocument(blobData) {
this.$nextTick(() => {
renderAsync(blobData, this.$refs.documentContainer, null, {
className: 'docx-preview'
})
})
},
renderSpreadsheet(url) {
previewExcel(url, this.$refs.documentContainer)
},
renderPDF(url) {
this.isLoading = true
const loader = vuePdf.createLoadingTask(url)
loader.promise
.then(pdfDoc => {
this.documentSource = loader
this.pageTotal = pdfDoc.numPages
})
.catch(error => {
console.error('PDF loading failed:', error)
this.isLoading = false
})
},
handlePdfProgress(progress) {
this.isLoading = progress < 1
},
handlePageLoaded(currentPage) {
if (currentPage === this.pageTotal) {
this.isLoading = false
}
},
debounceResize() {
clearTimeout(this.resizeTimer)
this.resizeTimer = setTimeout(() => {
this.calculateDimensions()
}, 200)
},
calculateDimensions() {
const viewportHeight = window.innerHeight
this.viewerHeight = viewportHeight - 280
},
exportDocument() {
if (this.fileData.binary) {
saveAs(this.fileData.binary, this.fileData.name)
}
},
printContent() {
const content = this.$refs.documentContainer?.innerHTML
if (!content) return
const originalBody = document.body.innerHTML
document.body.innerHTML = content
window.print()
document.body.innerHTML = originalBody
window.location.reload()
},
clearViewer() {
this.activeCategory = ''
this.pageTotal = 0
this.documentSource = ''
if (this.$refs.documentContainer) {
this.$refs.documentContainer.innerHTML = ''
}
}
}
}
</script>
<style lang="scss" scoped>
.document-viewer {
width: 100%;
background: #f5f5f5;
}
.document-wrapper {
width: 100%;
overflow-y: auto;
background: white;
padding: 20px;
box-sizing: border-box;
}
.pdf-container {
width: 100%;
overflow-y: auto;
background: #525659;
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 0;
.pdf-page {
width: 75%;
margin-bottom: 10px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
}
.legacy-doc-frame {
width: 100%;
border: none;
background: white;
}
</style>