feat(接口场景): 场景步骤 90%&导入系统请求API&CASE完成

This commit is contained in:
baiqi 2024-03-22 18:17:00 +08:00 committed by Craftsman
parent 09454001bd
commit cef0f5895f
23 changed files with 378 additions and 262 deletions

View File

@ -179,7 +179,7 @@ export function updateDefinition(data: ApiDefinitionUpdateParams) {
}
// 获取接口定义详情
export function getDefinitionDetail(id: string) {
export function getDefinitionDetail(id: string | number) {
return MSR.get<ApiDefinitionDetail>({ url: GetDefinitionDetailUrl, params: id });
}

View File

@ -416,16 +416,17 @@
background-color: rgb(var(--primary-7));
}
}
.arco-checkbox-disabled,
.arco-checkbox-disabled:hover {
.arco-checkbox-disabled.arco-checkbox-checked,
.arco-checkbox-disabled.arco-checkbox-checked:hover {
.arco-checkbox-icon {
@apply bg-white;
@apply bg-white text-white;
border: 1px solid var(--color-text-input-border);
border: none;
background: rgb(var(--primary-2)) !important;
}
.arco-checkbox-icon-check {
background-color: rgb(var(--primary-2)) !important;
}
}
.arco-checkbox-disabled.arco-checkbox-checked .arco-checkbox-icon {
background: rgb(var(--primary-2)) !important ;
}
/** radio **/

View File

@ -20,7 +20,7 @@
<slot name="title">
<div class="flex flex-1 items-center justify-between">
<div class="flex items-center">
<a-tooltip :content="props.title">
<a-tooltip :disabled="!props.title" :content="props.title">
<span> {{ characterLimit(props.title) }}</span>
</a-tooltip>
@ -161,7 +161,7 @@
showFullScreen: false,
okPermission: () => [], //
});
const emit = defineEmits(['update:visible', 'confirm', 'cancel', 'continue']);
const emit = defineEmits(['update:visible', 'confirm', 'cancel', 'continue', 'close']);
const { t } = useI18n();
@ -191,6 +191,7 @@
const handleClose = () => {
visible.value = false;
emit('update:visible', false);
emit('close');
};
const resizing = ref(false); //

View File

