fix(all): 修复一堆 bug
This commit is contained in:
parent
3e1a69fa41
commit
c6b443d96c
|
@ -134,7 +134,9 @@
|
|||
const menuSwitchOrgVisible = ref(false);
|
||||
const orgKeyword = ref('');
|
||||
const originOrgList = ref<{ id: string; name: string }[]>([]);
|
||||
const orgList = computed(() => originOrgList.value.filter((e) => e.name.includes(orgKeyword.value)));
|
||||
const orgList = computed(() =>
|
||||
originOrgList.value.filter((e) => e.name.toLowerCase().includes(orgKeyword.value.toLowerCase()))
|
||||
);
|
||||
|
||||
async function switchOrg(id: string) {
|
||||
try {
|
||||
|
@ -291,7 +293,7 @@
|
|||
}}
|
||||
>
|
||||
<a-tooltip content={item.name}>
|
||||
<div class="one-line-text max-w-[220px]">{item.name}</div>
|
||||
<div class="one-line-text flex-1">{item.name}</div>
|
||||
</a-tooltip>
|
||||
{item.id === appStore.currentOrgId ? (
|
||||
<MsTag
|
||||
|
|
|
@ -187,9 +187,6 @@
|
|||
}
|
||||
|
||||
async function testApi() {
|
||||
if (apiConfig.value.userUrl.trim() === '') {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
testApiLoading.value = true;
|
||||
const res = await validLocalConfig(apiConfig.value.userUrl.trim());
|
||||
|
@ -217,6 +214,7 @@
|
|||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
apiConfig.value.status = 2;
|
||||
} finally {
|
||||
testApiLoading.value = false;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div ref="treeContainerRef" :class="['ms-tree-container', containerStatusClass]">
|
||||
<a-tree
|
||||
v-show="data.length > 0"
|
||||
v-show="filterTreeData.length > 0"
|
||||
v-bind="props"
|
||||
ref="treeRef"
|
||||
v-model:expanded-keys="expandedKeys"
|
||||
|
@ -63,7 +63,7 @@
|
|||
</a-tree>
|
||||
<slot name="empty">
|
||||
<div
|
||||
v-show="data.length === 0 && props.emptyText"
|
||||
v-show="filterTreeData.length === 0 && props.emptyText"
|
||||
class="rounded-[var(--border-radius-small)] bg-[var(--color-fill-1)] p-[8px] text-[12px] leading-[16px] text-[var(--color-text-4)]"
|
||||
>
|
||||
{{ props.emptyText }}
|
||||
|
@ -74,7 +74,7 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { nextTick, onBeforeMount, Ref, ref, watch } from 'vue';
|
||||
import { cloneDeep, debounce } from 'lodash-es';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
|
||||
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'init', parseJson: string | Record<string, any>): void;
|
||||
(e: 'pick', path: string, parseJson: string | Record<string, any>, result: any[]): void;
|
||||
}>();
|
||||
|
||||
|
@ -45,6 +46,7 @@
|
|||
}) as Record<string, any>;
|
||||
}
|
||||
JPPicker.jsonPathPicker(jr.value, json.value, [ip.value], props.opt);
|
||||
emit('init', json.value);
|
||||
} catch (error) {
|
||||
JPPicker.jsonPathPicker(jr.value, props.data, [ip.value], props.opt);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
ResponseBodyDocumentAssertionType,
|
||||
ResponseBodyFormat,
|
||||
ResponseBodyXPathAssertionFormat,
|
||||
ScenarioExecuteStatus,
|
||||
} from '@/enums/apiEnum';
|
||||
|
||||
// 获取插件表单选项参数
|
||||
|
@ -411,6 +412,8 @@ export interface RequestResult {
|
|||
responseResult: ResponseResult;
|
||||
isSuccessful?: boolean;
|
||||
console?: string;
|
||||
status?: ScenarioExecuteStatus;
|
||||
fakeErrorCode?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
export interface RequestTaskResult {
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import { RequestDefinitionStatus, RequestImportFormat, RequestImportType } from '@/enums/apiEnum';
|
||||
import {
|
||||
RequestCaseStatus,
|
||||
RequestDefinitionStatus,
|
||||
RequestImportFormat,
|
||||
RequestImportType,
|
||||
RequestMethods,
|
||||
} from '@/enums/apiEnum';
|
||||
|
||||
import { BatchApiParams, ModuleTreeNode, TableQueryParams } from '../common';
|
||||
import { ExecuteRequestParams, ResponseDefinition } from './common';
|
||||
|
@ -19,7 +25,6 @@ export interface ApiDefinitionCreateParams extends ExecuteRequestParams {
|
|||
customFields: ApiDefinitionCustomField[];
|
||||
moduleId: string;
|
||||
versionId: string;
|
||||
|
||||
[key: string]: any; // 其他前端定义的参数
|
||||
}
|
||||
|
||||
|
@ -55,7 +60,7 @@ export interface ApiDefinitionDetail extends ApiDefinitionCreateParams {
|
|||
id: string;
|
||||
name: string;
|
||||
protocol: string;
|
||||
method: string;
|
||||
method: RequestMethods;
|
||||
path: string;
|
||||
num: number;
|
||||
pos: number;
|
||||
|
@ -181,7 +186,7 @@ export interface ApiDefinitionBatchParams extends BatchApiParams {
|
|||
export interface ApiDefinitionBatchUpdateParams extends ApiDefinitionBatchParams {
|
||||
type?: string;
|
||||
append?: boolean;
|
||||
method?: string;
|
||||
method?: RequestMethods;
|
||||
status?: RequestDefinitionStatus;
|
||||
versionId?: string;
|
||||
tags?: string[];
|
||||
|
@ -300,7 +305,7 @@ export interface ApiCaseDetail extends ExecuteRequestParams {
|
|||
name: string;
|
||||
priority: string;
|
||||
num: number;
|
||||
status: string;
|
||||
status: RequestCaseStatus;
|
||||
protocol: string;
|
||||
lastReportStatus: string;
|
||||
lastReportId: string;
|
||||
|
@ -309,7 +314,7 @@ export interface ApiCaseDetail extends ExecuteRequestParams {
|
|||
environmentId: string;
|
||||
environmentName: string;
|
||||
follow: boolean;
|
||||
method: string;
|
||||
method: RequestMethods;
|
||||
path: string;
|
||||
tags: string[];
|
||||
passRate: string;
|
||||
|
@ -335,7 +340,7 @@ export interface ApiCaseBatchParams extends BatchApiParams {
|
|||
export interface ApiCaseBatchEditParams extends ApiCaseBatchParams {
|
||||
priority?: string;
|
||||
tags?: string[];
|
||||
status?: string;
|
||||
status?: RequestCaseStatus;
|
||||
environmentId?: string;
|
||||
type: string;
|
||||
append?: boolean;
|
||||
|
@ -344,7 +349,7 @@ export interface ApiCaseBatchEditParams extends ApiCaseBatchParams {
|
|||
export interface AddApiCaseParams extends ExecuteRequestParams {
|
||||
name: string;
|
||||
priority: string;
|
||||
status: string;
|
||||
status: RequestCaseStatus;
|
||||
tags: string[];
|
||||
deleteFileIds?: string[];
|
||||
unLinkFileIds?: string[];
|
||||
|
@ -393,7 +398,7 @@ export interface ApiCaseExecuteHistoryItem {
|
|||
operationUser: string;
|
||||
createUser: string;
|
||||
startTime: number;
|
||||
status: string;
|
||||
status: RequestCaseStatus;
|
||||
triggerMode: string;
|
||||
deleted: boolean;
|
||||
}
|
||||
|
|
|
@ -352,6 +352,7 @@ export interface ScenarioStepItem {
|
|||
// 页面渲染以及交互需要字段
|
||||
checked?: boolean; // 是否选中
|
||||
expanded?: boolean; // 是否展开
|
||||
draggable?: boolean; // 是否可拖拽
|
||||
createActionsVisible?: boolean; // 是否展示创建步骤下拉
|
||||
responsePopoverVisible?: boolean; // 是否展示步骤响应 popover
|
||||
parent?: ScenarioStepItem; // 父级节点,第一层的父级节点为undefined
|
||||
|
|
|
@ -183,11 +183,13 @@ const useUserStore = defineStore('user', {
|
|||
async initLocalConfig() {
|
||||
try {
|
||||
const res = await getLocalConfig();
|
||||
const apiLocalExec = res.find((e) => e.type === 'API');
|
||||
if (apiLocalExec) {
|
||||
this.hasLocalExec = true;
|
||||
this.isPriorityLocalExec = apiLocalExec.enable || false;
|
||||
this.localExecuteUrl = apiLocalExec.userUrl || '';
|
||||
if (res) {
|
||||
const apiLocalExec = res.find((e) => e.type === 'API');
|
||||
if (apiLocalExec) {
|
||||
this.hasLocalExec = true;
|
||||
this.isPriorityLocalExec = apiLocalExec.enable || false;
|
||||
this.localExecuteUrl = apiLocalExec.userUrl || '';
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
|
|
|
@ -348,12 +348,13 @@
|
|||
:disabled="props.disabled"
|
||||
mode="button"
|
||||
:step="100"
|
||||
:min="0"
|
||||
:min="1"
|
||||
:precision="0"
|
||||
:max="600000"
|
||||
:default-value="1000"
|
||||
class="w-[160px]"
|
||||
model-event="input"
|
||||
@blur="handleDelayBlur"
|
||||
/>
|
||||
</div>
|
||||
<!-- 提取参数 -->
|
||||
|
@ -644,6 +645,7 @@ if (!result){
|
|||
try {
|
||||
emit('delete', condition.value.id);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
},
|
||||
|
@ -742,6 +744,12 @@ if (!result){
|
|||
}
|
||||
}
|
||||
|
||||
function handleDelayBlur() {
|
||||
if (!condition.value.delay) {
|
||||
condition.value.delay = 1000;
|
||||
}
|
||||
}
|
||||
|
||||
const extractParamsColumns: ParamTableColumn[] = [
|
||||
{
|
||||
title: 'apiTestDebug.paramName',
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
/>
|
||||
</div>
|
||||
<div v-else-if="expressionForm.extractType === RequestExtractExpressionEnum.JSON_PATH" class="code-container">
|
||||
<MsJsonPathPicker :data="props.response || ''" class="bg-white" @pick="handlePathPick" />
|
||||
<MsJsonPathPicker :data="props.response || ''" class="bg-white" @init="initJsonPath" @pick="handlePathPick" />
|
||||
</div>
|
||||
<div v-else-if="expressionForm.extractType === RequestExtractExpressionEnum.X_PATH" class="code-container">
|
||||
<MsXPathPicker :xml-string="props.response || ''" class="bg-white" @pick="handlePathPick" />
|
||||
|
@ -204,9 +204,14 @@
|
|||
}
|
||||
);
|
||||
|
||||
function initJsonPath(_parseJson: string | Record<string, any>) {
|
||||
parseJson.value = _parseJson;
|
||||
}
|
||||
|
||||
function handlePathPick(path: string, _parseJson: string | Record<string, any>) {
|
||||
expressionForm.value.expression = path;
|
||||
parseJson.value = _parseJson;
|
||||
expressionFormRef.value?.clearValidate();
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -271,6 +276,7 @@
|
|||
matchResult.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`正则匹配异常:${error}`);
|
||||
matchResult.value = [];
|
||||
}
|
||||
|
|
|
@ -76,6 +76,7 @@
|
|||
addModuleApi?: (params: { projectId: string; parentId: string; name: string }) => Promise<any>;
|
||||
updateModuleApi?: (params: { id: string; name: string }) => Promise<any>;
|
||||
updateApiNodeApi?: (params: { id: string; name: string }) => Promise<any>;
|
||||
repeatMessage?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['update:visible', 'close', 'addFinish', 'renameFinish']);
|
||||
|
@ -174,7 +175,7 @@
|
|||
|
||||
function validateName(value: any, callback: (error?: string | undefined) => void) {
|
||||
if (props.allNames.includes(value)) {
|
||||
callback(t('project.fileManagement.nameExist'));
|
||||
callback(props.repeatMessage || t('project.fileManagement.nameExist'));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
:field-config="{ field: t(tab.label || tab.name) }"
|
||||
:all-names="responseTabs.map((e) => t(e.label || e.name))"
|
||||
:popup-offset="20"
|
||||
:repeat-message="t('apiTestDebug.responseRepeatMessage')"
|
||||
@rename-finish="
|
||||
(val) => {
|
||||
tab.label = val;
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
v-if="props.requestResult?.responseResult?.responseCode"
|
||||
class="flex items-center justify-between gap-[24px] text-[14px]"
|
||||
>
|
||||
<a-tooltip :content="props.requestResult.fakeErrorCode">
|
||||
<executeStatus :status="props.requestResult.status" size="small" class="ml-[4px]" />
|
||||
</a-tooltip>
|
||||
<a-popover position="left" content-class="response-popover-content">
|
||||
<div class="one-line-text max-w-[200px]" :style="{ color: statusCodeColor }">
|
||||
{{ props.requestResult.responseResult.responseCode }}
|
||||
|
@ -44,6 +47,7 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import responseTimeLine from '@/views/api-test/components/responseTimeLine.vue';
|
||||
import executeStatus from '@/views/api-test/scenario/components/common/executeStatus.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
|
|
|
@ -204,4 +204,5 @@ export default {
|
|||
'apiTestDebug.regexMatchRules': 'Expression matching rules',
|
||||
'apiTestDebug.extractValueTitleTip':
|
||||
'Enter the column name and corresponding value in column storage. If you want to extract the first value of the name column, enter name_1',
|
||||
'apiTestDebug.responseRepeatMessage': 'The name is duplicated, please re-enter it.',
|
||||
};
|
||||
|
|
|
@ -190,4 +190,5 @@ export default {
|
|||
'apiTestDebug.searchByDataBaseName': '按数据源名称搜索',
|
||||
'apiTestDebug.regexMatchRules': '表达式匹配规则',
|
||||
'apiTestDebug.extractValueTitleTip': '输入按列存储中的列名和对应的数值,如提取name列的第一个值则输入name_1',
|
||||
'apiTestDebug.responseRepeatMessage': '名称重复,请重新输入',
|
||||
};
|
||||
|
|
|
@ -332,8 +332,10 @@
|
|||
return;
|
||||
}
|
||||
try {
|
||||
appStore.showLoading();
|
||||
loading.value = true;
|
||||
const res = await getDefinitionDetail(typeof apiInfo === 'string' ? apiInfo : apiInfo.id);
|
||||
appStore.hideLoading();
|
||||
let name = isCopy ? `copy_${res.name}` : res.name;
|
||||
if (name.length > 255) {
|
||||
name = name.slice(0, 255);
|
||||
|
@ -367,6 +369,7 @@
|
|||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
loading.value = false;
|
||||
appStore.hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,12 @@
|
|||
>
|
||||
<template #title>
|
||||
<div class="flex max-w-[60%] items-center gap-[8px]">
|
||||
<div
|
||||
v-if="props.step"
|
||||
class="flex h-[16px] min-w-[16px] items-center justify-center rounded-full bg-[var(--color-text-brand)] pr-[2px] !text-white"
|
||||
>
|
||||
{{ props.step.sort }}
|
||||
</div>
|
||||
<stepTypeVue
|
||||
v-if="props.step && [ScenarioStepType.API, ScenarioStepType.CUSTOM_REQUEST].includes(props.step?.stepType)"
|
||||
:step="props.step"
|
||||
|
@ -23,44 +29,46 @@
|
|||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div
|
||||
v-if="props.step && !props.step.isQuoteScenarioStep"
|
||||
class="right-operation-button-icon ml-auto flex items-center"
|
||||
>
|
||||
<replaceButton
|
||||
v-if="props.step.resourceId && props.step?.stepType !== ScenarioStepType.CUSTOM_REQUEST"
|
||||
:steps="props.steps"
|
||||
:step="props.step"
|
||||
:resource-id="props.step.resourceId"
|
||||
:scenario-id="scenarioId"
|
||||
@replace="handleReplace"
|
||||
/>
|
||||
<MsButton class="mr-4" type="icon" status="secondary" @click="emit('deleteStep')">
|
||||
<MsIcon type="icon-icon_delete-trash_outlined" />
|
||||
{{ t('common.delete') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
<div
|
||||
v-if="!props.step || props.step?.stepType === ScenarioStepType.CUSTOM_REQUEST"
|
||||
class="customApiDrawer-title-right ml-auto flex items-center gap-[16px]"
|
||||
>
|
||||
<a-tooltip :content="currentEnvConfig?.name" :disabled="!currentEnvConfig?.name">
|
||||
<div class="one-line-text max-w-[250px] text-[14px] font-normal text-[var(--color-text-4)]">
|
||||
{{ t('apiScenario.env', { name: currentEnvConfig?.name }) }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<a-select
|
||||
v-model:model-value="requestVModel.customizeRequestEnvEnable"
|
||||
class="w-[150px]"
|
||||
:disabled="props.step?.isQuoteScenarioStep"
|
||||
@change="handleUseEnvChange"
|
||||
<div class="ml-auto flex items-center gap-[16px]">
|
||||
<div
|
||||
v-if="!props.step || props.step?.stepType === ScenarioStepType.CUSTOM_REQUEST"
|
||||
class="customApiDrawer-title-right flex items-center gap-[16px]"
|
||||
>
|
||||
<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>
|
||||
<a-tooltip :content="currentEnvConfig?.name" :disabled="!currentEnvConfig?.name">
|
||||
<div class="one-line-text max-w-[250px] text-[14px] font-normal text-[var(--color-text-4)]">
|
||||
{{ t('apiScenario.env', { name: currentEnvConfig?.name }) }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<a-select
|
||||
v-model:model-value="requestVModel.customizeRequestEnvEnable"
|
||||
class="w-[150px]"
|
||||
:disabled="props.step?.isQuoteScenarioStep"
|
||||
@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>
|
||||
<div
|
||||
v-if="props.step && !props.step.isQuoteScenarioStep"
|
||||
class="right-operation-button-icon ml-auto flex items-center"
|
||||
>
|
||||
<replaceButton
|
||||
v-if="props.step.resourceId && props.step?.stepType !== ScenarioStepType.CUSTOM_REQUEST"
|
||||
:steps="props.steps"
|
||||
:step="props.step"
|
||||
:resource-id="props.step.resourceId"
|
||||
:scenario-id="scenarioId"
|
||||
@replace="handleReplace"
|
||||
/>
|
||||
<MsButton type="icon" status="secondary" @click="emit('deleteStep')">
|
||||
<MsIcon type="icon-icon_delete-trash_outlined" />
|
||||
{{ t('common.delete') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<a-empty
|
||||
|
|
|
@ -8,6 +8,12 @@
|
|||
@close="handleClose"
|
||||
>
|
||||
<template #title>
|
||||
<div
|
||||
v-if="activeStep"
|
||||
class="flex h-[16px] min-w-[16px] items-center justify-center rounded-full bg-[var(--color-text-brand)] pr-[2px] !text-white"
|
||||
>
|
||||
{{ activeStep.sort }}
|
||||
</div>
|
||||
<stepType v-if="activeStep?.stepType" :step="activeStep" class="mr-[4px]" />
|
||||
<a-input
|
||||
v-if="activeStep?.name"
|
||||
|
|
|
@ -506,6 +506,7 @@
|
|||
tableSelectedData.value = props.selectedScenarios;
|
||||
break;
|
||||
}
|
||||
keyword.value = '';
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -1,15 +1,5 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="mb-[8px] flex items-center justify-end">
|
||||
<a-input-search
|
||||
v-model:model-value="keyword"
|
||||
:placeholder="t('apiScenario.executeHistory.searchPlaceholder')"
|
||||
allow-clear
|
||||
class="mr-[8px] w-[240px]"
|
||||
@search="loadExecuteHistoryList"
|
||||
@press-enter="loadExecuteHistoryList"
|
||||
/>
|
||||
</div>
|
||||
<ms-base-table v-bind="propsRes" no-disable filter-icon-align-left v-on="propsEvent">
|
||||
<template #num="{ record }">
|
||||
<span type="text" class="px-0">{{ record.num }}</span>
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
/>
|
||||
<batchAddKeyVal
|
||||
:params="innerParams"
|
||||
:default-param-item="defaultHeaderParamsItem"
|
||||
:default-param-item="defaultParamItem"
|
||||
no-param-type
|
||||
@apply="handleBatchParamApply"
|
||||
/>
|
||||
|
@ -44,7 +44,6 @@
|
|||
|
||||
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<{
|
||||
|
@ -146,7 +145,7 @@
|
|||
* 批量参数代码转换为参数表格数据
|
||||
*/
|
||||
function handleBatchParamApply(resultArr: any[]) {
|
||||
const filterResult = filterKeyValParams(innerParams.value, defaultHeaderParamsItem);
|
||||
const filterResult = filterKeyValParams(innerParams.value, defaultParamItem);
|
||||
if (filterResult.lastDataIsDefault) {
|
||||
innerParams.value = [...resultArr, innerParams.value[innerParams.value.length - 1]].filter(Boolean);
|
||||
} else {
|
||||
|
|
|
@ -415,7 +415,13 @@
|
|||
</a-modal>
|
||||
|
||||
<!-- 表格批量操作-->
|
||||
<a-modal v-model:visible="showBatchModal" title-align="start" class="ms-modal-upload ms-modal-medium" :width="480">
|
||||
<a-modal
|
||||
v-model:visible="showBatchModal"
|
||||
title-align="start"
|
||||
class="ms-modal-upload ms-modal-medium"
|
||||
:width="480"
|
||||
@close="cancelBatch"
|
||||
>
|
||||
<template #title>
|
||||
{{ t('common.batchEdit') }}
|
||||
<div class="text-[var(--color-text-4)]">
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
class="max-w-[500px] px-[8px]"
|
||||
size="mini"
|
||||
:step="1000"
|
||||
:min="0"
|
||||
:min="1"
|
||||
:max="600000"
|
||||
:precision="0"
|
||||
model-event="input"
|
||||
|
@ -46,6 +46,10 @@
|
|||
});
|
||||
|
||||
function handleInputChange() {
|
||||
console.log('innerData.value.delay', innerData.value.delay);
|
||||
if (!innerData.value.delay) {
|
||||
innerData.value.delay = 1;
|
||||
}
|
||||
nextTick(() => {
|
||||
emit('change', innerData.value);
|
||||
});
|
||||
|
|
|
@ -64,14 +64,14 @@
|
|||
></a-switch>
|
||||
<!-- 步骤执行 -->
|
||||
<MsIcon
|
||||
v-show="!step.isExecuting && step.enable"
|
||||
v-show="!step.isExecuting"
|
||||
type="icon-icon_play-round_filled"
|
||||
:size="18"
|
||||
class="cursor-pointer text-[rgb(var(--link-6))]"
|
||||
@click.stop="executeStep(step)"
|
||||
/>
|
||||
<MsIcon
|
||||
v-show="step.isExecuting && step.enable"
|
||||
v-show="step.isExecuting"
|
||||
type="icon-icon_stop"
|
||||
:size="20"
|
||||
class="cursor-pointer text-[rgb(var(--link-6))]"
|
||||
|
@ -378,12 +378,9 @@
|
|||
<a-modal
|
||||
v-model:visible="saveNewApiModalVisible"
|
||||
:title="t('common.save')"
|
||||
:ok-loading="saveLoading"
|
||||
class="ms-modal-form"
|
||||
title-align="start"
|
||||
body-class="!p-0"
|
||||
@before-ok="handleSave"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<a-form ref="saveModalFormRef" :model="saveModalForm" layout="vertical">
|
||||
<a-form-item
|
||||
|
@ -414,7 +411,7 @@
|
|||
<a-form-item :label="t('apiTestDebug.requestModule')" class="mb-0">
|
||||
<a-tree-select
|
||||
v-model:modelValue="saveModalForm.moduleId"
|
||||
:data="moduleTree || []"
|
||||
:data="apiModuleTree"
|
||||
:field-names="{ title: 'name', key: 'id', children: 'children' }"
|
||||
:tree-props="{
|
||||
virtualListProps: {
|
||||
|
@ -426,6 +423,64 @@
|
|||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-[4px]">
|
||||
<a-checkbox v-model:model-value="saveModalForm.saveApiAsCase"></a-checkbox>
|
||||
{{ t('apiScenario.syncSaveAsCase') }}
|
||||
</div>
|
||||
<div class="flex items-center gap-[12px]">
|
||||
<a-button type="secondary" :disabled="saveLoading" @click="handleSaveApiCancel">
|
||||
{{ t('common.cancel') }}
|
||||
</a-button>
|
||||
<a-button type="primary" :loading="saveLoading" @click="handleSaveApi">{{ t('common.confirm') }}</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-modal>
|
||||
<a-modal
|
||||
v-model:visible="saveCaseModalVisible"
|
||||
:title="t('apiTestManagement.saveAsCase')"
|
||||
:ok-loading="saveCaseLoading"
|
||||
class="ms-modal-form"
|
||||
title-align="start"
|
||||
body-class="!p-0"
|
||||
@before-ok="saveAsCase"
|
||||
@cancel="handleSaveCaseCancel"
|
||||
>
|
||||
<a-form ref="saveCaseModalFormRef" :model="saveCaseModalForm" layout="vertical">
|
||||
<a-form-item
|
||||
field="name"
|
||||
:label="t('case.caseName')"
|
||||
:rules="[{ required: true, message: t('case.caseNameRequired') }]"
|
||||
asterisk-position="end"
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="saveCaseModalForm.name"
|
||||
:placeholder="t('case.caseNamePlaceholder')"
|
||||
:max-length="255"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item field="priority" :label="t('case.caseLevel')">
|
||||
<a-select v-model:model-value="saveCaseModalForm.priority" :options="casePriorityOptions"></a-select>
|
||||
</a-form-item>
|
||||
<a-form-item field="status" :label="t('common.status')">
|
||||
<a-select v-model:model-value="saveCaseModalForm.status">
|
||||
<a-option v-for="item in caseStatusOptions" :key="item.value" :value="item.value">
|
||||
{{ t(item.label) }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item field="tags" :label="t('common.tag')">
|
||||
<MsTagsInput
|
||||
v-model:model-value="saveCaseModalForm.tags"
|
||||
placeholder="common.tagsInputPlaceholder"
|
||||
allow-clear
|
||||
unique-value
|
||||
retain-input-value
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -437,6 +492,7 @@
|
|||
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
|
||||
import MsTree from '@/components/business/ms-tree/index.vue';
|
||||
import { MsTreeExpandedData, MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||
import { ImportData } from '../common/importApiDrawer/index.vue';
|
||||
|
@ -451,7 +507,12 @@
|
|||
import { RequestParam as CaseRequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||
|
||||
import { localExecuteApiDebug } from '@/api/modules/api-test/common';
|
||||
import { addDefinition } from '@/api/modules/api-test/management';
|
||||
import {
|
||||
addCase,
|
||||
addDefinition,
|
||||
getDefinitionDetail,
|
||||
getModuleTreeOnlyModules,
|
||||
} from '@/api/modules/api-test/management';
|
||||
import { debugScenario, getScenarioStep } from '@/api/modules/api-test/scenario';
|
||||
import { getSocket } from '@/api/modules/project-management/commonScript';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
@ -467,7 +528,12 @@
|
|||
TreeNode,
|
||||
} from '@/utils';
|
||||
|
||||
import { ExecuteConditionProcessor } from '@/models/apiTest/common';
|
||||
import {
|
||||
ExecuteApiRequestFullParams,
|
||||
ExecuteConditionProcessor,
|
||||
ExecutePluginRequestParams,
|
||||
} from '@/models/apiTest/common';
|
||||
import { AddApiCaseParams } from '@/models/apiTest/management';
|
||||
import {
|
||||
ApiScenarioDebugRequest,
|
||||
CreateStepAction,
|
||||
|
@ -477,9 +543,9 @@
|
|||
ScenarioStepFileParams,
|
||||
ScenarioStepItem,
|
||||
} from '@/models/apiTest/scenario';
|
||||
import { ModuleTreeNode } from '@/models/common';
|
||||
import { EnvConfig } from '@/models/projectManagement/environmental';
|
||||
import {
|
||||
RequestCaseStatus,
|
||||
RequestDefinitionStatus,
|
||||
ScenarioAddStepActionType,
|
||||
ScenarioExecuteStatus,
|
||||
|
@ -490,7 +556,7 @@
|
|||
import type { RequestParam } from '../common/customApiDrawer.vue';
|
||||
import updateStepStatus from '../utils';
|
||||
import useCreateActions from './createAction/useCreateActions';
|
||||
import { defaultResponseItem } from '@/views/api-test/components/config';
|
||||
import { casePriorityOptions, caseStatusOptions, defaultResponseItem } from '@/views/api-test/components/config';
|
||||
import { parseRequestBodyFiles } from '@/views/api-test/components/utils';
|
||||
import getStepType from '@/views/api-test/scenario/components/common/stepType/utils';
|
||||
import { defaultStepItemCommon } from '@/views/api-test/scenario/components/config';
|
||||
|
@ -534,8 +600,6 @@
|
|||
const isPriorityLocalExec = inject<Ref<boolean>>('isPriorityLocalExec');
|
||||
const localExecuteUrl = inject<Ref<string>>('localExecuteUrl');
|
||||
const currentEnvConfig = inject<Ref<EnvConfig>>('currentEnvConfig');
|
||||
const moduleTree = inject<Ref<ModuleTreeNode[]>>('moduleTree');
|
||||
const activeModule = inject<Ref<string>>('activeModule');
|
||||
|
||||
const permissionMap = {
|
||||
execute: 'PROJECT_API_SCENARIO:READ+EXECUTE',
|
||||
|
@ -666,7 +730,10 @@
|
|||
},
|
||||
];
|
||||
}
|
||||
if (_stepType.isQuoteCase) {
|
||||
if ((node as ScenarioStepItem).isQuoteScenarioStep) {
|
||||
return [];
|
||||
}
|
||||
if (_stepType.isQuoteApi || _stepType.isCopyApi) {
|
||||
return [
|
||||
{
|
||||
label: 'common.copy',
|
||||
|
@ -683,9 +750,6 @@
|
|||
},
|
||||
];
|
||||
}
|
||||
if ((node as ScenarioStepItem).isQuoteScenarioStep) {
|
||||
return [];
|
||||
}
|
||||
return stepMoreActions;
|
||||
}
|
||||
|
||||
|
@ -805,28 +869,95 @@
|
|||
}
|
||||
}
|
||||
|
||||
async function getStepDetail(step: ScenarioStepItem) {
|
||||
try {
|
||||
appStore.showLoading();
|
||||
const res = await getScenarioStep(step.copyFromStepId || step.id);
|
||||
let parseRequestBodyResult;
|
||||
if (step.config.protocol === 'HTTP' && res.body) {
|
||||
parseRequestBodyResult = parseRequestBodyFiles(res.body); // 解析请求体中的文件,将详情中的文件 id 集合收集,更新时以判断文件是否删除以及是否新上传的文件
|
||||
}
|
||||
stepDetails.value[step.id] = {
|
||||
...res,
|
||||
stepId: step.id,
|
||||
protocol: step.config.protocol,
|
||||
method: step.config.method,
|
||||
...parseRequestBodyResult,
|
||||
};
|
||||
scenario.value.stepFileParam[step.id] = parseRequestBodyResult;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
appStore.hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
const apiModuleTree = ref<MsTreeNodeData[]>([]);
|
||||
async function initApiModuleTree(protocol: string) {
|
||||
try {
|
||||
apiModuleTree.value = await getModuleTreeOnlyModules({
|
||||
keyword: '',
|
||||
protocol,
|
||||
projectId: appStore.currentProjectId,
|
||||
moduleIds: [],
|
||||
});
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
const saveNewApiModalVisible = ref(false);
|
||||
const saveModalForm = ref({
|
||||
name: '',
|
||||
path: '',
|
||||
moduleId: activeModule?.value || 'root',
|
||||
moduleId: 'root',
|
||||
saveApiAsCase: false,
|
||||
});
|
||||
const saveModalFormRef = ref<FormInstance>();
|
||||
const saveLoading = ref(false);
|
||||
|
||||
async function saveApiAsCase(id: string) {
|
||||
if (activeStep.value) {
|
||||
const detail = stepDetails.value[activeStep.value.id] as RequestParam;
|
||||
const fileParams = scenario.value.stepFileParam[activeStep.value.id];
|
||||
const url = new URL(saveModalForm.value.path);
|
||||
const path = url.pathname + url.search + url.hash;
|
||||
const params: AddApiCaseParams = {
|
||||
name: saveModalForm.value.name,
|
||||
projectId: appStore.currentProjectId,
|
||||
environmentId: currentEnvConfig?.value.id || '',
|
||||
apiDefinitionId: id,
|
||||
request: {
|
||||
...detail,
|
||||
url: path,
|
||||
},
|
||||
priority: 'P0',
|
||||
status: RequestCaseStatus.PROCESSING,
|
||||
tags: [],
|
||||
uploadFileIds: fileParams?.uploadFileIds || [],
|
||||
linkFileIds: fileParams?.linkFileIds || [],
|
||||
};
|
||||
await addCase(params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存请求
|
||||
* @param fullParams 保存时传入的参数
|
||||
* @param silence 是否静默保存(接口定义另存为用例时要先静默保存接口)
|
||||
* @param isSaveCase 是否需要保存用例
|
||||
*/
|
||||
async function realSave() {
|
||||
async function realSaveAsApi() {
|
||||
try {
|
||||
saveLoading.value = true;
|
||||
if (activeStep.value) {
|
||||
const detail = stepDetails.value[activeStep.value.id] as RequestParam;
|
||||
const fileParams = scenario.value.stepFileParam[activeStep.value.id];
|
||||
await addDefinition({
|
||||
const url = new URL(saveModalForm.value.path);
|
||||
const path = url.pathname + url.search + url.hash;
|
||||
const res = await addDefinition({
|
||||
...saveModalForm.value,
|
||||
path,
|
||||
projectId: appStore.currentProjectId,
|
||||
tags: [],
|
||||
description: '',
|
||||
|
@ -834,13 +965,20 @@
|
|||
customFields: [],
|
||||
versionId: '',
|
||||
environmentId: currentEnvConfig?.value.id || '',
|
||||
request: detail,
|
||||
request: {
|
||||
...detail,
|
||||
url: path,
|
||||
path,
|
||||
},
|
||||
uploadFileIds: fileParams?.uploadFileIds || [],
|
||||
linkFileIds: fileParams?.linkFileIds || [],
|
||||
response: [defaultResponseItem],
|
||||
method: detail?.method,
|
||||
protocol: detail?.protocol,
|
||||
});
|
||||
if (saveModalForm.value.saveApiAsCase) {
|
||||
await saveApiAsCase(res.id);
|
||||
}
|
||||
Message.success(t('common.saveSuccess'));
|
||||
saveNewApiModalVisible.value = false;
|
||||
saveLoading.value = false;
|
||||
|
@ -852,18 +990,99 @@
|
|||
}
|
||||
}
|
||||
|
||||
function handleSave(done: (closed: boolean) => void) {
|
||||
saveModalFormRef.value?.validate(async (errors) => {
|
||||
if (!errors) {
|
||||
await realSave();
|
||||
done(true);
|
||||
}
|
||||
});
|
||||
done(false);
|
||||
function handleSaveApiCancel() {
|
||||
saveModalFormRef.value?.resetFields();
|
||||
saveNewApiModalVisible.value = false;
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
saveModalFormRef.value?.resetFields();
|
||||
function handleSaveApi() {
|
||||
saveModalFormRef.value?.validate(async (errors) => {
|
||||
if (!errors) {
|
||||
await realSaveAsApi();
|
||||
handleSaveApiCancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const saveCaseModalVisible = ref(false);
|
||||
const saveCaseLoading = ref(false);
|
||||
const saveCaseModalForm = ref({
|
||||
name: '',
|
||||
priority: 'P0',
|
||||
status: RequestCaseStatus.PROCESSING,
|
||||
tags: [],
|
||||
});
|
||||
const saveCaseModalFormRef = ref<FormInstance>();
|
||||
|
||||
function handleSaveCaseCancel() {
|
||||
saveCaseModalForm.value = {
|
||||
name: '',
|
||||
priority: 'P0',
|
||||
status: RequestCaseStatus.PROCESSING,
|
||||
tags: [],
|
||||
};
|
||||
saveCaseModalVisible.value = false;
|
||||
}
|
||||
|
||||
function saveAsCase(done: (closed: boolean) => void) {
|
||||
saveCaseModalFormRef.value?.validate(async (errors) => {
|
||||
if (!errors) {
|
||||
try {
|
||||
if (activeStep.value) {
|
||||
saveCaseLoading.value = true;
|
||||
let detail = stepDetails.value[activeStep.value.id] as
|
||||
| ExecuteApiRequestFullParams
|
||||
| ExecutePluginRequestParams;
|
||||
if (!detail) {
|
||||
// 如果步骤没有查看过详情,则需要手动加载一次
|
||||
if (
|
||||
(stepDetails.value[activeStep.value.id] === undefined &&
|
||||
activeStep.value.copyFromStepId &&
|
||||
!activeStep.value.isNew) ||
|
||||
(stepDetails.value[activeStep.value.id] === undefined && !activeStep.value.isNew)
|
||||
) {
|
||||
// 详情映射中没有对应数据,初始化步骤详情(复制的步骤没有加载详情前就被复制,打开复制后的步骤就初始化被复制步骤的详情)
|
||||
await getStepDetail(activeStep.value);
|
||||
detail = stepDetails.value[activeStep.value.id] as
|
||||
| ExecuteApiRequestFullParams
|
||||
| ExecutePluginRequestParams;
|
||||
} else {
|
||||
const apiDetail = await getDefinitionDetail(activeStep.value.resourceId || '');
|
||||
detail = {
|
||||
...apiDetail.request,
|
||||
...apiDetail,
|
||||
};
|
||||
}
|
||||
}
|
||||
const fileParams = scenario.value.stepFileParam[activeStep.value.id];
|
||||
const params: AddApiCaseParams = {
|
||||
projectId: appStore.currentProjectId,
|
||||
environmentId: currentEnvConfig?.value.id || '',
|
||||
apiDefinitionId: activeStep.value.resourceId || '',
|
||||
request: detail,
|
||||
...saveCaseModalForm.value,
|
||||
uploadFileIds: fileParams?.uploadFileIds || [],
|
||||
linkFileIds: fileParams?.linkFileIds || [],
|
||||
};
|
||||
await addCase(params);
|
||||
done(true);
|
||||
Message.success(t('common.saveSuccess'));
|
||||
handleSaveCaseCancel();
|
||||
saveCaseLoading.value = false;
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
done(false);
|
||||
} finally {
|
||||
handleSaveCaseCancel();
|
||||
saveCaseLoading.value = false;
|
||||
}
|
||||
} else {
|
||||
saveCaseLoading.value = false;
|
||||
done(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleStepMoreActionSelect(item: ActionsItem, node: MsTreeNodeData) {
|
||||
|
@ -946,9 +1165,14 @@
|
|||
break;
|
||||
case 'saveAsApi':
|
||||
activeStep.value = node as ScenarioStepItem;
|
||||
initApiModuleTree((stepDetails.value[node.id] as RequestParam)?.protocol);
|
||||
saveModalForm.value.path = (stepDetails.value[node.id] as RequestParam)?.url;
|
||||
saveNewApiModalVisible.value = true;
|
||||
break;
|
||||
case 'saveAsCase':
|
||||
activeStep.value = node as ScenarioStepItem;
|
||||
saveCaseModalVisible.value = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -971,12 +1195,17 @@
|
|||
const input = treeRef.value?.$el.querySelector('.name-warp .arco-input-wrapper .arco-input') as HTMLInputElement;
|
||||
input?.focus();
|
||||
});
|
||||
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.uniqueId, 'uniqueId');
|
||||
if (realStep) {
|
||||
realStep.draggable = false; // 编辑时禁止拖拽
|
||||
}
|
||||
}
|
||||
|
||||
function applyStepNameChange(step: ScenarioStepItem) {
|
||||
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.uniqueId, 'uniqueId');
|
||||
if (realStep) {
|
||||
realStep.name = tempStepName.value;
|
||||
realStep.draggable = true; // 编辑完恢复拖拽
|
||||
}
|
||||
showStepNameEditInputStepId.value = '';
|
||||
scenario.value.unSaved = true;
|
||||
|
@ -995,12 +1224,17 @@
|
|||
const input = treeRef.value?.$el.querySelector('.desc-warp .arco-input-wrapper .arco-input') as HTMLInputElement;
|
||||
input?.focus();
|
||||
});
|
||||
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.uniqueId, 'uniqueId');
|
||||
if (realStep) {
|
||||
realStep.draggable = false; // 编辑时禁止拖拽
|
||||
}
|
||||
}
|
||||
|
||||
function applyStepDescChange(step: ScenarioStepItem) {
|
||||
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.uniqueId, 'uniqueId');
|
||||
if (realStep) {
|
||||
realStep.name = tempStepDesc.value;
|
||||
realStep.draggable = true; // 编辑完恢复拖拽
|
||||
}
|
||||
showStepDescEditInputStepId.value = '';
|
||||
scenario.value.unSaved = true;
|
||||
|
@ -1050,30 +1284,6 @@
|
|||
}
|
||||
});
|
||||
|
||||
async function getStepDetail(step: ScenarioStepItem) {
|
||||
try {
|
||||
appStore.showLoading();
|
||||
const res = await getScenarioStep(step.copyFromStepId || step.id);
|
||||
let parseRequestBodyResult;
|
||||
if (step.config.protocol === 'HTTP' && res.body) {
|
||||
parseRequestBodyResult = parseRequestBodyFiles(res.body); // 解析请求体中的文件,将详情中的文件 id 集合收集,更新时以判断文件是否删除以及是否新上传的文件
|
||||
}
|
||||
stepDetails.value[step.id] = {
|
||||
...res,
|
||||
stepId: step.id,
|
||||
protocol: step.config.protocol,
|
||||
method: step.config.method,
|
||||
...parseRequestBodyResult,
|
||||
};
|
||||
scenario.value.stepFileParam[step.id] = parseRequestBodyResult;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
appStore.hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
function handleAddStepDone(newStep: ScenarioStepItem) {
|
||||
selectedKeys.value = [newStep.uniqueId]; // 选中新添加的步骤
|
||||
emit('stepAdd');
|
||||
|
@ -1165,7 +1375,7 @@
|
|||
websocketMap[reportId]?.close();
|
||||
if (step.reportId === data.reportId) {
|
||||
step.isExecuting = false;
|
||||
updateStepStatus([step], _scenario.stepResponses);
|
||||
updateStepStatus([step], _scenario.stepResponses, step.uniqueId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1194,6 +1404,7 @@
|
|||
steps: mapTree(executeParams.steps, (node) => {
|
||||
return {
|
||||
...node,
|
||||
enable: node.uniqueId === currentStep.uniqueId || node.enable, // 单步骤执行,则临时无视顶层启用禁用状态
|
||||
parent: null, // 原树形结构存在循环引用,这里要去掉以免 axios 序列化失败
|
||||
};
|
||||
}),
|
||||
|
@ -1206,7 +1417,7 @@
|
|||
console.log(error);
|
||||
websocketMap[executeParams.reportId].close();
|
||||
currentStep.isExecuting = false;
|
||||
updateStepStatus([currentStep], scenario.value.stepResponses);
|
||||
updateStepStatus([currentStep], scenario.value.stepResponses, currentStep.uniqueId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1225,16 +1436,18 @@
|
|||
traverseTree(
|
||||
realStep,
|
||||
(step) => {
|
||||
if (step.enable) {
|
||||
// 启用的步骤才执行
|
||||
if (step.enable || step.uniqueId === realStep.uniqueId) {
|
||||
// 启用的步骤才执行;如果点击的是禁用步骤也执行,但是禁用的子步骤不执行
|
||||
_stepDetails[step.id] = stepDetails.value[step.id];
|
||||
step.executeStatus = ScenarioExecuteStatus.EXECUTING;
|
||||
} else {
|
||||
step.executeStatus = undefined;
|
||||
}
|
||||
delete scenario.value.stepResponses[step.uniqueId]; // 先移除上一次的执行结果
|
||||
},
|
||||
(step) => {
|
||||
// 当前步骤是启用的情况,才需要继续递归子孙步骤;否则无需向下递归
|
||||
return step.enable;
|
||||
// 当前步骤是启用的情或是在禁用的步骤上点击执行,才需要继续递归子孙步骤;否则无需向下递归
|
||||
return step.enable || step.uniqueId === realStep.uniqueId;
|
||||
}
|
||||
);
|
||||
realExecute(
|
||||
|
@ -1314,7 +1527,7 @@
|
|||
websocketMap[step.reportId].close();
|
||||
if (realStep) {
|
||||
realStep.isExecuting = false;
|
||||
updateStepStatus([realStep as ScenarioStepItem], scenario.value.stepResponses);
|
||||
updateStepStatus([realStep as ScenarioStepItem], scenario.value.stepResponses, realStep.uniqueId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,15 +5,18 @@ import { ScenarioExecuteStatus, ScenarioStepType } from '@/enums/apiEnum';
|
|||
/**
|
||||
* 调试或执行结束后,调用本方法更新步骤的执行状态
|
||||
* @param steps 响应式的步骤列表
|
||||
* @param stepResponses 步骤的执行结果
|
||||
* @param singleStepId 单步骤执行时的步骤ID
|
||||
*/
|
||||
export default function updateStepStatus(
|
||||
steps: ScenarioStepItem[],
|
||||
stepResponses: Record<string | number, RequestResult[]>
|
||||
stepResponses: Record<string | number, RequestResult[]>,
|
||||
singleStepId?: string | number
|
||||
) {
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
const node = steps[i];
|
||||
if (node.enable) {
|
||||
// 启用的步骤才计算
|
||||
if (node.enable || singleStepId === node.uniqueId) {
|
||||
// 启用的步骤才计算/如果是单步骤执行,无视顶层步骤的启用状态
|
||||
if (
|
||||
[
|
||||
ScenarioStepType.LOOP_CONTROLLER,
|
||||
|
@ -64,7 +67,6 @@ export default function updateStepStatus(
|
|||
}
|
||||
}
|
||||
} else if (node.stepType === ScenarioStepType.CONSTANT_TIMER) {
|
||||
// 等待时间直接设置为成功
|
||||
node.executeStatus = ScenarioExecuteStatus.SUCCESS;
|
||||
} else if (node.executeStatus === ScenarioExecuteStatus.EXECUTING) {
|
||||
// 非逻辑控制器直接更改本身状态
|
||||
|
|
|
@ -245,11 +245,11 @@
|
|||
activeScenarioTab.value.executeFakeErrorCount = 0;
|
||||
activeScenarioTab.value.stepResponses = {};
|
||||
activeScenarioTab.value.reportId = executeParams.reportId; // 存储报告ID
|
||||
activeScenarioTab.value.isDebug = !isExecute;
|
||||
debugSocket(activeScenarioTab.value, executeType, localExecuteUrl); // 开启websocket
|
||||
let res;
|
||||
if (isExecute && executeType !== 'localExec' && !activeScenarioTab.value.isNew) {
|
||||
// 执行场景且非本地执行且非未保存场景
|
||||
activeScenarioTab.value.isDebug = false;
|
||||
res = await executeScenario({
|
||||
id: activeScenarioTab.value.id,
|
||||
grouped: false,
|
||||
|
@ -267,6 +267,7 @@
|
|||
}),
|
||||
});
|
||||
} else {
|
||||
activeScenarioTab.value.isDebug = true;
|
||||
res = await debugScenario({
|
||||
id: activeScenarioTab.value.id,
|
||||
grouped: false,
|
||||
|
|
|
@ -191,6 +191,7 @@ export default {
|
|||
'apiScenario.sourceScenarioEnvTip': 'Runtime environment, including environment parameters',
|
||||
'apiScenario.setSuccess': 'Set Successful',
|
||||
'apiScenario.pleaseInputUrl': 'Please enter URL',
|
||||
'apiScenario.syncSaveAsCase': 'Synchronously add test interface case',
|
||||
// Execution History
|
||||
'apiScenario.executeHistory.searchPlaceholder': 'Search by ID or name',
|
||||
'apiScenario.executeHistory.num': 'No.',
|
||||
|
|
|
@ -180,6 +180,7 @@ export default {
|
|||
'apiScenario.sourceScenarioEnvTip': '运行环境,含环境参数',
|
||||
'apiScenario.setSuccess': '设置成功',
|
||||
'apiScenario.pleaseInputUrl': '请输入 url',
|
||||
'apiScenario.syncSaveAsCase': '同步添加测试接口用例',
|
||||
// 执行历史
|
||||
'apiScenario.executeHistory.searchPlaceholder': '通过ID或名称搜索',
|
||||
'apiScenario.executeHistory.num': '序号',
|
||||
|
|
|
@ -10,7 +10,7 @@ export default {
|
|||
'caseManagement.caseReview.creator': 'Creator',
|
||||
'caseManagement.caseReview.reviewer': 'Reviewer',
|
||||
'caseManagement.caseReview.reviewerRequired': 'Please select at least one reviewer',
|
||||
'caseManagement.caseReview.type': 'Review criteria',
|
||||
'caseManagement.caseReview.type': 'Review mode',
|
||||
'caseManagement.caseReview.status': 'Review status',
|
||||
'caseManagement.caseReview.caseCount': 'Use case number',
|
||||
'caseManagement.caseReview.passRate': 'Passing rate',
|
||||
|
|
|
@ -10,7 +10,7 @@ export default {
|
|||
'caseManagement.caseReview.creator': '创建人',
|
||||
'caseManagement.caseReview.reviewer': '评审人',
|
||||
'caseManagement.caseReview.reviewerRequired': '请至少选择一位评审人',
|
||||
'caseManagement.caseReview.type': '评审标准',
|
||||
'caseManagement.caseReview.type': '评审模式',
|
||||
'caseManagement.caseReview.status': '评审状态',
|
||||
'caseManagement.caseReview.caseCount': '用例数量',
|
||||
'caseManagement.caseReview.passRate': '通过率',
|
||||
|
|
|
@ -150,6 +150,12 @@
|
|||
if (!errors) {
|
||||
setLoading(true);
|
||||
try {
|
||||
try {
|
||||
await userStore.logout(); // 登录之前先注销,防止未登出就继续登录导致报错
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('logout error', error);
|
||||
}
|
||||
await userStore.login({
|
||||
username: encrypted(values.username),
|
||||
password: encrypted(values.password),
|
||||
|
|
|
@ -23,12 +23,7 @@
|
|||
/>
|
||||
</a-form-item>
|
||||
<a-form-item class="mb-0" field="userGroup" :label="t('system.user.createUserUserGroup')">
|
||||
<a-select
|
||||
v-model="emailForm.userGroup"
|
||||
multiple
|
||||
:placeholder="t('system.user.createUserUserGroupPlaceholder')"
|
||||
allow-clear
|
||||
>
|
||||
<a-select v-model="emailForm.userGroup" multiple :placeholder="t('system.user.createUserUserGroupPlaceholder')">
|
||||
<a-option
|
||||
v-for="item of userGroupOptions"
|
||||
:key="item.id"
|
||||
|
|
Loading…
Reference in New Issue