import { nextTick, computed } from 'vue'; import { useMainStore } from '@/store'; import { ElLoading } from 'element-plus'; import html2canvas from 'html2canvas'; import jsPDF from 'jspdf'; import { showMessage } from './common'; import { uploadFile } from '@/utils/http'; import { savePdf } from '@/utils/api'; const mainStore = useMainStore(); const expanded = computed(() => mainStore.getExpanded); const phone = computed(() => mainStore.getPhone); const recordId = computed(() => mainStore.getRecordId); const isLoadOver = computed(() => mainStore.getIsLoadOver); /** * 生成 PDF Blob */ async function generatePDFBlob( pdfContent?: HTMLElement, showLoading = false ): Promise { if (!isLoadOver.value) { showMessage({ type: 'warning', message: '数据加载中,请稍后再试' }); return null; } let loading: any = null; if (showLoading) { loading = ElLoading.service({ lock: true, text: '生成报告中...', background: 'rgba(0,0,0,0.7)' }); } try { if (!pdfContent) pdfContent = document.getElementById('Record') as HTMLElement; if (!expanded.value) { mainStore.setExpanded(true); await nextTick(); } await nextTick(); const canvas = await html2canvas(pdfContent, { scale: 2, useCORS: true, backgroundColor: '#fff' }); const canvasWidth = canvas.width; const canvasHeight = canvas.height; const pdf = new jsPDF('p', 'mm', 'a4'); const pageWidth = pdf.internal.pageSize.getWidth(); const pageHeight = pdf.internal.pageSize.getHeight(); const ratio = pageWidth / canvasWidth; const pagePixelHeight = pageHeight / ratio; // 页边距(像素) const marginTop = 20; // 仅作用于非第一页 const marginBottom = 40; // 所有页生效 let positionY = 0; let pageIndex = 0; while (positionY < canvasHeight) { // 判断是否第一页 const topMargin = pageIndex === 0 ? 0 : marginTop; // 每页实际可容纳的内容高度 const h = Math.min( pagePixelHeight - topMargin - marginBottom, canvasHeight - positionY ); const pageCanvas = document.createElement('canvas'); pageCanvas.width = canvasWidth; pageCanvas.height = h; const ctx = pageCanvas.getContext('2d'); ctx?.drawImage(canvas, 0, positionY, canvasWidth, h, 0, 0, canvasWidth, h); const imgData = pageCanvas.toDataURL('image/jpeg', 0.95); // 添加到 PDF 时,整体往下偏移 topMargin pdf.addImage(imgData, 'JPEG', 0, topMargin * ratio, pageWidth, h * ratio); positionY += h; pageIndex++; if (positionY < canvasHeight) pdf.addPage(); } return pdf.output('blob'); } catch (error) { console.error(error); showMessage({ type: 'error', message: '生成失败,请稍后再试' }); return null; } finally { mainStore.setExpanded(false); setTimeout(() => loading && loading.close(), 500); } } /** * 下载文件 */ function triggerDownload(blob: Blob, filename = 'analysis.pdf') { const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; a.click(); URL.revokeObjectURL(url); } /** * 上传文件 */ async function uploadPDFFile(blob: Blob) { const file = new File([blob], 'analysis.pdf', { type: 'application/pdf' }); const uploadRes = await uploadFile(file, { source: 'media', mediaType: 'file' }); const pdfUrl = uploadRes.data.url; await savePdf({ phone: phone.value, id: recordId.value, pdfUrl }); return pdfUrl; } /** * 统一入口:生成 PDF * @param pdfContent - PDF内容DOM * @param mode - "manual" 手动下载 | "auto" 自动下载并上传 */ export async function handlePDF(pdfContent?: HTMLElement, mode: 'manual' | 'auto' = 'manual') { const blob = await generatePDFBlob(pdfContent, true); if (!blob) return; // 下载 triggerDownload(blob); // 自动模式下再上传 if (mode === 'auto') { try { await uploadPDFFile(blob); // showMessage({ type: 'success', message: '报告已下载并上传成功' }); } catch (err) { // showMessage({ type: 'error', message: '报告上传失败' }); throw err; } } }