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: { proxy: {
'/ws': { '/ws': {
target: 'http://172.16.200.18:8081/', target: 'http://192.168.8.200:8081/',
changeOrigin: true, changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/front\/ws/, ''), rewrite: (path: string) => path.replace(/^\/front\/ws/, ''),
ws: true, ws: true,
}, },
'/front': { '/front': {
target: 'http://172.16.200.18:8081/', target: 'http://192.168.8.200:8081/',
changeOrigin: true, changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/front/, ''), rewrite: (path: string) => path.replace(/^\/front/, ''),
}, },
'/file': { '/file': {
target: 'http://172.16.200.18:8081/', target: 'http://192.168.8.200:8081/',
changeOrigin: true, changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/front\/file/, ''), rewrite: (path: string) => path.replace(/^\/front\/file/, ''),
}, },
'/attachment': { '/attachment': {
target: 'http://172.16.200.18:8081/', target: 'http://192.168.8.200:8081/',
changeOrigin: true, changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/front\/attachment/, ''), rewrite: (path: string) => path.replace(/^\/front\/attachment/, ''),
}, },
'/bug/attachment': { '/bug/attachment': {
target: 'http://172.16.200.18:8081/', target: 'http://192.168.8.200:8081/',
changeOrigin: true, changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/front\/bug\/attachment/, ''), rewrite: (path: string) => path.replace(/^\/front\/bug\/attachment/, ''),
}, },
'/plugin/image': { '/plugin/image': {
target: 'http://172.16.200.18:8081/', target: 'http://192.168.8.200:8081/',
changeOrigin: true, changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/front\/plugin\/image/, ''), rewrite: (path: string) => path.replace(/^\/front\/plugin\/image/, ''),
}, },
'/base-display': { '/base-display': {
target: 'http://172.16.200.18:8081/', target: 'http://192.168.8.200:8081/',
changeOrigin: true, changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/front\/base-display/, ''), rewrite: (path: string) => path.replace(/^\/front\/base-display/, ''),
}, },

View File

@ -14,6 +14,7 @@ import {
ExecuteHistoryUrl, ExecuteHistoryUrl,
GetModuleCountUrl, GetModuleCountUrl,
GetModuleTreeUrl, GetModuleTreeUrl,
GetScenarioStepUrl,
GetScenarioUrl, GetScenarioUrl,
GetTrashModuleCountUrl, GetTrashModuleCountUrl,
GetTrashModuleTreeUrl, GetTrashModuleTreeUrl,
@ -194,3 +195,8 @@ export function addScenario(params: Scenario) {
export function getScenarioDetail(id: string) { export function getScenarioDetail(id: string) {
return MSR.get<ScenarioDetail>({ url: GetScenarioUrl, params: id }); 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 ScenarioPageUrl = '/api/scenario/page'; // 接口场景列表
export const AddScenarioUrl = '/api/scenario/add'; // 添加接口场景 export const AddScenarioUrl = '/api/scenario/add'; // 添加接口场景
export const GetScenarioUrl = '/api/scenario/get'; // 获取接口场景详情 export const GetScenarioUrl = '/api/scenario/get'; // 获取接口场景详情
export const GetScenarioStepUrl = '/api/scenario/step/get'; // 获取接口场景步骤详情
export const UpdateScenarioUrl = '/api/scenario/update'; // 更新接口场景 export const UpdateScenarioUrl = '/api/scenario/update'; // 更新接口场景
export const RecycleScenarioUrl = '/api/scenario/delete-to-gc'; // 删除接口场景 export const RecycleScenarioUrl = '/api/scenario/delete-to-gc'; // 删除接口场景
export const BatchRecycleScenarioUrl = '/api/scenario/batch-operation/delete-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 currentIndex = assertions.value.findIndex((item) => item.id === activeKey.value);
const tmpArr = assertions.value; const tmpArr = assertions.value;
tmpArr[currentIndex] = cloneDeep(val); tmpArr[currentIndex] = cloneDeep(val);
console.log('tmpArr', assertions.value, tmpArr);
assertions.value = tmpArr; assertions.value = tmpArr;
}, },
}); });

View File

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

