mirror of
https://gitee.com/fit2cloud-feizhiyun/MeterSphere.git
synced 2024-11-30 19:18:59 +08:00
feat(测试计划): 测试计划组联调批量开启关闭定时任务&部分集成报告页面&拆分报告总结和报告头
This commit is contained in:
parent
5ad2775eb8
commit
4d0cbcbf7a
4
frontend/src/assets/svg/case_total.svg
Normal file
4
frontend/src/assets/svg/case_total.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="24" height="24" rx="4" fill="#9441B1"/>
|
||||||
|
<path d="M12.6667 4.66666L12.7207 4.66866L12.7661 4.674C12.7774 4.67574 12.789 4.67779 12.8006 4.68015C12.8152 4.68308 12.8297 4.68655 12.844 4.69047C12.8544 4.69339 12.8646 4.69648 12.8748 4.69982C12.8879 4.70407 12.9011 4.70887 12.9141 4.71406L12.9489 4.72911C12.9627 4.73557 12.9763 4.74252 12.9896 4.74991C12.9977 4.75432 13.0058 4.75906 13.0139 4.76399C13.0309 4.77447 13.0475 4.78565 13.0635 4.79753L13.0781 4.80861C13.0799 4.81004 13.0818 4.81157 13.0837 4.81312L13.1382 4.86192L16.4715 8.19525L16.5167 8.24666L16.5359 8.26979C16.5478 8.2859 16.5589 8.30247 16.5694 8.31955L16.5836 8.34395C16.5909 8.35713 16.5978 8.37073 16.6043 8.38459L16.6193 8.4191C16.6245 8.43227 16.6293 8.44546 16.6337 8.45883C16.6369 8.46876 16.64 8.47901 16.6429 8.48933C16.6469 8.5037 16.6503 8.51816 16.6533 8.53279C16.6556 8.54441 16.6577 8.55602 16.6594 8.56768C16.6605 8.5745 16.6614 8.58169 16.6623 8.58891L16.6643 8.60917C16.6653 8.62115 16.666 8.63315 16.6664 8.64515L16.6667 8.66666V12H15.3334V9.33332H12.6667C12.3249 9.33332 12.0431 9.07596 12.0046 8.7444L12.0001 8.66666V5.99999H6.00008V18H12.0001V19.3333H6.00008C5.2637 19.3333 4.66675 18.7364 4.66675 18V5.99999C4.66675 5.26361 5.2637 4.66666 6.00008 4.66666H12.6667ZM16.5388 12.8667C17.716 12.8667 18.5581 13.6422 18.6667 14.6371H17.2451C17.1636 14.2924 16.9735 14.0261 16.5298 14.0261H15.579C15.0538 14.0261 14.7188 14.4099 14.7188 14.8721L14.7279 17.3278C14.7279 17.79 15.0629 18.1739 15.5881 18.1739H16.5388C16.9735 18.1739 17.1546 17.9232 17.2451 17.5864H18.6758C18.5671 18.5656 17.716 19.3333 16.5479 19.3333H15.579C14.3476 19.3333 13.3425 18.4638 13.3425 17.3983L13.3334 14.8095C13.3334 13.7362 14.3385 12.8667 15.57 12.8667H16.5388ZM13.3334 6.94332V7.99999H14.3901L13.3334 6.94332Z" fill="white"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
11
frontend/src/assets/svg/plan_total.svg
Normal file
11
frontend/src/assets/svg/plan_total.svg
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="24" height="24" rx="4" fill="#3370FF"/>
|
||||||
|
<g clip-path="url(#clip0_2905_27805)">
|
||||||
|
<path d="M15.3334 5.33334C15.7016 5.33334 16.0001 5.63182 16.0001 6.00001V6.66668H18.0001C18.7365 6.66668 19.3334 7.26363 19.3334 8.00001V17.3333C19.3334 18.0697 18.7365 18.6667 18.0001 18.6667H6.00008C5.2637 18.6667 4.66675 18.0697 4.66675 17.3333V8.00001C4.66675 7.26363 5.2637 6.66668 6.00008 6.66668H8.00008V6.00001C8.00008 5.63182 8.29856 5.33334 8.66675 5.33334C9.03494 5.33334 9.33341 5.63182 9.33341 6.00001V6.66668H14.6667V6.00001C14.6667 5.63182 14.9652 5.33334 15.3334 5.33334ZM8.00008 8.00001H6.00008V17.3333H18.0001V8.00001H16.0001V8.66668C16.0001 9.03487 15.7016 9.33334 15.3334 9.33334C14.9652 9.33334 14.6667 9.03487 14.6667 8.66668V8.00001H9.33341V8.66668C9.33341 9.03487 9.03494 9.33334 8.66675 9.33334C8.29856 9.33334 8.00008 9.03487 8.00008 8.66668V8.00001ZM15.3334 13.6667C15.7016 13.6667 16.0001 13.9652 16.0001 14.3333C16.0001 14.7015 15.7016 15 15.3334 15H8.66675C8.29856 15 8.00008 14.7015 8.00008 14.3333C8.00008 13.9652 8.29856 13.6667 8.66675 13.6667H15.3334ZM12.3334 11C12.7016 11 13.0001 11.2985 13.0001 11.6667C13.0001 12.0349 12.7016 12.3333 12.3334 12.3333H8.66675C8.29856 12.3333 8.00008 12.0349 8.00008 11.6667C8.00008 11.2985 8.29856 11 8.66675 11H12.3334Z" fill="white"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_2905_27805">
|
||||||
|
<rect width="16" height="16" fill="white" transform="translate(4 4)"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
@ -46,6 +46,7 @@
|
|||||||
v-if="attrs.selectorType === 'checkbox'"
|
v-if="attrs.selectorType === 'checkbox'"
|
||||||
:value="getChecked(record)"
|
:value="getChecked(record)"
|
||||||
:indeterminate="getIndeterminate(record)"
|
:indeterminate="getIndeterminate(record)"
|
||||||
|
:disabled="isDisabledChildren(record)"
|
||||||
@click.stop
|
@click.stop
|
||||||
@change="rowSelectChange(record)"
|
@change="rowSelectChange(record)"
|
||||||
/>
|
/>
|
||||||
@ -329,6 +330,10 @@
|
|||||||
excludeKeys: Set<string>;
|
excludeKeys: Set<string>;
|
||||||
selectorStatus: SelectAllEnum;
|
selectorStatus: SelectAllEnum;
|
||||||
actionConfig?: BatchActionConfig;
|
actionConfig?: BatchActionConfig;
|
||||||
|
disabledConfig?: {
|
||||||
|
disabledChildren?: boolean;
|
||||||
|
parentKey?: string;
|
||||||
|
};
|
||||||
noDisable?: boolean;
|
noDisable?: boolean;
|
||||||
showSetting?: boolean;
|
showSetting?: boolean;
|
||||||
columns: MsTableColumn;
|
columns: MsTableColumn;
|
||||||
@ -737,6 +742,14 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isDisabledChildren(record: TableData) {
|
||||||
|
if (!props.disabledConfig?.disabledChildren) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 子级禁用
|
||||||
|
return !!record[props.disabledConfig.parentKey || 'parent'];
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await initColumn();
|
await initColumn();
|
||||||
batchLeft.value = getBatchLeft();
|
batchLeft.value = getBatchLeft();
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
import type { PassRateCountDetail, planStatusType, TestPlanDetail } from '@/models/testPlan/testPlan';
|
import type { PassRateCountDetail, planStatusType, TestPlanDetail } from '@/models/testPlan/testPlan';
|
||||||
import type { PlanReportDetail, StatusListType } from '@/models/testPlan/testPlanReport';
|
import type { countDetail, PlanReportDetail, StatusListType } from '@/models/testPlan/testPlanReport';
|
||||||
import { LastExecuteResults } from '@/enums/caseEnum';
|
import { LastExecuteResults } from '@/enums/caseEnum';
|
||||||
// TODO: 对照后端字段
|
// TODO: 对照后端字段
|
||||||
// 测试计划详情
|
// 测试计划详情
|
||||||
@ -55,6 +57,14 @@ export const defaultExecuteForm = {
|
|||||||
planCommentFileIds: [],
|
planCommentFileIds: [],
|
||||||
notifier: [] as string[],
|
notifier: [] as string[],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const defaultCount: countDetail = {
|
||||||
|
success: 0,
|
||||||
|
error: 0,
|
||||||
|
fakeError: 0,
|
||||||
|
block: 0,
|
||||||
|
pending: 0,
|
||||||
|
};
|
||||||
// 报告详情
|
// 报告详情
|
||||||
export const defaultReportDetail: PlanReportDetail = {
|
export const defaultReportDetail: PlanReportDetail = {
|
||||||
id: '',
|
id: '',
|
||||||
@ -68,20 +78,10 @@ export const defaultReportDetail: PlanReportDetail = {
|
|||||||
executeRate: 0, // 执行完成率
|
executeRate: 0, // 执行完成率
|
||||||
bugCount: 0,
|
bugCount: 0,
|
||||||
caseTotal: 0,
|
caseTotal: 0,
|
||||||
executeCount: {
|
executeCount: cloneDeep(defaultCount),
|
||||||
success: 0,
|
functionalCount: cloneDeep(defaultCount),
|
||||||
error: 0,
|
apiCaseCount: cloneDeep(defaultCount),
|
||||||
fakeError: 0,
|
apiScenarioCount: cloneDeep(defaultCount),
|
||||||
block: 0,
|
|
||||||
pending: 0,
|
|
||||||
},
|
|
||||||
functionalCount: {
|
|
||||||
success: 0,
|
|
||||||
error: 0,
|
|
||||||
fakeError: 0,
|
|
||||||
block: 0,
|
|
||||||
pending: 0,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const statusConfig: StatusListType[] = [
|
export const statusConfig: StatusListType[] = [
|
||||||
@ -102,15 +102,14 @@ export const statusConfig: StatusListType[] = [
|
|||||||
key: 'SUCCESS',
|
key: 'SUCCESS',
|
||||||
},
|
},
|
||||||
// TODO 这个版本不展示误报
|
// TODO 这个版本不展示误报
|
||||||
// {
|
{
|
||||||
// label: 'common.fakeError',
|
label: 'common.fakeError',
|
||||||
// value: 'fakeError',
|
value: 'fakeError',
|
||||||
// color: '#FFC14E',
|
color: '#FFC14E',
|
||||||
// class: 'bg-[rgb(var(--warning-6))]',
|
class: 'bg-[rgb(var(--warning-6))]',
|
||||||
// rateKey: 'requestFakeErrorRate',
|
rateKey: 'requestFakeErrorRate',
|
||||||
// key: 'FAKE_ERROR',
|
key: 'FAKE_ERROR',
|
||||||
// },
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
label: 'common.block',
|
label: 'common.block',
|
||||||
value: 'block',
|
value: 'block',
|
||||||
|
@ -19,11 +19,12 @@ export interface PlanReportDetail {
|
|||||||
caseTotal: number;
|
caseTotal: number;
|
||||||
executeCount: countDetail;
|
executeCount: countDetail;
|
||||||
functionalCount: countDetail;
|
functionalCount: countDetail;
|
||||||
// TOTO 这个版本不展示场景和接口
|
apiCaseCount: countDetail; // 接口场景用例分析-用例数
|
||||||
// apiCaseCount: countDetail; // 接口场景用例分析-用例数
|
apiScenarioCount: countDetail; // 接口场景用例分析-用例数
|
||||||
// apiScenarioCount: countDetail; // 接口场景用例分析-用例数
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AnalysisType = 'FUNCTIONAL' | 'API' | 'SCENARIO';
|
||||||
|
|
||||||
export interface ReportMetricsItemModel {
|
export interface ReportMetricsItemModel {
|
||||||
unit: string;
|
unit: string;
|
||||||
value: number | string;
|
value: number | string;
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
<div class="mb-4 flex items-center justify-between">
|
<div class="mb-4 flex items-center justify-between">
|
||||||
<a-radio-group v-model:model-value="showType" type="button" class="file-show-type" @change="changeShowType">
|
<a-radio-group v-model:model-value="showType" type="button" class="file-show-type" @change="changeShowType">
|
||||||
<a-radio value="All">{{ t('report.all') }}</a-radio>
|
<a-radio value="All">{{ t('report.all') }}</a-radio>
|
||||||
<a-radio value="INDEPENDENT">{{ t('report.independent') }}</a-radio>
|
<a-radio value="INDEPENDENT">{{ t('report.detail.testReport') }}</a-radio>
|
||||||
<a-radio value="INTEGRATED">{{ t('report.collection') }}</a-radio>
|
<a-radio value="INTEGRATED">{{ t('report.detail.testPlanGroupReport') }}</a-radio>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
<div class="items-right flex gap-[8px]">
|
<div class="items-right flex gap-[8px]">
|
||||||
<a-input-search
|
<a-input-search
|
||||||
|
@ -65,6 +65,18 @@
|
|||||||
{{ statusExecuteRate.blockRateResult }}
|
{{ statusExecuteRate.blockRateResult }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr v-if="props.status === 'fakeError'" class="popover-tr">
|
||||||
|
<td class="popover-label-td">
|
||||||
|
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[rgb(var(--warning-6))]"></div>
|
||||||
|
<div>{{ t('common.fail') }}</div>
|
||||||
|
</td>
|
||||||
|
<td class="popover-value-td-count">
|
||||||
|
{{ addCommasToNumber(countDetailData.fakeError) }}
|
||||||
|
</td>
|
||||||
|
<td class="popover-value-td-pass">
|
||||||
|
{{ statusExecuteRate.errorRateResult }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr v-if="props.status === 'error'" class="popover-tr">
|
<tr v-if="props.status === 'error'" class="popover-tr">
|
||||||
<td class="popover-label-td">
|
<td class="popover-label-td">
|
||||||
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[rgb(var(--danger-6))]"></div>
|
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[rgb(var(--danger-6))]"></div>
|
||||||
@ -88,16 +100,18 @@
|
|||||||
|
|
||||||
import MsColorLine from '@/components/pure/ms-color-line/index.vue';
|
import MsColorLine from '@/components/pure/ms-color-line/index.vue';
|
||||||
|
|
||||||
import { statusConfig } from '@/config/testPlan';
|
import { defaultCount, statusConfig } from '@/config/testPlan';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import { addCommasToNumber } from '@/utils';
|
import { addCommasToNumber } from '@/utils';
|
||||||
|
|
||||||
import type { countDetail, PlanReportDetail } from '@/models/testPlan/testPlanReport';
|
import type { AnalysisType, countDetail, PlanReportDetail } from '@/models/testPlan/testPlanReport';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
detail: PlanReportDetail;
|
detail: PlanReportDetail;
|
||||||
status: keyof countDetail;
|
status: keyof countDetail;
|
||||||
|
type: AnalysisType;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const defaultStatus = {
|
const defaultStatus = {
|
||||||
@ -113,8 +127,15 @@
|
|||||||
return statusConfig.find((e) => e.value === props.status) || defaultStatus;
|
return statusConfig.find((e) => e.value === props.status) || defaultStatus;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const analysisTypeMap: Record<AnalysisType, keyof PlanReportDetail> = {
|
||||||
|
FUNCTIONAL: 'functionalCount',
|
||||||
|
API: 'apiCaseCount',
|
||||||
|
SCENARIO: 'apiScenarioCount',
|
||||||
|
};
|
||||||
|
|
||||||
const countDetailData = computed(() => {
|
const countDetailData = computed(() => {
|
||||||
return props.detail.functionalCount;
|
const countKey = analysisTypeMap[props.type] as keyof PlanReportDetail;
|
||||||
|
return props.detail[countKey] ? (props.detail[countKey] as countDetail) : defaultCount;
|
||||||
});
|
});
|
||||||
|
|
||||||
const colorData = computed(() => {
|
const colorData = computed(() => {
|
||||||
|
@ -1,16 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<MsCard v-if="!props.isDrawer" class="mb-[16px]" hide-back hide-footer auto-height no-content-padding hide-divider>
|
<ReportHeader v-if="!props.isDrawer" :detail="detail" :share-id="shareId" />
|
||||||
<template #headerLeft>
|
|
||||||
<div class="flex items-center font-medium">
|
|
||||||
<a-tooltip :content="detail.name" :mouse-enter-delay="300"
|
|
||||||
><div class="one-line-text max-w-[300px]">{{ detail.name }}</div>
|
|
||||||
</a-tooltip>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #headerRight>
|
|
||||||
<PlanDetailHeaderRight :share-id="shareId" :detail="detail" />
|
|
||||||
</template>
|
|
||||||
</MsCard>
|
|
||||||
<div class="analysis-wrapper">
|
<div class="analysis-wrapper">
|
||||||
<div class="analysis min-w-[238px]">
|
<div class="analysis min-w-[238px]">
|
||||||
<div class="block-title">{{ t('report.detail.api.reportAnalysis') }}</div>
|
<div class="block-title">{{ t('report.detail.api.reportAnalysis') }}</div>
|
||||||
@ -30,16 +19,16 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- TODO 接口用例&场景用例待联调 -->
|
|
||||||
<div class="analysis-wrapper">
|
<div class="analysis-wrapper">
|
||||||
<div class="analysis min-w-[330px]">
|
<div class="analysis min-w-[330px]">
|
||||||
<div class="block-title">{{ t('report.detail.useCaseAnalysis') }}</div>
|
<div class="block-title">{{ t('report.detail.useCaseAnalysis') }}</div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="w-[70%]">
|
<div class="w-[70%]">
|
||||||
<SingleStatusProgress :detail="detail" status="pending" />
|
<SingleStatusProgress :detail="detail" type="FUNCTIONAL" status="pending" />
|
||||||
<SingleStatusProgress :detail="detail" status="success" />
|
<SingleStatusProgress :detail="detail" type="FUNCTIONAL" status="success" />
|
||||||
<SingleStatusProgress :detail="detail" status="block" />
|
<SingleStatusProgress :detail="detail" type="FUNCTIONAL" status="block" />
|
||||||
<SingleStatusProgress :detail="detail" status="error" />
|
<SingleStatusProgress :detail="detail" type="FUNCTIONAL" status="error" />
|
||||||
</div>
|
</div>
|
||||||
<div class="relative w-[30%] min-w-[150px]">
|
<div class="relative w-[30%] min-w-[150px]">
|
||||||
<div class="charts absolute w-full text-center">
|
<div class="charts absolute w-full text-center">
|
||||||
@ -62,14 +51,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- TODO 接口用例&场景用例待联调 -->
|
||||||
<div class="analysis min-w-[330px]">
|
<div class="analysis min-w-[330px]">
|
||||||
<div class="block-title">{{ t('report.detail.apiUseCaseAnalysis') }}</div>
|
<div class="block-title">{{ t('report.detail.apiUseCaseAnalysis') }}</div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="w-[70%]">
|
<div class="w-[70%]">
|
||||||
<SingleStatusProgress :detail="detail" status="pending" />
|
<SingleStatusProgress type="API" :detail="detail" status="pending" />
|
||||||
<SingleStatusProgress :detail="detail" status="success" />
|
<SingleStatusProgress type="API" :detail="detail" status="success" />
|
||||||
<SingleStatusProgress :detail="detail" status="block" />
|
<SingleStatusProgress type="API" :detail="detail" status="fakeError" />
|
||||||
<SingleStatusProgress :detail="detail" status="error" />
|
<SingleStatusProgress type="API" :detail="detail" status="error" />
|
||||||
</div>
|
</div>
|
||||||
<div class="relative w-[30%] min-w-[150px]">
|
<div class="relative w-[30%] min-w-[150px]">
|
||||||
<div class="charts absolute w-full text-center">
|
<div class="charts absolute w-full text-center">
|
||||||
@ -96,10 +86,10 @@
|
|||||||
<div class="block-title">{{ t('report.detail.scenarioUseCaseAnalysis') }}</div>
|
<div class="block-title">{{ t('report.detail.scenarioUseCaseAnalysis') }}</div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="w-[70%]">
|
<div class="w-[70%]">
|
||||||
<SingleStatusProgress :detail="detail" status="pending" />
|
<SingleStatusProgress type="SCENARIO" :detail="detail" status="pending" />
|
||||||
<SingleStatusProgress :detail="detail" status="success" />
|
<SingleStatusProgress type="SCENARIO" :detail="detail" status="success" />
|
||||||
<SingleStatusProgress :detail="detail" status="block" />
|
<SingleStatusProgress type="SCENARIO" :detail="detail" status="fakeError" />
|
||||||
<SingleStatusProgress :detail="detail" status="error" />
|
<SingleStatusProgress type="SCENARIO" :detail="detail" status="error" />
|
||||||
</div>
|
</div>
|
||||||
<div class="relative w-[30%] min-w-[150px]">
|
<div class="relative w-[30%] min-w-[150px]">
|
||||||
<div class="charts absolute w-full text-center">
|
<div class="charts absolute w-full text-center">
|
||||||
@ -123,35 +113,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MsCard class="mb-[16px]" simple auto-height auto-width>
|
<Summary
|
||||||
<div class="font-medium">{{ t('report.detail.reportSummary') }}</div>
|
v-model:richText="richText"
|
||||||
<div
|
:share-id="shareId"
|
||||||
:class="`${hasAnyPermission(['PROJECT_TEST_PLAN_REPORT:READ+UPDATE']) && !shareId ? '' : 'cursor-not-allowed'}`"
|
:show-button="showButton"
|
||||||
>
|
@update-summary="handleUpdateReportDetail"
|
||||||
<MsRichText
|
@cancel="handleCancel"
|
||||||
v-model:raw="richText.summary"
|
@handle-summary="handleSummary"
|
||||||
v-model:filedIds="richText.richTextTmpFileIds"
|
/>
|
||||||
:upload-image="handleUploadImage"
|
|
||||||
:preview-url="PreviewEditorImageUrl"
|
|
||||||
class="mt-[8px] w-full"
|
|
||||||
:editable="!!shareId"
|
|
||||||
/>
|
|
||||||
<MsFormItemSub
|
|
||||||
v-if="hasAnyPermission(['PROJECT_TEST_PLAN_REPORT:READ+UPDATE']) && !shareId && showButton"
|
|
||||||
:text="t('report.detail.oneClickSummary')"
|
|
||||||
:show-fill-icon="true"
|
|
||||||
@fill="handleSummary"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-show="showButton && hasAnyPermission(['PROJECT_TEST_PLAN_REPORT:READ+UPDATE']) && !shareId"
|
|
||||||
class="mt-[16px] flex items-center gap-[12px]"
|
|
||||||
>
|
|
||||||
<a-button type="primary" @click="handleUpdateReportDetail">{{ t('common.save') }}</a-button>
|
|
||||||
<a-button type="secondary" @click="handleCancel">{{ t('common.cancel') }}</a-button>
|
|
||||||
</div>
|
|
||||||
</MsCard>
|
|
||||||
<MsCard simple auto-height auto-width>
|
<MsCard simple auto-height auto-width>
|
||||||
<MsTab
|
<MsTab
|
||||||
v-model:active-key="activeTab"
|
v-model:active-key="activeTab"
|
||||||
@ -176,27 +145,29 @@
|
|||||||
|
|
||||||
import MsChart from '@/components/pure/chart/index.vue';
|
import MsChart from '@/components/pure/chart/index.vue';
|
||||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||||
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
|
|
||||||
import MsTab from '@/components/pure/ms-tab/index.vue';
|
import MsTab from '@/components/pure/ms-tab/index.vue';
|
||||||
import MsFormItemSub from '@/components/business/ms-form-item-sub/index.vue';
|
|
||||||
import PlanDetailHeaderRight from './planDetailHeaderRight.vue';
|
|
||||||
import ReportMetricsItem from './ReportMetricsItem.vue';
|
import ReportMetricsItem from './ReportMetricsItem.vue';
|
||||||
import SetReportChart from '@/views/api-test/report/component/case/setReportChart.vue';
|
import SetReportChart from '@/views/api-test/report/component/case/setReportChart.vue';
|
||||||
import SingleStatusProgress from '@/views/test-plan/report/component/singleStatusProgress.vue';
|
import SingleStatusProgress from '@/views/test-plan/report/component/singleStatusProgress.vue';
|
||||||
import ApiCaseTable from '@/views/test-plan/report/detail/component/apiCaseTable.vue';
|
import ApiCaseTable from '@/views/test-plan/report/detail/component/apiCaseTable.vue';
|
||||||
import BugTable from '@/views/test-plan/report/detail/component/bugTable.vue';
|
import BugTable from '@/views/test-plan/report/detail/component/bugTable.vue';
|
||||||
import FeatureCaseTable from '@/views/test-plan/report/detail/component/featureCaseTable.vue';
|
import FeatureCaseTable from '@/views/test-plan/report/detail/component/featureCaseTable.vue';
|
||||||
|
import ReportHeader from '@/views/test-plan/report/detail/component/reportHeader.vue';
|
||||||
import ScenarioCaseTable from '@/views/test-plan/report/detail/component/scenarioCaseTable.vue';
|
import ScenarioCaseTable from '@/views/test-plan/report/detail/component/scenarioCaseTable.vue';
|
||||||
|
import Summary from '@/views/test-plan/report/detail/component/summary.vue';
|
||||||
|
|
||||||
import { editorUploadFile, updateReportDetail } from '@/api/modules/test-plan/report';
|
import { updateReportDetail } from '@/api/modules/test-plan/report';
|
||||||
import { PreviewEditorImageUrl } from '@/api/requrls/case-management/featureCase';
|
|
||||||
import { defaultReportDetail, statusConfig } from '@/config/testPlan';
|
import { defaultReportDetail, statusConfig } from '@/config/testPlan';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import { addCommasToNumber } from '@/utils';
|
import { addCommasToNumber } from '@/utils';
|
||||||
import { hasAnyPermission } from '@/utils/permission';
|
|
||||||
|
|
||||||
import type { LegendData } from '@/models/apiTest/report';
|
import type { LegendData } from '@/models/apiTest/report';
|
||||||
import type { PlanReportDetail, ReportMetricsItemModel, StatusListType } from '@/models/testPlan/testPlanReport';
|
import type {
|
||||||
|
countDetail,
|
||||||
|
PlanReportDetail,
|
||||||
|
ReportMetricsItemModel,
|
||||||
|
StatusListType,
|
||||||
|
} from '@/models/testPlan/testPlanReport';
|
||||||
|
|
||||||
import { getIndicators } from '@/views/api-test/report/utils';
|
import { getIndicators } from '@/views/api-test/report/utils';
|
||||||
|
|
||||||
@ -213,7 +184,7 @@
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
const detail = ref<PlanReportDetail>({ ...cloneDeep(defaultReportDetail) });
|
const detail = ref<PlanReportDetail>({ ...cloneDeep(defaultReportDetail) });
|
||||||
const showButton = ref(false);
|
const showButton = ref<boolean>(false);
|
||||||
const richText = ref<{ summary: string; richTextTmpFileIds?: string[] }>({
|
const richText = ref<{ summary: string; richTextTmpFileIds?: string[] }>({
|
||||||
summary: '',
|
summary: '',
|
||||||
});
|
});
|
||||||
@ -373,13 +344,6 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleUploadImage(file: File) {
|
|
||||||
const { data } = await editorUploadFile({
|
|
||||||
fileList: [file],
|
|
||||||
});
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleUpdateReportDetail() {
|
async function handleUpdateReportDetail() {
|
||||||
try {
|
try {
|
||||||
await updateReportDetail({
|
await updateReportDetail({
|
||||||
@ -395,10 +359,6 @@
|
|||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function handleCancel() {
|
|
||||||
richText.value = { summary: detail.value.summary };
|
|
||||||
showButton.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const reportAnalysisList = computed<ReportMetricsItemModel[]>(() => [
|
const reportAnalysisList = computed<ReportMetricsItemModel[]>(() => [
|
||||||
{
|
{
|
||||||
@ -471,10 +431,68 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const summaryContent = ref<string>(`
|
function getSummaryDetail(detailCount: countDetail) {
|
||||||
<p style=""><span color="" fontsize="">本次完成 测试计划名称,功能测试,接口测试;共 300条 用例,已执行 285 条,未执行 15 条,执行率为 95%,通过用例 270 条,通过率为 90%,达到/未达到通过阈值(通过阈值为85%),xxx计划满足/不满足发布要求。<br>(1)本次测试包含100条功能测试用例,执行了95条,未执行5条,执行率为95%,通过用例90条,通过率为90%。共发现缺陷0个。<br>(2)本次测试包含100条接口测试用例,执行了95条,未执行5条,执行率为95%,通过用例90条,通过率为90%。共发现缺陷0个。<br>(3)本次测试包含100条场景测试用例,执行了95条,未执行5条,执行率为95%,通过用例90条,通过率为90%。共发现缺陷0个</span></p>
|
if (detailCount) {
|
||||||
`);
|
const { success, error, fakeError, pending, block } = detailCount;
|
||||||
// 一键总结 TODO 待联调
|
// 已执行用例
|
||||||
|
const hasExecutedCase = success + error + fakeError + block;
|
||||||
|
// 用例总数
|
||||||
|
const caseTotal = hasExecutedCase + pending;
|
||||||
|
// 执行率
|
||||||
|
const executedCount = (hasExecutedCase / caseTotal) * 100;
|
||||||
|
const apiExecutedRate = `${Number.isNaN(executedCount) ? 0 : executedCount.toFixed(2)}%`;
|
||||||
|
// 通过率
|
||||||
|
const successCount = (success / caseTotal) * 100;
|
||||||
|
const successRate = `${Number.isNaN(successCount) ? 0 : successCount.toFixed(2)}%`;
|
||||||
|
return {
|
||||||
|
hasExecutedCase,
|
||||||
|
caseTotal,
|
||||||
|
apiExecutedRate,
|
||||||
|
successRate,
|
||||||
|
pending,
|
||||||
|
success,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
hasExecutedCase: 0,
|
||||||
|
caseTotal: 0,
|
||||||
|
apiExecutedRate: 0,
|
||||||
|
successRate: 0,
|
||||||
|
pending: 0,
|
||||||
|
success: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const summaryContent = computed(() => {
|
||||||
|
const { functionalCount, apiCaseCount, apiScenarioCount } = detail.value;
|
||||||
|
const functionalCaseDetail = getSummaryDetail(functionalCount);
|
||||||
|
const apiCaseDetail = getSummaryDetail(apiCaseCount);
|
||||||
|
const apiScenarioDetail = getSummaryDetail(apiScenarioCount);
|
||||||
|
const allCaseTotal = functionalCaseDetail.caseTotal + apiCaseDetail.caseTotal + apiScenarioDetail.caseTotal;
|
||||||
|
const allHasExecutedCase =
|
||||||
|
functionalCaseDetail.hasExecutedCase + apiCaseDetail.hasExecutedCase + apiScenarioDetail.hasExecutedCase;
|
||||||
|
const allPendingCase = functionalCaseDetail.pending + apiCaseDetail.pending + apiScenarioDetail.pending;
|
||||||
|
const allSuccessCase = functionalCaseDetail.success + apiCaseDetail.success + apiScenarioDetail.success;
|
||||||
|
|
||||||
|
const allExecutedCount = (allHasExecutedCase / allCaseTotal) * 100;
|
||||||
|
const allExecutedRate = `${Number.isNaN(allExecutedCount) ? 0 : allExecutedCount.toFixed(2)}%`;
|
||||||
|
|
||||||
|
// 通过率
|
||||||
|
const allSuccessCount = (allSuccessCase / allCaseTotal) * 100;
|
||||||
|
const allSuccessRate = `${Number.isNaN(allSuccessCount) ? 0 : allSuccessCount.toFixed(2)}%`;
|
||||||
|
// 接口用例通过率
|
||||||
|
return `<p style=""><span color="" fontsize="">本次完成 ${detail.value.name}的功能测试,接口测试;共 ${allCaseTotal}条 用例,已执行 ${allHasExecutedCase} 条,未执行 ${allPendingCase} 条,执行率为 ${allExecutedRate}%,通过用例 ${allSuccessCase} 条,通过率为 ${allSuccessRate},达到/未达到通过阈值(通过阈值为${detail.value.passThreshold}%),xxx计划满足/不满足发布要求。<br>
|
||||||
|
(1)本次测试包含${functionalCaseDetail.caseTotal}条功能测试用例,执行了${functionalCaseDetail.hasExecutedCase}条,未执行${functionalCaseDetail.pending}条,执行率为${functionalCaseDetail.apiExecutedRate},通过用例${functionalCaseDetail.success}条,通过率为${functionalCaseDetail.successRate}。共发现缺陷0个。<br>
|
||||||
|
(2)本次测试包含${apiCaseDetail.caseTotal}条接口测试用例,执行了${apiCaseDetail.hasExecutedCase}条,未执行${apiCaseDetail.pending}条,执行率为${apiCaseDetail.apiExecutedRate},通过用例${apiCaseDetail.success}条,通过率为${apiCaseDetail.successRate}。共发现缺陷0个。<br>
|
||||||
|
(3)本次测试包含${apiScenarioDetail.caseTotal}条场景测试用例,执行了${apiScenarioDetail.hasExecutedCase}条,未执行${apiScenarioDetail.pending}条,执行率为${apiScenarioDetail.apiExecutedRate}%,通过用例${apiScenarioDetail.success}条,通过率为${apiScenarioDetail.successRate}。共发现缺陷0个</span></p>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleCancel() {
|
||||||
|
richText.value = { summary: detail.value.summary };
|
||||||
|
showButton.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
function handleSummary() {
|
function handleSummary() {
|
||||||
richText.value.summary = summaryContent.value;
|
richText.value.summary = summaryContent.value;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,246 @@
|
|||||||
|
<template>
|
||||||
|
<ReportHeader v-if="!props.isDrawer" :detail="detail" :share-id="shareId" />
|
||||||
|
<div class="analysis-wrapper">
|
||||||
|
<div class="analysis min-w-[238px]">
|
||||||
|
<div class="block-title">{{ t('report.detail.api.reportAnalysis') }}</div>
|
||||||
|
<ReportMetricsItem
|
||||||
|
v-for="analysisItem in reportAnalysisList"
|
||||||
|
:key="analysisItem.name"
|
||||||
|
:item-info="analysisItem"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="analysis min-w-[410px]">
|
||||||
|
<div class="block-title">{{ t('report.detail.executionAnalysis') }}</div>
|
||||||
|
<SetReportChart
|
||||||
|
size="160px"
|
||||||
|
:legend-data="legendData"
|
||||||
|
:options="charOptions"
|
||||||
|
:request-total="getIndicators(detail.caseTotal) || 0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Summary
|
||||||
|
v-model:richText="richText"
|
||||||
|
:share-id="shareId"
|
||||||
|
:show-button="showButton"
|
||||||
|
@update-summary="handleUpdateReportDetail"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
@handle-summary="handleSummary"
|
||||||
|
/>
|
||||||
|
<MsCard>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="block-title">{{ t('report.detail.api.reportDetail') }}</div>
|
||||||
|
<a-radio-group class="mb-2" :model-value="currentMode" type="button" @change="handleModeChange">
|
||||||
|
<a-radio value="drawer">
|
||||||
|
<div class="mode-button">
|
||||||
|
<MsIcon :class="{ 'active-color': currentMode === 'drawer' }" type="icon-icon_drawer" />
|
||||||
|
<span class="mode-button-title">{{ t('msTable.columnSetting.drawer') }}</span>
|
||||||
|
</div>
|
||||||
|
</a-radio>
|
||||||
|
<a-radio value="new_window">
|
||||||
|
<div class="mode-button">
|
||||||
|
<MsIcon :class="{ 'active-color': currentMode === 'new_window' }" type="icon-icon_into-item_outlined" />
|
||||||
|
<span class="mode-button-title">{{ t('msTable.columnSetting.newWindow') }}</span>
|
||||||
|
</div>
|
||||||
|
</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
</div>
|
||||||
|
</MsCard>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { Message } from '@arco-design/web-vue';
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
|
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||||
|
import SetReportChart from '@/views/api-test/report/component/case/setReportChart.vue';
|
||||||
|
import ReportHeader from '@/views/test-plan/report/detail/component/reportHeader.vue';
|
||||||
|
import ReportMetricsItem from '@/views/test-plan/report/detail/component/ReportMetricsItem.vue';
|
||||||
|
import Summary from '@/views/test-plan/report/detail/component/summary.vue';
|
||||||
|
|
||||||
|
import { updateReportDetail } from '@/api/modules/test-plan/report';
|
||||||
|
import { defaultReportDetail, statusConfig } from '@/config/testPlan';
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import { addCommasToNumber } from '@/utils';
|
||||||
|
|
||||||
|
import type { LegendData } from '@/models/apiTest/report';
|
||||||
|
import type {
|
||||||
|
countDetail,
|
||||||
|
PlanReportDetail,
|
||||||
|
ReportMetricsItemModel,
|
||||||
|
StatusListType,
|
||||||
|
} from '@/models/testPlan/testPlanReport';
|
||||||
|
|
||||||
|
import { getIndicators } from '@/views/api-test/report/utils';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const props = defineProps<{
|
||||||
|
detailInfo: PlanReportDetail;
|
||||||
|
isDrawer?: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'updateSuccess'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const detail = ref<PlanReportDetail>({ ...cloneDeep(defaultReportDetail) });
|
||||||
|
const shareId = ref<string>(route.query.shareId as string);
|
||||||
|
const charOptions = ref({
|
||||||
|
tooltip: {
|
||||||
|
show: false,
|
||||||
|
trigger: 'item',
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
series: {
|
||||||
|
name: '',
|
||||||
|
type: 'pie',
|
||||||
|
radius: ['62%', '80%'],
|
||||||
|
center: ['50%', '50%'],
|
||||||
|
avoidLabelOverlap: false,
|
||||||
|
label: {
|
||||||
|
show: false,
|
||||||
|
position: 'center',
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
label: {
|
||||||
|
show: false,
|
||||||
|
fontSize: 40,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
labelLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
value: 0,
|
||||||
|
name: t('common.success'),
|
||||||
|
itemStyle: {
|
||||||
|
color: '#00C261',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 0,
|
||||||
|
name: t('common.fakeError'),
|
||||||
|
itemStyle: {
|
||||||
|
color: '#FFC14E',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 0,
|
||||||
|
name: t('common.fail'),
|
||||||
|
itemStyle: {
|
||||||
|
color: '#ED0303',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 0,
|
||||||
|
name: t('common.unExecute'),
|
||||||
|
itemStyle: {
|
||||||
|
color: '#D4D4D8',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 0,
|
||||||
|
name: t('common.block'),
|
||||||
|
itemStyle: {
|
||||||
|
color: '#B379C8',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const legendData = ref<LegendData[]>([]);
|
||||||
|
const reportAnalysisList = computed<ReportMetricsItemModel[]>(() => [
|
||||||
|
{
|
||||||
|
name: t('report.detail.testPlanTotal'),
|
||||||
|
value: detail.value.passThreshold,
|
||||||
|
unit: t('report.detail.number'),
|
||||||
|
icon: 'plan_total',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t('report.detail.testPlanCaseTotal'),
|
||||||
|
value: detail.value.passRate,
|
||||||
|
unit: t('report.detail.number'),
|
||||||
|
icon: 'case_total',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t('report.passRate'),
|
||||||
|
value: detail.value.executeRate,
|
||||||
|
unit: '%',
|
||||||
|
icon: 'passRate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t('report.detail.totalDefects'),
|
||||||
|
value: addCommasToNumber(detail.value.bugCount),
|
||||||
|
unit: t('report.detail.number'),
|
||||||
|
icon: 'bugTotal',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const summaryContent = ref<string>('');
|
||||||
|
|
||||||
|
const showButton = ref(false);
|
||||||
|
const richText = ref<{ summary: string; richTextTmpFileIds?: string[] }>({
|
||||||
|
summary: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
async function handleUpdateReportDetail() {
|
||||||
|
try {
|
||||||
|
await updateReportDetail({
|
||||||
|
id: detail.value.id,
|
||||||
|
summary: richText.value.summary,
|
||||||
|
richTextTmpFileIds: richText.value.richTextTmpFileIds ?? [],
|
||||||
|
});
|
||||||
|
Message.success(t('common.updateSuccess'));
|
||||||
|
showButton.value = false;
|
||||||
|
emit('updateSuccess');
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancel() {
|
||||||
|
richText.value = { summary: detail.value.summary };
|
||||||
|
showButton.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSummary() {
|
||||||
|
richText.value.summary = summaryContent.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentMode = ref('');
|
||||||
|
const handleModeChange = (value: string | number | boolean) => {
|
||||||
|
currentMode.value = value as string;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.block-title {
|
||||||
|
@apply mb-4 font-medium;
|
||||||
|
}
|
||||||
|
.analysis-wrapper {
|
||||||
|
@apply mb-4 flex flex-wrap items-center gap-4;
|
||||||
|
.analysis {
|
||||||
|
padding: 24px;
|
||||||
|
height: 250px;
|
||||||
|
box-shadow: 0 0 10px rgba(120 56 135/ 5%);
|
||||||
|
@apply flex-1 rounded-xl bg-white;
|
||||||
|
.charts {
|
||||||
|
top: 36%;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 99;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,31 @@
|
|||||||
|
<template>
|
||||||
|
<MsCard v-if="!props.isDrawer" class="mb-[16px]" hide-back hide-footer auto-height no-content-padding hide-divider>
|
||||||
|
<template #headerLeft>
|
||||||
|
<div class="flex items-center font-medium">
|
||||||
|
<a-tooltip :content="detail.name" :mouse-enter-delay="300"
|
||||||
|
><div class="one-line-text max-w-[300px]">{{ detail.name }}</div>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #headerRight>
|
||||||
|
<PlanDetailHeaderRight :share-id="shareId" :detail="detail" />
|
||||||
|
</template>
|
||||||
|
</MsCard>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||||
|
import PlanDetailHeaderRight from '@/views/test-plan/report/detail/component/planDetailHeaderRight.vue';
|
||||||
|
|
||||||
|
import type { PlanReportDetail } from '@/models/testPlan/testPlanReport';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
detail: PlanReportDetail;
|
||||||
|
shareId?: string;
|
||||||
|
isDrawer?: boolean;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
@ -0,0 +1,81 @@
|
|||||||
|
<template>
|
||||||
|
<MsCard class="mb-[16px]" simple auto-height auto-width>
|
||||||
|
<div class="font-medium">{{ t('report.detail.reportSummary') }}</div>
|
||||||
|
<div
|
||||||
|
:class="`${hasAnyPermission(['PROJECT_TEST_PLAN_REPORT:READ+UPDATE']) && !shareId ? '' : 'cursor-not-allowed'}`"
|
||||||
|
>
|
||||||
|
<MsRichText
|
||||||
|
v-model:raw="innerSummary.summary"
|
||||||
|
v-model:filedIds="innerSummary.richTextTmpFileIds"
|
||||||
|
:upload-image="handleUploadImage"
|
||||||
|
:preview-url="PreviewEditorImageUrl"
|
||||||
|
class="mt-[8px] w-full"
|
||||||
|
:editable="!!shareId"
|
||||||
|
/>
|
||||||
|
<MsFormItemSub
|
||||||
|
v-if="hasAnyPermission(['PROJECT_TEST_PLAN_REPORT:READ+UPDATE']) && !shareId && props.showButton"
|
||||||
|
:text="t('report.detail.oneClickSummary')"
|
||||||
|
:show-fill-icon="true"
|
||||||
|
@fill="handleSummary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-show="props.showButton && hasAnyPermission(['PROJECT_TEST_PLAN_REPORT:READ+UPDATE']) && !shareId"
|
||||||
|
class="mt-[16px] flex items-center gap-[12px]"
|
||||||
|
>
|
||||||
|
<a-button type="primary" @click="handleUpdateReportDetail">{{ t('common.save') }}</a-button>
|
||||||
|
<a-button type="secondary" @click="handleCancel">{{ t('common.cancel') }}</a-button>
|
||||||
|
</div>
|
||||||
|
</MsCard>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useVModel } from '@vueuse/core';
|
||||||
|
|
||||||
|
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||||
|
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
|
||||||
|
import MsFormItemSub from '@/components/business/ms-form-item-sub/index.vue';
|
||||||
|
|
||||||
|
import { editorUploadFile } from '@/api/modules/test-plan/report';
|
||||||
|
import { PreviewEditorImageUrl } from '@/api/requrls/case-management/featureCase';
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import { hasAnyPermission } from '@/utils/permission';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const props = defineProps<{
|
||||||
|
richText: { summary: string; richTextTmpFileIds?: string[] };
|
||||||
|
shareId?: string;
|
||||||
|
showButton: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'updateSummary'): void;
|
||||||
|
(e: 'cancel'): void;
|
||||||
|
(e: 'handleSummary'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const innerSummary = useVModel(props, 'richText', emit);
|
||||||
|
|
||||||
|
function handleCancel() {
|
||||||
|
emit('cancel');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUpdateReportDetail() {
|
||||||
|
emit('updateSummary');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleUploadImage(file: File) {
|
||||||
|
const { data } = await editorUploadFile({
|
||||||
|
fileList: [file],
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSummary() {
|
||||||
|
emit('handleSummary');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
@ -0,0 +1,36 @@
|
|||||||
|
<template>
|
||||||
|
<PlanGroupDetail :detail-info="detail" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
|
import PlanGroupDetail from '@/views/test-plan/report/detail/component/planGroupDetail.vue';
|
||||||
|
|
||||||
|
import { getReportDetail } from '@/api/modules/test-plan/report';
|
||||||
|
import { defaultReportDetail } from '@/config/testPlan';
|
||||||
|
|
||||||
|
import type { PlanReportDetail } from '@/models/testPlan/testPlanReport';
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const reportId = ref<string>(route.query.id as string);
|
||||||
|
|
||||||
|
const detail = ref<PlanReportDetail>(cloneDeep(defaultReportDetail));
|
||||||
|
|
||||||
|
async function getDetail() {
|
||||||
|
try {
|
||||||
|
detail.value = await getReportDetail(reportId.value);
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
getDetail();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
@ -40,4 +40,6 @@ export default {
|
|||||||
'report.detail.apiCaseDetails': 'Api use case details',
|
'report.detail.apiCaseDetails': 'Api use case details',
|
||||||
'report.detail.scenarioCaseDetails': 'Scenario use case details',
|
'report.detail.scenarioCaseDetails': 'Scenario use case details',
|
||||||
'report.detail.oneClickSummary': 'One click report summary',
|
'report.detail.oneClickSummary': 'One click report summary',
|
||||||
|
'report.detail.testPlanTotal': 'Total plan',
|
||||||
|
'report.detail.testPlanCaseTotal': 'Total use cases',
|
||||||
};
|
};
|
||||||
|
@ -40,4 +40,8 @@ export default {
|
|||||||
'report.detail.apiCaseDetails': '接口用例明细',
|
'report.detail.apiCaseDetails': '接口用例明细',
|
||||||
'report.detail.scenarioCaseDetails': '场景用例明细',
|
'report.detail.scenarioCaseDetails': '场景用例明细',
|
||||||
'report.detail.oneClickSummary': '一键填写报告总结',
|
'report.detail.oneClickSummary': '一键填写报告总结',
|
||||||
|
'report.detail.testReport': '测试报告',
|
||||||
|
'report.detail.testPlanGroupReport': '测试组报告',
|
||||||
|
'report.detail.testPlanTotal': '计划总数',
|
||||||
|
'report.detail.testPlanCaseTotal': '用例总数',
|
||||||
};
|
};
|
||||||
|
@ -39,6 +39,10 @@
|
|||||||
:selectable="hasOperationPermission && showType !== testPlanTypeEnum.ALL"
|
:selectable="hasOperationPermission && showType !== testPlanTypeEnum.ALL"
|
||||||
filter-icon-align-left
|
filter-icon-align-left
|
||||||
:expanded-keys="expandedKeys"
|
:expanded-keys="expandedKeys"
|
||||||
|
:disabled-config="{
|
||||||
|
disabledChildren: true,
|
||||||
|
parentKey: 'parent',
|
||||||
|
}"
|
||||||
v-on="propsEvent"
|
v-on="propsEvent"
|
||||||
@batch-action="handleTableBatch"
|
@batch-action="handleTableBatch"
|
||||||
@filter-change="filterChange"
|
@filter-change="filterChange"
|
||||||
@ -365,6 +369,7 @@
|
|||||||
batchArchivedPlan,
|
batchArchivedPlan,
|
||||||
batchCopyPlan,
|
batchCopyPlan,
|
||||||
batchDeletePlan,
|
batchDeletePlan,
|
||||||
|
batchEditTestPlan,
|
||||||
batchMovePlan,
|
batchMovePlan,
|
||||||
deletePlan,
|
deletePlan,
|
||||||
deleteScheduleTask,
|
deleteScheduleTask,
|
||||||
@ -383,7 +388,7 @@
|
|||||||
import { characterLimit } from '@/utils';
|
import { characterLimit } from '@/utils';
|
||||||
import { hasAnyPermission } from '@/utils/permission';
|
import { hasAnyPermission } from '@/utils/permission';
|
||||||
|
|
||||||
import { DragSortParams, ModuleTreeNode } from '@/models/common';
|
import { DragSortParams, ModuleTreeNode, TableQueryParams } from '@/models/common';
|
||||||
import type {
|
import type {
|
||||||
AddTestPlanParams,
|
AddTestPlanParams,
|
||||||
BatchMoveParams,
|
BatchMoveParams,
|
||||||
@ -993,9 +998,41 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 打开关闭定时任务 TODO 待联调
|
* 打开关闭定时任务
|
||||||
*/
|
*/
|
||||||
function handleStatusTimingTask(enable: boolean) {}
|
async function handleStatusTimingTask(enable: boolean) {
|
||||||
|
const filterParams = {
|
||||||
|
...propsRes.value.filter,
|
||||||
|
};
|
||||||
|
if (isArchived.value) {
|
||||||
|
filterParams.status = ['ARCHIVED'];
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const { selectedIds, selectAll, excludeIds } = batchParams.value;
|
||||||
|
const params: TableQueryParams = {
|
||||||
|
selectIds: selectedIds || [],
|
||||||
|
selectAll: !!selectAll,
|
||||||
|
excludeIds: excludeIds || [],
|
||||||
|
projectId: appStore.currentProjectId,
|
||||||
|
moduleIds: props.activeFolder === 'all' ? [] : [props.activeFolder, ...props.offspringIds],
|
||||||
|
condition: {
|
||||||
|
filter: filterParams,
|
||||||
|
keyword: keyword.value,
|
||||||
|
},
|
||||||
|
type: showType.value,
|
||||||
|
scheduleOpen: enable,
|
||||||
|
};
|
||||||
|
await batchEditTestPlan(params);
|
||||||
|
Message.success(
|
||||||
|
enable
|
||||||
|
? t('testPlan.testPlanGroup.enableScheduleTaskSuccess')
|
||||||
|
: t('testPlan.testPlanGroup.closeScheduleTaskSuccess')
|
||||||
|
);
|
||||||
|
fetchData();
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 归档测试计划以及计划组
|
* 归档测试计划以及计划组
|
||||||
|
@ -161,7 +161,7 @@
|
|||||||
await configSchedule(params);
|
await configSchedule(params);
|
||||||
handleCancel();
|
handleCancel();
|
||||||
emit('handleSuccess');
|
emit('handleSuccess');
|
||||||
Message.success(t('common.createSuccess'));
|
Message.success(props.taskConfig ? t('common.updateSuccess') : t('common.createSuccess'));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
@ -134,4 +134,6 @@ export default {
|
|||||||
'testPlan.testPlanGroup.batchArchivedGroup': 'Confirm archive: {count} test plan groups',
|
'testPlan.testPlanGroup.batchArchivedGroup': 'Confirm archive: {count} test plan groups',
|
||||||
'testPlan.testPlanGroup.confirmBatchDeletePlanGroup': 'Are you sure to delete {count} test plan groups?',
|
'testPlan.testPlanGroup.confirmBatchDeletePlanGroup': 'Are you sure to delete {count} test plan groups?',
|
||||||
'testPlan.testPlanGroup.deleteScheduleTaskSuccess': 'Delete the scheduled task successfully',
|
'testPlan.testPlanGroup.deleteScheduleTaskSuccess': 'Delete the scheduled task successfully',
|
||||||
|
'testPlan.testPlanGroup.enableScheduleTaskSuccess': 'Start the scheduled task successfully',
|
||||||
|
'testPlan.testPlanGroup.closeScheduleTaskSuccess': 'Scheduled mission closed successfully',
|
||||||
};
|
};
|
||||||
|
@ -123,4 +123,6 @@ export default {
|
|||||||
'testPlan.testPlanGroup.batchArchivedGroup': '确认归档:{count} 个测试计划组吗',
|
'testPlan.testPlanGroup.batchArchivedGroup': '确认归档:{count} 个测试计划组吗',
|
||||||
'testPlan.testPlanGroup.confirmBatchDeletePlanGroup': '确认删除 {count} 个测试计划组吗?',
|
'testPlan.testPlanGroup.confirmBatchDeletePlanGroup': '确认删除 {count} 个测试计划组吗?',
|
||||||
'testPlan.testPlanGroup.deleteScheduleTaskSuccess': '删除定时任务成功',
|
'testPlan.testPlanGroup.deleteScheduleTaskSuccess': '删除定时任务成功',
|
||||||
|
'testPlan.testPlanGroup.enableScheduleTaskSuccess': '开启定时任务成功',
|
||||||
|
'testPlan.testPlanGroup.closeScheduleTaskSuccess': '关闭定时任务成功',
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user