feat(报告): 接口测试批量导出 PDF 报告

This commit is contained in:
baiqi 2024-09-14 11:20:31 +08:00 committed by Craftsman
parent 36d920e6d3
commit cef35278b9
9 changed files with 189 additions and 49 deletions

View File

@ -43,6 +43,7 @@ import {
ExecuteCaseUrl,
ExportDefinitionUrl,
GetApiDownloadFileUrl,
GetCaseBatchExportParamsUrl,
GetCaseDetailUrl,
GetCaseReportByIdUrl,
GetCaseReportDetailUrl,
@ -629,3 +630,8 @@ export function logCaseReportExport(reportId: string) {
export function logCaseReportBatchExport(data: BatchApiParams) {
return MSR.post({ url: `${CaseBatchExportLogUrl}`, data });
}
// 接口用例导出报告id集合
export function getCaseBatchExportParams(data: BatchApiParams) {
return MSR.post({ url: `${GetCaseBatchExportParamsUrl}`, data });
}

View File

@ -20,6 +20,7 @@ import {
FollowScenarioUrl,
GetModuleCountUrl,
GetModuleTreeUrl,
GetScenarioBatchExportParamsUrl,
GetScenarioStepUrl,
GetScenarioUrl,
GetStepProjectInfoUrl,
@ -317,3 +318,8 @@ export function logScenarioReportExport(reportId: string) {
export function logScenarioReportBatchExport(data: BatchApiParams) {
return MSR.post({ url: `${ScenarioBatchExportLogUrl}`, data });
}
// 场景导出报告id集合
export function getScenarioBatchExportParams(data: BatchApiParams) {
return MSR.post({ url: `${GetScenarioBatchExportParamsUrl}`, data });
}

View File

@ -98,6 +98,7 @@ export const GetCaseReportByIdUrl = '/api/report/case/get/'; // 接口用例报
export const GetCaseReportDetailUrl = '/api/report/case/get/detail/'; // 接口用例报告获取
export const CaseExportLogUrl = '/api/report/case/export'; // 接口用例导出报告日志记录
export const CaseBatchExportLogUrl = '/api/report/case/batch-export'; // 接口用例批量导出报告日志记录
export const GetCaseBatchExportParamsUrl = '/api/report/case/batch-param'; // 接口用例批量导出报告id集合
/**
*

View File

@ -43,3 +43,4 @@ export const ExecuteHistoryUrl = '/api/scenario/execute/page'; // 场景执行
export const ScenarioHistoryUrl = '/api/scenario/operation-history/page'; // 场景变更历史
export const ScenarioExportLogUrl = '/api/report/scenario/export'; // 场景导出报告日志记录
export const ScenarioBatchExportLogUrl = '/api/report/scenario/batch-export'; // 场景批量导出报告日志记录
export const GetScenarioBatchExportParamsUrl = '/api/report/scenario/batch-param'; // 场景批量导出报告id 集合

View File

@ -146,7 +146,7 @@
const appStore = useAppStore();
const tableStore = useTableStore();
const route = useRoute();
const { openNewPage } = useOpenNewPage();
const { openNewPage, openNewPageWithParams } = useOpenNewPage();
const { t } = useI18n();
const props = defineProps<{
@ -271,7 +271,7 @@
dataIndex: 'operation',
fixed: 'right',
title: hasAnyPermission(['PROJECT_API_REPORT:READ+DELETE']) ? 'common.operation' : '',
width: hasAnyPermission(['PROJECT_API_REPORT:READ+DELETE']) ? 100 : 50,
width: hasAnyPermission(['PROJECT_API_REPORT:READ+DELETE']) ? 130 : 50,
},
];
@ -340,6 +340,11 @@
eventTag: 'batchStop',
permission: ['PROJECT_API_REPORT:READ+DELETE'],
},
{
label: 'common.export',
eventTag: 'batchExport',
permission: ['PROJECT_TEST_PLAN_REPORT:READ+EXPORT'],
},
],
};
@ -364,31 +369,42 @@
},
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(props.moduleType, 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(
props.moduleType === ReportEnum.API_SCENARIO_REPORT
? FullPageEnum.FULL_PAGE_SCENARIO_EXPORT_PDF
: FullPageEnum.FULL_PAGE_API_CASE_EXPORT_PDF,
{
type: showType.value,
},
batchParams.value
);
} else {
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(props.moduleType, batchParams.value);
Message.success(t('apiTestDebug.deleteSuccess'));
resetSelector();
initData();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
},
hideCancel: false,
});
}
};
function searchList() {

View File

@ -235,6 +235,9 @@
const activeTab = ref<'tiled' | 'tab'>('tiled');
function getRote(count: number, countTotal: number) {
if (Number.isNaN(count) || Number.isNaN(countTotal) || countTotal === 0) {
return '0.00';
}
return (((count || 0) / countTotal) * 100).toFixed(2);
}
function initOptionsData() {

View File

@ -77,7 +77,11 @@
<div v-show="step.code" class="mr-2 text-[var(--color-text-4)]">
{{ t('report.detail.api.statusCode') }}</div
>
<div v-show="step.code" class="max-w-[200px]" :style="{ color: statusCodeColor(step.code) }">
<div
v-show="step.code"
class="max-w-[200px] break-all"
:style="{ color: statusCodeColor(step.code) }"
>
{{ step.code || '-' }}
</div>
</div>

View File

@ -1,7 +1,13 @@
<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 class="mb-[16px] rounded-[var(--border-radius-small)] bg-white p-[16px]">{{ reportStepDetail?.name }}</div>
<div class="mb-[16px] break-all rounded-[var(--border-radius-small)] bg-white p-[16px]">
{{ reportStepDetail?.name }}
</div>
<CaseReportCom :detail-info="reportStepDetail" is-export />
</div>
</a-spin>
@ -13,12 +19,17 @@
import CaseReportCom from './component/caseReportCom.vue';
import { logCaseReportExport } from '@/api/modules/api-test/management';
import {
getCaseBatchExportParams,
logCaseReportBatchExport,
logCaseReportExport,
} from '@/api/modules/api-test/management';
import { reportCaseDetail } from '@/api/modules/api-test/report';
import { useI18n } from '@/hooks/useI18n';
import exportPDF from '@/utils/exportPdf';
import { ReportDetail } from '@/models/apiTest/report';
import { BatchApiParams } from '@/models/common';
const route = useRoute();
const { t } = useI18n();
@ -26,10 +37,10 @@
const loading = ref(false);
const reportStepDetail = ref<ReportDetail>();
async function initReportDetail() {
async function initReportDetail(id?: string) {
try {
loading.value = true;
reportStepDetail.value = await reportCaseDetail(route.query.id as string);
reportStepDetail.value = await reportCaseDetail(id || (route.query.id as string));
setTimeout(() => {
nextTick(async () => {
await exportPDF(reportStepDetail.value?.name || '', 'report-detail');
@ -43,16 +54,55 @@
}
}
async function logExport() {
try {
async function logExport(params?: BatchApiParams) {
if (params) {
await logCaseReportBatchExport(params);
} else {
await logCaseReportExport(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 getCaseBatchExportParams(params);
exportTotal.value = batchIds.value.length;
while (batchIds.value.length > 0) {
exportCurrent.value += 1;
// eslint-disable-next-line no-await-in-loop
await initReportDetail(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(() => {
window.addEventListener('message', (event) => {
if (event.origin !== window.location.origin) {
return;
}
// id
const batchParams = event.data;
initBatchIds(batchParams);
logExport(batchParams);
});
if (route.query.id) {
initReportDetail();
logExport();

View File

@ -1,7 +1,13 @@
<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 class="mb-[16px] rounded-[var(--border-radius-small)] bg-white p-[16px]">{{ reportStepDetail?.name }}</div>
<div class="mb-[16px] break-all rounded-[var(--border-radius-small)] bg-white p-[16px]">
{{ reportStepDetail?.name }}
</div>
<ScenarioCom :detail-info="reportStepDetail" is-export />
</div>
</a-spin>
@ -14,11 +20,16 @@
import ScenarioCom from './component/scenarioCom.vue';
import { reportScenarioDetail } from '@/api/modules/api-test/report';
import { logScenarioReportExport } from '@/api/modules/api-test/scenario';
import {
getScenarioBatchExportParams,
logScenarioReportBatchExport,
logScenarioReportExport,
} from '@/api/modules/api-test/scenario';
import { useI18n } from '@/hooks/useI18n';
import exportPDF from '@/utils/exportPdf';
import { ReportDetail } from '@/models/apiTest/report';
import { BatchApiParams } from '@/models/common';
const route = useRoute();
const { t } = useI18n();
@ -26,33 +37,75 @@
const loading = ref(false);
const reportStepDetail = ref<ReportDetail>();
async function initReportDetail() {
async function initReportDetail(id?: string) {
try {
loading.value = true;
reportStepDetail.value = await reportScenarioDetail(route.query.id as string);
setTimeout(() => {
nextTick(async () => {
await exportPDF(reportStepDetail.value?.name || '', 'report-detail');
loading.value = false;
Message.success(t('report.detail.exportPdfSuccess'));
});
}, 500);
reportStepDetail.value = await reportScenarioDetail(id || (route.query.id as string));
await new Promise((resolve) => {
setTimeout(async () => {
await nextTick(async () => {
await exportPDF(reportStepDetail.value?.name || '', 'report-detail');
loading.value = false;
Message.success(t('report.detail.exportPdfSuccess'));
resolve(true);
});
}, 500); // TODO: pdf 500ms
});
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
async function logExport() {
try {
async function logExport(params?: BatchApiParams) {
if (params) {
await logScenarioReportBatchExport(params);
} else {
await logScenarioReportExport(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 getScenarioBatchExportParams(params);
exportTotal.value = batchIds.value.length;
while (batchIds.value.length > 0) {
exportCurrent.value += 1;
// eslint-disable-next-line no-await-in-loop
await initReportDetail(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(() => {
window.addEventListener('message', (event) => {
if (event.origin !== window.location.origin) {
return;
}
// id
const batchParams = event.data;
initBatchIds(batchParams);
logExport(batchParams);
});
if (route.query.id) {
initReportDetail();
logExport();