feat(接口测试): 接口测试报告详情页面
This commit is contained in:
parent
1e40d132db
commit
39847ccc93
|
@ -0,0 +1,15 @@
|
|||
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; // 脚本标识
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<template>
|
||||
<div class="condition-status" :style="getClass"> {{ t(scenarioStepMap[props.status].label) }} </div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { ScenarioStepType } from '@/enums/apiEnum';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps<{
|
||||
status: string;
|
||||
}>();
|
||||
// 场景步骤类型映射
|
||||
const scenarioStepMap = {
|
||||
[ScenarioStepType.QUOTE_API]: { label: 'apiScenario.quoteApi', color: 'rgb(var(--link-7))' },
|
||||
[ScenarioStepType.COPY_API]: { label: 'apiScenario.copyApi', color: 'rgb(var(--link-7))' },
|
||||
[ScenarioStepType.QUOTE_CASE]: { label: 'apiScenario.quoteCase', color: 'rgb(var(--success-7))' },
|
||||
[ScenarioStepType.COPY_CASE]: { label: 'apiScenario.copyCase', color: 'rgb(var(--success-7))' },
|
||||
[ScenarioStepType.QUOTE_SCENARIO]: { label: 'apiScenario.quoteScenario', color: 'rgb(var(--primary-7))' },
|
||||
[ScenarioStepType.COPY_SCENARIO]: { label: 'apiScenario.copyScenario', color: 'rgb(var(--primary-7))' },
|
||||
[ScenarioStepType.WAIT_TIME]: { label: 'apiScenario.waitTime', color: 'rgb(var(--warning-6))' },
|
||||
[ScenarioStepType.LOOP_CONTROL]: { label: 'apiScenario.loopControl', color: 'rgba(167, 98, 191, 1)' },
|
||||
[ScenarioStepType.CONDITION_CONTROL]: { label: 'apiScenario.conditionControl', color: 'rgba(238, 80, 163, 1)' },
|
||||
[ScenarioStepType.ONLY_ONCE_CONTROL]: { label: 'apiScenario.onlyOnceControl', color: 'rgba(211, 68, 0, 1)' },
|
||||
[ScenarioStepType.SCRIPT_OPERATION]: { label: 'apiScenario.scriptOperation', color: 'rgba(20, 225, 198, 1)' },
|
||||
[ScenarioStepType.CUSTOM_API]: { label: 'apiScenario.customApi', color: 'rgb(var(--link-4))' },
|
||||
};
|
||||
|
||||
const getClass = computed(() => {
|
||||
return {
|
||||
color: scenarioStepMap[props.status].color,
|
||||
border: `1px solid ${scenarioStepMap[props.status].color}`,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.condition-status {
|
||||
padding: 0 2px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
border-radius: 0 12px 12px 0; /* 设置左半边为正常边框 */
|
||||
}
|
||||
</style>
|
|
@ -99,18 +99,105 @@
|
|||
</div>
|
||||
</div>
|
||||
<StepProgress :report-detail="reportStepDetail" height="8px" radius="var(--border-radius-mini)" />
|
||||
<div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div class="card">
|
||||
<div class="timer-card mr-2">
|
||||
<div class="text-[var(--color-text-4)]">
|
||||
<MsIcon type="icon-icon_time_outlined" class="text-[var(--color-text-4)]x mr-[4px]" size="16" />
|
||||
总耗时
|
||||
</div>
|
||||
<div> <span class="ml-4 text-[18px] font-medium">3</span>s </div>
|
||||
</div>
|
||||
<div class="timer-card mr-2">
|
||||
<div class="text-[var(--color-text-4)]">
|
||||
<MsIcon type="icon-icon_time_outlined" class="mr-[4px] text-[var(--color-text-4)]" size="16" />
|
||||
请求总耗时
|
||||
</div>
|
||||
<div> <span class="ml-4 text-[18px] font-medium">3</span>s </div>
|
||||
</div>
|
||||
<div class="timer-card min-w-[200px]">
|
||||
<div class="text-[var(--color-text-4)]">
|
||||
<MsIcon type="icon-icon_yes_outlined" class="mr-[4px] text-[var(--color-text-4)]" size="16" />
|
||||
断言通过率
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span class="text-[18px] font-medium text-[var(--color-text-1)]">99.99 <span>%</span></span>
|
||||
<a-divider direction="vertical" :margin="0" class="!mx-1"></a-divider>
|
||||
<span class="text-[var(--color-text-1)]">1,000</span>
|
||||
<span class="text-[var(--color-text-4)]">/ 1,000</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="request-analyze"> </div>
|
||||
<div class="request-analyze">
|
||||
<div class="block-title">请求分析</div>
|
||||
<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))]">总数 (个)</div>
|
||||
<div class="text-[18px] font-medium">4</div>
|
||||
</div>
|
||||
<MsChart width="110px" height="110px" :options="charOptions" />
|
||||
</div>
|
||||
<div class="chart-legend grid flex-1 gap-y-4">
|
||||
<div class="chart-legend-item">
|
||||
<div class="chart-flag">
|
||||
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[rgb(var(--success-6))]"></div>
|
||||
<div class="mr-2 text-[var(--color-text-4)]">{{ t('report.detail.successCount') }}</div>
|
||||
</div>
|
||||
<div class="count">24</div>
|
||||
<div class="count">99.99%</div>
|
||||
</div>
|
||||
<div class="chart-legend-item">
|
||||
<div class="chart-flag">
|
||||
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[rgb(var(--warning-6))]"></div>
|
||||
<div class="mr-2 text-[var(--color-text-4)]">{{ t('report.detail.fakeErrorCount') }}</div>
|
||||
</div>
|
||||
|
||||
<div class="count">24</div>
|
||||
<div class="count">99.99%</div>
|
||||
</div>
|
||||
<div class="chart-legend-item">
|
||||
<div class="chart-flag">
|
||||
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[rgb(var(--danger-6))]"></div>
|
||||
<div class="mr-2 text-[var(--color-text-4)]">{{ t('report.detail.errorCount') }}</div>
|
||||
</div>
|
||||
<div class="count">24</div>
|
||||
<div class="count">99.99%</div>
|
||||
</div>
|
||||
<div class="chart-legend-item">
|
||||
<div class="chart-flag">
|
||||
<div
|
||||
class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[var(--color-text-input-border)]"
|
||||
></div>
|
||||
<div class="mr-2 text-[var(--color-text-4)]">{{ t('report.detail.pendingCount') }}</div>
|
||||
</div>
|
||||
|
||||
<div class="count">24</div>
|
||||
<div class="count">99.99%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 报告步骤分析和请求分析结束 -->
|
||||
<!-- 报告明细开始 -->
|
||||
<div class="report-info"></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]">报告明细</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="请选择过滤条件">
|
||||
<a-option :key="1" :value="1"> 1 </a-option>
|
||||
</a-select>
|
||||
</div>
|
||||
<TiledList v-show="activeTab === 'tiled'" />
|
||||
</div>
|
||||
<!-- 报告明细结束 -->
|
||||
</div>
|
||||
</template>
|
||||
|
@ -120,10 +207,12 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import MsChart from '@/components/pure/chart/index.vue';
|
||||
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 StepProgress from './stepProgress.vue';
|
||||
import TiledList from './tiledList.vue';
|
||||
|
||||
import { reportDetail } from '@/api/modules/api-test/report';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
@ -174,6 +263,81 @@
|
|||
pendingCount: 9,
|
||||
successCount: 9,
|
||||
});
|
||||
|
||||
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: 1048,
|
||||
name: '通过',
|
||||
itemStyle: {
|
||||
color: '#00C261',
|
||||
},
|
||||
},
|
||||
{
|
||||
value: 735,
|
||||
name: '误报',
|
||||
itemStyle: {
|
||||
color: '#FFC14E',
|
||||
},
|
||||
},
|
||||
{
|
||||
value: 580,
|
||||
name: '失败',
|
||||
itemStyle: {
|
||||
color: '#ED0303',
|
||||
},
|
||||
},
|
||||
{
|
||||
value: 484,
|
||||
name: '未执行',
|
||||
itemStyle: {
|
||||
color: '#D4D4D8',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const activeTab = ref('tiled');
|
||||
const condition = ref('');
|
||||
|
||||
const methods = ref([
|
||||
{
|
||||
label: '平铺展示',
|
||||
value: 'tiled',
|
||||
},
|
||||
{
|
||||
label: 'Tab展示',
|
||||
value: 'tab',
|
||||
},
|
||||
]);
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
@ -199,13 +363,37 @@
|
|||
.countItem {
|
||||
@apply mr-6 flex items-center;
|
||||
}
|
||||
.card {
|
||||
@apply mt-4 flex items-center justify-between;
|
||||
.timer-card {
|
||||
border-radius: 6px;
|
||||
background-color: var(--color-text-n9);
|
||||
@apply flex flex-1 flex-col p-4;
|
||||
}
|
||||
}
|
||||
}
|
||||
.request-analyze {
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
@apply h-full bg-white;
|
||||
@apply ml-4 h-full flex-grow bg-white;
|
||||
.chart-legend {
|
||||
.chart-legend-item {
|
||||
@apply grid grid-cols-3;
|
||||
}
|
||||
.chart-flag {
|
||||
@apply flex items-center;
|
||||
.count {
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.report-info {
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
@apply bg-white;
|
||||
}
|
||||
}
|
||||
.block-title {
|
||||
@apply mb-4 font-medium;
|
||||
|
|
|
@ -25,9 +25,12 @@
|
|||
@batch-action="handleTableBatch"
|
||||
>
|
||||
<template #name="{ record, rowIndex }">
|
||||
<a-button type="text" class="flex w-full" @click="showReportDetail(record.id, rowIndex)">{{
|
||||
record.name
|
||||
}}</a-button>
|
||||
<div
|
||||
type="text"
|
||||
class="one-text-line flex w-full text-[rgb(var(--primary-5))]"
|
||||
@click="showReportDetail(record.id, rowIndex)"
|
||||
>{{ characterLimit(record.name) }}</div
|
||||
>
|
||||
</template>
|
||||
<!-- 报告类型 -->
|
||||
<template #integrated="{ record }">
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
<template>
|
||||
<div
|
||||
class="scenario-class cursor-pointer"
|
||||
:class="[
|
||||
props.showBorder ? 'border border-solid border-[var(--color-text-n8)]' : '',
|
||||
props.hasBottomMargin ? 'mb-1' : '',
|
||||
]"
|
||||
>
|
||||
<div class="flex h-[46px] items-center">
|
||||
<!-- 序号 -->
|
||||
<span class="index text-[var(--color-text-4)]">{{ props.item.sort }}</span>
|
||||
<MsIcon type="icon-icon_split_turn-down_arrow" class="mx-[4px] text-[var(--color-text-4)]" size="16" />
|
||||
<!-- 场景count -->
|
||||
<span class="mr-2 text-[var(--color-text-4)]">8</span>
|
||||
<!-- 循环控制器 -->
|
||||
<ConditionStatus :status="props.item.stepType" />
|
||||
<span class="ml-2">{{ props.item.name }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<MsTag class="cursor-pointer" :type="props.item.status === 'SUCCESS' ? 'success' : 'danger'" theme="light">
|
||||
通过
|
||||
</MsTag>
|
||||
<span class="statusCode"
|
||||
>状态码 <span class="code">{{ props.item.code }}</span></span
|
||||
>
|
||||
<span class="resTime"
|
||||
>响应时间 <span class="resTimeCount">{{ props.item.requestTime }}ms</span></span
|
||||
>
|
||||
<span class="resSize"
|
||||
>响应大小 <span class="resTimeCount">{{ props.item.responseSize }} bytes</span></span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
|
||||
import ConditionStatus from './conditionStatus.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import type { ScenarioItemType } from '@/models/apiTest/report';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
showBorder?: boolean;
|
||||
hasBottomMargin?: boolean;
|
||||
item: ScenarioItemType;
|
||||
}>(),
|
||||
{
|
||||
showBorder: true,
|
||||
hasBottomMargin: true,
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.scenario-class {
|
||||
border-radius: 4px;
|
||||
@apply flex items-center justify-between px-2;
|
||||
.index {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
border-radius: 50%;
|
||||
color: white;
|
||||
background: var(--color-text-brand);
|
||||
@apply inline-block text-center;
|
||||
}
|
||||
.resTime,
|
||||
.resSize,
|
||||
.statusCode {
|
||||
margin-right: 8px;
|
||||
color: var(--color-text-4);
|
||||
.resTimeCount {
|
||||
color: rgb(var(--success-6));
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,70 @@
|
|||
<template>
|
||||
<ms-base-table ref="tableRef" v-bind="propsRes" no-disable :indent-size="0" v-on="propsEvent">
|
||||
<template #pass="{ record }">
|
||||
<MsTag theme="light" :type="record.pass ? 'success' : 'danger'">
|
||||
{{ record.pass ? '成功' : '失败' }}
|
||||
</MsTag>
|
||||
</template>
|
||||
<template #script="{ record }">
|
||||
{{ record.script }}
|
||||
</template>
|
||||
</ms-base-table>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
data: any[];
|
||||
}>();
|
||||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'report.detail.api.resContent',
|
||||
dataIndex: 'content',
|
||||
slotName: 'content',
|
||||
showTooltip: true,
|
||||
},
|
||||
{
|
||||
title: 'report.detail.api.resContent',
|
||||
dataIndex: 'pass',
|
||||
slotName: 'pass',
|
||||
showTooltip: true,
|
||||
},
|
||||
{
|
||||
title: 'report.detail.api.resContent',
|
||||
dataIndex: 'script',
|
||||
slotName: 'script',
|
||||
showTooltip: true,
|
||||
},
|
||||
];
|
||||
|
||||
const { propsRes, propsEvent } = useTable(undefined, {
|
||||
columns,
|
||||
scroll: { x: 'auto' },
|
||||
showPagination: false,
|
||||
hoverable: false,
|
||||
showExpand: true,
|
||||
rowKey: 'id',
|
||||
rowClass: (record: any) => {
|
||||
if (record.children) {
|
||||
return 'gray-td-bg';
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
propsRes.value.data = props.data;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -0,0 +1,36 @@
|
|||
<template>
|
||||
<MsCodeEditor
|
||||
v-model:model-value="innerValue"
|
||||
:show-theme-change="false"
|
||||
title=""
|
||||
width="100%"
|
||||
height="208px"
|
||||
:show-language-change="props.language ? true : false"
|
||||
theme="MS-text"
|
||||
:language="props.language"
|
||||
:show-charset-change="props.showCharsetChange"
|
||||
:read-only="false"
|
||||
:show-full-screen="false"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useVModel } from '@vueuse/core';
|
||||
|
||||
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
||||
import { CustomTheme, editorProps, Language, LanguageEnum, Theme } from '@/components/pure/ms-code-editor/types';
|
||||
|
||||
const props = defineProps<{
|
||||
script: string;
|
||||
language?: Language;
|
||||
showCharsetChange: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:script', value: string): void;
|
||||
}>();
|
||||
const innerValue = useVModel(props, 'script', emit);
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -0,0 +1,269 @@
|
|||
<template>
|
||||
<MsDrawer
|
||||
ref="detailDrawerRef"
|
||||
v-model:visible="showDrawer"
|
||||
:width="960"
|
||||
:footer="false"
|
||||
:title="t('步骤名称')"
|
||||
show-full-screen
|
||||
:unmount-on-close="true"
|
||||
>
|
||||
<template #headerLeft>
|
||||
<div class="scene-type"> API </div>
|
||||
</template>
|
||||
<div>
|
||||
<ms-base-table
|
||||
ref="tableRef"
|
||||
v-bind="propsRes"
|
||||
v-model:expandedKeys="expandedKeys"
|
||||
no-disable
|
||||
:indent-size="0"
|
||||
v-on="propsEvent"
|
||||
>
|
||||
<template #titleName>
|
||||
<div class="flex w-full justify-between">
|
||||
<div class="font-medium">响应内容</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>
|
||||
</template>
|
||||
<template #name="{ record }">
|
||||
<span class="font-medium">
|
||||
{{ record.name }}
|
||||
</span>
|
||||
|
||||
<div v-if="record.script && !record.isAssertion" class="w-full">
|
||||
<ResContent :script="record.script" language="JSON" :show-charset-change="record.script" />
|
||||
</div>
|
||||
<div v-if="record.isAssertion" class="w-full">
|
||||
<assertTable :data="record.assertions" />
|
||||
</div>
|
||||
</template>
|
||||
</ms-base-table>
|
||||
</div>
|
||||
</MsDrawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import type { MsPaginationI, MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import assertTable from './assertTable.vue';
|
||||
import ResContent from './resContent.vue';
|
||||
|
||||
import { reportDetail } from '@/api/modules/api-test/report';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
stepId: string;
|
||||
activeStepIndex: number;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', val: boolean): void;
|
||||
}>();
|
||||
|
||||
const showDrawer = computed({
|
||||
get() {
|
||||
return props.visible;
|
||||
},
|
||||
set(val) {
|
||||
emit('update:visible', val);
|
||||
},
|
||||
});
|
||||
const innerFileId = ref(props.stepId);
|
||||
function loadedStep(detail: Record<string, any>) {
|
||||
innerFileId.value = detail.id;
|
||||
}
|
||||
|
||||
const tableRef = ref<InstanceType<typeof MsBaseTable> | null>(null);
|
||||
|
||||
const expandedKeys = ref<string[]>([]);
|
||||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'report.detail.api.resContent',
|
||||
dataIndex: 'name',
|
||||
slotName: 'name',
|
||||
titleSlotName: 'titleName',
|
||||
fixed: 'left',
|
||||
headerCellClass: 'titleClass',
|
||||
bodyCellClass: (record) => {
|
||||
if (record.children) {
|
||||
return '';
|
||||
}
|
||||
return 'cellClassWrapper';
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(undefined, {
|
||||
columns,
|
||||
scroll: { x: 'auto' },
|
||||
showPagination: false,
|
||||
hoverable: false,
|
||||
showExpand: true,
|
||||
rowKey: 'id',
|
||||
rowClass: (record: any) => {
|
||||
if (record.children) {
|
||||
return 'gray-td-bg';
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const listMap = [
|
||||
{
|
||||
title: '响应体',
|
||||
value: 'body',
|
||||
},
|
||||
{
|
||||
title: '响应头',
|
||||
value: 'headers',
|
||||
},
|
||||
{
|
||||
title: '实际请求',
|
||||
value: 'request',
|
||||
},
|
||||
{
|
||||
title: '控制台',
|
||||
value: 'request',
|
||||
},
|
||||
];
|
||||
|
||||
onMounted(() => {
|
||||
// 虚拟数据
|
||||
propsRes.value.data = [
|
||||
{
|
||||
id: '1001',
|
||||
name: '响应体',
|
||||
script: '',
|
||||
isAssertion: false,
|
||||
assertions: [],
|
||||
children: [
|
||||
{
|
||||
script: '1',
|
||||
isAssertion: false,
|
||||
assertions: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '1002',
|
||||
name: '响应头',
|
||||
script: '',
|
||||
isAssertion: false,
|
||||
assertions: [],
|
||||
children: [
|
||||
{
|
||||
script: '2',
|
||||
isAssertion: false,
|
||||
assertions: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '1003',
|
||||
name: '实际请求',
|
||||
script: '',
|
||||
isAssertion: false,
|
||||
assertions: [],
|
||||
children: [
|
||||
{
|
||||
script: '3',
|
||||
isAssertion: false,
|
||||
assertions: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '1004',
|
||||
name: '控制台',
|
||||
script: '',
|
||||
isAssertion: false,
|
||||
assertions: [],
|
||||
children: [
|
||||
{
|
||||
script: '4',
|
||||
isAssertion: false,
|
||||
assertions: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '1005',
|
||||
name: '提取',
|
||||
script: '',
|
||||
isAssertion: false,
|
||||
assertions: [],
|
||||
children: [
|
||||
{
|
||||
script: '1',
|
||||
isAssertion: false,
|
||||
assertions: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '1007',
|
||||
name: '断言',
|
||||
script: '',
|
||||
isAssertion: false,
|
||||
assertions: [],
|
||||
children: [
|
||||
{
|
||||
script: '1',
|
||||
isAssertion: true,
|
||||
assertions: [
|
||||
{
|
||||
name: 'string',
|
||||
content: 'string',
|
||||
script: 'string',
|
||||
message: 'string',
|
||||
pass: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.scene-type {
|
||||
margin-left: 8px;
|
||||
padding: 0 2px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
border: 1px solid rgb(var(--link-6));
|
||||
border-radius: 0 12px 12px 0;
|
||||
color: rgb(var(--link-6));
|
||||
}
|
||||
.resContentHeader {
|
||||
@apply flex justify-between;
|
||||
}
|
||||
:deep(.gray-td-bg) {
|
||||
td {
|
||||
background-color: var(--color-text-n9);
|
||||
}
|
||||
}
|
||||
:deep(.titleClass .arco-table-th-title) {
|
||||
@apply w-full;
|
||||
}
|
||||
:deep(.cellClassWrapper .arco-table-cell) {
|
||||
padding: 0 !important;
|
||||
span {
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,147 @@
|
|||
<template>
|
||||
<div class="tiled-wrap">
|
||||
<ScenarioItem :item="scenario" :show-border="false" />
|
||||
<a-divider :margin="0" class="!mb-4"></a-divider>
|
||||
<div class="pl-[32px] pr-4">
|
||||
<MsList
|
||||
v-model:data="tiledList"
|
||||
mode="static"
|
||||
item-key-field="stepId"
|
||||
:item-border="false"
|
||||
class="w-full rounded-[var(--border-radius-small)]"
|
||||
:no-more-data="noMoreData"
|
||||
:draggable="false"
|
||||
:virtual-list-props="{
|
||||
height: 'calc(100vh - 438px)',
|
||||
}"
|
||||
>
|
||||
<template #item="{ item }">
|
||||
<ScenarioItem :item="item" @click="showDetail(item)" />
|
||||
</template>
|
||||
</MsList>
|
||||
</div>
|
||||
<StepDrawer v-model:visible="showStepDrawer" :step-id="activeDetailId" :active-step-index="activeStepIndex" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import MsList from '@/components/pure/ms-list/index.vue';
|
||||
import ScenarioItem from './scenarioItem.vue';
|
||||
import StepDrawer from './step/stepDrawer.vue';
|
||||
|
||||
import type { ScenarioItemType } from '@/models/apiTest/report';
|
||||
|
||||
const noMoreData = ref<boolean>(false);
|
||||
const tiledList = ref<ScenarioItemType[]>([
|
||||
{
|
||||
stepId: '步骤id',
|
||||
reportId: '报告id',
|
||||
name: '场景名称',
|
||||
sort: 0,
|
||||
stepType: 'QUOTE_API',
|
||||
parentId: 'string',
|
||||
status: 'SUCCESS',
|
||||
fakeCode: 'string',
|
||||
requestName: 'string',
|
||||
requestTime: 3000,
|
||||
code: '200',
|
||||
responseSize: 234543,
|
||||
scriptIdentifier: 'string',
|
||||
},
|
||||
{
|
||||
stepId: '步骤id1',
|
||||
reportId: '报告id',
|
||||
name: '场景名称',
|
||||
sort: 0,
|
||||
stepType: 'LOOP_CONTROL',
|
||||
parentId: 'string',
|
||||
status: 'SUCCESS',
|
||||
fakeCode: 'string',
|
||||
requestName: 'string',
|
||||
requestTime: 3000,
|
||||
code: '200',
|
||||
responseSize: 234543,
|
||||
scriptIdentifier: 'string',
|
||||
},
|
||||
{
|
||||
stepId: '步骤id1',
|
||||
reportId: '报告id',
|
||||
name: '场景名称',
|
||||
sort: 0,
|
||||
stepType: 'CONDITION_CONTROL',
|
||||
parentId: 'string',
|
||||
status: 'ERROR',
|
||||
fakeCode: 'string',
|
||||
requestName: 'string',
|
||||
requestTime: 3000,
|
||||
code: '200',
|
||||
responseSize: 234543,
|
||||
scriptIdentifier: 'string',
|
||||
},
|
||||
{
|
||||
stepId: '步骤id1',
|
||||
reportId: '报告id',
|
||||
name: '场景名称',
|
||||
sort: 0,
|
||||
stepType: 'ONLY_ONCE_CONTROL',
|
||||
parentId: 'string',
|
||||
status: 'SUCCESS',
|
||||
fakeCode: 'string',
|
||||
requestName: 'string',
|
||||
requestTime: 3000,
|
||||
code: '200',
|
||||
responseSize: 234543,
|
||||
scriptIdentifier: 'string',
|
||||
},
|
||||
{
|
||||
stepId: '步骤id1',
|
||||
reportId: '报告id',
|
||||
name: '场景名称',
|
||||
sort: 0,
|
||||
stepType: 'ONLY_ONCE_CONTROL',
|
||||
parentId: 'string',
|
||||
status: 'ERROR',
|
||||
fakeCode: 'string',
|
||||
requestName: 'string',
|
||||
requestTime: 3000,
|
||||
code: '200',
|
||||
responseSize: 234543,
|
||||
scriptIdentifier: 'string',
|
||||
},
|
||||
]);
|
||||
|
||||
const scenario = ref<ScenarioItemType>({
|
||||
stepId: '步骤id1',
|
||||
reportId: '报告id',
|
||||
name: '场景名称',
|
||||
sort: 0,
|
||||
stepType: 'ONLY_ONCE_CONTROL',
|
||||
parentId: 'string',
|
||||
status: 'string',
|
||||
fakeCode: 'string',
|
||||
requestName: 'string',
|
||||
requestTime: 3000,
|
||||
code: '200',
|
||||
responseSize: 234543,
|
||||
scriptIdentifier: 'string',
|
||||
});
|
||||
|
||||
const showStepDrawer = ref<boolean>(false);
|
||||
const activeDetailId = ref<string>('');
|
||||
const activeStepIndex = ref<number>(0);
|
||||
function showDetail(item: ScenarioItemType) {
|
||||
showStepDrawer.value = true;
|
||||
activeDetailId.value = item.stepId;
|
||||
activeStepIndex.value = item.sort;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.tiled-wrap {
|
||||
min-height: 300px;
|
||||
border: 1px solid var(--color-text-n8);
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
|
@ -32,4 +32,5 @@ export default {
|
|||
'report.detail.scenario.errorTip':
|
||||
'There is a pre/post script running error in the execution step of the current scenario.',
|
||||
'report.detail.api.errorTip': 'There are pre/post script running errors in the current use case execution.',
|
||||
'report.detail.api.resContent': 'Response content',
|
||||
};
|
||||
|
|
|
@ -31,4 +31,5 @@ export default {
|
|||
'report.detail.script.error': '脚本失败',
|
||||
'report.detail.scenario.errorTip': '当前场景的执行步骤中存在 前/后置脚本运行错误',
|
||||
'report.detail.api.errorTip': '当前用例执行存在 前/后置脚本运行错误',
|
||||
'report.detail.api.resContent': '响应内容',
|
||||
};
|
||||
|
|
|
@ -48,9 +48,12 @@
|
|||
<span type="text" class="px-0" @click="showCaseDetail(record.id, rowIndex)">{{ record.num }}</span>
|
||||
</template>
|
||||
<template #name="{ record, rowIndex }">
|
||||
<a-button type="text" class="flex w-full" @click="showCaseDetail(record.id, rowIndex)">{{
|
||||
characterLimit(record.name)
|
||||
}}</a-button>
|
||||
<div
|
||||
type="text"
|
||||
class="one-line-text text-[rgb(var(--primary-5))]"
|
||||
@click="showCaseDetail(record.id, rowIndex)"
|
||||
>{{ characterLimit(record.name) }}</div
|
||||
>
|
||||
</template>
|
||||
<template #updateUserName="{ record }">
|
||||
<span type="text" class="px-0">{{ record.updateUserName || '-' }}</span>
|
||||
|
|
Loading…
Reference in New Issue