refactor(接口管理): 接口定义-布局重构&另存为用例
This commit is contained in:
parent
ad99fd8f92
commit
63d905dc60
|
@ -1,5 +1,6 @@
|
|||
import MSR from '@/api/http/index';
|
||||
import {
|
||||
AddCaseUrl,
|
||||
AddDefinitionScheduleUrl,
|
||||
AddDefinitionUrl,
|
||||
AddModuleUrl,
|
||||
|
@ -53,6 +54,7 @@ import {
|
|||
|
||||
import { ExecuteRequestParams } from '@/models/apiTest/common';
|
||||
import {
|
||||
AddApiCaseParams,
|
||||
ApiCaseBatchEditParams,
|
||||
ApiCaseBatchParams,
|
||||
ApiCaseDetail,
|
||||
|
@ -342,3 +344,8 @@ export function batchEditCase(data: ApiCaseBatchEditParams) {
|
|||
export function dragSort(data: DragSortParams) {
|
||||
return MSR.post({ url: SortCaseUrl, data });
|
||||
}
|
||||
|
||||
// 添加接口用例
|
||||
export function addCase(data: AddApiCaseParams) {
|
||||
return MSR.post({ url: AddCaseUrl, data });
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ export const ToggleFollowDefinitionUrl = '/api/definition/follow'; // 接口定
|
|||
export const OperationHistoryUrl = '/api/definition/operation-history'; // 接口定义-变更历史
|
||||
export const SaveOperationHistoryUrl = '/api/definition/operation-history/save'; // 接口定义-另存变更历史为指定版本
|
||||
export const RecoverOperationHistoryUrl = '/api/definition/operation-history/recover'; // 接口定义-变更历史恢复
|
||||
export const DefinitionReferenceUrl = '/api/definition/get-reference'; // 获取接口引用关系
|
||||
|
||||
/**
|
||||
* Mock
|
||||
|
@ -42,11 +43,6 @@ export const DefinitionMockPageUrl = '/api/definition/mock/page'; // mock列表
|
|||
export const UpdateMockStatusUrl = '/api/definition/mock/enable/'; // 更新mock状态
|
||||
export const DeleteMockUrl = '/api/definition/mock/delete'; // 刪除mock
|
||||
|
||||
/**
|
||||
* 接口引用关系
|
||||
*/
|
||||
export const DefinitionReferenceUrl = '/api/definition/get-reference'; // 获取接口引用关系
|
||||
|
||||
/**
|
||||
* api回收站
|
||||
*/
|
||||
|
@ -65,3 +61,4 @@ export const DeleteCaseUrl = '/api/case/delete'; // 删除接口用例
|
|||
export const BatchDeleteCaseUrl = '/api/case/batch/delete'; // 批量删除接口用例
|
||||
export const BatchEditCaseUrl = '/api/case/batch/edit'; // 批量编辑接口用例
|
||||
export const SortCaseUrl = '/api/case/edit/pos'; // 接口用例拖拽
|
||||
export const AddCaseUrl = '/api/case/add'; // 添加用例
|
||||
|
|
|
@ -243,9 +243,6 @@
|
|||
});
|
||||
const buttonDropDownVisible = ref(false);
|
||||
|
||||
watchEffect(() => {
|
||||
console.log('innerFileList', innerFileList.value);
|
||||
});
|
||||
onBeforeMount(() => {
|
||||
// 回显文件
|
||||
const defaultFiles = innerFileList.value.filter((item) => item) || [];
|
||||
|
|
|
@ -193,9 +193,6 @@
|
|||
<div class="ms-params-popover-title !mb-[8px]">
|
||||
{{ t('ms.paramsInput.value') }}
|
||||
</div>
|
||||
<div class="ms-params-popover-subtitle">
|
||||
{{ t('ms.paramsInput.value') }}
|
||||
</div>
|
||||
<div class="ms-params-popover-value mb-[8px]">
|
||||
{{ innerValue }}
|
||||
</div>
|
||||
|
@ -702,7 +699,7 @@
|
|||
margin-bottom: 2px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: var(--color-text-4);
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
.ms-params-popover-value {
|
||||
min-width: 100px;
|
||||
|
|
|
@ -21,7 +21,10 @@
|
|||
:style="{ width: item.width }"
|
||||
>
|
||||
<div class="text-[var(--color-text-4)]">{{ t(item.locale) }}</div>
|
||||
<MsTagGroup v-if="Array.isArray(item.value)" :tag-list="item.value" size="small" is-string-tag />
|
||||
<div v-if="Array.isArray(item.value)">
|
||||
<MsTagGroup v-if="item.value.length > 0" :tag-list="item.value" size="small" is-string-tag />
|
||||
<div v-else>-</div>
|
||||
</div>
|
||||
<slot v-else :name="item.key" :value="item.value">
|
||||
<a-tooltip :content="item.value" :disabled="isEmpty(item.value)">
|
||||
<div class="text-[var(--color-text-1)]">{{ item.value || '-' }}</div>
|
||||
|
|
|
@ -21,13 +21,13 @@
|
|||
>
|
||||
<div
|
||||
v-if="props.direction === 'horizontal' && props.expandDirection === 'right' && !props.disabled"
|
||||
class="absolute right-0 h-full w-[16px]"
|
||||
class="absolute right-0 h-full w-[12px]"
|
||||
>
|
||||
<div class="expand-icon expand-icon--left" @click="() => changeExpand()">
|
||||
<MsIcon
|
||||
:type="isExpanded ? 'icon-icon_up-left_outlined' : 'icon-icon_down-right_outlined'"
|
||||
class="text-[var(--color-text-brand)]"
|
||||
size="12"
|
||||
class="!w-auto text-[var(--color-text-brand)]"
|
||||
size="9"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -43,13 +43,13 @@
|
|||
<div class="ms-split-box-second">
|
||||
<div
|
||||
v-if="props.direction === 'horizontal' && props.expandDirection === 'left' && !props.disabled"
|
||||
class="absolute h-full w-[16px]"
|
||||
class="absolute h-full w-[12px]"
|
||||
>
|
||||
<div class="expand-icon" @click="() => changeExpand()">
|
||||
<MsIcon
|
||||
:type="isExpanded ? 'icon-icon_up-left_outlined' : 'icon-icon_down-right_outlined'"
|
||||
class="text-[var(--color-text-brand)]"
|
||||
size="12"
|
||||
class="!w-auto text-[var(--color-text-brand)]"
|
||||
size="9"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -207,3 +207,9 @@ export enum RequestExtractResultMatchingRule {
|
|||
RANDOM = 'RANDOM', // 随机匹配
|
||||
SPECIFIC = 'SPECIFIC', // 指定匹配
|
||||
}
|
||||
// 接口用例状态
|
||||
export enum RequestCaseStatus {
|
||||
DEPRECATED = 'DEPRECATED',
|
||||
PROCESSING = 'PROCESSING',
|
||||
DONE = 'DONE',
|
||||
}
|
||||
|
|
|
@ -131,4 +131,5 @@ export default {
|
|||
'common.unFollowSuccess': 'Unfollow successfully',
|
||||
'common.share': 'Share',
|
||||
'common.notRemind': `Don't remind again`,
|
||||
'common.status': 'Status',
|
||||
};
|
||||
|
|
|
@ -134,4 +134,5 @@ export default {
|
|||
'common.unFollowSuccess': '取消关注成功',
|
||||
'common.share': '分享',
|
||||
'common.notRemind': '不再提醒',
|
||||
'common.status': '状态',
|
||||
};
|
||||
|
|
|
@ -331,3 +331,11 @@ export interface ApiCaseBatchEditParams extends ApiCaseBatchParams {
|
|||
environmentId?: string;
|
||||
type: string;
|
||||
}
|
||||
// 添加用例参数
|
||||
export interface AddApiCaseParams extends ExecuteRequestParams {
|
||||
name: string;
|
||||
priority: string;
|
||||
status: string;
|
||||
apiDefinitionId: string | number;
|
||||
tags: string[];
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ export interface CommonParams {
|
|||
[key: string]: any;
|
||||
}
|
||||
export interface EnvConfig {
|
||||
id?: string;
|
||||
commonParams?: CommonParams;
|
||||
commonVariables: EnvConfigItem[];
|
||||
httpConfig: EnvConfigItem[];
|
||||
|
|
|
@ -273,7 +273,7 @@
|
|||
</a-tooltip>
|
||||
</div>
|
||||
<paramTable
|
||||
v-model:params="condition.extractParams"
|
||||
:params="condition.extractParams"
|
||||
:columns="sqlSourceColumns"
|
||||
:selectable="false"
|
||||
:default-param-item="defaultKeyValueParamItem"
|
||||
|
@ -301,6 +301,7 @@
|
|||
mode="button"
|
||||
:step="100"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
class="w-[160px]"
|
||||
model-event="input"
|
||||
/>
|
||||
|
@ -309,7 +310,7 @@
|
|||
<div v-else-if="condition.processorType === RequestConditionProcessor.EXTRACT">
|
||||
<paramTable
|
||||
ref="extractParamsTableRef"
|
||||
v-model:params="condition.extractors"
|
||||
:params="condition.extractors"
|
||||
:default-param-item="defaultExtractParamItem"
|
||||
:columns="extractParamsColumns"
|
||||
:selectable="false"
|
||||
|
@ -842,8 +843,10 @@ if (!result){
|
|||
const protocolList = ref<ProtocolItem[]>([]);
|
||||
onBeforeMount(async () => {
|
||||
try {
|
||||
// TODO:数据从外面传进来
|
||||
protocolList.value = await getProtocolList(appStore.currentOrgId);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
|
@ -899,12 +902,6 @@ if (!result){
|
|||
line-height: 16px;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
.param-popover-subtitle {
|
||||
margin-bottom: 2px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: var(--color-text-4);
|
||||
}
|
||||
.param-popover-value {
|
||||
min-width: 100px;
|
||||
max-width: 280px;
|
||||
|
|
|
@ -5,7 +5,13 @@ import {
|
|||
KeyValueParam,
|
||||
ResponseDefinition,
|
||||
} from '@/models/apiTest/common';
|
||||
import { RequestContentTypeEnum, RequestParamsType, ResponseBodyFormat, ResponseComposition } from '@/enums/apiEnum';
|
||||
import {
|
||||
RequestCaseStatus,
|
||||
RequestContentTypeEnum,
|
||||
RequestParamsType,
|
||||
ResponseBodyFormat,
|
||||
ResponseComposition,
|
||||
} from '@/enums/apiEnum';
|
||||
|
||||
// 请求 body 参数表格默认行的值
|
||||
export const defaultBodyParamsItem: ExecuteRequestFormBodyFormValue = {
|
||||
|
@ -83,3 +89,18 @@ export const defaultKeyValueParamItem: KeyValueParam = {
|
|||
|
||||
// 请求的响应 response 的响应状态码集合
|
||||
export const statusCodes = [200, 201, 202, 203, 204, 205, 400, 401, 402, 403, 404, 405, 500, 501, 502, 503, 504, 505];
|
||||
|
||||
// 用例等级选项
|
||||
export const casePriorityOptions = [
|
||||
{ label: 'P0', value: 'P0' },
|
||||
{ label: 'P1', value: 'P1' },
|
||||
{ label: 'P2', value: 'P2' },
|
||||
{ label: 'P3', value: 'P3' },
|
||||
];
|
||||
|
||||
// 用例状态选项
|
||||
export const caseStatusOptions = [
|
||||
{ label: 'apiTestManagement.processing', value: RequestCaseStatus.PROCESSING },
|
||||
{ label: 'apiTestManagement.deprecate', value: RequestCaseStatus.DEPRECATED },
|
||||
{ label: 'apiTestManagement.done', value: RequestCaseStatus.DONE },
|
||||
];
|
||||
|
|
|
@ -66,12 +66,6 @@
|
|||
line-height: 16px;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
.param-popover-subtitle {
|
||||
margin-bottom: 2px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: var(--color-text-4);
|
||||
}
|
||||
.param-popover-value {
|
||||
min-width: 100px;
|
||||
max-width: 280px;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<MsFormTable v-bind="props" :data="paramsData">
|
||||
<MsFormTable v-bind="props" :data="paramsData" @change="handleFormTableChange">
|
||||
<!-- 展开行-->
|
||||
<template #expand-icon="{ record }">
|
||||
<div class="flex flex-row items-center gap-[2px] text-[var(--color-text-4)]">
|
||||
|
@ -341,7 +341,7 @@
|
|||
<span v-else></span>
|
||||
</template>
|
||||
<template #host="{ record }">
|
||||
<MsTagGroup
|
||||
<MsTagsGroup
|
||||
v-if="Array.isArray(record.domain)"
|
||||
:tag-list="getDomain(record.domain)"
|
||||
size="small"
|
||||
|
@ -459,7 +459,6 @@
|
|||
import MsFormTable, { FormTableColumn } from '@/components/pure/ms-form-table/index.vue';
|
||||
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
|
||||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||
import MsTagGroup from '@/components/pure/ms-tag/ms-tag-group.vue';
|
||||
import MsTagsGroup from '@/components/pure/ms-tag/ms-tag-group.vue';
|
||||
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
|
||||
import { MsFileItem } from '@/components/pure/ms-upload/types';
|
||||
|
@ -495,7 +494,7 @@
|
|||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
params?: any[];
|
||||
params?: Record<string, any>[];
|
||||
defaultParamItem?: Record<string, any>; // 默认参数项,用于添加新行时的默认值
|
||||
columns: ParamTableColumn[];
|
||||
scroll?: {
|
||||
|
@ -548,7 +547,7 @@
|
|||
}
|
||||
);
|
||||
const emit = defineEmits<{
|
||||
(e: 'change', data: any[]): void; // 都触发这个事件以通知父组件参数数组被更改
|
||||
(e: 'change', data: Record<string, any>[], isInit?: boolean): void; // 都触发这个事件以通知父组件参数数组被更改
|
||||
(e: 'moreActionSelect', event: ActionsItem, record: Record<string, any>): void;
|
||||
(e: 'projectChange', projectId: string): void;
|
||||
(e: 'treeDelete', record: Record<string, any>): void;
|
||||
|
@ -557,12 +556,10 @@
|
|||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const paramsData = ref<any[]>(props.params);
|
||||
const paramsData = ref<Record<string, any>[]>([]);
|
||||
|
||||
function emitChange(from: string, isInit?: boolean) {
|
||||
if (!isInit) {
|
||||
emit('change', paramsData.value);
|
||||
}
|
||||
emit('change', paramsData.value, isInit);
|
||||
}
|
||||
|
||||
const paramsLength = computed(() => paramsData.value.length);
|
||||
|
@ -659,28 +656,28 @@
|
|||
|
||||
const hostVisible = ref(false);
|
||||
const hostData = ref<any[]>([]);
|
||||
const hostColumn = [
|
||||
{
|
||||
title: t('project.environmental.http.host'),
|
||||
dataIndex: 'host',
|
||||
showTooltip: true,
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
title: t('project.environmental.http.desc'),
|
||||
dataIndex: 'description',
|
||||
},
|
||||
];
|
||||
// const hostColumn = [
|
||||
// {
|
||||
// title: t('project.environmental.http.host'),
|
||||
// dataIndex: 'host',
|
||||
// showTooltip: true,
|
||||
// width: 300,
|
||||
// },
|
||||
// {
|
||||
// title: t('project.environmental.http.desc'),
|
||||
// dataIndex: 'description',
|
||||
// },
|
||||
// ];
|
||||
|
||||
const showHostModal = (record: Record<string, any>) => {
|
||||
hostVisible.value = true;
|
||||
hostData.value = record.domain || [];
|
||||
};
|
||||
|
||||
const hostModalClose = () => {
|
||||
hostVisible.value = false;
|
||||
hostData.value = [];
|
||||
};
|
||||
// const hostModalClose = () => {
|
||||
// hostVisible.value = false;
|
||||
// hostData.value = [];
|
||||
// };
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.columns.some((e) => e.dataIndex === 'projectId')) {
|
||||
|
@ -698,6 +695,7 @@
|
|||
*/
|
||||
function addTableLine(rowIndex: number, addLineDisabled?: boolean, isInit?: boolean) {
|
||||
if (addLineDisabled) {
|
||||
emitChange('addTableLine addLineDisabled', isInit);
|
||||
return;
|
||||
}
|
||||
if (rowIndex === paramsData.value.length - 1) {
|
||||
|
@ -708,8 +706,8 @@
|
|||
...cloneDeep(props.defaultParamItem), // 深拷贝,避免有嵌套引用类型,数据隔离
|
||||
enable: true, // 是否勾选
|
||||
} as any);
|
||||
emitChange('addTableLine', isInit);
|
||||
}
|
||||
emitChange('addTableLine', isInit);
|
||||
handleMustContainColChange(true);
|
||||
handleTypeCheckingColChange(true);
|
||||
}
|
||||
|
@ -920,6 +918,11 @@
|
|||
});
|
||||
}
|
||||
|
||||
function handleFormTableChange(data: any[]) {
|
||||
paramsData.value = [...data];
|
||||
emitChange('handleFormTableChange');
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
addTableLine,
|
||||
});
|
||||
|
@ -942,12 +945,6 @@
|
|||
line-height: 16px;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
.param-popover-subtitle {
|
||||
margin-bottom: 2px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: var(--color-text-4);
|
||||
}
|
||||
.param-popover-value {
|
||||
min-width: 100px;
|
||||
max-width: 280px;
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
</div>
|
||||
<paramTable
|
||||
v-else-if="innerParams.bodyType === RequestBodyFormat.FORM_DATA"
|
||||
v-model:params="currentTableParams"
|
||||
:params="currentTableParams"
|
||||
:scroll="{ minWidth: 1160 }"
|
||||
:columns="columns"
|
||||
:height-used="heightUsed"
|
||||
|
@ -40,7 +40,7 @@
|
|||
/>
|
||||
<paramTable
|
||||
v-else-if="innerParams.bodyType === RequestBodyFormat.WWW_FORM"
|
||||
v-model:params="currentTableParams"
|
||||
:params="currentTableParams"
|
||||
:scroll="{ minWidth: 1160 }"
|
||||
:columns="columns"
|
||||
:height-used="heightUsed"
|
||||
|
@ -115,6 +115,7 @@
|
|||
import { RequestBodyFormat, RequestParamsType } from '@/enums/apiEnum';
|
||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||
|
||||
import { filterKeyValParams } from '../utils';
|
||||
import { defaultBodyParamsItem } from '@/views/api-test/components/config';
|
||||
|
||||
const props = defineProps<{
|
||||
|
@ -315,14 +316,15 @@
|
|||
*/
|
||||
function handleBatchParamApply(resultArr: any[]) {
|
||||
const files = currentTableParams.value.filter((item) => item.paramType === RequestParamsType.FILE);
|
||||
if (resultArr.length < currentTableParams.value.length) {
|
||||
currentTableParams.value.splice(0, currentTableParams.value.length - 1, ...files, ...resultArr);
|
||||
} else {
|
||||
const filterResult = filterKeyValParams(currentTableParams.value, defaultBodyParamsItem);
|
||||
if (filterResult.lastDataIsDefault) {
|
||||
currentTableParams.value = [
|
||||
...files,
|
||||
...resultArr,
|
||||
currentTableParams.value[currentTableParams.value.length - 1],
|
||||
].filter(Boolean);
|
||||
} else {
|
||||
currentTableParams.value = [...files, ...resultArr].filter(Boolean);
|
||||
}
|
||||
emit('change');
|
||||
}
|
||||
|
|
|
@ -24,10 +24,9 @@
|
|||
import batchAddKeyVal from '@/views/api-test/components/batchAddKeyVal.vue';
|
||||
import paramTable, { ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { EnableKeyValueParam } from '@/models/apiTest/common';
|
||||
|
||||
import { filterKeyValParams } from '../utils';
|
||||
import { defaultHeaderParamsItem } from '@/views/api-test/components/config';
|
||||
|
||||
const props = defineProps<{
|
||||
|
@ -41,8 +40,6 @@
|
|||
(e: 'change'): void; // 数据发生变化
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const innerParams = useVModel(props, 'params', emit);
|
||||
|
||||
const columns: ParamTableColumn[] = [
|
||||
|
@ -80,10 +77,11 @@
|
|||
* 批量参数代码转换为参数表格数据
|
||||
*/
|
||||
function handleBatchParamApply(resultArr: any[]) {
|
||||
if (resultArr.length < innerParams.value.length) {
|
||||
innerParams.value.splice(0, innerParams.value.length - 1, ...resultArr);
|
||||
const filterResult = filterKeyValParams(innerParams.value, defaultHeaderParamsItem);
|
||||
if (filterResult.lastDataIsDefault) {
|
||||
innerParams.value = [...resultArr, innerParams.value[innerParams.value.length - 1]].filter(Boolean);
|
||||
} else {
|
||||
innerParams.value = [...resultArr, innerParams.value[innerParams.value.length - 1]];
|
||||
innerParams.value = resultArr.filter(Boolean);
|
||||
}
|
||||
emit('change');
|
||||
}
|
||||
|
|
|
@ -114,7 +114,7 @@
|
|||
</template>
|
||||
</a-dropdown>
|
||||
<!-- 接口定义-定义模式,直接保存接口定义 -->
|
||||
<a-button v-else type="primary" @click="() => handleSelect('save')">
|
||||
<a-button v-else type="primary" :loading="saveLoading" @click="() => handleSelect('save')">
|
||||
{{ t('common.save') }}
|
||||
</a-button>
|
||||
</template>
|
||||
|
@ -138,32 +138,44 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="splitContainerRef" class="h-[calc(100%-40px)]">
|
||||
<div class="px-[16px]">
|
||||
<MsTab
|
||||
v-model:active-key="requestVModel.activeTab"
|
||||
:content-tab-list="contentTabList"
|
||||
:get-text-func="getTabBadge"
|
||||
class="no-content relative mt-[8px]"
|
||||
/>
|
||||
</div>
|
||||
<div ref="splitContainerRef" class="h-[calc(100%-97px)]">
|
||||
<MsSplitBox
|
||||
ref="splitBoxRef"
|
||||
ref="horizontalSplitBoxRef"
|
||||
:size="props.isDefinition ? 0.7 : 1"
|
||||
:max="props.isDefinition ? 0.9 : 1"
|
||||
:min="props.isDefinition ? 0.7 : 1"
|
||||
:disabled="!props.isDefinition"
|
||||
:class="!props.isDefinition ? 'hidden-second' : ''"
|
||||
:first-container-class="!props.isDefinition ? 'border-r-0' : ''"
|
||||
direction="horizontal"
|
||||
expand-direction="right"
|
||||
>
|
||||
<template #first>
|
||||
<MsSplitBox
|
||||
ref="verticalSplitBoxRef"
|
||||
v-model:size="splitBoxSize"
|
||||
:max="!showResponse ? 1 : 0.98"
|
||||
min="10px"
|
||||
:direction="activeLayout"
|
||||
second-container-class="!overflow-y-hidden"
|
||||
:class="!showResponse ? 'hidden-second' : ''"
|
||||
@expand-change="handleExpandChange"
|
||||
@expand-change="handleVerticalExpandChange"
|
||||
>
|
||||
<template #first>
|
||||
<a-spin class="block h-full w-full" :loading="requestVModel.executeLoading || loading">
|
||||
<div
|
||||
:class="`flex h-full min-w-[800px] flex-col px-[18px] pb-[16px] ${
|
||||
:class="`flex h-full min-w-[800px] flex-col p-[16px] ${
|
||||
activeLayout === 'horizontal' ? ' pr-[16px]' : ''
|
||||
}`"
|
||||
>
|
||||
<div>
|
||||
<MsTab
|
||||
v-model:active-key="requestVModel.activeTab"
|
||||
:content-tab-list="contentTabList"
|
||||
:get-text-func="getTabBadge"
|
||||
class="no-content relative mb-[8px] border-b border-[var(--color-text-n8)]"
|
||||
/>
|
||||
</div>
|
||||
<div class="tab-pane-container">
|
||||
<a-spin
|
||||
v-if="requestVModel.activeTab === RequestComposition.PLUGIN"
|
||||
|
@ -253,19 +265,126 @@
|
|||
:is-http-protocol="isHttpProtocol"
|
||||
:is-priority-local-exec="isPriorityLocalExec"
|
||||
:request-url="requestVModel.url"
|
||||
:is-expanded="isExpanded"
|
||||
:is-expanded="isVerticalExpanded"
|
||||
:hide-layout-switch="props.hideResponseLayoutSwitch"
|
||||
:request-task-result="requestVModel.response"
|
||||
:is-edit="props.isDefinition && isHttpProtocol"
|
||||
:upload-temp-file-api="props.uploadTempFileApi"
|
||||
:loading="requestVModel.executeLoading || loading"
|
||||
@change-expand="changeExpand"
|
||||
@change-expand="changeVerticalExpand"
|
||||
@change-layout="handleActiveLayoutChange"
|
||||
@change="handleActiveDebugChange"
|
||||
@execute="execute"
|
||||
/>
|
||||
</template>
|
||||
</MsSplitBox>
|
||||
</template>
|
||||
<template v-if="props.isDefinition" #second>
|
||||
<div class="p-[16px]">
|
||||
<!-- TODO:第一版没有模板 -->
|
||||
<!-- <MsFormCreate v-model:api="fApi" :rule="currentApiTemplateRules" :option="options" /> -->
|
||||
<a-form ref="activeApiTabFormRef" :model="requestVModel" layout="vertical">
|
||||
<a-form-item
|
||||
field="name"
|
||||
:label="t('apiTestManagement.apiName')"
|
||||
class="mb-[16px]"
|
||||
:rules="[{ required: true, message: t('apiTestManagement.apiNameRequired') }]"
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="requestVModel.name"
|
||||
:max-length="255"
|
||||
:placeholder="t('apiTestManagement.apiNamePlaceholder')"
|
||||
allow-clear
|
||||
@change="handleActiveApiChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('apiTestManagement.belongModule')" class="mb-[16px]">
|
||||
<a-tree-select
|
||||
v-model:modelValue="requestVModel.moduleId"
|
||||
:data="selectTree"
|
||||
:field-names="{ title: 'name', key: 'id', children: 'children' }"
|
||||
:tree-props="{
|
||||
virtualListProps: {
|
||||
height: 200,
|
||||
threshold: 200,
|
||||
},
|
||||
}"
|
||||
allow-search
|
||||
@change="handleActiveApiChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('apiTestManagement.apiStatus')" class="mb-[16px]">
|
||||
<a-select
|
||||
v-model:model-value="requestVModel.status"
|
||||
:placeholder="t('common.pleaseSelect')"
|
||||
class="param-input w-full"
|
||||
@change="handleActiveApiChange"
|
||||
>
|
||||
<template #label>
|
||||
<apiStatus :status="requestVModel.status" />
|
||||
</template>
|
||||
<a-option v-for="item of Object.values(RequestDefinitionStatus)" :key="item" :value="item">
|
||||
<apiStatus :status="item" />
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('common.tag')" class="mb-[16px]">
|
||||
<MsTagsInput v-model:model-value="requestVModel.tags" @change="handleActiveApiChange" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('common.desc')" class="mb-[16px]">
|
||||
<a-textarea
|
||||
v-model:model-value="requestVModel.description"
|
||||
:max-length="1000"
|
||||
@change="handleActiveApiChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<!-- TODO:第一版先不做依赖 -->
|
||||
<!-- <div class="mb-[8px] flex items-center">
|
||||
<div class="text-[var(--color-text-2)]">
|
||||
{{ t('apiTestManagement.addDependency') }}
|
||||
</div>
|
||||
<a-divider margin="4px" direction="vertical" />
|
||||
<MsButton
|
||||
type="text"
|
||||
class="font-medium"
|
||||
:disabled="requestVModel.preDependency.length === 0 && requestVModel.postDependency.length === 0"
|
||||
@click="clearAllDependency"
|
||||
>
|
||||
{{ t('apiTestManagement.clearSelected') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
<div class="rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] p-[12px]">
|
||||
<div class="flex items-center">
|
||||
<div class="flex items-center gap-[4px] text-[var(--color-text-2)]">
|
||||
{{ t('apiTestManagement.preDependency') }}
|
||||
<div class="text-[rgb(var(--primary-5))]">
|
||||
{{ requestVModel.preDependency.length }}
|
||||
</div>
|
||||
{{ t('apiTestManagement.dependencyUnit') }}
|
||||
</div>
|
||||
<a-divider margin="8px" direction="vertical" />
|
||||
<MsButton type="text" class="font-medium" @click="handleDddDependency('pre')">
|
||||
{{ t('apiTestManagement.addPreDependency') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
<div class="mt-[8px] flex items-center">
|
||||
<div class="flex items-center gap-[4px] text-[var(--color-text-2)]">
|
||||
{{ t('apiTestManagement.postDependency') }}
|
||||
<div class="text-[rgb(var(--primary-5))]">
|
||||
{{ requestVModel.postDependency.length }}
|
||||
</div>
|
||||
{{ t('apiTestManagement.dependencyUnit') }}
|
||||
</div>
|
||||
<a-divider margin="8px" direction="vertical" />
|
||||
<MsButton type="text" class="font-medium" @click="handleDddDependency('post')">
|
||||
{{ t('apiTestManagement.addPostDependency') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
</MsSplitBox>
|
||||
</div>
|
||||
</div>
|
||||
<a-modal
|
||||
|
@ -285,7 +404,11 @@
|
|||
:rules="[{ required: true, message: t('apiTestDebug.requestNameRequired') }]"
|
||||
asterisk-position="end"
|
||||
>
|
||||
<a-input v-model:model-value="saveModalForm.name" :placeholder="t('apiTestDebug.requestNamePlaceholder')" />
|
||||
<a-input
|
||||
v-model:model-value="saveModalForm.name"
|
||||
:max-length="255"
|
||||
:placeholder="t('apiTestDebug.requestNamePlaceholder')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
v-if="isHttpProtocol"
|
||||
|
@ -294,7 +417,11 @@
|
|||
:rules="[{ required: true, message: t('apiTestDebug.requestUrlRequired') }]"
|
||||
asterisk-position="end"
|
||||
>
|
||||
<a-input v-model:model-value="saveModalForm.path" :placeholder="t('apiTestDebug.commonPlaceholder')" />
|
||||
<a-input
|
||||
v-model:model-value="saveModalForm.path"
|
||||
:max-length="255"
|
||||
:placeholder="t('apiTestDebug.commonPlaceholder')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('apiTestDebug.requestModule')" class="mb-0">
|
||||
<a-tree-select
|
||||
|
@ -312,6 +439,43 @@
|
|||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<a-modal
|
||||
v-model:visible="saveCaseModalVisible"
|
||||
:title="t('common.save')"
|
||||
:ok-loading="saveCaseLoading"
|
||||
class="ms-modal-form"
|
||||
title-align="start"
|
||||
body-class="!p-0"
|
||||
@before-ok="saveAsCase"
|
||||
@cancel="handleSaveCaseCancel"
|
||||
>
|
||||
<a-form ref="saveCaseModalFormRef" :model="saveCaseModalForm" layout="vertical">
|
||||
<a-form-item
|
||||
field="name"
|
||||
:label="t('case.caseName')"
|
||||
:rules="[{ required: true, message: t('case.caseNameRequired') }]"
|
||||
asterisk-position="end"
|
||||
>
|
||||
<a-input v-model:model-value="saveCaseModalForm.name" :placeholder="t('case.caseNamePlaceholder')" />
|
||||
</a-form-item>
|
||||
<a-form-item field="priority" :label="t('case.caseLevel')">
|
||||
<a-select v-model:model-value="saveCaseModalForm.priority" :options="casePriorityOptions"></a-select>
|
||||
</a-form-item>
|
||||
<a-form-item field="status" :label="t('common.status')">
|
||||
<a-select v-model:model-value="saveCaseModalForm.status" :options="caseStatusOptions"></a-select>
|
||||
</a-form-item>
|
||||
<a-form-item field="tags" :label="t('common.tag')">
|
||||
<MsTagsInput
|
||||
v-model:model-value="saveCaseModalForm.tags"
|
||||
placeholder="common.tagsInputPlaceholder"
|
||||
allow-clear
|
||||
unique-value
|
||||
retain-input-value
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<addDependencyDrawer v-if="props.isDefinition" v-model:visible="showAddDependencyDrawer" :mode="addDependencyMode" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -323,6 +487,7 @@
|
|||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||
import MsTab from '@/components/pure/ms-tab/index.vue';
|
||||
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
|
||||
import auth from './auth.vue';
|
||||
import postcondition from './postcondition.vue';
|
||||
import precondition from './precondition.vue';
|
||||
|
@ -330,8 +495,10 @@
|
|||
import setting from './setting.vue';
|
||||
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
||||
import apiMethodSelect from '@/views/api-test/components/apiMethodSelect.vue';
|
||||
import apiStatus from '@/views/api-test/components/apiStatus.vue';
|
||||
|
||||
import { getPluginScript, getProtocolList } from '@/api/modules/api-test/common';
|
||||
import { addCase } from '@/api/modules/api-test/management';
|
||||
import { getSocket } from '@/api/modules/project-management/commonScript';
|
||||
import { getLocalConfig } from '@/api/modules/user/index';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
@ -348,18 +515,24 @@
|
|||
PluginConfig,
|
||||
RequestTaskResult,
|
||||
} from '@/models/apiTest/common';
|
||||
import { AddApiCaseParams } from '@/models/apiTest/management';
|
||||
import { ModuleTreeNode, TransferFileParams } from '@/models/common';
|
||||
import { EnvConfig } from '@/models/projectManagement/environmental';
|
||||
import {
|
||||
RequestAuthType,
|
||||
RequestBodyFormat,
|
||||
RequestCaseStatus,
|
||||
RequestComposition,
|
||||
RequestConditionProcessor,
|
||||
RequestDefinitionStatus,
|
||||
RequestMethods,
|
||||
RequestParamsType,
|
||||
} from '@/enums/apiEnum';
|
||||
|
||||
import type { ResponseItem } from './response/edit.vue';
|
||||
import {
|
||||
casePriorityOptions,
|
||||
caseStatusOptions,
|
||||
defaultBodyParamsItem,
|
||||
defaultHeaderParamsItem,
|
||||
defaultKeyValueParamItem,
|
||||
|
@ -373,6 +546,9 @@
|
|||
const httpBody = defineAsyncComponent(() => import('./body.vue'));
|
||||
const httpQuery = defineAsyncComponent(() => import('./query.vue'));
|
||||
const httpRest = defineAsyncComponent(() => import('./rest.vue'));
|
||||
const addDependencyDrawer = defineAsyncComponent(
|
||||
() => import('@/views/api-test/management/components/addDependencyDrawer.vue')
|
||||
);
|
||||
|
||||
export interface RequestCustomAttr {
|
||||
isNew: boolean;
|
||||
|
@ -395,6 +571,7 @@
|
|||
isDefinition?: boolean; // 是否是接口定义模式
|
||||
hideResponseLayoutSwitch?: boolean; // 是否隐藏响应体的布局切换
|
||||
otherParams?: Record<string, any>; // 保存请求时的其他参数
|
||||
currentEnvConfig?: EnvConfig;
|
||||
executeApi: (params: ExecuteRequestParams) => Promise<any>; // 执行接口
|
||||
localExecuteApi: (url: string, params: ExecuteRequestParams) => Promise<any>; // 本地执行接口
|
||||
createApi: (...args) => Promise<any>; // 创建接口
|
||||
|
@ -409,7 +586,7 @@
|
|||
update: string;
|
||||
};
|
||||
}>();
|
||||
const emit = defineEmits(['addDone', 'save', 'saveAsCase']);
|
||||
const emit = defineEmits(['addDone']);
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
|
@ -768,18 +945,18 @@
|
|||
}
|
||||
);
|
||||
|
||||
const splitBoxRef = ref<InstanceType<typeof MsSplitBox>>();
|
||||
const isExpanded = ref(true);
|
||||
|
||||
function handleExpandChange(val: boolean) {
|
||||
isExpanded.value = val;
|
||||
const horizontalSplitBoxRef = ref<InstanceType<typeof MsSplitBox>>();
|
||||
const verticalSplitBoxRef = ref<InstanceType<typeof MsSplitBox>>();
|
||||
const isVerticalExpanded = ref(true);
|
||||
function handleVerticalExpandChange(val: boolean) {
|
||||
isVerticalExpanded.value = val;
|
||||
}
|
||||
function changeExpand(val: boolean) {
|
||||
isExpanded.value = val;
|
||||
function changeVerticalExpand(val: boolean) {
|
||||
isVerticalExpanded.value = val;
|
||||
if (val) {
|
||||
splitBoxRef.value?.expand(0.6);
|
||||
verticalSplitBoxRef.value?.expand(0.6);
|
||||
} else {
|
||||
splitBoxRef.value?.collapse(
|
||||
verticalSplitBoxRef.value?.collapse(
|
||||
splitContainerRef.value
|
||||
? `${splitContainerRef.value.clientHeight - (props.hideResponseLayoutSwitch ? 37 : 42)}px`
|
||||
: 0
|
||||
|
@ -791,17 +968,23 @@
|
|||
() => showResponse.value,
|
||||
(val) => {
|
||||
if (val) {
|
||||
changeExpand(true);
|
||||
changeVerticalExpand(true);
|
||||
} else {
|
||||
changeExpand(false);
|
||||
changeVerticalExpand(false);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function handleActiveLayoutChange() {
|
||||
isExpanded.value = true;
|
||||
isVerticalExpanded.value = true;
|
||||
splitBoxSize.value = 0.6;
|
||||
splitBoxRef.value?.expand(0.6);
|
||||
verticalSplitBoxRef.value?.expand(0.6);
|
||||
}
|
||||
|
||||
function handleActiveApiChange() {
|
||||
if (requestVModel.value) {
|
||||
requestVModel.value.unSaved = true;
|
||||
}
|
||||
}
|
||||
|
||||
const reportId = ref('');
|
||||
|
@ -876,6 +1059,7 @@
|
|||
* @param executeType 执行类型,执行时传入
|
||||
*/
|
||||
function makeRequestParams(executeType?: 'localExec' | 'serverExec') {
|
||||
const isExecute = executeType === 'localExec' || executeType === 'serverExec';
|
||||
const { formDataBody, wwwFormBody } = requestVModel.value.body;
|
||||
const polymorphicName = protocolOptions.value.find(
|
||||
(e) => e.value === requestVModel.value.protocol
|
||||
|
@ -883,8 +1067,16 @@
|
|||
let parseRequestBodyResult;
|
||||
let requestParams;
|
||||
if (isHttpProtocol.value) {
|
||||
const realFormDataBodyValues = filterKeyValParams(formDataBody.formValues, defaultBodyParamsItem).validParams;
|
||||
const realWwwFormBodyValues = filterKeyValParams(wwwFormBody.formValues, defaultBodyParamsItem).validParams;
|
||||
const realFormDataBodyValues = filterKeyValParams(
|
||||
formDataBody.formValues,
|
||||
defaultBodyParamsItem,
|
||||
isExecute
|
||||
).validParams;
|
||||
const realWwwFormBodyValues = filterKeyValParams(
|
||||
wwwFormBody.formValues,
|
||||
defaultBodyParamsItem,
|
||||
isExecute
|
||||
).validParams;
|
||||
parseRequestBodyResult = parseRequestBodyFiles(
|
||||
requestVModel.value.body,
|
||||
requestVModel.value.uploadFileIds, // 外面解析详情的时候传入
|
||||
|
@ -901,12 +1093,12 @@
|
|||
formValues: realWwwFormBodyValues,
|
||||
},
|
||||
},
|
||||
headers: filterKeyValParams(requestVModel.value.headers, defaultHeaderParamsItem).validParams,
|
||||
headers: filterKeyValParams(requestVModel.value.headers, defaultHeaderParamsItem, isExecute).validParams,
|
||||
method: requestVModel.value.method,
|
||||
otherConfig: requestVModel.value.otherConfig,
|
||||
path: requestVModel.value.url || requestVModel.value.path,
|
||||
query: filterKeyValParams(requestVModel.value.query, defaultRequestParamsItem).validParams,
|
||||
rest: filterKeyValParams(requestVModel.value.rest, defaultRequestParamsItem).validParams,
|
||||
query: filterKeyValParams(requestVModel.value.query, defaultRequestParamsItem, isExecute).validParams,
|
||||
rest: filterKeyValParams(requestVModel.value.rest, defaultRequestParamsItem, isExecute).validParams,
|
||||
url: requestVModel.value.url,
|
||||
polymorphicName,
|
||||
};
|
||||
|
@ -932,7 +1124,7 @@
|
|||
status: requestVModel.value.status,
|
||||
response: requestVModel.value.responseDefinition?.map((e) => ({
|
||||
...e,
|
||||
headers: filterKeyValParams(e.headers, defaultKeyValueParamItem).validParams,
|
||||
headers: filterKeyValParams(e.headers, defaultKeyValueParamItem, isExecute).validParams,
|
||||
})),
|
||||
};
|
||||
} else {
|
||||
|
@ -942,7 +1134,7 @@
|
|||
return {
|
||||
id: requestVModel.value.id.toString(),
|
||||
reportId: reportId.value,
|
||||
environmentId: '',
|
||||
environmentId: props.currentEnvConfig?.id || '',
|
||||
name: requestName,
|
||||
moduleId: requestModuleId,
|
||||
...apiDefinitionParams,
|
||||
|
@ -1018,7 +1210,7 @@
|
|||
requestVModel.value.executeLoading = false;
|
||||
}
|
||||
|
||||
async function updateDebug() {
|
||||
async function updateRequest() {
|
||||
try {
|
||||
saveLoading.value = true;
|
||||
await props.updateApi({
|
||||
|
@ -1039,38 +1231,58 @@
|
|||
/**
|
||||
* 保存请求
|
||||
*/
|
||||
async function handleSave(done: (closed: boolean) => void) {
|
||||
saveModalFormRef.value?.validate(async (errors) => {
|
||||
if (!errors) {
|
||||
async function realSave(fullParams?: Record<string, any>, silence?: boolean) {
|
||||
try {
|
||||
if (!silence) {
|
||||
saveLoading.value = true;
|
||||
if (requestVModel.value.isNew) {
|
||||
// 若是新建的,走添加
|
||||
const res = await props.createApi({
|
||||
...makeRequestParams(),
|
||||
}
|
||||
let params;
|
||||
if (props.isDefinition) {
|
||||
params = {
|
||||
...(fullParams || makeRequestParams()),
|
||||
...props.otherParams,
|
||||
};
|
||||
} else {
|
||||
params = {
|
||||
...(fullParams || makeRequestParams()),
|
||||
...saveModalForm.value,
|
||||
path: isHttpProtocol.value ? saveModalForm.value.path : undefined,
|
||||
...props.otherParams,
|
||||
});
|
||||
};
|
||||
}
|
||||
const res = await props.createApi(params);
|
||||
if (!silence) {
|
||||
Message.success(t('common.saveSuccess'));
|
||||
}
|
||||
requestVModel.value.id = res.id;
|
||||
requestVModel.value.num = res.num;
|
||||
requestVModel.value.isNew = false;
|
||||
Message.success(t('common.saveSuccess'));
|
||||
requestVModel.value.unSaved = false;
|
||||
requestVModel.value.name = saveModalForm.value.name;
|
||||
requestVModel.value.label = saveModalForm.value.name;
|
||||
requestVModel.value.url = saveModalForm.value.path;
|
||||
saveLoading.value = false;
|
||||
requestVModel.value.name = res.name;
|
||||
requestVModel.value.label = res.name;
|
||||
requestVModel.value.url = res.path;
|
||||
requestVModel.value.path = res.path;
|
||||
console.log('requestVModel.value', requestVModel.value);
|
||||
if (!props.isDefinition) {
|
||||
saveModalVisible.value = false;
|
||||
done(true);
|
||||
}
|
||||
if (!silence) {
|
||||
saveLoading.value = false;
|
||||
emit('addDone');
|
||||
} else {
|
||||
updateDebug();
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
saveLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleSave(done: (closed: boolean) => void) {
|
||||
saveModalFormRef.value?.validate(async (errors) => {
|
||||
if (!errors) {
|
||||
await realSave();
|
||||
done(true);
|
||||
}
|
||||
});
|
||||
done(false);
|
||||
}
|
||||
|
@ -1079,22 +1291,27 @@
|
|||
* 保存快捷键处理
|
||||
*/
|
||||
async function handleSaveShortcut() {
|
||||
if (!requestVModel.value.isNew) {
|
||||
// 更新接口不需要弹窗,直接更新保存
|
||||
updateDebug();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (!isHttpProtocol.value) {
|
||||
// 插件需要校验动态表单
|
||||
await fApi.value?.validate();
|
||||
}
|
||||
if (!requestVModel.value.isNew) {
|
||||
// 更新接口不需要弹窗,直接更新保存
|
||||
updateRequest();
|
||||
return;
|
||||
}
|
||||
if (!props.isDefinition) {
|
||||
// 接口调试需要弹窗保存
|
||||
saveModalForm.value = {
|
||||
name: requestVModel.value.name || '',
|
||||
path: requestVModel.value.url || '',
|
||||
moduleId: 'root',
|
||||
};
|
||||
saveModalVisible.value = true;
|
||||
} else {
|
||||
realSave();
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
|
@ -1106,6 +1323,78 @@
|
|||
}
|
||||
}
|
||||
|
||||
const saveCaseModalVisible = ref(false);
|
||||
const saveCaseLoading = ref(false);
|
||||
const saveCaseModalForm = ref({
|
||||
name: '',
|
||||
priority: 'P0',
|
||||
status: RequestCaseStatus.PROCESSING,
|
||||
tags: [],
|
||||
});
|
||||
const saveCaseModalFormRef = ref<FormInstance>();
|
||||
|
||||
function handleSaveCaseCancel() {
|
||||
saveCaseModalForm.value = {
|
||||
name: '',
|
||||
priority: 'P0',
|
||||
status: RequestCaseStatus.PROCESSING,
|
||||
tags: [],
|
||||
};
|
||||
saveCaseModalVisible.value = false;
|
||||
}
|
||||
|
||||
// 保存为用例
|
||||
function saveAsCase() {
|
||||
saveCaseModalFormRef.value?.validate(async (errors) => {
|
||||
if (!errors) {
|
||||
try {
|
||||
saveCaseLoading.value = true;
|
||||
const definitionParams = makeRequestParams();
|
||||
if (requestVModel.value.isNew) {
|
||||
// 未保存过的接口保存为用例,先保存接口定义,再保存为用例
|
||||
await realSave(definitionParams, true);
|
||||
}
|
||||
const params: AddApiCaseParams = {
|
||||
...definitionParams,
|
||||
...saveCaseModalForm.value,
|
||||
projectId: appStore.currentProjectId,
|
||||
environmentId: props.currentEnvConfig?.id || '',
|
||||
apiDefinitionId: requestVModel.value.id,
|
||||
};
|
||||
await addCase(params);
|
||||
emit('addDone');
|
||||
Message.success(t('common.saveSuccess'));
|
||||
saveCaseModalVisible.value = false;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
saveCaseLoading.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 未保存过的接口保存为用例,先保存接口定义,再保存为用例
|
||||
async function saveNewDefinition() {
|
||||
try {
|
||||
if (!isHttpProtocol.value) {
|
||||
// 插件需要校验动态表单
|
||||
await fApi.value?.validate();
|
||||
}
|
||||
saveCaseModalVisible.value = true;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
// 校验不通过则不进行保存
|
||||
requestVModel.value.activeTab = RequestComposition.PLUGIN;
|
||||
nextTick(() => {
|
||||
scrollIntoView(document.querySelector('.arco-form-item-message'), { block: 'center' });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const activeApiTabFormRef = ref<FormInstance>();
|
||||
const isUrlError = ref(false);
|
||||
function handleSelect(value: string | number | Record<string, any> | undefined) {
|
||||
if (requestVModel.value.url === '' && requestVModel.value.protocol === 'HTTP') {
|
||||
|
@ -1113,18 +1402,62 @@
|
|||
return;
|
||||
}
|
||||
isUrlError.value = false;
|
||||
|
||||
activeApiTabFormRef.value?.validate(async (errors) => {
|
||||
if (errors) {
|
||||
horizontalSplitBoxRef.value?.expand();
|
||||
} else {
|
||||
switch (value) {
|
||||
case 'save':
|
||||
emit('save', makeRequestParams());
|
||||
handleSaveShortcut();
|
||||
break;
|
||||
case 'saveAsCase':
|
||||
emit('saveAsCase', makeRequestParams());
|
||||
saveNewDefinition();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// const fApi = ref();
|
||||
// const options = {
|
||||
// form: {
|
||||
// layout: 'vertical',
|
||||
// labelPosition: 'right',
|
||||
// size: 'small',
|
||||
// labelWidth: '00px',
|
||||
// hideRequiredAsterisk: false,
|
||||
// showMessage: true,
|
||||
// inlineMessage: false,
|
||||
// scrollToFirstError: true,
|
||||
// },
|
||||
// submitBtn: false,
|
||||
// resetBtn: false,
|
||||
// };
|
||||
// const currentApiTemplateRules = [];
|
||||
const showAddDependencyDrawer = ref(false);
|
||||
const addDependencyMode = ref<'pre' | 'post'>('pre');
|
||||
|
||||
// function handleDddDependency(value: string | number | Record<string, any> | undefined) {
|
||||
// switch (value) {
|
||||
// case 'pre':
|
||||
// addDependencyMode.value = 'pre';
|
||||
// showAddDependencyDrawer.value = true;
|
||||
// break;
|
||||
// case 'post':
|
||||
// addDependencyMode.value = 'post';
|
||||
// showAddDependencyDrawer.value = true;
|
||||
// break;
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// function clearAllDependency() {
|
||||
// activeApiTab.value.preDependency = [];
|
||||
// activeApiTab.value.postDependency = [];
|
||||
// }
|
||||
|
||||
function handleCancel() {
|
||||
saveModalFormRef.value?.resetFields();
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
/>
|
||||
</div>
|
||||
<paramTable
|
||||
v-model:params="innerParams"
|
||||
:params="innerParams"
|
||||
:columns="columns"
|
||||
:height-used="heightUsed"
|
||||
:scroll="{ minWidth: 1160 }"
|
||||
|
@ -36,6 +36,7 @@
|
|||
import { ExecuteRequestCommonParam } from '@/models/apiTest/common';
|
||||
import { RequestParamsType } from '@/enums/apiEnum';
|
||||
|
||||
import { filterKeyValParams } from '../utils';
|
||||
import { defaultRequestParamsItem } from '@/views/api-test/components/config';
|
||||
|
||||
const props = defineProps<{
|
||||
|
@ -130,10 +131,11 @@
|
|||
* 批量参数代码转换为参数表格数据
|
||||
*/
|
||||
function handleBatchParamApply(resultArr: any[]) {
|
||||
if (resultArr.length < innerParams.value.length) {
|
||||
innerParams.value.splice(0, innerParams.value.length - 1, ...resultArr);
|
||||
const filterResult = filterKeyValParams(innerParams.value, defaultRequestParamsItem);
|
||||
if (filterResult.lastDataIsDefault) {
|
||||
innerParams.value = [...resultArr, innerParams.value[innerParams.value.length - 1]].filter(Boolean);
|
||||
} else {
|
||||
innerParams.value = [...resultArr, innerParams.value[innerParams.value.length - 1]];
|
||||
innerParams.value = resultArr.filter(Boolean);
|
||||
}
|
||||
emit('change');
|
||||
}
|
||||
|
|
|
@ -146,7 +146,7 @@
|
|||
</template>
|
||||
<paramTable
|
||||
v-else-if="activeResponse.responseActiveTab === ResponseComposition.HEADER"
|
||||
v-model:params="activeResponse.headers"
|
||||
:params="activeResponse.headers"
|
||||
:columns="columns"
|
||||
:default-param-item="defaultKeyValueParamItem"
|
||||
:selectable="false"
|
||||
|
|
|
@ -153,7 +153,7 @@
|
|||
activeTab: ResponseComposition;
|
||||
isExpanded: boolean;
|
||||
isPriorityLocalExec: boolean;
|
||||
requestUrl: string;
|
||||
requestUrl?: string;
|
||||
isHttpProtocol: boolean;
|
||||
activeLayout?: Direction;
|
||||
responseDefinition?: ResponseItem[];
|
||||
|
|
|
@ -102,7 +102,7 @@
|
|||
requestResult?: RequestResult;
|
||||
console?: string;
|
||||
isPriorityLocalExec: boolean;
|
||||
requestUrl: string;
|
||||
requestUrl?: string;
|
||||
isHttpProtocol: boolean;
|
||||
}>();
|
||||
const emit = defineEmits(['execute']);
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
/>
|
||||
</div>
|
||||
<paramTable
|
||||
v-model:params="innerParams"
|
||||
:params="innerParams"
|
||||
:columns="columns"
|
||||
:height-used="heightUsed"
|
||||
:scroll="{ minWidth: 1160 }"
|
||||
|
@ -36,6 +36,7 @@
|
|||
import { ExecuteRequestCommonParam } from '@/models/apiTest/common';
|
||||
import { RequestParamsType } from '@/enums/apiEnum';
|
||||
|
||||
import { filterKeyValParams } from '../utils';
|
||||
import { defaultRequestParamsItem } from '@/views/api-test/components/config';
|
||||
|
||||
const props = defineProps<{
|
||||
|
@ -131,10 +132,11 @@
|
|||
* 批量参数代码转换为参数表格数据
|
||||
*/
|
||||
function handleBatchParamApply(resultArr: any[]) {
|
||||
if (resultArr.length < innerParams.value.length) {
|
||||
innerParams.value.splice(0, innerParams.value.length - 1, ...resultArr);
|
||||
const filterResult = filterKeyValParams(innerParams.value, defaultRequestParamsItem);
|
||||
if (filterResult.lastDataIsDefault) {
|
||||
innerParams.value = [...resultArr, innerParams.value[innerParams.value.length - 1]].filter(Boolean);
|
||||
} else {
|
||||
innerParams.value = [...resultArr, innerParams.value[innerParams.value.length - 1]];
|
||||
innerParams.value = resultArr.filter(Boolean);
|
||||
}
|
||||
emit('change');
|
||||
}
|
||||
|
|
|
@ -115,8 +115,13 @@ export function parseRequestBodyFiles(
|
|||
* 过滤无效参数
|
||||
* @param params 原始参数数组
|
||||
* @param defaultParamItem 默认参数项
|
||||
* @param filterEnable 是否过滤 enable 为 false 的参数
|
||||
*/
|
||||
export function filterKeyValParams<T>(params: (T & Record<string, any>)[], defaultParamItem: Record<string, any>) {
|
||||
export function filterKeyValParams<T>(
|
||||
params: (T & Record<string, any>)[],
|
||||
defaultParamItem: Record<string, any>,
|
||||
filterEnable = false
|
||||
) {
|
||||
const lastData = cloneDeep(params[params.length - 1]);
|
||||
const defaultParam = cloneDeep(defaultParamItem);
|
||||
if (!lastData || !defaultParam) {
|
||||
|
@ -138,6 +143,9 @@ export function filterKeyValParams<T>(params: (T & Record<string, any>)[], defau
|
|||
} else {
|
||||
validParams = params;
|
||||
}
|
||||
if (filterEnable) {
|
||||
validParams = validParams.filter((e) => e.enable === true);
|
||||
}
|
||||
return {
|
||||
lastDataIsDefault,
|
||||
validParams,
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
:add-module-api="addDebugModule"
|
||||
@add-finish="initModules"
|
||||
>
|
||||
<MsButton v-permission="['PROJECT_API_DEBUG:READ+ADD']" type="icon" class="!mr-0 p-[2px]">
|
||||
<MsButton type="icon" class="!mr-0 p-[2px]">
|
||||
<MsIcon
|
||||
type="icon-icon_create_planarity"
|
||||
size="18"
|
||||
|
|
|
@ -72,6 +72,8 @@ export default {
|
|||
'apiTestDebug.preconditionScriptName': 'Pre-script name',
|
||||
'apiTestDebug.preconditionAssociatedSceneResult': 'Associated scene result',
|
||||
'apiTestDebug.preconditionScriptNamePlaceholder': 'Please enter the pre-script name',
|
||||
'apiTestDebug.postConditionScriptName': 'Postscript name',
|
||||
'apiTestDebug.postConditionScriptNamePlaceholder': 'Please enter the postscript name',
|
||||
'apiTestDebug.preconditionAssociateResultDesc':
|
||||
'Counted in the scene execution result as a custom script step. Execution error will affect the final scene execution result',
|
||||
'apiTestDebug.manual': 'Manual entry',
|
||||
|
|
|
@ -68,6 +68,8 @@ export default {
|
|||
'apiTestDebug.preconditionScriptName': '前置脚本名称',
|
||||
'apiTestDebug.preconditionAssociatedSceneResult': '关联场景结果',
|
||||
'apiTestDebug.preconditionScriptNamePlaceholder': '请输入前置脚本名称',
|
||||
'apiTestDebug.postConditionScriptName': '后置脚本名称',
|
||||
'apiTestDebug.postConditionScriptNamePlaceholder': '请输入后置脚本名称',
|
||||
'apiTestDebug.preconditionAssociateResultDesc':
|
||||
'当作自定义脚本步骤统计到场景执行结果中,执行报错时会影响场景的最终执行结果',
|
||||
'apiTestDebug.manual': '手动录入',
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||
import apiTable from './apiTable.vue';
|
||||
import apiTable from './management/api/apiTable.vue';
|
||||
import moduleTree from '@/views/api-test/management/components/moduleTree.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div :class="['p-[16px_22px]', props.class]">
|
||||
<div :class="['p-[0_16px_16px_16px]', props.class]">
|
||||
<div class="mb-[16px] flex items-center justify-between">
|
||||
<div class="flex items-center gap-[8px]">
|
||||
<a-input-search
|
||||
|
@ -26,6 +26,7 @@
|
|||
v-on="propsEvent"
|
||||
@selected-change="handleTableSelect"
|
||||
@batch-action="handleTableBatch"
|
||||
@drag-change="handleTableDragSort"
|
||||
>
|
||||
<template v-if="props.protocol === 'HTTP'" #methodFilter="{ columnConfig }">
|
||||
<a-trigger
|
||||
|
@ -81,28 +82,30 @@
|
|||
v-if="props.protocol === 'HTTP'"
|
||||
v-model:model-value="record.method"
|
||||
class="param-input w-full"
|
||||
size="mini"
|
||||
@change="() => handleMethodChange(record)"
|
||||
>
|
||||
<template #label>
|
||||
<apiMethodName :method="record.method" is-tag />
|
||||
<apiMethodName :method="record.method" tag-size="small" is-tag />
|
||||
</template>
|
||||
<a-option v-for="item of Object.values(RequestMethods)" :key="item" :value="item">
|
||||
<apiMethodName :method="item" is-tag />
|
||||
<apiMethodName :method="item" tag-size="small" is-tag />
|
||||
</a-option>
|
||||
</a-select>
|
||||
<apiMethodName v-else :method="record.method" is-tag />
|
||||
<apiMethodName v-else :method="record.method" tag-size="small" is-tag />
|
||||
</template>
|
||||
<template #status="{ record }">
|
||||
<a-select
|
||||
v-model:model-value="record.status"
|
||||
class="param-input w-full"
|
||||
size="mini"
|
||||
@change="() => handleStatusChange(record)"
|
||||
>
|
||||
<template #label>
|
||||
<apiStatus :status="record.status" />
|
||||
<apiStatus :status="record.status" size="small" />
|
||||
</template>
|
||||
<a-option v-for="item of Object.values(RequestDefinitionStatus)" :key="item" :value="item">
|
||||
<apiStatus :status="item" />
|
||||
<apiStatus :status="item" size="small" />
|
||||
</a-option>
|
||||
</a-select>
|
||||
</template>
|
||||
|
@ -253,6 +256,7 @@
|
|||
batchUpdateDefinition,
|
||||
deleteDefinition,
|
||||
getDefinitionPage,
|
||||
sortDefinition,
|
||||
updateDefinition,
|
||||
} from '@/api/modules/api-test/management';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
@ -261,6 +265,7 @@
|
|||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
import { ApiDefinitionDetail } from '@/models/apiTest/management';
|
||||
import { DragSortParams } from '@/models/common';
|
||||
import { RequestDefinitionStatus, RequestMethods } from '@/enums/apiEnum';
|
||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||
|
||||
|
@ -375,7 +380,7 @@
|
|||
selectable: true,
|
||||
showSelectAll: !props.readOnly,
|
||||
draggable: props.readOnly ? undefined : { type: 'handle', width: 32 },
|
||||
heightUsed: 374,
|
||||
heightUsed: 308,
|
||||
},
|
||||
(item) => ({
|
||||
...item,
|
||||
|
@ -754,6 +759,20 @@
|
|||
emit('openCopyApiTab', record);
|
||||
}
|
||||
|
||||
// 拖拽排序
|
||||
async function handleTableDragSort(params: DragSortParams) {
|
||||
try {
|
||||
await sortDefinition({
|
||||
...params,
|
||||
moduleId: moduleIds.value[0] || '',
|
||||
});
|
||||
Message.success(t('caseManagement.featureCase.sortSuccess'));
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
loadApiList,
|
||||
});
|
||||
|
|
|
@ -1,27 +1,6 @@
|
|||
<template>
|
||||
<div class="flex h-full flex-col">
|
||||
<div class="border-b border-[var(--color-text-n8)] px-[22px] pb-[16px]">
|
||||
<MsEditableTab
|
||||
v-model:active-tab="activeApiTab"
|
||||
v-model:tabs="apiTabs"
|
||||
@add="addApiTab"
|
||||
@change="handleActiveTabChange"
|
||||
>
|
||||
<template #label="{ tab }">
|
||||
<apiMethodName
|
||||
v-if="tab.id !== 'all'"
|
||||
:method="tab.protocol === 'HTTP' ? tab.method : tab.protocol"
|
||||
class="mr-[4px]"
|
||||
/>
|
||||
<a-tooltip :content="tab.name || tab.label" :mouse-enter-delay="500">
|
||||
<div class="one-line-text max-w-[144px]">
|
||||
{{ tab.name || tab.label }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</MsEditableTab>
|
||||
</div>
|
||||
<div v-show="activeApiTab.id === 'all'" class="flex-1">
|
||||
<div class="flex flex-1 flex-col overflow-hidden">
|
||||
<div v-show="activeApiTab.id === 'all'" class="flex-1 pt-[16px]">
|
||||
<apiTable
|
||||
ref="apiTableRef"
|
||||
:active-module="props.activeModule"
|
||||
|
@ -48,15 +27,6 @@
|
|||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="definition" :title="t('apiTestManagement.definition')" class="ms-api-tab-pane">
|
||||
<MsSplitBox
|
||||
ref="splitBoxRef"
|
||||
:size="0.7"
|
||||
:max="0.9"
|
||||
:min="0.7"
|
||||
direction="horizontal"
|
||||
expand-direction="right"
|
||||
>
|
||||
<template #first>
|
||||
<requestComposition
|
||||
v-model:detail-loading="loading"
|
||||
v-model:request="activeApiTab"
|
||||
|
@ -75,118 +45,10 @@
|
|||
:file-save-as-source-id="activeApiTab.id"
|
||||
:file-module-options-api="getTransferOptions"
|
||||
:file-save-as-api="transferFile"
|
||||
:current-env-config="currentEnvConfig"
|
||||
is-definition
|
||||
@add-done="emit('addDone')"
|
||||
@save="handleSave"
|
||||
@save-as-case="handleSaveAsCase"
|
||||
@add-done="handleAddDone"
|
||||
/>
|
||||
</template>
|
||||
<template #second>
|
||||
<div class="p-[18px]">
|
||||
<!-- TODO:第一版没有模板 -->
|
||||
<!-- <MsFormCreate v-model:api="fApi" :rule="currentApiTemplateRules" :option="options" /> -->
|
||||
<a-form ref="activeApiTabFormRef" :model="activeApiTab" layout="vertical">
|
||||
<a-form-item
|
||||
field="name"
|
||||
:label="t('apiTestManagement.apiName')"
|
||||
class="mb-[16px]"
|
||||
:rules="[{ required: true, message: t('apiTestManagement.apiNameRequired') }]"
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="activeApiTab.name"
|
||||
:max-length="255"
|
||||
:placeholder="t('apiTestManagement.apiNamePlaceholder')"
|
||||
allow-clear
|
||||
@change="handleActiveApiChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('apiTestManagement.belongModule')" class="mb-[16px]">
|
||||
<a-tree-select
|
||||
v-model:modelValue="activeApiTab.moduleId"
|
||||
:data="selectTree"
|
||||
:field-names="{ title: 'name', key: 'id', children: 'children' }"
|
||||
:tree-props="{
|
||||
virtualListProps: {
|
||||
height: 200,
|
||||
threshold: 200,
|
||||
},
|
||||
}"
|
||||
allow-search
|
||||
@change="handleActiveApiChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('apiTestManagement.apiStatus')" class="mb-[16px]">
|
||||
<a-select
|
||||
v-model:model-value="activeApiTab.status"
|
||||
:placeholder="t('common.pleaseSelect')"
|
||||
class="param-input w-full"
|
||||
@change="handleActiveApiChange"
|
||||
>
|
||||
<template #label>
|
||||
<apiStatus :status="activeApiTab.status" />
|
||||
</template>
|
||||
<a-option v-for="item of Object.values(RequestDefinitionStatus)" :key="item" :value="item">
|
||||
<apiStatus :status="item" />
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('common.tag')" class="mb-[16px]">
|
||||
<MsTagsInput v-model:model-value="activeApiTab.tags" @change="handleActiveApiChange" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('common.desc')" class="mb-[16px]">
|
||||
<a-textarea
|
||||
v-model:model-value="activeApiTab.description"
|
||||
:max-length="1000"
|
||||
@change="handleActiveApiChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<!-- TODO:第一版先不做依赖 -->
|
||||
<!-- <div class="mb-[8px] flex items-center">
|
||||
<div class="text-[var(--color-text-2)]">
|
||||
{{ t('apiTestManagement.addDependency') }}
|
||||
</div>
|
||||
<a-divider margin="4px" direction="vertical" />
|
||||
<MsButton
|
||||
type="text"
|
||||
class="font-medium"
|
||||
:disabled="activeApiTab.preDependency.length === 0 && activeApiTab.postDependency.length === 0"
|
||||
@click="clearAllDependency"
|
||||
>
|
||||
{{ t('apiTestManagement.clearSelected') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
<div class="rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] p-[12px]">
|
||||
<div class="flex items-center">
|
||||
<div class="flex items-center gap-[4px] text-[var(--color-text-2)]">
|
||||
{{ t('apiTestManagement.preDependency') }}
|
||||
<div class="text-[rgb(var(--primary-5))]">
|
||||
{{ activeApiTab.preDependency.length }}
|
||||
</div>
|
||||
{{ t('apiTestManagement.dependencyUnit') }}
|
||||
</div>
|
||||
<a-divider margin="8px" direction="vertical" />
|
||||
<MsButton type="text" class="font-medium" @click="handleDddDependency('pre')">
|
||||
{{ t('apiTestManagement.addPreDependency') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
<div class="mt-[8px] flex items-center">
|
||||
<div class="flex items-center gap-[4px] text-[var(--color-text-2)]">
|
||||
{{ t('apiTestManagement.postDependency') }}
|
||||
<div class="text-[rgb(var(--primary-5))]">
|
||||
{{ activeApiTab.postDependency.length }}
|
||||
</div>
|
||||
{{ t('apiTestManagement.dependencyUnit') }}
|
||||
</div>
|
||||
<a-divider margin="8px" direction="vertical" />
|
||||
<MsButton type="text" class="font-medium" @click="handleDddDependency('post')">
|
||||
{{ t('apiTestManagement.addPostDependency') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
</MsSplitBox>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane v-if="!activeApiTab.isNew" key="case" :title="t('apiTestManagement.case')" class="ms-api-tab-pane">
|
||||
</a-tab-pane>
|
||||
|
@ -194,23 +56,15 @@
|
|||
</a-tabs>
|
||||
</div>
|
||||
</div>
|
||||
<addDependencyDrawer v-model:visible="showAddDependencyDrawer" :mode="addDependencyMode" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { FormInstance, Message } from '@arco-design/web-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
// import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsEditableTab from '@/components/pure/ms-editable-tab/index.vue';
|
||||
import { TabItem } from '@/components/pure/ms-editable-tab/types';
|
||||
// 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 addDependencyDrawer from './addDependencyDrawer.vue';
|
||||
import apiTable from './apiTable.vue';
|
||||
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
||||
import apiStatus from '@/views/api-test/components/apiStatus.vue';
|
||||
|
||||
import { getProtocolList, localExecuteApiDebug } from '@/api/modules/api-test/common';
|
||||
import {
|
||||
|
@ -224,15 +78,11 @@
|
|||
} from '@/api/modules/api-test/management';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { filterTree } from '@/utils';
|
||||
|
||||
import { ExecuteBody, ProtocolItem, RequestTaskResult } from '@/models/apiTest/common';
|
||||
import {
|
||||
ApiDefinitionCreateParams,
|
||||
ApiDefinitionDetail,
|
||||
ApiDefinitionUpdateParams,
|
||||
} from '@/models/apiTest/management';
|
||||
import { ApiDefinitionDetail } from '@/models/apiTest/management';
|
||||
import { ModuleTreeNode } from '@/models/common';
|
||||
import { EnvConfig } from '@/models/projectManagement/environmental';
|
||||
import {
|
||||
RequestAuthType,
|
||||
RequestBodyFormat,
|
||||
|
@ -258,13 +108,22 @@
|
|||
protocol: string;
|
||||
}>();
|
||||
const emit = defineEmits(['addDone']);
|
||||
const definitionActiveKey = ref('definition');
|
||||
const setActiveApi: ((params: RequestParam) => void) | undefined = inject('setActiveApi');
|
||||
|
||||
const refreshModuleTree: (() => Promise<any>) | undefined = inject('refreshModuleTree');
|
||||
|
||||
const definitionActiveKey = ref('definition');
|
||||
const currentEnvConfig = inject<Ref<EnvConfig>>('currentEnvConfig');
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const apiTabs = defineModel<RequestParam[]>('apiTabs', {
|
||||
required: true,
|
||||
});
|
||||
const activeApiTab = defineModel<RequestParam>('activeApiTab', {
|
||||
required: true,
|
||||
});
|
||||
|
||||
const protocols = ref<ProtocolItem[]>([]);
|
||||
async function initProtocolList() {
|
||||
try {
|
||||
|
@ -279,37 +138,6 @@
|
|||
initProtocolList();
|
||||
});
|
||||
|
||||
const apiTabs = ref<RequestParam[]>([
|
||||
{
|
||||
id: 'all',
|
||||
label: t('apiTestManagement.allApi'),
|
||||
closable: false,
|
||||
} as RequestParam,
|
||||
]);
|
||||
const activeApiTab = ref<RequestParam>(apiTabs.value[0] as RequestParam);
|
||||
|
||||
function handleActiveApiChange() {
|
||||
if (activeApiTab.value) {
|
||||
activeApiTab.value.unSaved = true;
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => activeApiTab.value.id,
|
||||
() => {
|
||||
if (typeof setActiveApi === 'function') {
|
||||
setActiveApi(activeApiTab.value);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const selectTree = computed(() =>
|
||||
filterTree(cloneDeep(props.moduleTree), (e) => {
|
||||
e.draggable = false;
|
||||
return e.type === 'MODULE';
|
||||
})
|
||||
);
|
||||
|
||||
const initDefaultId = `definition-${Date.now()}`;
|
||||
const defaultBodyParams: ExecuteBody = {
|
||||
bodyType: RequestBodyFormat.NONE,
|
||||
|
@ -438,11 +266,14 @@
|
|||
|
||||
const apiTableRef = ref<InstanceType<typeof apiTable>>();
|
||||
|
||||
function handleActiveTabChange(item: TabItem) {
|
||||
if (item.id === 'all') {
|
||||
watch(
|
||||
() => activeApiTab.value.id,
|
||||
(id) => {
|
||||
if (id === 'all') {
|
||||
apiTableRef.value?.loadApiList();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const loading = ref(false);
|
||||
async function openApiTab(apiInfo: ModuleTreeNode | ApiDefinitionDetail | string, isCopy = false) {
|
||||
|
@ -487,84 +318,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
// const fApi = ref();
|
||||
// const options = {
|
||||
// form: {
|
||||
// layout: 'vertical',
|
||||
// labelPosition: 'right',
|
||||
// size: 'small',
|
||||
// labelWidth: '00px',
|
||||
// hideRequiredAsterisk: false,
|
||||
// showMessage: true,
|
||||
// inlineMessage: false,
|
||||
// scrollToFirstError: true,
|
||||
// },
|
||||
// submitBtn: false,
|
||||
// resetBtn: false,
|
||||
// };
|
||||
// const currentApiTemplateRules = [];
|
||||
const showAddDependencyDrawer = ref(false);
|
||||
const addDependencyMode = ref<'pre' | 'post'>('pre');
|
||||
|
||||
// function handleDddDependency(value: string | number | Record<string, any> | undefined) {
|
||||
// switch (value) {
|
||||
// case 'pre':
|
||||
// addDependencyMode.value = 'pre';
|
||||
// showAddDependencyDrawer.value = true;
|
||||
// break;
|
||||
// case 'post':
|
||||
// addDependencyMode.value = 'post';
|
||||
// showAddDependencyDrawer.value = true;
|
||||
// break;
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// function clearAllDependency() {
|
||||
// activeApiTab.value.preDependency = [];
|
||||
// activeApiTab.value.postDependency = [];
|
||||
// }
|
||||
|
||||
const splitBoxRef = ref<InstanceType<typeof MsSplitBox>>();
|
||||
const activeApiTabFormRef = ref<FormInstance>();
|
||||
|
||||
function handleSave(params: ApiDefinitionCreateParams) {
|
||||
activeApiTabFormRef.value?.validate(async (errors) => {
|
||||
if (errors) {
|
||||
splitBoxRef.value?.expand();
|
||||
} else {
|
||||
try {
|
||||
appStore.showLoading();
|
||||
let res;
|
||||
params.versionId = 'v1.0';
|
||||
if (params.isNew) {
|
||||
res = await addDefinition(params);
|
||||
} else {
|
||||
res = await updateDefinition(params as ApiDefinitionUpdateParams);
|
||||
}
|
||||
activeApiTab.value.id = res.id;
|
||||
activeApiTab.value.isNew = false;
|
||||
Message.success(t('common.saveSuccess'));
|
||||
activeApiTab.value.unSaved = false;
|
||||
activeApiTab.value.name = res.name;
|
||||
activeApiTab.value.label = res.name;
|
||||
activeApiTab.value.url = res.path;
|
||||
function handleAddDone() {
|
||||
definitionActiveKey.value = 'preview'; // 保存完毕后切换到预览页
|
||||
if (typeof refreshModuleTree === 'function') {
|
||||
refreshModuleTree();
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
appStore.hideLoading();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function handleSaveAsCase(params: ApiDefinitionCreateParams) {
|
||||
console.log(params);
|
||||
}
|
||||
|
||||
function refreshTable() {
|
||||
|
@ -579,12 +337,15 @@
|
|||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ms-api-tab-nav {
|
||||
:deep(.ms-api-tab-nav) {
|
||||
@apply h-full;
|
||||
:deep(.arco-tabs-content) {
|
||||
.arco-tabs-nav-tab {
|
||||
border-bottom: 1px solid var(--color-text-n8);
|
||||
}
|
||||
.arco-tabs-content {
|
||||
@apply pt-0;
|
||||
|
||||
height: calc(100% - 51px);
|
||||
height: calc(100% - 48px);
|
||||
.arco-tabs-content-list {
|
||||
@apply h-full;
|
||||
.arco-tabs-pane {
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
</template>
|
||||
<div class="detail-collapse-item">
|
||||
<template v-if="props.detail.protocol === 'HTTP'">
|
||||
<div v-if="preivewDetail.headers.length > 0" class="detail-item">
|
||||
<div v-if="previewDetail.headers.length > 0" class="detail-item">
|
||||
<div class="detail-item-title">
|
||||
<div class="detail-item-title-text">{{ t('apiTestManagement.requestHeader') }}</div>
|
||||
<a-radio-group v-model:model-value="headerShowType" type="button" size="mini">
|
||||
|
@ -25,7 +25,7 @@
|
|||
<MsFormTable
|
||||
v-show="headerShowType === 'table'"
|
||||
:columns="headerColumns"
|
||||
:data="preivewDetail.headers || []"
|
||||
:data="previewDetail.headers || []"
|
||||
:selectable="false"
|
||||
/>
|
||||
<MsCodeEditor
|
||||
|
@ -53,7 +53,7 @@
|
|||
</MsCodeEditor>
|
||||
<a-divider type="dashed" :margin="0" class="!mt-[16px] border-[var(--color-text-n8)]" />
|
||||
</div>
|
||||
<div v-if="preivewDetail.query.length > 0" class="detail-item">
|
||||
<div v-if="previewDetail.query.length > 0" class="detail-item">
|
||||
<div class="detail-item-title">
|
||||
<div class="detail-item-title-text">Query</div>
|
||||
<a-radio-group v-model:model-value="queryShowType" type="button" size="mini">
|
||||
|
@ -64,7 +64,7 @@
|
|||
<MsFormTable
|
||||
v-show="queryShowType === 'table'"
|
||||
:columns="queryRestColumns"
|
||||
:data="preivewDetail.query || []"
|
||||
:data="previewDetail.query || []"
|
||||
:selectable="false"
|
||||
/>
|
||||
<MsCodeEditor
|
||||
|
@ -92,7 +92,7 @@
|
|||
</MsCodeEditor>
|
||||
<a-divider type="dashed" :margin="0" class="!mt-[16px] border-[var(--color-text-n8)]" />
|
||||
</div>
|
||||
<div v-if="preivewDetail.rest.length > 0" class="detail-item">
|
||||
<div v-if="previewDetail.rest.length > 0" class="detail-item">
|
||||
<div class="detail-item-title">
|
||||
<div class="detail-item-title-text">Rest</div>
|
||||
<a-radio-group v-model:model-value="restShowType" type="button" size="mini">
|
||||
|
@ -103,7 +103,7 @@
|
|||
<MsFormTable
|
||||
v-show="restShowType === 'table'"
|
||||
:columns="queryRestColumns"
|
||||
:data="preivewDetail.rest || []"
|
||||
:data="previewDetail.rest || []"
|
||||
:selectable="false"
|
||||
/>
|
||||
<MsCodeEditor
|
||||
|
@ -134,10 +134,10 @@
|
|||
<div class="detail-item">
|
||||
<div class="detail-item-title">
|
||||
<div class="detail-item-title-text">
|
||||
{{ `${t('apiTestManagement.requestBody')}-${preivewDetail.body.bodyType}` }}
|
||||
{{ `${t('apiTestManagement.requestBody')}-${previewDetail.body.bodyType}` }}
|
||||
</div>
|
||||
<!-- <a-radio-group
|
||||
v-if="preivewDetail.body.bodyType !== RequestBodyFormat.NONE"
|
||||
v-if="previewDetail.body.bodyType !== RequestBodyFormat.NONE"
|
||||
v-model:model-value="bodyShowType"
|
||||
type="button"
|
||||
size="mini"
|
||||
|
@ -147,15 +147,16 @@
|
|||
</a-radio-group> -->
|
||||
</div>
|
||||
<div
|
||||
v-if="preivewDetail.body.bodyType === RequestBodyFormat.NONE"
|
||||
v-if="previewDetail.body.bodyType === RequestBodyFormat.NONE"
|
||||
class="flex h-[100px] items-center justify-center rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] text-[var(--color-text-4)]"
|
||||
>
|
||||
{{ t('apiTestDebug.noneBody') }}
|
||||
</div>
|
||||
<MsFormTable
|
||||
v-else-if="
|
||||
preivewDetail.body.bodyType === RequestBodyFormat.FORM_DATA ||
|
||||
preivewDetail.body.bodyType === RequestBodyFormat.WWW_FORM
|
||||
previewDetail.body.bodyType === RequestBodyFormat.FORM_DATA ||
|
||||
previewDetail.body.bodyType === RequestBodyFormat.WWW_FORM ||
|
||||
previewDetail.body.bodyType === RequestBodyFormat.BINARY
|
||||
"
|
||||
:columns="bodyColumns"
|
||||
:data="bodyTableData"
|
||||
|
@ -164,7 +165,7 @@
|
|||
<MsCodeEditor
|
||||
v-else-if="
|
||||
[RequestBodyFormat.JSON, RequestBodyFormat.RAW, RequestBodyFormat.XML].includes(
|
||||
preivewDetail.body.bodyType
|
||||
previewDetail.body.bodyType
|
||||
)
|
||||
"
|
||||
:model-value="bodyCode"
|
||||
|
@ -235,8 +236,8 @@
|
|||
</a-collapse-item>
|
||||
<a-collapse-item
|
||||
v-if="
|
||||
preivewDetail.responseDefinition &&
|
||||
preivewDetail.responseDefinition.length > 0 &&
|
||||
previewDetail.responseDefinition &&
|
||||
previewDetail.responseDefinition.length > 0 &&
|
||||
props.detail.protocol === 'HTTP'
|
||||
"
|
||||
key="response"
|
||||
|
@ -254,7 +255,7 @@
|
|||
</template>
|
||||
<MsEditableTab
|
||||
v-model:active-tab="activeResponse"
|
||||
:tabs="preivewDetail.responseDefinition?.map((e) => ({ ...e, closable: false })) || []"
|
||||
:tabs="previewDetail.responseDefinition?.map((e) => ({ ...e, closable: false })) || []"
|
||||
hide-more-action
|
||||
readonly
|
||||
class="my-[8px]"
|
||||
|
@ -272,8 +273,14 @@
|
|||
{{ `${t('apiTestDebug.responseBody')}-${activeResponse?.body.bodyType}` }}
|
||||
</div>
|
||||
</div>
|
||||
<MsFormTable
|
||||
v-if="activeResponse?.body.bodyType === ResponseBodyFormat.BINARY"
|
||||
:columns="responseBodyColumns"
|
||||
:data="responseBodyTableData"
|
||||
:selectable="false"
|
||||
/>
|
||||
<MsCodeEditor
|
||||
v-if="activeResponse?.body.bodyType !== ResponseBodyFormat.BINARY"
|
||||
v-else
|
||||
:model-value="responseCode"
|
||||
class="flex-1"
|
||||
theme="vs"
|
||||
|
@ -329,7 +336,6 @@
|
|||
import { RequestBodyFormat, RequestParamsType, ResponseBodyFormat } from '@/enums/apiEnum';
|
||||
|
||||
import type { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||
import { getValidRequestTableParams } from '@/views/api-test/components/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
detail: RequestParam;
|
||||
|
@ -339,7 +345,7 @@
|
|||
const { t } = useI18n();
|
||||
const { copy, isSupported } = useClipboard();
|
||||
|
||||
const preivewDetail = ref<RequestParam>(cloneDeep(props.detail));
|
||||
const previewDetail = ref<RequestParam>(props.detail);
|
||||
const activeResponse = ref<TabItem & ResponseItem>();
|
||||
|
||||
const pluginLoading = ref(false);
|
||||
|
@ -358,21 +364,21 @@
|
|||
},
|
||||
];
|
||||
const pluginTableData = computed(() => {
|
||||
if (pluginScriptMap.value[preivewDetail.value.protocol]) {
|
||||
if (pluginScriptMap.value[previewDetail.value.protocol]) {
|
||||
return (
|
||||
pluginScriptMap.value[preivewDetail.value.protocol].apiDefinitionFields?.map((e) => ({
|
||||
pluginScriptMap.value[previewDetail.value.protocol].apiDefinitionFields?.map((e) => ({
|
||||
key: e,
|
||||
value: preivewDetail.value[e],
|
||||
value: previewDetail.value[e],
|
||||
})) || []
|
||||
);
|
||||
}
|
||||
return [];
|
||||
});
|
||||
const pluginRawCode = computed(() => {
|
||||
if (pluginScriptMap.value[preivewDetail.value.protocol]) {
|
||||
if (pluginScriptMap.value[previewDetail.value.protocol]) {
|
||||
return (
|
||||
pluginScriptMap.value[preivewDetail.value.protocol].apiDefinitionFields
|
||||
?.map((e) => `${e}:${preivewDetail.value[e]}`)
|
||||
pluginScriptMap.value[previewDetail.value.protocol].apiDefinitionFields
|
||||
?.map((e) => `${e}:${previewDetail.value[e]}`)
|
||||
.join('\n') || ''
|
||||
);
|
||||
}
|
||||
|
@ -404,28 +410,11 @@
|
|||
}
|
||||
|
||||
watchEffect(() => {
|
||||
preivewDetail.value = cloneDeep(props.detail); // props.detail是嵌套的引用类型,防止不必要的修改来源影响props.detail的数据
|
||||
const tableParam = getValidRequestTableParams(preivewDetail.value); // 在编辑props.detail时,参数表格会多出一行默认数据,需要去除
|
||||
preivewDetail.value = {
|
||||
...preivewDetail.value,
|
||||
body: {
|
||||
...preivewDetail.value.body,
|
||||
formDataBody: {
|
||||
formValues: tableParam.formDataBodyTableParams,
|
||||
},
|
||||
wwwFormBody: {
|
||||
formValues: tableParam.wwwFormBodyTableParams,
|
||||
},
|
||||
},
|
||||
headers: tableParam.headers,
|
||||
rest: tableParam.rest,
|
||||
query: tableParam.query,
|
||||
responseDefinition: tableParam.response,
|
||||
};
|
||||
[activeResponse.value] = tableParam.response;
|
||||
if (preivewDetail.value.protocol !== 'HTTP') {
|
||||
previewDetail.value = cloneDeep(props.detail); // props.detail是嵌套的引用类型,防止不必要的修改来源影响props.detail的数据
|
||||
[activeResponse.value] = previewDetail.value.responseDefinition || [];
|
||||
if (previewDetail.value.protocol !== 'HTTP') {
|
||||
// 初始化插件脚本
|
||||
initPluginScript(preivewDetail.value.protocol);
|
||||
initPluginScript(previewDetail.value.protocol);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -463,7 +452,7 @@
|
|||
];
|
||||
const headerShowType = ref('table');
|
||||
const headerRawCode = computed(() => {
|
||||
return preivewDetail.value.headers?.map((item) => `${item.key}:${item.value}`).join('\n');
|
||||
return previewDetail.value.headers?.map((item) => `${item.key}:${item.value}`).join('\n');
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -523,17 +512,19 @@
|
|||
];
|
||||
const queryShowType = ref('table');
|
||||
const queryRawCode = computed(() => {
|
||||
return preivewDetail.value.query?.map((item) => `${item.key}:${item.value}`).join('\n');
|
||||
return previewDetail.value.query?.map((item) => `${item.key}:${item.value}`).join('\n');
|
||||
});
|
||||
const restShowType = ref('table');
|
||||
const restRawCode = computed(() => {
|
||||
return preivewDetail.value.rest?.map((item) => `${item.key}:${item.value}`).join('\n');
|
||||
return previewDetail.value.rest?.map((item) => `${item.key}:${item.value}`).join('\n');
|
||||
});
|
||||
|
||||
/**
|
||||
* 请求体
|
||||
*/
|
||||
const bodyColumns: FormTableColumn[] = [
|
||||
const bodyColumns = computed<FormTableColumn[]>(() => {
|
||||
if ([RequestBodyFormat.FORM_DATA, RequestBodyFormat.WWW_FORM].includes(previewDetail.value.body.bodyType)) {
|
||||
return [
|
||||
{
|
||||
title: 'apiTestManagement.paramName',
|
||||
dataIndex: 'key',
|
||||
|
@ -587,41 +578,64 @@
|
|||
width: 100,
|
||||
},
|
||||
];
|
||||
}
|
||||
return [
|
||||
{
|
||||
title: 'common.desc',
|
||||
dataIndex: 'description',
|
||||
inputType: 'text',
|
||||
showTooltip: true,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.paramVal',
|
||||
dataIndex: 'value',
|
||||
inputType: 'text',
|
||||
showTooltip: true,
|
||||
},
|
||||
];
|
||||
});
|
||||
// const bodyShowType = ref('table');
|
||||
const bodyTableData = computed(() => {
|
||||
switch (preivewDetail.value.body.bodyType) {
|
||||
switch (previewDetail.value.body.bodyType) {
|
||||
case RequestBodyFormat.FORM_DATA:
|
||||
return (preivewDetail.value.body.formDataBody?.formValues || []).map((e) => ({
|
||||
return (previewDetail.value.body.formDataBody?.formValues || []).map((e) => ({
|
||||
...e,
|
||||
value: e.paramType === RequestParamsType.FILE ? e.files?.map((file) => file.fileName).join('\n') : e.value,
|
||||
}));
|
||||
case RequestBodyFormat.WWW_FORM:
|
||||
return preivewDetail.value.body.wwwFormBody?.formValues || [];
|
||||
return previewDetail.value.body.wwwFormBody?.formValues || [];
|
||||
case RequestBodyFormat.BINARY:
|
||||
return [
|
||||
{
|
||||
description: previewDetail.value.body.binaryBody.description,
|
||||
value: previewDetail.value.body.binaryBody.file?.fileName,
|
||||
},
|
||||
];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
});
|
||||
const bodyCode = computed(() => {
|
||||
switch (preivewDetail.value.body.bodyType) {
|
||||
switch (previewDetail.value.body.bodyType) {
|
||||
case RequestBodyFormat.FORM_DATA:
|
||||
return preivewDetail.value.body.formDataBody?.formValues?.map((item) => `${item.key}:${item.value}`).join('\n');
|
||||
return previewDetail.value.body.formDataBody?.formValues?.map((item) => `${item.key}:${item.value}`).join('\n');
|
||||
case RequestBodyFormat.WWW_FORM:
|
||||
return preivewDetail.value.body.wwwFormBody?.formValues?.map((item) => `${item.key}:${item.value}`).join('\n');
|
||||
return previewDetail.value.body.wwwFormBody?.formValues?.map((item) => `${item.key}:${item.value}`).join('\n');
|
||||
case RequestBodyFormat.RAW:
|
||||
return preivewDetail.value.body.rawBody?.value;
|
||||
return previewDetail.value.body.rawBody?.value;
|
||||
case RequestBodyFormat.JSON:
|
||||
return preivewDetail.value.body.jsonBody?.jsonValue;
|
||||
return previewDetail.value.body.jsonBody?.jsonValue;
|
||||
case RequestBodyFormat.XML:
|
||||
return preivewDetail.value.body.xmlBody?.value;
|
||||
return previewDetail.value.body.xmlBody?.value;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
});
|
||||
const bodyCodeLanguage = computed(() => {
|
||||
if (preivewDetail.value.body.bodyType === RequestBodyFormat.JSON) {
|
||||
if (previewDetail.value.body.bodyType === RequestBodyFormat.JSON) {
|
||||
return LanguageEnum.JSON;
|
||||
}
|
||||
if (preivewDetail.value.body.bodyType === RequestBodyFormat.XML) {
|
||||
if (previewDetail.value.body.bodyType === RequestBodyFormat.XML) {
|
||||
return LanguageEnum.XML;
|
||||
}
|
||||
return LanguageEnum.PLAINTEXT;
|
||||
|
@ -663,6 +677,30 @@
|
|||
inputType: 'text',
|
||||
},
|
||||
];
|
||||
const responseBodyColumns: FormTableColumn[] = [
|
||||
{
|
||||
title: 'common.desc',
|
||||
dataIndex: 'description',
|
||||
inputType: 'text',
|
||||
showTooltip: true,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.paramVal',
|
||||
dataIndex: 'value',
|
||||
inputType: 'text',
|
||||
showTooltip: true,
|
||||
},
|
||||
];
|
||||
const responseBodyTableData = computed(() => {
|
||||
return activeResponse.value?.body.bodyType === ResponseBodyFormat.BINARY
|
||||
? [
|
||||
{
|
||||
description: activeResponse.value?.body.binaryBody.description,
|
||||
value: activeResponse.value?.body.binaryBody.file?.fileName,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -123,7 +123,7 @@
|
|||
{
|
||||
key: 'path',
|
||||
locale: 'apiTestManagement.path',
|
||||
value: previewDetail.value.path,
|
||||
value: previewDetail.value.url || previewDetail.value.path,
|
||||
},
|
||||
{
|
||||
key: 'tags',
|
||||
|
|
|
@ -1,61 +1,69 @@
|
|||
<template>
|
||||
<a-tabs v-model:active-key="activeTab" animation lazy-load class="ms-api-tab-nav">
|
||||
<a-tab-pane key="api" title="API" class="ms-api-tab-pane">
|
||||
<api
|
||||
ref="apiRef"
|
||||
:module-tree="props.moduleTree"
|
||||
:active-module="props.activeModule"
|
||||
:offspring-ids="props.offspringIds"
|
||||
:protocol="protocol"
|
||||
<div class="flex gap-[8px] px-[16px] pt-[16px]">
|
||||
<a-select v-model:model-value="currentTab" class="w-[80px]" :options="tabOptions" />
|
||||
<MsEditableTab
|
||||
v-model:active-tab="activeApiTab"
|
||||
v-model:tabs="apiTabs"
|
||||
class="flex-1 overflow-hidden"
|
||||
@add="newTab"
|
||||
>
|
||||
<template #label="{ tab }">
|
||||
<apiMethodName
|
||||
v-if="tab.id !== 'all'"
|
||||
:method="tab.protocol === 'HTTP' ? tab.method : tab.protocol"
|
||||
class="mr-[4px]"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="case" title="CASE" class="ms-api-tab-pane">
|
||||
<apiCase :active-module="props.activeModule" :offspring-ids="props.offspringIds" :protocol="protocol" />
|
||||
</a-tab-pane>
|
||||
<!-- <a-tab-pane key="mock" title="MOCK" class="ms-api-tab-pane">
|
||||
<mock-table
|
||||
ref="mockRef"
|
||||
:module-tree="props.moduleTree"
|
||||
:active-module="props.activeModule"
|
||||
:offspring-ids="props.offspringIds"
|
||||
:protocol="protocol"
|
||||
/>
|
||||
</a-tab-pane> -->
|
||||
<!-- <a-tab-pane key="doc" title="API Docs" 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)]" />
|
||||
<a-tooltip :content="tab.name || tab.label" :mouse-enter-delay="500">
|
||||
<div class="one-line-text max-w-[144px]">
|
||||
{{ tab.name || tab.label }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-button>
|
||||
<MsSelect
|
||||
</MsEditableTab>
|
||||
<a-select
|
||||
v-model:model-value="currentEnv"
|
||||
mode="static"
|
||||
:options="envOptions"
|
||||
class="!w-[150px]"
|
||||
:search-keys="['label']"
|
||||
class="!w-[200px] pl-0 pr-[8px]"
|
||||
:loading="envLoading"
|
||||
allow-search
|
||||
@change="initEnvironment"
|
||||
/>
|
||||
>
|
||||
<template #prefix>
|
||||
<div class="flex cursor-pointer p-[8px]" @click.stop="goEnv">
|
||||
<icon-location class="text-[var(--color-text-4)]" />
|
||||
</div>
|
||||
</template>
|
||||
</a-tabs>
|
||||
</a-select>
|
||||
</div>
|
||||
<api
|
||||
v-if="currentTab === 'api'"
|
||||
ref="apiRef"
|
||||
v-model:active-api-tab="activeApiTab"
|
||||
v-model:api-tabs="apiTabs"
|
||||
:active-module="props.activeModule"
|
||||
:offspring-ids="props.offspringIds"
|
||||
:protocol="props.protocol"
|
||||
:module-tree="props.moduleTree"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SelectOptionData } from '@arco-design/web-vue';
|
||||
|
||||
import MsSelect from '@/components/business/ms-select';
|
||||
import MsEditableTab from '@/components/pure/ms-editable-tab/index.vue';
|
||||
import api from './api/index.vue';
|
||||
import apiCase from './case/index.vue';
|
||||
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
||||
import { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||
|
||||
// import MockTable from '@/views/api-test/management/components/management/mock/mockTable.vue';
|
||||
import { getEnvironment, getEnvList } from '@/api/modules/api-test/common';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import router from '@/router';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
import { ModuleTreeNode } from '@/models/common';
|
||||
import { EnvConfig } from '@/models/projectManagement/environmental';
|
||||
import { ProjectManagementRouteEnum } from '@/enums/routeEnum';
|
||||
|
||||
const props = defineProps<{
|
||||
activeModule: string;
|
||||
|
@ -65,8 +73,16 @@
|
|||
}>();
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const setActiveApi: ((params: RequestParam) => void) | undefined = inject('setActiveApi');
|
||||
|
||||
const currentTab = ref('api');
|
||||
const tabOptions = [
|
||||
{ label: 'API', value: 'api' },
|
||||
{ label: 'CASE', value: 'case' },
|
||||
];
|
||||
|
||||
const activeTab = ref('api');
|
||||
const apiRef = ref<InstanceType<typeof api>>();
|
||||
|
||||
function newTab(apiInfo?: ModuleTreeNode | string) {
|
||||
|
@ -77,14 +93,33 @@
|
|||
}
|
||||
}
|
||||
|
||||
const apiTabs = ref<RequestParam[]>([
|
||||
{
|
||||
id: 'all',
|
||||
label: t('apiTestManagement.allApi'),
|
||||
closable: false,
|
||||
} as RequestParam,
|
||||
]);
|
||||
const activeApiTab = ref<RequestParam>(apiTabs.value[0] as RequestParam);
|
||||
|
||||
watch(
|
||||
() => activeApiTab.value.id,
|
||||
() => {
|
||||
if (typeof setActiveApi === 'function') {
|
||||
setActiveApi(activeApiTab.value);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const currentEnv = ref('');
|
||||
const currentEnvConfig = ref({});
|
||||
const currentEnvConfig = ref<EnvConfig>();
|
||||
const envLoading = ref(false);
|
||||
const envOptions = ref<SelectOptionData[]>([]);
|
||||
|
||||
async function initEnvironment() {
|
||||
try {
|
||||
currentEnvConfig.value = await getEnvironment(currentEnv.value);
|
||||
currentEnvConfig.value.id = currentEnv.value;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
|
@ -113,6 +148,12 @@
|
|||
apiRef.value?.refreshTable();
|
||||
}
|
||||
|
||||
function goEnv() {
|
||||
router.push({
|
||||
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_ENVIRONMENT_MANAGEMENT,
|
||||
});
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
initEnvList();
|
||||
});
|
||||
|
@ -127,19 +168,10 @@
|
|||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ms-api-tab-nav {
|
||||
@apply h-full;
|
||||
:deep(.arco-tabs-content) {
|
||||
height: calc(100% - 51px);
|
||||
.arco-tabs-content-list {
|
||||
@apply h-full;
|
||||
.arco-tabs-pane {
|
||||
@apply h-full;
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.arco-tabs-nav) {
|
||||
border-bottom: 1px solid var(--color-text-n8);
|
||||
}
|
||||
.ms-input-group--prepend();
|
||||
:deep(.arco-select-view-prefix) {
|
||||
margin-right: 8px;
|
||||
padding-right: 0;
|
||||
border-right: 1px solid var(--color-text-input-border);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -32,7 +32,8 @@
|
|||
:content="isExpandApi ? t('apiTestManagement.collapseApi') : t('apiTestManagement.expandApi')"
|
||||
>
|
||||
<MsButton type="icon" status="secondary" class="!mr-0 p-[4px]" @click="changeApiExpand">
|
||||
<MsIcon :type="isExpandApi ? 'icon-icon_collapse_interface' : 'icon-icon_expand_interface'" />
|
||||
<icon-eye-invisible v-if="isExpandApi" />
|
||||
<icon-eye v-else />
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
<a-tooltip :content="isExpandAll ? t('common.collapseAll') : t('common.expandAll')">
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<MsCard :min-width="1180" simple no-content-padding>
|
||||
<MsCard simple no-content-padding>
|
||||
<MsSplitBox :size="0.25" :max="0.5">
|
||||
<template #first>
|
||||
<div class="p-[9px]">
|
||||
<div class="p-[16px]">
|
||||
<moduleTree
|
||||
ref="moduleTreeRef"
|
||||
:active-node-id="activeApi?.id"
|
||||
|
|
|
@ -168,4 +168,6 @@ export default {
|
|||
'case.batchDeleteCaseTip': 'Are you sure you want to delete {count} selected cases?',
|
||||
'case.deleteCaseTip':
|
||||
'Deleting an case will result in the execution failure of the test task that references the use case. Please be cautious!',
|
||||
'apiTestManagement.click': 'Click',
|
||||
'apiTestManagement.getResponse': 'Get response content',
|
||||
};
|
||||
|
|
|
@ -149,6 +149,8 @@ export default {
|
|||
'apiTestManagement.getResponse': '获取响应内容',
|
||||
'case.allCase': '全部CASE',
|
||||
'case.caseName': '用例名称',
|
||||
'case.caseNameRequired': '用例名称不能为空',
|
||||
'case.caseNamePlaceholder': '请输入用例名称',
|
||||
'case.caseLevel': '用例等级',
|
||||
'case.caseEnvironment': '用例环境',
|
||||
'case.tableColumnCreateUser': '创建人',
|
||||
|
|
Loading…
Reference in New Issue