95 lines
3.0 KiB
TypeScript
95 lines
3.0 KiB
TypeScript
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<HTMLImageElement> {
|
|
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<void> {
|
|
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);
|
|
}
|