Files
integral-shop/single_uniapp22miao/pages/index/index.vue
2026-03-14 20:31:27 +08:00

440 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="preview-page">
<!-- 加载提示 -->
<view class="loading-overlay" v-if="loadingPdf && usePdfJs">
<view class="loading-content">
<view class="loading-spinner"></view>
<text class="loading-text">正在加载PDF...</text>
</view>
</view>
<!-- PDF容器 - 移动端使用PDF.js渲染 -->
<view
class="pdf-container"
ref="pdfContainer"
v-if="usePdfJs"
:style="{ display: 'block' }"
></view>
<!-- Web-view - PC端或PDF.js加载失败时使用 -->
<web-view
class="pdf-view"
:src="pdfUrl"
v-if="!usePdfJs"
></web-view>
<!-- 底部操作栏 -->
<view class="fixed-footer">
<view class="footer-text" @click="goToSign">前往签字</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
pdfUrl: '/static/sign_contract_ccd.pdf',
userId: '',
isMobile: false,
usePdfJs: false,
pdfDoc: null,
scale: 1,
loadingPdf: false,
pdfReady: false
}
},
onLoad(options) {
// #ifdef H5
// 移动端检测UA + 视口宽度
const ua = (navigator.userAgent || '').toLowerCase()
const isMobileUA = /mobile|android|iphone|ipad|micromessenger/.test(ua)
const narrowViewport = (window.innerWidth || 0) <= 480
this.isMobile = isMobileUA || narrowViewport
this.usePdfJs = this.isMobile
const getUserIdFromUrl = () => {
let id = ''
const s = window.location.search || ''
if (s) {
const p = new URLSearchParams(s)
id = p.get('user_id') || p.get('userId') || ''
}
if (!id && window.location.hash) {
const hash = window.location.hash
const qIndex = hash.indexOf('?')
if (qIndex !== -1) {
const q = hash.substring(qIndex + 1)
const hp = new URLSearchParams(q)
id = hp.get('user_id') || hp.get('userId') || ''
}
}
if (!id) {
const href = window.location.href || ''
const m = href.match(/[?&]user_id=([^&#]+)/)
if (m) id = decodeURIComponent(m[1])
}
return id
}
this.userId = (options && options.user_id) ? options.user_id : getUserIdFromUrl()
console.log('===userId===', this.userId)
if (this.userId) {
try { localStorage.setItem('user_id', this.userId) } catch(e) {}
if (typeof uni !== 'undefined' && uni.setStorageSync) uni.setStorageSync('user_id', this.userId)
}
// #endif
},
onReady() {
// #ifdef H5
// 使用setTimeout确保toast显示后再跳转
// setTimeout(() => {
// uni.navigateTo({
// url: '/pages/integral/points',
// });
// }, 500);
// return;
if (this.usePdfJs) {
// 延迟初始化确保DOM完全就绪
this.$nextTick(() => {
setTimeout(() => {
this.initPdf()
}, 300)
})
}
// #endif
},
onShow() {
// 页面显示时的逻辑
},
methods: {
// 去搜索页
goToSearch() {
uni.navigateTo({
url: '/pages/sub-pages/search/index'
});
},
// 去签字页
goToSign() {
uni.navigateTo({
url: '/pages/sub-pages/webview/sign?user_id=' + this.userId
})
},
// 加载外部脚本PDF.js避免影响 PC 端
loadScript(src) {
return new Promise((resolve, reject) => {
if (document.querySelector(`script[src="${src}"]`)) return resolve()
const s = document.createElement('script')
s.src = src
s.onload = () => resolve()
s.onerror = (e) => reject(e)
document.head.appendChild(s)
})
},
async initPdf() {
uni.showLoading({
title: '加载PDF中...',
mask: true
})
try {
this.loadingPdf = true
this.pdfReady = false
// 引入 PDF.js 主库与 worker
console.log('开始加载 PDF.js 库...')
await this.loadScript('https://cdn.jsdelivr.net/npm/pdfjs-dist@3.11.174/build/pdf.min.js')
await this.loadScript('https://cdn.jsdelivr.net/npm/pdfjs-dist@3.11.174/build/pdf.worker.min.js')
console.log('PDF.js 库加载完成')
// 多次尝试确保容器就绪
let container = null
let attempts = 0
const maxAttempts = 5
while (!container && attempts < maxAttempts) {
await this.$nextTick()
await new Promise(resolve => setTimeout(resolve, 100))
container = this.ensurePdfContainer()
if (container && container.style) {
console.log('PDF容器已就绪')
break
}
attempts++
console.log(`尝试获取PDF容器 (${attempts}/${maxAttempts})`)
}
if (!container || !container.style) {
console.warn('PDF容器未就绪回退到 web-view')
this.usePdfJs = false
uni.hideLoading()
return
}
const pdfjsLib = window['pdfjsLib']
if (!pdfjsLib) {
console.error('PDF.js 库未加载')
this.usePdfJs = false
uni.hideLoading()
return
}
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@3.11.174/build/pdf.worker.min.js'
// 获取文档
console.log('开始加载 PDF 文档...')
this.pdfDoc = await pdfjsLib.getDocument(this.pdfUrl).promise
console.log('PDF 文档加载成功,开始渲染...')
await this.renderAllPages()
this.pdfReady = true
window.addEventListener('resize', this.handleResize, { passive: true })
uni.hideLoading()
uni.showToast({
title: 'PDF加载成功',
icon: 'success',
duration: 1500
})
} catch (e) {
console.error('PDF 加载失败', e)
uni.hideLoading()
uni.showToast({
title: 'PDF加载失败使用备用方式',
icon: 'none',
duration: 2000
})
// 加载失败时回退到 web-view
this.usePdfJs = false
} finally {
this.loadingPdf = false
}
},
ensurePdfContainer() {
// 首先尝试通过 ref 获取
if (this.$refs && this.$refs.pdfContainer) {
const el = this.$refs.pdfContainer
// 确保是真实的DOM元素
if (el && el.nodeType === 1) {
return el
}
}
// 尝试通过类名查找
if (typeof document !== 'undefined') {
let container = document.querySelector('.pdf-container')
if (container && container.nodeType === 1) {
return container
}
// 如果容器不存在,创建一个
const parent = document.querySelector('.preview-page')
if (parent) {
const footer = parent.querySelector('.fixed-footer')
const created = document.createElement('div')
created.className = 'pdf-container'
created.style.cssText = 'position:absolute;top:0;left:0;right:0;bottom:120rpx;overflow-y:auto;overflow-x:hidden;background:#fff;'
if (footer) {
parent.insertBefore(created, footer)
} else {
parent.appendChild(created)
}
console.log('PDF容器已创建')
return created
}
}
return null
},
getContainer() {
// 优先使用 ref
if (this.$refs && this.$refs.pdfContainer) {
const el = this.$refs.pdfContainer
if (el && el.nodeType === 1) {
return el
}
}
// 其次使用选择器
if (typeof document !== 'undefined') {
return document.querySelector('.pdf-container')
}
return null
},
async renderAllPages() {
if (!this.usePdfJs || !this.pdfDoc) {
console.log('取消渲染usePdfJs=', this.usePdfJs, 'pdfDoc=', !!this.pdfDoc)
return
}
// 确保容器存在
let container = this.getContainer()
if (!container) {
await this.$nextTick()
container = this.ensurePdfContainer()
}
if (!container || !container.style) {
console.warn('PDF容器不可用回退到 web-view')
this.usePdfJs = false
return
}
console.log('开始渲染PDF共', this.pdfDoc.numPages, '页')
// 清空容器
container.innerHTML = ''
container.style.overflowY = 'auto'
container.style.overflowX = 'hidden'
container.style.webkitOverflowScrolling = 'touch'
// 获取容器宽度
const cw = container.clientWidth || window.innerWidth || 375
try {
// 渲染所有页面
for (let i = 1; i <= this.pdfDoc.numPages; i++) {
const page = await this.pdfDoc.getPage(i)
const viewport = page.getViewport({ scale: this.scale })
const scaleToFit = cw / viewport.width
const vp = page.getViewport({ scale: this.scale * scaleToFit })
// 创建canvas
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = vp.width
canvas.height = vp.height
canvas.style.width = '100%'
canvas.style.height = `${vp.height}px`
canvas.style.display = 'block'
canvas.style.margin = '0 auto 12px'
container.appendChild(canvas)
// 渲染页面
await page.render({ canvasContext: ctx, viewport: vp }).promise
console.log(`PDF 第 ${i} 页渲染完成`)
}
console.log('PDF 全部渲染完成')
} catch (error) {
console.error('渲染PDF时出错:', error)
uni.showToast({
title: '渲染失败',
icon: 'none'
})
}
},
handleResize() {
// 响应式:窗口变化时重新按容器宽度渲染
if (this.usePdfJs && this.pdfDoc) {
this.$nextTick(() => this.renderAllPages())
}
}
}
}
</script>
<style lang="scss" scoped>
.preview-page {
position: relative;
height: 100vh;
background: #f5f5f5;
}
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.9);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.loading-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.loading-spinner {
width: 80rpx;
height: 80rpx;
border: 6rpx solid #f3f3f3;
border-top: 6rpx solid #f30303;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
margin-top: 30rpx;
font-size: 28rpx;
color: #666666;
}
.pdf-view {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 120rpx;
}
.pdf-container {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 120rpx;
overflow-y: auto;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
background: #fff;
}
.fixed-footer {
position: fixed;
left: 0;
right: 0;
bottom: 0;
height: 120rpx;
background: #ffffff;
border-top: 2rpx solid #eee;
display: flex;
align-items: center;
justify-content: center;
padding: 0 30rpx;
z-index: 10;
}
.footer-text {
flex: 1;
background: #f30303;
color: #fff;
font-size: 32rpx;
padding: 18rpx 30rpx;
border-radius: 20rpx;
text-align: center;
box-shadow: 0 8rpx 20rpx rgba(255, 45, 45, 0.25);
}
</style>