feat(报告): 测试计划批量导出 PDF 报告
This commit is contained in:
parent
69024bbe4c
commit
3f8c1da5e1
|
@ -182,3 +182,8 @@ export function logTestPlanReportExport(reportId: string) {
|
||||||
export function logTestPlanReportBatchExport(data: BatchApiParams) {
|
export function logTestPlanReportBatchExport(data: BatchApiParams) {
|
||||||
return MSR.post({ url: reportUrl.TestPlanBatchReportExportUrl, data });
|
return MSR.post({ url: reportUrl.TestPlanBatchReportExportUrl, data });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量导出报告日志
|
||||||
|
export function testPlanBatchReportExportGetIds(data: BatchApiParams) {
|
||||||
|
return MSR.post<string[]>({ url: reportUrl.TestPlanBatchReportExportGetIdsUrl, data });
|
||||||
|
}
|
||||||
|
|
|
@ -72,3 +72,5 @@ export const getReportShareLayoutUrl = '/test-plan/report/share/get-layout';
|
||||||
export const TestPlanReportExportUrl = '/test-plan/report/export';
|
export const TestPlanReportExportUrl = '/test-plan/report/export';
|
||||||
// 测试计划-报告-批量导出日志
|
// 测试计划-报告-批量导出日志
|
||||||
export const TestPlanBatchReportExportUrl = '/test-plan/report/batch-export';
|
export const TestPlanBatchReportExportUrl = '/test-plan/report/batch-export';
|
||||||
|
// 测试计划-报告-批量导出获取报告 ID 集合
|
||||||
|
export const TestPlanBatchReportExportGetIdsUrl = '/test-plan/report/batch-param';
|
||||||
|
|
|
@ -18,19 +18,7 @@ export const PAGE_PDF_WIDTH_RATIO = CONTAINER_WIDTH / PDF_WIDTH; // 页面容器
|
||||||
// 实际每页高度 = PDF页面高度/页面容器宽度与 pdf 宽度的比例(这里比例*SCALE_RATIO 是因为html2canvas截图时生成的是 SCALE_RATIO 倍的清晰度)
|
// 实际每页高度 = PDF页面高度/页面容器宽度与 pdf 宽度的比例(这里比例*SCALE_RATIO 是因为html2canvas截图时生成的是 SCALE_RATIO 倍的清晰度)
|
||||||
export const IMAGE_HEIGHT = Math.ceil(PAGE_HEIGHT * PAGE_PDF_WIDTH_RATIO * SCALE_RATIO);
|
export const IMAGE_HEIGHT = Math.ceil(PAGE_HEIGHT * PAGE_PDF_WIDTH_RATIO * SCALE_RATIO);
|
||||||
|
|
||||||
const commonOdfTableConfig: Partial<UserOptions> = {
|
export type PdfTableConfig = Pick<UserOptions, 'tableId' | 'columnStyles' | 'columns' | 'body'>;
|
||||||
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<UserOptions, 'columnStyles' | 'columns' | 'body'>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导出PDF
|
* 导出PDF
|
||||||
|
@ -43,6 +31,7 @@ export default async function exportPDF(
|
||||||
name: string,
|
name: string,
|
||||||
contentId: string,
|
contentId: string,
|
||||||
autoTableConfig: PdfTableConfig[],
|
autoTableConfig: PdfTableConfig[],
|
||||||
|
tableTitleMap?: Record<string, string>,
|
||||||
doneCallback?: () => void
|
doneCallback?: () => void
|
||||||
) {
|
) {
|
||||||
const element = document.getElementById(contentId);
|
const element = document.getElementById(contentId);
|
||||||
|
@ -95,6 +84,26 @@ export default async function exportPDF(
|
||||||
}
|
}
|
||||||
const lastImagePageUseHeight =
|
const lastImagePageUseHeight =
|
||||||
(canvasHeight > IMAGE_HEIGHT ? canvasHeight - IMAGE_HEIGHT : canvasHeight) / PAGE_PDF_WIDTH_RATIO / SCALE_RATIO; // 最后一页带图片的pdf页面被图片占用的高度
|
(canvasHeight > IMAGE_HEIGHT ? canvasHeight - IMAGE_HEIGHT : canvasHeight) / PAGE_PDF_WIDTH_RATIO / SCALE_RATIO; // 最后一页带图片的pdf页面被图片占用的高度
|
||||||
|
|
||||||
|
const commonOdfTableConfig: Partial<UserOptions> = {
|
||||||
|
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) => {
|
autoTableConfig.forEach((config, index) => {
|
||||||
autoTable(pdf, {
|
autoTable(pdf, {
|
||||||
...config,
|
...config,
|
||||||
|
|
|
@ -48,8 +48,40 @@ export default function useOpenNewPage() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openNewPageWithParams(
|
||||||
|
name: RouteRecordName | undefined,
|
||||||
|
query: Record<string, any> = {},
|
||||||
|
params: Record<string, any> = {}
|
||||||
|
) {
|
||||||
|
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 {
|
return {
|
||||||
openNewPage,
|
openNewPage,
|
||||||
openNewPageWidthSingleParam,
|
openNewPageWidthSingleParam,
|
||||||
|
openNewPageWithParams,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { TreeNodeData } from '@arco-design/web-vue';
|
import { TreeNodeData } from '@arco-design/web-vue';
|
||||||
|
|
||||||
|
import type { FilterResult } from '@/components/pure/ms-advance-filter/type';
|
||||||
|
|
||||||
import { RequestMethods } from '@/enums/apiEnum';
|
import { RequestMethods } from '@/enums/apiEnum';
|
||||||
|
|
||||||
// 请求返回结构
|
// 请求返回结构
|
||||||
|
@ -51,6 +53,7 @@ export interface BatchApiParams {
|
||||||
versionId?: string; // 版本 ID
|
versionId?: string; // 版本 ID
|
||||||
refId?: string; // 版本来源
|
refId?: string; // 版本来源
|
||||||
protocols?: string[]; // 协议集合
|
protocols?: string[]; // 协议集合
|
||||||
|
combineSearch?: FilterResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移动模块树
|
// 移动模块树
|
||||||
|
|
|
@ -153,12 +153,12 @@ export function desensitize(str: string): string {
|
||||||
* @param str 标题的动态内容
|
* @param str 标题的动态内容
|
||||||
* @returns 转化后的字符串
|
* @returns 转化后的字符串
|
||||||
*/
|
*/
|
||||||
export function characterLimit(str?: string): string {
|
export function characterLimit(str?: string, length?: number): string {
|
||||||
if (!str) return '';
|
if (!str) return '';
|
||||||
if (str.length <= 20) {
|
if (str.length <= (length || 20)) {
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
return `${str.slice(0, 20 - 3)}...`;
|
return `${str.slice(0, length || 20 - 3)}...`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -271,7 +271,7 @@
|
||||||
dataIndex: 'operation',
|
dataIndex: 'operation',
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
title: hasAnyPermission(['PROJECT_API_REPORT:READ+DELETE']) ? 'common.operation' : '',
|
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,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -135,7 +135,7 @@
|
||||||
const keyword = ref<string>('');
|
const keyword = ref<string>('');
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const { openNewPage } = useOpenNewPage();
|
const { openNewPage, openNewPageWithParams } = useOpenNewPage();
|
||||||
|
|
||||||
type ReportShowType = 'All' | 'INDEPENDENT' | 'INTEGRATED';
|
type ReportShowType = 'All' | 'INDEPENDENT' | 'INTEGRATED';
|
||||||
const showType = ref<ReportShowType>('All');
|
const showType = ref<ReportShowType>('All');
|
||||||
|
@ -340,6 +340,11 @@
|
||||||
eventTag: 'batchStop',
|
eventTag: 'batchStop',
|
||||||
permission: ['PROJECT_TEST_PLAN_REPORT:READ+DELETE'],
|
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,
|
projectId: appStore.currentProjectId,
|
||||||
};
|
};
|
||||||
|
|
||||||
openModal({
|
if (event.eventTag === 'batchExport') {
|
||||||
type: 'error',
|
openNewPageWithParams(
|
||||||
title: t('report.delete.tip', {
|
FullPageEnum.FULL_PAGE_TEST_PLAN_EXPORT_PDF,
|
||||||
count: params?.currentSelectCount || params?.selectedIds?.length,
|
{
|
||||||
}),
|
type: showType.value,
|
||||||
content: '',
|
},
|
||||||
okText: t('common.confirmDelete'),
|
batchParams.value
|
||||||
cancelText: t('common.cancel'),
|
);
|
||||||
okButtonProps: {
|
} else if (event.eventTag === 'batchStop') {
|
||||||
status: 'danger',
|
openModal({
|
||||||
},
|
type: 'error',
|
||||||
onBeforeOk: async () => {
|
title: t('report.delete.tip', {
|
||||||
try {
|
count: params?.currentSelectCount || params?.selectedIds?.length,
|
||||||
await reportBathDelete(batchParams.value);
|
}),
|
||||||
Message.success(t('apiTestDebug.deleteSuccess'));
|
content: '',
|
||||||
resetSelector();
|
okText: t('common.confirmDelete'),
|
||||||
initData();
|
cancelText: t('common.cancel'),
|
||||||
} catch (error) {
|
okButtonProps: {
|
||||||
// eslint-disable-next-line no-console
|
status: 'danger',
|
||||||
console.log(error);
|
},
|
||||||
}
|
onBeforeOk: async () => {
|
||||||
},
|
try {
|
||||||
hideCancel: false,
|
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() {
|
function searchList() {
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<a-spin :loading="loading" :tip="t('report.detail.exportingPdf')" class="report-detail-container">
|
<a-spin
|
||||||
|
:loading="batchLoading || loading"
|
||||||
|
:tip="batchLoading ? batchExportTip : t('report.detail.exportingPdf')"
|
||||||
|
class="report-detail-container"
|
||||||
|
>
|
||||||
<div id="report-detail" class="report-detail">
|
<div id="report-detail" class="report-detail">
|
||||||
<div class="report-header">
|
<div class="report-header">
|
||||||
<div class="flex-1 break-all">{{ detail.name }}</div>
|
<div class="flex-1 break-all">{{ detail.name }}</div>
|
||||||
|
@ -139,7 +143,9 @@
|
||||||
getReportShareBugList,
|
getReportShareBugList,
|
||||||
getReportShareFeatureCaseList,
|
getReportShareFeatureCaseList,
|
||||||
getScenarioPage,
|
getScenarioPage,
|
||||||
|
logTestPlanReportBatchExport,
|
||||||
logTestPlanReportExport,
|
logTestPlanReportExport,
|
||||||
|
testPlanBatchReportExportGetIds,
|
||||||
} from '@/api/modules/test-plan/report';
|
} from '@/api/modules/test-plan/report';
|
||||||
import {
|
import {
|
||||||
commonConfig,
|
commonConfig,
|
||||||
|
@ -151,8 +157,9 @@
|
||||||
} from '@/config/testPlan';
|
} from '@/config/testPlan';
|
||||||
import exportPDF, { PAGE_PDF_WIDTH_RATIO, PdfTableConfig } from '@/hooks/useExportPDF';
|
import exportPDF, { PAGE_PDF_WIDTH_RATIO, PdfTableConfig } from '@/hooks/useExportPDF';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import { addCommasToNumber } from '@/utils';
|
import { addCommasToNumber, characterLimit } from '@/utils';
|
||||||
|
|
||||||
|
import { BatchApiParams } from '@/models/common';
|
||||||
import type {
|
import type {
|
||||||
configItem,
|
configItem,
|
||||||
countDetail,
|
countDetail,
|
||||||
|
@ -175,13 +182,6 @@
|
||||||
const reportId = ref<string>(route.query.id as string);
|
const reportId = ref<string>(route.query.id as string);
|
||||||
const isGroup = computed(() => route.query.type === 'GROUP');
|
const isGroup = computed(() => route.query.type === 'GROUP');
|
||||||
const loading = ref<boolean>(true);
|
const loading = ref<boolean>(true);
|
||||||
const richText = ref<{ summary: string; richTextTmpFileIds?: string[] }>({
|
|
||||||
summary: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
const reportForm = ref({
|
|
||||||
reportName: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分享share
|
* 分享share
|
||||||
|
@ -620,149 +620,165 @@
|
||||||
return status && iconTypeStatus[status] ? iconTypeStatus[status] : iconTypeStatus.DEFAULT;
|
return status && iconTypeStatus[status] ? iconTypeStatus[status] : iconTypeStatus.DEFAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getDetail() {
|
async function realExportPdf(name: string) {
|
||||||
|
const tableArr: PdfTableConfig[] = [];
|
||||||
|
if (!isDefaultLayout.value) {
|
||||||
|
await getDefaultLayout();
|
||||||
|
} else {
|
||||||
|
innerCardList.value = (isGroup.value ? cloneDeep(defaultGroupConfig) : cloneDeep(defaultSingleConfig)).filter(
|
||||||
|
(e: any) => [ReportCardTypeEnum.CUSTOM_CARD, ReportCardTypeEnum.SUMMARY].includes(e.value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (isGroup.value) {
|
||||||
|
await initGroupList();
|
||||||
|
tableArr.push({
|
||||||
|
tableId: 'group',
|
||||||
|
columnStyles: {
|
||||||
|
testPlanName: { cellWidth: 710 / PAGE_PDF_WIDTH_RATIO },
|
||||||
|
resultStatus: { cellWidth: 130 / PAGE_PDF_WIDTH_RATIO },
|
||||||
|
passThreshold: { cellWidth: 130 / PAGE_PDF_WIDTH_RATIO },
|
||||||
|
passRate: { cellWidth: 130 / PAGE_PDF_WIDTH_RATIO },
|
||||||
|
caseTotal: { cellWidth: 90 / PAGE_PDF_WIDTH_RATIO },
|
||||||
|
},
|
||||||
|
columns: groupColumns.map((item) => ({
|
||||||
|
...item,
|
||||||
|
title: t(item.title as string),
|
||||||
|
dataKey: item.dataIndex,
|
||||||
|
})) as ColumnInput[],
|
||||||
|
body: fullGroupList.value.map((e) => ({
|
||||||
|
...e,
|
||||||
|
resultStatus: t(getExecutionResult(e.resultStatus).label),
|
||||||
|
passRate: `${e.passRate}%`,
|
||||||
|
passThreshold: `${e.passThreshold}%`,
|
||||||
|
})) as RowInput[],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await Promise.all([initBugList(), initCaseList(), initApiList(), initScenarioList()]);
|
||||||
|
if (fullBugList.value.length > 0) {
|
||||||
|
tableArr.push({
|
||||||
|
tableId: 'bug',
|
||||||
|
columnStyles: {
|
||||||
|
num: { cellWidth: 120 / PAGE_PDF_WIDTH_RATIO },
|
||||||
|
title: { cellWidth: 600 / PAGE_PDF_WIDTH_RATIO },
|
||||||
|
status: { cellWidth: 110 / PAGE_PDF_WIDTH_RATIO },
|
||||||
|
handleUserName: { cellWidth: 270 / PAGE_PDF_WIDTH_RATIO },
|
||||||
|
relationCaseCount: { cellWidth: 90 / PAGE_PDF_WIDTH_RATIO },
|
||||||
|
},
|
||||||
|
columns: bugColumns.map((item) => ({
|
||||||
|
...item,
|
||||||
|
title: t(item.title as string),
|
||||||
|
dataKey: item.dataIndex,
|
||||||
|
})) as ColumnInput[],
|
||||||
|
body: fullBugList.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (fullCaseList.value.length > 0) {
|
||||||
|
const columnStyles: Record<string, any> = {
|
||||||
|
num: { cellWidth: 100 / PAGE_PDF_WIDTH_RATIO },
|
||||||
|
name: { cellWidth: 240 / PAGE_PDF_WIDTH_RATIO },
|
||||||
|
executeResult: { cellWidth: 110 / PAGE_PDF_WIDTH_RATIO },
|
||||||
|
testPlanName: { cellWidth: 240 / PAGE_PDF_WIDTH_RATIO },
|
||||||
|
priority: { cellWidth: 110 / PAGE_PDF_WIDTH_RATIO },
|
||||||
|
moduleName: { cellWidth: 200 / PAGE_PDF_WIDTH_RATIO },
|
||||||
|
executeUser: { cellWidth: 100 / PAGE_PDF_WIDTH_RATIO },
|
||||||
|
relationCaseCount: { cellWidth: 90 / PAGE_PDF_WIDTH_RATIO },
|
||||||
|
};
|
||||||
|
if (!isGroup.value) {
|
||||||
|
delete columnStyles.testPlanName;
|
||||||
|
columnStyles.name.cellWidth = 480 / PAGE_PDF_WIDTH_RATIO;
|
||||||
|
}
|
||||||
|
tableArr.push({
|
||||||
|
tableId: 'case',
|
||||||
|
columnStyles,
|
||||||
|
columns: caseColumns.value.map((item) => ({
|
||||||
|
...item,
|
||||||
|
title: t(item.title as string),
|
||||||
|
dataKey: item.dataIndex,
|
||||||
|
})) as ColumnInput[],
|
||||||
|
body: fullCaseList.value.map((e: any) => ({
|
||||||
|
...e,
|
||||||
|
executeResult: t(lastExecuteResultMap[e.executeResult]?.statusText || '-'),
|
||||||
|
executeUser: e.executeUser?.name || '-',
|
||||||
|
})) as RowInput[],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const apiColumnStyles: Record<string, any> = {
|
||||||
|
num: { cellWidth: 150 / PAGE_PDF_WIDTH_RATIO },
|
||||||
|
name: { cellWidth: 240 / PAGE_PDF_WIDTH_RATIO },
|
||||||
|
executeResult: { cellWidth: 110 / PAGE_PDF_WIDTH_RATIO },
|
||||||
|
testPlanName: { cellWidth: 230 / PAGE_PDF_WIDTH_RATIO },
|
||||||
|
priority: { cellWidth: 80 / PAGE_PDF_WIDTH_RATIO },
|
||||||
|
moduleName: { cellWidth: 170 / PAGE_PDF_WIDTH_RATIO },
|
||||||
|
executeUser: { cellWidth: 120 / PAGE_PDF_WIDTH_RATIO },
|
||||||
|
bugCount: { cellWidth: 90 / PAGE_PDF_WIDTH_RATIO },
|
||||||
|
};
|
||||||
|
if (!isGroup.value) {
|
||||||
|
delete apiColumnStyles.testPlanName;
|
||||||
|
apiColumnStyles.name.cellWidth = 480 / PAGE_PDF_WIDTH_RATIO;
|
||||||
|
}
|
||||||
|
if (fullApiList.value.length > 0) {
|
||||||
|
tableArr.push({
|
||||||
|
tableId: 'apiCase',
|
||||||
|
columnStyles: apiColumnStyles,
|
||||||
|
columns: apiColumns.value.map((item) => ({
|
||||||
|
...item,
|
||||||
|
title: t(item.title as string),
|
||||||
|
dataKey: item.dataIndex,
|
||||||
|
})) as ColumnInput[],
|
||||||
|
body: fullApiList.value.map((e: any) => ({
|
||||||
|
...e,
|
||||||
|
executeResult: t(lastExecuteResultMap[e.executeResult]?.statusText || '-'),
|
||||||
|
executeUser: e.executeUser?.name || '-',
|
||||||
|
})) as RowInput[],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (fullScenarioList.value.length > 0) {
|
||||||
|
tableArr.push({
|
||||||
|
tableId: 'scenario',
|
||||||
|
columnStyles: apiColumnStyles,
|
||||||
|
columns: apiColumns.value.map((item) => ({
|
||||||
|
...item,
|
||||||
|
title: t(item.title as string),
|
||||||
|
dataKey: item.dataIndex,
|
||||||
|
})) as ColumnInput[],
|
||||||
|
body: fullScenarioList.value.map((e: any) => ({
|
||||||
|
...e,
|
||||||
|
executeResult: t(lastExecuteResultMap[e.executeResult]?.statusText || '-'),
|
||||||
|
executeUser: e.executeUser?.name || '-',
|
||||||
|
})) as RowInput[],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await nextTick(async () => {
|
||||||
|
exportPDF(
|
||||||
|
name,
|
||||||
|
'report-detail',
|
||||||
|
tableArr,
|
||||||
|
{
|
||||||
|
group: t('report.detail.subPlanDetails'),
|
||||||
|
bug: t('report.detail.bugDetails'),
|
||||||
|
case: t('report.detail.featureCaseDetails'),
|
||||||
|
apiCase: t('report.detail.apiCaseDetails'),
|
||||||
|
scenario: t('report.detail.scenarioCaseDetails'),
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
loading.value = false;
|
||||||
|
Message.success(t('report.detail.exportPdfSuccess', { name: characterLimit(name, 50) }));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getDetail(_reportId?: string) {
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
detail.value = await getReportDetail(reportId.value);
|
if (_reportId) {
|
||||||
const { defaultLayout, id, name, summary } = detail.value;
|
reportId.value = _reportId;
|
||||||
isDefaultLayout.value = defaultLayout;
|
|
||||||
richText.value.summary = summary;
|
|
||||||
reportForm.value.reportName = name;
|
|
||||||
initOptionsData();
|
|
||||||
if (!defaultLayout && id) {
|
|
||||||
await getDefaultLayout();
|
|
||||||
await initGroupList();
|
|
||||||
nextTick(async () => {
|
|
||||||
exportPDF(
|
|
||||||
name,
|
|
||||||
'report-detail',
|
|
||||||
[
|
|
||||||
{
|
|
||||||
columnStyles: {
|
|
||||||
testPlanName: { cellWidth: 710 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
resultStatus: { cellWidth: 130 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
passThreshold: { cellWidth: 130 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
passRate: { cellWidth: 130 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
caseTotal: { cellWidth: 90 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
},
|
|
||||||
columns: groupColumns.map((item) => ({
|
|
||||||
...item,
|
|
||||||
title: t(item.title as string),
|
|
||||||
dataKey: item.dataIndex,
|
|
||||||
})) as ColumnInput[],
|
|
||||||
body: fullGroupList.value.map((e) => ({
|
|
||||||
...e,
|
|
||||||
resultStatus: t(getExecutionResult(e.resultStatus).label),
|
|
||||||
passRate: `${e.passRate}%`,
|
|
||||||
passThreshold: `${e.passThreshold}%`,
|
|
||||||
})) as RowInput[],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
() => {
|
|
||||||
loading.value = false;
|
|
||||||
Message.success(t('report.detail.exportPdfSuccess'));
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
innerCardList.value = (isGroup.value ? cloneDeep(defaultGroupConfig) : cloneDeep(defaultSingleConfig)).filter(
|
|
||||||
(e: any) => [ReportCardTypeEnum.CUSTOM_CARD, ReportCardTypeEnum.SUMMARY].includes(e.value)
|
|
||||||
);
|
|
||||||
await Promise.all([initBugList(), initCaseList(), initApiList(), initScenarioList()]);
|
|
||||||
const tableArr: PdfTableConfig[] = [];
|
|
||||||
if (fullBugList.value.length > 0) {
|
|
||||||
tableArr.push({
|
|
||||||
columnStyles: {
|
|
||||||
num: { cellWidth: 120 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
title: { cellWidth: 600 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
status: { cellWidth: 110 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
handleUserName: { cellWidth: 270 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
relationCaseCount: { cellWidth: 90 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
},
|
|
||||||
columns: bugColumns.map((item) => ({
|
|
||||||
...item,
|
|
||||||
title: t(item.title as string),
|
|
||||||
dataKey: item.dataIndex,
|
|
||||||
})) as ColumnInput[],
|
|
||||||
body: fullBugList.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (fullCaseList.value.length > 0) {
|
|
||||||
tableArr.push({
|
|
||||||
columnStyles: {
|
|
||||||
num: { cellWidth: 100 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
name: { cellWidth: 480 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
executeResult: { cellWidth: 110 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
priority: { cellWidth: 110 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
moduleName: { cellWidth: 200 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
executeUser: { cellWidth: 100 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
relationCaseCount: { cellWidth: 90 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
},
|
|
||||||
columns: caseColumns.value.map((item) => ({
|
|
||||||
...item,
|
|
||||||
title: t(item.title as string),
|
|
||||||
dataKey: item.dataIndex,
|
|
||||||
})) as ColumnInput[],
|
|
||||||
body: fullCaseList.value.map((e: any) => ({
|
|
||||||
...e,
|
|
||||||
executeResult: t(lastExecuteResultMap[e.executeResult]?.statusText || '-'),
|
|
||||||
executeUser: e.executeUser?.name || '-',
|
|
||||||
})) as RowInput[],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (fullApiList.value.length > 0) {
|
|
||||||
tableArr.push({
|
|
||||||
columnStyles: {
|
|
||||||
num: { cellWidth: 130 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
name: { cellWidth: 450 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
executeResult: { cellWidth: 110 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
priority: { cellWidth: 80 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
moduleName: { cellWidth: 200 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
executeUser: { cellWidth: 130 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
bugCount: { cellWidth: 90 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
},
|
|
||||||
columns: apiColumns.value.map((item) => ({
|
|
||||||
...item,
|
|
||||||
title: t(item.title as string),
|
|
||||||
dataKey: item.dataIndex,
|
|
||||||
})) as ColumnInput[],
|
|
||||||
body: fullApiList.value.map((e: any) => ({
|
|
||||||
...e,
|
|
||||||
executeResult: t(lastExecuteResultMap[e.executeResult]?.statusText || '-'),
|
|
||||||
executeUser: e.executeUser?.name || '-',
|
|
||||||
})) as RowInput[],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (apiColumns.value.length > 0) {
|
|
||||||
tableArr.push({
|
|
||||||
columnStyles: {
|
|
||||||
num: { cellWidth: 100 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
name: { cellWidth: 480 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
executeResult: { cellWidth: 110 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
priority: { cellWidth: 80 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
moduleName: { cellWidth: 200 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
executeUser: { cellWidth: 130 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
bugCount: { cellWidth: 90 / PAGE_PDF_WIDTH_RATIO },
|
|
||||||
},
|
|
||||||
columns: apiColumns.value.map((item) => ({
|
|
||||||
...item,
|
|
||||||
title: t(item.title as string),
|
|
||||||
dataKey: item.dataIndex,
|
|
||||||
})) as ColumnInput[],
|
|
||||||
body: fullScenarioList.value.map((e: any) => ({
|
|
||||||
...e,
|
|
||||||
executeResult: t(lastExecuteResultMap[e.executeResult]?.statusText || '-'),
|
|
||||||
executeUser: e.executeUser?.name || '-',
|
|
||||||
})) as RowInput[],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
nextTick(async () => {
|
|
||||||
exportPDF(name, 'report-detail', tableArr, () => {
|
|
||||||
loading.value = false;
|
|
||||||
Message.success(t('report.detail.exportPdfSuccess'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
detail.value = await getReportDetail(reportId.value);
|
||||||
|
const { defaultLayout, name } = detail.value;
|
||||||
|
isDefaultLayout.value = defaultLayout;
|
||||||
|
initOptionsData();
|
||||||
|
await realExportPdf(name);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
@ -770,13 +786,59 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function logExport() {
|
async function logExport(params?: BatchApiParams) {
|
||||||
await logTestPlanReportExport(route.query.id as string);
|
if (params) {
|
||||||
|
await logTestPlanReportBatchExport(params);
|
||||||
|
} else {
|
||||||
|
await logTestPlanReportExport(route.query.id as string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const batchLoading = ref<boolean>(false);
|
||||||
|
const batchIds = ref<string[]>([]);
|
||||||
|
const exportCurrent = ref<number>(0);
|
||||||
|
const exportTotal = ref<number>(0);
|
||||||
|
const batchExportTip = computed(() =>
|
||||||
|
t('report.detail.batchExportingPdf', { current: exportCurrent.value, total: exportTotal.value })
|
||||||
|
);
|
||||||
|
|
||||||
|
async function initBatchIds(params: BatchApiParams) {
|
||||||
|
try {
|
||||||
|
batchLoading.value = true;
|
||||||
|
batchIds.value = await testPlanBatchReportExportGetIds(params);
|
||||||
|
exportTotal.value = batchIds.value.length;
|
||||||
|
while (batchIds.value.length > 0) {
|
||||||
|
exportCurrent.value += 1;
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await getDetail(batchIds.value.shift());
|
||||||
|
}
|
||||||
|
nextTick(() => {
|
||||||
|
// 等最后一条成功提示后清空
|
||||||
|
Message.clear();
|
||||||
|
Message.success(t('report.detail.batchExportPdfSuccess'));
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
batchLoading.value = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
getDetail();
|
window.addEventListener('message', (event) => {
|
||||||
logExport();
|
if (event.origin !== window.location.origin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 初始化批量导出报告 id 集合
|
||||||
|
const batchParams = event.data;
|
||||||
|
initBatchIds(batchParams);
|
||||||
|
logExport(batchParams);
|
||||||
|
});
|
||||||
|
if (reportId.value) {
|
||||||
|
getDetail();
|
||||||
|
logExport();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -59,5 +59,7 @@ export default {
|
||||||
'report.detail.manualGenReportSuccess': 'The custom generated report was successful',
|
'report.detail.manualGenReportSuccess': 'The custom generated report was successful',
|
||||||
'report.detail.exportPdf': 'Export PDF',
|
'report.detail.exportPdf': 'Export PDF',
|
||||||
'report.detail.exportingPdf': 'Exporting PDF report...',
|
'report.detail.exportingPdf': 'Exporting PDF report...',
|
||||||
'report.detail.exportPdfSuccess': 'PDF report exported successfully',
|
'report.detail.exportPdfSuccess': '{name} report exported successfully',
|
||||||
|
'report.detail.batchExportPdfSuccess': 'Batch export of PDF reports completed',
|
||||||
|
'report.detail.batchExportingPdf': 'Export progress: {current}/{total}',
|
||||||
};
|
};
|
||||||
|
|
|
@ -58,6 +58,8 @@ export default {
|
||||||
'report.detail.reportNameNotEmpty': '报告名称不能为空',
|
'report.detail.reportNameNotEmpty': '报告名称不能为空',
|
||||||
'report.detail.manualGenReportSuccess': '自定义生成报告成功',
|
'report.detail.manualGenReportSuccess': '自定义生成报告成功',
|
||||||
'report.detail.exportPdf': '导出 PDF',
|
'report.detail.exportPdf': '导出 PDF',
|
||||||
'report.detail.exportingPdf': 'PDF报告导出中...',
|
'report.detail.exportingPdf': 'PDF 报告导出中...',
|
||||||
'report.detail.exportPdfSuccess': 'PDF报告导出成功',
|
'report.detail.exportPdfSuccess': '{name} 报告导出成功',
|
||||||
|
'report.detail.batchExportPdfSuccess': '批量导出 PDF 报告已完成',
|
||||||
|
'report.detail.batchExportingPdf': '导出进度:{current}/{total}',
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue