代码复制可用
> <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模式(隐藏工具栏+禁用交互) -->
<view v-if="useWebView && !showPicture" class="web-view-wrap">
<!-- 核心:用遮罩层盖住工具栏 + 禁用web-view的默认交互 -->
<view class="web-view-mask"></view>
<web-view :src="pdfWebUrl" style="width: 100%; height: 100vh;" @message="handleWebViewMsg"></web-view>
</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)
const pdfLocalPath = ref('')
const pdfWebUrl = ref('') // 处理后的PDF路径(隐藏工具栏)
const pdfContainer = ref(null)
// 静态资源
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
}
// 处理web-view的PDF路径(添加参数隐藏工具栏)
pdfWebUrl.value = `${pdfLocalPath.value}#toolbar=0&navpanes=0&scrollbar=0&view=FitH`
// 3. 优先尝试无canvas渲染,失败则切web-view
if (flag.value !== 'manual') {
renderPdfWithoutCanvas().catch(() => {
useWebView.value = true
})
} else {
showPicture.value = true
}
})
onUnmounted(() => {
pdfInstance = null
})
/**
* 方案1:无canvas渲染(纯图片,无任何工具栏/按钮)
*/
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
// 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图片,插入到容器中
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 = 'white'
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
}
}
// web-view消息处理(防止工具栏弹出)
const handleWebViewMsg = (e) => {
// 禁用web-view的任何交互消息
console.log('web-view消息拦截:', e)
}
</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;
}
// ========== 核心:隐藏web-view的工具栏 ==========
.web-view-wrap {
width: 100%;
height: 100vh;
position: relative;
overflow: hidden;
}
// 遮罩层:盖住顶部/底部的工具栏
.web-view-mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100vh;
// 核心:只透明遮罩工具栏区域,不影响内容
background:
linear-gradient(to bottom, #f5f5f5 0px, transparent 50px) top,
linear-gradient(to top, #f5f5f5 0px, transparent 50px) bottom;
background-size: 100% 50px;
background-repeat: no-repeat;
pointer-events: none; // 不影响内容滚动/点击
z-index: 999;
}
// 手动引导页样式不变
.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>