diff --git a/frontend/src/api/modules/api-test/management.ts b/frontend/src/api/modules/api-test/management.ts index 8850a48e1b..ea53ddea2a 100644 --- a/frontend/src/api/modules/api-test/management.ts +++ b/frontend/src/api/modules/api-test/management.ts @@ -53,6 +53,7 @@ import { RecoverDefinitionUrl, RecoverOperationHistoryUrl, RecycleCasePageUrl, + RunCaseUrl, SaveOperationHistoryUrl, SortCaseUrl, SortDefinitionUrl, @@ -409,6 +410,11 @@ export function toggleFollowCase(id: string | number) { return MSR.get({ url: ToggleFollowCaseUrl, params: id }); } +// 用例执行,传请求详情执行 +export function runCase(data: ExecuteRequestParams) { + return MSR.post({ url: RunCaseUrl, data }); +} + /** * 接口用例回收站 */ diff --git a/frontend/src/api/requrls/api-test/management.ts b/frontend/src/api/requrls/api-test/management.ts index 3a1485924e..5b7bf1af18 100644 --- a/frontend/src/api/requrls/api-test/management.ts +++ b/frontend/src/api/requrls/api-test/management.ts @@ -74,6 +74,7 @@ export const GetExecuteHistoryUrl = '/api/case/execute/page'; // 获取用的执 export const GetDependencyUrl = '/api/case/get-reference'; // 获取用例的依赖关系 export const GetChangeHistoryUrl = '/api/case/operation-history/page'; // 获取用例的依赖关系 export const ToggleFollowCaseUrl = '/api/case/follow'; // 接口定义-关注/取消关注 +export const RunCaseUrl = '/api/case/run'; // 执行接口用例 export const GetCaseReportByIdUrl = '/api/report/case/get/'; // 接口用例报告获取 export const GetCaseReportDetailUrl = '/api/report/case/get/detail/'; // 接口用例报告获取 diff --git a/frontend/src/models/apiTest/management.ts b/frontend/src/models/apiTest/management.ts index 3a4d359b12..7dbc9f4e05 100644 --- a/frontend/src/models/apiTest/management.ts +++ b/frontend/src/models/apiTest/management.ts @@ -301,6 +301,7 @@ export interface ApiCaseDetail extends ExecuteRequestParams { priority: string; num: number; status: string; + protocol: string; lastReportStatus: string; lastReportId: string; projectId: string; diff --git a/frontend/src/views/api-test/components/requestComposition/index.vue b/frontend/src/views/api-test/components/requestComposition/index.vue index ecef56c324..371e00e286 100644 --- a/frontend/src/views/api-test/components/requestComposition/index.vue +++ b/frontend/src/views/api-test/components/requestComposition/index.vue @@ -587,6 +587,7 @@ request: RequestParam; // 请求参数集合 moduleTree?: ModuleTreeNode[]; // 模块树 isCase?: boolean; // 是否是用例引用的组件 + apiDetail?: RequestParam; // 用例引用的时候需要接口定义的数据 detailLoading?: boolean; // 详情加载状态 isDefinition?: boolean; // 是否是接口定义模式 hideResponseLayoutSwitch?: boolean; // 是否隐藏响应体的布局切换 @@ -677,10 +678,32 @@ label: t('apiTestDebug.setting'), }, ]; + const restNumApi = computed( + () => + filterKeyValParams(props.apiDetail?.rest ?? props.apiDetail?.request.rest, defaultRequestParamsItem).validParams + .length + ); + const queryNumApi = computed( + () => + filterKeyValParams(props.apiDetail?.query ?? props.apiDetail?.request.query, defaultRequestParamsItem).validParams + .length + ); + const bodyTabBadgeApi = computed(() => + props.apiDetail?.request.body?.bodyType !== RequestBodyFormat.NONE ? '1' : '' + ); // 根据协议类型获取请求内容tab const contentTabList = computed(() => { // HTTP 协议 tabs if (isHttpProtocol.value) { + if (props.isCase) { + // 定义没有参数BODY/QUERY/REST的,用例对应tab不显示 + return httpContentTabList.filter( + (e) => + !(!restNumApi.value && e.value === RequestComposition.REST) && + !(!queryNumApi.value && e.value === RequestComposition.QUERY) && + !(!bodyTabBadgeApi.value?.length && e.value === RequestComposition.BODY) + ); + } if (props.isDefinition) { // 接口定义,定义模式隐藏前后置、断言 return requestVModel.value.mode === 'debug' @@ -1214,6 +1237,14 @@ } else if (protocolOptions.value.length === 0) { await initProtocolList(); } + if ( + props.isCase && + requestVModel.value.protocol === 'HTTP' && + (restNumApi.value || queryNumApi.value || bodyTabBadgeApi.value?.length) + ) { + // 如果定义有参数BODY/QUERY/REST,用例默认tab是参数tab + requestVModel.value.activeTab = contentTabList.value[1].value; + } if (props.request.isExecute && !requestVModel.value.executeLoading) { // 如果是执行操作打开接口详情,且该接口不在执行状态中,则立即执行 execute(isPriorityLocalExec.value ? 'localExec' : 'serverExec'); diff --git a/frontend/src/views/api-test/management/components/management/api/index.vue b/frontend/src/views/api-test/management/components/management/api/index.vue index d60c53be26..d9044ffba5 100644 --- a/frontend/src/views/api-test/management/components/management/api/index.vue +++ b/frontend/src/views/api-test/management/components/management/api/index.vue @@ -54,7 +54,7 @@ diff --git a/frontend/src/views/api-test/management/components/management/api/preview/detail.vue b/frontend/src/views/api-test/management/components/management/api/preview/detail.vue index 8f43105a3a..9112d3f09b 100644 --- a/frontend/src/views/api-test/management/components/management/api/preview/detail.vue +++ b/frontend/src/views/api-test/management/components/management/api/preview/detail.vue @@ -234,85 +234,87 @@ - - - + - @@ -196,6 +225,9 @@ :deep(.arco-tabs-nav) { border-bottom: 1px solid var(--color-text-n8); } + :deep(.arco-tabs-nav-extra) { + line-height: 32px; + } :deep(.arco-tabs-content) { @apply pt-0; .arco-tabs-content-item { diff --git a/frontend/src/views/api-test/management/components/management/case/caseDetailDrawer.vue b/frontend/src/views/api-test/management/components/management/case/caseDetailDrawer.vue index 162adb29f5..1c607e7d3a 100644 --- a/frontend/src/views/api-test/management/components/management/case/caseDetailDrawer.vue +++ b/frontend/src/views/api-test/management/components/management/case/caseDetailDrawer.vue @@ -7,9 +7,6 @@ :footer="false" no-content-padding > - @@ -72,7 +63,6 @@ import MsButton from '@/components/pure/ms-button/index.vue'; import MsDrawer from '@/components/pure/ms-drawer/index.vue'; import MsIcon from '@/components/pure/ms-icon-font/index.vue'; - import environmentSelect from '../../environmentSelect.vue'; import caseDetail from './caseDetail.vue'; import { useI18n } from '@/hooks/useI18n'; @@ -81,7 +71,6 @@ const props = defineProps<{ detail: RequestParam; - protocol: string; apiDetail: RequestParam; }>(); @@ -91,6 +80,16 @@ required: true, }); const caseDerailRef = ref>(); + + function handleSelect(val: string | number | Record | undefined) { + switch (val) { + case 'delete': + caseDerailRef.value?.handleDelete(); + break; + default: + break; + } + } diff --git a/frontend/src/views/api-test/management/components/management/case/index.vue b/frontend/src/views/api-test/management/components/management/case/index.vue index 0eff958db7..691eda5cdc 100644 --- a/frontend/src/views/api-test/management/components/management/case/index.vue +++ b/frontend/src/views/api-test/management/components/management/case/index.vue @@ -2,6 +2,7 @@
@@ -42,6 +43,9 @@ protocol: string; moduleTree: ModuleTreeNode[]; // 模块树 }>(); + const emit = defineEmits<{ + (e: 'deleteCase', id: string): void; + }>(); const apiTabs = defineModel('apiTabs', { required: true, @@ -58,16 +62,15 @@ try { loading.value = true; const res = await getCaseDetail(id); - const parseRequestBodyResult = parseRequestBodyFiles(res.request.body); // 解析请求体中的文件,将详情中的文件 id 集合收集,更新时以判断文件是否删除以及是否新上传的文件; - // if (res.protocol === 'HTTP') { // TODO: 后端没protocol字段,问一下 - // parseRequestBodyResult = parseRequestBodyFiles(res.request.body); // 解析请求体中的文件,将详情中的文件 id 集合收集,更新时以判断文件是否删除以及是否新上传的文件 - // } + let parseRequestBodyResult; + if (res.protocol === 'HTTP') { + parseRequestBodyResult = parseRequestBodyFiles(res.request.body); // 解析请求体中的文件,将详情中的文件 id 集合收集,更新时以判断文件是否删除以及是否新上传的文件 + } const tabItemInfo = { ...cloneDeep(defaultCaseParams as RequestParam), ...({ ...res.request, ...res, - // responseDefinition: res.response.map((e) => ({ ...e, responseActiveTab: ResponseComposition.BODY })), // TODO: 后端没response字段,问一下 url: res.path, ...parseRequestBodyResult, } as Partial), @@ -92,7 +95,7 @@ } } - async function openCaseTab(apiInfo: ApiCaseDetail) { + async function openCaseTab(apiInfo: ApiCaseDetail | string) { const isLoadedTabIndex = apiTabs.value.findIndex( (e) => e.id === (typeof apiInfo === 'string' ? apiInfo : apiInfo.id) ); @@ -103,4 +106,14 @@ } await openOrUpdateCaseTab(true, typeof apiInfo === 'string' ? apiInfo : apiInfo.id); } + + const caseTableRef = ref>(); + function deleteCase(id: string) { + emit('deleteCase', id); + caseTableRef.value?.loadCaseList(); + } + + defineExpose({ + openCaseTab, + }); diff --git a/frontend/src/views/api-test/management/components/management/index.vue b/frontend/src/views/api-test/management/components/management/index.vue index 4568783f2f..71ed2d5ca5 100644 --- a/frontend/src/views/api-test/management/components/management/index.vue +++ b/frontend/src/views/api-test/management/components/management/index.vue @@ -47,12 +47,14 @@ :module-tree="props.moduleTree" /> @@ -67,11 +69,12 @@ import { RequestParam } from '@/views/api-test/components/requestComposition/index.vue'; // import MockTable from '@/views/api-test/management/components/management/mock/mockTable.vue'; - import { getEnvironment, getEnvList } from '@/api/modules/api-test/common'; + import { getEnvironment, getEnvList, getProtocolList } from '@/api/modules/api-test/common'; import { useI18n } from '@/hooks/useI18n'; import router from '@/router'; import useAppStore from '@/store/modules/app'; + import { ProtocolItem } from '@/models/apiTest/common'; import { ModuleTreeNode } from '@/models/common'; import { EnvConfig } from '@/models/projectManagement/environmental'; import { @@ -104,6 +107,7 @@ ]; const apiRef = ref>(); + const caseRef = ref>(); function newTab(apiInfo?: ModuleTreeNode | string, isCopy?: boolean, isExecute?: boolean) { if (apiInfo) { @@ -113,6 +117,10 @@ } } + function newCaseTab(id: string) { + caseRef.value?.openCaseTab(id); + } + const apiTabs = ref([ { id: 'all', @@ -310,16 +318,29 @@ } } + const protocols = ref([]); + async function initProtocolList() { + try { + protocols.value = await getProtocolList(appStore.currentOrgId); + } catch (error) { + // eslint-disable-next-line no-console + console.log(error); + } + } + onBeforeMount(() => { initEnvList(); + initProtocolList(); }); /** 向孙组件提供属性 */ provide('currentEnvConfig', readonly(currentEnvConfig)); provide('defaultCaseParams', readonly(defaultCaseParams)); + provide('protocols', readonly(protocols)); defineExpose({ newTab, + newCaseTab, refreshApiTable, handleApiUpdateFromModuleTree, handleDeleteApiFromModuleTree, diff --git a/frontend/src/views/api-test/management/index.vue b/frontend/src/views/api-test/management/index.vue index b6b477075c..8980fe3bc1 100644 --- a/frontend/src/views/api-test/management/index.vue +++ b/frontend/src/views/api-test/management/index.vue @@ -149,6 +149,9 @@ if (route.query.dId) { // 携带 dId 参数,自动打开接口定义详情 tab managementRef.value?.newTab(route.query.dId as string); + } else if (route.query.cId) { + // 携带 cId 参数,自动打开接口用例详情 tab + managementRef.value?.newCaseTab(route.query.cId as string); } });