客户端(浏览器)vue3本地预览txt,doc,docx,pptx,pdf,xlsx,csv,

预览文件

1、入口文件preview/index.vue

预览样式,如pdf

文件目录如图所示:

代码如下

javascript 复制代码
<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

javascript 复制代码
  // 跨窗口通信方法-读取文件
  readFileSend: (...args) => ipcRenderer.send("read-file", ...args),
  readFileReceive: (cb) => ipcRenderer.on('read-file', cb),

main.js

javascript 复制代码
// 通过文件的绝对路径获取文件流
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

javascript 复制代码
<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

javascript 复制代码
<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可在官网上下载

javascript 复制代码
<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>

5、预览pptx

6、预览xlsx

7、预览csv

相关推荐
JD技术委员会32 分钟前
Rust 语法噪音这么多,是否适合复杂项目?
开发语言·人工智能·rust
Hello.Reader35 分钟前
Rust 中的 `Drop` 特性:自动化资源清理的魔法
开发语言·rust·自动化
Vitalia39 分钟前
从零开始学 Rust:基本概念——变量、数据类型、函数、控制流
开发语言·后端·rust
小禾苗_2 小时前
C++ ——继承
开发语言·c++
李长渊哦2 小时前
Java 虚拟机(JVM)方法区详解
java·开发语言·jvm
进击ing小白2 小时前
Qt程序退出相关资源释放问题
开发语言·qt
烂蜻蜓3 小时前
前端已死?什么是前端
开发语言·前端·javascript·vue.js·uni-app
老猿讲编程3 小时前
安全C语言编码规范概述
c语言·开发语言·安全
Rowrey4 小时前
react+typescript,初始化与项目配置
javascript·react.js·typescript
Biomamba生信基地6 小时前
两天入门R语言,周末开讲
开发语言·r语言·生信