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

news2024/12/17 2:21:16

预览文件

  • 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>

5、预览pptx

6、预览xlsx

7、预览csv

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2260810.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

安卓低功耗蓝牙BLE官方开发例程(JAVA)翻译注释版

官方原文链接 https://developer.android.com/develop/connectivity/bluetooth/ble/ble-overview?hlzh-cn 目录 低功耗蓝牙 基础知识 关键术语和概念 角色和职责 查找 BLE 设备 连接到 GATT 服务器 设置绑定服务 设置 BluetoothAdapter 连接到设备 声明 GATT 回…

uniapp打包apk允许横屏竖屏内容翻转

文章目录 一、教程总结 一、教程 1.添加配置 "orientation": [//竖屏正方向"portrait-primary",//竖屏反方向"portrait-secondary",//横屏正方向"landscape-primary",//横屏反方向"landscape-secondary",//自然方向"…

ElasticSearch 常见故障解析与修复秘籍

文章目录 一、ElasticSearch启动服务提示无法使用root用户二、ElasticSearch启动提示进程可拥有的虚拟内存少三、ElasticSearch提示用户拥有的可创建文件描述符太少四、ElasticSearch集群yellow状态分析五、ElasticSearch节点磁盘使用率过高&#xff0c;read_only状态问题解决六…

Java——网络编程(上)

1 计算机网络 (作用资源共享和信息传递) (计算机网络组成——> 硬件——>计算机设备&#xff0c;外部设备&#xff0c;通信线路 软件——>网络操作系统&#xff0c;网络管理软件&#xff0c;网络通信协议) 计算机网络是指将地理位置不同的具有独立功能的多台计算机…

游戏引擎学习第50天

仓库: https://gitee.com/mrxiao_com/2d_game Minkowski 这个算法有点懵逼 回顾 基本上&#xff0c;现在我们所处的阶段是&#xff0c;回顾最初的代码&#xff0c;我们正在讨论我们希望在引擎中实现的所有功能。我们正在做的版本是初步的、粗略的版本&#xff0c;涵盖我们认…

Unix 和 Windows 的有趣比较

Unix 和 Windows NT 比较 来源于这两本书&#xff0c;把两本书对照来读&#xff0c;发现很多有意思的地方&#xff1a; 《Unix 传奇》 https://book.douban.com/subject/35292726/ 《观止 微软创建NT和未来的夺命狂奔 》 Showstopper!: The Breakneck Race to Create Windows…

攻防世界逆向刷题笔记(新手模式6-?)

6.1000clicks 看题目名字似乎是让咱们点击1000次之后才会出flag。本来打算用CE看能不能搜索出来数值&#xff0c;技术不到家&#xff0c;最后没有搜索到&#xff0c;还导致永劫无间打不了了。所以还是拿出IDA老实分析。 直接搜索flag字符&#xff0c;出来一大堆。张紫涵大佬说…

ANOMALY BERT 解读

出处&#xff1a; ICLR workshop 2023 代码&#xff1a;Jhryu30/AnomalyBERT 可视化效果&#xff1a; 一 提出动机 动机&#xff1a;无监督 TSAD 领域内&#xff0c;“训练集” 也缺失&#xff1a;真值标签&#xff08;GT&#xff09;&#xff1b;换句话说&#xff0c;一个…

Java——网络编程(中)—TCP通讯(下)

1 双向通讯—创建服务端 (双向通信是指通信双方中&#xff0c;任何一方都可为发送端&#xff0c;任何一方都可为接收端) (1 创建ServerSocket对象&#xff0c;accept()返回socket) (2 双向通讯——>也要创建键盘输入对象) (3 通过与客户端对应的Socket对象获取输入流对象…

JavaFX使用jfoenix的UI控件

jfoenix还是一个不错的样式&#xff0c;推荐使用&#xff0c;而且也可以支持scene builder中的拖拖拽拽 需要注意的是过高的javafx版本可能会使得某些样式或控件无法使用 比如alert控件&#xff0c;亲测javaFX 19版本可以正常使用 1.在pom.xml中引入依赖 GitHub地址https://gi…

利用cnocr库完成中文扫描pdf文件的文字识别

很多pdf文件文字识别软件都会收费&#xff0c;免费的网页版可能会带来信息泄露&#xff0c;还有一些类似于腾讯AI和百度AI的接口都有调用次数限制&#xff0c;因此&#xff0c;利用识别正确率极高且免费的cnocr库来自己动手做个pdf文件文字识别程序就是一个很不错的选择。以下程…

大数据笔记之flink-cdc实时同步数据

大数据笔记之flink-cdc实时同步数据(mysql -->doris) 一、基本概念 Flink CDC 是一个基于流的数据集成工具&#xff0c;旨在为用户提供一套功能更加全面的编程接口&#xff08;API&#xff09;。 该工具使得用户能够以 YAML配置文件的形式&#xff0c;优雅地定义其 ETL&…

【数学】矩阵的逆与伪逆 EEGLAB

文章目录 前言matlab代码作用EEGLAB 中的代码总结参考文献 前言 在 EEGLAB 的使用中&#xff0c;运行程序时出现了矩阵接近奇异值&#xff0c;或者缩放错误。结果可能不准确。RCOND 1.873732e-20 的 bug&#xff0c;调查 EEGLAB 后发现是 raw 数据的问题。 matlab代码 A_1 …

RTMP推流平台EasyDSS在无人机推流直播安防监控中的创新应用

无人机与低空经济的关系密切&#xff0c;并且正在快速发展。2024年中国低空经济行业市场规模达到5800亿元&#xff0c;其中低空制造产业占整个低空经济产业的88%。预计未来五年复合增速将达到16.03%。 随着科技的飞速发展&#xff0c;公共安防关乎每一个市民的生命财产安全。在…

【记录49】vue2 vue-office在线预览 docx、pdf、excel文档

vue2 在线预览 docx、pdf、excel文档 docx npm install vue-office/docx vue-demi0.14.6 指定版本 npm install vue-office/docx vue-demi <template><VueOfficeDocx :src"pdf" style"height: 100vh;" rendere"rendereHandler" error&…

C# 探险之旅:第二十四节 - 类型class基础,一场“类”似的奇妙冒险

嘿&#xff0c;勇敢的探险家们&#xff01;欢迎来到C#王国的“类”似奇妙冒险&#xff01;今天&#xff0c;我们要深入探索一个神秘而强大的领域——class&#xff08;类&#xff09;。想象一下&#xff0c;class就像C#世界里的一块魔法土地&#xff0c;每块土地上都能孕育出独…

Burp suite 3 (泷羽sec)

声明 学习视频来自B站UP主 泷羽sec,如涉及侵泷羽sec权马上删除文章。 笔记只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都与本人无关,切莫逾越法律红线,否则后果自负 这节课旨在扩大自己在网络安全方面的知识面&#xff0c;了解网络安全领域的见闻&#xff0c;了…

cpptoml介绍

cpptoml 是一个用于 C 的开源库&#xff0c;旨在提供对 TOML&#xff08;Toms Obvious, Minimal Language&#xff09;格式的支持。它允许开发者轻松地在 C 项目中读取、解析和生成 TOML 格式的配置文件。cpptoml 是一个轻量级、易于使用的库&#xff0c;适用于那些希望将 TOML…

用户认证系统登录界面

下面是使用HTML和JavaScript实现的一个中文版登录界面&#xff0c;包含登录、注册和修改密码功能。注册成功后会显示提示信息&#xff0c;在登录成功后进入一个大大的欢迎页面。 1.代码展示 <!DOCTYPE html> <html lang"zh-CN"> <head><meta …

Pyside6 --Qt设计师--简单了解各个控件的作用之:Item Views

目录 一、List View二、Tree View三、Table View四、Column View 一、List View 学习方法和Buttons一样&#xff0c;大家自己在qt设计师上面在属性编辑区进行相应的学习&#xff01; 我就先紧着qt设计师的页面进行讲解&#xff0c;部分内容查自AI。 后面有什么好用的控件或者…