feat(接口场景): 场景步骤自定义请求保存&查看详情
This commit is contained in:
parent
960f1e3505
commit
886d566c21
|
@ -1,6 +1,7 @@
|
|||
import MSR from '@/api/http/index';
|
||||
import {
|
||||
AddModuleUrl,
|
||||
AddScenarioUrl,
|
||||
BatchCopyScenarioUrl,
|
||||
BatchDeleteScenarioUrl,
|
||||
BatchEditScenarioUrl,
|
||||
|
@ -13,6 +14,7 @@ import {
|
|||
ExecuteHistoryUrl,
|
||||
GetModuleCountUrl,
|
||||
GetModuleTreeUrl,
|
||||
GetScenarioUrl,
|
||||
GetTrashModuleCountUrl,
|
||||
GetTrashModuleTreeUrl,
|
||||
MoveModuleUrl,
|
||||
|
@ -36,6 +38,8 @@ import {
|
|||
ApiScenarioUpdateDTO,
|
||||
ExecuteHistoryItem,
|
||||
ExecutePageParams,
|
||||
Scenario,
|
||||
ScenarioDetail,
|
||||
ScenarioHistoryItem,
|
||||
ScenarioHistoryPageParams,
|
||||
} from '@/models/apiTest/scenario';
|
||||
|
@ -180,3 +184,13 @@ export function batchDeleteScenario(data: {
|
|||
}) {
|
||||
return MSR.post({ url: BatchDeleteScenarioUrl, data });
|
||||
}
|
||||
|
||||
// 添加场景
|
||||
export function addScenario(params: Scenario) {
|
||||
return MSR.post({ url: AddScenarioUrl, params });
|
||||
}
|
||||
|
||||
// 获取场景详情
|
||||
export function getScenarioDetail(id: string) {
|
||||
return MSR.get<ScenarioDetail>({ url: GetScenarioUrl, params: id });
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ export const GetModuleCountUrl = '/api/scenario/module/count'; // 获取模块
|
|||
export const AddModuleUrl = '/api/scenario/module/add'; // 添加模块
|
||||
export const DeleteModuleUrl = '/api/scenario/module/delete'; // 删除模块
|
||||
export const ScenarioPageUrl = '/api/scenario/page'; // 接口场景列表
|
||||
export const AddScenarioUrl = '/api/scenario/add'; // 添加接口场景
|
||||
export const GetScenarioUrl = '/api/scenario/get'; // 获取接口场景详情
|
||||
export const UpdateScenarioUrl = '/api/scenario/update'; // 更新接口场景
|
||||
export const RecycleScenarioUrl = '/api/scenario/delete-to-gc'; // 删除接口场景
|
||||
export const BatchRecycleScenarioUrl = '/api/scenario/batch-operation/delete-gc'; // 批量删除接口场景
|
||||
|
|
|
@ -146,8 +146,8 @@
|
|||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { ExecuteAssertionConfig, ExecuteConditionProcessor } from '@/models/apiTest/common';
|
||||
import { RequestConditionScriptLanguage, ResponseAssertionType, ResponseBodyAssertionType } from '@/enums/apiEnum';
|
||||
import { ExecuteAssertionConfig } from '@/models/apiTest/common';
|
||||
import { ResponseAssertionType, ResponseBodyAssertionType } from '@/enums/apiEnum';
|
||||
|
||||
import { ExecuteAssertion, MsAssertionItem } from './type';
|
||||
|
||||
|
@ -175,7 +175,6 @@
|
|||
|
||||
const innerConfig = useVModel(props, 'assertionConfig', emit);
|
||||
|
||||
const activeIds = ref('');
|
||||
// Item点击的key
|
||||
const activeKey = ref<string>('');
|
||||
// 展示的value
|
||||
|
|
|
@ -330,7 +330,7 @@
|
|||
.handle {
|
||||
@apply absolute left-0 top-0 flex h-full items-center;
|
||||
|
||||
z-index: 1;
|
||||
z-index: 10;
|
||||
width: 8px;
|
||||
background-color: var(--color-neutral-3);
|
||||
cursor: col-resize;
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
|
||||
const checked = computed({
|
||||
get: () => {
|
||||
return props.selectedKeys.size === props.total;
|
||||
return props.selectedKeys.size > 0 && props.selectedKeys.size === props.total;
|
||||
},
|
||||
set: (value) => {
|
||||
return value;
|
||||
|
|
|
@ -231,6 +231,11 @@ export enum ScenarioCreateComposition {
|
|||
// 接口场景详情组成部分
|
||||
export enum ScenarioDetailComposition {
|
||||
BASE_INFO = 'BASE_INFO',
|
||||
STEP = 'STEP',
|
||||
PARAMS = 'PARAMS',
|
||||
PRE_POST = 'PRE_POST',
|
||||
ASSERTION = 'ASSERTION',
|
||||
SETTING = 'SETTING',
|
||||
EXECUTE_HISTORY = 'EXECUTE_HISTORY',
|
||||
CHANGE_HISTORY = 'CHANGE_HISTORY',
|
||||
DEPENDENCY = 'DEPENDENCY',
|
||||
|
@ -244,18 +249,6 @@ export enum ScenarioExecuteStatus {
|
|||
}
|
||||
// 场景步骤类型
|
||||
export enum ScenarioStepType {
|
||||
QUOTE_API = 'QUOTE_API',
|
||||
COPY_API = 'COPY_API',
|
||||
QUOTE_CASE = 'QUOTE_CASE',
|
||||
COPY_CASE = 'COPY_CASE',
|
||||
QUOTE_SCENARIO = 'QUOTE_SCENARIO',
|
||||
COPY_SCENARIO = 'COPY_SCENARIO',
|
||||
WAIT_TIME = 'WAIT_TIME',
|
||||
LOOP_CONTROL = 'LOOP_CONTROL',
|
||||
CONDITION_CONTROL = 'CONDITION_CONTROL',
|
||||
ONLY_ONCE_CONTROL = 'ONLY_ONCE_CONTROL',
|
||||
SCRIPT_OPERATION = 'SCRIPT_OPERATION',
|
||||
CUSTOM_API = 'CUSTOM_API',
|
||||
API_CASE = 'API_CASE', // 接口用例
|
||||
LOOP_CONTROLLER = 'LOOP_CONTROLLER', // 循环控制器
|
||||
API = 'API', // 接口定义
|
||||
|
@ -263,6 +256,14 @@ export enum ScenarioStepType {
|
|||
API_SCENARIO = ' API_SCENARIO', // 场景
|
||||
IF_CONTROLLER = 'IF_CONTROLLER', // 条件控制器
|
||||
ONCE_ONLY_CONTROLLER = 'ONCE_ONLY_CONTROLLER', // 一次控制器
|
||||
CONSTANT_TIMER = 'CONSTANT_TIMER', // 等待控制器
|
||||
SCRIPT = 'SCRIPT', // 脚本
|
||||
}
|
||||
export enum ScenarioStepRefType {
|
||||
COPY = 'COPY', // 复制
|
||||
DIRECT = 'DIRECT', // 在场景中直接创建的步骤 例如 自定义请求,逻辑控制器
|
||||
PARTIAL_REF = 'PARTIAL_REF', // 部分引用
|
||||
REF = 'REF', // 完全引用
|
||||
}
|
||||
// 场景添加步骤操作类型
|
||||
export enum ScenarioAddStepActionType {
|
||||
|
@ -293,3 +294,25 @@ export enum ChangeHistoryStatusFilters {
|
|||
IMPORT = 'IMPORT',
|
||||
DELETE = 'DELETE',
|
||||
}
|
||||
// 场景步骤-循环控制器类型
|
||||
export enum ScenarioStepLoopTypeEnum {
|
||||
WHILE = 'WHILE',
|
||||
LOOP_COUNT = 'LOOP_COUNT',
|
||||
FOREACH = 'FOREACH',
|
||||
}
|
||||
// 场景步骤-循环控制器-while循环类型
|
||||
export enum WhileConditionType {
|
||||
CONDITION = 'CONDITION',
|
||||
SCRIPT = 'SCRIPT',
|
||||
}
|
||||
export enum ScenarioStepPolymorphicName {
|
||||
COMMON_SCRIPT = 'MsCommentScriptElement',
|
||||
IF_CONTROLLER = 'MsIfController',
|
||||
LOOP_CONTROLLER = 'MsLoopController',
|
||||
ONLY_ONCE = 'MsOnceOnlyController',
|
||||
TIME_CONTROLLER = 'MsConstantTimerController',
|
||||
}
|
||||
export enum ScenarioFailureStrategy {
|
||||
CONTINUE = 'CONTINUE',
|
||||
STOP = 'STOP',
|
||||
}
|
||||
|
|
|
@ -314,7 +314,7 @@ export type ExecuteAssertionItem = ResponseAssertionCommon &
|
|||
ResponseVariableAssertion;
|
||||
// 执行请求-断言配置
|
||||
export interface ExecuteAssertionConfig {
|
||||
enableGlobal: boolean; // 是否启用全局断言
|
||||
enableGlobal?: boolean; // 是否启用全局断言,部分地方没有
|
||||
assertions: ExecuteAssertionItem[];
|
||||
}
|
||||
// 执行请求-共用配置子项
|
||||
|
|
|
@ -1,11 +1,28 @@
|
|||
import type { CaseLevel } from '@/components/business/ms-case-associate/types';
|
||||
import { ScenarioStepInfo } from '@/views/api-test/scenario/components/step/index.vue';
|
||||
|
||||
import { ApiDefinitionCustomField, ApiRunModeRequest } from '@/models/apiTest/management';
|
||||
import { ApiScenarioStatus, RequestComposition, RequestDefinitionStatus } from '@/enums/apiEnum';
|
||||
import {
|
||||
ApiScenarioStatus,
|
||||
RequestAssertionCondition,
|
||||
RequestComposition,
|
||||
RequestDefinitionStatus,
|
||||
RequestMethods,
|
||||
ScenarioExecuteStatus,
|
||||
ScenarioFailureStrategy,
|
||||
ScenarioStepLoopTypeEnum,
|
||||
ScenarioStepPolymorphicName,
|
||||
ScenarioStepRefType,
|
||||
ScenarioStepType,
|
||||
WhileConditionType,
|
||||
} from '@/enums/apiEnum';
|
||||
|
||||
import { BatchApiParams, TableQueryParams } from '../common';
|
||||
import { ExecuteApiRequestFullParams, ResponseDefinition } from './common';
|
||||
import {
|
||||
ExecuteApiRequestFullParams,
|
||||
ExecuteAssertionItem,
|
||||
ExecuteConditionConfig,
|
||||
ResponseDefinition,
|
||||
} from './common';
|
||||
|
||||
// 场景-更新模块参数
|
||||
export interface ApiScenarioModuleUpdateParams {
|
||||
|
@ -27,11 +44,11 @@ export interface ApiScenarioGetModuleParams {
|
|||
|
||||
// 场景修改参数
|
||||
export interface ApiScenarioUpdateDTO {
|
||||
id: string;
|
||||
id: string | number;
|
||||
name?: string;
|
||||
priority?: string;
|
||||
status?: ApiScenarioStatus;
|
||||
moduleId?: string;
|
||||
moduleId?: string | number;
|
||||
description?: string;
|
||||
tags?: string[];
|
||||
grouped?: boolean;
|
||||
|
@ -196,25 +213,171 @@ export type CustomApiStep = ExecuteApiRequestFullParams & {
|
|||
useEnv: string;
|
||||
};
|
||||
// 场景步骤-循环控制器类型
|
||||
export type ScenarioStepLoopType = 'num' | 'while' | 'forEach';
|
||||
// 场景步骤-循环控制器-循环类型
|
||||
export type ScenarioStepLoopWhileType = 'condition' | 'expression';
|
||||
export type ScenarioStepLoopType = ScenarioStepLoopTypeEnum;
|
||||
// 场景步骤-步骤插入类型
|
||||
export type CreateStepAction = 'inside' | 'before' | 'after';
|
||||
// 场景步骤
|
||||
export interface Scenario {
|
||||
export interface OtherConfig {
|
||||
enableGlobalCookie: boolean;
|
||||
enableCookieShare: boolean;
|
||||
stepWaitTime: number;
|
||||
enableStepWait: boolean;
|
||||
failureStrategy: ScenarioFailureStrategy;
|
||||
}
|
||||
export interface AssertionConfig {
|
||||
assertions: ExecuteAssertionItem[];
|
||||
}
|
||||
export interface CsvVariable {
|
||||
id: string;
|
||||
fileId: string;
|
||||
scenarioId: string;
|
||||
name: string;
|
||||
fileName: string;
|
||||
scope: string;
|
||||
enable: boolean;
|
||||
association: boolean;
|
||||
encoding: string;
|
||||
random: boolean;
|
||||
variableNames: string;
|
||||
ignoreFirstLine: boolean;
|
||||
delimiter: string;
|
||||
allowQuotedData: boolean;
|
||||
recycleOnEof: boolean;
|
||||
stopThreadOnEof: boolean;
|
||||
}
|
||||
export interface CommonVariable {
|
||||
id: string | number;
|
||||
key: string;
|
||||
paramType: string;
|
||||
value: string;
|
||||
enable: boolean;
|
||||
description: string;
|
||||
tags: string[];
|
||||
}
|
||||
export interface Variable {
|
||||
commonVariables: CommonVariable[];
|
||||
csvVariables: CsvVariable[];
|
||||
}
|
||||
export interface ScenarioConfig {
|
||||
variable: Variable;
|
||||
preProcessorConfig: ExecuteConditionConfig;
|
||||
postProcessorConfig: ExecuteConditionConfig;
|
||||
assertionConfig: AssertionConfig;
|
||||
otherConfig: OtherConfig;
|
||||
}
|
||||
export interface ForEachController {
|
||||
loopTime: number; // 循环间隔时间
|
||||
value: string; // 变量值
|
||||
variable: string; // 变量名
|
||||
}
|
||||
export interface CountController {
|
||||
loops: number; // 循环次数
|
||||
}
|
||||
export interface WhileScript {
|
||||
scriptValue: string; // 脚本值
|
||||
}
|
||||
export interface WhileVariable {
|
||||
condition: RequestAssertionCondition; // 条件操作符
|
||||
value: string; // 变量值
|
||||
variable: string; // 变量名
|
||||
}
|
||||
export interface WhileController {
|
||||
conditionType: WhileConditionType; // 条件类型
|
||||
timeout: number; // 超时时间
|
||||
msWhileScript: WhileScript; // 脚本
|
||||
msWhileVariable: WhileVariable; // 变量
|
||||
}
|
||||
export type ExtendedScenarioStepPolymorphicName = ScenarioStepPolymorphicName | string;
|
||||
// 场景步骤详情公共部分
|
||||
export interface StepDetailsCommon {
|
||||
id: string | number;
|
||||
copyFromStepId?: string; // 如果步骤是复制的,这个字段是复制的步骤id
|
||||
name: string;
|
||||
enable: boolean;
|
||||
polymorphicName: ExtendedScenarioStepPolymorphicName; // 多态名称,用于后台区分使用的是哪个组件
|
||||
}
|
||||
// 自定义请求
|
||||
export interface CustomApiStepDetail extends StepDetailsCommon {
|
||||
customizeRequest: boolean; // 是否自定义请求
|
||||
customizeRequestEnvEnable: boolean; // 是否启用环境
|
||||
}
|
||||
// 条件控制器
|
||||
export interface ConditionStepDetail extends StepDetailsCommon {
|
||||
value: string; // 变量值
|
||||
variable: string; // 变量名
|
||||
condition: RequestAssertionCondition; // 条件操作符
|
||||
}
|
||||
// 循环控制器
|
||||
export interface LoopStepDetail extends StepDetailsCommon {
|
||||
loopType: ScenarioStepLoopType;
|
||||
forEachController: ForEachController;
|
||||
msCountController: CountController;
|
||||
whileController: WhileController;
|
||||
}
|
||||
export type ScenarioStepDetail = Partial<CustomApiStepDetail & ConditionStepDetail & LoopStepDetail>;
|
||||
export interface ScenarioStepItem {
|
||||
id: string | number;
|
||||
sort: number;
|
||||
name: string;
|
||||
executeStatus?: ScenarioExecuteStatus;
|
||||
enable: boolean; // 是否启用
|
||||
resourceId?: string; // 详情或者引用的类型才有
|
||||
resourceNum?: string; // 详情或者引用的类型才有
|
||||
stepType: ScenarioStepType;
|
||||
refType: ScenarioStepRefType;
|
||||
config?: ScenarioStepDetail; // 对应场景里stepDetails里的详情信息,只有逻辑控制器需要
|
||||
csvFileIds?: string[];
|
||||
projectId?: string;
|
||||
versionId?: string;
|
||||
children?: ScenarioStepItem[];
|
||||
// 页面渲染以及交互需要字段
|
||||
checked?: boolean; // 是否选中
|
||||
expanded?: boolean; // 是否展开
|
||||
createActionsVisible?: boolean; // 是否展示创建步骤下拉
|
||||
parent?: ScenarioStepItem; // 父级节点,第一层的父级节点为undefined
|
||||
resourceName?: string; // 引用复制接口、用例、场景时的源资源名称
|
||||
method?: RequestMethods;
|
||||
}
|
||||
// 场景
|
||||
export interface Scenario {
|
||||
id?: string | number;
|
||||
num?: number;
|
||||
name: string;
|
||||
moduleId: string | number;
|
||||
stepInfo: ScenarioStepInfo;
|
||||
priority: CaseLevel;
|
||||
status: RequestDefinitionStatus;
|
||||
status: ApiScenarioStatus;
|
||||
tags: string[];
|
||||
params: Record<string, any>[];
|
||||
projectId: string;
|
||||
description: string;
|
||||
grouped?: boolean;
|
||||
environmentId?: string;
|
||||
scenarioConfig: ScenarioConfig;
|
||||
steps: ScenarioStepItem[];
|
||||
stepDetails: Record<string, ScenarioStepDetail>;
|
||||
follow?: boolean;
|
||||
uploadFileIds: string[];
|
||||
linkFileIds: string[];
|
||||
// 前端渲染字段
|
||||
label: string;
|
||||
closable: boolean;
|
||||
isNew: boolean;
|
||||
unSaved: boolean;
|
||||
executeLoading: boolean; // 执行loading
|
||||
executeTime?: string | number; // 执行时间
|
||||
executeSuccessCount?: number; // 执行成功数量
|
||||
executeFailCount?: number; // 执行失败数量
|
||||
}
|
||||
export interface ScenarioDetail extends Scenario {
|
||||
stepTotal: number;
|
||||
requestPassRate: string;
|
||||
lastReportStatus?: string;
|
||||
lastReportId?: string;
|
||||
deleted: boolean;
|
||||
versionId: string;
|
||||
refId: string;
|
||||
latest: boolean;
|
||||
modulePath: string;
|
||||
createUser: string;
|
||||
createTime: number;
|
||||
updateTime: number;
|
||||
updateUser: string;
|
||||
}
|
||||
|
|
|
@ -224,7 +224,7 @@ export function mapTree<T>(
|
|||
return _tree
|
||||
.map((node: TreeNode<T>, i: number) => {
|
||||
const fullPath = node.path ? `${_parentPath}/${node.path}`.replace(/\/+/g, '/') : '';
|
||||
node.order = i + 1; // order从 1 开始
|
||||
node.sort = i + 1; // order从 1 开始
|
||||
node.parent = _parent || undefined; // 没有父节点说明是树的第一层
|
||||
const newNode = typeof customNodeFn === 'function' ? customNodeFn(node, fullPath) : node;
|
||||
if (newNode) {
|
||||
|
@ -382,19 +382,19 @@ export function insertNodes<T>(
|
|||
// 插入节点数组
|
||||
newNodes.forEach((newNode, index) => {
|
||||
newNode.parent = parent;
|
||||
newNode.order = startOrder + index;
|
||||
newNode.sort = startOrder + index;
|
||||
});
|
||||
array.splice(startIndex, 0, ...newNodes);
|
||||
} else {
|
||||
// 插入单个节点
|
||||
newNodes.parent = parent;
|
||||
newNodes.order = startOrder;
|
||||
newNodes.sort = startOrder;
|
||||
array.splice(startIndex, 0, newNodes);
|
||||
}
|
||||
// 更新插入节点之后的节点的 order
|
||||
// 更新插入节点之后的节点的 sort
|
||||
const newLength = Array.isArray(newNodes) ? newNodes.length : 1;
|
||||
for (let j = startIndex + newLength; j < array.length; j++) {
|
||||
array[j].order += newLength;
|
||||
array[j].sort += newLength;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -406,9 +406,9 @@ export function insertNodes<T>(
|
|||
const parentChildren = parent ? parent.children || [] : treeArr; // 父节点没有 children 属性,说明是树的第一层,使用 treeArr
|
||||
const index = parentChildren.findIndex((item) => item[customKey] === node[customKey]);
|
||||
if (position === 'before') {
|
||||
insertNewNodes(parentChildren, index, parent || node.parent, node.order);
|
||||
insertNewNodes(parentChildren, index, parent || node.parent, node.sort);
|
||||
} else if (position === 'after') {
|
||||
insertNewNodes(parentChildren, index + 1, parent || node.parent, node.order + 1);
|
||||
insertNewNodes(parentChildren, index + 1, parent || node.parent, node.sort + 1);
|
||||
} else if (position === 'inside') {
|
||||
if (!node.children) {
|
||||
node.children = [];
|
||||
|
@ -460,9 +460,9 @@ export function handleTreeDragDrop<T>(
|
|||
if (index !== -1) {
|
||||
parentChildren.splice(index, 1);
|
||||
|
||||
// 更新删除节点后的节点的 order
|
||||
// 更新删除节点后的节点的 sort
|
||||
for (let i = index; i < parentChildren.length; i++) {
|
||||
parentChildren[i].order -= 1;
|
||||
parentChildren[i].sort -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -857,6 +857,7 @@
|
|||
nextTick(() => {
|
||||
// 如果是没有缓存也不是编辑,则需要重置表单,因为 form-create 只有一个实例,已经被其他有数据的 tab 污染了,需要重置
|
||||
fApi.value?.resetFields();
|
||||
isInitPluginForm.value = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1136,7 +1137,9 @@
|
|||
}
|
||||
reportId.value = getGenerateId();
|
||||
requestVModel.value.reportId = reportId.value; // 存储报告ID
|
||||
if (isExecute) {
|
||||
debugSocket(executeType); // 开启websocket
|
||||
}
|
||||
let requestName = '';
|
||||
let requestModuleId = '';
|
||||
let apiDefinitionParams: Record<string, any> = {};
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { ScenarioStepType } from '@/enums/apiEnum';
|
||||
|
@ -15,17 +13,19 @@
|
|||
}>();
|
||||
// 场景步骤类型映射
|
||||
const scenarioStepMap = {
|
||||
[ScenarioStepType.QUOTE_API]: { label: 'apiScenario.quoteApi', color: 'rgb(var(--link-7))' },
|
||||
[ScenarioStepType.COPY_API]: { label: 'apiScenario.copyApi', color: 'rgb(var(--link-7))' },
|
||||
[ScenarioStepType.QUOTE_CASE]: { label: 'apiScenario.quoteCase', color: 'rgb(var(--success-7))' },
|
||||
[ScenarioStepType.COPY_CASE]: { label: 'apiScenario.copyCase', color: 'rgb(var(--success-7))' },
|
||||
[ScenarioStepType.QUOTE_SCENARIO]: { label: 'apiScenario.quoteScenario', color: 'rgb(var(--primary-7))' },
|
||||
[ScenarioStepType.COPY_SCENARIO]: { label: 'apiScenario.copyScenario', color: 'rgb(var(--primary-7))' },
|
||||
[ScenarioStepType.WAIT_TIME]: { label: 'apiScenario.waitTime', color: 'rgb(var(--warning-6))' },
|
||||
// [ScenarioStepType.QUOTE_API]: { label: 'apiScenario.quoteApi', color: 'rgb(var(--link-7))' },
|
||||
// [ScenarioStepType.COPY_API]: { label: 'apiScenario.copyApi', color: 'rgb(var(--link-7))' },
|
||||
// [ScenarioStepType.QUOTE_CASE]: { label: 'apiScenario.quoteCase', color: 'rgb(var(--success-7))' },
|
||||
// [ScenarioStepType.COPY_CASE]: { label: 'apiScenario.copyCase', color: 'rgb(var(--success-7))' },
|
||||
// [ScenarioStepType.QUOTE_SCENARIO]: { label: 'apiScenario.quoteScenario', color: 'rgb(var(--primary-7))' },
|
||||
// [ScenarioStepType.COPY_SCENARIO]: { label: 'apiScenario.copyScenario', color: 'rgb(var(--primary-7))' },
|
||||
// [ScenarioStepType.WAIT_TIME]: { label: 'apiScenario.waitTime', color: 'rgb(var(--warning-6))' },
|
||||
[ScenarioStepType.LOOP_CONTROLLER]: { label: 'apiScenario.loopControl', color: 'rgba(167, 98, 191, 1)' },
|
||||
[ScenarioStepType.IF_CONTROLLER]: { label: 'apiScenario.conditionControl', color: 'rgba(238, 80, 163, 1)' },
|
||||
[ScenarioStepType.ONCE_ONLY_CONTROLLER]: { label: 'apiScenario.onlyOnceControl', color: 'rgba(211, 68, 0, 1)' },
|
||||
[ScenarioStepType.SCRIPT_OPERATION]: { label: 'apiScenario.scriptOperation', color: 'rgba(20, 225, 198, 1)' },
|
||||
[ScenarioStepType.SCRIPT]: { label: 'apiScenario.scriptOperation', color: 'rgba(20, 225, 198, 1)' },
|
||||
// [ScenarioStepType.SCRIPT_OPERATION]: { label: 'apiScenario.scriptOperation', color: 'rgba(20, 225, 198, 1)' },
|
||||
// [ScenarioStepType.CUSTOM_API]: { label: 'apiScenario.customApi', color: 'rgb(var(--link-4))' },
|
||||
[ScenarioStepType.API_CASE]: { label: 'report.detail.api.apiCase', color: 'rgb(var(--link-4))' },
|
||||
[ScenarioStepType.CUSTOM_REQUEST]: { label: 'report.detail.api.apiCase', color: 'rgb(var(--link-4))' },
|
||||
[ScenarioStepType.API]: { label: 'report.detail.api', color: 'rgb(var(--link-4))' },
|
||||
|
|
|
@ -5,10 +5,11 @@
|
|||
<script setup lang="ts">
|
||||
import assertion from '@/components/business/ms-assertion/index.vue';
|
||||
|
||||
import { AssertionConfig } from '@/models/apiTest/scenario';
|
||||
|
||||
const assertions = ref([]);
|
||||
const assertionConfig = ref({
|
||||
enableGlobal: false,
|
||||
assertions: [],
|
||||
const assertionConfig = defineModel<AssertionConfig>('assertionConfig', {
|
||||
required: true,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
<template>
|
||||
<MsDescription :descriptions="descriptions"> </MsDescription>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import MsDescription, { Description } from '@/components/pure/ms-description/index.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { ScenarioDetail } from '@/models/apiTest/scenario';
|
||||
|
||||
const props = defineProps<{
|
||||
scenario: ScenarioDetail;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const descriptions = computed<Description[]>(() => [
|
||||
{
|
||||
label: t('apiScenario.belongModule'),
|
||||
value: props.scenario.modulePath,
|
||||
},
|
||||
{
|
||||
label: t('apiScenario.table.columns.createUser'),
|
||||
value: props.scenario.createUser,
|
||||
},
|
||||
{
|
||||
label: t('apiScenario.table.columns.createTime'),
|
||||
value: dayjs(props.scenario.createTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||
},
|
||||
{
|
||||
label: t('apiScenario.table.columns.updateTime'),
|
||||
value: dayjs(props.scenario.updateTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||
},
|
||||
]);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-alert v-if="isShowTip" :show-icon="false" class="mb-[16px]" type="warning" closable @close="addVisited">
|
||||
<a-alert v-if="!getIsVisited()" :show-icon="false" class="mb-[16px]" type="warning" closable @close="addVisited">
|
||||
{{ t('apiScenario.historyListTip') }}
|
||||
<template #close-element>
|
||||
<span class="text-[14px]">{{ t('common.notRemind') }}</span>
|
||||
|
@ -11,7 +11,6 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
|
@ -26,11 +25,10 @@
|
|||
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
const isShowTip = ref<boolean>(true);
|
||||
const visitedKey = 'scenarioHistoryTip';
|
||||
const { addVisited, getIsVisited } = useVisit(visitedKey);
|
||||
const props = defineProps<{
|
||||
sourceId: string | number;
|
||||
sourceId?: string | number;
|
||||
}>();
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
|
|
|
@ -11,10 +11,10 @@
|
|||
>
|
||||
<template #title>
|
||||
<div class="flex items-center gap-[8px]">
|
||||
<stepType
|
||||
v-if="props.requestType"
|
||||
v-show="props.requestType !== ScenarioStepType.CUSTOM_API"
|
||||
:type="props.requestType"
|
||||
<stepTypeVue
|
||||
v-if="props.step"
|
||||
v-show="props.step.stepType !== ScenarioStepType.CUSTOM_REQUEST"
|
||||
:step="props.step"
|
||||
/>
|
||||
{{ title }}
|
||||
</div>
|
||||
|
@ -22,23 +22,24 @@
|
|||
<div v-show="requestVModel.useEnv === 'false'" class="text-[14px] font-normal text-[var(--color-text-4)]">
|
||||
{{ t('apiScenario.env', { name: props.envDetailItem?.name }) }}
|
||||
</div>
|
||||
<MsSelect
|
||||
v-model:model-value="requestVModel.useEnv"
|
||||
:allow-search="false"
|
||||
:options="[
|
||||
{ label: t('common.quote'), value: 'true' },
|
||||
{ label: t('common.notQuote'), value: 'false' },
|
||||
]"
|
||||
:multiple="false"
|
||||
value-key="value"
|
||||
label-key="label"
|
||||
:prefix="t('project.environmental.env')"
|
||||
class="w-[150px]"
|
||||
@change="handleUseEnvChange"
|
||||
>
|
||||
</MsSelect>
|
||||
<a-select v-model:model-value="requestVModel.useEnv" class="w-[150px]" @change="handleUseEnvChange">
|
||||
<template #prefix>
|
||||
<div> {{ t('project.environmental.env') }} </div>
|
||||
</template>
|
||||
<a-option :value="true">{{ t('common.quote') }}</a-option>
|
||||
<a-option :value="false">{{ t('common.notQuote') }}</a-option>
|
||||
</a-select>
|
||||
</div>
|
||||
</template>
|
||||
<a-empty
|
||||
v-if="pluginError && !isHttpProtocol"
|
||||
:description="t('apiTestDebug.noPlugin')"
|
||||
class="h-[200px] items-center justify-center"
|
||||
>
|
||||
<template #image>
|
||||
<MsIcon type="icon-icon_plugin_outlined" size="48" />
|
||||
</template>
|
||||
</a-empty>
|
||||
<div v-show="!pluginError || isHttpProtocol" class="flex h-full flex-col">
|
||||
<div class="px-[18px] pt-[8px]">
|
||||
<div class="flex flex-wrap items-center justify-between gap-[12px]">
|
||||
|
@ -48,7 +49,7 @@
|
|||
v-model:model-value="requestVModel.protocol"
|
||||
:options="protocolOptions"
|
||||
:loading="protocolLoading"
|
||||
:disabled="props.requestType === ScenarioStepType.QUOTE_API"
|
||||
:disabled="_stepType.isQuoteApi"
|
||||
class="w-[90px]"
|
||||
@change="(val) => handleActiveDebugProtocolChange(val as string)"
|
||||
/>
|
||||
|
@ -60,15 +61,15 @@
|
|||
is-tag
|
||||
class="flex items-center"
|
||||
/>
|
||||
<a-tooltip v-if="!isHttpProtocol" :content="requestVModel.label" :mouse-enter-delay="500">
|
||||
<div class="one-line-text max-w-[350px]"> {{ requestVModel.label }}</div>
|
||||
<a-tooltip v-if="!isHttpProtocol" :content="requestVModel.name" :mouse-enter-delay="500">
|
||||
<div class="one-line-text max-w-[350px]"> {{ requestVModel.name }}</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<a-input-group v-if="isHttpProtocol" class="flex-1">
|
||||
<apiMethodSelect
|
||||
v-model:model-value="requestVModel.method"
|
||||
class="w-[140px]"
|
||||
:disabled="props.requestType === ScenarioStepType.QUOTE_API"
|
||||
:disabled="_stepType.isQuoteApi"
|
||||
@change="handleActiveDebugChange"
|
||||
/>
|
||||
<a-input
|
||||
|
@ -78,7 +79,7 @@
|
|||
allow-clear
|
||||
class="hover:z-10"
|
||||
:style="isUrlError ? 'border: 1px solid rgb(var(--danger-6);z-index: 10' : ''"
|
||||
:disabled="props.requestType === ScenarioStepType.QUOTE_API"
|
||||
:disabled="_stepType.isQuoteApi"
|
||||
@input="() => (isUrlError = false)"
|
||||
@change="handleUrlChange"
|
||||
/>
|
||||
|
@ -109,11 +110,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<a-input
|
||||
v-if="
|
||||
props.requestType &&
|
||||
[ScenarioStepType.QUOTE_API, ScenarioStepType.COPY_API].includes(props.requestType) &&
|
||||
isHttpProtocol
|
||||
"
|
||||
v-if="props.step?.stepType && (_stepType.isCopyApi || _stepType.isQuoteApi) && isHttpProtocol"
|
||||
v-model:model-value="requestVModel.name"
|
||||
:max-length="255"
|
||||
:placeholder="t('apiTestManagement.apiNamePlaceholder')"
|
||||
|
@ -130,7 +127,7 @@
|
|||
class="no-content relative mt-[8px] border-b"
|
||||
/>
|
||||
</div>
|
||||
<div ref="splitContainerRef" class="request-and-response h-[calc(100%-87px)]">
|
||||
<div ref="splitContainerRef" class="h-[calc(100%-87px)]">
|
||||
<MsSplitBox
|
||||
ref="verticalSplitBoxRef"
|
||||
v-model:size="splitBoxSize"
|
||||
|
@ -150,7 +147,7 @@
|
|||
>
|
||||
<div class="tab-pane-container">
|
||||
<a-spin
|
||||
v-if="requestVModel.activeTab === RequestComposition.PLUGIN"
|
||||
v-show="requestVModel.activeTab === RequestComposition.PLUGIN"
|
||||
:loading="pluginLoading"
|
||||
class="min-h-[100px] w-full"
|
||||
>
|
||||
|
@ -267,15 +264,6 @@
|
|||
</MsSplitBox>
|
||||
</div>
|
||||
</div>
|
||||
<a-empty
|
||||
v-if="pluginError && !isHttpProtocol"
|
||||
:description="t('apiTestDebug.noPlugin')"
|
||||
class="h-[200px] items-center justify-center"
|
||||
>
|
||||
<template #image>
|
||||
<MsIcon type="icon-icon_plugin_outlined" size="48" />
|
||||
</template>
|
||||
</a-empty>
|
||||
<!-- <addDependencyDrawer v-model:visible="showAddDependencyDrawer" :mode="addDependencyMode" /> -->
|
||||
</MsDrawer>
|
||||
</template>
|
||||
|
@ -291,8 +279,7 @@
|
|||
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||
import MsTab from '@/components/pure/ms-tab/index.vue';
|
||||
import assertion from '@/components/business/ms-assertion/index.vue';
|
||||
import MsSelect from '@/components/business/ms-select';
|
||||
import stepType from './stepType.vue';
|
||||
import stepTypeVue from './stepType/stepType.vue';
|
||||
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
||||
import apiMethodSelect from '@/views/api-test/components/apiMethodSelect.vue';
|
||||
import auth from '@/views/api-test/components/requestComposition/auth.vue';
|
||||
|
@ -316,6 +303,7 @@
|
|||
PluginConfig,
|
||||
RequestTaskResult,
|
||||
} from '@/models/apiTest/common';
|
||||
import { ScenarioStepItem } from '@/models/apiTest/scenario';
|
||||
import { ModuleTreeNode, TransferFileParams } from '@/models/common';
|
||||
import {
|
||||
RequestAuthType,
|
||||
|
@ -327,6 +315,7 @@
|
|||
ScenarioStepType,
|
||||
} from '@/enums/apiEnum';
|
||||
|
||||
import getStepType from './stepType/utils';
|
||||
import {
|
||||
defaultBodyParams,
|
||||
defaultBodyParamsItem,
|
||||
|
@ -366,8 +355,7 @@
|
|||
|
||||
const props = defineProps<{
|
||||
request?: RequestParam; // 请求参数集合
|
||||
requestType?: ScenarioStepType;
|
||||
stepName: string;
|
||||
step?: ScenarioStepItem;
|
||||
detailLoading?: boolean; // 详情加载状态
|
||||
envDetailItem?: {
|
||||
id?: string;
|
||||
|
@ -402,12 +390,9 @@
|
|||
type: 'api',
|
||||
id: '',
|
||||
useEnv: 'false',
|
||||
moduleId: 'root',
|
||||
protocol: 'HTTP',
|
||||
url: '',
|
||||
activeTab: RequestComposition.HEADER,
|
||||
label: t('apiTestDebug.newApi'),
|
||||
closable: true,
|
||||
method: RequestMethods.GET,
|
||||
unSaved: false,
|
||||
headers: [],
|
||||
|
@ -415,9 +400,7 @@
|
|||
query: [],
|
||||
rest: [],
|
||||
polymorphicName: '',
|
||||
name: '',
|
||||
path: '',
|
||||
projectId: '',
|
||||
uploadFileIds: [],
|
||||
linkFileIds: [],
|
||||
authConfig: {
|
||||
|
@ -455,16 +438,25 @@
|
|||
followRedirects: true,
|
||||
autoRedirects: false,
|
||||
},
|
||||
responseActiveTab: ResponseComposition.BODY,
|
||||
response: cloneDeep(defaultResponse),
|
||||
responseActiveTab: ResponseComposition.BODY,
|
||||
isNew: true,
|
||||
executeLoading: false,
|
||||
};
|
||||
|
||||
const requestVModel = ref<RequestParam>(props.request || defaultDebugParams);
|
||||
const requestVModel = ref<RequestParam>(defaultDebugParams);
|
||||
const _stepType = computed(() => {
|
||||
if (props.step) {
|
||||
return getStepType(props.step);
|
||||
}
|
||||
return {
|
||||
isCopyApi: false,
|
||||
isQuoteApi: false,
|
||||
};
|
||||
});
|
||||
const title = computed(() => {
|
||||
if (props.requestType && [ScenarioStepType.COPY_API, ScenarioStepType.QUOTE_API].includes(props.requestType)) {
|
||||
return props.stepName;
|
||||
if (_stepType.value.isCopyApi || _stepType.value.isQuoteApi) {
|
||||
return props.step?.name;
|
||||
}
|
||||
return t('apiScenario.customApi');
|
||||
});
|
||||
|
@ -604,11 +596,9 @@
|
|||
const currentPluginScript = computed<Record<string, any>[]>(
|
||||
() => pluginScriptMap.value[requestVModel.value.protocol]?.script || []
|
||||
);
|
||||
const isCopyApiNeedInit = computed(
|
||||
() => props.requestType === ScenarioStepType.COPY_API && props.request?.request === null
|
||||
);
|
||||
const isCopyApiNeedInit = computed(() => _stepType.value.isCopyApi && props.request?.request === null);
|
||||
const isEditableApi = computed(
|
||||
() => props.requestType === ScenarioStepType.COPY_API || props.requestType === ScenarioStepType.CUSTOM_API
|
||||
() => _stepType.value.isCopyApi || props.step?.stepType === ScenarioStepType.CUSTOM_REQUEST || !props.step
|
||||
);
|
||||
|
||||
// 处理插件表单输入框变化
|
||||
|
@ -649,7 +639,7 @@
|
|||
controlPluginFormFields().forEach((key) => {
|
||||
form[key] = formData[key];
|
||||
});
|
||||
fApi.value?.setValue(form);
|
||||
fApi.value?.setValue(cloneDeep(form));
|
||||
setTimeout(() => {
|
||||
// 初始化时赋值会触发表单数据变更,300ms 是为了与 handlePluginFormChange的防抖时间保持一致
|
||||
isInitPluginForm.value = true;
|
||||
|
@ -657,8 +647,10 @@
|
|||
});
|
||||
}
|
||||
} else {
|
||||
nextTick(() => {
|
||||
fApi.value?.nextTick(() => {
|
||||
controlPluginFormFields();
|
||||
fApi.value?.resetFields();
|
||||
isInitPluginForm.value = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -802,11 +794,13 @@
|
|||
watch(
|
||||
() => showResponse.value,
|
||||
(val) => {
|
||||
nextTick(() => {
|
||||
if (val) {
|
||||
changeVerticalExpand(true);
|
||||
} else {
|
||||
changeVerticalExpand(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -869,13 +863,13 @@
|
|||
*/
|
||||
function makeRequestParams(executeType?: 'localExec' | 'serverExec') {
|
||||
const isExecute = executeType === 'localExec' || executeType === 'serverExec';
|
||||
const { formDataBody, wwwFormBody } = requestVModel.value.body;
|
||||
const polymorphicName = protocolOptions.value.find(
|
||||
(e) => e.value === requestVModel.value.protocol
|
||||
)?.polymorphicName; // 协议多态名称
|
||||
let parseRequestBodyResult;
|
||||
let requestParams;
|
||||
if (isHttpProtocol.value) {
|
||||
const { formDataBody, wwwFormBody } = requestVModel.value.body;
|
||||
const realFormDataBodyValues = filterKeyValParams(
|
||||
formDataBody.formValues,
|
||||
defaultBodyParamsItem,
|
||||
|
@ -903,7 +897,6 @@
|
|||
},
|
||||
},
|
||||
headers: filterKeyValParams(requestVModel.value.headers, defaultHeaderParamsItem, isExecute).validParams,
|
||||
method: requestVModel.value.method,
|
||||
otherConfig: requestVModel.value.otherConfig,
|
||||
path: requestVModel.value.url || requestVModel.value.path,
|
||||
query: filterKeyValParams(requestVModel.value.query, defaultRequestParamsItem, isExecute).validParams,
|
||||
|
@ -917,27 +910,17 @@
|
|||
polymorphicName,
|
||||
};
|
||||
}
|
||||
reportId.value = getGenerateId();
|
||||
requestVModel.value.reportId = reportId.value; // 存储报告ID
|
||||
if (isExecute) {
|
||||
debugSocket(executeType); // 开启websocket
|
||||
let requestName = '';
|
||||
let requestModuleId = '';
|
||||
const apiDefinitionParams: Record<string, any> = {};
|
||||
requestName = requestVModel.value.name;
|
||||
requestModuleId = requestVModel.value.moduleId;
|
||||
}
|
||||
return {
|
||||
id: requestVModel.value.id.toString(),
|
||||
reportId: reportId.value,
|
||||
environmentId: props.envDetailItem?.id || '',
|
||||
name: requestName,
|
||||
moduleId: requestModuleId,
|
||||
...apiDefinitionParams,
|
||||
...requestParams,
|
||||
id: requestVModel.value.id,
|
||||
activeTab: requestVModel.value.protocol === 'HTTP' ? RequestComposition.HEADER : RequestComposition.PLUGIN,
|
||||
responseActiveTab: ResponseComposition.BODY,
|
||||
protocol: requestVModel.value.protocol,
|
||||
method: isHttpProtocol.value ? requestVModel.value.method : requestVModel.value.protocol,
|
||||
path: isHttpProtocol.value ? requestVModel.value.url || requestVModel.value.path : undefined,
|
||||
request: {
|
||||
...requestParams,
|
||||
name: requestName,
|
||||
name: requestVModel.value.name,
|
||||
children: [
|
||||
{
|
||||
polymorphicName: 'MsCommonElement', // 协议多态名称,写死MsCommonElement
|
||||
|
@ -946,11 +929,7 @@
|
|||
preProcessorConfig: filterConditionsSqlValidParams(requestVModel.value.children[0].preProcessorConfig),
|
||||
},
|
||||
],
|
||||
},
|
||||
...parseRequestBodyResult,
|
||||
projectId: appStore.currentProjectId,
|
||||
frontendDebug: executeType === 'localExec',
|
||||
isNew: requestVModel.value.isNew,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -959,7 +938,6 @@
|
|||
* @param val 执行类型
|
||||
*/
|
||||
async function execute(executeType?: 'localExec' | 'serverExec') {
|
||||
// todo 执行,待测试
|
||||
if (isHttpProtocol.value) {
|
||||
try {
|
||||
if (!props.executeApi) return;
|
||||
|
@ -1008,8 +986,7 @@
|
|||
}
|
||||
|
||||
function handleContinue() {
|
||||
requestVModel.value.isNew = false; // 添加完就不是新建了
|
||||
emit('addStep', requestVModel.value);
|
||||
emit('addStep', cloneDeep(makeRequestParams()));
|
||||
}
|
||||
|
||||
function handleSave() {
|
||||
|
@ -1020,7 +997,7 @@
|
|||
function handleClose() {
|
||||
// 关闭时若不是创建行为则是编辑行为,需要触发 applyStep
|
||||
if (!requestVModel.value.isNew) {
|
||||
emit('applyStep', { ...requestVModel.value, ...makeRequestParams() });
|
||||
emit('applyStep', cloneDeep(makeRequestParams()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1037,7 +1014,6 @@
|
|||
parseRequestBodyResult = parseRequestBodyFiles(res.request.body); // 解析请求体中的文件,将详情中的文件 id 集合收集,更新时以判断文件是否删除以及是否新上传的文件
|
||||
}
|
||||
requestVModel.value = {
|
||||
responseActiveTab: ResponseComposition.BODY,
|
||||
executeLoading: false,
|
||||
activeTab: res.protocol === 'HTTP' ? RequestComposition.HEADER : RequestComposition.PLUGIN,
|
||||
unSaved: false,
|
||||
|
@ -1046,7 +1022,6 @@
|
|||
...res.request,
|
||||
...res,
|
||||
response: cloneDeep(defaultResponse),
|
||||
responseDefinition: res.response.map((e) => ({ ...e, responseActiveTab: ResponseComposition.BODY })),
|
||||
url: res.path,
|
||||
name: res.name, // request里面还有个name但是是null
|
||||
id: res.id,
|
||||
|
@ -1068,9 +1043,10 @@
|
|||
async (val) => {
|
||||
if (val) {
|
||||
if (props.request) {
|
||||
requestVModel.value = { ...defaultDebugParams, ...props.request };
|
||||
console.log('props.request', props.request);
|
||||
requestVModel.value = cloneDeep(props.request);
|
||||
if (
|
||||
props.requestType === ScenarioStepType.QUOTE_API ||
|
||||
_stepType.value.isQuoteApi ||
|
||||
isCopyApiNeedInit.value
|
||||
// 引用接口时,需要初始化引用接口的详情;复制只在第一次初始化的时候需要加载后台数据(request.request是复制请求时列表参数字段request会为 null,以此判断释放第一次初始化)
|
||||
) {
|
||||
|
@ -1106,12 +1082,20 @@
|
|||
// });
|
||||
// }
|
||||
// }
|
||||
} else {
|
||||
requestVModel.value = cloneDeep({
|
||||
...defaultDebugParams,
|
||||
id: getGenerateId(),
|
||||
});
|
||||
}
|
||||
await initProtocolList();
|
||||
if (props.request) {
|
||||
handleActiveDebugProtocolChange(requestVModel.value.protocol);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
<template>
|
||||
<div
|
||||
class="text-nowrap rounded-[0_999px_999px_0] border border-solid px-[8px] py-[2px] text-[12px] leading-[16px]"
|
||||
:style="{
|
||||
borderColor: type.color,
|
||||
color: type.color,
|
||||
}"
|
||||
>
|
||||
{{ type.label }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { ScenarioStepType } from '@/enums/apiEnum';
|
||||
|
||||
const props = defineProps<{
|
||||
type: ScenarioStepType;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// 场景步骤类型映射
|
||||
const scenarioStepMap = {
|
||||
[ScenarioStepType.QUOTE_API]: { label: 'apiScenario.quoteApi', color: 'rgb(var(--link-7))' },
|
||||
[ScenarioStepType.COPY_API]: { label: 'apiScenario.copyApi', color: 'rgb(var(--link-7))' },
|
||||
[ScenarioStepType.QUOTE_CASE]: { label: 'apiScenario.quoteCase', color: 'rgb(var(--success-7))' },
|
||||
[ScenarioStepType.COPY_CASE]: { label: 'apiScenario.copyCase', color: 'rgb(var(--success-7))' },
|
||||
[ScenarioStepType.QUOTE_SCENARIO]: { label: 'apiScenario.quoteScenario', color: 'rgb(var(--primary-7))' },
|
||||
[ScenarioStepType.COPY_SCENARIO]: { label: 'apiScenario.copyScenario', color: 'rgb(var(--primary-7))' },
|
||||
[ScenarioStepType.WAIT_TIME]: { label: 'apiScenario.waitTime', color: 'rgb(var(--warning-6))' },
|
||||
[ScenarioStepType.LOOP_CONTROL]: { label: 'apiScenario.loopControl', color: 'rgba(167, 98, 191, 1)' },
|
||||
[ScenarioStepType.CONDITION_CONTROL]: { label: 'apiScenario.conditionControl', color: 'rgba(238, 80, 163, 1)' },
|
||||
[ScenarioStepType.ONLY_ONCE_CONTROL]: { label: 'apiScenario.onlyOnceControl', color: 'rgba(211, 68, 0, 1)' },
|
||||
[ScenarioStepType.SCRIPT_OPERATION]: { label: 'apiScenario.scriptOperation', color: 'rgba(20, 225, 198, 1)' },
|
||||
[ScenarioStepType.CUSTOM_API]: { label: 'apiScenario.customApi', color: 'rgb(var(--link-4))' },
|
||||
};
|
||||
|
||||
const type = computed(() => {
|
||||
const config = scenarioStepMap[props.type];
|
||||
return {
|
||||
border: `1px solid ${config?.color}`,
|
||||
color: config?.color,
|
||||
label: t(config?.label),
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -0,0 +1,63 @@
|
|||
<template>
|
||||
<div
|
||||
class="text-nowrap rounded-[0_999px_999px_0] border border-solid px-[8px] py-[2px] text-[12px] leading-[16px]"
|
||||
:style="{
|
||||
borderColor: type.color,
|
||||
color: type.color,
|
||||
}"
|
||||
>
|
||||
{{ type.label }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { ScenarioStepItem } from '@/models/apiTest/scenario';
|
||||
import { ScenarioStepType } from '@/enums/apiEnum';
|
||||
|
||||
import getStepType from './utils';
|
||||
|
||||
const props = defineProps<{
|
||||
step: ScenarioStepItem;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// 场景步骤类型映射
|
||||
const scenarioStepMap = {
|
||||
[ScenarioStepType.CONSTANT_TIMER]: { label: 'apiScenario.waitTime', color: 'rgb(var(--warning-6))' },
|
||||
[ScenarioStepType.LOOP_CONTROLLER]: { label: 'apiScenario.loopControl', color: 'rgba(167, 98, 191, 1)' },
|
||||
[ScenarioStepType.IF_CONTROLLER]: { label: 'apiScenario.conditionControl', color: 'rgba(238, 80, 163, 1)' },
|
||||
[ScenarioStepType.ONCE_ONLY_CONTROLLER]: { label: 'apiScenario.onlyOnceControl', color: 'rgba(211, 68, 0, 1)' },
|
||||
[ScenarioStepType.SCRIPT]: { label: 'apiScenario.scriptOperation', color: 'rgba(20, 225, 198, 1)' },
|
||||
[ScenarioStepType.CUSTOM_REQUEST]: { label: 'apiScenario.customApi', color: 'rgb(var(--link-4))' },
|
||||
};
|
||||
|
||||
const type = computed(() => {
|
||||
let config = scenarioStepMap[props.step.stepType];
|
||||
const stepType = getStepType(props.step);
|
||||
if (!config) {
|
||||
if (stepType.isQuoteApi) {
|
||||
config = { label: 'apiScenario.quoteApi', color: 'rgb(var(--link-7))' };
|
||||
} else if (stepType.isCopyApi) {
|
||||
config = { label: 'apiScenario.copyApi', color: 'rgb(var(--link-7))' };
|
||||
} else if (stepType.isQuoteCase) {
|
||||
config = { label: 'apiScenario.quoteCase', color: 'rgb(var(--success-7))' };
|
||||
} else if (stepType.isCopyCase) {
|
||||
config = { label: 'apiScenario.copyCase', color: 'rgb(var(--success-7))' };
|
||||
} else if (stepType.isQuoteScenario) {
|
||||
config = { label: 'apiScenario.quoteScenario', color: 'rgb(var(--primary-7))' };
|
||||
} else if (stepType.isCopyScenario) {
|
||||
config = { label: 'apiScenario.copyScenario', color: 'rgb(var(--primary-7))' };
|
||||
}
|
||||
}
|
||||
return {
|
||||
border: `1px solid ${config?.color}`,
|
||||
color: config?.color,
|
||||
label: t(config?.label),
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -0,0 +1,22 @@
|
|||
import { ScenarioStepItem } from '@/models/apiTest/scenario';
|
||||
import { ScenarioStepRefType, ScenarioStepType } from '@/enums/apiEnum';
|
||||
|
||||
export default function getStepType(step: ScenarioStepItem) {
|
||||
const isCopyApi = step.stepType === ScenarioStepType.API && step.refType === ScenarioStepRefType.COPY;
|
||||
const isQuoteApi = step.stepType === ScenarioStepType.API && step.refType === ScenarioStepRefType.REF;
|
||||
const isCopyCase = step.stepType === ScenarioStepType.API_CASE && step.refType === ScenarioStepRefType.COPY;
|
||||
const isQuoteCase = step.stepType === ScenarioStepType.API_CASE && step.refType === ScenarioStepRefType.REF;
|
||||
const isCopyScenario = step.stepType === ScenarioStepType.API_SCENARIO && step.refType === ScenarioStepRefType.COPY;
|
||||
const isQuoteScenario =
|
||||
step.stepType === ScenarioStepType.API_SCENARIO &&
|
||||
[ScenarioStepRefType.REF, ScenarioStepRefType.PARTIAL_REF].includes(step.refType);
|
||||
|
||||
return {
|
||||
isCopyApi,
|
||||
isQuoteApi,
|
||||
isCopyCase,
|
||||
isQuoteCase,
|
||||
isCopyScenario,
|
||||
isQuoteScenario,
|
||||
};
|
||||
}
|
|
@ -1,64 +1,144 @@
|
|||
import { ScenarioStepLoopType, ScenarioStepLoopWhileType } from '@/models/apiTest/scenario';
|
||||
import { Scenario } from '@/models/apiTest/scenario';
|
||||
import {
|
||||
ApiScenarioStatus,
|
||||
RequestAssertionCondition,
|
||||
ScenarioFailureStrategy,
|
||||
ScenarioStepLoopTypeEnum,
|
||||
WhileConditionType,
|
||||
} from '@/enums/apiEnum';
|
||||
|
||||
export const defaultStepItemCommon = {
|
||||
checked: false,
|
||||
expanded: false,
|
||||
enabled: true,
|
||||
enable: true,
|
||||
children: [],
|
||||
loopNum: 0,
|
||||
loopType: 'num' as ScenarioStepLoopType,
|
||||
loopSpace: 0,
|
||||
variableName: '',
|
||||
variablePrefix: '',
|
||||
loopWhileType: 'condition' as ScenarioStepLoopWhileType,
|
||||
variableVal: '',
|
||||
condition: 'equal',
|
||||
overTime: 0,
|
||||
expression: '',
|
||||
waitTime: 0,
|
||||
description: '',
|
||||
config: {
|
||||
id: '',
|
||||
copyFromStepId: '', // 如果步骤是复制的,这个字段是复制的步骤id
|
||||
name: '',
|
||||
enable: true,
|
||||
polymorphicName: '', // 多态名称,用于后台区分使用的是哪个组件
|
||||
// 自定义请求
|
||||
customizeRequest: false, // 是否自定义请求
|
||||
customizeRequestEnvEnable: false, // 是否启用环境
|
||||
// 条件控制器
|
||||
value: '', // 变量值
|
||||
variable: '', // 变量名
|
||||
condition: RequestAssertionCondition.EQUALS, // 条件操作符
|
||||
loopType: ScenarioStepLoopTypeEnum.LOOP_COUNT,
|
||||
forEachController: {
|
||||
loopTime: 0, // 循环间隔时间
|
||||
value: '', // 变量值
|
||||
variable: '', // 变量名
|
||||
},
|
||||
msCountController: {
|
||||
loops: 0, // 循环次数
|
||||
},
|
||||
whileController: {
|
||||
conditionType: WhileConditionType.CONDITION, // 条件类型
|
||||
timeout: 0, // 超时时间
|
||||
msWhileScript: {
|
||||
scriptValue: '', // 脚本值
|
||||
}, // 脚本
|
||||
msWhileVariable: {
|
||||
condition: RequestAssertionCondition.EQUALS, // 条件操作符
|
||||
value: '', // 变量值
|
||||
variable: '', // 变量名
|
||||
}, // 变量
|
||||
},
|
||||
waitTime: 0, // 等待时间
|
||||
},
|
||||
createActionsVisible: false,
|
||||
};
|
||||
|
||||
export const defaultScenario: Scenario = {
|
||||
name: '',
|
||||
moduleId: '',
|
||||
priority: 'P0',
|
||||
status: ApiScenarioStatus.UNDERWAY,
|
||||
tags: [],
|
||||
projectId: '',
|
||||
description: '',
|
||||
grouped: false,
|
||||
environmentId: '',
|
||||
scenarioConfig: {
|
||||
variable: {
|
||||
commonVariables: [],
|
||||
csvVariables: [],
|
||||
},
|
||||
preProcessorConfig: {
|
||||
enableGlobal: false,
|
||||
processors: [],
|
||||
},
|
||||
postProcessorConfig: {
|
||||
enableGlobal: false,
|
||||
processors: [],
|
||||
},
|
||||
assertionConfig: {
|
||||
assertions: [],
|
||||
},
|
||||
otherConfig: {
|
||||
enableGlobalCookie: true,
|
||||
enableCookieShare: false,
|
||||
enableStepWait: false,
|
||||
stepWaitTime: 1000,
|
||||
failureStrategy: ScenarioFailureStrategy.CONTINUE,
|
||||
},
|
||||
},
|
||||
steps: [],
|
||||
stepDetails: {},
|
||||
executeTime: 0,
|
||||
executeSuccessCount: 0,
|
||||
executeFailCount: 0,
|
||||
uploadFileIds: [],
|
||||
linkFileIds: [],
|
||||
// 前端渲染字段
|
||||
label: '',
|
||||
closable: true,
|
||||
isNew: true,
|
||||
unSaved: false,
|
||||
executeLoading: false, // 执行loading
|
||||
};
|
||||
|
||||
export const conditionOptions = [
|
||||
{
|
||||
value: 'equal',
|
||||
value: RequestAssertionCondition.EQUALS,
|
||||
label: 'apiScenario.equal',
|
||||
},
|
||||
{
|
||||
value: 'notEqualTo',
|
||||
value: RequestAssertionCondition.NOT_EQUALS,
|
||||
label: 'apiScenario.notEqualTo',
|
||||
},
|
||||
{
|
||||
value: 'greater',
|
||||
value: RequestAssertionCondition.GT,
|
||||
label: 'apiScenario.greater',
|
||||
},
|
||||
{
|
||||
value: 'less',
|
||||
value: RequestAssertionCondition.LT,
|
||||
label: 'apiScenario.less',
|
||||
},
|
||||
{
|
||||
value: 'greaterOrEqual',
|
||||
value: RequestAssertionCondition.GT_OR_EQUALS,
|
||||
label: 'apiScenario.greaterOrEqual',
|
||||
},
|
||||
{
|
||||
value: 'lessOrEqual',
|
||||
value: RequestAssertionCondition.LT_OR_EQUALS,
|
||||
label: 'apiScenario.lessOrEqual',
|
||||
},
|
||||
{
|
||||
value: 'include',
|
||||
value: RequestAssertionCondition.CONTAINS,
|
||||
label: 'apiScenario.include',
|
||||
},
|
||||
{
|
||||
value: 'notInclude',
|
||||
value: RequestAssertionCondition.NOT_CONTAINS,
|
||||
label: 'apiScenario.notInclude',
|
||||
},
|
||||
{
|
||||
value: 'null',
|
||||
value: RequestAssertionCondition.EMPTY,
|
||||
label: 'apiScenario.null',
|
||||
},
|
||||
{
|
||||
value: 'notNull',
|
||||
value: RequestAssertionCondition.NOT_EMPTY,
|
||||
label: 'apiScenario.notNull',
|
||||
},
|
||||
];
|
||||
|
|
|
@ -96,7 +96,6 @@
|
|||
|
||||
import { getExecuteHistory } from '@/api/modules/api-test/scenario';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
import { ExecuteHistoryItem } from '@/models/apiTest/scenario';
|
||||
import { ExecuteStatusFilters } from '@/enums/apiEnum';
|
||||
|
@ -108,11 +107,10 @@
|
|||
const statusFilters = ref(Object.keys(ExecuteStatusFilters));
|
||||
const tableQueryParams = ref<any>();
|
||||
|
||||
const appStore = useAppStore();
|
||||
const keyword = ref('');
|
||||
const { t } = useI18n();
|
||||
const props = defineProps<{
|
||||
scenarioId: string; // 详情 id
|
||||
scenarioId?: string | number; // 详情 id
|
||||
readOnly?: boolean;
|
||||
}>();
|
||||
const columns: MsTableColumn = [
|
||||
|
@ -174,7 +172,7 @@
|
|||
},
|
||||
];
|
||||
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(
|
||||
getExecuteHistory,
|
||||
{
|
||||
columns,
|
||||
|
|
|
@ -42,21 +42,22 @@
|
|||
import batchAddKeyVal from '@/views/api-test/components/batchAddKeyVal.vue';
|
||||
import paramTable, { ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
|
||||
|
||||
import { CommonVariable } from '@/models/apiTest/scenario';
|
||||
|
||||
import { defaultHeaderParamsItem } from '@/views/api-test/components/config';
|
||||
import { filterKeyValParams } from '@/views/api-test/components/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
activeKey?: string;
|
||||
params: any[];
|
||||
params: CommonVariable[];
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:params', value: any[]): void;
|
||||
(e: 'update:params', value: CommonVariable[]): void;
|
||||
(e: 'change'): void; // 数据发生变化
|
||||
}>();
|
||||
const innerParams = useVModel(props, 'params', emit);
|
||||
const isShowTip = ref<boolean>(true);
|
||||
const { t } = useI18n();
|
||||
const activeRadio = ref('convention');
|
||||
const searchValue = ref('');
|
||||
const firstSearch = ref(true);
|
||||
const backupParams = ref(props.params);
|
||||
|
|
|
@ -14,22 +14,22 @@
|
|||
import postcondition from '@/views/api-test/components/requestComposition/postcondition.vue';
|
||||
import precondition from '@/views/api-test/components/requestComposition/precondition.vue';
|
||||
|
||||
import { ExecuteConditionConfig } from '@/models/apiTest/common';
|
||||
|
||||
const activeLayout = ref<'horizontal' | 'vertical'>('vertical');
|
||||
const preProcessorConfig = ref({
|
||||
enableGlobal: false,
|
||||
processors: [],
|
||||
const preProcessorConfig = defineModel<ExecuteConditionConfig>('preProcessorConfig', {
|
||||
required: true,
|
||||
});
|
||||
const postProcessorConfig = ref({
|
||||
enableGlobal: false,
|
||||
processors: [],
|
||||
const postProcessorConfig = defineModel<ExecuteConditionConfig>('postProcessorConfig', {
|
||||
required: true,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.condition {
|
||||
flex-shrink: 0;
|
||||
overflow: overlay;
|
||||
width: 100%;
|
||||
height: 700px;
|
||||
overflow: overlay;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -171,7 +171,7 @@
|
|||
readOnly: false,
|
||||
}
|
||||
);
|
||||
const emit = defineEmits(['init', 'newScenario', 'import', 'folderNodeSelect', 'clickScenario', 'changeProtocol']);
|
||||
const emit = defineEmits(['init', 'newScenario', 'import', 'folderNodeSelect', 'changeProtocol']);
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
|
|
|
@ -129,7 +129,7 @@
|
|||
v-permission="['PROJECT_API_SCENARIO:READ+EXECUTE']"
|
||||
type="text"
|
||||
class="!mr-0"
|
||||
@click="Message.info('// todo @ba1q1')"
|
||||
@click="openScenarioTab(record)"
|
||||
>
|
||||
{{ t('apiScenario.execute') }}
|
||||
</MsButton>
|
||||
|
@ -138,7 +138,7 @@
|
|||
v-permission="['PROJECT_API_SCENARIO:READ+ADD']"
|
||||
type="text"
|
||||
class="!mr-0"
|
||||
@click="Message.info('// todo @ba1q1')"
|
||||
@click="openScenarioTab(record, true)"
|
||||
>
|
||||
{{ t('common.copy') }}
|
||||
</MsButton>
|
||||
|
@ -317,6 +317,7 @@
|
|||
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
|
||||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
|
||||
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
||||
import type { CaseLevel } from '@/components/business/ms-case-associate/types';
|
||||
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||
import apiStatus from '@/views/api-test/components/apiStatus.vue';
|
||||
|
@ -349,6 +350,11 @@
|
|||
offspringIds: string[];
|
||||
readOnly?: boolean; // 是否是只读模式
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'openScenario', record: ApiScenarioTableItem, isCopy?: boolean): void;
|
||||
(e: 'refreshModuleTree', params: any): void;
|
||||
}>();
|
||||
|
||||
const lastReportStatusFilterVisible = ref(false);
|
||||
const lastReportStatusListFilters = ref<string[]>(Object.keys(ReportStatus[ReportEnum.API_SCENARIO_REPORT]));
|
||||
const lastReportStatusFilters = computed(() => {
|
||||
|
@ -375,7 +381,6 @@
|
|||
text: 'P3',
|
||||
},
|
||||
]);
|
||||
const emit = defineEmits(['refreshModuleTree']);
|
||||
const keyword = ref('');
|
||||
const moveModalVisible = ref(false);
|
||||
const isBatchMove = ref(false); // 是否批量移动场景
|
||||
|
@ -869,6 +874,7 @@
|
|||
loadScenarioList(true);
|
||||
resetSelector();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
scenarioBatchOptTreeLoading.value = false;
|
||||
|
@ -933,8 +939,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
function openScenarioTab(record: ApiScenarioTableItem) {
|
||||
Message.info('// todo @ba1q1');
|
||||
function openScenarioTab(record: ApiScenarioTableItem, isCopy = false) {
|
||||
emit('openScenario', record, isCopy);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
|
|
|
@ -12,11 +12,11 @@
|
|||
</a-tooltip>
|
||||
</div>
|
||||
<div class="mb-[16px] mt-[10px] flex items-center gap-[8px]">
|
||||
<a-switch v-model:model-value="form.envCookie" type="line" size="small" />
|
||||
<a-switch v-model:model-value="form.enableGlobalCookie" type="line" size="small" />
|
||||
{{ t('apiScenario.setting.environment.cookie') }}
|
||||
</div>
|
||||
<div class="mb-[16px] flex items-center gap-[8px]">
|
||||
<a-switch v-model:model-value="form.shareCookie" type="line" size="small" />
|
||||
<a-switch v-model:model-value="form.enableCookieShare" type="line" size="small" />
|
||||
{{ t('apiScenario.setting.share.cookie') }}
|
||||
<a-tooltip :content="t('apiScenario.setting.share.cookie.tip')" position="right">
|
||||
<div>
|
||||
|
@ -31,7 +31,7 @@
|
|||
{{ t('apiScenario.setting.run.config') }}
|
||||
</div>
|
||||
<div class="mb-[16px] mt-[10px] flex items-center gap-[8px]">
|
||||
<a-switch v-model:model-value="form.waitTime" type="line" size="small" />
|
||||
<a-switch v-model:model-value="form.enableStepWait" type="line" size="small" />
|
||||
{{ t('apiScenario.setting.step.waitTime') }}
|
||||
<a-tooltip :content="t('apiScenario.setting.waitTime.tip')">
|
||||
<div>
|
||||
|
@ -43,14 +43,14 @@
|
|||
</a-tooltip>
|
||||
</div>
|
||||
|
||||
<a-form-item v-if="form.waitTime" class="flex-1">
|
||||
<a-form-item v-if="form.stepWaitTime" class="flex-1">
|
||||
<template #label>
|
||||
<div class="flex items-center">
|
||||
{{ t('apiScenario.setting.waitTime') }}
|
||||
<div class="text-[var(--color-text-brand)]">(ms)</div>
|
||||
</div>
|
||||
</template>
|
||||
<a-input-number v-model:model-value="form.connectTimeout" mode="button" :step="100" :min="0" class="w-[160px]" />
|
||||
<a-input-number v-model:model-value="form.stepWaitTime" mode="button" :step="100" :min="0" class="w-[160px]" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item class="flex-1">
|
||||
|
@ -59,9 +59,9 @@
|
|||
{{ t('apiScenario.setting.step.rule') }}
|
||||
</div>
|
||||
</template>
|
||||
<a-radio-group v-model:model-value="form.rule">
|
||||
<a-radio value="ignore">{{ t('apiScenario.setting.step.rule.ignore') }}</a-radio>
|
||||
<a-radio value="stop">{{ t('apiScenario.setting.step.rule.stop') }}</a-radio>
|
||||
<a-radio-group v-model:model-value="form.failureStrategy">
|
||||
<a-radio :value="ScenarioFailureStrategy.CONTINUE">{{ t('apiScenario.setting.step.rule.ignore') }}</a-radio>
|
||||
<a-radio :value="ScenarioFailureStrategy.STOP">{{ t('apiScenario.setting.step.rule.stop') }}</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
@ -70,21 +70,14 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { OtherConfig } from '@/models/apiTest/scenario';
|
||||
import { ScenarioFailureStrategy } from '@/enums/apiEnum';
|
||||
|
||||
const { t } = useI18n();
|
||||
// const emit = defineEmits(['update:formModeValue']); // 更新表单值?
|
||||
|
||||
// const props = defineProps<{
|
||||
// 详情传入的表单值?
|
||||
// }>();
|
||||
|
||||
const initForm = {
|
||||
envCookie: false,
|
||||
shareCookie: false,
|
||||
waitTime: false,
|
||||
connectTimeout: 0,
|
||||
rule: 'ignore',
|
||||
};
|
||||
const form = ref({ ...initForm });
|
||||
const form = defineModel<OtherConfig>('otherConfig', {
|
||||
required: true,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
|
|
@ -45,13 +45,13 @@
|
|||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import { ScenarioStepItem } from '../stepTree.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { findNodeByKey, getGenerateId } from '@/utils';
|
||||
|
||||
import { CreateStepAction } from '@/models/apiTest/scenario';
|
||||
import { ScenarioAddStepActionType, ScenarioStepType } from '@/enums/apiEnum';
|
||||
import { CreateStepAction, ScenarioStepItem } from '@/models/apiTest/scenario';
|
||||
import { ScenarioAddStepActionType, ScenarioStepRefType, ScenarioStepType } from '@/enums/apiEnum';
|
||||
|
||||
import useCreateActions from './useCreateActions';
|
||||
import { defaultStepItemCommon } from '@/views/api-test/scenario/components/config';
|
||||
|
@ -74,6 +74,7 @@
|
|||
);
|
||||
}>();
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
|
@ -101,9 +102,10 @@
|
|||
if (step.value && props.createStepAction) {
|
||||
handleCreateStep(
|
||||
{
|
||||
type: ScenarioStepType.LOOP_CONTROL,
|
||||
stepType: ScenarioStepType.LOOP_CONTROLLER,
|
||||
name: t('apiScenario.loopControl'),
|
||||
} as ScenarioStepItem,
|
||||
projectId: appStore.currentProjectId,
|
||||
},
|
||||
step.value,
|
||||
steps.value,
|
||||
props.createStepAction,
|
||||
|
@ -112,10 +114,12 @@
|
|||
} else {
|
||||
steps.value.push({
|
||||
...cloneDeep(defaultStepItemCommon),
|
||||
stepId: getGenerateId(),
|
||||
order: steps.value.length + 1,
|
||||
type: ScenarioStepType.LOOP_CONTROL,
|
||||
id: getGenerateId(),
|
||||
sort: steps.value.length + 1,
|
||||
stepType: ScenarioStepType.LOOP_CONTROLLER,
|
||||
refType: ScenarioStepRefType.DIRECT,
|
||||
name: t('apiScenario.loopControl'),
|
||||
projectId: appStore.currentProjectId,
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
@ -123,9 +127,10 @@
|
|||
if (step.value && props.createStepAction) {
|
||||
handleCreateStep(
|
||||
{
|
||||
type: ScenarioStepType.CONDITION_CONTROL,
|
||||
stepType: ScenarioStepType.IF_CONTROLLER,
|
||||
name: t('apiScenario.conditionControl'),
|
||||
} as ScenarioStepItem,
|
||||
projectId: appStore.currentProjectId,
|
||||
},
|
||||
step.value,
|
||||
steps.value,
|
||||
props.createStepAction,
|
||||
|
@ -134,10 +139,12 @@
|
|||
} else {
|
||||
steps.value.push({
|
||||
...cloneDeep(defaultStepItemCommon),
|
||||
stepId: getGenerateId(),
|
||||
order: steps.value.length + 1,
|
||||
type: ScenarioStepType.CONDITION_CONTROL,
|
||||
id: getGenerateId(),
|
||||
sort: steps.value.length + 1,
|
||||
stepType: ScenarioStepType.IF_CONTROLLER,
|
||||
refType: ScenarioStepRefType.DIRECT,
|
||||
name: t('apiScenario.conditionControl'),
|
||||
projectId: appStore.currentProjectId,
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
@ -145,9 +152,10 @@
|
|||
if (step.value && props.createStepAction) {
|
||||
handleCreateStep(
|
||||
{
|
||||
type: ScenarioStepType.ONLY_ONCE_CONTROL,
|
||||
stepType: ScenarioStepType.ONCE_ONLY_CONTROLLER,
|
||||
name: t('apiScenario.onlyOnceControl'),
|
||||
} as ScenarioStepItem,
|
||||
projectId: appStore.currentProjectId,
|
||||
},
|
||||
step.value,
|
||||
steps.value,
|
||||
props.createStepAction,
|
||||
|
@ -156,10 +164,12 @@
|
|||
} else {
|
||||
steps.value.push({
|
||||
...cloneDeep(defaultStepItemCommon),
|
||||
stepId: getGenerateId(),
|
||||
order: steps.value.length + 1,
|
||||
type: ScenarioStepType.ONLY_ONCE_CONTROL,
|
||||
id: getGenerateId(),
|
||||
sort: steps.value.length + 1,
|
||||
stepType: ScenarioStepType.ONCE_ONLY_CONTROLLER,
|
||||
refType: ScenarioStepRefType.DIRECT,
|
||||
name: t('apiScenario.onlyOnceControl'),
|
||||
projectId: appStore.currentProjectId,
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
@ -167,9 +177,10 @@
|
|||
if (step.value && props.createStepAction) {
|
||||
handleCreateStep(
|
||||
{
|
||||
type: ScenarioStepType.WAIT_TIME,
|
||||
stepType: ScenarioStepType.CONSTANT_TIMER,
|
||||
name: t('apiScenario.waitTime'),
|
||||
} as ScenarioStepItem,
|
||||
projectId: appStore.currentProjectId,
|
||||
},
|
||||
step.value,
|
||||
steps.value,
|
||||
props.createStepAction,
|
||||
|
@ -178,10 +189,12 @@
|
|||
} else {
|
||||
steps.value.push({
|
||||
...cloneDeep(defaultStepItemCommon),
|
||||
stepId: getGenerateId(),
|
||||
order: steps.value.length + 1,
|
||||
type: ScenarioStepType.WAIT_TIME,
|
||||
id: getGenerateId(),
|
||||
sort: steps.value.length + 1,
|
||||
stepType: ScenarioStepType.CONSTANT_TIMER,
|
||||
refType: ScenarioStepRefType.DIRECT,
|
||||
name: t('apiScenario.waitTime'),
|
||||
projectId: appStore.currentProjectId,
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
@ -189,7 +202,7 @@
|
|||
case ScenarioAddStepActionType.CUSTOM_API:
|
||||
case ScenarioAddStepActionType.SCRIPT_OPERATION:
|
||||
if (step.value) {
|
||||
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.value.stepId, 'stepId');
|
||||
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.value.id, 'id');
|
||||
if (realStep) {
|
||||
emit('otherCreate', val, realStep as ScenarioStepItem);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
position="br"
|
||||
@popup-visible-change="handleActionTriggerChange"
|
||||
>
|
||||
<MsButton :id="step.stepId" type="icon" class="ms-tree-node-extra__btn !mr-[4px]" @click="emit('click')">
|
||||
<MsButton :id="step.id" type="icon" class="ms-tree-node-extra__btn !mr-[4px]" @click="emit('click')">
|
||||
<MsIcon type="icon-icon_add_outlined" size="14" class="text-[var(--color-text-4)]" />
|
||||
</MsButton>
|
||||
<template #content>
|
||||
|
@ -63,13 +63,12 @@
|
|||
<script setup lang="ts">
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import { ScenarioStepItem } from '../stepTree.vue';
|
||||
import createStepActions from './createStepActions.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { CreateStepAction } from '@/models/apiTest/scenario';
|
||||
import { ScenarioAddStepActionType, ScenarioStepType } from '@/enums/apiEnum';
|
||||
import { CreateStepAction, ScenarioStepItem } from '@/models/apiTest/scenario';
|
||||
import { ScenarioAddStepActionType, ScenarioStepRefType, ScenarioStepType } from '@/enums/apiEnum';
|
||||
|
||||
const props = defineProps<{
|
||||
step: ScenarioStepItem;
|
||||
|
@ -106,12 +105,15 @@
|
|||
);
|
||||
|
||||
const showAddChildStep = computed(() => {
|
||||
return [
|
||||
ScenarioStepType.LOOP_CONTROL,
|
||||
ScenarioStepType.CONDITION_CONTROL,
|
||||
ScenarioStepType.ONLY_ONCE_CONTROL,
|
||||
ScenarioStepType.COPY_SCENARIO,
|
||||
].includes(innerStep.value.type);
|
||||
return (
|
||||
[
|
||||
ScenarioStepType.LOOP_CONTROLLER,
|
||||
ScenarioStepType.IF_CONTROLLER,
|
||||
ScenarioStepType.ONCE_ONLY_CONTROLLER,
|
||||
].includes(innerStep.value.stepType) ||
|
||||
(innerStep.value.stepType === ScenarioStepType.API_SCENARIO &&
|
||||
innerStep.value.refType === ScenarioStepRefType.COPY)
|
||||
);
|
||||
});
|
||||
|
||||
const activeCreateAction = ref<CreateStepAction>();
|
||||
|
@ -131,7 +133,7 @@
|
|||
function handleActionsClose() {
|
||||
activeCreateAction.value = undefined;
|
||||
innerStep.value.createActionsVisible = false;
|
||||
document.getElementById(innerStep.value.stepId.toString())?.click();
|
||||
document.getElementById(innerStep.value.id.toString())?.click();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import { ScenarioStepItem } from '../stepTree.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { getGenerateId, insertNodes, TreeNode } from '@/utils';
|
||||
|
||||
import { CreateStepAction } from '@/models/apiTest/scenario';
|
||||
import { ScenarioStepType } from '@/enums/apiEnum';
|
||||
import { CreateStepAction, ScenarioStepItem } from '@/models/apiTest/scenario';
|
||||
import { ScenarioStepRefType, ScenarioStepType } from '@/enums/apiEnum';
|
||||
|
||||
import { defaultStepItemCommon } from '../../config';
|
||||
|
||||
|
@ -15,7 +13,7 @@ export default function useCreateActions() {
|
|||
|
||||
/**
|
||||
* 插入步骤时判断父节点是否选中,如果选中则需要把新节点也选中
|
||||
* @param selectedKeys 选中的步骤 stepId 集合
|
||||
* @param selectedKeys 选中的步骤 id 集合
|
||||
* @param steps 需要判断的步骤
|
||||
* @param parent 需要判断的父节点
|
||||
*/
|
||||
|
@ -24,9 +22,9 @@ export default function useCreateActions() {
|
|||
steps: (ScenarioStepItem | TreeNode<ScenarioStepItem>)[],
|
||||
parent?: TreeNode<ScenarioStepItem>
|
||||
) {
|
||||
if (parent && selectedKeys.includes(parent.stepId)) {
|
||||
if (parent && selectedKeys.includes(parent.id)) {
|
||||
// 添加子节点时,当前节点已选中,则需要把新节点也需要选中(因为父级选中子级也会展示选中状态)
|
||||
selectedKeys.push(...steps.map((item) => item.stepId));
|
||||
selectedKeys.push(...steps.map((item) => item.id));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,10 +34,10 @@ export default function useCreateActions() {
|
|||
* @param step 目标步骤
|
||||
* @param steps 顶层步骤列表
|
||||
* @param createStepAction 创建步骤操作类型
|
||||
* @param selectedKeys 选中的步骤 stepId 集合
|
||||
* @param selectedKeys 选中的步骤 id 集合
|
||||
*/
|
||||
function handleCreateStep(
|
||||
defaultStepInfo: ScenarioStepItem,
|
||||
defaultStepInfo: Record<string, any>,
|
||||
step: ScenarioStepItem,
|
||||
steps: ScenarioStepItem[],
|
||||
createStepAction: CreateStepAction,
|
||||
|
@ -47,94 +45,71 @@ export default function useCreateActions() {
|
|||
) {
|
||||
const newStep = {
|
||||
...cloneDeep(defaultStepItemCommon),
|
||||
id: getGenerateId(),
|
||||
...defaultStepInfo,
|
||||
stepId: getGenerateId(),
|
||||
};
|
||||
switch (createStepAction) {
|
||||
case 'inside':
|
||||
newStep.order = step.children ? step.children.length : 0;
|
||||
break;
|
||||
case 'before':
|
||||
newStep.order = step.order;
|
||||
break;
|
||||
case 'after':
|
||||
default:
|
||||
newStep.order = step.order + 1;
|
||||
break;
|
||||
}
|
||||
console.log('newStep', newStep);
|
||||
insertNodes<ScenarioStepItem>(
|
||||
step.parent?.children || steps,
|
||||
step.stepId,
|
||||
step.id,
|
||||
newStep,
|
||||
createStepAction,
|
||||
(newNode, parent) => checkedIfNeed(selectedKeys, [newNode], parent),
|
||||
'stepId'
|
||||
'id'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装插入操作的步骤信息
|
||||
* @param newSteps 新步骤信息集合
|
||||
* @param type 需要插入的步骤类型
|
||||
* @param stepType 需要插入的步骤类型
|
||||
* @param startOrder 步骤最开始的排序序号
|
||||
*/
|
||||
function buildInsertStepInfos(
|
||||
newSteps: Record<string, any>[],
|
||||
type: ScenarioStepType,
|
||||
stepType: ScenarioStepType,
|
||||
refType: ScenarioStepRefType,
|
||||
startOrder: number,
|
||||
stepsDetailMap: Record<string, any>
|
||||
stepDetails: Record<string, any>,
|
||||
projectId: string
|
||||
): ScenarioStepItem[] {
|
||||
let name: string;
|
||||
switch (type) {
|
||||
case ScenarioStepType.LOOP_CONTROL:
|
||||
switch (stepType) {
|
||||
case ScenarioStepType.LOOP_CONTROLLER:
|
||||
name = t('apiScenario.loopControl');
|
||||
break;
|
||||
case ScenarioStepType.CONDITION_CONTROL:
|
||||
case ScenarioStepType.IF_CONTROLLER:
|
||||
name = t('apiScenario.conditionControl');
|
||||
break;
|
||||
case ScenarioStepType.ONLY_ONCE_CONTROL:
|
||||
case ScenarioStepType.ONCE_ONLY_CONTROLLER:
|
||||
name = t('apiScenario.onlyOnceControl');
|
||||
break;
|
||||
case ScenarioStepType.WAIT_TIME:
|
||||
case ScenarioStepType.CONSTANT_TIMER:
|
||||
name = t('apiScenario.waitTime');
|
||||
break;
|
||||
case ScenarioStepType.QUOTE_API:
|
||||
name = t('apiScenario.quoteApi');
|
||||
break;
|
||||
case ScenarioStepType.COPY_API:
|
||||
name = t('apiScenario.copyApi');
|
||||
break;
|
||||
case ScenarioStepType.QUOTE_CASE:
|
||||
name = t('apiScenario.quoteCase');
|
||||
break;
|
||||
case ScenarioStepType.COPY_CASE:
|
||||
name = t('apiScenario.copyCase');
|
||||
break;
|
||||
case ScenarioStepType.QUOTE_SCENARIO:
|
||||
name = t('apiScenario.quoteScenario');
|
||||
break;
|
||||
case ScenarioStepType.COPY_SCENARIO:
|
||||
name = t('apiScenario.copyScenario');
|
||||
break;
|
||||
case ScenarioStepType.CUSTOM_API:
|
||||
case ScenarioStepType.CUSTOM_REQUEST:
|
||||
name = t('apiScenario.customApi');
|
||||
break;
|
||||
case ScenarioStepType.SCRIPT_OPERATION:
|
||||
case ScenarioStepType.SCRIPT:
|
||||
name = t('apiScenario.scriptOperation');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return newSteps.map((item, index) => {
|
||||
const stepId = getGenerateId();
|
||||
stepsDetailMap[stepId] = item; // 导入系统请求的引用接口和 case 的时候需要先存储一下引用的接口/用例信息
|
||||
const id = getGenerateId();
|
||||
stepDetails[id] = item; // 导入系统请求的引用接口和 case 的时候需要先存储一下引用的接口/用例信息
|
||||
return {
|
||||
...cloneDeep(defaultStepItemCommon),
|
||||
...item,
|
||||
stepId,
|
||||
type,
|
||||
name,
|
||||
order: startOrder + index,
|
||||
id,
|
||||
stepType,
|
||||
refType,
|
||||
resourceId: item.id,
|
||||
resourceName: item.name,
|
||||
name: name || item.name,
|
||||
sort: startOrder + index,
|
||||
projectId,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -145,8 +120,7 @@ export default function useCreateActions() {
|
|||
* @param readyInsertSteps 待插入的步骤信息数组(需要先buildInsertStepInfos得到构建后的步骤信息)
|
||||
* @param steps 顶层步骤列表
|
||||
* @param createStepAction 创建步骤操作类型
|
||||
* @param type 需要插入的步骤类型
|
||||
* @param selectedKeys 选中的步骤 stepId 集合
|
||||
* @param selectedKeys 选中的步骤 id 集合
|
||||
*/
|
||||
function handleCreateSteps(
|
||||
step: ScenarioStepItem,
|
||||
|
@ -157,11 +131,11 @@ export default function useCreateActions() {
|
|||
) {
|
||||
insertNodes<ScenarioStepItem>(
|
||||
step.parent?.children || steps,
|
||||
step.stepId,
|
||||
step.id,
|
||||
readyInsertSteps,
|
||||
createStepAction,
|
||||
undefined,
|
||||
'stepId'
|
||||
'id'
|
||||
);
|
||||
checkedIfNeed(selectedKeys, readyInsertSteps, step);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<div class="action-line">
|
||||
<div class="action-group">
|
||||
<a-checkbox
|
||||
v-show="stepInfo.steps.length > 0"
|
||||
v-show="scenario.steps.length > 0"
|
||||
v-model:model-value="checkedAll"
|
||||
:indeterminate="indeterminate"
|
||||
@change="handleChangeAll"
|
||||
|
@ -17,7 +17,7 @@
|
|||
<div class="action-group">
|
||||
<a-tooltip :content="isExpandAll ? t('apiScenario.collapseAllStep') : t('apiScenario.expandAllStep')">
|
||||
<a-button
|
||||
v-show="stepInfo.steps.length > 0"
|
||||
v-show="scenario.steps.length > 0"
|
||||
type="outline"
|
||||
class="expand-step-btn arco-btn-outline--secondary"
|
||||
size="mini"
|
||||
|
@ -42,20 +42,20 @@
|
|||
</a-button>
|
||||
</template>
|
||||
</div>
|
||||
<template v-if="stepInfo.executeTime">
|
||||
<template v-if="scenario.executeTime">
|
||||
<div class="action-group">
|
||||
<div class="text-[var(--color-text-4)]">{{ t('apiScenario.executeTime') }}</div>
|
||||
<div class="text-[var(--color-text-4)]">{{ stepInfo.executeTime }}</div>
|
||||
<div class="text-[var(--color-text-4)]">{{ scenario.executeTime }}</div>
|
||||
</div>
|
||||
<div class="action-group">
|
||||
<div class="text-[var(--color-text-4)]">{{ t('apiScenario.executeResult') }}</div>
|
||||
<div class="flex items-center gap-[4px]">
|
||||
<div class="text-[var(--color-text-1)]">{{ t('common.success') }}</div>
|
||||
<div class="text-[rgb(var(--success-6))]">{{ stepInfo.executeSuccessCount }}</div>
|
||||
<div class="text-[rgb(var(--success-6))]">{{ scenario.executeSuccessCount }}</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-[4px]">
|
||||
<div class="text-[var(--color-text-1)]">{{ t('common.fail') }}</div>
|
||||
<div class="text-[rgb(var(--success-6))]">{{ stepInfo.executeFailCount }}</div>
|
||||
<div class="text-[rgb(var(--success-6))]">{{ scenario.executeFailCount }}</div>
|
||||
</div>
|
||||
<MsButton type="text" @click="checkReport">{{ t('apiScenario.checkReport') }}</MsButton>
|
||||
</div>
|
||||
|
@ -82,11 +82,11 @@
|
|||
<div class="h-[calc(100%-48px)]">
|
||||
<stepTree
|
||||
ref="stepTreeRef"
|
||||
v-model:steps="stepInfo.steps"
|
||||
v-model:steps="scenario.steps"
|
||||
v-model:checked-keys="checkedKeys"
|
||||
v-model:stepKeyword="keyword"
|
||||
:expand-all="isExpandAll"
|
||||
:steps-detail-map="stepInfo.stepsDetailMap"
|
||||
:step-details="scenario.stepDetails"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -116,29 +116,20 @@
|
|||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import stepTree, { ScenarioStepItem } from './stepTree.vue';
|
||||
import stepTree from './stepTree.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { countNodes } from '@/utils/tree';
|
||||
|
||||
export interface ScenarioStepInfo {
|
||||
id: string | number;
|
||||
steps: ScenarioStepItem[];
|
||||
executeTime?: string; // 执行时间
|
||||
executeSuccessCount?: number; // 执行成功数量
|
||||
executeFailCount?: number; // 执行失败数量
|
||||
stepsDetailMap: Record<string, any>; // 步骤详情存储
|
||||
}
|
||||
import { Scenario } from '@/models/apiTest/scenario';
|
||||
|
||||
const props = defineProps<{
|
||||
isNew?: boolean; // 是否新建
|
||||
}>();
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const stepInfo = defineModel<ScenarioStepInfo>('step', {
|
||||
const scenario = defineModel<Scenario>('scenario', {
|
||||
required: true,
|
||||
});
|
||||
|
||||
|
@ -149,7 +140,7 @@
|
|||
const stepTreeRef = ref<InstanceType<typeof stepTree>>();
|
||||
const keyword = ref('');
|
||||
|
||||
const totalStepCount = computed(() => countNodes(stepInfo.value.steps));
|
||||
const totalStepCount = computed(() => countNodes(scenario.value.steps));
|
||||
|
||||
function handleChangeAll(value: boolean | (string | number | boolean)[]) {
|
||||
indeterminate.value = false;
|
||||
|
@ -202,7 +193,7 @@
|
|||
try {
|
||||
let ids = checkedKeys.value;
|
||||
if (batchToggleRange.value === 'top') {
|
||||
ids = stepInfo.value.steps.map((item) => item.stepId);
|
||||
ids = scenario.value.steps.map((item) => item.id);
|
||||
}
|
||||
console.log('ids', ids);
|
||||
await new Promise((resolve) => {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<template>
|
||||
<div class="flex items-center gap-[4px]" draggable="false">
|
||||
<a-tooltip :content="innerData.variableName" :disabled="!innerData.variableName">
|
||||
<a-tooltip :content="innerData.variable" :disabled="!innerData.variable">
|
||||
<a-input
|
||||
v-model:model-value="innerData.variableName"
|
||||
v-model:model-value="innerData.variable"
|
||||
size="mini"
|
||||
class="w-[100px] px-[8px]"
|
||||
:max-length="255"
|
||||
:placeholder="t('apiScenario.variableName', { suffix: '${var}' })"
|
||||
:placeholder="t('apiScenario.variable', { suffix: '${var}' })"
|
||||
@change="handleInputChange"
|
||||
>
|
||||
</a-input>
|
||||
|
@ -21,13 +21,13 @@
|
|||
{{ t(opt.label) }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
<a-tooltip :content="innerData.variableVal" :disabled="!innerData.variableVal">
|
||||
<a-tooltip :content="innerData.value" :disabled="!innerData.value">
|
||||
<a-input
|
||||
:id="innerData.stepId"
|
||||
v-model:model-value="innerData.variableVal"
|
||||
:id="innerData.id"
|
||||
v-model:model-value="innerData.value"
|
||||
size="mini"
|
||||
class="w-[110px] px-[8px]"
|
||||
:placeholder="t('apiScenario.variableVal')"
|
||||
:placeholder="t('apiScenario.value')"
|
||||
@change="handleInputChange"
|
||||
>
|
||||
</a-input>
|
||||
|
@ -38,21 +38,16 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { ConditionStepDetail } from '@/models/apiTest/scenario';
|
||||
|
||||
import { conditionOptions } from '@/views/api-test/scenario/components/config';
|
||||
|
||||
export interface ConditionContentProps {
|
||||
stepId: string;
|
||||
variableName: string;
|
||||
condition: string;
|
||||
variableVal: string;
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
data: ConditionContentProps;
|
||||
data: ConditionStepDetail;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'change', innerData: ConditionContentProps): void;
|
||||
(e: 'quickInput', dataKey: keyof ConditionContentProps): void;
|
||||
(e: 'change', innerData: ConditionStepDetail): void;
|
||||
(e: 'quickInput', dataKey: keyof ConditionStepDetail): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
@ -75,8 +70,8 @@
|
|||
() => dbClick?.value.timeStamp,
|
||||
() => {
|
||||
// @ts-ignore
|
||||
if ((dbClick?.value.e?.target as Element).parentNode?.id.includes(innerData.value.stepId)) {
|
||||
emit('quickInput', 'variableVal');
|
||||
if ((dbClick?.value.e?.target as Element).parentNode?.id.includes(innerData.value.id)) {
|
||||
emit('quickInput', 'value');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
@ -9,12 +9,12 @@
|
|||
@change="handleInputChange"
|
||||
/>
|
||||
<a-tooltip
|
||||
v-if="innerData.loopType === 'num'"
|
||||
:content="innerData.loopNum.toString()"
|
||||
:disabled="!innerData.loopNum"
|
||||
v-if="innerData.loopType === ScenarioStepLoopTypeEnum.LOOP_COUNT"
|
||||
:content="innerData.msCountController.loops.toString()"
|
||||
:disabled="!innerData.msCountController.loops"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:model-value="innerData.loopNum"
|
||||
v-model:model-value="innerData.msCountController.loops"
|
||||
class="w-[80px] px-[8px]"
|
||||
size="mini"
|
||||
:step="1"
|
||||
|
@ -30,53 +30,56 @@
|
|||
</a-input-number>
|
||||
</a-tooltip>
|
||||
</a-input-group>
|
||||
<template v-if="innerData.loopType === 'forEach'">
|
||||
<a-tooltip :content="innerData.variableName" :disabled="!innerData.variableName">
|
||||
<template v-if="innerData.loopType === ScenarioStepLoopTypeEnum.FOREACH">
|
||||
<a-tooltip :content="innerData.forEachController.variable" :disabled="!innerData.forEachController.variable">
|
||||
<a-input
|
||||
v-model:model-value="innerData.variableName"
|
||||
v-model:model-value="innerData.forEachController.variable"
|
||||
size="mini"
|
||||
class="w-[110px] px-[8px]"
|
||||
:max-length="255"
|
||||
:placeholder="t('apiScenario.variableName')"
|
||||
:placeholder="t('apiScenario.variable')"
|
||||
@change="handleInputChange"
|
||||
>
|
||||
</a-input>
|
||||
</a-tooltip>
|
||||
<div class="font-medium">in</div>
|
||||
<a-tooltip :content="innerData.variablePrefix" :disabled="!innerData.variablePrefix">
|
||||
<a-tooltip :content="innerData.forEachController.value" :disabled="!innerData.forEachController.value">
|
||||
<a-input
|
||||
v-model:model-value="innerData.variablePrefix"
|
||||
v-model:model-value="innerData.forEachController.value"
|
||||
size="mini"
|
||||
class="w-[110px] px-[8px]"
|
||||
:placeholder="t('apiScenario.variablePrefix')"
|
||||
:placeholder="t('apiScenario.valuePrefix')"
|
||||
:max-length="255"
|
||||
@change="handleInputChange"
|
||||
>
|
||||
</a-input>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-else-if="innerData.loopType === 'while'">
|
||||
<template v-else-if="innerData.loopType === ScenarioStepLoopTypeEnum.WHILE">
|
||||
<a-select
|
||||
v-model:model-value="innerData.loopWhileType"
|
||||
v-model:model-value="innerData.whileController.conditionType"
|
||||
:options="whileOptions"
|
||||
size="mini"
|
||||
class="w-[75px] px-[8px]"
|
||||
@change="handleInputChange"
|
||||
/>
|
||||
<template v-if="innerData.loopWhileType === 'condition'">
|
||||
<a-tooltip :content="innerData.variableName" :disabled="!innerData.variableName">
|
||||
<template v-if="innerData.whileController.conditionType === WhileConditionType.CONDITION">
|
||||
<a-tooltip
|
||||
:content="innerData.whileController.msWhileVariable.variable"
|
||||
:disabled="!innerData.whileController.msWhileVariable.variable"
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="innerData.variableName"
|
||||
v-model:model-value="innerData.whileController.msWhileVariable.variable"
|
||||
size="mini"
|
||||
class="w-[100px] px-[8px]"
|
||||
:max-length="255"
|
||||
:placeholder="t('apiScenario.variableName', { suffix: '${var}' })"
|
||||
:placeholder="t('apiScenario.variable', { suffix: '${var}' })"
|
||||
@change="handleInputChange"
|
||||
>
|
||||
</a-input>
|
||||
</a-tooltip>
|
||||
<a-select
|
||||
v-model:model-value="innerData.condition"
|
||||
v-model:model-value="innerData.whileController.msWhileVariable.condition"
|
||||
size="mini"
|
||||
class="w-[90px] px-[8px]"
|
||||
@change="handleInputChange"
|
||||
|
@ -85,22 +88,29 @@
|
|||
{{ t(opt.label) }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
<a-tooltip :content="innerData.variableVal" :disabled="!innerData.variableVal">
|
||||
<a-tooltip
|
||||
:content="innerData.whileController.msWhileVariable.value"
|
||||
:disabled="!innerData.whileController.msWhileVariable.value"
|
||||
>
|
||||
<a-input
|
||||
:id="innerData.stepId"
|
||||
v-model:model-value="innerData.variableVal"
|
||||
:id="stepId"
|
||||
v-model:model-value="innerData.whileController.msWhileVariable.value"
|
||||
size="mini"
|
||||
class="w-[110px] px-[8px]"
|
||||
:placeholder="t('apiScenario.variableVal')"
|
||||
:placeholder="t('apiScenario.value')"
|
||||
@change="handleInputChange"
|
||||
>
|
||||
</a-input>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-tooltip v-else :content="innerData.expression" :disabled="!innerData.expression">
|
||||
<a-tooltip
|
||||
v-else
|
||||
:content="innerData.whileController.msWhileScript.scriptValue"
|
||||
:disabled="!innerData.whileController.msWhileScript.scriptValue"
|
||||
>
|
||||
<a-input
|
||||
:id="innerData.stepId"
|
||||
v-model:model-value="innerData.expression"
|
||||
:id="stepId"
|
||||
v-model:model-value="innerData.whileController.msWhileScript.scriptValue"
|
||||
size="mini"
|
||||
class="w-[200px] px-[8px]"
|
||||
:placeholder="t('apiScenario.expression')"
|
||||
|
@ -108,9 +118,9 @@
|
|||
>
|
||||
</a-input>
|
||||
</a-tooltip>
|
||||
<a-tooltip :content="innerData.overTime.toString()" :disabled="!innerData.overTime">
|
||||
<a-tooltip :content="innerData.whileController.timeout.toString()" :disabled="!innerData.whileController.timeout">
|
||||
<a-input-number
|
||||
v-model:model-value="innerData.overTime"
|
||||
v-model:model-value="innerData.whileController.timeout"
|
||||
class="w-[100px] px-[8px]"
|
||||
size="mini"
|
||||
:step="1"
|
||||
|
@ -121,18 +131,18 @@
|
|||
@blur="handleInputChange"
|
||||
>
|
||||
<template #prefix>
|
||||
<div class="text-[12px] text-[var(--color-text-4)]">{{ t('apiScenario.overTime') }}:</div>
|
||||
<div class="text-[12px] text-[var(--color-text-4)]">{{ t('apiScenario.timeout') }}:</div>
|
||||
</template>
|
||||
</a-input-number>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-tooltip
|
||||
v-if="innerData.loopType !== 'while'"
|
||||
:content="innerData.loopSpace.toString()"
|
||||
:disabled="!innerData.loopSpace"
|
||||
v-if="innerData.loopType !== ScenarioStepLoopTypeEnum.WHILE"
|
||||
:content="innerData.forEachController.loopTime.toString()"
|
||||
:disabled="!innerData.forEachController.loopTime"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:model-value="innerData.loopSpace"
|
||||
v-model:model-value="innerData.forEachController.loopTime"
|
||||
size="mini"
|
||||
:step="1"
|
||||
:min="0"
|
||||
|
@ -153,34 +163,18 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { ScenarioStepLoopType, ScenarioStepLoopWhileType } from '@/models/apiTest/scenario';
|
||||
import { ScenarioStepType } from '@/enums/apiEnum';
|
||||
import { LoopStepDetail } from '@/models/apiTest/scenario';
|
||||
import { ScenarioStepLoopTypeEnum, WhileConditionType } from '@/enums/apiEnum';
|
||||
|
||||
import { conditionOptions } from '@/views/api-test/scenario/components/config';
|
||||
|
||||
export interface LoopContentProps {
|
||||
stepId: string | number;
|
||||
num: number;
|
||||
name: string;
|
||||
type: ScenarioStepType;
|
||||
loopNum: number;
|
||||
loopType: ScenarioStepLoopType;
|
||||
loopSpace: number;
|
||||
variableName: string;
|
||||
variablePrefix: string;
|
||||
loopWhileType: ScenarioStepLoopWhileType;
|
||||
variableVal: string;
|
||||
condition: string;
|
||||
overTime: number;
|
||||
expression: string;
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
data: LoopContentProps;
|
||||
data: LoopStepDetail;
|
||||
stepId: string | number;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'change', innerData: LoopContentProps): void;
|
||||
(e: 'quickInput', dataKey: keyof LoopContentProps): void;
|
||||
(e: 'change', innerData: LoopStepDetail): void;
|
||||
(e: 'quickInput', dataKey: string): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
@ -188,25 +182,25 @@
|
|||
const innerData = ref(props.data);
|
||||
const loopOptions = [
|
||||
{
|
||||
value: 'num',
|
||||
value: ScenarioStepLoopTypeEnum.LOOP_COUNT,
|
||||
label: t('apiScenario.num'),
|
||||
},
|
||||
{
|
||||
value: 'while',
|
||||
value: ScenarioStepLoopTypeEnum.WHILE,
|
||||
label: 'while',
|
||||
},
|
||||
{
|
||||
value: 'forEach',
|
||||
value: ScenarioStepLoopTypeEnum.FOREACH,
|
||||
label: 'forEach',
|
||||
},
|
||||
];
|
||||
const whileOptions = [
|
||||
{
|
||||
value: 'condition',
|
||||
value: WhileConditionType.CONDITION,
|
||||
label: t('apiScenario.condition'),
|
||||
},
|
||||
{
|
||||
value: 'expression',
|
||||
value: WhileConditionType.SCRIPT,
|
||||
label: t('apiScenario.expression'),
|
||||
},
|
||||
];
|
||||
|
@ -227,8 +221,13 @@
|
|||
() => dbClick?.value.timeStamp,
|
||||
() => {
|
||||
// @ts-ignore
|
||||
if ((dbClick?.value.e?.target as Element).parentNode?.id.includes(innerData.value.stepId)) {
|
||||
emit('quickInput', innerData.value.loopWhileType === 'condition' ? 'variableVal' : 'expression');
|
||||
if ((dbClick?.value.e?.target as Element).parentNode?.id === props.stepId) {
|
||||
emit(
|
||||
'quickInput',
|
||||
innerData.value.whileController.conditionType === WhileConditionType.CONDITION
|
||||
? 'whileController.msWhileVariable.value'
|
||||
: 'whileController.msWhileScript.scriptValue'
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
@ -7,20 +7,20 @@
|
|||
<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 }}
|
||||
<!-- {{ 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}` }}
|
||||
{{ `【${props.data.resourceNum}】${props.data.resourceName}` }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-popover>
|
||||
<MsTag
|
||||
v-if="props.data.belongProjectId !== props.data.currentProjectId"
|
||||
v-if="props.data.projectId !== appStore.currentProjectId"
|
||||
theme="outline"
|
||||
size="small"
|
||||
:self-style="{
|
||||
|
@ -40,39 +40,35 @@
|
|||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useOpenNewPage from '@/hooks/useOpenNewPage';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
import { ScenarioStepType } from '@/enums/apiEnum';
|
||||
import { ScenarioStepItem } from '@/models/apiTest/scenario';
|
||||
import { ApiTestRouteEnum } from '@/enums/routeEnum';
|
||||
|
||||
import getStepType from '@/views/api-test/scenario/components/common/stepType/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
data: {
|
||||
id: string | number;
|
||||
stepId: string | number;
|
||||
belongProjectId: string;
|
||||
belongProjectName: string;
|
||||
num: number;
|
||||
name: string;
|
||||
type: ScenarioStepType;
|
||||
currentProjectId: string;
|
||||
};
|
||||
data: ScenarioStepItem;
|
||||
}>();
|
||||
|
||||
const appStore = useAppStore();
|
||||
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 });
|
||||
const _stepType = getStepType(props.data);
|
||||
switch (true) {
|
||||
case _stepType.isCopyApi:
|
||||
case _stepType.isQuoteApi:
|
||||
openNewPage(ApiTestRouteEnum.API_TEST_MANAGEMENT, { dId: props.data.id, pId: props.data.projectId });
|
||||
break;
|
||||
case ScenarioStepType.QUOTE_SCENARIO:
|
||||
case ScenarioStepType.COPY_SCENARIO:
|
||||
openNewPage(ApiTestRouteEnum.API_TEST_SCENARIO, { sId: props.data.id });
|
||||
case _stepType.isCopyScenario:
|
||||
case _stepType.isQuoteScenario:
|
||||
openNewPage(ApiTestRouteEnum.API_TEST_SCENARIO, { sId: props.data.id, pId: props.data.projectId });
|
||||
break;
|
||||
case ScenarioStepType.COPY_CASE:
|
||||
case ScenarioStepType.QUOTE_CASE:
|
||||
openNewPage(ApiTestRouteEnum.API_TEST_MANAGEMENT, { cId: props.data.id });
|
||||
case _stepType.isQuoteCase:
|
||||
case _stepType.isCopyCase:
|
||||
openNewPage(ApiTestRouteEnum.API_TEST_MANAGEMENT, { cId: props.data.id, pId: props.data.projectId });
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
export interface WaitTimeContentProps {
|
||||
stepId: string | number;
|
||||
id: string | number;
|
||||
waitTime: number;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
:expand-all="props.expandAll"
|
||||
:node-more-actions="stepMoreActions"
|
||||
:filter-more-action-func="setStepMoreAction"
|
||||
:field-names="{ title: 'name', key: 'stepId', children: 'children' }"
|
||||
:field-names="{ title: 'name', key: 'id', children: 'children' }"
|
||||
:virtual-list-props="{
|
||||
height: '100%',
|
||||
threshold: 20,
|
||||
|
@ -37,7 +37,7 @@
|
|||
<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 }}
|
||||
{{ step.sort }}
|
||||
</div>
|
||||
<div class="step-node-content">
|
||||
<!-- 步骤展开折叠按钮 -->
|
||||
|
@ -60,9 +60,9 @@
|
|||
<div class="mr-[8px] flex items-center gap-[8px]">
|
||||
<!-- 步骤启用/禁用 -->
|
||||
<a-switch
|
||||
:default-checked="step.enabled"
|
||||
:default-checked="step.enable"
|
||||
size="small"
|
||||
@click.stop="step.enabled = !step.enabled"
|
||||
@click.stop="handleStepToggleEnable(step)"
|
||||
></a-switch>
|
||||
<!-- 步骤执行 -->
|
||||
<MsIcon
|
||||
|
@ -73,13 +73,14 @@
|
|||
/>
|
||||
</div>
|
||||
<!-- 步骤类型 -->
|
||||
<stepType :type="step.type" />
|
||||
<stepType :step="step" />
|
||||
<!-- 步骤整体内容 -->
|
||||
<div class="relative flex flex-1 items-center gap-[4px]">
|
||||
<!-- 步骤差异内容,按步骤类型展示不同组件 -->
|
||||
<component
|
||||
:is="getStepContent(step)"
|
||||
:data="step"
|
||||
:data="step.config"
|
||||
:step-id="step.id"
|
||||
@quick-input="setQuickInput(step, $event)"
|
||||
@change="handleStepContentChange($event, step)"
|
||||
@click.stop
|
||||
|
@ -88,7 +89,7 @@
|
|||
<template v-if="checkStepIsApi(step)">
|
||||
<apiMethodName v-if="checkStepShowMethod(step)" :method="step.method" />
|
||||
<div
|
||||
v-if="step.stepId === showStepNameEditInputStepId"
|
||||
v-if="step.id === showStepNameEditInputStepId"
|
||||
class="name-warp absolute left-0 top-[-2px] z-10 w-[calc(100%-24px)]"
|
||||
@click.stop
|
||||
>
|
||||
|
@ -117,12 +118,12 @@
|
|||
<!-- 其他步骤描述 -->
|
||||
<template v-else>
|
||||
<div
|
||||
v-if="step.stepId === showStepDescEditInputStepId"
|
||||
v-if="step.id === showStepDescEditInputStepId"
|
||||
class="desc-warp absolute left-0 top-[-2px] z-10 w-[calc(100%-24px)]"
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="tempStepDesc"
|
||||
:default-value="step.description || t('apiScenario.pleaseInputStepDesc')"
|
||||
:default-value="step.name || t('apiScenario.pleaseInputStepDesc')"
|
||||
:placeholder="t('apiScenario.pleaseInputStepDesc')"
|
||||
:max-length="255"
|
||||
size="small"
|
||||
|
@ -135,14 +136,14 @@
|
|||
</template>
|
||||
</a-input>
|
||||
</div>
|
||||
<a-tooltip :content="step.description" :disabled="!step.description">
|
||||
<a-tooltip :content="step.name" :disabled="!step.name">
|
||||
<div class="step-name-container">
|
||||
<div
|
||||
:class="`one-line-text mr-[4px] ${
|
||||
step.type === ScenarioStepType.ONLY_ONCE_CONTROL ? 'max-w-[750px]' : 'max-w-[150px]'
|
||||
step.stepType === ScenarioStepType.ONCE_ONLY_CONTROLLER ? 'max-w-[750px]' : 'max-w-[150px]'
|
||||
} font-normal text-[var(--color-text-4)]`"
|
||||
>
|
||||
{{ step.description || t('apiScenario.pleaseInputStepDesc') }}
|
||||
{{ step.name || t('apiScenario.pleaseInputStepDesc') }}
|
||||
</div>
|
||||
<MsIcon
|
||||
type="icon-icon_edit_outlined"
|
||||
|
@ -161,7 +162,7 @@
|
|||
v-model:selected-keys="selectedKeys"
|
||||
v-model:steps="steps"
|
||||
:step="step"
|
||||
@click="setFocusNodeKey(step.stepId)"
|
||||
@click="setFocusNodeKey(step.id)"
|
||||
@other-create="handleOtherCreate"
|
||||
@close="setFocusNodeKey('')"
|
||||
/>
|
||||
|
@ -190,8 +191,7 @@
|
|||
v-model:visible="customApiDrawerVisible"
|
||||
:env-detail-item="{ id: 'demp-id-112233', projectId: '123456', name: 'demo环境' }"
|
||||
:request="currentStepDetail"
|
||||
:request-type="activeStep?.type"
|
||||
:step-name="activeStep?.name || ''"
|
||||
:step="activeStep"
|
||||
@add-step="addCustomApiStep"
|
||||
@apply-step="applyApiStep"
|
||||
/>
|
||||
|
@ -257,7 +257,7 @@
|
|||
import { MsTreeExpandedData, MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||
import executeStatus from '../common/executeStatus.vue';
|
||||
import { ImportData } from '../common/importApiDrawer/index.vue';
|
||||
import stepType from '../common/stepType.vue';
|
||||
import stepType from '../common/stepType/stepType.vue';
|
||||
import createStepActions from './createAction/createStepActions.vue';
|
||||
import stepInsertStepTrigger from './createAction/stepInsertStepTrigger.vue';
|
||||
import conditionContent from './stepNodeComposition/conditionContent.vue';
|
||||
|
@ -281,11 +281,12 @@
|
|||
} from '@/utils';
|
||||
|
||||
import { ExecuteConditionProcessor } from '@/models/apiTest/common';
|
||||
import { CreateStepAction, ScenarioStepLoopWhileType } from '@/models/apiTest/scenario';
|
||||
import { RequestMethods, ScenarioAddStepActionType, ScenarioExecuteStatus, ScenarioStepType } from '@/enums/apiEnum';
|
||||
import { CreateStepAction, ScenarioStepItem } from '@/models/apiTest/scenario';
|
||||
import { ScenarioAddStepActionType, ScenarioStepRefType, ScenarioStepType } from '@/enums/apiEnum';
|
||||
|
||||
import type { RequestParam } from '../common/customApiDrawer.vue';
|
||||
import useCreateActions from './createAction/useCreateActions';
|
||||
import getStepType from '@/views/api-test/scenario/components/common/stepType/utils';
|
||||
import { defaultStepItemCommon } from '@/views/api-test/scenario/components/config';
|
||||
|
||||
// 非首屏渲染必要组件,异步加载
|
||||
|
@ -295,41 +296,6 @@
|
|||
const importApiDrawer = defineAsyncComponent(() => import('../common/importApiDrawer/index.vue'));
|
||||
const scriptOperationDrawer = defineAsyncComponent(() => import('../common/scriptOperationDrawer.vue'));
|
||||
|
||||
export interface ScenarioStepItem {
|
||||
stepId: string | number;
|
||||
order: number;
|
||||
enabled: boolean; // 是否启用
|
||||
type: ScenarioStepType;
|
||||
name: string;
|
||||
description: string;
|
||||
method?: RequestMethods;
|
||||
executeStatus?: ScenarioExecuteStatus;
|
||||
num?: number; // 详情或者引用的类型才有
|
||||
// 引用类型专有字段
|
||||
belongProjectId?: string;
|
||||
belongProjectName?: string;
|
||||
children?: ScenarioStepItem[];
|
||||
// 自定义请求
|
||||
request?: RequestParam;
|
||||
// 脚本操作
|
||||
script?: ExecuteConditionProcessor;
|
||||
// 页面渲染以及交互需要字段
|
||||
// renderId: string; // 渲染id
|
||||
checked: boolean; // 是否选中
|
||||
expanded: boolean; // 是否展开
|
||||
createActionsVisible?: boolean; // 是否展示创建步骤下拉
|
||||
parent?: ScenarioStepItem; // 父级节点,第一层的父级节点为undefined
|
||||
loopNum: number;
|
||||
loopType: 'num' | 'while' | 'forEach';
|
||||
loopSpace: number;
|
||||
variableName: string;
|
||||
variablePrefix: string;
|
||||
loopWhileType: ScenarioStepLoopWhileType;
|
||||
variableVal: string;
|
||||
condition: string;
|
||||
overTime: number;
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
stepKeyword: string;
|
||||
expandAll?: boolean;
|
||||
|
@ -345,7 +311,7 @@
|
|||
required: true,
|
||||
});
|
||||
// 步骤详情映射,存储部分抽屉展示详情的数据
|
||||
const stepsDetailMap = defineModel<Record<string, any>>('stepsDetailMap', {
|
||||
const stepDetails = defineModel<Record<string, any>>('stepDetails', {
|
||||
required: true,
|
||||
});
|
||||
|
||||
|
@ -358,18 +324,18 @@
|
|||
* 根据步骤类型获取步骤内容组件
|
||||
*/
|
||||
function getStepContent(step: ScenarioStepItem) {
|
||||
switch (step.type) {
|
||||
case ScenarioStepType.QUOTE_API:
|
||||
case ScenarioStepType.QUOTE_CASE:
|
||||
case ScenarioStepType.QUOTE_SCENARIO:
|
||||
const _stepType = getStepType(step);
|
||||
if (_stepType.isQuoteApi || _stepType.isQuoteCase || _stepType.isQuoteScenario) {
|
||||
return quoteContent;
|
||||
case ScenarioStepType.CUSTOM_API:
|
||||
}
|
||||
switch (step.stepType) {
|
||||
case ScenarioStepType.CUSTOM_REQUEST:
|
||||
return customApiContent;
|
||||
case ScenarioStepType.LOOP_CONTROL:
|
||||
case ScenarioStepType.LOOP_CONTROLLER:
|
||||
return loopControlContent;
|
||||
case ScenarioStepType.CONDITION_CONTROL:
|
||||
case ScenarioStepType.IF_CONTROLLER:
|
||||
return conditionContent;
|
||||
case ScenarioStepType.WAIT_TIME:
|
||||
case ScenarioStepType.CONSTANT_TIMER:
|
||||
return waitTimeContent;
|
||||
default:
|
||||
return () => null;
|
||||
|
@ -381,34 +347,25 @@
|
|||
}
|
||||
|
||||
function checkStepIsApi(step: ScenarioStepItem) {
|
||||
return [
|
||||
ScenarioStepType.QUOTE_API,
|
||||
ScenarioStepType.COPY_API,
|
||||
ScenarioStepType.QUOTE_CASE,
|
||||
ScenarioStepType.COPY_CASE,
|
||||
ScenarioStepType.CUSTOM_API,
|
||||
].includes(step.type);
|
||||
return [ScenarioStepType.API, ScenarioStepType.API_CASE, ScenarioStepType.CUSTOM_REQUEST].includes(step.stepType);
|
||||
}
|
||||
|
||||
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);
|
||||
ScenarioStepType.API,
|
||||
ScenarioStepType.API_CASE,
|
||||
ScenarioStepType.CUSTOM_REQUEST,
|
||||
ScenarioStepType.API_SCENARIO,
|
||||
].includes(step.stepType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加步骤时判断父节点是否选中,如果选中则需要把新节点也选中
|
||||
*/
|
||||
function checkedIfNeed(step: TreeNode<ScenarioStepItem>, parent?: TreeNode<ScenarioStepItem>) {
|
||||
if (parent && selectedKeys.value.includes(parent.stepId)) {
|
||||
if (parent && selectedKeys.value.includes(parent.id)) {
|
||||
// 添加子节点时,当前节点已选中,则需要把新节点也需要选中(因为父级选中子级也会展示选中状态)
|
||||
selectedKeys.value.push(step.stepId);
|
||||
selectedKeys.value.push(step.id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -425,7 +382,8 @@
|
|||
];
|
||||
|
||||
function setStepMoreAction(items: ActionsItem[], node: MsTreeNodeData) {
|
||||
if ((node as ScenarioStepItem).type === ScenarioStepType.CUSTOM_API) {
|
||||
const _stepType = getStepType(node as ScenarioStepItem);
|
||||
if ((node as ScenarioStepItem).stepType === ScenarioStepType.CUSTOM_REQUEST) {
|
||||
// 自定义请求
|
||||
return [
|
||||
{
|
||||
|
@ -443,7 +401,7 @@
|
|||
},
|
||||
];
|
||||
}
|
||||
if ((node as ScenarioStepItem).type === ScenarioStepType.QUOTE_SCENARIO) {
|
||||
if (_stepType.isQuoteScenario) {
|
||||
return [
|
||||
{
|
||||
label: 'common.copy',
|
||||
|
@ -460,7 +418,7 @@
|
|||
},
|
||||
];
|
||||
}
|
||||
if ((node as ScenarioStepItem).type === ScenarioStepType.QUOTE_CASE) {
|
||||
if (_stepType.isQuoteCase) {
|
||||
return [
|
||||
{
|
||||
label: 'common.copy',
|
||||
|
@ -486,30 +444,30 @@
|
|||
const id = getGenerateId();
|
||||
insertNodes<ScenarioStepItem>(
|
||||
steps.value,
|
||||
node.stepId,
|
||||
node.id,
|
||||
{
|
||||
...cloneDeep(
|
||||
mapTree<ScenarioStepItem>(node, (childNode) => {
|
||||
return {
|
||||
...childNode,
|
||||
stepId: getGenerateId(), // TODO:引用类型额外需要一个复制来源 ID
|
||||
id: getGenerateId(), // TODO:引用类型额外需要一个复制来源 ID
|
||||
};
|
||||
})[0]
|
||||
),
|
||||
name: `copy-${node.name}`,
|
||||
order: node.order + 1,
|
||||
stepId: id,
|
||||
sort: node.sort + 1,
|
||||
id,
|
||||
},
|
||||
'after',
|
||||
checkedIfNeed,
|
||||
'stepId'
|
||||
'id'
|
||||
);
|
||||
break;
|
||||
case 'config':
|
||||
console.log('config', node);
|
||||
break;
|
||||
case 'delete':
|
||||
deleteNode(steps.value, node.stepId, 'stepId');
|
||||
deleteNode(steps.value, node.id, 'id');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -527,7 +485,7 @@
|
|||
const tempStepName = ref('');
|
||||
function handleStepNameClick(step: ScenarioStepItem) {
|
||||
tempStepName.value = step.name;
|
||||
showStepNameEditInputStepId.value = step.stepId;
|
||||
showStepNameEditInputStepId.value = step.id;
|
||||
nextTick(() => {
|
||||
// 等待输入框渲染完成后聚焦
|
||||
const input = treeRef.value?.$el.querySelector('.name-warp .arco-input-wrapper .arco-input') as HTMLInputElement;
|
||||
|
@ -536,7 +494,7 @@
|
|||
}
|
||||
|
||||
function applyStepNameChange(step: ScenarioStepItem) {
|
||||
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.stepId, 'stepId');
|
||||
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.id, 'id');
|
||||
if (realStep) {
|
||||
realStep.name = tempStepName.value;
|
||||
}
|
||||
|
@ -549,8 +507,8 @@
|
|||
const showStepDescEditInputStepId = ref<string | number>('');
|
||||
const tempStepDesc = ref('');
|
||||
function handleStepDescClick(step: ScenarioStepItem) {
|
||||
tempStepDesc.value = step.description;
|
||||
showStepDescEditInputStepId.value = step.stepId;
|
||||
tempStepDesc.value = step.name;
|
||||
showStepDescEditInputStepId.value = step.id;
|
||||
nextTick(() => {
|
||||
// 等待输入框渲染完成后聚焦
|
||||
const input = treeRef.value?.$el.querySelector('.desc-warp .arco-input-wrapper .arco-input') as HTMLInputElement;
|
||||
|
@ -559,19 +517,21 @@
|
|||
}
|
||||
|
||||
function applyStepDescChange(step: ScenarioStepItem) {
|
||||
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.stepId, 'stepId');
|
||||
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.id, 'id');
|
||||
if (realStep) {
|
||||
realStep.description = tempStepDesc.value;
|
||||
realStep.name = tempStepDesc.value;
|
||||
}
|
||||
showStepDescEditInputStepId.value = '';
|
||||
}
|
||||
|
||||
function handleStepContentChange($event, step: ScenarioStepItem) {
|
||||
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.stepId, 'stepId');
|
||||
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.id, 'id');
|
||||
if (realStep) {
|
||||
Object.keys($event).forEach((key) => {
|
||||
realStep[key] = $event[key];
|
||||
});
|
||||
// Object.keys($event).forEach((key) => {
|
||||
// realStep.config[key] = $event[key];
|
||||
// });
|
||||
realStep.config = $event;
|
||||
console.log('handleStepContentChange', $event);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -579,12 +539,19 @@
|
|||
* 处理步骤展开折叠
|
||||
*/
|
||||
function handleStepExpand(data: MsTreeExpandedData) {
|
||||
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, data.node?.stepId, 'stepId');
|
||||
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, data.node?.id, 'id');
|
||||
if (realStep) {
|
||||
realStep.expanded = !realStep.expanded;
|
||||
}
|
||||
}
|
||||
|
||||
function handleStepToggleEnable(data: ScenarioStepItem) {
|
||||
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, data.id, 'id');
|
||||
if (realStep) {
|
||||
realStep.enable = !realStep.enable;
|
||||
}
|
||||
}
|
||||
|
||||
const importApiDrawerVisible = ref(false);
|
||||
const customCaseDrawerVisible = ref(false);
|
||||
const customApiDrawerVisible = ref(false);
|
||||
|
@ -594,7 +561,7 @@
|
|||
const currentStepDetail = computed<any>(() => {
|
||||
// TODO: 步骤详情类型
|
||||
if (activeStep.value) {
|
||||
return stepsDetailMap.value[activeStep.value.stepId];
|
||||
return stepDetails.value[activeStep.value.id];
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
@ -605,19 +572,20 @@
|
|||
* @param step 点击的步骤节点
|
||||
*/
|
||||
function handleStepSelect(_selectedKeys: Array<string | number>, step: ScenarioStepItem) {
|
||||
const _stepType = getStepType(step);
|
||||
const offspringIds: string[] = [];
|
||||
mapTree(step.children || [], (e) => {
|
||||
offspringIds.push(e.stepId);
|
||||
offspringIds.push(e.id);
|
||||
return e;
|
||||
});
|
||||
selectedKeys.value = [step.stepId, ...offspringIds];
|
||||
if ([ScenarioStepType.CUSTOM_API, ScenarioStepType.QUOTE_API, ScenarioStepType.COPY_API].includes(step.type)) {
|
||||
selectedKeys.value = [step.id, ...offspringIds];
|
||||
if (_stepType.isCopyApi || _stepType.isQuoteApi || step.stepType === ScenarioStepType.CUSTOM_REQUEST) {
|
||||
activeStep.value = step;
|
||||
customApiDrawerVisible.value = true;
|
||||
} else if ([ScenarioStepType.QUOTE_CASE, ScenarioStepType.COPY_CASE].includes(step.type)) {
|
||||
activeStep.value = step;
|
||||
customCaseDrawerVisible.value = true;
|
||||
} else if (step.type === ScenarioStepType.SCRIPT_OPERATION) {
|
||||
} else if (step.stepType === ScenarioStepType.SCRIPT) {
|
||||
activeStep.value = step;
|
||||
scriptOperationDrawerVisible.value = true;
|
||||
}
|
||||
|
@ -663,39 +631,46 @@
|
|||
* @param data 导入数据
|
||||
*/
|
||||
function handleImportApiApply(type: 'copy' | 'quote', data: ImportData) {
|
||||
let order = steps.value.length + 1;
|
||||
let sort = steps.value.length + 1;
|
||||
if (activeStep.value && activeCreateAction.value) {
|
||||
switch (activeCreateAction.value) {
|
||||
case 'inside':
|
||||
order = activeStep.value.children ? activeStep.value.children.length : 0;
|
||||
sort = activeStep.value.children ? activeStep.value.children.length : 0;
|
||||
break;
|
||||
case 'before':
|
||||
order = activeStep.value.order;
|
||||
sort = activeStep.value.sort;
|
||||
break;
|
||||
case 'after':
|
||||
order = activeStep.value.order + 1;
|
||||
sort = activeStep.value.sort + 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
const refType = type === 'copy' ? ScenarioStepRefType.COPY : ScenarioStepRefType.REF;
|
||||
const insertApiSteps = buildInsertStepInfos(
|
||||
data.api,
|
||||
type === 'copy' ? ScenarioStepType.COPY_API : ScenarioStepType.QUOTE_API,
|
||||
order,
|
||||
stepsDetailMap.value
|
||||
ScenarioStepType.API,
|
||||
refType,
|
||||
sort,
|
||||
stepDetails.value,
|
||||
appStore.currentProjectId
|
||||
);
|
||||
const insertCaseSteps = buildInsertStepInfos(
|
||||
data.case,
|
||||
type === 'copy' ? ScenarioStepType.COPY_CASE : ScenarioStepType.QUOTE_CASE,
|
||||
order + insertApiSteps.length,
|
||||
stepsDetailMap.value
|
||||
ScenarioStepType.API_CASE,
|
||||
refType,
|
||||
sort + insertApiSteps.length,
|
||||
stepDetails.value,
|
||||
appStore.currentProjectId
|
||||
);
|
||||
const insertScenarioSteps = buildInsertStepInfos(
|
||||
data.scenario,
|
||||
type === 'copy' ? ScenarioStepType.COPY_SCENARIO : ScenarioStepType.QUOTE_SCENARIO,
|
||||
order + insertApiSteps.length + insertCaseSteps.length,
|
||||
stepsDetailMap.value
|
||||
ScenarioStepType.API_SCENARIO,
|
||||
refType,
|
||||
sort + insertApiSteps.length + insertCaseSteps.length,
|
||||
stepDetails.value,
|
||||
appStore.currentProjectId
|
||||
);
|
||||
const insertSteps = insertApiSteps.concat(insertCaseSteps).concat(insertScenarioSteps);
|
||||
if (activeStep.value && activeCreateAction.value) {
|
||||
|
@ -709,14 +684,17 @@
|
|||
* 添加自定义 API 步骤
|
||||
*/
|
||||
function addCustomApiStep(request: RequestParam) {
|
||||
const id = getGenerateId();
|
||||
stepsDetailMap.value[id] = request;
|
||||
request.isNew = false;
|
||||
stepDetails.value[request.id] = request;
|
||||
if (activeStep.value && activeCreateAction.value) {
|
||||
handleCreateStep(
|
||||
{
|
||||
type: ScenarioStepType.CUSTOM_API,
|
||||
stepType: ScenarioStepType.CUSTOM_REQUEST,
|
||||
name: t('apiScenario.customApi'),
|
||||
} as ScenarioStepItem,
|
||||
method: request.method,
|
||||
id: request.id,
|
||||
projectId: appStore.currentProjectId,
|
||||
},
|
||||
activeStep.value,
|
||||
steps.value,
|
||||
activeCreateAction.value,
|
||||
|
@ -725,11 +703,14 @@
|
|||
} else {
|
||||
steps.value.push({
|
||||
...cloneDeep(defaultStepItemCommon),
|
||||
stepId: id,
|
||||
order: steps.value.length + 1,
|
||||
type: ScenarioStepType.CUSTOM_API,
|
||||
id: request.id,
|
||||
sort: steps.value.length + 1,
|
||||
stepType: ScenarioStepType.CUSTOM_REQUEST,
|
||||
refType: ScenarioStepRefType.DIRECT,
|
||||
name: t('apiScenario.customApi'),
|
||||
} as ScenarioStepItem);
|
||||
method: request.method,
|
||||
projectId: appStore.currentProjectId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -738,7 +719,7 @@
|
|||
*/
|
||||
function applyApiStep(request: RequestParam | CaseRequestParam) {
|
||||
if (activeStep.value) {
|
||||
stepsDetailMap.value[activeStep.value?.stepId] = request;
|
||||
stepDetails.value[activeStep.value?.id] = request;
|
||||
activeStep.value = undefined;
|
||||
}
|
||||
}
|
||||
|
@ -760,13 +741,14 @@
|
|||
*/
|
||||
function addScriptStep(name: string, scriptProcessor: ExecuteConditionProcessor) {
|
||||
const id = getGenerateId();
|
||||
stepsDetailMap.value[id] = cloneDeep(scriptProcessor);
|
||||
stepDetails.value[id] = cloneDeep(scriptProcessor);
|
||||
if (activeStep.value && activeCreateAction.value) {
|
||||
handleCreateStep(
|
||||
{
|
||||
type: ScenarioStepType.SCRIPT_OPERATION,
|
||||
stepType: ScenarioStepType.SCRIPT,
|
||||
name,
|
||||
} as ScenarioStepItem,
|
||||
projectId: appStore.currentProjectId,
|
||||
},
|
||||
activeStep.value,
|
||||
steps.value,
|
||||
activeCreateAction.value,
|
||||
|
@ -775,11 +757,13 @@
|
|||
} else {
|
||||
steps.value.push({
|
||||
...cloneDeep(defaultStepItemCommon),
|
||||
stepId: id,
|
||||
order: steps.value.length + 1,
|
||||
type: ScenarioStepType.SCRIPT_OPERATION,
|
||||
id,
|
||||
sort: steps.value.length + 1,
|
||||
stepType: ScenarioStepType.SCRIPT,
|
||||
refType: ScenarioStepRefType.DIRECT,
|
||||
name,
|
||||
} as ScenarioStepItem);
|
||||
projectId: appStore.currentProjectId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -789,10 +773,10 @@
|
|||
*/
|
||||
function isAllowDropInside(dropNode: MsTreeNodeData) {
|
||||
return [
|
||||
ScenarioStepType.LOOP_CONTROL,
|
||||
ScenarioStepType.CONDITION_CONTROL,
|
||||
ScenarioStepType.ONLY_ONCE_CONTROL,
|
||||
].includes(dropNode.type);
|
||||
ScenarioStepType.LOOP_CONTROLLER,
|
||||
ScenarioStepType.IF_CONTROLLER,
|
||||
ScenarioStepType.ONCE_ONLY_CONTROLLER,
|
||||
].includes(dropNode.stepType);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -816,20 +800,20 @@
|
|||
loading.value = true;
|
||||
const offspringIds: string[] = [];
|
||||
mapTree(dragNode.children || [], (e) => {
|
||||
offspringIds.push(e.stepId);
|
||||
offspringIds.push(e.id);
|
||||
return e;
|
||||
});
|
||||
const stepIdAndOffspringIds = [dragNode.stepId, ...offspringIds];
|
||||
const stepIdAndOffspringIds = [dragNode.id, ...offspringIds];
|
||||
if (dropPosition === 0) {
|
||||
// 拖拽到节点内
|
||||
if (selectedKeys.value.includes(dropNode.stepId)) {
|
||||
if (selectedKeys.value.includes(dropNode.id)) {
|
||||
// 释放位置的节点已选中,则需要把拖动的节点及其子孙节点也需要选中(因为父级选中子级也会展示选中状态)
|
||||
selectedKeys.value = selectedKeys.value.concat(stepIdAndOffspringIds);
|
||||
}
|
||||
} else if (dropNode.parent && selectedKeys.value.includes(dropNode.parent.stepId)) {
|
||||
} else if (dropNode.parent && selectedKeys.value.includes(dropNode.parent.id)) {
|
||||
// 释放位置的节点的父节点已选中,则需要把拖动的节点及其子孙节点也需要选中(因为父级选中子级也会展示选中状态)
|
||||
selectedKeys.value = selectedKeys.value.concat(stepIdAndOffspringIds);
|
||||
} else if (dragNode.parent && selectedKeys.value.includes(dragNode.parent.stepId)) {
|
||||
} else if (dragNode.parent && selectedKeys.value.includes(dragNode.parent.id)) {
|
||||
// 如果被拖动的节点的父节点在选中的节点中,则需要把被拖动的节点及其子孙节点从选中的节点中移除
|
||||
selectedKeys.value = selectedKeys.value.filter((e) => {
|
||||
for (let i = 0; i < stepIdAndOffspringIds.length; i++) {
|
||||
|
@ -842,7 +826,7 @@
|
|||
return true;
|
||||
});
|
||||
}
|
||||
const dragResult = handleTreeDragDrop(steps.value, dragNode, dropNode, dropPosition, 'stepId');
|
||||
const dragResult = handleTreeDragDrop(steps.value, dragNode, dropNode, dropPosition, 'id');
|
||||
if (dragResult) {
|
||||
Message.success(t('common.moveSuccess'));
|
||||
}
|
||||
|
@ -861,12 +845,12 @@
|
|||
const quickInputDataKey = ref('');
|
||||
|
||||
function setQuickInput(step: ScenarioStepItem, dataKey: string) {
|
||||
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.stepId, 'stepId');
|
||||
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.id, 'id');
|
||||
if (realStep) {
|
||||
activeStep.value = realStep as ScenarioStepItem;
|
||||
}
|
||||
quickInputDataKey.value = dataKey;
|
||||
quickInputParamValue.value = step.variableVal;
|
||||
quickInputParamValue.value = step.config?.[dataKey] || '';
|
||||
showQuickInput.value = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,19 +3,32 @@
|
|||
<template #first>
|
||||
<a-tabs v-model:active-key="activeKey" class="h-full" animation lazy-load>
|
||||
<a-tab-pane :key="ScenarioCreateComposition.STEP" :title="t('apiScenario.step')" class="p-[16px]">
|
||||
<step v-if="activeKey === ScenarioCreateComposition.STEP" v-model:step="scenario.stepInfo" is-new />
|
||||
<step v-if="activeKey === ScenarioCreateComposition.STEP" v-model:scenario="scenario" is-new />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane :key="ScenarioCreateComposition.PARAMS" :title="t('apiScenario.params')" class="p-[16px]">
|
||||
<params v-if="activeKey === ScenarioCreateComposition.PARAMS" v-model:params="scenario.params" />
|
||||
<params
|
||||
v-if="activeKey === ScenarioCreateComposition.PARAMS"
|
||||
v-model:params="scenario.scenarioConfig.variable.commonVariables"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane :key="ScenarioCreateComposition.PRE_POST" :title="t('apiScenario.prePost')" class="p-[16px]">
|
||||
<prePost v-if="activeKey === ScenarioCreateComposition.PRE_POST" />
|
||||
<prePost
|
||||
v-if="activeKey === ScenarioCreateComposition.PRE_POST"
|
||||
v-model:post-processor-config="scenario.scenarioConfig.postProcessorConfig"
|
||||
v-model:pre-processor-config="scenario.scenarioConfig.preProcessorConfig"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane :key="ScenarioCreateComposition.ASSERTION" :title="t('apiScenario.assertion')" class="p-[16px]">
|
||||
<assertion v-if="activeKey === ScenarioCreateComposition.ASSERTION" />
|
||||
<assertion
|
||||
v-if="activeKey === ScenarioCreateComposition.ASSERTION"
|
||||
v-model:assertion-config="scenario.scenarioConfig.assertionConfig"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane :key="ScenarioCreateComposition.SETTING" :title="t('common.setting')" class="p-[16px]">
|
||||
<setting v-if="activeKey === ScenarioCreateComposition.SETTING" />
|
||||
<setting
|
||||
v-if="activeKey === ScenarioCreateComposition.SETTING"
|
||||
v-model:other-config="scenario.scenarioConfig.otherConfig"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</template>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<div class="h-full w-full overflow-hidden">
|
||||
<div class="px-[24px] pt-[16px]">
|
||||
<MsDetailCard :title="`【${previewDetail.num}】${previewDetail.name}`" :description="description">
|
||||
<MsDetailCard :title="`【${scenario.num}】${scenario.name}`" :description="description">
|
||||
<template #titleAppend>
|
||||
<apiStatus :status="previewDetail.status" size="small" />
|
||||
<apiStatus :status="scenario.status" size="small" />
|
||||
</template>
|
||||
<template #titleRight>
|
||||
<a-button
|
||||
|
@ -15,11 +15,11 @@
|
|||
>
|
||||
<div class="flex items-center gap-[4px]">
|
||||
<MsIcon
|
||||
:type="previewDetail.follow ? 'icon-icon_collect_filled' : 'icon-icon_collection_outlined'"
|
||||
:class="`${previewDetail.follow ? 'text-[rgb(var(--warning-6))]' : 'text-[var(--color-text-4)]'}`"
|
||||
:type="scenario.follow ? 'icon-icon_collect_filled' : 'icon-icon_collection_outlined'"
|
||||
:class="`${scenario.follow ? 'text-[rgb(var(--warning-6))]' : 'text-[var(--color-text-4)]'}`"
|
||||
:size="14"
|
||||
/>
|
||||
{{ t(previewDetail.follow ? 'common.forked' : 'common.fork') }}
|
||||
{{ t(scenario.follow ? 'common.forked' : 'common.fork') }}
|
||||
</div>
|
||||
</a-button>
|
||||
<a-button type="outline" size="mini" class="arco-btn-outline--secondary !bg-transparent" @click="share">
|
||||
|
@ -29,8 +29,8 @@
|
|||
</div>
|
||||
</a-button>
|
||||
</template>
|
||||
<template #type="{ value }">
|
||||
<apiMethodName :method="value as RequestMethods" tag-size="small" is-tag />
|
||||
<template #priority="{ value }">
|
||||
<caseLevel :case-level="value as CaseLevel" />
|
||||
</template>
|
||||
</MsDetailCard>
|
||||
</div>
|
||||
|
@ -41,50 +41,57 @@
|
|||
:title="t('apiScenario.baseInfo')"
|
||||
class="px-[24px] py-[16px]"
|
||||
>
|
||||
BASE_INFO
|
||||
<baseInfo :scenario="scenario as ScenarioDetail" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane :key="ScenarioCreateComposition.STEP" :title="t('apiScenario.step')" class="px-[24px] py-[16px]">
|
||||
<step v-if="activeKey === ScenarioCreateComposition.STEP" :step="previewDetail.step" />
|
||||
<a-tab-pane :key="ScenarioDetailComposition.STEP" :title="t('apiScenario.step')" class="px-[24px] py-[16px]">
|
||||
<step v-if="activeKey === ScenarioDetailComposition.STEP" v-model:scenario="scenario" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane
|
||||
:key="ScenarioCreateComposition.PARAMS"
|
||||
:key="ScenarioDetailComposition.PARAMS"
|
||||
:title="t('apiScenario.params')"
|
||||
class="px-[24px] py-[16px]"
|
||||
>
|
||||
<params v-if="activeKey === ScenarioCreateComposition.PARAMS" v-model:params="allParams" />
|
||||
<params
|
||||
v-if="activeKey === ScenarioDetailComposition.PARAMS"
|
||||
v-model:params="scenario.scenarioConfig.variable.commonVariables"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane
|
||||
:key="ScenarioCreateComposition.PRE_POST"
|
||||
:key="ScenarioDetailComposition.PRE_POST"
|
||||
:title="t('apiScenario.prePost')"
|
||||
class="px-[24px] py-[16px]"
|
||||
>
|
||||
<prePost v-if="activeKey === ScenarioCreateComposition.PRE_POST" />
|
||||
<prePost
|
||||
v-if="activeKey === ScenarioDetailComposition.PRE_POST"
|
||||
v-model:post-processor-config="scenario.scenarioConfig.postProcessorConfig"
|
||||
v-model:pre-processor-config="scenario.scenarioConfig.preProcessorConfig"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane
|
||||
:key="ScenarioCreateComposition.ASSERTION"
|
||||
:key="ScenarioDetailComposition.ASSERTION"
|
||||
:title="t('apiScenario.assertion')"
|
||||
class="px-[24px] py-[16px]"
|
||||
>
|
||||
<assertion v-if="activeKey === ScenarioCreateComposition.ASSERTION" />
|
||||
<assertion
|
||||
v-if="activeKey === ScenarioDetailComposition.ASSERTION"
|
||||
v-model:assertion-config="scenario.scenarioConfig.assertionConfig"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane
|
||||
:key="ScenarioDetailComposition.EXECUTE_HISTORY"
|
||||
:title="t('apiScenario.executeHistory')"
|
||||
class="px-[24px] py-[16px]"
|
||||
>
|
||||
<executeHistory
|
||||
v-if="activeKey === ScenarioDetailComposition.EXECUTE_HISTORY"
|
||||
:scenario-id="previewDetail.id"
|
||||
/>
|
||||
<executeHistory v-if="activeKey === ScenarioDetailComposition.EXECUTE_HISTORY" :scenario-id="scenario.id" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane
|
||||
:key="ScenarioDetailComposition.CHANGE_HISTORY"
|
||||
:title="t('apiScenario.changeHistory')"
|
||||
class="px-[24px] py-[16px]"
|
||||
>
|
||||
<changeHistory v-if="activeKey === ScenarioDetailComposition.CHANGE_HISTORY" :source-id="previewDetail.id" />
|
||||
<changeHistory v-if="activeKey === ScenarioDetailComposition.CHANGE_HISTORY" :source-id="scenario.id" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane
|
||||
<!-- <a-tab-pane
|
||||
:key="ScenarioDetailComposition.DEPENDENCY"
|
||||
:title="t('apiScenario.dependency')"
|
||||
class="px-[24px] py-[16px]"
|
||||
|
@ -93,9 +100,12 @@
|
|||
</a-tab-pane>
|
||||
<a-tab-pane :key="ScenarioDetailComposition.QUOTE" :title="t('apiScenario.quote')" class="px-[24px] py-[16px]">
|
||||
<quote v-if="activeKey === ScenarioDetailComposition.QUOTE" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane :key="ScenarioCreateComposition.SETTING" :title="t('common.setting')" class="px-[24px] py-[16px]">
|
||||
<setting v-if="activeKey === ScenarioCreateComposition.SETTING" />
|
||||
</a-tab-pane> -->
|
||||
<a-tab-pane :key="ScenarioDetailComposition.SETTING" :title="t('common.setting')" class="px-[24px] py-[16px]">
|
||||
<setting
|
||||
v-if="activeKey === ScenarioDetailComposition.SETTING"
|
||||
v-model:other-config="scenario.scenarioConfig.otherConfig"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
|
@ -106,47 +116,52 @@
|
|||
import { useI18n } from 'vue-i18n';
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import MsDetailCard from '@/components/pure/ms-detail-card/index.vue';
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
||||
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
||||
import type { CaseLevel } from '@/components/business/ms-case-associate/types';
|
||||
import baseInfo from '../components/baseInfo.vue';
|
||||
import step from '../components/step/index.vue';
|
||||
import apiStatus from '@/views/api-test/components/apiStatus.vue';
|
||||
|
||||
import { RequestMethods, ScenarioCreateComposition, ScenarioDetailComposition } from '@/enums/apiEnum';
|
||||
import { Scenario, ScenarioDetail } from '@/models/apiTest/scenario';
|
||||
import { ScenarioDetailComposition } from '@/enums/apiEnum';
|
||||
|
||||
// 组成部分异步导入
|
||||
const step = defineAsyncComponent(() => import('../components/step/index.vue'));
|
||||
const params = defineAsyncComponent(() => import('../components/params.vue'));
|
||||
const prePost = defineAsyncComponent(() => import('../components/prePost.vue'));
|
||||
const assertion = defineAsyncComponent(() => import('../components/assertion.vue'));
|
||||
const executeHistory = defineAsyncComponent(() => import('../components/executeHistory.vue'));
|
||||
const changeHistory = defineAsyncComponent(() => import('../components/changeHistory.vue'));
|
||||
const dependency = defineAsyncComponent(() => import('../components/dependency.vue'));
|
||||
const quote = defineAsyncComponent(() => import('../components/quote.vue'));
|
||||
// const dependency = defineAsyncComponent(() => import('../components/dependency.vue'));
|
||||
// const quote = defineAsyncComponent(() => import('../components/quote.vue'));
|
||||
const setting = defineAsyncComponent(() => import('../components/setting.vue'));
|
||||
|
||||
const allParams = ref<any[]>([]);
|
||||
const props = defineProps<{
|
||||
detail: Record<string, any>;
|
||||
}>();
|
||||
const emit = defineEmits(['updateFollow']);
|
||||
|
||||
const { copy, isSupported } = useClipboard();
|
||||
const { t } = useI18n();
|
||||
|
||||
const previewDetail = ref<Record<string, any>>(cloneDeep(props.detail));
|
||||
const scenario = defineModel<Scenario>('scenario', {
|
||||
required: true,
|
||||
});
|
||||
|
||||
const description = computed(() => [
|
||||
{
|
||||
key: 'type',
|
||||
locale: 'something.type',
|
||||
value: 'type',
|
||||
key: 'priority',
|
||||
locale: 'apiScenario.scenarioLevel',
|
||||
value: scenario.value.priority,
|
||||
},
|
||||
{
|
||||
key: 'path',
|
||||
locale: 'something.path',
|
||||
value: 'path',
|
||||
key: 'tag',
|
||||
locale: 'common.tag',
|
||||
value: scenario.value.tags,
|
||||
},
|
||||
{
|
||||
key: 'description',
|
||||
locale: 'common.desc',
|
||||
value: scenario.value.description,
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -154,7 +169,7 @@
|
|||
async function toggleFollowReview() {
|
||||
try {
|
||||
followLoading.value = true;
|
||||
Message.success(previewDetail.value.follow ? t('common.unFollowSuccess') : t('common.followSuccess'));
|
||||
Message.success(scenario.value.follow ? t('common.unFollowSuccess') : t('common.followSuccess'));
|
||||
emit('updateFollow');
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
|
@ -166,17 +181,20 @@
|
|||
|
||||
function share() {
|
||||
if (isSupported) {
|
||||
copy(`${window.location.href}&dId=${previewDetail.value.id}`);
|
||||
copy(`${window.location.href}&dId=${scenario.value.id}`);
|
||||
Message.success(t('common.copySuccess'));
|
||||
} else {
|
||||
Message.error(t('common.copyNotSupport'));
|
||||
}
|
||||
}
|
||||
|
||||
const activeKey = ref<ScenarioCreateComposition | ScenarioDetailComposition>(ScenarioDetailComposition.BASE_INFO);
|
||||
const activeKey = ref<ScenarioDetailComposition>(ScenarioDetailComposition.STEP);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.arco-tabs-nav) {
|
||||
@apply border-b;
|
||||
}
|
||||
:deep(.arco-tabs-content) {
|
||||
@apply pt-0;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
v-model:active-tab="activeScenarioTab"
|
||||
v-model:tabs="apiTabs"
|
||||
class="flex-1 overflow-hidden"
|
||||
@add="newTab"
|
||||
@add="() => newTab()"
|
||||
>
|
||||
<template #label="{ tab }">
|
||||
<a-tooltip :content="tab.label" :mouse-enter-delay="500">
|
||||
|
@ -34,7 +34,7 @@
|
|||
:is-show-scenario="isShowScenario"
|
||||
@folder-node-select="handleNodeSelect"
|
||||
@init="handleModuleInit"
|
||||
@new-scenario="newTab"
|
||||
@new-scenario="() => newTab()"
|
||||
></scenarioModuleTree>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
|
@ -55,6 +55,7 @@
|
|||
:active-module="activeModule"
|
||||
:offspring-ids="offspringIds"
|
||||
@refresh-module-tree="refreshTree"
|
||||
@open-scenario="openScenarioTab"
|
||||
/>
|
||||
</template>
|
||||
</MsSplitBox>
|
||||
|
@ -63,7 +64,7 @@
|
|||
<create v-model:scenario="activeScenarioTab" :module-tree="folderTree"></create>
|
||||
</div>
|
||||
<div v-else class="pageWrap">
|
||||
<detail :detail="activeScenarioTab"></detail>
|
||||
<detail v-model:scenario="activeScenarioTab"></detail>
|
||||
</div>
|
||||
</MsCard>
|
||||
</template>
|
||||
|
@ -74,66 +75,69 @@
|
|||
*/
|
||||
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||
import MsEditableTab from '@/components/pure/ms-editable-tab/index.vue';
|
||||
import { TabItem } from '@/components/pure/ms-editable-tab/types';
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||
import scenarioModuleTree from './components/scenarioModuleTree.vue';
|
||||
import { ScenarioStepInfo } from './components/step/index.vue';
|
||||
import environmentSelect from '@/views/api-test/components/environmentSelect.vue';
|
||||
// import executeButton from '@/views/api-test/components/executeButton.vue';
|
||||
import ScenarioTable from '@/views/api-test/scenario/components/scenarioTable.vue';
|
||||
|
||||
import { getTrashModuleCount } from '@/api/modules/api-test/scenario';
|
||||
import { addScenario, getScenarioDetail, getTrashModuleCount, updateScenario } from '@/api/modules/api-test/scenario';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import router from '@/router';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { getGenerateId, mapTree, TreeNode } from '@/utils';
|
||||
|
||||
import { ApiScenarioGetModuleParams, Scenario } from '@/models/apiTest/scenario';
|
||||
import {
|
||||
ApiScenarioGetModuleParams,
|
||||
ApiScenarioTableItem,
|
||||
Scenario,
|
||||
ScenarioStepItem,
|
||||
} from '@/models/apiTest/scenario';
|
||||
import { ModuleTreeNode } from '@/models/common';
|
||||
import { RequestDefinitionStatus } from '@/enums/apiEnum';
|
||||
import { ApiTestRouteEnum } from '@/enums/routeEnum';
|
||||
|
||||
import { defaultScenario } from './components/config';
|
||||
|
||||
// 异步导入
|
||||
const detail = defineAsyncComponent(() => import('./detail/index.vue'));
|
||||
const create = defineAsyncComponent(() => import('./create/index.vue'));
|
||||
|
||||
export type ScenarioParams = Scenario & TabItem;
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const apiTabs = ref<Scenario[]>([
|
||||
const apiTabs = ref<ScenarioParams[]>([
|
||||
{
|
||||
id: 'all',
|
||||
label: t('apiScenario.allScenario'),
|
||||
closable: false,
|
||||
} as Scenario,
|
||||
} as ScenarioParams,
|
||||
]);
|
||||
const activeScenarioTab = ref<Scenario>(apiTabs.value[0]);
|
||||
const activeScenarioTab = ref<ScenarioParams>(apiTabs.value[0] as ScenarioParams);
|
||||
|
||||
function newTab() {
|
||||
function newTab(defaultScenarioInfo?: Scenario, isCopy = false) {
|
||||
if (defaultScenarioInfo) {
|
||||
apiTabs.value.push({
|
||||
...defaultScenarioInfo,
|
||||
id: isCopy ? getGenerateId() : defaultScenarioInfo.id || '',
|
||||
label: isCopy ? `copy-${defaultScenarioInfo.name}` : defaultScenarioInfo.name,
|
||||
});
|
||||
} else {
|
||||
apiTabs.value.push({
|
||||
...cloneDeep(defaultScenario),
|
||||
id: `${t('apiScenario.createScenario')}${apiTabs.value.length}`,
|
||||
label: `${t('apiScenario.createScenario')}${apiTabs.value.length}`,
|
||||
closable: true,
|
||||
isNew: true,
|
||||
name: '',
|
||||
moduleId: 'root',
|
||||
priority: 'P0',
|
||||
stepInfo: {
|
||||
id: new Date().getTime(),
|
||||
steps: [],
|
||||
executeTime: '',
|
||||
executeSuccessCount: 0,
|
||||
executeFailCount: 0,
|
||||
stepsDetailMap: {},
|
||||
} as ScenarioStepInfo,
|
||||
status: RequestDefinitionStatus.PROCESSING,
|
||||
tags: [],
|
||||
params: [],
|
||||
executeLoading: false,
|
||||
unSaved: false,
|
||||
});
|
||||
activeScenarioTab.value = apiTabs.value[apiTabs.value.length - 1];
|
||||
}
|
||||
activeScenarioTab.value = apiTabs.value[apiTabs.value.length - 1] as ScenarioParams;
|
||||
}
|
||||
|
||||
const folderTree = ref<ModuleTreeNode[]>([]);
|
||||
|
@ -185,16 +189,49 @@
|
|||
const saveLoading = ref(false);
|
||||
|
||||
async function saveScenario() {
|
||||
try {
|
||||
saveLoading.value = true;
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve('');
|
||||
}, 1000);
|
||||
if (activeScenarioTab.value.isNew) {
|
||||
const res = await addScenario({
|
||||
...activeScenarioTab.value,
|
||||
projectId: appStore.currentProjectId,
|
||||
});
|
||||
const scenarioDetail = await getScenarioDetail(res.id);
|
||||
activeScenarioTab.value = scenarioDetail as ScenarioParams;
|
||||
} else {
|
||||
await updateScenario({
|
||||
...activeScenarioTab.value,
|
||||
});
|
||||
}
|
||||
Message.success(activeScenarioTab.value.isNew ? t('common.createSuccess') : t('common.saveSuccess'));
|
||||
activeScenarioTab.value.isNew = false;
|
||||
activeScenarioTab.value.unSaved = false;
|
||||
saveLoading.value = false;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
saveLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function openScenarioTab(record: ApiScenarioTableItem, isCopy?: boolean) {
|
||||
try {
|
||||
appStore.showLoading();
|
||||
const res = await getScenarioDetail(record.id);
|
||||
res.stepDetails = {};
|
||||
// mapTree<ScenarioStepItem>(res.steps, (node: TreeNode<ScenarioStepItem>) => {
|
||||
// res.stepDetails[node.id] = node.config;
|
||||
// return node;
|
||||
// });
|
||||
newTab(res, isCopy);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
nextTick(() => {
|
||||
appStore.hideLoading();
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -110,12 +110,14 @@ export default {
|
|||
'apiScenario.after': '在之后插入步骤',
|
||||
'apiScenario.num': '次数',
|
||||
'apiScenario.space': '间隔(ms)',
|
||||
'apiScenario.overTime': '超时(ms)',
|
||||
'apiScenario.timeout': '超时(ms)',
|
||||
'apiScenario.waitTimeMs': '等待(ms)',
|
||||
'apiScenario.pleaseInputStepDesc': '请输入步骤描述',
|
||||
'apiScenario.variableName': '变量名称{suffix}',
|
||||
'apiScenario.variablePrefix': '变量前缀',
|
||||
'apiScenario.variableVal': '变量值',
|
||||
'apiScenario.variable': '变量名称{suffix}',
|
||||
'apiScenario.valuePrefix': '变量前缀',
|
||||
'apiScenario.value': '变量值',
|
||||
'apiScenario.whileController.msWhileVariable.value': '变量值',
|
||||
'apiScenario.whileController.msWhileScript.scriptValue': '表达式',
|
||||
'apiScenario.condition': '条件',
|
||||
'apiScenario.expression': '表达式',
|
||||
'apiScenario.equal': '等于',
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
</template>
|
||||
</MsSplitBox>
|
||||
</div>
|
||||
<detail v-else :detail="activeApiTab"></detail>
|
||||
<detail v-else v-model:scenario="activeApiTab"></detail>
|
||||
</MsCard>
|
||||
</template>
|
||||
|
||||
|
|
Loading…
Reference in New Issue