feat(接口场景): 场景步骤 10%
This commit is contained in:
parent
b99d7a60fa
commit
f5dc90ffd8
|
@ -443,16 +443,16 @@
|
||||||
height: 14px;
|
height: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
margin-right: 24px;
|
margin-right: 24px;
|
||||||
}
|
}
|
||||||
.arco-form-item{
|
.arco-form-item {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
.arco-icon-hover.arco-radio-icon-hover::before {
|
.arco-icon-hover.arco-radio-icon-hover::before {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.arco-radio-checked:not(.arco-radio-disabled) {
|
.arco-radio-checked:not(.arco-radio-disabled) {
|
||||||
.arco-radio-icon {
|
.arco-radio-icon {
|
||||||
@apply !bg-white;
|
@apply !bg-white;
|
||||||
|
@ -653,6 +653,12 @@
|
||||||
.arco-switch {
|
.arco-switch {
|
||||||
margin-left: 2px; // 避免开关圆形左边被遮挡
|
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-type-line.arco-switch-small {
|
||||||
.arco-switch-handle {
|
.arco-switch-handle {
|
||||||
width: 14px;
|
width: 14px;
|
||||||
|
@ -661,7 +667,7 @@
|
||||||
}
|
}
|
||||||
.arco-switch-type-line.arco-switch-small.arco-switch-checked {
|
.arco-switch-type-line.arco-switch-small.arco-switch-checked {
|
||||||
.arco-switch-handle {
|
.arco-switch-handle {
|
||||||
left: calc(100% - 14px - 0px);
|
left: calc(100% - 14px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div ref="treeContainerRef" :class="['ms-tree-container', containerStatusClass]">
|
<div ref="treeContainerRef" :class="['ms-tree-container', containerStatusClass]">
|
||||||
<a-tree
|
<a-tree
|
||||||
v-show="treeData.length > 0"
|
v-show="data.length > 0"
|
||||||
v-bind="props"
|
v-bind="props"
|
||||||
ref="treeRef"
|
ref="treeRef"
|
||||||
v-model:expanded-keys="expandedKeys"
|
v-model:expanded-keys="expandedKeys"
|
||||||
v-model:selected-keys="selectedKeys"
|
v-model:selected-keys="selectedKeys"
|
||||||
v-model:checked-keys="checkedKeys"
|
v-model:checked-keys="checkedKeys"
|
||||||
:data="treeData"
|
:data="data"
|
||||||
class="ms-tree"
|
class="ms-tree"
|
||||||
:allow-drop="handleAllowDrop"
|
:allow-drop="handleAllowDrop"
|
||||||
@drag-start="onDragStart"
|
@drag-start="onDragStart"
|
||||||
|
@ -15,6 +15,7 @@
|
||||||
@drop="onDrop"
|
@drop="onDrop"
|
||||||
@select="select"
|
@select="select"
|
||||||
@check="checked"
|
@check="checked"
|
||||||
|
@expand="expand"
|
||||||
>
|
>
|
||||||
<template v-if="$slots['title']" #title="_props">
|
<template v-if="$slots['title']" #title="_props">
|
||||||
<a-tooltip
|
<a-tooltip
|
||||||
|
@ -64,7 +65,7 @@
|
||||||
</a-tree>
|
</a-tree>
|
||||||
<slot name="empty">
|
<slot name="empty">
|
||||||
<div
|
<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)]"
|
class="rounded-[var(--border-radius-small)] bg-[var(--color-fill-1)] p-[8px] text-[12px] leading-[16px] text-[var(--color-text-4)]"
|
||||||
>
|
>
|
||||||
{{ props.emptyText }}
|
{{ props.emptyText }}
|
||||||
|
@ -74,8 +75,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { nextTick, onBeforeMount, Ref, ref, watch, watchEffect } from 'vue';
|
import { nextTick, onBeforeMount, Ref, ref, watch } from 'vue';
|
||||||
import { debounce } from 'lodash-es';
|
import { cloneDeep, debounce } from 'lodash-es';
|
||||||
|
|
||||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
import MsIcon from '@/components/pure/ms-icon-font/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 type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||||
|
|
||||||
import useContainerShadow from '@/hooks/useContainerShadow';
|
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';
|
import { VirtualListProps } from '@arco-design/web-vue/es/_components/virtual-list-v2/interface';
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
data: MsTreeNodeData[];
|
|
||||||
keyword?: string; // 搜索关键字
|
keyword?: string; // 搜索关键字
|
||||||
searchDebounce?: number; // 搜索防抖 ms 数
|
searchDebounce?: number; // 搜索防抖 ms 数
|
||||||
draggable?: boolean; // 是否可拖拽
|
draggable?: boolean; // 是否可拖拽
|
||||||
|
@ -107,6 +106,8 @@
|
||||||
checkedStrategy?: 'all' | 'parent' | 'child'; // 选中节点时的策略
|
checkedStrategy?: 'all' | 'parent' | 'child'; // 选中节点时的策略
|
||||||
virtualListProps?: VirtualListProps; // 虚拟滚动列表的属性
|
virtualListProps?: VirtualListProps; // 虚拟滚动列表的属性
|
||||||
disabledTitleTooltip?: boolean; // 是否禁用标题 tooltip
|
disabledTitleTooltip?: boolean; // 是否禁用标题 tooltip
|
||||||
|
actionOnNodeClick?: 'expand'; // 点击节点时的操作
|
||||||
|
nodeHighlightBackgroundColor?: string; // 节点高亮背景色
|
||||||
titleTooltipPosition?:
|
titleTooltipPosition?:
|
||||||
| 'top'
|
| 'top'
|
||||||
| 'tl'
|
| 'tl'
|
||||||
|
@ -151,8 +152,12 @@
|
||||||
(e: 'moreActionSelect', item: ActionsItem, node: MsTreeNodeData): void;
|
(e: 'moreActionSelect', item: ActionsItem, node: MsTreeNodeData): void;
|
||||||
(e: 'moreActionsClose'): void;
|
(e: 'moreActionsClose'): void;
|
||||||
(e: 'check', val: Array<string | number>): 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', {
|
const selectedKeys = defineModel<(string | number)[]>('selectedKeys', {
|
||||||
default: [],
|
default: [],
|
||||||
});
|
});
|
||||||
|
@ -172,25 +177,8 @@
|
||||||
overHeight: 32,
|
overHeight: 32,
|
||||||
containerClassName: 'ms-tree-container',
|
containerClassName: 'ms-tree-container',
|
||||||
});
|
});
|
||||||
const originalTreeData = ref<MsTreeNodeData[]>([]);
|
|
||||||
|
|
||||||
function init(isFirstInit = false) {
|
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(() => {
|
nextTick(() => {
|
||||||
if (isFirstInit) {
|
if (isFirstInit) {
|
||||||
if (props.defaultExpandAll) {
|
if (props.defaultExpandAll) {
|
||||||
|
@ -212,10 +200,18 @@
|
||||||
init(true);
|
init(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const originTreeData = ref<MsTreeNodeData[]>([]); // 初始化时全量的树数据或在非搜索情况下更新后的全量树数据
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.data,
|
() => data.value,
|
||||||
() => {
|
(val) => {
|
||||||
init();
|
if (!props.keyword) {
|
||||||
|
originTreeData.value = cloneDeep(val);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -224,16 +220,17 @@
|
||||||
* @param keyword 搜索关键字
|
* @param keyword 搜索关键字
|
||||||
*/
|
*/
|
||||||
function searchData(keyword: string) {
|
function searchData(keyword: string) {
|
||||||
const search = (data: MsTreeNodeData[]) => {
|
const search = (_data: MsTreeNodeData[]) => {
|
||||||
const result: MsTreeNodeData[] = [];
|
const result: MsTreeNodeData[] = [];
|
||||||
data.forEach((item) => {
|
_data.forEach((item) => {
|
||||||
if (item[props.fieldNames.title].toLowerCase().indexOf(keyword.toLowerCase()) > -1) {
|
if (item[props.fieldNames.title].toLowerCase().indexOf(keyword.toLowerCase()) > -1) {
|
||||||
result.push({ ...item });
|
result.push({ ...item, expanded: true });
|
||||||
} else if (item[props.fieldNames.children]) {
|
} else if (item[props.fieldNames.children]) {
|
||||||
const filterData = search(item[props.fieldNames.children]);
|
const filterData = search(item[props.fieldNames.children]);
|
||||||
if (filterData.length) {
|
if (filterData.length) {
|
||||||
result.push({
|
result.push({
|
||||||
...item,
|
...item,
|
||||||
|
expanded: true,
|
||||||
[props.fieldNames.children]: filterData,
|
[props.fieldNames.children]: filterData,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -243,25 +240,26 @@
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
return search(originalTreeData.value);
|
return search(originTreeData.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
const treeData = ref<MsTreeNodeData[]>([]);
|
|
||||||
|
|
||||||
// 防抖搜索
|
// 防抖搜索
|
||||||
const updateDebouncedSearch = debounce(() => {
|
const updateDebouncedSearch = debounce(() => {
|
||||||
if (props.keyword) {
|
if (props.keyword) {
|
||||||
treeData.value = searchData(props.keyword);
|
data.value = searchData(props.keyword);
|
||||||
}
|
}
|
||||||
}, props.searchDebounce);
|
}, props.searchDebounce);
|
||||||
|
|
||||||
watchEffect(() => {
|
watch(
|
||||||
if (!props.keyword) {
|
() => props.keyword,
|
||||||
treeData.value = originalTreeData.value;
|
(val) => {
|
||||||
} else {
|
if (!val) {
|
||||||
updateDebouncedSearch();
|
data.value = cloneDeep(originTreeData.value);
|
||||||
|
} else {
|
||||||
|
updateDebouncedSearch();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
function loop(
|
function loop(
|
||||||
_data: MsTreeNodeData[],
|
_data: MsTreeNodeData[],
|
||||||
|
@ -309,13 +307,13 @@
|
||||||
dropNode: MsTreeNodeData; // 放入的节点
|
dropNode: MsTreeNodeData; // 放入的节点
|
||||||
dropPosition: number; // 放入的位置,-1 为放入节点前,1 为放入节点后,0 为放入节点内
|
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);
|
arr.splice(index, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (dropPosition === 0) {
|
if (dropPosition === 0) {
|
||||||
// 放入节点内
|
// 放入节点内
|
||||||
loop(originalTreeData.value, dropNode.key, (item) => {
|
loop(data.value, dropNode.key, (item) => {
|
||||||
item.children = item.children || [];
|
item.children = item.children || [];
|
||||||
item.children.push(dragNode);
|
item.children.push(dragNode);
|
||||||
});
|
});
|
||||||
|
@ -325,18 +323,18 @@
|
||||||
}
|
}
|
||||||
} else {
|
} 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);
|
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) {
|
function select(_selectedKeys: Array<string | number>, _data: MsTreeSelectedData) {
|
||||||
emit('select', _selectedKeys, data.selectedNodes[0]);
|
emit('select', _selectedKeys, _data.selectedNodes[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function checked(_checkedKeys: Array<string | number>) {
|
function checked(_checkedKeys: Array<string | number>) {
|
||||||
|
@ -351,7 +349,7 @@
|
||||||
if (val?.toString() !== '') {
|
if (val?.toString() !== '') {
|
||||||
focusEl.value = treeRef.value?.$el.querySelector(`[data-key="${val}"]`);
|
focusEl.value = treeRef.value?.$el.querySelector(`[data-key="${val}"]`);
|
||||||
if (focusEl.value) {
|
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) {
|
} else if (focusEl.value) {
|
||||||
focusEl.value.style.backgroundColor = '';
|
focusEl.value.style.backgroundColor = '';
|
||||||
|
@ -380,6 +378,10 @@
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function expand(expandKeys: Array<string | number>, node: MsTreeExpandedData) {
|
||||||
|
emit('expand', node);
|
||||||
|
}
|
||||||
|
|
||||||
function checkAll(val: boolean) {
|
function checkAll(val: boolean) {
|
||||||
treeRef.value?.checkAll(val);
|
treeRef.value?.checkAll(val);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ export interface MsTreeFieldNames extends TreeFieldNames {
|
||||||
export type MsTreeNodeData = {
|
export type MsTreeNodeData = {
|
||||||
hideMoreAction?: boolean; // 隐藏更多操作
|
hideMoreAction?: boolean; // 隐藏更多操作
|
||||||
parentId?: string;
|
parentId?: string;
|
||||||
|
expanded?: boolean; // 是否展开
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
} & TreeNodeData;
|
} & TreeNodeData;
|
||||||
|
|
||||||
|
@ -28,3 +29,10 @@ export interface MsTreeSelectedData {
|
||||||
node?: MsTreeNodeData;
|
node?: MsTreeNodeData;
|
||||||
e?: Event;
|
e?: Event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MsTreeExpandedData {
|
||||||
|
expanded?: boolean;
|
||||||
|
expandedNodes: MsTreeNodeData[];
|
||||||
|
node?: MsTreeNodeData;
|
||||||
|
e?: Event;
|
||||||
|
}
|
||||||
|
|
|
@ -268,7 +268,11 @@ export function filterTree<T>(
|
||||||
* @param customKey 默认为 key,可自定义需要匹配的属性名
|
* @param customKey 默认为 key,可自定义需要匹配的属性名
|
||||||
* @returns 匹配的节点/null
|
* @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++) {
|
for (let i = 0; i < trees.length; i++) {
|
||||||
const node = trees[i];
|
const node = trees[i];
|
||||||
if (node[customKey] === targetKey) {
|
if (node[customKey] === targetKey) {
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -2,11 +2,11 @@
|
||||||
<div
|
<div
|
||||||
class="rounded-[0_999px_999px_0] border border-solid px-[8px] py-[2px] text-[12px] leading-[16px]"
|
class="rounded-[0_999px_999px_0] border border-solid px-[8px] py-[2px] text-[12px] leading-[16px]"
|
||||||
:style="{
|
:style="{
|
||||||
borderColor: status.color,
|
borderColor: type.color,
|
||||||
color: status.color,
|
color: type.color,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
{{ status.label }}
|
{{ type.label }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
import { ScenarioStepType } from '@/enums/apiEnum';
|
import { ScenarioStepType } from '@/enums/apiEnum';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
status: ScenarioStepType;
|
type: ScenarioStepType;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
@ -37,8 +37,8 @@
|
||||||
[ScenarioStepType.CUSTOM_API]: { label: 'apiScenario.customApi', color: 'rgb(var(--link-4))' },
|
[ScenarioStepType.CUSTOM_API]: { label: 'apiScenario.customApi', color: 'rgb(var(--link-4))' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const status = computed(() => {
|
const type = computed(() => {
|
||||||
const config = scenarioStepMap[props.status];
|
const config = scenarioStepMap[props.type];
|
||||||
return {
|
return {
|
||||||
border: `1px solid ${config?.color}`,
|
border: `1px solid ${config?.color}`,
|
||||||
color: config?.color,
|
color: config?.color,
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
export const defaultStepItemCommon = {
|
||||||
|
checked: false,
|
||||||
|
expanded: false,
|
||||||
|
enabled: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {};
|
|
@ -61,13 +61,11 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div v-if="!checkedAll && !indeterminate" class="action-group ml-auto">
|
<div v-if="!checkedAll && !indeterminate" class="action-group ml-auto">
|
||||||
<a-input-search
|
<a-input
|
||||||
v-model:model-value="keyword"
|
v-model:model-value="keyword"
|
||||||
:placeholder="t('apiScenario.searchByName')"
|
:placeholder="t('apiScenario.searchByName')"
|
||||||
allow-clear
|
allow-clear
|
||||||
class="w-[200px]"
|
class="w-[200px]"
|
||||||
@search="searchStep"
|
|
||||||
@press-enter="searchStep"
|
|
||||||
/>
|
/>
|
||||||
<a-button
|
<a-button
|
||||||
v-if="!props.isNew"
|
v-if="!props.isNew"
|
||||||
|
@ -82,7 +80,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -96,7 +100,7 @@
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
import { ScenarioExecuteStatus, ScenarioStepType } from '@/enums/apiEnum';
|
import { RequestMethods, ScenarioExecuteStatus, ScenarioStepType } from '@/enums/apiEnum';
|
||||||
|
|
||||||
export interface ScenarioStepInfo {
|
export interface ScenarioStepInfo {
|
||||||
id: string | number;
|
id: string | number;
|
||||||
|
@ -125,16 +129,20 @@
|
||||||
id: 1,
|
id: 1,
|
||||||
order: 1,
|
order: 1,
|
||||||
checked: false,
|
checked: false,
|
||||||
type: ScenarioStepType.CUSTOM_API,
|
expanded: false,
|
||||||
|
enabled: true,
|
||||||
|
type: ScenarioStepType.QUOTE_API,
|
||||||
name: 'API1',
|
name: 'API1',
|
||||||
description: 'API1描述',
|
description: 'API1描述',
|
||||||
status: ScenarioExecuteStatus.SUCCESS,
|
method: RequestMethods.GET,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
id: 11,
|
id: 11,
|
||||||
order: 1,
|
order: 1,
|
||||||
checked: false,
|
checked: false,
|
||||||
type: ScenarioStepType.CUSTOM_API,
|
expanded: false,
|
||||||
|
enabled: true,
|
||||||
|
type: ScenarioStepType.QUOTE_CASE,
|
||||||
name: 'API11',
|
name: 'API11',
|
||||||
description: 'API11描述',
|
description: 'API11描述',
|
||||||
status: ScenarioExecuteStatus.SUCCESS,
|
status: ScenarioExecuteStatus.SUCCESS,
|
||||||
|
@ -143,7 +151,9 @@
|
||||||
id: 12,
|
id: 12,
|
||||||
order: 2,
|
order: 2,
|
||||||
checked: false,
|
checked: false,
|
||||||
type: ScenarioStepType.CUSTOM_API,
|
expanded: false,
|
||||||
|
enabled: true,
|
||||||
|
type: ScenarioStepType.QUOTE_SCENARIO,
|
||||||
name: 'API12',
|
name: 'API12',
|
||||||
description: 'API12描述',
|
description: 'API12描述',
|
||||||
status: ScenarioExecuteStatus.SUCCESS,
|
status: ScenarioExecuteStatus.SUCCESS,
|
||||||
|
@ -154,7 +164,20 @@
|
||||||
id: 2,
|
id: 2,
|
||||||
order: 2,
|
order: 2,
|
||||||
checked: false,
|
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',
|
name: 'API1',
|
||||||
description: 'API1描述',
|
description: 'API1描述',
|
||||||
status: ScenarioExecuteStatus.SUCCESS,
|
status: ScenarioExecuteStatus.SUCCESS,
|
||||||
|
@ -215,10 +238,6 @@
|
||||||
function refreshStepInfo() {
|
function refreshStepInfo() {
|
||||||
console.log('刷新步骤信息');
|
console.log('刷新步骤信息');
|
||||||
}
|
}
|
||||||
|
|
||||||
function searchStep(val: string) {
|
|
||||||
stepInfo.value.steps = stepInfo.value.steps.filter((item) => item.name.includes(val));
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
<template>
|
||||||
|
<div>condition </div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<template>
|
||||||
|
<div>custom </div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<template>
|
||||||
|
<div>loop </div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<template>
|
||||||
|
<div>only once </div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<template>
|
||||||
|
<div>quote </div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<template>
|
||||||
|
<div>waitTime </div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
|
@ -1,50 +1,120 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col gap-[16px]">
|
<div class="flex flex-col gap-[16px]">
|
||||||
<MsTree
|
<div class="max-h-[calc(100vh-305px)]">
|
||||||
ref="treeRef"
|
<MsTree
|
||||||
v-model:checked-keys="checkedKeys"
|
ref="treeRef"
|
||||||
v-model:focus-node-key="focusStepKey"
|
v-model:checked-keys="checkedKeys"
|
||||||
:data="props.steps"
|
v-model:focus-node-key="focusStepKey"
|
||||||
:node-more-actions="stepMoreActions"
|
v-model:data="steps"
|
||||||
:field-names="{ title: 'name', key: 'id', children: 'children' }"
|
:keyword="props.stepKeyword"
|
||||||
:selectable="false"
|
:expand-all="props.expandAll"
|
||||||
disabled-title-tooltip
|
:node-more-actions="stepMoreActions"
|
||||||
checkable
|
:field-names="{ title: 'name', key: 'id', children: 'children' }"
|
||||||
block-node
|
:selectable="false"
|
||||||
draggable
|
:virtual-list-props="{
|
||||||
>
|
height: '100%',
|
||||||
<template #title="step">
|
threshold: 200,
|
||||||
<div class="flex items-center gap-[8px]">
|
fixedSize: true,
|
||||||
<div
|
buffer: 15, // 缓冲区默认 10 的时候,虚拟滚动的底部 padding 计算有问题
|
||||||
class="flex h-[16px] min-w-[16px] items-center justify-center rounded-full bg-[var(--color-text-brand)] px-[2px] !text-white"
|
}"
|
||||||
>
|
node-highlight-background-color="var(--color-text-n9)"
|
||||||
{{ step.order }}
|
action-on-node-click="expand"
|
||||||
</div>
|
disabled-title-tooltip
|
||||||
<div class="step-node-first">
|
checkable
|
||||||
|
block-node
|
||||||
|
draggable
|
||||||
|
@expand="handleStepExpand"
|
||||||
|
>
|
||||||
|
<template #title="step">
|
||||||
|
<div class="flex w-full items-center gap-[8px]">
|
||||||
|
<!-- 步骤序号 -->
|
||||||
<div
|
<div
|
||||||
v-show="step.children?.length > 0"
|
class="flex h-[16px] min-w-[16px] items-center justify-center rounded-full bg-[var(--color-text-brand)] px-[2px] !text-white"
|
||||||
class="flex cursor-pointer items-center gap-[2px] text-[var(--color-text-1)]"
|
|
||||||
@click.stop="toggleNodeExpand(step)"
|
|
||||||
>
|
>
|
||||||
<MsIcon
|
{{ step.order }}
|
||||||
:type="step.expanded ? 'icon-icon_split_turn-down_arrow' : 'icon-icon_split-turn-down-left'"
|
</div>
|
||||||
:size="14"
|
<div class="step-node-content">
|
||||||
/>
|
<!-- 步骤展开折叠按钮 -->
|
||||||
{{ step.children?.length || 0 }}
|
<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>
|
||||||
<div class="text-[var(--color-text-1)]">{{ step.name }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</template>
|
<template #extra="step">
|
||||||
<template #extra="step">
|
<MsButton :id="step.id" type="icon" class="ms-tree-node-extra__btn !mr-[4px]" @click="setFocusNodeKey(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)]" />
|
||||||
<MsIcon type="icon-icon_add_outlined" size="14" class="text-[var(--color-text-4)]" />
|
</MsButton>
|
||||||
</MsButton>
|
</template>
|
||||||
</template>
|
<template #extraEnd="step">
|
||||||
<template #extraEnd="step">
|
<executeStatus v-if="step.status" :status="step.status" size="small" />
|
||||||
<executeStatus :status="step.status" size="small" />
|
</template>
|
||||||
</template>
|
<template v-if="steps.length === 0 && stepKeyword.trim() !== ''" #empty>
|
||||||
</MsTree>
|
<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
|
<actionDropdown
|
||||||
class="scenario-action-dropdown"
|
class="scenario-action-dropdown"
|
||||||
@select="(val) => handleActionSelect(val as ScenarioAddStepActionType)"
|
@select="(val) => handleActionSelect(val as ScenarioAddStepActionType)"
|
||||||
|
@ -57,6 +127,8 @@
|
||||||
</a-button>
|
</a-button>
|
||||||
</actionDropdown>
|
</actionDropdown>
|
||||||
<importApiDrawer v-model:visible="importApiDrawerVisible" />
|
<importApiDrawer v-model:visible="importApiDrawerVisible" />
|
||||||
|
<customApiDrawer v-model:visible="customApiDrawerVisible" />
|
||||||
|
<scriptOperationDrawer v-model:visible="scriptOperationDrawerVisible" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -65,36 +137,58 @@
|
||||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||||
import MsTree from '@/components/business/ms-tree/index.vue';
|
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 executeStatus from '../common/executeStatus.vue';
|
||||||
import importApiDrawer from '../common/importApiDrawer/index.vue';
|
import importApiDrawer from '../common/importApiDrawer/index.vue';
|
||||||
|
import scriptOperationDrawer from '../common/scriptOperationDrawer.vue';
|
||||||
import stepType from '../common/stepType.vue';
|
import stepType from '../common/stepType.vue';
|
||||||
import actionDropdown from './actionDropdown.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 { 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 {
|
export interface ScenarioStepItem {
|
||||||
id: string | number;
|
id: string | number;
|
||||||
order: number;
|
order: number;
|
||||||
checked: boolean;
|
enabled: boolean; // 是否启用
|
||||||
type: ScenarioStepType;
|
type: ScenarioStepType;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
status: ScenarioExecuteStatus;
|
method?: RequestMethods;
|
||||||
|
status?: ScenarioExecuteStatus;
|
||||||
|
projectId?: string;
|
||||||
children?: ScenarioStepItem[];
|
children?: ScenarioStepItem[];
|
||||||
|
// 页面渲染以及交互需要字段
|
||||||
|
checked: boolean; // 是否选中
|
||||||
|
expanded: boolean; // 是否展开
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
steps: ScenarioStepItem[];
|
stepKeyword: string;
|
||||||
|
expandAll?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const steps = defineModel<ScenarioStepItem[]>('steps', {
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
const checkedKeys = defineModel<string[]>('checkedKeys', {
|
const checkedKeys = defineModel<string[]>('checkedKeys', {
|
||||||
required: true,
|
required: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const treeRef = ref<InstanceType<typeof MsTree>>();
|
const treeRef = ref<InstanceType<typeof MsTree>>();
|
||||||
const focusStepKey = ref<string>(''); // 聚焦的key
|
const focusStepKey = ref<string>(''); // 聚焦的key
|
||||||
const stepMoreActions: ActionsItem[] = [
|
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) {
|
function setFocusNodeKey(node: MsTreeNodeData) {
|
||||||
focusStepKey.value = node.id || '';
|
focusStepKey.value = node.id || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleNodeExpand(node: MsTreeNodeData) {
|
function checkStepIsApi(step: ScenarioStepItem) {
|
||||||
if (node.id) {
|
return [ScenarioStepType.QUOTE_API, ScenarioStepType.COPY_API, ScenarioStepType.CUSTOM_API].includes(step.type);
|
||||||
treeRef.value?.expandNode(node.id, !node.expanded);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkAll(val: boolean) {
|
function checkAll(val: boolean) {
|
||||||
treeRef.value?.checkAll(val);
|
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 importApiDrawerVisible = ref(false);
|
||||||
|
const customApiDrawerVisible = ref(false);
|
||||||
|
const scriptOperationDrawerVisible = ref(false);
|
||||||
|
|
||||||
function handleActionSelect(val: ScenarioAddStepActionType) {
|
function handleActionSelect(val: ScenarioAddStepActionType) {
|
||||||
switch (val) {
|
switch (val) {
|
||||||
|
@ -139,22 +290,50 @@
|
||||||
importApiDrawerVisible.value = true;
|
importApiDrawerVisible.value = true;
|
||||||
break;
|
break;
|
||||||
case ScenarioAddStepActionType.CUSTOM_API:
|
case ScenarioAddStepActionType.CUSTOM_API:
|
||||||
console.log('自定义API');
|
customApiDrawerVisible.value = true;
|
||||||
break;
|
break;
|
||||||
case ScenarioAddStepActionType.LOOP_CONTROL:
|
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;
|
break;
|
||||||
case ScenarioAddStepActionType.CONDITION_CONTROL:
|
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;
|
break;
|
||||||
case ScenarioAddStepActionType.ONLY_ONCE_CONTROL:
|
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;
|
break;
|
||||||
case ScenarioAddStepActionType.SCRIPT_OPERATION:
|
case ScenarioAddStepActionType.SCRIPT_OPERATION:
|
||||||
console.log('脚本操作');
|
scriptOperationDrawerVisible.value = true;
|
||||||
break;
|
break;
|
||||||
case ScenarioAddStepActionType.WAIT_TIME:
|
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;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -180,7 +359,7 @@
|
||||||
background-color: rgb(var(--primary-1));
|
background-color: rgb(var(--primary-1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 循环生成树的左边距样式
|
// 循环生成树的左边距样式 TODO:transform性能更高以及保留步骤完整宽度,需要加横向滚动
|
||||||
.loop-levels(@index, @max) when (@index <= @max) {
|
.loop-levels(@index, @max) when (@index <= @max) {
|
||||||
:deep(.arco-tree-node[data-level='@{index}']) {
|
:deep(.arco-tree-node[data-level='@{index}']) {
|
||||||
margin-left: @index * 32px;
|
margin-left: @index * 32px;
|
||||||
|
@ -189,7 +368,7 @@
|
||||||
}
|
}
|
||||||
.loop-levels(0, 99); // 最大层级
|
.loop-levels(0, 99); // 最大层级
|
||||||
:deep(.arco-tree-node) {
|
:deep(.arco-tree-node) {
|
||||||
padding: 7px 8px;
|
padding: 0 8px;
|
||||||
border: 1px solid var(--color-text-n8);
|
border: 1px solid var(--color-text-n8);
|
||||||
border-radius: var(--border-radius-medium) !important;
|
border-radius: var(--border-radius-medium) !important;
|
||||||
&:not(:first-child) {
|
&:not(:first-child) {
|
||||||
|
@ -202,19 +381,41 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.arco-tree-node-title {
|
.arco-tree-node-title {
|
||||||
|
@apply !cursor-pointer;
|
||||||
|
|
||||||
|
padding: 12px 4px;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--color-text-n9) !important;
|
background-color: var(--color-text-n9) !important;
|
||||||
}
|
}
|
||||||
.step-node-first {
|
.step-node-content {
|
||||||
@apply flex items-center;
|
@apply flex w-full flex-1 items-center;
|
||||||
|
|
||||||
gap: 8px;
|
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 {
|
&[draggable='true']:hover {
|
||||||
.step-node-first {
|
.step-node-content {
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.arco-tree-node-title-text {
|
||||||
|
@apply flex-1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.arco-tree-node-indent {
|
.arco-tree-node-indent {
|
||||||
@apply hidden;
|
@apply hidden;
|
||||||
|
@ -225,7 +426,7 @@
|
||||||
.arco-tree-node-drag-icon {
|
.arco-tree-node-drag-icon {
|
||||||
@apply ml-0;
|
@apply ml-0;
|
||||||
|
|
||||||
top: 6px;
|
top: 13px;
|
||||||
left: 24px;
|
left: 24px;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
|
|
|
@ -154,6 +154,9 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
:deep(.arco-tabs-nav) {
|
||||||
|
@apply border-b;
|
||||||
|
}
|
||||||
:deep(.arco-tabs-content) {
|
:deep(.arco-tabs-content) {
|
||||||
@apply pt-0;
|
@apply pt-0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,6 +99,8 @@ export default {
|
||||||
'apiScenario.scenario': '场景',
|
'apiScenario.scenario': '场景',
|
||||||
'apiScenario.sumSelected': '共选择',
|
'apiScenario.sumSelected': '共选择',
|
||||||
'apiScenario.scenarioConfig': '场景配置',
|
'apiScenario.scenarioConfig': '场景配置',
|
||||||
|
'apiScenario.noMatchStep': '暂无匹配的步骤数据',
|
||||||
|
'apiScenario.pleaseInputStepName': '请输入步骤名称',
|
||||||
// 执行历史
|
// 执行历史
|
||||||
'apiScenario.executeHistory.searchPlaceholder': '通过ID或名称搜索',
|
'apiScenario.executeHistory.searchPlaceholder': '通过ID或名称搜索',
|
||||||
'apiScenario.executeHistory.num': '序号',
|
'apiScenario.executeHistory.num': '序号',
|
||||||
|
|
Loading…
Reference in New Issue