feat(接口场景): 场景步骤插件保存&查看&更新

This commit is contained in:
baiqi 2024-03-24 18:16:55 +08:00 committed by Craftsman
parent 886d566c21
commit ff316eac16
17 changed files with 294 additions and 186 deletions

View File

@ -13,38 +13,38 @@ export default mergeConfig(
},
proxy: {
'/ws': {
target: 'http://172.16.200.18:8081/',
target: 'http://192.168.8.200:8081/',
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/front\/ws/, ''),
ws: true,
},
'/front': {
target: 'http://172.16.200.18:8081/',
target: 'http://192.168.8.200:8081/',
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/front/, ''),
},
'/file': {
target: 'http://172.16.200.18:8081/',
target: 'http://192.168.8.200:8081/',
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/front\/file/, ''),
},
'/attachment': {
target: 'http://172.16.200.18:8081/',
target: 'http://192.168.8.200:8081/',
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/front\/attachment/, ''),
},
'/bug/attachment': {
target: 'http://172.16.200.18:8081/',
target: 'http://192.168.8.200:8081/',
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/front\/bug\/attachment/, ''),
},
'/plugin/image': {
target: 'http://172.16.200.18:8081/',
target: 'http://192.168.8.200:8081/',
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/front\/plugin\/image/, ''),
},
'/base-display': {
target: 'http://172.16.200.18:8081/',
target: 'http://192.168.8.200:8081/',
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/front\/base-display/, ''),
},

View File

@ -14,6 +14,7 @@ import {
ExecuteHistoryUrl,
GetModuleCountUrl,
GetModuleTreeUrl,
GetScenarioStepUrl,
GetScenarioUrl,
GetTrashModuleCountUrl,
GetTrashModuleTreeUrl,
@ -194,3 +195,8 @@ export function addScenario(params: Scenario) {
export function getScenarioDetail(id: string) {
return MSR.get<ScenarioDetail>({ url: GetScenarioUrl, params: id });
}
// 获取场景步骤详情
export function getScenarioStep(stepId: string | number) {
return MSR.get({ url: GetScenarioStepUrl, params: stepId });
}

View File

@ -7,6 +7,7 @@ export const DeleteModuleUrl = '/api/scenario/module/delete'; // 删除模块
export const ScenarioPageUrl = '/api/scenario/page'; // 接口场景列表
export const AddScenarioUrl = '/api/scenario/add'; // 添加接口场景
export const GetScenarioUrl = '/api/scenario/get'; // 获取接口场景详情
export const GetScenarioStepUrl = '/api/scenario/step/get'; // 获取接口场景步骤详情
export const UpdateScenarioUrl = '/api/scenario/update'; // 更新接口场景
export const RecycleScenarioUrl = '/api/scenario/delete-to-gc'; // 删除接口场景
export const BatchRecycleScenarioUrl = '/api/scenario/batch-operation/delete-gc'; // 批量删除接口场景

View File

@ -236,6 +236,7 @@
const currentIndex = assertions.value.findIndex((item) => item.id === activeKey.value);
const tmpArr = assertions.value;
tmpArr[currentIndex] = cloneDeep(val);
console.log('tmpArr', assertions.value, tmpArr);
assertions.value = tmpArr;
},
});

View File

