feat(报告): 测试计划独立报告导出 pdf 终版
This commit is contained in:
parent
7ed7a70e84
commit
40bdb9fe2e
|
@ -3,4 +3,5 @@
|
||||||
dist
|
dist
|
||||||
postcss.config.js
|
postcss.config.js
|
||||||
*.md
|
*.md
|
||||||
/src/assets/icon-font/iconfont.js
|
/src/assets/icon-font/iconfont.js
|
||||||
|
/src/assets/fonts/AlibabaPuHuiTi-3-55-Regular-normal.js
|
|
@ -66,6 +66,7 @@
|
||||||
"json-schema-traverse": "^1.0.0",
|
"json-schema-traverse": "^1.0.0",
|
||||||
"jsonpath-plus": "^8.1.0",
|
"jsonpath-plus": "^8.1.0",
|
||||||
"jspdf": "^2.5.1",
|
"jspdf": "^2.5.1",
|
||||||
|
"jspdf-autotable": "^3.8.3",
|
||||||
"localforage": "^1.10.0",
|
"localforage": "^1.10.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"lossless-json": "^4.0.1",
|
"lossless-json": "^4.0.1",
|
||||||
|
@ -128,9 +129,9 @@
|
||||||
"less": "^4.2.0",
|
"less": "^4.2.0",
|
||||||
"less-loader": "^11.1.4",
|
"less-loader": "^11.1.4",
|
||||||
"lint-staged": "^13.3.0",
|
"lint-staged": "^13.3.0",
|
||||||
"postcss": "^8.4.39",
|
"postcss": "^8.4.45",
|
||||||
"postcss-html": "^1.7.0",
|
"postcss-html": "^1.7.0",
|
||||||
"postcss-import": "^15.1.0",
|
"postcss-import": "^16.1.0",
|
||||||
"postcss-less": "^6.0.0",
|
"postcss-less": "^6.0.0",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
"prettier-plugin-tailwindcss": "^0.3.0",
|
"prettier-plugin-tailwindcss": "^0.3.0",
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,12 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="props.executeResult" class="flex items-center">
|
<div v-if="props.executeResult" class="flex items-center">
|
||||||
<!-- <MsIcon
|
<MsIcon
|
||||||
:type="lastExecuteResultMap[props.executeResult]?.icon || ''"
|
:type="lastExecuteResultMap[props.executeResult]?.icon || ''"
|
||||||
class="mr-1"
|
class="mr-1"
|
||||||
:size="16"
|
:size="16"
|
||||||
:style="{ color: lastExecuteResultMap[props.executeResult]?.color }"
|
:style="{ color: lastExecuteResultMap[props.executeResult]?.color }"
|
||||||
></MsIcon> -->
|
></MsIcon>
|
||||||
<span class="text-[14px]">{{ lastExecuteResultMap[props.executeResult]?.statusText || '-' }}</span>
|
<span class="text-[14px]">{{ t(lastExecuteResultMap[props.executeResult]?.statusText || '-') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -15,7 +15,9 @@
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
import { LastExecuteResults, StatusType } from '@/enums/caseEnum';
|
import { LastExecuteResults } from '@/enums/caseEnum';
|
||||||
|
|
||||||
|
import { lastExecuteResultMap } from './utils';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
@ -23,33 +25,6 @@
|
||||||
executeResult?: LastExecuteResults;
|
executeResult?: LastExecuteResults;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const lastExecuteResultMap = {
|
|
||||||
PENDING: {
|
|
||||||
label: 'PENDING',
|
|
||||||
icon: StatusType.PENDING,
|
|
||||||
statusText: t('common.unExecute'),
|
|
||||||
color: 'var(--color-text-brand)',
|
|
||||||
},
|
|
||||||
SUCCESS: {
|
|
||||||
label: 'SUCCESS',
|
|
||||||
icon: StatusType.SUCCESS,
|
|
||||||
statusText: t('common.success'),
|
|
||||||
color: '',
|
|
||||||
},
|
|
||||||
BLOCKED: {
|
|
||||||
label: 'BLOCKED',
|
|
||||||
icon: StatusType.BLOCKED,
|
|
||||||
statusText: t('common.block'),
|
|
||||||
color: 'var(--color-fill-p-3)',
|
|
||||||
},
|
|
||||||
ERROR: {
|
|
||||||
label: 'ERROR',
|
|
||||||
icon: StatusType.ERROR,
|
|
||||||
statusText: t('common.fail'),
|
|
||||||
color: '',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// const status = computed(() => {
|
// const status = computed(() => {
|
||||||
// if (props.executeResult) {
|
// if (props.executeResult) {
|
||||||
// const config = lastExecuteResultMap[props.executeResult];
|
// const config = lastExecuteResultMap[props.executeResult];
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
import { getModuleTreeCounts } from '@/api/modules/bug-management';
|
import { getModuleTreeCounts } from '@/api/modules/bug-management';
|
||||||
import { getCaseModulesCounts, getPublicLinkCaseModulesCounts } from '@/api/modules/case-management/featureCase';
|
import { getCaseModulesCounts, getPublicLinkCaseModulesCounts } from '@/api/modules/case-management/featureCase';
|
||||||
|
|
||||||
|
import { StatusType } from '@/enums/caseEnum';
|
||||||
|
|
||||||
export enum RequestModuleEnum {
|
export enum RequestModuleEnum {
|
||||||
API_CASE = 'API_CASE',
|
API_CASE = 'API_CASE',
|
||||||
CASE_MANAGEMENT = 'CASE_MANAGEMENT',
|
CASE_MANAGEMENT = 'CASE_MANAGEMENT',
|
||||||
|
@ -21,4 +23,29 @@ export function initGetModuleCountFunc(type: RequestModuleEnum[keyof RequestModu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {};
|
export const lastExecuteResultMap: Record<string, any> = {
|
||||||
|
PENDING: {
|
||||||
|
label: 'PENDING',
|
||||||
|
icon: StatusType.PENDING,
|
||||||
|
statusText: 'common.unExecute',
|
||||||
|
color: 'var(--color-text-brand)',
|
||||||
|
},
|
||||||
|
SUCCESS: {
|
||||||
|
label: 'SUCCESS',
|
||||||
|
icon: StatusType.SUCCESS,
|
||||||
|
statusText: 'common.success',
|
||||||
|
color: '',
|
||||||
|
},
|
||||||
|
BLOCKED: {
|
||||||
|
label: 'BLOCKED',
|
||||||
|
icon: StatusType.BLOCKED,
|
||||||
|
statusText: 'common.block',
|
||||||
|
color: 'var(--color-fill-p-3)',
|
||||||
|
},
|
||||||
|
ERROR: {
|
||||||
|
label: 'ERROR',
|
||||||
|
icon: StatusType.ERROR,
|
||||||
|
statusText: 'common.fail',
|
||||||
|
color: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,169 @@
|
||||||
|
import '@/assets/fonts/AlibabaPuHuiTi-3-55-Regular-normal';
|
||||||
|
|
||||||
|
import { Canvg } from 'canvg';
|
||||||
|
import html2canvas from 'html2canvas-pro';
|
||||||
|
import JSPDF from 'jspdf';
|
||||||
|
import autoTable, { UserOptions } from 'jspdf-autotable';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 替换svg为base64
|
||||||
|
*/
|
||||||
|
async function inlineSvgUseElements(container: HTMLElement) {
|
||||||
|
const useElements = container.querySelectorAll('use');
|
||||||
|
useElements.forEach((useElement) => {
|
||||||
|
const href = useElement.getAttribute('xlink:href') || useElement.getAttribute('href');
|
||||||
|
if (href) {
|
||||||
|
const symbolId = href.substring(1);
|
||||||
|
const symbol = document.getElementById(symbolId);
|
||||||
|
if (symbol) {
|
||||||
|
const svgElement = useElement.closest('svg');
|
||||||
|
if (svgElement) {
|
||||||
|
svgElement.innerHTML = symbol.innerHTML;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将svg转换为base64
|
||||||
|
*/
|
||||||
|
async function convertSvgToBase64(svgElement: SVGSVGElement) {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const svgString = new XMLSerializer().serializeToString(svgElement);
|
||||||
|
if (ctx) {
|
||||||
|
const v = Canvg.fromString(ctx, svgString);
|
||||||
|
canvas.width = svgElement.clientWidth;
|
||||||
|
canvas.height = svgElement.clientHeight;
|
||||||
|
await v.render();
|
||||||
|
}
|
||||||
|
return canvas.toDataURL('image/png');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 替换svg为base64
|
||||||
|
*/
|
||||||
|
async function replaceSvgWithBase64(container: HTMLElement) {
|
||||||
|
await inlineSvgUseElements(container);
|
||||||
|
const svgElements = container.querySelectorAll('.c-icon');
|
||||||
|
svgElements.forEach(async (svgElement) => {
|
||||||
|
const img = new Image();
|
||||||
|
img.src = await convertSvgToBase64(svgElement as SVGSVGElement);
|
||||||
|
img.width = svgElement.clientWidth;
|
||||||
|
img.height = svgElement.clientHeight;
|
||||||
|
img.style.marginRight = '8px';
|
||||||
|
svgElement.parentNode?.replaceChild(img, svgElement);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const A4_WIDTH = 595;
|
||||||
|
const A4_HEIGHT = 842;
|
||||||
|
const HEADER_HEIGHT = 16;
|
||||||
|
const FOOTER_HEIGHT = 16;
|
||||||
|
export const PAGE_HEIGHT = A4_HEIGHT - FOOTER_HEIGHT - HEADER_HEIGHT;
|
||||||
|
export const PDF_WIDTH = A4_WIDTH - 32; // 左右分别 16px 间距
|
||||||
|
export const CONTAINER_WIDTH = 1190;
|
||||||
|
export const SCALE_RATIO = 1.5;
|
||||||
|
export const PAGE_PDF_WIDTH_RATIO = CONTAINER_WIDTH / PDF_WIDTH; // 页面容器宽度与 pdf 宽度的比例
|
||||||
|
// 实际每页高度 = PDF页面高度/页面容器宽度与 pdf 宽度的比例(这里比例*SCALE_RATIO 是因为html2canvas截图时生成的是 SCALE_RATIO 倍的清晰度)
|
||||||
|
export const IMAGE_HEIGHT = Math.ceil(PAGE_HEIGHT * PAGE_PDF_WIDTH_RATIO * SCALE_RATIO);
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PdfTableConfig = Pick<UserOptions, 'columnStyles' | 'columns' | 'body'>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出PDF
|
||||||
|
* @param name 文件名
|
||||||
|
* @param contentId 内容DOM id
|
||||||
|
* @description 通过html2canvas生成图片,再通过jsPDF生成pdf
|
||||||
|
* (使用html2canvas截图时,因为插件有截图极限,超出极限部分会出现截图失败,所以这里设置了MAX_CANVAS_HEIGHT截图高度,然后根据这个截图高度分页截图,然后根据每个截图裁剪每页 pdf 的图片并添加到 pdf 内)
|
||||||
|
*/
|
||||||
|
export default async function exportPDF(
|
||||||
|
name: string,
|
||||||
|
contentId: string,
|
||||||
|
autoTableConfig: PdfTableConfig[],
|
||||||
|
doneCallback?: () => void
|
||||||
|
) {
|
||||||
|
const element = document.getElementById(contentId);
|
||||||
|
if (element) {
|
||||||
|
await replaceSvgWithBase64(element); // 替换截图容器内的svg为base64,因为html2canvas无法截取url-link方式的svg
|
||||||
|
// jsPDF实例
|
||||||
|
const pdf = new JSPDF({
|
||||||
|
unit: 'pt',
|
||||||
|
format: 'a4',
|
||||||
|
orientation: 'p',
|
||||||
|
});
|
||||||
|
const canvas = await html2canvas(element, {
|
||||||
|
x: 0,
|
||||||
|
width: CONTAINER_WIDTH,
|
||||||
|
height: element.clientHeight,
|
||||||
|
backgroundColor: '#f9f9fe',
|
||||||
|
scale: window.devicePixelRatio * SCALE_RATIO, // 缩放增加清晰度
|
||||||
|
});
|
||||||
|
pdf.setFont('AlibabaPuHuiTi-3-55-Regular');
|
||||||
|
pdf.setFontSize(10);
|
||||||
|
// 创建图片裁剪画布
|
||||||
|
const cropCanvas = document.createElement('canvas');
|
||||||
|
cropCanvas.width = CONTAINER_WIDTH * SCALE_RATIO;
|
||||||
|
cropCanvas.height = IMAGE_HEIGHT;
|
||||||
|
const tempContext = cropCanvas.getContext('2d', { willReadFrequently: true });
|
||||||
|
// 生成 PDF
|
||||||
|
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) * IMAGE_HEIGHT;
|
||||||
|
if (tempContext) {
|
||||||
|
if (i === pages) {
|
||||||
|
// 填充背景颜色为白色
|
||||||
|
tempContext.fillStyle = '#ffffff';
|
||||||
|
tempContext.fillRect(0, 0, cropCanvas.width, cropCanvas.height);
|
||||||
|
}
|
||||||
|
// 将大分页的画布图片裁剪成pdf 页面内容大小,并渲染到临时画布上
|
||||||
|
tempContext.drawImage(canvas, 0, -pagePosition, canvasWidth, canvasHeight);
|
||||||
|
const tempCanvasData = cropCanvas.toDataURL('image/jpeg');
|
||||||
|
// 将临时画布图片渲染到 pdf 上
|
||||||
|
pdf.addImage(tempCanvasData, 'jpeg', 16, 16, PDF_WIDTH, PAGE_HEIGHT);
|
||||||
|
}
|
||||||
|
cropCanvas.remove();
|
||||||
|
if (i < pages) {
|
||||||
|
pdf.text(`${i}`, pdf.internal.pageSize.width / 2 - 10, pdf.internal.pageSize.height - 4);
|
||||||
|
pdf.addPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const lastImagePageUseHeight = (canvasHeight - IMAGE_HEIGHT) / PAGE_PDF_WIDTH_RATIO / SCALE_RATIO; // 最后一页带图片的pdf页面被图片占用的高度
|
||||||
|
autoTableConfig.forEach((config, index) => {
|
||||||
|
autoTable(pdf, {
|
||||||
|
...config,
|
||||||
|
startY: index === 0 && lastImagePageUseHeight > 0 ? lastImagePageUseHeight + 32 : undefined, // 第一页表格如果和图片同一页,则需要设置 startY 为当前图片占用高度+32,以避免表格遮挡图片
|
||||||
|
...(commonOdfTableConfig as UserOptions),
|
||||||
|
didDrawPage: (data) => {
|
||||||
|
pdf.text(
|
||||||
|
`${data.doc.internal.getCurrentPageInfo().pageNumber}`,
|
||||||
|
pdf.internal.pageSize.width / 2 - 10,
|
||||||
|
pdf.internal.pageSize.height - 4
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
pdf.save(`${name}.pdf`);
|
||||||
|
nextTick(() => {
|
||||||
|
if (doneCallback) {
|
||||||
|
doneCallback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<a-spin :loading="loading" class="report-detail-container">
|
<a-spin :loading="loading" :tip="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>
|
||||||
|
@ -89,15 +89,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-[16px]">
|
<div class="mt-[16px]">
|
||||||
<div
|
<div v-for="item of innerCardList" :id="`${item.value}`" :key="item.id" class="card-item mt-[16px]">
|
||||||
v-for="item of innerCardList"
|
|
||||||
v-show="showItem(item)"
|
|
||||||
:key="item.id"
|
|
||||||
class="card-item mt-[16px]"
|
|
||||||
:class="`${item.value}`"
|
|
||||||
>
|
|
||||||
<div class="wrapper-preview-card">
|
<div class="wrapper-preview-card">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div v-if="item.value !== ReportCardTypeEnum.CUSTOM_CARD" class="mb-[8px] font-medium">
|
<div v-if="item.value !== ReportCardTypeEnum.CUSTOM_CARD" class="mb-[8px] font-medium">
|
||||||
|
@ -111,43 +104,8 @@
|
||||||
:share-id="shareId"
|
:share-id="shareId"
|
||||||
is-preview
|
is-preview
|
||||||
/>
|
/>
|
||||||
<div v-else-if="item.value === ReportCardTypeEnum.SUMMARY" v-html="getContent(item).content"></div>
|
|
||||||
<MsBaseTable v-else-if="item.value === ReportCardTypeEnum.BUG_DETAIL" v-bind="bugTableProps"> </MsBaseTable>
|
|
||||||
<div v-else-if="item.value === ReportCardTypeEnum.FUNCTIONAL_DETAIL" id="functionalCase">
|
|
||||||
<MsBaseTable v-bind="caseTableProps">
|
|
||||||
<template #caseLevel="{ record }">
|
|
||||||
<CaseLevel :case-level="record.priority" />
|
|
||||||
</template>
|
|
||||||
<template #lastExecResult="{ record }">
|
|
||||||
<ExecuteResult :execute-result="record.executeResult" />
|
|
||||||
</template>
|
|
||||||
</MsBaseTable>
|
|
||||||
</div>
|
|
||||||
<MsBaseTable
|
|
||||||
v-else-if="item.value === ReportCardTypeEnum.API_CASE_DETAIL"
|
|
||||||
v-bind="useApiTable.propsRes.value"
|
|
||||||
>
|
|
||||||
<template #priority="{ record }">
|
|
||||||
<caseLevel :case-level="record.priority" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #lastExecResult="{ record }">
|
|
||||||
<ExecutionStatus :module-type="ReportEnum.API_REPORT" :status="record.executeResult" />
|
|
||||||
</template>
|
|
||||||
</MsBaseTable>
|
|
||||||
<MsBaseTable
|
|
||||||
v-else-if="item.value === ReportCardTypeEnum.SCENARIO_CASE_DETAIL"
|
|
||||||
v-bind="useScenarioTable.propsRes.value"
|
|
||||||
>
|
|
||||||
<template #priority="{ record }">
|
|
||||||
<caseLevel :case-level="record.priority" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #lastExecResult="{ record }">
|
|
||||||
<ExecutionStatus :module-type="ReportEnum.API_REPORT" :status="record.executeResult" />
|
|
||||||
</template>
|
|
||||||
</MsBaseTable>
|
|
||||||
<div v-else-if="item.value === ReportCardTypeEnum.CUSTOM_CARD" v-html="item.content"></div>
|
<div v-else-if="item.value === ReportCardTypeEnum.CUSTOM_CARD" v-html="item.content"></div>
|
||||||
|
<div v-else-if="item.value === ReportCardTypeEnum.SUMMARY" v-html="detail.summary"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -157,16 +115,13 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
|
import { Message } from '@arco-design/web-vue';
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
import MsChart from '@/components/pure/chart/index.vue';
|
import MsChart from '@/components/pure/chart/index.vue';
|
||||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
|
||||||
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||||
import useTable from '@/components/pure/ms-table/useTable';
|
import { lastExecuteResultMap } from '@/components/business/ms-case-associate/utils';
|
||||||
import CaseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
|
||||||
import ExecuteResult from '@/components/business/ms-case-associate/executeResult.vue';
|
|
||||||
import ExecutionStatus from '@/views/api-test/report/component/reportStatus.vue';
|
|
||||||
import SingleStatusProgress from '@/views/test-plan/report/component/singleStatusProgress.vue';
|
import SingleStatusProgress from '@/views/test-plan/report/component/singleStatusProgress.vue';
|
||||||
import ExecuteAnalysis from '@/views/test-plan/report/detail/component/system-card/executeAnalysis.vue';
|
import ExecuteAnalysis from '@/views/test-plan/report/detail/component/system-card/executeAnalysis.vue';
|
||||||
import ReportDetailTable from '@/views/test-plan/report/detail/component/system-card/reportDetailTable.vue';
|
import ReportDetailTable from '@/views/test-plan/report/detail/component/system-card/reportDetailTable.vue';
|
||||||
|
@ -190,6 +145,7 @@
|
||||||
statusConfig,
|
statusConfig,
|
||||||
toolTipConfig,
|
toolTipConfig,
|
||||||
} from '@/config/testPlan';
|
} from '@/config/testPlan';
|
||||||
|
import exportPDF, { PAGE_PDF_WIDTH_RATIO } from '@/hooks/useExportPDF';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import { addCommasToNumber } from '@/utils';
|
import { addCommasToNumber } from '@/utils';
|
||||||
import exportPdf, { MAX_CANVAS_HEIGHT, SCALE_RATIO } from '@/utils/exportPdf';
|
import exportPdf, { MAX_CANVAS_HEIGHT, SCALE_RATIO } from '@/utils/exportPdf';
|
||||||
|
@ -201,25 +157,21 @@
|
||||||
ReportMetricsItemModel,
|
ReportMetricsItemModel,
|
||||||
StatusListType,
|
StatusListType,
|
||||||
} from '@/models/testPlan/testPlanReport';
|
} from '@/models/testPlan/testPlanReport';
|
||||||
import { customValueForm } from '@/models/testPlan/testPlanReport';
|
|
||||||
import { ReportEnum } from '@/enums/reportEnum';
|
|
||||||
import { ReportCardTypeEnum } from '@/enums/testPlanReportEnum';
|
import { ReportCardTypeEnum } from '@/enums/testPlanReportEnum';
|
||||||
|
|
||||||
import { defaultGroupConfig, defaultSingleConfig } from './component/reportConfig';
|
import { defaultGroupConfig, defaultSingleConfig } from './component/reportConfig';
|
||||||
import { getSummaryDetail } from '@/views/test-plan/report/utils';
|
import { getSummaryDetail } from '@/views/test-plan/report/utils';
|
||||||
import html2canvas from 'html2canvas-pro';
|
import { ColumnInput, RowInput } from 'jspdf-autotable';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
const innerCardList = defineModel<configItem[]>('cardList', {
|
const innerCardList = ref<configItem[]>([]);
|
||||||
default: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const detail = ref<PlanReportDetail>({ ...cloneDeep(defaultReportDetail) });
|
const detail = ref<PlanReportDetail>({ ...cloneDeep(defaultReportDetail) });
|
||||||
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>(false);
|
const loading = ref<boolean>(true);
|
||||||
const richText = ref<{ summary: string; richTextTmpFileIds?: string[] }>({
|
const richText = ref<{ summary: string; richTextTmpFileIds?: string[] }>({
|
||||||
summary: '',
|
summary: '',
|
||||||
});
|
});
|
||||||
|
@ -399,19 +351,6 @@
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
function showItem(item: configItem) {
|
|
||||||
switch (item.value) {
|
|
||||||
case ReportCardTypeEnum.FUNCTIONAL_DETAIL:
|
|
||||||
return functionalCaseTotal.value > 0;
|
|
||||||
case ReportCardTypeEnum.API_CASE_DETAIL:
|
|
||||||
return apiCaseTotal.value > 0;
|
|
||||||
case ReportCardTypeEnum.SCENARIO_CASE_DETAIL:
|
|
||||||
return scenarioCaseTotal.value > 0;
|
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const cardCount = computed(() => {
|
const cardCount = computed(() => {
|
||||||
const totalList = [functionalCaseTotal.value, apiCaseTotal.value, scenarioCaseTotal.value];
|
const totalList = [functionalCaseTotal.value, apiCaseTotal.value, scenarioCaseTotal.value];
|
||||||
let count = 2;
|
let count = 2;
|
||||||
|
@ -422,25 +361,24 @@
|
||||||
});
|
});
|
||||||
return count;
|
return count;
|
||||||
});
|
});
|
||||||
|
const currentMode = ref<string>('drawer');
|
||||||
const originLayoutInfo = ref([]);
|
|
||||||
|
|
||||||
async function getDefaultLayout() {
|
async function getDefaultLayout() {
|
||||||
try {
|
try {
|
||||||
const res = await getReportLayout(detail.value.id, shareId.value);
|
const res = await getReportLayout(detail.value.id, shareId.value);
|
||||||
const result = res.map((item: any) => {
|
innerCardList.value = res
|
||||||
return {
|
.filter((e: any) => [ReportCardTypeEnum.CUSTOM_CARD, ReportCardTypeEnum.SUMMARY].includes(e.value))
|
||||||
id: item.id,
|
.map((item: any) => {
|
||||||
value: item.name,
|
return {
|
||||||
label: item.label,
|
id: item.id,
|
||||||
content: item.value || '',
|
value: item.name,
|
||||||
type: item.type,
|
label: item.label,
|
||||||
enableEdit: false,
|
content: item.value || '',
|
||||||
richTextTmpFileIds: item.richTextTmpFileIds,
|
type: item.type,
|
||||||
};
|
enableEdit: false,
|
||||||
});
|
richTextTmpFileIds: item.richTextTmpFileIds,
|
||||||
innerCardList.value = result;
|
};
|
||||||
originLayoutInfo.value = cloneDeep(result);
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
@ -449,24 +387,6 @@
|
||||||
|
|
||||||
const isDefaultLayout = ref<boolean>(false);
|
const isDefaultLayout = ref<boolean>(false);
|
||||||
|
|
||||||
// 获取内容详情
|
|
||||||
function getContent(item: configItem): customValueForm {
|
|
||||||
if (isDefaultLayout.value) {
|
|
||||||
return {
|
|
||||||
content: richText.value.summary || '',
|
|
||||||
label: t(item.label),
|
|
||||||
richTextTmpFileIds: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
content: item.content || '',
|
|
||||||
label: t(item.label),
|
|
||||||
richTextTmpFileIds: item.richTextTmpFileIds,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentMode = ref<string>('drawer');
|
|
||||||
|
|
||||||
/** 缺陷明细 */
|
/** 缺陷明细 */
|
||||||
const bugColumns: MsTableColumn = [
|
const bugColumns: MsTableColumn = [
|
||||||
{
|
{
|
||||||
|
@ -497,16 +417,6 @@
|
||||||
const reportBugList = () => {
|
const reportBugList = () => {
|
||||||
return !shareId.value ? getReportBugList : getReportShareBugList;
|
return !shareId.value ? getReportBugList : getReportShareBugList;
|
||||||
};
|
};
|
||||||
const {
|
|
||||||
propsRes: bugTableProps,
|
|
||||||
loadList: loadBugList,
|
|
||||||
setLoadListParams: setLoadBugListParams,
|
|
||||||
} = useTable(reportBugList(), {
|
|
||||||
scroll: { x: '100%', y: 'auto' },
|
|
||||||
columns: bugColumns,
|
|
||||||
showSelectorAll: false,
|
|
||||||
hoverable: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** 用例明细 */
|
/** 用例明细 */
|
||||||
const staticColumns: MsTableColumn = [
|
const staticColumns: MsTableColumn = [
|
||||||
|
@ -571,18 +481,6 @@
|
||||||
const reportFeatureCaseList = () => {
|
const reportFeatureCaseList = () => {
|
||||||
return !shareId.value ? getReportFeatureCaseList : getReportShareFeatureCaseList;
|
return !shareId.value ? getReportFeatureCaseList : getReportShareFeatureCaseList;
|
||||||
};
|
};
|
||||||
const {
|
|
||||||
propsRes: caseTableProps,
|
|
||||||
loadList: loadCaseList,
|
|
||||||
setLoadListParams: setLoadCaseListParams,
|
|
||||||
setPagination: setCasePagination,
|
|
||||||
} = useTable(reportFeatureCaseList(), {
|
|
||||||
scroll: { x: '100%', y: 'auto' },
|
|
||||||
columns: caseColumns.value,
|
|
||||||
heightUsed: 20,
|
|
||||||
showSelectorAll: false,
|
|
||||||
hoverable: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** 接口/场景明细 */
|
/** 接口/场景明细 */
|
||||||
const apiStaticColumns: MsTableColumn = [
|
const apiStaticColumns: MsTableColumn = [
|
||||||
|
@ -605,31 +503,72 @@
|
||||||
title: 'common.executionResult',
|
title: 'common.executionResult',
|
||||||
dataIndex: 'executeResult',
|
dataIndex: 'executeResult',
|
||||||
slotName: 'lastExecResult',
|
slotName: 'lastExecResult',
|
||||||
width: 80,
|
width: 100,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const apiColumns = computed(() => {
|
const apiColumns = computed(() => {
|
||||||
if (isGroup.value) {
|
if (isGroup.value) {
|
||||||
return [...apiStaticColumns, ...testPlanNameColumns, ...lastStaticColumns];
|
return [
|
||||||
|
...apiStaticColumns,
|
||||||
|
...testPlanNameColumns,
|
||||||
|
...lastStaticColumns.filter((e) => e.dataIndex !== 'priority'),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
return [...apiStaticColumns, ...lastStaticColumns];
|
return [...apiStaticColumns, ...lastStaticColumns.filter((e) => e.dataIndex !== 'priority')];
|
||||||
});
|
});
|
||||||
|
|
||||||
const useApiTable = useTable(getApiPage, {
|
const fullCaseList = ref<any>([]);
|
||||||
scroll: { x: '100%', y: 'auto' },
|
async function initCaseList() {
|
||||||
columns: apiColumns.value,
|
fullCaseList.value = (
|
||||||
showSelectorAll: false,
|
await reportFeatureCaseList()({
|
||||||
showSetting: false,
|
current: 1,
|
||||||
hoverable: false,
|
pageSize: 500,
|
||||||
});
|
reportId: reportId.value,
|
||||||
const useScenarioTable = useTable(getScenarioPage, {
|
shareId: shareId.value ?? undefined,
|
||||||
scroll: { x: '100%', y: 'auto' },
|
startPager: false,
|
||||||
columns: apiColumns.value,
|
})
|
||||||
showSelectorAll: false,
|
).list;
|
||||||
showSetting: false,
|
}
|
||||||
hoverable: false,
|
|
||||||
});
|
const fullBugList = ref<any>([]);
|
||||||
|
async function initBugList() {
|
||||||
|
fullBugList.value = (
|
||||||
|
await reportBugList()({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 500,
|
||||||
|
reportId: reportId.value,
|
||||||
|
shareId: shareId.value ?? undefined,
|
||||||
|
startPager: false,
|
||||||
|
})
|
||||||
|
).list;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullApiList = ref<any>([]);
|
||||||
|
async function initApiList() {
|
||||||
|
fullApiList.value = (
|
||||||
|
await getApiPage({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 500,
|
||||||
|
reportId: reportId.value,
|
||||||
|
shareId: shareId.value ?? undefined,
|
||||||
|
startPager: false,
|
||||||
|
})
|
||||||
|
).list;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullScenarioList = ref<any>([]);
|
||||||
|
async function initScenarioList() {
|
||||||
|
fullScenarioList.value = (
|
||||||
|
await getScenarioPage({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 500,
|
||||||
|
reportId: reportId.value,
|
||||||
|
shareId: shareId.value ?? undefined,
|
||||||
|
startPager: false,
|
||||||
|
})
|
||||||
|
).list;
|
||||||
|
}
|
||||||
|
|
||||||
async function getDetail() {
|
async function getDetail() {
|
||||||
try {
|
try {
|
||||||
|
@ -643,50 +582,104 @@
|
||||||
if (!defaultLayout && id) {
|
if (!defaultLayout && id) {
|
||||||
getDefaultLayout();
|
getDefaultLayout();
|
||||||
} else {
|
} else {
|
||||||
innerCardList.value = isGroup.value ? cloneDeep(defaultGroupConfig) : cloneDeep(defaultSingleConfig);
|
innerCardList.value = (isGroup.value ? cloneDeep(defaultGroupConfig) : cloneDeep(defaultSingleConfig)).filter(
|
||||||
|
(e: any) => [ReportCardTypeEnum.CUSTOM_CARD, ReportCardTypeEnum.SUMMARY].includes(e.value)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
setLoadBugListParams({ reportId: reportId.value, shareId: shareId.value ?? undefined, pageSize: 500 });
|
await Promise.all([initBugList(), initCaseList(), initApiList(), initScenarioList()]);
|
||||||
setLoadCaseListParams({ reportId: reportId.value, shareId: shareId.value ?? undefined, pageSize: 500 });
|
nextTick(async () => {
|
||||||
useApiTable.setLoadListParams({
|
exportPDF(
|
||||||
reportId: reportId.value,
|
name,
|
||||||
shareId: shareId.value ?? undefined,
|
'report-detail',
|
||||||
pageSize: 500,
|
[
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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[],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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[],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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[],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
() => {
|
||||||
|
loading.value = false;
|
||||||
|
Message.success(t('report.detail.exportPdfSuccess'));
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
useScenarioTable.setLoadListParams({
|
|
||||||
reportId: reportId.value,
|
|
||||||
shareId: shareId.value ?? undefined,
|
|
||||||
pageSize: 500,
|
|
||||||
});
|
|
||||||
await Promise.all([loadBugList(), loadCaseList(), useApiTable.loadList(), useScenarioTable.loadList()]);
|
|
||||||
setTimeout(() => {
|
|
||||||
exportPdf(detail.value.name, 'report-detail');
|
|
||||||
// nextTick(async () => {
|
|
||||||
// const element = document.getElementById('functionalCase');
|
|
||||||
// if (element) {
|
|
||||||
// while (caseTableProps.value.msPagination!.current * 500 < caseTableProps.value.msPagination!.total) {
|
|
||||||
// console.log('start html2canvas', new Date().getMinutes(), new Date().getSeconds());
|
|
||||||
// // eslint-disable-next-line no-await-in-loop
|
|
||||||
// const canvas = await html2canvas(element, {
|
|
||||||
// x: 0,
|
|
||||||
// y: 848,
|
|
||||||
// width: 1190,
|
|
||||||
// height: MAX_CANVAS_HEIGHT,
|
|
||||||
// backgroundColor: '#f9f9fe',
|
|
||||||
// scale: window.devicePixelRatio * SCALE_RATIO, // 缩放增加清晰度
|
|
||||||
// });
|
|
||||||
// console.log('end html2canvas', new Date().getMinutes(), new Date().getSeconds());
|
|
||||||
// exportPdf(detail.value.name, 'report-detail', canvas);
|
|
||||||
// setCasePagination({ current: caseTableProps.value.msPagination!.current + 1 });
|
|
||||||
// // eslint-disable-next-line no-await-in-loop
|
|
||||||
// await loadCaseList();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
}, 0);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(error);
|
console.log(error);
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -696,6 +689,12 @@
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.arco-spin-mask-icon {
|
||||||
|
@apply !fixed;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.report-detail-container {
|
.report-detail-container {
|
||||||
@apply flex justify-center;
|
@apply flex justify-center;
|
||||||
|
|
|
@ -57,4 +57,6 @@ export default {
|
||||||
'report.detail.systemInternalTooltip': 'System built-in, not editable',
|
'report.detail.systemInternalTooltip': 'System built-in, not editable',
|
||||||
'report.detail.reportNameNotEmpty': 'The report name cannot be empty',
|
'report.detail.reportNameNotEmpty': 'The report name cannot be empty',
|
||||||
'report.detail.manualGenReportSuccess': 'The custom generated report was successful',
|
'report.detail.manualGenReportSuccess': 'The custom generated report was successful',
|
||||||
|
'report.detail.exportingPdf': 'Exporting PDF report...',
|
||||||
|
'report.detail.exportPdfSuccess': 'PDF report exported successfully',
|
||||||
};
|
};
|
||||||
|
|
|
@ -57,4 +57,6 @@ export default {
|
||||||
'report.detail.systemInternalTooltip': '系统内置,不可编辑',
|
'report.detail.systemInternalTooltip': '系统内置,不可编辑',
|
||||||
'report.detail.reportNameNotEmpty': '报告名称不能为空',
|
'report.detail.reportNameNotEmpty': '报告名称不能为空',
|
||||||
'report.detail.manualGenReportSuccess': '自定义生成报告成功',
|
'report.detail.manualGenReportSuccess': '自定义生成报告成功',
|
||||||
|
'report.detail.exportingPdf': 'PDF报告导出中...',
|
||||||
|
'report.detail.exportPdfSuccess': 'PDF报告导出成功',
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
// eslint-disable-next-line import/default
|
|
||||||
import exportPDFWorker from './exportPDFWorker?worker';
|
|
||||||
import html2canvas from 'html2canvas-pro';
|
|
||||||
|
|
||||||
// eslint-disable-next-line new-cap
|
|
||||||
const worker = new exportPDFWorker();
|
|
||||||
|
|
||||||
worker.onmessage = (event: MessageEvent) => {
|
|
||||||
const { name, pdfBlob } = event.data;
|
|
||||||
const url = URL.createObjectURL(pdfBlob);
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = url;
|
|
||||||
a.download = `${name}.pdf`;
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
document.body.removeChild(a);
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
};
|
|
||||||
|
|
||||||
const exportPdf = async (name: string, contentId: string) => {
|
|
||||||
const element = document.getElementById(contentId);
|
|
||||||
if (element) {
|
|
||||||
// await replaceSvgWithBase64(element);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default exportPdf;
|
|
|
@ -1,6 +1,6 @@
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
export default {
|
export default {
|
||||||
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
|
content: ['./index.html', './src/**/*.{vue,ts,jsx,tsx}'],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,7 +12,8 @@
|
||||||
"build/**/*.d.ts",
|
"build/**/*.d.ts",
|
||||||
"mock/**/*.ts",
|
"mock/**/*.ts",
|
||||||
"__test__/**/*.ts",
|
"__test__/**/*.ts",
|
||||||
"node_modules/monaco-editor/monaco.d.ts"
|
"node_modules/monaco-editor/monaco.d.ts",
|
||||||
|
"src/views/test-plan/report/detail/alibabapuhuiti.js"
|
||||||
], // TS解析路径配置
|
], // TS解析路径配置
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"allowJs": true, // 允许编译器编译JS,JSX文件
|
"allowJs": true, // 允许编译器编译JS,JSX文件
|
||||||
|
|
Loading…
Reference in New Issue