feat(接口管理): 接口定义响应内容调整

This commit is contained in:
baiqi 2024-03-05 21:13:19 +08:00 committed by Craftsman
parent 05638aab20
commit 5e17fe21cb
8 changed files with 137 additions and 116 deletions

View File

@ -1,6 +1,6 @@
export interface TabItem {
id: string | number;
label: string;
label?: string;
closable?: boolean;
unSaved?: boolean; // 未保存
draggable?: boolean;

View File

@ -414,18 +414,13 @@ export interface ResponseDefinitionBody {
rawBody: ExecuteValueBody;
binaryBody: ExecuteBinaryBody;
}
export interface ResponseDefinitionHeader extends EnableKeyValueParam {
notBlankValue: boolean;
valid: boolean;
}
// 响应定义
export interface ResponseDefinition {
id: string | number;
statusCode: string | number;
defaultFlag: boolean; // 默认响应标志
name: string; // 响应名称
headers: ResponseDefinitionHeader[];
headers: KeyValueParam[];
body: ResponseDefinitionBody;
[key: string]: any; // 用于前端渲染时填充的自定义信息,后台无此字段
}

View File

@ -2,6 +2,7 @@ import {
EnableKeyValueParam,
ExecuteRequestCommonParam,
ExecuteRequestFormBodyFormValue,
ResponseDefinition,
} from '@/models/apiTest/common';
import { RequestContentTypeEnum, RequestParamsType, ResponseBodyFormat, ResponseComposition } from '@/enums/apiEnum';
@ -42,10 +43,10 @@ export const defaultRequestParamsItem: ExecuteRequestCommonParam = {
};
// 请求的响应 response 默认的响应信息项
export const defaultResponseItem = {
export const defaultResponseItem: ResponseDefinition = {
id: new Date().getTime(),
label: 'apiTestManagement.response',
name: 'apiTestManagement.response',
label: 'apiTestManagement.response',
closable: false,
statusCode: 200,
defaultFlag: true,
@ -72,3 +73,6 @@ export const defaultResponseItem = {
},
},
};
// 请求的响应 response 的响应状态码集合
export const statusCodes = [200, 201, 202, 203, 204, 205, 400, 401, 402, 403, 404, 405, 500, 501, 502, 503, 504, 505];

View File

@ -102,15 +102,15 @@
</div>
</div>
</div>
<!-- 接口定义多出一个输入框占高度 40px -->
<div ref="splitContainerRef" :class="`${props.isDefinition ? 'h-[calc(100%-80px)]' : 'h-[calc(100%-40px)]'}`">
<div ref="splitContainerRef" :class="splitContainerClass">
<MsSplitBox
ref="splitBoxRef"
v-model:size="splitBoxSize"
:max="0.98"
:max="!showResponse ? 1 : 0.98"
min="10px"
:direction="activeLayout"
second-container-class="!overflow-y-hidden"
:class="!showResponse ? 'hidden-second' : ''"
@expand-change="handleExpandChange"
>
<template #first>
@ -204,13 +204,14 @@
</template>
<template #second>
<response
v-show="showResponse"
v-model:active-layout="activeLayout"
v-model:active-tab="requestVModel.responseActiveTab"
:is-expanded="isExpanded"
:response-definition="requestVModel.responseDefinition"
:hide-layout-switch="props.hideResponseLayoutSwitch"
:request-task-result="requestVModel.response"
:is-edit="props.isDefinition"
:is-edit="props.isDefinition && isHttpProtocol"
:upload-temp-file-api="props.uploadTempFileApi"
:loading="requestVModel.executeLoading || loading"
@change-expand="changeExpand"
@ -547,9 +548,16 @@
const formData = tempForm || requestVModel.value;
if (fApi.value) {
const form = {};
pluginScriptMap.value[requestVModel.value.protocol].apiDebugFields?.forEach((key) => {
form[key] = formData[key];
});
if (props.isDefinition) {
// 使
pluginScriptMap.value[requestVModel.value.protocol].apiDefinitionFields?.forEach((key) => {
form[key] = formData[key];
});
} else {
pluginScriptMap.value[requestVModel.value.protocol].apiDebugFields?.forEach((key) => {
form[key] = formData[key];
});
}
fApi.value?.setValue(form);
}
});
@ -654,10 +662,22 @@
handleActiveDebugChange();
}
const splitBoxSize = ref<string | number>(0.6);
const showResponse = computed(
() =>
isHttpProtocol.value ||
!props.isDefinition ||
requestVModel.value.response?.requestResults[0]?.responseResult.responseCode
);
const splitBoxSize = ref<string | number>(!showResponse.value ? 1 : 0.6);
const activeLayout = ref<'horizontal' | 'vertical'>('vertical');
const splitContainerRef = ref<HTMLElement>();
const secondBoxHeight = ref(0);
const splitContainerClass = computed(() => {
if (!showResponse.value) {
return 'h-full';
}
return 'h-[calc(100%-40px)]';
});
watch(
() => splitBoxSize.value,
@ -1056,4 +1076,9 @@
:deep(.arco-tabs-tab) {
@apply leading-none;
}
.hidden-second {
:deep(.arco-split-trigger) {
@apply hidden;
}
}
</style>

View File

@ -10,7 +10,7 @@
<template #label="{ tab }">
<div class="response-tab">
<div v-if="tab.defaultFlag" class="response-tab-default-icon"></div>
{{ t(tab.label) }}({{ tab.statusCode }})
{{ t(tab.label || '') }}({{ tab.statusCode }})
<MsMoreAction
:list="
tab.defaultFlag
@ -23,8 +23,8 @@
<popConfirm
v-model:visible="tab.showRenamePopConfirm"
mode="tabRename"
:field-config="{ field: t(tab.label) }"
:all-names="responseTabs.map((e) => t(tab.label))"
:field-config="{ field: t(tab.label || '') }"
:all-names="responseTabs.map((e) => t(tab.label || ''))"
:popup-offset="20"
@rename-finish="
(val) => {
@ -64,33 +64,33 @@
<a-tab-pane v-for="item of responseCompositionTabList" :key="item.value" :title="item.label" />
</a-tabs>
<div class="response-container">
<div class="mb-[8px] flex items-center justify-between">
<a-radio-group
v-model:model-value="activeResponse.body.bodyType"
type="button"
size="small"
@change="(val) => changeBodyFormat(val as ResponseBodyFormat)"
>
<a-radio v-for="item of ResponseBodyFormat" :key="item" :value="item">
{{ ResponseBodyFormat[item].toLowerCase() }}
</a-radio>
</a-radio-group>
<div v-if="activeResponse.body.bodyType === ResponseBodyFormat.JSON" class="ml-auto flex items-center">
<template v-if="activeResponse.responseActiveTab === ResponseComposition.BODY">
<div class="mb-[8px] flex items-center justify-between">
<a-radio-group
v-model:model-value="activeResponse.body.jsonBody.enableJsonSchema"
size="mini"
@change="emit('change')"
v-model:model-value="activeResponse.body.bodyType"
type="button"
size="small"
@change="(val) => changeBodyFormat(val as ResponseBodyFormat)"
>
<a-radio :value="false">Json</a-radio>
<a-radio class="mr-0" :value="true"> Json Schema </a-radio>
<a-radio v-for="item of ResponseBodyFormat" :key="item" :value="item">
{{ ResponseBodyFormat[item].toLowerCase() }}
</a-radio>
</a-radio-group>
<div class="flex items-center gap-[8px]">
<a-switch v-model:model-value="activeResponse.body.jsonBody.enableTransition" size="small" type="line" />
{{ t('apiTestManagement.dynamicConversion') }}
<div v-if="activeResponse.body.bodyType === ResponseBodyFormat.JSON" class="ml-auto flex items-center">
<a-radio-group
v-model:model-value="activeResponse.body.jsonBody.enableJsonSchema"
size="mini"
@change="emit('change')"
>
<a-radio :value="false">Json</a-radio>
<a-radio class="mr-0" :value="true"> Json Schema </a-radio>
</a-radio-group>
<div class="flex items-center gap-[8px]">
<a-switch v-model:model-value="activeResponse.body.jsonBody.enableTransition" size="small" type="line" />
{{ t('apiTestManagement.dynamicConversion') }}
</div>
</div>
</div>
</div>
<template v-if="activeResponse.responseActiveTab === ResponseComposition.BODY">
<div
v-if="
[ResponseBodyFormat.JSON, ResponseBodyFormat.XML, ResponseBodyFormat.RAW].includes(
@ -133,6 +133,20 @@
</div>
</div>
</template>
<paramTable
v-else-if="activeResponse.responseActiveTab === ResponseComposition.HEADER"
v-model:params="activeResponse.headers"
:columns="columns"
:default-param-item="[
{
key: '',
value: '',
},
]"
:selectable="false"
@change="emit('change')"
/>
<a-select v-else v-model:model-value="activeResponse.statusCode" :options="statusCodeOptions" class="w-[200px]" />
</div>
</template>
@ -143,12 +157,11 @@
import { LanguageEnum } from '@/components/pure/ms-code-editor/types';
import MsEditableTab from '@/components/pure/ms-editable-tab/index.vue';
import { TabItem } from '@/components/pure/ms-editable-tab/types';
import { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import MsMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import { MsFileItem } from '@/components/pure/ms-upload/types';
import MsAddAttachment from '@/components/business/ms-add-attachment/index.vue';
import paramTable, { ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
import popConfirm from '@/views/api-test/components/popConfirm.vue';
import { useI18n } from '@/hooks/useI18n';
@ -157,7 +170,7 @@
import { ResponseDefinition } from '@/models/apiTest/common';
import { ResponseBodyFormat, ResponseComposition } from '@/enums/apiEnum';
import { defaultResponseItem } from '../../config';
import { defaultResponseItem, statusCodes } from '../../config';
const props = defineProps<{
responseDefinition: ResponseDefinition[];
@ -338,42 +351,24 @@
}
}
const columns: MsTableColumn = [
const columns: ParamTableColumn[] = [
{
title: 'apiTestDebug.content',
dataIndex: 'content',
showTooltip: true,
title: 'apiTestManagement.paramName',
dataIndex: 'key',
slotName: 'key',
},
{
title: 'apiTestDebug.status',
dataIndex: 'status',
slotName: 'status',
width: 80,
},
{
title: '',
dataIndex: 'desc',
showTooltip: true,
title: 'apiTestManagement.paramVal',
dataIndex: 'value',
slotName: 'value',
isNormal: true,
},
];
const { propsRes, propsEvent } = useTable(() => Promise.resolve([]), {
scroll: { x: '100%' },
columns,
});
propsRes.value.data = [
{
id: new Date().getTime(),
content: 'Response Code equals: 200',
status: 1,
desc: '',
},
{
id: new Date().getTime(),
content: '$.users[1].age REGEX: 31',
status: 0,
desc: `Value expected to match regexp '31', but it did not match: '30' match: '30'`,
},
] as any;
const statusCodeOptions = statusCodes.map((e) => ({
label: e.toString(),
value: e,
}));
</script>
<style lang="less" scoped>

View File

@ -131,23 +131,6 @@
<a-tab-pane v-if="!activeApiTab.isNew" key="case" :title="t('apiTestManagement.case')" class="ms-api-tab-pane">
</a-tab-pane>
<a-tab-pane v-if="!activeApiTab.isNew" key="mock" title="MOCK" class="ms-api-tab-pane"> </a-tab-pane>
<template #extra>
<div class="flex items-center gap-[8px] pr-[24px]">
<a-button type="outline" class="arco-btn-outline--secondary !p-[8px]">
<template #icon>
<icon-location class="text-[var(--color-text-4)]" />
</template>
</a-button>
<MsSelect
v-model:model-value="checkedEnv"
mode="static"
:options="envOptions"
class="!w-[150px]"
:search-keys="['label']"
allow-search
/>
</div>
</template>
</a-tabs>
</div>
</div>
@ -163,7 +146,6 @@
// import MsFormCreate from '@/components/pure/ms-form-create/formCreate.vue';
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
import MsSelect from '@/components/business/ms-select';
import addDependencyDrawer from './addDependencyDrawer.vue';
import apiTable from './apiTable.vue';
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
@ -402,26 +384,6 @@
}
}
const checkedEnv = ref('DEV');
const envOptions = ref([
{
label: 'DEV',
value: 'DEV',
},
{
label: 'TEST',
value: 'TEST',
},
{
label: 'PRE',
value: 'PRE',
},
{
label: 'PROD',
value: 'PROD',
},
]);
// const fApi = ref();
// const options = {
// form: {

View File

@ -13,10 +13,28 @@
<a-tab-pane key="case" title="CASE" class="ms-api-tab-pane"> </a-tab-pane>
<a-tab-pane key="mock" title="MOCK" class="ms-api-tab-pane"> </a-tab-pane>
<a-tab-pane key="doc" :title="t('apiTestManagement.doc')" class="ms-api-tab-pane"> </a-tab-pane>
<template #extra>
<div class="flex items-center gap-[8px] pr-[24px]">
<a-button type="outline" class="arco-btn-outline--secondary !p-[8px]">
<template #icon>
<icon-location class="text-[var(--color-text-4)]" />
</template>
</a-button>
<MsSelect
v-model:model-value="checkedEnv"
mode="static"
:options="envOptions"
class="!w-[150px]"
:search-keys="['label']"
allow-search
/>
</div>
</template>
</a-tabs>
</template>
<script setup lang="ts">
import MsSelect from '@/components/business/ms-select';
import api from './api/index.vue';
import { useI18n } from '@/hooks/useI18n';
@ -44,6 +62,26 @@
}
}
const checkedEnv = ref('DEV');
const envOptions = ref([
{
label: 'DEV',
value: 'DEV',
},
{
label: 'TEST',
value: 'TEST',
},
{
label: 'PRE',
value: 'PRE',
},
{
label: 'PROD',
value: 'PROD',
},
]);
defineExpose({
newTab,
});

View File

@ -88,6 +88,8 @@ export default {
'apiTestManagement.response': '响应{count}',
'apiTestManagement.responseCode': '响应码',
'apiTestManagement.dynamicConversion': '动态转换',
'apiTestManagement.expandApi': '展开全部请求',
'apiTestManagement.collapseApi': '收起全部请求',
'apiTestManagement.expandApi': '显示全部请求',
'apiTestManagement.collapseApi': '隐藏全部请求',
'apiTestManagement.paramName': '参数名',
'apiTestManagement.paramVal': '参数值',
};