npm install vue-pdf
npm install print-js
<template>
<div>
<!-- PDF 预览模态框 -->
<a-modal
:visible="showDialog"
:footer="null"
@cancel="handleCancel"
:width="800"
:maskClosable="true"
:keyboard="true"
>
<div style="overflow-y: auto; overflow-x: hidden; height: 600px">
<!-- 使用 sticky 定位打印按钮 -->
<div style="position: sticky; top: 0; background: white; padding: 0px 0; z-index: 1;">
<a-button
shape="round"
icon="file-pdf"
@click="handlePrint"
size="small"
style="margin-bottom: 10px"
>打印</a-button
>
</div>
<div id="printFrom">
<pdf
v-if="isPdf"
ref="pdf"
v-for="item in pageTotal"
:src="previewFileSrc"
:key="item"
:page="item"
></pdf>
<img
v-else-if="isImage"
:src="previewImage"
style="max-width: 100%; max-height: 500px; display: block; margin: 0 auto"
/>
<div v-else style="text-align: center">
<p>不支持预览此文件类型</p>
<a :href="previewFileSrc" download>下载文件</a>
</div>
</div>
</div>
</a-modal>
</div>
</template>
<script>
import Vue from "vue";
import { ACCESS_TOKEN } from "@/store/mutation-types";
import { getFileAccessHttpUrl } from "@/api/manage";
import pdf from "vue-pdf";
import printJS from "print-js";
const FILE_TYPE_ALL = "all";
const FILE_TYPE_IMG = "image";
const FILE_TYPE_TXT = "file";
export default {
name: "AutoFilePreview",
components: { pdf },
props: {
// 是否显示预览
showDialog: {
type: Boolean,
default: false,
},
},
data() {
return {
fileUrl: null,
printData: {
printable: "printFrom",
header: "",
ignore: ["no-print"],
},
previewImage: "",
previewFileSrc: "",
pageTotal: null,
isImage: false,
isPdf: false,
};
},
watch: {
fileUrl: {
immediate: true,
handler(newVal) {
if (newVal) {
this.previewFileSrc = newVal;
this.checkFileType();
}
},
},
showDialog: {
immediate: true,
handler(newVal) {
if (newVal && this.fileUrl) {
this.previewFileSrc = this.fileUrl;
this.checkFileType();
}
},
},
},
methods: {
handlePrint() {
printJS({
printable: "printFrom",
type: "html",
header: "",
targetStyles: ["*"],
style: "@page {margin:0 10mm};",
ignoreElements: ["no-print"],
});
this.test();
},
async checkFileType() {
// 重置状态
this.isImage = false;
this.isPdf = false;
// 获取文件类型
const fileType = this.matchFileType(this.fileUrl);
if (fileType === "image") {
this.previewImage = this.fileUrl;
this.isImage = true;
this.pageTotal = 1;
} else if (fileType === "pdf") {
this.isPdf = true;
await this.getTotal();
} else {
// 其他文件类型直接下载
this.isImage = false;
this.isPdf = false;
}
},
async getTotal() {
try {
// 多页pdf的src中不能直接使用后端获取的pdf地址
// 需要使用下述方法的返回值作为url
const loadingTask = pdf.createLoadingTask(this.previewFileSrc);
this.previewFileSrc = loadingTask;
// 获取页码
const pdfDoc = await loadingTask.promise;
this.pageTotal = pdfDoc.numPages;
} catch (error) {
console.error("PDF加载错误:", error);
this.$message.error("PDF文件加载失败");
}
},
matchFileType(fileName) {
// 后缀获取
let suffix = "";
// 获取类型结果
let result = "";
// 从URL中提取文件名
const urlParts = fileName.split("/");
const fullFileName = urlParts[urlParts.length - 1];
try {
// 截取文件后缀
suffix = fullFileName.substr(fullFileName.lastIndexOf(".") + 1, fullFileName.length);
// 文件后缀转小写,方便匹配
suffix = suffix.toLowerCase();
} catch (err) {
suffix = "";
}
// fileName无后缀返回 false
if (!suffix) {
result = false;
return result;
}
const fileTypeList = [
// 图片类型
{ typeName: "image", types: ["png", "jpg", "jpeg", "bmp", "gif"] },
// 文本类型
{ typeName: "txt", types: ["txt"] },
// excel类型
{ typeName: "excel", types: ["xls", "xlsx"] },
{ typeName: "word", types: ["doc", "docx"] },
{ typeName: "pdf", types: ["pdf"] },
{ typeName: "ppt", types: ["ppt"] },
// 视频类型
{ typeName: "video", types: ["mp4", "m2v", "mkv"] },
// 音频
{ typeName: "radio", types: ["mp3", "wav", "wmv"] },
];
for (let i = 0; i < fileTypeList.length; i++) {
const fileTypeItem = fileTypeList[i];
const typeName = fileTypeItem.typeName;
const types = fileTypeItem.types;
result = types.some(function(item) {
return item === suffix;
});
if (result) {
return typeName;
}
}
return "other";
},
handleCancel() {
this.$emit("update:showDialog", false);
},
loadFileUrl(url) {
this.fileUrl = url
}
},
};
</script>
<style lang="less" scoped>
/* 可以根据需要添加样式 */
</style>
父组件调用
<a-button
:ghost="true"
type="primary"
icon="eye"
size="small"
@click="showFilePreview(text)">
预览
</a-button>
<auto-file-preview ref="autoFilePreview" :show-dialog.sync="showPreview"></auto-file-preview>
showFilePreview(url){
this.showPreview = true
this.$refs.autoFilePreview.loadFileUrl(this.getImgView(url))
}
show-dialog是否展示true,false
效果:
进阶版,支持word,excel,音频,视频预览,组件使用方式相同
先安装所需依赖:
npm install xlsx mammoth
<template>
<div>
<a-modal
:visible="showDialog"
:footer="null"
@cancel="handleCancel"
:width="800"
:maskClosable="true"
:keyboard="true"
>
<div style="overflow-y: auto; overflow-x: hidden; height: 600px">
<div style="position: sticky; top: 0; background: white; padding: 10px 0; z-index: 1;">
<a-button
shape="round"
icon="file-pdf"
@click="handlePrint"
size="small"
style="margin-bottom: 10px"
>打印</a-button>
</div>
<div id="printFrom">
<pdf
v-if="isPdf"
ref="pdf"
v-for="item in pageTotal"
:src="previewFileSrc"
:key="item"
:page="item"
></pdf>
<img
v-else-if="isImage"
:src="previewImage"
style="max-width: 100%; max-height: 500px; display: block; margin: 0 auto"
/>
<pre v-else-if="isText" style="white-space: pre-wrap; word-wrap: break-word;">
{{ textContent }}
</pre>
<table v-else-if="isExcel">
<tr v-for="(row, index) in excelData" :key="index">
<td v-for="(value, key) in row" :key="key">{{ value }}</td>
</tr>
</table>
<pre v-else-if="isWord" style="white-space: pre-wrap; word-wrap: break-word;">
{{ wordContent }}
</pre>
<video
v-else-if="isVideo"
controls
style="max-width: 100%; display: block; margin: 0 auto"
>
<source :src="previewFileSrc" :type="getVideoType(previewFileSrc)" />
您的浏览器不支持视频标签。
</video>
<audio
v-else-if="isAudio"
controls
style="display: block; margin: 0 auto"
>
<source :src="previewFileSrc" :type="getAudioType(previewFileSrc)" />
您的浏览器不支持音频标签。
</audio>
<div v-else-if="isUnsupported" style="text-align: center">
<p>不支持预览此文件类型</p>
<a :href="previewFileSrc" download>下载文件</a>
</div>
</div>
</div>
</a-modal>
</div>
</template>
<script>
import pdf from "vue-pdf";
import printJS from "print-js";
import * as XLSX from "xlsx";
import mammoth from "mammoth";
export default {
name: "AutoFilePreview",
components: { pdf },
props: {
showDialog: {
type: Boolean,
default: false,
},
},
data() {
return {
fileUrl: null,
previewImage: "",
previewFileSrc: "",
pageTotal: null,
isImage: false,
isPdf: false,
isText: false,
isExcel: false,
isWord: false,
isVideo: false,
isAudio: false,
isUnsupported: false,
textContent: "",
excelData: [],
wordContent: "",
};
},
watch: {
fileUrl: {
immediate: true,
handler(newVal) {
if (newVal) {
this.previewFileSrc = newVal;
this.checkFileType();
}
},
},
showDialog: {
immediate: true,
handler(newVal) {
if (newVal && this.fileUrl) {
this.previewFileSrc = this.fileUrl;
this.checkFileType();
}
},
},
},
methods: {
handlePrint() {
printJS({
printable: "printFrom",
type: "html",
header: "",
targetStyles: ["*"],
style: "@page {margin:0 10mm};",
ignoreElements: ["no-print"],
});
},
async checkFileType() {
this.isImage = false;
this.isPdf = false;
this.isText = false;
this.isExcel = false;
this.isWord = false;
this.isVideo = false;
this.isAudio = false;
this.isUnsupported = false;
const fileType = this.matchFileType(this.fileUrl);
if (fileType === "image") {
this.previewImage = this.fileUrl;
this.isImage = true;
this.pageTotal = 1;
} else if (fileType === "pdf") {
this.isPdf = true;
await this.getTotal();
} else if (fileType === "txt") {
await this.fetchTextFile();
this.isText = true;
} else if (fileType === "excel") {
await this.fetchExcelFile();
this.isExcel = true;
} else if (fileType === "word") {
await this.fetchWordFile();
this.isWord = true;
} else if (fileType === "video") {
this.isVideo = true;
} else if (fileType === "audio") {
this.isAudio = true;
} else {
this.isUnsupported = true;
}
},
async getTotal() {
try {
const loadingTask = pdf.createLoadingTask(this.previewFileSrc);
const pdfDoc = await loadingTask.promise;
this.pageTotal = pdfDoc.numPages;
} catch (error) {
console.error("PDF加载错误:", error);
this.$message.error("PDF文件加载失败");
}
},
matchFileType(fileName) {
let suffix = "";
try {
suffix = fileName.split(".").pop().toLowerCase();
} catch (e) {
suffix = "";
}
const fileTypeList = [
{ typeName: "image", types: ["png", "jpg", "jpeg", "bmp", "gif"] },
{ typeName: "pdf", types: ["pdf"] },
{ typeName: "txt", types: ["txt"] },
{ typeName: "excel", types: ["xls", "xlsx"] },
{ typeName: "word", types: ["doc", "docx"] },
{ typeName: "video", types: ["mp4", "webm", "ogg", "mov", "avi"] },
{ typeName: "audio", types: ["mp3", "wav", "ogg", "aac"] },
];
for (const fileTypeItem of fileTypeList) {
if (fileTypeItem.types.includes(suffix)) {
return fileTypeItem.typeName;
}
}
return "other";
},
async fetchTextFile() {
try {
const response = await fetch(this.fileUrl);
this.textContent = await response.text();
} catch (error) {
console.error("加载文本文件失败:", error);
this.$message.error("文件加载失败");
}
},
async fetchExcelFile() {
try {
const response = await fetch(this.fileUrl);
const arrayBuffer = await response.arrayBuffer();
const workbook = XLSX.read(arrayBuffer, { type: "array" });
const firstSheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[firstSheetName];
this.excelData = XLSX.utils.sheet_to_json(worksheet);
} catch (error) {
console.error("加载 Excel 文件失败:", error);
this.$message.error("文件加载失败");
}
},
async fetchWordFile() {
try {
const response = await fetch(this.fileUrl);
const arrayBuffer = await response.arrayBuffer();
const result = await mammoth.extractRawText({ arrayBuffer });
this.wordContent = result.value;
} catch (error) {
console.error("加载 Word 文件失败:", error);
this.$message.error("文件加载失败");
}
},
getVideoType(url) {
let suffix = "";
try {
suffix = url.split(".").pop().toLowerCase();
} catch (e) {
suffix = "";
}
const videoTypes = {
mp4: "video/mp4",
webm: "video/webm",
ogg: "video/ogg",
mov: "video/quicktime",
avi: "video/x-msvideo",
};
return videoTypes[suffix] || "video/mp4";
},
getAudioType(url) {
let suffix = "";
try {
suffix = url.split(".").pop().toLowerCase();
} catch (e) {
suffix = "";
}
const audioTypes = {
mp3: "audio/mpeg",
wav: "audio/wav",
ogg: "audio/ogg",
aac: "audio/aac",
};
return audioTypes[suffix] || "audio/mpeg";
},
handleCancel() {
this.$emit("update:showDialog", false);
},
loadFileUrl(url) {
this.fileUrl = url;
},
},
};
</script>
<style lang="less" scoped>
/* 可以根据需要添加样式 */
</style>