feat(接口测试): mock 页面部分接口联调&部分页面调整
This commit is contained in:
parent
e4c1a9d9d7
commit
bdc1843176
|
@ -3,12 +3,15 @@ import {
|
||||||
AddCaseUrl,
|
AddCaseUrl,
|
||||||
AddDefinitionScheduleUrl,
|
AddDefinitionScheduleUrl,
|
||||||
AddDefinitionUrl,
|
AddDefinitionUrl,
|
||||||
|
AddMockUrl,
|
||||||
AddModuleUrl,
|
AddModuleUrl,
|
||||||
BatchCleanOutApiUrl,
|
BatchCleanOutApiUrl,
|
||||||
BatchDeleteCaseUrl,
|
BatchDeleteCaseUrl,
|
||||||
BatchDeleteDefinitionUrl,
|
BatchDeleteDefinitionUrl,
|
||||||
|
BatchDeleteMockUrl,
|
||||||
BatchDeleteRecycleCaseUrl,
|
BatchDeleteRecycleCaseUrl,
|
||||||
BatchEditCaseUrl,
|
BatchEditCaseUrl,
|
||||||
|
BatchEditMockUrl,
|
||||||
BatchExecuteCaseUrl,
|
BatchExecuteCaseUrl,
|
||||||
BatchMoveDefinitionUrl,
|
BatchMoveDefinitionUrl,
|
||||||
BatchRecoverApiUrl,
|
BatchRecoverApiUrl,
|
||||||
|
@ -16,6 +19,7 @@ import {
|
||||||
BatchUpdateDefinitionUrl,
|
BatchUpdateDefinitionUrl,
|
||||||
CasePageUrl,
|
CasePageUrl,
|
||||||
CheckDefinitionScheduleUrl,
|
CheckDefinitionScheduleUrl,
|
||||||
|
CopyMockUrl,
|
||||||
DebugCaseUrl,
|
DebugCaseUrl,
|
||||||
DebugDefinitionUrl,
|
DebugDefinitionUrl,
|
||||||
DefinitionMockPageUrl,
|
DefinitionMockPageUrl,
|
||||||
|
@ -39,6 +43,7 @@ import {
|
||||||
GetEnvListUrl,
|
GetEnvListUrl,
|
||||||
GetEnvModuleUrl,
|
GetEnvModuleUrl,
|
||||||
GetExecuteHistoryUrl,
|
GetExecuteHistoryUrl,
|
||||||
|
GetMockUrlUrl,
|
||||||
GetModuleCountUrl,
|
GetModuleCountUrl,
|
||||||
GetModuleOnlyTreeUrl,
|
GetModuleOnlyTreeUrl,
|
||||||
GetModuleTreeUrl,
|
GetModuleTreeUrl,
|
||||||
|
@ -46,6 +51,7 @@ import {
|
||||||
GetTrashModuleCountUrl,
|
GetTrashModuleCountUrl,
|
||||||
GetTrashModuleTreeUrl,
|
GetTrashModuleTreeUrl,
|
||||||
ImportDefinitionUrl,
|
ImportDefinitionUrl,
|
||||||
|
MockDetailUrl,
|
||||||
MoveModuleUrl,
|
MoveModuleUrl,
|
||||||
OperationHistoryUrl,
|
OperationHistoryUrl,
|
||||||
PoolOption,
|
PoolOption,
|
||||||
|
@ -64,15 +70,19 @@ import {
|
||||||
TransferFileModuleOptionCaseUrl,
|
TransferFileModuleOptionCaseUrl,
|
||||||
TransferFileModuleOptionUrl,
|
TransferFileModuleOptionUrl,
|
||||||
TransferFileUrl,
|
TransferFileUrl,
|
||||||
|
TransferMockFileModuleOptionUrl,
|
||||||
|
TransferMockFileUrl,
|
||||||
UpdateCasePriorityUrl,
|
UpdateCasePriorityUrl,
|
||||||
UpdateCaseStatusUrl,
|
UpdateCaseStatusUrl,
|
||||||
UpdateCaseUrl,
|
UpdateCaseUrl,
|
||||||
UpdateDefinitionScheduleUrl,
|
UpdateDefinitionScheduleUrl,
|
||||||
UpdateDefinitionUrl,
|
UpdateDefinitionUrl,
|
||||||
UpdateMockStatusUrl,
|
UpdateMockStatusUrl,
|
||||||
|
UpdateMockUrl,
|
||||||
UpdateModuleUrl,
|
UpdateModuleUrl,
|
||||||
UploadTempFileCaseUrl,
|
UploadTempFileCaseUrl,
|
||||||
UploadTempFileUrl,
|
UploadTempFileUrl,
|
||||||
|
UploadTempMockFileUrl,
|
||||||
} from '@/api/requrls/api-test/management';
|
} from '@/api/requrls/api-test/management';
|
||||||
|
|
||||||
import { ApiCaseReportDetail, ExecuteRequestParams } from '@/models/apiTest/common';
|
import { ApiCaseReportDetail, ExecuteRequestParams } from '@/models/apiTest/common';
|
||||||
|
@ -113,8 +123,10 @@ import {
|
||||||
RecoverDefinitionParams,
|
RecoverDefinitionParams,
|
||||||
UpdateScheduleParams,
|
UpdateScheduleParams,
|
||||||
} from '@/models/apiTest/management';
|
} from '@/models/apiTest/management';
|
||||||
|
import type { BatchEditMockParams, MockDetail, MockParams, UpdateMockParams } from '@/models/apiTest/mock';
|
||||||
import {
|
import {
|
||||||
AddModuleParams,
|
AddModuleParams,
|
||||||
|
type BatchApiParams,
|
||||||
CommonList,
|
CommonList,
|
||||||
DragSortParams,
|
DragSortParams,
|
||||||
ModuleTreeNode,
|
ModuleTreeNode,
|
||||||
|
@ -302,10 +314,59 @@ export function updateMockStatusPage(id: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 刪除mock接口
|
// 刪除mock接口
|
||||||
export function deleteDefinitionMockMock(data: mockParams) {
|
export function deleteMock(data: mockParams) {
|
||||||
return MSR.post({ url: DeleteMockUrl, data });
|
return MSR.post({ url: DeleteMockUrl, data });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 上传文件
|
||||||
|
export function uploadMockTempFile(file: File) {
|
||||||
|
return MSR.uploadFile({ url: UploadTempMockFileUrl }, { fileList: [file] }, 'file');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件转存
|
||||||
|
export function transferMockFile(data: TransferFileParams) {
|
||||||
|
return MSR.post({ url: TransferMockFileUrl, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件转存目录
|
||||||
|
export function getMockTransferOptions(projectId: string) {
|
||||||
|
return MSR.get<ModuleTreeNode[]>({ url: TransferMockFileModuleOptionUrl, params: projectId });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新 mock
|
||||||
|
export function updateMock(data: UpdateMockParams) {
|
||||||
|
return MSR.post({ url: UpdateMockUrl, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 mock 详情
|
||||||
|
export function getMockDetail(data: { id: string; projectId: string }) {
|
||||||
|
return MSR.post<MockDetail>({ url: MockDetailUrl, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制 mock
|
||||||
|
export function copyMock(data: { id: string; projectId: string }) {
|
||||||
|
return MSR.post({ url: CopyMockUrl, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量编辑 mock
|
||||||
|
export function batchEditMock(data: BatchEditMockParams) {
|
||||||
|
return MSR.post({ url: BatchEditMockUrl, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除 mock
|
||||||
|
export function batchDeleteMock(data: BatchApiParams) {
|
||||||
|
return MSR.post({ url: BatchDeleteMockUrl, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加 mock
|
||||||
|
export function addMock(data: MockParams) {
|
||||||
|
return MSR.post({ url: AddMockUrl, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 mock url
|
||||||
|
export function getMockUrl(id: string) {
|
||||||
|
return MSR.get({ url: GetMockUrlUrl, params: id });
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 回收站
|
* 回收站
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -42,6 +42,16 @@ export const DefinitionReferenceUrl = '/api/definition/get-reference'; // 获取
|
||||||
export const DefinitionMockPageUrl = '/api/definition/mock/page'; // mock列表
|
export const DefinitionMockPageUrl = '/api/definition/mock/page'; // mock列表
|
||||||
export const UpdateMockStatusUrl = '/api/definition/mock/enable'; // 更新mock状态
|
export const UpdateMockStatusUrl = '/api/definition/mock/enable'; // 更新mock状态
|
||||||
export const DeleteMockUrl = '/api/definition/mock/delete'; // 刪除mock
|
export const DeleteMockUrl = '/api/definition/mock/delete'; // 刪除mock
|
||||||
|
export const UploadTempMockFileUrl = '/api/definition/mock/upload/temp/file'; // mock临时上传文件
|
||||||
|
export const TransferMockFileUrl = '/api/definition/mock/transfer'; // mock临时文件转存
|
||||||
|
export const TransferMockFileModuleOptionUrl = '/api/definition/mock/transfer/options'; // mock临时文件转存目录下拉框
|
||||||
|
export const UpdateMockUrl = '/api/definition/mock/update'; // mock更新
|
||||||
|
export const MockDetailUrl = '/api/definition/mock/detail'; // mock详情
|
||||||
|
export const CopyMockUrl = '/api/definition/mock/copy'; // 复制mock
|
||||||
|
export const BatchEditMockUrl = '/api/definition/mock/batch/edit'; // 批量编辑mock
|
||||||
|
export const BatchDeleteMockUrl = '/api/definition/mock/batch/delete'; // 批量删除mock
|
||||||
|
export const AddMockUrl = '/api/definition/mock/add'; // 添加mock
|
||||||
|
export const GetMockUrlUrl = '/api/definition/mock/get-url'; // 获取mock url
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* api回收站
|
* api回收站
|
||||||
|
|
|
@ -274,7 +274,7 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: 1px solid var(--color-text-input-border);
|
border: 1px solid var(--color-text-input-border);
|
||||||
background-color: var(--color-text-fff);
|
background-color: var(--color-text-fff);
|
||||||
&:not(:disabled):hover {
|
&:not(:disabled, .arco-input-tag-disabled, .arco-input-disabled, .arco-select-view-disabled):hover {
|
||||||
border-color: rgb(var(--primary-5)) !important;
|
border-color: rgb(var(--primary-5)) !important;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
@ -282,6 +282,7 @@
|
||||||
color: var(--color-text-brand);
|
color: var(--color-text-brand);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.arco-input-tag-disabled,
|
||||||
.arco-select-view-disabled,
|
.arco-select-view-disabled,
|
||||||
.arco-input-disabled {
|
.arco-input-disabled {
|
||||||
border-color: var(--color-text-n8) !important;
|
border-color: var(--color-text-n8) !important;
|
||||||
|
|
|
@ -73,7 +73,7 @@
|
||||||
arrow-class="hidden"
|
arrow-class="hidden"
|
||||||
:popup-offset="0"
|
:popup-offset="0"
|
||||||
>
|
>
|
||||||
<div class="!w-[calc(100%-28px)]">
|
<div class="h-full flex-1">
|
||||||
<MsTagsInput
|
<MsTagsInput
|
||||||
v-model:model-value="inputFiles"
|
v-model:model-value="inputFiles"
|
||||||
:input-class="props.inputClass"
|
:input-class="props.inputClass"
|
||||||
|
|
|
@ -21,15 +21,17 @@
|
||||||
</template>
|
</template>
|
||||||
<template #title="_props">
|
<template #title="_props">
|
||||||
<div class="flex w-full items-center gap-[4px] overflow-hidden">
|
<div class="flex w-full items-center gap-[4px] overflow-hidden">
|
||||||
<div
|
<template v-if="!props.hideSwitcher">
|
||||||
v-if="_props.children && _props.children.length > 0"
|
<div
|
||||||
class="cursor-pointer"
|
v-if="_props.children && _props.children.length > 0"
|
||||||
@click.stop="handleExpand(_props)"
|
class="cursor-pointer"
|
||||||
>
|
@click.stop="handleExpand(_props)"
|
||||||
<icon-caret-down v-if="_props.expanded" class="text-[var(--color-text-4)]" />
|
>
|
||||||
<icon-caret-right v-else class="text-[var(--color-text-4)]" />
|
<icon-caret-down v-if="_props.expanded" class="text-[var(--color-text-4)]" />
|
||||||
</div>
|
<icon-caret-right v-else class="text-[var(--color-text-4)]" />
|
||||||
<div v-else class="h-full w-[16px]"></div>
|
</div>
|
||||||
|
<div v-else class="h-full w-[16px]"></div>
|
||||||
|
</template>
|
||||||
<a-tooltip
|
<a-tooltip
|
||||||
v-if="$slots['title']"
|
v-if="$slots['title']"
|
||||||
:content="_props[props.fieldNames.title]"
|
:content="_props[props.fieldNames.title]"
|
||||||
|
@ -121,6 +123,7 @@
|
||||||
disabledTitleTooltip?: boolean; // 是否禁用标题 tooltip
|
disabledTitleTooltip?: boolean; // 是否禁用标题 tooltip
|
||||||
actionOnNodeClick?: 'expand'; // 点击节点时的操作
|
actionOnNodeClick?: 'expand'; // 点击节点时的操作
|
||||||
nodeHighlightClass?: string; // 节点高亮背景色
|
nodeHighlightClass?: string; // 节点高亮背景色
|
||||||
|
hideSwitcher?: boolean; // 隐藏展开折叠图标
|
||||||
titleTooltipPosition?:
|
titleTooltipPosition?:
|
||||||
| 'top'
|
| 'top'
|
||||||
| 'tl'
|
| 'tl'
|
||||||
|
|
|
@ -147,6 +147,7 @@
|
||||||
drawerStyle?: Record<string, string>; // 抽屉样式
|
drawerStyle?: Record<string, string>; // 抽屉样式
|
||||||
showFullScreen?: boolean; // 是否显示全屏按钮
|
showFullScreen?: boolean; // 是否显示全屏按钮
|
||||||
maskClosable?: boolean; // 点击遮罩是否关闭
|
maskClosable?: boolean; // 点击遮罩是否关闭
|
||||||
|
unmountOnClose?: boolean; // 关闭时销毁组件
|
||||||
handleBeforeCancel?: () => boolean;
|
handleBeforeCancel?: () => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,6 +161,7 @@
|
||||||
disabledWidthDrag: false,
|
disabledWidthDrag: false,
|
||||||
showFullScreen: false,
|
showFullScreen: false,
|
||||||
maskClosable: true,
|
maskClosable: true,
|
||||||
|
unmountOnClose: false,
|
||||||
okPermission: () => [], // 确认按钮权限
|
okPermission: () => [], // 确认按钮权限
|
||||||
});
|
});
|
||||||
const emit = defineEmits(['update:visible', 'confirm', 'cancel', 'continue', 'close']);
|
const emit = defineEmits(['update:visible', 'confirm', 'cancel', 'continue', 'close']);
|
||||||
|
|
|
@ -1,13 +1,26 @@
|
||||||
import type { RequestBodyFormat } from '@/enums/apiEnum';
|
import type { MsFileItem } from '@/components/pure/ms-upload/types';
|
||||||
|
|
||||||
|
import type { RequestBodyFormat, RequestParamsType } from '@/enums/apiEnum';
|
||||||
|
|
||||||
|
import type { BatchApiParams } from '../common';
|
||||||
import type { ExecuteBinaryBody, KeyValueParam, ResponseDefinitionBody } from './common';
|
import type { ExecuteBinaryBody, KeyValueParam, ResponseDefinitionBody } from './common';
|
||||||
|
|
||||||
// mock 信息-匹配项
|
// mock 信息-匹配项
|
||||||
export interface MatchRuleItem {
|
export interface MatchRuleItem {
|
||||||
|
id?: string; // 用于前端标识
|
||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
condition: string;
|
condition: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
paramType: RequestParamsType;
|
||||||
|
files: ({
|
||||||
|
fileId: string;
|
||||||
|
fileName: string;
|
||||||
|
local: boolean; // 是否是本地上传的文件
|
||||||
|
fileAlias: string; // 文件别名
|
||||||
|
delete: boolean; // 是否删除
|
||||||
|
[key: string]: any; // 用于前端渲染时填充的自定义信息,后台无此字段
|
||||||
|
} & MsFileItem)[];
|
||||||
}
|
}
|
||||||
// mock 信息-响应内容
|
// mock 信息-响应内容
|
||||||
export interface MockResponse {
|
export interface MockResponse {
|
||||||
|
@ -45,9 +58,28 @@ export interface MockParams {
|
||||||
tags: string[];
|
tags: string[];
|
||||||
mockMatchRule: MockMatchRule;
|
mockMatchRule: MockMatchRule;
|
||||||
response: MockResponse;
|
response: MockResponse;
|
||||||
apiDefinitionId: string;
|
apiDefinitionId: string | number;
|
||||||
uploadFileIds: string[];
|
uploadFileIds: string[];
|
||||||
linkFileIds: string[];
|
linkFileIds: string[];
|
||||||
// 前端扩展字段
|
// 前端扩展字段
|
||||||
unSaved?: boolean;
|
unSaved?: boolean;
|
||||||
|
isNew: boolean;
|
||||||
|
}
|
||||||
|
// mock 信息-更新
|
||||||
|
export interface UpdateMockParams extends MockParams {
|
||||||
|
id: string;
|
||||||
|
deleteFileIds: string[];
|
||||||
|
unLinkFileIds: string[];
|
||||||
|
}
|
||||||
|
// mock 信息-详情
|
||||||
|
export interface MockDetail extends MockParams {
|
||||||
|
id: string;
|
||||||
|
matching: MockMatchRule;
|
||||||
|
}
|
||||||
|
// 批量编辑 mock
|
||||||
|
export interface BatchEditMockParams extends BatchApiParams {
|
||||||
|
type: 'Status' | 'Tags'; // 编辑类型
|
||||||
|
tags: string[]; // 标签
|
||||||
|
append: boolean; // 是否追加
|
||||||
|
enable: boolean; // 是否启用
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,8 @@ export interface BatchApiParams {
|
||||||
currentSelectCount?: number; // 当前已选择的数量
|
currentSelectCount?: number; // 当前已选择的数量
|
||||||
projectId?: string; // 项目 ID
|
projectId?: string; // 项目 ID
|
||||||
moduleIds?: (string | number)[]; // 模块 ID 集合
|
moduleIds?: (string | number)[]; // 模块 ID 集合
|
||||||
|
versionId?: string; // 版本 ID
|
||||||
|
refId?: string; // 版本来源
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移动模块树
|
// 移动模块树
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
export interface NodesListItem {
|
export interface NodesListItem {
|
||||||
ip: string;
|
ip: string;
|
||||||
port: string;
|
port: string;
|
||||||
monitor: string;
|
|
||||||
concurrentNumber: number;
|
concurrentNumber: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
import { EQUAL } from '@/components/pure/ms-advance-filter';
|
import { EQUAL } from '@/components/pure/ms-advance-filter';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -237,57 +239,39 @@ export const regexDefaultParamItem = {
|
||||||
responseFormat: ResponseBodyXPathAssertionFormat.XML,
|
responseFormat: ResponseBodyXPathAssertionFormat.XML,
|
||||||
moreSettingPopoverVisible: false,
|
moreSettingPopoverVisible: false,
|
||||||
};
|
};
|
||||||
|
// mock 匹配规则默认项
|
||||||
|
export const defaultMatchRuleItem = {
|
||||||
|
key: '',
|
||||||
|
value: '',
|
||||||
|
condition: 'EQUALS',
|
||||||
|
description: '',
|
||||||
|
paramType: RequestParamsType.STRING,
|
||||||
|
files: [],
|
||||||
|
};
|
||||||
// mock 默认参数
|
// mock 默认参数
|
||||||
export const mockDefaultParams: MockParams = {
|
export const mockDefaultParams: MockParams = {
|
||||||
|
isNew: true,
|
||||||
projectId: '',
|
projectId: '',
|
||||||
name: '',
|
name: '',
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
tags: [],
|
tags: [],
|
||||||
mockMatchRule: {
|
mockMatchRule: {
|
||||||
header: {
|
header: {
|
||||||
matchRules: [
|
matchRules: [],
|
||||||
{
|
|
||||||
key: '',
|
|
||||||
value: '',
|
|
||||||
condition: 'EQUALS',
|
|
||||||
description: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
matchAll: true,
|
matchAll: true,
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
matchRules: [
|
matchRules: [],
|
||||||
{
|
|
||||||
key: '',
|
|
||||||
value: '',
|
|
||||||
condition: 'EQUALS',
|
|
||||||
description: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
matchAll: true,
|
matchAll: true,
|
||||||
},
|
},
|
||||||
rest: {
|
rest: {
|
||||||
matchRules: [
|
matchRules: [],
|
||||||
{
|
|
||||||
key: '',
|
|
||||||
value: '',
|
|
||||||
condition: 'EQUALS',
|
|
||||||
description: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
matchAll: true,
|
matchAll: true,
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
paramType: RequestBodyFormat.FORM_DATA,
|
paramType: RequestBodyFormat.FORM_DATA,
|
||||||
formDataMatch: {
|
formDataMatch: {
|
||||||
matchRules: [
|
matchRules: [],
|
||||||
{
|
|
||||||
key: '',
|
|
||||||
value: '',
|
|
||||||
condition: 'EQUALS',
|
|
||||||
description: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
matchAll: true,
|
matchAll: true,
|
||||||
},
|
},
|
||||||
binaryBody: {
|
binaryBody: {
|
||||||
|
@ -300,13 +284,7 @@ export const mockDefaultParams: MockParams = {
|
||||||
},
|
},
|
||||||
response: {
|
response: {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
headers: [
|
headers: [],
|
||||||
{
|
|
||||||
key: '',
|
|
||||||
value: '',
|
|
||||||
description: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
useApiResponse: false,
|
useApiResponse: false,
|
||||||
apiResponseId: '',
|
apiResponseId: '',
|
||||||
body: {
|
body: {
|
||||||
|
@ -333,3 +311,65 @@ export const mockDefaultParams: MockParams = {
|
||||||
uploadFileIds: [],
|
uploadFileIds: [],
|
||||||
linkFileIds: [],
|
linkFileIds: [],
|
||||||
};
|
};
|
||||||
|
export const makeDefaultParams = () => {
|
||||||
|
const defaultParams = cloneDeep(mockDefaultParams);
|
||||||
|
defaultParams.id = Date.now().toString();
|
||||||
|
defaultParams.mockMatchRule.body.formDataMatch.matchRules.push({
|
||||||
|
...cloneDeep(defaultMatchRuleItem),
|
||||||
|
id: Date.now().toString(),
|
||||||
|
});
|
||||||
|
defaultParams.mockMatchRule.header.matchRules.push({ ...cloneDeep(defaultMatchRuleItem), id: Date.now().toString() });
|
||||||
|
defaultParams.mockMatchRule.query.matchRules.push({ ...cloneDeep(defaultMatchRuleItem), id: Date.now().toString() });
|
||||||
|
defaultParams.mockMatchRule.rest.matchRules.push({ ...cloneDeep(defaultMatchRuleItem), id: Date.now().toString() });
|
||||||
|
defaultParams.response.headers.push({ ...cloneDeep(defaultMatchRuleItem), id: Date.now().toString() });
|
||||||
|
return defaultParams;
|
||||||
|
};
|
||||||
|
// mock 匹配规则选项
|
||||||
|
export const matchRuleOptions = [
|
||||||
|
{
|
||||||
|
label: 'mockManagement.equals',
|
||||||
|
value: 'EQUALS',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'mockManagement.notEquals',
|
||||||
|
value: 'NOT_EQUALS',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'mockManagement.lengthEquals',
|
||||||
|
value: 'LENGTH_EQUALS',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'mockManagement.lengthNotEquals',
|
||||||
|
value: 'LENGTH_NOT_EQUALS',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'mockManagement.lengthLarge',
|
||||||
|
value: 'LENGTH_LARGE',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'mockManagement.lengthLess',
|
||||||
|
value: 'LENGTH_SHOT',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'mockManagement.contain',
|
||||||
|
value: 'CONTAINS',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'mockManagement.notContain',
|
||||||
|
value: 'NOT_CONTAINS',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'mockManagement.empty',
|
||||||
|
value: 'IS_EMPTY',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'mockManagement.notEmpty',
|
||||||
|
value: 'IS_NOT_EMPTY',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'mockManagement.regular',
|
||||||
|
value: 'REGULAR_MATCH',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
// mock 参数为文件类型的匹配规则选项
|
||||||
|
export const mockFileMatchRules = ['EQUALS', 'NOT_EQUALS', 'IS_EMPTY', 'IS_NOT_EMPTY'];
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
type ExecuteConditionConfig,
|
type ExecuteConditionConfig,
|
||||||
type ResponseDefinition,
|
type ResponseDefinition,
|
||||||
} from '@/models/apiTest/common';
|
} from '@/models/apiTest/common';
|
||||||
|
import type { MockBody } from '@/models/apiTest/mock';
|
||||||
import { RequestConditionProcessor, RequestParamsType } from '@/enums/apiEnum';
|
import { RequestConditionProcessor, RequestParamsType } from '@/enums/apiEnum';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -28,22 +29,24 @@ export interface ParseResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析接口请求 body 内的文件列表
|
* 解析接口请求/Mock body 内的文件列表
|
||||||
* @param body body 参数对象
|
* @param body body 参数对象
|
||||||
*/
|
*/
|
||||||
export function parseRequestBodyFiles(
|
export function parseRequestBodyFiles(
|
||||||
body: ExecuteBody,
|
body: ExecuteBody | MockBody,
|
||||||
response?: ResponseDefinition[],
|
response?: ResponseDefinition[],
|
||||||
saveUploadFileIds?: string[],
|
saveUploadFileIds?: string[],
|
||||||
saveLinkFileIds?: string[]
|
saveLinkFileIds?: string[]
|
||||||
): ParseResult {
|
): ParseResult {
|
||||||
const { formDataBody, binaryBody } = body;
|
const { binaryBody } = body;
|
||||||
const uploadFileIds = new Set<string>(); // 存储本地上传的文件 id 集合
|
const uploadFileIds = new Set<string>(); // 存储本地上传的文件 id 集合
|
||||||
const linkFileIds = new Set<string>(); // 存储关联文件 id 集合
|
const linkFileIds = new Set<string>(); // 存储关联文件 id 集合
|
||||||
const tempSaveUploadFileIds = new Set<string>(); // 临时存储 body 内已保存的上传文件 id 集合,用于对比 saveUploadFileIds 以判断有哪些文件被删除
|
const tempSaveUploadFileIds = new Set<string>(); // 临时存储 body 内已保存的上传文件 id 集合,用于对比 saveUploadFileIds 以判断有哪些文件被删除
|
||||||
const tempSaveLinkFileIds = new Set<string>(); // 临时存储 body 内已保存的关联文件 id 集合,用于对比 saveLinkFileIds 以判断有哪些文件被取消关联
|
const tempSaveLinkFileIds = new Set<string>(); // 临时存储 body 内已保存的关联文件 id 集合,用于对比 saveLinkFileIds 以判断有哪些文件被取消关联
|
||||||
// 获取上传文件和关联文件
|
// 获取上传文件和关联文件
|
||||||
const formValues = formDataBody?.formValues.filter((e) => e) || [];
|
const formValues =
|
||||||
|
((body as ExecuteBody).formDataBody?.formValues || (body as MockBody).formDataMatch.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) {
|
||||||
|
|
|
@ -115,6 +115,7 @@
|
||||||
:active-module="props.activeModule"
|
:active-module="props.activeModule"
|
||||||
:offspring-ids="props.offspringIds"
|
:offspring-ids="props.offspringIds"
|
||||||
:definition-detail="activeApiTab"
|
:definition-detail="activeApiTab"
|
||||||
|
:protocol="activeApiTab.protocol"
|
||||||
is-api
|
is-api
|
||||||
/>
|
/>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
<template>
|
<template>
|
||||||
<MsDrawer
|
<MsDrawer
|
||||||
v-model:visible="visible"
|
v-model:visible="visible"
|
||||||
unmount-on-close
|
:title="title"
|
||||||
:title="mockDetail.id ? t('mockManagement.mockDetail') : t('mockManagement.createMock')"
|
|
||||||
:width="960"
|
:width="960"
|
||||||
:footer="!mockDetail.id || isEdit"
|
:footer="!isReadOnly || isEdit"
|
||||||
:ok-text="isEdit ? t('common.save') : t('common.create')"
|
:ok-text="isEdit ? t('common.save') : t('common.create')"
|
||||||
:save-continue-text="t('mockManagement.saveAndContinue')"
|
:save-continue-text="t('mockManagement.saveAndContinue')"
|
||||||
:show-continue="!isEdit"
|
:show-continue="!isEdit"
|
||||||
|
:ok-loading="loading"
|
||||||
no-content-padding
|
no-content-padding
|
||||||
|
unmount-on-close
|
||||||
|
@confirm="handleSave"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
@close="handleCancel"
|
||||||
>
|
>
|
||||||
<template #tbutton>
|
<template #tbutton>
|
||||||
<div v-if="mockDetail.id" class="right-operation-button-icon flex items-center gap-[4px]">
|
<div v-if="mockDetail.id" class="right-operation-button-icon flex items-center gap-[4px]">
|
||||||
|
@ -23,11 +27,12 @@
|
||||||
</MsButton>
|
</MsButton>
|
||||||
<MsButton
|
<MsButton
|
||||||
v-permission="['PROJECT_API_DEFINITION_MOCK:READ+DELETE']"
|
v-permission="['PROJECT_API_DEFINITION_MOCK:READ+DELETE']"
|
||||||
|
class="mr-0"
|
||||||
type="icon"
|
type="icon"
|
||||||
status="danger"
|
status="secondary"
|
||||||
@click="handleDelete"
|
@click="handleDelete"
|
||||||
>
|
>
|
||||||
<MsIcon type="icon-icon_delete-trash_outlined" class="text-[rgb(var(--danger-6))]" />
|
<MsIcon type="icon-icon_delete-trash_outlined" />
|
||||||
{{ t('common.delete') }}
|
{{ t('common.delete') }}
|
||||||
</MsButton>
|
</MsButton>
|
||||||
</div>
|
</div>
|
||||||
|
@ -53,7 +58,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</MsDetailCard>
|
</MsDetailCard>
|
||||||
<a-form ref="mockForm" :model="mockDetail">
|
<a-form ref="mockForm" :model="mockDetail" :disabled="isReadOnly">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
class="hidden-item"
|
class="hidden-item"
|
||||||
field="name"
|
field="name"
|
||||||
|
@ -63,7 +68,6 @@
|
||||||
v-model:model-value="mockDetail.name"
|
v-model:model-value="mockDetail.name"
|
||||||
:placeholder="t('mockManagement.namePlaceholder')"
|
:placeholder="t('mockManagement.namePlaceholder')"
|
||||||
class="mb-[16px] w-[732px]"
|
class="mb-[16px] w-[732px]"
|
||||||
:disabled="isReadOnly"
|
|
||||||
></a-input>
|
></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item class="hidden-item" :rules="[{ required: true, message: t('mockManagement.nameNotNull') }]">
|
<a-form-item class="hidden-item" :rules="[{ required: true, message: t('mockManagement.nameNotNull') }]">
|
||||||
|
@ -74,7 +78,6 @@
|
||||||
unique-value
|
unique-value
|
||||||
retain-input-value
|
retain-input-value
|
||||||
:max-tag-count="5"
|
:max-tag-count="5"
|
||||||
:disabled="isReadOnly"
|
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
@ -91,9 +94,11 @@
|
||||||
activeTab === RequestComposition.QUERY ||
|
activeTab === RequestComposition.QUERY ||
|
||||||
activeTab === RequestComposition.REST
|
activeTab === RequestComposition.REST
|
||||||
"
|
"
|
||||||
|
:id="mockDetail.id"
|
||||||
v-model:matchAll="currentMatchAll"
|
v-model:matchAll="currentMatchAll"
|
||||||
v-model:matchRules="currentMatchRules"
|
v-model:matchRules="currentMatchRules"
|
||||||
:key-options="currentKeyOptions"
|
:key-options="currentKeyOptions"
|
||||||
|
:disabled="isReadOnly"
|
||||||
/>
|
/>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="mb-[8px] flex items-center justify-between">
|
<div class="mb-[8px] flex items-center justify-between">
|
||||||
|
@ -101,6 +106,7 @@
|
||||||
v-model:model-value="mockDetail.mockMatchRule.body.paramType"
|
v-model:model-value="mockDetail.mockMatchRule.body.paramType"
|
||||||
type="button"
|
type="button"
|
||||||
size="small"
|
size="small"
|
||||||
|
:disabled="isReadOnly"
|
||||||
@change="handleMockBodyTypeChange"
|
@change="handleMockBodyTypeChange"
|
||||||
>
|
>
|
||||||
<a-radio v-for="item of RequestBodyFormat" :key="item" :value="item">
|
<a-radio v-for="item of RequestBodyFormat" :key="item" :value="item">
|
||||||
|
@ -118,9 +124,11 @@
|
||||||
v-else-if="
|
v-else-if="
|
||||||
[RequestBodyFormat.FORM_DATA, RequestBodyFormat.WWW_FORM].includes(mockDetail.mockMatchRule.body.paramType)
|
[RequestBodyFormat.FORM_DATA, RequestBodyFormat.WWW_FORM].includes(mockDetail.mockMatchRule.body.paramType)
|
||||||
"
|
"
|
||||||
|
:id="mockDetail.id"
|
||||||
v-model:matchAll="mockDetail.mockMatchRule.body.formDataMatch.matchAll"
|
v-model:matchAll="mockDetail.mockMatchRule.body.formDataMatch.matchAll"
|
||||||
v-model:matchRules="mockDetail.mockMatchRule.body.formDataMatch.matchRules"
|
v-model:matchRules="mockDetail.mockMatchRule.body.formDataMatch.matchRules"
|
||||||
:key-options="currentBodyKeyOptions"
|
:key-options="currentBodyKeyOptions"
|
||||||
|
:disabled="isReadOnly"
|
||||||
/>
|
/>
|
||||||
<div v-else-if="mockDetail.mockMatchRule.body.paramType === RequestBodyFormat.BINARY">
|
<div v-else-if="mockDetail.mockMatchRule.body.paramType === 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]">
|
||||||
|
@ -132,6 +140,10 @@
|
||||||
id: 'fileId',
|
id: 'fileId',
|
||||||
name: 'fileName',
|
name: 'fileName',
|
||||||
}"
|
}"
|
||||||
|
:file-save-as-source-id="mockDetail.id"
|
||||||
|
:file-save-as-api="transferMockFile"
|
||||||
|
:file-module-options-api="getMockTransferOptions"
|
||||||
|
:disabled="isReadOnly"
|
||||||
@change="handleFileChange"
|
@change="handleFileChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -160,6 +172,7 @@
|
||||||
:show-theme-change="false"
|
:show-theme-change="false"
|
||||||
:show-code-format="true"
|
:show-code-format="true"
|
||||||
:language="currentCodeLanguage"
|
:language="currentCodeLanguage"
|
||||||
|
:read-only="isReadOnly"
|
||||||
>
|
>
|
||||||
</MsCodeEditor>
|
</MsCodeEditor>
|
||||||
</div>
|
</div>
|
||||||
|
@ -167,13 +180,14 @@
|
||||||
<mockResponse
|
<mockResponse
|
||||||
v-model:mock-response="mockDetail.response"
|
v-model:mock-response="mockDetail.response"
|
||||||
:definition-responses="props.definitionDetail.responseDefinition || []"
|
:definition-responses="props.definitionDetail.responseDefinition || []"
|
||||||
|
:disabled="isReadOnly"
|
||||||
/>
|
/>
|
||||||
</a-spin>
|
</a-spin>
|
||||||
</MsDrawer>
|
</MsDrawer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { Message } from '@arco-design/web-vue';
|
||||||
|
|
||||||
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';
|
||||||
|
@ -190,26 +204,40 @@
|
||||||
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
||||||
import { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
import { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
addMock,
|
||||||
|
getMockDetail,
|
||||||
|
getMockTransferOptions,
|
||||||
|
transferMockFile,
|
||||||
|
updateMock,
|
||||||
|
uploadMockTempFile,
|
||||||
|
} from '@/api/modules/api-test/management';
|
||||||
import { requestBodyTypeMap } from '@/config/apiTest';
|
import { requestBodyTypeMap } from '@/config/apiTest';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import useAppStore from '@/store/modules/app';
|
||||||
|
|
||||||
import { MockParams } from '@/models/apiTest/mock';
|
import { MockParams } from '@/models/apiTest/mock';
|
||||||
import { RequestBodyFormat, RequestComposition } from '@/enums/apiEnum';
|
import { RequestBodyFormat, RequestComposition } from '@/enums/apiEnum';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
defaultHeaderParamsItem,
|
defaultHeaderParamsItem,
|
||||||
|
defaultMatchRuleItem,
|
||||||
defaultRequestParamsItem,
|
defaultRequestParamsItem,
|
||||||
mockDefaultParams,
|
makeDefaultParams,
|
||||||
} from '@/views/api-test/components/config';
|
} from '@/views/api-test/components/config';
|
||||||
import { filterKeyValParams } from '@/views/api-test/components/utils';
|
import { filterKeyValParams, parseRequestBodyFiles } from '@/views/api-test/components/utils';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
definitionDetail: RequestParam;
|
definitionDetail: RequestParam;
|
||||||
|
detailId?: string;
|
||||||
|
isCopy?: boolean;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'delete'): void;
|
(e: 'delete'): void;
|
||||||
|
(e: 'addDone'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const appStore = useAppStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const visible = defineModel<boolean>('visible', {
|
const visible = defineModel<boolean>('visible', {
|
||||||
|
@ -218,8 +246,17 @@
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const isEdit = ref(false);
|
const isEdit = ref(false);
|
||||||
const mockDetail = ref<MockParams>(cloneDeep(mockDefaultParams));
|
const mockDetail = ref<MockParams>(makeDefaultParams());
|
||||||
const isReadOnly = computed(() => !!mockDetail.value.id && !isEdit.value);
|
const isReadOnly = computed(() => !mockDetail.value.isNew && !isEdit.value);
|
||||||
|
const title = computed(() => {
|
||||||
|
if (isReadOnly.value) {
|
||||||
|
return t('mockManagement.mockDetail');
|
||||||
|
}
|
||||||
|
if (isEdit.value) {
|
||||||
|
return t('mockManagement.updateMock');
|
||||||
|
}
|
||||||
|
return t('mockManagement.createMock');
|
||||||
|
});
|
||||||
const activeTab = ref<RequestComposition>(RequestComposition.BODY);
|
const activeTab = ref<RequestComposition>(RequestComposition.BODY);
|
||||||
const mockTabList = [
|
const mockTabList = [
|
||||||
{
|
{
|
||||||
|
@ -329,17 +366,17 @@
|
||||||
const currentKeyOptions = computed(() => {
|
const currentKeyOptions = computed(() => {
|
||||||
switch (activeTab.value) {
|
switch (activeTab.value) {
|
||||||
case RequestComposition.HEADER:
|
case RequestComposition.HEADER:
|
||||||
return props.definitionDetail.headers.filter((e) => ({
|
return filterKeyValParams(props.definitionDetail.headers, defaultMatchRuleItem).validParams.filter((e) => ({
|
||||||
label: e.key,
|
label: e.key,
|
||||||
value: e.value,
|
value: e.value,
|
||||||
}));
|
}));
|
||||||
case RequestComposition.QUERY:
|
case RequestComposition.QUERY:
|
||||||
return props.definitionDetail.query.filter((e) => ({
|
return filterKeyValParams(props.definitionDetail.query, defaultMatchRuleItem).validParams.filter((e) => ({
|
||||||
label: e.key,
|
label: e.key,
|
||||||
value: e.value,
|
value: e.value,
|
||||||
}));
|
}));
|
||||||
case RequestComposition.REST:
|
case RequestComposition.REST:
|
||||||
return props.definitionDetail.rest.filter((e) => ({
|
return filterKeyValParams(props.definitionDetail.rest, defaultMatchRuleItem).validParams.filter((e) => ({
|
||||||
label: e.key,
|
label: e.key,
|
||||||
value: e.value,
|
value: e.value,
|
||||||
}));
|
}));
|
||||||
|
@ -350,12 +387,19 @@
|
||||||
const currentBodyKeyOptions = computed(() => {
|
const currentBodyKeyOptions = computed(() => {
|
||||||
switch (mockDetail.value.mockMatchRule.body.paramType) {
|
switch (mockDetail.value.mockMatchRule.body.paramType) {
|
||||||
case RequestBodyFormat.FORM_DATA:
|
case RequestBodyFormat.FORM_DATA:
|
||||||
return props.definitionDetail.body.formDataBody.formValues.filter((e) => ({
|
return filterKeyValParams(
|
||||||
|
props.definitionDetail.body.formDataBody.formValues,
|
||||||
|
defaultMatchRuleItem
|
||||||
|
).validParams.map((e) => ({
|
||||||
label: e.key,
|
label: e.key,
|
||||||
value: e.value,
|
value: e.value,
|
||||||
|
paramType: e.paramType,
|
||||||
}));
|
}));
|
||||||
case RequestBodyFormat.WWW_FORM:
|
case RequestBodyFormat.WWW_FORM:
|
||||||
return props.definitionDetail.body.wwwFormBody.formValues.filter((e) => ({
|
return filterKeyValParams(
|
||||||
|
props.definitionDetail.body.wwwFormBody.formValues,
|
||||||
|
defaultMatchRuleItem
|
||||||
|
).validParams.filter((e) => ({
|
||||||
label: e.key,
|
label: e.key,
|
||||||
value: e.value,
|
value: e.value,
|
||||||
}));
|
}));
|
||||||
|
@ -374,6 +418,61 @@
|
||||||
return LanguageEnum.PLAINTEXT;
|
return LanguageEnum.PLAINTEXT;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function initMockDetail() {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const res = await getMockDetail({
|
||||||
|
id: props.detailId || '',
|
||||||
|
projectId: appStore.currentProjectId,
|
||||||
|
});
|
||||||
|
const parseFileResult = parseRequestBodyFiles(res.matching.body);
|
||||||
|
const formDataMatch =
|
||||||
|
res.matching.body.paramType === RequestBodyFormat.FORM_DATA
|
||||||
|
? res.matching.body.formDataMatch.matchRules.map((item) => {
|
||||||
|
const newParamType =
|
||||||
|
currentBodyKeyOptions.value.find((e) => e.value === item.key)?.paramType ||
|
||||||
|
defaultMatchRuleItem.paramType;
|
||||||
|
item.paramType = newParamType;
|
||||||
|
item.files = item.files || [];
|
||||||
|
return item;
|
||||||
|
})
|
||||||
|
: res.matching.body.formDataMatch.matchRules;
|
||||||
|
mockDetail.value = {
|
||||||
|
...res,
|
||||||
|
id: props.isCopy ? '' : res.id,
|
||||||
|
isNew: props.isCopy,
|
||||||
|
mockMatchRule: {
|
||||||
|
...res.matching,
|
||||||
|
body: {
|
||||||
|
...res.matching.body,
|
||||||
|
formDataMatch: {
|
||||||
|
...res.matching.body.formDataMatch,
|
||||||
|
matchRules: formDataMatch,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...parseFileResult,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => visible.value,
|
||||||
|
(val) => {
|
||||||
|
if (val && props.detailId) {
|
||||||
|
initMockDetail();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const fileList = ref<MsFileItem[]>([]);
|
const fileList = ref<MsFileItem[]>([]);
|
||||||
|
|
||||||
async function handleFileChange(files: MsFileItem[], file?: MsFileItem) {
|
async function handleFileChange(files: MsFileItem[], file?: MsFileItem) {
|
||||||
|
@ -381,7 +480,7 @@
|
||||||
if (file?.local && file.file) {
|
if (file?.local && file.file) {
|
||||||
// 本地上传
|
// 本地上传
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const res = await Promise.resolve({ data: 'fileId' });
|
const res = await uploadMockTempFile(file.file);
|
||||||
mockDetail.value.mockMatchRule.body.binaryBody.file = {
|
mockDetail.value.mockMatchRule.body.binaryBody.file = {
|
||||||
...file,
|
...file,
|
||||||
fileId: res.data,
|
fileId: res.data,
|
||||||
|
@ -426,8 +525,90 @@
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function handleCancel() {
|
||||||
|
mockDetail.value = makeDefaultParams();
|
||||||
|
isEdit.value = false;
|
||||||
|
visible.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
emit('delete');
|
emit('delete');
|
||||||
|
handleCancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSave() {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const { body } = mockDetail.value.mockMatchRule;
|
||||||
|
const validBodyMatchRules = filterKeyValParams(body.formDataMatch.matchRules, defaultMatchRuleItem).validParams;
|
||||||
|
const validHeaderMatchRules = filterKeyValParams(
|
||||||
|
mockDetail.value.mockMatchRule.header.matchRules,
|
||||||
|
defaultMatchRuleItem
|
||||||
|
).validParams;
|
||||||
|
const validQueryMatchRules = filterKeyValParams(
|
||||||
|
mockDetail.value.mockMatchRule.query.matchRules,
|
||||||
|
defaultMatchRuleItem
|
||||||
|
).validParams;
|
||||||
|
const validRestMatchRules = filterKeyValParams(
|
||||||
|
mockDetail.value.mockMatchRule.rest.matchRules,
|
||||||
|
defaultMatchRuleItem
|
||||||
|
).validParams;
|
||||||
|
const validResponseHeaders = filterKeyValParams(
|
||||||
|
mockDetail.value.response.headers,
|
||||||
|
defaultHeaderParamsItem
|
||||||
|
).validParams;
|
||||||
|
const parseFileResult = parseRequestBodyFiles(mockDetail.value.mockMatchRule.body);
|
||||||
|
const params = {
|
||||||
|
...mockDetail.value,
|
||||||
|
statusCode: mockDetail.value.response.statusCode,
|
||||||
|
mockMatchRule: {
|
||||||
|
...mockDetail.value.mockMatchRule,
|
||||||
|
body: {
|
||||||
|
...mockDetail.value.mockMatchRule.body,
|
||||||
|
formDataMatch: {
|
||||||
|
...mockDetail.value.mockMatchRule.body.formDataMatch,
|
||||||
|
matchRules: validBodyMatchRules,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
...mockDetail.value.mockMatchRule.header,
|
||||||
|
matchRules: validHeaderMatchRules,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
...mockDetail.value.mockMatchRule.query,
|
||||||
|
matchRules: validQueryMatchRules,
|
||||||
|
},
|
||||||
|
rest: {
|
||||||
|
...mockDetail.value.mockMatchRule.rest,
|
||||||
|
matchRules: validRestMatchRules,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
...mockDetail.value.response,
|
||||||
|
headers: validResponseHeaders,
|
||||||
|
},
|
||||||
|
...parseFileResult,
|
||||||
|
apiDefinitionId: props.definitionDetail.id,
|
||||||
|
projectId: appStore.currentProjectId,
|
||||||
|
};
|
||||||
|
if (isEdit.value) {
|
||||||
|
await updateMock({
|
||||||
|
id: mockDetail.value.id || '',
|
||||||
|
...params,
|
||||||
|
});
|
||||||
|
Message.success(t('common.updateSuccess'));
|
||||||
|
} else {
|
||||||
|
await addMock(params);
|
||||||
|
Message.success(t('common.createSuccess'));
|
||||||
|
}
|
||||||
|
emit('addDone');
|
||||||
|
handleCancel();
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,56 +1,84 @@
|
||||||
<template>
|
<template>
|
||||||
<a-form ref="formRef" :model="formModel" layout="vertical">
|
<a-form ref="formRef" :model="formModel" layout="vertical">
|
||||||
<div
|
<a-spin :loading="loading" class="block">
|
||||||
:class="`flex ${
|
<div
|
||||||
matchRules.length > 1 ? 'items-stretch' : 'items-center'
|
:class="`flex ${
|
||||||
} gap-[16px] overflow-hidden bg-[var(--color-text-n9)] p-[12px]`"
|
matchRules.length > 1 ? 'items-stretch' : 'items-center'
|
||||||
>
|
} gap-[16px] bg-[var(--color-text-n9)] p-[12px]`"
|
||||||
<div class="flex h-auto flex-col items-center">
|
>
|
||||||
<a-divider v-show="matchRules.length > 1" direction="vertical" class="h-full" />
|
<div class="flex h-auto flex-col items-center">
|
||||||
<a-select v-model:model-value="matchAll" size="small" class="w-[75px]">
|
<a-divider v-show="matchRules.length > 1" direction="vertical" class="h-full" />
|
||||||
<a-option :value="true">AND</a-option>
|
<a-select v-model:model-value="matchAll" size="small" :disabled="props.disabled" class="w-[75px]">
|
||||||
<a-option :value="false">OR</a-option>
|
<a-option :value="true">AND</a-option>
|
||||||
</a-select>
|
<a-option :value="false">OR</a-option>
|
||||||
<a-divider v-show="matchRules.length > 1" direction="vertical" class="h-full" />
|
</a-select>
|
||||||
</div>
|
<a-divider v-show="matchRules.length > 1" direction="vertical" class="h-full" />
|
||||||
<div class="flex max-h-[300px] flex-1 flex-col gap-[8px]">
|
</div>
|
||||||
<div v-for="(item, idx) in matchRules" :key="`filter_item_${idx}`" class="flex items-start gap-[8px]">
|
<div class="flex flex-1 flex-col gap-[8px]">
|
||||||
<div class="w-[220px]">
|
<div v-for="(item, idx) in matchRules" :key="`filter_item_${idx}`" class="flex items-start gap-[8px]">
|
||||||
<a-form-item
|
<div class="w-[220px]">
|
||||||
:field="`list[${idx}].key`"
|
<a-form-item
|
||||||
hide-asterisk
|
:field="`matchRules[${idx}].key`"
|
||||||
class="hidden-item"
|
hide-asterisk
|
||||||
:rules="[{ required: true, message: t('mockManagement.paramNameNotNull') }]"
|
class="hidden-item"
|
||||||
>
|
:rules="[{ required: true, message: t('mockManagement.paramNameNotNull') }]"
|
||||||
<a-select
|
:disabled="props.disabled"
|
||||||
v-model="item.key"
|
|
||||||
:placeholder="t('apiTestDebug.paramName')"
|
|
||||||
:options="props.keyOptions"
|
|
||||||
allow-search
|
|
||||||
@change="() => addMatchRule(idx)"
|
|
||||||
>
|
>
|
||||||
</a-select>
|
<a-select
|
||||||
</a-form-item>
|
v-model="item.key"
|
||||||
</div>
|
:placeholder="t('apiTestDebug.paramName')"
|
||||||
<div class="w-[100px]">
|
:options="props.keyOptions"
|
||||||
<a-form-item :field="`list[${idx}].condition`" hide-asterisk class="hidden-item">
|
allow-search
|
||||||
<a-select v-model="item.condition" :options="props.keyOptions" @change="() => addMatchRule(idx)">
|
@change="(val) => selectedKey(item, idx)"
|
||||||
</a-select>
|
>
|
||||||
</a-form-item>
|
</a-select>
|
||||||
</div>
|
</a-form-item>
|
||||||
<div class="flex-1">
|
</div>
|
||||||
<a-form-item :field="`list[${idx}].value`" class="hidden-item">
|
<div class="w-[100px]">
|
||||||
<MsParamsInput
|
<a-form-item
|
||||||
v-model:value="item.value"
|
:field="`matchRules[${idx}].condition`"
|
||||||
set-default-class
|
hide-asterisk
|
||||||
@change="() => addMatchRule(idx)"
|
class="hidden-item"
|
||||||
@dblclick="quickInputParams(item)"
|
:disabled="props.disabled"
|
||||||
@apply="() => addMatchRule(idx)"
|
>
|
||||||
/>
|
<a-select
|
||||||
</a-form-item>
|
v-model="item.condition"
|
||||||
</div>
|
:options="getMatchRuleOptions(item.paramType)"
|
||||||
<!-- <div class="grow-0">
|
@change="() => addMatchRule(idx)"
|
||||||
<a-form-item :field="`list[${idx}].description`" class="hidden-item">
|
>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<a-form-item :field="`matchRules[${idx}].value`" class="hidden-item" :disabled="props.disabled">
|
||||||
|
<MsAddAttachment
|
||||||
|
v-if="item.paramType === RequestParamsType.FILE"
|
||||||
|
v-model:file-list="item.files"
|
||||||
|
mode="input"
|
||||||
|
:fields="{
|
||||||
|
id: 'fileId',
|
||||||
|
name: 'fileName',
|
||||||
|
}"
|
||||||
|
input-class="h-[32px]"
|
||||||
|
:file-save-as-source-id="props.id"
|
||||||
|
:file-save-as-api="transferMockFile"
|
||||||
|
:file-module-options-api="getMockTransferOptions"
|
||||||
|
:disabled="props.disabled"
|
||||||
|
@change="(files, file) => handleFileChange(files, item, idx, file)"
|
||||||
|
/>
|
||||||
|
<MsParamsInput
|
||||||
|
v-else
|
||||||
|
v-model:value="item.value"
|
||||||
|
set-default-class
|
||||||
|
:disabled="props.disabled"
|
||||||
|
@change="() => addMatchRule(idx)"
|
||||||
|
@dblclick="quickInputParams(item)"
|
||||||
|
@apply="() => addMatchRule(idx)"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="grow-0">
|
||||||
|
<a-form-item :field="`matchRules[${idx}].description`" class="hidden-item">
|
||||||
<paramDescInput
|
<paramDescInput
|
||||||
v-model:desc="item.description"
|
v-model:desc="item.description"
|
||||||
@input="() => addMatchRule(idx)"
|
@input="() => addMatchRule(idx)"
|
||||||
|
@ -59,16 +87,17 @@
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</div> -->
|
</div> -->
|
||||||
<div
|
<div
|
||||||
v-if="matchRules.length > 1"
|
v-if="matchRules.length > 1 && !props.disabled"
|
||||||
class="mt-[8px] flex h-full cursor-pointer items-start justify-center text-[var(--color-text-4)]"
|
class="mt-[8px] flex h-full cursor-pointer items-start justify-center text-[var(--color-text-4)]"
|
||||||
@click="handleDeleteItem(idx)"
|
@click="handleDeleteItem(idx)"
|
||||||
>
|
>
|
||||||
<icon-minus-circle />
|
<icon-minus-circle />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a-spin>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-modal
|
<a-modal
|
||||||
v-model:visible="showQuickInputParam"
|
v-model:visible="showQuickInputParam"
|
||||||
|
@ -121,22 +150,33 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { FormInstance, SelectOptionData } from '@arco-design/web-vue';
|
import { FormInstance, SelectOptionData } from '@arco-design/web-vue';
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
||||||
|
import { MsFileItem } from '@/components/pure/ms-upload/types';
|
||||||
|
import MsAddAttachment from '@/components/business/ms-add-attachment/index.vue';
|
||||||
import MsParamsInput from '@/components/business/ms-params-input/index.vue';
|
import MsParamsInput from '@/components/business/ms-params-input/index.vue';
|
||||||
|
|
||||||
|
import { getMockTransferOptions, transferMockFile, uploadMockTempFile } from '@/api/modules/api-test/management';
|
||||||
// import paramDescInput from '@/views/api-test/components/paramDescInput.vue';
|
// import paramDescInput from '@/views/api-test/components/paramDescInput.vue';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
|
import { MatchRuleItem } from '@/models/apiTest/mock';
|
||||||
|
import { RequestParamsType } from '@/enums/apiEnum';
|
||||||
|
|
||||||
|
import { defaultMatchRuleItem, matchRuleOptions, mockFileMatchRules } from '@/views/api-test/components/config';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
id?: string;
|
||||||
keyOptions: SelectOptionData[];
|
keyOptions: SelectOptionData[];
|
||||||
|
disabled: boolean;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(
|
(
|
||||||
e: 'change',
|
e: 'change',
|
||||||
form: {
|
form: {
|
||||||
matchAll: boolean;
|
matchAll: boolean;
|
||||||
matchRules: Record<string, any>[];
|
matchRules: MatchRuleItem[];
|
||||||
},
|
},
|
||||||
isInit?: boolean
|
isInit?: boolean
|
||||||
): void;
|
): void;
|
||||||
|
@ -147,10 +187,11 @@
|
||||||
const matchAll = defineModel<boolean>('matchAll', {
|
const matchAll = defineModel<boolean>('matchAll', {
|
||||||
required: true,
|
required: true,
|
||||||
});
|
});
|
||||||
const matchRules = defineModel<Record<string, any>[]>('matchRules', {
|
const matchRules = defineModel<MatchRuleItem[]>('matchRules', {
|
||||||
required: true,
|
required: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
const formRef = ref<FormInstance>();
|
const formRef = ref<FormInstance>();
|
||||||
const formModel = ref({
|
const formModel = ref({
|
||||||
matchAll: matchAll.value,
|
matchAll: matchAll.value,
|
||||||
|
@ -164,17 +205,88 @@
|
||||||
function addMatchRule(rowIndex: number) {
|
function addMatchRule(rowIndex: number) {
|
||||||
if (rowIndex === matchRules.value.length - 1) {
|
if (rowIndex === matchRules.value.length - 1) {
|
||||||
matchRules.value.push({
|
matchRules.value.push({
|
||||||
key: '',
|
id: `${Date.now() + rowIndex}`,
|
||||||
value: '',
|
...cloneDeep(defaultMatchRuleItem),
|
||||||
description: '',
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择参数名称
|
||||||
|
* @param ruleItem 当前规则项
|
||||||
|
* @param rowIndex 当前行索引
|
||||||
|
*/
|
||||||
|
function selectedKey(ruleItem: MatchRuleItem, rowIndex: number) {
|
||||||
|
const item = formModel.value.matchRules[rowIndex];
|
||||||
|
if (item) {
|
||||||
|
const newParamType =
|
||||||
|
props.keyOptions.find((e) => e.value === ruleItem.key)?.paramType || defaultMatchRuleItem.paramType;
|
||||||
|
item.paramType = newParamType;
|
||||||
|
if (newParamType === RequestParamsType.FILE && !mockFileMatchRules.includes(item.condition)) {
|
||||||
|
// 如果选择的参数类型是文件,且当前条件不在文件匹配规则中,则默认为等于
|
||||||
|
item.condition = 'EQUALS';
|
||||||
|
}
|
||||||
|
addMatchRule(rowIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取对应参数类型的匹配规则选项
|
||||||
|
* @param paramType 参数类型
|
||||||
|
*/
|
||||||
|
function getMatchRuleOptions(paramType: RequestParamsType) {
|
||||||
|
if (paramType === RequestParamsType.FILE) {
|
||||||
|
return matchRuleOptions
|
||||||
|
.filter((e) => mockFileMatchRules.includes(e.value))
|
||||||
|
.map((e) => ({ ...e, label: t(e.label) }));
|
||||||
|
}
|
||||||
|
return matchRuleOptions.map((e) => ({ ...e, label: t(e.label) }));
|
||||||
|
}
|
||||||
|
|
||||||
function emitChange(from: string, isInit?: boolean) {
|
function emitChange(from: string, isInit?: boolean) {
|
||||||
emit('change', formModel.value, isInit);
|
emit('change', formModel.value, isInit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleFileChange(
|
||||||
|
files: MsFileItem[],
|
||||||
|
record: Record<string, any>,
|
||||||
|
rowIndex: number,
|
||||||
|
file?: MsFileItem
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (file?.local && file.file) {
|
||||||
|
// 本地上传单次只能选一个文件
|
||||||
|
loading.value = true;
|
||||||
|
const res = await uploadMockTempFile(file.file);
|
||||||
|
for (let i = 0; i < record.files.length; i++) {
|
||||||
|
const item = record.files[i];
|
||||||
|
if ([item.fileId, item.uid].includes(file.uid)) {
|
||||||
|
record.files[i] = {
|
||||||
|
...file,
|
||||||
|
fileId: res.data,
|
||||||
|
fileName: file.name || '',
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 关联文件可选多个文件
|
||||||
|
record.files = files.map((e) => ({
|
||||||
|
...e,
|
||||||
|
fileId: e.uid || e.fileId || '',
|
||||||
|
fileName: e.name || e.fileName || '',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
addMatchRule(rowIndex);
|
||||||
|
emitChange('handleFileChange');
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const showQuickInputParam = ref(false);
|
const showQuickInputParam = ref(false);
|
||||||
const activeQuickInputRecord = ref<any>({});
|
const activeQuickInputRecord = ref<any>({});
|
||||||
const quickInputParamValue = ref('');
|
const quickInputParamValue = ref('');
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<a-spin :loading="loading" class="block">
|
<a-spin :loading="loading" class="block">
|
||||||
<div class="mt-[16px] font-medium">{{ t('apiTestManagement.responseContent') }}</div>
|
<div class="mt-[16px] font-medium">{{ t('apiTestManagement.responseContent') }}</div>
|
||||||
<div class="mt-[8px] flex items-center gap-[4px]">
|
<div class="mt-[8px] flex items-center gap-[4px]">
|
||||||
<a-switch v-model:model-value="mockResponse.useApiResponse" size="small"></a-switch>
|
<a-switch v-model:model-value="mockResponse.useApiResponse" size="small" :disabled="props.disabled"></a-switch>
|
||||||
{{ t('mockManagement.followDefinition') }}
|
{{ t('mockManagement.followDefinition') }}
|
||||||
</div>
|
</div>
|
||||||
<template v-if="!mockResponse.useApiResponse">
|
<template v-if="!mockResponse.useApiResponse">
|
||||||
|
@ -19,6 +19,7 @@
|
||||||
v-model:model-value="mockResponse.body.bodyType"
|
v-model:model-value="mockResponse.body.bodyType"
|
||||||
type="button"
|
type="button"
|
||||||
size="small"
|
size="small"
|
||||||
|
:disabled="props.disabled"
|
||||||
@change="(val) => emit('change')"
|
@change="(val) => emit('change')"
|
||||||
>
|
>
|
||||||
<a-radio
|
<a-radio
|
||||||
|
@ -67,6 +68,7 @@
|
||||||
:show-language-change="false"
|
:show-language-change="false"
|
||||||
:show-charset-change="false"
|
:show-charset-change="false"
|
||||||
show-code-format
|
show-code-format
|
||||||
|
:read-only="props.disabled"
|
||||||
>
|
>
|
||||||
</MsCodeEditor>
|
</MsCodeEditor>
|
||||||
</div>
|
</div>
|
||||||
|
@ -76,6 +78,7 @@
|
||||||
v-model:model-value="mockResponse.body.binaryBody.description"
|
v-model:model-value="mockResponse.body.binaryBody.description"
|
||||||
:placeholder="t('common.desc')"
|
:placeholder="t('common.desc')"
|
||||||
:max-length="255"
|
:max-length="255"
|
||||||
|
:disabled="props.disabled"
|
||||||
/>
|
/>
|
||||||
<MsAddAttachment
|
<MsAddAttachment
|
||||||
v-model:file-list="fileList"
|
v-model:file-list="fileList"
|
||||||
|
@ -85,6 +88,7 @@
|
||||||
id: 'fileId',
|
id: 'fileId',
|
||||||
name: 'fileName',
|
name: 'fileName',
|
||||||
}"
|
}"
|
||||||
|
:disabled="props.disabled"
|
||||||
@change="handleFileChange"
|
@change="handleFileChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -94,6 +98,7 @@
|
||||||
class="mr-[8px]"
|
class="mr-[8px]"
|
||||||
size="small"
|
size="small"
|
||||||
type="line"
|
type="line"
|
||||||
|
:disabled="props.disabled"
|
||||||
></a-switch>
|
></a-switch>
|
||||||
<span>{{ t('apiTestDebug.sendAsMainText') }}</span>
|
<span>{{ t('apiTestDebug.sendAsMainText') }}</span>
|
||||||
<a-tooltip position="right">
|
<a-tooltip position="right">
|
||||||
|
@ -115,6 +120,8 @@
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:default-param-item="defaultKeyValueParamItem"
|
:default-param-item="defaultKeyValueParamItem"
|
||||||
:selectable="false"
|
:selectable="false"
|
||||||
|
:disabled-param-value="props.disabled"
|
||||||
|
:disabled-except-param="props.disabled"
|
||||||
@change="handleResponseTableChange"
|
@change="handleResponseTableChange"
|
||||||
/>
|
/>
|
||||||
<a-select
|
<a-select
|
||||||
|
@ -122,6 +129,7 @@
|
||||||
v-model:model-value="mockResponse.statusCode"
|
v-model:model-value="mockResponse.statusCode"
|
||||||
:options="statusCodeOptions"
|
:options="statusCodeOptions"
|
||||||
class="w-[200px]"
|
class="w-[200px]"
|
||||||
|
:disabled="props.disabled"
|
||||||
@change="() => emit('change')"
|
@change="() => emit('change')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -131,6 +139,7 @@
|
||||||
v-model:model-value="mockResponse.apiResponseId"
|
v-model:model-value="mockResponse.apiResponseId"
|
||||||
:options="mockResponseOptions"
|
:options="mockResponseOptions"
|
||||||
class="w-[150px]"
|
class="w-[150px]"
|
||||||
|
:disabled="props.disabled"
|
||||||
></a-select>
|
></a-select>
|
||||||
</div>
|
</div>
|
||||||
</a-spin>
|
</a-spin>
|
||||||
|
@ -156,6 +165,7 @@
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
definitionResponses: ResponseItem[];
|
definitionResponses: ResponseItem[];
|
||||||
uploadTempFileApi?: (...args: any) => Promise<any>; // 上传临时文件接口
|
uploadTempFileApi?: (...args: any) => Promise<any>; // 上传临时文件接口
|
||||||
|
disabled: boolean;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'change'): void;
|
(e: 'change'): void;
|
||||||
|
|
|
@ -36,9 +36,9 @@
|
||||||
@selected-change="handleTableSelect"
|
@selected-change="handleTableSelect"
|
||||||
@batch-action="handleTableBatch"
|
@batch-action="handleTableBatch"
|
||||||
>
|
>
|
||||||
<template #num="{ record }">
|
<template #expectNum="{ record }">
|
||||||
<MsButton type="text" @click="openMockDetailDrawer(record)">
|
<MsButton type="text" @click="handleOpenDetail(record)">
|
||||||
{{ record.num }}
|
{{ record.expectNum }}
|
||||||
</MsButton>
|
</MsButton>
|
||||||
</template>
|
</template>
|
||||||
<template #enable="{ record }">
|
<template #enable="{ record }">
|
||||||
|
@ -50,10 +50,14 @@
|
||||||
></a-switch>
|
></a-switch>
|
||||||
</template>
|
</template>
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
<MsButton type="text" @click="debugMock(record)">
|
<MsButton type="text" class="!mr-0" @click="debugMock(record)">
|
||||||
{{ t('apiTestManagement.debug') }}
|
{{ t('apiTestManagement.debug') }}
|
||||||
</MsButton>
|
</MsButton>
|
||||||
<a-divider direction="vertical" :margin="8"></a-divider>
|
<a-divider direction="vertical" :margin="8"></a-divider>
|
||||||
|
<MsButton type="text" class="!mr-0" @click="handleCopyMock(record)">
|
||||||
|
{{ t('common.copy') }}
|
||||||
|
</MsButton>
|
||||||
|
<a-divider direction="vertical" :margin="8"></a-divider>
|
||||||
<MsTableMoreAction :list="tableMoreActionList" @select="handleTableMoreActionSelect($event, record)" />
|
<MsTableMoreAction :list="tableMoreActionList" @select="handleTableMoreActionSelect($event, record)" />
|
||||||
</template>
|
</template>
|
||||||
<template v-if="hasAnyPermission(['PROJECT_API_DEFINITION_MOCK:READ+ADD']) && props.isApi" #empty>
|
<template v-if="hasAnyPermission(['PROJECT_API_DEFINITION_MOCK:READ+ADD']) && props.isApi" #empty>
|
||||||
|
@ -66,10 +70,19 @@
|
||||||
</template>
|
</template>
|
||||||
</ms-base-table>
|
</ms-base-table>
|
||||||
</div>
|
</div>
|
||||||
<mockDetailDrawer v-model:visible="mockDetailDrawerVisible" :definition-detail="props.definitionDetail" />
|
<mockDetailDrawer
|
||||||
|
v-if="mockDetailDrawerVisible"
|
||||||
|
v-model:visible="mockDetailDrawerVisible"
|
||||||
|
:definition-detail="mockBelongDefinitionDetail"
|
||||||
|
:detail-id="activeMockRecord?.id"
|
||||||
|
:is-copy="isCopy"
|
||||||
|
@add-done="loadMockList"
|
||||||
|
@delete="() => removeMock(activeMockRecord)"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useClipboard } from '@vueuse/core';
|
||||||
import { Message } from '@arco-design/web-vue';
|
import { Message } from '@arco-design/web-vue';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
@ -82,8 +95,11 @@
|
||||||
import { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
import { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
deleteDefinitionMockMock,
|
batchDeleteMock,
|
||||||
|
deleteMock,
|
||||||
|
getDefinitionDetail,
|
||||||
getDefinitionMockPage,
|
getDefinitionMockPage,
|
||||||
|
getMockUrl,
|
||||||
updateMockStatusPage,
|
updateMockStatusPage,
|
||||||
} from '@/api/modules/api-test/management';
|
} from '@/api/modules/api-test/management';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
@ -94,6 +110,7 @@
|
||||||
|
|
||||||
import { ApiDefinitionMockDetail } from '@/models/apiTest/management';
|
import { ApiDefinitionMockDetail } from '@/models/apiTest/management';
|
||||||
import { OrdTemplateManagement } from '@/models/setting/template';
|
import { OrdTemplateManagement } from '@/models/setting/template';
|
||||||
|
import { RequestComposition } from '@/enums/apiEnum';
|
||||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||||
|
|
||||||
const mockDetailDrawer = defineAsyncComponent(() => import('./mockDetailDrawer.vue'));
|
const mockDetailDrawer = defineAsyncComponent(() => import('./mockDetailDrawer.vue'));
|
||||||
|
@ -105,6 +122,7 @@
|
||||||
offspringIds: string[];
|
offspringIds: string[];
|
||||||
definitionDetail: RequestParam;
|
definitionDetail: RequestParam;
|
||||||
readOnly?: boolean; // 是否是只读模式
|
readOnly?: boolean; // 是否是只读模式
|
||||||
|
protocol: string; // 查看的协议类型
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'init', params: any): void;
|
(e: 'init', params: any): void;
|
||||||
|
@ -112,6 +130,7 @@
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
const tableStore = useTableStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { openModal } = useModal();
|
const { openModal } = useModal();
|
||||||
|
|
||||||
|
@ -120,8 +139,8 @@
|
||||||
let columns: MsTableColumn = [
|
let columns: MsTableColumn = [
|
||||||
{
|
{
|
||||||
title: 'ID',
|
title: 'ID',
|
||||||
dataIndex: 'id',
|
dataIndex: 'expectNum',
|
||||||
slotName: 'id',
|
slotName: 'expectNum',
|
||||||
sortIndex: 1,
|
sortIndex: 1,
|
||||||
sortable: {
|
sortable: {
|
||||||
sortDirections: ['ascend', 'descend'],
|
sortDirections: ['ascend', 'descend'],
|
||||||
|
@ -235,11 +254,27 @@
|
||||||
|
|
||||||
const tableQueryParams = ref<any>();
|
const tableQueryParams = ref<any>();
|
||||||
|
|
||||||
function loadMockList() {
|
async function getModuleIds() {
|
||||||
|
let moduleIds: string[] = [];
|
||||||
|
if (props.activeModule !== 'all') {
|
||||||
|
moduleIds = [props.activeModule];
|
||||||
|
const getAllChildren = await tableStore.getSubShow(TableKeyEnum.API_TEST_MANAGEMENT_CASE);
|
||||||
|
if (getAllChildren) {
|
||||||
|
moduleIds = [props.activeModule, ...props.offspringIds];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return moduleIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadMockList() {
|
||||||
|
const selectModules = await getModuleIds();
|
||||||
const params = {
|
const params = {
|
||||||
keyword: keyword.value,
|
keyword: keyword.value,
|
||||||
projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
|
protocol: props.protocol,
|
||||||
|
apiDefinitionId: props.definitionDetail.id !== 'all' ? props.definitionDetail.id : undefined,
|
||||||
filter: {},
|
filter: {},
|
||||||
|
moduleIds: selectModules,
|
||||||
};
|
};
|
||||||
setLoadListParams(params);
|
setLoadListParams(params);
|
||||||
loadList();
|
loadList();
|
||||||
|
@ -253,19 +288,11 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watchEffect(() => {
|
||||||
() => props.activeModule,
|
if (props.activeModule || props.protocol) {
|
||||||
() => {
|
|
||||||
loadMockList();
|
loadMockList();
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.definitionDetail.protocol,
|
|
||||||
() => {
|
|
||||||
loadMockList();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const changeDefault = async (value: any, record: OrdTemplateManagement) => {
|
const changeDefault = async (value: any, record: OrdTemplateManagement) => {
|
||||||
try {
|
try {
|
||||||
|
@ -278,10 +305,6 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onBeforeMount(() => {
|
|
||||||
loadMockList();
|
|
||||||
});
|
|
||||||
|
|
||||||
const tableSelected = ref<(string | number)[]>([]);
|
const tableSelected = ref<(string | number)[]>([]);
|
||||||
const batchParams = ref<BatchActionQueryParams>({
|
const batchParams = ref<BatchActionQueryParams>({
|
||||||
selectedIds: [],
|
selectedIds: [],
|
||||||
|
@ -293,7 +316,7 @@
|
||||||
/**
|
/**
|
||||||
* 删除接口
|
* 删除接口
|
||||||
*/
|
*/
|
||||||
function deleteMock(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.deleteApiTipTitle', { name: record?.name });
|
||||||
let selectIds = [record?.id || ''];
|
let selectIds = [record?.id || ''];
|
||||||
if (isBatch) {
|
if (isBatch) {
|
||||||
|
@ -315,15 +338,17 @@
|
||||||
onBeforeOk: async () => {
|
onBeforeOk: async () => {
|
||||||
try {
|
try {
|
||||||
if (isBatch) {
|
if (isBatch) {
|
||||||
// await batchDeleteMock({
|
const selectModules = await getModuleIds();
|
||||||
// selectIds,
|
await batchDeleteMock({
|
||||||
// selectAll: !!params?.selectAll,
|
selectIds,
|
||||||
// excludeIds: params?.excludeIds || [],
|
selectAll: !!params?.selectAll,
|
||||||
// condition: { keyword: keyword.value },
|
excludeIds: params?.excludeIds || [],
|
||||||
// projectId: appStore.currentProjectId,
|
condition: { keyword: keyword.value },
|
||||||
// });
|
projectId: appStore.currentProjectId,
|
||||||
|
moduleIds: selectModules,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
await deleteDefinitionMockMock({
|
await deleteMock({
|
||||||
id: record?.id as string,
|
id: record?.id as string,
|
||||||
projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
});
|
});
|
||||||
|
@ -340,6 +365,26 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { copy, isSupported } = useClipboard({ legacy: true });
|
||||||
|
|
||||||
|
async function copyMockUrl(record: ApiDefinitionMockDetail) {
|
||||||
|
try {
|
||||||
|
appStore.showLoading();
|
||||||
|
const url = await getMockUrl(record.id);
|
||||||
|
if (isSupported) {
|
||||||
|
copy(url);
|
||||||
|
Message.success(t('common.copySuccess'));
|
||||||
|
} else {
|
||||||
|
Message.warning(t('common.copyNotSupport'));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
appStore.hideLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理表格更多按钮事件
|
* 处理表格更多按钮事件
|
||||||
* @param item
|
* @param item
|
||||||
|
@ -349,6 +394,9 @@
|
||||||
case 'delete':
|
case 'delete':
|
||||||
deleteMock(record);
|
deleteMock(record);
|
||||||
break;
|
break;
|
||||||
|
case 'copyMock':
|
||||||
|
copyMockUrl(record);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -370,7 +418,7 @@
|
||||||
batchParams.value = params;
|
batchParams.value = params;
|
||||||
switch (event.eventTag) {
|
switch (event.eventTag) {
|
||||||
case 'delete':
|
case 'delete':
|
||||||
deleteMock(undefined, true, params);
|
removeMock(undefined, true, params);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -385,15 +433,50 @@
|
||||||
mockDetailDrawerVisible.value = true;
|
mockDetailDrawerVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function openMockDetailDrawer(record: ApiDefinitionMockDetail) {
|
const mockBelongDefinitionDetail = ref<RequestParam>(props.definitionDetail);
|
||||||
activeMockRecord.value = record;
|
async function openMockDetailDrawer(record: ApiDefinitionMockDetail) {
|
||||||
mockDetailDrawerVisible.value = true;
|
try {
|
||||||
|
activeMockRecord.value = record;
|
||||||
|
if (props.definitionDetail.id === 'all') {
|
||||||
|
// 从全部 mock 列表页查看 mock 详情,需要先加载其接口定义详情
|
||||||
|
appStore.showLoading();
|
||||||
|
const res = await getDefinitionDetail(record.apiDefinitionId);
|
||||||
|
mockBelongDefinitionDetail.value = {
|
||||||
|
...(res.request as RequestParam),
|
||||||
|
id: res.id,
|
||||||
|
type: 'mock',
|
||||||
|
isNew: false,
|
||||||
|
protocol: res.protocol,
|
||||||
|
activeTab: RequestComposition.BODY,
|
||||||
|
executeLoading: false,
|
||||||
|
responseDefinition: res.response,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
mockBelongDefinitionDetail.value = props.definitionDetail;
|
||||||
|
}
|
||||||
|
mockDetailDrawerVisible.value = true;
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
appStore.hideLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isCopy = ref(false);
|
||||||
|
|
||||||
|
function handleOpenDetail(record: ApiDefinitionMockDetail) {
|
||||||
|
isCopy.value = false;
|
||||||
|
openMockDetailDrawer(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCopyMock(record: ApiDefinitionMockDetail) {
|
||||||
|
isCopy.value = true;
|
||||||
|
openMockDetailDrawer(record);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mockDebugDrawerVisible = ref(false);
|
|
||||||
function debugMock(record: ApiDefinitionMockDetail) {
|
function debugMock(record: ApiDefinitionMockDetail) {
|
||||||
activeMockRecord.value = record;
|
activeMockRecord.value = record;
|
||||||
mockDebugDrawerVisible.value = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
|
@ -401,7 +484,6 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!props.readOnly) {
|
if (!props.readOnly) {
|
||||||
const tableStore = useTableStore();
|
|
||||||
await tableStore.initColumn(TableKeyEnum.API_TEST, columns, 'drawer');
|
await tableStore.initColumn(TableKeyEnum.API_TEST, columns, 'drawer');
|
||||||
} else {
|
} else {
|
||||||
columns = columns.filter(
|
columns = columns.filter(
|
||||||
|
|
|
@ -210,6 +210,7 @@ export default {
|
||||||
'mockManagement.batchDeleteMockTip': '确认删除已选中的 {count} 个Mock吗?',
|
'mockManagement.batchDeleteMockTip': '确认删除已选中的 {count} 个Mock吗?',
|
||||||
'mockManagement.allMock': '全部 MOCK',
|
'mockManagement.allMock': '全部 MOCK',
|
||||||
'mockManagement.createMock': '创建 MOCK',
|
'mockManagement.createMock': '创建 MOCK',
|
||||||
|
'mockManagement.updateMock': '更新 MOCK',
|
||||||
'mockManagement.mockDetail': 'MOCK 详情',
|
'mockManagement.mockDetail': 'MOCK 详情',
|
||||||
'mockManagement.namePlaceholder': '请输入期望名称',
|
'mockManagement.namePlaceholder': '请输入期望名称',
|
||||||
'mockManagement.nameNotNull': '期望名称不能为空',
|
'mockManagement.nameNotNull': '期望名称不能为空',
|
||||||
|
@ -217,4 +218,15 @@ export default {
|
||||||
'mockManagement.saveAndContinue': '保存并继续创建',
|
'mockManagement.saveAndContinue': '保存并继续创建',
|
||||||
'mockManagement.paramNameNotNull': '参数名称不能为空',
|
'mockManagement.paramNameNotNull': '参数名称不能为空',
|
||||||
'mockManagement.followDefinition': '跟随 API 定义',
|
'mockManagement.followDefinition': '跟随 API 定义',
|
||||||
|
'mockManagement.equals': '等于',
|
||||||
|
'mockManagement.notEquals': '不等于',
|
||||||
|
'mockManagement.lengthEquals': '长度等于',
|
||||||
|
'mockManagement.lengthLarge': '长度大于',
|
||||||
|
'mockManagement.lengthLess': '长度小于',
|
||||||
|
'mockManagement.lengthNotEquals': '长度不等于',
|
||||||
|
'mockManagement.contain': '包含',
|
||||||
|
'mockManagement.notContain': '不包含',
|
||||||
|
'mockManagement.empty': '为空',
|
||||||
|
'mockManagement.notEmpty': '非空',
|
||||||
|
'mockManagement.regular': '正则匹配',
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
checkable
|
checkable
|
||||||
block-node
|
block-node
|
||||||
draggable
|
draggable
|
||||||
|
hide-switcher
|
||||||
@select="(selectedKeys, node) => handleStepSelect(selectedKeys, node as ScenarioStepItem)"
|
@select="(selectedKeys, node) => handleStepSelect(selectedKeys, node as ScenarioStepItem)"
|
||||||
@expand="handleStepExpand"
|
@expand="handleStepExpand"
|
||||||
@more-actions-close="() => setFocusNodeKey('')"
|
@more-actions-close="() => setFocusNodeKey('')"
|
||||||
|
|
|
@ -455,7 +455,7 @@
|
||||||
{
|
{
|
||||||
scroll: { x: '100%' },
|
scroll: { x: '100%' },
|
||||||
tableKey: TableKeyEnum.CASE_MANAGEMENT_REVIEW_CASE,
|
tableKey: TableKeyEnum.CASE_MANAGEMENT_REVIEW_CASE,
|
||||||
heightUsed: 372,
|
heightUsed: 375,
|
||||||
showSetting: true,
|
showSetting: true,
|
||||||
selectable: true,
|
selectable: true,
|
||||||
showSelectAll: true,
|
showSelectAll: true,
|
||||||
|
@ -503,9 +503,6 @@
|
||||||
keyword: keyword.value,
|
keyword: keyword.value,
|
||||||
viewFlag: props.onlyMine,
|
viewFlag: props.onlyMine,
|
||||||
filter: { status: statusFilters.value, caseLevel: caseFilters.value },
|
filter: { status: statusFilters.value, caseLevel: caseFilters.value },
|
||||||
current: propsRes.value.msPagination?.current,
|
|
||||||
pageSize: propsRes.value.msPagination?.pageSize,
|
|
||||||
total: propsRes.value.msPagination?.total,
|
|
||||||
combine: filter
|
combine: filter
|
||||||
? {
|
? {
|
||||||
...filter.combine,
|
...filter.combine,
|
||||||
|
@ -516,6 +513,9 @@
|
||||||
loadList();
|
loadList();
|
||||||
emit('init', {
|
emit('init', {
|
||||||
...tableParams.value,
|
...tableParams.value,
|
||||||
|
current: propsRes.value.msPagination?.current,
|
||||||
|
pageSize: propsRes.value.msPagination?.pageSize,
|
||||||
|
total: propsRes.value.msPagination?.total,
|
||||||
moduleIds: [],
|
moduleIds: [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,11 +111,17 @@
|
||||||
<statusTag :status="record.status" size="small" />
|
<statusTag :status="record.status" size="small" />
|
||||||
</template>
|
</template>
|
||||||
<template #reviewPassRule="{ record }">
|
<template #reviewPassRule="{ record }">
|
||||||
{{
|
<a-tag
|
||||||
record.reviewPassRule === 'SINGLE'
|
:color="record.reviewPassRule === 'SINGLE' ? 'rgb(var(--success-2))' : 'rgb(var(--link-2))'"
|
||||||
? t('caseManagement.caseReview.single')
|
:class="record.reviewPassRule === 'SINGLE' ? '!text-[rgb(var(--success-6))]' : '!text-[rgb(var(--link-6))]'"
|
||||||
: t('caseManagement.caseReview.multi')
|
size="small"
|
||||||
}}
|
>
|
||||||
|
{{
|
||||||
|
record.reviewPassRule === 'SINGLE'
|
||||||
|
? t('caseManagement.caseReview.single')
|
||||||
|
: t('caseManagement.caseReview.multi')
|
||||||
|
}}
|
||||||
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<template #reviewers="{ record }">
|
<template #reviewers="{ record }">
|
||||||
<a-tooltip :content="record.reviewers.join('、')">
|
<a-tooltip :content="record.reviewers.join('、')">
|
||||||
|
@ -435,6 +441,7 @@
|
||||||
{
|
{
|
||||||
title: 'caseManagement.caseReview.caseCount',
|
title: 'caseManagement.caseReview.caseCount',
|
||||||
dataIndex: 'caseCount',
|
dataIndex: 'caseCount',
|
||||||
|
showDrag: true,
|
||||||
width: 90,
|
width: 90,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -442,18 +449,21 @@
|
||||||
dataIndex: 'status',
|
dataIndex: 'status',
|
||||||
slotName: 'status',
|
slotName: 'status',
|
||||||
titleSlotName: 'statusFilter',
|
titleSlotName: 'statusFilter',
|
||||||
|
showDrag: true,
|
||||||
width: 150,
|
width: 150,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'caseManagement.caseReview.passRate',
|
title: 'caseManagement.caseReview.passRate',
|
||||||
slotName: 'passRate',
|
slotName: 'passRate',
|
||||||
titleSlotName: 'passRateColumn',
|
titleSlotName: 'passRateColumn',
|
||||||
|
showDrag: true,
|
||||||
width: 200,
|
width: 200,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'caseManagement.caseReview.type',
|
title: 'caseManagement.caseReview.type',
|
||||||
slotName: 'reviewPassRule',
|
slotName: 'reviewPassRule',
|
||||||
dataIndex: 'reviewPassRule',
|
dataIndex: 'reviewPassRule',
|
||||||
|
showDrag: true,
|
||||||
width: 90,
|
width: 90,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -461,35 +471,41 @@
|
||||||
slotName: 'reviewers',
|
slotName: 'reviewers',
|
||||||
dataIndex: 'reviewers',
|
dataIndex: 'reviewers',
|
||||||
titleSlotName: 'reviewersFilter',
|
titleSlotName: 'reviewersFilter',
|
||||||
|
showDrag: true,
|
||||||
width: 150,
|
width: 150,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'caseManagement.caseReview.creator',
|
title: 'caseManagement.caseReview.creator',
|
||||||
dataIndex: 'createUserName',
|
dataIndex: 'createUserName',
|
||||||
showTooltip: true,
|
showTooltip: true,
|
||||||
|
showDrag: true,
|
||||||
width: 120,
|
width: 120,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'caseManagement.caseReview.module',
|
title: 'caseManagement.caseReview.module',
|
||||||
dataIndex: 'moduleName',
|
dataIndex: 'moduleName',
|
||||||
slotName: 'moduleName',
|
slotName: 'moduleName',
|
||||||
|
showDrag: true,
|
||||||
width: 120,
|
width: 120,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'caseManagement.caseReview.tag',
|
title: 'caseManagement.caseReview.tag',
|
||||||
dataIndex: 'tags',
|
dataIndex: 'tags',
|
||||||
isTag: true,
|
isTag: true,
|
||||||
|
showDrag: true,
|
||||||
width: 170,
|
width: 170,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'caseManagement.caseReview.desc',
|
title: 'caseManagement.caseReview.desc',
|
||||||
dataIndex: 'description',
|
dataIndex: 'description',
|
||||||
width: 150,
|
width: 150,
|
||||||
|
showDrag: true,
|
||||||
showTooltip: true,
|
showTooltip: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'caseManagement.caseReview.cycle',
|
title: 'caseManagement.caseReview.cycle',
|
||||||
dataIndex: 'cycle',
|
dataIndex: 'cycle',
|
||||||
|
showDrag: true,
|
||||||
width: 350,
|
width: 350,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -92,15 +92,15 @@
|
||||||
<passRateLine :review-detail="reviewDetail" height="8px" radius="var(--border-radius-mini)" />
|
<passRateLine :review-detail="reviewDetail" height="8px" radius="var(--border-radius-mini)" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="px-[24px]">
|
<!-- <div class="px-[24px]">
|
||||||
<a-divider class="my-0" />
|
<a-divider class="my-0" />
|
||||||
<a-tabs v-model:active-key="showTab" class="no-content">
|
<a-tabs v-model:active-key="showTab" class="no-content">
|
||||||
<a-tab-pane v-for="item of tabList" :key="item.key" :title="item.title" />
|
<a-tab-pane v-for="item of tabList" :key="item.key" :title="item.title" />
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
</div>
|
</div> -->
|
||||||
</MsCard>
|
</MsCard>
|
||||||
<!-- special-height的170: 上面卡片高度154 + mt的16 -->
|
<!-- special-height的170: 上面卡片高度105 + mt的16 -->
|
||||||
<MsCard class="mt-[16px]" :special-height="170" simple has-breadcrumb no-content-padding>
|
<MsCard class="mt-[16px]" :special-height="121" simple has-breadcrumb no-content-padding>
|
||||||
<MsSplitBox>
|
<MsSplitBox>
|
||||||
<template #first>
|
<template #first>
|
||||||
<div class="p-[16px]">
|
<div class="p-[16px]">
|
||||||
|
@ -203,13 +203,13 @@
|
||||||
|
|
||||||
const onlyMine = ref(false);
|
const onlyMine = ref(false);
|
||||||
|
|
||||||
const showTab = ref(0);
|
// const showTab = ref(0);
|
||||||
const tabList = ref([
|
// const tabList = ref([
|
||||||
{
|
// {
|
||||||
key: 0,
|
// key: 0,
|
||||||
title: t('menu.caseManagement.featureCase'),
|
// title: t('menu.caseManagement.featureCase'),
|
||||||
},
|
// },
|
||||||
]);
|
// ]);
|
||||||
|
|
||||||
const modulesCount = ref<Record<string, any>>({});
|
const modulesCount = ref<Record<string, any>>({});
|
||||||
|
|
||||||
|
|
|
@ -91,18 +91,6 @@
|
||||||
></a-input>
|
></a-input>
|
||||||
<MsFormItemSub :text="t('system.config.baseInfo.pageUrlSub', { url: defaultUrl })" @fill="fillDefaultUrl" />
|
<MsFormItemSub :text="t('system.config.baseInfo.pageUrlSub', { url: defaultUrl })" @fill="fillDefaultUrl" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-xpack :label="t('system.config.prometheus')" field="prometheusHost" asterisk-position="end">
|
|
||||||
<a-input
|
|
||||||
v-model:model-value="baseInfoForm.prometheusHost"
|
|
||||||
:max-length="255"
|
|
||||||
:placeholder="t('system.config.baseInfo.prometheusPlaceholder')"
|
|
||||||
allow-clear
|
|
||||||
></a-input>
|
|
||||||
<MsFormItemSub
|
|
||||||
:text="t('system.config.baseInfo.prometheusSub', { prometheus: defaultPrometheus })"
|
|
||||||
@fill="fillDefaultPrometheus"
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
|
||||||
</a-form>
|
</a-form>
|
||||||
</MsDrawer>
|
</MsDrawer>
|
||||||
<MsDrawer
|
<MsDrawer
|
||||||
|
@ -239,22 +227,16 @@
|
||||||
const baseFormRef = ref<FormInstance>();
|
const baseFormRef = ref<FormInstance>();
|
||||||
const baseInfo = ref({
|
const baseInfo = ref({
|
||||||
url: 'http://127.0.0.1:8081',
|
url: 'http://127.0.0.1:8081',
|
||||||
prometheusHost: 'http://prometheus:9090',
|
|
||||||
});
|
});
|
||||||
const baseInfoForm = ref({ ...baseInfo.value });
|
const baseInfoForm = ref({ ...baseInfo.value });
|
||||||
const baseInfoDesc = ref<Description[]>([]);
|
const baseInfoDesc = ref<Description[]>([]);
|
||||||
// 默认示例
|
// 默认示例
|
||||||
const defaultUrl = 'https://metersphere.com';
|
const defaultUrl = 'https://metersphere.com';
|
||||||
const defaultPrometheus = 'http://prometheus:9090';
|
|
||||||
|
|
||||||
function fillDefaultUrl() {
|
function fillDefaultUrl() {
|
||||||
baseInfoForm.value.url = defaultUrl;
|
baseInfoForm.value.url = defaultUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
function fillDefaultPrometheus() {
|
|
||||||
baseInfoForm.value.prometheusHost = defaultPrometheus;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化基础信息
|
* 初始化基础信息
|
||||||
*/
|
*/
|
||||||
|
@ -272,10 +254,6 @@
|
||||||
label: t('system.config.pageUrl'),
|
label: t('system.config.pageUrl'),
|
||||||
value: res.url,
|
value: res.url,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: t('system.config.prometheus'),
|
|
||||||
value: res.prometheusHost,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
baseInfoDesc.value = [
|
baseInfoDesc.value = [
|
||||||
|
@ -297,11 +275,8 @@
|
||||||
* 拼接基础信息参数
|
* 拼接基础信息参数
|
||||||
*/
|
*/
|
||||||
function makeBaseInfoParams() {
|
function makeBaseInfoParams() {
|
||||||
const { url, prometheusHost } = baseInfoForm.value;
|
const { url } = baseInfoForm.value;
|
||||||
return [
|
return [{ paramKey: 'base.url', paramValue: url, type: 'text' }];
|
||||||
{ paramKey: 'base.url', paramValue: url, type: 'text' },
|
|
||||||
{ paramKey: 'base.prometheus.host', paramValue: prometheusHost, type: 'text' },
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -78,22 +78,6 @@
|
||||||
<a-option v-for="org of orgOptions" :key="org.id" :value="org.id">{{ org.name }}</a-option>
|
<a-option v-for="org of orgOptions" :key="org.id" :value="org.id">{{ org.name }}</a-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
|
||||||
:label="t('system.resourcePool.use')"
|
|
||||||
field="use"
|
|
||||||
class="form-item"
|
|
||||||
:rules="[{ required: true, message: t('system.resourcePool.useRequired') }]"
|
|
||||||
asterisk-position="end"
|
|
||||||
>
|
|
||||||
<a-checkbox-group v-model:model-value="form.use" @change="() => setIsSave(false)">
|
|
||||||
<a-checkbox v-for="use of useList" :key="use.value" :value="use.value">{{ t(use.label) }}</a-checkbox>
|
|
||||||
</a-checkbox-group>
|
|
||||||
<MsFormItemSub
|
|
||||||
v-if="form.use.length === 3"
|
|
||||||
:text="t('system.resourcePool.allUseTip')"
|
|
||||||
:show-fill-icon="false"
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
|
||||||
<!--TODO:暂无性能测试-->
|
<!--TODO:暂无性能测试-->
|
||||||
<!-- <template v-if="isCheckedPerformance">
|
<!-- <template v-if="isCheckedPerformance">
|
||||||
<a-form-item :label="t('system.resourcePool.mirror')" field="testResourceDTO.loadTestImage" class="form-item">
|
<a-form-item :label="t('system.resourcePool.mirror')" field="testResourceDTO.loadTestImage" class="form-item">
|
||||||
|
@ -583,13 +567,6 @@
|
||||||
rules: [{ required: true, message: t('system.resourcePool.portRequired') }],
|
rules: [{ required: true, message: t('system.resourcePool.portRequired') }],
|
||||||
placeholder: 'system.resourcePool.portPlaceholder',
|
placeholder: 'system.resourcePool.portPlaceholder',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
filed: 'monitor',
|
|
||||||
type: 'input',
|
|
||||||
label: 'system.resourcePool.monitor',
|
|
||||||
rules: [{ required: true, message: t('system.resourcePool.monitorRequired') }],
|
|
||||||
placeholder: 'system.resourcePool.monitorPlaceholder',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
filed: 'concurrentNumber',
|
filed: 'concurrentNumber',
|
||||||
type: 'inputNumber',
|
type: 'inputNumber',
|
||||||
|
@ -628,8 +605,8 @@
|
||||||
// 按顺序拼接:ip、port、monitor、concurrentNumber
|
// 按顺序拼接:ip、port、monitor、concurrentNumber
|
||||||
if (!Object.values(node).every((e) => isEmpty(e))) {
|
if (!Object.values(node).every((e) => isEmpty(e))) {
|
||||||
res += `${node.ip},${node.port === undefined ? '' : node.port},${
|
res += `${node.ip},${node.port === undefined ? '' : node.port},${
|
||||||
node.monitor === undefined ? '' : node.monitor
|
node.concurrentNumber === undefined ? '' : node.concurrentNumber
|
||||||
},${node.concurrentNumber === undefined ? '' : node.concurrentNumber}\r`;
|
}\r`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
editorContent.value = res;
|
editorContent.value = res;
|
||||||
|
@ -655,12 +632,11 @@
|
||||||
if (e.trim() !== '') {
|
if (e.trim() !== '') {
|
||||||
// 排除空串
|
// 排除空串
|
||||||
const line = e.split(',');
|
const line = e.split(',');
|
||||||
if (line.every((s) => s.trim() !== '') && !Number.isNaN(Number(line[3]))) {
|
if (line.every((s) => s.trim() !== '') && !Number.isNaN(Number(line[2]))) {
|
||||||
const item = {
|
const item = {
|
||||||
ip: line[0],
|
ip: line[0],
|
||||||
port: line[1],
|
port: line[1],
|
||||||
monitor: line[2],
|
concurrentNumber: Number(line[2]),
|
||||||
concurrentNumber: Number(line[3]),
|
|
||||||
};
|
};
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
// 第四个是concurrentNumber,需要是数字
|
// 第四个是concurrentNumber,需要是数字
|
||||||
|
|
|
@ -318,7 +318,7 @@
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
label: t('system.resourcePool.detailResources'),
|
label: t('system.resourcePool.detailResources'),
|
||||||
value: nodesList?.map((e) => `${e.ip},${e.port},${e.monitor},${e.concurrentNumber}`),
|
value: nodesList?.map((e) => `${e.ip},${e.port},${e.concurrentNumber}`),
|
||||||
tagTheme: 'light' as Theme,
|
tagTheme: 'light' as Theme,
|
||||||
tagType: 'default' as TagType,
|
tagType: 'default' as TagType,
|
||||||
tagMaxWidth: '280px',
|
tagMaxWidth: '280px',
|
||||||
|
|
Loading…
Reference in New Issue