feat(接口管理): 用例执行&用例详情删除&部分细节修改
This commit is contained in:
parent
7791b800a4
commit
efa5387cf1
|
@ -53,6 +53,7 @@ import {
|
||||||
RecoverDefinitionUrl,
|
RecoverDefinitionUrl,
|
||||||
RecoverOperationHistoryUrl,
|
RecoverOperationHistoryUrl,
|
||||||
RecycleCasePageUrl,
|
RecycleCasePageUrl,
|
||||||
|
RunCaseUrl,
|
||||||
SaveOperationHistoryUrl,
|
SaveOperationHistoryUrl,
|
||||||
SortCaseUrl,
|
SortCaseUrl,
|
||||||
SortDefinitionUrl,
|
SortDefinitionUrl,
|
||||||
|
@ -409,6 +410,11 @@ export function toggleFollowCase(id: string | number) {
|
||||||
return MSR.get({ url: ToggleFollowCaseUrl, params: id });
|
return MSR.get({ url: ToggleFollowCaseUrl, params: id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 用例执行,传请求详情执行
|
||||||
|
export function runCase(data: ExecuteRequestParams) {
|
||||||
|
return MSR.post({ url: RunCaseUrl, data });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 接口用例回收站
|
* 接口用例回收站
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -74,6 +74,7 @@ export const GetExecuteHistoryUrl = '/api/case/execute/page'; // 获取用的执
|
||||||
export const GetDependencyUrl = '/api/case/get-reference'; // 获取用例的依赖关系
|
export const GetDependencyUrl = '/api/case/get-reference'; // 获取用例的依赖关系
|
||||||
export const GetChangeHistoryUrl = '/api/case/operation-history/page'; // 获取用例的依赖关系
|
export const GetChangeHistoryUrl = '/api/case/operation-history/page'; // 获取用例的依赖关系
|
||||||
export const ToggleFollowCaseUrl = '/api/case/follow'; // 接口定义-关注/取消关注
|
export const ToggleFollowCaseUrl = '/api/case/follow'; // 接口定义-关注/取消关注
|
||||||
|
export const RunCaseUrl = '/api/case/run'; // 执行接口用例
|
||||||
export const GetCaseReportByIdUrl = '/api/report/case/get/'; // 接口用例报告获取
|
export const GetCaseReportByIdUrl = '/api/report/case/get/'; // 接口用例报告获取
|
||||||
export const GetCaseReportDetailUrl = '/api/report/case/get/detail/'; // 接口用例报告获取
|
export const GetCaseReportDetailUrl = '/api/report/case/get/detail/'; // 接口用例报告获取
|
||||||
|
|
||||||
|
|
|
@ -301,6 +301,7 @@ export interface ApiCaseDetail extends ExecuteRequestParams {
|
||||||
priority: string;
|
priority: string;
|
||||||
num: number;
|
num: number;
|
||||||
status: string;
|
status: string;
|
||||||
|
protocol: string;
|
||||||
lastReportStatus: string;
|
lastReportStatus: string;
|
||||||
lastReportId: string;
|
lastReportId: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
|
|
|
@ -587,6 +587,7 @@
|
||||||
request: RequestParam; // 请求参数集合
|
request: RequestParam; // 请求参数集合
|
||||||
moduleTree?: ModuleTreeNode[]; // 模块树
|
moduleTree?: ModuleTreeNode[]; // 模块树
|
||||||
isCase?: boolean; // 是否是用例引用的组件
|
isCase?: boolean; // 是否是用例引用的组件
|
||||||
|
apiDetail?: RequestParam; // 用例引用的时候需要接口定义的数据
|
||||||
detailLoading?: boolean; // 详情加载状态
|
detailLoading?: boolean; // 详情加载状态
|
||||||
isDefinition?: boolean; // 是否是接口定义模式
|
isDefinition?: boolean; // 是否是接口定义模式
|
||||||
hideResponseLayoutSwitch?: boolean; // 是否隐藏响应体的布局切换
|
hideResponseLayoutSwitch?: boolean; // 是否隐藏响应体的布局切换
|
||||||
|
@ -677,10 +678,32 @@
|
||||||
label: t('apiTestDebug.setting'),
|
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
|
// 根据协议类型获取请求内容tab
|
||||||
const contentTabList = computed(() => {
|
const contentTabList = computed(() => {
|
||||||
// HTTP 协议 tabs
|
// HTTP 协议 tabs
|
||||||
if (isHttpProtocol.value) {
|
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) {
|
if (props.isDefinition) {
|
||||||
// 接口定义,定义模式隐藏前后置、断言
|
// 接口定义,定义模式隐藏前后置、断言
|
||||||
return requestVModel.value.mode === 'debug'
|
return requestVModel.value.mode === 'debug'
|
||||||
|
@ -1214,6 +1237,14 @@
|
||||||
} else if (protocolOptions.value.length === 0) {
|
} else if (protocolOptions.value.length === 0) {
|
||||||
await initProtocolList();
|
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) {
|
if (props.request.isExecute && !requestVModel.value.executeLoading) {
|
||||||
// 如果是执行操作打开接口详情,且该接口不在执行状态中,则立即执行
|
// 如果是执行操作打开接口详情,且该接口不在执行状态中,则立即执行
|
||||||
execute(isPriorityLocalExec.value ? 'localExec' : 'serverExec');
|
execute(isPriorityLocalExec.value ? 'localExec' : 'serverExec');
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
<caseTable
|
<caseTable
|
||||||
:is-api="true"
|
:is-api="true"
|
||||||
:active-module="props.activeModule"
|
:active-module="props.activeModule"
|
||||||
:protocol="props.protocol"
|
:protocol="activeApiTab.protocol"
|
||||||
:api-detail="activeApiTab"
|
:api-detail="activeApiTab"
|
||||||
/>
|
/>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
|
|
@ -234,85 +234,87 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a-collapse-item>
|
</a-collapse-item>
|
||||||
<a-collapse-item
|
<a-spin :loading="previewDetail.executeLoading" class="w-full">
|
||||||
v-if="
|
<a-collapse-item
|
||||||
previewDetail.responseDefinition &&
|
v-if="
|
||||||
previewDetail.responseDefinition.length > 0 &&
|
previewDetail.responseDefinition &&
|
||||||
props.detail.protocol === 'HTTP'
|
previewDetail.responseDefinition.length > 0 &&
|
||||||
"
|
props.detail.protocol === 'HTTP'
|
||||||
key="response"
|
"
|
||||||
>
|
key="response"
|
||||||
<template #header>
|
|
||||||
<div class="flex items-center gap-[4px]">
|
|
||||||
<div v-if="activeDetailKey.includes('response')" class="down-icon">
|
|
||||||
<icon-down :size="10" class="block" />
|
|
||||||
</div>
|
|
||||||
<div v-else class="h-[16px] w-[16px] !rounded-full p-[4px]">
|
|
||||||
<icon-right :size="10" class="block" />
|
|
||||||
</div>
|
|
||||||
<div class="font-medium">{{ t('apiTestManagement.responseContent') }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<MsEditableTab
|
|
||||||
v-model:active-tab="activeResponse"
|
|
||||||
:tabs="previewDetail.responseDefinition?.map((e) => ({ ...e, closable: false })) || []"
|
|
||||||
hide-more-action
|
|
||||||
readonly
|
|
||||||
class="my-[8px]"
|
|
||||||
>
|
>
|
||||||
<template #label="{ tab }">
|
<template #header>
|
||||||
<div class="response-tab">
|
<div class="flex items-center gap-[4px]">
|
||||||
<div v-if="tab.defaultFlag" class="response-tab-default-icon"></div>
|
<div v-if="activeDetailKey.includes('response')" class="down-icon">
|
||||||
{{ t(tab.label || tab.name) }}({{ tab.statusCode }})
|
<icon-down :size="10" class="block" />
|
||||||
|
</div>
|
||||||
|
<div v-else class="h-[16px] w-[16px] !rounded-full p-[4px]">
|
||||||
|
<icon-right :size="10" class="block" />
|
||||||
|
</div>
|
||||||
|
<div class="font-medium">{{ t('apiTestManagement.responseContent') }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</MsEditableTab>
|
<MsEditableTab
|
||||||
<div class="detail-item !pt-0">
|
v-model:active-tab="activeResponse"
|
||||||
<div class="detail-item-title">
|
:tabs="previewDetail.responseDefinition?.map((e) => ({ ...e, closable: false })) || []"
|
||||||
<div class="detail-item-title-text">
|
hide-more-action
|
||||||
{{ `${t('apiTestDebug.responseBody')}-${activeResponse?.body.bodyType}` }}
|
readonly
|
||||||
</div>
|
class="my-[8px]"
|
||||||
</div>
|
|
||||||
<MsFormTable
|
|
||||||
v-if="activeResponse?.body.bodyType === ResponseBodyFormat.BINARY"
|
|
||||||
:columns="responseBodyColumns"
|
|
||||||
:data="responseBodyTableData"
|
|
||||||
:selectable="false"
|
|
||||||
/>
|
|
||||||
<MsCodeEditor
|
|
||||||
v-else
|
|
||||||
:model-value="responseCode"
|
|
||||||
class="flex-1"
|
|
||||||
theme="vs"
|
|
||||||
height="200px"
|
|
||||||
:language="responseCodeLanguage"
|
|
||||||
:show-full-screen="false"
|
|
||||||
:show-theme-change="false"
|
|
||||||
read-only
|
|
||||||
>
|
>
|
||||||
<template #rightTitle>
|
<template #label="{ tab }">
|
||||||
<a-button
|
<div class="response-tab">
|
||||||
type="outline"
|
<div v-if="tab.defaultFlag" class="response-tab-default-icon"></div>
|
||||||
class="arco-btn-outline--secondary p-[0_8px]"
|
{{ t(tab.label || tab.name) }}({{ tab.statusCode }})
|
||||||
size="mini"
|
</div>
|
||||||
@click="copyScript(responseCode || '')"
|
|
||||||
>
|
|
||||||
<template #icon>
|
|
||||||
<MsIcon type="icon-icon_copy_outlined" class="text-var(--color-text-4)" size="12" />
|
|
||||||
</template>
|
|
||||||
</a-button>
|
|
||||||
</template>
|
</template>
|
||||||
</MsCodeEditor>
|
</MsEditableTab>
|
||||||
</div>
|
<div class="detail-item !pt-0">
|
||||||
<div v-if="activeResponse?.headers && activeResponse?.headers.length > 0" class="detail-item">
|
<div class="detail-item-title">
|
||||||
<div class="detail-item-title">
|
<div class="detail-item-title-text">
|
||||||
<div class="detail-item-title-text">
|
{{ `${t('apiTestDebug.responseBody')}-${activeResponse?.body.bodyType}` }}
|
||||||
{{ t('apiTestDebug.responseHeader') }}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<MsFormTable
|
||||||
|
v-if="activeResponse?.body.bodyType === ResponseBodyFormat.BINARY"
|
||||||
|
:columns="responseBodyColumns"
|
||||||
|
:data="responseBodyTableData"
|
||||||
|
:selectable="false"
|
||||||
|
/>
|
||||||
|
<MsCodeEditor
|
||||||
|
v-else
|
||||||
|
:model-value="responseCode"
|
||||||
|
class="flex-1"
|
||||||
|
theme="vs"
|
||||||
|
height="200px"
|
||||||
|
:language="responseCodeLanguage"
|
||||||
|
:show-full-screen="false"
|
||||||
|
:show-theme-change="false"
|
||||||
|
read-only
|
||||||
|
>
|
||||||
|
<template #rightTitle>
|
||||||
|
<a-button
|
||||||
|
type="outline"
|
||||||
|
class="arco-btn-outline--secondary p-[0_8px]"
|
||||||
|
size="mini"
|
||||||
|
@click="copyScript(responseCode || '')"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<MsIcon type="icon-icon_copy_outlined" class="text-var(--color-text-4)" size="12" />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</MsCodeEditor>
|
||||||
</div>
|
</div>
|
||||||
<MsFormTable :columns="responseHeaderColumns" :data="activeResponse?.headers || []" :selectable="false" />
|
<div v-if="activeResponse?.headers && activeResponse?.headers.length > 0" class="detail-item">
|
||||||
</div>
|
<div class="detail-item-title">
|
||||||
</a-collapse-item>
|
<div class="detail-item-title-text">
|
||||||
|
{{ t('apiTestDebug.responseHeader') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<MsFormTable :columns="responseHeaderColumns" :data="activeResponse?.headers || []" :selectable="false" />
|
||||||
|
</div>
|
||||||
|
</a-collapse-item>
|
||||||
|
</a-spin>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,13 @@
|
||||||
<div class="h-full w-full overflow-hidden">
|
<div class="h-full w-full overflow-hidden">
|
||||||
<a-tabs v-model:active-key="activeKey" class="h-full px-[16px]" animation lazy-load>
|
<a-tabs v-model:active-key="activeKey" class="h-full px-[16px]" animation lazy-load>
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<div v-show="!props.isDrawer" class="flex gap-[12px]">
|
<div class="flex gap-[12px]">
|
||||||
<a-button type="primary">
|
<environmentSelect v-if="props.isDrawer" ref="environmentSelectRef" />
|
||||||
{{ t('apiTestManagement.execute') }}
|
<execute v-model:detail="caseDetail" :environment-id="environmentId as string" />
|
||||||
</a-button>
|
|
||||||
<a-dropdown position="br" :hide-on-select="false" @select="handleSelect">
|
<a-dropdown position="br" :hide-on-select="false" @select="handleSelect">
|
||||||
<a-button>{{ t('common.operation') }}</a-button>
|
<a-button v-if="!props.isDrawer">{{ t('common.operation') }}</a-button>
|
||||||
<template #content>
|
<template #content>
|
||||||
<a-doption value="edit">
|
<a-doption v-permission="['PROJECT_API_DEFINITION_CASE:READ+UPDATE']" value="edit">
|
||||||
<MsIcon type="icon-icon_edit_outlined" />
|
<MsIcon type="icon-icon_edit_outlined" />
|
||||||
{{ t('common.edit') }}
|
{{ t('common.edit') }}
|
||||||
</a-doption>
|
</a-doption>
|
||||||
|
@ -17,15 +16,19 @@
|
||||||
<MsIcon type="icon-icon_share1" />
|
<MsIcon type="icon-icon_share1" />
|
||||||
{{ t('common.share') }}
|
{{ t('common.share') }}
|
||||||
</a-doption>
|
</a-doption>
|
||||||
<a-doption value="fork">
|
<a-doption v-permission="['PROJECT_API_DEFINITION_CASE:READ+UPDATE']" value="fork">
|
||||||
<MsIcon
|
<MsIcon
|
||||||
:type="caseDetail.follow ? 'icon-icon_collect_filled' : 'icon-icon_collection_outlined'"
|
:type="caseDetail.follow ? 'icon-icon_collect_filled' : 'icon-icon_collection_outlined'"
|
||||||
:class="`${caseDetail.follow ? 'text-[rgb(var(--warning-6))]' : ''}`"
|
:class="`${caseDetail.follow ? 'text-[rgb(var(--warning-6))]' : ''}`"
|
||||||
/>
|
/>
|
||||||
{{ t('common.fork') }}
|
{{ t('common.fork') }}
|
||||||
</a-doption>
|
</a-doption>
|
||||||
<a-divider margin="4px" />
|
<a-divider v-permission="['PROJECT_API_DEFINITION_CASE:READ+DELETE']" margin="4px" />
|
||||||
<a-doption class="error-6 text-[rgb(var(--danger-6))]">
|
<a-doption
|
||||||
|
v-permission="['PROJECT_API_DEFINITION_CASE:READ+DELETE']"
|
||||||
|
value="delete"
|
||||||
|
class="error-6 text-[rgb(var(--danger-6))]"
|
||||||
|
>
|
||||||
<MsIcon type="icon-icon_delete-trash_outlined" class="text-[rgb(var(--danger-6))]" />
|
<MsIcon type="icon-icon_delete-trash_outlined" class="text-[rgb(var(--danger-6))]" />
|
||||||
{{ t('common.delete') }}
|
{{ t('common.delete') }}
|
||||||
</a-doption>
|
</a-doption>
|
||||||
|
@ -35,11 +38,6 @@
|
||||||
</template>
|
</template>
|
||||||
<a-tab-pane key="detail" :title="t('apiTestManagement.detail')" class="px-[18px] py-[16px]">
|
<a-tab-pane key="detail" :title="t('apiTestManagement.detail')" class="px-[18px] py-[16px]">
|
||||||
<MsDetailCard :title="`【${caseDetail.num}】${caseDetail.name}`" :description="description" class="mb-[8px]">
|
<MsDetailCard :title="`【${caseDetail.num}】${caseDetail.name}`" :description="description" class="mb-[8px]">
|
||||||
<template #titleAppend>
|
|
||||||
<a-button v-show="props.isDrawer" type="primary" size="mini">
|
|
||||||
{{ t('apiTestManagement.execute') }}
|
|
||||||
</a-button>
|
|
||||||
</template>
|
|
||||||
<template #type="{ value }">
|
<template #type="{ value }">
|
||||||
<apiMethodName :method="value as RequestMethods" tag-size="small" is-tag />
|
<apiMethodName :method="value as RequestMethods" tag-size="small" is-tag />
|
||||||
</template>
|
</template>
|
||||||
|
@ -47,13 +45,13 @@
|
||||||
<caseLevel :case-level="value as CaseLevel" />
|
<caseLevel :case-level="value as CaseLevel" />
|
||||||
</template>
|
</template>
|
||||||
</MsDetailCard>
|
</MsDetailCard>
|
||||||
<detailTab :detail="caseDetail" :protocols="protocols" is-case />
|
<detailTab :detail="caseDetail" :protocols="protocols as ProtocolItem[]" is-case />
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="reference" :title="t('apiTestManagement.reference')" class="px-[18px] py-[16px]">
|
<a-tab-pane key="reference" :title="t('apiTestManagement.reference')" class="px-[18px] py-[16px]">
|
||||||
<tab-case-dependency :source-id="caseDetail.id" />
|
<tab-case-dependency :source-id="caseDetail.id" />
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="executeHistory" :title="t('apiTestManagement.executeHistory')" class="px-[18px] py-[16px]">
|
<a-tab-pane key="executeHistory" :title="t('apiTestManagement.executeHistory')" class="px-[18px] py-[16px]">
|
||||||
<tab-case-execute-history :source-id="caseDetail.id" module-type="API_REPORT" :protocol="props.protocol" />
|
<tab-case-execute-history :source-id="caseDetail.id" module-type="API_REPORT" :protocol="caseDetail.protocol" />
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<!-- <a-tab-pane key="dependencies" :title="t('apiTestManagement.dependencies')" class="px-[18px] py-[16px]">
|
<!-- <a-tab-pane key="dependencies" :title="t('apiTestManagement.dependencies')" class="px-[18px] py-[16px]">
|
||||||
</a-tab-pane> -->
|
</a-tab-pane> -->
|
||||||
|
@ -62,7 +60,7 @@
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
</div>
|
</div>
|
||||||
<createAndEditCaseDrawer ref="createAndEditCaseDrawerRef" :protocol="props.protocol" v-bind="$attrs" />
|
<createAndEditCaseDrawer ref="createAndEditCaseDrawerRef" v-bind="$attrs" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -74,33 +72,41 @@
|
||||||
import MsDetailCard from '@/components/pure/ms-detail-card/index.vue';
|
import MsDetailCard from '@/components/pure/ms-detail-card/index.vue';
|
||||||
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
||||||
import type { CaseLevel } from '@/components/business/ms-case-associate/types';
|
import type { CaseLevel } from '@/components/business/ms-case-associate/types';
|
||||||
|
import environmentSelect from '../../environmentSelect.vue';
|
||||||
import detailTab from '../api/preview/detail.vue';
|
import detailTab from '../api/preview/detail.vue';
|
||||||
import createAndEditCaseDrawer from './createAndEditCaseDrawer.vue';
|
import createAndEditCaseDrawer from './createAndEditCaseDrawer.vue';
|
||||||
|
import execute from './execute.vue';
|
||||||
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
||||||
import { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
import { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||||
import TabCaseChangeHistory from '@/views/api-test/management/components/management/case/tabContent/tabCaseChangeHistory.vue';
|
import TabCaseChangeHistory from '@/views/api-test/management/components/management/case/tabContent/tabCaseChangeHistory.vue';
|
||||||
import TabCaseDependency from '@/views/api-test/management/components/management/case/tabContent/tabCaseDependency.vue';
|
import TabCaseDependency from '@/views/api-test/management/components/management/case/tabContent/tabCaseDependency.vue';
|
||||||
import TabCaseExecuteHistory from '@/views/api-test/management/components/management/case/tabContent/tabCaseExecuteHistory.vue';
|
import TabCaseExecuteHistory from '@/views/api-test/management/components/management/case/tabContent/tabCaseExecuteHistory.vue';
|
||||||
|
|
||||||
import { getProtocolList } from '@/api/modules/api-test/common';
|
import { deleteCase, toggleFollowCase } from '@/api/modules/api-test/management';
|
||||||
import { toggleFollowCase } from '@/api/modules/api-test/management';
|
import useModal from '@/hooks/useModal';
|
||||||
import useAppStore from '@/store/modules/app';
|
|
||||||
|
|
||||||
import { ProtocolItem } from '@/models/apiTest/common';
|
import { ProtocolItem } from '@/models/apiTest/common';
|
||||||
|
import { EnvConfig } from '@/models/projectManagement/environmental';
|
||||||
import { RequestMethods } from '@/enums/apiEnum';
|
import { RequestMethods } from '@/enums/apiEnum';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
isDrawer?: boolean; // 抽屉
|
isDrawer?: boolean; // 抽屉
|
||||||
detail: RequestParam;
|
detail: RequestParam;
|
||||||
protocol: string;
|
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits(['updateFollow']);
|
const emit = defineEmits<{
|
||||||
|
(e: 'updateFollow'): void;
|
||||||
|
(e: 'deleteCase', id: string): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
const { copy, isSupported } = useClipboard();
|
const { copy, isSupported } = useClipboard();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const appStore = useAppStore();
|
const { openModal } = useModal();
|
||||||
|
|
||||||
|
const caseDetail = ref<RequestParam>(cloneDeep(props.detail)); // props.detail是嵌套的引用类型,防止不必要的修改来源影响props.detail的数据
|
||||||
|
watchEffect(() => {
|
||||||
|
caseDetail.value = cloneDeep(props.detail); // props.detail是嵌套的引用类型,防止不必要的修改来源影响props.detail的数据
|
||||||
|
});
|
||||||
|
|
||||||
const caseDetail = computed<RequestParam>(() => cloneDeep(props.detail)); // props.detail是嵌套的引用类型,防止不必要的修改来源影响props.detail的数据
|
|
||||||
const activeKey = ref('detail');
|
const activeKey = ref('detail');
|
||||||
|
|
||||||
const description = computed(() => [
|
const description = computed(() => [
|
||||||
|
@ -126,20 +132,6 @@
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const protocols = ref<ProtocolItem[]>([]);
|
|
||||||
async function initProtocolList() {
|
|
||||||
try {
|
|
||||||
protocols.value = await getProtocolList(appStore.currentOrgId);
|
|
||||||
} catch (error) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onBeforeMount(() => {
|
|
||||||
initProtocolList();
|
|
||||||
});
|
|
||||||
|
|
||||||
const followLoading = ref(false);
|
const followLoading = ref(false);
|
||||||
async function follow() {
|
async function follow() {
|
||||||
try {
|
try {
|
||||||
|
@ -157,7 +149,7 @@
|
||||||
|
|
||||||
function share() {
|
function share() {
|
||||||
if (isSupported) {
|
if (isSupported) {
|
||||||
copy(`${window.location.href}&dId=${caseDetail.value.id}`);
|
copy(`${window.location.href}&cId=${caseDetail.value.id}`);
|
||||||
Message.success(t('apiTestManagement.shareUrlCopied'));
|
Message.success(t('apiTestManagement.shareUrlCopied'));
|
||||||
} else {
|
} else {
|
||||||
Message.error(t('common.copyNotSupport'));
|
Message.error(t('common.copyNotSupport'));
|
||||||
|
@ -169,6 +161,30 @@
|
||||||
createAndEditCaseDrawerRef.value?.open(caseDetail.value.apiDefinitionId, caseDetail.value, false);
|
createAndEditCaseDrawerRef.value?.open(caseDetail.value.apiDefinitionId, caseDetail.value, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleDelete() {
|
||||||
|
openModal({
|
||||||
|
type: 'error',
|
||||||
|
title: t('apiTestManagement.deleteApiTipTitle', { name: caseDetail.value.name }),
|
||||||
|
content: t('case.deleteCaseTip'),
|
||||||
|
okText: t('common.confirmDelete'),
|
||||||
|
cancelText: t('common.cancel'),
|
||||||
|
okButtonProps: {
|
||||||
|
status: 'danger',
|
||||||
|
},
|
||||||
|
maskClosable: false,
|
||||||
|
onBeforeOk: async () => {
|
||||||
|
try {
|
||||||
|
await deleteCase(caseDetail.value.id as string);
|
||||||
|
emit('deleteCase', caseDetail.value.id as string);
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hideCancel: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function handleSelect(val: string | number | Record<string, any> | undefined) {
|
function handleSelect(val: string | number | Record<string, any> | undefined) {
|
||||||
switch (val) {
|
switch (val) {
|
||||||
case 'edit':
|
case 'edit':
|
||||||
|
@ -180,15 +196,28 @@
|
||||||
case 'fork':
|
case 'fork':
|
||||||
follow();
|
follow();
|
||||||
break;
|
break;
|
||||||
|
case 'delete':
|
||||||
|
handleDelete();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const protocols = inject<Ref<ProtocolItem[]>>('protocols');
|
||||||
|
|
||||||
|
const environmentSelectRef = ref<InstanceType<typeof environmentSelect>>();
|
||||||
|
const currentEnvConfigByDrawer = computed<EnvConfig | undefined>(() => environmentSelectRef.value?.currentEnvConfig);
|
||||||
|
const currentEnvConfigByInject = inject<Ref<EnvConfig>>('currentEnvConfig');
|
||||||
|
const environmentId = computed(() =>
|
||||||
|
props.isDrawer ? currentEnvConfigByDrawer.value?.id : currentEnvConfigByInject?.value?.id
|
||||||
|
);
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
editCase,
|
editCase,
|
||||||
share,
|
share,
|
||||||
follow,
|
follow,
|
||||||
|
handleDelete,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -196,6 +225,9 @@
|
||||||
:deep(.arco-tabs-nav) {
|
:deep(.arco-tabs-nav) {
|
||||||
border-bottom: 1px solid var(--color-text-n8);
|
border-bottom: 1px solid var(--color-text-n8);
|
||||||
}
|
}
|
||||||
|
:deep(.arco-tabs-nav-extra) {
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
:deep(.arco-tabs-content) {
|
:deep(.arco-tabs-content) {
|
||||||
@apply pt-0;
|
@apply pt-0;
|
||||||
.arco-tabs-content-item {
|
.arco-tabs-content-item {
|
||||||
|
|
|
@ -7,9 +7,6 @@
|
||||||
:footer="false"
|
:footer="false"
|
||||||
no-content-padding
|
no-content-padding
|
||||||
>
|
>
|
||||||
<template #headerLeft>
|
|
||||||
<environmentSelect ref="environmentSelectRef" class="ml-[16px]" />
|
|
||||||
</template>
|
|
||||||
<template #tbutton>
|
<template #tbutton>
|
||||||
<div class="flex items-center gap-[4px]">
|
<div class="flex items-center gap-[4px]">
|
||||||
<MsButton
|
<MsButton
|
||||||
|
@ -39,7 +36,7 @@
|
||||||
{{ t('common.fork') }}
|
{{ t('common.fork') }}
|
||||||
</MsButton>
|
</MsButton>
|
||||||
<MsButton type="icon" status="secondary">
|
<MsButton type="icon" status="secondary">
|
||||||
<a-dropdown position="br">
|
<a-dropdown position="br" @select="handleSelect">
|
||||||
<div>
|
<div>
|
||||||
<icon-more class="mr-[8px]" />
|
<icon-more class="mr-[8px]" />
|
||||||
<span> {{ t('common.more') }}</span>
|
<span> {{ t('common.more') }}</span>
|
||||||
|
@ -47,6 +44,7 @@
|
||||||
<template #content>
|
<template #content>
|
||||||
<a-doption
|
<a-doption
|
||||||
v-permission="['PROJECT_API_DEFINITION_CASE:READ+DELETE']"
|
v-permission="['PROJECT_API_DEFINITION_CASE:READ+DELETE']"
|
||||||
|
value="delete"
|
||||||
class="error-6 text-[rgb(var(--danger-6))]"
|
class="error-6 text-[rgb(var(--danger-6))]"
|
||||||
>
|
>
|
||||||
<MsIcon type="icon-icon_delete-trash_outlined" class="text-[rgb(var(--danger-6))]" />
|
<MsIcon type="icon-icon_delete-trash_outlined" class="text-[rgb(var(--danger-6))]" />
|
||||||
|
@ -57,14 +55,7 @@
|
||||||
</MsButton>
|
</MsButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<caseDetail
|
<caseDetail ref="caseDerailRef" is-drawer :detail="props.detail" :api-detail="props.apiDetail" v-bind="$attrs" />
|
||||||
ref="caseDerailRef"
|
|
||||||
is-drawer
|
|
||||||
:detail="props.detail"
|
|
||||||
:protocol="props.protocol"
|
|
||||||
:api-detail="props.apiDetail"
|
|
||||||
v-bind="$attrs"
|
|
||||||
/>
|
|
||||||
</MsDrawer>
|
</MsDrawer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -72,7 +63,6 @@
|
||||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||||
import environmentSelect from '../../environmentSelect.vue';
|
|
||||||
import caseDetail from './caseDetail.vue';
|
import caseDetail from './caseDetail.vue';
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
@ -81,7 +71,6 @@
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
detail: RequestParam;
|
detail: RequestParam;
|
||||||
protocol: string;
|
|
||||||
apiDetail: RequestParam;
|
apiDetail: RequestParam;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
@ -91,6 +80,16 @@
|
||||||
required: true,
|
required: true,
|
||||||
});
|
});
|
||||||
const caseDerailRef = ref<InstanceType<typeof caseDetail>>();
|
const caseDerailRef = ref<InstanceType<typeof caseDetail>>();
|
||||||
|
|
||||||
|
function handleSelect(val: string | number | Record<string, any> | undefined) {
|
||||||
|
switch (val) {
|
||||||
|
case 'delete':
|
||||||
|
caseDerailRef.value?.handleDelete();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
</template>
|
</template>
|
||||||
<template #caseLevel="{ record }">
|
<template #caseLevel="{ record }">
|
||||||
<a-select
|
<a-select
|
||||||
|
v-if="hasAnyPermission(['PROJECT_API_DEFINITION_CASE:READ+UPDATE'])"
|
||||||
v-model:model-value="record.priority"
|
v-model:model-value="record.priority"
|
||||||
:placeholder="t('common.pleaseSelect')"
|
:placeholder="t('common.pleaseSelect')"
|
||||||
class="param-input w-full"
|
class="param-input w-full"
|
||||||
|
@ -56,6 +57,7 @@
|
||||||
<caseLevel :case-level="item.text" />
|
<caseLevel :case-level="item.text" />
|
||||||
</a-option>
|
</a-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
|
<span v-else class="text-[var(--color-text-2)]"> <caseLevel :case-level="record.priority" /></span>
|
||||||
</template>
|
</template>
|
||||||
<template #caseLevelFilter="{ columnConfig }">
|
<template #caseLevelFilter="{ columnConfig }">
|
||||||
<a-trigger v-model:popup-visible="caseFilterVisible" trigger="click" @popup-visible-change="handleFilterHidden">
|
<a-trigger v-model:popup-visible="caseFilterVisible" trigger="click" @popup-visible-change="handleFilterHidden">
|
||||||
|
@ -78,6 +80,7 @@
|
||||||
</template>
|
</template>
|
||||||
<template #status="{ record }">
|
<template #status="{ record }">
|
||||||
<a-select
|
<a-select
|
||||||
|
v-if="hasAnyPermission(['PROJECT_API_DEFINITION_CASE:READ+UPDATE'])"
|
||||||
v-model:model-value="record.status"
|
v-model:model-value="record.status"
|
||||||
:placeholder="t('common.pleaseSelect')"
|
:placeholder="t('common.pleaseSelect')"
|
||||||
class="param-input w-full"
|
class="param-input w-full"
|
||||||
|
@ -91,6 +94,7 @@
|
||||||
<apiStatus :status="item" size="small" />
|
<apiStatus :status="item" size="small" />
|
||||||
</a-option>
|
</a-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
|
<apiStatus v-else :status="record.status" size="small" />
|
||||||
</template>
|
</template>
|
||||||
<template #statusFilter="{ columnConfig }">
|
<template #statusFilter="{ columnConfig }">
|
||||||
<a-trigger
|
<a-trigger
|
||||||
|
@ -153,12 +157,26 @@
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #action="{ record }">
|
<template #operation="{ record }">
|
||||||
<MsButton type="text" class="!mr-0" @click="onExecute(record.id)">
|
<MsButton
|
||||||
|
v-permission="['PROJECT_API_DEFINITION_CASE:READ+EXECUTE']"
|
||||||
|
type="text"
|
||||||
|
class="!mr-0"
|
||||||
|
@click="onExecute(record.id)"
|
||||||
|
>
|
||||||
{{ t('apiTestManagement.execute') }}
|
{{ t('apiTestManagement.execute') }}
|
||||||
</MsButton>
|
</MsButton>
|
||||||
<a-divider direction="vertical" :margin="8"></a-divider>
|
<a-divider
|
||||||
<MsButton type="text" class="!mr-0" @click="copyCase(record)">
|
v-permission="['PROJECT_API_DEFINITION_CASE:READ+EXECUTE']"
|
||||||
|
direction="vertical"
|
||||||
|
:margin="8"
|
||||||
|
></a-divider>
|
||||||
|
<MsButton
|
||||||
|
v-permission="['PROJECT_API_DEFINITION_CASE:READ+ADD']"
|
||||||
|
type="text"
|
||||||
|
class="!mr-0"
|
||||||
|
@click="copyCase(record)"
|
||||||
|
>
|
||||||
{{ t('common.copy') }}
|
{{ t('common.copy') }}
|
||||||
</MsButton>
|
</MsButton>
|
||||||
<a-divider direction="vertical" :margin="8"></a-divider>
|
<a-divider direction="vertical" :margin="8"></a-divider>
|
||||||
|
@ -239,17 +257,16 @@
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<createAndEditCaseDrawer
|
<createAndEditCaseDrawer
|
||||||
ref="createAndEditCaseDrawerRef"
|
ref="createAndEditCaseDrawerRef"
|
||||||
:protocol="props.protocol"
|
|
||||||
:api-detail="apiDetail"
|
:api-detail="apiDetail"
|
||||||
@load-case="loadCaseListAndResetSelector()"
|
@load-case="loadCaseListAndResetSelector()"
|
||||||
/>
|
/>
|
||||||
<caseDetailDrawer
|
<caseDetailDrawer
|
||||||
v-model:visible="caseDetailDrawerVisible"
|
v-model:visible="caseDetailDrawerVisible"
|
||||||
:detail="caseDetail as RequestParam"
|
:detail="caseDetail as RequestParam"
|
||||||
:protocol="props.protocol"
|
|
||||||
:api-detail="apiDetail as RequestParam"
|
:api-detail="apiDetail as RequestParam"
|
||||||
@update-follow="caseDetail.follow = !caseDetail.follow"
|
@update-follow="caseDetail.follow = !caseDetail.follow"
|
||||||
@load-case="(id: string) => loadCase(id)"
|
@load-case="(id: string) => loadCase(id)"
|
||||||
|
@delete-case="deleteCaseByDetail"
|
||||||
/>
|
/>
|
||||||
<a-modal v-model:visible="showBatchExecute" title-align="start" class="ms-modal-upload ms-modal-medium" :width="480">
|
<a-modal v-model:visible="showBatchExecute" title-align="start" class="ms-modal-upload ms-modal-medium" :width="480">
|
||||||
<template #title>
|
<template #title>
|
||||||
|
@ -381,6 +398,7 @@
|
||||||
import useModal from '@/hooks/useModal';
|
import useModal from '@/hooks/useModal';
|
||||||
import useTableStore from '@/hooks/useTableStore';
|
import useTableStore from '@/hooks/useTableStore';
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
|
import { hasAnyPermission } from '@/utils/permission';
|
||||||
|
|
||||||
import { ApiCaseDetail, Environment } from '@/models/apiTest/management';
|
import { ApiCaseDetail, Environment } from '@/models/apiTest/management';
|
||||||
import { DragSortParams } from '@/models/common';
|
import { DragSortParams } from '@/models/common';
|
||||||
|
@ -410,6 +428,13 @@
|
||||||
const keyword = ref('');
|
const keyword = ref('');
|
||||||
const refreshModuleTree: (() => Promise<any>) | undefined = inject('refreshModuleTree');
|
const refreshModuleTree: (() => Promise<any>) | undefined = inject('refreshModuleTree');
|
||||||
|
|
||||||
|
const hasOperationPermission = computed(() =>
|
||||||
|
hasAnyPermission([
|
||||||
|
'PROJECT_API_DEFINITION_CASE:READ+DELETE',
|
||||||
|
'PROJECT_API_DEFINITION_CASE:READ+ADD',
|
||||||
|
'PROJECT_API_DEFINITION_CASE:READ+EXECUTE',
|
||||||
|
])
|
||||||
|
);
|
||||||
const columns: MsTableColumn = [
|
const columns: MsTableColumn = [
|
||||||
{
|
{
|
||||||
title: 'ID',
|
title: 'ID',
|
||||||
|
@ -529,14 +554,13 @@
|
||||||
width: 180,
|
width: 180,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'common.operation',
|
title: hasOperationPermission.value ? 'common.operation' : '',
|
||||||
slotName: 'action',
|
slotName: 'operation',
|
||||||
dataIndex: 'operation',
|
dataIndex: 'operation',
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
width: 150,
|
width: hasOperationPermission.value ? 150 : 50,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
await tableStore.initColumn(TableKeyEnum.API_TEST_MANAGEMENT_CASE, columns, 'drawer');
|
|
||||||
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(getCasePage, {
|
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(getCasePage, {
|
||||||
columns,
|
columns,
|
||||||
scroll: { x: '100%' },
|
scroll: { x: '100%' },
|
||||||
|
@ -666,6 +690,7 @@
|
||||||
watch(
|
watch(
|
||||||
() => props.protocol,
|
() => props.protocol,
|
||||||
() => {
|
() => {
|
||||||
|
if (props.isApi) return;
|
||||||
loadCaseListAndResetSelector();
|
loadCaseListAndResetSelector();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -980,16 +1005,15 @@
|
||||||
async function getCaseDetailInfo(id: string) {
|
async function getCaseDetailInfo(id: string) {
|
||||||
try {
|
try {
|
||||||
const res = await getCaseDetail(id);
|
const res = await getCaseDetail(id);
|
||||||
const parseRequestBodyResult = parseRequestBodyFiles(res.request.body); // 解析请求体中的文件,将详情中的文件 id 集合收集,更新时以判断文件是否删除以及是否新上传的文件;
|
let parseRequestBodyResult;
|
||||||
// if (res.protocol === 'HTTP') { // TODO: 后端没protocol字段,问一下
|
if (res.protocol === 'HTTP') {
|
||||||
// parseRequestBodyResult = parseRequestBodyFiles(res.request.body); // 解析请求体中的文件,将详情中的文件 id 集合收集,更新时以判断文件是否删除以及是否新上传的文件
|
parseRequestBodyResult = parseRequestBodyFiles(res.request.body); // 解析请求体中的文件,将详情中的文件 id 集合收集,更新时以判断文件是否删除以及是否新上传的文件
|
||||||
// }
|
}
|
||||||
caseDetail.value = {
|
caseDetail.value = {
|
||||||
...cloneDeep(defaultCaseParams as RequestParam),
|
...cloneDeep(defaultCaseParams as RequestParam),
|
||||||
...({
|
...({
|
||||||
...res.request,
|
...res.request,
|
||||||
...res,
|
...res,
|
||||||
// responseDefinition: res.response.map((e) => ({ ...e, responseActiveTab: ResponseComposition.BODY })), // TODO: 后端没response字段,问一下
|
|
||||||
url: res.path,
|
url: res.path,
|
||||||
...parseRequestBodyResult,
|
...parseRequestBodyResult,
|
||||||
} as Partial<TabItem>),
|
} as Partial<TabItem>),
|
||||||
|
@ -1004,11 +1028,22 @@
|
||||||
caseDetailDrawerVisible.value = true;
|
caseDetailDrawerVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deleteCaseByDetail() {
|
||||||
|
caseDetailDrawerVisible.value = false;
|
||||||
|
loadCaseList();
|
||||||
|
}
|
||||||
|
|
||||||
// 在api下的用例里打开用例详情抽屉,点击编辑,编辑后在此刷新数据
|
// 在api下的用例里打开用例详情抽屉,点击编辑,编辑后在此刷新数据
|
||||||
async function loadCase(id: string) {
|
function loadCase(id: string) {
|
||||||
getCaseDetailInfo(id);
|
getCaseDetailInfo(id);
|
||||||
loadCaseList();
|
loadCaseList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
loadCaseList,
|
||||||
|
});
|
||||||
|
|
||||||
|
await tableStore.initColumn(TableKeyEnum.API_TEST_MANAGEMENT_CASE, columns, 'drawer');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
:title="isEdit ? t('case.updateCase') : t('case.createCase')"
|
:title="isEdit ? t('case.updateCase') : t('case.createCase')"
|
||||||
:width="894"
|
:width="894"
|
||||||
no-content-padding
|
no-content-padding
|
||||||
|
unmount-on-close
|
||||||
:ok-text="isEdit ? 'common.update' : 'common.create'"
|
:ok-text="isEdit ? 'common.update' : 'common.create'"
|
||||||
:ok-loading="drawerLoading"
|
:ok-loading="drawerLoading"
|
||||||
:save-continue-text="t('case.saveContinueText')"
|
:save-continue-text="t('case.saveContinueText')"
|
||||||
|
@ -12,9 +13,6 @@
|
||||||
@continue="handleDrawerConfirm(true)"
|
@continue="handleDrawerConfirm(true)"
|
||||||
@cancel="handleSaveCaseCancel"
|
@cancel="handleSaveCaseCancel"
|
||||||
>
|
>
|
||||||
<template #headerLeft>
|
|
||||||
<environmentSelect ref="environmentSelectRef" class="ml-[16px]" />
|
|
||||||
</template>
|
|
||||||
<div class="flex h-full flex-col overflow-hidden">
|
<div class="flex h-full flex-col overflow-hidden">
|
||||||
<div class="px-[16px] pt-[16px]">
|
<div class="px-[16px] pt-[16px]">
|
||||||
<MsDetailCard
|
<MsDetailCard
|
||||||
|
@ -36,9 +34,12 @@
|
||||||
:max-length="255"
|
:max-length="255"
|
||||||
show-word-limit
|
show-word-limit
|
||||||
/>
|
/>
|
||||||
<a-button type="primary">
|
<environmentSelect ref="environmentSelectRef" />
|
||||||
{{ t('apiTestManagement.execute') }}
|
<execute
|
||||||
</a-button>
|
v-model:detail="detailForm"
|
||||||
|
:environment-id="currentEnvConfig?.id as string"
|
||||||
|
:request="requestCompositionRef?.makeRequestParams"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<div class="flex gap-[16px]">
|
<div class="flex gap-[16px]">
|
||||||
|
@ -74,6 +75,7 @@
|
||||||
ref="requestCompositionRef"
|
ref="requestCompositionRef"
|
||||||
v-model:request="detailForm"
|
v-model:request="detailForm"
|
||||||
:is-case="true"
|
:is-case="true"
|
||||||
|
:api-detail="apiDetailInfo as RequestParam"
|
||||||
hide-response-layout-switch
|
hide-response-layout-switch
|
||||||
:upload-temp-file-api="uploadTempFileCase"
|
:upload-temp-file-api="uploadTempFileCase"
|
||||||
:file-save-as-source-id="detailForm.id"
|
:file-save-as-source-id="detailForm.id"
|
||||||
|
@ -97,6 +99,7 @@
|
||||||
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
||||||
import type { CaseLevel } from '@/components/business/ms-case-associate/types';
|
import type { CaseLevel } from '@/components/business/ms-case-associate/types';
|
||||||
import environmentSelect from '../../environmentSelect.vue';
|
import environmentSelect from '../../environmentSelect.vue';
|
||||||
|
import execute from './execute.vue';
|
||||||
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
||||||
import apiStatus from '@/views/api-test/components/apiStatus.vue';
|
import apiStatus from '@/views/api-test/components/apiStatus.vue';
|
||||||
import requestComposition, { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
import requestComposition, { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||||
|
@ -161,21 +164,32 @@
|
||||||
const formRef = ref<FormInstance>();
|
const formRef = ref<FormInstance>();
|
||||||
const requestCompositionRef = ref<InstanceType<typeof requestComposition>>();
|
const requestCompositionRef = ref<InstanceType<typeof requestComposition>>();
|
||||||
const defaultCaseParams = inject<RequestParam>('defaultCaseParams');
|
const defaultCaseParams = inject<RequestParam>('defaultCaseParams');
|
||||||
const defaultDetail: RequestParam = {
|
const defaultDetail = computed<RequestParam>(() => {
|
||||||
apiDefinitionId: apiDefinitionId.value,
|
return {
|
||||||
...(defaultCaseParams as RequestParam),
|
...(defaultCaseParams as RequestParam),
|
||||||
};
|
apiDefinitionId: apiDefinitionId.value,
|
||||||
const detailForm = ref(cloneDeep(defaultDetail));
|
protocol: apiDetailInfo.value.protocol,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const detailForm = ref(cloneDeep(defaultDetail.value));
|
||||||
const isEdit = ref(false);
|
const isEdit = ref(false);
|
||||||
|
|
||||||
function open(apiId: string, record?: ApiCaseDetail | RequestParam, isCopy?: boolean) {
|
async function open(apiId: string, record?: ApiCaseDetail | RequestParam, isCopy?: boolean) {
|
||||||
apiDefinitionId.value = apiId;
|
apiDefinitionId.value = apiId;
|
||||||
// 从api下的用例里打开抽屉有api信息,从case下直接复制没有api信息
|
// 从api下的用例里打开抽屉有api信息,从case下直接复制没有api信息
|
||||||
if (props.apiDetail) {
|
if (props.apiDetail) {
|
||||||
apiDetailInfo.value = props.apiDetail;
|
apiDetailInfo.value = cloneDeep(props.apiDetail);
|
||||||
} else {
|
} else {
|
||||||
getApiDetail();
|
await getApiDetail();
|
||||||
}
|
}
|
||||||
|
// 创建或者复制的时候,请求参数为接口定义的请求参数
|
||||||
|
detailForm.value = {
|
||||||
|
...cloneDeep(defaultDetail.value),
|
||||||
|
headers: apiDetailInfo.value.headers ?? apiDetailInfo.value.request.headers,
|
||||||
|
body: apiDetailInfo.value.body ?? apiDetailInfo.value.request.body,
|
||||||
|
rest: apiDetailInfo.value.rest ?? apiDetailInfo.value.request.rest,
|
||||||
|
query: apiDetailInfo.value.query ?? apiDetailInfo.value.request.query,
|
||||||
|
};
|
||||||
// 复制
|
// 复制
|
||||||
if (isCopy) {
|
if (isCopy) {
|
||||||
detailForm.value.name = `copy_${record?.name}`;
|
detailForm.value.name = `copy_${record?.name}`;
|
||||||
|
@ -184,6 +198,7 @@
|
||||||
if (!isCopy && record?.id) {
|
if (!isCopy && record?.id) {
|
||||||
isEdit.value = true;
|
isEdit.value = true;
|
||||||
detailForm.value = cloneDeep(record as RequestParam);
|
detailForm.value = cloneDeep(record as RequestParam);
|
||||||
|
detailForm.value.isNew = false;
|
||||||
}
|
}
|
||||||
innerVisible.value = true;
|
innerVisible.value = true;
|
||||||
}
|
}
|
||||||
|
@ -193,7 +208,6 @@
|
||||||
isEdit.value = false;
|
isEdit.value = false;
|
||||||
innerVisible.value = false;
|
innerVisible.value = false;
|
||||||
formRef.value?.resetFields();
|
formRef.value?.resetFields();
|
||||||
detailForm.value = cloneDeep(defaultDetail);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDrawerConfirm(isContinue: boolean) {
|
function handleDrawerConfirm(isContinue: boolean) {
|
||||||
|
@ -236,7 +250,7 @@
|
||||||
if (!isContinue) {
|
if (!isContinue) {
|
||||||
handleSaveCaseCancel();
|
handleSaveCaseCancel();
|
||||||
}
|
}
|
||||||
detailForm.value = cloneDeep(defaultDetail);
|
detailForm.value = cloneDeep(defaultDetail.value);
|
||||||
drawerLoading.value = false;
|
drawerLoading.value = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
<template>
|
||||||
|
<a-dropdown-button
|
||||||
|
v-if="!caseDetail.executeLoading"
|
||||||
|
v-permission="['PROJECT_API_DEFINITION_CASE:READ+EXECUTE']"
|
||||||
|
class="exec-btn"
|
||||||
|
@click="() => execute(isPriorityLocalExec ? 'localExec' : 'serverExec')"
|
||||||
|
@select="execute"
|
||||||
|
>
|
||||||
|
{{ isPriorityLocalExec ? t('apiTestDebug.localExec') : t('apiTestDebug.serverExec') }}
|
||||||
|
<template v-if="hasLocalExec" #icon>
|
||||||
|
<icon-down />
|
||||||
|
</template>
|
||||||
|
<template v-if="hasLocalExec" #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" @click="stopDebug">{{ t('common.stop') }}</a-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
|
import { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||||
|
|
||||||
|
import { localExecuteApiDebug } from '@/api/modules/api-test/common';
|
||||||
|
import { debugCase, runCase } from '@/api/modules/api-test/management';
|
||||||
|
import { getSocket } from '@/api/modules/project-management/commonScript';
|
||||||
|
import { getLocalConfig } from '@/api/modules/user/index';
|
||||||
|
import useAppStore from '@/store/modules/app';
|
||||||
|
import { getGenerateId } from '@/utils';
|
||||||
|
|
||||||
|
import { defaultResponse } from '@/views/api-test/components/config';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
environmentId: string;
|
||||||
|
request?: (...args) => Record<string, any>;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const appStore = useAppStore();
|
||||||
|
|
||||||
|
const caseDetail = defineModel<RequestParam>('detail', {
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasLocalExec = ref(false); // 是否配置了api本地执行
|
||||||
|
const isPriorityLocalExec = ref(false); // 是否优先本地执行
|
||||||
|
const localExecuteUrl = ref('');
|
||||||
|
const reportId = ref('');
|
||||||
|
const websocket = ref<WebSocket>();
|
||||||
|
const temporaryResponseMap = {}; // 缓存websocket返回的报告内容,避免执行接口后切换tab导致报告丢失
|
||||||
|
|
||||||
|
async function initLocalConfig() {
|
||||||
|
if (hasLocalExec.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await getLocalConfig(); // TODO: 会报错
|
||||||
|
const apiLocalExec = res.find((e) => e.type === 'API');
|
||||||
|
if (apiLocalExec) {
|
||||||
|
hasLocalExec.value = true;
|
||||||
|
isPriorityLocalExec.value = apiLocalExec.enable || false;
|
||||||
|
localExecuteUrl.value = apiLocalExec.userUrl || '';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开启websocket监听,接收执行结果
|
||||||
|
*/
|
||||||
|
function debugSocket(executeType?: 'localExec' | 'serverExec') {
|
||||||
|
websocket.value = getSocket(
|
||||||
|
reportId.value,
|
||||||
|
executeType === 'localExec' ? '/ws/debug' : '',
|
||||||
|
executeType === 'localExec' ? localExecuteUrl.value : ''
|
||||||
|
);
|
||||||
|
websocket.value.addEventListener('message', (event) => {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
if (data.msgType === 'EXEC_RESULT') {
|
||||||
|
if (caseDetail.value.reportId === data.reportId) {
|
||||||
|
// 判断当前查看的tab是否是当前返回的报告的tab,是的话直接赋值
|
||||||
|
// TODO: 渲染出用例详情的响应数据
|
||||||
|
caseDetail.value.response = data.taskResult; // 渲染出创建用例抽屉的响应数据
|
||||||
|
caseDetail.value.executeLoading = false;
|
||||||
|
} else {
|
||||||
|
// 不是则需要把报告缓存起来,等切换到对应的tab再赋值
|
||||||
|
temporaryResponseMap[data.reportId] = data.taskResult;
|
||||||
|
}
|
||||||
|
} else if (data.msgType === 'EXEC_END') {
|
||||||
|
// 执行结束,关闭websocket
|
||||||
|
websocket.value?.close();
|
||||||
|
caseDetail.value.executeLoading = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function execute(executeType?: 'localExec' | 'serverExec') {
|
||||||
|
try {
|
||||||
|
caseDetail.value.executeLoading = true;
|
||||||
|
caseDetail.value.response = cloneDeep(defaultResponse);
|
||||||
|
const makeRequestParams = props.request && props.request(executeType); // 写在reportId之前,防止覆盖reportId
|
||||||
|
reportId.value = getGenerateId();
|
||||||
|
caseDetail.value.reportId = reportId.value; // 存储报告ID
|
||||||
|
let res;
|
||||||
|
const params = {
|
||||||
|
environmentId: props.environmentId as string,
|
||||||
|
frontendDebug: executeType === 'localExec',
|
||||||
|
reportId: reportId.value,
|
||||||
|
};
|
||||||
|
debugSocket(executeType); // 开启websocket
|
||||||
|
if ((caseDetail.value.id as string).startsWith('c')) {
|
||||||
|
// 还没创建
|
||||||
|
res = await debugCase({
|
||||||
|
request: makeRequestParams?.request,
|
||||||
|
linkFileIds: makeRequestParams?.linkFileIds,
|
||||||
|
uploadFileIds: makeRequestParams?.uploadFileIds,
|
||||||
|
id: `case-${Date.now()}`,
|
||||||
|
projectId: appStore.currentProjectId,
|
||||||
|
...params,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res = await runCase({
|
||||||
|
request: caseDetail.value.request,
|
||||||
|
id: caseDetail.value.id as string,
|
||||||
|
projectId: caseDetail.value.projectId,
|
||||||
|
linkFileIds: caseDetail.value.linkFileIds,
|
||||||
|
uploadFileIds: caseDetail.value.uploadFileIds,
|
||||||
|
...params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (executeType === 'localExec') {
|
||||||
|
await localExecuteApiDebug(localExecuteUrl.value, res); // TODO: 会报错
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
caseDetail.value.executeLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopDebug() {
|
||||||
|
websocket.value?.close();
|
||||||
|
caseDetail.value.executeLoading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
initLocalConfig();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.exec-btn :deep(.arco-btn) {
|
||||||
|
color: white !important;
|
||||||
|
background-color: rgb(var(--primary-5)) !important;
|
||||||
|
.btn-base-primary-hover();
|
||||||
|
.btn-base-primary-active();
|
||||||
|
.btn-base-primary-disabled();
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -2,6 +2,7 @@
|
||||||
<div class="flex flex-1 flex-col overflow-hidden">
|
<div class="flex flex-1 flex-col overflow-hidden">
|
||||||
<div v-show="activeApiTab.id === 'all'" class="flex-1 overflow-hidden">
|
<div v-show="activeApiTab.id === 'all'" class="flex-1 overflow-hidden">
|
||||||
<caseTable
|
<caseTable
|
||||||
|
ref="caseTableRef"
|
||||||
:is-api="false"
|
:is-api="false"
|
||||||
:active-module="props.activeModule"
|
:active-module="props.activeModule"
|
||||||
:protocol="props.protocol"
|
:protocol="props.protocol"
|
||||||
|
@ -12,7 +13,7 @@
|
||||||
<caseDetail
|
<caseDetail
|
||||||
:detail="activeApiTab"
|
:detail="activeApiTab"
|
||||||
:module-tree="props.moduleTree"
|
:module-tree="props.moduleTree"
|
||||||
:protocol="props.protocol"
|
@delete-case="deleteCase"
|
||||||
@update-follow="activeApiTab.follow = !activeApiTab.follow"
|
@update-follow="activeApiTab.follow = !activeApiTab.follow"
|
||||||
@load-case="(id: string) => openOrUpdateCaseTab(false, id)"
|
@load-case="(id: string) => openOrUpdateCaseTab(false, id)"
|
||||||
/>
|
/>
|
||||||
|
@ -42,6 +43,9 @@
|
||||||
protocol: string;
|
protocol: string;
|
||||||
moduleTree: ModuleTreeNode[]; // 模块树
|
moduleTree: ModuleTreeNode[]; // 模块树
|
||||||
}>();
|
}>();
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'deleteCase', id: string): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
const apiTabs = defineModel<RequestParam[]>('apiTabs', {
|
const apiTabs = defineModel<RequestParam[]>('apiTabs', {
|
||||||
required: true,
|
required: true,
|
||||||
|
@ -58,16 +62,15 @@
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const res = await getCaseDetail(id);
|
const res = await getCaseDetail(id);
|
||||||
const parseRequestBodyResult = parseRequestBodyFiles(res.request.body); // 解析请求体中的文件,将详情中的文件 id 集合收集,更新时以判断文件是否删除以及是否新上传的文件;
|
let parseRequestBodyResult;
|
||||||
// if (res.protocol === 'HTTP') { // TODO: 后端没protocol字段,问一下
|
if (res.protocol === 'HTTP') {
|
||||||
// parseRequestBodyResult = parseRequestBodyFiles(res.request.body); // 解析请求体中的文件,将详情中的文件 id 集合收集,更新时以判断文件是否删除以及是否新上传的文件
|
parseRequestBodyResult = parseRequestBodyFiles(res.request.body); // 解析请求体中的文件,将详情中的文件 id 集合收集,更新时以判断文件是否删除以及是否新上传的文件
|
||||||
// }
|
}
|
||||||
const tabItemInfo = {
|
const tabItemInfo = {
|
||||||
...cloneDeep(defaultCaseParams as RequestParam),
|
...cloneDeep(defaultCaseParams as RequestParam),
|
||||||
...({
|
...({
|
||||||
...res.request,
|
...res.request,
|
||||||
...res,
|
...res,
|
||||||
// responseDefinition: res.response.map((e) => ({ ...e, responseActiveTab: ResponseComposition.BODY })), // TODO: 后端没response字段,问一下
|
|
||||||
url: res.path,
|
url: res.path,
|
||||||
...parseRequestBodyResult,
|
...parseRequestBodyResult,
|
||||||
} as Partial<TabItem>),
|
} as Partial<TabItem>),
|
||||||
|
@ -92,7 +95,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openCaseTab(apiInfo: ApiCaseDetail) {
|
async function openCaseTab(apiInfo: ApiCaseDetail | string) {
|
||||||
const isLoadedTabIndex = apiTabs.value.findIndex(
|
const isLoadedTabIndex = apiTabs.value.findIndex(
|
||||||
(e) => e.id === (typeof apiInfo === 'string' ? apiInfo : apiInfo.id)
|
(e) => e.id === (typeof apiInfo === 'string' ? apiInfo : apiInfo.id)
|
||||||
);
|
);
|
||||||
|
@ -103,4 +106,14 @@
|
||||||
}
|
}
|
||||||
await openOrUpdateCaseTab(true, typeof apiInfo === 'string' ? apiInfo : apiInfo.id);
|
await openOrUpdateCaseTab(true, typeof apiInfo === 'string' ? apiInfo : apiInfo.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const caseTableRef = ref<InstanceType<typeof caseTable>>();
|
||||||
|
function deleteCase(id: string) {
|
||||||
|
emit('deleteCase', id);
|
||||||
|
caseTableRef.value?.loadCaseList();
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
openCaseTab,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -47,12 +47,14 @@
|
||||||
:module-tree="props.moduleTree"
|
:module-tree="props.moduleTree"
|
||||||
/>
|
/>
|
||||||
<apiCase
|
<apiCase
|
||||||
v-if="(activeApiTab.id === 'all' && currentTab === 'case') || activeApiTab.type === 'case'"
|
v-show="(activeApiTab.id === 'all' && currentTab === 'case') || activeApiTab.type === 'case'"
|
||||||
|
ref="caseRef"
|
||||||
v-model:api-tabs="apiTabs"
|
v-model:api-tabs="apiTabs"
|
||||||
v-model:active-api-tab="activeApiTab"
|
v-model:active-api-tab="activeApiTab"
|
||||||
:active-module="props.activeModule"
|
:active-module="props.activeModule"
|
||||||
:protocol="props.protocol"
|
:protocol="props.protocol"
|
||||||
:module-tree="props.moduleTree"
|
:module-tree="props.moduleTree"
|
||||||
|
@delete-case="(id) => handleDeleteApiFromModuleTree(id)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -67,11 +69,12 @@
|
||||||
import { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
import { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||||
|
|
||||||
// import MockTable from '@/views/api-test/management/components/management/mock/mockTable.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 { useI18n } from '@/hooks/useI18n';
|
||||||
import router from '@/router';
|
import router from '@/router';
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
|
|
||||||
|
import { ProtocolItem } from '@/models/apiTest/common';
|
||||||
import { ModuleTreeNode } from '@/models/common';
|
import { ModuleTreeNode } from '@/models/common';
|
||||||
import { EnvConfig } from '@/models/projectManagement/environmental';
|
import { EnvConfig } from '@/models/projectManagement/environmental';
|
||||||
import {
|
import {
|
||||||
|
@ -104,6 +107,7 @@
|
||||||
];
|
];
|
||||||
|
|
||||||
const apiRef = ref<InstanceType<typeof api>>();
|
const apiRef = ref<InstanceType<typeof api>>();
|
||||||
|
const caseRef = ref<InstanceType<typeof apiCase>>();
|
||||||
|
|
||||||
function newTab(apiInfo?: ModuleTreeNode | string, isCopy?: boolean, isExecute?: boolean) {
|
function newTab(apiInfo?: ModuleTreeNode | string, isCopy?: boolean, isExecute?: boolean) {
|
||||||
if (apiInfo) {
|
if (apiInfo) {
|
||||||
|
@ -113,6 +117,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function newCaseTab(id: string) {
|
||||||
|
caseRef.value?.openCaseTab(id);
|
||||||
|
}
|
||||||
|
|
||||||
const apiTabs = ref<RequestParam[]>([
|
const apiTabs = ref<RequestParam[]>([
|
||||||
{
|
{
|
||||||
id: 'all',
|
id: 'all',
|
||||||
|
@ -310,16 +318,29 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const protocols = ref<ProtocolItem[]>([]);
|
||||||
|
async function initProtocolList() {
|
||||||
|
try {
|
||||||
|
protocols.value = await getProtocolList(appStore.currentOrgId);
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
initEnvList();
|
initEnvList();
|
||||||
|
initProtocolList();
|
||||||
});
|
});
|
||||||
|
|
||||||
/** 向孙组件提供属性 */
|
/** 向孙组件提供属性 */
|
||||||
provide('currentEnvConfig', readonly(currentEnvConfig));
|
provide('currentEnvConfig', readonly(currentEnvConfig));
|
||||||
provide('defaultCaseParams', readonly(defaultCaseParams));
|
provide('defaultCaseParams', readonly(defaultCaseParams));
|
||||||
|
provide('protocols', readonly(protocols));
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
newTab,
|
newTab,
|
||||||
|
newCaseTab,
|
||||||
refreshApiTable,
|
refreshApiTable,
|
||||||
handleApiUpdateFromModuleTree,
|
handleApiUpdateFromModuleTree,
|
||||||
handleDeleteApiFromModuleTree,
|
handleDeleteApiFromModuleTree,
|
||||||
|
|
|
@ -149,6 +149,9 @@
|
||||||
if (route.query.dId) {
|
if (route.query.dId) {
|
||||||
// 携带 dId 参数,自动打开接口定义详情 tab
|
// 携带 dId 参数,自动打开接口定义详情 tab
|
||||||
managementRef.value?.newTab(route.query.dId as string);
|
managementRef.value?.newTab(route.query.dId as string);
|
||||||
|
} else if (route.query.cId) {
|
||||||
|
// 携带 cId 参数,自动打开接口用例详情 tab
|
||||||
|
managementRef.value?.newCaseTab(route.query.cId as string);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue