fix(all): 场景api、case替换&修复部分 bug
This commit is contained in:
parent
6e2dc8068d
commit
6178b24103
|
@ -13,38 +13,38 @@ export default mergeConfig(
|
|||
},
|
||||
proxy: {
|
||||
'/ws': {
|
||||
target: 'http://172.16.200.18:8081/',
|
||||
target: 'https://qadevtest.fit2cloud.com/',
|
||||
changeOrigin: true,
|
||||
rewrite: (path: string) => path.replace(/^\/front\/ws/, ''),
|
||||
ws: true,
|
||||
},
|
||||
'/front': {
|
||||
target: 'http://172.16.200.18:8081/',
|
||||
target: 'https://qadevtest.fit2cloud.com/',
|
||||
changeOrigin: true,
|
||||
rewrite: (path: string) => path.replace(/^\/front/, ''),
|
||||
},
|
||||
'/file': {
|
||||
target: 'http://172.16.200.18:8081/',
|
||||
target: 'https://qadevtest.fit2cloud.com/',
|
||||
changeOrigin: true,
|
||||
rewrite: (path: string) => path.replace(/^\/front\/file/, ''),
|
||||
},
|
||||
'/attachment': {
|
||||
target: 'http://172.16.200.18:8081/',
|
||||
target: 'https://qadevtest.fit2cloud.com/',
|
||||
changeOrigin: true,
|
||||
rewrite: (path: string) => path.replace(/^\/front\/attachment/, ''),
|
||||
},
|
||||
'/bug/attachment': {
|
||||
target: 'http://172.16.200.18:8081/',
|
||||
target: 'https://qadevtest.fit2cloud.com/',
|
||||
changeOrigin: true,
|
||||
rewrite: (path: string) => path.replace(/^\/front\/bug\/attachment/, ''),
|
||||
},
|
||||
'/plugin/image': {
|
||||
target: 'http://172.16.200.18:8081/',
|
||||
target: 'https://qadevtest.fit2cloud.com/',
|
||||
changeOrigin: true,
|
||||
rewrite: (path: string) => path.replace(/^\/front\/plugin\/image/, ''),
|
||||
},
|
||||
'/base-display': {
|
||||
target: 'http://172.16.200.18:8081/',
|
||||
target: 'https://qadevtest.fit2cloud.com/',
|
||||
changeOrigin: true,
|
||||
rewrite: (path: string) => path.replace(/^\/front\/base-display/, ''),
|
||||
},
|
||||
|
|
|
@ -745,6 +745,9 @@
|
|||
background-color: rgb(var(--primary-5)) !important;
|
||||
}
|
||||
.arco-switch-type-circle.arco-switch-disabled {
|
||||
background-color: var(--color-text-n8) !important;
|
||||
}
|
||||
.arco-switch-type-circle.arco-switch-checked.arco-switch-disabled {
|
||||
background-color: rgb(var(--primary-3)) !important;
|
||||
}
|
||||
.arco-switch-type-line.arco-switch-small {
|
||||
|
@ -774,13 +777,14 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2px 8px;
|
||||
border-radius: var(--border-radius-small);
|
||||
background: var(--color-text-n9);
|
||||
gap: 8px;
|
||||
.ms-pagination-jumper-input {
|
||||
padding: 3px 8px;
|
||||
width: 57px;
|
||||
border: 1px solid var(--color-text-input-border);
|
||||
border-radius: 3px;
|
||||
border-radius: var(--border-radius-small);
|
||||
color: var(--color-text-1);
|
||||
background: var(--color-text-10);
|
||||
box-sizing: border-box;
|
||||
|
|
|
@ -132,7 +132,7 @@
|
|||
v-model:data="getCurrentItemState"
|
||||
:disabled="props.disabled"
|
||||
@change="handleChange"
|
||||
@deleteScriptItem="deleteScriptItem"
|
||||
@delete-script-item="deleteScriptItem"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -157,6 +157,7 @@
|
|||
import ScriptTab from './comp/ScriptTab.vue';
|
||||
import StatusCodeTab from './comp/StatusCodeTab.vue';
|
||||
import VariableTab from './comp/VariableTab.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useModal from '@/hooks/useModal';
|
||||
import { characterLimit } from '@/utils';
|
||||
|
@ -556,4 +557,4 @@
|
|||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -207,11 +207,11 @@
|
|||
</template>
|
||||
</a-table>
|
||||
<div
|
||||
v-if="attrs.showFooterActionWrap"
|
||||
v-if="showBatchAction || !!attrs.showPagination"
|
||||
class="mt-[16px] flex h-[32px] flex-row flex-nowrap items-center"
|
||||
:class="{ 'justify-between': showBatchAction }"
|
||||
>
|
||||
<span v-if="!props.actionConfig && selectedCount > 0" class="title text-[var(--color-text-2)]">
|
||||
<span v-if="props.actionConfig && selectedCount > 0" class="title text-[var(--color-text-2)]">
|
||||
{{ t('msTable.batch.selected', { count: selectedCount }) }}
|
||||
<a-button class="clear-btn ml-[12px] px-2" type="text" @click="emit('clearSelector')">
|
||||
{{ t('msTable.batch.clear') }}
|
||||
|
@ -232,7 +232,8 @@
|
|||
v-if="!!attrs.showPagination"
|
||||
size="small"
|
||||
v-bind="(attrs.msPagination as MsPaginationI)"
|
||||
:simple="showBatchAction"
|
||||
:simple="!!showBatchAction"
|
||||
:show-jumper="(attrs.msPagination as MsPaginationI).total / (attrs.msPagination as MsPaginationI).pageSize > 5"
|
||||
@change="pageChange"
|
||||
@page-size-change="pageSizeChange"
|
||||
/>
|
||||
|
@ -293,7 +294,6 @@
|
|||
|
||||
const props = defineProps<{
|
||||
selectedKeys: Set<string>;
|
||||
selectedKey: string;
|
||||
excludeKeys: Set<string>;
|
||||
selectorStatus: SelectAllEnum;
|
||||
actionConfig?: BatchActionConfig;
|
||||
|
@ -316,7 +316,6 @@
|
|||
firstColumnWidth?: number; // 选择、拖拽列的宽度
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:selectedKey', value: string): void;
|
||||
(e: 'batchAction', value: BatchActionParams, queryParams: BatchActionQueryParams): void;
|
||||
(e: 'pageChange', value: number): void;
|
||||
(e: 'pageSizeChange', value: number): void;
|
||||
|
@ -413,15 +412,15 @@
|
|||
}
|
||||
};
|
||||
|
||||
const innerSelectedKey = defineModel<string>('selectedKey', { default: '' }); // 内部维护的单选选中项
|
||||
const selectedKey = defineModel<string>('selectedKey', { default: '' }); // 内部维护的单选选中项
|
||||
const tempRecord = ref<TableData>({});
|
||||
|
||||
watch(
|
||||
() => attrs.data,
|
||||
(arr) => {
|
||||
if (innerSelectedKey.value && Array.isArray(arr) && arr.length > 0) {
|
||||
if (selectedKey.value && Array.isArray(arr) && arr.length > 0) {
|
||||
arr = arr.map((item: TableData) => {
|
||||
if (item.id === innerSelectedKey.value) {
|
||||
if (item.id === selectedKey.value) {
|
||||
item.tableChecked = true;
|
||||
tempRecord.value = item;
|
||||
}
|
||||
|
@ -436,7 +435,7 @@
|
|||
|
||||
function handleRadioChange(val: boolean, record: TableData) {
|
||||
if (val) {
|
||||
innerSelectedKey.value = record.id;
|
||||
selectedKey.value = record.id;
|
||||
record.tableChecked = true;
|
||||
tempRecord.value.tableChecked = false;
|
||||
tempRecord.value = record;
|
||||
|
@ -469,7 +468,7 @@
|
|||
});
|
||||
|
||||
const showBatchAction = computed(() => {
|
||||
return selectedCount.value > 0 && !!attrs.selectable;
|
||||
return selectedCount.value > 0 && !!attrs.selectable && props.actionConfig;
|
||||
});
|
||||
|
||||
const handleBatchAction = (value: BatchActionParams) => {
|
||||
|
|
|
@ -79,7 +79,6 @@ export default function useTableProps<T>(
|
|||
emptyDataShowLine: true, // 空数据是否显示 "-"
|
||||
/** Column Selector */
|
||||
showJumpMethod: false, // 是否显示跳转方法
|
||||
showFooterActionWrap: false, // 是否显示底部操作区域
|
||||
isSimpleSetting: false, // 是否是简易column设置
|
||||
filterIconAlignLeft: true, // 筛选图标是否靠左
|
||||
...props,
|
||||
|
@ -446,22 +445,9 @@ export default function useTableProps<T>(
|
|||
});
|
||||
|
||||
watchEffect(() => {
|
||||
const { heightUsed, showPagination, selectedKeys, msPagination } = propsRes.value;
|
||||
let hasFooterAction = false;
|
||||
if (showPagination) {
|
||||
const { pageSize, total } = msPagination as Pagination;
|
||||
/*
|
||||
* 是否有底部操作栏 包括 批量操作 和 分页器
|
||||
* 1. 有分页器,且总条数大于每页条数
|
||||
* 2. 有选中项
|
||||
*/
|
||||
hasFooterAction = total > pageSize || selectedKeys.size > 0;
|
||||
}
|
||||
|
||||
propsRes.value.showFooterActionWrap = hasFooterAction;
|
||||
const { heightUsed } = propsRes.value;
|
||||
if (props?.heightUsed) {
|
||||
const currentY =
|
||||
appStore.innerHeight - (heightUsed || defaultHeightUsed) + (hasFooterAction ? 0 : footerActionWrapHeight);
|
||||
const currentY = appStore.innerHeight - (heightUsed || defaultHeightUsed);
|
||||
propsRes.value.scroll = { ...propsRes.value.scroll, y: currentY };
|
||||
}
|
||||
});
|
||||
|
|
|
@ -249,6 +249,7 @@ export enum ScenarioExecuteStatus {
|
|||
FAILED = 'FAILED',
|
||||
STOP = 'STOP',
|
||||
UN_EXECUTE = 'UN_EXECUTE',
|
||||
FAKE_ERROR = 'FAKE_ERROR',
|
||||
}
|
||||
// 场景步骤类型
|
||||
export enum ScenarioStepType {
|
||||
|
|
|
@ -148,7 +148,7 @@ export default {
|
|||
'common.value.notNull': '属性值不能为空',
|
||||
'common.nameNotNull': '名称不能为空',
|
||||
'common.namePlaceholder': '请输入名称,按回车键保存',
|
||||
'common.unsavedLeave': '有标签页的内容未保存,离开后后未保存的内容将丢失,确定要离开吗?',
|
||||
'common.unsavedLeave': '有标签页的内容未保存,离开后未保存的内容将丢失,确定要离开吗?',
|
||||
'common.image': '图片',
|
||||
'common.text': '文本',
|
||||
};
|
||||
|
|
|
@ -402,6 +402,7 @@ export interface Scenario {
|
|||
executeTime?: string | number; // 执行时间
|
||||
executeSuccessCount: number; // 执行成功数量
|
||||
executeFailCount: number; // 执行失败数量
|
||||
executeFakeErrorCount: number; // 执行误报数量
|
||||
reportId: string | number; // 场景报告 id
|
||||
stepResponses: Record<string | number, Array<RequestResult>>; // 步骤响应集合,key 为步骤 id,value 为步骤响应内容
|
||||
isExecute?: boolean; // 是否从列表执行进去场景详情
|
||||
|
|
|
@ -261,7 +261,7 @@ export function mapTree<T>(
|
|||
if (newNode) {
|
||||
newNode.level = _level;
|
||||
if (newNode[customChildrenKey] && newNode[customChildrenKey].length > 0) {
|
||||
newNode[customChildrenKey] = mapFunc(newNode[customChildrenKey], fullPath, _level + 1, node);
|
||||
newNode[customChildrenKey] = mapFunc(newNode[customChildrenKey], fullPath, _level + 1, newNode);
|
||||
}
|
||||
}
|
||||
return newNode;
|
||||
|
|
|
@ -61,9 +61,11 @@
|
|||
value: item.id,
|
||||
}));
|
||||
currentEnv.value = currentEnv.value.length ? currentEnv.value : res[0]?.id;
|
||||
if (currentEnv.value) {
|
||||
await initEnvironment();
|
||||
}
|
||||
nextTick(() => {
|
||||
if (currentEnv.value) {
|
||||
initEnvironment();
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
|
@ -82,6 +84,18 @@
|
|||
openNewPage(ProjectManagementRouteEnum.PROJECT_MANAGEMENT_ENVIRONMENT_MANAGEMENT);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => currentEnv.value,
|
||||
(val) => {
|
||||
if (!val) {
|
||||
currentEnv.value = (envOptions.value[0]?.value as string) || '';
|
||||
nextTick(() => {
|
||||
initEnvironment();
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onBeforeMount(() => {
|
||||
initEnvList();
|
||||
});
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<MsCodeEditor
|
||||
v-show="!showImg || showType === 'text'"
|
||||
ref="responseEditorRef"
|
||||
:model-value="props.requestResult?.responseResult.body"
|
||||
:model-value="props.requestResult?.responseResult.body || ''"
|
||||
:language="responseLanguage"
|
||||
theme="vs"
|
||||
:height="showImg ? 'calc(100% - 26px)' : '100%'"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<MsCodeEditor
|
||||
:model-value="props.console?.trim()"
|
||||
:model-value="props.console?.trim() || ''"
|
||||
:language="LanguageEnum.PLAINTEXT"
|
||||
theme="MS-text"
|
||||
height="100%"
|
||||
|
|
|
@ -68,7 +68,7 @@
|
|||
import MsDetailDrawer from '@/components/business/ms-detail-drawer/index.vue';
|
||||
import CaseReportCom from './caseReportCom.vue';
|
||||
|
||||
import { getShareInfo, getShareTime, reportCaseDetail } from '@/api/modules/api-test/report';
|
||||
import { getShareInfo, reportCaseDetail } from '@/api/modules/api-test/report';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useAppStore } from '@/store';
|
||||
|
||||
|
|
|
@ -22,6 +22,23 @@
|
|||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
props.step &&
|
||||
!props.step.isQuoteScenarioStep &&
|
||||
props.step.resourceId &&
|
||||
props.step?.stepType !== ScenarioStepType.CUSTOM_REQUEST
|
||||
"
|
||||
class="ml-auto"
|
||||
>
|
||||
<replaceButton
|
||||
:steps="props.steps"
|
||||
:step="props.step"
|
||||
:resource-id="props.step.resourceId"
|
||||
:scenario-id="scenarioId"
|
||||
@replace="handleReplace"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="!props.step || props.step?.stepType === ScenarioStepType.CUSTOM_REQUEST"
|
||||
class="customApiDrawer-title-right ml-auto flex items-center gap-[16px]"
|
||||
|
@ -306,6 +323,7 @@
|
|||
import MsTab from '@/components/pure/ms-tab/index.vue';
|
||||
import assertion from '@/components/business/ms-assertion/index.vue';
|
||||
import loopPagination from './loopPagination.vue';
|
||||
import replaceButton from './replaceButton.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';
|
||||
|
@ -391,6 +409,7 @@
|
|||
const props = defineProps<{
|
||||
request?: RequestParam; // 请求参数集合
|
||||
step?: ScenarioStepItem;
|
||||
steps: ScenarioStepItem[];
|
||||
detailLoading?: boolean; // 详情加载状态
|
||||
permissionMap?: {
|
||||
execute: string;
|
||||
|
@ -404,6 +423,7 @@
|
|||
(e: 'applyStep', request: RequestParam): void;
|
||||
(e: 'execute', request: RequestParam, executeType?: 'localExec' | 'serverExec'): void;
|
||||
(e: 'stopDebug'): void;
|
||||
(e: 'replace', newStep: ScenarioStepItem): void;
|
||||
}>();
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
@ -492,7 +512,11 @@
|
|||
});
|
||||
// 抽屉标题
|
||||
const title = computed(() => {
|
||||
if (_stepType.value.isCopyApi || _stepType.value.isQuoteApi) {
|
||||
if (
|
||||
_stepType.value.isCopyApi ||
|
||||
_stepType.value.isQuoteApi ||
|
||||
props.step?.stepType === ScenarioStepType.CUSTOM_REQUEST
|
||||
) {
|
||||
return props.step?.name;
|
||||
}
|
||||
return t('apiScenario.customApi');
|
||||
|
@ -1014,6 +1038,7 @@
|
|||
}
|
||||
|
||||
function stopDebug() {
|
||||
requestVModel.value.executeLoading = false;
|
||||
emit('stopDebug');
|
||||
}
|
||||
|
||||
|
@ -1097,6 +1122,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 替换步骤
|
||||
* @param newStep 替换的新步骤
|
||||
*/
|
||||
function handleReplace(newStep: ScenarioStepItem) {
|
||||
emit('replace', newStep);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => visible.value,
|
||||
async (val) => {
|
||||
|
|
|
@ -28,11 +28,17 @@
|
|||
</a-tooltip>
|
||||
<MsIcon type="icon-icon_edit_outlined" class="edit-script-name-icon" @click="showEditScriptNameInput" />
|
||||
</div>
|
||||
<div class="right-operation-button-icon flex items-center">
|
||||
<MsButton type="icon" status="secondary">
|
||||
<MsIcon type="icon-icon_swich" />
|
||||
{{ t('common.replace') }}
|
||||
</MsButton>
|
||||
<div
|
||||
v-if="activeStep && !activeStep.isQuoteScenarioStep && requestVModel.resourceId"
|
||||
class="right-operation-button-icon flex items-center"
|
||||
>
|
||||
<replaceButton
|
||||
:steps="props.steps"
|
||||
:step="activeStep"
|
||||
:resource-id="requestVModel.resourceId"
|
||||
:scenario-id="scenarioId"
|
||||
@replace="handleReplace"
|
||||
/>
|
||||
<MsButton class="mr-4" type="icon" status="secondary" @click="handleDelete">
|
||||
<MsIcon type="icon-icon_delete-trash_outlined" />
|
||||
{{ t('common.delete') }}
|
||||
|
@ -255,6 +261,7 @@
|
|||
import MsTab from '@/components/pure/ms-tab/index.vue';
|
||||
import assertion from '@/components/business/ms-assertion/index.vue';
|
||||
import loopPagination from './loopPagination.vue';
|
||||
import replaceButton from './replaceButton.vue';
|
||||
import stepType from './stepType/stepType.vue';
|
||||
import auth from '@/views/api-test/components/requestComposition/auth.vue';
|
||||
import postcondition from '@/views/api-test/components/requestComposition/postcondition.vue';
|
||||
|
@ -306,6 +313,7 @@
|
|||
|
||||
const props = defineProps<{
|
||||
request?: RequestParam; // 请求参数集合
|
||||
steps: ScenarioStepItem[];
|
||||
stepResponses?: Record<string | number, RequestResult[]>;
|
||||
fileParams?: ScenarioStepFileParams;
|
||||
permissionMap?: {
|
||||
|
@ -317,6 +325,7 @@
|
|||
(e: 'deleteStep'): void;
|
||||
(e: 'execute', request: RequestParam, executeType?: 'localExec' | 'serverExec'): void;
|
||||
(e: 'stopDebug'): void;
|
||||
(e: 'replace', newStep: ScenarioStepItem): void;
|
||||
}>();
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
@ -903,6 +912,7 @@
|
|||
}
|
||||
|
||||
function stopDebug() {
|
||||
requestVModel.value.executeLoading = false;
|
||||
emit('stopDebug');
|
||||
}
|
||||
|
||||
|
@ -956,6 +966,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 替换步骤
|
||||
* @param newStep 替换的新步骤
|
||||
*/
|
||||
function handleReplace(newStep: ScenarioStepItem) {
|
||||
emit('replace', newStep);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => visible.value,
|
||||
async (val) => {
|
||||
|
@ -968,6 +986,7 @@
|
|||
...defaultApiParams,
|
||||
...props.request,
|
||||
isNew: false,
|
||||
stepId: activeStep.value?.uniqueId || '',
|
||||
});
|
||||
if (isQuote.value || isCopyNeedInit.value) {
|
||||
// 引用时,需要初始化引用的详情;复制只在第一次初始化的时候需要加载后台数据(request.request是复制请求时列表参数字段request会为 null,以此判断释放第一次初始化)
|
||||
|
|
|
@ -38,6 +38,11 @@
|
|||
color: 'rgb(var(--danger-6))',
|
||||
text: 'common.fail',
|
||||
},
|
||||
[ScenarioExecuteStatus.FAKE_ERROR]: {
|
||||
bgColor: 'rgb(var(--warning-2))',
|
||||
color: 'rgb(var(--warning-5))',
|
||||
text: 'report.fake.error',
|
||||
},
|
||||
[ScenarioExecuteStatus.SUCCESS]: {
|
||||
bgColor: 'rgb(var(--success-2))',
|
||||
color: 'rgb(var(--success-6))',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<MsDrawer
|
||||
v-model:visible="visible"
|
||||
:title="t('apiScenario.importSystemApi')"
|
||||
:title="props.singleSelect ? t('common.replace') : t('apiScenario.importSystemApi')"
|
||||
:width="1200"
|
||||
no-content-padding
|
||||
disabled-width-drag
|
||||
|
@ -47,6 +47,9 @@
|
|||
:selected-cases="selectedCases"
|
||||
:selected-scenarios="selectedScenarios"
|
||||
:scenario-id="props.scenarioId"
|
||||
:case-id="props.caseId"
|
||||
:api-id="props.apiId"
|
||||
:single-select="props.singleSelect"
|
||||
@select="handleTableSelect"
|
||||
/>
|
||||
</div>
|
||||
|
@ -67,7 +70,12 @@
|
|||
<div class="second-text">{{ t('apiScenario.scenario') }}</div>
|
||||
<div class="main-text">{{ selectedScenarios.length }}</div>
|
||||
<a-divider v-show="totalSelected > 0" direction="vertical" :margin="4"></a-divider>
|
||||
<MsButton v-show="totalSelected > 0" type="text" class="!mr-0 ml-[4px]" @click="clearAll">
|
||||
<MsButton
|
||||
v-show="totalSelected > 0 && !props.singleSelect"
|
||||
type="text"
|
||||
class="!mr-0 ml-[4px]"
|
||||
@click="clearAll"
|
||||
>
|
||||
{{ t('common.clear') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
|
@ -115,6 +123,9 @@
|
|||
|
||||
const props = defineProps<{
|
||||
scenarioId?: string | number;
|
||||
caseId?: string | number;
|
||||
apiId?: string | number;
|
||||
singleSelect?: boolean;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'copy', data: ImportData): void;
|
||||
|
@ -138,6 +149,11 @@
|
|||
});
|
||||
|
||||
function handleTableSelect(data: MsTableDataItem<ApiCaseDetail | ApiDefinitionDetail | ApiScenarioTableItem>[]) {
|
||||
if (props.singleSelect) {
|
||||
selectedApis.value = [];
|
||||
selectedCases.value = [];
|
||||
selectedScenarios.value = [];
|
||||
}
|
||||
if (activeKey.value === 'api') {
|
||||
selectedApis.value = data as MsTableDataItem<ApiDefinitionDetail>[];
|
||||
} else if (activeKey.value === 'case') {
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
</div>
|
||||
<ms-base-table
|
||||
v-bind="currentTable.propsRes.value"
|
||||
v-model:selected-key="selectedKey"
|
||||
no-disable
|
||||
filter-icon-align-left
|
||||
v-on="currentTable.propsEvent.value"
|
||||
|
@ -87,7 +88,7 @@
|
|||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import { MsTableColumn, MsTableDataItem } from '@/components/pure/ms-table/type';
|
||||
import { MsTableDataItem } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
||||
|
@ -114,6 +115,9 @@
|
|||
selectedCases: MsTableDataItem<ApiCaseDetail>[]; // 已选中的用例
|
||||
selectedScenarios: MsTableDataItem<ApiScenarioTableItem>[]; // 已选中的场景
|
||||
scenarioId?: string | number;
|
||||
caseId?: string | number;
|
||||
apiId?: string | number;
|
||||
singleSelect?: boolean; // 是否单选
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'select', data: MsTableDataItem<ApiCaseDetail | ApiDefinitionDetail | ApiScenarioTableItem>[]): void;
|
||||
|
@ -129,6 +133,7 @@
|
|||
selectable: true,
|
||||
showSelectorAll: false,
|
||||
heightUsed: 300,
|
||||
selectorType: props.singleSelect ? 'radio' : ('checkbox' as 'checkbox' | 'radio' | 'none' | undefined),
|
||||
};
|
||||
// 接口定义表格
|
||||
const useApiTable = useTable(getDefinitionPage, {
|
||||
|
@ -357,6 +362,17 @@
|
|||
emit('select', tableSelectedData.value);
|
||||
}
|
||||
|
||||
const selectedKey = ref('');
|
||||
|
||||
watch(
|
||||
() => selectedKey.value,
|
||||
(val) => {
|
||||
const selectedData = currentTable.value.propsRes.value.data.find((e: any) => e.id === val);
|
||||
tableSelectedData.value = selectedData ? [selectedData] : [];
|
||||
emit('select', tableSelectedData.value);
|
||||
}
|
||||
);
|
||||
|
||||
function clearSelector() {
|
||||
tableSelectedData.value = [];
|
||||
currentTable.value.clearSelector();
|
||||
|
@ -399,7 +415,7 @@
|
|||
status: statusFilters.value,
|
||||
method: methodFilters.value,
|
||||
},
|
||||
excludeIds: [props.scenarioId || ''],
|
||||
excludeIds: [props.scenarioId || '', props.caseId || '', props.apiId || ''],
|
||||
});
|
||||
currentTable.value.loadList();
|
||||
});
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
<template>
|
||||
<MsButton type="icon" status="secondary" @click="importApiDrawerVisible = true">
|
||||
<MsIcon type="icon-icon_swich" class="mr-[8px]" />
|
||||
{{ t('common.replace') }}
|
||||
</MsButton>
|
||||
<importApiDrawer
|
||||
v-if="importApiDrawerVisible"
|
||||
v-model:visible="importApiDrawerVisible"
|
||||
:scenario-id="props.scenarioId"
|
||||
:case-id="props.resourceId"
|
||||
:api-id="props.resourceId"
|
||||
single-select
|
||||
@copy="handleImportApiApply('copy', $event)"
|
||||
@quote="handleImportApiApply('quote', $event)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import { ImportData } from './importApiDrawer/index.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
import { ScenarioStepItem } from '@/models/apiTest/scenario';
|
||||
import { ScenarioStepRefType, ScenarioStepType } from '@/enums/apiEnum';
|
||||
|
||||
import useCreateActions from '../step/createAction/useCreateActions';
|
||||
|
||||
const importApiDrawer = defineAsyncComponent(() => import('./importApiDrawer/index.vue'));
|
||||
|
||||
const props = defineProps<{
|
||||
steps: ScenarioStepItem[];
|
||||
step: ScenarioStepItem;
|
||||
resourceId: string | number;
|
||||
scenarioId?: string | number;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'replace', replaceItem: ScenarioStepItem): void;
|
||||
}>();
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
const { buildInsertStepInfos } = useCreateActions();
|
||||
|
||||
const importApiDrawerVisible = ref(false);
|
||||
|
||||
/**
|
||||
* 处理导入系统请求
|
||||
* @param type 导入类型
|
||||
* @param data 导入数据
|
||||
*/
|
||||
function handleImportApiApply(type: 'copy' | 'quote', data: ImportData) {
|
||||
const refType = type === 'copy' ? ScenarioStepRefType.COPY : ScenarioStepRefType.REF;
|
||||
let replaceItem: ScenarioStepItem[];
|
||||
if (data.api.length > 0) {
|
||||
replaceItem = buildInsertStepInfos(
|
||||
data.api,
|
||||
ScenarioStepType.API,
|
||||
refType,
|
||||
props.step.sort,
|
||||
props.step.projectId || appStore.currentProjectId
|
||||
);
|
||||
} else if (data.case.length > 0) {
|
||||
replaceItem = buildInsertStepInfos(
|
||||
data.case,
|
||||
ScenarioStepType.API_CASE,
|
||||
refType,
|
||||
props.step.sort,
|
||||
props.step.projectId || appStore.currentProjectId
|
||||
);
|
||||
} else {
|
||||
replaceItem = buildInsertStepInfos(
|
||||
data.scenario,
|
||||
ScenarioStepType.API_SCENARIO,
|
||||
refType,
|
||||
props.step.sort,
|
||||
props.step.projectId || appStore.currentProjectId
|
||||
);
|
||||
}
|
||||
emit('replace', replaceItem[0]);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -1,10 +1,22 @@
|
|||
<template>
|
||||
<a-popover
|
||||
v-if="
|
||||
[
|
||||
ScenarioStepType.API,
|
||||
ScenarioStepType.API_CASE,
|
||||
ScenarioStepType.SCRIPT,
|
||||
ScenarioStepType.CUSTOM_REQUEST,
|
||||
].includes(step.stepType) &&
|
||||
props.finalExecuteStatus &&
|
||||
[ScenarioExecuteStatus.SUCCESS, ScenarioExecuteStatus.FAILED, ScenarioExecuteStatus.FAKE_ERROR].includes(
|
||||
props.finalExecuteStatus
|
||||
)
|
||||
"
|
||||
position="lt"
|
||||
content-class="scenario-step-response-popover"
|
||||
@popup-visible-change="emit('visibleChange', $event, props.step)"
|
||||
>
|
||||
<executeStatus :status="finalExecuteStatus" size="small" class="ml-[4px]" />
|
||||
<executeStatus :status="props.finalExecuteStatus" size="small" class="ml-[4px]" />
|
||||
<template #content>
|
||||
<div class="flex h-full flex-col">
|
||||
<loopPagination v-model:current-loop="currentLoop" :loop-total="loopTotal" />
|
||||
|
@ -40,6 +52,13 @@
|
|||
</div>
|
||||
</template>
|
||||
</a-popover>
|
||||
<executeStatus
|
||||
v-else-if="step.executeStatus"
|
||||
:status="props.finalExecuteStatus"
|
||||
:extra-text="getExecuteStatusExtraText(step)"
|
||||
size="small"
|
||||
class="ml-[4px]"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
@ -50,7 +69,12 @@
|
|||
|
||||
import { RequestResult } from '@/models/apiTest/common';
|
||||
import { ScenarioStepItem } from '@/models/apiTest/scenario';
|
||||
import { ResponseComposition, ScenarioExecuteStatus, ScenarioStepType } from '@/enums/apiEnum';
|
||||
import {
|
||||
ResponseComposition,
|
||||
ScenarioExecuteStatus,
|
||||
ScenarioStepLoopTypeEnum,
|
||||
ScenarioStepType,
|
||||
} from '@/enums/apiEnum';
|
||||
|
||||
const responseResult = defineAsyncComponent(
|
||||
() => import('@/views/api-test/components/requestComposition/response/index.vue')
|
||||
|
@ -59,6 +83,7 @@
|
|||
const props = defineProps<{
|
||||
step: ScenarioStepItem;
|
||||
stepResponses: Record<string | number, Array<RequestResult>>;
|
||||
finalExecuteStatus?: ScenarioExecuteStatus;
|
||||
}>();
|
||||
const emit = defineEmits(['visibleChange']);
|
||||
|
||||
|
@ -67,15 +92,27 @@
|
|||
const currentLoop = ref(1);
|
||||
const currentResponse = computed(() => props.stepResponses?.[props.step.uniqueId]?.[currentLoop.value - 1]);
|
||||
const loopTotal = computed(() => props.stepResponses?.[props.step.uniqueId]?.length || 0);
|
||||
const finalExecuteStatus = computed(() => {
|
||||
if (props.stepResponses[props.step.uniqueId] && props.stepResponses[props.step.uniqueId].length > 0) {
|
||||
// 有一次失败就是失败
|
||||
return props.stepResponses[props.step.uniqueId].some((report) => !report.isSuccessful)
|
||||
? ScenarioExecuteStatus.FAILED
|
||||
: ScenarioExecuteStatus.SUCCESS;
|
||||
|
||||
function getExecuteStatusExtraText(step: ScenarioStepItem) {
|
||||
if (
|
||||
step.stepType === ScenarioStepType.LOOP_CONTROLLER &&
|
||||
step.config.loopType === ScenarioStepLoopTypeEnum.LOOP_COUNT &&
|
||||
step.config.msCountController &&
|
||||
step.config.msCountController.loops > 0
|
||||
) {
|
||||
// 循环控制器展示当前执行次数/总次数
|
||||
const firstHasResultChild = step.children?.find((child) => {
|
||||
return (
|
||||
[ScenarioStepType.API, ScenarioStepType.API_CASE, ScenarioStepType.CUSTOM_REQUEST].includes(step.stepType) ||
|
||||
child.stepType === ScenarioStepType.SCRIPT
|
||||
);
|
||||
});
|
||||
return firstHasResultChild && props.stepResponses[firstHasResultChild.uniqueId]
|
||||
? `${props.stepResponses[firstHasResultChild.uniqueId].length}/${step.config.msCountController.loops}`
|
||||
: undefined;
|
||||
}
|
||||
return props.step.executeStatus;
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
|
|
|
@ -37,10 +37,10 @@
|
|||
<a-button type="secondary" @click="handleDrawerCancel">
|
||||
{{ t('common.cancel') }}
|
||||
</a-button>
|
||||
<a-button type="secondary" @click="saveAndContinue">
|
||||
<a-button type="secondary" :disabled="!scriptName" @click="saveAndContinue">
|
||||
{{ t('common.saveAndContinue') }}
|
||||
</a-button>
|
||||
<a-button type="primary" @click="save">
|
||||
<a-button type="primary" :disabled="!scriptName" @click="save">
|
||||
{{ t('common.add') }}
|
||||
</a-button>
|
||||
</template>
|
||||
|
|
|
@ -21,7 +21,7 @@ export const defaultLoopController = {
|
|||
},
|
||||
whileController: {
|
||||
conditionType: WhileConditionType.CONDITION, // 条件类型
|
||||
timeout: 0, // 超时时间
|
||||
timeout: 3000, // 超时时间
|
||||
msWhileScript: {
|
||||
scriptValue: '', // 脚本值
|
||||
}, // 脚本
|
||||
|
@ -118,6 +118,7 @@ export const defaultScenario: Scenario = {
|
|||
executeTime: 0,
|
||||
executeSuccessCount: 0,
|
||||
executeFailCount: 0,
|
||||
executeFakeErrorCount: 0,
|
||||
uploadFileIds: [],
|
||||
linkFileIds: [],
|
||||
reportId: '',
|
||||
|
|
|
@ -133,8 +133,8 @@ export default function useCreateActions() {
|
|||
}
|
||||
return {
|
||||
...cloneDeep(defaultStepItemCommon),
|
||||
id,
|
||||
uniqueId: getGenerateId(), // 生成唯一 ID,避免重复引用的步骤无法读取正确的执行结果
|
||||
id: item.uniqueId || id,
|
||||
uniqueId: item.uniqueId || id, // 生成唯一 ID,避免重复引用的步骤无法读取正确的执行结果
|
||||
config: {
|
||||
...defaultStepItemCommon.config,
|
||||
...config,
|
||||
|
|
|
@ -58,7 +58,11 @@
|
|||
</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))]">{{ scenario.executeFailCount }}</div>
|
||||
<div class="text-[rgb(var(--danger-6))]">{{ scenario.executeFailCount }}</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-[4px]">
|
||||
<div class="text-[var(--color-text-1)]">{{ t('report.fake.error') }}</div>
|
||||
<div class="text-[rgb(var(--warning-5))]">{{ scenario.executeFakeErrorCount }}</div>
|
||||
</div>
|
||||
<MsButton
|
||||
v-if="scenario.isDebug === false && !scenario.executeLoading && !scenario.isNew"
|
||||
|
|
|
@ -133,7 +133,7 @@
|
|||
>
|
||||
<a-input-number
|
||||
v-model:model-value="innerData.whileController.timeout"
|
||||
class="w-[100px] px-[8px]"
|
||||
class="w-[120px] px-[8px]"
|
||||
size="mini"
|
||||
:step="1"
|
||||
:min="0"
|
||||
|
|
|
@ -152,7 +152,7 @@
|
|||
<div
|
||||
:class="`one-line-text mr-[4px] ${
|
||||
step.stepType === ScenarioStepType.ONCE_ONLY_CONTROLLER ? 'max-w-[750px]' : 'max-w-[150px]'
|
||||
} font-normal text-[var(--color-text-4)]`"
|
||||
} font-normal text-[var(--color-text-1)]`"
|
||||
>
|
||||
{{ step.name || t('apiScenario.pleaseInputStepDesc') }}
|
||||
</div>
|
||||
|
@ -190,27 +190,11 @@
|
|||
</template>
|
||||
<template #extraEnd="step">
|
||||
<responsePopover
|
||||
v-if="
|
||||
[
|
||||
ScenarioStepType.API,
|
||||
ScenarioStepType.API_CASE,
|
||||
ScenarioStepType.SCRIPT,
|
||||
ScenarioStepType.CUSTOM_REQUEST,
|
||||
].includes(step.stepType) &&
|
||||
(getExecuteStatus(step) === ScenarioExecuteStatus.SUCCESS ||
|
||||
getExecuteStatus(step) === ScenarioExecuteStatus.FAILED)
|
||||
"
|
||||
:step="step"
|
||||
:step-responses="scenario.stepResponses"
|
||||
:final-execute-status="getExecuteStatus(step)"
|
||||
@visible-change="handleResponsePopoverVisibleChange"
|
||||
/>
|
||||
<executeStatus
|
||||
v-else-if="step.executeStatus"
|
||||
:status="getExecuteStatus(step)"
|
||||
:extra-text="getExecuteStatusExtraText(step)"
|
||||
size="small"
|
||||
class="ml-[4px]"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="steps.length === 0 && stepKeyword.trim() !== ''" #empty>
|
||||
<div
|
||||
|
@ -240,24 +224,30 @@
|
|||
:request="currentStepDetail as unknown as RequestParam"
|
||||
:file-params="currentStepFileParams"
|
||||
:step="activeStep"
|
||||
:scenario-id="scenario.id"
|
||||
:step-responses="scenario.stepResponses"
|
||||
:permission-map="permissionMap"
|
||||
:steps="steps"
|
||||
@add-step="addCustomApiStep"
|
||||
@apply-step="applyApiStep"
|
||||
@stop-debug="handleStopExecute(activeStep)"
|
||||
@execute="handleApiExecute"
|
||||
@replace="handleReplaceStep"
|
||||
/>
|
||||
<customCaseDrawer
|
||||
v-model:visible="customCaseDrawerVisible"
|
||||
:active-step="activeStep"
|
||||
:request="currentStepDetail as unknown as RequestParam"
|
||||
:scenario-id="scenario.id"
|
||||
:file-params="currentStepFileParams"
|
||||
:steps="steps"
|
||||
:step-responses="scenario.stepResponses"
|
||||
:permission-map="permissionMap"
|
||||
@apply-step="applyApiStep"
|
||||
@delete-step="deleteCaseStep(activeStep)"
|
||||
@stop-debug="handleStopExecute(activeStep)"
|
||||
@execute="(request, executeType) => handleApiExecute((request as unknown as RequestParam), executeType)"
|
||||
@replace="handleReplaceStep"
|
||||
/>
|
||||
<importApiDrawer
|
||||
v-if="importApiDrawerVisible"
|
||||
|
@ -451,7 +441,6 @@
|
|||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||
import MsTree from '@/components/business/ms-tree/index.vue';
|
||||
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/stepType.vue';
|
||||
import createStepActions from './createAction/createStepActions.vue';
|
||||
|
@ -496,7 +485,6 @@
|
|||
RequestDefinitionStatus,
|
||||
ScenarioAddStepActionType,
|
||||
ScenarioExecuteStatus,
|
||||
ScenarioStepLoopTypeEnum,
|
||||
ScenarioStepRefType,
|
||||
ScenarioStepType,
|
||||
} from '@/enums/apiEnum';
|
||||
|
@ -569,32 +557,20 @@
|
|||
|
||||
function getExecuteStatus(step: ScenarioStepItem) {
|
||||
if (scenario.value.stepResponses && scenario.value.stepResponses[step.uniqueId]) {
|
||||
if (
|
||||
scenario.value.stepResponses[step.uniqueId].some((report) => report.status === ScenarioExecuteStatus.FAKE_ERROR)
|
||||
) {
|
||||
return ScenarioExecuteStatus.FAKE_ERROR;
|
||||
}
|
||||
// 有一次失败就是失败
|
||||
return scenario.value.stepResponses[step.uniqueId].some((report) => !report.isSuccessful)
|
||||
? ScenarioExecuteStatus.FAILED
|
||||
: ScenarioExecuteStatus.SUCCESS;
|
||||
if (scenario.value.stepResponses[step.uniqueId].some((report) => !report.isSuccessful)) {
|
||||
return ScenarioExecuteStatus.FAILED;
|
||||
}
|
||||
return ScenarioExecuteStatus.SUCCESS;
|
||||
}
|
||||
return step.executeStatus;
|
||||
}
|
||||
|
||||
function getExecuteStatusExtraText(step: ScenarioStepItem) {
|
||||
if (
|
||||
step.stepType === ScenarioStepType.LOOP_CONTROLLER &&
|
||||
step.config.loopType === ScenarioStepLoopTypeEnum.LOOP_COUNT &&
|
||||
step.config.msCountController &&
|
||||
step.config.msCountController.loops > 0
|
||||
) {
|
||||
// 循环控制器展示当前执行次数/总次数
|
||||
const firstHasResultChild = step.children?.find((child) => {
|
||||
return checkStepIsApi(child) || child.stepType === ScenarioStepType.SCRIPT;
|
||||
});
|
||||
return firstHasResultChild && scenario.value.stepResponses[firstHasResultChild.uniqueId]
|
||||
? `${scenario.value.stepResponses[firstHasResultChild.uniqueId].length}/${step.config.msCountController.loops}`
|
||||
: undefined;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function handleResponsePopoverVisibleChange(visible: boolean, step: ScenarioStepItem) {
|
||||
if (visible) {
|
||||
setFocusNodeKey(step.uniqueId);
|
||||
|
@ -914,8 +890,8 @@
|
|||
...cloneDeep(
|
||||
mapTree<ScenarioStepItem>(node, (childNode) => {
|
||||
const childId = getGenerateId();
|
||||
const childStepDetail = stepDetails.value[node.id];
|
||||
const childStepFileParam = scenario.value.stepFileParam[node.id];
|
||||
const childStepDetail = stepDetails.value[childNode.id];
|
||||
const childStepFileParam = scenario.value.stepFileParam[childNode.id];
|
||||
let childCopyFromStepId = childNode.id;
|
||||
if (childStepDetail) {
|
||||
// 如果复制的步骤下子步骤还有详情数据,则也复制详情数据
|
||||
|
@ -923,7 +899,7 @@
|
|||
}
|
||||
if (childStepFileParam) {
|
||||
// 如果复制的步骤下子步骤还有详情数据,则也复制详情数据
|
||||
scenario.value.stepFileParam[id] = cloneDeep(childStepFileParam);
|
||||
scenario.value.stepFileParam[childNode.id] = cloneDeep(childStepFileParam);
|
||||
}
|
||||
if (!isQuoteScenario) {
|
||||
// 非引用场景才处理复制来源 id
|
||||
|
@ -1211,7 +1187,7 @@
|
|||
const res = await debugScenario({
|
||||
id: scenario.value.id || '',
|
||||
grouped: false,
|
||||
environmentId: currentEnvConfig?.value.id || '',
|
||||
environmentId: currentEnvConfig?.value?.id || '',
|
||||
projectId: appStore.currentProjectId,
|
||||
scenarioConfig: scenario.value.scenarioConfig,
|
||||
frontendDebug: executeType === 'localExec',
|
||||
|
@ -1302,6 +1278,7 @@
|
|||
} else {
|
||||
// 步骤列表找不到该步骤,说明是新建的自定义请求还未保存,则临时创建一个步骤进行调试(不保存步骤信息)
|
||||
const reportId = getGenerateId();
|
||||
delete scenario.value.stepResponses[request.stepId]; // 先移除上一次的执行结果
|
||||
request.executeLoading = true;
|
||||
activeStep.value = {
|
||||
id: request.stepId,
|
||||
|
@ -1343,6 +1320,41 @@
|
|||
}
|
||||
}
|
||||
|
||||
function handleReplaceStep(newStep: ScenarioStepItem) {
|
||||
if (activeStep.value) {
|
||||
// 替换步骤,删除原本的详情数据
|
||||
delete scenario.value.stepResponses[activeStep.value.uniqueId];
|
||||
delete scenario.value.stepFileParam[activeStep.value.id];
|
||||
delete stepDetails.value[activeStep.value.id];
|
||||
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, activeStep.value.uniqueId, 'uniqueId');
|
||||
if (realStep) {
|
||||
// 将旧步骤替换为新步骤
|
||||
if (realStep.parent?.children) {
|
||||
// 如果被替换的步骤是子孙步骤,则需要在父级的 children 中替换
|
||||
const index = realStep.parent.children.findIndex((item) => item.uniqueId === realStep.uniqueId);
|
||||
realStep.parent.children.splice(index, 1, newStep);
|
||||
} else {
|
||||
// 如果被替换的步骤是第一层级步骤,则直接替换
|
||||
const index = steps.value.findIndex((item) => item.uniqueId === realStep.uniqueId);
|
||||
steps.value.splice(index, 1, newStep);
|
||||
}
|
||||
}
|
||||
}
|
||||
Message.success(t('apiScenario.replaceSuccess'));
|
||||
scenario.value.unSaved = true;
|
||||
if (newStep.stepType === ScenarioStepType.API_SCENARIO) {
|
||||
customCaseDrawerVisible.value = false;
|
||||
customApiDrawerVisible.value = false;
|
||||
} else {
|
||||
customCaseDrawerVisible.value = false;
|
||||
customApiDrawerVisible.value = false;
|
||||
nextTick(() => {
|
||||
// 等待抽屉关闭后再打开新的抽屉
|
||||
handleStepSelect([newStep.uniqueId], newStep);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理抽屉资源类型步骤创建动作
|
||||
*/
|
||||
|
@ -1609,7 +1621,7 @@
|
|||
}
|
||||
loading.value = true;
|
||||
const offspringIds: string[] = [];
|
||||
mapTree(dragNode.children || [], (e) => {
|
||||
mapTree(cloneDeep(dragNode.children || []), (e) => {
|
||||
offspringIds.push(e.uniqueId);
|
||||
return e;
|
||||
});
|
||||
|
|
|
@ -27,6 +27,7 @@ export default function updateStepStatus(
|
|||
// 逻辑控制器和场景内部可以放入任意步骤,所以它的最终执行结果是根据内部步骤的执行结果来判断的
|
||||
let hasNotExecuted = false;
|
||||
let hasFailure = false;
|
||||
let hasFakeError = false;
|
||||
if (!node.children || node.children.length === 0) {
|
||||
// 逻辑控制器内无步骤,则直接是未执行
|
||||
node.executeStatus = ScenarioExecuteStatus.UN_EXECUTE;
|
||||
|
@ -43,13 +44,18 @@ export default function updateStepStatus(
|
|||
} else if (childNode.executeStatus === ScenarioExecuteStatus.FAILED) {
|
||||
// 子节点有一个失败,逻辑控制器就是失败
|
||||
hasFailure = true;
|
||||
} else if (childNode.executeStatus === ScenarioExecuteStatus.FAKE_ERROR) {
|
||||
// 子节点有一个误报,逻辑控制器就是误报
|
||||
hasFakeError = true;
|
||||
}
|
||||
}
|
||||
// 递归完子节点后,判断当前逻辑控制器的状态
|
||||
if (hasFailure) {
|
||||
node.executeStatus = ScenarioExecuteStatus.FAILED;
|
||||
} else if (hasNotExecuted) {
|
||||
if (hasNotExecuted) {
|
||||
node.executeStatus = ScenarioExecuteStatus.UN_EXECUTE;
|
||||
} else if (hasFailure) {
|
||||
node.executeStatus = ScenarioExecuteStatus.FAILED;
|
||||
} else if (hasFakeError) {
|
||||
node.executeStatus = ScenarioExecuteStatus.FAKE_ERROR;
|
||||
} else {
|
||||
node.executeStatus = ScenarioExecuteStatus.SUCCESS;
|
||||
}
|
||||
|
@ -60,9 +66,13 @@ export default function updateStepStatus(
|
|||
} else if (node.executeStatus === ScenarioExecuteStatus.EXECUTING) {
|
||||
// 非逻辑控制器直接更改本身状态
|
||||
if (stepResponses[node.uniqueId] && stepResponses[node.uniqueId].length > 0) {
|
||||
node.executeStatus = stepResponses[node.uniqueId].some((report) => !report.isSuccessful)
|
||||
? ScenarioExecuteStatus.FAILED
|
||||
: ScenarioExecuteStatus.SUCCESS;
|
||||
if (stepResponses[node.uniqueId].some((report) => !report.isSuccessful)) {
|
||||
node.executeStatus = ScenarioExecuteStatus.FAILED;
|
||||
} else if (stepResponses[node.uniqueId].some((report) => report.status === ScenarioExecuteStatus.FAKE_ERROR)) {
|
||||
node.executeStatus = ScenarioExecuteStatus.FAKE_ERROR;
|
||||
} else {
|
||||
node.executeStatus = ScenarioExecuteStatus.SUCCESS;
|
||||
}
|
||||
} else {
|
||||
node.executeStatus = ScenarioExecuteStatus.UN_EXECUTE;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,10 @@
|
|||
</template>
|
||||
</MsEditableTab>
|
||||
<div v-show="activeScenarioTab.id !== 'all'" class="flex items-center gap-[8px]">
|
||||
<environmentSelect v-model:current-env-config="currentEnvConfig" />
|
||||
<environmentSelect
|
||||
v-model:currentEnv="activeScenarioTab.environmentId"
|
||||
v-model:current-env-config="currentEnvConfig"
|
||||
/>
|
||||
<executeButton
|
||||
ref="executeButtonRef"
|
||||
v-permission="['PROJECT_API_SCENARIO:READ+EXECUTE']"
|
||||
|
@ -161,6 +164,7 @@
|
|||
id: 'all',
|
||||
label: t('apiScenario.allScenario'),
|
||||
closable: false,
|
||||
environmentId: '',
|
||||
} as ScenarioParams,
|
||||
]);
|
||||
const activeScenarioTab = ref<ScenarioParams>(scenarioTabs.value[0] as ScenarioParams);
|
||||
|
@ -195,7 +199,9 @@
|
|||
...result,
|
||||
console: data.taskResult.console,
|
||||
});
|
||||
if (result.isSuccessful) {
|
||||
if (result.status === ScenarioExecuteStatus.FAKE_ERROR) {
|
||||
scenario.executeFakeErrorCount += 1;
|
||||
} else if (result.isSuccessful) {
|
||||
scenario.executeSuccessCount += 1;
|
||||
} else {
|
||||
scenario.executeFailCount += 1;
|
||||
|
@ -233,6 +239,7 @@
|
|||
activeScenarioTab.value.executeTime = dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||
activeScenarioTab.value.executeSuccessCount = 0;
|
||||
activeScenarioTab.value.executeFailCount = 0;
|
||||
activeScenarioTab.value.executeFakeErrorCount = 0;
|
||||
activeScenarioTab.value.stepResponses = {};
|
||||
activeScenarioTab.value.reportId = executeParams.reportId; // 存储报告ID
|
||||
activeScenarioTab.value.isDebug = !isExecute;
|
||||
|
@ -403,6 +410,7 @@
|
|||
scenarioTabs.value.push({
|
||||
...cloneDeep(defaultScenario),
|
||||
id: getGenerateId(),
|
||||
environmentId: currentEnvConfig.value?.id || '',
|
||||
label: `${t('apiScenario.createScenario')}${scenarioTabs.value.length}`,
|
||||
moduleId: activeModule.value === 'all' ? 'root' : activeModule.value,
|
||||
projectId: appStore.currentProjectId,
|
||||
|
|
|
@ -70,6 +70,7 @@ export default {
|
|||
'apiScenario.executionResult': '执行结果',
|
||||
'apiScenario.refreshRefScenario': '刷新引用场景数据',
|
||||
'apiScenario.updateRefScenarioSuccess': '引用场景数据已更新',
|
||||
'apiScenario.replaceSuccess': '步骤替换成功',
|
||||
// 批量操作文案
|
||||
'api_scenario.batch_operation.success': '成功{opt}至 {name}',
|
||||
'api_scenario.table.batchMoveConfirm': '{opt}{count}个场景至已选模块',
|
||||
|
|
Loading…
Reference in New Issue