Files
art-design/src/composables/usePdfPreview.ts
T

141 lines
4.0 KiB
TypeScript
Raw Normal View History

2025-09-18 10:57:17 +08:00
import { ref, reactive } from 'vue'
export const usePdfPreview = () => {
const state = reactive({
visible: false,
url: '',
name: '',
blobUrl: '',
images: [] as string[]
})
const frameRef = ref<HTMLIFrameElement | null>(null)
// 动态加载 pdf.js(优先本地依赖,失败回退 CDN)
const loadPdfJs = async (): Promise<any> => {
// 统一使用 CDN,避免本地依赖缺失导致打包时报错
const ensure = (src: string) =>
new Promise<void>((resolve, reject) => {
const s = document.createElement('script')
s.src = src
s.onload = () => resolve()
s.onerror = () => reject(new Error('load script failed'))
document.head.appendChild(s)
})
await ensure('https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js')
await ensure('https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js')
return (window as any).pdfjsLib
}
const open = (url: string, name = '') => {
if (!url) return
state.url = url
state.name = name
state.visible = true
state.images = []
}
const openWithResponse = (res: any) => {
const url = res?.data?.file_url_full || res?.file_url_full
const name = res?.data?.file_url || res?.file_url || ''
open(url, name)
}
const inferFilenameFromUrl = (url: string): string => {
try {
const u = new URL(url)
const parts = u.pathname.split('/')
return parts[parts.length - 1] || 'document.pdf'
} catch (e) {
const parts = (url || '').split('?')[0].split('/')
return parts[parts.length - 1] || 'document.pdf'
}
}
const download = async () => {
if (!state.url) return
const filename = state.name || inferFilenameFromUrl(state.url)
try {
const resp = await fetch(state.url, { credentials: 'omit' })
const blob = await resp.blob()
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = filename
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
} catch {
window.open(state.url, '_blank')
}
}
// 预览时也用 blob,提升兼容性
const preparePreview = async () => {
if (!state.url) return
try {
const resp = await fetch(state.url, { credentials: 'omit' })
const blob = await resp.blob()
state.blobUrl && window.URL.revokeObjectURL(state.blobUrl)
state.blobUrl = window.URL.createObjectURL(blob)
} catch {
state.blobUrl = ''
}
}
// 生成图片以便无需浏览器 PDF 插件即可展示
const prepareImages = async () => {
if (!state.url) return
state.images = []
try {
const resp = await fetch(state.url, { credentials: 'omit' })
const arrayBuffer = await resp.arrayBuffer()
const pdfjsLib = await loadPdfJs()
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise
const images: string[] = []
for (let p = 1; p <= pdf.numPages; p++) {
const page = await pdf.getPage(p)
const viewport = page.getViewport({ scale: 1.5 })
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
if (!context) continue
canvas.width = viewport.width
canvas.height = viewport.height
await page.render({ canvasContext: context, viewport }).promise
images.push(canvas.toDataURL('image/png'))
}
state.images = images
} catch {
state.images = []
}
}
const print = () => {
const frame = frameRef.value
if (frame && frame.contentWindow) {
frame.contentWindow.focus()
frame.contentWindow.print()
return
}
if (state.url) {
const win = window.open(state.url, '_blank')
if (win) {
win.addEventListener('load', () => win.print())
}
}
}
return {
pdf: state,
frameRef,
open,
openWithResponse,
download,
print,
preparePreview,
prepareImages
}
}