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