feat(接口测试): 用例详情页面布局调整&diff抽屉调整&编辑详情页面新增对比
This commit is contained in:
parent
c96bd87065
commit
bc1d906ae6
|
@ -100,9 +100,7 @@
|
||||||
.ms-detail-card {
|
.ms-detail-card {
|
||||||
@apply relative flex flex-col;
|
@apply relative flex flex-col;
|
||||||
|
|
||||||
padding: 16px;
|
|
||||||
border-radius: var(--border-radius-small);
|
border-radius: var(--border-radius-small);
|
||||||
background-color: var(--color-text-n9);
|
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
.ms-detail-card-title {
|
.ms-detail-card-title {
|
||||||
@apply flex items-center justify-between overflow-hidden;
|
@apply flex items-center justify-between overflow-hidden;
|
||||||
|
|
|
@ -3,6 +3,7 @@ export enum ApiTestRouteEnum {
|
||||||
API_TEST_DEBUG_MANAGEMENT = 'apiTestDebug',
|
API_TEST_DEBUG_MANAGEMENT = 'apiTestDebug',
|
||||||
API_TEST_MANAGEMENT = 'apiTestManagement',
|
API_TEST_MANAGEMENT = 'apiTestManagement',
|
||||||
API_TEST_MANAGEMENT_RECYCLE = 'apiTestManagementRecycle',
|
API_TEST_MANAGEMENT_RECYCLE = 'apiTestManagementRecycle',
|
||||||
|
API_TEST_MANAGEMENT_CASE_DETAIL = 'apiTestManagementCaseDetail',
|
||||||
API_TEST_SCENARIO = 'apiTestScenario',
|
API_TEST_SCENARIO = 'apiTestScenario',
|
||||||
API_TEST_SCENARIO_RECYCLE = 'apiTestScenarioRecycle',
|
API_TEST_SCENARIO_RECYCLE = 'apiTestScenarioRecycle',
|
||||||
API_TEST_REPORT = 'apiTestReport',
|
API_TEST_REPORT = 'apiTestReport',
|
||||||
|
|
|
@ -64,6 +64,26 @@ const ApiTest: AppRouteRecordRaw = {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// 接口定义-API-用例-用例列表详情
|
||||||
|
{
|
||||||
|
path: 'caseDetail',
|
||||||
|
name: ApiTestRouteEnum.API_TEST_MANAGEMENT_CASE_DETAIL,
|
||||||
|
component: () => import('@/views/api-test/management/components/management/case/apiCaseDetail.vue'),
|
||||||
|
meta: {
|
||||||
|
locale: 'case.apiCaseDetail',
|
||||||
|
roles: ['PROJECT_API_DEFINITION:READ'],
|
||||||
|
breadcrumbs: [
|
||||||
|
{
|
||||||
|
name: ApiTestRouteEnum.API_TEST_MANAGEMENT,
|
||||||
|
locale: 'case.apiCaseList',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: ApiTestRouteEnum.API_TEST_MANAGEMENT_CASE_DETAIL,
|
||||||
|
locale: 'case.apiCaseDetail',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'scenario',
|
path: 'scenario',
|
||||||
name: ApiTestRouteEnum.API_TEST_SCENARIO,
|
name: ApiTestRouteEnum.API_TEST_SCENARIO,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
import { EQUAL } from '@/components/pure/ms-advance-filter';
|
import { EQUAL } from '@/components/pure/ms-advance-filter';
|
||||||
|
import { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
|
@ -18,14 +19,18 @@ import type { MockParams } from '@/models/apiTest/mock';
|
||||||
import {
|
import {
|
||||||
FullResponseAssertionType,
|
FullResponseAssertionType,
|
||||||
RequestAssertionCondition,
|
RequestAssertionCondition,
|
||||||
|
RequestAuthType,
|
||||||
RequestBodyFormat,
|
RequestBodyFormat,
|
||||||
RequestCaseStatus,
|
RequestCaseStatus,
|
||||||
|
RequestComposition,
|
||||||
RequestContentTypeEnum,
|
RequestContentTypeEnum,
|
||||||
|
RequestDefinitionStatus,
|
||||||
RequestExtractEnvType,
|
RequestExtractEnvType,
|
||||||
RequestExtractExpressionEnum,
|
RequestExtractExpressionEnum,
|
||||||
RequestExtractExpressionRuleType,
|
RequestExtractExpressionRuleType,
|
||||||
RequestExtractResultMatchingRule,
|
RequestExtractResultMatchingRule,
|
||||||
RequestExtractScope,
|
RequestExtractScope,
|
||||||
|
RequestMethods,
|
||||||
RequestParamsType,
|
RequestParamsType,
|
||||||
ResponseBodyFormat,
|
ResponseBodyFormat,
|
||||||
ResponseBodyXPathAssertionFormat,
|
ResponseBodyXPathAssertionFormat,
|
||||||
|
@ -432,3 +437,74 @@ export const lastReportStatusListOptions = computed(() => {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// api下的创建用例弹窗也用到了defaultCaseParams
|
||||||
|
const initDefaultId = `case-${Date.now()}`;
|
||||||
|
export const defaultCaseParams: RequestParam = {
|
||||||
|
id: initDefaultId,
|
||||||
|
type: 'case',
|
||||||
|
moduleId: '',
|
||||||
|
protocol: 'HTTP',
|
||||||
|
tags: [],
|
||||||
|
description: '',
|
||||||
|
priority: 'P0',
|
||||||
|
status: RequestDefinitionStatus.PROCESSING,
|
||||||
|
url: '',
|
||||||
|
activeTab: RequestComposition.HEADER,
|
||||||
|
closable: true,
|
||||||
|
method: RequestMethods.GET,
|
||||||
|
headers: [],
|
||||||
|
body: cloneDeep(defaultBodyParams),
|
||||||
|
query: [],
|
||||||
|
rest: [],
|
||||||
|
polymorphicName: '',
|
||||||
|
name: '',
|
||||||
|
path: '',
|
||||||
|
projectId: '',
|
||||||
|
uploadFileIds: [],
|
||||||
|
linkFileIds: [],
|
||||||
|
authConfig: {
|
||||||
|
authType: RequestAuthType.NONE,
|
||||||
|
basicAuth: {
|
||||||
|
userName: '',
|
||||||
|
password: '',
|
||||||
|
},
|
||||||
|
digestAuth: {
|
||||||
|
userName: '',
|
||||||
|
password: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
polymorphicName: 'MsCommonElement', // 协议多态名称,写死MsCommonElement
|
||||||
|
assertionConfig: {
|
||||||
|
enableGlobal: true,
|
||||||
|
assertions: [],
|
||||||
|
},
|
||||||
|
postProcessorConfig: {
|
||||||
|
enableGlobal: true,
|
||||||
|
processors: [],
|
||||||
|
},
|
||||||
|
preProcessorConfig: {
|
||||||
|
enableGlobal: true,
|
||||||
|
processors: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
otherConfig: {
|
||||||
|
connectTimeout: 60000,
|
||||||
|
responseTimeout: 60000,
|
||||||
|
certificateAlias: '',
|
||||||
|
followRedirects: true,
|
||||||
|
autoRedirects: false,
|
||||||
|
},
|
||||||
|
responseActiveTab: ResponseComposition.BODY,
|
||||||
|
response: cloneDeep(defaultResponse),
|
||||||
|
responseDefinition: [cloneDeep(defaultResponseItem)],
|
||||||
|
isNew: true,
|
||||||
|
unSaved: false,
|
||||||
|
executeLoading: false,
|
||||||
|
preDependency: [], // 前置依赖
|
||||||
|
postDependency: [], // 后置依赖
|
||||||
|
errorMessageInfo: {},
|
||||||
|
};
|
||||||
|
|
|
@ -2,14 +2,29 @@
|
||||||
<a-collapse v-model:active-key="activeDetailKey" :bordered="false">
|
<a-collapse v-model:active-key="activeDetailKey" :bordered="false">
|
||||||
<a-collapse-item key="request">
|
<a-collapse-item key="request">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-center gap-[4px]">
|
<div class="flex items-center justify-between">
|
||||||
<div v-if="activeDetailKey.includes('request')" class="down-icon">
|
<div class="flex items-center gap-[4px]">
|
||||||
<icon-down :size="10" class="block" />
|
<div class="font-medium">{{ t('apiTestManagement.requestParams') }}</div>
|
||||||
|
<div v-if="activeDetailKey.includes('request')" 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>
|
</div>
|
||||||
<div v-else class="h-[16px] w-[16px] !rounded-full p-[4px]">
|
<MsTag
|
||||||
<icon-right :size="10" class="block" />
|
v-if="props.detail.inconsistentWithApi"
|
||||||
</div>
|
class="cursor-pointer"
|
||||||
<div class="font-medium">{{ t('apiTestManagement.requestParams') }}</div>
|
type="warning"
|
||||||
|
theme="light"
|
||||||
|
:tooltip-disabled="true"
|
||||||
|
@click.stop="showDiffDrawer"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<MsIcon type="icon-icon_warning_colorful" size="16" />
|
||||||
|
</template>
|
||||||
|
<span class="ml-[8px]"> {{ statusText }}</span>
|
||||||
|
</MsTag>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="detail-collapse-item">
|
<div class="detail-collapse-item">
|
||||||
|
@ -258,13 +273,13 @@
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex items-center gap-[4px]">
|
<div class="flex items-center gap-[4px]">
|
||||||
|
<div class="font-medium">{{ t('apiTestManagement.responseContent') }}</div>
|
||||||
<div v-if="activeDetailKey.includes('response')" class="down-icon">
|
<div v-if="activeDetailKey.includes('response')" class="down-icon">
|
||||||
<icon-down :size="10" class="block" />
|
<icon-down :size="10" class="block" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="h-[16px] w-[16px] !rounded-full p-[4px]">
|
<div v-else class="h-[16px] w-[16px] !rounded-full p-[4px]">
|
||||||
<icon-right :size="10" class="block" />
|
<icon-right :size="10" class="block" />
|
||||||
</div>
|
</div>
|
||||||
<div class="font-medium">{{ t('apiTestManagement.responseContent') }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
<responseCodeTimeSize v-if="props.isCase" :request-result="previewDetail.response?.requestResults[0]" />
|
<responseCodeTimeSize v-if="props.isCase" :request-result="previewDetail.response?.requestResults[0]" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -404,6 +419,7 @@
|
||||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||||
import MsJsonSchema from '@/components/pure/ms-json-schema/index.vue';
|
import MsJsonSchema from '@/components/pure/ms-json-schema/index.vue';
|
||||||
import { parseSchemaToJsonSchemaTableData } from '@/components/pure/ms-json-schema/utils';
|
import { parseSchemaToJsonSchemaTableData } from '@/components/pure/ms-json-schema/utils';
|
||||||
|
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
|
||||||
import { ResponseItem } from '@/views/api-test/components/requestComposition/response/edit.vue';
|
import { ResponseItem } from '@/views/api-test/components/requestComposition/response/edit.vue';
|
||||||
import responseCodeTimeSize from '@/views/api-test/components/requestComposition/response/responseCodeTimeSize.vue';
|
import responseCodeTimeSize from '@/views/api-test/components/requestComposition/response/responseCodeTimeSize.vue';
|
||||||
import Result from '@/views/api-test/components/requestComposition/response/result.vue';
|
import Result from '@/views/api-test/components/requestComposition/response/result.vue';
|
||||||
|
@ -425,6 +441,7 @@
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'execute', val: 'localExec' | 'serverExec'): void;
|
(e: 'execute', val: 'localExec' | 'serverExec'): void;
|
||||||
|
(e: 'showDiff'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
@ -864,11 +881,23 @@
|
||||||
activeResponse.value.body.jsonBody.enableJsonSchema = type === 'Schema';
|
activeResponse.value.body.jsonBody.enableJsonSchema = type === 'Schema';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const statusText = computed(() => {
|
||||||
|
if (props.detail.inconsistentWithApi) {
|
||||||
|
return t('case.definitionInconsistent');
|
||||||
|
}
|
||||||
|
// TODO 这里的交互参数等待协调
|
||||||
|
});
|
||||||
|
|
||||||
|
// 查看diff对比
|
||||||
|
function showDiffDrawer() {
|
||||||
|
emit('showDiff');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.down-icon {
|
.down-icon {
|
||||||
padding: 4px;
|
padding: 3px;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
@ -886,9 +915,8 @@
|
||||||
.arco-collapse-item-header-title {
|
.arco-collapse-item-header-title {
|
||||||
@apply block w-full;
|
@apply block w-full;
|
||||||
|
|
||||||
padding: 8px 16px;
|
padding: 8px 0;
|
||||||
border-radius: var(--border-radius-small);
|
border-radius: var(--border-radius-small);
|
||||||
background-color: var(--color-text-n9);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.detail-collapse-item {
|
.detail-collapse-item {
|
||||||
|
|
|
@ -0,0 +1,416 @@
|
||||||
|
<template>
|
||||||
|
<MsCard :header-min-width="1100" :min-width="150" auto-height hide-footer no-content-padding hide-divider hide-back>
|
||||||
|
<template #headerLeft>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<caseLevel :case-level="caseDetail.priority as CaseLevel" />
|
||||||
|
<div class="one-line-text max-w-[300px] font-medium text-[var(--color-text-1)]">
|
||||||
|
{{ `[${caseDetail.num}] ${caseDetail.name}` }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-tooltip :content="t(caseDetail.follow ? 'common.forked' : 'common.notForked')">
|
||||||
|
<MsIcon
|
||||||
|
v-permission="['PROJECT_API_DEFINITION_CASE:READ+UPDATE']"
|
||||||
|
:loading="followLoading"
|
||||||
|
:type="caseDetail.follow ? 'icon-icon_collect_filled' : 'icon-icon_collection_outlined'"
|
||||||
|
:class="`${caseDetail.follow ? 'text-[rgb(var(--warning-6))]' : 'text-[var(--color-text-4)]'}`"
|
||||||
|
class="cursor-pointer"
|
||||||
|
:size="16"
|
||||||
|
@click="follow"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
|
||||||
|
<a-tooltip :content="t('report.detail.api.copyLink')">
|
||||||
|
<MsIcon type="icon-icon_share1" class="cursor-pointer text-[var(--color-text-4)]" :size="16" @click="share" />
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #headerRight>
|
||||||
|
<div class="flex gap-[12px]">
|
||||||
|
<MsEnvironmentSelect :env="environmentIdByDrawer" />
|
||||||
|
<executeButton
|
||||||
|
ref="executeRef"
|
||||||
|
v-permission="['PROJECT_API_DEFINITION_CASE:READ+EXECUTE']"
|
||||||
|
:execute-loading="caseDetail.executeLoading"
|
||||||
|
@stop-debug="stopDebug"
|
||||||
|
@execute="handleExecute"
|
||||||
|
/>
|
||||||
|
<a-dropdown-button type="outline" @click="editCase">
|
||||||
|
{{ t('common.edit') }}
|
||||||
|
<template #icon>
|
||||||
|
<icon-down />
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<a-doption
|
||||||
|
v-permission="['PROJECT_API_DEFINITION_CASE:READ+DELETE']"
|
||||||
|
value="delete"
|
||||||
|
class="error-6 text-[rgb(var(--danger-6))]"
|
||||||
|
@click="handleDelete"
|
||||||
|
>
|
||||||
|
<MsIcon type="icon-icon_delete-trash_outlined1" class="text-[rgb(var(--danger-6))]" />
|
||||||
|
{{ t('common.delete') }}
|
||||||
|
</a-doption>
|
||||||
|
</template>
|
||||||
|
</a-dropdown-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<createAndEditCaseDrawer
|
||||||
|
ref="createAndEditCaseDrawerRef"
|
||||||
|
v-bind="$attrs"
|
||||||
|
@load-case="(id)=>getCaseDetailInfo(id as string)"
|
||||||
|
/>
|
||||||
|
<a-divider :margin="0"></a-divider>
|
||||||
|
<MsTab
|
||||||
|
v-model:active-key="activeKey"
|
||||||
|
:show-badge="false"
|
||||||
|
:content-tab-list="tabList"
|
||||||
|
no-content
|
||||||
|
class="relative mx-[16px] border-b"
|
||||||
|
/>
|
||||||
|
</MsCard>
|
||||||
|
<MsCard class="mt-[16px]" :special-height="174" simple>
|
||||||
|
<div v-if="activeKey === 'detail'">
|
||||||
|
<MsDetailCard :title="t('common.baseInfo')" :description="description" class="mb-[8px]">
|
||||||
|
<template #type="{ value }">
|
||||||
|
<apiMethodName v-if="value" :method="value as RequestMethods" tag-size="small" is-tag />
|
||||||
|
</template>
|
||||||
|
</MsDetailCard>
|
||||||
|
<detailTab
|
||||||
|
:detail="caseDetail as RequestParam"
|
||||||
|
:protocols="protocols as ProtocolItem[]"
|
||||||
|
:is-priority-local-exec="isPriorityLocalExec"
|
||||||
|
is-case
|
||||||
|
@execute="handleExecute"
|
||||||
|
@show-diff="showDiffDrawer"
|
||||||
|
/>
|
||||||
|
<DifferentDrawer
|
||||||
|
v-model:visible="showDifferentDrawer"
|
||||||
|
:active-api-case-id="activeApiCaseId"
|
||||||
|
:active-defined-id="activeDefinedId"
|
||||||
|
@close="closeDifferent"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<tab-case-dependency v-else-if="activeKey === 'reference'" :source-id="caseDetail.id" />
|
||||||
|
<tab-case-execute-history
|
||||||
|
v-else-if="activeKey === 'executeHistory'"
|
||||||
|
ref="executeHistoryRef"
|
||||||
|
:source-id="caseDetail.id"
|
||||||
|
module-type="API_REPORT"
|
||||||
|
:protocol="caseDetail.protocol"
|
||||||
|
/>
|
||||||
|
<tab-case-change-history v-else :source-id="caseDetail.id" />
|
||||||
|
</MsCard>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
import { useClipboard } from '@vueuse/core';
|
||||||
|
import { Message } from '@arco-design/web-vue';
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
|
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||||
|
import MsDetailCard from '@/components/pure/ms-detail-card/index.vue';
|
||||||
|
import { TabItem } from '@/components/pure/ms-editable-tab/types';
|
||||||
|
import MsTab from '@/components/pure/ms-tab/index.vue';
|
||||||
|
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
||||||
|
import type { CaseLevel } from '@/components/business/ms-case-associate/types';
|
||||||
|
import MsEnvironmentSelect from '@/components/business/ms-environment-select/index.vue';
|
||||||
|
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
||||||
|
import executeButton from '@/views/api-test/components/executeButton.vue';
|
||||||
|
import { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||||
|
import detailTab from '@/views/api-test/management/components/management/api/preview/detail.vue';
|
||||||
|
import createAndEditCaseDrawer from '@/views/api-test/management/components/management/case/createAndEditCaseDrawer.vue';
|
||||||
|
import DifferentDrawer from '@/views/api-test/management/components/management/case/differentDrawer.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 TabCaseExecuteHistory from '@/views/api-test/management/components/management/case/tabContent/tabCaseExecuteHistory.vue';
|
||||||
|
|
||||||
|
import { getProtocolList, localExecuteApiDebug, stopExecute, stopLocalExecute } from '@/api/modules/api-test/common';
|
||||||
|
import { debugCase, deleteCase, getCaseDetail, runCase, toggleFollowCase } from '@/api/modules/api-test/management';
|
||||||
|
import { getSocket } from '@/api/modules/project-management/commonScript';
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import useModal from '@/hooks/useModal';
|
||||||
|
import useAppStore from '@/store/modules/app';
|
||||||
|
import { getGenerateId } from '@/utils';
|
||||||
|
|
||||||
|
import { ProtocolItem } from '@/models/apiTest/common';
|
||||||
|
import { RequestMethods, ScenarioStepType } from '@/enums/apiEnum';
|
||||||
|
|
||||||
|
import { defaultCaseParams, defaultResponse } from '@/views/api-test/components/config';
|
||||||
|
import { parseRequestBodyFiles } from '@/views/api-test/components/utils';
|
||||||
|
|
||||||
|
const { openModal } = useModal();
|
||||||
|
const appStore = useAppStore();
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const activeKey = ref('detail');
|
||||||
|
|
||||||
|
const { copy, isSupported } = useClipboard({ legacy: true });
|
||||||
|
|
||||||
|
const caseDetail = ref<RequestParam>(cloneDeep(defaultCaseParams));
|
||||||
|
const environmentIdByDrawer = ref('');
|
||||||
|
const followLoading = ref(false);
|
||||||
|
async function follow() {
|
||||||
|
try {
|
||||||
|
followLoading.value = true;
|
||||||
|
await toggleFollowCase(caseDetail.value.id);
|
||||||
|
Message.success(caseDetail.value.follow ? t('common.unFollowSuccess') : t('common.followSuccess'));
|
||||||
|
caseDetail.value.follow = !caseDetail.value.follow;
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
followLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const executeRef = ref<InstanceType<typeof executeButton>>();
|
||||||
|
const reportId = ref('');
|
||||||
|
const executeCase = ref<boolean>(false);
|
||||||
|
const websocket = ref<WebSocket>();
|
||||||
|
async function stopDebug() {
|
||||||
|
try {
|
||||||
|
if (caseDetail.value.frontendDebug) {
|
||||||
|
await stopLocalExecute(executeRef.value?.localExecuteUrl || '', reportId.value, ScenarioStepType.API_CASE);
|
||||||
|
} else {
|
||||||
|
await stopExecute(reportId.value, ScenarioStepType.API_CASE);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
websocket.value?.close();
|
||||||
|
caseDetail.value.executeLoading = false;
|
||||||
|
executeCase.value = false;
|
||||||
|
}
|
||||||
|
const temporaryResponseMap: Record<string, any> = {}; // 缓存websocket返回的报告内容,避免执行接口后切换tab导致报告丢失
|
||||||
|
const executeHistoryRef = ref<InstanceType<typeof TabCaseExecuteHistory>>();
|
||||||
|
|
||||||
|
// 开启websocket监听,接收执行结果
|
||||||
|
function debugSocket(executeType?: 'localExec' | 'serverExec') {
|
||||||
|
websocket.value = getSocket(
|
||||||
|
reportId.value,
|
||||||
|
executeType === 'localExec' ? '/ws/debug' : '',
|
||||||
|
executeType === 'localExec' ? executeRef.value?.localExecuteUrl : ''
|
||||||
|
);
|
||||||
|
websocket.value.addEventListener('message', (event) => {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
if (data.msgType === 'EXEC_RESULT') {
|
||||||
|
if (caseDetail.value.reportId === data.reportId) {
|
||||||
|
// 判断当前查看的tab是否是当前返回的报告的tab,是的话直接赋值
|
||||||
|
caseDetail.value.response = data.taskResult; // 渲染出用例详情和创建用例抽屉的响应数据
|
||||||
|
caseDetail.value.executeLoading = false;
|
||||||
|
executeCase.value = false;
|
||||||
|
} else {
|
||||||
|
// 不是则需要把报告缓存起来,等切换到对应的tab再赋值
|
||||||
|
temporaryResponseMap[data.reportId] = data.taskResult;
|
||||||
|
}
|
||||||
|
} else if (data.msgType === 'EXEC_END') {
|
||||||
|
// 执行结束,关闭websocket
|
||||||
|
websocket.value?.close();
|
||||||
|
caseDetail.value.executeLoading = false;
|
||||||
|
executeCase.value = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 执行
|
||||||
|
async function handleExecute(executeType?: 'localExec' | 'serverExec') {
|
||||||
|
try {
|
||||||
|
caseDetail.value.executeLoading = true;
|
||||||
|
caseDetail.value.response = cloneDeep(defaultResponse);
|
||||||
|
reportId.value = getGenerateId();
|
||||||
|
caseDetail.value.reportId = reportId.value; // 存储报告ID
|
||||||
|
let res;
|
||||||
|
const params = {
|
||||||
|
id: caseDetail.value.id as string,
|
||||||
|
environmentId: appStore.currentEnvConfig?.id || '',
|
||||||
|
frontendDebug: executeType === 'localExec',
|
||||||
|
reportId: reportId.value,
|
||||||
|
apiDefinitionId: caseDetail.value.apiDefinitionId,
|
||||||
|
request: caseDetail.value.request,
|
||||||
|
projectId: caseDetail.value.projectId,
|
||||||
|
linkFileIds: caseDetail.value.linkFileIds,
|
||||||
|
uploadFileIds: caseDetail.value.uploadFileIds,
|
||||||
|
};
|
||||||
|
debugSocket(executeType); // 开启websocket
|
||||||
|
if (executeType === 'serverExec') {
|
||||||
|
// 已创建的服务端
|
||||||
|
res = await runCase(params);
|
||||||
|
} else {
|
||||||
|
res = await debugCase(params);
|
||||||
|
}
|
||||||
|
if (executeType === 'localExec') {
|
||||||
|
await localExecuteApiDebug(executeRef.value?.localExecuteUrl as string, res);
|
||||||
|
}
|
||||||
|
// 执行完更新执行历史
|
||||||
|
executeHistoryRef.value?.loadExecuteList();
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
caseDetail.value.executeLoading = false;
|
||||||
|
executeCase.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const createAndEditCaseDrawerRef = ref<InstanceType<typeof createAndEditCaseDrawer>>();
|
||||||
|
function editCase() {
|
||||||
|
createAndEditCaseDrawerRef.value?.open(
|
||||||
|
caseDetail.value.apiDefinitionId,
|
||||||
|
caseDetail.value as unknown as RequestParam,
|
||||||
|
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);
|
||||||
|
// TODO 删除后这里返回原本的页面
|
||||||
|
router.back();
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hideCancel: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
provide('defaultCaseParams', readonly(defaultCaseParams));
|
||||||
|
|
||||||
|
function share() {
|
||||||
|
if (isSupported) {
|
||||||
|
const url = window.location.href;
|
||||||
|
const dIdParam = `&cId=${caseDetail.value.id}`;
|
||||||
|
const copyUrl = url.includes('cId') ? url.split('&cId')[0] : url;
|
||||||
|
copy(`${copyUrl}${dIdParam}`);
|
||||||
|
Message.success(t('apiTestManagement.shareUrlCopied'));
|
||||||
|
} else {
|
||||||
|
Message.error(t('common.copyNotSupport'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getCaseDetailInfo(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await getCaseDetail(id);
|
||||||
|
let parseRequestBodyResult;
|
||||||
|
if (res.protocol === 'HTTP') {
|
||||||
|
parseRequestBodyResult = parseRequestBodyFiles(res.request.body); // 解析请求体中的文件,将详情中的文件 id 集合收集,更新时以判断文件是否删除以及是否新上传的文件
|
||||||
|
}
|
||||||
|
caseDetail.value = {
|
||||||
|
...cloneDeep(defaultCaseParams as RequestParam),
|
||||||
|
...({
|
||||||
|
...res.request,
|
||||||
|
...res,
|
||||||
|
url: res.path,
|
||||||
|
...parseRequestBodyResult,
|
||||||
|
} as Partial<TabItem>),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const description = computed(() => [
|
||||||
|
{
|
||||||
|
key: 'type',
|
||||||
|
locale: 'apiTestManagement.apiType',
|
||||||
|
value: caseDetail.value.method,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'name',
|
||||||
|
locale: 'case.belongingApi',
|
||||||
|
value: `[${caseDetail.value.num}] ${caseDetail.value.name}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'path',
|
||||||
|
locale: 'apiTestManagement.path',
|
||||||
|
value: caseDetail.value.url || caseDetail.value.path,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'tags',
|
||||||
|
locale: 'common.tag',
|
||||||
|
value: caseDetail.value.tags,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const tabList = ref([
|
||||||
|
{
|
||||||
|
value: 'detail',
|
||||||
|
label: t('case.detail'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'reference',
|
||||||
|
label: t('apiTestManagement.reference'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'executeHistory',
|
||||||
|
label: t('apiTestManagement.executeHistory'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'changeHistory',
|
||||||
|
label: t('apiTestManagement.changeHistory'),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const protocols = ref<ProtocolItem[]>([]);
|
||||||
|
provide('protocols', readonly(protocols));
|
||||||
|
async function initProtocolList() {
|
||||||
|
try {
|
||||||
|
protocols.value = await getProtocolList(appStore.currentOrgId);
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义id
|
||||||
|
const activeDefinedId = ref<string>('');
|
||||||
|
// 用例id
|
||||||
|
const activeApiCaseId = ref<string>('');
|
||||||
|
const showDifferentDrawer = ref<boolean>(false);
|
||||||
|
// 查看diff对比
|
||||||
|
function showDiffDrawer() {
|
||||||
|
activeApiCaseId.value = caseDetail.value.id as string;
|
||||||
|
activeDefinedId.value = caseDetail.value.apiDefinitionId;
|
||||||
|
showDifferentDrawer.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDifferent() {
|
||||||
|
showDifferentDrawer.value = false;
|
||||||
|
activeApiCaseId.value = '';
|
||||||
|
activeDefinedId.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const isPriorityLocalExec = computed(() => executeRef.value?.isPriorityLocalExec ?? false);
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
initProtocolList();
|
||||||
|
const caseId = route.query.id;
|
||||||
|
getCaseDetailInfo(caseId as string);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
:deep(.ms-detail-card-desc) {
|
||||||
|
gap: 16px;
|
||||||
|
flex-wrap: nowrap !important;
|
||||||
|
& > div:nth-of-type(n) {
|
||||||
|
width: auto;
|
||||||
|
max-width: 30%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -66,6 +66,13 @@
|
||||||
:is-priority-local-exec="isPriorityLocalExec"
|
:is-priority-local-exec="isPriorityLocalExec"
|
||||||
is-case
|
is-case
|
||||||
@execute="handleExecute"
|
@execute="handleExecute"
|
||||||
|
@show-diff="showDiffDrawer"
|
||||||
|
/>
|
||||||
|
<DifferentDrawer
|
||||||
|
v-model:visible="showDifferentDrawer"
|
||||||
|
:active-api-case-id="activeApiCaseId"
|
||||||
|
:active-defined-id="activeDefinedId"
|
||||||
|
@close="closeDifferent"
|
||||||
/>
|
/>
|
||||||
</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]">
|
||||||
|
@ -104,6 +111,7 @@
|
||||||
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
||||||
import executeButton from '@/views/api-test/components/executeButton.vue';
|
import executeButton from '@/views/api-test/components/executeButton.vue';
|
||||||
import { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
import { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||||
|
import DifferentDrawer from '@/views/api-test/management/components/management/case/differentDrawer.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';
|
||||||
|
@ -315,6 +323,25 @@
|
||||||
executeCase.value = false;
|
executeCase.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 定义id
|
||||||
|
const activeDefinedId = ref<string>('');
|
||||||
|
// 用例id
|
||||||
|
const activeApiCaseId = ref<string>('');
|
||||||
|
|
||||||
|
const showDifferentDrawer = ref<boolean>(false);
|
||||||
|
// 查看diff对比
|
||||||
|
function showDiffDrawer() {
|
||||||
|
activeApiCaseId.value = caseDetail.value.id as string;
|
||||||
|
activeDefinedId.value = caseDetail.value.apiDefinitionId;
|
||||||
|
showDifferentDrawer.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDifferent() {
|
||||||
|
showDifferentDrawer.value = false;
|
||||||
|
activeApiCaseId.value = '';
|
||||||
|
activeDefinedId.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.detail,
|
() => props.detail,
|
||||||
() => {
|
() => {
|
||||||
|
|
|
@ -51,7 +51,6 @@
|
||||||
<MsButton type="text" @click="isApi ? openCaseDetailDrawer(record.id) : openCaseTab(record)">
|
<MsButton type="text" @click="isApi ? openCaseDetailDrawer(record.id) : openCaseTab(record)">
|
||||||
{{ record.num }}
|
{{ record.num }}
|
||||||
</MsButton>
|
</MsButton>
|
||||||
<!-- TODO 后台缺少字段 等待联调 -->
|
|
||||||
<a-tooltip v-if="record.apiChange" class="ms-tooltip-white">
|
<a-tooltip v-if="record.apiChange" class="ms-tooltip-white">
|
||||||
<!-- 接口参数发生变更提示 -->
|
<!-- 接口参数发生变更提示 -->
|
||||||
<MsIcon type="icon-icon_warning_colorful" size="16" />
|
<MsIcon type="icon-icon_warning_colorful" size="16" />
|
||||||
|
@ -278,7 +277,9 @@
|
||||||
ref="createAndEditCaseDrawerRef"
|
ref="createAndEditCaseDrawerRef"
|
||||||
:api-detail="apiDetail"
|
:api-detail="apiDetail"
|
||||||
@load-case="loadCaseListAndResetSelector()"
|
@load-case="loadCaseListAndResetSelector()"
|
||||||
|
@show-diff="showDifferences"
|
||||||
/>
|
/>
|
||||||
|
<!-- TODO 之后要去掉 使用页面代替抽屉 -->
|
||||||
<caseDetailDrawer
|
<caseDetailDrawer
|
||||||
v-model:visible="caseDetailDrawerVisible"
|
v-model:visible="caseDetailDrawerVisible"
|
||||||
v-model:execute-case="caseExecute"
|
v-model:execute-case="caseExecute"
|
||||||
|
@ -303,8 +304,6 @@
|
||||||
<!-- diff对比抽屉 -->
|
<!-- diff对比抽屉 -->
|
||||||
<DifferentDrawer
|
<DifferentDrawer
|
||||||
v-model:visible="showDifferentDrawer"
|
v-model:visible="showDifferentDrawer"
|
||||||
:detail="caseDetail as RequestParam"
|
|
||||||
:api-detail="apiDetail as RequestParam"
|
|
||||||
:active-api-case-id="activeApiCaseId"
|
:active-api-case-id="activeApiCaseId"
|
||||||
:active-defined-id="activeDefinedId"
|
:active-defined-id="activeDefinedId"
|
||||||
@close="closeDifferent"
|
@close="closeDifferent"
|
||||||
|
@ -312,6 +311,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { FormInstance, Message } from '@arco-design/web-vue';
|
import { FormInstance, Message } from '@arco-design/web-vue';
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
|
@ -357,6 +357,7 @@
|
||||||
import { DragSortParams } from '@/models/common';
|
import { DragSortParams } from '@/models/common';
|
||||||
import { RequestCaseStatus } from '@/enums/apiEnum';
|
import { RequestCaseStatus } from '@/enums/apiEnum';
|
||||||
import { ReportEnum, ReportStatus } from '@/enums/reportEnum';
|
import { ReportEnum, ReportStatus } from '@/enums/reportEnum';
|
||||||
|
import { ApiTestRouteEnum } from '@/enums/routeEnum';
|
||||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||||
import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum';
|
import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum';
|
||||||
|
|
||||||
|
@ -382,6 +383,8 @@
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const tableStore = useTableStore();
|
const tableStore = useTableStore();
|
||||||
const { openModal } = useModal();
|
const { openModal } = useModal();
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const keyword = ref('');
|
const keyword = ref('');
|
||||||
|
|
||||||
|
@ -925,7 +928,13 @@
|
||||||
async function openCaseDetailDrawer(id: string) {
|
async function openCaseDetailDrawer(id: string) {
|
||||||
await getCaseDetailInfo(id);
|
await getCaseDetailInfo(id);
|
||||||
caseExecute.value = false;
|
caseExecute.value = false;
|
||||||
caseDetailDrawerVisible.value = true;
|
router.push({
|
||||||
|
name: ApiTestRouteEnum.API_TEST_MANAGEMENT_CASE_DETAIL,
|
||||||
|
query: {
|
||||||
|
...route.query,
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openCaseDetailDrawerAndExecute(id: string) {
|
async function openCaseDetailDrawerAndExecute(id: string) {
|
||||||
|
@ -972,7 +981,6 @@
|
||||||
activeDefinedId.value = record.apiDefinitionId;
|
activeDefinedId.value = record.apiDefinitionId;
|
||||||
showDifferentDrawer.value = true;
|
showDifferentDrawer.value = true;
|
||||||
}
|
}
|
||||||
// 关闭对比 TODO 等待联调
|
|
||||||
function closeDifferent() {
|
function closeDifferent() {
|
||||||
showDifferentDrawer.value = false;
|
showDifferentDrawer.value = false;
|
||||||
activeApiCaseId.value = '';
|
activeApiCaseId.value = '';
|
||||||
|
|
|
@ -69,7 +69,24 @@
|
||||||
<MsTagsInput v-model:model-value="detailForm.tags" :max-tag-count="1" />
|
<MsTagsInput v-model:model-value="detailForm.tags" :max-tag-count="1" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<div class="px-[16px] font-medium">{{ t('apiTestManagement.requestParams') }}</div>
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="px-[16px] font-medium">{{ t('apiTestManagement.requestParams') }}</div>
|
||||||
|
<!-- 与定义不一致 TODO 等待联调 -->
|
||||||
|
<MsTag
|
||||||
|
v-if="detailForm.inconsistentWithApi"
|
||||||
|
class="cursor-pointer"
|
||||||
|
type="warning"
|
||||||
|
theme="light"
|
||||||
|
:tooltip-disabled="true"
|
||||||
|
@click="showDiffDrawer"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<MsIcon type="icon-icon_warning_colorful" size="16" />
|
||||||
|
</template>
|
||||||
|
<span class="ml-[8px]"> {{ t('case.definitionInconsistent') }}</span>
|
||||||
|
</MsTag>
|
||||||
|
</div>
|
||||||
|
|
||||||
<requestComposition
|
<requestComposition
|
||||||
ref="requestCompositionRef"
|
ref="requestCompositionRef"
|
||||||
v-model:request="detailForm"
|
v-model:request="detailForm"
|
||||||
|
@ -94,6 +111,7 @@
|
||||||
|
|
||||||
import MsDetailCard, { type Description } from '@/components/pure/ms-detail-card/index.vue';
|
import MsDetailCard, { type Description } from '@/components/pure/ms-detail-card/index.vue';
|
||||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||||
|
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
|
||||||
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
|
import MsTagsInput from '@/components/pure/ms-tags-input/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';
|
||||||
|
@ -129,6 +147,7 @@
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'loadCase', id?: string): void;
|
(e: 'loadCase', id?: string): void;
|
||||||
|
(e: 'showDiff', apiDetailInfo: ApiCaseDetail): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
@ -384,6 +403,11 @@
|
||||||
detailForm.value.executeLoading = false;
|
detailForm.value.executeLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查看与定义不一致
|
||||||
|
function showDiffDrawer() {
|
||||||
|
emit('showDiff', detailForm.value as unknown as ApiCaseDetail);
|
||||||
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
open,
|
open,
|
||||||
});
|
});
|
||||||
|
|
|
@ -32,22 +32,15 @@
|
||||||
</a-checkbox-group>
|
</a-checkbox-group>
|
||||||
<a-divider direction="vertical" :margin="0" class="!mr-[8px]"></a-divider>
|
<a-divider direction="vertical" :margin="0" class="!mr-[8px]"></a-divider>
|
||||||
<a-switch v-model:model-value="form.ignoreUpdate" size="small" />
|
<a-switch v-model:model-value="form.ignoreUpdate" size="small" />
|
||||||
<a-select
|
<div class="ml-[8px]">{{ t('case.ignoreAllChange') }}</div>
|
||||||
v-model="form.ignoreUpdateType"
|
|
||||||
class="ml-[8px] w-[160px]"
|
|
||||||
:placeholder="t('caseManagement.featureCase.PleaseSelect')"
|
|
||||||
:disabled="!form.ignoreUpdate"
|
|
||||||
@change="changeIgnoreType"
|
|
||||||
>
|
|
||||||
<a-option v-for="item of ignoreList" :key="item.value" :value="item.value">
|
|
||||||
{{ t(item.label) }}
|
|
||||||
</a-option>
|
|
||||||
</a-select>
|
|
||||||
<a-divider direction="vertical" :margin="8"></a-divider>
|
<a-divider direction="vertical" :margin="8"></a-divider>
|
||||||
<a-switch v-model:model-value="form.deleteParams" size="small" />
|
<a-switch v-model:model-value="form.deleteParams" size="small" />
|
||||||
<div class="ml-[8px] font-normal text-[var(--color-text-1)]">{{ t('case.deleteNotCorrespondValue') }}</div>
|
<div class="ml-[8px] font-normal text-[var(--color-text-1)]">{{ t('case.deleteNotCorrespondValue') }}</div>
|
||||||
<a-divider direction="vertical" :margin="0" class="!ml-[8px]"></a-divider>
|
<a-divider direction="vertical" :margin="0" class="!ml-[8px]"></a-divider>
|
||||||
<a-button class="mx-[12px]" type="secondary" @click="cancel">{{ t('common.cancel') }}</a-button>
|
<a-button class="mx-[12px]" type="secondary" @click="cancel">{{ t('common.cancel') }}</a-button>
|
||||||
|
<a-button class="mr-[12px]" type="outline">
|
||||||
|
{{ t('case.ignoreAllChange') }}
|
||||||
|
</a-button>
|
||||||
<a-button type="primary" :loading="syncLoading" :disabled="!form.checkType.length" @click="confirmBatchSync">
|
<a-button type="primary" :loading="syncLoading" :disabled="!form.checkType.length" @click="confirmBatchSync">
|
||||||
{{ t('case.apiSyncChange') }}
|
{{ t('case.apiSyncChange') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
|
@ -157,17 +150,6 @@
|
||||||
|
|
||||||
const form = ref({ ...initForm });
|
const form = ref({ ...initForm });
|
||||||
|
|
||||||
const ignoreList = ref([
|
|
||||||
{
|
|
||||||
value: 'THIS_TIME',
|
|
||||||
label: t('case.ignoreThisChange'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'ALL',
|
|
||||||
label: t('case.ignoreAllChange'),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 忽略更新
|
// 忽略更新
|
||||||
function changeIgnoreType() {}
|
function changeIgnoreType() {}
|
||||||
|
|
||||||
|
|
|
@ -222,9 +222,15 @@ export default {
|
||||||
'case.NoticeApiScenarioCreator': 'Notify the founder of citing the use case scenario',
|
'case.NoticeApiScenarioCreator': 'Notify the founder of citing the use case scenario',
|
||||||
'case.apiAndCaseDiff': 'Interface vs. use case differences',
|
'case.apiAndCaseDiff': 'Interface vs. use case differences',
|
||||||
'case.ignoreThisChange': 'Ignore this change',
|
'case.ignoreThisChange': 'Ignore this change',
|
||||||
'case.ignoreAllChange': 'Ignore all changes',
|
'case.ignoreAllChange': 'Ignore each time difference',
|
||||||
'case.diffAdd': 'Add',
|
'case.diffAdd': 'Add',
|
||||||
'case.notSetData': 'No data has been set',
|
'case.notSetData': 'No data has been set',
|
||||||
|
'case.definitionInconsistent': 'Inconsistent with the definition',
|
||||||
|
'case.haveIgnoredTheChange': 'Have ignored the change',
|
||||||
|
'case.eachHasBeenIgnored': 'Each change difference has been ignored',
|
||||||
|
'case.apiCaseList': 'List',
|
||||||
|
'case.apiCaseDetail': 'Use case details',
|
||||||
|
'case.belongingApi': 'Belonging interface',
|
||||||
'case.saveContinueText': 'Save and Continue Creating',
|
'case.saveContinueText': 'Save and Continue Creating',
|
||||||
'case.detail.changeHistoryTip':
|
'case.detail.changeHistoryTip':
|
||||||
"View and compare historical changes. According to the administrator's settings, historical data will be automatically deleted",
|
"View and compare historical changes. According to the administrator's settings, historical data will be automatically deleted",
|
||||||
|
|
|
@ -209,9 +209,15 @@ export default {
|
||||||
'case.NoticeApiScenarioCreator': '通知引用该用例的场景创建人',
|
'case.NoticeApiScenarioCreator': '通知引用该用例的场景创建人',
|
||||||
'case.apiAndCaseDiff': '接口与用例差异对比',
|
'case.apiAndCaseDiff': '接口与用例差异对比',
|
||||||
'case.ignoreThisChange': '忽略本次变更差异',
|
'case.ignoreThisChange': '忽略本次变更差异',
|
||||||
'case.ignoreAllChange': '忽略全部变更差异',
|
'case.ignoreAllChange': '忽略每次变更差异',
|
||||||
'case.diffAdd': '新增',
|
'case.diffAdd': '新增',
|
||||||
'case.notSetData': '暂未设置数据',
|
'case.notSetData': '暂未设置数据',
|
||||||
|
'case.definitionInconsistent': '与定义不一致',
|
||||||
|
'case.haveIgnoredTheChange': '已忽略本次变更差异',
|
||||||
|
'case.eachHasBeenIgnored': '已忽略每次变更差异',
|
||||||
|
'case.apiCaseList': '列表',
|
||||||
|
'case.apiCaseDetail': '用例详情',
|
||||||
|
'case.belongingApi': '所属接口',
|
||||||
'case.saveContinueText': '保存并继续创建',
|
'case.saveContinueText': '保存并继续创建',
|
||||||
'case.detail.changeHistoryTip': '查看、对比历史修改,根据管理员设置规则,变更历史数据将自动删除',
|
'case.detail.changeHistoryTip': '查看、对比历史修改,根据管理员设置规则,变更历史数据将自动删除',
|
||||||
'case.detail.noReminders': '不再提醒',
|
'case.detail.noReminders': '不再提醒',
|
||||||
|
|
|
@ -115,7 +115,7 @@
|
||||||
const apiCaseText = `▪ 本次测试包含${apiCaseDetail.caseTotal}条接口测试用例,执行了${apiCaseDetail.hasExecutedCase}条,未执行${apiCaseDetail.pending}条,执行率为${apiCaseDetail.apiExecutedRate},通过用例${apiCaseDetail.success}条,通过率为${apiCaseDetail.successRate}。共发现缺陷 ${props.detail.apiBugCount} 个。<br>`;
|
const apiCaseText = `▪ 本次测试包含${apiCaseDetail.caseTotal}条接口测试用例,执行了${apiCaseDetail.hasExecutedCase}条,未执行${apiCaseDetail.pending}条,执行率为${apiCaseDetail.apiExecutedRate},通过用例${apiCaseDetail.success}条,通过率为${apiCaseDetail.successRate}。共发现缺陷 ${props.detail.apiBugCount} 个。<br>`;
|
||||||
const apiCaseDesc = apiCaseDetail.caseTotal ? `${apiCaseText}` : ``;
|
const apiCaseDesc = apiCaseDetail.caseTotal ? `${apiCaseText}` : ``;
|
||||||
|
|
||||||
const scenarioCaseText = `▪ 本次测试包含${apiScenarioDetail.caseTotal}条场景测试用例,执行了${apiScenarioDetail.hasExecutedCase}条,未执行${apiScenarioDetail.pending}条,执行率为${apiScenarioDetail.apiExecutedRate}%,通过用例${apiScenarioDetail.success}条,通过率为${apiScenarioDetail.successRate}。共发现缺陷${props.detail.scenarioBugCount}个`;
|
const scenarioCaseText = `▪ 本次测试包含${apiScenarioDetail.caseTotal}条场景测试用例,执行了${apiScenarioDetail.hasExecutedCase}条,未执行${apiScenarioDetail.pending}条,执行率为${apiScenarioDetail.apiExecutedRate},通过用例${apiScenarioDetail.success}条,通过率为${apiScenarioDetail.successRate}。共发现缺陷${props.detail.scenarioBugCount}个`;
|
||||||
const scenarioCaseDesc = apiScenarioDetail.caseTotal ? `${scenarioCaseText}` : ``;
|
const scenarioCaseDesc = apiScenarioDetail.caseTotal ? `${scenarioCaseText}` : ``;
|
||||||
|
|
||||||
const isPass = Number(allSuccessRate) >= Number(props.detail.passThreshold);
|
const isPass = Number(allSuccessRate) >= Number(props.detail.passThreshold);
|
||||||
|
|
Loading…
Reference in New Issue