@ -313,7 +313,9 @@ export interface LoopStepDetail extends StepDetailsCommon {
msCountController: CountController;
whileController: WhileController;
}
export type ScenarioStepDetail = Partial<CustomApiStepDetail & ConditionStepDetail & LoopStepDetail>;
export type ScenarioStepDetail = Partial<
CustomApiStepDetail & ConditionStepDetail & LoopStepDetail & { protocol: string; method: RequestMethods }
>;
export interface ScenarioStepItem {
id: string | number;
sort: number;
@ -324,7 +326,7 @@ export interface ScenarioStepItem {
resourceNum?: string; // 详情或者引用的类型才有
stepType: ScenarioStepType;
refType: ScenarioStepRefType;
config?: ScenarioStepDetail; // 对应场景里stepDetails里的详情信息只有逻辑控制器需要
config: ScenarioStepDetail; // 存储步骤列表需要展示的信息
csvFileIds?: string[];
projectId?: string;
versionId?: string;

View File

@ -224,7 +224,7 @@ export function mapTree<T>(
return _tree
.map((node: TreeNode<T>, i: number) => {
const fullPath = node.path ? `${_parentPath}/${node.path}`.replace(/\/+/g, '/') : '';
node.sort = i + 1; // order从 1 开始
node.sort = i + 1; // sort 从 1 开始
node.parent = _parent || undefined; // 没有父节点说明是树的第一层
const newNode = typeof customNodeFn === 'function' ? customNodeFn(node, fullPath) : node;
if (newNode) {

View File

@ -4,7 +4,7 @@
:width="960"
no-content-padding
:show-continue="true"
:footer="!!requestVModel.isNew"
:footer="requestVModel.isNew === true"
@confirm="handleSave"
@continue="handleContinue"
@close="handleClose"
@ -18,11 +18,21 @@
/>
{{ 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)]">
<div
v-if="!props.step || props.step?.stepType === ScenarioStepType.CUSTOM_REQUEST"
class="ml-auto flex items-center gap-[16px]"
>
<div
v-show="!requestVModel.customizeRequestEnvEnable"
class="text-[14px] font-normal text-[var(--color-text-4)]"
>
{{ t('apiScenario.env', { name: props.envDetailItem?.name }) }}
</div>
<a-select v-model:model-value="requestVModel.useEnv" class="w-[150px]" @change="handleUseEnvChange">
<a-select
v-model:model-value="requestVModel.customizeRequestEnvEnable"
class="w-[150px]"
@change="handleUseEnvChange"
>
<template #prefix>
<div> {{ t('project.environmental.env') }} </div>
</template>
@ -348,7 +358,7 @@
export type RequestParam = ExecuteApiRequestFullParams & {
response?: RequestTaskResult;
useEnv: string;
customizeRequestEnvEnable: boolean;
request?: ExecuteApiRequestFullParams; //
} & RequestCustomAttr &
TabItem;
@ -389,7 +399,7 @@
const defaultDebugParams: RequestParam = {
type: 'api',
id: '',
useEnv: 'false',
customizeRequestEnvEnable: false,
protocol: 'HTTP',
url: '',
activeTab: RequestComposition.HEADER,
@ -614,14 +624,22 @@
* 控制插件表单字段显示
*/
function controlPluginFormFields() {
const allFields = fApi.value?.fields();
const currentFormFields = fApi.value?.fields();
let fields: string[] = [];
if (requestVModel.value.useEnv === 'true') {
if (requestVModel.value.customizeRequestEnvEnable) {
fields = pluginScriptMap.value[requestVModel.value.protocol].apiDefinitionFields || [];
} else {
fields = pluginScriptMap.value[requestVModel.value.protocol].apiDebugFields || [];
}
fApi.value?.hidden(true, allFields?.filter((e) => !fields.includes(e)) || []);
// fields
if (currentFormFields && currentFormFields.length < fields.length) {
fApi.value?.hidden(false, fields);
fApi.value?.hidden(true, currentFormFields?.filter((e) => !fields.includes(e)) || []);
fApi.value?.refresh();
} else {
//
fApi.value?.hidden(true, currentFormFields?.filter((e) => !fields.includes(e)) || []);
}
return fields;
}
@ -640,6 +658,7 @@
form[key] = formData[key];
});
fApi.value?.setValue(cloneDeep(form));
fApi.value?.clearValidateState();
setTimeout(() => {
// 300ms handlePluginFormChange
isInitPluginForm.value = true;
@ -744,17 +763,6 @@
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) => {
@ -785,9 +793,7 @@
if (val) {
verticalSplitBoxRef.value?.expand(0.6);
} else {
verticalSplitBoxRef.value?.collapse(
splitContainerRef.value ? `${splitContainerRef.value.clientHeight - 42}px` : 0
);
verticalSplitBoxRef.value?.collapse(1);
}
}
@ -921,6 +927,7 @@
protocol: requestVModel.value.protocol,
method: isHttpProtocol.value ? requestVModel.value.method : requestVModel.value.protocol,
name: requestVModel.value.name,
customizeRequestEnvEnable: requestVModel.value.customizeRequestEnvEnable,
children: [
{
polymorphicName: 'MsCommonElement', // MsCommonElement
@ -1042,9 +1049,15 @@
() => visible.value,
async (val) => {
if (val) {
if (protocolOptions.value.length === 0) {
await initProtocolList();
}
if (props.request) {
console.log('props.request', props.request);
requestVModel.value = cloneDeep(props.request);
requestVModel.value = cloneDeep({
...defaultDebugParams,
...props.request,
isNew: false,
});
if (
_stepType.value.isQuoteApi ||
isCopyApiNeedInit.value
@ -1082,16 +1095,13 @@
// });
// }
// }
handleActiveDebugProtocolChange(requestVModel.value.protocol);
} else {
requestVModel.value = cloneDeep({
...defaultDebugParams,
id: getGenerateId(),
});
}
await initProtocolList();
if (props.request) {
handleActiveDebugProtocolChange(requestVModel.value.protocol);
}
}
},
{
@ -1100,6 +1110,19 @@
);
</script>
<style lang="less">
.hidden-second {
:deep(.arco-split-trigger, .arco-split-pane-second) {
@apply hidden;
}
}
.show-second {
:deep(.arco-split-trigger, .arco-split-pane-second) {
@apply block;
}
}
</style>
<style lang="less" scoped>
.exec-btn {
margin-right: 12px;
@ -1126,14 +1149,4 @@
:deep(.arco-tabs-tab) {
@apply leading-none;
}
.hidden-second {
:deep(.arco-split-trigger) {
@apply hidden;
}
}
.show-second {
:deep(.arco-split-trigger) {
@apply block;
}
}
</style>

View File

@ -10,7 +10,7 @@
@close="handleClose"
>
<template #title>
<stepType v-if="activeStep?.type" :type="activeStep?.type" class="mr-[4px]" />
<stepType v-if="props.activeStep?.stepType" :step="props.activeStep" class="mr-[4px]" />
<a-input
v-if="activeStep?.name"
v-show="isShowEditStepNameInput"
@ -84,8 +84,7 @@
import MsButton from '@/components/pure/ms-button/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import { ScenarioStepItem } from '../step/stepTree.vue';
import stepType from './stepType.vue';
import stepType from './stepType/stepType.vue';
import executeButton from '@/views/api-test/components/executeButton.vue';
import requestAndResponse from '@/views/api-test/components/requestAndResponse.vue';
import { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
@ -103,12 +102,14 @@
import { getLocalConfig } from '@/api/modules/user/index';
import { characterLimit, getGenerateId } from '@/utils';
import { ScenarioStepItem } from '@/models/apiTest/scenario';
import { LocalConfig } from '@/models/user';
import {
RequestAuthType,
RequestComposition,
RequestMethods,
ResponseComposition,
ScenarioStepRefType,
ScenarioStepType,
} from '@/enums/apiEnum';
@ -202,6 +203,20 @@
};
const requestVModel = ref<RequestParam>(props.request || cloneDeep(defaultCaseParams));
const isCopyCase = computed(
() =>
props.activeStep?.stepType === ScenarioStepType.API_CASE && props.activeStep?.refType === ScenarioStepRefType.COPY
);
const isCopyNeedInit = computed(() => isCopyCase.value && props.request?.request === null);
const isQuote = computed(
() =>
props.activeStep?.stepType === ScenarioStepType.API_CASE && props.activeStep?.refType === ScenarioStepRefType.REF
);
const stepName = ref(props.activeStep?.name);
watchEffect(() => {
stepName.value = props.activeStep?.name;
});
const executeRef = ref<InstanceType<typeof executeButton>>();
const requestAndResponseRef = ref<InstanceType<typeof requestAndResponse>>();

View File

@ -7,24 +7,8 @@ import {
WhileConditionType,
} from '@/enums/apiEnum';
export const defaultStepItemCommon = {
checked: false,
expanded: false,
enable: true,
children: [],
config: {
id: '',
copyFromStepId: '', // 如果步骤是复制的这个字段是复制的步骤id
name: '',
enable: true,
polymorphicName: '', // 多态名称,用于后台区分使用的是哪个组件
// 自定义请求
customizeRequest: false, // 是否自定义请求
customizeRequestEnvEnable: false, // 是否启用环境
// 条件控制器
value: '', // 变量值
variable: '', // 变量名
condition: RequestAssertionCondition.EQUALS, // 条件操作符
// 循环控制器
export const defaultLoopController = {
loopType: ScenarioStepLoopTypeEnum.LOOP_COUNT,
forEachController: {
loopTime: 0, // 循环间隔时间
@ -46,6 +30,32 @@ export const defaultStepItemCommon = {
variable: '', // 变量名
}, // 变量
},
};
// 自定义请求
export const defaultCustomApiConfig = {
customizeRequest: false, // 是否自定义请求
customizeRequestEnvEnable: false, // 是否启用环境
};
// 条件控制器
export const defaultConditionController = {
value: '', // 变量值
variable: '', // 变量名
condition: RequestAssertionCondition.EQUALS, // 条件操作符
};
export const defaultStepItemCommon = {
checked: false,
expanded: false,
enable: true,
children: [],
copyFromStepId: '', // 如果步骤是复制的这个字段是复制的步骤id
isNew: true, // 是否新建的步骤
config: {
id: '',
name: '',
enable: true,
waitTime: 0, // 等待时间
},
createActionsVisible: false,

View File

@ -54,7 +54,11 @@
import { ScenarioAddStepActionType, ScenarioStepRefType, ScenarioStepType } from '@/enums/apiEnum';
import useCreateActions from './useCreateActions';
import { defaultStepItemCommon } from '@/views/api-test/scenario/components/config';
import {
defaultConditionController,
defaultLoopController,
defaultStepItemCommon,
} from '@/views/api-test/scenario/components/config';
import { DropdownPosition } from '@arco-design/web-vue/es/dropdown/interface';
const props = defineProps<{
@ -90,7 +94,7 @@
default: undefined,
});
const { handleCreateStep } = useCreateActions();
const { handleCreateStep, buildInsertStepInfos } = useCreateActions();
/**
* 处理创建步骤操作
@ -112,15 +116,15 @@
selectedKeys.value
);
} else {
steps.value.push({
...cloneDeep(defaultStepItemCommon),
id: getGenerateId(),
sort: steps.value.length + 1,
stepType: ScenarioStepType.LOOP_CONTROLLER,
refType: ScenarioStepRefType.DIRECT,
name: t('apiScenario.loopControl'),
projectId: appStore.currentProjectId,
});
steps.value.push(
buildInsertStepInfos(
[cloneDeep(defaultStepItemCommon)],
ScenarioStepType.LOOP_CONTROLLER,
ScenarioStepRefType.DIRECT,
steps.value.length + 1,
appStore.currentProjectId
)[0]
);
}
break;
case ScenarioAddStepActionType.CONDITION_CONTROL:
@ -137,15 +141,15 @@
selectedKeys.value
);
} else {
steps.value.push({
...cloneDeep(defaultStepItemCommon),
id: getGenerateId(),
sort: steps.value.length + 1,
stepType: ScenarioStepType.IF_CONTROLLER,
refType: ScenarioStepRefType.DIRECT,
name: t('apiScenario.conditionControl'),
projectId: appStore.currentProjectId,
});
steps.value.push(
buildInsertStepInfos(
[cloneDeep(defaultStepItemCommon)],
ScenarioStepType.IF_CONTROLLER,
ScenarioStepRefType.DIRECT,
steps.value.length + 1,
appStore.currentProjectId
)[0]
);
}
break;
case ScenarioAddStepActionType.ONLY_ONCE_CONTROL:
@ -162,15 +166,15 @@
selectedKeys.value
);
} else {
steps.value.push({
...cloneDeep(defaultStepItemCommon),
id: getGenerateId(),
sort: steps.value.length + 1,
stepType: ScenarioStepType.ONCE_ONLY_CONTROLLER,
refType: ScenarioStepRefType.DIRECT,
name: t('apiScenario.onlyOnceControl'),
projectId: appStore.currentProjectId,
});
steps.value.push(
buildInsertStepInfos(
[cloneDeep(defaultStepItemCommon)],
ScenarioStepType.ONCE_ONLY_CONTROLLER,
ScenarioStepRefType.DIRECT,
steps.value.length + 1,
appStore.currentProjectId
)[0]
);
}
break;
case ScenarioAddStepActionType.WAIT_TIME:
@ -187,15 +191,15 @@
selectedKeys.value
);
} else {
steps.value.push({
...cloneDeep(defaultStepItemCommon),
id: getGenerateId(),
sort: steps.value.length + 1,
stepType: ScenarioStepType.CONSTANT_TIMER,
refType: ScenarioStepRefType.DIRECT,
name: t('apiScenario.waitTime'),
projectId: appStore.currentProjectId,
});
steps.value.push(
buildInsertStepInfos(
[cloneDeep(defaultStepItemCommon)],
ScenarioStepType.CONSTANT_TIMER,
ScenarioStepRefType.DIRECT,
steps.value.length + 1,
appStore.currentProjectId
)[0]
);
}
break;
case ScenarioAddStepActionType.IMPORT_SYSTEM_API:

View File

@ -6,7 +6,7 @@ import { getGenerateId, insertNodes, TreeNode } from '@/utils';
import { CreateStepAction, ScenarioStepItem } from '@/models/apiTest/scenario';
import { ScenarioStepRefType, ScenarioStepType } from '@/enums/apiEnum';
import { defaultStepItemCommon } from '../../config';
import { defaultConditionController, defaultLoopController, defaultStepItemCommon } from '../../config';
export default function useCreateActions() {
const { t } = useI18n();
@ -48,7 +48,6 @@ export default function useCreateActions() {
id: getGenerateId(),
...defaultStepInfo,
};
console.log('newStep', newStep);
insertNodes<ScenarioStepItem>(
step.parent?.children || steps,
step.id,
@ -70,7 +69,6 @@ export default function useCreateActions() {
stepType: ScenarioStepType,
refType: ScenarioStepRefType,
startOrder: number,
stepDetails: Record<string, any>,
projectId: string
): ScenarioStepItem[] {
let name: string;
@ -98,15 +96,40 @@ export default function useCreateActions() {
}
return newSteps.map((item, index) => {
const id = getGenerateId();
stepDetails[id] = item; // 导入系统请求的引用接口和 case 的时候需要先存储一下引用的接口/用例信息
let resourceField = {};
let config = {};
if (stepType === ScenarioStepType.LOOP_CONTROLLER) {
config = cloneDeep(defaultLoopController);
} else if (stepType === ScenarioStepType.IF_CONTROLLER) {
config = cloneDeep(defaultConditionController);
}
if (item.id) {
// 引用复制接口、用例、场景时的源资源信息
resourceField = {
resourceId: item.id,
resourceNum: item.num,
resourceName: item.name,
};
}
if (item.protocol) {
// 自定义请求、api、case 添加协议和方法
config = {
...config,
protocol: item.protocol,
method: item.method,
};
}
return {
...cloneDeep(defaultStepItemCommon),
...item,
id,
config: {
...defaultStepItemCommon.config,
...config,
},
stepType,
refType,
resourceId: item.id,
resourceName: item.name,
...resourceField,
name: name || item.name,
sort: startOrder + index,
projectId,

View File

@ -44,10 +44,11 @@
const props = defineProps<{
data: ConditionStepDetail;
stepId: string | number;
}>();
const emit = defineEmits<{
(e: 'change', innerData: ConditionStepDetail): void;
(e: 'quickInput', dataKey: keyof ConditionStepDetail): void;
(e: 'quickInput', dataKey: string): void;
}>();
const { t } = useI18n();
@ -70,8 +71,8 @@
() => dbClick?.value.timeStamp,
() => {
// @ts-ignore
if ((dbClick?.value.e?.target as Element).parentNode?.id.includes(innerData.value.id)) {
emit('quickInput', 'value');
if ((dbClick?.value.e?.target as Element).parentNode?.id.includes(props.stepId)) {
emit('quickInput', 'conditionValue');
}
}
);

View File

@ -225,8 +225,8 @@
emit(
'quickInput',
innerData.value.whileController.conditionType === WhileConditionType.CONDITION
? 'whileController.msWhileVariable.value'
: 'whileController.msWhileScript.scriptValue'
? 'msWhileVariableValue'
: 'msWhileVariableScriptValue'
);
}
}

View File

@ -1,13 +1,13 @@
<template>
<div class="flex items-center gap-[4px]">
<a-popover position="bl" content-class="detail-popover" arrow-class="hidden">
<!-- <a-popover position="bl" content-class="detail-popover" arrow-class="hidden">
<MsIcon type="icon-icon-draft" class="text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]" />
<template #content>
<div class="flex flex-col gap-[16px]">
<div>
<div class="mb-[2px] text-[var(--color-text-4)]">{{ t('apiScenario.belongProject') }}</div>
<div class="text-[14px] text-[var(--color-text-1)]">
<!-- {{ props.data.belongProjectName }} -->
{{ props.data.belongProjectName }}
</div>
</div>
<div>
@ -18,7 +18,7 @@
</div>
</div>
</template>
</a-popover>
</a-popover> -->
<MsTag
v-if="props.data.projectId !== appStore.currentProjectId"
theme="outline"
@ -35,17 +35,17 @@
</template>
<script setup lang="ts">
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
// import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import { useI18n } from '@/hooks/useI18n';
import useOpenNewPage from '@/hooks/useOpenNewPage';
// import useOpenNewPage from '@/hooks/useOpenNewPage';
import useAppStore from '@/store/modules/app';
import { ScenarioStepItem } from '@/models/apiTest/scenario';
import { ApiTestRouteEnum } from '@/enums/routeEnum';
// import { ApiTestRouteEnum } from '@/enums/routeEnum';
import getStepType from '@/views/api-test/scenario/components/common/stepType/utils';
// import getStepType from '@/views/api-test/scenario/components/common/stepType/utils';
const props = defineProps<{
data: ScenarioStepItem;
@ -53,27 +53,27 @@
const appStore = useAppStore();
const { t } = useI18n();
const { openNewPage } = useOpenNewPage();
// const { openNewPage } = useOpenNewPage();
function goDetail() {
const _stepType = getStepType(props.data);
switch (true) {
case _stepType.isCopyApi:
case _stepType.isQuoteApi:
openNewPage(ApiTestRouteEnum.API_TEST_MANAGEMENT, { dId: props.data.id, pId: props.data.projectId });
break;
case _stepType.isCopyScenario:
case _stepType.isQuoteScenario:
openNewPage(ApiTestRouteEnum.API_TEST_SCENARIO, { sId: props.data.id, pId: props.data.projectId });
break;
case _stepType.isQuoteCase:
case _stepType.isCopyCase:
openNewPage(ApiTestRouteEnum.API_TEST_MANAGEMENT, { cId: props.data.id, pId: props.data.projectId });
break;
default:
break;
}
}
// function goDetail() {
// const _stepType = getStepType(props.data);
// switch (true) {
// case _stepType.isCopyApi:
// case _stepType.isQuoteApi:
// openNewPage(ApiTestRouteEnum.API_TEST_MANAGEMENT, { dId: props.data.id, pId: props.data.projectId });
// break;
// case _stepType.isCopyScenario:
// case _stepType.isQuoteScenario:
// openNewPage(ApiTestRouteEnum.API_TEST_SCENARIO, { sId: props.data.id, pId: props.data.projectId });
// break;
// case _stepType.isQuoteCase:
// case _stepType.isCopyCase:
// openNewPage(ApiTestRouteEnum.API_TEST_MANAGEMENT, { cId: props.data.id, pId: props.data.projectId });
// break;
// default:
// break;
// }
// }
</script>
<style lang="less" scoped>

View File

@ -87,7 +87,7 @@
/>
<!-- APICASE场景步骤名称 -->
<template v-if="checkStepIsApi(step)">
<apiMethodName v-if="checkStepShowMethod(step)" :method="step.method" />
<apiMethodName v-if="checkStepShowMethod(step)" :method="step.config.method" />
<div
v-if="step.id === showStepNameEditInputStepId"
class="name-warp absolute left-0 top-[-2px] z-10 w-[calc(100%-24px)]"
@ -268,6 +268,7 @@
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
import { RequestParam as CaseRequestParam } from '@/views/api-test/components/requestComposition/index.vue';
import { getScenarioStep } from '@/api/modules/api-test/scenario';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import {
@ -527,11 +528,9 @@
function handleStepContentChange($event, step: ScenarioStepItem) {
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.id, 'id');
if (realStep) {
// Object.keys($event).forEach((key) => {
// realStep.config[key] = $event[key];
// });
realStep.config = $event;
console.log('handleStepContentChange', $event);
Object.keys($event).forEach((key) => {
realStep.config[key] = $event[key];
});
}
}
@ -566,12 +565,29 @@
return undefined;
});
async function getStepDetail(step: ScenarioStepItem) {
try {
appStore.showLoading();
const res = await getScenarioStep(step.id);
stepDetails.value[step.id] = {
...res,
protocol: step.config.protocol,
method: step.config.method,
};
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
appStore.hideLoading();
}
}
/**
* 处理步骤选中事件
* @param _selectedKeys 选中的 key集合
* @param step 点击的步骤节点
*/
function handleStepSelect(_selectedKeys: Array<string | number>, step: ScenarioStepItem) {
async function handleStepSelect(_selectedKeys: Array<string | number>, step: ScenarioStepItem) {
const _stepType = getStepType(step);
const offspringIds: string[] = [];
mapTree(step.children || [], (e) => {
@ -580,9 +596,14 @@
});
selectedKeys.value = [step.id, ...offspringIds];
if (_stepType.isCopyApi || _stepType.isQuoteApi || step.stepType === ScenarioStepType.CUSTOM_REQUEST) {
// api api api
activeStep.value = step;
if (stepDetails.value[step.id] === undefined) {
// api api
await getStepDetail(step);
}
customApiDrawerVisible.value = true;
} else if ([ScenarioStepType.QUOTE_CASE, ScenarioStepType.COPY_CASE].includes(step.type)) {
} else if (step.stepType === ScenarioStepType.API_CASE) {
activeStep.value = step;
customCaseDrawerVisible.value = true;
} else if (step.stepType === ScenarioStepType.SCRIPT) {
@ -653,7 +674,6 @@
ScenarioStepType.API,
refType,
sort,
stepDetails.value,
appStore.currentProjectId
);
const insertCaseSteps = buildInsertStepInfos(
@ -661,7 +681,6 @@
ScenarioStepType.API_CASE,
refType,
sort + insertApiSteps.length,
stepDetails.value,
appStore.currentProjectId
);
const insertScenarioSteps = buildInsertStepInfos(
@ -669,7 +688,6 @@
ScenarioStepType.API_SCENARIO,
refType,
sort + insertApiSteps.length + insertCaseSteps.length,
stepDetails.value,
appStore.currentProjectId
);
const insertSteps = insertApiSteps.concat(insertCaseSteps).concat(insertScenarioSteps);
@ -703,12 +721,17 @@
} else {
steps.value.push({
...cloneDeep(defaultStepItemCommon),
config: {
customizeRequest: true,
customizeRequestEnvEnable: request.customizeRequestEnvEnable,
protocol: request.protocol,
method: request.method,
},
id: request.id,
sort: steps.value.length + 1,
stepType: ScenarioStepType.CUSTOM_REQUEST,
refType: ScenarioStepRefType.DIRECT,
name: t('apiScenario.customApi'),
method: request.method,
projectId: appStore.currentProjectId,
});
}
@ -719,6 +742,7 @@
*/
function applyApiStep(request: RequestParam | CaseRequestParam) {
if (activeStep.value) {
request.isNew = false;
stepDetails.value[activeStep.value?.id] = request;
activeStep.value = undefined;
}
@ -730,8 +754,8 @@
function deleteCaseStep() {
if (activeStep.value) {
customCaseDrawerVisible.value = false;
steps.value = steps.value.filter((item) => item.stepId !== activeStep.value?.stepId);
delete stepsDetailMap.value[activeStep.value?.stepId];
steps.value = steps.value.filter((item) => item.id !== activeStep.value?.id);
delete stepDetails.value[activeStep.value?.id];
activeStep.value = undefined;
}
}
@ -851,6 +875,13 @@
}
quickInputDataKey.value = dataKey;
quickInputParamValue.value = step.config?.[dataKey] || '';
if (quickInputDataKey.value === 'msWhileVariableValue' && activeStep.value?.config.whileController) {
quickInputParamValue.value = activeStep.value.config.whileController.msWhileVariable.value;
} else if (quickInputDataKey.value === 'msWhileVariableScriptValue' && activeStep.value?.config.whileController) {
quickInputParamValue.value = activeStep.value.config.whileController.msWhileScript.scriptValue;
} else if (quickInputDataKey.value === 'conditionValue' && activeStep.value?.config) {
quickInputParamValue.value = activeStep.value.config.value || '';
}
showQuickInput.value = true;
}
@ -862,7 +893,13 @@
function applyQuickInput() {
if (activeStep.value) {
activeStep.value[quickInputDataKey.value] = quickInputParamValue.value;
if (quickInputDataKey.value === 'msWhileVariableValue' && activeStep.value.config.whileController) {
activeStep.value.config.whileController.msWhileVariable.value = quickInputParamValue.value;
} else if (quickInputDataKey.value === 'msWhileVariableScriptValue' && activeStep.value.config.whileController) {
activeStep.value.config.whileController.msWhileScript.scriptValue = quickInputParamValue.value;
} else if (quickInputDataKey.value === 'conditionValue' && activeStep.value.config) {
activeStep.value.config.value = quickInputParamValue.value;
}
showQuickInput.value = false;
clearQuickInput();
}

View File

@ -91,14 +91,9 @@
import { useI18n } from '@/hooks/useI18n';
import router from '@/router';
import useAppStore from '@/store/modules/app';
import { getGenerateId, mapTree, TreeNode } from '@/utils';
import { getGenerateId } from '@/utils';
import {
ApiScenarioGetModuleParams,
ApiScenarioTableItem,
Scenario,
ScenarioStepItem,
} from '@/models/apiTest/scenario';
import { ApiScenarioGetModuleParams, ApiScenarioTableItem, Scenario } from '@/models/apiTest/scenario';
import { ModuleTreeNode } from '@/models/common';
import { ApiTestRouteEnum } from '@/enums/routeEnum';
@ -127,6 +122,7 @@
...defaultScenarioInfo,
id: isCopy ? getGenerateId() : defaultScenarioInfo.id || '',
label: isCopy ? `copy-${defaultScenarioInfo.name}` : defaultScenarioInfo.name,
isNew: false,
});
} else {
apiTabs.value.push({
@ -197,6 +193,8 @@
projectId: appStore.currentProjectId,
});
const scenarioDetail = await getScenarioDetail(res.id);
scenarioDetail.stepDetails = {};
scenarioDetail.isNew = false;
activeScenarioTab.value = scenarioDetail as ScenarioParams;
} else {
await updateScenario({
@ -219,10 +217,6 @@
appStore.showLoading();
const res = await getScenarioDetail(record.id);
res.stepDetails = {};
// mapTree<ScenarioStepItem>(res.steps, (node: TreeNode<ScenarioStepItem>) => {
// res.stepDetails[node.id] = node.config;
// return node;
// });
newTab(res, isCopy);
} catch (error) {
// eslint-disable-next-line no-console

View File

@ -116,8 +116,9 @@ export default {
'apiScenario.variable': '变量名称{suffix}',
'apiScenario.valuePrefix': '变量前缀',
'apiScenario.value': '变量值',
'apiScenario.whileController.msWhileVariable.value': '变量值',
'apiScenario.whileController.msWhileScript.scriptValue': '表达式',
'apiScenario.conditionValue': '变量值',
'apiScenario.msWhileVariableValue': '变量值',
'apiScenario.msWhileVariableScriptValue': '表达式',
'apiScenario.condition': '条件',
'apiScenario.expression': '表达式',
'apiScenario.equal': '等于',