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

This commit is contained in:
baiqi 2024-03-18 21:07:26 +08:00 committed by Craftsman
parent 393bcabc3e
commit 488a93ea2c
12 changed files with 493 additions and 86 deletions

View File

@ -167,11 +167,11 @@
</a-form>
<div
v-if="paramSettingType === 'mock' && paramForm.type !== ''"
class="mb-[16px] flex items-center gap-[16px] bg-[var(--color-text-n9)] p-[5px_8px]"
class="mb-[16px] flex items-baseline gap-[16px] overflow-hidden bg-[var(--color-text-n9)] p-[5px_8px]"
>
<div class="text-[var(--color-text-3)]">{{ t('ms.paramsInput.preview') }}</div>
<a-spin :loading="previewLoading" class="flex gap-[8px]">
<div class="text-[var(--color-text-1)]">{{ paramPreview }}</div>
<div class="break-all text-[var(--color-text-3)]">{{ t('ms.paramsInput.preview') }}</div>
<a-spin :loading="previewLoading" class="flex flex-1 flex-wrap gap-[8px]">
<div class="param-preview">{{ paramPreview }}</div>
<MsButton type="text" @click="getMockValue">{{ t('ms.paramsInput.previewClick') }}</MsButton>
</a-spin>
</div>
@ -625,6 +625,13 @@
overflow-y: auto;
margin-right: -6px;
max-height: 400px;
.ms-params-input-setting-trigger-content-scroll-preview {
@apply w-full overflow-y-auto overflow-x-hidden break-all;
.ms-scroll-bar();
max-height: 100px;
color: var(--color-text-1);
}
}
}
}

View File

