feat(接口测试): mock接口联调
This commit is contained in:
parent
c245a17d72
commit
d785728c2e
|
@ -92,7 +92,7 @@
|
||||||
:size="props.tagSize"
|
:size="props.tagSize"
|
||||||
class="m-0 border-none p-0"
|
class="m-0 border-none p-0"
|
||||||
:self-style="{ backgroundColor: 'transparent !important' }"
|
:self-style="{ backgroundColor: 'transparent !important' }"
|
||||||
:closable="data.value !== '__arco__more' || props.disabled"
|
:closable="data.value !== '__arco__more' && !props.disabled"
|
||||||
@close="handleClose(data)"
|
@close="handleClose(data)"
|
||||||
>
|
>
|
||||||
{{ data.value === '__arco__more' ? data.label.replace('...', '') : data.label }}
|
{{ data.value === '__arco__more' ? data.label.replace('...', '') : data.label }}
|
||||||
|
@ -106,7 +106,9 @@
|
||||||
<div class="flex items-center gap-[4px]">
|
<div class="flex items-center gap-[4px]">
|
||||||
<icon-exclamation-circle-fill class="!text-[rgb(var(--warning-6))]" :size="18" />
|
<icon-exclamation-circle-fill class="!text-[rgb(var(--warning-6))]" :size="18" />
|
||||||
<div class="text-[var(--color-text-4)]">{{ t('ms.add.attachment.alreadyDelete') }}</div>
|
<div class="text-[var(--color-text-4)]">{{ t('ms.add.attachment.alreadyDelete') }}</div>
|
||||||
<MsButton type="text" @click="clearDeletedFiles">{{ t('ms.add.attachment.quickClear') }}</MsButton>
|
<MsButton type="text" :disabled="props.disabled" @click="clearDeletedFiles">
|
||||||
|
{{ t('ms.add.attachment.quickClear') }}
|
||||||
|
</MsButton>
|
||||||
</div>
|
</div>
|
||||||
<div class="file-list">
|
<div class="file-list">
|
||||||
<div v-for="file of alreadyDeleteFiles" :key="file.value" class="file-list-item">
|
<div v-for="file of alreadyDeleteFiles" :key="file.value" class="file-list-item">
|
||||||
|
@ -116,8 +118,12 @@
|
||||||
</MsTag>
|
</MsTag>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip :content="t('ms.add.attachment.remove')">
|
<a-tooltip :content="t('ms.add.attachment.remove')">
|
||||||
<MsButton type="text" status="secondary" @click="handleClose(file)">
|
<MsButton type="text" status="secondary" :disabled="props.disabled" @click="handleClose(file)">
|
||||||
<MsIcon type="icon-icon_unlink" class="hover:text-[rgb(var(--primary-5))]" size="16" />
|
<MsIcon
|
||||||
|
type="icon-icon_unlink"
|
||||||
|
:class="props.disabled ? '' : 'hover:text-[rgb(var(--primary-5))]'"
|
||||||
|
size="16"
|
||||||
|
/>
|
||||||
</MsButton>
|
</MsButton>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
@ -137,25 +143,39 @@
|
||||||
<div v-if="file.local === true" class="flex items-center">
|
<div v-if="file.local === true" class="flex items-center">
|
||||||
<template v-if="hasAnyPermission(['PROJECT_FILE_MANAGEMENT:READ+ADD'])">
|
<template v-if="hasAnyPermission(['PROJECT_FILE_MANAGEMENT:READ+ADD'])">
|
||||||
<a-tooltip :content="t('ms.add.attachment.saveAs')">
|
<a-tooltip :content="t('ms.add.attachment.saveAs')">
|
||||||
<MsButton type="text" status="secondary" class="!mr-0" @click="handleOpenSaveAs(file)">
|
<MsButton
|
||||||
<MsIcon type="icon-icon_unloading" class="hover:text-[rgb(var(--primary-5))]" size="16" />
|
type="text"
|
||||||
|
status="secondary"
|
||||||
|
class="!mr-0"
|
||||||
|
:disabled="props.disabled"
|
||||||
|
@click="handleOpenSaveAs(file)"
|
||||||
|
>
|
||||||
|
<MsIcon
|
||||||
|
type="icon-icon_unloading"
|
||||||
|
:class="props.disabled ? '' : 'hover:text-[rgb(var(--primary-5))]'"
|
||||||
|
size="16"
|
||||||
|
/>
|
||||||
</MsButton>
|
</MsButton>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-divider direction="vertical" :margin="4"></a-divider>
|
<a-divider direction="vertical" :margin="4"></a-divider>
|
||||||
</template>
|
</template>
|
||||||
<a-tooltip :content="t('ms.add.attachment.remove')">
|
<a-tooltip :content="t('ms.add.attachment.remove')">
|
||||||
<MsButton type="text" status="secondary" @click="handleClose(file)">
|
<MsButton type="text" status="secondary" :disabled="props.disabled" @click="handleClose(file)">
|
||||||
<MsIcon
|
<MsIcon
|
||||||
type="icon-icon_delete-trash_outlined"
|
type="icon-icon_delete-trash_outlined"
|
||||||
class="hover:text-[rgb(var(--primary-5))]"
|
:class="props.disabled ? '' : 'hover:text-[rgb(var(--primary-5))]'"
|
||||||
size="16"
|
size="16"
|
||||||
/>
|
/>
|
||||||
</MsButton>
|
</MsButton>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<a-tooltip v-else :content="t('ms.add.attachment.cancelAssociate')">
|
<a-tooltip v-else :content="t('ms.add.attachment.cancelAssociate')">
|
||||||
<MsButton type="text" status="secondary" @click="handleClose(file)">
|
<MsButton type="text" status="secondary" :disabled="props.disabled" @click="handleClose(file)">
|
||||||
<MsIcon type="icon-icon_unlink" class="hover:text-[rgb(var(--primary-5))]" size="16" />
|
<MsIcon
|
||||||
|
type="icon-icon_unlink"
|
||||||
|
:class="props.disabled ? '' : 'hover:text-[rgb(var(--primary-5))]'"
|
||||||
|
size="16"
|
||||||
|
/>
|
||||||
</MsButton>
|
</MsButton>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,21 +4,25 @@
|
||||||
<span class="text-[var(--color-text-1)]">{{ t('ms.assertion.responseTime') }}</span>
|
<span class="text-[var(--color-text-1)]">{{ t('ms.assertion.responseTime') }}</span>
|
||||||
<span class="text-[var(--color-text-4)]">(ms)</span>
|
<span class="text-[var(--color-text-4)]">(ms)</span>
|
||||||
</div>
|
</div>
|
||||||
<a-input-number
|
<div class="flex items-center gap-[4px]">
|
||||||
v-model="condition.expectedValue"
|
<div class="whitespace-nowrap">{{ t('advanceFilter.operator.le') }}</div>
|
||||||
:disabled="props.disabled"
|
<a-input-number
|
||||||
:step="100"
|
v-model="condition.expectedValue"
|
||||||
:min="0"
|
:disabled="props.disabled"
|
||||||
:precision="0"
|
:step="100"
|
||||||
mode="button"
|
:min="0"
|
||||||
model-event="input"
|
:precision="0"
|
||||||
@change="
|
mode="button"
|
||||||
() =>
|
model-event="input"
|
||||||
emit('change', {
|
class="w-[250px]"
|
||||||
...condition,
|
@change="
|
||||||
})
|
() =>
|
||||||
"
|
emit('change', {
|
||||||
/>
|
...condition,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -84,13 +84,7 @@
|
||||||
getCurrentItemState.assertionType !== ResponseAssertionType.SCRIPT,
|
getCurrentItemState.assertionType !== ResponseAssertionType.SCRIPT,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<a-scrollbar
|
<div class="w-full">
|
||||||
:style="{
|
|
||||||
overflow: 'auto',
|
|
||||||
height: '100%',
|
|
||||||
width: '100%',
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<!-- 响应头 -->
|
<!-- 响应头 -->
|
||||||
<ResponseHeaderTab
|
<ResponseHeaderTab
|
||||||
v-if="getCurrentItemState.assertionType === ResponseAssertionType.RESPONSE_HEADER"
|
v-if="getCurrentItemState.assertionType === ResponseAssertionType.RESPONSE_HEADER"
|
||||||
|
@ -130,7 +124,7 @@
|
||||||
@change="handleChange"
|
@change="handleChange"
|
||||||
/>
|
/>
|
||||||
<!-- 脚本 -->
|
<!-- 脚本 -->
|
||||||
</a-scrollbar>
|
</div>
|
||||||
<ScriptTab
|
<ScriptTab
|
||||||
v-if="getCurrentItemState.assertionType === ResponseAssertionType.SCRIPT"
|
v-if="getCurrentItemState.assertionType === ResponseAssertionType.SCRIPT"
|
||||||
v-model:data="getCurrentItemState"
|
v-model:data="getCurrentItemState"
|
||||||
|
|
|
@ -461,7 +461,7 @@
|
||||||
key={element?.name}
|
key={element?.name}
|
||||||
v-slots={{
|
v-slots={{
|
||||||
icon,
|
icon,
|
||||||
title: () => h(t(element?.meta?.locale || '')),
|
title: () => h('div', t(element?.meta?.locale || '')),
|
||||||
}}
|
}}
|
||||||
class={BOTTOM_MENU_LIST.includes(element?.name as string) ? 'arco-menu-inline--bottom' : ''}
|
class={BOTTOM_MENU_LIST.includes(element?.name as string) ? 'arco-menu-inline--bottom' : ''}
|
||||||
>
|
>
|
||||||
|
|
|
@ -282,7 +282,6 @@
|
||||||
if (height > 1000) {
|
if (height > 1000) {
|
||||||
codeheight.value = `1000px`;
|
codeheight.value = `1000px`;
|
||||||
}
|
}
|
||||||
editor.layout();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const init = () => {
|
const init = () => {
|
||||||
|
|
|
@ -3,16 +3,22 @@ import type { MsFileItem } from '@/components/pure/ms-upload/types';
|
||||||
import type { RequestBodyFormat, RequestParamsType } from '@/enums/apiEnum';
|
import type { RequestBodyFormat, RequestParamsType } from '@/enums/apiEnum';
|
||||||
|
|
||||||
import type { BatchApiParams } from '../common';
|
import type { BatchApiParams } from '../common';
|
||||||
import type { ExecuteBinaryBody, KeyValueParam, ResponseDefinitionBody } from './common';
|
import type {
|
||||||
|
ExecuteBinaryBody,
|
||||||
|
ExecuteJsonBody,
|
||||||
|
ExecuteValueBody,
|
||||||
|
KeyValueParam,
|
||||||
|
ResponseDefinitionBody,
|
||||||
|
} from './common';
|
||||||
|
|
||||||
// mock 信息-匹配项
|
// mock 信息-匹配项
|
||||||
export interface MatchRuleItem {
|
export interface MatchRuleItem {
|
||||||
id?: string; // 用于前端标识
|
id?: string; // 用于前端标识
|
||||||
|
paramType: RequestParamsType; // 用于前端标识
|
||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
condition: string;
|
condition: string;
|
||||||
description: string;
|
description: string;
|
||||||
paramType: RequestParamsType;
|
|
||||||
files: ({
|
files: ({
|
||||||
fileId: string;
|
fileId: string;
|
||||||
fileName: string;
|
fileName: string;
|
||||||
|
@ -37,10 +43,13 @@ export interface MockMatchRuleCommon {
|
||||||
}
|
}
|
||||||
// mock 信息-请求体匹配规则
|
// mock 信息-请求体匹配规则
|
||||||
export interface MockBody {
|
export interface MockBody {
|
||||||
paramType: RequestBodyFormat;
|
bodyType: RequestBodyFormat;
|
||||||
formDataMatch: MockMatchRuleCommon;
|
formDataBody: MockMatchRuleCommon;
|
||||||
|
wwwFormBody: MockMatchRuleCommon;
|
||||||
|
jsonBody: ExecuteJsonBody;
|
||||||
|
xmlBody: ExecuteValueBody;
|
||||||
|
rawBody: ExecuteValueBody;
|
||||||
binaryBody: ExecuteBinaryBody;
|
binaryBody: ExecuteBinaryBody;
|
||||||
raw: string;
|
|
||||||
}
|
}
|
||||||
// mock 信息-匹配规则集合
|
// mock 信息-匹配规则集合
|
||||||
export interface MockMatchRule {
|
export interface MockMatchRule {
|
||||||
|
@ -74,7 +83,6 @@ export interface UpdateMockParams extends MockParams {
|
||||||
// mock 信息-详情
|
// mock 信息-详情
|
||||||
export interface MockDetail extends MockParams {
|
export interface MockDetail extends MockParams {
|
||||||
id: string;
|
id: string;
|
||||||
matching: MockMatchRule;
|
|
||||||
}
|
}
|
||||||
// 批量编辑 mock
|
// 批量编辑 mock
|
||||||
export interface BatchEditMockParams extends BatchApiParams {
|
export interface BatchEditMockParams extends BatchApiParams {
|
||||||
|
|
|
@ -22,11 +22,10 @@
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="flex h-full">
|
<div class="h-full">
|
||||||
<MsCodeEditor
|
<MsCodeEditor
|
||||||
v-if="visible"
|
v-if="visible"
|
||||||
v-model:model-value="batchParamsCode"
|
v-model:model-value="batchParamsCode"
|
||||||
class="flex-1"
|
|
||||||
theme="vs"
|
theme="vs"
|
||||||
height="100%"
|
height="100%"
|
||||||
:show-full-screen="false"
|
:show-full-screen="false"
|
||||||
|
|
|
@ -123,10 +123,9 @@
|
||||||
{{ t('common.copy') }}
|
{{ t('common.copy') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex h-[412px]">
|
<div class="h-[412px]">
|
||||||
<MsCodeEditor
|
<MsCodeEditor
|
||||||
v-model:model-value="scriptEx"
|
v-model:model-value="scriptEx"
|
||||||
class="flex-1"
|
|
||||||
theme="vs"
|
theme="vs"
|
||||||
:language="LanguageEnum.BEANSHELL_JSR233"
|
:language="LanguageEnum.BEANSHELL_JSR233"
|
||||||
width="500px"
|
width="500px"
|
||||||
|
|
|
@ -114,12 +114,12 @@ export const defaultBodyParams: ExecuteBody = {
|
||||||
jsonValue: '',
|
jsonValue: '',
|
||||||
},
|
},
|
||||||
xmlBody: { value: '' },
|
xmlBody: { value: '' },
|
||||||
|
rawBody: { value: '' },
|
||||||
binaryBody: {
|
binaryBody: {
|
||||||
description: '',
|
description: '',
|
||||||
file: undefined,
|
file: undefined,
|
||||||
sendAsBody: false,
|
sendAsBody: false,
|
||||||
},
|
},
|
||||||
rawBody: { value: '' },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 默认的响应内容结构
|
// 默认的响应内容结构
|
||||||
|
@ -299,17 +299,25 @@ export const mockDefaultParams: MockParams = {
|
||||||
matchAll: true,
|
matchAll: true,
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
paramType: RequestBodyFormat.FORM_DATA,
|
bodyType: RequestBodyFormat.FORM_DATA,
|
||||||
formDataMatch: {
|
formDataBody: {
|
||||||
matchRules: [],
|
matchRules: [],
|
||||||
matchAll: true,
|
matchAll: true,
|
||||||
},
|
},
|
||||||
|
wwwFormBody: {
|
||||||
|
matchRules: [],
|
||||||
|
matchAll: true,
|
||||||
|
},
|
||||||
|
jsonBody: {
|
||||||
|
jsonValue: '',
|
||||||
|
},
|
||||||
|
xmlBody: { value: '' },
|
||||||
|
rawBody: { value: '' },
|
||||||
binaryBody: {
|
binaryBody: {
|
||||||
description: '',
|
description: '',
|
||||||
file: undefined,
|
file: undefined,
|
||||||
sendAsBody: false,
|
sendAsBody: false,
|
||||||
},
|
},
|
||||||
raw: '',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
response: {
|
response: {
|
||||||
|
@ -344,15 +352,19 @@ export const mockDefaultParams: MockParams = {
|
||||||
export const makeDefaultParams = () => {
|
export const makeDefaultParams = () => {
|
||||||
const defaultParams = cloneDeep(mockDefaultParams);
|
const defaultParams = cloneDeep(mockDefaultParams);
|
||||||
defaultParams.id = Date.now().toString();
|
defaultParams.id = Date.now().toString();
|
||||||
defaultParams.mockMatchRule.body.formDataMatch.matchRules.push({
|
defaultParams.mockMatchRule.body.formDataBody.matchRules.push({
|
||||||
...cloneDeep(defaultMatchRuleItem),
|
...defaultMatchRuleItem,
|
||||||
id: Date.now().toString(),
|
id: Date.now().toString(),
|
||||||
});
|
});
|
||||||
defaultParams.mockMatchRule.header.matchRules.push({ ...cloneDeep(defaultMatchRuleItem), id: Date.now().toString() });
|
defaultParams.mockMatchRule.body.wwwFormBody.matchRules.push({
|
||||||
defaultParams.mockMatchRule.query.matchRules.push({ ...cloneDeep(defaultMatchRuleItem), id: Date.now().toString() });
|
...defaultMatchRuleItem,
|
||||||
defaultParams.mockMatchRule.rest.matchRules.push({ ...cloneDeep(defaultMatchRuleItem), id: Date.now().toString() });
|
id: Date.now().toString(),
|
||||||
defaultParams.response.headers.push({ ...cloneDeep(defaultMatchRuleItem), id: Date.now().toString() });
|
});
|
||||||
return defaultParams;
|
defaultParams.mockMatchRule.header.matchRules.push({ ...defaultMatchRuleItem, id: Date.now().toString() });
|
||||||
|
defaultParams.mockMatchRule.query.matchRules.push({ ...defaultMatchRuleItem, id: Date.now().toString() });
|
||||||
|
defaultParams.mockMatchRule.rest.matchRules.push({ ...defaultMatchRuleItem, id: Date.now().toString() });
|
||||||
|
defaultParams.response.headers.push({ ...defaultMatchRuleItem, id: Date.now().toString() });
|
||||||
|
return cloneDeep(defaultParams);
|
||||||
};
|
};
|
||||||
// mock 匹配规则选项
|
// mock 匹配规则选项
|
||||||
export const matchRuleOptions = [
|
export const matchRuleOptions = [
|
||||||
|
|
|
@ -84,11 +84,10 @@
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div> -->
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="flex h-[calc(100%-34px)]">
|
<div v-else class="h-[calc(100%-34px)]">
|
||||||
<MsCodeEditor
|
<MsCodeEditor
|
||||||
v-model:model-value="currentBodyCode"
|
v-model:model-value="currentBodyCode"
|
||||||
:read-only="props.disabledExceptParam"
|
:read-only="props.disabledExceptParam"
|
||||||
class="flex-1"
|
|
||||||
theme="vs"
|
theme="vs"
|
||||||
height="100%"
|
height="100%"
|
||||||
:show-full-screen="false"
|
:show-full-screen="false"
|
||||||
|
|
|
@ -13,7 +13,11 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #condition="{ record }">
|
<template #condition="{ record }">
|
||||||
{{ t(statusCodeOptions.find((item) => item.value === record.condition)?.label || '') }}
|
{{
|
||||||
|
record.assertionType === FullResponseAssertionType.RESPONSE_TIME
|
||||||
|
? t('advanceFilter.operator.le')
|
||||||
|
: t(statusCodeOptions.find((item) => item.value === record.condition)?.label || '')
|
||||||
|
}}
|
||||||
</template>
|
</template>
|
||||||
<template #status="{ record }">
|
<template #status="{ record }">
|
||||||
<MsTag :type="record.pass === true ? 'success' : 'danger'" theme="light">
|
<MsTag :type="record.pass === true ? 'success' : 'danger'" theme="light">
|
||||||
|
@ -33,6 +37,7 @@
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
import { RequestResult, ResponseAssertionTableItem } from '@/models/apiTest/common';
|
import { RequestResult, ResponseAssertionTableItem } from '@/models/apiTest/common';
|
||||||
|
import { FullResponseAssertionType } from '@/enums/apiEnum';
|
||||||
|
|
||||||
import { responseAssertionTypeMap } from '@/views/api-test/components/config';
|
import { responseAssertionTypeMap } from '@/views/api-test/components/config';
|
||||||
|
|
||||||
|
|
|
@ -45,8 +45,9 @@ export function parseRequestBodyFiles(
|
||||||
const tempSaveLinkFileIds = new Set<string>(); // 临时存储 body 内已保存的关联文件 id 集合,用于对比 saveLinkFileIds 以判断有哪些文件被取消关联
|
const tempSaveLinkFileIds = new Set<string>(); // 临时存储 body 内已保存的关联文件 id 集合,用于对比 saveLinkFileIds 以判断有哪些文件被取消关联
|
||||||
// 获取上传文件和关联文件
|
// 获取上传文件和关联文件
|
||||||
const formValues =
|
const formValues =
|
||||||
((body as ExecuteBody).formDataBody?.formValues || (body as MockBody).formDataMatch.matchRules).filter((e) => e) ||
|
((body as ExecuteBody).formDataBody?.formValues || (body as MockBody).formDataBody?.matchRules || []).filter(
|
||||||
[];
|
(e) => e
|
||||||
|
) || [];
|
||||||
for (let i = 0; i < formValues.length; i++) {
|
for (let i = 0; i < formValues.length; i++) {
|
||||||
const item = formValues[i];
|
const item = formValues[i];
|
||||||
if (item.paramType === RequestParamsType.FILE) {
|
if (item.paramType === RequestParamsType.FILE) {
|
||||||
|
|
|
@ -151,6 +151,7 @@
|
||||||
|
|
||||||
import { ProtocolItem } from '@/models/apiTest/common';
|
import { ProtocolItem } from '@/models/apiTest/common';
|
||||||
import { ApiDefinitionDetail } from '@/models/apiTest/management';
|
import { ApiDefinitionDetail } from '@/models/apiTest/management';
|
||||||
|
import { MockDetail } from '@/models/apiTest/mock';
|
||||||
import { ModuleTreeNode } from '@/models/common';
|
import { ModuleTreeNode } from '@/models/common';
|
||||||
import {
|
import {
|
||||||
RequestAuthType,
|
RequestAuthType,
|
||||||
|
@ -380,6 +381,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function openApiTabAndDebugMock(mock: MockDetail) {
|
||||||
|
await openApiTab(mock.apiDefinitionId as string);
|
||||||
|
}
|
||||||
|
|
||||||
// 新建接口后没有创建人,创建时间,更新时间的信息。所以需要刷新数据
|
// 新建接口后没有创建人,创建时间,更新时间的信息。所以需要刷新数据
|
||||||
watch(
|
watch(
|
||||||
() => activeApiTab.value.isNew,
|
() => activeApiTab.value.isNew,
|
||||||
|
@ -452,6 +457,7 @@
|
||||||
defineExpose({
|
defineExpose({
|
||||||
openApiTab,
|
openApiTab,
|
||||||
addApiTab,
|
addApiTab,
|
||||||
|
openApiTabAndDebugMock,
|
||||||
refreshTable,
|
refreshTable,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -31,7 +31,6 @@
|
||||||
<MsCodeEditor
|
<MsCodeEditor
|
||||||
v-show="headerShowType === 'raw'"
|
v-show="headerShowType === 'raw'"
|
||||||
:model-value="headerRawCode"
|
:model-value="headerRawCode"
|
||||||
class="flex-1"
|
|
||||||
theme="MS-text"
|
theme="MS-text"
|
||||||
height="200px"
|
height="200px"
|
||||||
:show-full-screen="false"
|
:show-full-screen="false"
|
||||||
|
@ -70,7 +69,6 @@
|
||||||
<MsCodeEditor
|
<MsCodeEditor
|
||||||
v-show="queryShowType === 'raw'"
|
v-show="queryShowType === 'raw'"
|
||||||
:model-value="queryRawCode"
|
:model-value="queryRawCode"
|
||||||
class="flex-1"
|
|
||||||
theme="MS-text"
|
theme="MS-text"
|
||||||
height="200px"
|
height="200px"
|
||||||
:show-full-screen="false"
|
:show-full-screen="false"
|
||||||
|
@ -109,7 +107,6 @@
|
||||||
<MsCodeEditor
|
<MsCodeEditor
|
||||||
v-show="restShowType === 'raw'"
|
v-show="restShowType === 'raw'"
|
||||||
:model-value="restRawCode"
|
:model-value="restRawCode"
|
||||||
class="flex-1"
|
|
||||||
theme="MS-text"
|
theme="MS-text"
|
||||||
height="200px"
|
height="200px"
|
||||||
:show-full-screen="false"
|
:show-full-screen="false"
|
||||||
|
@ -169,7 +166,6 @@
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
:model-value="bodyCode"
|
:model-value="bodyCode"
|
||||||
class="flex-1"
|
|
||||||
theme="vs"
|
theme="vs"
|
||||||
height="200px"
|
height="200px"
|
||||||
:language="bodyCodeLanguage"
|
:language="bodyCodeLanguage"
|
||||||
|
@ -210,7 +206,6 @@
|
||||||
<MsCodeEditor
|
<MsCodeEditor
|
||||||
v-show="pluginShowType === 'raw'"
|
v-show="pluginShowType === 'raw'"
|
||||||
:model-value="pluginRawCode"
|
:model-value="pluginRawCode"
|
||||||
class="flex-1"
|
|
||||||
theme="MS-text"
|
theme="MS-text"
|
||||||
height="400px"
|
height="400px"
|
||||||
:show-full-screen="false"
|
:show-full-screen="false"
|
||||||
|
@ -287,7 +282,6 @@
|
||||||
<MsCodeEditor
|
<MsCodeEditor
|
||||||
v-else
|
v-else
|
||||||
:model-value="responseCode"
|
:model-value="responseCode"
|
||||||
class="flex-1"
|
|
||||||
theme="vs"
|
theme="vs"
|
||||||
height="200px"
|
height="200px"
|
||||||
:language="responseCodeLanguage"
|
:language="responseCodeLanguage"
|
||||||
|
|
|
@ -67,6 +67,7 @@
|
||||||
:offspring-ids="props.offspringIds"
|
:offspring-ids="props.offspringIds"
|
||||||
:protocol="props.protocol"
|
:protocol="props.protocol"
|
||||||
:definition-detail="activeApiTab"
|
:definition-detail="activeApiTab"
|
||||||
|
@debug="handleMockDebug"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -88,6 +89,7 @@
|
||||||
import { hasAnyPermission } from '@/utils/permission';
|
import { hasAnyPermission } from '@/utils/permission';
|
||||||
|
|
||||||
import { ProtocolItem } from '@/models/apiTest/common';
|
import { ProtocolItem } from '@/models/apiTest/common';
|
||||||
|
import { MockDetail } from '@/models/apiTest/mock';
|
||||||
import { ModuleTreeNode } from '@/models/common';
|
import { ModuleTreeNode } from '@/models/common';
|
||||||
import {
|
import {
|
||||||
RequestAuthType,
|
RequestAuthType,
|
||||||
|
@ -323,6 +325,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleMockDebug(mock: MockDetail) {
|
||||||
|
apiRef.value?.openApiTabAndDebugMock(mock);
|
||||||
|
}
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
initMemberOptions();
|
initMemberOptions();
|
||||||
initProtocolList();
|
initProtocolList();
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
v-permission="['PROJECT_API_DEFINITION_MOCK:READ+UPDATE']"
|
v-permission="['PROJECT_API_DEFINITION_MOCK:READ+UPDATE']"
|
||||||
type="icon"
|
type="icon"
|
||||||
status="secondary"
|
status="secondary"
|
||||||
@click="isEdit = true"
|
@click="handleChangeEdit"
|
||||||
>
|
>
|
||||||
<MsIcon type="icon-icon_edit_outlined" />
|
<MsIcon type="icon-icon_edit_outlined" />
|
||||||
{{ t('common.edit') }}
|
{{ t('common.edit') }}
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
</MsButton>
|
</MsButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<a-spin :loading="loading" class="block p-[16px]">
|
<a-spin v-if="visible" :loading="loading" class="block p-[16px]">
|
||||||
<MsDetailCard
|
<MsDetailCard
|
||||||
:title="`【${props.definitionDetail.num}】${props.definitionDetail.name}`"
|
:title="`【${props.definitionDetail.num}】${props.definitionDetail.name}`"
|
||||||
:description="[]"
|
:description="[]"
|
||||||
|
@ -103,7 +103,7 @@
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="mb-[8px] flex items-center justify-between">
|
<div class="mb-[8px] flex items-center justify-between">
|
||||||
<a-radio-group
|
<a-radio-group
|
||||||
v-model:model-value="mockDetail.mockMatchRule.body.paramType"
|
v-model:model-value="mockDetail.mockMatchRule.body.bodyType"
|
||||||
type="button"
|
type="button"
|
||||||
size="small"
|
size="small"
|
||||||
:disabled="isReadOnly"
|
:disabled="isReadOnly"
|
||||||
|
@ -115,22 +115,28 @@
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="mockDetail.mockMatchRule.body.paramType === RequestBodyFormat.NONE"
|
v-if="mockDetail.mockMatchRule.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)]"
|
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') }}
|
{{ t('apiTestDebug.noneBody') }}
|
||||||
</div>
|
</div>
|
||||||
<mockMatchRuleForm
|
<mockMatchRuleForm
|
||||||
v-else-if="
|
v-else-if="mockDetail.mockMatchRule.body.bodyType === RequestBodyFormat.FORM_DATA"
|
||||||
[RequestBodyFormat.FORM_DATA, RequestBodyFormat.WWW_FORM].includes(mockDetail.mockMatchRule.body.paramType)
|
|
||||||
"
|
|
||||||
:id="mockDetail.id"
|
:id="mockDetail.id"
|
||||||
v-model:matchAll="mockDetail.mockMatchRule.body.formDataMatch.matchAll"
|
v-model:matchAll="mockDetail.mockMatchRule.body.formDataBody.matchAll"
|
||||||
v-model:matchRules="mockDetail.mockMatchRule.body.formDataMatch.matchRules"
|
v-model:matchRules="mockDetail.mockMatchRule.body.formDataBody.matchRules"
|
||||||
:key-options="currentBodyKeyOptions"
|
:key-options="currentBodyKeyOptions"
|
||||||
:disabled="isReadOnly"
|
:disabled="isReadOnly"
|
||||||
/>
|
/>
|
||||||
<div v-else-if="mockDetail.mockMatchRule.body.paramType === RequestBodyFormat.BINARY">
|
<mockMatchRuleForm
|
||||||
|
v-else-if="mockDetail.mockMatchRule.body.bodyType === RequestBodyFormat.WWW_FORM"
|
||||||
|
:id="mockDetail.id"
|
||||||
|
v-model:matchAll="mockDetail.mockMatchRule.body.wwwFormBody.matchAll"
|
||||||
|
v-model:matchRules="mockDetail.mockMatchRule.body.wwwFormBody.matchRules"
|
||||||
|
:key-options="currentBodyKeyOptions"
|
||||||
|
:disabled="isReadOnly"
|
||||||
|
/>
|
||||||
|
<div v-else-if="mockDetail.mockMatchRule.body.bodyType === RequestBodyFormat.BINARY">
|
||||||
<div class="mb-[16px] flex justify-between gap-[8px] bg-[var(--color-text-n9)] p-[12px]">
|
<div class="mb-[16px] flex justify-between gap-[8px] bg-[var(--color-text-n9)] p-[12px]">
|
||||||
<MsAddAttachment
|
<MsAddAttachment
|
||||||
v-model:file-list="fileList"
|
v-model:file-list="fileList"
|
||||||
|
@ -162,12 +168,11 @@
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div> -->
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="flex h-[300px]">
|
<div v-else>
|
||||||
<MsCodeEditor
|
<MsCodeEditor
|
||||||
v-model:model-value="mockDetail.mockMatchRule.body.raw"
|
v-model:model-value="currentBodyCode"
|
||||||
class="flex-1"
|
|
||||||
theme="vs"
|
theme="vs"
|
||||||
height="100%"
|
is-adaptive
|
||||||
:show-full-screen="false"
|
:show-full-screen="false"
|
||||||
:show-theme-change="false"
|
:show-theme-change="false"
|
||||||
:show-code-format="true"
|
:show-code-format="true"
|
||||||
|
@ -188,6 +193,7 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Message } from '@arco-design/web-vue';
|
import { Message } from '@arco-design/web-vue';
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
||||||
|
@ -216,6 +222,7 @@
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
|
|
||||||
|
import { ResponseDefinition } from '@/models/apiTest/common';
|
||||||
import { MockParams } from '@/models/apiTest/mock';
|
import { MockParams } from '@/models/apiTest/mock';
|
||||||
import { RequestBodyFormat, RequestComposition } from '@/enums/apiEnum';
|
import { RequestBodyFormat, RequestComposition } from '@/enums/apiEnum';
|
||||||
|
|
||||||
|
@ -231,6 +238,7 @@
|
||||||
definitionDetail: RequestParam;
|
definitionDetail: RequestParam;
|
||||||
detailId?: string;
|
detailId?: string;
|
||||||
isCopy?: boolean;
|
isCopy?: boolean;
|
||||||
|
isEditMode?: boolean;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'delete'): void;
|
(e: 'delete'): void;
|
||||||
|
@ -245,7 +253,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const isEdit = ref(false);
|
const isEdit = ref(props.isEditMode);
|
||||||
const mockDetail = ref<MockParams>(makeDefaultParams());
|
const mockDetail = ref<MockParams>(makeDefaultParams());
|
||||||
const isReadOnly = computed(() => !mockDetail.value.isNew && !isEdit.value);
|
const isReadOnly = computed(() => !mockDetail.value.isNew && !isEdit.value);
|
||||||
const title = computed(() => {
|
const title = computed(() => {
|
||||||
|
@ -287,7 +295,7 @@
|
||||||
.validParams.length;
|
.validParams.length;
|
||||||
return `${headerNum > 0 ? headerNum : ''}`;
|
return `${headerNum > 0 ? headerNum : ''}`;
|
||||||
case RequestComposition.BODY:
|
case RequestComposition.BODY:
|
||||||
return mockDetail.value.mockMatchRule.body.paramType !== RequestBodyFormat.NONE ? '1' : '';
|
return mockDetail.value.mockMatchRule.body.bodyType !== RequestBodyFormat.NONE ? '1' : '';
|
||||||
case RequestComposition.QUERY:
|
case RequestComposition.QUERY:
|
||||||
const queryNum = filterKeyValParams(mockDetail.value.mockMatchRule.query.matchRules, defaultRequestParamsItem)
|
const queryNum = filterKeyValParams(mockDetail.value.mockMatchRule.query.matchRules, defaultRequestParamsItem)
|
||||||
.validParams.length;
|
.validParams.length;
|
||||||
|
@ -384,15 +392,16 @@
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// 当前请求 body 的参数名选项集合
|
||||||
const currentBodyKeyOptions = computed(() => {
|
const currentBodyKeyOptions = computed(() => {
|
||||||
switch (mockDetail.value.mockMatchRule.body.paramType) {
|
switch (mockDetail.value.mockMatchRule.body.bodyType) {
|
||||||
case RequestBodyFormat.FORM_DATA:
|
case RequestBodyFormat.FORM_DATA:
|
||||||
return filterKeyValParams(
|
return filterKeyValParams(
|
||||||
props.definitionDetail.body.formDataBody.formValues,
|
props.definitionDetail.body.formDataBody.formValues,
|
||||||
defaultMatchRuleItem
|
defaultMatchRuleItem
|
||||||
).validParams.map((e) => ({
|
).validParams.map((e) => ({
|
||||||
label: e.key,
|
label: e.key,
|
||||||
value: e.value,
|
value: e.key,
|
||||||
paramType: e.paramType,
|
paramType: e.paramType,
|
||||||
}));
|
}));
|
||||||
case RequestBodyFormat.WWW_FORM:
|
case RequestBodyFormat.WWW_FORM:
|
||||||
|
@ -407,17 +416,71 @@
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// 当前代码编辑器的语言
|
|
||||||
|
// 当前请求 body 显示的代码
|
||||||
|
const currentBodyCode = computed({
|
||||||
|
get() {
|
||||||
|
if (mockDetail.value.mockMatchRule.body.bodyType === RequestBodyFormat.JSON) {
|
||||||
|
return mockDetail.value.mockMatchRule.body.jsonBody.jsonValue;
|
||||||
|
}
|
||||||
|
if (mockDetail.value.mockMatchRule.body.bodyType === RequestBodyFormat.XML) {
|
||||||
|
return mockDetail.value.mockMatchRule.body.xmlBody.value;
|
||||||
|
}
|
||||||
|
return mockDetail.value.mockMatchRule.body.rawBody.value;
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
if (mockDetail.value.mockMatchRule.body.bodyType === RequestBodyFormat.JSON) {
|
||||||
|
mockDetail.value.mockMatchRule.body.jsonBody.jsonValue = val;
|
||||||
|
} else if (mockDetail.value.mockMatchRule.body.bodyType === RequestBodyFormat.XML) {
|
||||||
|
mockDetail.value.mockMatchRule.body.xmlBody.value = val;
|
||||||
|
} else {
|
||||||
|
mockDetail.value.mockMatchRule.body.rawBody.value = val;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// 当前请求 body 代码编辑器的语言
|
||||||
const currentCodeLanguage = computed(() => {
|
const currentCodeLanguage = computed(() => {
|
||||||
if (mockDetail.value.mockMatchRule.body.paramType === RequestBodyFormat.JSON) {
|
if (mockDetail.value.mockMatchRule.body.bodyType === RequestBodyFormat.JSON) {
|
||||||
return LanguageEnum.JSON;
|
return LanguageEnum.JSON;
|
||||||
}
|
}
|
||||||
if (mockDetail.value.mockMatchRule.body.paramType === RequestBodyFormat.XML) {
|
if (mockDetail.value.mockMatchRule.body.bodyType === RequestBodyFormat.XML) {
|
||||||
return LanguageEnum.XML;
|
return LanguageEnum.XML;
|
||||||
}
|
}
|
||||||
return LanguageEnum.PLAINTEXT;
|
return LanguageEnum.PLAINTEXT;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加默认的匹配规则项
|
||||||
|
*/
|
||||||
|
function appendDefaultMatchRuleItem() {
|
||||||
|
const { body } = mockDetail.value.mockMatchRule;
|
||||||
|
mockDetail.value.mockMatchRule.body = {
|
||||||
|
...body,
|
||||||
|
formDataBody: {
|
||||||
|
matchAll: body.formDataBody.matchAll,
|
||||||
|
matchRules: [...body.formDataBody.matchRules, cloneDeep(defaultMatchRuleItem)],
|
||||||
|
},
|
||||||
|
wwwFormBody: {
|
||||||
|
matchAll: body.wwwFormBody.matchAll,
|
||||||
|
matchRules: [...body.wwwFormBody.matchRules, cloneDeep(defaultMatchRuleItem)],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mockDetail.value.mockMatchRule.header.matchRules = [
|
||||||
|
...mockDetail.value.mockMatchRule.header.matchRules,
|
||||||
|
cloneDeep(defaultMatchRuleItem),
|
||||||
|
];
|
||||||
|
mockDetail.value.mockMatchRule.query.matchRules = [
|
||||||
|
...mockDetail.value.mockMatchRule.query.matchRules,
|
||||||
|
cloneDeep(defaultMatchRuleItem),
|
||||||
|
];
|
||||||
|
mockDetail.value.mockMatchRule.rest.matchRules = [
|
||||||
|
...mockDetail.value.mockMatchRule.rest.matchRules,
|
||||||
|
cloneDeep(defaultMatchRuleItem),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileList = ref<MsFileItem[]>([]);
|
||||||
|
|
||||||
async function initMockDetail() {
|
async function initMockDetail() {
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
@ -425,34 +488,45 @@
|
||||||
id: props.detailId || '',
|
id: props.detailId || '',
|
||||||
projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
});
|
});
|
||||||
const parseFileResult = parseRequestBodyFiles(res.matching.body);
|
// form-data 的匹配规则含有文件类型,特殊处理
|
||||||
const formDataMatch =
|
const formDataMatch = res.mockMatchRule.body.formDataBody.matchRules.map((item) => {
|
||||||
res.matching.body.paramType === RequestBodyFormat.FORM_DATA
|
const newParamType =
|
||||||
? res.matching.body.formDataMatch.matchRules.map((item) => {
|
currentBodyKeyOptions.value.find((e) => e.value === item.key)?.paramType || defaultMatchRuleItem.paramType;
|
||||||
const newParamType =
|
item.paramType = newParamType;
|
||||||
currentBodyKeyOptions.value.find((e) => e.value === item.key)?.paramType ||
|
item.files = item.files || [];
|
||||||
defaultMatchRuleItem.paramType;
|
return item;
|
||||||
item.paramType = newParamType;
|
});
|
||||||
item.files = item.files || [];
|
|
||||||
return item;
|
|
||||||
})
|
|
||||||
: res.matching.body.formDataMatch.matchRules;
|
|
||||||
mockDetail.value = {
|
mockDetail.value = {
|
||||||
...res,
|
...res,
|
||||||
id: props.isCopy ? '' : res.id,
|
id: props.isCopy ? '' : res.id,
|
||||||
isNew: props.isCopy,
|
isNew: props.isCopy,
|
||||||
|
name: props.isCopy ? `${res.name}_copy` : res.name,
|
||||||
mockMatchRule: {
|
mockMatchRule: {
|
||||||
...res.matching,
|
...res.mockMatchRule,
|
||||||
body: {
|
body: {
|
||||||
...res.matching.body,
|
...res.mockMatchRule.body,
|
||||||
formDataMatch: {
|
formDataBody: {
|
||||||
...res.matching.body.formDataMatch,
|
...res.mockMatchRule.body.formDataBody,
|
||||||
matchRules: formDataMatch,
|
matchRules: formDataMatch,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
// 等formDataMatch解析完毕赋值后才能正确解析 body 内的文件类型值
|
||||||
|
const parseFileResult = parseRequestBodyFiles(mockDetail.value.mockMatchRule.body, [
|
||||||
|
res.response as unknown as ResponseDefinition,
|
||||||
|
]);
|
||||||
|
mockDetail.value = {
|
||||||
|
...mockDetail.value,
|
||||||
...parseFileResult,
|
...parseFileResult,
|
||||||
};
|
};
|
||||||
|
fileList.value = mockDetail.value.mockMatchRule.body.binaryBody.file
|
||||||
|
? [mockDetail.value.mockMatchRule.body.binaryBody.file as unknown as MsFileItem]
|
||||||
|
: [];
|
||||||
|
if (props.isCopy) {
|
||||||
|
appendDefaultMatchRuleItem();
|
||||||
|
}
|
||||||
|
isEdit.value = !!props.isEditMode;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
@ -464,8 +538,12 @@
|
||||||
watch(
|
watch(
|
||||||
() => visible.value,
|
() => visible.value,
|
||||||
(val) => {
|
(val) => {
|
||||||
if (val && props.detailId) {
|
if (val) {
|
||||||
initMockDetail();
|
if (props.detailId) {
|
||||||
|
initMockDetail();
|
||||||
|
} else {
|
||||||
|
fileList.value = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -473,8 +551,6 @@
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const fileList = ref<MsFileItem[]>([]);
|
|
||||||
|
|
||||||
async function handleFileChange(files: MsFileItem[], file?: MsFileItem) {
|
async function handleFileChange(files: MsFileItem[], file?: MsFileItem) {
|
||||||
try {
|
try {
|
||||||
if (file?.local && file.file) {
|
if (file?.local && file.file) {
|
||||||
|
@ -512,25 +588,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
|
||||||
() => mockDetail.value.mockMatchRule.body.paramType,
|
|
||||||
(val) => {
|
|
||||||
if (val === RequestBodyFormat.JSON) {
|
|
||||||
mockDetail.value.mockMatchRule.body.raw = props.definitionDetail.body.jsonBody.jsonValue;
|
|
||||||
} else if (val === RequestBodyFormat.XML) {
|
|
||||||
mockDetail.value.mockMatchRule.body.raw = props.definitionDetail.body.xmlBody.value || '';
|
|
||||||
} else if (val === RequestBodyFormat.RAW) {
|
|
||||||
mockDetail.value.mockMatchRule.body.raw = props.definitionDetail.body.rawBody.value || '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
function handleCancel() {
|
function handleCancel() {
|
||||||
mockDetail.value = makeDefaultParams();
|
mockDetail.value = makeDefaultParams();
|
||||||
isEdit.value = false;
|
isEdit.value = false;
|
||||||
visible.value = false;
|
visible.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleChangeEdit() {
|
||||||
|
appendDefaultMatchRuleItem();
|
||||||
|
isEdit.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
emit('delete');
|
emit('delete');
|
||||||
handleCancel();
|
handleCancel();
|
||||||
|
@ -540,7 +608,14 @@
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const { body } = mockDetail.value.mockMatchRule;
|
const { body } = mockDetail.value.mockMatchRule;
|
||||||
const validBodyMatchRules = filterKeyValParams(body.formDataMatch.matchRules, defaultMatchRuleItem).validParams;
|
const validFormDataBodyMatchRules = filterKeyValParams(
|
||||||
|
body.formDataBody.matchRules,
|
||||||
|
defaultMatchRuleItem
|
||||||
|
).validParams;
|
||||||
|
const validWwwFormBodyMatchRules = filterKeyValParams(
|
||||||
|
body.wwwFormBody.matchRules,
|
||||||
|
defaultMatchRuleItem
|
||||||
|
).validParams;
|
||||||
const validHeaderMatchRules = filterKeyValParams(
|
const validHeaderMatchRules = filterKeyValParams(
|
||||||
mockDetail.value.mockMatchRule.header.matchRules,
|
mockDetail.value.mockMatchRule.header.matchRules,
|
||||||
defaultMatchRuleItem
|
defaultMatchRuleItem
|
||||||
|
@ -557,17 +632,26 @@
|
||||||
mockDetail.value.response.headers,
|
mockDetail.value.response.headers,
|
||||||
defaultHeaderParamsItem
|
defaultHeaderParamsItem
|
||||||
).validParams;
|
).validParams;
|
||||||
const parseFileResult = parseRequestBodyFiles(mockDetail.value.mockMatchRule.body);
|
const parseFileResult = parseRequestBodyFiles(
|
||||||
const params = {
|
mockDetail.value.mockMatchRule.body,
|
||||||
|
[mockDetail.value.response as unknown as ResponseDefinition],
|
||||||
|
mockDetail.value.uploadFileIds,
|
||||||
|
mockDetail.value.linkFileIds
|
||||||
|
);
|
||||||
|
const params: MockParams = {
|
||||||
...mockDetail.value,
|
...mockDetail.value,
|
||||||
statusCode: mockDetail.value.response.statusCode,
|
statusCode: mockDetail.value.response.statusCode,
|
||||||
mockMatchRule: {
|
mockMatchRule: {
|
||||||
...mockDetail.value.mockMatchRule,
|
...mockDetail.value.mockMatchRule,
|
||||||
body: {
|
body: {
|
||||||
...mockDetail.value.mockMatchRule.body,
|
...mockDetail.value.mockMatchRule.body,
|
||||||
formDataMatch: {
|
formDataBody: {
|
||||||
...mockDetail.value.mockMatchRule.body.formDataMatch,
|
...mockDetail.value.mockMatchRule.body.formDataBody,
|
||||||
matchRules: validBodyMatchRules,
|
matchRules: validFormDataBodyMatchRules,
|
||||||
|
},
|
||||||
|
wwwFormBody: {
|
||||||
|
...mockDetail.value.mockMatchRule.body.wwwFormBody,
|
||||||
|
matchRules: validWwwFormBodyMatchRules,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
|
@ -587,14 +671,16 @@
|
||||||
...mockDetail.value.response,
|
...mockDetail.value.response,
|
||||||
headers: validResponseHeaders,
|
headers: validResponseHeaders,
|
||||||
},
|
},
|
||||||
...parseFileResult,
|
|
||||||
apiDefinitionId: props.definitionDetail.id,
|
apiDefinitionId: props.definitionDetail.id,
|
||||||
projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
|
uploadFileIds: parseFileResult.uploadFileIds,
|
||||||
|
linkFileIds: parseFileResult.linkFileIds,
|
||||||
};
|
};
|
||||||
if (isEdit.value) {
|
if (isEdit.value) {
|
||||||
await updateMock({
|
await updateMock({
|
||||||
id: mockDetail.value.id || '',
|
id: mockDetail.value.id || '',
|
||||||
...params,
|
...params,
|
||||||
|
...parseFileResult,
|
||||||
});
|
});
|
||||||
Message.success(t('common.updateSuccess'));
|
Message.success(t('common.updateSuccess'));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -154,6 +154,7 @@
|
||||||
import paramTable, { ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
|
import paramTable, { ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
|
||||||
import { ResponseItem } from '@/views/api-test/components/requestComposition/response/edit.vue';
|
import { ResponseItem } from '@/views/api-test/components/requestComposition/response/edit.vue';
|
||||||
|
|
||||||
|
import { uploadMockTempFile } from '@/api/modules/api-test/management';
|
||||||
import { responseHeaderOption } from '@/config/apiTest';
|
import { responseHeaderOption } from '@/config/apiTest';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
|
@ -164,7 +165,6 @@
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
definitionResponses: ResponseItem[];
|
definitionResponses: ResponseItem[];
|
||||||
uploadTempFileApi?: (...args: any) => Promise<any>; // 上传临时文件接口
|
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
@ -267,16 +267,17 @@
|
||||||
|
|
||||||
const fileList = ref<MsFileItem[]>([]);
|
const fileList = ref<MsFileItem[]>([]);
|
||||||
const loading = ref<boolean>(false);
|
const loading = ref<boolean>(false);
|
||||||
async function handleFileChange() {
|
|
||||||
|
async function handleFileChange(files: MsFileItem[], file?: MsFileItem) {
|
||||||
try {
|
try {
|
||||||
if (fileList.value[0] && fileList.value[0].local && fileList.value[0].file && props.uploadTempFileApi) {
|
if (file?.local && file.file) {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const res = await props.uploadTempFileApi(fileList.value[0].file);
|
const res = await uploadMockTempFile(file.file);
|
||||||
mockResponse.value.body.binaryBody.file = {
|
mockResponse.value.body.binaryBody.file = {
|
||||||
...fileList.value[0],
|
...file,
|
||||||
fileId: res.data,
|
fileId: res.data,
|
||||||
fileName: fileList.value[0]?.name || '',
|
fileName: file?.name || '',
|
||||||
fileAlias: fileList.value[0]?.name || '',
|
fileAlias: file?.name || '',
|
||||||
local: true,
|
local: true,
|
||||||
};
|
};
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
|
@ -298,6 +299,19 @@
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => mockResponse.value.body.binaryBody.file?.fileId,
|
||||||
|
() => {
|
||||||
|
fileList.value = mockResponse.value.body.binaryBody.file
|
||||||
|
? [mockResponse.value.body.binaryBody.file as unknown as MsFileItem]
|
||||||
|
: [];
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|
|
@ -46,10 +46,14 @@
|
||||||
v-model="record.enable"
|
v-model="record.enable"
|
||||||
size="small"
|
size="small"
|
||||||
type="line"
|
type="line"
|
||||||
@change="(value) => changeDefault(value, record)"
|
:before-change="() => handleBeforeEnableChange(record)"
|
||||||
></a-switch>
|
></a-switch>
|
||||||
</template>
|
</template>
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
|
<MsButton type="text" class="!mr-0" @click="editMock(record)">
|
||||||
|
{{ t('common.edit') }}
|
||||||
|
</MsButton>
|
||||||
|
<a-divider direction="vertical" :margin="8"></a-divider>
|
||||||
<MsButton type="text" class="!mr-0" @click="debugMock(record)">
|
<MsButton type="text" class="!mr-0" @click="debugMock(record)">
|
||||||
{{ t('apiTestManagement.debug') }}
|
{{ t('apiTestManagement.debug') }}
|
||||||
</MsButton>
|
</MsButton>
|
||||||
|
@ -70,12 +74,104 @@
|
||||||
</template>
|
</template>
|
||||||
</ms-base-table>
|
</ms-base-table>
|
||||||
</div>
|
</div>
|
||||||
|
<a-modal v-model:visible="showBatchModal" title-align="start" class="ms-modal-upload ms-modal-medium" :width="480">
|
||||||
|
<template #title>
|
||||||
|
{{ t('common.edit') }}
|
||||||
|
<div class="text-[var(--color-text-4)]">
|
||||||
|
{{
|
||||||
|
t('apiTestManagement.batchModalSubTitle', {
|
||||||
|
count: batchParams.currentSelectCount || tableSelected.length,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<a-form ref="batchFormRef" class="rounded-[4px]" :model="batchForm" layout="vertical">
|
||||||
|
<a-form-item
|
||||||
|
field="attr"
|
||||||
|
:label="t('apiTestManagement.chooseAttr')"
|
||||||
|
:rules="[{ required: true, message: t('apiTestManagement.attrRequired') }]"
|
||||||
|
asterisk-position="end"
|
||||||
|
>
|
||||||
|
<a-select v-model="batchForm.attr" :placeholder="t('common.pleaseSelect')">
|
||||||
|
<a-option v-for="item of attrOptions" :key="item.value" :value="item.value">
|
||||||
|
{{ t(item.name) }}
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
v-if="batchForm.attr === 'Tags'"
|
||||||
|
field="values"
|
||||||
|
:label="t('apiTestManagement.batchUpdate')"
|
||||||
|
:validate-trigger="['blur', 'input']"
|
||||||
|
:rules="[{ required: true, message: t('apiTestManagement.valueRequired') }]"
|
||||||
|
asterisk-position="end"
|
||||||
|
class="mb-0"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<MsTagsInput
|
||||||
|
v-model:model-value="batchForm.values"
|
||||||
|
placeholder="common.tagsInputPlaceholder"
|
||||||
|
allow-clear
|
||||||
|
unique-value
|
||||||
|
retain-input-value
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
v-else
|
||||||
|
field="value"
|
||||||
|
:label="t('apiTestManagement.batchUpdate')"
|
||||||
|
:rules="[{ required: true, message: t('apiTestManagement.valueRequired') }]"
|
||||||
|
asterisk-position="end"
|
||||||
|
class="mb-0"
|
||||||
|
>
|
||||||
|
<a-radio-group v-model:model-value="batchForm.value">
|
||||||
|
<a-radio :value="true">
|
||||||
|
{{ t('common.enable') }}
|
||||||
|
</a-radio>
|
||||||
|
<a-radio :value="false">
|
||||||
|
{{ t('common.disable') }}
|
||||||
|
</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex" :class="[batchForm.attr === 'Tags' ? 'justify-between' : 'justify-end']">
|
||||||
|
<div
|
||||||
|
v-if="batchForm.attr === 'Tags'"
|
||||||
|
class="flex flex-row items-center justify-center"
|
||||||
|
style="padding-top: 10px"
|
||||||
|
>
|
||||||
|
<a-switch v-model="batchForm.append" class="mr-1" size="small" type="line" />
|
||||||
|
<span class="flex items-center">
|
||||||
|
<span class="mr-1">{{ t('caseManagement.featureCase.appendTag') }}</span>
|
||||||
|
<span class="mt-[2px]">
|
||||||
|
<a-tooltip>
|
||||||
|
<IconQuestionCircle class="h-[16px] w-[16px] text-[rgb(var(--primary-5))]" />
|
||||||
|
<template #content>
|
||||||
|
<div>{{ t('caseManagement.featureCase.enableTags') }}</div>
|
||||||
|
<div>{{ t('caseManagement.featureCase.closeTags') }}</div>
|
||||||
|
</template>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<a-button type="secondary" :disabled="batchUpdateLoading" @click="cancelBatch">
|
||||||
|
{{ t('common.cancel') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button class="ml-3" type="primary" :loading="batchUpdateLoading" @click="batchUpdate">
|
||||||
|
{{ t('common.update') }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
<mockDetailDrawer
|
<mockDetailDrawer
|
||||||
v-if="mockDetailDrawerVisible"
|
|
||||||
v-model:visible="mockDetailDrawerVisible"
|
v-model:visible="mockDetailDrawerVisible"
|
||||||
:definition-detail="mockBelongDefinitionDetail"
|
:definition-detail="mockBelongDefinitionDetail"
|
||||||
:detail-id="activeMockRecord?.id"
|
:detail-id="activeMockRecord?.id"
|
||||||
:is-copy="isCopy"
|
:is-copy="isCopy"
|
||||||
|
:is-edit-mode="isEdit"
|
||||||
@add-done="loadMockList"
|
@add-done="loadMockList"
|
||||||
@delete="() => removeMock(activeMockRecord)"
|
@delete="() => removeMock(activeMockRecord)"
|
||||||
/>
|
/>
|
||||||
|
@ -83,7 +179,7 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useClipboard } from '@vueuse/core';
|
import { useClipboard } from '@vueuse/core';
|
||||||
import { Message } from '@arco-design/web-vue';
|
import { FormInstance, Message } from '@arco-design/web-vue';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
|
@ -92,13 +188,17 @@
|
||||||
import useTable from '@/components/pure/ms-table/useTable';
|
import useTable from '@/components/pure/ms-table/useTable';
|
||||||
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
|
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
|
||||||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||||
|
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
|
||||||
|
import mockDetailDrawer from './mockDetailDrawer.vue';
|
||||||
import { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
import { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
batchDeleteMock,
|
batchDeleteMock,
|
||||||
|
batchEditMock,
|
||||||
deleteMock,
|
deleteMock,
|
||||||
getDefinitionDetail,
|
getDefinitionDetail,
|
||||||
getDefinitionMockPage,
|
getDefinitionMockPage,
|
||||||
|
getMockDetail,
|
||||||
getMockUrl,
|
getMockUrl,
|
||||||
updateMockStatusPage,
|
updateMockStatusPage,
|
||||||
} from '@/api/modules/api-test/management';
|
} from '@/api/modules/api-test/management';
|
||||||
|
@ -109,12 +209,10 @@
|
||||||
import { hasAnyPermission } from '@/utils/permission';
|
import { hasAnyPermission } from '@/utils/permission';
|
||||||
|
|
||||||
import { ApiDefinitionMockDetail } from '@/models/apiTest/management';
|
import { ApiDefinitionMockDetail } from '@/models/apiTest/management';
|
||||||
import { OrdTemplateManagement } from '@/models/setting/template';
|
import { MockDetail } from '@/models/apiTest/mock';
|
||||||
import { RequestComposition } from '@/enums/apiEnum';
|
import { RequestComposition } from '@/enums/apiEnum';
|
||||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||||
|
|
||||||
const mockDetailDrawer = defineAsyncComponent(() => import('./mockDetailDrawer.vue'));
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
isApi?: boolean; // 接口定义详情的case tab下
|
isApi?: boolean; // 接口定义详情的case tab下
|
||||||
class?: string;
|
class?: string;
|
||||||
|
@ -127,6 +225,7 @@
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'init', params: any): void;
|
(e: 'init', params: any): void;
|
||||||
(e: 'change'): void;
|
(e: 'change'): void;
|
||||||
|
(e: 'debug', mock: MockDetail): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
@ -147,7 +246,7 @@
|
||||||
sorter: true,
|
sorter: true,
|
||||||
},
|
},
|
||||||
fixed: 'left',
|
fixed: 'left',
|
||||||
width: 100,
|
width: 120,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'mockManagement.name',
|
title: 'mockManagement.name',
|
||||||
|
@ -200,7 +299,7 @@
|
||||||
slotName: 'action',
|
slotName: 'action',
|
||||||
dataIndex: 'operation',
|
dataIndex: 'operation',
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
width: 150,
|
width: 200,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
|
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
|
||||||
|
@ -223,17 +322,11 @@
|
||||||
const batchActions = {
|
const batchActions = {
|
||||||
baseAction: [
|
baseAction: [
|
||||||
{
|
{
|
||||||
label: 'mockManagement.batchEnable',
|
label: 'mockManagement.batchEdit',
|
||||||
eventTag: 'batchEnable',
|
eventTag: 'edit',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'mockManagement.batchDisEnable',
|
label: 'mockManagement.batchDelete',
|
||||||
eventTag: 'batchDisEnable',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
moreAction: [
|
|
||||||
{
|
|
||||||
label: 'common.delete',
|
|
||||||
eventTag: 'delete',
|
eventTag: 'delete',
|
||||||
danger: true,
|
danger: true,
|
||||||
},
|
},
|
||||||
|
@ -294,16 +387,17 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const changeDefault = async (value: any, record: OrdTemplateManagement) => {
|
async function handleBeforeEnableChange(record: ApiDefinitionMockDetail) {
|
||||||
try {
|
try {
|
||||||
await updateMockStatusPage(record.id);
|
await updateMockStatusPage(record.id);
|
||||||
Message.success(t('system.orgTemplate.setSuccessfully'));
|
Message.success(record.enable ? t('common.disableSuccess') : t('common.enableSuccess'));
|
||||||
loadMockList();
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const tableSelected = ref<(string | number)[]>([]);
|
const tableSelected = ref<(string | number)[]>([]);
|
||||||
const batchParams = ref<BatchActionQueryParams>({
|
const batchParams = ref<BatchActionQueryParams>({
|
||||||
|
@ -317,10 +411,10 @@
|
||||||
* 删除接口
|
* 删除接口
|
||||||
*/
|
*/
|
||||||
function removeMock(record?: ApiDefinitionMockDetail, isBatch?: boolean, params?: BatchActionQueryParams) {
|
function removeMock(record?: ApiDefinitionMockDetail, isBatch?: boolean, params?: BatchActionQueryParams) {
|
||||||
let title = t('apiTestManagement.deleteApiTipTitle', { name: record?.name });
|
let title = t('apiTestManagement.deleteMockTip', { name: record?.name });
|
||||||
let selectIds = [record?.id || ''];
|
let selectIds = [record?.id || ''];
|
||||||
if (isBatch) {
|
if (isBatch) {
|
||||||
title = t('apiTestManagement.batchDeleteMockTip', {
|
title = t('mockManagement.batchDeleteMockTip', {
|
||||||
count: params?.currentSelectCount || tableSelected.value.length,
|
count: params?.currentSelectCount || tableSelected.value.length,
|
||||||
});
|
});
|
||||||
selectIds = tableSelected.value as string[];
|
selectIds = tableSelected.value as string[];
|
||||||
|
@ -392,7 +486,7 @@
|
||||||
function handleTableMoreActionSelect(item: ActionsItem, record: ApiDefinitionMockDetail) {
|
function handleTableMoreActionSelect(item: ActionsItem, record: ApiDefinitionMockDetail) {
|
||||||
switch (item.eventTag) {
|
switch (item.eventTag) {
|
||||||
case 'delete':
|
case 'delete':
|
||||||
deleteMock(record);
|
removeMock(record);
|
||||||
break;
|
break;
|
||||||
case 'copyMock':
|
case 'copyMock':
|
||||||
copyMockUrl(record);
|
copyMockUrl(record);
|
||||||
|
@ -409,6 +503,76 @@
|
||||||
tableSelected.value = arr;
|
tableSelected.value = arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const showBatchModal = ref(false);
|
||||||
|
const batchUpdateLoading = ref(false);
|
||||||
|
const batchFormRef = ref<FormInstance>();
|
||||||
|
const batchForm = ref({
|
||||||
|
attr: 'Status' as 'Status' | 'Tags',
|
||||||
|
value: true,
|
||||||
|
values: [],
|
||||||
|
append: false,
|
||||||
|
});
|
||||||
|
const fullAttrs = [
|
||||||
|
{
|
||||||
|
name: 'common.status',
|
||||||
|
value: 'Status',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'common.tag',
|
||||||
|
value: 'Tags',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const attrOptions = computed(() => {
|
||||||
|
if (props.protocol === 'HTTP') {
|
||||||
|
return fullAttrs;
|
||||||
|
}
|
||||||
|
return fullAttrs.filter((e) => e.value !== 'method');
|
||||||
|
});
|
||||||
|
|
||||||
|
function cancelBatch() {
|
||||||
|
showBatchModal.value = false;
|
||||||
|
batchFormRef.value?.resetFields();
|
||||||
|
batchForm.value = {
|
||||||
|
attr: 'Status',
|
||||||
|
value: true,
|
||||||
|
values: [],
|
||||||
|
append: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function batchUpdate() {
|
||||||
|
batchFormRef.value?.validate(async (errors) => {
|
||||||
|
if (!errors) {
|
||||||
|
try {
|
||||||
|
batchUpdateLoading.value = true;
|
||||||
|
await batchEditMock({
|
||||||
|
selectIds: batchParams.value?.selectedIds || [],
|
||||||
|
selectAll: !!batchParams.value?.selectAll,
|
||||||
|
excludeIds: batchParams.value?.excludeIds || [],
|
||||||
|
condition: {
|
||||||
|
keyword: keyword.value,
|
||||||
|
},
|
||||||
|
projectId: appStore.currentProjectId,
|
||||||
|
moduleIds: await getModuleIds(),
|
||||||
|
type: batchForm.value.attr,
|
||||||
|
append: batchForm.value.append,
|
||||||
|
tags: batchForm.value.attr === 'Tags' ? batchForm.value.values : [],
|
||||||
|
enable: batchForm.value.attr === 'Status' ? batchForm.value.value : false,
|
||||||
|
});
|
||||||
|
Message.success(t('common.updateSuccess'));
|
||||||
|
cancelBatch();
|
||||||
|
resetSelector();
|
||||||
|
loadMockList();
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
batchUpdateLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理表格选中后批量操作
|
* 处理表格选中后批量操作
|
||||||
* @param event 批量操作事件对象
|
* @param event 批量操作事件对象
|
||||||
|
@ -420,6 +584,9 @@
|
||||||
case 'delete':
|
case 'delete':
|
||||||
removeMock(undefined, true, params);
|
removeMock(undefined, true, params);
|
||||||
break;
|
break;
|
||||||
|
case 'edit':
|
||||||
|
showBatchModal.value = true;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -427,9 +594,13 @@
|
||||||
|
|
||||||
const mockDetailDrawerVisible = ref(false);
|
const mockDetailDrawerVisible = ref(false);
|
||||||
const activeMockRecord = ref<ApiDefinitionMockDetail>();
|
const activeMockRecord = ref<ApiDefinitionMockDetail>();
|
||||||
|
const isCopy = ref(false);
|
||||||
|
const isEdit = ref(false);
|
||||||
|
|
||||||
function createMock() {
|
function createMock() {
|
||||||
activeMockRecord.value = undefined;
|
activeMockRecord.value = undefined;
|
||||||
|
isCopy.value = false;
|
||||||
|
isEdit.value = false;
|
||||||
mockDetailDrawerVisible.value = true;
|
mockDetailDrawerVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -463,20 +634,37 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isCopy = ref(false);
|
|
||||||
|
|
||||||
function handleOpenDetail(record: ApiDefinitionMockDetail) {
|
function handleOpenDetail(record: ApiDefinitionMockDetail) {
|
||||||
|
isEdit.value = false;
|
||||||
isCopy.value = false;
|
isCopy.value = false;
|
||||||
openMockDetailDrawer(record);
|
openMockDetailDrawer(record);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCopyMock(record: ApiDefinitionMockDetail) {
|
function handleCopyMock(record: ApiDefinitionMockDetail) {
|
||||||
isCopy.value = true;
|
isCopy.value = true;
|
||||||
|
isEdit.value = false;
|
||||||
openMockDetailDrawer(record);
|
openMockDetailDrawer(record);
|
||||||
}
|
}
|
||||||
|
|
||||||
function debugMock(record: ApiDefinitionMockDetail) {
|
function editMock(record: ApiDefinitionMockDetail) {
|
||||||
activeMockRecord.value = record;
|
isEdit.value = true;
|
||||||
|
openMockDetailDrawer(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function debugMock(record: ApiDefinitionMockDetail) {
|
||||||
|
try {
|
||||||
|
appStore.showLoading();
|
||||||
|
const res = await getMockDetail({
|
||||||
|
id: record.id,
|
||||||
|
projectId: appStore.currentProjectId,
|
||||||
|
});
|
||||||
|
emit('debug', res);
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
appStore.hideLoading();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
|
|
|
@ -123,7 +123,8 @@ export default {
|
||||||
'apiTestManagement.collapseApi': 'Hide all requests',
|
'apiTestManagement.collapseApi': 'Hide all requests',
|
||||||
'apiTestManagement.paramName': 'Parameter name',
|
'apiTestManagement.paramName': 'Parameter name',
|
||||||
'apiTestManagement.paramVal': 'Parameter value',
|
'apiTestManagement.paramVal': 'Parameter value',
|
||||||
'apiTestManagement.deleteMockTip': 'After deletion, it cannot be restored. Are you sure you want to delete it?',
|
'apiTestManagement.deleteMockTip':
|
||||||
|
'Deleting a mock expectation will cause the test task using the expectation to fail, so please operate with caution!',
|
||||||
'apiTestManagement.preview': 'Preview',
|
'apiTestManagement.preview': 'Preview',
|
||||||
'apiTestManagement.shareUrlCopied': 'Sharing link copied to clipboard',
|
'apiTestManagement.shareUrlCopied': 'Sharing link copied to clipboard',
|
||||||
'apiTestManagement.detail': 'Detail',
|
'apiTestManagement.detail': 'Detail',
|
||||||
|
|
|
@ -118,7 +118,7 @@ export default {
|
||||||
'apiTestManagement.collapseApi': '隐藏全部请求',
|
'apiTestManagement.collapseApi': '隐藏全部请求',
|
||||||
'apiTestManagement.paramName': '参数名',
|
'apiTestManagement.paramName': '参数名',
|
||||||
'apiTestManagement.paramVal': '参数值',
|
'apiTestManagement.paramVal': '参数值',
|
||||||
'apiTestManagement.deleteMockTip': '删除后不可恢复,确认删除吗?',
|
'apiTestManagement.deleteMockTip': '删除 Mock 期望会导致使用了该期望的测试任务执行失败,请谨慎操作!',
|
||||||
'apiTestManagement.preview': '预览',
|
'apiTestManagement.preview': '预览',
|
||||||
'apiTestManagement.shareUrlCopied': '分享链接已复制到剪贴板',
|
'apiTestManagement.shareUrlCopied': '分享链接已复制到剪贴板',
|
||||||
'apiTestManagement.detail': '详情',
|
'apiTestManagement.detail': '详情',
|
||||||
|
@ -213,7 +213,7 @@ export default {
|
||||||
'mockManagement.copyMock': '复制Mock地址',
|
'mockManagement.copyMock': '复制Mock地址',
|
||||||
'mockManagement.batchEnable': '批量启用',
|
'mockManagement.batchEnable': '批量启用',
|
||||||
'mockManagement.batchDisEnable': '批量禁用',
|
'mockManagement.batchDisEnable': '批量禁用',
|
||||||
'mockManagement.batchDeleteMockTip': '确认删除已选中的 {count} 个Mock吗?',
|
'mockManagement.batchDeleteMockTip': '确认删除已选中的 {count} 个 Mock 吗?',
|
||||||
'mockManagement.allMock': '全部 MOCK',
|
'mockManagement.allMock': '全部 MOCK',
|
||||||
'mockManagement.createMock': '创建 MOCK',
|
'mockManagement.createMock': '创建 MOCK',
|
||||||
'mockManagement.updateMock': '更新 MOCK',
|
'mockManagement.updateMock': '更新 MOCK',
|
||||||
|
@ -235,4 +235,6 @@ export default {
|
||||||
'mockManagement.empty': '为空',
|
'mockManagement.empty': '为空',
|
||||||
'mockManagement.notEmpty': '非空',
|
'mockManagement.notEmpty': '非空',
|
||||||
'mockManagement.regular': '正则匹配',
|
'mockManagement.regular': '正则匹配',
|
||||||
|
'mockManagement.batchEdit': '批量编辑',
|
||||||
|
'mockManagement.batchDelete': '批量删除',
|
||||||
};
|
};
|
||||||
|
|
|
@ -253,7 +253,7 @@
|
||||||
class="overflow-hidden"
|
class="overflow-hidden"
|
||||||
is-preview
|
is-preview
|
||||||
>
|
>
|
||||||
<div class="absolute w-full bg-white" style="height: calc(100% - 28px)"></div>
|
<div class="w-full bg-white" style="height: calc(100% - 28px)"></div>
|
||||||
</defaultLayout>
|
</defaultLayout>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,18 +9,15 @@
|
||||||
@close="handleClose"
|
@close="handleClose"
|
||||||
>
|
>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<div class="w-full">
|
<MsCodeEditor
|
||||||
<MsCodeEditor
|
v-model:model-value="pluginScript"
|
||||||
v-model:model-value="pluginScript"
|
title="JSON"
|
||||||
title="JSON"
|
height="calc(100vh - 155px)"
|
||||||
width="100%"
|
theme="MS-text"
|
||||||
height="calc(100vh - 155px)"
|
:read-only="props.readOnly"
|
||||||
theme="MS-text"
|
:show-theme-change="false"
|
||||||
:read-only="props.readOnly"
|
:show-title-line="true"
|
||||||
:show-theme-change="false"
|
/>
|
||||||
:show-title-line="true"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</MsDrawer>
|
</MsDrawer>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -71,7 +71,7 @@ export default {
|
||||||
'system.resourcePool.batchAddTipConfirm': 'Got it',
|
'system.resourcePool.batchAddTipConfirm': 'Got it',
|
||||||
'system.resourcePool.batchAddResource': 'Batch add resources',
|
'system.resourcePool.batchAddResource': 'Batch add resources',
|
||||||
'system.resourcePool.changeAddTypeTip':
|
'system.resourcePool.changeAddTypeTip':
|
||||||
'After switching, the content of the added resources will continue to appear in yaml; the added resources can be modified in batches',
|
'After switching, the content of the added resources will continue to appear in csv; the added resources can be modified in batches',
|
||||||
'system.resourcePool.changeAddTypePopTitle': 'Toggle add resource type?',
|
'system.resourcePool.changeAddTypePopTitle': 'Toggle add resource type?',
|
||||||
'system.resourcePool.ip': 'IP',
|
'system.resourcePool.ip': 'IP',
|
||||||
'system.resourcePool.ipRequired': 'Please enter an IP address',
|
'system.resourcePool.ipRequired': 'Please enter an IP address',
|
||||||
|
|
|
@ -68,7 +68,7 @@ export default {
|
||||||
'system.resourcePool.batchAdd': '批量添加',
|
'system.resourcePool.batchAdd': '批量添加',
|
||||||
'system.resourcePool.batchAddTipConfirm': '知道了',
|
'system.resourcePool.batchAddTipConfirm': '知道了',
|
||||||
'system.resourcePool.batchAddResource': '批量添加资源',
|
'system.resourcePool.batchAddResource': '批量添加资源',
|
||||||
'system.resourcePool.changeAddTypeTip': '切换后,已添加资源内容将继续显示在 yaml 内;可批量修改已添加资源',
|
'system.resourcePool.changeAddTypeTip': '切换后,已添加资源内容将继续显示在 csv 内;可批量修改已添加资源',
|
||||||
'system.resourcePool.changeAddTypePopTitle': '切换添加资源类型?',
|
'system.resourcePool.changeAddTypePopTitle': '切换添加资源类型?',
|
||||||
'system.resourcePool.allUseTip': '如果配置多个测试类型,会存在抢占资源的情况,建议一种测试类型配置一个资源池',
|
'system.resourcePool.allUseTip': '如果配置多个测试类型,会存在抢占资源的情况,建议一种测试类型配置一个资源池',
|
||||||
'system.resourcePool.ip': 'IP',
|
'system.resourcePool.ip': 'IP',
|
||||||
|
|
Loading…
Reference in New Issue