预览文件
- 1、入口文件preview/index.vue
- 2、预览txt
- 3、预览doc
- 4、预览pdf
- 5、预览pptx
- 6、预览xlsx
- 7、预览csv
1、入口文件preview/index.vue
预览样式,如pdf
文件目录如图所示:
代码如下
<template>
<div class="preview-wrap" ref="previewDom" v-if="hasPreviewFlag">
<DocxPreview v-if="fileType === 'docx' || fileType === 'doc'" :file-data="fileData" />
<PdfPreview v-else-if="fileType === 'pdf'" :file-data="fileData" />
<TxtPreview v-else-if="fileType === 'txt'" :file-data="fileData" />
<XlsxPreview v-else-if="fileType === 'xlsx'" :file-data="fileData" />
<PptxPreview v-else-if="fileType === 'pptx'" :file-data="fileData" :file-id="row.id"/>
<CsvPreview v-else-if="fileType === 'csv'" :file-data="fileData" file-type="csv" />
<div v-else>
<el-result
icon="error"
title="提示"
sub-title="文件不存在或者异常"
/>
</div>
</div>
</template>
<script setup>
import { ElMessage } from 'element-plus'
import DocxPreview from './components/DocxPreview.vue'
import PdfPreview from './components/PdfPreview.vue'
import TxtPreview from './components/TxtPreview.vue'
import XlsxPreview from './components/XlsxPreview.vue'
import PptxPreview from './components/PptxPreview.vue'
import CsvPreview from './components/CsvPreview.vue'
import { ref, onMounted, defineProps } from 'vue'
const props = defineProps({
row: {
type: Object,
default: () => {},
},
})
const filePath = ref('')
const fileType = ref('')
const fileData = ref(null)
const hasPreviewFlag = ref(true)
onMounted(() => {
// console.log('row', props.row)
const typeList = ['docx', 'doc', 'txt', 'pdf', 'xlsx', 'pptx', 'csv'] // 目前支持的类型
filePath.value = props.row.cached_file_path || ''
filePath.value = 'D:\\work\\test.txt' || props.row // 调试用
// filePath.value = 'D:\\work\\test.docx' || props.row
// filePath.value = 'D:\\work\\test.xlsx' || props.row
// filePath.value = 'D:\\work\\test.csv' || props.row
// filePath.value = 'D:\\work\\test.pdf' || props.row
// filePath.value = 'D:\\work\\test.pptx'
fileType.value = (filePath.value.split('.').pop() || props.row.file_type).toLowerCase()
if (!typeList.includes(fileType.value)) {
return ElMessage.error('此文件不支持预览')
}
if (typeList.includes(fileType.value)) {
hasPreviewFlag.value = true
window.electronAPI?.readFileSend(filePath.value) // 通过绝对路径获取文件流信息
} else {
hasPreviewFlag.value = false
}
})
// 这里根据客户端的主进程和渲染进行通过文件的绝对路径获取文件流
window.electronAPI?.readFileReceive((event, data) => {
// console.log('arraybuffer---->', data)
// 将arraybuffer转换成bolb形式
fileData.value = new Blob([data])
})
</script>
<style lang="scss">
.preview-wrap {
height: calc(100vh - 130px);
overflow: auto;
z-index: 1;
position: relative;
padding: 8px;
background: white;
border: 1px solid #ccc;
}
::-webkit-scrollbar {
width: 0 !important;
}
::-webkit-scrollbar {
width: 0 !important;
height: 0;
}
</style>
preload.js
// 跨窗口通信方法-读取文件
readFileSend: (...args) => ipcRenderer.send("read-file", ...args),
readFileReceive: (cb) => ipcRenderer.on('read-file', cb),
main.js
// 通过文件的绝对路径获取文件流
ipcMain.on('read-file', (event, filePath) => {
const currentWindow = BrowserWindow.fromWebContents(event.sender)
fs.readFile(filePath, (err, res) => {
if (err) {
console.log('read-file', err)
} else {
currentWindow && currentWindow.webContents.send('read-file', res)
}
})
})
2、预览txt
<template>
<div>
<pre class="txtViewer">{{ txtContent }}</pre>
</div>
</template>
<script setup>
import { ref, watch, toRefs, defineProps } from 'vue'
const props = defineProps({
fileData: {
type: Blob,
default: null
}
})
const { fileData } = toRefs(props)
const txtContent = ref('')
watch(
() => fileData.value,
(newv, oldv) => {
previewFile(newv)
}
)
const previewFile = fileData => {
const reader = new FileReader();
reader.onload = () => {
txtContent.value = reader.result;
};
reader.readAsText(fileData);
}
</script>
<style lang="scss" scoped>
.txtViewer {
width: 100%;
height: 100%;
overflow: auto;
margin-bottom: 0;
white-space: pre-wrap;
}
</style>
3、预览doc
采用docx-preview
<template>
<div>
<div id="docx-content-preview" class="docFile" v-show="!loading"></div>
</div>
</template>
<script setup>
import { renderAsync } from 'docx-preview'
import { ref, defineProps, watch, toRefs } from 'vue'
const loading = ref(true)
const props = defineProps({
fileData: {
type: Object,
default: () => {},
},
})
const { fileData } = toRefs(props);
watch(
() => fileData.value,
val => {
loading.value = true
previewfile(val)
},
{ deep: true }
)
function previewfile(fileData) {
// 选择要渲染的元素
const docFile = document.getElementsByClassName('docFile')
// const blob = new Blob([fileData])
// 用docx-preview渲染
renderAsync(fileData, docFile[0]).then(res => {
console.log('res---->', res)
loading.value = false
})
}
</script>
<style lang="scss" scoped>
#docx-content-preview {
min-height: 200px;
overflow-x: hidden;
padding: 10px;
}
.docFile {
:deep(.docx-wrapper) {
background: white;
}
:deep(section) {
width: 100% !important;
box-shadow: none;
}
}
:deep(.docx-wrapper > section.docx) {
width: 100% !important;
padding: 0rem !important;
min-height: auto !important;
box-shadow: none;
margin-bottom: 0;
article {
overflow: auto;
}
}
</style>
4、预览pdf
实现的方式有多种,可采用pdfjs-dist、或
pdf.worker.min.mj可在官网上下载
<template>
<div style="position:relative;">
<div style="text-align: center; position: relative" ref="pdfViewer" class="pdfViewer"></div>
</div>
</template>
<script setup>
import * as pdfjsLib from 'pdfjs-dist'
import { ref, watch, toRefs } from 'vue'
pdfjsLib.GlobalWorkerOptions.workerSrc = "./pdf.worker.min.mjs";
const props = defineProps({
fileData: {
type: Blob,
default: null
}
})
const { fileData } = toRefs(props);
const pdfViewer = ref()
const loading = ref(true)
const allParagraphs = ref([])
const canvasHistory = ref({})
// const scale = 1.5
const scale = window.devicePixelRatio > 1.3 ? 1 : 1.5
watch(
() => fileData.value,
async (newVal, oldVal) => {
clearRec();
previewFile(newVal)
},
{ deep: true }
)
const previewFile = (fileData) => {
getUint8ArrayData(fileData).then((uint8Array) => {
renderPdf(uint8Array)
})
}
const getUint8ArrayData = (blob) => {
return new Promise((resolve, reject) => {
// 使用FileReader读取Blob对象
const fileReader = new FileReader()
fileReader.onload = function () {
// 读取完成后,result属性将包含Uint8Array数据
const uint8Array = new Uint8Array(fileReader.result)
resolve(uint8Array)
}
fileReader.onerror = function () {
console.error('读取Blob时发生错误')
reject(new Error('读取Blob时发生错误'))
}
// 开始读取Blob
fileReader.readAsArrayBuffer(blob)
})
}
const renderPdf = async (uint8Array) => {
const loadingTask = pdfjsLib.getDocument(uint8Array)
const pdf = await loadingTask.promise
for (let i = 1; i <= pdf.numPages; i++) {
await renderPage(pdf, i)
}
loading.value = false;
}
const renderPage = async (pdf, pageNumber) => {
// 构造canvas
const canvas = document.createElement('canvas')
canvas.style = 'direction: ltr;position: relative'
canvas.id = `canvas_page_${pageNumber}`
canvas.className = `canvas_page`
// 构造文本层
const layerDiv = document.createElement('div')
layerDiv.style =
'position: absolute;left: 0;top: 0;right: 0;bottom: 0;overflow: hidden;opacity: 0.4;line-height: 1.0;'
layerDiv.id = `canvas_layer_page_${pageNumber}`
layerDiv.className = 'canvas-layer'
const div = document.createElement('div')
div.style = 'width: fit-content;margin: 0 auto;position:relative; border: 1px solid #ccc; margin-bottom: 8px;'
div.id = `page-container-${pageNumber}`
div.className = `page-container`
div.appendChild(canvas)
// div.appendChild(layerDiv)
pdfViewer.value.appendChild(div)
const page = await pdf.getPage(pageNumber)
const viewport = page.getViewport({ scale })
// Support HiDPI-screens.
const outputScale = window.devicePixelRatio || 1
// Prepare canvas using PDF page dimensions
const context = canvas.getContext('2d')
canvas.width = Math.floor(viewport.width * outputScale)
canvas.height = Math.floor(viewport.height * outputScale)
canvas.style.width = Math.floor(viewport.width) + 'px'
canvas.style.height = Math.floor(viewport.height) + 'px'
canvas.style.maxWidth = 1200 + 'px' // 解决pdf文档宽度过长导致样式错乱
const transform =
outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null
// Render PDF page into canvas context
const renderContext = {
canvasContext: context,
transform,
viewport,
}
await page.render(renderContext).promise
// 存入画布渲染pdf的状态,用于还原
canvasHistory.value[pageNumber] = canvas.toDataURL()
const textContent = await page.getTextContent()
// Pass the data to the method for rendering of text over the pdf canvas.
// const task = pdfjsLib.renderTextLayer({
// textContentSource: page.streamTextContent(),
// container: layerDiv,
// viewport,
// textDivs: [],
// })
// await task.promise
// 处理得到每页的paragraph
const pageParagraphs = handleParagraphs(
pageNumber,
canvas,
viewport,
textContent.items
)
await clearRec()
allParagraphs.value = allParagraphs.value.concat(pageParagraphs)
}
const handleParagraphs = (pageNumber, canvas, viewport, textContent) => {
const newArr = groupByTransform(
pageNumber,
(textContent || []).filter((item) => !!item.str.trim())
)
const paragraphs = newArr.map((item) => {
return {
pageNumber,
canvas,
viewport,
content: item
.map((it) => it.str)
.reduce((a, b) => {
return a + b
}, ''),
items: item,
}
})
return paragraphs
}
const groupByTransform = (pageNumber, array) => {
const result = []
const map = new Map()
array.forEach((obj) => {
const key = obj.transform[5]
if (!map.has(key)) {
map.set(key, [obj])
} else {
map.get(key).push(obj)
}
})
map.forEach((value) => {
result.push(value)
})
return result
}
const clearRec = async () => {
for (const key in canvasHistory.value) {
const canvas = document.getElementById(`canvas_page_${key}`)
if (canvas) {
const context = canvas.getContext("2d");
const canvasPic = await loadPdfImage(key)
context.drawImage(canvasPic, 0, 0);
} else {
console.log('获取节点失败', `canvas_page_${key}`)
}
}
}
const loadPdfImage = (index) => {
return new Promise((resolve, reject) => {
const canvasPic = new Image();
canvasPic.src = canvasHistory.value[index]
canvasPic.onload = () => {
// 当图像加载完成后进行resolve,确保drawImage执行成功
resolve(canvasPic)
}
canvasPic.onerror = () => {
reject(new Error('加载还原图像失败'))
}
})
}
</script>
<style lang="scss" scoped>
.pdfViewer {
min-height: 100%;
:deep(.canvas-layer > span) {
color: transparent;
position: absolute;
white-space: pre;
cursor: text;
transform-origin: 0% 0%;
background: transparent;
z-index: 1;
}
:deep(.page-container) {
width: 100% !important;
.canvas_page {
width: 100% !important;
height: 100% !important;
}
}
}
</style>