@ -24,13 +24,15 @@
:position="props.titleTooltipPosition"
:disabled="props.disabledTitleTooltip"
>
<slot name="title" v-bind="_props"></slot>
<span :class="props.titleClass || 'ms-tree-node-title'">
<slot name="title" v-bind="_props"></slot>
</span>
</a-tooltip>
</template>
<template v-if="$slots['drag-icon']" #drag-icon="_props">
<slot name="title" v-bind="_props"></slot>
</template>
<template v-if="$slots['extra']" #extra="_props">
<template v-if="$slots['extra'] || props.nodeMoreActions" #extra="_props">
<div
v-if="_props.hideMoreAction !== true"
:class="[
@ -91,6 +93,7 @@
const props = withDefaults(
defineProps<{
keyword?: string; //
titleClass?: string; //
searchDebounce?: number; // ms
draggable?: boolean; //
blockNode?: boolean; //
@ -509,8 +512,11 @@
.arco-tree-node-title {
font-weight: 500 !important;
color: rgb(var(--primary-5));
* {
.ms-tree-node-title {
color: rgb(var(--primary-5));
* {
color: rgb(var(--primary-5));
}
}
}
}

View File

@ -33,7 +33,7 @@
type?: TagType; // tag
size?: Size; // tag
theme?: Theme; // tag
selfStyle?: any; //
selfStyle?: Record<string, any>; //
width?: number; // tag,max-width
maxWidth?: string;
noMargin?: boolean; // tag

View File

@ -345,6 +345,68 @@ export function findNodePathByKey<T>(
return null;
}
/**
*
* @param treeArr
* @param targetKey
* @param newNode
* @param position
* @param customKey key
*/
export function insertNode<T>(
treeArr: TreeNode<T>[],
targetKey: string,
newNode: TreeNode<T>,
position: 'before' | 'after',
customKey = 'key'
): void {
function insertNodeInTree(tree: TreeNode<T>[], parent?: TreeNode<T>): boolean {
for (let i = 0; i < tree.length; i++) {
const node = tree[i];
if (node[customKey] === targetKey) {
// 如果当前节点的 customKey 与目标 customKey 匹配,则在当前节点前/后插入新节点
const childrenArray = parent ? parent.children || [] : treeArr; // 父节点没有 children 属性,说明是树的第一层,使用 treeArr
const index = childrenArray.findIndex((item) => item[customKey] === node[customKey]);
if (position === 'before') {
childrenArray.splice(index, 0, newNode);
} else if (position === 'after') {
childrenArray.splice(index + 1, 0, newNode);
}
// 插入后返回 true
return true;
}
if (Array.isArray(node.children) && insertNodeInTree(node.children, node)) {
return true;
}
}
return false;
}
insertNodeInTree(treeArr);
}
/**
*
* @param treeArr
* @param targetKey
*/
export function deleteNode<T>(treeArr: TreeNode<T>[], targetKey: string, customKey = 'key'): void {
function deleteNodeInTree(tree: TreeNode<T>[]): void {
for (let i = 0; i < tree.length; i++) {
const node = tree[i];
if (node[customKey] === targetKey) {
tree.splice(i, 1); // 直接删除当前节点
return;
}
if (Array.isArray(node.children)) {
deleteNodeInTree(node.children); // 递归删除子节点
}
}
}
deleteNodeInTree(treeArr);
}
/**
*
* @param targetMap

View File

@ -1245,6 +1245,8 @@
/**
* 保存请求
* @param fullParams 保存时传入的参数
* @param silence 是否静默保存接口定义另存为用例时要先静默保存接口
*/
async function realSave(fullParams?: Record<string, any>, silence?: boolean) {
try {
@ -1278,6 +1280,7 @@
requestVModel.value.label = res.name;
requestVModel.value.url = res.path;
requestVModel.value.path = res.path;
requestVModel.value.moduleId = res.moduleId;
if (!props.isDefinition) {
saveModalVisible.value = false;
}

View File

@ -47,7 +47,7 @@
</div>
</div>
<a-divider class="my-[8px]" />
<a-spin class="h-[calc(100%-98px)] w-full" :loading="loading">
<a-spin class="max-h-[calc(100%-98px)] w-full" :loading="loading">
<MsTree
v-model:selected-keys="selectedKeys"
v-model:focus-node-key="focusNodeKey"

View File

@ -1,5 +1,11 @@
<template>
<a-dropdown class="scenario-action-dropdown" @select="(val) => emit('select', val as ScenarioAddStepActionType)">
<a-dropdown
v-model:popup-visible="visible"
:position="props.position || 'bottom'"
:popup-translate="props.popupTranslate"
class="scenario-action-dropdown"
@select="(val) => emit('select', val as ScenarioAddStepActionType)"
>
<slot></slot>
<template #content>
<a-dgroup :title="t('apiScenario.requestScenario')">
@ -35,18 +41,29 @@
</template>
<script setup lang="ts">
import { TriggerPopupTranslate } from '@arco-design/web-vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import { useI18n } from '@/hooks/useI18n';
import { ScenarioAddStepActionType } from '@/enums/apiEnum';
import { DropdownPosition } from '@arco-design/web-vue/es/dropdown/interface';
const props = defineProps<{
position?: DropdownPosition;
popupTranslate?: TriggerPopupTranslate;
}>();
const emit = defineEmits<{
(e: 'select', val: ScenarioAddStepActionType): void;
}>();
const { t } = useI18n();
const visible = defineModel<boolean>('visible', {
default: false,
});
function openTutorial() {
window.open('https://zhuanlan.zhihu.com/p/597905464?utm_id=0', '_blank');
}

View File

@ -1,5 +1,5 @@
<template>
<div class="flex flex-col gap-[16px]">
<div class="flex h-full flex-col gap-[16px]">
<div class="action-line">
<div class="action-group">
<a-checkbox
@ -79,7 +79,7 @@
</a-button>
</div>
</div>
<div>
<div class="h-[calc(100%-48px)]">
<stepTree
ref="stepTreeRef"
v-model:steps="stepInfo.steps"
@ -99,6 +99,7 @@
import stepTree, { ScenarioStepItem } from './stepTree.vue';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import { RequestMethods, ScenarioExecuteStatus, ScenarioStepType } from '@/enums/apiEnum';
@ -114,6 +115,7 @@
isNew?: boolean; //
}>();
const appStore = useAppStore();
const { t } = useI18n();
const checkedAll = ref(false); //
@ -127,6 +129,7 @@
steps: [
{
id: 1,
num: 10086,
order: 1,
checked: false,
expanded: false,
@ -135,6 +138,9 @@
name: 'API1',
description: 'API1描述',
method: RequestMethods.GET,
belongProjectId: appStore.currentProjectId,
belongProjectName: '项目名称',
actionDropdownVisible: false,
children: [
{
id: 11,
@ -146,6 +152,10 @@
name: 'API11',
description: 'API11描述',
status: ScenarioExecuteStatus.SUCCESS,
num: 100861,
belongProjectId: '989d23d23d',
belongProjectName: '项目名称1',
actionDropdownVisible: false,
},
{
id: 12,
@ -157,6 +167,10 @@
name: 'API12',
description: 'API12描述',
status: ScenarioExecuteStatus.SUCCESS,
num: 100862,
belongProjectId: '989d23d23d',
belongProjectName: '项目名称2',
actionDropdownVisible: false,
},
],
},
@ -170,6 +184,7 @@
name: 'API1',
description: 'API1描述',
status: ScenarioExecuteStatus.SUCCESS,
actionDropdownVisible: false,
},
{
id: 3,
@ -181,6 +196,7 @@
name: 'API1',
description: 'API1描述',
status: ScenarioExecuteStatus.SUCCESS,
actionDropdownVisible: false,
},
],
executeTime: '',

View File

@ -1,7 +1,86 @@
<template>
<div>quote </div>
<div class="flex items-center gap-[4px]">
<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 }}
</div>
</div>
<div>
<div class="mb-[2px] text-[var(--color-text-4)]">{{ t('apiScenario.detailName') }}</div>
<div class="cursor-pointer text-[14px] text-[rgb(var(--primary-5))]" @click="goDetail">
{{ `${props.data.num}${props.data.name}` }}
</div>
</div>
</div>
</template>
</a-popover>
<MsTag
v-if="props.data.belongProjectId !== props.data.currentProjectId"
theme="outline"
size="small"
:self-style="{
color: 'var(--color-text-4)',
border: '1px solid var(--color-text-input-border)',
backgroundColor: 'transparent',
}"
>
{{ t('apiScenario.crossProject') }}
</MsTag>
</div>
</template>
<script setup lang="ts"></script>
<script setup lang="ts">
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
<style lang="less" scoped></style>
import { useI18n } from '@/hooks/useI18n';
import useOpenNewPage from '@/hooks/useOpenNewPage';
import { ScenarioStepType } from '@/enums/apiEnum';
import { ApiTestRouteEnum } from '@/enums/routeEnum';
const props = defineProps<{
data: {
id: string | number;
belongProjectId: string;
belongProjectName: string;
num: number;
name: string;
type: ScenarioStepType;
currentProjectId: string;
};
}>();
const { t } = useI18n();
const { openNewPage } = useOpenNewPage();
function goDetail() {
switch (props.data.type) {
case ScenarioStepType.COPY_API:
case ScenarioStepType.QUOTE_API:
openNewPage(ApiTestRouteEnum.API_TEST_MANAGEMENT, { dId: props.data.id });
break;
case ScenarioStepType.QUOTE_SCENARIO:
case ScenarioStepType.COPY_SCENARIO:
openNewPage(ApiTestRouteEnum.API_TEST_SCENARIO, { sId: props.data.id });
break;
case ScenarioStepType.COPY_CASE:
case ScenarioStepType.QUOTE_CASE:
openNewPage(ApiTestRouteEnum.API_TEST_MANAGEMENT, { cId: props.data.id });
break;
default:
break;
}
}
</script>
<style lang="less" scoped>
.detail-popover {
width: 350px;
}
</style>