View File

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

View File

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

View File

@ -10,7 +10,7 @@
@close="handleClose" @close="handleClose"
> >
<template #title> <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 <a-input
v-if="activeStep?.name" v-if="activeStep?.name"
v-show="isShowEditStepNameInput" v-show="isShowEditStepNameInput"
@ -84,8 +84,7 @@
import MsButton from '@/components/pure/ms-button/index.vue'; import MsButton from '@/components/pure/ms-button/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue'; import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import { ScenarioStepItem } from '../step/stepTree.vue'; import stepType from './stepType/stepType.vue';
import stepType from './stepType.vue';
import executeButton from '@/views/api-test/components/executeButton.vue'; import executeButton from '@/views/api-test/components/executeButton.vue';
import requestAndResponse from '@/views/api-test/components/requestAndResponse.vue'; import requestAndResponse from '@/views/api-test/components/requestAndResponse.vue';
import { RequestParam } from '@/views/api-test/components/requestComposition/index.vue'; import { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
@ -103,12 +102,14 @@
import { getLocalConfig } from '@/api/modules/user/index'; import { getLocalConfig } from '@/api/modules/user/index';
import { characterLimit, getGenerateId } from '@/utils'; import { characterLimit, getGenerateId } from '@/utils';
import { ScenarioStepItem } from '@/models/apiTest/scenario';
import { LocalConfig } from '@/models/user'; import { LocalConfig } from '@/models/user';
import { import {
RequestAuthType, RequestAuthType,
RequestComposition, RequestComposition,
RequestMethods, RequestMethods,
ResponseComposition, ResponseComposition,
ScenarioStepRefType,
ScenarioStepType, ScenarioStepType,
} from '@/enums/apiEnum'; } from '@/enums/apiEnum';
@ -202,6 +203,20 @@
}; };
const requestVModel = ref<RequestParam>(props.request || cloneDeep(defaultCaseParams)); 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 executeRef = ref<InstanceType<typeof executeButton>>();
const requestAndResponseRef = ref<InstanceType<typeof requestAndResponse>>(); const requestAndResponseRef = ref<InstanceType<typeof requestAndResponse>>();

View File

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

View File

