diff --git a/frontend/src/assets/style/arco-reset.less b/frontend/src/assets/style/arco-reset.less index 1a6dcd0c5f..941d243194 100644 --- a/frontend/src/assets/style/arco-reset.less +++ b/frontend/src/assets/style/arco-reset.less @@ -443,16 +443,16 @@ height: 14px; } } + margin-right: 24px; } -.arco-form-item{ +.arco-form-item { margin-bottom: 16px; } .arco-icon-hover.arco-radio-icon-hover::before { width: 16px; height: 16px; } - .arco-radio-checked:not(.arco-radio-disabled) { .arco-radio-icon { @apply !bg-white; @@ -653,6 +653,12 @@ .arco-switch { margin-left: 2px; // 避免开关圆形左边被遮挡 } +.arco-switch-type-circle { + background-color: var(--color-text-brand) !important; +} +.arco-switch-type-circle.arco-switch-checked { + background-color: rgb(var(--primary-5)) !important; +} .arco-switch-type-line.arco-switch-small { .arco-switch-handle { width: 14px; @@ -661,7 +667,7 @@ } .arco-switch-type-line.arco-switch-small.arco-switch-checked { .arco-switch-handle { - left: calc(100% - 14px - 0px); + left: calc(100% - 14px); } } diff --git a/frontend/src/components/business/ms-tree/index.vue b/frontend/src/components/business/ms-tree/index.vue index 4e340c30b3..f21ca08ef7 100644 --- a/frontend/src/components/business/ms-tree/index.vue +++ b/frontend/src/components/business/ms-tree/index.vue @@ -1,13 +1,13 @@ @@ -16,7 +16,7 @@ import { ScenarioStepType } from '@/enums/apiEnum'; const props = defineProps<{ - status: ScenarioStepType; + type: ScenarioStepType; }>(); const { t } = useI18n(); @@ -37,8 +37,8 @@ [ScenarioStepType.CUSTOM_API]: { label: 'apiScenario.customApi', color: 'rgb(var(--link-4))' }, }; - const status = computed(() => { - const config = scenarioStepMap[props.status]; + const type = computed(() => { + const config = scenarioStepMap[props.type]; return { border: `1px solid ${config?.color}`, color: config?.color, diff --git a/frontend/src/views/api-test/scenario/components/config.ts b/frontend/src/views/api-test/scenario/components/config.ts new file mode 100644 index 0000000000..afa160e7f6 --- /dev/null +++ b/frontend/src/views/api-test/scenario/components/config.ts @@ -0,0 +1,7 @@ +export const defaultStepItemCommon = { + checked: false, + expanded: false, + enabled: true, +}; + +export default {}; diff --git a/frontend/src/views/api-test/scenario/components/step/index.vue b/frontend/src/views/api-test/scenario/components/step/index.vue index 86186bcc62..d20b5de510 100644 --- a/frontend/src/views/api-test/scenario/components/step/index.vue +++ b/frontend/src/views/api-test/scenario/components/step/index.vue @@ -61,13 +61,11 @@
-
- +
@@ -96,7 +100,7 @@ import { useI18n } from '@/hooks/useI18n'; - import { ScenarioExecuteStatus, ScenarioStepType } from '@/enums/apiEnum'; + import { RequestMethods, ScenarioExecuteStatus, ScenarioStepType } from '@/enums/apiEnum'; export interface ScenarioStepInfo { id: string | number; @@ -125,16 +129,20 @@ id: 1, order: 1, checked: false, - type: ScenarioStepType.CUSTOM_API, + expanded: false, + enabled: true, + type: ScenarioStepType.QUOTE_API, name: 'API1', description: 'API1描述', - status: ScenarioExecuteStatus.SUCCESS, + method: RequestMethods.GET, children: [ { id: 11, order: 1, checked: false, - type: ScenarioStepType.CUSTOM_API, + expanded: false, + enabled: true, + type: ScenarioStepType.QUOTE_CASE, name: 'API11', description: 'API11描述', status: ScenarioExecuteStatus.SUCCESS, @@ -143,7 +151,9 @@ id: 12, order: 2, checked: false, - type: ScenarioStepType.CUSTOM_API, + expanded: false, + enabled: true, + type: ScenarioStepType.QUOTE_SCENARIO, name: 'API12', description: 'API12描述', status: ScenarioExecuteStatus.SUCCESS, @@ -154,7 +164,20 @@ id: 2, order: 2, checked: false, - type: ScenarioStepType.CUSTOM_API, + expanded: false, + enabled: true, + type: ScenarioStepType.LOOP_CONTROL, + name: 'API1', + description: 'API1描述', + status: ScenarioExecuteStatus.SUCCESS, + }, + { + id: 3, + order: 3, + checked: false, + expanded: false, + enabled: true, + type: ScenarioStepType.ONLY_ONCE_CONTROL, name: 'API1', description: 'API1描述', status: ScenarioExecuteStatus.SUCCESS, @@ -215,10 +238,6 @@ function refreshStepInfo() { console.log('刷新步骤信息'); } - - function searchStep(val: string) { - stepInfo.value.steps = stepInfo.value.steps.filter((item) => item.name.includes(val)); - } diff --git a/frontend/src/views/api-test/scenario/components/step/stepNodeComposition/customApiContent.vue b/frontend/src/views/api-test/scenario/components/step/stepNodeComposition/customApiContent.vue new file mode 100644 index 0000000000..81fa7d251f --- /dev/null +++ b/frontend/src/views/api-test/scenario/components/step/stepNodeComposition/customApiContent.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/frontend/src/views/api-test/scenario/components/step/stepNodeComposition/loopContent.vue b/frontend/src/views/api-test/scenario/components/step/stepNodeComposition/loopContent.vue new file mode 100644 index 0000000000..f324cc72d3 --- /dev/null +++ b/frontend/src/views/api-test/scenario/components/step/stepNodeComposition/loopContent.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/frontend/src/views/api-test/scenario/components/step/stepNodeComposition/onlyOnceContent.vue b/frontend/src/views/api-test/scenario/components/step/stepNodeComposition/onlyOnceContent.vue new file mode 100644 index 0000000000..2d1b5c8427 --- /dev/null +++ b/frontend/src/views/api-test/scenario/components/step/stepNodeComposition/onlyOnceContent.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/frontend/src/views/api-test/scenario/components/step/stepNodeComposition/quoteContent.vue b/frontend/src/views/api-test/scenario/components/step/stepNodeComposition/quoteContent.vue new file mode 100644 index 0000000000..47ffeda710 --- /dev/null +++ b/frontend/src/views/api-test/scenario/components/step/stepNodeComposition/quoteContent.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/frontend/src/views/api-test/scenario/components/step/stepNodeComposition/waitTimeContent.vue b/frontend/src/views/api-test/scenario/components/step/stepNodeComposition/waitTimeContent.vue new file mode 100644 index 0000000000..f967078ee7 --- /dev/null +++ b/frontend/src/views/api-test/scenario/components/step/stepNodeComposition/waitTimeContent.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/frontend/src/views/api-test/scenario/components/step/stepTree.vue b/frontend/src/views/api-test/scenario/components/step/stepTree.vue index 76fc91e72c..7ba948f7e5 100644 --- a/frontend/src/views/api-test/scenario/components/step/stepTree.vue +++ b/frontend/src/views/api-test/scenario/components/step/stepTree.vue @@ -1,50 +1,120 @@ @@ -65,36 +137,58 @@ import MsIcon from '@/components/pure/ms-icon-font/index.vue'; import { ActionsItem } from '@/components/pure/ms-table-more-action/types'; import MsTree from '@/components/business/ms-tree/index.vue'; - import { MsTreeNodeData } from '@/components/business/ms-tree/types'; + import { MsTreeExpandedData, MsTreeNodeData } from '@/components/business/ms-tree/types'; + import customApiDrawer from '../common/customApiDrawer.vue'; import executeStatus from '../common/executeStatus.vue'; import importApiDrawer from '../common/importApiDrawer/index.vue'; + import scriptOperationDrawer from '../common/scriptOperationDrawer.vue'; import stepType from '../common/stepType.vue'; import actionDropdown from './actionDropdown.vue'; + import conditionContent from './stepNodeComposition/conditionContent.vue'; + import customApiContent from './stepNodeComposition/customApiContent.vue'; + import loopControlContent from './stepNodeComposition/loopContent.vue'; + import onlyOnceControlContent from './stepNodeComposition/onlyOnceContent.vue'; + import quoteContent from './stepNodeComposition/quoteContent.vue'; + import waitTimeContent from './stepNodeComposition/waitTimeContent.vue'; + import apiMethodName from '@/views/api-test/components/apiMethodName.vue'; import { useI18n } from '@/hooks/useI18n'; + import { findNodeByKey } from '@/utils'; - import { ScenarioAddStepActionType, ScenarioExecuteStatus, ScenarioStepType } from '@/enums/apiEnum'; + import { RequestMethods, ScenarioAddStepActionType, ScenarioExecuteStatus, ScenarioStepType } from '@/enums/apiEnum'; + + import { defaultStepItemCommon } from '../config'; export interface ScenarioStepItem { id: string | number; order: number; - checked: boolean; + enabled: boolean; // 是否启用 type: ScenarioStepType; name: string; description: string; - status: ScenarioExecuteStatus; + method?: RequestMethods; + status?: ScenarioExecuteStatus; + projectId?: string; children?: ScenarioStepItem[]; + // 页面渲染以及交互需要字段 + checked: boolean; // 是否选中 + expanded: boolean; // 是否展开 } const props = defineProps<{ - steps: ScenarioStepItem[]; + stepKeyword: string; + expandAll?: boolean; }>(); const { t } = useI18n(); + const steps = defineModel('steps', { + required: true, + }); const checkedKeys = defineModel('checkedKeys', { required: true, }); + const treeRef = ref>(); const focusStepKey = ref(''); // 聚焦的key const stepMoreActions: ActionsItem[] = [ @@ -117,21 +211,78 @@ }, ]; + function getStepContent(step: ScenarioStepItem) { + switch (step.type) { + case ScenarioStepType.QUOTE_API: + case ScenarioStepType.QUOTE_CASE: + case ScenarioStepType.QUOTE_SCENARIO: + return quoteContent; + case ScenarioStepType.CUSTOM_API: + return customApiContent; + case ScenarioStepType.LOOP_CONTROL: + return loopControlContent; + case ScenarioStepType.CONDITION_CONTROL: + return conditionContent; + case ScenarioStepType.ONLY_ONCE_CONTROL: + return onlyOnceControlContent; + case ScenarioStepType.WAIT_TIME: + return waitTimeContent; + default: + return ''; + } + } + function setFocusNodeKey(node: MsTreeNodeData) { focusStepKey.value = node.id || ''; } - function toggleNodeExpand(node: MsTreeNodeData) { - if (node.id) { - treeRef.value?.expandNode(node.id, !node.expanded); - } + function checkStepIsApi(step: ScenarioStepItem) { + return [ScenarioStepType.QUOTE_API, ScenarioStepType.COPY_API, ScenarioStepType.CUSTOM_API].includes(step.type); } function checkAll(val: boolean) { treeRef.value?.checkAll(val); } + /** + * 处理步骤名称编辑 + */ + const showStepNameEditInputStepId = ref(''); + const tempStepName = ref(''); + function handleStepNameClick(step: ScenarioStepItem) { + tempStepName.value = step.name; + showStepNameEditInputStepId.value = step.id; + nextTick(() => { + const input = treeRef.value?.$el.querySelector('.arco-input') as HTMLInputElement; + input?.focus(); + }); + } + + function applyStepChange(step: ScenarioStepItem) { + const realStep = findNodeByKey(steps.value, step.id, 'id'); + if (realStep) { + realStep.name = tempStepName.value; + } + showStepNameEditInputStepId.value = ''; + } + + /** + * 处理步骤展开折叠 + */ + function handleStepExpand(data: MsTreeExpandedData) { + const realStep = findNodeByKey(steps.value, data.node?.id, 'id'); + if (realStep) { + realStep.expanded = !realStep.expanded; + } + } + + function executeStep(node: MsTreeNodeData) { + console.log('执行步骤', node); + } + const importApiDrawerVisible = ref(false); + const customApiDrawerVisible = ref(false); + const scriptOperationDrawerVisible = ref(false); function handleActionSelect(val: ScenarioAddStepActionType) { switch (val) { @@ -139,22 +290,50 @@ importApiDrawerVisible.value = true; break; case ScenarioAddStepActionType.CUSTOM_API: - console.log('自定义API'); + customApiDrawerVisible.value = true; break; case ScenarioAddStepActionType.LOOP_CONTROL: - console.log('循环控制'); + steps.value.push({ + ...defaultStepItemCommon, + id: Date.now(), + order: steps.value.length + 1, + type: ScenarioStepType.LOOP_CONTROL, + name: '循环控制', + description: '循环控制描述', + }); break; case ScenarioAddStepActionType.CONDITION_CONTROL: - console.log('条件控制'); + steps.value.push({ + ...defaultStepItemCommon, + id: Date.now(), + order: steps.value.length + 1, + type: ScenarioStepType.CONDITION_CONTROL, + name: '条件控制', + description: '条件控制描述', + }); break; case ScenarioAddStepActionType.ONLY_ONCE_CONTROL: - console.log('仅执行一次'); + steps.value.push({ + ...defaultStepItemCommon, + id: Date.now(), + order: steps.value.length + 1, + type: ScenarioStepType.ONLY_ONCE_CONTROL, + name: '仅执行一次', + description: '仅执行一次描述', + }); break; case ScenarioAddStepActionType.SCRIPT_OPERATION: - console.log('脚本操作'); + scriptOperationDrawerVisible.value = true; break; case ScenarioAddStepActionType.WAIT_TIME: - console.log('等待时间'); + steps.value.push({ + ...defaultStepItemCommon, + id: Date.now(), + order: steps.value.length + 1, + type: ScenarioStepType.WAIT_TIME, + name: '等待时间', + description: '等待时间描述', + }); break; default: break; @@ -180,7 +359,7 @@ background-color: rgb(var(--primary-1)); } } - // 循环生成树的左边距样式 + // 循环生成树的左边距样式 TODO:transform性能更高以及保留步骤完整宽度,需要加横向滚动 .loop-levels(@index, @max) when (@index <= @max) { :deep(.arco-tree-node[data-level='@{index}']) { margin-left: @index * 32px; @@ -189,7 +368,7 @@ } .loop-levels(0, 99); // 最大层级 :deep(.arco-tree-node) { - padding: 7px 8px; + padding: 0 8px; border: 1px solid var(--color-text-n8); border-radius: var(--border-radius-medium) !important; &:not(:first-child) { @@ -202,19 +381,41 @@ } } .arco-tree-node-title { + @apply !cursor-pointer; + + padding: 12px 4px; &:hover { background-color: var(--color-text-n9) !important; } - .step-node-first { - @apply flex items-center; + .step-node-content { + @apply flex w-full flex-1 items-center; gap: 8px; + margin-right: 6px; + } + .step-name-container { + @apply flex items-center; + + margin-right: 16px; + &:hover { + .edit-script-name-icon { + @apply visible; + } + } + .edit-script-name-icon { + @apply invisible cursor-pointer; + + color: rgb(var(--primary-5)); + } } &[draggable='true']:hover { - .step-node-first { + .step-node-content { padding-left: 20px; } } + .arco-tree-node-title-text { + @apply flex-1; + } } .arco-tree-node-indent { @apply hidden; @@ -225,7 +426,7 @@ .arco-tree-node-drag-icon { @apply ml-0; - top: 6px; + top: 13px; left: 24px; width: 16px; height: 16px; diff --git a/frontend/src/views/api-test/scenario/create/index.vue b/frontend/src/views/api-test/scenario/create/index.vue index 87bba616ae..70a3599196 100644 --- a/frontend/src/views/api-test/scenario/create/index.vue +++ b/frontend/src/views/api-test/scenario/create/index.vue @@ -154,6 +154,9 @@