pdfjsLib预览本地PDF文件,可下载、打印双页操作。代码复制即可使用

2 阅读2分钟

复制到本地即可调试

    <view class="m-vue-pdf">
        <!-- 方案1:优先用无canvas渲染(体验好) -->
        <view v-if="!loaded && !showPicture && !useWebView" class="loading">
            <uni-load-more type="loading" color="#007AFF"></uni-load-more>
        </view>

        <view v-show="loaded && !showPicture && !useWebView" class="pdf-wrap">
            <scroll-view class="pdf-scroll" scroll-y="true" style="height: 100vh;">
                <view class="pdf-container" ref="pdfContainer"></view>
            </scroll-view>
        </view>

        <!-- 方案2:兜底用web-view(绝对稳定) -->
        <web-view v-if="useWebView && !showPicture" :src="pdfLocalPath" style="width: 100%; height: 100vh;"></web-view>

    </view>
</template>

<script setup>
import { ref, onMounted, nextTick, onUnmounted } from 'vue'
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf'
import pdfjsWorker from 'pdfjs-dist/legacy/build/pdf.worker.mjs?url'

// 配置PDF.js
pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker
let pdfInstance = null

// 响应式数据
const deptId = ref('')
const riskCode = ref('')
const loaded = ref(false)
const flag = ref('')
const showPicture = ref(false)
const useWebView = ref(false) // 是否用web-view兜底
const pdfLocalPath = ref('') // PDF本地路径(给web-view用)
const pdfContainer = ref(null) // PDF容器

// 静态资源
const staticAssets = {
    pdfs: {
        hospital: '/static/hospital.pdf',
        policy: '/static/policy.pdf'
    }
}

onMounted(() => {
    // 1. 获取路由参数
    const pages = getCurrentPages()
    const currentPage = pages[pages.length - 1]
    const query = currentPage.options || {}

    riskCode.value = query.riskCode
    flag.value = query.flag
    deptId.value = query.deptId

    if (!riskCode.value) {
        uni.setNavigationBarTitle({ title: '隐私政策' })
    }

    // 2. 确定PDF路径
    if (riskCode.value && deptId.value) {
        pdfLocalPath.value = `/static/${deptId.value}${riskCode.value}.pdf`
    } else if (flag.value === 'hospital') {
        pdfLocalPath.value = staticAssets.pdfs.hospital
    } else {
        pdfLocalPath.value = staticAssets.pdfs.policy
    }

    // 3. 优先尝试无canvas渲染,失败则切web-view
    if (flag.value !== 'manual') {
        renderPdfWithoutCanvas().catch(() => {
            useWebView.value = true // 兜底用web-view
        })
    } else {
        showPicture.value = true
    }
})

onUnmounted(() => {
    pdfInstance = null
})

/**
 * 方案1:无canvas渲染(将PDF转为图片,高度100%自适应)
 * 核心:绕开canvas组件,用img标签展示(img在UniApp中高度无限制)
 */
const renderPdfWithoutCanvas = async () => {
    try {
        // 1. 加载PDF
        const loadingTask = pdfjsLib.getDocument({
            url: pdfLocalPath.value,
            cMapUrl: 'https://cdn.jsdelivr.net/npm/pdfjs-dist/2.16.105/cmaps/',
            cMapPacked: true
        })
        pdfInstance = await loadingTask.promise
        const numPages = pdfInstance.numPages
        const systemInfo = uni.getSystemInfoSync()
        const targetWidth = systemInfo.screenWidth - 20 // 左右留10px边距

        // 2. 逐页转为图片
        for (let i = 1; i <= numPages; i++) {
            const page = await pdfInstance.getPage(i)
            const viewport = page.getViewport({ scale: 1 })
            const scale = targetWidth / viewport.width
            const scaledViewport = page.getViewport({ scale })

            // 3. 渲染为图片数据
            const canvas = document.createElement('canvas')
            canvas.width = scaledViewport.width * systemInfo.pixelRatio
            canvas.height = scaledViewport.height * systemInfo.pixelRatio
            const ctx = canvas.getContext('2d')
            ctx.scale(systemInfo.pixelRatio, systemInfo.pixelRatio)
            await page.render({ canvasContext: ctx, viewport: scaledViewport }).promise

            // 4. 转为base64图片,插入到容器中(img标签无高度限制)
            const imgBase64 = canvas.toDataURL('image/png')
            const imgEl = document.createElement('img')
            imgEl.src = imgBase64
            imgEl.style.width = '100%'
            imgEl.style.height = 'auto'
            imgEl.style.display = 'block'
            imgEl.style.margin = '0 auto 10px'
            imgEl.style.background = '#fff'
            imgEl.style.borderRadius = '8px'
            imgEl.style.boxShadow = '0 2px 8px rgba(0,0,0,0.05)'

            // 插入到容器
            pdfContainer.value.appendChild(imgEl)
            await nextTick()
        }

        loaded.value = true
    } catch (error) {
        console.error('无canvas渲染失败,切web-view:', error)
        throw error
    }
}

</script>

<style scoped lang="scss">
.m-vue-pdf {
    width: 100%;
    min-height: 100vh;
    background-color: #f5f5f5;
    box-sizing: border-box;
}

.loading {
    padding: 40px 0;
    text-align: center;
}

.pdf-wrap {
    width: 100%;
    height: 100vh;
    box-sizing: border-box;
}

.pdf-scroll {
    width: 100%;
    height: 100%;
    -webkit-overflow-scrolling: touch;
    box-sizing: border-box;
    padding: 10px;
}

.pdf-container {
    width: 100%;
    box-sizing: border-box;
}

// 手动引导页样式不变
.manual {
    padding: 0 20px;
    box-sizing: border-box;

    .video-title {
        font-weight: 600;
        font-size: 14px;
        color: red;
        line-height: 22px;
        text-indent: 2em;
        text-align: justify;
        margin: 10px 0;
        display: block;
    }

    .video {
        width: 100%;
        height: auto;
        background-color: #000;
        margin: 10px 0;
        border-radius: 8px;
    }

    .title {
        height: 50px;
        line-height: 50px;
        font-weight: 600;
        font-size: 18px;
        color: #333;
        text-align: center;
    }

    .content {
        font-size: 16px;
        color: #333;
        text-align: justify;
        text-indent: 2em;
        line-height: 25px;
        display: block;
        margin: 5px 0;
    }

    .imgs {
        margin: 0 auto;
        text-align: center;

        .img {
            width: 90%;
            margin: 20px 0;
            border: 1px solid #eee;
            border-radius: 8px;
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
        }
    }
}
</style>