@ -54,7 +54,11 @@
import { ScenarioAddStepActionType, ScenarioStepRefType, ScenarioStepType } from '@/enums/apiEnum'; import { ScenarioAddStepActionType, ScenarioStepRefType, ScenarioStepType } from '@/enums/apiEnum';
import useCreateActions from './useCreateActions'; 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'; import { DropdownPosition } from '@arco-design/web-vue/es/dropdown/interface';
const props = defineProps<{ const props = defineProps<{
@ -90,7 +94,7 @@
default: undefined, default: undefined,
}); });
const { handleCreateStep } = useCreateActions(); const { handleCreateStep, buildInsertStepInfos } = useCreateActions();
/** /**
* 处理创建步骤操作 * 处理创建步骤操作
@ -112,15 +116,15 @@
selectedKeys.value selectedKeys.value
); );
} else { } else {
steps.value.push({ steps.value.push(
...cloneDeep(defaultStepItemCommon), buildInsertStepInfos(
id: getGenerateId(), [cloneDeep(defaultStepItemCommon)],
sort: steps.value.length + 1, ScenarioStepType.LOOP_CONTROLLER,
stepType: ScenarioStepType.LOOP_CONTROLLER, ScenarioStepRefType.DIRECT,
refType: ScenarioStepRefType.DIRECT, steps.value.length + 1,
name: t('apiScenario.loopControl'), appStore.currentProjectId
projectId: appStore.currentProjectId, )[0]
}); );
} }
break; break;
case ScenarioAddStepActionType.CONDITION_CONTROL: case ScenarioAddStepActionType.CONDITION_CONTROL:
@ -137,15 +141,15 @@
selectedKeys.value selectedKeys.value
); );
} else { } else {
steps.value.push({ steps.value.push(
...cloneDeep(defaultStepItemCommon), buildInsertStepInfos(
id: getGenerateId(), [cloneDeep(defaultStepItemCommon)],
sort: steps.value.length + 1, ScenarioStepType.IF_CONTROLLER,
stepType: ScenarioStepType.IF_CONTROLLER, ScenarioStepRefType.DIRECT,
refType: ScenarioStepRefType.DIRECT, steps.value.length + 1,
name: t('apiScenario.conditionControl'), appStore.currentProjectId
projectId: appStore.currentProjectId, )[0]
}); );
} }
break; break;
case ScenarioAddStepActionType.ONLY_ONCE_CONTROL: case ScenarioAddStepActionType.ONLY_ONCE_CONTROL:
@ -162,15 +166,15 @@
selectedKeys.value selectedKeys.value
); );
} else { } else {
steps.value.push({ steps.value.push(
...cloneDeep(defaultStepItemCommon), buildInsertStepInfos(
id: getGenerateId(), [cloneDeep(defaultStepItemCommon)],
sort: steps.value.length + 1, ScenarioStepType.ONCE_ONLY_CONTROLLER,
stepType: ScenarioStepType.ONCE_ONLY_CONTROLLER, ScenarioStepRefType.DIRECT,
refType: ScenarioStepRefType.DIRECT, steps.value.length + 1,
name: t('apiScenario.onlyOnceControl'), appStore.currentProjectId
projectId: appStore.currentProjectId, )[0]
}); );
} }
break; break;
case ScenarioAddStepActionType.WAIT_TIME: case ScenarioAddStepActionType.WAIT_TIME:
@ -187,15 +191,15 @@
selectedKeys.value selectedKeys.value
); );
} else { } else {
steps.value.push({ steps.value.push(
...cloneDeep(defaultStepItemCommon), buildInsertStepInfos(
id: getGenerateId(), [cloneDeep(defaultStepItemCommon)],
sort: steps.value.length + 1, ScenarioStepType.CONSTANT_TIMER,
stepType: ScenarioStepType.CONSTANT_TIMER, ScenarioStepRefType.DIRECT,
refType: ScenarioStepRefType.DIRECT, steps.value.length + 1,
name: t('apiScenario.waitTime'), appStore.currentProjectId
projectId: appStore.currentProjectId, )[0]
}); );
} }
break; break;
case ScenarioAddStepActionType.IMPORT_SYSTEM_API: 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 { CreateStepAction, ScenarioStepItem } from '@/models/apiTest/scenario';
import { ScenarioStepRefType, ScenarioStepType } from '@/enums/apiEnum'; import { ScenarioStepRefType, ScenarioStepType } from '@/enums/apiEnum';
import { defaultStepItemCommon } from '../../config'; import { defaultConditionController, defaultLoopController, defaultStepItemCommon } from '../../config';
export default function useCreateActions() { export default function useCreateActions() {
const { t } = useI18n(); const { t } = useI18n();
@ -48,7 +48,6 @@ export default function useCreateActions() {
id: getGenerateId(), id: getGenerateId(),
...defaultStepInfo, ...defaultStepInfo,
}; };
console.log('newStep', newStep);
insertNodes<ScenarioStepItem>( insertNodes<ScenarioStepItem>(
step.parent?.children || steps, step.parent?.children || steps,
step.id, step.id,
@ -70,7 +69,6 @@ export default function useCreateActions() {
stepType: ScenarioStepType, stepType: ScenarioStepType,
refType: ScenarioStepRefType, refType: ScenarioStepRefType,
startOrder: number, startOrder: number,
stepDetails: Record<string, any>,
projectId: string projectId: string
): ScenarioStepItem[] { ): ScenarioStepItem[] {
let name: string; let name: string;
@ -98,15 +96,40 @@ export default function useCreateActions() {
} }
return newSteps.map((item, index) => { return newSteps.map((item, index) => {
const id = getGenerateId(); 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 { return {
...cloneDeep(defaultStepItemCommon), ...cloneDeep(defaultStepItemCommon),
...item, ...item,
id, id,
config: {
...defaultStepItemCommon.config,
...config,
},
stepType, stepType,
refType, refType,
resourceId: item.id, ...resourceField,
resourceName: item.name,
name: name || item.name, name: name || item.name,
sort: startOrder + index, sort: startOrder + index,
projectId, projectId,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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