View File

@ -1,8 +1,9 @@
<template>
<div class="flex flex-col gap-[16px]">
<div class="max-h-[calc(100vh-305px)]">
<div class="flex h-full flex-col gap-[16px]">
<a-spin class="max-h-[calc(100%-46px)] w-full" :loading="loading">
<MsTree
ref="treeRef"
v-model:selected-keys="selectedKeys"
v-model:checked-keys="checkedKeys"
v-model:focus-node-key="focusStepKey"
v-model:data="steps"
@ -10,20 +11,23 @@
:expand-all="props.expandAll"
:node-more-actions="stepMoreActions"
:field-names="{ title: 'name', key: 'id', children: 'children' }"
:selectable="false"
:virtual-list-props="{
height: '100%',
threshold: 200,
threshold: 20,
fixedSize: true,
buffer: 15, // 10 padding
}"
title-class="step-tree-node-title"
node-highlight-background-color="var(--color-text-n9)"
action-on-node-click="expand"
disabled-title-tooltip
checkable
block-node
draggable
@select="handleStepSelect"
@expand="handleStepExpand"
@more-actions-close="() => setFocusNodeKey('')"
@more-action-select="handleStepMoreActionSelect"
>
<template #title="step">
<div class="flex w-full items-center gap-[8px]">
@ -35,16 +39,18 @@
</div>
<div class="step-node-content">
<!-- 步骤展开折叠按钮 -->
<div
<a-tooltip
v-if="step.children?.length > 0"
class="flex cursor-pointer items-center gap-[2px] text-[var(--color-text-1)]"
:content="t('apiScenario.expandStepTip', { count: step.children.length })"
>
<MsIcon
:type="step.expanded ? 'icon-icon_split_turn-down_arrow' : 'icon-icon_split-turn-down-left'"
:size="14"
/>
{{ step.children?.length || 0 }}
</div>
<div class="flex cursor-pointer items-center gap-[2px] text-[var(--color-text-1)]">
<MsIcon
:type="step.expanded ? 'icon-icon_split_turn-down_arrow' : 'icon-icon_split-turn-down-left'"
:size="14"
/>
{{ step.children?.length || 0 }}
</div>
</a-tooltip>
<div class="mr-[8px] flex items-center gap-[8px]">
<!-- 步骤启用/禁用 -->
<a-switch
@ -62,46 +68,105 @@
</div>
<!-- 步骤类型 -->
<stepType :type="step.type" />
<apiMethodName v-if="checkStepIsApi(step)" :method="step.method" />
<!-- 步骤名称 -->
<div v-if="checkStepIsApi(step)" class="relative flex flex-1 items-center">
<div
v-if="step.id === showStepNameEditInputStepId"
class="absolute left-0 top-[-2px] z-10 w-[calc(100%-24px)]"
@click.stop
>
<a-input
:id="step.id"
v-model:model-value="tempStepName"
:placeholder="t('apiScenario.pleaseInputStepName')"
:max-length="255"
size="small"
@press-enter="applyStepChange(step)"
@blur="applyStepChange(step)"
/>
</div>
<a-tooltip :content="step.name">
<div class="step-name-container">
<div class="one-line-text mr-[4px] max-w-[250px] font-medium text-[var(--color-text-1)]">
{{ step.name }}
</div>
<MsIcon
type="icon-icon_edit_outlined"
class="edit-script-name-icon"
@click.stop="handleStepNameClick(step)"
<!-- 步骤整体内容 -->
<div class="relative flex flex-1 items-center gap-[4px]">
<!-- 步骤差异内容按步骤类型展示不同组件 -->
<component :is="getStepContent(step)" :data="getStepContentData(step)" />
<!-- APICASE场景步骤名称 -->
<template v-if="checkStepIsApi(step)">
<apiMethodName v-if="checkStepShowMethod(step)" :method="step.method" />
<div
v-if="step.id === showStepNameEditInputStepId"
class="absolute left-0 top-[-2px] z-10 w-[calc(100%-24px)]"
@click.stop
>
<a-input
:id="step.id"
v-model:model-value="tempStepName"
:placeholder="t('apiScenario.pleaseInputStepName')"
:max-length="255"
size="small"
@press-enter="applyStepChange(step)"
@blur="applyStepChange(step)"
/>
</div>
</a-tooltip>
<a-tooltip :content="step.name">
<div class="step-name-container">
<div class="one-line-text mr-[4px] max-w-[250px] font-medium text-[var(--color-text-1)]">
{{ step.name }}
</div>
<MsIcon
type="icon-icon_edit_outlined"
class="edit-script-name-icon"
@click.stop="handleStepNameClick(step)"
/>
</div>
</a-tooltip>
</template>
</div>
<!-- 步骤内容按步骤类型展示不同组件 -->
<component :is="getStepContent(step)" />
</div>
</div>
</template>
<template #extra="step">
<MsButton :id="step.id" type="icon" class="ms-tree-node-extra__btn !mr-[4px]" @click="setFocusNodeKey(step)">
<MsIcon type="icon-icon_add_outlined" size="14" class="text-[var(--color-text-4)]" />
</MsButton>
<a-trigger
trigger="click"
class="arco-trigger-menu absolute"
content-class="w-[160px]"
position="br"
@popup-visible-change="handleActionTriggerChange($event, step)"
>
<MsButton
:id="step.id"
type="icon"
class="ms-tree-node-extra__btn !mr-[4px]"
@click="setFocusNodeKey(step.id)"
>
<MsIcon type="icon-icon_add_outlined" size="14" class="text-[var(--color-text-4)]" />
</MsButton>
<template #content>
<actionDropdown
v-model:visible="step.actionDropdownVisible"
position="br"
class="scenario-action-dropdown"
:popup-translate="[-7, -10]"
@select="(val) => handleActionSelect(val as ScenarioAddStepActionType, step)"
>
<span></span>
</actionDropdown>
<div class="arco-trigger-menu-inner">
<div
:class="[
'arco-trigger-menu-item !mx-0 !w-full',
activeAction === 'addChildStep' ? 'step-tree-active-action' : '',
]"
@click="handleTriggerActionClick(step, 'addChildStep')"
>
<icon-plus size="12" />
{{ t('apiScenario.addChildStep') }}
</div>
<div
:class="[
'arco-trigger-menu-item !mx-0 !w-full',
activeAction === 'insertBefore' ? 'step-tree-active-action' : '',
]"
@click="handleTriggerActionClick(step, 'insertBefore')"
>
<icon-left size="12" />
{{ t('apiScenario.insertBefore') }}
</div>
<div
:class="[
'arco-trigger-menu-item !mx-0 !w-full',
activeAction === 'insertAfter' ? 'step-tree-active-action' : '',
]"
@click="handleTriggerActionClick(step, 'insertAfter')"
>
<icon-left size="12" />
{{ t('apiScenario.insertAfter') }}
</div>
</div>
</template>
</a-trigger>
</template>
<template #extraEnd="step">
<executeStatus v-if="step.status" :status="step.status" size="small" />
@ -114,7 +179,7 @@
</div>
</template>
</MsTree>
</div>
</a-spin>
<actionDropdown
class="scenario-action-dropdown"
@select="(val) => handleActionSelect(val as ScenarioAddStepActionType)"
@ -133,6 +198,8 @@
</template>
<script setup lang="ts">
import { cloneDeep } from 'lodash-es';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
@ -153,7 +220,8 @@
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
import { useI18n } from '@/hooks/useI18n';
import { findNodeByKey } from '@/utils';
import useAppStore from '@/store/modules/app';
import { deleteNode, findNodeByKey, getGenerateId, insertNode, mapTree } from '@/utils';
import { RequestMethods, ScenarioAddStepActionType, ScenarioExecuteStatus, ScenarioStepType } from '@/enums/apiEnum';
@ -168,11 +236,16 @@
description: string;
method?: RequestMethods;
status?: ScenarioExecuteStatus;
projectId?: string;
num?: number; //
//
belongProjectId?: string;
belongProjectName?: string;
children?: ScenarioStepItem[];
//
// renderId: string; // id
checked: boolean; //
expanded: boolean; //
actionDropdownVisible?: boolean; //
}
const props = defineProps<{
@ -180,6 +253,7 @@
expandAll?: boolean;
}>();
const appStore = useAppStore();
const { t } = useI18n();
const steps = defineModel<ScenarioStepItem[]>('steps', {
@ -189,27 +263,10 @@
required: true,
});
const selectedKeys = ref<string[]>([]); //
const loading = ref(false);
const treeRef = ref<InstanceType<typeof MsTree>>();
const focusStepKey = ref<string>(''); // key
const stepMoreActions: ActionsItem[] = [
{
label: 'common.execute',
eventTag: 'execute',
},
{
label: 'common.copy',
eventTag: 'copy',
},
{
label: 'apiScenario.scenarioConfig',
eventTag: 'config',
},
{
label: 'common.delete',
eventTag: 'delete',
danger: true,
},
];
function getStepContent(step: ScenarioStepItem) {
switch (step.type) {
@ -232,12 +289,138 @@
}
}
function setFocusNodeKey(node: MsTreeNodeData) {
focusStepKey.value = node.id || '';
function getStepContentData(step: ScenarioStepItem) {
switch (step.type) {
case ScenarioStepType.QUOTE_API:
case ScenarioStepType.QUOTE_CASE:
case ScenarioStepType.QUOTE_SCENARIO:
return {
id: step.id,
belongProjectId: step.belongProjectId,
belongProjectName: step.belongProjectName,
num: step.num,
name: step.name,
type: step.type,
currentProjectId: appStore.currentProjectId,
};
case ScenarioStepType.CUSTOM_API:
return {};
case ScenarioStepType.LOOP_CONTROL:
return {};
case ScenarioStepType.CONDITION_CONTROL:
return {};
case ScenarioStepType.ONLY_ONCE_CONTROL:
return {};
case ScenarioStepType.WAIT_TIME:
return {};
default:
return '';
}
}
function setFocusNodeKey(id: string) {
focusStepKey.value = id || '';
}
function checkStepIsApi(step: ScenarioStepItem) {
return [ScenarioStepType.QUOTE_API, ScenarioStepType.COPY_API, ScenarioStepType.CUSTOM_API].includes(step.type);
return [
ScenarioStepType.QUOTE_API,
ScenarioStepType.COPY_API,
ScenarioStepType.QUOTE_CASE,
ScenarioStepType.COPY_CASE,
ScenarioStepType.CUSTOM_API,
].includes(step.type);
}
function checkStepShowMethod(step: ScenarioStepItem) {
return [
ScenarioStepType.QUOTE_API,
ScenarioStepType.COPY_API,
ScenarioStepType.QUOTE_CASE,
ScenarioStepType.COPY_CASE,
ScenarioStepType.CUSTOM_API,
ScenarioStepType.QUOTE_SCENARIO,
ScenarioStepType.COPY_SCENARIO,
].includes(step.type);
}
const activeAction = ref('');
function handleTriggerActionClick(step: ScenarioStepItem, action: string) {
step.actionDropdownVisible = true;
activeAction.value = action;
}
function handleActionTriggerChange(val: boolean, step: ScenarioStepItem) {
if (!val) {
activeAction.value = '';
step.actionDropdownVisible = false;
setFocusNodeKey('');
}
}
const stepMoreActions: ActionsItem[] = [
{
label: 'common.execute',
eventTag: 'execute',
},
{
label: 'common.copy',
eventTag: 'copy',
},
{
label: 'apiScenario.scenarioConfig',
eventTag: 'config',
},
{
label: 'common.delete',
eventTag: 'delete',
danger: true,
},
];
function handleStepMoreActionSelect(item: ActionsItem, node: MsTreeNodeData) {
switch (item.eventTag) {
case 'execute':
console.log('执行步骤', node);
break;
case 'copy':
const id = getGenerateId();
insertNode<ScenarioStepItem>(
steps.value,
node.id,
{
...cloneDeep(
mapTree<ScenarioStepItem>(node, (childNode) => {
const childId = getGenerateId();
if (selectedKeys.value.includes(node.id)) {
//
selectedKeys.value.push(childId);
}
return {
...childNode,
id: childId,
};
})[0]
),
name: `copy-${node.name}`,
id,
},
'after',
'id'
);
if (selectedKeys.value.includes(node.id)) {
//
selectedKeys.value.push(id);
}
break;
case 'config':
console.log('config', node);
break;
case 'delete':
deleteNode(steps.value, node.id, 'id');
break;
default:
break;
}
}
function checkAll(val: boolean) {
@ -276,6 +459,15 @@
}
}
function handleStepSelect(_selectedKeys: Array<string | number>, node: MsTreeNodeData) {
const offspringIds: string[] = [];
mapTree(node.children || [], (e) => {
offspringIds.push(e.id);
return e;
});
selectedKeys.value = [node.id, ...offspringIds];
}
function executeStep(node: MsTreeNodeData) {
console.log('执行步骤', node);
}
@ -284,7 +476,7 @@
const customApiDrawerVisible = ref(false);
const scriptOperationDrawerVisible = ref(false);
function handleActionSelect(val: ScenarioAddStepActionType) {
function handleActionSelect(val: ScenarioAddStepActionType, step?: ScenarioStepItem) {
switch (val) {
case ScenarioAddStepActionType.IMPORT_SYSTEM_API:
importApiDrawerVisible.value = true;
@ -338,6 +530,9 @@
default:
break;
}
if (step) {
document.getElementById(step.id.toString())?.click();
}
}
defineExpose({
@ -345,6 +540,13 @@
});
</script>
<style lang="less">
.step-tree-active-action {
color: rgb(var(--primary-5));
background-color: rgb(var(--primary-1));
}
</style>
<style lang="less" scoped>
.add-step-btn {
@apply bg-white;

View File

@ -159,5 +159,13 @@
}
:deep(.arco-tabs-content) {
@apply pt-0;
height: calc(100% - 49px);
.arco-tabs-content-list {
@apply h-full;
.arco-tabs-pane {
@apply h-full;
}
}
}
</style>

View File

@ -101,6 +101,13 @@ export default {
'apiScenario.scenarioConfig': '场景配置',
'apiScenario.noMatchStep': '暂无匹配的步骤数据',
'apiScenario.pleaseInputStepName': '请输入步骤名称',
'apiScenario.belongProject': '所属项目',
'apiScenario.detailName': '名称',
'apiScenario.crossProject': '跨项目',
'apiScenario.expandStepTip': '展开 {count} 个子步骤',
'apiScenario.addChildStep': '添加子步骤',
'apiScenario.insertBefore': '在之前插入步骤',
'apiScenario.insertAfter': '在之后插入步骤',
// 执行历史
'apiScenario.executeHistory.searchPlaceholder': '通过ID或名称搜索',
'apiScenario.executeHistory.num': '序号',