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>
|
||||||
</div>
|
</div>
|
||||||
<StepProgress :report-detail="reportStepDetail" height="8px" radius="var(--border-radius-mini)" />
|
<StepProgress :report-detail="reportStepDetail" height="8px" radius="var(--border-radius-mini)" />
|
||||||
<div>
|
<div class="card">
|
||||||
<div></div>
|
<div class="timer-card mr-2">
|
||||||
<div></div>
|
<div class="text-[var(--color-text-4)]">
|
||||||
<div></div>
|
<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>
|
</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>
|
||||||
<!-- 报告步骤分析和请求分析结束 -->
|
<!-- 报告步骤分析和请求分析结束 -->
|
||||||
<!-- 报告明细开始 -->
|
<!-- 报告明细开始 -->
|
||||||
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -120,10 +207,12 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import MsChart from '@/components/pure/chart/index.vue';
|
||||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
import type { MsPaginationI } from '@/components/pure/ms-table/type';
|
import type { MsPaginationI } from '@/components/pure/ms-table/type';
|
||||||
import MsDetailDrawer from '@/components/business/ms-detail-drawer/index.vue';
|
import MsDetailDrawer from '@/components/business/ms-detail-drawer/index.vue';
|
||||||
import StepProgress from './stepProgress.vue';
|
import StepProgress from './stepProgress.vue';
|
||||||
|
import TiledList from './tiledList.vue';
|
||||||
|
|
||||||
import { reportDetail } from '@/api/modules/api-test/report';
|
import { reportDetail } from '@/api/modules/api-test/report';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
@ -174,6 +263,81 @@
|
||||||
pendingCount: 9,
|
pendingCount: 9,
|
||||||
successCount: 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>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
|
@ -199,13 +363,37 @@
|
||||||
.countItem {
|
.countItem {
|
||||||
@apply mr-6 flex items-center;
|
@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 {
|
.request-analyze {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
border-radius: 4px;
|
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 {
|
.block-title {
|
||||||
@apply mb-4 font-medium;
|
@apply mb-4 font-medium;
|
||||||
|
|
|
@ -25,9 +25,12 @@
|
||||||
@batch-action="handleTableBatch"
|
@batch-action="handleTableBatch"
|
||||||
>
|
>
|
||||||
<template #name="{ record, rowIndex }">
|
<template #name="{ record, rowIndex }">
|
||||||
<a-button type="text" class="flex w-full" @click="showReportDetail(record.id, rowIndex)">{{
|
<div
|
||||||
record.name
|
type="text"
|
||||||
}}</a-button>
|
class="one-text-line flex w-full text-[rgb(var(--primary-5))]"
|
||||||
|
@click="showReportDetail(record.id, rowIndex)"
|
||||||
|
>{{ characterLimit(record.name) }}</div
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
<!-- 报告类型 -->
|
<!-- 报告类型 -->
|
||||||
<template #integrated="{ record }">
|
<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':
|
'report.detail.scenario.errorTip':
|
||||||
'There is a pre/post script running error in the execution step of the current scenario.',
|
'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.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.script.error': '脚本失败',
|
||||||
'report.detail.scenario.errorTip': '当前场景的执行步骤中存在 前/后置脚本运行错误',
|
'report.detail.scenario.errorTip': '当前场景的执行步骤中存在 前/后置脚本运行错误',
|
||||||
'report.detail.api.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>
|
<span type="text" class="px-0" @click="showCaseDetail(record.id, rowIndex)">{{ record.num }}</span>
|
||||||
</template>
|
</template>
|
||||||
<template #name="{ record, rowIndex }">
|
<template #name="{ record, rowIndex }">
|
||||||
<a-button type="text" class="flex w-full" @click="showCaseDetail(record.id, rowIndex)">{{
|
<div
|
||||||
characterLimit(record.name)
|
type="text"
|
||||||
}}</a-button>
|
class="one-line-text text-[rgb(var(--primary-5))]"
|
||||||
|
@click="showCaseDetail(record.id, rowIndex)"
|
||||||
|
>{{ characterLimit(record.name) }}</div
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
<template #updateUserName="{ record }">
|
<template #updateUserName="{ record }">
|
||||||
<span type="text" class="px-0">{{ record.updateUserName || '-' }}</span>
|
<span type="text" class="px-0">{{ record.updateUserName || '-' }}</span>
|
||||||
|
|
Loading…
Reference in New Issue