feat(接口测试): curl导入解析

This commit is contained in:
baiqi 2024-09-02 17:40:31 +08:00 committed by Craftsman
parent 6020b0781c
commit 2ad6cc78d0
6 changed files with 464 additions and 366 deletions

View File

@ -5,12 +5,14 @@ import {
GetPluginOptionsUrl, GetPluginOptionsUrl,
GetPluginScriptUrl, GetPluginScriptUrl,
GetProtocolListUrl, GetProtocolListUrl,
ImportCurlUrl,
LocalExecuteApiDebugUrl, LocalExecuteApiDebugUrl,
StopExecuteUrl, StopExecuteUrl,
StopLocalExecuteUrl, StopLocalExecuteUrl,
} from '@/api/requrls/api-test/common'; } from '@/api/requrls/api-test/common';
import { import {
type CurlParseResult,
ExecuteRequestParams, ExecuteRequestParams,
GetPluginOptionsParams, GetPluginOptionsParams,
PluginConfig, PluginConfig,
@ -61,3 +63,8 @@ export function stopLocalExecute(host: string, id: string | number, type?: Scena
export function stopExecute(id: string | number, type?: ScenarioStepType) { export function stopExecute(id: string | number, type?: ScenarioStepType) {
return MSR.get({ url: type ? `${StopExecuteUrl}/${type}/${id}` : `${StopExecuteUrl}/${id}` }); return MSR.get({ url: type ? `${StopExecuteUrl}/${type}/${id}` : `${StopExecuteUrl}/${id}` });
} }
// 导入curl
export function importByCurl(curl: string) {
return MSR.post<CurlParseResult>({ url: ImportCurlUrl, data: { curl } });
}

View File

@ -6,3 +6,4 @@ export const GetEnvListUrl = '/api/test/env-list'; // 获取接口测试环境
export const GetEnvironmentUrl = '/api/test/environment'; // 获取接口测试环境详情 export const GetEnvironmentUrl = '/api/test/environment'; // 获取接口测试环境详情
export const StopExecuteUrl = '/task/center/api/project/stop'; // 停止执行 export const StopExecuteUrl = '/task/center/api/project/stop'; // 停止执行
export const StopLocalExecuteUrl = '/api/stop'; // 停止本地执行 export const StopLocalExecuteUrl = '/api/stop'; // 停止本地执行
export const ImportCurlUrl = '/api/debug/import-curl'; // 导入curl

View File

@ -463,3 +463,11 @@ export type ApiCaseReportDetail = {
scriptIdentifier: string; scriptIdentifier: string;
content: RequestResult; content: RequestResult;
}; };
// curl解析结果
export interface CurlParseResult {
method: RequestMethods | string;
url: string;
headers: Record<string, any>;
body: string;
queryParams: Record<string, any>;
}

View File

@ -0,0 +1,82 @@
<template>
<a-modal
v-model:visible="visible"
title-align="start"
:width="680"
:ok-disabled="curlCode.trim() === ''"
:ok-text="t('common.import')"
:ok-button-props="{
disabled: curlCode.trim() === '',
}"
class="ms-modal-form"
@cancel="curlCode = ''"
@before-ok="handleCurlImportConfirm"
>
<template #title>
<a-tooltip position="right" :content="t('apiTestDebug.importByCURLTip')">
<span
>{{ t('apiTestDebug.importByCURL') }}
<icon-question-circle
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
size="16"
/>
</span>
</a-tooltip>
</template>
<div class="h-full">
<MsCodeEditor
v-if="visible"
v-model:model-value="curlCode"
theme="MS-text"
height="500px"
:language="LanguageEnum.PLAINTEXT"
:show-theme-change="false"
:show-full-screen="false"
>
</MsCodeEditor>
</div>
</a-modal>
</template>
<script setup lang="ts">
import { Message } from '@arco-design/web-vue';
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
import { LanguageEnum } from '@/components/pure/ms-code-editor/types';
import { importByCurl } from '@/api/modules/api-test/common';
import { useI18n } from '@/hooks/useI18n';
import type { CurlParseResult } from '@/models/apiTest/common';
const emit = defineEmits<{
(e: 'done', res: CurlParseResult): void;
}>();
const { t } = useI18n();
const visible = defineModel('visible', {
type: Boolean,
required: true,
});
const importLoading = ref(false);
const curlCode = ref('');
async function handleCurlImportConfirm(done: (closed: boolean) => void) {
try {
importLoading.value = true;
const res = await importByCurl(curlCode.value);
emit('done', res);
done(true);
Message.success(t('common.importSuccess'));
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
importLoading.value = false;
}
}
</script>
<style lang="less" scoped></style>

View File

@ -9,7 +9,7 @@
@init="(val) => (folderTree = val)" @init="(val) => (folderTree = val)"
@new-api="addDebugTab" @new-api="addDebugTab"
@click-api-node="openApiTab" @click-api-node="openApiTab"
@import="importDrawerVisible = true" @import="importDialogVisible = true"
@update-api-node="handleApiUpdateFromModuleTree" @update-api-node="handleApiUpdateFromModuleTree"
@delete-finish="handleDeleteFinish" @delete-finish="handleDeleteFinish"
/> />
@ -63,38 +63,7 @@
</template> </template>
</MsSplitBox> </MsSplitBox>
</MsCard> </MsCard>
<MsDrawer <importCurlDialog v-model:visible="importDialogVisible" @done="handleImportCurlDone" />
v-model:visible="importDrawerVisible"
:width="680"
:ok-disabled="curlCode.trim() === ''"
disabled-width-drag
@cancel="curlCode = ''"
@confirm="handleCurlImportConfirm"
>
<template #title>
<a-tooltip position="right" :content="t('apiTestDebug.importByCURLTip')">
<span
>{{ t('apiTestDebug.importByCURL') }}
<icon-exclamation-circle
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
size="16"
/>
</span>
</a-tooltip>
</template>
<div class="h-full">
<MsCodeEditor
v-if="importDrawerVisible"
v-model:model-value="curlCode"
theme="MS-text"
height="100%"
:language="LanguageEnum.PLAINTEXT"
:show-theme-change="false"
:show-full-screen="false"
>
</MsCodeEditor>
</div>
</MsDrawer>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -105,14 +74,12 @@
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import MsCard from '@/components/pure/ms-card/index.vue'; import MsCard from '@/components/pure/ms-card/index.vue';
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
import { LanguageEnum } from '@/components/pure/ms-code-editor/types';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsEditableTab from '@/components/pure/ms-editable-tab/index.vue'; import MsEditableTab from '@/components/pure/ms-editable-tab/index.vue';
import { TabItem } from '@/components/pure/ms-editable-tab/types'; import { TabItem } from '@/components/pure/ms-editable-tab/types';
import MsSplitBox from '@/components/pure/ms-split-box/index.vue'; import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
import moduleTree from './components/moduleTree.vue'; import moduleTree from './components/moduleTree.vue';
import apiMethodName from '@/views/api-test/components/apiMethodName.vue'; import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
import importCurlDialog from '@/views/api-test/components/importCurlDialog.vue';
import requestComposition, { RequestParam } from '@/views/api-test/components/requestComposition/index.vue'; import requestComposition, { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
import { localExecuteApiDebug } from '@/api/modules/api-test/common'; import { localExecuteApiDebug } from '@/api/modules/api-test/common';
@ -128,9 +95,9 @@
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useLeaveTabUnSaveCheck from '@/hooks/useLeaveTabUnSaveCheck'; import useLeaveTabUnSaveCheck from '@/hooks/useLeaveTabUnSaveCheck';
import useRequestCompositionStore from '@/store/modules/api/requestComposition'; import useRequestCompositionStore from '@/store/modules/api/requestComposition';
import { parseCurlScript } from '@/utils';
import { hasAnyPermission } from '@/utils/permission'; import { hasAnyPermission } from '@/utils/permission';
import { CurlParseResult } from '@/models/apiTest/common';
import { ModuleTreeNode } from '@/models/common'; import { ModuleTreeNode } from '@/models/common';
import { import {
ProtocolKeyEnum, ProtocolKeyEnum,
@ -151,8 +118,7 @@
const moduleTreeRef = ref<InstanceType<typeof moduleTree>>(); const moduleTreeRef = ref<InstanceType<typeof moduleTree>>();
const folderTree = ref<ModuleTreeNode[]>([]); const folderTree = ref<ModuleTreeNode[]>([]);
const importDrawerVisible = ref(false); const importDialogVisible = ref(false);
const curlCode = ref('');
const loading = ref(false); const loading = ref(false);
async function handleDebugAddDone() { async function handleDebugAddDone() {
@ -283,20 +249,21 @@
} }
} }
function handleCurlImportConfirm() { function handleImportCurlDone(res: CurlParseResult) {
const { url, headers, queryParameters, method } = parseCurlScript(curlCode.value); const { url, method, headers, queryParams } = res;
addDebugTab({ addDebugTab({
url, url,
method: method?.toUpperCase() || RequestMethods.GET, method: method?.toUpperCase() || RequestMethods.GET,
headers: headers:
headers?.map((e) => ({ Object.keys(headers)?.map((e) => ({
contentType: RequestContentTypeEnum.TEXT, contentType: RequestContentTypeEnum.TEXT,
description: '', description: '',
enable: true, enable: true,
...e, key: e,
value: headers[e],
})) || [], })) || [],
query: query:
queryParameters?.map((e) => ({ Object.keys(queryParams)?.map((e) => ({
paramType: RequestParamsType.STRING, paramType: RequestParamsType.STRING,
description: '', description: '',
required: false, required: false,
@ -304,11 +271,11 @@
minLength: undefined, minLength: undefined,
encode: false, encode: false,
enable: true, enable: true,
...e, key: e,
value: queryParams[e],
})) || [], })) || [],
}); });
curlCode.value = ''; importDialogVisible.value = false;
importDrawerVisible.value = false;
nextTick(() => { nextTick(() => {
handleActiveDebugChange(); handleActiveDebugChange();
}); });

View File

@ -1,344 +1,354 @@
<template> <template>
<MsDrawer <div>
v-model:visible="visible" <MsDrawer
:width="960" v-model:visible="visible"
class="customApiDrawer" :width="960"
no-content-padding class="customApiDrawer"
:show-continue="true" no-content-padding
:footer="requestVModel.isNew === true" :show-continue="true"
:ok-disabled="requestVModel.executeLoading || (isHttpProtocol && !requestVModel.url)" :footer="requestVModel.isNew === true"
:handle-before-cancel="handleBeforeCancel" :ok-disabled="requestVModel.executeLoading || (isHttpProtocol && !requestVModel.url)"
show-full-screen :handle-before-cancel="handleBeforeCancel"
unmount-on-close show-full-screen
@confirm="handleSave" unmount-on-close
@continue="handleContinue" @confirm="handleSave"
@close="handleClose" @continue="handleContinue"
> @close="handleClose"
<template #title> >
<div class="flex flex-1 items-center gap-[8px] overflow-hidden"> <template #title>
<div <div class="flex flex-1 items-center gap-[8px] overflow-hidden">
v-if="props.step" <div
class="flex h-[16px] min-w-[16px] items-center justify-center rounded-full bg-[var(--color-text-brand)] pr-[2px] !text-white" 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> {{ props.step.sort }}
<stepTypeVue
v-if="props.step && [ScenarioStepType.API, ScenarioStepType.CUSTOM_REQUEST].includes(props.step?.stepType)"
:step="props.step"
/>
<a-tooltip v-if="!isShowEditStepNameInput" :content="title" position="bottom">
<div class="flex flex-1 items-center gap-[4px] overflow-hidden">
<div class="one-line-text">
{{ title }}
</div>
<MsIcon
v-if="!props.step || !props.step.isQuoteScenarioStep"
type="icon-icon_edit_outlined"
class="min-w-[16px] cursor-pointer hover:text-[rgb(var(--primary-5))]"
@click="isShowEditStepNameInput = true"
/>
</div> </div>
</a-tooltip> <stepTypeVue
<a-input v-if="props.step && [ScenarioStepType.API, ScenarioStepType.CUSTOM_REQUEST].includes(props.step?.stepType)"
v-if="isShowEditStepNameInput" :step="props.step"
ref="stepNameInputRef" />
v-model:model-value="requestVModel.stepName" <a-tooltip v-if="!isShowEditStepNameInput" :content="title" position="bottom">
class="flex-1" <div class="flex flex-1 items-center gap-[4px] overflow-hidden">
:placeholder="t('apiScenario.pleaseInputStepName')" <div class="one-line-text">
:max-length="255" {{ title }}
show-word-limit </div>
@press-enter="updateStepName" <MsIcon
@blur="updateStepName" v-if="!props.step || !props.step.isQuoteScenarioStep"
/> type="icon-icon_edit_outlined"
</div> class="min-w-[16px] cursor-pointer hover:text-[rgb(var(--primary-5))]"
<div v-show="!isShowEditStepNameInput" class="ml-auto flex items-center gap-[16px]"> @click="isShowEditStepNameInput = true"
<div />
v-if="!props.step || props.step?.stepType === ScenarioStepType.CUSTOM_REQUEST"
class="customApiDrawer-title-right flex items-center gap-[16px]"
>
<a-tooltip :content="appStore.currentEnvConfig?.name" :disabled="!appStore.currentEnvConfig?.name">
<div class="one-line-text max-w-[250px] text-[14px] font-normal text-[var(--color-text-4)]">
{{ t('apiScenario.env', { name: appStore.currentEnvConfig?.name }) }}
</div> </div>
</a-tooltip> </a-tooltip>
<a-select <a-input
v-model:model-value="requestVModel.customizeRequestEnvEnable" v-if="isShowEditStepNameInput"
class="w-[150px]" v-model:model-value="requestVModel.stepName"
:disabled="props.step?.isQuoteScenarioStep" class="flex-1"
@change="handleUseEnvChange" :placeholder="t('apiScenario.pleaseInputStepName')"
> :max-length="255"
<template #prefix> show-word-limit
<div> {{ t('project.environmental.env') }} </div> @press-enter="updateStepName"
</template> @blur="updateStepName"
<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" class="mr-4" @click="emit('deleteStep')">
<MsIcon type="icon-icon_delete-trash_outlined1" />
{{ t('common.delete') }}
</MsButton>
</div> </div>
</div> <div v-show="!isShowEditStepNameInput" class="ml-auto flex items-center gap-[16px]">
</template> <div
<a-empty v-if="!props.step || props.step?.stepType === ScenarioStepType.CUSTOM_REQUEST"
v-if="pluginError && !isHttpProtocol" class="customApiDrawer-title-right flex items-center gap-[16px]"
: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="flex flex-wrap items-center justify-between gap-[12px] px-[18px] pt-[8px]">
<div class="flex flex-1 items-center gap-[16px]">
<a-select
v-if="requestVModel.isNew"
v-model:model-value="requestVModel.protocol"
:loading="protocolLoading"
:disabled="_stepType.isQuoteApi || props.step?.isQuoteScenarioStep"
class="w-[90px]"
> >
<a-tooltip <a-tooltip :content="appStore.currentEnvConfig?.name" :disabled="!appStore.currentEnvConfig?.name">
v-for="item of protocolOptions" <div class="one-line-text max-w-[250px] text-[14px] font-normal text-[var(--color-text-4)]">
:key="item.value as string" {{ t('apiScenario.env', { name: appStore.currentEnvConfig?.name }) }}
:content="item.label" </div>
:mouse-enter-delay="300" </a-tooltip>
<a-select
v-model:model-value="requestVModel.customizeRequestEnvEnable"
class="w-[150px]"
:disabled="props.step?.isQuoteScenarioStep"
@change="handleUseEnvChange"
> >
<a-option :value="item.value"> <template #prefix>
{{ item.label }} <div> {{ t('project.environmental.env') }} </div>
</a-option> </template>
</a-tooltip> <a-option :value="true">{{ t('common.quote') }}</a-option>
</a-select> <a-option :value="false">{{ t('common.notQuote') }}</a-option>
<div v-else class="flex items-center gap-[4px]"> </a-select>
<apiMethodName
:method="(requestVModel.protocol as RequestMethods)"
tag-background-color="rgb(var(--link-7))"
tag-text-color="white"
is-tag
class="flex items-center"
/>
<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> </div>
<a-input-group v-if="isHttpProtocol" class="flex-1"> <div
<apiMethodSelect v-if="props.step && !props.step.isQuoteScenarioStep"
v-model:model-value="requestVModel.method" class="right-operation-button-icon ml-auto flex items-center"
class="w-[140px]" >
:disabled="_stepType.isQuoteApi || props.step?.isQuoteScenarioStep" <replaceButton
@change="handleActiveDebugChange" 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"
/> />
<a-input <MsButton type="icon" status="secondary" class="mr-4" @click="emit('deleteStep')">
v-model:model-value="requestVModel.url" <MsIcon type="icon-icon_delete-trash_outlined1" />
:max-length="255" {{ t('common.delete') }}
:placeholder="showEnvPrefix ? t('apiScenario.pleaseInputUrl') : t('apiTestDebug.urlPlaceholder')" </MsButton>
allow-clear </div>
class="hover:z-10"
:style="isUrlError ? 'border: 1px solid rgb(var(--danger-6);z-index: 10' : ''"
:disabled="_stepType.isQuoteApi || props.step?.isQuoteScenarioStep"
@input="() => (isUrlError = false)"
@change="handleUrlChange"
>
<template v-if="showEnvPrefix" #prefix>
{{ (appStore.currentEnvConfig as EnvConfig)?.httpConfig.find((e) => e.type === 'NONE')?.url }}
</template>
</a-input>
</a-input-group>
</div> </div>
<div v-permission="[props.permissionMap?.execute]"> </template>
<template v-if="hasLocalExec"> <a-empty
<a-dropdown-button v-if="pluginError && !isHttpProtocol"
v-if="!requestVModel.executeLoading" :description="t('apiTestDebug.noPlugin')"
:disabled="requestVModel.executeLoading || (isHttpProtocol && !requestVModel.url)" class="h-[200px] items-center justify-center"
class="exec-btn" >
@click="() => execute(isPriorityLocalExec ? 'localExec' : 'serverExec')" <template #image>
@select="execute" <MsIcon type="icon-icon_plugin_outlined" size="48" />
</template>
</a-empty>
<div v-show="!pluginError || isHttpProtocol" class="flex h-full flex-col">
<div class="flex flex-wrap items-center justify-between gap-[12px] px-[18px] pt-[8px]">
<div class="flex flex-1 items-center gap-[16px]">
<a-select
v-if="requestVModel.isNew"
v-model:model-value="requestVModel.protocol"
:loading="protocolLoading"
:disabled="_stepType.isQuoteApi || props.step?.isQuoteScenarioStep"
class="w-[90px]"
> >
{{ isPriorityLocalExec ? t('apiTestDebug.localExec') : t('apiTestDebug.serverExec') }} <a-tooltip
<template #icon> v-for="item of protocolOptions"
<icon-down /> :key="item.value as string"
</template> :content="item.label"
<template #content> :mouse-enter-delay="300"
<a-doption :value="isPriorityLocalExec ? 'serverExec' : 'localExec'"> >
{{ isPriorityLocalExec ? t('apiTestDebug.serverExec') : t('apiTestDebug.localExec') }} <a-option :value="item.value">
</a-doption> {{ item.label }}
</template> </a-option>
</a-dropdown-button> </a-tooltip>
</a-select>
<div v-else class="flex items-center gap-[4px]">
<apiMethodName
:method="(requestVModel.protocol as RequestMethods)"
tag-background-color="rgb(var(--link-7))"
tag-text-color="white"
is-tag
class="flex items-center"
/>
<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="_stepType.isQuoteApi || props.step?.isQuoteScenarioStep"
@change="handleActiveDebugChange"
/>
<a-input
v-model:model-value="requestVModel.url"
:max-length="255"
:placeholder="showEnvPrefix ? t('apiScenario.pleaseInputUrl') : t('apiTestDebug.urlPlaceholder')"
allow-clear
class="hover:z-10"
:style="isUrlError ? 'border: 1px solid rgb(var(--danger-6);z-index: 10' : ''"
:disabled="_stepType.isQuoteApi || props.step?.isQuoteScenarioStep"
@input="() => (isUrlError = false)"
@change="handleUrlChange"
>
<template v-if="showEnvPrefix" #prefix>
{{ (appStore.currentEnvConfig as EnvConfig)?.httpConfig.find((e) => e.type === 'NONE')?.url }}
</template>
<template #suffix>
<MsIcon
type="icon-icon_curl"
:size="18"
class="cursor-pointer hover:text-[rgb(var(--primary-5))]"
@click="importDialogVisible = true"
/>
</template>
</a-input>
</a-input-group>
</div>
<div v-permission="[props.permissionMap?.execute]">
<template v-if="hasLocalExec">
<a-dropdown-button
v-if="!requestVModel.executeLoading"
:disabled="requestVModel.executeLoading || (isHttpProtocol && !requestVModel.url)"
class="exec-btn"
@click="() => execute(isPriorityLocalExec ? 'localExec' : 'serverExec')"
@select="execute"
>
{{ isPriorityLocalExec ? t('apiTestDebug.localExec') : t('apiTestDebug.serverExec') }}
<template #icon>
<icon-down />
</template>
<template #content>
<a-doption :value="isPriorityLocalExec ? 'serverExec' : 'localExec'">
{{ isPriorityLocalExec ? t('apiTestDebug.serverExec') : t('apiTestDebug.localExec') }}
</a-doption>
</template>
</a-dropdown-button>
<a-button v-else type="primary" class="mr-[12px]" @click="stopDebug">
{{ t('common.stop') }}
</a-button>
</template>
<a-button
v-else-if="!requestVModel.executeLoading"
class="mr-[12px]"
type="primary"
@click="() => execute('serverExec')"
>
{{ t('apiTestDebug.serverExec') }}
</a-button>
<a-button v-else type="primary" class="mr-[12px]" @click="stopDebug"> <a-button v-else type="primary" class="mr-[12px]" @click="stopDebug">
{{ t('common.stop') }} {{ t('common.stop') }}
</a-button> </a-button>
</template> </div>
<a-button
v-else-if="!requestVModel.executeLoading"
class="mr-[12px]"
type="primary"
@click="() => execute('serverExec')"
>
{{ t('apiTestDebug.serverExec') }}
</a-button>
<a-button v-else type="primary" class="mr-[12px]" @click="stopDebug">
{{ t('common.stop') }}
</a-button>
</div> </div>
</div> <div class="request-tab-and-response flex-1">
<div class="request-tab-and-response flex-1"> <div class="px-[18px]">
<div class="px-[18px]"> <a-input
<a-input v-if="props.step?.stepType && _stepType.isQuoteApi && isHttpProtocol"
v-if="props.step?.stepType && _stepType.isQuoteApi && isHttpProtocol" v-model:model-value="requestVModel.name"
v-model:model-value="requestVModel.name" :max-length="255"
:max-length="255" :placeholder="t('apiTestManagement.apiNamePlaceholder')"
:placeholder="t('apiTestManagement.apiNamePlaceholder')" :disabled="!isEditableApi"
:disabled="!isEditableApi" allow-clear
allow-clear class="my-[8px]"
class="my-[8px]" />
</div>
<MsTab
v-if="requestVModel.activeTab"
v-model:active-key="requestVModel.activeTab"
:content-tab-list="contentTabList"
:get-text-func="getTabBadge"
class="sticky-content no-content relative top-0 mx-[16px] border-b"
@tab-click="requestTabClick"
/> />
</div> <div :class="`request-content-and-response ${activeLayout}`">
<MsTab <a-spin class="request block h-full w-full" :loading="requestVModel.executeLoading || loading">
v-if="requestVModel.activeTab" <div class="request-tab-pane flex flex-col p-[16px]">
v-model:active-key="requestVModel.activeTab" <a-spin
:content-tab-list="contentTabList" v-show="requestVModel.activeTab === RequestComposition.PLUGIN"
:get-text-func="getTabBadge" :loading="pluginLoading"
class="sticky-content no-content relative top-0 mx-[16px] border-b" class="min-h-[100px] w-full"
@tab-click="requestTabClick" >
/> <MsFormCreate
<div :class="`request-content-and-response ${activeLayout}`"> v-model:api="fApi"
<a-spin class="request block h-full w-full" :loading="requestVModel.executeLoading || loading"> :rule="currentPluginScript"
<div class="request-tab-pane flex flex-col p-[16px]"> :option="currentPluginOptions"
<a-spin @change="
v-show="requestVModel.activeTab === RequestComposition.PLUGIN" () => {
:loading="pluginLoading" if (isInitPluginForm) {
class="min-h-[100px] w-full" handlePluginFormChange();
> }
<MsFormCreate
v-model:api="fApi"
:rule="currentPluginScript"
:option="currentPluginOptions"
@change="
() => {
if (isInitPluginForm) {
handlePluginFormChange();
} }
} "
" />
</a-spin>
<httpHeader
v-if="requestVModel.activeTab === RequestComposition.HEADER"
v-model:params="requestVModel.headers"
:disabled-param-value="!isEditableApi && !isEditableParamValue"
:disabled-except-param="!isEditableApi"
:layout="activeLayout"
@change="handleActiveDebugChange"
/> />
</a-spin> <httpBody
<httpHeader v-else-if="requestVModel.activeTab === RequestComposition.BODY"
v-if="requestVModel.activeTab === RequestComposition.HEADER" v-model:params="requestVModel.body"
v-model:params="requestVModel.headers" :disabled-param-value="!isEditableApi && !isEditableParamValue"
:disabled-param-value="!isEditableApi && !isEditableParamValue" :disabled-except-param="!isEditableApi"
:disabled-except-param="!isEditableApi" :disabled-body-type="!isEditableApi"
:layout="activeLayout" :upload-temp-file-api="uploadTempFile"
@change="handleActiveDebugChange" :file-save-as-source-id="props.step?.id"
/> :file-save-as-api="stepTransferFile"
<httpBody :file-module-options-api="getTransferOptions"
v-else-if="requestVModel.activeTab === RequestComposition.BODY" @change="handleActiveDebugChange"
v-model:params="requestVModel.body" />
:disabled-param-value="!isEditableApi && !isEditableParamValue" <httpQuery
:disabled-except-param="!isEditableApi" v-else-if="requestVModel.activeTab === RequestComposition.QUERY"
:disabled-body-type="!isEditableApi" v-model:params="requestVModel.query"
:upload-temp-file-api="uploadTempFile" :disabled-param-value="!isEditableApi && !isEditableParamValue"
:file-save-as-source-id="props.step?.id" :disabled-except-param="!isEditableApi"
:file-save-as-api="stepTransferFile" @change="handleActiveDebugChange"
:file-module-options-api="getTransferOptions" />
@change="handleActiveDebugChange" <httpRest
/> v-else-if="requestVModel.activeTab === RequestComposition.REST"
<httpQuery v-model:params="requestVModel.rest"
v-else-if="requestVModel.activeTab === RequestComposition.QUERY" :disabled-param-value="!isEditableApi && !isEditableParamValue"
v-model:params="requestVModel.query" :disabled-except-param="!isEditableApi"
:disabled-param-value="!isEditableApi && !isEditableParamValue" @change="handleActiveDebugChange"
:disabled-except-param="!isEditableApi" />
@change="handleActiveDebugChange" <precondition
/> v-else-if="requestVModel.activeTab === RequestComposition.PRECONDITION"
<httpRest v-model:config="requestVModel.children[0].preProcessorConfig"
v-else-if="requestVModel.activeTab === RequestComposition.REST" is-definition
v-model:params="requestVModel.rest" :disabled="!isEditableApi"
:disabled-param-value="!isEditableApi && !isEditableParamValue" :tip-content="t('apiScenario.openGlobalPreConditionTip')"
:disabled-except-param="!isEditableApi" @change="handleActiveDebugChange"
@change="handleActiveDebugChange" />
/> <postcondition
<precondition v-else-if="requestVModel.activeTab === RequestComposition.POST_CONDITION"
v-else-if="requestVModel.activeTab === RequestComposition.PRECONDITION" v-model:config="requestVModel.children[0].postProcessorConfig"
v-model:config="requestVModel.children[0].preProcessorConfig" :response="responseResultBody"
is-definition :disabled="!isEditableApi"
:disabled="!isEditableApi" :tip-content="t('apiScenario.openGlobalPostConditionTip')"
:tip-content="t('apiScenario.openGlobalPreConditionTip')" is-definition
@change="handleActiveDebugChange" @change="handleActiveDebugChange"
/> />
<postcondition <assertion
v-else-if="requestVModel.activeTab === RequestComposition.POST_CONDITION" v-else-if="requestVModel.activeTab === RequestComposition.ASSERTION"
v-model:config="requestVModel.children[0].postProcessorConfig" v-model:params="requestVModel.children[0].assertionConfig.assertions"
:response="responseResultBody" :response="responseResultBody"
:disabled="!isEditableApi" is-definition
:tip-content="t('apiScenario.openGlobalPostConditionTip')" :disabled="!isEditableApi"
is-definition :assertion-config="requestVModel.children[0].assertionConfig"
@change="handleActiveDebugChange" :show-extraction="true"
/> />
<assertion <auth
v-else-if="requestVModel.activeTab === RequestComposition.ASSERTION" v-else-if="requestVModel.activeTab === RequestComposition.AUTH"
v-model:params="requestVModel.children[0].assertionConfig.assertions" v-model:params="requestVModel.authConfig"
:response="responseResultBody" :disabled="!isEditableApi"
is-definition @change="handleActiveDebugChange"
:disabled="!isEditableApi" />
:assertion-config="requestVModel.children[0].assertionConfig" <setting
:show-extraction="true" v-else-if="requestVModel.activeTab === RequestComposition.SETTING"
/> v-model:params="requestVModel.otherConfig"
<auth :disabled="!isEditableApi"
v-else-if="requestVModel.activeTab === RequestComposition.AUTH" @change="handleActiveDebugChange"
v-model:params="requestVModel.authConfig" />
:disabled="!isEditableApi" </div>
@change="handleActiveDebugChange" </a-spin>
/>
<setting
v-else-if="requestVModel.activeTab === RequestComposition.SETTING"
v-model:params="requestVModel.otherConfig"
:disabled="!isEditableApi"
@change="handleActiveDebugChange"
/>
</div>
</a-spin>
<response <response
v-if="visible" v-if="visible"
v-show="showResponse" v-show="showResponse"
ref="responseRef" ref="responseRef"
v-model:active-layout="activeLayout" v-model:active-layout="activeLayout"
v-model:active-tab="requestVModel.responseActiveTab" v-model:active-tab="requestVModel.responseActiveTab"
class="response" class="response"
:is-http-protocol="isHttpProtocol" :is-http-protocol="isHttpProtocol"
:is-priority-local-exec="isPriorityLocalExec" :is-priority-local-exec="isPriorityLocalExec"
:request-url="requestVModel.url" :request-url="requestVModel.url"
:is-expanded="isVerticalExpanded" :is-expanded="isVerticalExpanded"
:request-result="currentResponse" :request-result="currentResponse"
:console="currentResponse?.console" :console="currentResponse?.console"
:is-edit="false" :is-edit="false"
is-definition is-definition
hide-layout-switch hide-layout-switch
:loading="requestVModel.executeLoading || loading" :loading="requestVModel.executeLoading || loading"
@execute="execute" @execute="execute"
> >
<template #titleRight> <template #titleRight>
<loopPagination v-model:current-loop="currentLoop" :loop-total="loopTotal" /> <loopPagination v-model:current-loop="currentLoop" :loop-total="loopTotal" />
</template> </template>
</response> </response>
</div>
</div> </div>
</div> </div>
</div> <!-- <addDependencyDrawer v-model:visible="showAddDependencyDrawer" :mode="addDependencyMode" /> -->
<!-- <addDependencyDrawer v-model:visible="showAddDependencyDrawer" :mode="addDependencyMode" /> --> </MsDrawer>
</MsDrawer> <importCurlDialog v-model:visible="importDialogVisible" @done="handleImportCurlDone" />
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -356,6 +366,7 @@
import stepTypeVue from './stepType/stepType.vue'; import stepTypeVue from './stepType/stepType.vue';
import apiMethodName from '@/views/api-test/components/apiMethodName.vue'; import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
import apiMethodSelect from '@/views/api-test/components/apiMethodSelect.vue'; import apiMethodSelect from '@/views/api-test/components/apiMethodSelect.vue';
import importCurlDialog from '@/views/api-test/components/importCurlDialog.vue';
import auth from '@/views/api-test/components/requestComposition/auth.vue'; import auth from '@/views/api-test/components/requestComposition/auth.vue';
import { TabErrorMessage } from '@/views/api-test/components/requestComposition/index.vue'; import { TabErrorMessage } from '@/views/api-test/components/requestComposition/index.vue';
import postcondition from '@/views/api-test/components/requestComposition/postcondition.vue'; import postcondition from '@/views/api-test/components/requestComposition/postcondition.vue';
@ -372,6 +383,7 @@
import { getLocalStorage, setLocalStorage } from '@/utils/local-storage'; import { getLocalStorage, setLocalStorage } from '@/utils/local-storage';
import { import {
CurlParseResult,
EnableKeyValueParam, EnableKeyValueParam,
ExecuteApiRequestFullParams, ExecuteApiRequestFullParams,
ExecuteBody, ExecuteBody,
@ -1268,6 +1280,27 @@
}); });
} }
const importDialogVisible = ref(false);
function handleImportCurlDone(res: CurlParseResult) {
const { url, method, headers, queryParams } = res;
requestVModel.value.url = url;
requestVModel.value.method = method;
requestVModel.value.headers = Object.keys(headers).map((e) => ({
...defaultHeaderParamsItem,
key: e,
value: headers[e],
}));
requestVModel.value.query = Object.keys(queryParams).map((e) => ({
...defaultRequestParamsItem,
key: e,
value: queryParams[e],
}));
importDialogVisible.value = false;
nextTick(() => {
handleActiveDebugChange();
});
}
watch( watch(
() => props.request?.uniqueId, () => props.request?.uniqueId,
() => { () => {