feat(接口场景): 场景步骤 40%

This commit is contained in:
baiqi 2024-03-20 21:21:47 +08:00 committed by Craftsman
parent 04b10fb846
commit abe982852a
8 changed files with 277 additions and 152 deletions

View File

@ -498,7 +498,6 @@
import { FormInstance, Message, SelectOptionData } from '@arco-design/web-vue';
import { cloneDeep, debounce } from 'lodash-es';
import { statusCodeOptions } from '@/components/pure/ms-advance-filter/index';
import { TabItem } from '@/components/pure/ms-editable-tab/types';
import MsFormCreate from '@/components/pure/ms-form-create/formCreate.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';

View File

@ -25,12 +25,12 @@
<span
v-show="requestVModel.useEnv === 'false'"
style="
float: left;
margin-top: 8px;
margin-right: 16px;
font-size: 14px;
font-weight: 400;
line-height: 22px;
margin-right: 16px;
margin-top: 8px;
float: left;
"
>{{ t('apiScenario.env', { name: props.envDetailItem?.name }) }}
</span>
@ -237,7 +237,6 @@
v-show="showResponse"
v-model:active-layout="activeLayout"
v-model:active-tab="requestVModel.responseActiveTab"
v-model:response-definition="requestVModel.responseDefinition"
:is-http-protocol="isHttpProtocol"
:is-priority-local-exec="isPriorityLocalExec"
:request-url="requestVModel.url"
@ -314,7 +313,6 @@
defaultRequestParamsItem,
defaultResponse,
} from '@/views/api-test/components/config';
import type { ResponseItem } from '@/views/api-test/components/requestComposition/response/edit.vue';
import { filterKeyValParams, parseRequestBodyFiles } from '@/views/api-test/components/utils';
import type { Api } from '@form-create/arco-design';
@ -330,27 +328,25 @@
);
export interface RequestCustomAttr {
type: 'api' | 'case' | 'mock' | 'doc'; // tab api
type: 'api';
isNew: boolean;
protocol: string;
activeTab: RequestComposition;
mode?: 'definition' | 'debug'; // / tab
mode?: 'debug';
executeLoading: boolean; // loading
isCopy?: boolean; //
isExecute?: boolean; //
}
export type RequestParam = ExecuteApiRequestFullParams & {
responseDefinition?: ResponseItem[];
response?: RequestTaskResult;
useEnv: string;
} & RequestCustomAttr &
TabItem;
const props = defineProps<{
request?: CustomApiStep; //
request?: RequestParam; //
detailLoading?: boolean; //
envDetailItem?: {
id?: string;
projectId: string;
@ -369,7 +365,9 @@
};
}>();
const emit = defineEmits(['addStep']);
const emit = defineEmits<{
(e: 'addStep', request: RequestParam): void;
}>();
const appStore = useAppStore();
const { t } = useI18n();
@ -439,7 +437,7 @@
executeLoading: false,
};
const requestVModel = ref<RequestParam>(defaultDebugParams);
const requestVModel = ref<RequestParam>(props.request || defaultDebugParams);
const isHttpProtocol = computed(() => requestVModel.value.protocol === 'HTTP');
const temporaryResponseMap = {}; // websockettab
@ -964,11 +962,8 @@
requestVModel.value.executeLoading = false;
}
const step = ref<CustomApiStep>();
function handleContinue() {
step.value = { ...requestVModel.value };
emit('addStep', step.value);
emit('addStep', requestVModel.value);
requestVModel.value = { ...defaultDebugParams };
}
@ -984,7 +979,6 @@
watch(
() => visible.value,
async (val) => {
step.value = { ...defaultDebugParams };
if (val) {
await initProtocolList();
if (props.request) {
@ -1003,7 +997,6 @@
<style lang="less" scoped>
.exec-btn {
margin-right: 12px;
:deep(.arco-btn) {
color: white !important;
background-color: rgb(var(--primary-5)) !important;
@ -1012,32 +1005,26 @@
.btn-base-primary-disabled();
}
}
.tab-pane-container {
@apply flex-1 overflow-y-auto;
.ms-scroll-bar();
}
:deep(.no-content) {
.arco-tabs-content {
display: none;
}
}
:deep(.arco-tabs-tab:first-child) {
margin-left: 0;
}
:deep(.arco-tabs-tab) {
@apply leading-none;
}
.hidden-second {
:deep(.arco-split-trigger) {
@apply hidden;
}
}
.show-second {
:deep(.arco-split-trigger) {
@apply block;

View File

@ -42,17 +42,23 @@
import { useI18n } from '@/hooks/useI18n';
import { ExecuteConditionProcessor, ScriptProcessor } from '@/models/apiTest/common';
import { ExecuteConditionProcessor } from '@/models/apiTest/common';
import { RequestConditionProcessor } from '@/enums/apiEnum';
const scriptName = ref('');
const activeItem = ref({
const props = defineProps<{
script?: ExecuteConditionProcessor;
name?: string;
}>();
const defaultScript = {
processorType: RequestConditionProcessor.SCRIPT,
enableCommonScript: false,
script: '',
scriptLanguage: LanguageEnum.BEANSHELL,
commonScriptInfo: {},
} as ExecuteConditionProcessor);
} as ExecuteConditionProcessor;
const scriptName = ref(props.name || '');
const activeItem = ref(props.script || defaultScript);
const { t } = useI18n();
@ -79,12 +85,12 @@
}
function saveAndContinue() {
emit('save', scriptName.value, activeItem.value as ScriptProcessor);
emit('save', scriptName.value, activeItem.value);
resetField();
}
function save() {
emit('save', scriptName.value, activeItem.value as ScriptProcessor);
emit('save', scriptName.value, activeItem.value);
resetField();
visible.value = false;
}

View File

@ -17,6 +17,7 @@ export const defaultStepItemCommon = {
expression: '',
waitTime: 0,
description: '',
createActionsVisible: false,
};
export const conditionOptions = [

View File

@ -48,11 +48,12 @@
import { ScenarioStepItem } from '../stepTree.vue';
import { useI18n } from '@/hooks/useI18n';
import { findNodeByKey, getGenerateId, insertNode, TreeNode } from '@/utils';
import { findNodeByKey, getGenerateId } from '@/utils';
import { CreateStepAction } from '@/models/apiTest/scenario';
import { ScenarioAddStepActionType, ScenarioStepType } from '@/enums/apiEnum';
import useCreateActions from './useCreateActions';
import { defaultStepItemCommon } from '@/views/api-test/scenario/components/config';
import { DropdownPosition } from '@arco-design/web-vue/es/dropdown/interface';
@ -88,79 +89,7 @@
default: undefined,
});
/**
* 增加步骤时判断父节点是否选中如果选中则需要把新节点也选中
*/
function isParentSelected(parent?: TreeNode<ScenarioStepItem>) {
if (parent && selectedKeys.value.includes(parent.id)) {
//
selectedKeys.value.push(step.value.id);
}
}
/**
* 处理添加子步骤插入步骤前/后操作
*/
function handleCreateStep(defaultStepInfo: ScenarioStepItem) {
switch (props.createStepAction) {
case 'addChildStep':
const id = getGenerateId();
if (step.value?.children) {
step.value.children.push({
...cloneDeep(defaultStepItemCommon),
...defaultStepInfo,
id,
order: step.value.children.length + 1,
});
} else {
step.value.children = [
{
...cloneDeep(defaultStepItemCommon),
...defaultStepInfo,
id,
order: 1,
},
];
}
if (selectedKeys.value.includes(step.value.id)) {
//
selectedKeys.value.push(id);
}
break;
case 'insertBefore':
insertNode<ScenarioStepItem>(
step.value.children || steps.value,
step.value.id,
{
...cloneDeep(defaultStepItemCommon),
...defaultStepInfo,
id: getGenerateId(),
order: step.value.order,
},
'before',
isParentSelected,
'id'
);
break;
case 'insertAfter':
insertNode<ScenarioStepItem>(
step.value.children || steps.value,
step.value.id,
{
...cloneDeep(defaultStepItemCommon),
...defaultStepInfo,
id: getGenerateId(),
order: step.value.order + 1,
},
'after',
isParentSelected,
'id'
);
break;
default:
break;
}
}
const { handleCreateStep } = useCreateActions();
/**
* 处理创建步骤操作
@ -170,10 +99,15 @@
switch (val) {
case ScenarioAddStepActionType.LOOP_CONTROL:
if (step.value) {
handleCreateStep({
type: ScenarioStepType.LOOP_CONTROL,
name: t('apiScenario.loopControl'),
} as ScenarioStepItem);
handleCreateStep(
{
type: ScenarioStepType.LOOP_CONTROL,
name: t('apiScenario.loopControl'),
} as ScenarioStepItem,
step.value,
props.createStepAction,
selectedKeys.value
);
} else {
steps.value.push({
...cloneDeep(defaultStepItemCommon),
@ -186,10 +120,15 @@
break;
case ScenarioAddStepActionType.CONDITION_CONTROL:
if (step.value) {
handleCreateStep({
type: ScenarioStepType.CONDITION_CONTROL,
name: t('apiScenario.conditionControl'),
} as ScenarioStepItem);
handleCreateStep(
{
type: ScenarioStepType.CONDITION_CONTROL,
name: t('apiScenario.conditionControl'),
} as ScenarioStepItem,
step.value,
props.createStepAction,
selectedKeys.value
);
} else {
steps.value.push({
...cloneDeep(defaultStepItemCommon),
@ -202,10 +141,15 @@
break;
case ScenarioAddStepActionType.ONLY_ONCE_CONTROL:
if (step.value) {
handleCreateStep({
type: ScenarioStepType.ONLY_ONCE_CONTROL,
name: t('apiScenario.onlyOnceControl'),
} as ScenarioStepItem);
handleCreateStep(
{
type: ScenarioStepType.ONLY_ONCE_CONTROL,
name: t('apiScenario.onlyOnceControl'),
} as ScenarioStepItem,
step.value,
props.createStepAction,
selectedKeys.value
);
} else {
steps.value.push({
...cloneDeep(defaultStepItemCommon),
@ -218,10 +162,15 @@
break;
case ScenarioAddStepActionType.WAIT_TIME:
if (step.value) {
handleCreateStep({
type: ScenarioStepType.WAIT_TIME,
name: t('apiScenario.waitTime'),
} as ScenarioStepItem);
handleCreateStep(
{
type: ScenarioStepType.WAIT_TIME,
name: t('apiScenario.waitTime'),
} as ScenarioStepItem,
step.value,
props.createStepAction,
selectedKeys.value
);
} else {
steps.value.push({
...cloneDeep(defaultStepItemCommon),
@ -248,7 +197,7 @@
break;
}
if (step.value) {
document.getElementById(step.value.id.toString())?.click();
emit('close');
}
}

View File

@ -11,15 +11,15 @@
</MsButton>
<template #content>
<createStepActions
v-model:visible="innerStep.actionDropdownVisible"
v-model:visible="innerStep.createActionsVisible"
v-model:selected-keys="selectedKeys"
v-model:steps="steps"
v-model:step="innerStep"
:create-step-action="activeCreateAction"
position="br"
:popup-translate="[-7, -10]"
@other-create="(type, step) => emit('otherCreate', type, step)"
@close="emit('close')"
@other-create="(type, step) => emit('otherCreate', type, step, activeCreateAction)"
@close="handleActionsClose"
>
<span></span>
</createStepActions>
@ -83,7 +83,8 @@
| ScenarioAddStepActionType.IMPORT_SYSTEM_API
| ScenarioAddStepActionType.CUSTOM_API
| ScenarioAddStepActionType.SCRIPT_OPERATION,
step?: ScenarioStepItem
step?: ScenarioStepItem,
activeCreateAction?: CreateStepAction
);
}>();
@ -115,18 +116,23 @@
const activeCreateAction = ref<CreateStepAction>();
function handleTriggerActionClick(action: CreateStepAction) {
innerStep.value.actionDropdownVisible = true;
innerStep.value.createActionsVisible = true;
activeCreateAction.value = action;
}
function handleActionTriggerChange(val: boolean) {
if (!val) {
// TODO:
activeCreateAction.value = undefined;
innerStep.value.actionDropdownVisible = false;
innerStep.value.createActionsVisible = false;
emit('close');
}
}
function handleActionsClose() {
activeCreateAction.value = undefined;
innerStep.value.createActionsVisible = false;
document.getElementById(innerStep.value.id.toString())?.click();
}
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,101 @@
import { cloneDeep } from 'lodash-es';
import { ScenarioStepItem } from '../stepTree.vue';
import { getGenerateId, insertNode, TreeNode } from '@/utils';
import { CreateStepAction } from '@/models/apiTest/scenario';
import { defaultStepItemCommon } from '../../config';
import steps from '@arco-design/web-vue/es/steps';
export default function useCreateActions() {
/**
*
*/
function isParentSelected(
selectedKeys: (string | number)[],
step: ScenarioStepItem,
parent?: TreeNode<ScenarioStepItem>
) {
if (parent && selectedKeys.includes(parent.id)) {
// 添加子节点时,当前节点已选中,则需要把新节点也需要选中(因为父级选中子级也会展示选中状态)
selectedKeys.push(step.id);
}
}
/**
* /
*/
function handleCreateStep(
defaultStepInfo: ScenarioStepItem,
step: ScenarioStepItem,
createStepAction: CreateStepAction,
selectedKeys: (string | number)[]
) {
switch (createStepAction) {
case 'addChildStep':
const id = getGenerateId();
if (step.children) {
step.children.push({
...cloneDeep(defaultStepItemCommon),
...defaultStepInfo,
id,
order: step.children.length + 1,
});
} else {
step.children = [
{
...cloneDeep(defaultStepItemCommon),
...defaultStepInfo,
id,
order: 1,
},
];
}
if (selectedKeys.includes(step.id)) {
// 添加子节点时,当前节点已选中,则需要把新节点也需要选中(因为父级选中子级也会展示选中状态)
selectedKeys.push(id);
}
break;
case 'insertBefore':
insertNode<ScenarioStepItem>(
step.children || steps.value,
step.id,
{
...cloneDeep(defaultStepItemCommon),
...defaultStepInfo,
id: getGenerateId(),
order: step.order,
},
'before',
(parent) => isParentSelected(selectedKeys, step, parent),
'id'
);
break;
case 'insertAfter':
insertNode<ScenarioStepItem>(
step.children || steps.value,
step.id,
{
...cloneDeep(defaultStepItemCommon),
...defaultStepInfo,
id: getGenerateId(),
order: step.order + 1,
},
'after',
(parent) => isParentSelected(selectedKeys, step, parent),
'id'
);
break;
default:
break;
}
}
return {
handleCreateStep,
isParentSelected,
};
}

View File

@ -25,7 +25,7 @@
checkable
block-node
draggable
@select="handleStepSelect"
@select="(selectedKeys, node) => handleStepSelect(selectedKeys, node as ScenarioStepItem)"
@expand="handleStepExpand"
@more-actions-close="() => setFocusNodeKey('')"
@more-action-select="handleStepMoreActionSelect"
@ -186,17 +186,21 @@
</div>
</a-button>
</createStepActions>
<!-- todo 执行上传文件转存文件等需要传入相关方法 当前场景环境使用的是假数据 add-step暂时只是将数据传递到当前组件的customDemoStep对象中用于再次打开的时候测试编辑功能 -->
<customApiDrawer
v-if="customApiDrawerVisible"
v-model:visible="customApiDrawerVisible"
:env-detail-item="{ id: 'demp-id-112233', projectId: '123456', name: 'demo环境' }"
:request="customDemoStep"
:request="activeStep?.request"
@add-step="addCustomApiStep"
/>
<scriptOperationDrawer v-model:visible="scriptOperationDrawerVisible" />
<importApiDrawer v-if="importApiDrawerVisible" v-model:visible="importApiDrawerVisible" />
<scriptOperationDrawer v-if="scriptOperationDrawerVisible" v-model:visible="scriptOperationDrawerVisible" />
<scriptOperationDrawer
v-if="scriptOperationDrawerVisible"
v-model:visible="scriptOperationDrawerVisible"
:script="activeStep?.script"
:name="activeStep?.name"
@save="addScriptStep"
/>
<a-modal
v-model:visible="showQuickInput"
:title="quickInputDataKey ? t(`apiScenario.${quickInputDataKey}`) : ''"
@ -252,9 +256,14 @@
import useAppStore from '@/store/modules/app';
import { deleteNode, findNodeByKey, getGenerateId, handleTreeDragDrop, insertNode, mapTree, TreeNode } from '@/utils';
import { CustomApiStep, ScenarioStepLoopWhileType } from '@/models/apiTest/scenario';
import { ExecuteConditionProcessor } from '@/models/apiTest/common';
import { CreateStepAction, ScenarioStepLoopWhileType } from '@/models/apiTest/scenario';
import { RequestMethods, ScenarioAddStepActionType, ScenarioExecuteStatus, ScenarioStepType } from '@/enums/apiEnum';
import type { RequestParam } from '../common/customApiDrawer.vue';
import useCreateActions from './createAction/useCreateActions';
import { defaultStepItemCommon } from '@/views/api-test/scenario/components/config';
//
const MsCodeEditor = defineAsyncComponent(() => import('@/components/pure/ms-code-editor/index.vue'));
const customApiDrawer = defineAsyncComponent(() => import('../common/customApiDrawer.vue'));
@ -275,11 +284,15 @@
belongProjectId?: string;
belongProjectName?: string;
children?: ScenarioStepItem[];
//
request?: RequestParam;
//
script?: ExecuteConditionProcessor;
//
// renderId: string; // id
checked: boolean; //
expanded: boolean; //
actionDropdownVisible?: boolean; //
createActionsVisible?: boolean; //
parent?: ScenarioStepItem | ScenarioStepItem[]; // undefined
loopNum: number;
loopType: 'num' | 'while' | 'forEach';
@ -543,32 +556,46 @@
}
}
function handleStepSelect(_selectedKeys: Array<string | number>, node: MsTreeNodeData) {
const importApiDrawerVisible = ref(false);
const customApiDrawerVisible = ref(false);
const scriptOperationDrawerVisible = ref(false);
const activeStep = ref<ScenarioStepItem>(); //
const activeCreateAction = ref<CreateStepAction>(); //
function handleStepSelect(_selectedKeys: Array<string | number>, step: ScenarioStepItem) {
const offspringIds: string[] = [];
mapTree(node.children || [], (e) => {
mapTree(step.children || [], (e) => {
offspringIds.push(e.id);
return e;
});
selectedKeys.value = [node.id, ...offspringIds];
selectedKeys.value = [step.id, ...offspringIds];
if (step.type === ScenarioStepType.CUSTOM_API) {
activeStep.value = step;
customApiDrawerVisible.value = true;
} else if (step.type === ScenarioStepType.SCRIPT_OPERATION) {
activeStep.value = step;
console.log('activeStep', activeStep.value);
scriptOperationDrawerVisible.value = true;
}
}
function executeStep(node: MsTreeNodeData) {
console.log('执行步骤', node);
}
const importApiDrawerVisible = ref(false);
const customApiDrawerVisible = ref(false);
const scriptOperationDrawerVisible = ref(false);
const activeStep = ref<ScenarioStepItem>(); //
/**
* 处理抽屉资源类型步骤创建动作
*/
function handleOtherCreate(
type:
| ScenarioAddStepActionType.IMPORT_SYSTEM_API
| ScenarioAddStepActionType.CUSTOM_API
| ScenarioAddStepActionType.SCRIPT_OPERATION,
step?: ScenarioStepItem
step?: ScenarioStepItem,
_activeCreateAction?: CreateStepAction
) {
activeStep.value = step;
activeCreateAction.value = _activeCreateAction;
switch (type) {
case ScenarioAddStepActionType.IMPORT_SYSTEM_API:
importApiDrawerVisible.value = true;
@ -584,6 +611,62 @@
}
}
const { handleCreateStep } = useCreateActions();
/**
* 添加自定义 API 步骤
*/
function addCustomApiStep(request: RequestParam) {
if (activeStep.value) {
handleCreateStep(
{
type: ScenarioStepType.CUSTOM_API,
name: t('apiScenario.customApi'),
request: cloneDeep(request),
} as ScenarioStepItem,
activeStep.value,
activeCreateAction.value,
selectedKeys.value
);
} else {
steps.value.push({
...cloneDeep(defaultStepItemCommon),
id: getGenerateId(),
order: steps.value.length + 1,
type: ScenarioStepType.CUSTOM_API,
name: t('apiScenario.customApi'),
request: cloneDeep(request),
} as ScenarioStepItem);
}
}
/**
* 添加脚本操作步骤
*/
function addScriptStep(name: string, scriptProcessor: ExecuteConditionProcessor) {
if (activeStep.value) {
handleCreateStep(
{
type: ScenarioStepType.SCRIPT_OPERATION,
name,
script: cloneDeep(scriptProcessor),
} as ScenarioStepItem,
activeStep.value,
activeCreateAction.value,
selectedKeys.value
);
console.log('activeStep', activeStep.value);
} else {
steps.value.push({
...cloneDeep(defaultStepItemCommon),
id: getGenerateId(),
order: steps.value.length + 1,
type: ScenarioStepType.SCRIPT_OPERATION,
name,
script: cloneDeep(scriptProcessor),
} as ScenarioStepItem);
}
}
/**
* 处理文件夹树节点拖拽事件
* @param tree 树数据
@ -626,13 +709,6 @@
}
}
const customDemoStep = ref<CustomApiStep>();
function addCustomApiStep(step: CustomApiStep) {
// todo: api
customDemoStep.value = { ...step };
}
const showQuickInput = ref(false);
const quickInputParamValue = ref('');
const quickInputDataKey = ref('');