純靜態(tài)網(wǎng)站制作seo整站優(yōu)化報價
實際開發(fā)中,常見pdf|word|excel等文件的預(yù)覽和下載
- 背景
- 相關(guān)類型數(shù)據(jù)之間的轉(zhuǎn)換
- 1、File轉(zhuǎn)Blob
- 2、File轉(zhuǎn)ArrayBuffer
- 3、Blob轉(zhuǎn)ArrayBuffer
- 4、Blob轉(zhuǎn)File
- 5、ArrayBuffer轉(zhuǎn)Blob
- 6、ArrayBuffer轉(zhuǎn)File
- 根據(jù)Blob/File類型生成可預(yù)覽的Base64地址
- 基于Blob類型的各種文件的下載
- 各種類型文件的預(yù)覽及其效果
- 1、當(dāng)前使用的node版本
- 2、 業(yè)務(wù)場景
- 3、圖片類型預(yù)覽
- 3.1、安裝依賴
- 3.2、ImagePreview.vue
- 3.3、效果
- 4、Excel文件的預(yù)覽
- 4.1、依賴安裝
- 4.2、ExcelPreview.vue
- 4.3、預(yù)覽效果
- 5、word文件的預(yù)覽
- 5.1、依賴安裝
- 5.2、WordPreview.vue
- 5.3、預(yù)覽效果
- 6、pdf文件的預(yù)覽
- 6.1、依賴安裝
- 6.2、PdfPreview.vue
- 6.3、預(yù)覽效果
- 7、json/xml文件的預(yù)覽
- 7.1、依賴安裝
- 7.2、全局引入
- 7.3、JsonViewer組件的使用
- 7.4、預(yù)覽效果
- 8、bim文件的預(yù)覽
- 8.1、依賴安裝
- 8.2、GeoBimPreview.vue
- 8.3、預(yù)覽效果
背景
實際開發(fā)中,大部分文件的預(yù)覽會以流的方式傳輸,前端通過Element等UI庫提供的上傳組件傳給后端
File
類型數(shù)據(jù), 后端返回給前端Blob
/ArrayBuffer
類型數(shù)據(jù) , 前端最終借助各種第三方工具或者自定義tool
方法, 實現(xiàn)各種類型文件的下載或者預(yù)覽. 少部分的會以文件地址的方式進(jìn)行傳輸, 那么我們直接訪問那個文件url即可.
相關(guān)類型數(shù)據(jù)之間的轉(zhuǎn)換
1、File轉(zhuǎn)Blob
export function fileToBlob(file: File) {return new Promise((resolve, reject) => {const reader = new FileReader();reader.onload = () => {const arrayBuffer: any = reader.result;const blob = new Blob([arrayBuffer], { type: file.type });resolve(blob);};reader.onerror = reject;reader.readAsArrayBuffer(file);});
}
2、File轉(zhuǎn)ArrayBuffer
export function fileToArrayBuffer(file: File) {return new Promise((resolve, reject) => {const reader = new FileReader();reader.onload = () => {const arrayBuffer: any = reader.result;resolve(arrayBuffer);};reader.onerror = reject;reader.readAsArrayBuffer(file);});
}
3、Blob轉(zhuǎn)ArrayBuffer
export function blobToArrayBuffer(blob) {return new Promise((resolve, reject) => {const reader = new FileReader();reader.onload = () => resolve(reader.result);reader.onerror = reject;reader.readAsArrayBuffer(blob);});
}
4、Blob轉(zhuǎn)File
export function blobToFile(blob, fileName, fileType) {return new File([blob], fileName, { type: fileType })
}
5、ArrayBuffer轉(zhuǎn)Blob
export function arrayBufferToBlob(arrayBuffer, blobType = 'application/octet-stream') {const blob = new Blob([arrayBuffer], { type: blobType });return blob;
}
6、ArrayBuffer轉(zhuǎn)File
export function arrayBufferToFile(arrayBuffer, fileName, fileType = 'text/plain') {const file= new File([arrayBuffer], fileName, { type: fileType });return file;
}
根據(jù)Blob/File類型生成可預(yù)覽的Base64地址
有些第三方預(yù)覽工具不識別Blob/File, 如
viewerjs
、v-viewer
預(yù)覽圖片的時候,是需要圖片對應(yīng)的src的,而不是Blob/File
export function createUrlByBlobOrFile(data: any) {return new Promise((resolve, reject) => {const reader = new FileReader();reader.onload = () => {resolve(reader.result);};reader.onerror = reject;reader.readAsDataURL(data);});
}
基于Blob類型的各種文件的下載
下載的文件響應(yīng)類型可打印FIle/Blob對象查看,可執(zhí)行:
downloadFileUtil(fileBlob, fileBlob.type, fileBlob.fileName)
export function downloadFileUtil(data: Blob, responseType: string, fileName: any = new Date().valueOf()) {const blob = new Blob([data], { type: responseType });// 創(chuàng)建一個<a></a>標(biāo)簽let a: HTMLAnchorElement | null = document.createElement('a');const blobUrl = window.URL.createObjectURL(blob);a.href = blobUrl;a.download = fileName;a.style.display = 'none';document.body.appendChild(a);a.click();a.remove();// 釋放createObjectURL創(chuàng)建的資源window.URL.revokeObjectURL(blobUrl);
}
各種類型文件的預(yù)覽及其效果
個別預(yù)覽的第三方插件庫,需要使用特定的某些版本,當(dāng)前指定的版本庫都是可用的。
1、當(dāng)前使用的node版本
2、 業(yè)務(wù)場景
- 用戶通過上傳組件上傳附件
用戶從本地上傳的附件拿到的類型是
File
, 保存之后, 拿到的就是文件列表項對應(yīng)的Blob
類型。
3、圖片類型預(yù)覽
圖片類型預(yù)覽使用的是
v-viewer
和viewerjs
, 可支持的預(yù)覽圖片類型有:jpg
,jpeg
,png
,gif
3.1、安裝依賴
yarn add v-viewer@^3.0.21 viewerjs@^1.11.7
3.2、ImagePreview.vue
v-viewer
和viewerjs
可以通過指令、組件和api三種方式實現(xiàn)預(yù)覽。 實際開發(fā)中,基本上都是使用的是Blob
類型,Blob
類型轉(zhuǎn)換為Base64
地址后, 是不能通過import { api as viewerApi } from 'v-viewer';
的方式預(yù)覽的,盡管api的方式很簡單,但它貌似只是支持本地文件URL/服務(wù)器文件URL。
通過使用viewer組件,借助img標(biāo)簽可以識別Base64圖片路徑,從而通過點擊img列表,實現(xiàn)圖片預(yù)覽
<template><div class="image-preview"><viewer :images="props.images" class="v-viewer"><imgv-for="(imgItem, index) in props.images":key="index"class="view-img-item":src="imgItem.url":alt="imgItem.name":title="imgItem.name"/></viewer><div class="auto-close-preview-com"><Close class="close-icon" @click="closeImgPreviewFn" /></div></div>
</template><script lang="ts" setup>
import 'viewerjs/dist/viewer.css';
import { component as Viewer } from 'v-viewer';
import { onMounted } from 'vue';
import { ElMessage } from 'element-plus';const props = defineProps({images: {type: Array as any, // images存儲的是Blob轉(zhuǎn)成Base64的數(shù)組,類型轉(zhuǎn)換上文createUrlByBlobOrFile可實現(xiàn)default: () => [],},
});
const emits = defineEmits(['closeImgPreview']);function closeImgPreviewFn() {emits('closeImgPreview');
}
onMounted(() => {ElMessage.info('點擊圖片列表可預(yù)覽~');
});
</script><style lang="css" scoped>
.image-preview {position: fixed;left: 0;top: 0;right: 0;bottom: 0;z-index: 9998;background-color: rgb(0 0 0 / 70%);.v-viewer {width: 100%;height: 100%;.view-img-item {width: 250px;height: 250px;margin-right: 20px;}}.auto-close-preview-com {position: absolute;-webkit-app-region: no-drag;background-color: rgb(0 0 0 / 50%);border-radius: 50%;cursor: pointer;height: 80px;overflow: hidden;right: -40px;top: -40px;transition: background-color 0.15s;width: 80px;color: #ffffff;.close-icon {bottom: 15px;left: 15px;position: absolute;background-position: -260px 0;font-size: 0;height: 20px;line-height: 0;width: 20px;}}
}
</style>
3.3、效果
4、Excel文件的預(yù)覽
Excel文件預(yù)覽使用的是
xlsx
插件庫, 可支持類型有:xls
,xlsx
4.1、依賴安裝
yarn add xlsx@^0.18.5
4.2、ExcelPreview.vue
<template><div class="xlsx-preview-box"></div>
</template><script lang="ts" setup>
import { onMounted } from 'vue';
// XLSX: 無法預(yù)覽docx文件, 預(yù)覽pdf也會亂碼 只能預(yù)覽xlsx文件
import * as XLSX from 'xlsx';const props = defineProps({fileBlob: {type: Blob,default: () => null,},
});onMounted(() => {if (props.fileBlob) {const reader = new FileReader();// 通過readAsArrayBuffer將blob轉(zhuǎn)換為ArrayBufferreader.readAsArrayBuffer(props.fileBlob);reader.onload = (event: any) => {// 讀取ArrayBuffer數(shù)據(jù)變成Uint8Arrayconst data = new Uint8Array(event.target.result);// 這里的data里面的類型和后面的type類型要對應(yīng)const workbook = XLSX.read(data, { type: 'array' });const sheetNames = workbook.SheetNames; // 工作表名稱const worksheet = workbook.Sheets[sheetNames[0]];const html = XLSX.utils.sheet_to_html(worksheet);document.getElementsByClassName('xlsx-preview-box')[0].innerHTML = html;};}
});
</script><style lang="css">
.xlsx-preview-box {width: 100%;height: 100%;overflow: auto;table {width: 100%;border-spacing: 0;tr {height: 40px;font-size: 14px;color: #666666;line-height: 14px;font-weight: 400;}tr:first-child {background-color: #ececec !important;height: 60px;font-size: 16px;color: #666666;font-weight: 700;}td {min-width: 80px;text-align: center;border: 1px solid #cccccc;}tr:nth-child(2n) {background-color: #fafafa;}tr:nth-child(2n + 1) {background-color: #ffffff;}}
}
</style>
4.3、預(yù)覽效果
5、word文件的預(yù)覽
word文件預(yù)覽使用的是
docx-preview
插件庫, 可支持類型有:doc
,docx
。
5.1、依賴安裝
yarn add docx-preview@0.3.0
docx-preview 需要是0.3.0版本,最新的0.3.3版本會報docx-preview類型錯誤。且最新的版本解析的blob文件類型和0.3.0版本不一致,最新版本還會預(yù)覽失敗:報(Can’t find end of central directory : is this a zip file ? If it is, see)。
5.2、WordPreview.vue
<template><div ref="wordPreviewRef" class="word-preview"></div>
</template><script lang="ts" setup>
import { ref, nextTick } from 'vue';
// docx-preview 需要是0.3.0版本,最新的0.3.3版本會報docx-preview類型錯誤
// 且最新的版本解析的blob類型和0.3.0版本不一致
// 最新版本還會預(yù)覽失敗:報(Can't find end of central directory : is this a zip file ? If it is, see)
import { renderAsync } from 'docx-preview';const props = defineProps<{wordBlob: any;
}>();const wordPreviewRef = ref({});nextTick(() => {renderAsync(props.wordBlob, // blob 的type: application/vnd.openxmlformats-officedocument.wordprocessingml.documentwordPreviewRef.value as HTMLElement, // HTMLElement 渲染文檔內(nèi)容的元素,);
});
</script><style lang="scss" scoped>
.word-preview {width: 100%;height: 100%;overflow: auto;
}
</style>
5.3、預(yù)覽效果
6、pdf文件的預(yù)覽
pdf文件預(yù)覽使用的是
pdfjs-dist
插件庫, 可支持類型有:
6.1、依賴安裝
yarn add pdfjs-dist@2.16.105
pdfjs-dist 底層是pdfjs。不建議使用打包后的mjs類型的版本包。因為不支持線上環(huán)境對GlobalWorkerOptions.workerSrc
的支持。具體的是:本地可以引入node_module路徑,但是正式環(huán)境沒這個路徑;如果把對應(yīng)的pdf.worker.min.mjs
放到assets下,會報錯:Failed to resolve module specifier '@/assets/pdfjs/pdf.worker.min.mjs
; 如果放到public下,會報錯Failed to load module script
, public目錄文件不會被編譯,瀏覽器無法識別mjs文件
6.2、PdfPreview.vue
<template><div class="pdf-preview"><!-- block: 避免一個視圖顯示多個canvas頁 --><canvasv-for="pageIndex in pdfPages":id="`pdf-canvas-` + pageIndex"ref="pdfPreviewRef":key="pageIndex"style="display: block"></canvas></div>
</template><script lang="ts" setup>
import { ref, onMounted, nextTick, reactive } from 'vue';// import 'pdfjs-dist/web/pdf_viewer.css';
// 4.5.136版本
// import * as pdfjsLib from 'pdfjs-dist'; // /legacy/build/pdf.js
// import * as pdfjsViewer from 'pdfjs-dist/web/pdf_viewer.js';
import 'pdfjs-dist/web/pdf_viewer.css';
import * as pdfjsLib from 'pdfjs-dist';
import { blobToArrayBuffer } from '@/utils/tools';const props = defineProps<{pdfBlob: any;
}>();const pdfPreviewRef = ref({});
// pdf頁數(shù)
const pdfPages = ref(0);
// pdf縮放比例
const pdfScale = ref(2.5); // 可以控制canvas的寬高
// pdf文檔流,
// 這個不能使用ref,使用ref會報錯: Cannot read from private field
let pdfDoc = reactive<any>({});const renderPdf = (num) => {pdfDoc.getPage(num).then((page) => {const canvasId = `pdf-canvas-${num}`;const canvas: any = document.getElementById(canvasId);const ctx = canvas?.getContext('2d');const dpr = window.devicePixelRatio || 1;const bsr =ctx.webkitBackingStorePixelRatio ||ctx.mozBackingStorePixelRatio ||ctx.msBackingStorePixelRatio ||ctx.oBackingStorePixelRatio ||ctx.backingStorePixelRatio ||1;const ratio = dpr / bsr;const viewport = page.getViewport({ scale: pdfScale.value });canvas.width = viewport.width * ratio;canvas.height = viewport.height * ratio;canvas.style.width = `${viewport.width}px`;canvas.style.height = `${viewport.height}px`;ctx.setTransform(ratio, 0, 0, ratio, 0, 0);const renderContext = {canvasContext: ctx,viewport: viewport,};page.render(renderContext);if (num < pdfPages.value) {renderPdf(num + 1);}});
};// 獲取pdf文檔流與pdf文件的頁數(shù)
const loadFile = async () => {// string | URL | TypedArray | ArrayBuffer | DocumentInitParametersconst pdfArrayBuffer: any = await blobToArrayBuffer(props.pdfBlob);const loadingTask = pdfjsLib.getDocument(pdfArrayBuffer);loadingTask.promise.then((pdf) => {pdfDoc = pdf; // 獲取pdf文檔流pdfPages.value = pdf.numPages; // 獲取pdf文件的頁數(shù)nextTick(() => {renderPdf(1);});});
};onMounted(async () => {// 正式環(huán)境找不到node_modules// pdfjsLib.GlobalWorkerOptions.workerSrc =// '../../../node_modules/pdfjs-dist/build/pdf.worker.min.mjs';// 放在assets下: Failed to resolve module specifier '@/assets/pdfjs/pdf.worker.min.mjs// pdfjsLib.GlobalWorkerOptions.workerSrc = '@/assets/pdfjs/pdf.worker.min.mjs';// const baseurl = window.location.origin + window.location.pathname; // 本地路徑// ${baseurl}pdfjs/pdf.worker.min.mjs 靜態(tài)服務(wù)訪問的返回的是流// pdfjsLib.GlobalWorkerOptions.workerSrc = `${baseurl}pdfjs/pdf.worker.min.mjs`; // Failed to load module script// public/pdfjs/pdf.worker.js: 將'../../../node_modules/pdfjs-dist/build/pdf.worker.js';復(fù)制到public目錄下pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'; // “pdfjs/”不能寫成“/pdfjs/”, 前者是相對路徑, 后者是絕對路徑(相對線上環(huán)境服務(wù)器)loadFile();
});
</script>
<style lang="scss" scoped>
.pdf-preview {width: 100%;height: 100%;overflow: auto;
}
</style>
6.3、預(yù)覽效果
7、json/xml文件的預(yù)覽
vue-json-viewer
支持json
和xml
文件的預(yù)覽
7.1、依賴安裝
yarn add vue-json-viewer@^3.0.4
7.2、全局引入
7.3、JsonViewer組件的使用
fileData存儲的是后端接口返回的json字符串
<json-viewer v-else-if="preState.fileType === 'Json'" :value="preState.fileData" />
7.4、預(yù)覽效果
8、bim文件的預(yù)覽
geobim
文件的預(yù)覽使用的是@xbim/viewer
插件庫,當(dāng)前使用的方式支持Blob
和Url
兩種方式
8.1、依賴安裝
yarn add @xbim/viewer@^2.1.0-pre202305041434
8.2、GeoBimPreview.vue
該組件接收的是url, 但是loadGeoBim處理兼容了Blob
<template><canvas id="bim-canvas" style="width: 100%; height: 100%"></canvas>
</template><script lang="ts" setup>
import { watch, nextTick } from 'vue';
import { Grid, NavigationCube, Viewer, ViewType } from '@xbim/viewer';const props = defineProps({dwgUrl: {type: String,default: () => '',},
});
let viewer;
const setViewerOptions = () => {viewer.background = [26, 51, 76, 255];viewer.highlightingColour = [0, 0, 225, 200];viewer.brightness = -0.5;viewer.hoverPickColour = [0, 0, 225, 200];
};
const setViewerPlugin = () => {const cube = new NavigationCube();cube.ratio = 0.05;// eslint-disable-next-line no-multi-assigncube.passiveAlpha = cube.activeAlpha = 0.85;viewer.addPlugin(new Grid());viewer.addPlugin(cube);
};
const token = localStorage.getItem('TOKEN') as string;
const headers = {Authorization: `Bearer ${JSON.parse(token).access_token}`,
};
const loadGeoBim = (dwgUrl) => {const check = Viewer.check();if (check.noErrors) {nextTick(() => {viewer = new Viewer('bim-canvas');setViewerOptions();setViewerPlugin();viewer.on('loaded', function () {viewer.show(ViewType.DEFAULT, undefined, undefined, false);viewer.start();});// 前置管理、任務(wù)管理、數(shù)據(jù)管理里訪問的數(shù)據(jù)是四庫的后端接口返回的文件流,服務(wù)管理里訪問的是可視化系統(tǒng)后臺接口返回的文件地址// node_modules\.vite\deps\@xbim_viewer.js 修復(fù)bim的左右鍵fetch(dwgUrl, { headers }).then((responce) => responce.arrayBuffer()).then((arrayBuffer) => {const blob = new Blob([arrayBuffer], { type: 'application/octet-stream' });viewer.load(blob);}).catch((err) => {viewer.load(dwgUrl);});});}
};
watch(() => props.dwgUrl,(dwgUrl) => {loadGeoBim(dwgUrl);},{immediate: true,deep: true,},
);
</script>