<template>
|
<el-dialog
|
class="file-preview-dialog"
|
:title="previewTitle"
|
:visible.sync="previewVisible"
|
width="80%"
|
top="5vh"
|
:close-on-click-modal="true"
|
append-to-body
|
@close="handleClose"
|
>
|
<!-- 加载状态 -->
|
<div v-if="loading" class="preview-loading">
|
<i class="el-icon-loading" style="font-size: 40px; margin-bottom: 16px;"></i>
|
<span>文件加载中...</span>
|
</div>
|
|
<!-- 图片预览 -->
|
<div v-else-if="fileType === 'image'" class="preview-container">
|
<div class="image-toolbar">
|
<el-button-group>
|
<el-button size="mini" @click="zoomImageIn" :disabled="imageScale >= 300">
|
<i class="el-icon-zoom-in"></i> 放大
|
</el-button>
|
<el-button size="mini" disabled>{{ imageScale }}%</el-button>
|
<el-button size="mini" @click="zoomImageOut" :disabled="imageScale <= 50">
|
<i class="el-icon-zoom-out"></i> 缩小
|
</el-button>
|
<el-button size="mini" @click="resetImageZoom">
|
<i class="el-icon-refresh-left"></i> 重置
|
</el-button>
|
</el-button-group>
|
|
<el-button size="mini" type="success" @click="handleDownload">
|
<i class="el-icon-download"></i> 下载
|
</el-button>
|
</div>
|
|
<div class="image-viewport">
|
<img
|
:src="fileUrl"
|
alt="预览图片"
|
class="preview-image"
|
:style="{ transform: `scale(${imageScale / 100})` }"
|
@load="handleImageLoad"
|
/>
|
</div>
|
</div>
|
|
<!-- PDF预览 -->
|
<div v-else-if="fileType === 'pdf'" class="preview-container pdf-preview">
|
<div class="pdf-toolbar">
|
<el-button-group>
|
<el-button
|
size="mini"
|
@click="changePage(currentPage - 1)"
|
:disabled="currentPage <= 1"
|
>
|
<i class="el-icon-arrow-left"></i> 上一页
|
</el-button>
|
<el-button size="mini" disabled>
|
第 {{ currentPage }} 页 / 共 {{ pageCount }} 页
|
</el-button>
|
<el-button
|
size="mini"
|
@click="changePage(currentPage + 1)"
|
:disabled="currentPage >= pageCount"
|
>
|
<i class="el-icon-arrow-right"></i> 下一页
|
</el-button>
|
</el-button-group>
|
|
<el-button-group class="zoom-controls">
|
<el-button size="mini" @click="zoomOut" :disabled="scale <= 50">
|
<i class="el-icon-zoom-out"></i> 缩小
|
</el-button>
|
<el-button size="mini" disabled> {{ scale }}% </el-button>
|
<el-button size="mini" @click="zoomIn" :disabled="scale >= 200">
|
<i class="el-icon-zoom-in"></i> 放大
|
</el-button>
|
<el-button size="mini" @click="resetZoom">
|
<i class="el-icon-refresh-left"></i> 重置
|
</el-button>
|
</el-button-group>
|
|
<el-button
|
size="mini"
|
type="success"
|
@click="handleDownload"
|
>
|
<i class="el-icon-download"></i> 下载
|
</el-button>
|
</div>
|
|
<div class="pdf-content" v-loading="pdfLoading">
|
<pdf
|
ref="pdf"
|
:src="pdfUrl"
|
:page="currentPage"
|
@num-pages="pageCount = $event"
|
@page-loaded="currentPage = $event"
|
@loaded="loadPdfHandler"
|
@error="pdfErrorHandler"
|
:style="{
|
width: scale + '%',
|
transform: 'scale(' + scale / 100 + ')',
|
transformOrigin: '0 0'
|
}"
|
></pdf>
|
</div>
|
</div>
|
|
<!-- Office文档预览 -->
|
<div v-else-if="fileType === 'office'" class="preview-container office-preview">
|
<div class="office-toolbar">
|
<el-alert
|
title="Office文档预览提示"
|
description="建议下载后使用本地Office软件查看以获得最佳体验"
|
type="info"
|
show-icon
|
:closable="false"
|
/>
|
<el-button size="mini" type="success" @click="handleDownload">
|
<i class="el-icon-download"></i> 下载文档
|
</el-button>
|
</div>
|
|
<div class="office-content">
|
<iframe
|
:src="`https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(fileUrl)}`"
|
width="100%"
|
height="600px"
|
frameborder="0"
|
></iframe>
|
</div>
|
</div>
|
|
<!-- 不支持预览的文件类型 -->
|
<div v-else class="preview-container unsupported-preview">
|
<el-alert
|
title="该文件格式不支持在线预览"
|
:description="`文件类型: ${getFileExtension(fileName)}`"
|
type="warning"
|
show-icon
|
:closable="false"
|
/>
|
|
<div class="unsupported-actions">
|
<el-button type="primary" @click="handleDownload">
|
<i class="el-icon-download"></i> 下载文件
|
</el-button>
|
<el-button @click="handleClose">关闭</el-button>
|
</div>
|
|
<div class="file-info">
|
<p><strong>文件名:</strong> {{ fileName }}</p>
|
<p><strong>文件类型:</strong> {{ getFileExtension(fileName) }}</p>
|
</div>
|
</div>
|
</el-dialog>
|
</template>
|
|
<script>
|
import pdf from "vue-pdf";
|
|
export default {
|
name: "FilePreviewDialog",
|
components: {
|
pdf
|
},
|
props: {
|
visible: {
|
type: Boolean,
|
default: false
|
},
|
file: {
|
type: Object,
|
default: () => ({})
|
}
|
},
|
data() {
|
return {
|
// 预览控制
|
previewVisible: this.visible,
|
// 加载状态
|
loading: false,
|
pdfLoading: false,
|
// 文件信息
|
fileUrl: "",
|
fileName: "",
|
fileType: "",
|
// PDF相关
|
currentPage: 1,
|
pageCount: 0,
|
scale: 100,
|
pageRotate: 0,
|
// 图片相关
|
imageScale: 100
|
};
|
},
|
computed: {
|
previewTitle() {
|
return `文件预览 - ${this.fileName}`;
|
},
|
pdfUrl() {
|
return this.fileUrl;
|
}
|
},
|
watch: {
|
visible(newVal) {
|
this.previewVisible = newVal;
|
if (newVal) {
|
this.initPreview();
|
}
|
},
|
previewVisible(newVal) {
|
this.$emit('update:visible', newVal);
|
if (!newVal) {
|
this.handleClose();
|
}
|
}
|
},
|
methods: {
|
/** 初始化预览 */
|
initPreview() {
|
if (!this.file) return;
|
|
this.fileName = this.file.fileName || this.file.name || "未知文件";
|
this.fileUrl = this.file.fileUrl || this.file.path || this.file.url;
|
this.fileType = this.getFileType(this.fileName);
|
|
this.loading = true;
|
|
// 重置状态
|
this.currentPage = 1;
|
this.scale = 100;
|
this.imageScale = 100;
|
this.pageCount = 0;
|
|
// 模拟加载延迟
|
setTimeout(() => {
|
this.loading = false;
|
if (this.fileType === "pdf") {
|
this.pdfLoading = true;
|
}
|
}, 300);
|
},
|
|
/** 获取文件类型 */
|
getFileType(fileName) {
|
if (!fileName) return "other";
|
|
const extension = fileName.split('.').pop().toLowerCase();
|
const imageTypes = ["jpg", "jpeg", "png", "gif", "bmp", "webp"];
|
const pdfTypes = ["pdf"];
|
const officeTypes = ["doc", "docx", "xls", "xlsx", "ppt", "pptx"];
|
|
if (imageTypes.includes(extension)) return "image";
|
if (pdfTypes.includes(extension)) return "pdf";
|
if (officeTypes.includes(extension)) return "office";
|
return "other";
|
},
|
|
/** 获取文件扩展名 */
|
getFileExtension(filename) {
|
return filename.split('.').pop().toLowerCase() || "未知";
|
},
|
|
/** PDF加载完成 */
|
loadPdfHandler() {
|
this.pdfLoading = false;
|
this.currentPage = 1;
|
},
|
|
/** PDF加载错误 */
|
pdfErrorHandler(error) {
|
console.error("PDF加载失败:", error);
|
this.pdfLoading = false;
|
this.$message.error("PDF文件加载失败,请尝试下载后查看");
|
this.previewVisible = false;
|
},
|
|
/** 图片加载完成 */
|
handleImageLoad() {
|
this.loading = false;
|
},
|
|
/** 改变PDF页码 */
|
changePage(newPage) {
|
if (newPage < 1 || newPage > this.pageCount) return;
|
this.currentPage = newPage;
|
},
|
|
/** PDF放大 */
|
zoomIn() {
|
if (this.scale >= 200) {
|
this.$message.info("已放大到最大比例");
|
return;
|
}
|
this.scale += 10;
|
},
|
|
/** PDF缩小 */
|
zoomOut() {
|
if (this.scale <= 50) {
|
this.$message.info("已缩小到最小比例");
|
return;
|
}
|
this.scale -= 10;
|
},
|
|
/** PDF重置缩放 */
|
resetZoom() {
|
this.scale = 100;
|
},
|
|
/** 图片放大 */
|
zoomImageIn() {
|
if (this.imageScale >= 300) {
|
this.$message.info("已放大到最大比例");
|
return;
|
}
|
this.imageScale += 20;
|
},
|
|
/** 图片缩小 */
|
zoomImageOut() {
|
if (this.imageScale <= 50) {
|
this.$message.info("已缩小到最小比例");
|
return;
|
}
|
this.imageScale -= 20;
|
},
|
|
/** 图片重置缩放 */
|
resetImageZoom() {
|
this.imageScale = 100;
|
},
|
|
/** 下载文件 */
|
handleDownload() {
|
if (!this.fileUrl) {
|
this.$message.warning("文件路径不存在,无法下载");
|
return;
|
}
|
|
const link = document.createElement("a");
|
link.href = this.fileUrl;
|
link.download = this.fileName;
|
link.style.display = "none";
|
document.body.appendChild(link);
|
link.click();
|
document.body.removeChild(link);
|
|
this.$message.success("开始下载文件");
|
},
|
|
/** 关闭对话框 */
|
handleClose() {
|
this.previewVisible = false;
|
this.$emit("close");
|
|
// 清理资源
|
if (this.fileUrl && this.fileUrl.startsWith("blob:")) {
|
URL.revokeObjectURL(this.fileUrl);
|
}
|
}
|
}
|
};
|
</script>
|
|
<style scoped>
|
.file-preview-dialog {
|
.preview-loading {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
height: 400px;
|
color: #909399;
|
}
|
|
.preview-container {
|
display: flex;
|
flex-direction: column;
|
height: 70vh;
|
}
|
|
/* 工具栏通用样式 */
|
.image-toolbar,
|
.pdf-toolbar,
|
.office-toolbar {
|
padding: 15px 20px;
|
background: #f5f7fa;
|
border-bottom: 1px solid #ebeef5;
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
flex-wrap: wrap;
|
gap: 10px;
|
}
|
|
/* 图片预览样式 */
|
.image-viewport {
|
flex: 1;
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
overflow: auto;
|
padding: 20px;
|
background: #f8f9fa;
|
}
|
|
.preview-image {
|
max-width: 100%;
|
max-height: 100%;
|
transition: transform 0.3s ease;
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
}
|
|
/* PDF预览样式 */
|
.pdf-content {
|
flex: 1;
|
overflow: auto;
|
padding: 20px;
|
background: #f8f9fa;
|
display: flex;
|
justify-content: center;
|
align-items: flex-start;
|
}
|
|
/* Office预览样式 */
|
.office-content {
|
flex: 1;
|
padding: 20px;
|
background: #f8f9fa;
|
}
|
|
/* 不支持预览的样式 */
|
.unsupported-preview {
|
padding: 40px 20px;
|
text-align: center;
|
}
|
|
.unsupported-actions {
|
margin: 30px 0;
|
}
|
|
.unsupported-actions .el-button {
|
margin: 0 10px;
|
}
|
|
.file-info {
|
text-align: left;
|
max-width: 400px;
|
margin: 0 auto;
|
padding: 20px;
|
background: #f5f7fa;
|
border-radius: 4px;
|
}
|
|
.file-info p {
|
margin: 8px 0;
|
}
|
}
|
|
/* 对话框样式调整 */
|
:deep(.el-dialog) {
|
margin-top: 5vh !important;
|
}
|
|
:deep(.el-dialog__body) {
|
padding: 0;
|
}
|
|
/* 响应式设计 */
|
@media (max-width: 768px) {
|
.file-preview-dialog {
|
:deep(.el-dialog) {
|
width: 95% !important;
|
}
|
|
.preview-container {
|
.image-toolbar,
|
.pdf-toolbar,
|
.office-toolbar {
|
flex-direction: column;
|
gap: 10px;
|
}
|
}
|
}
|
}
|
</style>
|