From 3f8c1da5e1dd5fe855e5a47a5af8c5befa2d0a16 Mon Sep 17 00:00:00 2001 From: baiqi Date: Fri, 13 Sep 2024 17:17:08 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=8A=A5=E5=91=8A):=20=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E8=AE=A1=E5=88=92=E6=89=B9=E9=87=8F=E5=AF=BC=E5=87=BA=20PDF=20?= =?UTF-8?q?=E6=8A=A5=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/modules/test-plan/report.ts | 5 + frontend/src/api/requrls/test-plan/report.ts | 2 + frontend/src/hooks/useExportPDF.ts | 35 +- frontend/src/hooks/useOpenNewPage.ts | 32 ++ frontend/src/models/common.ts | 3 + frontend/src/utils/index.ts | 6 +- .../api-test/report/component/reportList.vue | 2 +- .../test-plan/report/component/reportList.vue | 65 ++-- .../test-plan/report/detail/exportPDF.vue | 368 ++++++++++-------- .../views/test-plan/report/locale/en-US.ts | 4 +- .../views/test-plan/report/locale/zh-CN.ts | 6 +- 11 files changed, 330 insertions(+), 198 deletions(-) diff --git a/frontend/src/api/modules/test-plan/report.ts b/frontend/src/api/modules/test-plan/report.ts index 8baa6dc264..7edef434bf 100644 --- a/frontend/src/api/modules/test-plan/report.ts +++ b/frontend/src/api/modules/test-plan/report.ts @@ -182,3 +182,8 @@ export function logTestPlanReportExport(reportId: string) { export function logTestPlanReportBatchExport(data: BatchApiParams) { return MSR.post({ url: reportUrl.TestPlanBatchReportExportUrl, data }); } + +// 批量导出报告日志 +export function testPlanBatchReportExportGetIds(data: BatchApiParams) { + return MSR.post({ url: reportUrl.TestPlanBatchReportExportGetIdsUrl, data }); +} diff --git a/frontend/src/api/requrls/test-plan/report.ts b/frontend/src/api/requrls/test-plan/report.ts index 4dd0a3dc23..6d9b129646 100644 --- a/frontend/src/api/requrls/test-plan/report.ts +++ b/frontend/src/api/requrls/test-plan/report.ts @@ -72,3 +72,5 @@ export const getReportShareLayoutUrl = '/test-plan/report/share/get-layout'; export const TestPlanReportExportUrl = '/test-plan/report/export'; // 测试计划-报告-批量导出日志 export const TestPlanBatchReportExportUrl = '/test-plan/report/batch-export'; +// 测试计划-报告-批量导出获取报告 ID 集合 +export const TestPlanBatchReportExportGetIdsUrl = '/test-plan/report/batch-param'; diff --git a/frontend/src/hooks/useExportPDF.ts b/frontend/src/hooks/useExportPDF.ts index f396e1d1bd..c008a41b87 100644 --- a/frontend/src/hooks/useExportPDF.ts +++ b/frontend/src/hooks/useExportPDF.ts @@ -18,19 +18,7 @@ export const PAGE_PDF_WIDTH_RATIO = CONTAINER_WIDTH / PDF_WIDTH; // 页面容器 // 实际每页高度 = PDF页面高度/页面容器宽度与 pdf 宽度的比例(这里比例*SCALE_RATIO 是因为html2canvas截图时生成的是 SCALE_RATIO 倍的清晰度) export const IMAGE_HEIGHT = Math.ceil(PAGE_HEIGHT * PAGE_PDF_WIDTH_RATIO * SCALE_RATIO); -const commonOdfTableConfig: Partial = { - headStyles: { - fillColor: '#793787', - }, - styles: { - font: 'AlibabaPuHuiTi-3-55-Regular', - }, - rowPageBreak: 'avoid', - margin: { top: 16, left: 16, right: 16, bottom: 16 }, - tableWidth: PDF_WIDTH, -}; - -export type PdfTableConfig = Pick; +export type PdfTableConfig = Pick; /** * 导出PDF @@ -43,6 +31,7 @@ export default async function exportPDF( name: string, contentId: string, autoTableConfig: PdfTableConfig[], + tableTitleMap?: Record, doneCallback?: () => void ) { const element = document.getElementById(contentId); @@ -95,6 +84,26 @@ export default async function exportPDF( } const lastImagePageUseHeight = (canvasHeight > IMAGE_HEIGHT ? canvasHeight - IMAGE_HEIGHT : canvasHeight) / PAGE_PDF_WIDTH_RATIO / SCALE_RATIO; // 最后一页带图片的pdf页面被图片占用的高度 + + const commonOdfTableConfig: Partial = { + headStyles: { + fillColor: '#793787', + }, + styles: { + font: 'AlibabaPuHuiTi-3-55-Regular', + }, + rowPageBreak: 'avoid', + margin: { top: 16, left: 16, right: 16, bottom: 16 }, + tableWidth: PDF_WIDTH, + willDrawPage: (data) => { + const title = tableTitleMap?.[data.table.id as string]; + if (title && data.cursor) { + pdf.text(title, 16, data.cursor.y + 4); + // 在文字后加入 8px 高的空白 + data.cursor.y += 12; + } + }, + }; autoTableConfig.forEach((config, index) => { autoTable(pdf, { ...config, diff --git a/frontend/src/hooks/useOpenNewPage.ts b/frontend/src/hooks/useOpenNewPage.ts index f9a430293b..b5039d1970 100644 --- a/frontend/src/hooks/useOpenNewPage.ts +++ b/frontend/src/hooks/useOpenNewPage.ts @@ -48,8 +48,40 @@ export default function useOpenNewPage() { ); } + function openNewPageWithParams( + name: RouteRecordName | undefined, + query: Record = {}, + params: Record = {} + ) { + const pId = query.pId || appStore.currentProjectId; + if (pId) { + // 如果传入参数指定了项目 id,则使用传入的项目 id + delete query.pId; + } + const orgId = query.orgId || appStore.currentOrgId; + if (orgId) { + // 如果传入参数指定了组织 id,则使用传入的组织 id + delete query.orgId; + } + const queryParams = new URLSearchParams(query).toString(); + const newTab = window.open( + `${window.location.origin}#${router.resolve({ name }).fullPath}?orgId=${orgId}&pId=${pId}&${queryParams}`, + '_blank' + ); + + // 等待新标签页加载完成后发送消息 + if (newTab) { + newTab.onload = () => { + setTimeout(() => { + newTab.postMessage(JSON.stringify(params), window.location.origin); + }, 300); + }; + } + } + return { openNewPage, openNewPageWidthSingleParam, + openNewPageWithParams, }; } diff --git a/frontend/src/models/common.ts b/frontend/src/models/common.ts index 16b887468d..bdc8ab70c1 100644 --- a/frontend/src/models/common.ts +++ b/frontend/src/models/common.ts @@ -1,5 +1,7 @@ import { TreeNodeData } from '@arco-design/web-vue'; +import type { FilterResult } from '@/components/pure/ms-advance-filter/type'; + import { RequestMethods } from '@/enums/apiEnum'; // 请求返回结构 @@ -51,6 +53,7 @@ export interface BatchApiParams { versionId?: string; // 版本 ID refId?: string; // 版本来源 protocols?: string[]; // 协议集合 + combineSearch?: FilterResult; } // 移动模块树 diff --git a/frontend/src/utils/index.ts b/frontend/src/utils/index.ts index 57d87ad720..a3ee17e555 100644 --- a/frontend/src/utils/index.ts +++ b/frontend/src/utils/index.ts @@ -153,12 +153,12 @@ export function desensitize(str: string): string { * @param str 标题的动态内容 * @returns 转化后的字符串 */ -export function characterLimit(str?: string): string { +export function characterLimit(str?: string, length?: number): string { if (!str) return ''; - if (str.length <= 20) { + if (str.length <= (length || 20)) { return str; } - return `${str.slice(0, 20 - 3)}...`; + return `${str.slice(0, length || 20 - 3)}...`; } /** diff --git a/frontend/src/views/api-test/report/component/reportList.vue b/frontend/src/views/api-test/report/component/reportList.vue index f875c85161..10c7966c79 100644 --- a/frontend/src/views/api-test/report/component/reportList.vue +++ b/frontend/src/views/api-test/report/component/reportList.vue @@ -271,7 +271,7 @@ dataIndex: 'operation', fixed: 'right', title: hasAnyPermission(['PROJECT_API_REPORT:READ+DELETE']) ? 'common.operation' : '', - width: hasAnyPermission(['PROJECT_API_REPORT:READ+DELETE']) ? 130 : 50, + width: hasAnyPermission(['PROJECT_API_REPORT:READ+DELETE']) ? 100 : 50, }, ]; diff --git a/frontend/src/views/test-plan/report/component/reportList.vue b/frontend/src/views/test-plan/report/component/reportList.vue index 6b3b3cc8a8..6b16f6151a 100644 --- a/frontend/src/views/test-plan/report/component/reportList.vue +++ b/frontend/src/views/test-plan/report/component/reportList.vue @@ -135,7 +135,7 @@ const keyword = ref(''); const router = useRouter(); const route = useRoute(); - const { openNewPage } = useOpenNewPage(); + const { openNewPage, openNewPageWithParams } = useOpenNewPage(); type ReportShowType = 'All' | 'INDEPENDENT' | 'INTEGRATED'; const showType = ref('All'); @@ -340,6 +340,11 @@ eventTag: 'batchStop', permission: ['PROJECT_TEST_PLAN_REPORT:READ+DELETE'], }, + { + label: 'common.export', + eventTag: 'batchExport', + permission: ['PROJECT_TEST_PLAN_REPORT:READ+EXPORT'], + }, ], }; @@ -362,30 +367,40 @@ projectId: appStore.currentProjectId, }; - openModal({ - type: 'error', - title: t('report.delete.tip', { - count: params?.currentSelectCount || params?.selectedIds?.length, - }), - content: '', - okText: t('common.confirmDelete'), - cancelText: t('common.cancel'), - okButtonProps: { - status: 'danger', - }, - onBeforeOk: async () => { - try { - await reportBathDelete(batchParams.value); - Message.success(t('apiTestDebug.deleteSuccess')); - resetSelector(); - initData(); - } catch (error) { - // eslint-disable-next-line no-console - console.log(error); - } - }, - hideCancel: false, - }); + if (event.eventTag === 'batchExport') { + openNewPageWithParams( + FullPageEnum.FULL_PAGE_TEST_PLAN_EXPORT_PDF, + { + type: showType.value, + }, + batchParams.value + ); + } else if (event.eventTag === 'batchStop') { + openModal({ + type: 'error', + title: t('report.delete.tip', { + count: params?.currentSelectCount || params?.selectedIds?.length, + }), + content: '', + okText: t('common.confirmDelete'), + cancelText: t('common.cancel'), + okButtonProps: { + status: 'danger', + }, + onBeforeOk: async () => { + try { + await reportBathDelete(batchParams.value); + Message.success(t('apiTestDebug.deleteSuccess')); + resetSelector(); + initData(); + } catch (error) { + // eslint-disable-next-line no-console + console.log(error); + } + }, + hideCancel: false, + }); + } }; function searchList() { diff --git a/frontend/src/views/test-plan/report/detail/exportPDF.vue b/frontend/src/views/test-plan/report/detail/exportPDF.vue index f45b5e9c5a..c729bb9e29 100644 --- a/frontend/src/views/test-plan/report/detail/exportPDF.vue +++ b/frontend/src/views/test-plan/report/detail/exportPDF.vue @@ -1,5 +1,9 @@