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

This commit is contained in:
baiqi 2024-03-17 17:12:59 +08:00 committed by Craftsman
parent b99d7a60fa
commit f5dc90ffd8
18 changed files with 474 additions and 134 deletions

View File

@ -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);
}
}

View File

@ -1,13 +1,13 @@
<template>
<div ref="treeContainerRef" :class="['ms-tree-container', containerStatusClass]">
<a-tree
v-show="treeData.length > 0"
v-show="data.length > 0"
v-bind="props"
ref="treeRef"
v-model:expanded-keys="expandedKeys"
v-model:selected-keys="selectedKeys"
v-model:checked-keys="checkedKeys"
:data="treeData"
:data="data"
class="ms-tree"
:allow-drop="handleAllowDrop"
@drag-start="onDragStart"
@ -15,6 +15,7 @@
@drop="onDrop"
@select="select"
@check="checked"
@expand="expand"
>
<template v-if="$slots['title']" #title="_props">
<a-tooltip
@ -64,7 +65,7 @@
</a-tree>
<slot name="empty">
<div
v-show="treeData.length === 0 && props.emptyText"
v-show="data.length === 0 && props.emptyText"
class="rounded-[var(--border-radius-small)] bg-[var(--color-fill-1)] p-[8px] text-[12px] leading-[16px] text-[var(--color-text-4)]"
>
{{ props.emptyText }}
@ -74,8 +75,8 @@
</template>
<script setup lang="ts">
import { nextTick, onBeforeMount, Ref, ref, watch, watchEffect } from 'vue';
import { debounce } from 'lodash-es';
import { nextTick, onBeforeMount, Ref, ref, watch } from 'vue';
import { cloneDeep, debounce } from 'lodash-es';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
@ -83,14 +84,12 @@
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import useContainerShadow from '@/hooks/useContainerShadow';
import { mapTree } from '@/utils/index';
import type { MsTreeFieldNames, MsTreeNodeData, MsTreeSelectedData } from './types';
import type { MsTreeExpandedData, MsTreeFieldNames, MsTreeNodeData, MsTreeSelectedData } from './types';
import { VirtualListProps } from '@arco-design/web-vue/es/_components/virtual-list-v2/interface';
const props = withDefaults(
defineProps<{
data: MsTreeNodeData[];
keyword?: string; //
searchDebounce?: number; // ms
draggable?: boolean; //
@ -107,6 +106,8 @@
checkedStrategy?: 'all' | 'parent' | 'child'; //
virtualListProps?: VirtualListProps; //
disabledTitleTooltip?: boolean; // tooltip
actionOnNodeClick?: 'expand'; //
nodeHighlightBackgroundColor?: string; //
titleTooltipPosition?:
| 'top'
| 'tl'
@ -151,8 +152,12 @@
(e: 'moreActionSelect', item: ActionsItem, node: MsTreeNodeData): void;
(e: 'moreActionsClose'): void;
(e: 'check', val: Array<string | number>): void;
(e: 'expand', node: MsTreeExpandedData): void;
}>();
const data = defineModel<MsTreeNodeData[]>('data', {
required: true,
});
const selectedKeys = defineModel<(string | number)[]>('selectedKeys', {
default: [],
});
@ -172,25 +177,8 @@
overHeight: 32,
containerClassName: 'ms-tree-container',
});
const originalTreeData = ref<MsTreeNodeData[]>([]);
function init(isFirstInit = false) {
originalTreeData.value = mapTree<MsTreeNodeData>(props.data);
// (node: MsTreeNodeData) => {
// // if (!props.showLine) {
// // // 线线 switcherIcon switcherIcon
// // node.icon = () => h('span', { class: 'hidden' });
// // }
// // if (
// // node[props.fieldNames.isLeaf || 'isLeaf'] ||
// // !node[props.fieldNames.children] ||
// // node[props.fieldNames.children]?.length === 0
// // ) {
// // // icon线 switcherIcon 线 icon
// // node[props.showLine ? 'switcherIcon' : 'icon'] = () => h('span', { class: 'hidden' });
// // }
// return node;
// });
nextTick(() => {
if (isFirstInit) {
if (props.defaultExpandAll) {
@ -212,10 +200,18 @@
init(true);
});
const originTreeData = ref<MsTreeNodeData[]>([]); //
watch(
() => props.data,
() => {
init();
() => data.value,
(val) => {
if (!props.keyword) {
originTreeData.value = cloneDeep(val);
}
},
{
deep: true,
immediate: true,
}
);
@ -224,16 +220,17 @@
* @param keyword 搜索关键字
*/
function searchData(keyword: string) {
const search = (data: MsTreeNodeData[]) => {
const search = (_data: MsTreeNodeData[]) => {
const result: MsTreeNodeData[] = [];
data.forEach((item) => {
_data.forEach((item) => {
if (item[props.fieldNames.title].toLowerCase().indexOf(keyword.toLowerCase()) > -1) {
result.push({ ...item });
result.push({ ...item, expanded: true });
} else if (item[props.fieldNames.children]) {
const filterData = search(item[props.fieldNames.children]);
if (filterData.length) {
result.push({
...item,
expanded: true,
[props.fieldNames.children]: filterData,
});
}
@ -243,25 +240,26 @@
return result;
};
return search(originalTreeData.value);
return search(originTreeData.value);
}
const treeData = ref<MsTreeNodeData[]>([]);
//
const updateDebouncedSearch = debounce(() => {
if (props.keyword) {
treeData.value = searchData(props.keyword);
data.value = searchData(props.keyword);
}
}, props.searchDebounce);
watchEffect(() => {
if (!props.keyword) {
treeData.value = originalTreeData.value;
} else {
updateDebouncedSearch();
watch(
() => props.keyword,
(val) => {
if (!val) {
data.value = cloneDeep(originTreeData.value);
} else {
updateDebouncedSearch();
}
}
});
);
function loop(
_data: MsTreeNodeData[],
@ -309,13 +307,13 @@
dropNode: MsTreeNodeData; //
dropPosition: number; // -1 1 0
}) {
loop(originalTreeData.value, dragNode.key, (item, index, arr) => {
loop(data.value, dragNode.key, (item, index, arr) => {
arr.splice(index, 1);
});
if (dropPosition === 0) {
//
loop(originalTreeData.value, dropNode.key, (item) => {
loop(data.value, dropNode.key, (item) => {
item.children = item.children || [];
item.children.push(dragNode);
});
@ -325,18 +323,18 @@
}
} else {
//
loop(originalTreeData.value, dropNode.key, (item, index, arr) => {
loop(data.value, dropNode.key, (item, index, arr) => {
arr.splice(dropPosition < 0 ? index : index + 1, 0, dragNode);
});
}
emit('drop', originalTreeData.value, dragNode, dropNode, dropPosition);
emit('drop', data.value, dragNode, dropNode, dropPosition);
}
/**
* 处理树节点选中非复选框
*/
function select(_selectedKeys: Array<string | number>, data: MsTreeSelectedData) {
emit('select', _selectedKeys, data.selectedNodes[0]);
function select(_selectedKeys: Array<string | number>, _data: MsTreeSelectedData) {
emit('select', _selectedKeys, _data.selectedNodes[0]);
}
function checked(_checkedKeys: Array<string | number>) {
@ -351,7 +349,7 @@
if (val?.toString() !== '') {
focusEl.value = treeRef.value?.$el.querySelector(`[data-key="${val}"]`);
if (focusEl.value) {
focusEl.value.style.backgroundColor = 'rgb(var(--primary-1))';
focusEl.value.style.backgroundColor = props.nodeHighlightBackgroundColor || 'rgb(var(--primary-1))';
}
} else if (focusEl.value) {
focusEl.value.style.backgroundColor = '';
@ -380,6 +378,10 @@
}
);
function expand(expandKeys: Array<string | number>, node: MsTreeExpandedData) {
emit('expand', node);
}
function checkAll(val: boolean) {
treeRef.value?.checkAll(val);
}

View File

@ -10,6 +10,7 @@ export interface MsTreeFieldNames extends TreeFieldNames {
export type MsTreeNodeData = {
hideMoreAction?: boolean; // 隐藏更多操作
parentId?: string;
expanded?: boolean; // 是否展开
[key: string]: any;
} & TreeNodeData;
@ -28,3 +29,10 @@ export interface MsTreeSelectedData {
node?: MsTreeNodeData;
e?: Event;
}
export interface MsTreeExpandedData {
expanded?: boolean;
expandedNodes: MsTreeNodeData[];
node?: MsTreeNodeData;
e?: Event;
}

View File

@ -268,7 +268,11 @@ export function filterTree<T>(
* @param customKey key
* @returns /null
*/
export function findNodeByKey<T>(trees: TreeNode<T>[], targetKey: string, customKey = 'key'): TreeNode<T> | T | null {
export function findNodeByKey<T>(
trees: TreeNode<T>[],
targetKey: string | number,
customKey = 'key'
): TreeNode<T> | T | null {
for (let i = 0; i < trees.length; i++) {
const node = trees[i];
if (node[customKey] === targetKey) {

View File

@ -0,0 +1,23 @@
<template>
<MsDrawer
v-model:visible="visible"
:title="t('apiScenario.scriptOperation')"
:width="960"
no-content-padding
disabled-width-drag
>
waiting customApi
</MsDrawer>
</template>
<script setup lang="ts">
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import { useI18n } from '@/hooks/useI18n';
const { t } = useI18n();
const visible = defineModel<boolean>('visible', { required: true });
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,23 @@
<template>
<MsDrawer
v-model:visible="visible"
:title="t('apiScenario.scriptOperation')"
:width="960"
no-content-padding
disabled-width-drag
>
waiting scriptOperation
</MsDrawer>
</template>
<script setup lang="ts">
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import { useI18n } from '@/hooks/useI18n';
const { t } = useI18n();
const visible = defineModel<boolean>('visible', { required: true });
</script>
<style lang="less" scoped></style>

View File

@ -2,11 +2,11 @@
<div
class="rounded-[0_999px_999px_0] border border-solid px-[8px] py-[2px] text-[12px] leading-[16px]"
:style="{
borderColor: status.color,
color: status.color,
borderColor: type.color,
color: type.color,
}"
>
{{ status.label }}
{{ type.label }}
</div>
</template>
@ -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,

View File

@ -0,0 +1,7 @@
export const defaultStepItemCommon = {
checked: false,
expanded: false,
enabled: true,
};
export default {};

View File

@ -61,13 +61,11 @@
</div>
</template>
<div v-if="!checkedAll && !indeterminate" class="action-group ml-auto">
<a-input-search
<a-input
v-model:model-value="keyword"
:placeholder="t('apiScenario.searchByName')"
allow-clear
class="w-[200px]"
@search="searchStep"
@press-enter="searchStep"
/>
<a-button
v-if="!props.isNew"
@ -82,7 +80,13 @@
</div>
</div>
<div>
<stepTree ref="stepTreeRef" v-model:checked-keys="checkedKeys" :steps="stepInfo.steps" />
<stepTree
ref="stepTreeRef"
v-model:steps="stepInfo.steps"
v-model:checked-keys="checkedKeys"
v-model:stepKeyword="keyword"
:expand-all="isExpandAll"
/>
</div>
</div>
</template>
@ -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));
}
</script>
<style lang="less">

View File

@ -0,0 +1,7 @@
<template>
<div>condition </div>
</template>
<script setup lang="ts"></script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,7 @@
<template>
<div>custom </div>
</template>
<script setup lang="ts"></script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,7 @@
<template>
<div>loop </div>
</template>
<script setup lang="ts"></script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,7 @@
<template>
<div>only once </div>
</template>
<script setup lang="ts"></script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,7 @@
<template>
<div>quote </div>
</template>
<script setup lang="ts"></script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,7 @@
<template>
<div>waitTime </div>
</template>
<script setup lang="ts"></script>
<style lang="less" scoped></style>

View File

@ -1,50 +1,120 @@
<template>
<div class="flex flex-col gap-[16px]">
<MsTree
ref="treeRef"
v-model:checked-keys="checkedKeys"
v-model:focus-node-key="focusStepKey"
:data="props.steps"
:node-more-actions="stepMoreActions"
:field-names="{ title: 'name', key: 'id', children: 'children' }"
:selectable="false"
disabled-title-tooltip
checkable
block-node
draggable
>
<template #title="step">
<div class="flex items-center gap-[8px]">
<div
class="flex h-[16px] min-w-[16px] items-center justify-center rounded-full bg-[var(--color-text-brand)] px-[2px] !text-white"
>
{{ step.order }}
</div>
<div class="step-node-first">
<div class="max-h-[calc(100vh-305px)]">
<MsTree
ref="treeRef"
v-model:checked-keys="checkedKeys"
v-model:focus-node-key="focusStepKey"
v-model:data="steps"
:keyword="props.stepKeyword"
: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,
fixedSize: true,
buffer: 15, // 10 padding
}"
node-highlight-background-color="var(--color-text-n9)"
action-on-node-click="expand"
disabled-title-tooltip
checkable
block-node
draggable
@expand="handleStepExpand"
>
<template #title="step">
<div class="flex w-full items-center gap-[8px]">
<!-- 步骤序号 -->
<div
v-show="step.children?.length > 0"
class="flex cursor-pointer items-center gap-[2px] text-[var(--color-text-1)]"
@click.stop="toggleNodeExpand(step)"
class="flex h-[16px] min-w-[16px] items-center justify-center rounded-full bg-[var(--color-text-brand)] px-[2px] !text-white"
>
<MsIcon
:type="step.expanded ? 'icon-icon_split_turn-down_arrow' : 'icon-icon_split-turn-down-left'"
:size="14"
/>
{{ step.children?.length || 0 }}
{{ step.order }}
</div>
<div class="step-node-content">
<!-- 步骤展开折叠按钮 -->
<div
v-if="step.children?.length > 0"
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>
<div class="mr-[8px] flex items-center gap-[8px]">
<!-- 步骤启用/禁用 -->
<a-switch
:default-checked="step.enabled"
size="small"
@click.stop="step.enabled = !step.enabled"
></a-switch>
<!-- 步骤执行 -->
<MsIcon
type="icon-icon_play-round_filled"
:size="18"
class="cursor-pointer text-[rgb(var(--link-6))]"
@click.stop="executeStep(step)"
/>
</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>
</a-tooltip>
</div>
<!-- 步骤内容按步骤类型展示不同组件 -->
<component :is="getStepContent(step)" />
</div>
<div class="text-[var(--color-text-1)]">{{ step.name }}</div>
</div>
</div>
</template>
<template #extra="step">
<MsButton :id="step.key" 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>
</template>
<template #extraEnd="step">
<executeStatus :status="step.status" size="small" />
</template>
</MsTree>
</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>
</template>
<template #extraEnd="step">
<executeStatus v-if="step.status" :status="step.status" size="small" />
</template>
<template v-if="steps.length === 0 && stepKeyword.trim() !== ''" #empty>
<div
class="rounded-[var(--border-radius-small)] bg-[var(--color-fill-1)] p-[8px] text-center text-[12px] leading-[16px] text-[var(--color-text-4)]"
>
{{ t('apiScenario.noMatchStep') }}
</div>
</template>
</MsTree>
</div>
<actionDropdown
class="scenario-action-dropdown"
@select="(val) => handleActionSelect(val as ScenarioAddStepActionType)"
@ -57,6 +127,8 @@
</a-button>
</actionDropdown>
<importApiDrawer v-model:visible="importApiDrawerVisible" />
<customApiDrawer v-model:visible="customApiDrawerVisible" />
<scriptOperationDrawer v-model:visible="scriptOperationDrawerVisible" />
</div>
</template>
@ -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<ScenarioStepItem[]>('steps', {
required: true,
});
const checkedKeys = defineModel<string[]>('checkedKeys', {
required: true,
});
const treeRef = ref<InstanceType<typeof MsTree>>();
const focusStepKey = ref<string>(''); // 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<string | number>('');
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<ScenarioStepItem>(steps.value, step.id, 'id');
if (realStep) {
realStep.name = tempStepName.value;
}
showStepNameEditInputStepId.value = '';
}
/**
* 处理步骤展开折叠
*/
function handleStepExpand(data: MsTreeExpandedData) {
const realStep = findNodeByKey<ScenarioStepItem>(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;

View File

@ -154,6 +154,9 @@
</script>
<style lang="less" scoped>
:deep(.arco-tabs-nav) {
@apply border-b;
}
:deep(.arco-tabs-content) {
@apply pt-0;
}

View File

@ -99,6 +99,8 @@ export default {
'apiScenario.scenario': '场景',
'apiScenario.sumSelected': '共选择',
'apiScenario.scenarioConfig': '场景配置',
'apiScenario.noMatchStep': '暂无匹配的步骤数据',
'apiScenario.pleaseInputStepName': '请输入步骤名称',
// 执行历史
'apiScenario.executeHistory.searchPlaceholder': '通过ID或名称搜索',
'apiScenario.executeHistory.num': '序号',