vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > vue中的ofd/pdf预览

vue中的ofd/pdf预览实现过程

作者:Qredsun

这篇文章主要介绍了vue中的ofd/pdf预览实现过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

背景

实现预览ofd/pdf超链接功能

业务实现

1.pdf的预览

实现方式:

1.直接使用 <iframe :src="${url}#navpanes=0&toolbar=0" /> 实现pdf的预览。

2.使用pdf.js,代码先行:

<template>
  <a-tabs
    v-if="props.urls.length > 0"
    :default-active-key="activateTab"
    type="card"
    class="pdf-tabs"
    @change="tabChangeHandler"
  >
    <a-tab-pane v-for="url in props.urls" :key="url" :tab="fileName(url)">
      <div class="pdf-container">
        <canvas
          v-if="url.endsWith('.pdf')"
          class="canvas"
          :ref="(el) => (canvasRefs[url] = el)"
        ></canvas>
        <a-button class="mb-2" type="link" @click="handleDownload(url)">
          {{ fileName(url) }}
        </a-button>
      </div>
    </a-tab-pane>
  </a-tabs>
</template>

<script lang="ts" setup>
import { ref, watch, nextTick } from 'vue'
import * as pdfjsLib from 'pdfjs-dist'
import { debounce } from 'lodash-es'
import { saveAs } from 'file-saver'
import EasyOFD from 'easyofd'

interface Props {
  urls?: string[]
}

const props = withDefaults(defineProps<Props>(), {
  urls: () => [],
})
const url = ref<string>('')
const activateTab = ref<string>('')
const canvasRefs = ref<Record<string, HTMLCanvasElement | null>>({})

// 文件类型判断
const ext = ref<string>('pdf')
const isOfd = ref<boolean>(false)
const isPdf = ref<boolean>(false)

//  设置 PDF.js worker 路径(推荐方式)
pdfjsLib.GlobalWorkerOptions.workerSrc = '/pdf.worker.min.mjs'

//  从 URL 中提取文件名
function fileName(url: string): string {
  try {
    const decodeURL = decodeURIComponent(url).split('/')
    const lastSegment = decodeURL[decodeURL.length - 1]

    const firstIndex = lastSegment.indexOf('-')
    const lastIndex = lastSegment.lastIndexOf('-')

    if (firstIndex === -1 || lastIndex === -1 || lastIndex <= firstIndex) {
      return lastSegment.split('.')[0] // fallback 文件名
    }

    const name = lastSegment.substring(firstIndex + 1, lastIndex)
    const ext = name.split('.').pop()

    if (['pdf', 'ofd'].includes(ext ?? '')) {
      return name.substring(0, name.lastIndexOf('.'))
    }

    return name
  } catch {
    return 'unknown'
  }
}

// 获取文件类型
const getFileType = (url: string) => {
  const decodeURL = decodeURIComponent(url)
  ext.value = decodeURL.endsWith('.pdf') ? 'pdf' : 'ofd'
  isPdf.value = ext.value === 'pdf'
  isOfd.value = ext.value === 'ofd'
  isPdf.value ? loadAndRenderPdf(url) : loadAndRenderOfd(url)
}
//  下载文件
const handleDownload = debounce((url: string) => {
  saveAs(url, `${fileName(url)}.${ext.value}`)
}, 300)

//  加载并渲染 PDF
async function loadAndRenderPdf(pdfUrl: string) {
  try {
    const canvas = canvasRefs.value[pdfUrl]
    if (!canvas) return

    const loadingTask = pdfjsLib.getDocument(pdfUrl)
    const pdf = await loadingTask.promise
    const page = await pdf.getPage(1)

    const viewport = page.getViewport({ scale: 1.3 })

    canvas.height = viewport.height
    canvas.width = viewport.width

    const context = canvas.getContext('2d')
    if (!context) return

    const renderContext = {
      canvasContext: context,
      viewport,
    }

    await page.render(renderContext).promise
  } catch (error) {
    console.error('PDF 渲染失败:', error)
  }
}

//  标签页切换时加载 PDF
async function tabChangeHandler(key: string) {
  url.value = key
  activateTab.value = fileName(key)

  await nextTick() // 等待 DOM 更新
  if (key.endsWith('.pdf')) {
    await loadAndRenderPdf(key)
  }
}

//  页面初始化时自动加载第一个 PDF
watch(
  () => props.urls,
  async (newUrls) => {
    if (newUrls && newUrls.length > 0) {
      console.log('newUrls:', newUrls)
      url.value = newUrls[0]
      activateTab.value = fileName(newUrls[0])
      await nextTick()
      getFileType(newUrls[0])
    }
  },
  { immediate: true },
)
</script>

<style lang="less" scoped>
.canvas {
  border: 1px solid #000;
  width: 100%; // 响应式宽度
  border: 1px solid #000;
}
.pdf-container {
  display: flex;
  flex-direction: column;
  align-items: start;
  gap: 12px;
  max-width: 100%; // 限制最大宽度
  max-height: 400px;
  overflow: auto;
}
</style>

说一下重点:

问题一: 通过命令pnpm install pdf.js安装后,通常出现引用问题;Cannot resolve pdf.worker.entry。代码中使用的版本"pdfjs-dist": "^5.2.133"

import * as pdfjsLib from 'pdfjs-dist';
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.entry';

pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker;

解决方案:

ps: 上面的代码中包含了文件的下载功能,需要安装 "file-saver": "^2.0.5",

将文件从node_modules/pdfjs-dist/build/pdf.worker.min.mjs移动至项目的public/pdf.worker.min.mjs,可以使用命令 cp node_modules/pdfjs-dist/build/pdf.worker.min.mjs public/pdf.worker.min.mjs

修改引用:

import * as pdfjsLib from 'pdfjs-dist';

pdfjsLib.GlobalWorkerOptions.workerSrc = '/pdf.worker.min.mjs'

2.ofd的预览

实现方式:http://www.easyofd.cn/

安装的依赖:

pnpm -i jszip x2js jb2 opentype.js easyofd

业务实现:

<template>
  <div ref="containerRef" style="width: 100%; height: 800px;"></div>
</template>

<script setup>
import EasyOFD from "easyofd"
import { ref, onMounted } from 'vue'

const containerRef = ref(null)

onMounted(async () => {
  if (!containerRef.value) {
    console.error('OFD 容器不存在')
    return
  }

  const ofd = new EasyOFD('myOFD', containerRef.value)

  try {
    const response = await fetch('/files/sample.ofd')
    const blob = await response.blob()
    ofd.loadFromBlob(blob)
  } catch (e) {
    console.error('OFD 加载失败:', e)
  }
})
</script>
	
<style lang="less" scoped>
// 隐藏右侧的ppi模块,减少空白
:deep(#myOFD-ppi) {
  display: none;
}
// 增加边框
:deep(#myOFD-ofd-canvas) {
  border: 1px solid #000;
}
// 隐藏顶部按钮
:deep(.OfdButton) {
  display: none !important;
}
</style>

官网效果:(easyOfd官网手册)

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文