@ -29,8 +29,9 @@
<template #title>
<SelectALL
v-if="attrs.selectorType === 'checkbox'"
:total="selectTotal"
:current="selectCurrent"
:total="attrs.showPagination ? (attrs.msPagination as MsPaginationI).total : (attrs.data as MsTableDataItem<TableData>[]).length"
:selected-keys="props.selectedKeys"
:current-data="attrs.data as Record<string,any>[]"
:show-select-all="!!attrs.showPagination && props.showSelectorAll"
:disabled="(attrs.data as []).length === 0"
@change="handleSelectAllChange"
@ -208,17 +209,17 @@
class="mt-[16px] flex h-[32px] flex-row flex-nowrap items-center"
:class="{ 'justify-between': showBatchAction }"
>
<span v-if="!props.actionConfig && selectCurrent > 0" class="title text-[var(--color-text-2)]"
>{{ t('msTable.batch.selected', { count: selectCurrent }) }}
<a-button class="clear-btn ml-[12px] px-2" type="text" @click="emit('clearSelector')">{{
t('msTable.batch.clear')
}}</a-button></span
>
<span v-if="!props.actionConfig && selectedCount > 0" class="title text-[var(--color-text-2)]">
{{ t('msTable.batch.selected', { count: selectedCount }) }}
<a-button class="clear-btn ml-[12px] px-2" type="text" @click="emit('clearSelector')">
{{ t('msTable.batch.clear') }}
</a-button>
</span>
<div class="flex flex-grow">
<batch-action
v-if="showBatchAction"
class="flex-1"
:select-row-count="selectCurrent"
:select-row-count="selectedCount"
:action-config="props.actionConfig"
@batch-action="handleBatchAction"
@clear="emit('clearSelector')"
@ -227,7 +228,6 @@
<div class="min-w-[500px]">
<ms-pagination
v-if="!!attrs.showPagination"
v-show="props.selectorStatus !== SelectAllEnum.CURRENT"
size="small"
v-bind="(attrs.msPagination as MsPaginationI)"
hide-on-single-page
@ -329,31 +329,6 @@
(e: 'moduleChange'): void;
}>();
const attrs = useAttrs();
// -
const selectTotal = computed(() => {
const { selectorStatus } = props;
if (!attrs.showPagination) {
// total
return (attrs.data as MsTableDataItem<TableData>[]).length;
}
if (selectorStatus === SelectAllEnum.CURRENT) {
const { pageSize, total } = attrs.msPagination as MsPaginationI;
if (pageSize > total) {
return total;
}
return pageSize;
}
return (attrs.msPagination as MsPaginationI)?.total || appStore.pageSize;
});
// -
const selectCurrent = computed(() => {
const { selectorStatus, excludeKeys, selectedKeys } = props;
if (selectorStatus === SelectAllEnum.ALL) {
return selectTotal.value - excludeKeys.size;
}
return selectedKeys.size;
});
// Active
const editActiveKey = ref<string>('');
@ -483,8 +458,15 @@
emit('pageSizeChange', v);
};
const selectedCount = computed(() => {
if (props.selectorStatus === SelectAllEnum.ALL && attrs.msPagination) {
return (attrs.msPagination as MsPaginationI).total - props.excludeKeys.size;
}
return props.selectedKeys.size;
});
const showBatchAction = computed(() => {
return selectCurrent.value > 0 && attrs.selectable;
return selectedCount.value > 0 && attrs.selectable;
});
const handleBatchAction = (value: BatchActionParams) => {
@ -493,7 +475,7 @@
selectedIds: Array.from(selectedKeys),
excludeIds: Array.from(excludeKeys),
selectAll: selectorStatus === SelectAllEnum.ALL,
currentSelectCount: selectCurrent.value,
currentSelectCount: selectedCount.value,
params: {
...(attrs.msPagination as MsPaginationI),
},

View File

@ -1,7 +1,7 @@
<template>
<div class="ms-table-select-all">
<a-checkbox
v-model="checked"
v-model:model-value="checked"
:disabled="props.disabled"
class="text-base"
:indeterminate="indeterminate"
@ -20,14 +20,14 @@
</template>
<script lang="ts" setup>
import { ref, watchEffect } from 'vue';
import MsIcon from '../ms-icon-font/index.vue';
import { useI18n } from '@/hooks/useI18n';
import { SelectAllEnum } from '@/enums/tableEnum';
import { MsTableDataItem } from './type';
const { t } = useI18n();
const emit = defineEmits<{
@ -35,7 +35,13 @@
}>();
const props = withDefaults(
defineProps<{ current: number; total: number; showSelectAll: boolean; disabled: boolean }>(),
defineProps<{
selectedKeys: Set<string>;
total: number;
currentData: MsTableDataItem<Record<string, any>>[];
showSelectAll: boolean;
disabled: boolean;
}>(),
{
current: 0,
total: 0,
@ -44,20 +50,17 @@
}
);
const checked = ref(false);
const indeterminate = ref(false);
watchEffect(() => {
if (props.current === 0) {
checked.value = false;
indeterminate.value = false;
} else if (props.current < props.total) {
checked.value = false;
indeterminate.value = true;
} else if (props.current === props.total) {
checked.value = true;
indeterminate.value = false;
}
const checked = computed({
get: () => {
return props.selectedKeys.size === props.total;
},
set: (value) => {
return value;
},
});
const indeterminate = computed(() => {
// 0
return props.selectedKeys.size > 0 && props.selectedKeys.size < props.total;
});
const handleSelect = (v: string | number | Record<string, any> | undefined) => {
@ -65,9 +68,11 @@
};
const handleCheckChange = () => {
if (checked.value) {
if (props.currentData.some((item) => !props.selectedKeys.has(item.id))) {
//
handleSelect(SelectAllEnum.CURRENT);
} else {
//
handleSelect(SelectAllEnum.NONE);
}
};

View File

@ -59,6 +59,7 @@ export type MsTableErrorStatus = boolean | 'error' | 'empty';
export type MsTableDataItem<T> = T & {
updateTime?: string | number | null;
createTime?: string | number | null;
children?: MsTableDataItem<T>[];
} & TableData;
// 表格属性
export interface MsTableProps<T> {

View File

@ -296,8 +296,17 @@ export default function useTableProps<T>(
// 重置选择器
const resetSelector = (isNone = true) => {
propsRes.value.selectedKeys.clear();
propsRes.value.excludeKeys.clear();
if (propsRes.value.selectorStatus === SelectAllEnum.ALL) {
// 当前是跨页全部选中状态,则取消当前页的选中项
propsRes.value.data.forEach((item) => {
propsRes.value.selectedKeys.delete(item.id);
propsRes.value.excludeKeys.add(item.id);
});
} else {
// 当前是当前页选中状态,则清空选中项
propsRes.value.selectedKeys.clear();
propsRes.value.excludeKeys.clear();
}
if (isNone) {
propsRes.value.selectorStatus = SelectAllEnum.NONE;
}
@ -311,26 +320,18 @@ export default function useTableProps<T>(
// 如果是全选状态,返回总数减去排除的数量
return msPagination.total - excludeKeys.size;
}
// if (selectorStatus === SelectAllEnum.NONE) {
// 如果是全不选状态,返回选中的数量
return selectedKeys.size;
// }
// if (selectorStatus === SelectAllEnum.CURRENT) {
// // 如果是当前页状态,返回当前页减去排除的数量
// return msPagination.pageSize - excludeKeys.size;
// }
}
};
const collectIds = (data, rowKey: string, selectedKeys: Set<string>) => {
data.forEach((item: any) => {
if (item[rowKey] && !selectedKeys.has(item[rowKey])) {
selectedKeys.add(item[rowKey]);
const collectIds = (data: MsTableDataItem<T>[], rowKey: string) => {
data.forEach((item: MsTableDataItem<T>) => {
if (item[rowKey] && !propsRes.value.selectedKeys.has(item[rowKey])) {
propsRes.value.selectedKeys.add(item[rowKey]);
}
if (item.children) {
collectIds(item.children, rowKey, selectedKeys);
collectIds(item.children, rowKey);
}
});
return selectedKeys;
};
// 获取表格请求参数
@ -398,19 +399,19 @@ export default function useTableProps<T>(
},
// 重置筛选
clearSelector: () => {
propsRes.value.selectorStatus = SelectAllEnum.NONE; // 重置选择器状态
resetSelector();
},
// 表格SelectAll change
selectAllChange: (v: SelectAllEnum) => {
propsRes.value.selectorStatus = v;
const { data, rowKey, selectedKeys } = propsRes.value;
const { data, rowKey } = propsRes.value;
if (v === SelectAllEnum.NONE) {
// 清空选中项
resetSelector();
} else {
resetSelector(false);
propsRes.value.selectedKeys = collectIds(data, rowKey, selectedKeys);
collectIds(data as MsTableDataItem<T>[], rowKey);
}
},

View File

@ -417,9 +417,9 @@ export function insertNodes<T>(
}
if (typeof customFunc === 'function') {
if (Array.isArray(newNodes)) {
newNodes.forEach((newNode) => customFunc(newNode, parent || node.parent));
newNodes.forEach((newNode) => customFunc(newNode, position === 'inside' ? node : parent));
} else {
customFunc(newNodes, parent || node.parent);
customFunc(newNodes, position === 'inside' ? node : parent);
}
}
// 插入后返回 true

View File

@ -1,5 +1,5 @@
interface Tree {
id: string | number;
id?: string | number;
groupId?: number;
children?: Tree[];
[key: string]: any;

View File

@ -2,6 +2,7 @@
<a-select
v-model:model-value="method"
:placeholder="t('common.pleaseSelect')"
:disabled="props.disabled"
@change="(val) => emit('change', val as string)"
>
<template #label="{ data }">
@ -24,6 +25,7 @@
const props = defineProps<{
modelValue: string;
disabled?: boolean;
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: string): void;

View File

@ -834,6 +834,7 @@
const formData = tempForm || requestVModel.value;
if (fApi.value) {
fApi.value.nextTick(() => {
// 使nextTick使v-if
const form = {};
controlPluginFormFields().forEach((key) => {
form[key] = formData[key];
@ -934,6 +935,17 @@
const splitContainerRef = ref<HTMLElement>();
const secondBoxHeight = ref(0);
watch(
() => showResponse.value,
(val) => {
if (val) {
splitBoxSize.value = 0.6;
} else {
splitBoxSize.value = 1;
}
}
);
watch(
() => splitBoxSize.value,
debounce((val) => {

View File

@ -1,56 +1,42 @@
<template>
<MsDrawer
v-model:visible="visible"
:title="t('apiScenario.customApi')"
:width="960"
no-content-padding
disabled-width-drag
:show-continue="true"
:footer="!!requestVModel.isNew"
@confirm="handleSave"
@continue="handleContinue"
@close="handleClose"
>
<a-empty
v-if="pluginError && !isHttpProtocol"
:description="t('apiTestDebug.noPlugin')"
class="h-[200px] items-center justify-center"
>
<template #image>
<MsIcon type="icon-icon_plugin_outlined" size="48" />
</template>
</a-empty>
<template #title>
<div style="width: 100%">
<div style="float: left"> {{ t('apiScenario.customApi') }}</div>
<div style="float: right">
<span
v-show="requestVModel.useEnv === 'false'"
style="
float: left;
margin-top: 8px;
margin-right: 16px;
font-size: 14px;
font-weight: 400;
line-height: 22px;
"
>{{ t('apiScenario.env', { name: props.envDetailItem?.name }) }}
</span>
<MsSelect
v-model:model-value="requestVModel.useEnv"
:style="{ width: '150px', float: 'right' }"
:allow-search="false"
:options="[
{ label: t('common.quote'), value: 'true' },
{ label: t('common.notQuote'), value: 'false' },
]"
:multiple="false"
value-key="value"
label-key="label"
:prefix="t('project.environmental.env')"
@change="handleUseEnvChange"
>
</MsSelect>
<div class="flex items-center gap-[8px]">
<stepType
v-if="props.requestType"
v-show="props.requestType !== ScenarioStepType.CUSTOM_API"
:type="props.requestType"
/>
{{ title }}
</div>
<div v-if="requestVModel.isNew" class="ml-auto flex items-center gap-[16px]">
<div v-show="requestVModel.useEnv === 'false'" class="text-[14px] font-normal text-[var(--color-text-4)]">
{{ t('apiScenario.env', { name: props.envDetailItem?.name }) }}
</div>
<MsSelect
v-model:model-value="requestVModel.useEnv"
:allow-search="false"
:options="[
{ label: t('common.quote'), value: 'true' },
{ label: t('common.notQuote'), value: 'false' },
]"
:multiple="false"
value-key="value"
label-key="label"
:prefix="t('project.environmental.env')"
class="w-[150px]"
@change="handleUseEnvChange"
>
</MsSelect>
</div>
</template>
<div v-show="!pluginError || isHttpProtocol" class="flex h-full flex-col">
@ -62,6 +48,7 @@
v-model:model-value="requestVModel.protocol"
:options="protocolOptions"
:loading="protocolLoading"
:disabled="props.requestType === ScenarioStepType.QUOTE_API"
class="w-[90px]"
@change="(val) => handleActiveDebugProtocolChange(val as string)"
/>
@ -73,10 +60,7 @@
is-tag
class="flex items-center"
/>
<a-tooltip v-if="!isHttpProtocol" content="requestVModel.label" :mouse-enter-delay="500">
<div class="one-line-text max-w-[350px]"> requestVModel.label</div>
</a-tooltip>
<a-tooltip :content="requestVModel.label" :mouse-enter-delay="500">
<a-tooltip v-if="!isHttpProtocol" :content="requestVModel.label" :mouse-enter-delay="500">
<div class="one-line-text max-w-[350px]"> {{ requestVModel.label }}</div>
</a-tooltip>
</div>
@ -84,6 +68,7 @@
<apiMethodSelect
v-model:model-value="requestVModel.method"
class="w-[140px]"
:disabled="props.requestType === ScenarioStepType.QUOTE_API"
@change="handleActiveDebugChange"
/>
<a-input
@ -93,6 +78,7 @@
allow-clear
class="hover:z-10"
:style="isUrlError ? 'border: 1px solid rgb(var(--danger-6);z-index: 10' : ''"
:disabled="props.requestType === ScenarioStepType.QUOTE_API"
@input="() => (isUrlError = false)"
@change="handleUrlChange"
/>
@ -100,25 +86,41 @@
</div>
<div>
<a-dropdown-button
v-if="!requestVModel.executeLoading"
v-if="hasLocalExec"
:disabled="requestVModel.executeLoading || (isHttpProtocol && !requestVModel.url)"
class="exec-btn"
@click="() => execute(isPriorityLocalExec ? 'localExec' : 'serverExec')"
@select="execute"
>
{{ isPriorityLocalExec ? t('apiTestDebug.localExec') : t('apiTestDebug.serverExec') }}
<template v-if="hasLocalExec" #icon>
<template #icon>
<icon-down />
</template>
<template v-if="hasLocalExec" #content>
<template #content>
<a-doption :value="isPriorityLocalExec ? 'serverExec' : 'localExec'">
{{ isPriorityLocalExec ? t('apiTestDebug.serverExec') : t('apiTestDebug.localExec') }}
</a-doption>
</template>
</a-dropdown-button>
<a-button v-else-if="!requestVModel.executeLoading" type="primary" @click="() => execute('serverExec')">
{{ t('apiTestDebug.serverExec') }}
</a-button>
<a-button v-else type="primary" class="mr-[12px]" @click="stopDebug">{{ t('common.stop') }}</a-button>
</div>
</div>
<a-input
v-if="
props.requestType &&
[ScenarioStepType.QUOTE_API, ScenarioStepType.COPY_API].includes(props.requestType) &&
isHttpProtocol
"
v-model:model-value="requestVModel.name"
:max-length="255"
:placeholder="t('apiTestManagement.apiNamePlaceholder')"
disabled
allow-clear
class="mt-[8px]"
/>
</div>
<div class="px-[16px]">
<MsTab
@ -234,6 +236,7 @@
</template>
<template #second>
<response
v-if="visible"
v-show="showResponse"
v-model:active-layout="activeLayout"
v-model:active-tab="requestVModel.responseActiveTab"
@ -255,7 +258,16 @@
</MsSplitBox>
</div>
</div>
<addDependencyDrawer v-model:visible="showAddDependencyDrawer" :mode="addDependencyMode" />
<a-empty
v-if="pluginError && !isHttpProtocol"
:description="t('apiTestDebug.noPlugin')"
class="h-[200px] items-center justify-center"
>
<template #image>
<MsIcon type="icon-icon_plugin_outlined" size="48" />
</template>
</a-empty>
<!-- <addDependencyDrawer v-model:visible="showAddDependencyDrawer" :mode="addDependencyMode" /> -->
</MsDrawer>
</template>
@ -271,6 +283,7 @@
import MsTab from '@/components/pure/ms-tab/index.vue';
import assertion from '@/components/business/ms-assertion/index.vue';
import MsSelect from '@/components/business/ms-select';
import stepType from './stepType.vue';
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
import apiMethodSelect from '@/views/api-test/components/apiMethodSelect.vue';
import auth from '@/views/api-test/components/requestComposition/auth.vue';
@ -280,8 +293,8 @@
import setting from '@/views/api-test/components/requestComposition/setting.vue';
import { getPluginScript, getProtocolList } from '@/api/modules/api-test/common';
import { getDefinitionDetail } from '@/api/modules/api-test/management';
import { getSocket } from '@/api/modules/project-management/commonScript';
import { getLocalConfig } from '@/api/modules/user/index';
import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store';
import { getGenerateId, parseQueryParams } from '@/utils';
@ -294,7 +307,6 @@
PluginConfig,
RequestTaskResult,
} from '@/models/apiTest/common';
import { CustomApiStep } from '@/models/apiTest/scenario';
import { ModuleTreeNode, TransferFileParams } from '@/models/common';
import {
RequestAuthType,
@ -303,6 +315,7 @@
RequestConditionProcessor,
RequestMethods,
ResponseComposition,
ScenarioStepType,
} from '@/enums/apiEnum';
import {
@ -316,23 +329,20 @@
import { filterKeyValParams, parseRequestBodyFiles } from '@/views/api-test/components/utils';
import type { Api } from '@form-create/arco-design';
const visible = defineModel<boolean>('visible', { required: true });
// Http
const httpHeader = defineAsyncComponent(() => import('@/views/api-test/components/requestComposition/header.vue'));
const httpBody = defineAsyncComponent(() => import('@/views/api-test/components/requestComposition/body.vue'));
const httpQuery = defineAsyncComponent(() => import('@/views/api-test/components/requestComposition/query.vue'));
const httpRest = defineAsyncComponent(() => import('@/views/api-test/components/requestComposition/rest.vue'));
const addDependencyDrawer = defineAsyncComponent(
() => import('@/views/api-test/management/components/addDependencyDrawer.vue')
);
// const addDependencyDrawer = defineAsyncComponent(
// () => import('@/views/api-test/management/components/addDependencyDrawer.vue')
// );
export interface RequestCustomAttr {
type: 'api';
isNew: boolean;
protocol: string;
activeTab: RequestComposition;
mode?: 'debug';
executeLoading: boolean; // loading
isCopy?: boolean; //
isExecute?: boolean; //
@ -341,11 +351,14 @@
export type RequestParam = ExecuteApiRequestFullParams & {
response?: RequestTaskResult;
useEnv: string;
request?: ExecuteApiRequestFullParams; //
} & RequestCustomAttr &
TabItem;
const props = defineProps<{
request?: RequestParam; //
requestType?: ScenarioStepType;
stepName: string;
detailLoading?: boolean; //
envDetailItem?: {
id?: string;
@ -367,11 +380,13 @@
const emit = defineEmits<{
(e: 'addStep', request: RequestParam): void;
(e: 'applyStep', request: RequestParam): void;
}>();
const appStore = useAppStore();
const { t } = useI18n();
const visible = defineModel<boolean>('visible', { required: true });
const loading = defineModel<boolean>('detailLoading', { default: false });
const defaultDebugParams: RequestParam = {
@ -438,7 +453,12 @@
};
const requestVModel = ref<RequestParam>(props.request || defaultDebugParams);
const title = computed(() => {
if (props.requestType && [ScenarioStepType.COPY_API, ScenarioStepType.QUOTE_API].includes(props.requestType)) {
return props.stepName;
}
return t('apiScenario.customApi');
});
const isHttpProtocol = computed(() => requestVModel.value.protocol === 'HTTP');
const temporaryResponseMap = {}; // websockettab
const isInitPluginForm = ref(false);
@ -506,9 +526,7 @@
const contentTabList = computed(() => {
// HTTP tabs
if (isHttpProtocol.value) {
return requestVModel.value.mode === 'debug'
? httpContentTabList
: httpContentTabList.filter((e) => !commonContentTabKey.includes(e.value));
return httpContentTabList;
}
return [...pluginContentTab, ...httpContentTabList.filter((e) => commonContentTabKey.includes(e.value))];
});
@ -568,7 +586,7 @@
const localExecuteUrl = ref('');
const pluginScriptMap = ref<Record<string, PluginConfig>>({}); //
const temporaryPluginFormMap: Record<string, any> = {}; // tab
const temporaryPluginFormMap: Record<string, any> = {}; // API
const pluginLoading = ref(false);
const fApi = ref<Api>();
const currentPluginOptions = computed<Record<string, any>>(
@ -577,10 +595,19 @@
const currentPluginScript = computed<Record<string, any>[]>(
() => pluginScriptMap.value[requestVModel.value.protocol]?.script || []
);
const isCopyApiNeedInit = computed(
() => props.requestType === ScenarioStepType.COPY_API && props.request?.request === null
);
const isEditableApi = computed(
() => props.requestType === ScenarioStepType.COPY_API || props.requestType === ScenarioStepType.CUSTOM_API
);
//
const handlePluginFormChange = debounce(() => {
temporaryPluginFormMap[requestVModel.value.id] = fApi.value?.formData();
if (isEditableApi.value) {
//
temporaryPluginFormMap[requestVModel.value.id] = fApi.value?.formData();
}
handleActiveDebugChange();
}, 300);
@ -604,11 +631,11 @@
*/
function setPluginFormData() {
const tempForm = temporaryPluginFormMap[requestVModel.value.id];
if (tempForm || !requestVModel.value.isNew || requestVModel.value.isCopy) {
if (tempForm || !requestVModel.value.isNew) {
//
const formData = tempForm || requestVModel.value;
const formData = isEditableApi.value ? tempForm || requestVModel.value : requestVModel.value;
if (fApi.value) {
fApi.value.nextTick(() => {
fApi.value.nextRefresh(() => {
const form = {};
controlPluginFormFields().forEach((key) => {
form[key] = formData[key];
@ -621,12 +648,8 @@
});
}
} else {
fApi.value?.nextTick(() => {
controlPluginFormFields();
});
nextTick(() => {
// form-create tab
fApi.value?.resetFields();
controlPluginFormFields();
});
}
}
@ -720,6 +743,17 @@
const splitContainerRef = ref<HTMLElement>();
const secondBoxHeight = ref(0);
watch(
() => showResponse.value,
(val) => {
if (val) {
splitBoxSize.value = 0.6;
} else {
splitBoxSize.value = 1;
}
}
);
watch(
() => splitBoxSize.value,
debounce((val) => {
@ -927,6 +961,7 @@
await props.localExecuteApi(localExecuteUrl.value, res);
}
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
requestVModel.value.executeLoading = false;
@ -964,8 +999,8 @@
}
function handleContinue() {
requestVModel.value.isNew = false; //
emit('addStep', requestVModel.value);
requestVModel.value = { ...defaultDebugParams };
}
function handleSave() {
@ -973,36 +1008,73 @@
visible.value = false;
}
const isUrlError = ref(false);
const showAddDependencyDrawer = ref(false);
const addDependencyMode = ref<'pre' | 'post'>('pre');
// watch(
// () => visible.value,
// async (val) => {
// if (val) {
// await initProtocolList();
// if (props.request) {
// requestVModel.value = { ...defaultDebugParams, ...props.request };
// handleActiveDebugProtocolChange(requestVModel.value.protocol);
// } else {
// requestVModel.value = { ...defaultDebugParams };
// }
// } else {
// requestVModel.value = { ...defaultDebugParams };
// }
// }
// );
onBeforeMount(() => {
initProtocolList();
if (props.request) {
requestVModel.value = { ...defaultDebugParams, ...props.request };
handleActiveDebugProtocolChange(requestVModel.value.protocol);
} else {
requestVModel.value = { ...defaultDebugParams };
function handleClose() {
// applyStep
if (!requestVModel.value.isNew) {
emit('applyStep', requestVModel.value);
}
});
}
const isUrlError = ref(false);
// const showAddDependencyDrawer = ref(false);
// const addDependencyMode = ref<'pre' | 'post'>('pre');
async function initQuoteApiDetail() {
try {
loading.value = true;
const res = await getDefinitionDetail(requestVModel.value.id);
let parseRequestBodyResult;
if (res.protocol === 'HTTP') {
parseRequestBodyResult = parseRequestBodyFiles(res.request.body); // id
}
requestVModel.value = {
responseActiveTab: ResponseComposition.BODY,
executeLoading: false,
activeTab: res.protocol === 'HTTP' ? RequestComposition.HEADER : RequestComposition.PLUGIN,
unSaved: false,
isNew: false,
label: res.name,
...res.request,
...res,
response: cloneDeep(defaultResponse),
responseDefinition: res.response.map((e) => ({ ...e, responseActiveTab: ResponseComposition.BODY })),
url: res.path,
name: res.name, // requestnamenull
id: res.id,
...parseRequestBodyResult,
};
nextTick(() => {
// loading
loading.value = false;
});
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
loading.value = false;
}
}
watch(
() => visible.value,
async (val) => {
if (val) {
if (props.request) {
requestVModel.value = { ...defaultDebugParams, ...props.request };
if (
props.requestType === ScenarioStepType.QUOTE_API ||
isCopyApiNeedInit.value
// (request.requestrequest null)
) {
initQuoteApiDetail();
}
}
await initProtocolList();
if (props.request) {
handleActiveDebugProtocolChange(requestVModel.value.protocol);
}
}
}
);
</script>
<style lang="less" scoped>

View File

@ -233,9 +233,10 @@
*/
function handleSelectAllChange(v: SelectAllEnum) {
if (v === SelectAllEnum.CURRENT) {
tableSelectedData.value = currentTable.value.propsRes.value.data;
tableSelectedData.value.push(...currentTable.value.propsRes.value.data);
} else {
tableSelectedData.value = [];
const dataSet = new Set(currentTable.value.propsRes.value.data.map((e) => e.id));
tableSelectedData.value = tableSelectedData.value.filter((e) => !dataSet.has(e.id));
}
emit('select', tableSelectedData.value);
}

View File

@ -112,7 +112,7 @@
} else {
steps.value.push({
...cloneDeep(defaultStepItemCommon),
id: getGenerateId(),
stepId: getGenerateId(),
order: steps.value.length + 1,
type: ScenarioStepType.LOOP_CONTROL,
name: t('apiScenario.loopControl'),
@ -134,7 +134,7 @@
} else {
steps.value.push({
...cloneDeep(defaultStepItemCommon),
id: getGenerateId(),
stepId: getGenerateId(),
order: steps.value.length + 1,
type: ScenarioStepType.CONDITION_CONTROL,
name: t('apiScenario.conditionControl'),
@ -156,7 +156,7 @@
} else {
steps.value.push({
...cloneDeep(defaultStepItemCommon),
id: getGenerateId(),
stepId: getGenerateId(),
order: steps.value.length + 1,
type: ScenarioStepType.ONLY_ONCE_CONTROL,
name: t('apiScenario.onlyOnceControl'),
@ -178,7 +178,7 @@
} else {
steps.value.push({
...cloneDeep(defaultStepItemCommon),
id: getGenerateId(),
stepId: getGenerateId(),
order: steps.value.length + 1,
type: ScenarioStepType.WAIT_TIME,
name: t('apiScenario.waitTime'),
@ -189,7 +189,7 @@
case ScenarioAddStepActionType.CUSTOM_API:
case ScenarioAddStepActionType.SCRIPT_OPERATION:
if (step.value) {
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.value.id, 'id');
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.value.stepId, 'stepId');
if (realStep) {
emit('otherCreate', val, realStep as ScenarioStepItem);
}

View File

@ -6,7 +6,7 @@
position="br"
@popup-visible-change="handleActionTriggerChange"
>
<MsButton :id="step.id" type="icon" class="ms-tree-node-extra__btn !mr-[4px]" @click="emit('click')">
<MsButton :id="step.stepId" type="icon" class="ms-tree-node-extra__btn !mr-[4px]" @click="emit('click')">
<MsIcon type="icon-icon_add_outlined" size="14" class="text-[var(--color-text-4)]" />
</MsButton>
<template #content>
@ -131,7 +131,7 @@
function handleActionsClose() {
activeCreateAction.value = undefined;
innerStep.value.createActionsVisible = false;
document.getElementById(innerStep.value.id.toString())?.click();
document.getElementById(innerStep.value.stepId.toString())?.click();
}
</script>

View File

@ -15,18 +15,18 @@ export default function useCreateActions() {
/**
*
* @param selectedKeys id
* @param step
* @param selectedKeys stepId
* @param steps
* @param parent
*/
function checkedIfNeed(
selectedKeys: (string | number)[],
step: (ScenarioStepItem | TreeNode<ScenarioStepItem>)[],
steps: (ScenarioStepItem | TreeNode<ScenarioStepItem>)[],
parent?: TreeNode<ScenarioStepItem>
) {
if (parent && selectedKeys.includes(parent.id)) {
if (parent && selectedKeys.includes(parent.stepId)) {
// 添加子节点时,当前节点已选中,则需要把新节点也需要选中(因为父级选中子级也会展示选中状态)
selectedKeys = selectedKeys.concat(step.map((item) => item.id));
selectedKeys.push(...steps.map((item) => item.stepId));
}
}
@ -36,7 +36,7 @@ export default function useCreateActions() {
* @param step
* @param steps
* @param createStepAction
* @param selectedKeys id
* @param selectedKeys stepId
*/
function handleCreateStep(
defaultStepInfo: ScenarioStepItem,
@ -48,7 +48,7 @@ export default function useCreateActions() {
const newStep = {
...cloneDeep(defaultStepItemCommon),
...defaultStepInfo,
id: getGenerateId(),
stepId: getGenerateId(),
};
switch (createStepAction) {
case 'inside':
@ -64,11 +64,11 @@ export default function useCreateActions() {
}
insertNodes<ScenarioStepItem>(
step.parent?.children || steps,
step.id,
step.stepId,
newStep,
createStepAction,
(newNode, parent) => checkedIfNeed(selectedKeys, [newNode], parent),
'id'
'stepId'
);
}
@ -81,7 +81,8 @@ export default function useCreateActions() {
function buildInsertStepInfos(
newSteps: Record<string, any>[],
type: ScenarioStepType,
startOrder: number
startOrder: number,
stepsDetailMap: Record<string, any>
): ScenarioStepItem[] {
let name: string;
switch (type) {
@ -125,10 +126,12 @@ export default function useCreateActions() {
break;
}
return newSteps.map((item, index) => {
const stepId = getGenerateId();
stepsDetailMap[stepId] = item; // 导入系统请求的引用接口和 case 的时候需要先存储一下引用的接口/用例信息
return {
...cloneDeep(defaultStepItemCommon),
...item,
id: getGenerateId(),
stepId,
type,
name,
order: startOrder + index,
@ -143,7 +146,7 @@ export default function useCreateActions() {
* @param steps
* @param createStepAction
* @param type
* @param selectedKeys id
* @param selectedKeys stepId
*/
function handleCreateSteps(
step: ScenarioStepItem,
@ -154,11 +157,11 @@ export default function useCreateActions() {
) {
insertNodes<ScenarioStepItem>(
step.parent?.children || steps,
step.id,
step.stepId,
readyInsertSteps,
createStepAction,
undefined,
'id'
'stepId'
);
checkedIfNeed(selectedKeys, readyInsertSteps, step);
}

View File

@ -86,6 +86,7 @@
v-model:checked-keys="checkedKeys"
v-model:stepKeyword="keyword"
:expand-all="isExpandAll"
:steps-detail-map="stepInfo.stepsDetailMap"
/>
</div>
</div>
@ -127,6 +128,7 @@
executeTime?: string; //
executeSuccessCount?: number; //
executeFailCount?: number; //
stepsDetailMap: Record<string, any>; //
}
const props = defineProps<{
@ -200,7 +202,7 @@
try {
let ids = checkedKeys.value;
if (batchToggleRange.value === 'top') {
ids = stepInfo.value.steps.map((item) => item.id);
ids = stepInfo.value.steps.map((item) => item.stepId);
}
console.log('ids', ids);
await new Promise((resolve) => {

View File

@ -23,7 +23,7 @@
</a-select>
<a-tooltip :content="innerData.variableVal" :disabled="!innerData.variableVal">
<a-input
:id="innerData.id"
:id="innerData.stepId"
v-model:model-value="innerData.variableVal"
size="mini"
class="w-[110px] px-[8px]"
@ -41,7 +41,7 @@
import { conditionOptions } from '@/views/api-test/scenario/components/config';
export interface ConditionContentProps {
id: string;
stepId: string;
variableName: string;
condition: string;
variableVal: string;
@ -75,7 +75,7 @@
() => dbClick?.value.timeStamp,
() => {
// @ts-ignore
if ((dbClick?.value.e?.target as Element).parentNode?.id.includes(innerData.value.id)) {
if ((dbClick?.value.e?.target as Element).parentNode?.id.includes(innerData.value.stepId)) {
emit('quickInput', 'variableVal');
}
}

View File

@ -87,7 +87,7 @@
</a-select>
<a-tooltip :content="innerData.variableVal" :disabled="!innerData.variableVal">
<a-input
:id="innerData.id"
:id="innerData.stepId"
v-model:model-value="innerData.variableVal"
size="mini"
class="w-[110px] px-[8px]"
@ -99,7 +99,7 @@
</template>
<a-tooltip v-else :content="innerData.expression" :disabled="!innerData.expression">
<a-input
:id="innerData.id"
:id="innerData.stepId"
v-model:model-value="innerData.expression"
size="mini"
class="w-[200px] px-[8px]"
@ -159,7 +159,7 @@
import { conditionOptions } from '@/views/api-test/scenario/components/config';
export interface LoopContentProps {
id: string | number;
stepId: string | number;
num: number;
name: string;
type: ScenarioStepType;
@ -227,7 +227,7 @@
() => dbClick?.value.timeStamp,
() => {
// @ts-ignore
if ((dbClick?.value.e?.target as Element).parentNode?.id.includes(innerData.value.id)) {
if ((dbClick?.value.e?.target as Element).parentNode?.id.includes(innerData.value.stepId)) {
emit('quickInput', innerData.value.loopWhileType === 'condition' ? 'variableVal' : 'expression');
}
}

View File

@ -47,6 +47,7 @@
const props = defineProps<{
data: {
id: string | number;
stepId: string | number;
belongProjectId: string;
belongProjectName: string;
num: number;

View File

@ -24,7 +24,7 @@
import { useI18n } from '@/hooks/useI18n';
export interface WaitTimeContentProps {
id: string | number;
stepId: string | number;
waitTime: number;
}

View File

@ -11,7 +11,7 @@
:expand-all="props.expandAll"
:node-more-actions="stepMoreActions"
:filter-more-action-func="setStepMoreAction"
:field-names="{ title: 'name', key: 'id', children: 'children' }"
:field-names="{ title: 'name', key: 'stepId', children: 'children' }"
:virtual-list-props="{
height: '100%',
threshold: 20,
@ -88,7 +88,7 @@
<template v-if="checkStepIsApi(step)">
<apiMethodName v-if="checkStepShowMethod(step)" :method="step.method" />
<div
v-if="step.id === showStepNameEditInputStepId"
v-if="step.stepId === showStepNameEditInputStepId"
class="name-warp absolute left-0 top-[-2px] z-10 w-[calc(100%-24px)]"
@click.stop
>
@ -117,7 +117,7 @@
<!-- 其他步骤描述 -->
<template v-else>
<div
v-if="step.id === showStepDescEditInputStepId"
v-if="step.stepId === showStepDescEditInputStepId"
class="desc-warp absolute left-0 top-[-2px] z-10 w-[calc(100%-24px)]"
>
<a-input
@ -161,7 +161,7 @@
v-model:selected-keys="selectedKeys"
v-model:steps="steps"
:step="step"
@click="setFocusNodeKey(step.id)"
@click="setFocusNodeKey(step.stepId)"
@other-create="handleOtherCreate"
@close="setFocusNodeKey('')"
/>
@ -187,11 +187,13 @@
</a-button>
</createStepActions>
<customApiDrawer
v-if="customApiDrawerVisible"
v-model:visible="customApiDrawerVisible"
:env-detail-item="{ id: 'demp-id-112233', projectId: '123456', name: 'demo环境' }"
:request="activeStep?.request"
:request="currentStepDetail"
:request-type="activeStep?.type"
:step-name="activeStep?.name || ''"
@add-step="addCustomApiStep"
@apply-step="applyApiStep"
/>
<importApiDrawer
v-if="importApiDrawerVisible"
@ -202,7 +204,7 @@
<scriptOperationDrawer
v-if="scriptOperationDrawerVisible"
v-model:visible="scriptOperationDrawerVisible"
:script="activeStep?.script"
:script="currentStepDetail"
:name="activeStep?.name"
@save="addScriptStep"
/>
@ -285,7 +287,7 @@
const scriptOperationDrawer = defineAsyncComponent(() => import('../common/scriptOperationDrawer.vue'));
export interface ScenarioStepItem {
id: string | number;
stepId: string | number;
order: number;
enabled: boolean; //
type: ScenarioStepType;
@ -333,6 +335,10 @@
const checkedKeys = defineModel<(string | number)[]>('checkedKeys', {
required: true,
});
//
const stepsDetailMap = defineModel<Record<string, any>>('stepsDetailMap', {
required: true,
});
const selectedKeys = ref<(string | number)[]>([]); //
const loading = ref(false);
@ -391,9 +397,9 @@
* 增加步骤时判断父节点是否选中如果选中则需要把新节点也选中
*/
function checkedIfNeed(step: TreeNode<ScenarioStepItem>, parent?: TreeNode<ScenarioStepItem>) {
if (parent && selectedKeys.value.includes(parent.id)) {
if (parent && selectedKeys.value.includes(parent.stepId)) {
//
selectedKeys.value.push(step.id);
selectedKeys.value.push(step.stepId);
}
}
@ -452,7 +458,7 @@
eventTag: 'copy',
},
{
label: 'apiScenario.saveAsCase',
label: 'apiTestManagement.saveAsCase',
eventTag: 'saveAsCase',
},
{
@ -471,30 +477,30 @@
const id = getGenerateId();
insertNodes<ScenarioStepItem>(
steps.value,
node.id,
node.stepId,
{
...cloneDeep(
mapTree<ScenarioStepItem>(node, (childNode) => {
return {
...childNode,
id: getGenerateId(), // TODO: ID
stepId: getGenerateId(), // TODO: ID
};
})[0]
),
name: `copy-${node.name}`,
order: node.order + 1,
id,
stepId: id,
},
'after',
checkedIfNeed,
'id'
'stepId'
);
break;
case 'config':
console.log('config', node);
break;
case 'delete':
deleteNode(steps.value, node.id, 'id');
deleteNode(steps.value, node.stepId, 'stepId');
break;
default:
break;
@ -512,7 +518,7 @@
const tempStepName = ref('');
function handleStepNameClick(step: ScenarioStepItem) {
tempStepName.value = step.name;
showStepNameEditInputStepId.value = step.id;
showStepNameEditInputStepId.value = step.stepId;
nextTick(() => {
//
const input = treeRef.value?.$el.querySelector('.name-warp .arco-input-wrapper .arco-input') as HTMLInputElement;
@ -521,7 +527,7 @@
}
function applyStepNameChange(step: ScenarioStepItem) {
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.id, 'id');
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.stepId, 'stepId');
if (realStep) {
realStep.name = tempStepName.value;
}
@ -535,7 +541,7 @@
const tempStepDesc = ref('');
function handleStepDescClick(step: ScenarioStepItem) {
tempStepDesc.value = step.description;
showStepDescEditInputStepId.value = step.id;
showStepDescEditInputStepId.value = step.stepId;
nextTick(() => {
//
const input = treeRef.value?.$el.querySelector('.desc-warp .arco-input-wrapper .arco-input') as HTMLInputElement;
@ -544,7 +550,7 @@
}
function applyStepDescChange(step: ScenarioStepItem) {
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.id, 'id');
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.stepId, 'stepId');
if (realStep) {
realStep.description = tempStepDesc.value;
}
@ -552,7 +558,7 @@
}
function handleStepContentChange($event, step: ScenarioStepItem) {
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.id, 'id');
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.stepId, 'stepId');
if (realStep) {
Object.keys($event).forEach((key) => {
realStep[key] = $event[key];
@ -564,7 +570,7 @@
* 处理步骤展开折叠
*/
function handleStepExpand(data: MsTreeExpandedData) {
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, data.node?.id, 'id');
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, data.node?.stepId, 'stepId');
if (realStep) {
realStep.expanded = !realStep.expanded;
}
@ -575,15 +581,27 @@
const scriptOperationDrawerVisible = ref(false);
const activeStep = ref<ScenarioStepItem>(); //
const activeCreateAction = ref<CreateStepAction>(); //
const currentStepDetail = computed<any>(() => {
// TODO:
if (activeStep.value) {
return stepsDetailMap.value[activeStep.value.stepId];
}
return undefined;
});
/**
* 处理步骤选中事件
* @param _selectedKeys 选中的 key集合
* @param step 点击的步骤节点
*/
function handleStepSelect(_selectedKeys: Array<string | number>, step: ScenarioStepItem) {
const offspringIds: string[] = [];
mapTree(step.children || [], (e) => {
offspringIds.push(e.id);
offspringIds.push(e.stepId);
return e;
});
selectedKeys.value = [step.id, ...offspringIds];
if (step.type === ScenarioStepType.CUSTOM_API) {
selectedKeys.value = [step.stepId, ...offspringIds];
if ([ScenarioStepType.CUSTOM_API, ScenarioStepType.QUOTE_API, ScenarioStepType.COPY_API].includes(step.type)) {
activeStep.value = step;
customApiDrawerVisible.value = true;
} else if (step.type === ScenarioStepType.SCRIPT_OPERATION) {
@ -651,17 +669,20 @@
const insertApiSteps = buildInsertStepInfos(
data.api,
type === 'copy' ? ScenarioStepType.COPY_API : ScenarioStepType.QUOTE_API,
order
order,
stepsDetailMap.value
);
const insertCaseSteps = buildInsertStepInfos(
data.case,
type === 'copy' ? ScenarioStepType.COPY_CASE : ScenarioStepType.QUOTE_CASE,
order + insertApiSteps.length
order + insertApiSteps.length,
stepsDetailMap.value
);
const insertScenarioSteps = buildInsertStepInfos(
data.scenario,
type === 'copy' ? ScenarioStepType.COPY_SCENARIO : ScenarioStepType.QUOTE_SCENARIO,
order + insertApiSteps.length + insertCaseSteps.length
order + insertApiSteps.length + insertCaseSteps.length,
stepsDetailMap.value
);
const insertSteps = insertApiSteps.concat(insertCaseSteps).concat(insertScenarioSteps);
if (activeStep.value && activeCreateAction.value) {
@ -675,12 +696,13 @@
* 添加自定义 API 步骤
*/
function addCustomApiStep(request: RequestParam) {
const id = getGenerateId();
stepsDetailMap.value[id] = request;
if (activeStep.value && activeCreateAction.value) {
handleCreateStep(
{
type: ScenarioStepType.CUSTOM_API,
name: t('apiScenario.customApi'),
request: cloneDeep(request),
} as ScenarioStepItem,
activeStep.value,
steps.value,
@ -690,25 +712,35 @@
} else {
steps.value.push({
...cloneDeep(defaultStepItemCommon),
id: getGenerateId(),
stepId: id,
order: steps.value.length + 1,
type: ScenarioStepType.CUSTOM_API,
name: t('apiScenario.customApi'),
request: cloneDeep(request),
} as ScenarioStepItem);
}
}
/**
* API 详情抽屉关闭时应用更改
*/
function applyApiStep(request: RequestParam) {
if (activeStep.value) {
stepsDetailMap.value[activeStep.value?.stepId] = request;
activeStep.value = undefined;
}
}
/**
* 添加脚本操作步骤
*/
function addScriptStep(name: string, scriptProcessor: ExecuteConditionProcessor) {
const id = getGenerateId();
stepsDetailMap.value[id] = cloneDeep(scriptProcessor);
if (activeStep.value && activeCreateAction.value) {
handleCreateStep(
{
type: ScenarioStepType.SCRIPT_OPERATION,
name,
script: cloneDeep(scriptProcessor),
} as ScenarioStepItem,
activeStep.value,
steps.value,
@ -718,11 +750,10 @@
} else {
steps.value.push({
...cloneDeep(defaultStepItemCommon),
id: getGenerateId(),
stepId: id,
order: steps.value.length + 1,
type: ScenarioStepType.SCRIPT_OPERATION,
name,
script: cloneDeep(scriptProcessor),
} as ScenarioStepItem);
}
}
@ -760,20 +791,20 @@
loading.value = true;
const offspringIds: string[] = [];
mapTree(dragNode.children || [], (e) => {
offspringIds.push(e.id);
offspringIds.push(e.stepId);
return e;
});
const stepIdAndOffspringIds = [dragNode.id, ...offspringIds];
const stepIdAndOffspringIds = [dragNode.stepId, ...offspringIds];
if (dropPosition === 0) {
//
if (selectedKeys.value.includes(dropNode.id)) {
if (selectedKeys.value.includes(dropNode.stepId)) {
//
selectedKeys.value = selectedKeys.value.concat(stepIdAndOffspringIds);
}
} else if (dropNode.parent && selectedKeys.value.includes(dropNode.parent.id)) {
} else if (dropNode.parent && selectedKeys.value.includes(dropNode.parent.stepId)) {
//
selectedKeys.value = selectedKeys.value.concat(stepIdAndOffspringIds);
} else if (dragNode.parent && selectedKeys.value.includes(dragNode.parent.id)) {
} else if (dragNode.parent && selectedKeys.value.includes(dragNode.parent.stepId)) {
//
selectedKeys.value = selectedKeys.value.filter((e) => {
for (let i = 0; i < stepIdAndOffspringIds.length; i++) {
@ -786,7 +817,7 @@
return true;
});
}
const dragResult = handleTreeDragDrop(steps.value, dragNode, dropNode, dropPosition, 'id');
const dragResult = handleTreeDragDrop(steps.value, dragNode, dropNode, dropPosition, 'stepId');
if (dragResult) {
Message.success(t('common.moveSuccess'));
}
@ -805,7 +836,7 @@
const quickInputDataKey = ref('');
function setQuickInput(step: ScenarioStepItem, dataKey: string) {
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.id, 'id');
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.stepId, 'stepId');
if (realStep) {
activeStep.value = realStep as ScenarioStepItem;
}

View File

@ -125,6 +125,7 @@
executeTime: '',
executeSuccessCount: 0,
executeFailCount: 0,
stepsDetailMap: {},
} as ScenarioStepInfo,
status: RequestDefinitionStatus.PROCESSING,
tags: [],