直接上代码
<view class="m-vue-pdf">
<!-- 加载中 -->
<view v-if="!loaded && !showPicture" class="loading">
<uni-load-more type="loading" color="#007AFF"></uni-load-more>
</view>
<!-- PDF渲染容器(Vue响应式渲染,无手动DOM操作) -->
<view v-show="loaded && !showPicture" class="pdf-wrap">
<scroll-view class="pdf-scroll" scroll-y="true" style="height: 100vh;">
<view class="pdf-container">
<!-- 核心:用Vue循环渲染PDF图片,完全避开DOM操作 -->
<image v-for="(img, index) in pdfImages" :key="index" :src="img" class="pdf-page" mode="widthFix">
</image>
</view>
</scroll-view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted, 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
// 响应式数据(核心:新增pdfImages数组存储Base64图片)
const deptId = ref('')
const riskCode = ref('')
const loaded = ref(false)
const flag = ref('')
const showPicture = ref(false)
const pdfLocalPath = ref('')
const pdfImages = ref([]) // 存储PDF各页的Base64图片
// 静态资源
const staticAssets = {
pdfs: {
hospital: '/static/hospital.pdf',
policy: '/static/policy.pdf'
}
}
onMounted(async () => {
try {
// 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. 处理手动引导页/渲染PDF
if (flag.value !== 'manual') {
const fullPdfPath = getFullPdfPath(pdfLocalPath.value)
// 直接渲染PDF(无需等待DOM,因为用Vue响应式渲染)
await renderPdfToImages(fullPdfPath)
loaded.value = true
} else {
showPicture.value = true
}
} catch (error) {
console.error('初始化失败:', error)
uni.showToast({ title: '页面加载失败', icon: 'none' })
}
})
onUnmounted(() => {
// 销毁PDF实例,释放内存
if (pdfInstance) {
pdfInstance.destroy()
pdfInstance = null
}
// 清空图片数组,释放内存
pdfImages.value = []
})
/**
* 获取适配H5+App的完整PDF路径
* @param {string} relativePath PDF相对路径
* @returns {string} 完整可访问路径
*/
const getFullPdfPath = (relativePath) => {
// #ifdef H5
// H5环境:拼接当前域名,避免file://协议
const baseUrl = window.location.origin
return `${baseUrl}${relativePath}`
// #endif
// #ifdef APP-PLUS
// App环境:转换为本地文件系统路径
return plus.io.convertLocalFileSystemURL(`_www${relativePath}`)
// #endif
// 默认返回相对路径
return relativePath
}
/**
* 核心重构:PDF转Base64图片数组(无任何DOM操作)
* @param {string} pdfPath PDF完整路径
*/
const renderPdfToImages = async (pdfPath) => {
try {
// 清空历史图片
pdfImages.value = []
// 1. 加载PDF
const loadingTask = pdfjsLib.getDocument({
url: pdfPath,
cMapUrl: 'https://cdn.jsdelivr.net/npm/pdfjs-dist/2.16.105/cmaps/',
cMapPacked: true,
disableWorker: true, // App端禁用Worker
verbosity: pdfjsLib.VerbosityLevel.ERRORS
})
pdfInstance = await loadingTask.promise
const numPages = pdfInstance.numPages
const systemInfo = uni.getSystemInfoSync()
const targetWidth = systemInfo.screenWidth - 20 // 留边距
// 2. 逐页渲染为Base64图片
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 })
// 创建canvas(仅内存中使用,不插入DOM)
const canvas = document.createElement('canvas')
canvas.width = scaledViewport.width * systemInfo.pixelRatio
canvas.height = scaledViewport.height * systemInfo.pixelRatio
const ctx = canvas.getContext('2d')
// 白色背景
ctx.fillStyle = '#FFFFFF'
ctx.fillRect(0, 0, canvas.width, canvas.height)
// 适配设备像素比
ctx.scale(systemInfo.pixelRatio, systemInfo.pixelRatio)
await page.render({ canvasContext: ctx, viewport: scaledViewport }).promise
// 转为Base64并添加到数组(核心:不操作DOM,只存数据)
const imgBase64 = canvas.toDataURL('image/png')
pdfImages.value.push(imgBase64)
// 释放canvas内存
canvas.remove()
}
} catch (error) {
console.error('PDF转图片失败:', error)
// 兜底:如果PDF加载失败,提示用户并尝试web-view(仅作为备选)
uni.showModal({
title: '提示',
content: 'PDF预览失败,是否尝试其他方式打开?',
success: (res) => {
if (res.confirm) {
uni.openDocument({
filePath: pdfLocalPath.value,
showMenu: false,
success: () => { },
fail: () => {
uni.showToast({ title: '打开失败', icon: 'none' })
}
})
}
}
})
throw error
}
}
</script>
<style scoped lang="scss">
// 全局背景改为纯白色
.m-vue-pdf {
width: 100%;
min-height: 100vh;
background-color: #FFFFFF !important;
box-sizing: border-box;
// 全局隐藏滚动条(兼容所有平台)
::-webkit-scrollbar {
display: none !important;
width: 0 !important;
height: 0 !important;
}
-ms-overflow-style: none !important; // IE/Edge
scrollbar-width: none !important; // Firefox
}
.loading {
padding: 40px 0;
text-align: center;
background-color: #FFFFFF;
}
.pdf-wrap {
width: 100%;
height: 100vh;
box-sizing: border-box;
background-color: #FFFFFF;
overflow: hidden;
}
// 核心:隐藏scroll-view的滚动条
.pdf-scroll {
width: 100%;
height: 100%;
-webkit-overflow-scrolling: touch;
box-sizing: border-box;
padding: 10px;
background-color: #FFFFFF;
::-webkit-scrollbar {
display: none !important;
width: 0 !important;
}
-ms-overflow-style: none !important;
scrollbar-width: none !important;
overflow-x: hidden !important;
overflow-y: auto !important;
}
.pdf-container {
width: 100%;
box-sizing: border-box;
background-color: #FFFFFF;
}
// PDF每页图片样式(替代手动创建的img样式)
.pdf-page {
width: 100%;
height: auto;
display: block;
margin: 0 auto 10px;
background-color: #FFFFFF;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
// 手动引导页样式不变
.manual {
padding: 0 20px;
box-sizing: border-box;
background-color: #FFFFFF;
overflow: hidden;
.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>
接口返回PDF文件
pdfLocalPath当前选取的是项目本地目录静态资源static下的PDF文件,如果要取接口文件,则调取接口将返回的数据赋值给pdfLocalPath即可