smart_archives_web/src/views/viewFile/viewFile.vue

442 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<!-- 预览文件 -->
<el-dialog class="l-dialog" :class="lDialog" :title="title" :visible.sync="dialogVisible" :showClose="true"
:closeOnClickModal="false" @close="handleClose" :append-to-body="true">
<div style="text-align:center">
<!-- 图片预览 -->
<template v-if="isImage">
<div class="image-toolbar">
<el-button size="mini" @click="downloadFile"><i class="el-icon-download"></i> 下载</el-button>
</div>
<el-image :src="processedFileUrl" :preview-src-list="previewList" fit="contain"
style="max-width:100%;max-height:70vh">
<div slot="error" class="image-slot">
<i class="el-icon-picture-outline"></i>
</div>
</el-image>
</template>
<!-- PDF 预览 -->
<template v-else>
<div class="pdf-container">
<!-- PDF加载状态 -->
<div v-if="pdfLoading" class="pdf-loading">
<i class="el-icon-loading"></i>
<p>PDF加载中...</p>
</div>
<!-- PDF错误状态 -->
<div v-else-if="pdfError" class="pdf-error">
<i class="el-icon-warning"></i>
<p>PDF加载失败</p>
<el-button size="small" @click="retryLoadPdf">重试</el-button>
</div>
<!-- PDF内容 -->
<div v-else class="pdf-content">
<!-- PDF工具栏 -->
<div class="pdf-toolbar">
<div class="pdf-controls">
<el-button size="mini" :disabled="currentPage <= 1" @click="prevPage">
<i class="el-icon-arrow-left"></i> 上一页
</el-button>
<span class="page-info">
{{ currentPage }} / {{ numPages }}
</span>
<el-button size="mini" :disabled="currentPage >= numPages" @click="nextPage">
下一页 <i class="el-icon-arrow-right"></i>
</el-button>
</div>
<div class="pdf-actions">
<el-button size="mini" @click="downloadFile"><i class="el-icon-download"></i> 下载</el-button>
</div>
</div>
<!-- PDF显示区域 -->
<div class="pdf-viewer" @wheel="handleWheel" ref="pdfViewer">
<pdf :src="processedFileUrl" :page="currentPage" @num-pages="numPages = $event" @loaded="onPdfLoaded"
@error="onPdfError" style="width:100%;height:70vh" />
</div>
</div>
</div>
</template>
</div>
</el-dialog>
</template>
<script>
import pdf from 'vue-pdf'
import { getFileAsBase64Api } from '@/api/archivesManagement/fileManager/fileManager'
export default {
name: 'ViewFile',
props: ['width', 'hight', 'dataForm', 'title', 'disabled', 'isAdd', 'rowData', 'projectId'],
components: { pdf },
data() {
return {
dialogVisible: true,
lDialog: this.width > 500 ? "w700" : "w500",
fileUrl: '', // 实际文件地址
fileName: '', // 用于从文件名判断类型
previewList: [], // 预览组(可只放当前图片)
// PDF相关状态
pdfLoading: false,
pdfError: false,
currentPage: 1,
numPages: 0,
// 滚轮翻页相关
wheelTimeout: null,
isWheelScrolling: false
}
},
computed: {
isImage() {
// 允许的图片类型
const exts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']
const url = (this.fileUrl || '')
const name = (this.fileName || '').toLowerCase()
return exts.some(ext => url.endsWith(ext) || name.endsWith(ext))
},
isPdf() {
// 判断是否为PDF文件
const exts = ['.pdf']
const url = (this.fileUrl || '')
const name = (this.fileName || '').toLowerCase()
return exts.some(ext => url.endsWith(ext) || name.endsWith(ext))
},
processedFileUrl() {
// 处理文件URL为PDF和图片添加data前缀
if (this.fileUrl && !this.fileUrl.startsWith('data:')) {
if (this.isPdf) {
return `data:application/pdf;base64,${this.fileUrl}`
} else if (this.isImage) {
// 根据图片类型添加相应的MIME类型
const imageType = this.getImageMimeType()
return `data:${imageType};base64,${this.fileUrl}`
}
}
return this.fileUrl
}
},
created() {
this.getFileAsBase64();
},
watch: {
isPdf(newVal) {
if (newVal) {
this.resetPdfState();
}
}
},
methods: {
handleClose() {
this.dialogVisible = false
this.$emit('closeDialog')
},
/* 获取文件的base64 */
getFileAsBase64() {
if (this.isPdf) {
this.pdfLoading = true;
this.pdfError = false;
}
getFileAsBase64Api({ id: this.rowData.fileId }).then(res => {
const obj = res.data;
this.fileUrl = obj?.fileBase64 || ''
this.fileName = obj?.fileName || ''
// 预览组(如果你有多张,可放数组;没有就放当前一张)
if (this.isImage && this.fileUrl) {
this.previewList = [this.processedFileUrl]
}
}).catch(error => {
if (this.isPdf) {
this.pdfError = true;
this.pdfLoading = false;
}
console.error('获取文件失败:', error);
})
},
// PDF相关方法
resetPdfState() {
this.currentPage = 1;
this.numPages = 0;
this.pdfLoading = false;
this.pdfError = false;
this.isWheelScrolling = false;
if (this.wheelTimeout) {
clearTimeout(this.wheelTimeout);
this.wheelTimeout = null;
}
},
onPdfLoaded() {
this.pdfLoading = false;
this.pdfError = false;
},
onPdfError(error) {
this.pdfLoading = false;
this.pdfError = true;
console.error('PDF加载失败:', error);
},
retryLoadPdf() {
this.resetPdfState();
this.getFileAsBase64();
},
prevPage() {
if (this.currentPage > 1) {
this.currentPage--;
}
},
nextPage() {
if (this.currentPage < this.numPages) {
this.currentPage++;
}
},
// 下载文件图片或PDFbase64 -> Blob避免在控制台暴露base64
downloadFile() {
if (!this.fileUrl) return
const filename = this.fileName || '文件'
// 推断 MIME 类型
let mime = this.isPdf ? 'application/pdf' : this.getImageMimeType()
// 如果带有 data: 前缀,从中解析 mime 与数据体
let base64Data = this.fileUrl
if (typeof base64Data === 'string' && base64Data.startsWith('data:')) {
try {
const parts = base64Data.split(',')
const header = parts[0]
const dataPart = parts[1]
const match = header.match(/^data:(.*?);base64$/)
if (match && match[1]) mime = match[1]
base64Data = dataPart
} catch (e) {
// 解析失败则按原样处理
}
}
const blob = this.base64ToBlob(base64Data, mime)
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.style.display = 'none'
a.href = url
a.download = filename
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
URL.revokeObjectURL(url)
},
// base64(不含data前缀) 转 Blob
base64ToBlob(base64, mime) {
const byteChars = atob(base64)
const sliceSize = 1024
const byteArrays = []
for (let offset = 0; offset < byteChars.length; offset += sliceSize) {
const slice = byteChars.slice(offset, offset + sliceSize)
const byteNumbers = new Array(slice.length)
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i)
}
const byteArray = new Uint8Array(byteNumbers)
byteArrays.push(byteArray)
}
return new Blob(byteArrays, { type: mime || 'application/octet-stream' })
},
// 获取图片MIME类型
getImageMimeType() {
// 根据文件扩展名返回对应的MIME类型
const name = (this.fileName || '').toLowerCase()
if (name.endsWith('.jpg') || name.endsWith('.jpeg')) {
return 'image/jpeg'
} else if (name.endsWith('.png')) {
return 'image/png'
} else if (name.endsWith('.gif')) {
return 'image/gif'
} else if (name.endsWith('.bmp')) {
return 'image/bmp'
} else if (name.endsWith('.webp')) {
return 'image/webp'
}
// 默认返回jpeg
return 'image/jpeg'
},
// 处理滚轮事件
handleWheel(event) {
// 阻止默认滚动行为
event.preventDefault();
// 如果正在滚轮翻页中,忽略新的滚轮事件
if (this.isWheelScrolling) {
return;
}
// 设置滚轮翻页状态
this.isWheelScrolling = true;
// 清除之前的定时器
if (this.wheelTimeout) {
clearTimeout(this.wheelTimeout);
}
// 根据滚轮方向翻页
if (event.deltaY > 0) {
// 向下滚动 - 下一页
this.nextPage();
} else if (event.deltaY < 0) {
// 向上滚动 - 上一页
this.prevPage();
}
// 设置定时器,防止滚轮翻页过于频繁
this.wheelTimeout = setTimeout(() => {
this.isWheelScrolling = false;
}, 300);
}
},
}
</script>
<style lang="scss" scoped>
.w700 .el-dialog {
width: 700px;
}
.w500 .el-dialog {
width: 500px;
}
.w500 .el-dialog__header,
.w700 .el-dialog__header {
// background: #eeeeee;
.el-dialog__title {
font-size: 16px;
}
}
.yxq .el-range-separator {
margin-right: 7px !important;
}
.el-date-editor--daterange.el-input__inner {
width: 260px;
}
.form-item {
width: 100%;
}
.select-style {
display: flex;
justify-content: space-between;
}
.image-slot {
width: 100%;
height: 240px;
display: flex;
align-items: center;
justify-content: center;
color: #909399;
font-size: 24px;
}
/* PDF预览样式 */
.pdf-container {
width: 100%;
height: 70vh;
position: relative;
}
.pdf-loading,
.pdf-error {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: #909399;
font-size: 16px;
}
.pdf-loading i {
font-size: 32px;
margin-bottom: 16px;
animation: rotating 2s linear infinite;
}
.pdf-error i {
font-size: 32px;
margin-bottom: 16px;
color: #F56C6C;
}
.pdf-content {
height: 100%;
display: flex;
flex-direction: column;
}
.pdf-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
background: #f5f5f5;
border-bottom: 1px solid #e4e7ed;
flex-shrink: 0;
}
.pdf-controls {
display: flex;
align-items: center;
gap: 8px;
}
.image-toolbar {
display: flex;
justify-content: flex-end;
padding: 4px 0 8px 0;
}
.pdf-actions {
display: flex;
align-items: center;
gap: 8px;
}
.page-info {
font-size: 14px;
color: #606266;
min-width: 60px;
text-align: center;
}
.pdf-viewer {
flex: 1;
overflow: hidden;
display: flex;
justify-content: center;
background: #f8f9fa;
cursor: pointer;
}
/* 移除滚动条样式,因为现在使用滚轮翻页 */
@keyframes rotating {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>