import type { ECharts } from 'echarts'; const TITLE_FONT = 'bold 32px "Microsoft YaHei","PingFang SC","Noto Sans SC",sans-serif'; const SUB_FONT = '22px "Microsoft YaHei","PingFang SC","Noto Sans SC",sans-serif'; type ChartShot = { title: string; instance: ECharts | undefined; }; function loadImage(src: string): Promise { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => resolve(img); img.onerror = () => reject(new Error('图表截图失败')); img.src = src; }); } async function composePage(params: { chartPng: string; title: string; subtitle: string; }): Promise<{ dataUrl: string; width: number; height: number }> { const img = await loadImage(params.chartPng); const header = 96; const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height + header; const ctx = canvas.getContext('2d'); if (!ctx) throw new Error('无法生成 PDF 画布'); ctx.fillStyle = '#ffffff'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.textBaseline = 'top'; ctx.fillStyle = '#1f1f1f'; ctx.font = TITLE_FONT; ctx.fillText(params.title, 24, 16, canvas.width - 48); ctx.fillStyle = '#8c8c8c'; ctx.font = SUB_FONT; ctx.fillText(params.subtitle, 24, 56, canvas.width - 48); ctx.drawImage(img, 0, header); return { dataUrl: canvas.toDataURL('image/jpeg', 0.92), width: canvas.width, height: canvas.height, }; } /** 把当前已渲染折线图打成横向 A4 PDF(不含 KPI / 待办)。 */ export async function downloadDashboardChartsPdf(params: { filename: string; subtitle: string; charts: ChartShot[]; }): Promise { const ready = params.charts.filter((c): c is { title: string; instance: ECharts } => !!c.instance); if (ready.length === 0) { throw new Error('暂无可下载的图'); } if (ready.length !== params.charts.length) { throw new Error('图表尚未渲染完成,请稍后再试'); } const { jsPDF } = await import('jspdf'); const pdf = new jsPDF({ orientation: 'landscape', unit: 'mm', format: 'a4' }); const pageW = pdf.internal.pageSize.getWidth(); const pageH = pdf.internal.pageSize.getHeight(); const margin = 10; for (let i = 0; i < ready.length; i += 1) { const chart = ready[i]; const png = chart.instance.getDataURL({ type: 'png', pixelRatio: 2, backgroundColor: '#ffffff', }); const page = await composePage({ chartPng: png, title: chart.title, subtitle: params.subtitle, }); const maxW = pageW - margin * 2; const maxH = pageH - margin * 2; const ratio = Math.min(maxW / page.width, maxH / page.height); const w = page.width * ratio; const h = page.height * ratio; const x = (pageW - w) / 2; const y = margin + (maxH - h) / 2; if (i > 0) pdf.addPage(); pdf.addImage(page.dataUrl, 'JPEG', x, y, w, h); } const filename = params.filename.endsWith('.pdf') ? params.filename : `${params.filename}.pdf`; pdf.save(filename); }