复制到本地即可调试
<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>