diff --git a/frontend/src/components/pure/ms-table/type.ts b/frontend/src/components/pure/ms-table/type.ts index e87a15e4a7..6dfee06a9e 100644 --- a/frontend/src/components/pure/ms-table/type.ts +++ b/frontend/src/components/pure/ms-table/type.ts @@ -131,6 +131,7 @@ export interface MsTableProps { // 行选择器禁用配置 rowSelectionDisabledConfig?: MsTableRowSelectionDisabledConfig; sorter?: Record; // 排序 + hoverable?: boolean; // 是否展示hover效果 [key: string]: any; } diff --git a/frontend/src/workers/exportPDF/exportPDFWorker.ts b/frontend/src/utils/exportPdf.ts similarity index 62% rename from frontend/src/workers/exportPDF/exportPDFWorker.ts rename to frontend/src/utils/exportPdf.ts index a7b7af8483..6cfc49062b 100644 --- a/frontend/src/workers/exportPDF/exportPDFWorker.ts +++ b/frontend/src/utils/exportPdf.ts @@ -7,10 +7,12 @@ const A4_HEIGHT = 842; const HEADER_HEIGHT = 16; const FOOTER_HEIGHT = 16; const PAGE_HEIGHT = A4_HEIGHT - FOOTER_HEIGHT - HEADER_HEIGHT; -const pdfWidth = A4_WIDTH - 32; // 左右分别 16px 间距 -const totalWidth = 1370; -const realPageHeight = Math.ceil(PAGE_HEIGHT * (totalWidth / pdfWidth) * 1.2); // 实际每页高度 -const MAX_CANVAS_HEIGHT = realPageHeight * 23; // 一次截图最大高度是 25 页整 +const PDF_WIDTH = A4_WIDTH - 32; // 左右分别 16px 间距 +const CONTAINER_WIDTH = 1190; +export const SCALE_RATIO = 1.2; +// 实际每页高度 = PDF页面高度/页面容器宽度与 pdf 宽度的比例(这里比例*SCALE_RATIO 是因为html2canvas截图时生成的是 SCALE_RATIO 倍的清晰度) +export const IMAGE_HEIGHT = Math.ceil(PAGE_HEIGHT * (CONTAINER_WIDTH / PDF_WIDTH) * SCALE_RATIO); +export const MAX_CANVAS_HEIGHT = IMAGE_HEIGHT * 25; // 一次截图最大高度是 20 页整(过长会无法截完整,出现空白) /** * 替换svg为base64 @@ -72,7 +74,6 @@ async function replaceSvgWithBase64(container: HTMLElement) { * (使用html2canvas截图时,因为插件有截图极限,超出极限部分会出现截图失败,所以这里设置了MAX_CANVAS_HEIGHT截图高度,然后根据这个截图高度分页截图,然后根据每个截图裁剪每页 pdf 的图片并添加到 pdf 内) */ export default async function exportPDF(name: string, contentId: string) { - console.log('start', new Date().getMinutes(), new Date().getSeconds()); const element = document.getElementById(contentId); if (element) { await replaceSvgWithBase64(element); @@ -86,52 +87,55 @@ export default async function exportPDF(name: string, contentId: string) { pdf.setFontSize(10); // 计算pdf总页数 let totalPages = 0; - let position = 0; + let position = 0; // 当前截图位置 let pageIndex = 1; let loopTimes = 0; - const canvasList: HTMLCanvasElement[] = []; + const screenshotList: HTMLCanvasElement[] = []; + // 创建图片裁剪画布 + const cropCanvas = document.createElement('canvas'); + cropCanvas.width = CONTAINER_WIDTH * SCALE_RATIO; // 因为截图时放大了 SCALE_RATIO 倍,所以这里也要放大 + cropCanvas.height = IMAGE_HEIGHT; + const tempContext = cropCanvas.getContext('2d', { willReadFrequently: true }); + // 这里是大的分页,也就是截图画布的分页 while (position < totalHeight) { - // 这里是大的分页,也就是截图画布的分页 - const offscreenHeight = Math.min(MAX_CANVAS_HEIGHT, totalHeight - position); + // 截图高度 + const screenshotHeight = Math.min(MAX_CANVAS_HEIGHT, totalHeight - position); // eslint-disable-next-line no-await-in-loop const canvas = await html2canvas(element, { x: 0, y: position, - width: totalWidth, - height: offscreenHeight, + width: CONTAINER_WIDTH, + height: screenshotHeight, backgroundColor: '#f9f9fe', - scale: window.devicePixelRatio * 1.2, // 增加清晰度 + scale: window.devicePixelRatio * SCALE_RATIO, // 缩放增加清晰度 }); - canvasList.push(canvas); - position += offscreenHeight; - totalPages += Math.ceil(canvas.height / realPageHeight); + screenshotList.push(canvas); + position += screenshotHeight; + totalPages += Math.ceil(canvas.height / IMAGE_HEIGHT); loopTimes++; } totalPages -= loopTimes - 1; // 减去多余的页数 // 生成 PDF - canvasList.forEach((canvas) => { - const canvasWidth = canvas.width; - const canvasHeight = canvas.height; - const pages = Math.ceil(canvasHeight / realPageHeight); + screenshotList.forEach((_canvas) => { + const canvasWidth = _canvas.width; + const canvasHeight = _canvas.height; + const pages = Math.ceil(canvasHeight / IMAGE_HEIGHT); for (let i = 1; i <= pages; i++) { // 这里是小的分页,是 pdf 的每一页 - const pagePosition = (i - 1) * realPageHeight; - // 创建临时画布 - const tempCanvas = document.createElement('canvas'); - tempCanvas.width = canvasWidth; - tempCanvas.height = realPageHeight; - const tempContext = tempCanvas.getContext('2d', { willReadFrequently: true }); + const pagePosition = (i - 1) * IMAGE_HEIGHT; if (tempContext) { - // 填充背景颜色为白色 - tempContext.fillStyle = '#ffffff'; - tempContext.fillRect(0, 0, tempCanvas.width, tempCanvas.height); + if (pageIndex === totalPages) { + // 填充背景颜色为白色 + tempContext.fillStyle = '#ffffff'; + tempContext.fillRect(0, 0, cropCanvas.width, cropCanvas.height); + } // 将大分页的画布图片裁剪成pdf 页面内容大小,并渲染到临时画布上 - tempContext.drawImage(canvas, 0, -pagePosition, canvasWidth, canvasHeight); - const tempCanvasData = tempCanvas.toDataURL('image/jpeg', 0.8); + tempContext.drawImage(_canvas, 0, -pagePosition, canvasWidth, canvasHeight); + const tempCanvasData = cropCanvas.toDataURL('image/jpeg', 0.8); // 将临时画布图片渲染到 pdf 上 - pdf.addImage(tempCanvasData, 'jpeg', 16, 16, pdfWidth, PAGE_HEIGHT); + pdf.addImage(tempCanvasData, 'jpeg', 16, 16, PDF_WIDTH, PAGE_HEIGHT); } - tempCanvas.remove(); + cropCanvas.remove(); pdf.text( `${pageIndex} / ${totalPages}`, pdf.internal.pageSize.width / 2 - 10, @@ -142,9 +146,8 @@ export default async function exportPDF(name: string, contentId: string) { pageIndex++; } } - canvas.remove(); + _canvas.remove(); }); pdf.save(`${name}.pdf`); - console.log('end', new Date().getMinutes(), new Date().getSeconds()); } } diff --git a/frontend/src/views/test-plan/report/detail/exportPDF.vue b/frontend/src/views/test-plan/report/detail/exportPDF.vue index 2db7dd8f3c..66f686c9ad 100644 --- a/frontend/src/views/test-plan/report/detail/exportPDF.vue +++ b/frontend/src/views/test-plan/report/detail/exportPDF.vue @@ -1,6 +1,6 @@