fix: 修改全局部分bug

This commit is contained in:
xinxin.wu 2024-03-29 19:10:16 +08:00 committed by Craftsman
parent fa1016566f
commit c64b97ba06
30 changed files with 349 additions and 369 deletions

View File

@ -38,6 +38,7 @@
v-model:model-value="record.expression"
class="ms-params-input"
:max-length="255"
size="mini"
:disabled="props.disabled"
:placeholder="t('apiTestDebug.commonPlaceholder')"
@input="() => handleExpressionChange(rowIndex)"
@ -133,6 +134,7 @@
:disabled="props.disabled"
class="ms-params-input"
:max-length="255"
size="mini"
:placeholder="t('apiTestDebug.commonPlaceholder')"
@input="() => handleExpressionChange(rowIndex)"
@change="() => handleExpressionChange(rowIndex)"
@ -281,6 +283,7 @@
:disabled="props.disabled"
class="ms-params-input"
:max-length="255"
size="mini"
@input="() => handleExpressionChange(rowIndex)"
@change="() => handleExpressionChange(rowIndex)"
>
@ -457,7 +460,6 @@
dataIndex: 'condition',
slotName: 'condition',
options: statusCodeOptions,
width: 120,
},
{
title: 'ms.assertion.matchValue',

View File

@ -40,15 +40,13 @@
>
<div class="ms-assertion-body-left-item-row">
<span class="ms-assertion-body-left-item-row-num">{{ index + 1 }}</span>
<div class="one-text-line">{{ item.name }}</div>
<div class="one-line-text" :class="{ 'text-[rgb(var(--primary-5))]': activeKey === item.id }">{{
item.name
}}</div>
</div>
<div class="ms-assertion-body-left-item-switch">
<div v-show="!props.disabled" class="ms-assertion-body-left-item-switch-action">
<!-- <MsIcon
type="icon-icon_drag"
class="action-btn-move sort-handle cursor-move text-[12px] text-[var(--color-text-4)]"
/> -->
<icon-drag-dot-vertical class="ms-list-drag-icon" />
<icon-drag-dot-vertical class="ms-list-drag-icon sort-handle" />
<MsTableMoreAction
:list="getItemMoreActions(item)"
trigger="click"
@ -267,14 +265,21 @@
case ResponseAssertionType.RESPONSE_HEADER:
assertions.value.push({
...tmpObj,
assertions: [],
assertions: [
{
header: '',
condition: EQUAL.value,
expectedValue: '',
enable: true,
},
],
});
break;
//
case ResponseAssertionType.RESPONSE_CODE:
assertions.value.push({
...tmpObj,
condition: 'EQUALS',
condition: EQUAL.value,
expectedValue: '200',
});
break;

View File

@ -82,16 +82,6 @@
innerKeyword.value = inputVal;
}, 300);
watch(
() => props.modelValue,
(val) => {
if (val) {
selectValue.value = val as any;
}
},
{ immediate: true }
);
watch(
() => props.keyword,
(val) => {
@ -116,6 +106,9 @@
if (props.options) {
optionsList.value = props.options;
}
if (props.modelValue) {
selectValue.value = props.modelValue;
}
});
onMounted(() => {

View File

@ -1,4 +1,4 @@
import { ExecuteConditionProcessor } from '../apiTest/common';
import { EnableKeyValueParam, ExecuteConditionProcessor } from '@/models/apiTest/common';
export interface EnvListItem {
mock?: boolean;
@ -140,7 +140,7 @@ export interface HttpForm {
description?: string;
hostname: string;
type: string;
headers: Record<string, any>[];
headers: EnableKeyValueParam[];
// pathMatchRule: {
path: string;
condition: string;

View File

@ -113,6 +113,12 @@
return data.value.filter((item: any) => item.processorType === RequestConditionProcessor.EXTRACT).length > 0;
});
const hasSql = computed(
() =>
data.value.filter((item: any) => item.processorType === RequestConditionProcessor.SQL).length > 0 &&
props.showPrePostRequest
);
const itemMoreActions: ActionsItem[] = [
{
label: 'common.copy',
@ -131,7 +137,7 @@
watchEffect(() => {
activeItem.value = data.value.find((item) => item.id === props.activeId) || data.value[0] || {};
emit('activeChange', activeItem.value);
if (hasPreAndPost.value || hasEXTRACT.value) {
if (hasPreAndPost.value || hasEXTRACT.value || hasSql.value) {
moreActions = itemMoreActions.slice(-1);
} else {
moreActions = itemMoreActions;

View File

@ -61,7 +61,16 @@
</div>
</template>
</a-popover>
<span v-if="props.showType && props.showType !== 'CASE'">{{ props.environmentName }}</span>
<a-popover position="left" content-class="response-popover-content">
<div v-if="props.showType && props.showType !== 'CASE'" class="one-line-text max-w-[150px]">{{
props.environmentName
}}</div>
<template #content>
<div v-if="props.showType && props.showType !== 'CASE'" class="one-line-text">{{
props.environmentName
}}</div>
</template>
</a-popover>
</div>
</div>
<div v-if="activeType === 'SubRequest'" class="my-4 flex justify-start">
@ -112,7 +121,7 @@
import { reportCaseStepDetail, reportStepDetail } from '@/api/modules/api-test/report';
import { useI18n } from '@/hooks/useI18n';
import { findNodeByKey } from '@/utils';
import { findNodeByKey, formatDuration } from '@/utils';
import type { ReportStepDetail, ReportStepDetailItem, ScenarioItemType } from '@/models/apiTest/report';
import { ResponseComposition, ScenarioStepType } from '@/enums/apiEnum';

View File

@ -421,7 +421,7 @@
dataIndex: 'tags',
isTag: true,
isStringTag: true,
width: 150,
width: 400,
showDrag: true,
},
{

View File

@ -482,6 +482,7 @@
isTag: true,
isStringTag: true,
showDrag: true,
width: 400,
},
{
title: 'case.lastReportStatus',

View File

@ -1,74 +1,14 @@
<template>
<div class="report-container h-full">
<!-- 报告参数开始 -->
<div class="report-header flex items-center justify-between">
<span>
<span v-if="route.query.shareId" class="font-medium"
>报告名称 <span>{{ detail.name }}</span>
<a-divider direction="vertical" :margin="4" class="!mx-2"></a-divider
></span>
<a-popover position="left" content-class="response-popover-content">
<span> {{ detail.environmentName || t('report.detail.api.defaultEnv') }}</span>
<a-divider direction="vertical" :margin="4" class="!mx-2"></a-divider>
<template #content>
<div class="flex items-center gap-[8px] text-[14px]">
<div class="text-[var(--color-text-4)]">{{ t('report.detail.api.executeEnv') }}</div>
<span class="mx-1"> {{ detail.environmentName || t('report.detail.api.caseSaveEnv') }}</span>
</div>
</template>
</a-popover>
<a-popover position="bottom" content-class="response-popover-content">
<span> {{ detail.poolName || '-' }}</span>
<a-divider direction="vertical" :margin="4" class="!mx-2"></a-divider>
<template #content>
<div class="flex items-center gap-[8px] text-[14px]">
<div class="text-[var(--color-text-4)]">{{ t('project.taskCenter.resourcePool') }}</div>
<span class="mx-1"> {{ detail.poolName || '-' }}</span>
</div>
</template>
</a-popover>
<a-popover position="left" content-class="response-popover-content">
<span v-if="detail.runMode">
{{ detail.runMode === 'SERIAL' ? t('case.execute.serial') : t('case.execute.parallel') }}</span
>
<a-divider v-if="detail.runMode" direction="vertical" :margin="4" class="!mx-2"></a-divider>
<template #content>
<div class="items-center gap-[8px] text-[14px]">
<div class="text-[var(--color-text-4)]">{{ t('report.detail.api.runMode') }}</div>
<div class="mx-1 mt-1">
{{ detail.runMode === 'SERIAL' ? t('case.execute.serial') : t('case.execute.parallel') }}</div
>
</div>
</template>
</a-popover>
<a-popover position="bottom" content-class="response-popover-content">
<span> {{ detail.creatUserName || '-' }}</span>
<template #content>
<div class="items-center gap-[8px] text-[14px]">
<div class="text-[var(--color-text-4)]">{{ t('report.detail.api.reportCreator') }}</div>
<div class="mt-1"> {{ detail.creatUserName || '-' }}</div>
</div>
</template>
</a-popover>
</span>
<span>
<span class="text-[var(--color-text-4)]">{{ t('report.detail.api.executionTime') }}</span>
{{ detail.startTime ? dayjs(detail.startTime).format('YYYY-MM-DD HH:mm:ss') : '-' }}
<span class="text-[var(--color-text-4)]">{{ t('report.detail.api.executionTimeTo') }}</span>
{{ detail.endTime ? dayjs(detail.endTime).format('YYYY-MM-DD HH:mm:ss') : '-' }}
</span>
</div>
<ReportDetailHeader :detail="detail" show-type="CASE" />
<!-- 报告参数结束 -->
<!-- 报告分析开始 -->
<div class="analyze mb-1">
<!-- 请求分析 -->
<div class="request-analyze min-h-[110px]">
<div class="block-title mb-4">{{ t('report.detail.api.requestAnalysis') }}</div>
<!-- 独立报告 -->
<SetReportChart :legend-data="legendData" :options="charOptions" :request-total="getIndicators(detail.total)" />
<!-- 集合报告 -->
<!-- </div> -->
</div>
<!-- 耗时分析 -->
<div class="time-analyze">
@ -76,15 +16,17 @@
<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.split('-')[0] || '-' }}</span
<span class="count">{{ getTotalTime.split('-')[0] || 0 }}</span
><span class="time-card-item-title">{{ getTotalTime.split('-')[1] || 'ms' }}</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">{{ formatDuration(detail.requestDuration).split('-')[0] || '-' }}</span
<span class="count">{{
detail.requestDuration !== null ? formatDuration(detail.requestDuration).split('-')[0] : '-'
}}</span
><span class="time-card-item-title">{{
formatDuration(detail.requestDuration).split('-')[1] || 'ms'
detail.requestDuration !== null ? formatDuration(detail.requestDuration).split('-')[1] : 'ms'
}}</span>
</div>
</div>
@ -132,7 +74,7 @@
<!-- 报告步骤分析结束 -->
<!-- 报告明细开始 -->
<div class="report-info">
<reportInfoHeader v-model:keyword="cascaderKeywords" v-model:active-tab="activeTab" />
<reportInfoHeader v-model:keyword="cascaderKeywords" v-model:active-tab="activeTab" show-type="CASE" />
<TiledList
:key-words="cascaderKeywords"
show-type="CASE"
@ -147,9 +89,10 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import dayjs from 'dayjs';
import { cloneDeep } from 'lodash-es';
import SetReportChart from './case/setReportChart.vue';
import ReportDetailHeader from './reportDetailHeader.vue';
import reportInfoHeader from './step/reportInfoHeaders.vue';
import TiledList from './tiledList.vue';
@ -329,7 +272,12 @@
rateKey: 'requestPendingRate',
},
];
const validArr = props?.detailInfo?.integrated ? tempArr : tempArr.slice(0, 1);
let validArr;
if (props?.detailInfo?.integrated) {
validArr = cloneDeep(tempArr);
} else {
validArr = props?.detailInfo?.status === 'SUCCESS' ? [tempArr[0]] : [tempArr[2]];
}
charOptions.value.series.data = validArr.map((item: any) => {
return {
value: detail.value[item.value] || 0,

View File

@ -0,0 +1,102 @@
<template>
<div class="report-header flex items-center justify-between">
<div class="flex items-center">
<div v-if="route.query.shareId" class="font-medium"
>{{ t('report.name') }}
<div class="one-line-text max-w-[150px]">{{ props.detail.name }}</div>
<a-divider direction="vertical" :margin="4" class="!mx-2"></a-divider
></div>
<a-popover position="left" content-class="response-popover-content">
<div class="one-line-text max-w-[150px]"> {{ props.detail.environmentName || '-' }}</div>
<a-divider direction="vertical" :margin="4" class="!mx-2"></a-divider>
<template #content>
<div class="flex items-center gap-[8px] text-[14px]">
<div class="text-[var(--color-text-4)]">{{ t('report.detail.api.executeEnv') }}</div>
<div class="mx-1"> {{ props.detail.environmentName || '-' }}</div>
</div>
</template>
</a-popover>
<a-popover position="bottom" content-class="response-popover-content">
<div class="one-line-text max-w-[150px]"> {{ props.detail.poolName || '-' }}</div>
<a-divider direction="vertical" :margin="4" class="!mx-2"></a-divider>
<template #content>
<div class="flex items-center gap-[8px] text-[14px]">
<div class="text-[var(--color-text-4)]">{{ t('project.taskCenter.resourcePool') }}</div>
<span class="mx-1"> {{ props.detail.poolName || '-' }}</span>
</div>
</template>
</a-popover>
<a-popover position="left" content-class="response-popover-content">
<span v-if="!props.detail.integrated && props.showType === 'API'">
{{ props.detail.waitingTime ? formatDuration(props.detail.waitingTime).split('-')[0] : '-' }}
<span>{{ props.detail.waitingTime ? formatDuration(props.detail.waitingTime).split('-')[1] : 'ms' }}</span>
<a-divider direction="vertical" :margin="4" class="!mx-2"></a-divider>
</span>
<template #content>
<div class="flex items-center gap-[8px] text-[14px]">
<div class="text-[var(--color-text-4)]">{{ t('report.detail.api.globalWaitingTime') }}</div>
{{ props.detail.waitingTime ? formatDuration(props.detail.waitingTime).split('-')[0] : '-' }}
<span class="mx-1">{{
props.detail.waitingTime ? formatDuration(props.detail.waitingTime).split('-')[1] : 'ms'
}}</span>
</div>
</template>
</a-popover>
<a-popover position="left" content-class="response-popover-content">
<span v-if="showRunMode">
{{ props.detail.runMode === 'SERIAL' ? t('case.execute.serial') : t('case.execute.parallel') }}</span
>
<a-divider v-if="showRunMode" direction="vertical" :margin="4" class="!mx-2"></a-divider>
<template #content>
<div class="items-center gap-[8px] text-[14px]">
<div class="text-[var(--color-text-4)]">{{ t('report.detail.api.runMode') }}</div>
<div class="mt-1">
{{ props.detail.runMode === 'SERIAL' ? t('case.execute.serial') : t('case.execute.parallel') }}</div
>
</div>
</template>
</a-popover>
<a-popover position="bottom" content-class="response-popover-content">
<div class="one-line-text max-w-[150px]"> {{ props.detail.creatUserName || '-' }}</div>
<template #content>
<div class="items-center gap-[8px] text-[14px]">
<div class="text-[var(--color-text-4)]">{{ t('report.detail.api.reportCreator') }}</div>
<div class="mx-1 mt-1"> {{ props.detail.creatUserName || '-' }}</div>
</div>
</template>
</a-popover>
</div>
<div>
<span class="text-[var(--color-text-4)]">{{ t('report.detail.api.executionTime') }}</span>
{{ props.detail.startTime ? dayjs(props.detail.startTime).format('YYYY-MM-DD HH:mm:ss') : '-' }}
<span class="text-[var(--color-text-4)]">{{ t('report.detail.api.executionTimeTo') }}</span>
{{ props.detail.endTime ? dayjs(props.detail.endTime).format('YYYY-MM-DD HH:mm:ss') : '-' }}
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import dayjs from 'dayjs';
import { useI18n } from '@/hooks/useI18n';
import { formatDuration } from '@/utils';
import type { ReportDetail } from '@/models/apiTest/report';
const { t } = useI18n();
const route = useRoute();
const props = defineProps<{
detail: ReportDetail;
showType: 'API' | 'CASE';
}>();
const showRunMode = computed(() => {
return props.showType === 'API' ? props.detail.runMode : props.detail.runMode && props.detail.integrated;
});
</script>
<style scoped></style>

View File

@ -27,7 +27,7 @@
<template #name="{ record, rowIndex }">
<div
type="text"
class="one-text-line flex w-full text-[rgb(var(--primary-5))]"
class="one-line-text flex w-full text-[rgb(var(--primary-5))]"
@click="showReportDetail(record.id, rowIndex, record.integrated)"
>{{ characterLimit(record.name) }}</div
>
@ -87,8 +87,12 @@
</a-button>
<template #content>
<div class="arco-table-filters-content">
<div class="ml-[6px] flex items-center justify-start px-[6px] py-[2px]">
<a-checkbox-group v-model:model-value="statusListFilters" direction="vertical" size="small">
<div class="flex items-center justify-center px-[6px] py-[2px]">
<a-checkbox-group
v-model:model-value="statusListFiltersMap[showType]"
direction="vertical"
size="small"
>
<a-checkbox v-for="key of statusFilters" :key="key" :value="key">
<ExecutionStatus :module-type="props.moduleType" :status="key" />
</a-checkbox>
@ -153,6 +157,7 @@
<script setup lang="ts">
import { ref } from 'vue';
import { Message } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es';
import dayjs from 'dayjs';
import MsButton from '@/components/pure/ms-button/index.vue';
@ -190,7 +195,6 @@
const statusFilterVisible = ref(false);
const triggerModeFilterVisible = ref(false);
const statusListFilters = ref<string[]>([]);
const triggerModeListFilters = ref<string[]>([]);
type ReportShowType = 'All' | 'INDEPENDENT' | 'INTEGRATED';
@ -251,8 +255,9 @@
slotName: 'createUserName',
dataIndex: 'createUserName',
showInTable: true,
width: 200,
width: 300,
showDrag: true,
showTooltip: true,
},
{
title: 'report.operating',
@ -303,6 +308,15 @@
}),
rename
);
//
const allListFilters = ref<string[]>([]);
const independentListFilters = ref<string[]>([]);
const integratedListFilters = ref<string[]>([]);
const statusListFiltersMap = ref<Record<string, string[]>>({
all: allListFilters.value,
INDEPENDENT: independentListFilters.value,
INTEGRATED: integratedListFilters.value,
});
function initData() {
setLoadListParams({
@ -310,7 +324,7 @@
projectId: appStore.currentProjectId,
moduleType: props.moduleType,
filter: {
status: statusListFilters.value,
status: statusListFiltersMap.value[showType.value],
integrated: showType.value === 'All' ? undefined : Array.of((showType.value === 'INTEGRATED').toString()),
triggerMode: triggerModeListFilters.value,
},
@ -342,7 +356,7 @@
selectIds: params?.selectedIds || [],
condition: {
filter: {
status: statusListFilters.value,
status: statusListFiltersMap.value[showType.value],
integrated: showType.value === 'All' ? undefined : Array.of((showType.value === 'INTEGRATED').toString()),
triggerMode: triggerModeListFilters.value,
},
@ -429,7 +443,7 @@
function resetStatusFilter() {
statusFilterVisible.value = false;
statusListFilters.value = [];
statusListFiltersMap.value[showType.value] = [];
initData();
}

View File

@ -1,78 +1,7 @@
<template>
<div class="report-container h-full">
<!-- 报告参数开始 -->
<div class="report-header flex items-center justify-between">
<!-- TODO 虚拟数据替换接口后边 -->
<span>
<a-popover position="left" content-class="response-popover-content">
<span> {{ detail.environmentName || t('report.detail.api.defaultEnv') }}</span>
<a-divider direction="vertical" :margin="4" class="!mx-2"></a-divider>
<template #content>
<div class="flex items-center gap-[8px] text-[14px]">
<div class="text-[var(--color-text-4)]">{{ t('report.detail.api.executeEnv') }}</div>
<span class="mx-1"> {{ detail.environmentName || t('report.detail.api.scenarioSavedEnv') }}</span>
</div>
</template>
</a-popover>
<a-popover position="bottom" content-class="response-popover-content">
<span> {{ detail.poolName || '-' }}</span>
<a-divider direction="vertical" :margin="4" class="!mx-2"></a-divider>
<template #content>
<div class="flex items-center gap-[8px] text-[14px]">
<div class="text-[var(--color-text-4)]">{{ t('project.taskCenter.resourcePool') }}</div>
<span class="mx-1"> {{ detail.poolName || '-' }}</span>
</div>
</template>
</a-popover>
<a-popover position="left" content-class="response-popover-content">
<span v-if="!detail.integrated">
{{ detail.waitingTime ? formatDuration(detail.waitingTime).split('-')[0] : '-' }}
<span>{{ detail.waitingTime ? formatDuration(detail.waitingTime).split('-')[1] : 'ms' }}</span>
<a-divider direction="vertical" :margin="4" class="!mx-2"></a-divider>
</span>
<template #content>
<div class="flex items-center gap-[8px] text-[14px]">
<div class="text-[var(--color-text-4)]">{{ t('report.detail.api.globalWaitingTime') }}</div>
{{ detail.waitingTime ? formatDuration(detail.waitingTime).split('-')[0] : '-' }}
<span class="mx-1">{{
detail.waitingTime ? formatDuration(detail.waitingTime).split('-')[1] : 'ms'
}}</span>
</div>
</template>
</a-popover>
<a-popover position="left" content-class="response-popover-content">
<span v-if="detail.runMode">
{{ detail.runMode === 'SERIAL' ? t('case.execute.serial') : t('case.execute.parallel') }}</span
>
<a-divider v-if="detail.runMode" direction="vertical" :margin="4" class="!mx-2"></a-divider>
<template #content>
<div class="items-center gap-[8px] text-[14px]">
<div class="text-[var(--color-text-4)]">{{ t('report.detail.api.runMode') }}</div>
<div class="mt-1">
{{ detail.runMode === 'SERIAL' ? t('case.execute.serial') : t('case.execute.parallel') }}</div
>
</div>
</template>
</a-popover>
<a-popover position="bottom" content-class="response-popover-content">
<span> {{ detail.creatUserName || '-' }}</span>
<template #content>
<div class="items-center gap-[8px] text-[14px]">
<div class="text-[var(--color-text-4)]">{{ t('report.detail.api.reportCreator') }}</div>
<div class="mx-1 mt-1"> {{ detail.creatUserName || '-' }}</div>
</div>
</template>
</a-popover>
</span>
<span>
<span class="text-[var(--color-text-4)]">{{ t('report.detail.api.executionTime') }}</span>
{{ detail.startTime ? dayjs(detail.startTime).format('YYYY-MM-DD HH:mm:ss') : '-' }}
<span class="text-[var(--color-text-4)]">{{ t('report.detail.api.executionTimeTo') }}</span>
{{ detail.endTime ? dayjs(detail.endTime).format('YYYY-MM-DD HH:mm:ss') : '-' }}
</span>
</div>
<ReportDetailHeader :detail="detail" show-type="API" />
<!-- 报告参数结束 -->
<!-- 报告步骤分析和请求分析开始 -->
<div class="analyze mb-1">
@ -128,10 +57,10 @@
</div>
<div>
<span class="ml-4 text-[18px] font-medium">{{
formatDuration(detail.requestDuration).split('-')[0] || '-'
detail.requestDuration !== null ? formatDuration(detail.requestDuration).split('-')[0] : '-'
}}</span>
<span class="ml-1 text-[var(--color-text-4)]">{{
formatDuration(detail.requestDuration).split('-')[1] || 'ms'
detail.requestDuration !== null ? formatDuration(detail.requestDuration).split('-')[1] : 'ms'
}}</span>
</div>
</div>
@ -188,7 +117,7 @@
<!-- 报告步骤分析和请求分析结束 -->
<!-- 报告明细开始 -->
<div class="report-info">
<reportInfoHeader v-model:keyword="cascaderKeywords" v-model:active-tab="activeTab" />
<reportInfoHeader v-model:keyword="cascaderKeywords" v-model:active-tab="activeTab" show-type="API" />
<TiledList :key-words="cascaderKeywords" show-type="API" :active-type="activeTab" :report-detail="detail || []" />
</div>
<!-- 报告明细结束 -->
@ -197,9 +126,11 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import dayjs from 'dayjs';
import MsChart from '@/components/pure/chart/index.vue';
import ReportDetailHeader from './reportDetailHeader.vue';
import reportInfoHeader from './step/reportInfoHeaders.vue';
import StepProgress from './stepProgress.vue';
import TiledList from './tiledList.vue';
@ -211,6 +142,8 @@
import { getIndicators } from '../utils';
const route = useRoute();
const { t } = useI18n();
const props = defineProps<{
detailInfo?: ReportDetail;

View File

@ -15,7 +15,7 @@
option-size="small"
class="w-full"
:multiple="false"
:options="cascaderOptions || []"
:options="filterOptions || []"
:virtual-list-props="{ height: 200 }"
:placeholder="t('report.detail.api.filterPlaceholder')"
>
@ -49,6 +49,7 @@
const props = defineProps<{
activeTab: 'tiled' | 'tab';
keyword: string;
showType: 'API' | 'CASE';
}>();
const emit = defineEmits(['update:activeTab', 'update:keyword']);
@ -101,6 +102,9 @@
children: createChildOption(ScenarioStepType.CUSTOM_REQUEST),
},
]);
const filterOptions = computed(() =>
props.showType === 'API' ? cascaderOptions.value : cascaderOptions.value.slice(-1)
);
</script>
<style scoped lang="less"></style>

View File

@ -39,6 +39,7 @@
import { useI18n } from '@/hooks/useI18n';
import type { ScenarioItemType } from '@/models/apiTest/report';
import { ScenarioStepType } from '@/enums/apiEnum';
const { t } = useI18n();
const props = defineProps<{
@ -65,12 +66,12 @@
});
const showCondition = ref<string[]>([
'API',
'API_CASE',
' CUSTOM_REQUEST',
' LOOP_CONTROLLER',
'IF_CONTROLLER',
'ONCE_ONLY_CONTROLLER',
ScenarioStepType.API,
ScenarioStepType.API_CASE,
ScenarioStepType.CUSTOM_REQUEST,
ScenarioStepType.LOOP_CONTROLLER,
ScenarioStepType.IF_CONTROLLER,
ScenarioStepType.ONCE_ONLY_CONTROLLER,
]);
</script>

View File

@ -71,7 +71,7 @@
</a-tooltip>
</div>
<div class="flex">
<stepStatus v-if="step.status" :status="step.status" />
<stepStatus :status="step.status || 'PENDING'" />
<!-- 脚本报错 -->
<a-popover position="left" content-class="response-popover-content">
<MsTag
@ -94,9 +94,13 @@
</a-popover>
<div v-show="showStatus(step)" class="flex">
<span class="statusCode mx-2">
<div class="mr-2"> {{ t('report.detail.api.statusCode') }}</div>
<div v-if="step.code" 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(step.code) }">
<div
v-if="step.code"
class="one-line-text max-w-[200px]"
:style="{ color: statusCodeColor(step.code) }"
>
{{ step.code || '-' }}
</div>
<template #content>
@ -109,25 +113,39 @@
</template>
</a-popover>
</span>
<span class="resTime">
<span v-if="step.requestTime !== null" class="resTime">
{{ t('report.detail.api.responseTime') }}
<span class="resTimeCount ml-2"
>{{ step.requestTime ? formatDuration(step.requestTime).split('-')[0] : '-'
}}{{ step.requestTime ? formatDuration(step.requestTime).split('-')[1] : 'ms' }}</span
></span
>
<a-popover position="left" content-class="response-popover-content">
<span class="resSize">
{{ t('report.detail.api.responseSize') }}
<span class="resTimeCount ml-2">{{ step.responseSize || 0 }} bytes</span></span
>
<template #content>
<span class="resSize">
{{ t('report.detail.api.responseSize') }}
<span class="resTimeCount ml-2">{{ step.responseSize || 0 }} bytes</span></span
<a-popover position="left" content-class="response-popover-content">
<span class="resTimeCount ml-2"
>{{ step.requestTime !== null ? formatDuration(step.requestTime).split('-')[0] : '-'
}}{{ step.requestTime !== null ? formatDuration(step.requestTime).split('-')[1] : 'ms' }}</span
>
</template>
</a-popover>
<template #content>
<span v-if="step.requestTime !== null" class="resTime">
{{ t('report.detail.api.responseTime') }}
<span class="resTimeCount ml-2"
>{{ step.requestTime !== null ? formatDuration(step.requestTime).split('-')[0] : '-'
}}{{
step.requestTime !== null ? formatDuration(step.requestTime).split('-')[1] : 'ms'
}}</span
></span
>
</template>
</a-popover></span
>
<span v-if="step.responseSize !== null" class="resSize">
{{ t('report.detail.api.responseSize') }}
<a-popover position="left" content-class="response-popover-content">
<span class="resTimeCount ml-2">{{ step.responseSize || 0 }} bytes</span>
<template #content>
<span class="resSize">
{{ t('report.detail.api.responseSize') }}
<span class="resTimeCount ml-2">{{ step.responseSize || 0 }} bytes</span></span
>
</template>
</a-popover></span
>
</div>
</div>
</div>

View File

@ -51,13 +51,13 @@
<div class="ml-[48px] mt-[8px] text-[var(--color-text-4)]">
{{ dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss') }}
<a-tooltip :content="item.reviewName" :mouse-enter-delay="300">
<span v-if="item.deleted" class="one-text-line ml-[16px] max-w-[300px] break-words break-all">
<span v-if="item.deleted" class="one-line-text ml-[16px] max-w-[300px] break-words break-all">
{{ characterLimit(item.reviewName) }}
</span>
<span
v-else
class="one-text-line ml-[16px] max-w-[300px] cursor-pointer break-words break-all text-[rgb(var(--primary-5))]"
class="one-line-text ml-[16px] max-w-[300px] cursor-pointer break-words break-all text-[rgb(var(--primary-5))]"
@click="review(item)"
>
{{ characterLimit(item.reviewName) }}

View File

@ -3,7 +3,7 @@
<template #demandName="{ record }">
<span :class="[props.highlightName ? 'text-[rgb(var(--primary-5))]' : '']" @click="emit('open', record)">
{{ characterLimit(record.demandName) }} </span
><span class="one-text-line text-[rgb(var(--primary-5))]">({{ (record.children || []).length || 0 }})</span>
><span class="one-line-text text-[rgb(var(--primary-5))]">({{ (record.children || []).length || 0 }})</span>
</template>
<template #operation="{ record }">
<MsButton

View File

@ -30,7 +30,6 @@
import useProjectEnvStore from '@/store/modules/setting/useProjectEnvStore';
import { EnvConfigItem } from '@/models/projectManagement/environmental';
import { TableKeyEnum } from '@/enums/tableEnum';
const projectEnvStore = useProjectEnvStore();
const appStore = useAppStore();

View File

@ -3,47 +3,30 @@
ref="popoverRef"
:popup-visible="currentVisible"
position="bottom"
trigger="click"
class="ms-pop-confirm--hidden-icon"
:content-class="props.id ? 'move-left' : ''"
:ok-loading="loading"
:on-before-ok="handleBeforeOk"
:cancel-button-props="{ disabled: loading }"
@popup-visible-change="reset"
>
<template #content>
<div class="mb-[1px] text-[14px] font-medium text-[var(--color-text-1)]">{{
<div class="mb-[8px] text-[14px] font-medium text-[var(--color-text-1)]">{{
props.id ? t('system.userGroup.rename') : t('system.userGroup.createUserGroup')
}}</div>
<div v-outer="handleOutsideClick">
<div class="form">
<a-form ref="formRef" :model="form" layout="vertical">
<a-form-item field="name" :rules="[{ validator: validateName }]">
<a-input
v-model="form.name"
class="w-[245px]"
:placeholder="t('system.userGroup.pleaseInputUserGroupName')"
allow-clear
:max-length="255"
@press-enter="handleBeforeOk"
@keyup.esc="handleCancel"
/>
</a-form-item>
</a-form>
</div>
<!-- <div class="mb-1 mt-4 flex flex-row flex-nowrap justify-end gap-2">
<a-button type="secondary" size="mini" :disabled="loading" @click="handleCancel">
{{ t('common.cancel') }}
</a-button>
<a-button
type="primary"
size="mini"
:loading="loading"
:disabled="form.name.length === 0"
@click="handleBeforeOk"
>
{{ t('common.confirm') }}
</a-button>
</div> -->
<a-form ref="formRef" :model="form" layout="vertical">
<a-form-item class="hidden-item" field="name" :rules="[{ validator: validateName }]">
<a-input
v-model="form.name"
class="w-[245px]"
:placeholder="t('system.userGroup.pleaseInputUserGroupName')"
allow-clear
:max-length="255"
@press-enter="handleBeforeOk(undefined)"
@keyup.esc="handleCancel"
/>
</a-form-item>
</a-form>
</div>
</template>
<slot></slot>
@ -118,43 +101,43 @@
emit('cancel', false);
};
const handleBeforeOk = () => {
const handleBeforeOk = (done?: (closed: boolean) => void) => {
formRef.value?.validate(async (errors: undefined | Record<string, ValidatedError>) => {
if (errors) {
return false;
}
// let res: EnvListItem;
try {
loading.value = true;
if (!errors) {
try {
loading.value = true;
if (props.type === EnvAuthScopeEnum.PROJECT) {
await updateOrAddEnv({ fileList: [], request: { ...store.currentEnvDetailInfo, name: form.name } });
} else {
const id = store.currentGroupId === NEW_ENV_GROUP ? undefined : store.currentGroupId;
if (id) {
const detail: Record<string, any> = await getGroupDetailEnv(id);
const envGroupProject = detail?.environmentGroupInfo.filter(
(item: any) => item.projectId && item.environmentId
);
const params = {
id,
name: form.name,
description: detail.description,
projectId: appStore.currentProjectId,
envGroupProject,
};
if (props.type === EnvAuthScopeEnum.PROJECT) {
await updateOrAddEnv({ fileList: [], request: { ...store.currentEnvDetailInfo, name: form.name } });
} else {
const id = store.currentGroupId === NEW_ENV_GROUP ? undefined : store.currentGroupId;
if (id) {
const detail: Record<string, any> = await getGroupDetailEnv(id);
const envGroupProject = detail?.environmentGroupInfo.filter(
(item: any) => item.projectId && item.environmentId
);
const params = {
id,
name: form.name,
description: detail.description,
projectId: appStore.currentProjectId,
envGroupProject,
};
await groupUpdateEnv(params);
await groupUpdateEnv(params);
}
}
Message.success(t('project.fileManagement.renameSuccess'));
emit('success');
handleCancel();
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
} finally {
loading.value = false;
}
Message.success(t('project.fileManagement.renameSuccess'));
emit('success');
handleCancel();
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
} finally {
loading.value = false;
} else if (done) {
done(false);
}
});
};

View File

@ -7,6 +7,7 @@
v-model="keyword"
class="w-[240px]"
allow-clear
:placeholder="t('project.menu.nameSearch')"
@press-enter="fetchData"
@search="fetchData"
></a-input-search>

View File

@ -74,6 +74,17 @@
/>
</div>
</template>
<template #empty>
<div class="flex w-full items-center justify-center text-[var(--color-text-4)]">
<span v-if="hasAnyPermission(['PROJECT_ENVIRONMENT:READ+UPDATE'])">{{
t('caseManagement.caseReview.tableNoData')
}}</span>
<span v-else>{{ t('caseManagement.featureCase.tableNoData') }}</span>
<MsButton v-permission="['PROJECT_ENVIRONMENT:READ+UPDATE']" class="ml-[8px]">
{{ t('project.environmental.addHttp') }}
</MsButton>
</div>
</template>
</MsBaseTable>
<AddHttpDrawer
v-model:visible="addVisible"

View File

@ -94,8 +94,10 @@
<a-form-item
v-if="form.type === 'MODULE'"
class="mb-[16px]"
field="description"
field="moduleId"
:label="t('project.environmental.http.selectApiModule')"
:rules="[{ required: true, message: t('project.environmental.http.selectModule') }]"
asterisk-position="end"
>
<!-- TODO 先做普通树 放在下一个版本 -->
<!-- <ApiTree
@ -161,7 +163,13 @@
</a-input-group>
</a-form-item>
</a-form>
<RequestHeader v-model:params="form.headers" :no-param-type="true" />
<httpHeader
v-model:params="form.headers"
:layout="activeLayout"
:disabled-param-value="false"
:disabled-except-param="false"
:second-box-height="secondBoxHeight"
/>
</MsDrawer>
</template>
@ -170,13 +178,10 @@
import { Message } from '@arco-design/web-vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
import RequestHeader from '../../requestHeader/index.vue';
import { getEnvModules } from '@/api/modules/api-test/management';
// import ApiTree from './apiTree.vue';
import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store';
import useProjectEnvStore from '@/store/modules/setting/useProjectEnvStore';
@ -186,6 +191,8 @@
import type { ModuleTreeNode } from '@/models/common';
import { HttpForm } from '@/models/projectManagement/environmental';
const httpHeader = defineAsyncComponent(() => import('@/views/api-test/components/requestComposition/header.vue'));
const props = defineProps<{
currentId: string;
isCopy: boolean;
@ -236,6 +243,8 @@
const form = ref<HttpForm>({ ...initForm });
const hostType = ref<string>('http://');
const secondBoxHeight = ref(0);
const activeLayout = ref<'horizontal' | 'vertical'>('vertical');
const httpRef = ref();
@ -307,22 +316,11 @@
store.currentEnvDetailInfo.config.httpConfig.push(httpItem);
}
emit('close');
resetForm();
};
const envTree = ref<ModuleTreeNode[]>([]);
const moreActions: ActionsItem[] = [
{
label: 'caseManagement.featureCase.copyStep',
eventTag: 'copyStep',
},
];
const selectedKeys = ref<string[]>([]);
const focusNodeKey = ref<string>('');
function handleMoreActionSelect(item: ActionsItem, node: MsTreeNodeData) {}
const title = ref<string>('');
watchEffect(() => {
title.value = props.currentId ? t('project.environmental.http.edit') : t('project.environmental.http.add');

View File

@ -145,7 +145,7 @@
type: Boolean,
});
const form = ref<DataSourceItem>({
const initForm = {
id: '',
dataSource: '',
driverId: '',
@ -154,6 +154,10 @@
password: '',
poolMax: 1,
timeout: 1000,
};
const form = ref<DataSourceItem>({
...initForm,
});
const getDriverOption = async () => {
@ -196,10 +200,6 @@
);
};
const isXpack = computed(() => {
return licenseStore.hasLicense();
});
const formReset = () => {
form.value = {
id: '',
@ -250,6 +250,7 @@
};
store.currentEnvDetailInfo.config.dataSources.push(dataSourceItem);
}
formReset();
currentVisible.value = false;
} catch (error) {
// eslint-disable-next-line no-console

View File

@ -5,7 +5,7 @@
</a-tabs>
<a-divider margin="0"></a-divider>
<div v-if="activeTab === 'scenarioProcessorConfig'" class="h-[calc(100vh - 100px)] mt-4">
<a-alert class="mb-4"> {{ t('project.environmental.sceneAlertDesc') }} </a-alert>
<a-alert class="mb-4" closable> {{ t('project.environmental.sceneAlertDesc') }} </a-alert>
<a-scrollbar
:style="{
overflow: 'auto',
@ -27,7 +27,7 @@
</a-scrollbar>
</div>
<div v-if="activeTab === 'requestProcessorConfig'" class="mt-4 h-full">
<a-alert class="mb-4"> {{ t('project.environmental.requestAlertDesc') }} </a-alert>
<a-alert class="mb-4" closable> {{ t('project.environmental.requestAlertDesc') }} </a-alert>
<PreTab
v-if="props.activeType === 'pre'"
:show-associated-scene="showAssociatedScene"

View File

@ -1,29 +1,20 @@
<template>
<div v-permission="['PROJECT_ENVIRONMENT:READ+UPDATE']" class="mb-[8px] flex items-center justify-between">
<div class="font-medium">{{ t('apiTestDebug.header') }}</div>
<batchAddKeyVal :no-param-type="props.noParamType" :params="innerParams" @apply="handleBatchParamApply" />
</div>
<paramsTable
<httpHeader
v-model:params="innerParams"
:selectable="true"
:show-setting="false"
:columns="columns"
:table-key="TableKeyEnum.PROJECT_MANAGEMENT_ENV_ALL_PARAM_HEADER"
:default-param-item="defaultParamItem"
@change="handleParamTableChange"
:layout="activeLayout"
:disabled-param-value="props.disabledParamValue"
:disabled-except-param="props.disabledExceptParam"
:second-box-height="secondBoxHeight"
@change="emit('change')"
/>
</template>
<script setup lang="ts">
import { useVModel } from '@vueuse/core';
import batchAddKeyVal from '@/views/api-test/components/batchAddKeyVal.vue';
import paramsTable, { type ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
import { responseHeaderOption } from '@/config/apiTest';
import { useI18n } from '@/hooks/useI18n';
import { TableKeyEnum } from '@/enums/tableEnum';
const httpHeader = defineAsyncComponent(() => import('@/views/api-test/components/requestComposition/header.vue'));
defineOptions({
name: 'EnvManangeGloblaRequestHeader',
@ -32,67 +23,17 @@
const props = defineProps<{
params: any[];
noParamType?: boolean;
disabledParamValue?: boolean;
disabledExceptParam?: boolean; //
}>();
const emit = defineEmits<{
(e: 'update:params', value: any[]): void;
(e: 'change'): void; //
}>();
const { t } = useI18n();
const innerParams = useVModel(props, 'params', emit);
const defaultParamItem = {
key: '',
value: '',
description: '',
};
const columns: ParamTableColumn[] = [
{
title: 'apiTestDebug.paramName',
dataIndex: 'key',
slotName: 'key',
permission: ['PROJECT_ENVIRONMENT:READ+UPDATE'],
inputType: 'autoComplete',
autoCompleteParams: responseHeaderOption,
},
{
title: 'apiTestDebug.paramValue',
dataIndex: 'value',
slotName: 'value',
permission: ['PROJECT_ENVIRONMENT:READ+UPDATE'],
},
{
title: 'apiTestDebug.desc',
dataIndex: 'description',
slotName: 'description',
permission: ['PROJECT_ENVIRONMENT:READ+UPDATE'],
},
{
title: '',
slotName: 'operation',
width: 50,
},
];
/**
* 批量参数代码转换为参数表格数据
*/
function handleBatchParamApply(resultArr: any[]) {
if (resultArr.length < innerParams.value.length) {
innerParams.value.splice(0, innerParams.value.length - 1, ...resultArr);
} else {
innerParams.value = [...resultArr, innerParams.value[innerParams.value.length - 1]];
}
emit('change');
}
function handleParamTableChange(resultArr: any[], isInit?: boolean) {
innerParams.value = [...resultArr];
if (!isInit) {
emit('change');
}
}
const secondBoxHeight = ref(0);
const activeLayout = ref<'horizontal' | 'vertical'>('vertical');
</script>
<style lang="less" scoped></style>

View File

@ -126,4 +126,5 @@ export default {
'Deleting it will cause scenes that reference this environment group to fail to execute properly!',
'project.environmental.database.nameIsExist': 'Database name already exists',
'project.environmental.http.noneDataExist': 'There is already a domain name with an enabled range of none!',
'project.environmental.http.selectModule': 'Please select module',
};

View File

@ -130,4 +130,5 @@ export default {
'project.environmental.env.deleteGroupTip': '删除后会导致引用此环境组的场景无法正常执行',
'project.environmental.database.nameIsExist': '数据源名称已存在',
'project.environmental.http.noneDataExist': '已存在启用范围为无的域名!',
'project.environmental.http.selectModule': '请选择模块',
};

View File

@ -200,6 +200,7 @@
resetForm();
};
const handlePlatformChange = async (value: SelectValue) => {
platformRules.value = [];
try {
if (value) {
const res = await getPlatformInfo(value as string, MenuEnum.bugManagement);

View File

@ -171,15 +171,17 @@
title: 'project.taskCenter.resourceID',
dataIndex: 'resourceId',
slotName: 'resourceId',
width: 300,
width: 200,
showInTable: true,
showTooltip: true,
},
{
title: 'project.taskCenter.resourceName',
slotName: 'resourceName',
dataIndex: 'resourceName',
width: 200,
width: 300,
showDrag: true,
showTooltip: true,
},
{
title: 'project.taskCenter.executionResult',
@ -203,16 +205,16 @@
slotName: 'poolName',
dataIndex: 'poolName',
showInTable: true,
width: 200,
showDrag: true,
showTooltip: true,
},
{
title: 'project.taskCenter.operator',
slotName: 'operationName',
dataIndex: 'operationName',
showInTable: true,
width: 300,
showDrag: true,
showTooltip: true,
},
{
title: 'project.taskCenter.operating',
@ -367,7 +369,7 @@
showDetailDrawer.value = true;
}
activeDetailId.value = id;
activeReportIndex.value = rowIndex;
activeReportIndex.value = rowIndex - 1;
}
watch(

View File

@ -78,6 +78,7 @@
slotName: 'resourceNum',
width: 300,
showInTable: true,
showTooltip: true,
},
{
title: 'project.taskCenter.resourceName',
@ -85,6 +86,7 @@
dataIndex: 'resourceName',
width: 200,
showDrag: true,
showTooltip: true,
},
{
title: 'project.taskCenter.resourceClassification',
@ -93,6 +95,7 @@
showInTable: true,
width: 150,
showDrag: true,
showTooltip: true,
},
{
title: 'project.taskCenter.operationRule',
@ -101,6 +104,7 @@
showInTable: true,
width: 150,
showDrag: true,
showTooltip: true,
},
{
title: 'project.taskCenter.operator',
@ -109,6 +113,7 @@
showInTable: true,
width: 200,
showDrag: true,
showTooltip: true,
},
{
title: 'project.taskCenter.operating',