云计算百科
云计算领域专业知识百科平台

vue--ofd/pdf预览实现

背景

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

业务实现

  • pdf的预览

    实现方式:

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

    • navpanes=0 隐藏侧边栏
    • toolbar=0 隐藏顶部工具栏
  • 使用pdf.js,代码先行:

    <template>
    <atabs
    vif="props.urls.length > 0"
    :defaultactivekey="activateTab"
    type="card"
    class="pdf-tabs"
    @change="tabChangeHandler"
    >
    <atabpane vfor="url in props.urls" :key="url" :tab="fileName(url)">
    <div class="pdf-container">
    <canvas
    vif="url.endsWith('.pdf')"
    class="canvas"
    :ref="(el) => (canvasRefs[url] = el)"
    ></canvas>
    <abutton class="mb-2" type="link" @click="handleDownload(url)">
    {{ fileName(url) }}
    </abutton>
    </div>
    </atabpane>
    </atabs>
    </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;
    }
    .pdfcontainer {
    display: flex;
    flexdirection: column;
    alignitems: start;
    gap: 12px;
    maxwidth: 100%; // 限制最大宽度
    maxheight: 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;

    解决方案:

  • 将文件从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'

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

  • ofd的预览

    实现方式:easyofd 安装的依赖: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(#myOFDppi) {
    display: none;
    }
    // 增加边框
    :deep(#myOFDofdcanvas) {
    border: 1px solid #000;
    }
    // 隐藏顶部按钮
    :deep(.OfdButton) {
    display: none !important;
    }
    </style>

  • 官网效果:(easyOfd官网手册) 在这里插入图片描述

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » vue--ofd/pdf预览实现
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!