fix(报表统计): 优化饼状图的展示方式以及导出细节

--bug=1021171 --user=宋天阳 【报表统计】github#20870,测试用例统计图,报表展示数值重叠,导出后缺少图表
https://www.tapd.cn/55049933/s/1351691
This commit is contained in:
song-tianyang 2023-03-16 18:26:40 +08:00 committed by 建国
parent 1c7ef48abd
commit 1d124b57f4
10 changed files with 780 additions and 366 deletions

View File

@ -8,6 +8,14 @@ import {
import i18n from "../i18n"; import i18n from "../i18n";
import html2canvas from "html2canvas"; import html2canvas from "html2canvas";
import JsPDF from "jspdf"; import JsPDF from "jspdf";
/**
* 同一行的多个文本框高度保持一致
* 同时支持 autosize 的功能
* @param size 同一行中文本框的个数
* @param index 编辑行的下标
* 如果编辑某一行则只调整某一行提升效率
*/
import calcTextareaHeight from "element-ui/packages/input/src/calcTextareaHeight";
export function setCustomizeColor(color) { export function setCustomizeColor(color) {
// 自定义主题风格 // 自定义主题风格
@ -309,10 +317,10 @@ export function sizeToByte(size) {
let k = 1024, let k = 1024,
sizeUnits = ["B", "K", "M", "G", "T", "P", "E", "Z", "Y"]; sizeUnits = ["B", "K", "M", "G", "T", "P", "E", "Z", "Y"];
let i = 1; let i = 1;
for (i++; i < sizeUnits.length;) { for (i++; i < sizeUnits.length; ) {
let unit = sizeUnits[i]; let unit = sizeUnits[i];
if (size.indexOf(unit) !== -1) { if (size.indexOf(unit) !== -1) {
return size.toString().replace(unit, "") * Math.pow(k, i) return size.toString().replace(unit, "") * Math.pow(k, i);
} }
} }
} }
@ -325,15 +333,6 @@ export function getTypeByFileName(filename) {
return type.toUpperCase(); return type.toUpperCase();
} }
/**
* 同一行的多个文本框高度保持一致
* 同时支持 autosize 的功能
* @param size 同一行中文本框的个数
* @param index 编辑行的下标
* 如果编辑某一行则只调整某一行提升效率
*/
import calcTextareaHeight from "element-ui/packages/input/src/calcTextareaHeight";
export function resizeTextarea(size = 2, index) { export function resizeTextarea(size = 2, index) {
let textareaList = document.querySelectorAll( let textareaList = document.querySelectorAll(
".sync-textarea .el-textarea__inner" ".sync-textarea .el-textarea__inner"
@ -404,7 +403,7 @@ export function downloadPDF(ele, pdfName) {
let scale = canvas.height / ((canvas.width / 592.28) * 841.89); let scale = canvas.height / ((canvas.width / 592.28) * 841.89);
scale = scale > 3 ? 1 : 2; scale = scale > 3 ? 1 : 2;
context.translate(-eleOffsetLeft - abs, -eleOffsetTop); context.translate(-eleOffsetLeft - abs, -eleOffsetTop);
let scrollWidth = document.getElementById("apiTestReport").scrollWidth; let scrollWidth = ele.scrollWidth;
html2canvas(ele, { html2canvas(ele, {
scale: scale, // 背景灰色 scale: scale, // 背景灰色
background: "#FFFFFF", background: "#FFFFFF",

View File

@ -2,6 +2,7 @@ package io.metersphere.reportstatistics.service;
import io.metersphere.base.domain.CustomField; import io.metersphere.base.domain.CustomField;
import io.metersphere.base.domain.User; import io.metersphere.base.domain.User;
import io.metersphere.commons.constants.MicroServiceName;
import io.metersphere.commons.utils.DateUtils; import io.metersphere.commons.utils.DateUtils;
import io.metersphere.commons.utils.JSON; import io.metersphere.commons.utils.JSON;
import io.metersphere.i18n.Translator; import io.metersphere.i18n.Translator;
@ -15,15 +16,16 @@ import io.metersphere.reportstatistics.service.remote.apitest.ScenarioRemoteServ
import io.metersphere.reportstatistics.service.remote.performance.PerformanceRemoteService; import io.metersphere.reportstatistics.service.remote.performance.PerformanceRemoteService;
import io.metersphere.reportstatistics.service.remote.projectmanagement.TestCaseTemplateRemoteService; import io.metersphere.reportstatistics.service.remote.projectmanagement.TestCaseTemplateRemoteService;
import io.metersphere.reportstatistics.service.remote.track.TestCaseRemoteService; import io.metersphere.reportstatistics.service.remote.track.TestCaseRemoteService;
import io.metersphere.reportstatistics.utils.DiscoveryUtil;
import io.metersphere.request.member.QueryMemberRequest; import io.metersphere.request.member.QueryMemberRequest;
import io.metersphere.service.BaseUserService; import io.metersphere.service.BaseUserService;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils; import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import jakarta.annotation.Resource;
import java.util.*; import java.util.*;
@Service @Service
@ -126,7 +128,6 @@ public class TestCaseCountService {
} else if (StringUtils.equalsIgnoreCase(request.getTimeRangeUnit(), "year")) { } else if (StringUtils.equalsIgnoreCase(request.getTimeRangeUnit(), "year")) {
dateCountType = Calendar.YEAR; dateCountType = Calendar.YEAR;
} }
if (dateCountType != 0 && request.getTimeRange() != 0) { if (dateCountType != 0 && request.getTimeRange() != 0) {
long startTime = DateUtils.dateSum(new Date(), (0 - request.getTimeRange()), dateCountType).getTime(); long startTime = DateUtils.dateSum(new Date(), (0 - request.getTimeRange()), dateCountType).getTime();
request.setStartTime(startTime); request.setStartTime(startTime);
@ -246,7 +247,7 @@ public class TestCaseCountService {
} }
if (yAxisSelectTestCase) { if (yAxisSelectTestCase && DiscoveryUtil.hasService(MicroServiceName.TEST_TRACK)) {
functionCaseCountResult = testCaseRemoteService.countTestCaseByRequest(request); functionCaseCountResult = testCaseRemoteService.countTestCaseByRequest(request);
if (functionCaseCountResult.isEmpty() && StringUtils.equalsIgnoreCase(request.getXaxis(), "casetype")) { if (functionCaseCountResult.isEmpty() && StringUtils.equalsIgnoreCase(request.getXaxis(), "casetype")) {
TestCaseCountChartResult result = new TestCaseCountChartResult(); TestCaseCountChartResult result = new TestCaseCountChartResult();
@ -255,7 +256,7 @@ public class TestCaseCountService {
functionCaseCountResult.add(result); functionCaseCountResult.add(result);
} }
} }
if (yAxisSelectApi) { if (yAxisSelectApi && DiscoveryUtil.hasService(MicroServiceName.API_TEST)) {
Map<String, List<String>> apiCaseFilterList = new HashMap<>(); Map<String, List<String>> apiCaseFilterList = new HashMap<>();
if (MapUtils.isNotEmpty(request.getFilterSearchList())) { if (MapUtils.isNotEmpty(request.getFilterSearchList())) {
for (Map.Entry<String, List<String>> entry : request.getFilterSearchList().entrySet()) { for (Map.Entry<String, List<String>> entry : request.getFilterSearchList().entrySet()) {
@ -274,7 +275,7 @@ public class TestCaseCountService {
apiCaseCountResult.add(result); apiCaseCountResult.add(result);
} }
} }
if (yAxisSelectScenarioCase) { if (yAxisSelectScenarioCase && DiscoveryUtil.hasService(MicroServiceName.API_TEST)) {
scenarioCaseCount = scenarioRemoteService.countTestCaseByRequest(request); scenarioCaseCount = scenarioRemoteService.countTestCaseByRequest(request);
if (scenarioCaseCount.isEmpty() && StringUtils.equalsIgnoreCase(request.getXaxis(), "casetype")) { if (scenarioCaseCount.isEmpty() && StringUtils.equalsIgnoreCase(request.getXaxis(), "casetype")) {
TestCaseCountChartResult result = new TestCaseCountChartResult(); TestCaseCountChartResult result = new TestCaseCountChartResult();
@ -283,7 +284,7 @@ public class TestCaseCountService {
scenarioCaseCount.add(result); scenarioCaseCount.add(result);
} }
} }
if (yAxisSelectLoad) { if (yAxisSelectLoad && DiscoveryUtil.hasService(MicroServiceName.PERFORMANCE_TEST)) {
Map<String, List<String>> loadCaseFilterMap = new HashMap<>(); Map<String, List<String>> loadCaseFilterMap = new HashMap<>();
if (MapUtils.isNotEmpty(request.getFilterSearchList())) { if (MapUtils.isNotEmpty(request.getFilterSearchList())) {
for (Map.Entry<String, List<String>> entry : request.getFilterSearchList().entrySet()) { for (Map.Entry<String, List<String>> entry : request.getFilterSearchList().entrySet()) {

View File

@ -0,0 +1,30 @@
package io.metersphere.reportstatistics.utils;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.dto.ServiceDTO;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class DiscoveryUtil {
public static boolean hasService(String serviceId) {
return getServiceIdSet().contains(serviceId);
}
public static Set<String> getServiceIdSet() {
DiscoveryClient discoveryClient = CommonBeanFactory.getBean(DiscoveryClient.class);
return discoveryClient.getServices()
.stream()
.collect(Collectors.toSet());
}
public static List<ServiceDTO> getServices() {
DiscoveryClient discoveryClient = CommonBeanFactory.getBean(DiscoveryClient.class);
return discoveryClient.getServices().stream()
.map(service -> new ServiceDTO(service, discoveryClient.getInstances(service).get(0).getPort()))
.collect(Collectors.toList());
}
}

View File

@ -1,35 +1,55 @@
<template> <template>
<div class="ms-header"> <div class="ms-header">
<el-row> <el-row>
<div class="ms-div">{{ title }} <div class="ms-div">
{{ title }}
<span v-if="historyReportName" class="history-report-name"> <span v-if="historyReportName" class="history-report-name">
{{ historyReportName }} {{ historyReportName }}
</span> </span>
</div> </div>
<div class="ms-header-right"> <div class="ms-header-right">
<el-button type="primary" v-if="isSaveAsButtonShow" size="mini" @click="handleSaveAs" :disabled="readOnly"> <el-button
{{ $t('commons.save_as') }}<i class="el-icon-files el-icon--right"></i></el-button> type="primary"
<el-button type="primary" v-if="isSaveButtonShow" size="mini" @click="handleSave" :disabled="readOnly"> v-if="isSaveAsButtonShow"
{{ $t('commons.save') }}<i class="el-icon-files el-icon--right"></i></el-button> size="mini"
<el-button type="" size="mini" @click="handleExport" :disabled="readOnly">{{ $t('report.export') }}<i @click="handleSaveAs"
class="el-icon-download el-icon--right"></i></el-button> :disabled="readOnly"
>
{{ $t("commons.save_as") }}<i class="el-icon-files el-icon--right"></i
></el-button>
<el-button
type="primary"
v-if="isSaveButtonShow"
size="mini"
@click="handleSave"
:disabled="readOnly"
>
{{ $t("commons.save") }}<i class="el-icon-files el-icon--right"></i
></el-button>
<el-button
type=""
size="mini"
@click="handleExport"
:disabled="readOnly"
>{{ $t("report.export")
}}<i class="el-icon-download el-icon--right"></i
></el-button>
<span class="ms-span">|</span> <span class="ms-span">|</span>
<i class="el-icon-close report-alt-ico" @click="close"/> <i class="el-icon-close report-alt-ico" @click="close" />
</div> </div>
</el-row> </el-row>
</div> </div>
</template> </template>
<script> <script>
import {exportPdf,} from "metersphere-frontend/src/utils"; import { hasPermission } from "metersphere-frontend/src/utils/permission";
import {hasPermission} from "metersphere-frontend/src/utils/permission"; import html2canvas from "html2canvas";
import html2canvas from 'html2canvas';
export default { export default {
name: "ReportHeader", name: "ReportHeader",
components: {}, components: {},
data() { data() {
return {} return {};
}, },
props: { props: {
title: String, title: String,
@ -37,20 +57,22 @@ export default {
historyReportName: String, historyReportName: String,
}, },
watch: { watch: {
historyReportName() { historyReportName() {},
}
},
created() {
}, },
created() {},
computed: { computed: {
readOnly() { readOnly() {
return !hasPermission('PROJECT_REPORT_ANALYSIS:READ+EXPORT'); return !hasPermission("PROJECT_REPORT_ANALYSIS:READ+EXPORT");
}, },
isSaveAsButtonShow() { isSaveAsButtonShow() {
if (!this.historyReportId || this.historyReportId === null || this.historyReportId === '') { if (
!this.historyReportId ||
this.historyReportId === null ||
this.historyReportId === ""
) {
return false; return false;
} else { } else {
if (hasPermission('PROJECT_REPORT_ANALYSIS:READ+CREATE')) { if (hasPermission("PROJECT_REPORT_ANALYSIS:READ+CREATE")) {
return true; return true;
} else { } else {
return false; return false;
@ -58,28 +80,23 @@ export default {
} }
}, },
isSaveButtonShow() { isSaveButtonShow() {
if (hasPermission('PROJECT_REPORT_ANALYSIS:READ+UPDATE')) { if (hasPermission("PROJECT_REPORT_ANALYSIS:READ+UPDATE")) {
return true; return true;
} else { } else {
return false; return false;
} }
} },
}, },
methods: { methods: {
handleExport() { handleExport() {
let name = this.title; this.$emit("exportReport", this.title);
this.$nextTick(function () {
setTimeout(() => {
html2canvas(document.getElementById('reportAnalysis'), {
scale: 2
}).then(function (canvas) {
exportPdf(name, [canvas]);
});
}, 1000);
});
}, },
handleSave() { handleSave() {
if (!this.historyReportId || this.historyReportId === null || this.historyReportId === '') { if (
!this.historyReportId ||
this.historyReportId === null ||
this.historyReportId === ""
) {
this.$emit("selectAndSaveReport"); this.$emit("selectAndSaveReport");
} else { } else {
this.$emit("updateReport"); this.$emit("updateReport");
@ -89,16 +106,16 @@ export default {
this.$emit("selectAndSaveReport"); this.$emit("selectAndSaveReport");
}, },
close() { close() {
this.$emit('closePage'); this.$emit("closePage");
}, },
}, },
} };
</script> </script>
<style scoped> <style scoped>
.ms-header { .ms-header {
border-bottom: 1px solid #E6E6E6; border-bottom: 1px solid #e6e6e6;
background-color: #FFF; background-color: #fff;
} }
.ms-div { .ms-div {

View File

@ -2,56 +2,101 @@
<div> <div>
<el-row type="flex"> <el-row type="flex">
<p class="tip"> <p class="tip">
<span class="ms-span">{{ $t('commons.report_statistics.name') }}</span> <span class="ms-span">{{ $t("commons.report_statistics.name") }}</span>
<el-select v-model="reportType" class="ms-col-type" size="mini" style="width: 120px"> <el-select
<el-option :key="t.id" :value="t.id" :label="t.name" v-for="t in reportTypes"/> v-model="reportType"
class="ms-col-type"
size="mini"
style="width: 120px"
>
<el-option
:key="t.id"
:value="t.id"
:label="t.name"
v-for="t in reportTypes"
/>
</el-select> </el-select>
</p> </p>
</el-row> </el-row>
<transition> <transition>
<keep-alive> <keep-alive>
<report-card @openCard="openCard"/> <report-card @openCard="openCard" />
</keep-alive> </keep-alive>
</transition> </transition>
<!-- 测试用例趋势页面 --> <!-- 测试用例趋势页面 -->
<ms-drawer :visible="testCaseTrendDrawer" :size="100" @close="close" direction="right" :show-full-screen="false" <ms-drawer
:is-show-close="false" style="overflow: hidden"> :visible="testCaseTrendDrawer"
:size="100"
@close="close"
direction="right"
:show-full-screen="false"
:is-show-close="false"
style="overflow: hidden"
>
<template v-slot:header> <template v-slot:header>
<report-header :title="$t('commons.report_statistics.test_case_analysis')" :history-report-id="historyReportId" <report-header
:history-report-name="historyReportName" :title="$t('commons.report_statistics.test_case_analysis')"
@closePage="close" @updateReport="updateReport" :history-report-id="historyReportId"
@selectAndSaveReport="openSaveReportDialog('saveAs')"/> :history-report-name="historyReportName"
@closePage="close"
@updateReport="updateReport"
@exportReport="exportReport"
@selectAndSaveReport="openSaveReportDialog('saveAs')"
/>
</template> </template>
<test-analysis-container @initHistoryReportId="initHistoryReportId" ref="testAnalysisContainer"/> <test-analysis-container
@initHistoryReportId="initHistoryReportId"
ref="testAnalysisContainer"
/>
</ms-drawer> </ms-drawer>
<!-- 测试用例分析页面 --> <!-- 测试用例分析页面 -->
<ms-drawer :visible="testCaseCountDrawer" :size="100" @close="close" direction="right" :show-full-screen="false" <ms-drawer
:is-show-close="false" style="overflow: hidden"> :visible="testCaseCountDrawer"
:size="100"
@close="close"
direction="right"
:show-full-screen="false"
:is-show-close="false"
style="overflow: hidden"
>
<template v-slot:header> <template v-slot:header>
<report-header :title="$t('commons.report_statistics.test_case_count')" :history-report-id="historyReportId" <report-header
@closePage="close" :title="$t('commons.report_statistics.test_case_count')"
:history-report-name="historyReportName" :history-report-id="historyReportId"
@updateReport="updateReport" @closePage="close"
@selectAndSaveReport="openSaveReportDialog('saveAs')"/> :history-report-name="historyReportName"
@updateReport="updateReport"
@exportReport="exportReport"
@selectAndSaveReport="openSaveReportDialog('saveAs')"
/>
</template> </template>
<test-case-count-container @initHistoryReportId="initHistoryReportId" ref="testCaseCountContainer"/> <test-case-count-container
@initHistoryReportId="initHistoryReportId"
ref="testCaseCountContainer"
/>
</ms-drawer> </ms-drawer>
<el-dialog <el-dialog
:title="$t('commons.save')" :title="$t('commons.save')"
:visible.sync="dialogFormVisible" :visible.sync="dialogFormVisible"
width="30%" width="30%"
:before-close="handleCloseSaveReportDialog"> :before-close="handleCloseSaveReportDialog"
>
<el-form :model="form" :rules="saveReportRules" ref="saveReportRuleForm"> <el-form :model="form" :rules="saveReportRules" ref="saveReportRuleForm">
<el-form-item :label="$t('commons.input_name')" prop="reportName" label-width="120px"> <el-form-item
:label="$t('commons.input_name')"
prop="reportName"
label-width="120px"
>
<el-input v-model="form.reportName" autocomplete="off"></el-input> <el-input v-model="form.reportName" autocomplete="off"></el-input>
</el-form-item> </el-form-item>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button type="primary" @click="saveReport">{{ $t('commons.confirm') }}</el-button> <el-button type="primary" @click="saveReport">{{
$t("commons.confirm")
}}</el-button>
</div> </div>
</el-dialog> </el-dialog>
</div> </div>
@ -63,11 +108,17 @@ import TestAnalysisContainer from "@/business/projectstatistics/track/TestAnalys
import MsDrawer from "metersphere-frontend/src/components/MsDrawer"; import MsDrawer from "metersphere-frontend/src/components/MsDrawer";
import ReportHeader from "@/business/base/ReportHeader"; import ReportHeader from "@/business/base/ReportHeader";
import TestCaseCountContainer from "@/business/projectstatistics/casecount/TestCaseCountContainer"; import TestCaseCountContainer from "@/business/projectstatistics/casecount/TestCaseCountContainer";
import html2canvas from "html2canvas";
export default { export default {
name: "ReportAnalysis", name: "ReportAnalysis",
components: {ReportCard, TestAnalysisContainer, MsDrawer, ReportHeader, TestCaseCountContainer}, components: {
ReportCard,
TestAnalysisContainer,
MsDrawer,
ReportHeader,
TestCaseCountContainer,
},
data() { data() {
return { return {
reportType: "track", reportType: "track",
@ -75,7 +126,7 @@ export default {
testCaseCountDrawer: false, testCaseCountDrawer: false,
historyReportId: "", historyReportId: "",
historyReportName: "", historyReportName: "",
reportTypes: [{id: 'track', name: this.$t('test_track.test_track')}], reportTypes: [{ id: "track", name: this.$t("test_track.test_track") }],
dialogFormVisible: false, dialogFormVisible: false,
form: { form: {
reportName: "", reportName: "",
@ -83,19 +134,30 @@ export default {
}, },
saveReportRules: { saveReportRules: {
reportName: [ reportName: [
{required: true, message: this.$t('commons.input_name'), trigger: 'blur'}, {
{min: 1, max: 20, message: '长度不大于20个字符', trigger: 'blur'} required: true,
message: this.$t("commons.input_name"),
trigger: "blur",
},
{ min: 1, max: 20, message: "长度不大于20个字符", trigger: "blur" },
], ],
} },
} };
}, },
methods: { methods: {
exportReport(title) {
if (this.testCaseCountDrawer && this.$refs.testCaseCountContainer) {
this.$refs.testCaseCountContainer.handleExport();
} else if (this.testCaseTrendDrawer) {
this.$refs.testAnalysisContainer.handleExport();
}
},
openCard(type) { openCard(type) {
this.historyReportName = ""; this.historyReportName = "";
this.historyReportId = ""; this.historyReportId = "";
if (type === 'trackTestCase') { if (type === "trackTestCase") {
this.testCaseTrendDrawer = true; this.testCaseTrendDrawer = true;
} else if (type === 'countTestCase') { } else if (type === "countTestCase") {
this.testCaseCountDrawer = true; this.testCaseCountDrawer = true;
} }
}, },
@ -119,12 +181,18 @@ export default {
} }
}, },
saveReport() { saveReport() {
this.$refs['saveReportRuleForm'].validate((valid) => { this.$refs["saveReportRuleForm"].validate((valid) => {
if (valid) { if (valid) {
if (this.testCaseTrendDrawer) { if (this.testCaseTrendDrawer) {
this.$refs.testAnalysisContainer.saveAndSaveAsReport(this.form.reportName, this.form.saveType); this.$refs.testAnalysisContainer.saveAndSaveAsReport(
this.form.reportName,
this.form.saveType
);
} else if (this.testCaseCountDrawer) { } else if (this.testCaseCountDrawer) {
this.$refs.testCaseCountContainer.saveAndSaveAsReport(this.form.reportName, this.form.saveType); this.$refs.testCaseCountContainer.saveAndSaveAsReport(
this.form.reportName,
this.form.saveType
);
} }
this.form.reportName = ""; this.form.reportName = "";
this.form.saveType = ""; this.form.saveType = "";
@ -142,14 +210,14 @@ export default {
this.form.reportName = ""; this.form.reportName = "";
this.form.saveType = ""; this.form.saveType = "";
this.dialogFormVisible = false; this.dialogFormVisible = false;
} },
}, },
} };
</script> </script>
<style scoped> <style scoped>
.ms-span { .ms-span {
margin: 10px 10px 0px margin: 10px 10px 0px;
} }
.tip { .tip {

View File

@ -1,24 +1,53 @@
<template> <template>
<div> <div>
<el-container v-loading="loading" :style="{ 'max-height': (h-50) + 'px', 'overflow': 'auto'}"> <el-container
<el-aside v-if="!isHide" :width="!isHide ?'235px':'0px'" :style="{ 'margin-left': '5px'}"> v-loading="loading"
<history-report-data report-type="TEST_CASE_COUNT" :style="{ 'max-height': h - 50 + 'px', overflow: 'auto' }"
@selectReport="selectReport" @removeHistoryReportId="removeHistoryReportId" >
ref="historyReport"/> <el-aside
v-if="!isHide"
:width="!isHide ? '235px' : '0px'"
:style="{ 'margin-left': '5px' }"
>
<history-report-data
report-type="TEST_CASE_COUNT"
@selectReport="selectReport"
@removeHistoryReportId="removeHistoryReportId"
ref="historyReport"
/>
</el-aside> </el-aside>
<el-main class="ms-main" id="reportAnalysis" style="padding: 0px 5px 0px"> <el-main class="ms-main" id="reportAnalysis" style="padding: 0px 5px 0px">
<div> <div id="testCaseCountPicDiv">
<test-case-count-chart @hidePage="hidePage" @orderCharts="orderCharts" <div>
ref="analysisChart" @updateChartType="updateChartType" <test-case-count-chart
:chart-width="chartWidth" :load-option="loadOption" :pie-option="pieOption"/> @hidePage="hidePage"
</div> @orderCharts="orderCharts"
<div class="ms-row" v-if="!isHide"> ref="analysisChart"
<test-case-count-table :group-name="getGroupNameStr(options.xaxis)" :show-columns="options.yaxis" @updateChartType="updateChartType"
:tableData="tableData"/> :chart-width="chartWidth"
:load-option="loadOption"
:pie-option="pieOption"
/>
</div>
<div class="ms-row">
<test-case-count-table
:group-name="getGroupNameStr(options.xaxis)"
:show-columns="options.yaxis"
ref="caseCountTable"
:tableData="tableData"
/>
</div>
</div> </div>
</el-main> </el-main>
<el-aside v-if="!isHide" style="height: 100%" :width="!isHide ?'485px':'0px'"> <el-aside
<test-case-count-filter @filterCharts="filterCharts" ref="countFilter"/> v-if="!isHide"
style="height: 100%"
:width="!isHide ? '485px' : '0px'"
>
<test-case-count-filter
@filterCharts="filterCharts"
ref="countFilter"
/>
</el-aside> </el-aside>
</el-container> </el-container>
</div> </div>
@ -28,16 +57,24 @@
import TestCaseCountChart from "./chart/TestCaseCountChart"; import TestCaseCountChart from "./chart/TestCaseCountChart";
import TestCaseCountTable from "@/business/projectstatistics/casecount/table/TestCaseCountTable"; import TestCaseCountTable from "@/business/projectstatistics/casecount/table/TestCaseCountTable";
import TestCaseCountFilter from "./filter/TestCaseCountFilter"; import TestCaseCountFilter from "./filter/TestCaseCountFilter";
import {exportPdf} from "metersphere-frontend/src/utils"; import { getCurrentProjectID } from "metersphere-frontend/src/utils/token";
import {getCurrentProjectID} from "metersphere-frontend/src/utils/token"; import { downloadPDF } from "metersphere-frontend/src/utils";
import html2canvas from 'html2canvas'; import { getCountReport } from "@/api/report";
import {getCountReport} from "@/api/report";
import HistoryReportData from "../../base/HistoryReportData"; import HistoryReportData from "../../base/HistoryReportData";
import {createHistoryReport, selectHistoryReportById, updateHistoryReport} from "@/api/history-report"; import {
createHistoryReport,
selectHistoryReportById,
updateHistoryReport,
} from "@/api/history-report";
export default { export default {
name: "TestCaseCountContainer", name: "TestCaseCountContainer",
components: {TestCaseCountChart, TestCaseCountTable, TestCaseCountFilter, HistoryReportData}, components: {
TestCaseCountChart,
TestCaseCountTable,
TestCaseCountFilter,
HistoryReportData,
},
data() { data() {
return { return {
isHide: false, isHide: false,
@ -52,7 +89,7 @@ export default {
yAxis: {}, yAxis: {},
label: {}, label: {},
tooltip: {}, tooltip: {},
series: [] series: [],
}, },
pieOption: { pieOption: {
legend: {}, legend: {},
@ -71,26 +108,29 @@ export default {
this.chartType = value; this.chartType = value;
}, },
handleExport() { handleExport() {
let name = this.$t('commons.report_statistics.test_case_analysis'); this.hidePage(true);
let name = this.$t("commons.report_statistics.test_case_count");
this.$nextTick(function () { this.$nextTick(function () {
setTimeout(() => { setTimeout(() => {
html2canvas(document.getElementById('reportAnalysis'), { downloadPDF(
scale: 2 document.getElementById("testCaseCountPicDiv"),
}).then(function (canvas) { name || "MeterSphere-report"
exportPdf(name, [canvas]); );
});
}, 1000); }, 1000);
}); });
}, },
hidePage(isHide) { hidePage(isHide) {
this.isHide = isHide; this.isHide = isHide;
this.$refs.analysisChart.reCountWidth(isHide);
this.$refs.caseCountTable.reCountWidth(isHide);
}, },
close() { close() {
this.$emit('closePage'); this.$emit("closePage");
}, },
init(opt) { init(opt) {
this.options = opt; this.options = opt;
this.loading = getCountReport(opt).then(response => { this.loading = getCountReport(opt).then((response) => {
let data = response.data.barChartDTO; let data = response.data.barChartDTO;
let pieData = response.data.pieChartDTO; let pieData = response.data.pieChartDTO;
let selectTableData = response.data.tableDTOs; let selectTableData = response.data.tableDTOs;
@ -101,19 +141,18 @@ export default {
if (!formatData) { if (!formatData) {
return; return;
} }
if (typeof (formatData.legend) === 'string') { if (typeof formatData.legend === "string") {
formatData.legend = JSON.parse(formatData.legend); formatData.legend = JSON.parse(formatData.legend);
} }
if (typeof (formatData.xaxis) === 'string') { if (typeof formatData.xaxis === "string") {
formatData.xaxis = JSON.parse(formatData.xaxis); formatData.xaxis = JSON.parse(formatData.xaxis);
} }
if (typeof (formatData.series) === 'string') { if (typeof formatData.series === "string") {
formatData.series = JSON.parse(formatData.series); formatData.series = JSON.parse(formatData.series);
} }
if (typeof (formatData.title) === 'string') { if (typeof formatData.title === "string") {
formatData.title = JSON.parse(formatData.title); formatData.title = JSON.parse(formatData.title);
} }
}, },
initPic(barData, pieData, selectTableData) { initPic(barData, pieData, selectTableData) {
this.loading = true; this.loading = true;
@ -126,9 +165,9 @@ export default {
this.loadOption.xaxis = barData.xaxis; this.loadOption.xaxis = barData.xaxis;
this.loadOption.series = barData.series; this.loadOption.series = barData.series;
this.loadOption.grid = { this.loadOption.grid = {
bottom: '75px',// bottom: "75px", //
}; };
this.loadOption.series.forEach(item => { this.loadOption.series.forEach((item) => {
if (this.$refs.analysisChart) { if (this.$refs.analysisChart) {
item.type = this.$refs.analysisChart.chartType; item.type = this.$refs.analysisChart.chartType;
} }
@ -141,14 +180,14 @@ export default {
this.pieOption.series = pieData.series; this.pieOption.series = pieData.series;
this.pieOption.title = pieData.title; this.pieOption.title = pieData.title;
this.pieOption.grid = { this.pieOption.grid = {
bottom: '75px',// bottom: "75px", //
}; };
if (pieData.width) { if (pieData.width) {
this.pieOption.width = pieData.width; this.pieOption.width = pieData.width;
this.chartWidth = pieData.width; this.chartWidth = pieData.width;
} }
if (this.pieOption.series) { if (this.pieOption.series) {
this.pieOption.series.forEach(item => { this.pieOption.series.forEach((item) => {
if (this.$refs.analysisChart) { if (this.$refs.analysisChart) {
item.type = this.$refs.analysisChart.chartType; item.type = this.$refs.analysisChart.chartType;
} }
@ -158,7 +197,10 @@ export default {
if (selectTableData) { if (selectTableData) {
this.tableData = selectTableData; this.tableData = selectTableData;
} }
this.$refs.analysisChart.setPieOptionAndBarOption(this.loadOption, this.pieOption); this.$refs.analysisChart.setPieOptionAndBarOption(
this.loadOption,
this.pieOption
);
this.loading = false; this.loading = false;
this.$refs.analysisChart.generateOption(this.chartType); this.$refs.analysisChart.generateOption(this.chartType);
}, },
@ -181,9 +223,9 @@ export default {
chartType: this.chartType, chartType: this.chartType,
}; };
obj.dataOption = JSON.stringify(dataOptionObj); obj.dataOption = JSON.stringify(dataOptionObj);
obj.reportType = 'TEST_CASE_COUNT'; obj.reportType = "TEST_CASE_COUNT";
updateHistoryReport(obj).then(() => { updateHistoryReport(obj).then(() => {
this.$alert(this.$t('commons.save_success')); this.$alert(this.$t("commons.save_success"));
this.$refs.historyReport.initReportData(); this.$refs.historyReport.initReportData();
}); });
}, },
@ -199,9 +241,9 @@ export default {
chartType: this.chartType, chartType: this.chartType,
}; };
obj.dataOption = JSON.stringify(dataOptionObj); obj.dataOption = JSON.stringify(dataOptionObj);
obj.reportType = 'TEST_CASE_COUNT'; obj.reportType = "TEST_CASE_COUNT";
createHistoryReport(obj).then(() => { createHistoryReport(obj).then(() => {
this.$alert(this.$t('commons.save_success')); this.$alert(this.$t("commons.save_success"));
this.$refs.historyReport.initReportData(); this.$refs.historyReport.initReportData();
}); });
}, },
@ -210,54 +252,71 @@ export default {
if (selectId) { if (selectId) {
this.loading = true; this.loading = true;
let paramObj = { let paramObj = {
id: selectId id: selectId,
} };
selectHistoryReportById(paramObj).then((response) => { selectHistoryReportById(paramObj).then(
let reportData = response.data; (response) => {
if (reportData) { let reportData = response.data;
selectName = reportData.name; if (reportData) {
if (reportData.dataOption) { selectName = reportData.name;
let dataOptionObj = JSON.parse(reportData.dataOption); if (reportData.dataOption) {
if (dataOptionObj.chartType) { let dataOptionObj = JSON.parse(reportData.dataOption);
if (dataOptionObj.chartType === "\"bar\"") { if (dataOptionObj.chartType) {
this.chartType = "bar"; if (dataOptionObj.chartType === '"bar"') {
} else if (dataOptionObj.chartType === "\"pie\"") { this.chartType = "bar";
this.chartType = "pie"; } else if (dataOptionObj.chartType === '"pie"') {
this.chartType = "pie";
} else {
this.chartType = dataOptionObj.chartType;
}
} else { } else {
this.chartType = dataOptionObj.chartType; this.chartType = "bar";
} }
} else { this.initPic(
this.chartType = "bar"; dataOptionObj.loadOption,
dataOptionObj.pieOption,
dataOptionObj.tableData
);
} }
this.initPic(dataOptionObj.loadOption, dataOptionObj.pieOption, dataOptionObj.tableData); if (reportData.selectOption) {
} let selectOptionObj = JSON.parse(reportData.selectOption);
if (reportData.selectOption) { this.$refs.countFilter.initSelectOption(selectOptionObj);
let selectOptionObj = JSON.parse(reportData.selectOption); }
this.$refs.countFilter.initSelectOption(selectOptionObj); this.loading = false;
} }
this.$emit("initHistoryReportId", selectId, selectName);
},
(error) => {
this.loading = false; this.loading = false;
this.removeHistoryReportId();
} }
this.$emit('initHistoryReportId', selectId, selectName); );
}, (error) => {
this.loading = false;
this.removeHistoryReportId();
});
} }
}, },
removeHistoryReportId() { removeHistoryReportId() {
this.$emit('initHistoryReportId', "", ""); this.$emit("initHistoryReportId", "", "");
}, },
getGroupNameStr(groupName) { getGroupNameStr(groupName) {
if (groupName === 'creator') { if (groupName === "creator") {
return this.$t('commons.report_statistics.report_filter.select_options.creator'); return this.$t(
} else if (groupName === 'maintainer') { "commons.report_statistics.report_filter.select_options.creator"
return this.$t('commons.report_statistics.report_filter.select_options.maintainer'); );
} else if (groupName === 'casetype') { } else if (groupName === "maintainer") {
return this.$t('commons.report_statistics.report_filter.select_options.case_type'); return this.$t(
} else if (groupName === 'casestatus') { "commons.report_statistics.report_filter.select_options.maintainer"
return this.$t('commons.report_statistics.report_filter.select_options.case_status'); );
} else if (groupName === 'caselevel') { } else if (groupName === "casetype") {
return this.$t('commons.report_statistics.report_filter.select_options.case_level'); return this.$t(
"commons.report_statistics.report_filter.select_options.case_type"
);
} else if (groupName === "casestatus") {
return this.$t(
"commons.report_statistics.report_filter.select_options.case_status"
);
} else if (groupName === "caselevel") {
return this.$t(
"commons.report_statistics.report_filter.select_options.case_level"
);
} else { } else {
return ""; return "";
} }
@ -269,7 +328,7 @@ export default {
yAxis: {}, yAxis: {},
label: {}, label: {},
tooltip: {}, tooltip: {},
series: [] series: [],
}; };
this.pieOption = { this.pieOption = {
legend: {}, legend: {},
@ -286,12 +345,12 @@ export default {
this.saveReport(reportName); this.saveReport(reportName);
}, },
saveAndSaveAsReport(reportName, saveType) { saveAndSaveAsReport(reportName, saveType) {
if (saveType === 'save') { if (saveType === "save") {
this.saveReport(reportName); this.saveReport(reportName);
} else if (saveType === 'saveAs') { } else if (saveType === "saveAs") {
this.selectAndSaveReport(reportName); this.selectAndSaveReport(reportName);
} }
} },
}, },
}; };
</script> </script>
@ -301,7 +360,7 @@ export default {
padding-top: 5px; padding-top: 5px;
} }
:deep(.el-main ) { :deep(.el-main) {
padding: 0px 20px 0px; padding: 0px 20px 0px;
} }
</style> </style>

View File

@ -1,12 +1,20 @@
<template> <template>
<div v-loading="loading"> <div v-loading="loading">
<el-card class="ms-test-chart" :style="{ width: w+'px', height: h + 'px'}" ref="msDrawer"> <el-card
class="ms-test-chart"
:style="{ width: w + 'px', height: h + 'px' }"
ref="msDrawer"
>
<el-row class="ms-row"> <el-row class="ms-row">
<p class="tip"><span style="margin-left: 5px"></span> {{ $t('commons.report_statistics.chart') }} </p> <p class="tip">
<span style="margin-left: 5px"></span>
{{ $t("commons.report_statistics.chart") }}
</p>
<div class="ms-test-chart-header" v-if="!readOnly"> <div class="ms-test-chart-header" v-if="!readOnly">
<el-dropdown @command="exportCommand" :hide-on-click="false"> <el-dropdown @command="exportCommand" :hide-on-click="false">
<span class="el-dropdown-link"> <span class="el-dropdown-link">
{{ $t('commons.export') }}<i class="el-icon-arrow-down el-icon--right"></i> {{ $t("commons.export")
}}<i class="el-icon-arrow-down el-icon--right"></i>
</span> </span>
<el-dropdown-menu slot="dropdown"> <el-dropdown-menu slot="dropdown">
<el-dropdown-item command="jpg">JPG</el-dropdown-item> <el-dropdown-item command="jpg">JPG</el-dropdown-item>
@ -14,26 +22,63 @@
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
<span style="margin: 0px 10px 10px">|</span> <span style="margin: 0px 10px 10px">|</span>
<el-select v-model="chartType" class="ms-col-type" size="mini" style="width: 100px" @change="generateOption"> <el-select
<el-option :key="t.id" :value="t.id" :label="t.name" v-for="t in charts"/> v-model="chartType"
class="ms-col-type"
size="mini"
style="width: 100px"
@change="generateOption"
>
<el-option
:key="t.id"
:value="t.id"
:label="t.name"
v-for="t in charts"
/>
</el-select> </el-select>
<span style="margin: 0px 10px 10px">|</span> <span style="margin: 0px 10px 10px">|</span>
<el-select v-model="order" class="ms-col-type" size="mini" style="width: 120px" @change="orderCharts"> <el-select
<el-option :key="t.id" :value="t.id" :label="t.name" v-for="t in orders"/> v-model="order"
class="ms-col-type"
size="mini"
style="width: 120px"
@change="orderCharts"
>
<el-option
:key="t.id"
:value="t.id"
:label="t.name"
v-for="t in orders"
/>
</el-select> </el-select>
<span style="margin: 0px 10px 10px">|</span> <span style="margin: 0px 10px 10px">|</span>
<font-awesome-icon v-if="!isFullScreen && showFullScreen" class="report-alt-ico" :icon="['fa', 'expand-alt']" <font-awesome-icon
size="lg" @click="fullScreen"/> v-if="!isFullScreen && showFullScreen"
<font-awesome-icon v-if="isFullScreen && showFullScreen" class="report-alt-ico" :icon="['fa', 'compress-alt']" class="report-alt-ico"
size="lg" @click="unFullScreen"/> :icon="['fa', 'expand-alt']"
size="lg"
@click="fullScreen"
/>
<font-awesome-icon
v-if="isFullScreen && showFullScreen"
class="report-alt-ico"
:icon="['fa', 'compress-alt']"
size="lg"
@click="unFullScreen"
/>
</div> </div>
</el-row> </el-row>
<el-row> <el-row>
<div class="chart-style"> <div class="chart-style">
<ms-chart ref="chart1" v-if="!loading" :options="dataOption" <ms-chart
:style="{width: chartWidthNumber+'px', height: (h-70) + 'px'}" class="chart-config" ref="chart1"
:autoresize="true" v-if="!loading"
id="picChart"/> :options="dataOption"
:style="{ width: chartWidthNumber + 'px', height: h - 70 + 'px' }"
class="chart-config"
:autoresize="true"
id="picChart"
/>
</div> </div>
</el-row> </el-row>
</el-card> </el-card>
@ -42,10 +87,11 @@
<script> <script>
import MsChart from "metersphere-frontend/src/components/chart/MsChart"; import MsChart from "metersphere-frontend/src/components/chart/MsChart";
import html2canvas from "html2canvas";
export default { export default {
name: "TestCaseCountChart", name: "TestCaseCountChart",
components: {MsChart}, components: { MsChart },
props: { props: {
chartWidth: Number, chartWidth: Number,
needFullScreen: Boolean, needFullScreen: Boolean,
@ -68,24 +114,28 @@ export default {
type: Boolean, type: Boolean,
default() { default() {
return true; return true;
} },
}, },
charts: [ charts: [
{id: 'bar', name: this.$t('commons.report_statistics.bar')}, { id: "bar", name: this.$t("commons.report_statistics.bar") },
{id: 'pie', name: this.$t('commons.report_statistics.pie')} { id: "pie", name: this.$t("commons.report_statistics.pie") },
], ],
order: "", order: "",
orders: [{id: '', name: this.$t('commons.sort_default')}, { orders: [
id: 'desc', { id: "", name: this.$t("commons.sort_default") },
name: this.$t('commons.report_statistics.desc') {
}, { id: "desc",
id: 'asc', name: this.$t("commons.report_statistics.desc"),
name: this.$t('commons.report_statistics.asc') },
}], {
id: "asc",
name: this.$t("commons.report_statistics.asc"),
},
],
loading: false, loading: false,
options: {}, options: {},
chartType: "bar", chartType: "bar",
} };
}, },
created() { created() {
this.dataOption = this.loadOption; this.dataOption = this.loadOption;
@ -104,32 +154,109 @@ export default {
}, },
}, },
methods: { methods: {
reCountWidth(isHideOther) {
if (isHideOther) {
this.w = document.documentElement.clientWidth - 50;
} else {
this.w = document.documentElement.clientWidth - 760;
}
this.generateOption(this.chartType);
this.countChartWidth();
this.reload();
},
countChartWidth() { countChartWidth() {
if (this.chartWidth === 0 || this.chartType === 'bar') { if (this.chartWidth === 0 || this.chartType === "bar") {
this.chartWidthNumber = this.w; this.chartWidthNumber = this.w;
} else if (
this.chartType === "pie" &&
this.dataOption.width &&
this.dataOption.width > 0
) {
this.chartWidthNumber = this.dataOption.width;
} else { } else {
this.chartWidthNumber = this.chartWidth; this.chartWidthNumber = this.chartWidth;
} }
}, },
orderCharts() { orderCharts() {
this.$emit('orderCharts', this.order); this.$emit("orderCharts", this.order);
}, },
generateOption(chartTypeParam) { generateOption(chartTypeParam) {
if (chartTypeParam) { if (chartTypeParam) {
this.chartType = chartTypeParam; this.chartType = chartTypeParam;
} }
if (this.chartType === 'pie') { if (this.chartType === "pie") {
this.dataOption = this.pieOption; this.dataOption = this.formatPieOption(this.pieOption);
} else { } else {
this.dataOption = this.loadOption; this.dataOption = this.loadOption;
} }
if (this.dataOption.series) { if (this.dataOption.series) {
this.dataOption.series.forEach(item => { this.dataOption.series.forEach((item) => {
item.type = this.chartType; item.type = chartTypeParam;
}); });
} }
this.reload(); this.reload();
}, },
formatPieOption(reformatPieOption) {
let finalPieOption = reformatPieOption;
/**
* 根据现有的 width动态计算饼图各个模块的位置
* 假设饼图共n个 一行为c一共r行
* r 200 + 350 * c <= width 的最大值
* 高度为 n/r 进一法取整数 height = 200*n
* 第一行
* 第一个饼图位置(center属性)"center": ["200", "150"],title位置"left": "200","top": "200",
* 第二个饼图位置(center属性)"center": ["550", "150"],title位置"left": "550","top": "200",
* 第二行
* 第一个饼图位置(center属性)"center": ["200", "350"],title位置"left": "200","top": "400",
* 第二个饼图位置(center属性)"center": ["550", "350"],title位置"left": "550","top": "400",
*/
this.h = document.documentElement.clientHeight * 0.5;
// "series" "title"
if (
finalPieOption.series &&
finalPieOption.title &&
finalPieOption.series.length > 0 &&
finalPieOption.series.length === finalPieOption.title.length
) {
let cNum = 0;
let rNum = 0;
let countWidth = this.w;
let countHeight = 0;
let allPieCount = finalPieOption.series.length;
if (countWidth >= 550) {
countWidth = countWidth - 200;
do {
cNum++;
countWidth = countWidth - 350;
} while (countWidth > 350);
if (cNum > 0) {
rNum = Math.ceil(allPieCount / cNum);
countHeight = 200 * rNum + 100;
if (countHeight > this.h) {
this.h = countHeight;
}
let pieRowIndex = 1; //
for (let pieIndex = 0; pieIndex < allPieCount; pieIndex++) {
if (pieIndex >= cNum * pieRowIndex) {
pieRowIndex++;
}
let pieInRow = pieIndex;
if (pieIndex >= cNum) {
pieInRow = pieIndex % cNum;
}
finalPieOption.series[pieIndex].center = [
200 + 350 * pieInRow,
200 * pieRowIndex - 60,
];
finalPieOption.title[pieIndex].left = 200 + 350 * pieInRow;
finalPieOption.title[pieIndex].top = 200 * pieRowIndex;
}
finalPieOption.width = this.w;
}
}
}
return finalPieOption;
},
setPieOptionAndBarOption(barOption, pieOption) { setPieOptionAndBarOption(barOption, pieOption) {
if (barOption) { if (barOption) {
this.loadOption = barOption; this.loadOption = barOption;
@ -139,33 +266,32 @@ export default {
} }
}, },
reload() { reload() {
this.loading = true this.loading = true;
this.$nextTick(() => { this.$nextTick(() => {
this.loading = false; this.loading = false;
}) });
}, },
fullScreen() { fullScreen() {
this.originalW = this.w; this.originalW = this.w;
this.originalH = this.h;
this.w = document.body.clientWidth - 50; this.w = document.body.clientWidth - 50;
this.h = document.body.clientHeight;
this.isFullScreen = true; this.isFullScreen = true;
this.$emit('hidePage', true); this.$emit("hidePage", true);
}, },
unFullScreen() { unFullScreen() {
this.w = this.originalW; this.w = this.originalW;
this.h = this.originalH;
this.isFullScreen = false; this.isFullScreen = false;
this.$emit('hidePage', false); this.$emit("hidePage", false);
}, },
getImages(command) { getImages(command) {
let imageType = 'image/png'; let imageType = "image/png";
if (command === 'jpg') { if (command === "jpg") {
imageType = 'image/jpg'; imageType = "image/jpg";
} }
let returnImageData = ""; let returnImageData = "";
if (document.getElementById('picChart')) { if (document.getElementById("picChart")) {
let chartsCanvas = document.getElementById('picChart').querySelectorAll('canvas')[0]; let chartsCanvas = document
.getElementById("picChart")
.querySelectorAll("canvas")[0];
if (chartsCanvas) { if (chartsCanvas) {
// toDataURL()canvascanvasbase64 // toDataURL()canvascanvasbase64
returnImageData = chartsCanvas && chartsCanvas.toDataURL(imageType); returnImageData = chartsCanvas && chartsCanvas.toDataURL(imageType);
@ -175,46 +301,50 @@ export default {
return returnImageData; return returnImageData;
}, },
exportCommand(command) { exportCommand(command) {
let fileName = 'report_pic.' + command; let fileName = "report_pic." + command;
if (document.getElementById('picChart')) { if (document.getElementById("picChart")) {
let chartsCanvas = document.getElementById('picChart').querySelectorAll('canvas')[0] let chartsCanvas = document
let mime = 'image/png'; .getElementById("picChart")
if (command === 'jpg') { .querySelectorAll("canvas")[0];
mime = 'image/jpg'; let mime = "image/png";
if (command === "jpg") {
mime = "image/jpg";
} }
if (chartsCanvas) { if (chartsCanvas) {
// toDataURL()canvascanvasbase64 // toDataURL()canvascanvasbase64
let imageUrl = chartsCanvas && chartsCanvas.toDataURL(mime) let imageUrl = chartsCanvas && chartsCanvas.toDataURL(mime);
if (navigator.userAgent.indexOf('Trident') > -1) { if (navigator.userAgent.indexOf("Trident") > -1) {
// IE11 // IE11
let arr = imageUrl.split(',') let arr = imageUrl.split(",");
// atob() 使base64 // atob() 使base64
let bstr = atob(arr[1]) let bstr = atob(arr[1]);
let bstrLen = bstr.length let bstrLen = bstr.length;
// Uint8Array, 8 0 // Uint8Array, 8 0
let u8arr = new Uint8Array(bstrLen) let u8arr = new Uint8Array(bstrLen);
while (bstrLen--) { while (bstrLen--) {
// charCodeAt() Unicode // charCodeAt() Unicode
u8arr[bstrLen] = bstr.charCodeAt(bstrLen) u8arr[bstrLen] = bstr.charCodeAt(bstrLen);
} }
// msSaveOrOpenBlob Internet // msSaveOrOpenBlob Internet
window.navigator.msSaveOrOpenBlob(new Blob([u8arr], {type: mime}), fileName); window.navigator.msSaveOrOpenBlob(
new Blob([u8arr], { type: mime }),
fileName
);
} else { } else {
// //
let $a = document.createElement('a') let $a = document.createElement("a");
$a.setAttribute('href', imageUrl) $a.setAttribute("href", imageUrl);
$a.setAttribute('download', fileName) $a.setAttribute("download", fileName);
$a.click() $a.click();
} }
} }
} }
}, },
}, },
} };
</script> </script>
<style scoped> <style scoped>
.ms-test-chart-header { .ms-test-chart-header {
z-index: 999; z-index: 999;
width: 380px; width: 380px;
@ -234,7 +364,7 @@ export default {
font-size: 18px; font-size: 18px;
} }
:deep(.echarts ) { :deep(.echarts) {
height: calc(100vh / 1.95); height: calc(100vh / 1.95);
} }
@ -258,8 +388,7 @@ export default {
width: 100%; width: 100%;
} }
:deep(.el-card__body ) { :deep(.el-card__body) {
padding: 0px; padding: 0px;
} }
</style> </style>

View File

@ -1,46 +1,63 @@
<template> <template>
<div v-loading="loading" class="ms-div"> <div v-loading="loading" class="ms-div">
<el-card :style="{ width: w+'px'}"> <el-card :style="{ width: w + 'px' }">
<el-row style="padding-top: 10px"> <el-row style="padding-top: 10px">
<p class="tip"><span style="margin-left: 5px"></span>{{ $t('commons.report_statistics.excel') }} </p> <p class="tip">
<span style="margin-left: 5px"></span
>{{ $t("commons.report_statistics.excel") }}
</p>
</el-row> </el-row>
<el-row> <el-row>
<el-table <el-table
:data="tableData" :data="tableData"
:max-height="tableHeight" :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
row-key="id" row-key="id"
border border
class="ms-table"> class="ms-table"
<el-table-column >
prop="name" <el-table-column prop="name" :label="groupName"> </el-table-column>
:label="groupName">
</el-table-column>
<el-table-column <el-table-column
prop="allCount" prop="allCount"
:label="$t('commons.report_statistics.count')"> :label="$t('commons.report_statistics.count')"
>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
prop="testCaseCount" prop="testCaseCount"
:label="$t('api_test.home_page.failed_case_list.table_value.case_type.functional')" :label="
$t(
'api_test.home_page.failed_case_list.table_value.case_type.functional'
)
"
v-if="isShowColumn('testCase')" v-if="isShowColumn('testCase')"
> >
</el-table-column> </el-table-column>
<el-table-column <el-table-column
prop="apiCaseCount" prop="apiCaseCount"
:label="$t('api_test.home_page.failed_case_list.table_value.case_type.api')" :label="
$t(
'api_test.home_page.failed_case_list.table_value.case_type.api'
)
"
v-if="isShowColumn('apiCase')" v-if="isShowColumn('apiCase')"
> >
</el-table-column> </el-table-column>
<el-table-column <el-table-column
prop="scenarioCaseCount" prop="scenarioCaseCount"
:label="$t('api_test.home_page.failed_case_list.table_value.case_type.scene')" :label="
$t(
'api_test.home_page.failed_case_list.table_value.case_type.scene'
)
"
v-if="isShowColumn('scenarioCase')" v-if="isShowColumn('scenarioCase')"
> >
</el-table-column> </el-table-column>
<el-table-column <el-table-column
prop="loadCaseCount" prop="loadCaseCount"
:label="$t('api_test.home_page.failed_case_list.table_value.case_type.load')" :label="
$t(
'api_test.home_page.failed_case_list.table_value.case_type.load'
)
"
v-if="isShowColumn('loadCase')" v-if="isShowColumn('loadCase')"
> >
</el-table-column> </el-table-column>
@ -62,15 +79,15 @@ export default {
type: Boolean, type: Boolean,
default() { default() {
return false; return false;
} },
} },
}, },
data() { data() {
return { return {
tableHeight: "100px", tableHeight: "100px",
w: document.documentElement.clientWidth - 760, w: document.documentElement.clientWidth - 760,
loading: false, loading: false,
} };
}, },
created() { created() {
this.getTableHeight(); this.getTableHeight();
@ -79,25 +96,30 @@ export default {
} }
}, },
methods: { methods: {
reCountWidth(isHideOther) {
if (isHideOther) {
this.w = document.documentElement.clientWidth - 50;
} else {
this.w = document.documentElement.clientWidth - 760;
}
},
isShowColumn(type) { isShowColumn(type) {
if (this.showColumns) { if (this.showColumns) {
return this.showColumns.findIndex(item => item === type) >= 0; return this.showColumns.findIndex((item) => item === type) >= 0;
} else { } else {
return false; return false;
} }
}, },
getTableHeight() { getTableHeight() {
let countNumber = document.documentElement.clientHeight * 0.4 / 1 - 140; let countNumber = (document.documentElement.clientHeight * 0.4) / 1 - 140;
countNumber = Math.ceil(countNumber); countNumber = Math.ceil(countNumber);
this.tableHeight = countNumber + 'px'; this.tableHeight = countNumber + "px";
} },
}, },
} };
</script> </script>
<style scoped> <style scoped>
.tip { .tip {
float: left; float: left;
font-size: 14px; font-size: 14px;
@ -115,8 +137,7 @@ export default {
margin: 20px; margin: 20px;
} }
:deep( .el-card__body ) { :deep(.el-card__body) {
padding: 0px; padding: 0px;
} }
</style> </style>

View File

@ -1,23 +1,36 @@
<template> <template>
<div :style="{ height: h + 'px'}"> <div :style="{ height: h + 'px' }">
<el-container v-loading="loading" style="overflow: scroll"> <el-container v-loading="loading" style="overflow: scroll">
<el-container class="ms-row"> <el-container class="ms-row">
<el-aside :width="!isHide ?'235px':'0px'" style="margin-left: 5px; max-height: 843px"> <el-aside
<history-report-data report-type="TEST_CASE_ANALYSIS" :width="!isHide ? '235px' : '0px'"
@selectReport="selectReport" @removeHistoryReportId="removeHistoryReportId" style="margin-left: 5px; max-height: 843px"
ref="historyReport"/> >
<history-report-data
report-type="TEST_CASE_ANALYSIS"
@selectReport="selectReport"
@removeHistoryReportId="removeHistoryReportId"
ref="historyReport"
/>
</el-aside> </el-aside>
<el-main class="ms-main" id="reportAnalysis"> <el-main class="ms-main" id="reportAnalysis">
<div> <div>
<test-analysis-chart @hidePage="hidePage" @orderCharts="orderCharts" ref="analysisChart" <test-analysis-chart
:load-option="loadOption"/> @hidePage="hidePage"
@orderCharts="orderCharts"
ref="analysisChart"
:load-option="loadOption"
/>
</div> </div>
<div class="ms-row" v-if="!isHide"> <div class="ms-row" v-if="!isHide">
<test-analysis-table :tableData="tableData"/> <test-analysis-table :tableData="tableData" />
</div> </div>
</el-main> </el-main>
<el-aside :width="!isHide ?'485px':'0px'"> <el-aside :width="!isHide ? '485px' : '0px'">
<test-analysis-filter @filterCharts="filterCharts" ref="analysisFilter"/> <test-analysis-filter
@filterCharts="filterCharts"
ref="analysisFilter"
/>
</el-aside> </el-aside>
</el-container> </el-container>
</el-container> </el-container>
@ -28,16 +41,25 @@
import TestAnalysisChart from "./chart/TestAnalysisChart"; import TestAnalysisChart from "./chart/TestAnalysisChart";
import TestAnalysisTable from "./table/TestAnalysisTable"; import TestAnalysisTable from "./table/TestAnalysisTable";
import TestAnalysisFilter from "./filter/TestAnalysisFilter"; import TestAnalysisFilter from "./filter/TestAnalysisFilter";
import {getCurrentProjectID} from "metersphere-frontend/src/utils/token"; import { getCurrentProjectID } from "metersphere-frontend/src/utils/token";
import {exportPdf} from "metersphere-frontend/src/utils"; import { exportPdf } from "metersphere-frontend/src/utils";
import {getAnalysisReport} from "@/api/report"; import { getAnalysisReport } from "@/api/report";
import html2canvas from 'html2canvas'; import html2canvas from "html2canvas";
import HistoryReportData from "../../base/HistoryReportData"; import HistoryReportData from "../../base/HistoryReportData";
import {createHistoryReport, selectHistoryReportById, updateHistoryReport} from "@/api/history-report"; import {
createHistoryReport,
selectHistoryReportById,
updateHistoryReport,
} from "@/api/history-report";
export default { export default {
name: "TestAnalysisContainer", name: "TestAnalysisContainer",
components: {TestAnalysisChart, TestAnalysisTable, TestAnalysisFilter, HistoryReportData}, components: {
TestAnalysisChart,
TestAnalysisTable,
TestAnalysisFilter,
HistoryReportData,
},
data() { data() {
return { return {
isHide: false, isHide: false,
@ -49,35 +71,41 @@ export default {
yAxis: {}, yAxis: {},
label: {}, label: {},
tooltip: {}, tooltip: {},
series: [] series: [],
}, },
tableData: [], tableData: [],
h: document.documentElement.clientHeight - 40, h: document.documentElement.clientHeight - 40,
} };
}, },
methods: { methods: {
handleExport() { handleExport() {
let name = this.$t('commons.report_statistics.test_case_analysis'); this.hidePage(true);
let name = this.$t("commons.report_statistics.test_case_analysis");
this.$nextTick(function () { this.$nextTick(function () {
setTimeout(() => { setTimeout(() => {
html2canvas(document.getElementById('reportAnalysis'), { html2canvas(document.getElementById("reportAnalysis"), {
scale: 2 scale: 2,
}).then(function (canvas) { })
exportPdf(name, [canvas]); .then(function (canvas) {
}); exportPdf(name, [canvas]);
})
.then(() => {
this.hidePage(false);
});
}, 1000); }, 1000);
}); });
}, },
hidePage(isHide) { hidePage(isHide) {
this.isHide = isHide; this.isHide = isHide;
this.$refs.analysisChart.reCountWidth(isHide);
}, },
close() { close() {
this.$emit('closePage'); this.$emit("closePage");
}, },
init(opt) { init(opt) {
this.loading = true; this.loading = true;
this.options = opt; this.options = opt;
getAnalysisReport(opt).then(response => { getAnalysisReport(opt).then((response) => {
let data = response.data.chartDTO; let data = response.data.chartDTO;
let tableDTOs = response.data.tableDTOs; let tableDTOs = response.data.tableDTOs;
this.initPic(data, tableDTOs); this.initPic(data, tableDTOs);
@ -102,9 +130,9 @@ export default {
tableData: this.tableData, tableData: this.tableData,
}; };
obj.dataOption = JSON.stringify(dataOptionObj); obj.dataOption = JSON.stringify(dataOptionObj);
obj.reportType = 'TEST_CASE_ANALYSIS'; obj.reportType = "TEST_CASE_ANALYSIS";
updateHistoryReport(obj).then(() => { updateHistoryReport(obj).then(() => {
this.$alert(this.$t('commons.save_success')); this.$alert(this.$t("commons.save_success"));
this.$refs.historyReport.initReportData(); this.$refs.historyReport.initReportData();
}); });
}, },
@ -119,9 +147,9 @@ export default {
tableData: this.tableData, tableData: this.tableData,
}; };
obj.dataOption = JSON.stringify(dataOptionObj); obj.dataOption = JSON.stringify(dataOptionObj);
obj.reportType = 'TEST_CASE_ANALYSIS'; obj.reportType = "TEST_CASE_ANALYSIS";
createHistoryReport(obj).then(() => { createHistoryReport(obj).then(() => {
this.$alert(this.$t('commons.save_success')); this.$alert(this.$t("commons.save_success"));
this.$refs.historyReport.initReportData(); this.$refs.historyReport.initReportData();
}); });
}, },
@ -132,11 +160,11 @@ export default {
this.loadOption.xAxis = loadOptionParam.xaxis; this.loadOption.xAxis = loadOptionParam.xaxis;
this.loadOption.series = loadOptionParam.series; this.loadOption.series = loadOptionParam.series;
this.loadOption.grid = { this.loadOption.grid = {
bottom: '75px',// bottom: "75px", //
} };
this.loadOption.series.forEach(item => { this.loadOption.series.forEach((item) => {
item.type = this.$refs.analysisChart.chartType; item.type = this.$refs.analysisChart.chartType;
}) });
} }
if (tableData) { if (tableData) {
this.tableData = tableData; this.tableData = tableData;
@ -148,32 +176,37 @@ export default {
if (selectId) { if (selectId) {
this.loading = true; this.loading = true;
let paramObj = { let paramObj = {
id: selectId id: selectId,
} };
selectHistoryReportById(paramObj).then(response => { selectHistoryReportById(paramObj)
let reportData = response.data; .then((response) => {
if (reportData) { let reportData = response.data;
selectName = reportData.name; if (reportData) {
if (reportData.dataOption) { selectName = reportData.name;
let dataOptionObj = JSON.parse(reportData.dataOption); if (reportData.dataOption) {
this.initPic(dataOptionObj.loadOption, dataOptionObj.pieOption, dataOptionObj.tableData); let dataOptionObj = JSON.parse(reportData.dataOption);
this.initPic(
dataOptionObj.loadOption,
dataOptionObj.pieOption,
dataOptionObj.tableData
);
}
if (reportData.selectOption) {
let selectOptionObj = JSON.parse(reportData.selectOption);
this.$refs.analysisFilter.initSelectOption(selectOptionObj);
}
} }
if (reportData.selectOption) { this.$emit("initHistoryReportId", selectId, selectName);
let selectOptionObj = JSON.parse(reportData.selectOption); this.loading = false;
this.$refs.analysisFilter.initSelectOption(selectOptionObj); })
} .catch(() => {
} this.loading = false;
this.$emit('initHistoryReportId', selectId, selectName); this.removeHistoryReportId();
this.loading = false; });
}).catch(() => {
this.loading = false;
this.removeHistoryReportId();
});
} }
}, },
removeHistoryReportId() { removeHistoryReportId() {
this.$emit('initHistoryReportId', "", ""); this.$emit("initHistoryReportId", "", "");
}, },
selectAndSaveReport(reportName) { selectAndSaveReport(reportName) {
let opt = this.$refs.analysisFilter.getOption(); let opt = this.$refs.analysisFilter.getOption();
@ -181,14 +214,14 @@ export default {
this.saveReport(reportName); this.saveReport(reportName);
}, },
saveAndSaveAsReport(reportName, saveType) { saveAndSaveAsReport(reportName, saveType) {
if (saveType === 'save') { if (saveType === "save") {
this.saveReport(reportName); this.saveReport(reportName);
} else if (saveType === 'saveAs') { } else if (saveType === "saveAs") {
this.selectAndSaveReport(reportName); this.selectAndSaveReport(reportName);
} }
} },
}, },
} };
</script> </script>
<style scoped> <style scoped>
@ -196,7 +229,7 @@ export default {
padding-top: 10px; padding-top: 10px;
} }
:deep(.el-main ) { :deep(.el-main) {
padding: 0px 20px 0px; padding: 0px 20px 0px;
} }
</style> </style>

View File

@ -1,25 +1,69 @@
<template> <template>
<div v-loading="loading"> <div v-loading="loading">
<el-card class="ms-test-chart" :style="{ width: w+'px', height: h + 'px'}" ref="msDrawer"> <el-card
class="ms-test-chart"
:style="{ width: w + 'px', height: h + 'px' }"
ref="msDrawer"
>
<el-row class="ms-row"> <el-row class="ms-row">
<p class="tip"><span style="margin-left: 5px"></span> {{ $t('commons.report_statistics.chart') }} </p> <p class="tip">
<span style="margin-left: 5px"></span>
{{ $t("commons.report_statistics.chart") }}
</p>
<div class="ms-test-chart-header"> <div class="ms-test-chart-header">
<el-select v-model="chartType" class="ms-col-type" size="mini" style="width: 100px" @change="generateOption"> <el-select
<el-option :key="t.id" :value="t.id" :label="t.name" v-for="t in charts"/> v-model="chartType"
class="ms-col-type"
size="mini"
style="width: 100px"
@change="generateOption"
>
<el-option
:key="t.id"
:value="t.id"
:label="t.name"
v-for="t in charts"
/>
</el-select> </el-select>
<span style="margin: 0px 10px 10px">|</span> <span style="margin: 0px 10px 10px">|</span>
<el-select v-model="order" class="ms-col-type" size="mini" style="width: 120px" @change="orderCharts"> <el-select
<el-option :key="t.id" :value="t.id" :label="t.name" v-for="t in orders"/> v-model="order"
class="ms-col-type"
size="mini"
style="width: 120px"
@change="orderCharts"
>
<el-option
:key="t.id"
:value="t.id"
:label="t.name"
v-for="t in orders"
/>
</el-select> </el-select>
<span style="margin: 0px 10px 10px">|</span> <span style="margin: 0px 10px 10px">|</span>
<font-awesome-icon v-if="!isFullScreen && showFullScreen" class="report-alt-ico" :icon="['fa', 'expand-alt']" size="lg" <font-awesome-icon
@click="fullScreen"/> v-if="!isFullScreen && showFullScreen"
<font-awesome-icon v-if="isFullScreen && showFullScreen" class="report-alt-ico" :icon="['fa', 'compress-alt']" size="lg" class="report-alt-ico"
@click="unFullScreen"/> :icon="['fa', 'expand-alt']"
size="lg"
@click="fullScreen"
/>
<font-awesome-icon
v-if="isFullScreen && showFullScreen"
class="report-alt-ico"
:icon="['fa', 'compress-alt']"
size="lg"
@click="unFullScreen"
/>
</div> </div>
</el-row> </el-row>
<el-row> <el-row>
<ms-chart ref="chart1" :options="loadOption" class="chart-config" :autoresize="true"/> <ms-chart
ref="chart1"
:options="loadOption"
class="chart-config"
:autoresize="true"
/>
</el-row> </el-row>
</el-card> </el-card>
</div> </div>
@ -32,7 +76,7 @@ import MsChart from "metersphere-frontend/src/components/chart/MsChart";
export default { export default {
name: "TestAnalysisChart", name: "TestAnalysisChart",
components: {MsChart}, components: { MsChart },
props: { props: {
loadOption: {}, loadOption: {},
}, },
@ -49,35 +93,50 @@ export default {
type: Boolean, type: Boolean,
default() { default() {
return true; return true;
} },
}, },
// //
chartType: "line", chartType: "line",
charts: [{id: 'line', name: this.$t('commons.report_statistics.line')}, {id: 'bar', name: this.$t('commons.report_statistics.bar')}], charts: [
{ id: "line", name: this.$t("commons.report_statistics.line") },
{ id: "bar", name: this.$t("commons.report_statistics.bar") },
],
order: "", order: "",
orders: [{id: '', name: this.$t('commons.sort_default')}, {id: 'desc', name: this.$t('commons.report_statistics.desc')}, { orders: [
id: 'asc', { id: "", name: this.$t("commons.sort_default") },
name: this.$t('commons.report_statistics.asc') { id: "desc", name: this.$t("commons.report_statistics.desc") },
}], {
id: "asc",
name: this.$t("commons.report_statistics.asc"),
},
],
loading: false, loading: false,
options: {}, options: {},
} };
}, },
methods: { methods: {
reCountWidth(isHideOther) {
if (isHideOther) {
this.w = document.documentElement.clientWidth - 20;
} else {
this.w = document.documentElement.clientWidth - 760;
}
this.reload();
},
orderCharts() { orderCharts() {
this.$emit('orderCharts', this.order); this.$emit("orderCharts", this.order);
}, },
generateOption() { generateOption() {
this.loadOption.series.forEach(item => { this.loadOption.series.forEach((item) => {
item.type = this.chartType; item.type = this.chartType;
}) });
this.reload(); this.reload();
}, },
reload() { reload() {
this.loading = true this.loading = true;
this.$nextTick(() => { this.$nextTick(() => {
this.loading = false this.loading = false;
}) });
}, },
fullScreen() { fullScreen() {
this.originalW = this.w; this.originalW = this.w;
@ -85,23 +144,22 @@ export default {
this.w = document.body.clientWidth - 50; this.w = document.body.clientWidth - 50;
this.h = document.body.clientHeight; this.h = document.body.clientHeight;
this.isFullScreen = true; this.isFullScreen = true;
this.$emit('hidePage', true); this.$emit("hidePage", true);
}, },
unFullScreen() { unFullScreen() {
this.w = this.originalW; this.w = this.originalW;
this.h = this.originalH; this.h = this.originalH;
this.isFullScreen = false; this.isFullScreen = false;
this.$emit('hidePage', false); this.$emit("hidePage", false);
}, },
getOptions() { getOptions() {
return this.loadOption; return this.loadOption;
} },
}, },
} };
</script> </script>
<style scoped> <style scoped>
.ms-test-chart-header { .ms-test-chart-header {
z-index: 999; z-index: 999;
width: 320px; width: 320px;
@ -121,7 +179,7 @@ export default {
font-size: 18px; font-size: 18px;
} }
:deep(.echarts ) { :deep(.echarts) {
height: calc(100vh / 1.95); height: calc(100vh / 1.95);
} }
@ -141,8 +199,7 @@ export default {
width: 100%; width: 100%;
} }
:deep(.el-card__body ) { :deep(.el-card__body) {
padding: 0px; padding: 0px;
} }
</style> </style>