feat(接口测试): 用例报告联调部分
This commit is contained in:
parent
abe982852a
commit
09cc282696
|
@ -37,7 +37,7 @@ export function reportBathDelete(moduleType: string, data: TableQueryParams) {
|
||||||
return MSR.post({ url: reportUrl.ApiBatchDeleteUrl, data });
|
return MSR.post({ url: reportUrl.ApiBatchDeleteUrl, data });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 报告详情
|
// Api报告详情
|
||||||
export function reportDetail(reportId: string) {
|
export function reportDetail(reportId: string) {
|
||||||
return MSR.get<ReportDetail>({ url: `${reportUrl.ScenarioReportDetailUrl}/${reportId}` });
|
return MSR.get<ReportDetail>({ url: `${reportUrl.ScenarioReportDetailUrl}/${reportId}` });
|
||||||
}
|
}
|
||||||
|
@ -45,5 +45,14 @@ export function reportDetail(reportId: string) {
|
||||||
export function reportStepDetail(reportId: string, stepId: string) {
|
export function reportStepDetail(reportId: string, stepId: string) {
|
||||||
return MSR.get<ReportStepDetail>({ url: `${reportUrl.ScenarioReportDetailStepUrl}/${reportId}/${stepId}` });
|
return MSR.get<ReportStepDetail>({ url: `${reportUrl.ScenarioReportDetailStepUrl}/${reportId}/${stepId}` });
|
||||||
}
|
}
|
||||||
|
// 用例报告详情
|
||||||
|
export function reportCaseDetail(reportId: string) {
|
||||||
|
return MSR.get<ReportDetail>({ url: `${reportUrl.CaseReportDetailUrl}/${reportId}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 报告步骤详情
|
||||||
|
export function reportCaseStepDetail(reportId: string, stepId: string) {
|
||||||
|
return MSR.get<ReportStepDetail[]>({ url: `${reportUrl.CaseStepDetailStepUrl}/${reportId}/${stepId}` });
|
||||||
|
}
|
||||||
|
|
||||||
export default {};
|
export default {};
|
||||||
|
|
|
@ -16,7 +16,11 @@ export const ApiDeleteUrl = '/api/report/case/delete';
|
||||||
// 批量删除接口用例报告
|
// 批量删除接口用例报告
|
||||||
export const ApiBatchDeleteUrl = '/api/report/case/batch/delete';
|
export const ApiBatchDeleteUrl = '/api/report/case/batch/delete';
|
||||||
|
|
||||||
// 场景报告详情
|
// 场景报告详情(API)
|
||||||
export const ScenarioReportDetailUrl = '/api/report/scenario/get';
|
export const ScenarioReportDetailUrl = '/api/report/scenario/get';
|
||||||
// 报告详情步骤
|
// 场景报告详情(用例)
|
||||||
|
export const CaseReportDetailUrl = '/api/report/case/get';
|
||||||
|
// 报告详情步骤(API)
|
||||||
export const ScenarioReportDetailStepUrl = '/api/report/scenario/get/detail';
|
export const ScenarioReportDetailStepUrl = '/api/report/scenario/get/detail';
|
||||||
|
// 报告详情步骤(用例)
|
||||||
|
export const CaseStepDetailStepUrl = '/api/report/case/get/detail';
|
||||||
|
|
|
@ -1,63 +1,3 @@
|
||||||
// 步骤
|
|
||||||
export interface ScenarioItemType {
|
|
||||||
stepId: string;
|
|
||||||
reportId: string;
|
|
||||||
name: string; // 步骤名称
|
|
||||||
sort: number; // 序号
|
|
||||||
stepType: string; // 步骤类型/API/CASE等
|
|
||||||
parentId: string; // 父级id
|
|
||||||
status: string; // 结果状态 SUCCESS/ERROR
|
|
||||||
fakeCode: string; // 误报编号/误报状态独有
|
|
||||||
requestName: string; // 请求名称
|
|
||||||
requestTime: number; // 请求耗时
|
|
||||||
code: string; // 请求响应码
|
|
||||||
responseSize: number; // 响应内容大小
|
|
||||||
scriptIdentifier: string; // 脚本标识
|
|
||||||
fold: boolean; // 是否展示折叠
|
|
||||||
children: ScenarioItemType[];
|
|
||||||
level?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ScenarioDetailItem = Partial<ScenarioItemType>;
|
|
||||||
// 报告场景的详情
|
|
||||||
export interface ReportDetail {
|
|
||||||
id: string;
|
|
||||||
name: string; // 报告名称
|
|
||||||
testPlanId: string;
|
|
||||||
createUser: string;
|
|
||||||
deleteTime: number;
|
|
||||||
deleteUser: string;
|
|
||||||
deleted: boolean;
|
|
||||||
updateUser: string;
|
|
||||||
updateTime: number;
|
|
||||||
startTime: number; // 开始时间/同创建时间一致
|
|
||||||
endTime: number; // 结束时间/报告执行完成
|
|
||||||
requestDuration: number; // 请求总耗时
|
|
||||||
status: string; // 报告状态/SUCCESS/ERROR
|
|
||||||
triggerMode: string; // 触发方式
|
|
||||||
runMode: string; // 执行模式
|
|
||||||
poolId: string; // 资源池
|
|
||||||
poolName: string; // 资源池名称
|
|
||||||
versionId: string;
|
|
||||||
integrated: boolean; // 是否是集成报告
|
|
||||||
projectId: string;
|
|
||||||
environmentId: string; // 环境id
|
|
||||||
environmentName: string; // 环境名称
|
|
||||||
errorCount: number; // 失败数
|
|
||||||
fakeErrorCount: number; // 误报数
|
|
||||||
pendingCount: number; // 未执行数
|
|
||||||
successCount: number; // 成功数
|
|
||||||
assertionCount: number; // 总断言数
|
|
||||||
assertionSuccessCount: number; // 成功断言数
|
|
||||||
requestErrorRate: string; // 请求失败率
|
|
||||||
requestPendingRate: string; // 请求未执行率
|
|
||||||
requestFakeErrorRate: string; // 请求误报率
|
|
||||||
requestPassRate: string; // 请求通过率
|
|
||||||
assertionPassRate: string; // 断言通过率
|
|
||||||
scriptIdentifier: string; // 脚本标识
|
|
||||||
children: ScenarioItemType[]; // 步骤列表
|
|
||||||
stepTotal: number; // 步骤总数
|
|
||||||
}
|
|
||||||
export interface LegendData {
|
export interface LegendData {
|
||||||
label: string;
|
label: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
@ -114,11 +54,12 @@ export interface StepContent {
|
||||||
method: string;
|
method: string;
|
||||||
assertionTotal: number;
|
assertionTotal: number;
|
||||||
passAssertionsTotal: number;
|
passAssertionsTotal: number;
|
||||||
subRequestResults: string[];
|
subRequestResults: any;
|
||||||
responseResult: ResponseResult;
|
responseResult: ResponseResult;
|
||||||
isSuccessful: boolean;
|
isSuccessful: boolean;
|
||||||
fakeErrorCode: string;
|
fakeErrorCode: string;
|
||||||
scriptIdentifier: string;
|
scriptIdentifier: string;
|
||||||
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 步骤详情
|
// 步骤详情
|
||||||
|
@ -134,6 +75,73 @@ export interface ReportStepDetailItem {
|
||||||
responseSize: number;
|
responseSize: number;
|
||||||
scriptIdentifier: string;
|
scriptIdentifier: string;
|
||||||
content: StepContent;
|
content: StepContent;
|
||||||
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ReportStepDetail = Partial<ReportStepDetailItem>;
|
export type ReportStepDetail = Partial<ReportStepDetailItem>;
|
||||||
|
|
||||||
|
// 步骤
|
||||||
|
export interface ScenarioItemType {
|
||||||
|
stepId: string;
|
||||||
|
reportId: string;
|
||||||
|
name: string; // 步骤名称
|
||||||
|
sort: number; // 序号
|
||||||
|
stepType: string; // 步骤类型/API/CASE等
|
||||||
|
parentId: string; // 父级id
|
||||||
|
status: string; // 结果状态 SUCCESS/ERROR
|
||||||
|
fakeCode: string; // 误报编号/误报状态独有
|
||||||
|
requestName: string; // 请求名称
|
||||||
|
requestTime: number; // 请求耗时
|
||||||
|
code: string; // 请求响应码
|
||||||
|
responseSize: number; // 响应内容大小
|
||||||
|
scriptIdentifier: string; // 脚本标识
|
||||||
|
fold: boolean; // 是否展示折叠
|
||||||
|
children: ScenarioItemType[];
|
||||||
|
level?: number;
|
||||||
|
stepDetail: ReportStepDetailItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ScenarioDetailItem = Partial<ScenarioItemType>;
|
||||||
|
// 报告场景的详情
|
||||||
|
export interface ReportDetail {
|
||||||
|
id: string;
|
||||||
|
name: string; // 报告名称
|
||||||
|
testPlanId: string;
|
||||||
|
createUser: string;
|
||||||
|
deleteTime: number;
|
||||||
|
deleteUser: string;
|
||||||
|
deleted: boolean;
|
||||||
|
updateUser: string;
|
||||||
|
updateTime: number;
|
||||||
|
startTime: number; // 开始时间/同创建时间一致
|
||||||
|
endTime: number; // 结束时间/报告执行完成
|
||||||
|
requestDuration: number; // 请求总耗时
|
||||||
|
status: string; // 报告状态/SUCCESS/ERROR
|
||||||
|
triggerMode: string; // 触发方式
|
||||||
|
runMode: string; // 执行模式
|
||||||
|
poolId: string; // 资源池
|
||||||
|
poolName: string; // 资源池名称
|
||||||
|
versionId: string;
|
||||||
|
integrated: boolean; // 是否是集成报告
|
||||||
|
projectId: string;
|
||||||
|
environmentId: string; // 环境id
|
||||||
|
environmentName: string; // 环境名称
|
||||||
|
errorCount: number; // 失败数
|
||||||
|
fakeErrorCount: number; // 误报数
|
||||||
|
pendingCount: number; // 未执行数
|
||||||
|
successCount: number; // 成功数
|
||||||
|
assertionCount: number; // 总断言数
|
||||||
|
assertionSuccessCount: number; // 成功断言数
|
||||||
|
requestErrorRate: string; // 请求失败率
|
||||||
|
requestPendingRate: string; // 请求未执行率
|
||||||
|
requestFakeErrorRate: string; // 请求误报率
|
||||||
|
requestPassRate: string; // 请求通过率
|
||||||
|
assertionPassRate: string; // 断言通过率
|
||||||
|
scriptIdentifier: string; // 脚本标识
|
||||||
|
children: ScenarioItemType[]; // 步骤列表
|
||||||
|
stepTotal: number; // 步骤总数
|
||||||
|
console: string; // 控制台
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ReportDetailPartial = Partial<ScenarioItemType>;
|
||||||
|
|
|
@ -862,7 +862,7 @@ export function addCommasToNumber(number: number) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 给树添加深度
|
* 给树添加深度
|
||||||
* @param number 目标值
|
* @param tree 目标树
|
||||||
*/
|
*/
|
||||||
export function addLevelToTree<T>(tree: TreeNode<T>[], level = 0): TreeNode<T>[] {
|
export function addLevelToTree<T>(tree: TreeNode<T>[], level = 0): TreeNode<T>[] {
|
||||||
if (!tree || !Array.isArray(tree)) {
|
if (!tree || !Array.isArray(tree)) {
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex min-h-[110px] items-center justify-between">
|
||||||
|
<div class="relative mr-4">
|
||||||
|
<div class="absolute bottom-0 left-[30%] top-[35%] text-center">
|
||||||
|
<div class="text-[12px] text-[(var(--color-text-4))]">{{ t('report.detail.api.total') }}</div>
|
||||||
|
<div class="text-[18px] font-medium">1</div>
|
||||||
|
</div>
|
||||||
|
<MsChart width="110px" height="110px" :options="props.options" />
|
||||||
|
</div>
|
||||||
|
<div class="chart-legend grid flex-1 gap-y-3">
|
||||||
|
<!-- 图例开始 -->
|
||||||
|
<div v-for="item of props.legendData" :key="item.value" class="chart-legend-item">
|
||||||
|
<div class="chart-flag">
|
||||||
|
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full" :class="item.class"></div>
|
||||||
|
<div class="mr-2 text-[var(--color-text-4)]">{{ item.label }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="count">{{ item.count || 0 }}</div>
|
||||||
|
<div class="count">{{ item.rote || 0 }}%</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* @description 用例报告独立报告
|
||||||
|
*/
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import MsChart from '@/components/pure/chart/index.vue';
|
||||||
|
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
|
import type { LegendData } from '@/models/apiTest/report';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const props = defineProps<{
|
||||||
|
options: Record<string, any>;
|
||||||
|
legendData: LegendData[];
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.chart-legend {
|
||||||
|
.chart-legend-item {
|
||||||
|
@apply grid grid-cols-3 gap-2;
|
||||||
|
}
|
||||||
|
.chart-flag {
|
||||||
|
@apply flex items-center;
|
||||||
|
.count {
|
||||||
|
color: var(--color-text-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,55 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex min-h-[110px] items-center">
|
||||||
|
<div class="relative mr-4">
|
||||||
|
<div class="absolute bottom-0 left-[30%] top-[35%] text-center">
|
||||||
|
<div class="text-[12px] text-[(var(--color-text-4))]">{{ t('report.detail.api.total') }}</div>
|
||||||
|
<div class="text-[18px] font-medium">4</div>
|
||||||
|
</div>
|
||||||
|
<MsChart width="110px" height="110px" :options="props.options" />
|
||||||
|
</div>
|
||||||
|
<div class="chart-legend grid flex-1 gap-y-3">
|
||||||
|
<!-- 图例开始 -->
|
||||||
|
<div v-for="item of props.legendData" :key="item.value" class="chart-legend-item">
|
||||||
|
<div class="chart-flag">
|
||||||
|
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full" :class="item.class"></div>
|
||||||
|
<div class="mr-2 text-[var(--color-text-4)]">{{ item.label }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="count">{{ item.count || 0 }}</div>
|
||||||
|
<div class="count">{{ item.rote || 0 }}%</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* @description 用例报告独立报告
|
||||||
|
*/
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import MsChart from '@/components/pure/chart/index.vue';
|
||||||
|
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
|
import type { LegendData } from '@/models/apiTest/report';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const props = defineProps<{
|
||||||
|
options: Record<string, any>;
|
||||||
|
legendData: LegendData[];
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.chart-legend {
|
||||||
|
.chart-legend-item {
|
||||||
|
@apply grid grid-cols-3;
|
||||||
|
}
|
||||||
|
.chart-flag {
|
||||||
|
@apply flex items-center;
|
||||||
|
.count {
|
||||||
|
color: var(--color-text-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,469 @@
|
||||||
|
<template>
|
||||||
|
<MsDetailDrawer
|
||||||
|
ref="detailDrawerRef"
|
||||||
|
v-model:visible="showDrawer"
|
||||||
|
:width="1200"
|
||||||
|
:footer="false"
|
||||||
|
:title="reportStepDetail.name"
|
||||||
|
:detail-id="props.reportId"
|
||||||
|
:detail-index="props.activeReportIndex"
|
||||||
|
:get-detail-func="reportCaseDetail"
|
||||||
|
:pagination="props.pagination"
|
||||||
|
:table-data="props.tableData"
|
||||||
|
:page-change="props.pageChange"
|
||||||
|
show-full-screen
|
||||||
|
:unmount-on-close="true"
|
||||||
|
@loaded="loadedReport"
|
||||||
|
>
|
||||||
|
<template #titleRight="{ loading }">
|
||||||
|
<div class="rightButtons flex items-center">
|
||||||
|
<MsButton
|
||||||
|
type="icon"
|
||||||
|
status="secondary"
|
||||||
|
class="mr-4 !rounded-[var(--border-radius-small)]"
|
||||||
|
:disabled="loading"
|
||||||
|
:loading="shareLoading"
|
||||||
|
@click="shareHandler"
|
||||||
|
>
|
||||||
|
<MsIcon type="icon-icon_share1" class="mr-2 font-[16px]" />
|
||||||
|
{{ t('common.share') }}
|
||||||
|
</MsButton>
|
||||||
|
<MsButton
|
||||||
|
type="icon"
|
||||||
|
status="secondary"
|
||||||
|
class="mr-4 !rounded-[var(--border-radius-small)]"
|
||||||
|
:disabled="loading"
|
||||||
|
:loading="exportLoading"
|
||||||
|
@click="exportHandler"
|
||||||
|
>
|
||||||
|
<MsIcon type="icon-icon_move_outlined" class="mr-2 font-[16px]" />
|
||||||
|
{{ t('common.export') }}
|
||||||
|
</MsButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #default="{ detail }">
|
||||||
|
<div class="report-container h-full">
|
||||||
|
<!-- 报告参数开始 -->
|
||||||
|
<div class="report-header flex items-center justify-between">
|
||||||
|
<span>
|
||||||
|
{{ detail.environmentName || '-' }}
|
||||||
|
<a-divider direction="vertical" :margin="4"></a-divider>
|
||||||
|
{{ detail.poolName || '-' }}
|
||||||
|
<a-divider direction="vertical" :margin="4"></a-divider>
|
||||||
|
{{ detail.requestDuration || '-' }}
|
||||||
|
<a-divider direction="vertical" :margin="4"></a-divider>
|
||||||
|
{{ detail.creatUserName || '-' }}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<span class="text-[var(--color-text-4)]">{{ t('report.detail.api.executionTime') }}</span>
|
||||||
|
{{ dayjs(detail.startTime).format('YYYY-MM-DD HH:mm:ss') || '-' }}
|
||||||
|
<span class="text-[var(--color-text-4)]">{{ t('report.detail.api.executionTimeTo') }}</span>
|
||||||
|
{{ dayjs(detail.endTime).format('YYYY-MM-DD HH:mm:ss') || '-' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<!-- 报告参数结束 -->
|
||||||
|
<!-- 报告分析开始 -->
|
||||||
|
<div class="analyze mb-1">
|
||||||
|
<!-- 请求分析 -->
|
||||||
|
<div class="request-analyze min-h-[110px]">
|
||||||
|
<div class="block-title mb-4">{{ t('report.detail.api.requestAnalysis') }}</div>
|
||||||
|
<!-- 独立报告 -->
|
||||||
|
<IndepReportChart
|
||||||
|
v-if="props.showType === 'INDEPENDENT'"
|
||||||
|
:legend-data="legendData"
|
||||||
|
:options="charOptions"
|
||||||
|
/>
|
||||||
|
<SetReportChart v-else :legend-data="legendData" :options="charOptions" />
|
||||||
|
<!-- 集合报告 -->
|
||||||
|
<!-- </div> -->
|
||||||
|
</div>
|
||||||
|
<!-- 耗时分析 -->
|
||||||
|
<div class="time-analyze">
|
||||||
|
<div class="time-card mb-2 mt-[16px] h-[40px] flex-1 gap-4">
|
||||||
|
<div class="time-card-item flex h-full">
|
||||||
|
<MsIcon type="icon-icon_time_outlined" class="mr-[4px] text-[var(--color-text-4)]" size="16" />
|
||||||
|
<span class="time-card-item-title">{{ t('report.detail.api.totalTime') }}</span>
|
||||||
|
<span class="count">{{ getTotalTime }}</span
|
||||||
|
><span class="time-card-item-title">s</span>
|
||||||
|
</div>
|
||||||
|
<div class="time-card-item h-full">
|
||||||
|
<MsIcon type="icon-icon_time_outlined" class="mr-[4px] text-[var(--color-text-4)]" size="16" />
|
||||||
|
<span class="time-card-item-title"> {{ t('report.detail.api.requestTotalTime') }}</span>
|
||||||
|
<span class="count">{{ detail.requestDuration || '-' }}</span
|
||||||
|
><span class="time-card-item-title">s</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="time-card flex-1 gap-4">
|
||||||
|
<!-- 执行率 -->
|
||||||
|
<div v-if="props.showType === 'INTEGRATED'" class="time-card-item-rote">
|
||||||
|
<div class="time-card-item-rote-title">
|
||||||
|
<MsIcon type="icon-icon_yes_outlined" class="mr-[4px] text-[var(--color-text-4)]" size="16" />
|
||||||
|
{{ t('report.detail.api.executionRate') }}
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="count"> {{ getExcuteRate(detail) }} %</span>
|
||||||
|
<a-divider direction="vertical" class="!h-[16px]" :margin="8"></a-divider>
|
||||||
|
<span>{{ getRequestEacuteCount }}</span>
|
||||||
|
<span class="mx-1 text-[var(--color-text-4)]">/ {{ getRequestTotalCount || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="time-card-item-rote">
|
||||||
|
<div class="time-card-item-rote-title">
|
||||||
|
<MsIcon type="icon-icon_yes_outlined" class="mr-[4px] text-[var(--color-text-4)]" size="16" />
|
||||||
|
{{ t('report.detail.api.assertPass') }}
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="count"
|
||||||
|
>{{ detail.assertionPassRate === 'Calculating' ? '-' : detail.assertionPassRate || '0.00' }}%</span
|
||||||
|
>
|
||||||
|
<a-divider direction="vertical" class="!h-[16px]" :margin="8"></a-divider>
|
||||||
|
<span>{{ addCommasToNumber(detail.assertionSuccessCount || 0) }}</span>
|
||||||
|
<span class="mx-1 text-[var(--color-text-4)]">/ {{ detail.assertionCount || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 报告步骤分析结束 -->
|
||||||
|
<!-- 报告明细开始 -->
|
||||||
|
<div class="report-info">
|
||||||
|
<div class="mb-4 flex h-[36px] items-center justify-between">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="mr-2 font-medium leading-[36px]">{{ t('report.detail.api.reportDetail') }}</div>
|
||||||
|
<a-radio-group v-model:model-value="activeTab" type="button" size="small">
|
||||||
|
<a-radio v-for="item of methods" :key="item.value" :value="item.value">
|
||||||
|
{{ t(item.label) }}
|
||||||
|
</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
</div>
|
||||||
|
<a-select v-model="condition" class="w-[240px]" :placeholder="t('report.detail.api.filterPlaceholder')">
|
||||||
|
<a-option :key="1" :value="1"> 1 </a-option>
|
||||||
|
</a-select>
|
||||||
|
</div>
|
||||||
|
<!-- 平铺模式 -->
|
||||||
|
<TiledList
|
||||||
|
v-show="activeTab === 'tiled'"
|
||||||
|
show-type="CASE"
|
||||||
|
:active-type="activeTab"
|
||||||
|
:report-detail="detail || []"
|
||||||
|
/>
|
||||||
|
<!-- tab展示 -->
|
||||||
|
<TiledList
|
||||||
|
v-show="activeTab === 'tab'"
|
||||||
|
show-type="CASE"
|
||||||
|
:active-type="activeTab"
|
||||||
|
:report-detail="detail || []"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- 报告明细结束 -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</MsDetailDrawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
|
import type { MsPaginationI } from '@/components/pure/ms-table/type';
|
||||||
|
import MsDetailDrawer from '@/components/business/ms-detail-drawer/index.vue';
|
||||||
|
import IndepReportChart from './case/IndepReportChart.vue';
|
||||||
|
import SetReportChart from './case/setReportChart.vue';
|
||||||
|
import TiledList from './tiledList.vue';
|
||||||
|
|
||||||
|
import { reportCaseDetail } from '@/api/modules/api-test/report';
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import { addCommasToNumber } from '@/utils';
|
||||||
|
|
||||||
|
import type { LegendData, ReportDetail } from '@/models/apiTest/report';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
visible: boolean;
|
||||||
|
reportId: string;
|
||||||
|
activeReportIndex: number;
|
||||||
|
tableData: any[];
|
||||||
|
pagination: MsPaginationI;
|
||||||
|
pageChange: (page: number) => Promise<void>;
|
||||||
|
showType: string; // 报告类型 独立报告或集合报告
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:visible', val: boolean): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const showDrawer = computed({
|
||||||
|
get() {
|
||||||
|
return props.visible;
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
emit('update:visible', val);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const innerReportId = ref(props.reportId);
|
||||||
|
|
||||||
|
const reportStepDetail = ref<ReportDetail>({
|
||||||
|
id: '',
|
||||||
|
name: '', // 报告名称
|
||||||
|
testPlanId: '',
|
||||||
|
createUser: '',
|
||||||
|
deleteTime: 0,
|
||||||
|
deleteUser: '',
|
||||||
|
deleted: false,
|
||||||
|
updateUser: '',
|
||||||
|
updateTime: 0,
|
||||||
|
startTime: 0, // 开始时间/同创建时间一致
|
||||||
|
endTime: 0, // 结束时间/报告执行完成
|
||||||
|
requestDuration: 0, // 请求总耗时
|
||||||
|
status: '', // 报告状态/SUCCESS/ERROR
|
||||||
|
triggerMode: '', // 触发方式
|
||||||
|
runMode: '', // 执行模式
|
||||||
|
poolId: '', // 资源池
|
||||||
|
poolName: '', // 资源池名称
|
||||||
|
versionId: '',
|
||||||
|
integrated: false, // 是否是集成报告
|
||||||
|
projectId: '',
|
||||||
|
environmentId: '', // 环境id
|
||||||
|
environmentName: '', // 环境名称
|
||||||
|
errorCount: 0, // 失败数
|
||||||
|
fakeErrorCount: 0, // 误报数
|
||||||
|
pendingCount: 0, // 未执行数
|
||||||
|
successCount: 0, // 成功数
|
||||||
|
assertionCount: 0, // 总断言数
|
||||||
|
assertionSuccessCount: 0, // 成功断言数
|
||||||
|
requestErrorRate: '', // 请求失败率
|
||||||
|
requestPendingRate: '', // 请求未执行率
|
||||||
|
requestFakeErrorRate: '', // 请求误报率
|
||||||
|
requestPassRate: '', // 请求通过率
|
||||||
|
assertionPassRate: '', // 断言通过率
|
||||||
|
scriptIdentifier: '', // 脚本标识
|
||||||
|
children: [], // 步骤列表
|
||||||
|
stepTotal: 0, // 步骤总数
|
||||||
|
console: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分享share
|
||||||
|
*/
|
||||||
|
const shareLoading = ref<boolean>(false);
|
||||||
|
function shareHandler() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出
|
||||||
|
*/
|
||||||
|
const exportLoading = ref<boolean>(false);
|
||||||
|
function exportHandler() {}
|
||||||
|
|
||||||
|
const charOptions = ref({
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
series: {
|
||||||
|
name: '',
|
||||||
|
type: 'pie',
|
||||||
|
radius: ['65%', '80%'],
|
||||||
|
avoidLabelOverlap: false,
|
||||||
|
label: {
|
||||||
|
show: false,
|
||||||
|
position: 'center',
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
label: {
|
||||||
|
show: false,
|
||||||
|
fontSize: 40,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
labelLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
value: 0,
|
||||||
|
name: t('report.detail.api.pass'),
|
||||||
|
itemStyle: {
|
||||||
|
color: '#00C261',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const methods = ref([
|
||||||
|
{
|
||||||
|
label: t('report.detail.api.tiledDisplay'),
|
||||||
|
value: 'tiled',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('report.detail.api.tabDisplay'),
|
||||||
|
value: 'tab',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const legendData = ref<LegendData[]>([]);
|
||||||
|
const activeTab = ref('tiled');
|
||||||
|
const condition = ref('');
|
||||||
|
|
||||||
|
const getRequestTotalCount = computed(() => {
|
||||||
|
const { errorCount, successCount, fakeErrorCount, pendingCount } = reportStepDetail.value;
|
||||||
|
return addCommasToNumber(errorCount + successCount + fakeErrorCount + pendingCount);
|
||||||
|
});
|
||||||
|
// 执行数量
|
||||||
|
const getRequestEacuteCount = computed(() => {
|
||||||
|
const { errorCount, successCount, fakeErrorCount } = reportStepDetail.value;
|
||||||
|
return addCommasToNumber(errorCount + successCount + fakeErrorCount);
|
||||||
|
});
|
||||||
|
|
||||||
|
function getExcuteRate(detail: ReportDetail) {
|
||||||
|
return 100 - Number(detail.requestPendingRate) ? (100 - Number(detail.requestPendingRate)).toFixed(2) : '0.00';
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTotalTime = computed(() => {
|
||||||
|
const { endTime, startTime } = reportStepDetail.value;
|
||||||
|
if (endTime && startTime && endTime !== 0 && startTime !== 0) {
|
||||||
|
return endTime - startTime;
|
||||||
|
}
|
||||||
|
return '-';
|
||||||
|
});
|
||||||
|
|
||||||
|
function initOptionsData() {
|
||||||
|
const tempArr = [
|
||||||
|
{
|
||||||
|
label: 'report.detail.api.pass',
|
||||||
|
value: 'successCount',
|
||||||
|
color: '#00C261',
|
||||||
|
class: 'bg-[rgb(var(--success-6))]',
|
||||||
|
rateKey: 'requestPassRate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'report.detail.api.misstatement',
|
||||||
|
value: 'fakeErrorCount',
|
||||||
|
color: '#FFC14E',
|
||||||
|
class: 'bg-[rgb(var(--warning-6))]',
|
||||||
|
rateKey: 'requestFakeErrorRate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'report.detail.api.error',
|
||||||
|
value: 'errorCount',
|
||||||
|
color: '#ED0303',
|
||||||
|
class: 'bg-[rgb(var(--danger-6))]',
|
||||||
|
rateKey: 'requestErrorRate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'report.detail.api.pending',
|
||||||
|
value: 'pendingCount',
|
||||||
|
color: '#D4D4D8',
|
||||||
|
class: 'bg-[var(--color-text-input-border)]',
|
||||||
|
rateKey: 'requestPendingRate',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const validArr = props.showType === 'INTEGRATED' ? tempArr : tempArr.slice(0, 1);
|
||||||
|
|
||||||
|
charOptions.value.series.data = validArr.map((item: any) => {
|
||||||
|
return {
|
||||||
|
value: reportStepDetail.value[item.value] || 0,
|
||||||
|
name: t(item.label),
|
||||||
|
itemStyle: {
|
||||||
|
color: item.color,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
legendData.value = validArr.map((item: any) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
label: t(item.label),
|
||||||
|
count: reportStepDetail.value[item.value] || 0,
|
||||||
|
rote: reportStepDetail.value[item.rateKey] === 'Calculating' ? '-' : reportStepDetail.value[item.rateKey],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 详情
|
||||||
|
function loadedReport(detail: ReportDetail) {
|
||||||
|
innerReportId.value = detail.id;
|
||||||
|
reportStepDetail.value = cloneDeep(detail);
|
||||||
|
initOptionsData();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.report-container {
|
||||||
|
padding: 16px;
|
||||||
|
height: calc(100vh - 56px);
|
||||||
|
background: var(--color-text-n9);
|
||||||
|
.report-header {
|
||||||
|
padding: 0 16px;
|
||||||
|
height: 54px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: white;
|
||||||
|
@apply mb-4 bg-white;
|
||||||
|
}
|
||||||
|
.analyze {
|
||||||
|
min-height: 196px;
|
||||||
|
max-height: 200px;
|
||||||
|
border-radius: 4px;
|
||||||
|
@apply mb-2 flex justify-between bg-white;
|
||||||
|
.request-analyze {
|
||||||
|
@apply flex flex-1 flex-col p-4;
|
||||||
|
.chart-legend {
|
||||||
|
.chart-legend-item {
|
||||||
|
@apply grid grid-cols-3 gap-2;
|
||||||
|
}
|
||||||
|
.chart-flag {
|
||||||
|
@apply flex items-center;
|
||||||
|
.count {
|
||||||
|
color: var(--color-text-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.time-analyze {
|
||||||
|
@apply flex flex-1 flex-col p-4;
|
||||||
|
.time-card {
|
||||||
|
@apply flex items-center justify-between;
|
||||||
|
.time-card-item {
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--color-text-n9);
|
||||||
|
@apply mt-4 flex flex-1 flex-grow items-center px-4;
|
||||||
|
.time-card-item-title {
|
||||||
|
color: var(--color-text-4);
|
||||||
|
}
|
||||||
|
.count {
|
||||||
|
font-size: 18px;
|
||||||
|
@apply mx-2 font-medium;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.time-card-item-rote {
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--color-text-n9);
|
||||||
|
@apply mt-4 flex flex-1 flex-grow flex-col p-4;
|
||||||
|
.time-card-item-rote-title {
|
||||||
|
color: var(--color-text-4);
|
||||||
|
@apply mb-2;
|
||||||
|
}
|
||||||
|
.count {
|
||||||
|
font-size: 18px;
|
||||||
|
@apply mx-2 font-medium;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.report-info {
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
@apply bg-white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.block-title {
|
||||||
|
font-size: 14px;
|
||||||
|
@apply font-medium;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -2,7 +2,7 @@
|
||||||
<MsDetailDrawer
|
<MsDetailDrawer
|
||||||
ref="detailDrawerRef"
|
ref="detailDrawerRef"
|
||||||
v-model:visible="showDrawer"
|
v-model:visible="showDrawer"
|
||||||
:width="960"
|
:width="1200"
|
||||||
:footer="false"
|
:footer="false"
|
||||||
:title="t('project.fileManagement.detail')"
|
:title="t('project.fileManagement.detail')"
|
||||||
:detail-id="props.reportId"
|
:detail-id="props.reportId"
|
||||||
|
@ -106,7 +106,7 @@
|
||||||
{{ t('report.detail.api.totalTime') }}
|
{{ t('report.detail.api.totalTime') }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span class="ml-4 text-[18px] font-medium">{{ detail.requestDuration || 0 }}</span
|
<span class="ml-4 text-[18px] font-medium">{{ getTotalTime }}</span
|
||||||
>s
|
>s
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -116,7 +116,7 @@
|
||||||
{{ t('report.detail.api.requestTotalTime') }}
|
{{ t('report.detail.api.requestTotalTime') }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span class="ml-4 text-[18px] font-medium">{{ detail.requestDuration }}</span
|
<span class="ml-4 text-[18px] font-medium">{{ detail.requestDuration || '-' }}</span
|
||||||
>s
|
>s
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -129,11 +129,13 @@
|
||||||
<span class="text-[18px] font-medium text-[var(--color-text-1)]"
|
<span class="text-[18px] font-medium text-[var(--color-text-1)]"
|
||||||
>{{ detail.assertionPassRate || 0 }} <span>%</span></span
|
>{{ detail.assertionPassRate || 0 }} <span>%</span></span
|
||||||
>
|
>
|
||||||
<a-divider direction="vertical" :margin="0" class="!mx-1"></a-divider>
|
<a-divider direction="vertical" :margin="0" class="!mx-2 h-[16px]"></a-divider>
|
||||||
<span class="text-[var(--color-text-1)]">{{
|
<span class="text-[var(--color-text-1)]">{{
|
||||||
addCommasToNumber(detail.assertionSuccessCount || 0)
|
addCommasToNumber(detail.assertionSuccessCount || 0)
|
||||||
}}</span>
|
}}</span>
|
||||||
<span class="text-[var(--color-text-4)]">/ {{ addCommasToNumber(detail.assertionCount) || 0 }}</span>
|
<span class="text-[var(--color-text-4)]"
|
||||||
|
><span class="mx-1">/</span> {{ addCommasToNumber(detail.assertionCount) || 0 }}</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -180,9 +182,19 @@
|
||||||
</a-select>
|
</a-select>
|
||||||
</div>
|
</div>
|
||||||
<!-- 平铺模式 -->
|
<!-- 平铺模式 -->
|
||||||
<TiledList v-show="activeTab === 'tiled'" :active-type="activeTab" :report-detail="detail || []" />
|
<TiledList
|
||||||
|
v-show="activeTab === 'tiled'"
|
||||||
|
show-type="API"
|
||||||
|
:active-type="activeTab"
|
||||||
|
:report-detail="detail || []"
|
||||||
|
/>
|
||||||
<!-- tab展示 -->
|
<!-- tab展示 -->
|
||||||
<TiledList v-show="activeTab === 'tab'" :active-type="activeTab" :report-detail="detail || []" />
|
<TiledList
|
||||||
|
v-show="activeTab === 'tab'"
|
||||||
|
show-type="API"
|
||||||
|
:active-type="activeTab"
|
||||||
|
:report-detail="detail || []"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<!-- 报告明细结束 -->
|
<!-- 报告明细结束 -->
|
||||||
</div>
|
</div>
|
||||||
|
@ -216,6 +228,7 @@
|
||||||
tableData: any[];
|
tableData: any[];
|
||||||
pagination: MsPaginationI;
|
pagination: MsPaginationI;
|
||||||
pageChange: (page: number) => Promise<void>;
|
pageChange: (page: number) => Promise<void>;
|
||||||
|
showType: string; // 报告类型
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
@ -230,7 +243,7 @@
|
||||||
emit('update:visible', val);
|
emit('update:visible', val);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const innerFileId = ref(props.reportId);
|
const innerReportId = ref(props.reportId);
|
||||||
|
|
||||||
const reportStepDetail = ref<ReportDetail>({
|
const reportStepDetail = ref<ReportDetail>({
|
||||||
id: '',
|
id: '',
|
||||||
|
@ -269,6 +282,7 @@
|
||||||
scriptIdentifier: '', // 脚本标识
|
scriptIdentifier: '', // 脚本标识
|
||||||
children: [], // 步骤列表
|
children: [], // 步骤列表
|
||||||
stepTotal: 0, // 步骤总数
|
stepTotal: 0, // 步骤总数
|
||||||
|
console: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const charOptions = ref({
|
const charOptions = ref({
|
||||||
|
@ -330,6 +344,14 @@
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getTotalTime = computed(() => {
|
||||||
|
const { endTime, startTime } = reportStepDetail.value;
|
||||||
|
if (endTime && startTime && endTime !== 0 && startTime !== 0) {
|
||||||
|
return endTime - startTime;
|
||||||
|
}
|
||||||
|
return '-';
|
||||||
|
});
|
||||||
|
|
||||||
const legendData = ref<LegendData[]>([]);
|
const legendData = ref<LegendData[]>([]);
|
||||||
|
|
||||||
function initOptionsData() {
|
function initOptionsData() {
|
||||||
|
@ -385,7 +407,7 @@
|
||||||
|
|
||||||
// 详情
|
// 详情
|
||||||
function loadedReport(detail: ReportDetail) {
|
function loadedReport(detail: ReportDetail) {
|
||||||
innerFileId.value = detail.id;
|
innerReportId.value = detail.id;
|
||||||
reportStepDetail.value = cloneDeep(detail);
|
reportStepDetail.value = cloneDeep(detail);
|
||||||
initOptionsData();
|
initOptionsData();
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
<div
|
<div
|
||||||
type="text"
|
type="text"
|
||||||
class="one-text-line flex w-full text-[rgb(var(--primary-5))]"
|
class="one-text-line flex w-full text-[rgb(var(--primary-5))]"
|
||||||
@click="showReportDetail(record.id, rowIndex)"
|
@click="showReportDetail(record.id, rowIndex, record.integrated)"
|
||||||
>{{ characterLimit(record.name) }}</div
|
>{{ characterLimit(record.name) }}</div
|
||||||
>
|
>
|
||||||
</template>
|
</template>
|
||||||
|
@ -120,6 +120,16 @@
|
||||||
:table-data="propsRes.data"
|
:table-data="propsRes.data"
|
||||||
:page-change="propsEvent.pageChange"
|
:page-change="propsEvent.pageChange"
|
||||||
:pagination="propsRes.msPagination!"
|
:pagination="propsRes.msPagination!"
|
||||||
|
:show-type="showType"
|
||||||
|
/>
|
||||||
|
<CaseReportDrawer
|
||||||
|
v-model:visible="showCaseDetailDrawer"
|
||||||
|
:report-id="activeDetailId"
|
||||||
|
:active-report-index="activeReportIndex"
|
||||||
|
:table-data="propsRes.data"
|
||||||
|
:page-change="propsEvent.pageChange"
|
||||||
|
:pagination="propsRes.msPagination!"
|
||||||
|
:show-type="activeCaseReportType"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -134,6 +144,7 @@
|
||||||
import type { BatchActionParams, BatchActionQueryParams, MsTableColumn } from '@/components/pure/ms-table/type';
|
import type { BatchActionParams, BatchActionQueryParams, MsTableColumn } from '@/components/pure/ms-table/type';
|
||||||
import useTable from '@/components/pure/ms-table/useTable';
|
import useTable from '@/components/pure/ms-table/useTable';
|
||||||
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
|
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
|
||||||
|
import CaseReportDrawer from './caseReportDrawer.vue';
|
||||||
import ReportDetailDrawer from './reportDetailDrawer.vue';
|
import ReportDetailDrawer from './reportDetailDrawer.vue';
|
||||||
import ExecutionStatus from '@/views/api-test/report/component/reportStatus.vue';
|
import ExecutionStatus from '@/views/api-test/report/component/reportStatus.vue';
|
||||||
|
|
||||||
|
@ -388,8 +399,16 @@
|
||||||
const activeDetailId = ref<string>('');
|
const activeDetailId = ref<string>('');
|
||||||
const activeReportIndex = ref<number>(0);
|
const activeReportIndex = ref<number>(0);
|
||||||
const showDetailDrawer = ref<boolean>(false);
|
const showDetailDrawer = ref<boolean>(false);
|
||||||
function showReportDetail(id: string, rowIndex: number) {
|
const showCaseDetailDrawer = ref<boolean>(false);
|
||||||
showDetailDrawer.value = true;
|
const activeCaseReportType = ref('');
|
||||||
|
function showReportDetail(id: string, rowIndex: number, integrated: boolean) {
|
||||||
|
if (props.moduleType === ReportEnum.API_SCENARIO_REPORT) {
|
||||||
|
showDetailDrawer.value = true;
|
||||||
|
} else {
|
||||||
|
showCaseDetailDrawer.value = true;
|
||||||
|
activeCaseReportType.value = integrated ? 'INTEGRATED' : 'INDEPENDENT';
|
||||||
|
}
|
||||||
|
|
||||||
activeDetailId.value = id;
|
activeDetailId.value = id;
|
||||||
activeReportIndex.value = rowIndex;
|
activeReportIndex.value = rowIndex;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,217 @@
|
||||||
|
<template>
|
||||||
|
<div class="resContentWrapper">
|
||||||
|
<!-- 循环计数器 -->
|
||||||
|
<div v-if="detailItem.stepType === 'LOOP_CONTROLLER'" class="mb-4 flex justify-start">
|
||||||
|
<MsPagination
|
||||||
|
v-model:page-size="pageNation.pageSize"
|
||||||
|
v-model:current="pageNation.current"
|
||||||
|
:total="pageNation.total"
|
||||||
|
size="mini"
|
||||||
|
@change="loadLoop"
|
||||||
|
@page-size-change="loadLoop"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="resContent">
|
||||||
|
<div class="flex h-full w-full items-center justify-between rounded bg-[var(--color-text-n9)] px-4">
|
||||||
|
<div class="font-medium">
|
||||||
|
<span>{{ t('report.detail.api.resContent') }}</span>
|
||||||
|
<span class="text-[rgb(var(--primary-5))]">
|
||||||
|
<a-divider direction="vertical" :margin="8"></a-divider>
|
||||||
|
|
||||||
|
子请求</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-4 gap-2 text-center">
|
||||||
|
<a-popover position="left" content-class="response-popover-content">
|
||||||
|
<div
|
||||||
|
class="one-line-text max-w-[200px]"
|
||||||
|
:style="{ color: statusCodeColor(activeStepDetail?.content?.responseResult.responseCode || '') }"
|
||||||
|
>
|
||||||
|
{{ activeStepDetail?.content?.responseResult.responseCode || '-' }}
|
||||||
|
</div>
|
||||||
|
<template #content>
|
||||||
|
<div class="flex items-center gap-[8px] text-[14px]">
|
||||||
|
<div class="text-[var(--color-text-4)]">{{ t('apiTestDebug.statusCode') }}</div>
|
||||||
|
<div :style="{ color: statusCodeColor(activeStepDetail?.content?.responseResult.responseCode || '') }">
|
||||||
|
{{ activeStepDetail?.content?.responseResult.responseCode || '-' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-popover>
|
||||||
|
<span class="text-[rgb(var(--success-6))]"
|
||||||
|
>{{ activeStepDetail?.content?.responseResult?.responseTime }}ms</span
|
||||||
|
>
|
||||||
|
<span class="text-[rgb(var(--success-6))]"
|
||||||
|
>{{ activeStepDetail?.content?.responseResult?.responseSize }}bytes</span
|
||||||
|
>
|
||||||
|
<!-- <span>Mock</span> -->
|
||||||
|
<span>{{ props.environmentName }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 子请求开始 -->
|
||||||
|
<div>
|
||||||
|
<!-- TODO 最后写 看看能不能使用其他的代替 -->
|
||||||
|
</div>
|
||||||
|
<!-- 子请求结束 -->
|
||||||
|
<!-- 响应内容tab开始 -->
|
||||||
|
<div>
|
||||||
|
<a-tabs v-model:active-key="showTab" class="no-content">
|
||||||
|
<a-tab-pane v-for="it of tabList" :key="it.key" :title="t(it.title)" />
|
||||||
|
</a-tabs>
|
||||||
|
<a-divider :margin="0"></a-divider>
|
||||||
|
<div v-if="showTab !== 'assertions'">
|
||||||
|
<ResContent :script="showContent || ''" language="JSON" show-charset-change
|
||||||
|
/></div>
|
||||||
|
<div v-else>
|
||||||
|
<assertTable :data="showContent" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 响应内容tab结束 -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
|
import MsPagination from '@/components/pure/ms-pagination/index';
|
||||||
|
import assertTable from './step/assertTable.vue';
|
||||||
|
import ResContent from './step/resContent.vue';
|
||||||
|
|
||||||
|
import { reportCaseStepDetail, reportStepDetail } from '@/api/modules/api-test/report';
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
|
import type { ReportStepDetail, ScenarioItemType } from '@/models/apiTest/report';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const props = defineProps<{
|
||||||
|
detailItem: ScenarioItemType; // 报告详情
|
||||||
|
showType: 'API' | 'CASE'; // 接口 | 用例
|
||||||
|
console?: string; // 控制台
|
||||||
|
environmentName?: string; // 环境
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const pageNation = ref({
|
||||||
|
total: 1000,
|
||||||
|
pageSize: 10,
|
||||||
|
current: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载用例列表
|
||||||
|
async function loadLoop() {}
|
||||||
|
|
||||||
|
const tabList = ref([
|
||||||
|
{
|
||||||
|
key: 'body',
|
||||||
|
title: 'report.detail.api.resBody',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'headers',
|
||||||
|
title: 'report.detail.api.resHeader',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'realReq',
|
||||||
|
title: 'report.detail.api.realReq',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'console',
|
||||||
|
title: 'report.detail.api.console',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'extract',
|
||||||
|
title: 'report.detail.api.extract',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'assertions',
|
||||||
|
title: 'report.detail.api.assert',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
/**
|
||||||
|
* 响应状态码对应颜色
|
||||||
|
*/
|
||||||
|
|
||||||
|
function statusCodeColor(code: string) {
|
||||||
|
if (code) {
|
||||||
|
const resCode = Number(code);
|
||||||
|
if (resCode >= 200 && resCode < 300) {
|
||||||
|
return 'rgb(var(--success-7)';
|
||||||
|
}
|
||||||
|
if (resCode >= 300 && resCode < 400) {
|
||||||
|
return 'rgb(var(--warning-7)';
|
||||||
|
}
|
||||||
|
return 'rgb(var(--danger-7)';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const showTab = ref('body');
|
||||||
|
|
||||||
|
const reportDetailMap = {
|
||||||
|
API: {
|
||||||
|
stepDetail: reportStepDetail,
|
||||||
|
},
|
||||||
|
CASE: {
|
||||||
|
stepDetail: reportCaseStepDetail,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const activeIndex = ref<number>(0);
|
||||||
|
const activeStepDetail = ref<ReportStepDetail>({});
|
||||||
|
/**
|
||||||
|
* 获取步骤详情
|
||||||
|
*/
|
||||||
|
const stepDetailInfo = ref<ReportStepDetail[]>([]);
|
||||||
|
async function getStepDetail() {
|
||||||
|
try {
|
||||||
|
const result = await reportDetailMap[props.showType].stepDetail(
|
||||||
|
props.detailItem.reportId,
|
||||||
|
props.detailItem.stepId
|
||||||
|
);
|
||||||
|
stepDetailInfo.value = cloneDeep(result) as ReportStepDetail[];
|
||||||
|
activeStepDetail.value = stepDetailInfo.value[activeIndex.value];
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取请求内容
|
||||||
|
*/
|
||||||
|
const showContent = computed(() => {
|
||||||
|
if (showTab.value === 'console') {
|
||||||
|
return props.console;
|
||||||
|
}
|
||||||
|
if (showTab.value === 'realReq') {
|
||||||
|
return activeStepDetail.value.content?.body
|
||||||
|
? `${t('apiTestDebug.requestUrl')}:\n${activeStepDetail.value.content.url}\n${t('apiTestDebug.header')}:\n${
|
||||||
|
activeStepDetail.value.content.headers
|
||||||
|
}\nBody:\n${activeStepDetail.value.content.body.trim()}`
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
if (showTab.value === 'extract') {
|
||||||
|
return activeStepDetail.value.content?.responseResult.vars?.trim();
|
||||||
|
}
|
||||||
|
return activeStepDetail.value.content?.responseResult[showTab.value];
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (!props.detailItem.fold) {
|
||||||
|
getStepDetail();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.resContentWrapper {
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid var(--color-text-n8);
|
||||||
|
border-top: none;
|
||||||
|
border-radius: 0 0 6px 6px;
|
||||||
|
@apply mb-4 bg-white p-4;
|
||||||
|
.resContent {
|
||||||
|
height: 38px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<template v-for="(item, index) in list" :key="item.stepId">
|
<template v-for="item in list" :key="item.stepId">
|
||||||
<div
|
<div
|
||||||
:style="{
|
:style="{
|
||||||
'padding-left': `${16 * (item.level as number)}px`,
|
'padding-left': `${16 * (item.level as number)}px`,
|
||||||
|
@ -7,20 +7,14 @@
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="scenario-class cursor-pointer rounded-t-md px-8"
|
class="scenario-class cursor-pointer rounded-t-md px-8"
|
||||||
:class="[
|
:class="[...getBorderAndRadius(item), ...getBorderClass(item)]"
|
||||||
item.level !== 0 ? 'border border-solid border-[var(--color-text-n8)]' : '',
|
|
||||||
...getBorderAndRadius(item),
|
|
||||||
]"
|
|
||||||
@click="showDetail(item)"
|
@click="showDetail(item)"
|
||||||
>
|
>
|
||||||
<div class="flex h-[46px] items-center">
|
<div class="flex h-[46px] items-center">
|
||||||
<!-- 序号 -->
|
<!-- 序号 -->
|
||||||
<span class="index mr-2 text-[var(--color-text-4)]">{{ index }}</span>
|
<span class="index mr-2 text-[var(--color-text-4)]">{{ item.sort }}</span>
|
||||||
<!-- 展开折叠控制器 -->
|
<!-- 展开折叠控制器 -->
|
||||||
<div
|
<div v-if="getShowExpand(item)" class="mx-2">
|
||||||
v-if="item.level !== 0 && showApiType.includes(item.stepType) && props.activeType === 'tab'"
|
|
||||||
class="mx-2"
|
|
||||||
>
|
|
||||||
<span
|
<span
|
||||||
v-if="item.fold"
|
v-if="item.fold"
|
||||||
class="collapsebtn flex items-center justify-center"
|
class="collapsebtn flex items-center justify-center"
|
||||||
|
@ -33,84 +27,92 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MsIcon type="icon-icon_split_turn-down_arrow" class="mx-[4px] text-[var(--color-text-4)]" size="16" />
|
<MsIcon
|
||||||
|
v-if="props.showType === 'API'"
|
||||||
|
type="icon-icon_split_turn-down_arrow"
|
||||||
|
class="mx-[4px] text-[var(--color-text-4)]"
|
||||||
|
size="16"
|
||||||
|
/>
|
||||||
<!-- 场景count -->
|
<!-- 场景count -->
|
||||||
<span class="mr-2 text-[var(--color-text-4)]">{{ (item.children || []).length }}</span>
|
<span v-if="props.showType === 'API'" class="mr-2 text-[var(--color-text-4)]">{{
|
||||||
|
(item.children || []).length
|
||||||
|
}}</span>
|
||||||
<!-- 循环控制器 -->
|
<!-- 循环控制器 -->
|
||||||
<ConditionStatus :status="item.stepType || ''" />
|
<ConditionStatus v-if="props.showType === 'API'" :status="item.stepType || ''" />
|
||||||
<span class="ml-2">{{ item.name || '-' }}</span>
|
<a-popover position="left" content-class="response-popover-content">
|
||||||
|
<div class="one-line-text max-w-[200px]">
|
||||||
|
{{ item.name || '-' }}
|
||||||
|
</div>
|
||||||
|
<template #content>
|
||||||
|
<div class="flex items-center gap-[8px] text-[14px]">
|
||||||
|
<div class="max-w-[300px]">
|
||||||
|
{{ item.name || '-' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-popover>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="flex">
|
||||||
<MsTag class="cursor-pointer" :type="item.status === 'SUCCESS' ? 'success' : 'danger'" theme="light">
|
<MsTag class="cursor-pointer" :type="item.status === 'SUCCESS' ? 'success' : 'danger'" theme="light">
|
||||||
{{ item.status === 'SUCCESS' ? t('report.detail.api.pass') : t('report.detail.api.resError') }}
|
{{ item.status === 'SUCCESS' ? t('report.detail.api.pass') : t('report.detail.api.resError') }}
|
||||||
</MsTag>
|
</MsTag>
|
||||||
<span class="statusCode">
|
<span class="statusCode mx-2">
|
||||||
{{ t('report.detail.api.statusCode') }} <span class="code">{{ item.code || '-' }}</span></span
|
<div class="mr-2"> {{ t('report.detail.api.statusCode') }}</div>
|
||||||
>
|
<a-popover position="left" content-class="response-popover-content">
|
||||||
|
<div class="one-line-text max-w-[200px]" :style="{ color: statusCodeColor(item.code) }">
|
||||||
|
{{ item.code || '-' }}
|
||||||
|
</div>
|
||||||
|
<template #content>
|
||||||
|
<div class="flex items-center gap-[8px] text-[14px]">
|
||||||
|
<div class="text-[var(--color-text-4)]">{{ t('apiTestDebug.statusCode') }}</div>
|
||||||
|
<div :style="{ color: statusCodeColor(item.code) }">
|
||||||
|
{{ item.code || '-' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-popover>
|
||||||
|
</span>
|
||||||
<span class="resTime">
|
<span class="resTime">
|
||||||
{{ t('report.detail.api.responseTime') }}
|
{{ t('report.detail.api.responseTime') }}
|
||||||
<span class="resTimeCount">{{ item.requestTime || 0 }}ms</span></span
|
<span class="resTimeCount ml-2">{{ item.requestTime || 0 }}ms</span></span
|
||||||
>
|
>
|
||||||
<span class="resSize">
|
<span class="resSize">
|
||||||
{{ t('report.detail.api.responseSize') }}
|
{{ t('report.detail.api.responseSize') }}
|
||||||
<span class="resTimeCount">{{ item.responseSize || 0 }} bytes</span></span
|
<span class="resTimeCount ml-2">{{ item.responseSize || 0 }} bytes</span></span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a-divider v-if="item.level === 0" :margin="0" class="!mb-4"></a-divider>
|
<a-divider
|
||||||
|
v-if="item.level === 0 && props.showType !== 'CASE'"
|
||||||
|
:margin="0"
|
||||||
|
class="!mb-4"
|
||||||
|
:class="props.showType === 'API' ? '!mb-4' : '!mb-0'"
|
||||||
|
></a-divider>
|
||||||
|
|
||||||
<!-- 响应内容开始 -->
|
<!-- 响应内容开始 -->
|
||||||
<div
|
<div
|
||||||
v-if="item.level !== 0 && showApiType.includes(item.stepType) && props.activeType === 'tab' && !item.fold"
|
v-if="showResContent(item)"
|
||||||
:style="{
|
:style="{
|
||||||
'padding-left': `${16 * (item.level as number)}px`,
|
'padding-left': `${16 * (item.level as number)}px`,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div class="resContentWrapper">
|
<ResponseContent
|
||||||
<!-- 循环计数器 -->
|
:detail-item="item"
|
||||||
<div v-if="item.stepType === 'LOOP_CONTROLLER'" class="mb-4 flex justify-start">
|
:show-type="props.showType"
|
||||||
<MsPagination
|
:console="props.console"
|
||||||
v-model:page-size="pageNation.pageSize"
|
:environment-name="props.environmentName"
|
||||||
v-model:current="pageNation.current"
|
/>
|
||||||
:total="pageNation.total"
|
|
||||||
size="mini"
|
|
||||||
@change="loadLoop"
|
|
||||||
@page-size-change="loadLoop"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="resContent">
|
|
||||||
<div class="flex h-full w-full items-center justify-between rounded bg-[var(--color-text-n9)] px-4">
|
|
||||||
<div class="font-medium">{{ t('report.detail.api.resContent') }}</div>
|
|
||||||
<div class="grid grid-cols-5 gap-2 text-center">
|
|
||||||
<span>401</span>
|
|
||||||
<span class="text-[rgb(var(--success-6))]">247ms</span>
|
|
||||||
<span class="text-[rgb(var(--success-6))]">50bytes</span>
|
|
||||||
<span>Mock</span>
|
|
||||||
<span>66</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 响应内容tab开始 -->
|
|
||||||
<div>
|
|
||||||
<a-tabs v-model:active-key="showTab" class="no-content">
|
|
||||||
<a-tab-pane v-for="it of tabList" :key="it.key" :title="t(it.title)" />
|
|
||||||
</a-tabs>
|
|
||||||
<a-divider :margin="0"></a-divider>
|
|
||||||
<div v-if="showTab !== 'assertions'">
|
|
||||||
<ResContent :script="showContent || ''" language="JSON" show-charset-change
|
|
||||||
/></div>
|
|
||||||
<div v-else>
|
|
||||||
<assertTable :data="showContent || []" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 响应内容tab结束 -->
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- </div> -->
|
||||||
<!-- 响应内容结束 -->
|
<!-- 响应内容结束 -->
|
||||||
<ScenarioItem
|
<ScenarioItem
|
||||||
v-if="'children' in item"
|
v-if="'children' in item"
|
||||||
:list="item.children"
|
:list="item.children"
|
||||||
:active-type="props.activeType"
|
:active-type="props.activeType"
|
||||||
|
:show-type="props.showType"
|
||||||
|
:console="props.console"
|
||||||
|
:environment-name="props.environmentName"
|
||||||
@detail="showDetail"
|
@detail="showDetail"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
@ -119,16 +121,13 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import MsPagination from '@/components/pure/ms-pagination/index';
|
|
||||||
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
|
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
|
||||||
import ConditionStatus from './conditionStatus.vue';
|
import ConditionStatus from './conditionStatus.vue';
|
||||||
import assertTable from './step/assertTable.vue';
|
import ResponseContent from './responseContent.vue';
|
||||||
import ResContent from './step/resContent.vue';
|
|
||||||
|
|
||||||
import { reportStepDetail } from '@/api/modules/api-test/report';
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
import type { ReportStepDetail, ScenarioItemType } from '@/models/apiTest/report';
|
import type { ScenarioItemType } from '@/models/apiTest/report';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
|
@ -137,6 +136,9 @@
|
||||||
hasBottomMargin?: boolean;
|
hasBottomMargin?: boolean;
|
||||||
list: ScenarioItemType[];
|
list: ScenarioItemType[];
|
||||||
activeType: string;
|
activeType: string;
|
||||||
|
showType: 'API' | 'CASE';
|
||||||
|
console?: string; // 控制台
|
||||||
|
environmentName?: string; // 环境
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
showBorder: true,
|
showBorder: true,
|
||||||
|
@ -147,94 +149,71 @@
|
||||||
const emit = defineEmits(['expand', 'detail']);
|
const emit = defineEmits(['expand', 'detail']);
|
||||||
const activeItem = ref();
|
const activeItem = ref();
|
||||||
function showDetail(item: ScenarioItemType) {
|
function showDetail(item: ScenarioItemType) {
|
||||||
|
if (props.activeType === 'tab') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
activeItem.value = item;
|
activeItem.value = item;
|
||||||
emit('detail', activeItem.value);
|
emit('detail', activeItem.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
const pageNation = ref({
|
|
||||||
total: 1000,
|
|
||||||
pageSize: 10,
|
|
||||||
current: 1,
|
|
||||||
});
|
|
||||||
// 加载用例列表
|
|
||||||
async function loadLoop() {}
|
|
||||||
|
|
||||||
// const scenarioItem = computed({
|
|
||||||
// get: () => {
|
|
||||||
// return props.list;
|
|
||||||
// },
|
|
||||||
// set: (val) => {
|
|
||||||
// scenarioItem.value = val;
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
const showApiType = ref<string[]>(['API', 'API_CASE', 'CUSTOM_API', 'LOOP_CONTROLLER']);
|
const showApiType = ref<string[]>(['API', 'API_CASE', 'CUSTOM_API', 'LOOP_CONTROLLER']);
|
||||||
|
|
||||||
const stepDetail = ref<ReportStepDetail>({});
|
|
||||||
|
|
||||||
async function getStepDetail(item: ScenarioItemType) {
|
|
||||||
try {
|
|
||||||
const result = await reportStepDetail(item.reportId, item.stepId);
|
|
||||||
stepDetail.value = result;
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function expandHandler(item: ScenarioItemType) {
|
async function expandHandler(item: ScenarioItemType) {
|
||||||
item.fold = !item.fold;
|
item.fold = !item.fold;
|
||||||
// 如果展开则获取报告步骤详情
|
|
||||||
if (!item.fold) {
|
|
||||||
getStepDetail(item);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBorderAndRadius(item: ScenarioItemType) {
|
function getBorderAndRadius(item: ScenarioItemType) {
|
||||||
if (props.activeType === 'tab') {
|
if (props.showType === 'API') {
|
||||||
if (!item.fold && showApiType.value.includes(item.stepType)) {
|
if (props.activeType === 'tab') {
|
||||||
return ['rounded-b-none', 'mb-0'];
|
if (!item.fold && showApiType.value.includes(item.stepType)) {
|
||||||
|
return ['rounded-b-none', 'mb-0'];
|
||||||
|
}
|
||||||
|
return ['mb-1', 'rounded-[4px]'];
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
return ['mb-1', 'rounded-[4px]'];
|
return ['mb-1', 'rounded-[4px]'];
|
||||||
}
|
}
|
||||||
return ['mb-1', 'rounded-[4px]'];
|
return ['mb-1', 'rounded-[4px]'];
|
||||||
}
|
}
|
||||||
|
|
||||||
const showTab = ref('body');
|
function getBorderClass(item: ScenarioItemType) {
|
||||||
const tabList = ref([
|
if (props.showType === 'API') {
|
||||||
{
|
return item.level !== 0 ? ['border', 'border-solid', 'border-[var(--color-text-n8)]'] : [''];
|
||||||
key: 'body',
|
}
|
||||||
title: 'report.detail.api.resBody',
|
return ['border', 'border-solid', 'border-[var(--color-text-n8)]'];
|
||||||
},
|
}
|
||||||
{
|
|
||||||
key: 'headers',
|
|
||||||
title: 'report.detail.api.resHeader',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'realReq',
|
|
||||||
title: 'report.detail.api.realReq',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'console',
|
|
||||||
title: 'report.detail.api.console',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'extract',
|
|
||||||
title: 'report.detail.api.extract',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'assertions',
|
|
||||||
title: 'report.detail.api.assert',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const showContent = computed(() => {
|
function getShowExpand(item: ScenarioItemType) {
|
||||||
return stepDetail.value.content?.responseResult[showTab.value];
|
if (props.showType === 'API') {
|
||||||
});
|
return item.level !== 0 && showApiType.value.includes(item.stepType) && props.activeType === 'tab';
|
||||||
|
}
|
||||||
|
return props.activeType === 'tab';
|
||||||
|
}
|
||||||
|
|
||||||
|
function showResContent(item: ScenarioItemType) {
|
||||||
|
if (props.showType === 'API') {
|
||||||
|
return showApiType.value.includes(item.stepType) && props.activeType === 'tab' && !item.fold;
|
||||||
|
}
|
||||||
|
return props.activeType === 'tab' && !item.fold;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应状态码对应颜色
|
||||||
|
function statusCodeColor(code: string) {
|
||||||
|
if (code) {
|
||||||
|
const resCode = Number(code);
|
||||||
|
if (resCode >= 200 && resCode < 300) {
|
||||||
|
return 'rgb(var(--success-7)';
|
||||||
|
}
|
||||||
|
if (resCode >= 300 && resCode < 400) {
|
||||||
|
return 'rgb(var(--warning-7)';
|
||||||
|
}
|
||||||
|
return 'rgb(var(--danger-7)';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.scenario-class {
|
.scenario-class {
|
||||||
// border-radius: 4px;
|
|
||||||
@apply flex items-center justify-between px-2;
|
@apply flex items-center justify-between px-2;
|
||||||
.index {
|
.index {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
|
@ -250,9 +229,17 @@
|
||||||
.statusCode {
|
.statusCode {
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
color: var(--color-text-4);
|
color: var(--color-text-4);
|
||||||
|
@apply flex;
|
||||||
.resTimeCount {
|
.resTimeCount {
|
||||||
color: rgb(var(--success-6));
|
color: rgb(var(--success-6));
|
||||||
}
|
}
|
||||||
|
.code {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 60px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
word-break: keep-all;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.resContentWrapper {
|
.resContentWrapper {
|
||||||
|
@ -291,4 +278,11 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.ellipsis {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -34,12 +34,33 @@
|
||||||
<template #titleName>
|
<template #titleName>
|
||||||
<div class="flex w-full justify-between">
|
<div class="flex w-full justify-between">
|
||||||
<div class="font-medium">{{ t('report.detail.api.resContent') }}</div>
|
<div class="font-medium">{{ t('report.detail.api.resContent') }}</div>
|
||||||
<div class="grid grid-cols-5 gap-2 text-center">
|
<div class="grid grid-cols-4 gap-2 text-center">
|
||||||
<span>401</span>
|
<a-popover position="left" content-class="response-popover-content">
|
||||||
<span class="text-[rgb(var(--success-6))]">247ms</span>
|
<div
|
||||||
<span class="text-[rgb(var(--success-6))]">50bytes</span>
|
class="one-line-text max-w-[200px]"
|
||||||
<span>Mock</span>
|
:style="{ color: statusCodeColor(activeStepDetail?.content?.responseResult.responseCode || '') }"
|
||||||
<span>66</span>
|
>
|
||||||
|
{{ activeStepDetail?.content?.responseResult.responseCode || '-' }}
|
||||||
|
</div>
|
||||||
|
<template #content>
|
||||||
|
<div class="flex items-center gap-[8px] text-[14px]">
|
||||||
|
<div class="text-[var(--color-text-4)]">{{ t('apiTestDebug.statusCode') }}</div>
|
||||||
|
<div
|
||||||
|
:style="{ color: statusCodeColor(activeStepDetail.content?.responseResult.responseCode || '') }"
|
||||||
|
>
|
||||||
|
{{ activeStepDetail?.content?.responseResult.responseCode || '-' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-popover>
|
||||||
|
<span class="text-[rgb(var(--success-6))]"
|
||||||
|
>{{ activeStepDetail.content?.responseResult.responseTime }}ms</span
|
||||||
|
>
|
||||||
|
<span class="text-[rgb(var(--success-6))]">
|
||||||
|
{{ activeStepDetail.content?.responseResult.responseSize }} bytes</span
|
||||||
|
>
|
||||||
|
<!-- <span>Mock</span> -->
|
||||||
|
<span>{{ props.environmentName }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -48,8 +69,8 @@
|
||||||
{{ record.name }}
|
{{ record.name }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div v-if="record.script && !record.isAssertion" class="w-full">
|
<div v-if="record.showScript && !record.isAssertion" class="w-full">
|
||||||
<ResContent :script="record.script" language="JSON" :show-charset-change="record.script" />
|
<ResContent :script="record.script || ''" language="JSON" :show-charset-change="record.showScript" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="record.isAssertion" class="w-full">
|
<div v-if="record.isAssertion" class="w-full">
|
||||||
<assertTable :data="record.assertions" />
|
<assertTable :data="record.assertions" />
|
||||||
|
@ -62,6 +83,7 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||||
import MsPagination from '@/components/pure/ms-pagination/index';
|
import MsPagination from '@/components/pure/ms-pagination/index';
|
||||||
|
@ -71,10 +93,11 @@
|
||||||
import assertTable from './assertTable.vue';
|
import assertTable from './assertTable.vue';
|
||||||
import ResContent from './resContent.vue';
|
import ResContent from './resContent.vue';
|
||||||
|
|
||||||
import { reportDetail } from '@/api/modules/api-test/report';
|
import { reportCaseStepDetail, reportStepDetail } from '@/api/modules/api-test/report';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import { getGenerateId } from '@/utils';
|
||||||
|
|
||||||
import type { ScenarioDetailItem } from '@/models/apiTest/report';
|
import type { ReportStepDetail, ScenarioDetailItem } from '@/models/apiTest/report';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
@ -82,6 +105,9 @@
|
||||||
stepId: string;
|
stepId: string;
|
||||||
activeStepIndex: number;
|
activeStepIndex: number;
|
||||||
scenarioDetail: ScenarioDetailItem;
|
scenarioDetail: ScenarioDetailItem;
|
||||||
|
showType: 'API' | 'CASE'; // 接口场景|用例
|
||||||
|
console?: string; // 控制台
|
||||||
|
environmentName?: string; // 环境名称
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
@ -129,7 +155,7 @@
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(undefined, {
|
const { propsRes, propsEvent } = useTable(undefined, {
|
||||||
columns,
|
columns,
|
||||||
scroll: { x: 'auto' },
|
scroll: { x: 'auto' },
|
||||||
showPagination: false,
|
showPagination: false,
|
||||||
|
@ -145,102 +171,195 @@
|
||||||
|
|
||||||
async function loadLoop() {}
|
async function loadLoop() {}
|
||||||
|
|
||||||
onMounted(() => {
|
/**
|
||||||
// 虚拟数据
|
* 响应状态码对应颜色
|
||||||
propsRes.value.data = [
|
*/
|
||||||
|
|
||||||
|
function statusCodeColor(code: string) {
|
||||||
|
if (code) {
|
||||||
|
const resCode = Number(code);
|
||||||
|
if (resCode >= 200 && resCode < 300) {
|
||||||
|
return 'rgb(var(--success-7)';
|
||||||
|
}
|
||||||
|
if (resCode >= 300 && resCode < 400) {
|
||||||
|
return 'rgb(var(--warning-7)';
|
||||||
|
}
|
||||||
|
return 'rgb(var(--danger-7)';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const stepDetailInfo = ref<ReportStepDetail[]>([]);
|
||||||
|
|
||||||
|
// 处理内容
|
||||||
|
function getRequestItem(item: ReportStepDetail) {
|
||||||
|
const headers = item.content?.responseResult.headers;
|
||||||
|
const body = item.content?.responseResult.body;
|
||||||
|
const realRequest = item.content?.body
|
||||||
|
? `${t('apiTestDebug.requestUrl')}:\n${item.content?.url}\n${t('apiTestDebug.header')}:\n${
|
||||||
|
item.content?.headers
|
||||||
|
}\nBody:\n${item.content?.body.trim()}`
|
||||||
|
: '';
|
||||||
|
const assertionList = item.content?.responseResult.assertions;
|
||||||
|
const extractValue = item.content?.responseResult.vars?.trim();
|
||||||
|
return {
|
||||||
|
headers,
|
||||||
|
body,
|
||||||
|
realRequest,
|
||||||
|
assertionList,
|
||||||
|
extractValue,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const tempStepMap = ref<Record<string, any>>({});
|
||||||
|
function setTableMaps(currentStepId: string, paramsObj: Record<string, any>) {
|
||||||
|
const { headers, body, realRequest, assertionList, extractValue } = paramsObj;
|
||||||
|
tempStepMap.value[currentStepId] = [
|
||||||
{
|
{
|
||||||
id: '1001',
|
id: getGenerateId(),
|
||||||
name: '响应体',
|
name: '响应体',
|
||||||
script: '',
|
script: '',
|
||||||
|
showScript: false,
|
||||||
isAssertion: false,
|
isAssertion: false,
|
||||||
assertions: [],
|
assertions: [],
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
script: '1',
|
showScript: true,
|
||||||
|
script: body,
|
||||||
isAssertion: false,
|
isAssertion: false,
|
||||||
assertions: [],
|
assertions: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '1002',
|
id: getGenerateId(),
|
||||||
name: '响应头',
|
name: '响应头',
|
||||||
script: '',
|
script: '',
|
||||||
|
showScript: false,
|
||||||
isAssertion: false,
|
isAssertion: false,
|
||||||
assertions: [],
|
assertions: [],
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
script: '2',
|
script: headers,
|
||||||
|
showScript: true,
|
||||||
isAssertion: false,
|
isAssertion: false,
|
||||||
assertions: [],
|
assertions: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '1003',
|
id: getGenerateId(),
|
||||||
name: '实际请求',
|
name: '实际请求',
|
||||||
script: '',
|
script: '',
|
||||||
|
showScript: false,
|
||||||
isAssertion: false,
|
isAssertion: false,
|
||||||
assertions: [],
|
assertions: [],
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
script: '3',
|
script: realRequest,
|
||||||
|
showScript: true,
|
||||||
isAssertion: false,
|
isAssertion: false,
|
||||||
assertions: [],
|
assertions: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '1004',
|
id: getGenerateId(),
|
||||||
name: '控制台',
|
name: '控制台',
|
||||||
script: '',
|
script: '',
|
||||||
isAssertion: false,
|
isAssertion: false,
|
||||||
|
showScript: false,
|
||||||
assertions: [],
|
assertions: [],
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
script: '4',
|
script: props.console,
|
||||||
|
showScript: true,
|
||||||
isAssertion: false,
|
isAssertion: false,
|
||||||
assertions: [],
|
assertions: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '1005',
|
id: getGenerateId(),
|
||||||
name: '提取',
|
name: '提取',
|
||||||
script: '',
|
script: '',
|
||||||
isAssertion: false,
|
isAssertion: false,
|
||||||
|
showScript: false,
|
||||||
assertions: [],
|
assertions: [],
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
script: '1',
|
script: extractValue,
|
||||||
|
showScript: true,
|
||||||
isAssertion: false,
|
isAssertion: false,
|
||||||
assertions: [],
|
assertions: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '1007',
|
id: getGenerateId(),
|
||||||
name: '断言',
|
name: '断言',
|
||||||
script: '',
|
script: '',
|
||||||
|
showScript: false,
|
||||||
isAssertion: false,
|
isAssertion: false,
|
||||||
assertions: [],
|
assertions: [],
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
script: '1',
|
script: '',
|
||||||
|
showScript: false,
|
||||||
isAssertion: true,
|
isAssertion: true,
|
||||||
assertions: [
|
assertions: assertionList,
|
||||||
{
|
|
||||||
name: 'string',
|
|
||||||
content: 'string',
|
|
||||||
script: 'string',
|
|
||||||
message: 'string',
|
|
||||||
pass: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function setResValue(list: ReportStepDetail[]) {
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
const currentStepId = list[i].id as string;
|
||||||
|
const paramsObj = getRequestItem(list[i]);
|
||||||
|
setTableMaps(currentStepId, paramsObj);
|
||||||
|
if (list[i].content?.subRequestResults && list[i].content?.subRequestResults.length) {
|
||||||
|
setResValue(list[i].content?.subRequestResults);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const reportDetailMap = {
|
||||||
|
API: {
|
||||||
|
stepDetail: reportStepDetail,
|
||||||
|
},
|
||||||
|
CASE: {
|
||||||
|
stepDetail: reportCaseStepDetail,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取步骤详情
|
||||||
|
*/
|
||||||
|
const activeStepId = ref<string>('');
|
||||||
|
const activeStepDetail = ref<ReportStepDetail>({});
|
||||||
|
const activeIndex = ref<number>(0);
|
||||||
|
async function getStepDetail() {
|
||||||
|
try {
|
||||||
|
const result = await reportDetailMap[props.showType].stepDetail(
|
||||||
|
props.scenarioDetail.reportId as string,
|
||||||
|
props.scenarioDetail.stepId as string
|
||||||
|
);
|
||||||
|
stepDetailInfo.value = cloneDeep(result) as ReportStepDetail[];
|
||||||
|
activeStepId.value = stepDetailInfo.value[0].id as string;
|
||||||
|
activeStepDetail.value = stepDetailInfo.value.find((item) => item.id === activeStepId.value) || {};
|
||||||
|
setResValue(stepDetailInfo.value);
|
||||||
|
propsRes.value.data = tempStepMap.value[activeStepId.value];
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (props.scenarioDetail.reportId && props.scenarioDetail.stepId) {
|
||||||
|
getStepDetail();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
<template>
|
||||||
|
<div> </div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const subRequestResults = ref([
|
||||||
|
{
|
||||||
|
requestName: '子请求001',
|
||||||
|
id: 1001,
|
||||||
|
reportId: '1001',
|
||||||
|
stepId: 'step1001',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
|
@ -1,11 +1,37 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="tiled-wrap">
|
<div
|
||||||
<ScenarioItem :list="tiledList" :show-border="true" :active-type="props.activeType" @detail="showDetail" />
|
class="tiled-wrap"
|
||||||
|
:class="{
|
||||||
|
'border border-solid border-[var(--color-text-n8)]': props.showType === 'API',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<a-scrollbar
|
||||||
|
:style="{
|
||||||
|
overflow: 'auto',
|
||||||
|
height: 'calc(100vh - 424px)',
|
||||||
|
width: '100%',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<ScenarioItem
|
||||||
|
v-if="tiledList.length > 0"
|
||||||
|
:show-type="props.showType"
|
||||||
|
:list="tiledList"
|
||||||
|
:show-border="true"
|
||||||
|
:active-type="props.activeType"
|
||||||
|
:console="props.reportDetail.console"
|
||||||
|
:environment-name="props.reportDetail.environmentName"
|
||||||
|
@detail="showDetail"
|
||||||
|
/>
|
||||||
|
<MsEmpty v-else />
|
||||||
|
</a-scrollbar>
|
||||||
<StepDrawer
|
<StepDrawer
|
||||||
v-model:visible="showStepDrawer"
|
v-model:visible="showStepDrawer"
|
||||||
:step-id="activeDetailId"
|
:step-id="activeDetailId"
|
||||||
:active-step-index="activeStepIndex"
|
:active-step-index="activeStepIndex"
|
||||||
:scenario-detail="scenarioDetail"
|
:scenario-detail="scenarioDetail"
|
||||||
|
:show-type="props.showType"
|
||||||
|
:console="props.reportDetail.console"
|
||||||
|
:environment-name="props.reportDetail.environmentName"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -14,6 +40,7 @@
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
|
import MsEmpty from '@/components/pure/ms-empty/index.vue';
|
||||||
import ScenarioItem from './scenarioItem.vue';
|
import ScenarioItem from './scenarioItem.vue';
|
||||||
import StepDrawer from './step/stepDrawer.vue';
|
import StepDrawer from './step/stepDrawer.vue';
|
||||||
|
|
||||||
|
@ -21,108 +48,24 @@
|
||||||
|
|
||||||
import type { ReportDetail, ScenarioDetailItem, ScenarioItemType } from '@/models/apiTest/report';
|
import type { ReportDetail, ScenarioDetailItem, ScenarioItemType } from '@/models/apiTest/report';
|
||||||
|
|
||||||
|
import { addFoldField } from '../utils';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
reportDetail: ReportDetail;
|
reportDetail: ReportDetail;
|
||||||
activeType: string;
|
activeType: string; // 平铺模式|tab模式
|
||||||
|
showType: 'API' | 'CASE'; // 接口场景|用例
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
// TODO 虚拟数据
|
const tiledList = ref<ScenarioItemType[]>([]);
|
||||||
// const tiledList = ref<ScenarioItemType[]>([]);
|
watchEffect(() => {
|
||||||
// watchEffect(() => {
|
if (props.reportDetail && props.reportDetail.children) {
|
||||||
// if (props.reportDetail && props.reportDetail.children) {
|
tiledList.value = props.reportDetail.children || [];
|
||||||
// tiledList.value = props.reportDetail.children || [];
|
tiledList.value = addLevelToTree<ScenarioItemType>(tiledList.value) as ScenarioItemType[];
|
||||||
// }
|
tiledList.value.forEach((item) => {
|
||||||
// });
|
addFoldField(item);
|
||||||
const tiledList = ref<ScenarioItemType[]>([
|
});
|
||||||
{
|
}
|
||||||
stepId: '1001',
|
});
|
||||||
reportId: '12345657687',
|
|
||||||
name: '场景名称',
|
|
||||||
sort: 0,
|
|
||||||
stepType: 'QUOTE_API',
|
|
||||||
parentId: 'string',
|
|
||||||
status: 'SUCCESS',
|
|
||||||
fakeCode: 'string',
|
|
||||||
requestName: 'string',
|
|
||||||
requestTime: 3000,
|
|
||||||
code: '200',
|
|
||||||
responseSize: 234543,
|
|
||||||
scriptIdentifier: 'string',
|
|
||||||
fold: true,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
stepId: '1001102',
|
|
||||||
reportId: '12345657687',
|
|
||||||
name: '场景名称1-1',
|
|
||||||
sort: 0,
|
|
||||||
stepType: 'LOOP_CONTROLLER',
|
|
||||||
parentId: 'string',
|
|
||||||
status: 'SUCCESS',
|
|
||||||
fakeCode: 'string',
|
|
||||||
requestName: 'string',
|
|
||||||
requestTime: 3000,
|
|
||||||
code: '200',
|
|
||||||
responseSize: 234543,
|
|
||||||
scriptIdentifier: 'string',
|
|
||||||
fold: true,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
stepId: '100103',
|
|
||||||
reportId: '12345657687',
|
|
||||||
name: '场景名称1-1-1',
|
|
||||||
sort: 0,
|
|
||||||
stepType: 'CUSTOM_API',
|
|
||||||
parentId: 'string',
|
|
||||||
status: 'SUCCESS',
|
|
||||||
fakeCode: 'string',
|
|
||||||
requestName: 'string',
|
|
||||||
requestTime: 3000,
|
|
||||||
code: '200',
|
|
||||||
responseSize: 234543,
|
|
||||||
scriptIdentifier: 'string',
|
|
||||||
fold: true,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
stepId: '100104',
|
|
||||||
reportId: '12345657687',
|
|
||||||
name: '场景名称1-1-1-1',
|
|
||||||
sort: 0,
|
|
||||||
stepType: 'LOOP_CONTROLLER',
|
|
||||||
parentId: 'string',
|
|
||||||
status: 'SUCCESS',
|
|
||||||
fakeCode: 'string',
|
|
||||||
requestName: 'string',
|
|
||||||
requestTime: 3000,
|
|
||||||
code: '200',
|
|
||||||
responseSize: 234543,
|
|
||||||
scriptIdentifier: 'string',
|
|
||||||
fold: true,
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
stepId: '步骤id',
|
|
||||||
reportId: '12345657687',
|
|
||||||
name: '场景名称1-1-1',
|
|
||||||
sort: 0,
|
|
||||||
stepType: 'QUOTE_API',
|
|
||||||
parentId: 'string',
|
|
||||||
status: 'SUCCESS',
|
|
||||||
fakeCode: 'string',
|
|
||||||
requestName: 'string',
|
|
||||||
requestTime: 3000,
|
|
||||||
code: '200',
|
|
||||||
responseSize: 234543,
|
|
||||||
scriptIdentifier: 'string',
|
|
||||||
fold: true,
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const showStepDrawer = ref<boolean>(false);
|
const showStepDrawer = ref<boolean>(false);
|
||||||
const activeDetailId = ref<string>('');
|
const activeDetailId = ref<string>('');
|
||||||
|
@ -142,8 +85,7 @@
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.tiled-wrap {
|
.tiled-wrap {
|
||||||
min-height: 300px;
|
height: calc(100vh - 424px);
|
||||||
border: 1px solid var(--color-text-n8);
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -41,6 +41,7 @@ export default {
|
||||||
'report.detail.api.totalTime': 'total time',
|
'report.detail.api.totalTime': 'total time',
|
||||||
'report.detail.api.requestTotalTime': 'req total time',
|
'report.detail.api.requestTotalTime': 'req total time',
|
||||||
'report.detail.api.assertPass': 'Assert pass',
|
'report.detail.api.assertPass': 'Assert pass',
|
||||||
|
'report.detail.api.executionRate': 'Req execution Rate',
|
||||||
'report.detail.api.requestAnalysis': 'Request analysis',
|
'report.detail.api.requestAnalysis': 'Request analysis',
|
||||||
'report.detail.api.total': 'total',
|
'report.detail.api.total': 'total',
|
||||||
'report.detail.api.reportDetail': 'Report detail',
|
'report.detail.api.reportDetail': 'Report detail',
|
||||||
|
@ -56,4 +57,11 @@ export default {
|
||||||
'report.detail.api.responseSize': 'Response size',
|
'report.detail.api.responseSize': 'Response size',
|
||||||
'report.detail.api.resSuccess': 'Success',
|
'report.detail.api.resSuccess': 'Success',
|
||||||
'report.detail.api.resError': 'Error',
|
'report.detail.api.resError': 'Error',
|
||||||
|
'report.detail.api.apiCase': 'Api Case',
|
||||||
|
'report.detail.api.resBody': 'response body',
|
||||||
|
'report.detail.api.resHeader': 'headers',
|
||||||
|
'report.detail.api.realReq': 'real request',
|
||||||
|
'report.detail.api.console': 'console',
|
||||||
|
'report.detail.api.extract': 'extract',
|
||||||
|
'report.detail.api.assert': 'assertion',
|
||||||
};
|
};
|
||||||
|
|
|
@ -40,6 +40,7 @@ export default {
|
||||||
'report.detail.api.totalTime': '总耗时',
|
'report.detail.api.totalTime': '总耗时',
|
||||||
'report.detail.api.requestTotalTime': '请求总耗时',
|
'report.detail.api.requestTotalTime': '请求总耗时',
|
||||||
'report.detail.api.assertPass': '断言通过率',
|
'report.detail.api.assertPass': '断言通过率',
|
||||||
|
'report.detail.api.executionRate': '请求执行率',
|
||||||
'report.detail.api.requestAnalysis': '请求分析',
|
'report.detail.api.requestAnalysis': '请求分析',
|
||||||
'report.detail.api.total': '总数(个)',
|
'report.detail.api.total': '总数(个)',
|
||||||
'report.detail.api.reportDetail': '报告明细',
|
'report.detail.api.reportDetail': '报告明细',
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import type { ScenarioItemType } from '@/models/apiTest/report';
|
||||||
|
|
||||||
|
export function addFoldField(node: ScenarioItemType) {
|
||||||
|
if (node.children && node.children.length > 0) {
|
||||||
|
node.fold = true;
|
||||||
|
node.children.forEach((child: ScenarioItemType) => {
|
||||||
|
addFoldField(child);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
node.fold = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {};
|
Loading…
Reference in New Issue