feat(接口测试): mock 页面&部分 bug 修复
This commit is contained in:
parent
f7cdcea873
commit
bb6556ae4d
|
@ -134,12 +134,14 @@
|
|||
</MsTag>
|
||||
</a-tooltip>
|
||||
<div v-if="file.local === true" class="flex items-center">
|
||||
<a-tooltip :content="t('ms.add.attachment.saveAs')">
|
||||
<MsButton type="text" status="secondary" class="!mr-0" @click="handleOpenSaveAs(file)">
|
||||
<MsIcon type="icon-icon_unloading" class="hover:text-[rgb(var(--primary-5))]" size="16" />
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
<a-divider direction="vertical" :margin="4"></a-divider>
|
||||
<template v-if="hasAnyPermission(['PROJECT_FILE_MANAGEMENT:READ+ADD'])">
|
||||
<a-tooltip :content="t('ms.add.attachment.saveAs')">
|
||||
<MsButton type="text" status="secondary" class="!mr-0" @click="handleOpenSaveAs(file)">
|
||||
<MsIcon type="icon-icon_unloading" class="hover:text-[rgb(var(--primary-5))]" size="16" />
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
<a-divider direction="vertical" :margin="4"></a-divider>
|
||||
</template>
|
||||
<a-tooltip :content="t('ms.add.attachment.remove')">
|
||||
<MsButton type="text" status="secondary" @click="handleClose(file)">
|
||||
<MsIcon
|
||||
|
@ -203,6 +205,7 @@
|
|||
import { getAssociatedFileListUrl } from '@/api/modules/case-management/featureCase';
|
||||
import { getModules, getModulesCount } from '@/api/modules/project-management/fileManagement';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { hasAnyPermission } from '@/utils/permission';
|
||||
|
||||
import { AssociatedList } from '@/models/caseManagement/featureCase';
|
||||
import { TableQueryParams, TransferFileParams } from '@/models/common';
|
||||
|
|
|
@ -168,7 +168,6 @@
|
|||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import MsProjectSelect from '@/components/business/ms-project-select/index.vue';
|
||||
import MsTree from '@/components/business/ms-tree/index.vue';
|
||||
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||
import caseLevel from './caseLevel.vue';
|
||||
|
@ -184,7 +183,6 @@
|
|||
import { CaseLinkEnum } from '@/enums/caseEnum';
|
||||
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
|
||||
|
||||
import type { CaseLevel } from './types';
|
||||
import { initGetModuleCountFunc, type RequestModuleEnum } from './utils';
|
||||
|
||||
const router = useRouter();
|
||||
|
|
|
@ -213,7 +213,9 @@
|
|||
:disabled="props.disabled"
|
||||
:data="autoCompleteParams"
|
||||
:placeholder="t('ms.paramsInput.placeholder', { at: '@' })"
|
||||
:class="`ms-params-input ${paramSettingVisible ? 'ms-params-input--focus' : ''}`"
|
||||
:class="`${props.setDefaultClass ? 'ms-params-input--default' : 'ms-params-input'} ${
|
||||
paramSettingVisible ? 'ms-params-input--focus' : ''
|
||||
}`"
|
||||
:trigger-props="{ contentClass: 'ms-params-input-trigger' }"
|
||||
:filter-option="false"
|
||||
:size="props.size"
|
||||
|
@ -271,6 +273,7 @@
|
|||
value: string;
|
||||
disabled?: boolean;
|
||||
size?: 'small' | 'large' | 'medium' | 'mini';
|
||||
setDefaultClass?: boolean;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', val: string): void;
|
||||
|
@ -707,7 +710,8 @@
|
|||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
.ms-params-input {
|
||||
.ms-params-input,
|
||||
.ms-params-input--default {
|
||||
.ms-params-input-suffix-icon,
|
||||
.ms-params-input-suffix-icon--disabled {
|
||||
@apply invisible;
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<slot name="titleRight"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ms-detail-card-desc">
|
||||
<div v-if="showingDescription.length > 0" class="ms-detail-card-desc">
|
||||
<div
|
||||
v-for="item of showingDescription"
|
||||
:key="item.key"
|
||||
|
|
|
@ -58,26 +58,22 @@
|
|||
>
|
||||
<icon-drag-dot-vertical class="absolute left-[-3px] top-[50%] w-[14px]" size="14" />
|
||||
</div>
|
||||
<a-scrollbar class="ms-drawer-body-scrollbar">
|
||||
<div class="ms-drawer-body">
|
||||
<slot>
|
||||
<MsDescription
|
||||
v-if="props.descriptions && props.descriptions.length > 0"
|
||||
:descriptions="props.descriptions"
|
||||
:show-skeleton="props.showSkeleton"
|
||||
:skeleton-line="10"
|
||||
>
|
||||
<template #value="{ item }">
|
||||
<slot name="descValue" :item="item">
|
||||
{{
|
||||
item.value === undefined || item.value === null || item.value?.toString() === '' ? '-' : item.value
|
||||
}}
|
||||
</slot>
|
||||
</template>
|
||||
</MsDescription>
|
||||
</slot>
|
||||
</div>
|
||||
</a-scrollbar>
|
||||
<div class="ms-drawer-body">
|
||||
<slot>
|
||||
<MsDescription
|
||||
v-if="props.descriptions && props.descriptions.length > 0"
|
||||
:descriptions="props.descriptions"
|
||||
:show-skeleton="props.showSkeleton"
|
||||
:skeleton-line="10"
|
||||
>
|
||||
<template #value="{ item }">
|
||||
<slot name="descValue" :item="item">
|
||||
{{ item.value === undefined || item.value === null || item.value?.toString() === '' ? '-' : item.value }}
|
||||
</slot>
|
||||
</template>
|
||||
</MsDescription>
|
||||
</slot>
|
||||
</div>
|
||||
<template #footer>
|
||||
<slot name="footer">
|
||||
<div class="flex items-center justify-between">
|
||||
|
@ -310,15 +306,13 @@
|
|||
.arco-drawer-body {
|
||||
@apply overflow-hidden;
|
||||
}
|
||||
.ms-drawer-body-scrollbar {
|
||||
.ms-drawer-body {
|
||||
@apply h-full w-full overflow-auto;
|
||||
.ms-scroll-bar();
|
||||
|
||||
min-width: 650px;
|
||||
min-height: 500px;
|
||||
}
|
||||
.ms-drawer-body {
|
||||
@apply h-full;
|
||||
}
|
||||
.arco-scrollbar-track-direction-vertical {
|
||||
right: -12px;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<a-tabs v-model:active-key="innerActiveKey" :class="[props.class, props.noContent ? 'no-content' : '']">
|
||||
<a-tab-pane v-for="item of props.contentTabList" :key="item.value" :title="item.label">
|
||||
<template #title>
|
||||
<template v-if="props.showBadge" #title>
|
||||
<a-badge
|
||||
v-if="props.getTextFunc(item.value) !== ''"
|
||||
:class="item.value === innerActiveKey ? 'active-badge' : ''"
|
||||
|
@ -28,8 +28,10 @@
|
|||
class?: string;
|
||||
getTextFunc?: (value: any) => string;
|
||||
noContent?: boolean;
|
||||
showBadge?: boolean;
|
||||
}>(),
|
||||
{
|
||||
showBadge: true,
|
||||
getTextFunc: (value: any) => value,
|
||||
class: '',
|
||||
}
|
||||
|
|
|
@ -268,7 +268,7 @@
|
|||
</style>
|
||||
|
||||
<style>
|
||||
.column-drawer .ms-drawer-body-scrollbar {
|
||||
.column-drawer .ms-drawer-body {
|
||||
min-width: auto !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -199,7 +199,7 @@
|
|||
|
||||
watch(
|
||||
() => appStore.getCurrentTopMenu?.name,
|
||||
(val) => {
|
||||
() => {
|
||||
checkMessageRead();
|
||||
},
|
||||
{
|
||||
|
|
|
@ -22,14 +22,13 @@
|
|||
|
||||
<div class="divider h-full">
|
||||
<Suspense>
|
||||
<TaskCenter group="project" mode="modal"></TaskCenter>
|
||||
<TaskCenter v-if="visible" group="project" mode="modal"></TaskCenter>
|
||||
</Suspense>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, Suspense } from 'vue';
|
||||
import { useVModel } from '@vueuse/core';
|
||||
|
||||
import TaskCenter from '@/views/project-management/taskCenter/component/taskCom.vue';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { ConditionType } from '@/models/apiTest/common';
|
||||
import { RequestBodyFormat, RequestConditionProcessor, ScenarioStepType } from '@/enums/apiEnum';
|
||||
import { RequestBodyFormat, RequestConditionProcessor } from '@/enums/apiEnum';
|
||||
|
||||
// 条件操作类型
|
||||
export type ConditionTypeNameMap = Record<ConditionType, string>;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { RouteEnum } from '@/enums/routeEnum';
|
||||
import { TaskCenterEnum } from '@/enums/taskCenter';
|
||||
|
||||
export const MENU_LEVEL = ['SYSTEM', 'ORGANIZATION', 'PROJECT'] as const; // 菜单级别
|
||||
|
||||
|
@ -321,6 +322,73 @@ export const pathMap: PathMapItem[] = [
|
|||
route: RouteEnum.SETTING_SYSTEM_TASK_CENTER,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[0],
|
||||
children: [
|
||||
{
|
||||
key: 'SETTING_SYSTEM_TASK_CENTER_REAL_TIME', // 系统设置-系统-任务中心-实时任务
|
||||
locale: 'project.taskCenter.real',
|
||||
route: RouteEnum.SETTING_SYSTEM_TASK_CENTER,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[0],
|
||||
children: [
|
||||
{
|
||||
key: 'SETTING_SYSTEM_TASK_CENTER_REAL_TIME_API_CASE', // 系统设置-系统-任务中心-实时任务-接口用例
|
||||
locale: 'project.taskCenter.interfaceCase',
|
||||
route: RouteEnum.SETTING_SYSTEM_TASK_CENTER,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[0],
|
||||
routeQuery: {
|
||||
tab: 'real',
|
||||
type: TaskCenterEnum.API_CASE,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'SETTING_SYSTEM_TASK_CENTER_REAL_TIME_API_SCENARIO', // 系统设置-系统-任务中心-实时任务-接口场景
|
||||
locale: 'project.taskCenter.apiScenario',
|
||||
route: RouteEnum.SETTING_SYSTEM_TASK_CENTER,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[0],
|
||||
routeQuery: {
|
||||
tab: 'real',
|
||||
type: TaskCenterEnum.API_SCENARIO,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'SETTING_SYSTEM_TASK_CENTER_TIME', // 系统设置-系统-任务中心-定时任务
|
||||
locale: 'apiTestManagement.timeTask',
|
||||
route: RouteEnum.SETTING_SYSTEM_TASK_CENTER,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[0],
|
||||
routeQuery: {
|
||||
tab: 'timeTask',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
key: 'SETTING_SYSTEM_TASK_CENTER_TIME_API_SCENARIO', // 系统设置-系统-任务中心-定时任务-接口场景
|
||||
locale: 'project.taskCenter.apiScenario',
|
||||
route: RouteEnum.SETTING_SYSTEM_TASK_CENTER,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[0],
|
||||
routeQuery: {
|
||||
tab: 'timeTask',
|
||||
type: TaskCenterEnum.API_SCENARIO,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'SETTING_SYSTEM_TASK_CENTER_TIME_API_IMPORT', // 系统设置-系统-任务中心-定时任务-接口导入
|
||||
locale: 'project.taskCenter.apiImport',
|
||||
route: RouteEnum.SETTING_SYSTEM_TASK_CENTER,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[0],
|
||||
routeQuery: {
|
||||
tab: 'timeTask',
|
||||
type: TaskCenterEnum.API_IMPORT,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'SETTING_SYSTEM_PLUGIN_MANAGEMENT', // 系统设置-系统-插件管理
|
||||
|
@ -372,6 +440,73 @@ export const pathMap: PathMapItem[] = [
|
|||
route: RouteEnum.SETTING_ORGANIZATION_TASK_CENTER,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[1],
|
||||
children: [
|
||||
{
|
||||
key: 'SETTING_ORGANIZATION_TASK_CENTER_REAL_TIME', // 系统设置-组织-任务中心-实时任务
|
||||
locale: 'project.taskCenter.real',
|
||||
route: RouteEnum.SETTING_ORGANIZATION_TASK_CENTER,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[1],
|
||||
children: [
|
||||
{
|
||||
key: 'SETTING_ORGANIZATION_TASK_CENTER_REAL_TIME_API_CASE', // 系统设置-组织-任务中心-实时任务-接口用例
|
||||
locale: 'project.taskCenter.interfaceCase',
|
||||
route: RouteEnum.SETTING_ORGANIZATION_TASK_CENTER,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[1],
|
||||
routeQuery: {
|
||||
tab: 'real',
|
||||
type: TaskCenterEnum.API_CASE,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'SETTING_ORGANIZATION_TASK_CENTER_REAL_TIME_API_SCENARIO', // 系统设置-组织-任务中心-实时任务-接口场景
|
||||
locale: 'project.taskCenter.apiScenario',
|
||||
route: RouteEnum.SETTING_ORGANIZATION_TASK_CENTER,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[1],
|
||||
routeQuery: {
|
||||
tab: 'real',
|
||||
type: TaskCenterEnum.API_SCENARIO,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'SETTING_ORGANIZATION_TASK_CENTER_TIME', // 系统设置-组织-任务中心-定时任务
|
||||
locale: 'apiTestManagement.timeTask',
|
||||
route: RouteEnum.SETTING_ORGANIZATION_TASK_CENTER,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[1],
|
||||
routeQuery: {
|
||||
tab: 'timeTask',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
key: 'SETTING_ORGANIZATION_TASK_CENTER_TIME_API_SCENARIO', // 系统设置-组织-任务中心-定时任务-接口场景
|
||||
locale: 'project.taskCenter.apiScenario',
|
||||
route: RouteEnum.SETTING_ORGANIZATION_TASK_CENTER,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[1],
|
||||
routeQuery: {
|
||||
tab: 'timeTask',
|
||||
type: TaskCenterEnum.API_SCENARIO,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'SETTING_ORGANIZATION_TASK_CENTER_TIME_API_IMPORT', // 系统设置-组织-任务中心-定时任务-接口导入
|
||||
locale: 'project.taskCenter.apiImport',
|
||||
route: RouteEnum.SETTING_ORGANIZATION_TASK_CENTER,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[1],
|
||||
routeQuery: {
|
||||
tab: 'timeTask',
|
||||
type: TaskCenterEnum.API_IMPORT,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'SETTING_ORGANIZATION_TEMPLATE', // 系统设置-组织-模板管理
|
||||
|
@ -735,11 +870,76 @@ export const pathMap: PathMapItem[] = [
|
|||
key: 'PROJECT_MANAGEMENT_TASK_CENTER', // 项目管理-任务中心
|
||||
locale: 'menu.projectManagement.taskCenter',
|
||||
route: '',
|
||||
routeQuery: {
|
||||
task: 'projectTask',
|
||||
},
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
children: [
|
||||
{
|
||||
key: 'PROJECT_MANAGEMENT_TASK_CENTER_REAL_TIME', // 项目管理-任务中心-实时任务
|
||||
locale: 'project.taskCenter.realTimeTask',
|
||||
route: '',
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
children: [
|
||||
{
|
||||
key: 'PROJECT_MANAGEMENT_TASK_CENTER_REAL_TIME_API_CASE', // 项目管理-任务中心-实时任务-接口用例
|
||||
locale: 'project.taskCenter.interfaceCase',
|
||||
route: '',
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
routeQuery: {
|
||||
task: true,
|
||||
tab: 'real',
|
||||
type: TaskCenterEnum.API_CASE,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'PROJECT_MANAGEMENT_TASK_CENTER_REAL_TIME_API_SCENARIO', // 项目管理-任务中心-实时任务-接口场景
|
||||
locale: 'project.taskCenter.apiScenario',
|
||||
route: '',
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
routeQuery: {
|
||||
task: true,
|
||||
tab: 'real',
|
||||
type: TaskCenterEnum.API_SCENARIO,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'PROJECT_MANAGEMENT_TASK_CENTER_TIME', // 项目管理-任务中心-定时任务
|
||||
locale: 'project.taskCenter.scheduledTask',
|
||||
route: '',
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
children: [
|
||||
{
|
||||
key: 'PROJECT_MANAGEMENT_TASK_CENTER_TIME_API_SCENARIO', // 项目管理-任务中心-定时任务-接口场景
|
||||
locale: 'project.taskCenter.apiScenario',
|
||||
route: '',
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
routeQuery: {
|
||||
task: true,
|
||||
tab: 'timing',
|
||||
type: TaskCenterEnum.API_SCENARIO,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'PROJECT_MANAGEMENT_TASK_CENTER_TIME_API_IMPORT', // 项目管理-任务中心-定时任务-接口导入
|
||||
locale: 'project.taskCenter.apiImport',
|
||||
route: '',
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
routeQuery: {
|
||||
task: true,
|
||||
tab: 'timing',
|
||||
type: TaskCenterEnum.API_IMPORT,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -38,20 +38,20 @@ export default function usePathMap() {
|
|||
*/
|
||||
const jumpRouteByMapKey = (key: PathMapRoute, routeQuery?: Record<string, any>, openNewPage = false) => {
|
||||
const pathNode = findNodeByKey<PathMapItem>(pathMap, key as unknown as string);
|
||||
if (pathNode) {
|
||||
if (pathNode && (pathNode.route || key.includes('PROJECT_MANAGEMENT_TASK_CENTER'))) {
|
||||
if (openNewPage) {
|
||||
window.open(
|
||||
`${window.location.origin}#${router.resolve({ name: pathNode?.route }).fullPath}?${new URLSearchParams({
|
||||
`${window.location.origin}#${router.resolve({ name: pathNode.route }).fullPath}?${new URLSearchParams({
|
||||
...routeQuery,
|
||||
...pathNode?.routeQuery,
|
||||
...pathNode.routeQuery,
|
||||
}).toString()}`
|
||||
);
|
||||
} else {
|
||||
router.push({
|
||||
name: pathNode?.route,
|
||||
name: pathNode.route,
|
||||
query: {
|
||||
...routeQuery,
|
||||
...pathNode?.routeQuery,
|
||||
...pathNode.routeQuery,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
import type { RequestBodyFormat } from '@/enums/apiEnum';
|
||||
|
||||
import type { ExecuteBinaryBody, KeyValueParam, ResponseDefinitionBody } from './common';
|
||||
|
||||
// mock 信息-匹配项
|
||||
export interface MatchRuleItem {
|
||||
key: string;
|
||||
value: string;
|
||||
condition: string;
|
||||
description: string;
|
||||
}
|
||||
// mock 信息-响应内容
|
||||
export interface MockResponse {
|
||||
statusCode: number;
|
||||
headers: KeyValueParam[];
|
||||
useApiResponse: boolean;
|
||||
apiResponseId?: string; // useApiResponse 为 true 时必填
|
||||
body: ResponseDefinitionBody;
|
||||
}
|
||||
// mock 信息-请求通用匹配规则
|
||||
export interface MockMatchRuleCommon {
|
||||
matchRules: MatchRuleItem[];
|
||||
matchAll: boolean;
|
||||
}
|
||||
// mock 信息-请求体匹配规则
|
||||
export interface MockBody {
|
||||
paramType: RequestBodyFormat;
|
||||
formDataMatch: MockMatchRuleCommon;
|
||||
binaryBody: ExecuteBinaryBody;
|
||||
raw: string;
|
||||
}
|
||||
// mock 信息-匹配规则集合
|
||||
export interface MockMatchRule {
|
||||
header: MockMatchRuleCommon;
|
||||
query: MockMatchRuleCommon;
|
||||
rest: MockMatchRuleCommon;
|
||||
body: MockBody;
|
||||
}
|
||||
// mock 信息
|
||||
export interface MockParams {
|
||||
id?: string;
|
||||
projectId: string;
|
||||
name: string;
|
||||
statusCode: number;
|
||||
tags: string[];
|
||||
mockMatchRule: MockMatchRule;
|
||||
response: MockResponse;
|
||||
apiDefinitionId: string;
|
||||
uploadFileIds: string[];
|
||||
linkFileIds: string[];
|
||||
// 前端扩展字段
|
||||
unSaved?: boolean;
|
||||
}
|
|
@ -4,6 +4,7 @@ import type { BreadcrumbItem } from '@/components/business/ms-breadcrumb/types';
|
|||
import { EnvConfig, EnvironmentItem } from '@/models/projectManagement/environmental';
|
||||
import type { LoginConfig, PageConfig, PlatformConfig, ThemeConfig } from '@/models/setting/config';
|
||||
import { ProjectListItem } from '@/models/setting/project';
|
||||
import type { TaskCenterEnum } from '@/enums/taskCenter';
|
||||
|
||||
import type { RouteRecordNormalized, RouteRecordRaw } from 'vue-router';
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
method?: RequestMethods;
|
||||
method: RequestMethods | string;
|
||||
isTag?: boolean;
|
||||
tagSize?: Size;
|
||||
tagBackgroundColor?: string;
|
||||
|
@ -64,7 +64,7 @@
|
|||
|
||||
const methodColor = computed(() => {
|
||||
if (props.method) {
|
||||
const colorMap = colorMaps.find((item) => item.includes.includes(props.method!));
|
||||
const colorMap = colorMaps.find((item) => item.includes.includes(props.method as RequestMethods));
|
||||
return colorMap?.color || 'rgb(var(--link-7))'; // 方法映射内找不到对应的 key 说明是插件,所有的插件协议颜色都是一样的
|
||||
}
|
||||
return 'rgb(var(--link-7))';
|
||||
|
|
|
@ -471,7 +471,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useClipboard, useVModel } from '@vueuse/core';
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
import { InputInstance, Message } from '@arco-design/web-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
|
@ -506,7 +506,7 @@
|
|||
XPathExtract,
|
||||
} from '@/models/apiTest/common';
|
||||
import { ParamsRequestType } from '@/models/projectManagement/commonScript';
|
||||
import { DataSourceItem, EnvConfig } from '@/models/projectManagement/environmental';
|
||||
import { DataSourceItem } from '@/models/projectManagement/environmental';
|
||||
import {
|
||||
RequestConditionProcessor,
|
||||
RequestExtractEnvType,
|
||||
|
@ -624,10 +624,6 @@ if (!result){
|
|||
|
||||
const scriptDefinedRef = ref<InstanceType<typeof MsScriptDefined>>();
|
||||
|
||||
function undoScript() {
|
||||
scriptDefinedRef.value?.undoHandler();
|
||||
}
|
||||
|
||||
function clearScript() {
|
||||
condition.value.script = '';
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
</div>
|
||||
<div v-else class="flex min-w-[42px] items-center justify-between">
|
||||
<div class="one-line-text">
|
||||
{{ item.name || t(conditionTypeNameMap[item.processorType as keyof typeof conditionTypeNameMap]) }}</div
|
||||
{{ t(conditionTypeNameMap[item.processorType as keyof typeof conditionTypeNameMap]) }}</div
|
||||
>
|
||||
<a-badge
|
||||
v-if="item.processorType === RequestConditionProcessor.REQUEST_SCRIPT"
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
ResponseAssertionItem,
|
||||
ResponseDefinition,
|
||||
} from '@/models/apiTest/common';
|
||||
import type { MockParams } from '@/models/apiTest/mock';
|
||||
import {
|
||||
RequestAssertionCondition,
|
||||
RequestBodyFormat,
|
||||
|
@ -236,3 +237,99 @@ export const regexDefaultParamItem = {
|
|||
responseFormat: ResponseBodyXPathAssertionFormat.XML,
|
||||
moreSettingPopoverVisible: false,
|
||||
};
|
||||
// mock 默认参数
|
||||
export const mockDefaultParams: MockParams = {
|
||||
projectId: '',
|
||||
name: '',
|
||||
statusCode: 200,
|
||||
tags: [],
|
||||
mockMatchRule: {
|
||||
header: {
|
||||
matchRules: [
|
||||
{
|
||||
key: '',
|
||||
value: '',
|
||||
condition: 'EQUALS',
|
||||
description: '',
|
||||
},
|
||||
],
|
||||
matchAll: true,
|
||||
},
|
||||
query: {
|
||||
matchRules: [
|
||||
{
|
||||
key: '',
|
||||
value: '',
|
||||
condition: 'EQUALS',
|
||||
description: '',
|
||||
},
|
||||
],
|
||||
matchAll: true,
|
||||
},
|
||||
rest: {
|
||||
matchRules: [
|
||||
{
|
||||
key: '',
|
||||
value: '',
|
||||
condition: 'EQUALS',
|
||||
description: '',
|
||||
},
|
||||
],
|
||||
matchAll: true,
|
||||
},
|
||||
body: {
|
||||
paramType: RequestBodyFormat.FORM_DATA,
|
||||
formDataMatch: {
|
||||
matchRules: [
|
||||
{
|
||||
key: '',
|
||||
value: '',
|
||||
condition: 'EQUALS',
|
||||
description: '',
|
||||
},
|
||||
],
|
||||
matchAll: true,
|
||||
},
|
||||
binaryBody: {
|
||||
description: '',
|
||||
file: undefined,
|
||||
sendAsBody: false,
|
||||
},
|
||||
raw: '',
|
||||
},
|
||||
},
|
||||
response: {
|
||||
statusCode: 200,
|
||||
headers: [
|
||||
{
|
||||
key: '',
|
||||
value: '',
|
||||
description: '',
|
||||
},
|
||||
],
|
||||
useApiResponse: false,
|
||||
apiResponseId: '',
|
||||
body: {
|
||||
bodyType: ResponseBodyFormat.JSON,
|
||||
jsonBody: {
|
||||
jsonValue: '',
|
||||
enableJsonSchema: false,
|
||||
enableTransition: false,
|
||||
},
|
||||
xmlBody: {
|
||||
value: '',
|
||||
},
|
||||
rawBody: {
|
||||
value: '',
|
||||
},
|
||||
binaryBody: {
|
||||
description: '',
|
||||
file: undefined,
|
||||
sendAsBody: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
apiDefinitionId: '',
|
||||
uploadFileIds: [],
|
||||
linkFileIds: [],
|
||||
};
|
||||
|
|
|
@ -828,9 +828,11 @@
|
|||
() => props.params,
|
||||
(arr) => {
|
||||
if (arr.length > 0) {
|
||||
let hasNoIdItem = false;
|
||||
paramsData.value = arr.map((item, i) => {
|
||||
if (!item) {
|
||||
// 批量添加过来的数据最后一行会是 undefined
|
||||
hasNoIdItem = true;
|
||||
return {
|
||||
...cloneDeep(props.defaultParamItem),
|
||||
id: new Date().getTime() + i,
|
||||
|
@ -838,6 +840,7 @@
|
|||
}
|
||||
if (!item.id) {
|
||||
// 后台存储无id,渲染时需要手动添加一次
|
||||
hasNoIdItem = true;
|
||||
return {
|
||||
...item,
|
||||
id: new Date().getTime() + i,
|
||||
|
@ -845,7 +848,7 @@
|
|||
}
|
||||
return item;
|
||||
});
|
||||
if (!filterKeyValParams(arr, props.defaultParamItem).lastDataIsDefault && !props.isTreeTable) {
|
||||
if (hasNoIdItem && !filterKeyValParams(arr, props.defaultParamItem).lastDataIsDefault && !props.isTreeTable) {
|
||||
addTableLine(arr.length - 1, false, true);
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -325,9 +325,11 @@
|
|||
emit('change');
|
||||
}
|
||||
|
||||
function handleParamTableChange(resultArr: any[]) {
|
||||
function handleParamTableChange(resultArr: any[], isInit?: boolean) {
|
||||
currentTableParams.value = [...resultArr];
|
||||
emit('change');
|
||||
if (!isInit) {
|
||||
emit('change');
|
||||
}
|
||||
}
|
||||
|
||||
function changeBodyFormat(val: RequestBodyFormat) {
|
||||
|
|
|
@ -114,7 +114,7 @@
|
|||
<mockTable
|
||||
:active-module="props.activeModule"
|
||||
:offspring-ids="props.offspringIds"
|
||||
:protocol="activeApiTab.protocol"
|
||||
:definition-detail="activeApiTab"
|
||||
is-api
|
||||
/>
|
||||
</a-tab-pane>
|
||||
|
|
|
@ -64,6 +64,7 @@
|
|||
:active-module="props.activeModule"
|
||||
:offspring-ids="props.offspringIds"
|
||||
:protocol="props.protocol"
|
||||
:definition-detail="activeApiTab"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
<template>
|
||||
<MsDrawer
|
||||
v-model:visible="visible"
|
||||
unmount-on-close
|
||||
:title="t('caseManagement.featureCase.caseDetail')"
|
||||
:width="960"
|
||||
:footer="false"
|
||||
no-content-padding
|
||||
>
|
||||
</MsDrawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
required: true,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -2,13 +2,16 @@
|
|||
<MsDrawer
|
||||
v-model:visible="visible"
|
||||
unmount-on-close
|
||||
:title="t('mockManagement.mockDetail')"
|
||||
:title="mockDetail.id ? t('mockManagement.mockDetail') : t('mockManagement.createMock')"
|
||||
:width="960"
|
||||
:footer="false"
|
||||
:footer="!mockDetail.id || isEdit"
|
||||
:ok-text="isEdit ? t('common.save') : t('common.create')"
|
||||
:save-continue-text="t('mockManagement.saveAndContinue')"
|
||||
:show-continue="!isEdit"
|
||||
no-content-padding
|
||||
>
|
||||
<template #tbutton>
|
||||
<div 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]">
|
||||
<MsButton
|
||||
v-permission="['PROJECT_API_DEFINITION_MOCK:READ+UPDATE']"
|
||||
type="icon"
|
||||
|
@ -29,94 +32,180 @@
|
|||
</MsButton>
|
||||
</div>
|
||||
</template>
|
||||
<MsDetailCard :title="`【${mockDetail.num}】${mockDetail.name}`" :description="[]" class="mb-[16px]">
|
||||
<template #titleRight>
|
||||
<div class="flex items-center gap-[16px]">
|
||||
<div class="flex items-center gap-[8px]">
|
||||
<div class="whitespace-nowrap text-[var(--color-text-4)]">{{ t('apiTestManagement.apiType') }}</div>
|
||||
<apiMethodName :method="mockDetail.method" tag-size="small" is-tag />
|
||||
<a-spin :loading="loading" class="block p-[16px]">
|
||||
<MsDetailCard
|
||||
:title="`【${props.definitionDetail.num}】${props.definitionDetail.name}`"
|
||||
:description="[]"
|
||||
class="mb-[16px]"
|
||||
>
|
||||
<template #titleRight>
|
||||
<div class="flex items-center gap-[16px]">
|
||||
<div class="flex items-center gap-[8px]">
|
||||
<div class="whitespace-nowrap text-[var(--color-text-4)]">{{ t('apiTestManagement.apiType') }}</div>
|
||||
<apiMethodName :method="props.definitionDetail.method" tag-size="small" is-tag />
|
||||
</div>
|
||||
<div class="flex items-center gap-[8px]">
|
||||
<div class="whitespace-nowrap text-[var(--color-text-4)]">{{ t('apiTestManagement.path') }}</div>
|
||||
<a-tooltip :content="props.definitionDetail.url">
|
||||
<div class="one-line-text">{{ props.definitionDetail.url }}</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-[8px]">
|
||||
<div class="whitespace-nowrap text-[var(--color-text-4)]">{{ t('apiTestManagement.path') }}</div>
|
||||
<a-tooltip :content="mockDetail.apiPath">
|
||||
<div class="one-line-text">{{ mockDetail.apiPath }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</MsDetailCard>
|
||||
<a-form ref="mockForm" :model="mockDetail">
|
||||
<a-form-item
|
||||
class="hidden-item"
|
||||
field="name"
|
||||
:rules="[{ required: true, message: t('mockManagement.nameNotNull') }]"
|
||||
>
|
||||
<a-input
|
||||
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') }]">
|
||||
<MsTagsInput
|
||||
v-model:model-value="mockDetail.tags"
|
||||
class="mb-[16px] w-[732px]"
|
||||
allow-clear
|
||||
unique-value
|
||||
retain-input-value
|
||||
:max-tag-count="5"
|
||||
:disabled="isReadOnly"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="font-medium">{{ t('mockManagement.matchRule') }}</div>
|
||||
<MsTab
|
||||
v-model:active-key="activeTab"
|
||||
:content-tab-list="mockTabList"
|
||||
:get-text-func="getTabBadge"
|
||||
class="no-content relative my-[8px] border-b"
|
||||
/>
|
||||
<mockMatchRuleForm
|
||||
v-if="
|
||||
activeTab === RequestComposition.HEADER ||
|
||||
activeTab === RequestComposition.QUERY ||
|
||||
activeTab === RequestComposition.REST
|
||||
"
|
||||
v-model:matchAll="currentMatchAll"
|
||||
v-model:matchRules="currentMatchRules"
|
||||
:key-options="currentKeyOptions"
|
||||
/>
|
||||
<template v-else>
|
||||
<div class="mb-[8px] flex items-center justify-between">
|
||||
<a-radio-group
|
||||
v-model:model-value="mockDetail.mockMatchRule.body.paramType"
|
||||
type="button"
|
||||
size="small"
|
||||
@change="handleMockBodyTypeChange"
|
||||
>
|
||||
<a-radio v-for="item of RequestBodyFormat" :key="item" :value="item">
|
||||
{{ requestBodyTypeMap[item] }}
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
<div
|
||||
v-if="mockDetail.mockMatchRule.body.paramType === RequestBodyFormat.NONE"
|
||||
class="flex h-[100px] items-center justify-center rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] text-[var(--color-text-4)]"
|
||||
>
|
||||
{{ t('apiTestDebug.noneBody') }}
|
||||
</div>
|
||||
<mockMatchRuleForm
|
||||
v-else-if="
|
||||
[RequestBodyFormat.FORM_DATA, RequestBodyFormat.WWW_FORM].includes(mockDetail.mockMatchRule.body.paramType)
|
||||
"
|
||||
v-model:matchAll="mockDetail.mockMatchRule.body.formDataMatch.matchAll"
|
||||
v-model:matchRules="mockDetail.mockMatchRule.body.formDataMatch.matchRules"
|
||||
:key-options="currentBodyKeyOptions"
|
||||
/>
|
||||
<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]">
|
||||
<MsAddAttachment
|
||||
v-model:file-list="fileList"
|
||||
mode="input"
|
||||
:multiple="false"
|
||||
:fields="{
|
||||
id: 'fileId',
|
||||
name: 'fileName',
|
||||
}"
|
||||
@change="handleFileChange"
|
||||
/>
|
||||
</div>
|
||||
<!-- <div class="flex items-center">
|
||||
<a-switch v-model:model-value="innerParams.binarySend" class="mr-[8px]" size="small" type="line"></a-switch>
|
||||
<span>{{ t('apiTestDebug.sendAsMainText') }}</span>
|
||||
<a-tooltip position="right">
|
||||
<template #content>
|
||||
<div>{{ t('apiTestDebug.sendAsMainTextTip1') }}</div>
|
||||
<div>{{ t('apiTestDebug.sendAsMainTextTip2') }}</div>
|
||||
</template>
|
||||
<icon-question-circle
|
||||
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</div> -->
|
||||
</div>
|
||||
<div v-else class="flex h-[300px]">
|
||||
<MsCodeEditor
|
||||
v-model:model-value="mockDetail.mockMatchRule.body.raw"
|
||||
class="flex-1"
|
||||
theme="vs"
|
||||
height="100%"
|
||||
:show-full-screen="false"
|
||||
:show-theme-change="false"
|
||||
:show-code-format="true"
|
||||
:language="currentCodeLanguage"
|
||||
>
|
||||
</MsCodeEditor>
|
||||
</div>
|
||||
</template>
|
||||
</MsDetailCard>
|
||||
<a-form ref="mockForm" :model="mockDetail">
|
||||
<a-form-item
|
||||
class="hidden-item"
|
||||
field="name"
|
||||
:rules="[{ required: true, message: t('mockManagement.nameNotNull') }]"
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="mockDetail.name"
|
||||
:placeholder="t('mockManagement.namePlaceholder')"
|
||||
class="w-[732px]"
|
||||
:disabled="isReadOnly"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item class="hidden-item" :rules="[{ required: true, message: t('mockManagement.nameNotNull') }]">
|
||||
<MsTagsInput
|
||||
v-model:model-value="mockDetail.tags"
|
||||
class="w-[732px]"
|
||||
:placeholder="t('mockManagement.namePlaceholder')"
|
||||
allow-clear
|
||||
unique-value
|
||||
retain-input-value
|
||||
:max-tag-count="5"
|
||||
:disabled="isReadOnly"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="mb-[8px] font-medium">{{ t('mockManagement.matchRule') }}</div>
|
||||
<div class="mb-[8px] flex items-center justify-between">
|
||||
<a-radio-group v-model:model-value="mockDetail.bodyType" type="button" size="small" :disabled="isReadOnly">
|
||||
<a-radio v-for="item of RequestBodyFormat" :key="item" :value="item">
|
||||
{{ requestBodyTypeMap[item] }}
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
<div
|
||||
v-if="mockDetail.bodyType === RequestBodyFormat.NONE"
|
||||
class="flex h-[100px] items-center justify-center rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] text-[var(--color-text-4)]"
|
||||
>
|
||||
{{ t('apiTestDebug.noneBody') }}
|
||||
</div>
|
||||
<div v-else class="flex h-[calc(100%-34px)]">
|
||||
<MsCodeEditor
|
||||
v-model:model-value="currentBodyCode"
|
||||
:read-only="isReadOnly"
|
||||
class="flex-1"
|
||||
theme="vs"
|
||||
height="100%"
|
||||
:show-full-screen="false"
|
||||
:show-theme-change="false"
|
||||
:show-code-format="true"
|
||||
:language="currentCodeLanguage"
|
||||
>
|
||||
</MsCodeEditor>
|
||||
</div>
|
||||
<mockResponse
|
||||
v-model:mock-response="mockDetail.response"
|
||||
:definition-responses="props.definitionDetail.responseDefinition || []"
|
||||
/>
|
||||
</a-spin>
|
||||
</MsDrawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
||||
import { LanguageEnum } from '@/components/pure/ms-code-editor/types';
|
||||
import MsDetailCard from '@/components/pure/ms-detail-card/index.vue';
|
||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import MsTab from '@/components/pure/ms-tab/index.vue';
|
||||
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
|
||||
import { MsFileItem } from '@/components/pure/ms-upload/types';
|
||||
import MsAddAttachment from '@/components/business/ms-add-attachment/index.vue';
|
||||
import mockMatchRuleForm from './mockMatchRuleForm.vue';
|
||||
import mockResponse from './mockResponse.vue';
|
||||
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
||||
import { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||
|
||||
import { requestBodyTypeMap } from '@/config/apiTest';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { RequestBodyFormat } from '@/enums/apiEnum';
|
||||
import { MockParams } from '@/models/apiTest/mock';
|
||||
import { RequestBodyFormat, RequestComposition } from '@/enums/apiEnum';
|
||||
|
||||
import {
|
||||
defaultHeaderParamsItem,
|
||||
defaultRequestParamsItem,
|
||||
mockDefaultParams,
|
||||
} from '@/views/api-test/components/config';
|
||||
import { filterKeyValParams } from '@/views/api-test/components/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
definitionDetail: RequestParam;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'delete'): void;
|
||||
}>();
|
||||
|
@ -127,42 +216,216 @@
|
|||
required: true,
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
const isEdit = ref(false);
|
||||
const mockDetail = ref<any>();
|
||||
const isReadOnly = computed(() => !isEdit.value && !mockDetail.value.id);
|
||||
const mockDetail = ref<MockParams>(cloneDeep(mockDefaultParams));
|
||||
const isReadOnly = computed(() => !!mockDetail.value.id && !isEdit.value);
|
||||
const activeTab = ref<RequestComposition>(RequestComposition.BODY);
|
||||
const mockTabList = [
|
||||
{
|
||||
value: RequestComposition.BODY,
|
||||
label: t('apiTestDebug.body'),
|
||||
},
|
||||
{
|
||||
value: RequestComposition.HEADER,
|
||||
label: t('apiTestDebug.header'),
|
||||
},
|
||||
{
|
||||
value: RequestComposition.QUERY,
|
||||
label: 'Query',
|
||||
},
|
||||
{
|
||||
value: RequestComposition.REST,
|
||||
label: RequestComposition.REST,
|
||||
},
|
||||
];
|
||||
|
||||
// 当前显示的代码
|
||||
const currentBodyCode = computed({
|
||||
/**
|
||||
* 获取 tab 的参数数量徽标
|
||||
*/
|
||||
function getTabBadge(tabKey: RequestComposition) {
|
||||
switch (tabKey) {
|
||||
case RequestComposition.HEADER:
|
||||
const headerNum = filterKeyValParams(mockDetail.value.mockMatchRule.header.matchRules, defaultHeaderParamsItem)
|
||||
.validParams.length;
|
||||
return `${headerNum > 0 ? headerNum : ''}`;
|
||||
case RequestComposition.BODY:
|
||||
return mockDetail.value.mockMatchRule.body.paramType !== RequestBodyFormat.NONE ? '1' : '';
|
||||
case RequestComposition.QUERY:
|
||||
const queryNum = filterKeyValParams(mockDetail.value.mockMatchRule.query.matchRules, defaultRequestParamsItem)
|
||||
.validParams.length;
|
||||
return `${queryNum > 0 ? queryNum : ''}`;
|
||||
case RequestComposition.REST:
|
||||
const restNum = filterKeyValParams(mockDetail.value.mockMatchRule.rest.matchRules, defaultRequestParamsItem)
|
||||
.validParams.length;
|
||||
return `${restNum > 0 ? restNum : ''}`;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function handleMockBodyTypeChange() {
|
||||
mockDetail.value.unSaved = true;
|
||||
}
|
||||
|
||||
const currentMatchAll = computed({
|
||||
get() {
|
||||
if (mockDetail.value.bodyType === RequestBodyFormat.JSON) {
|
||||
return mockDetail.value.jsonBody.jsonValue;
|
||||
switch (activeTab.value) {
|
||||
case RequestComposition.HEADER:
|
||||
return mockDetail.value.mockMatchRule.header.matchAll;
|
||||
case RequestComposition.QUERY:
|
||||
return mockDetail.value.mockMatchRule.query.matchAll;
|
||||
case RequestComposition.REST:
|
||||
return mockDetail.value.mockMatchRule.rest.matchAll;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
if (mockDetail.value.bodyType === RequestBodyFormat.XML) {
|
||||
return mockDetail.value.xmlBody.value;
|
||||
}
|
||||
return mockDetail.value.rawBody.value;
|
||||
},
|
||||
set(val) {
|
||||
if (mockDetail.value.bodyType === RequestBodyFormat.JSON) {
|
||||
mockDetail.value.jsonBody.jsonValue = val;
|
||||
} else if (mockDetail.value.bodyType === RequestBodyFormat.XML) {
|
||||
mockDetail.value.xmlBody.value = val;
|
||||
} else {
|
||||
mockDetail.value.rawBody.value = val;
|
||||
switch (activeTab.value) {
|
||||
case RequestComposition.HEADER:
|
||||
mockDetail.value.mockMatchRule.header.matchAll = val;
|
||||
break;
|
||||
case RequestComposition.QUERY:
|
||||
mockDetail.value.mockMatchRule.query.matchAll = val;
|
||||
break;
|
||||
case RequestComposition.REST:
|
||||
mockDetail.value.mockMatchRule.rest.matchAll = val;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
});
|
||||
const currentMatchRules = computed({
|
||||
get() {
|
||||
switch (activeTab.value) {
|
||||
case RequestComposition.HEADER:
|
||||
return mockDetail.value.mockMatchRule.header.matchRules;
|
||||
case RequestComposition.QUERY:
|
||||
return mockDetail.value.mockMatchRule.query.matchRules;
|
||||
case RequestComposition.REST:
|
||||
return mockDetail.value.mockMatchRule.rest.matchRules;
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
},
|
||||
set(val) {
|
||||
switch (activeTab.value) {
|
||||
case RequestComposition.HEADER:
|
||||
mockDetail.value.mockMatchRule.header.matchRules = val;
|
||||
break;
|
||||
case RequestComposition.QUERY:
|
||||
mockDetail.value.mockMatchRule.query.matchRules = val;
|
||||
break;
|
||||
case RequestComposition.REST:
|
||||
mockDetail.value.mockMatchRule.rest.matchRules = val;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
});
|
||||
const currentKeyOptions = computed(() => {
|
||||
switch (activeTab.value) {
|
||||
case RequestComposition.HEADER:
|
||||
return props.definitionDetail.headers.filter((e) => ({
|
||||
label: e.key,
|
||||
value: e.value,
|
||||
}));
|
||||
case RequestComposition.QUERY:
|
||||
return props.definitionDetail.query.filter((e) => ({
|
||||
label: e.key,
|
||||
value: e.value,
|
||||
}));
|
||||
case RequestComposition.REST:
|
||||
return props.definitionDetail.rest.filter((e) => ({
|
||||
label: e.key,
|
||||
value: e.value,
|
||||
}));
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
});
|
||||
const currentBodyKeyOptions = computed(() => {
|
||||
switch (mockDetail.value.mockMatchRule.body.paramType) {
|
||||
case RequestBodyFormat.FORM_DATA:
|
||||
return props.definitionDetail.body.formDataBody.formValues.filter((e) => ({
|
||||
label: e.key,
|
||||
value: e.value,
|
||||
}));
|
||||
case RequestBodyFormat.WWW_FORM:
|
||||
return props.definitionDetail.body.wwwFormBody.formValues.filter((e) => ({
|
||||
label: e.key,
|
||||
value: e.value,
|
||||
}));
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
});
|
||||
// 当前代码编辑器的语言
|
||||
const currentCodeLanguage = computed(() => {
|
||||
if (mockDetail.value.bodyType === RequestBodyFormat.JSON) {
|
||||
if (mockDetail.value.mockMatchRule.body.paramType === RequestBodyFormat.JSON) {
|
||||
return LanguageEnum.JSON;
|
||||
}
|
||||
if (mockDetail.value.bodyType === RequestBodyFormat.XML) {
|
||||
if (mockDetail.value.mockMatchRule.body.paramType === RequestBodyFormat.XML) {
|
||||
return LanguageEnum.XML;
|
||||
}
|
||||
return LanguageEnum.PLAINTEXT;
|
||||
});
|
||||
|
||||
const fileList = ref<MsFileItem[]>([]);
|
||||
|
||||
async function handleFileChange(files: MsFileItem[], file?: MsFileItem) {
|
||||
try {
|
||||
if (file?.local && file.file) {
|
||||
// 本地上传
|
||||
loading.value = true;
|
||||
const res = await Promise.resolve({ data: 'fileId' });
|
||||
mockDetail.value.mockMatchRule.body.binaryBody.file = {
|
||||
...file,
|
||||
fileId: res.data,
|
||||
fileName: file?.name || '',
|
||||
fileAlias: file?.name || '',
|
||||
local: true,
|
||||
};
|
||||
loading.value = false;
|
||||
} else {
|
||||
// 关联文件
|
||||
mockDetail.value.mockMatchRule.body.binaryBody.file = {
|
||||
...fileList.value[0],
|
||||
fileId: fileList.value[0]?.uid,
|
||||
fileName: fileList.value[0]?.originalName || '',
|
||||
fileAlias: fileList.value[0]?.name || '',
|
||||
local: false,
|
||||
};
|
||||
}
|
||||
if (
|
||||
mockDetail.value.mockMatchRule.body.binaryBody.file &&
|
||||
!mockDetail.value.mockMatchRule.body.binaryBody.file.fileId
|
||||
) {
|
||||
mockDetail.value.mockMatchRule.body.binaryBody.file = undefined;
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => mockDetail.value.mockMatchRule.body.paramType,
|
||||
(val) => {
|
||||
if (val === RequestBodyFormat.JSON) {
|
||||
mockDetail.value.mockMatchRule.body.raw = props.definitionDetail.body.jsonBody.jsonValue;
|
||||
} else if (val === RequestBodyFormat.XML) {
|
||||
mockDetail.value.mockMatchRule.body.raw = props.definitionDetail.body.xmlBody.value || '';
|
||||
} else if (val === RequestBodyFormat.RAW) {
|
||||
mockDetail.value.mockMatchRule.body.raw = props.definitionDetail.body.rawBody.value || '';
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function handleDelete() {
|
||||
emit('delete');
|
||||
}
|
||||
|
|
|
@ -0,0 +1,228 @@
|
|||
<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-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">
|
||||
<paramDescInput
|
||||
v-model:desc="item.description"
|
||||
@input="() => addMatchRule(idx)"
|
||||
@dblclick="quickInputDesc(item)"
|
||||
@change="handleDescChange"
|
||||
/>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-form>
|
||||
<a-modal
|
||||
v-model:visible="showQuickInputParam"
|
||||
:title="t('ms.paramsInput.value')"
|
||||
:ok-text="t('apiTestDebug.apply')"
|
||||
:ok-button-props="{ disabled: !quickInputParamValue || quickInputParamValue.trim() === '' }"
|
||||
class="ms-modal-form"
|
||||
body-class="!p-0"
|
||||
:width="680"
|
||||
title-align="start"
|
||||
@ok="applyQuickInputParam"
|
||||
@close="clearQuickInputParam"
|
||||
>
|
||||
<MsCodeEditor
|
||||
v-if="showQuickInputParam"
|
||||
v-model:model-value="quickInputParamValue"
|
||||
theme="vs"
|
||||
height="300px"
|
||||
:show-full-screen="false"
|
||||
>
|
||||
<template #rightTitle>
|
||||
<div class="flex justify-between">
|
||||
<div class="text-[var(--color-text-4)]">
|
||||
{{ t('apiTestDebug.quickInputParamsTip') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MsCodeEditor>
|
||||
</a-modal>
|
||||
<!-- <a-modal
|
||||
v-model:visible="showQuickInputDesc"
|
||||
:title="t('apiTestDebug.desc')"
|
||||
:ok-text="t('common.save')"
|
||||
:ok-button-props="{ disabled: !quickInputDescValue || quickInputDescValue.trim() === '' }"
|
||||
class="ms-modal-form"
|
||||
body-class="!p-0"
|
||||
:width="480"
|
||||
title-align="start"
|
||||
:auto-size="{ minRows: 2 }"
|
||||
@ok="applyQuickInputDesc"
|
||||
@close="clearQuickInputDesc"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:model-value="quickInputDescValue"
|
||||
:placeholder="t('apiTestDebug.descPlaceholder')"
|
||||
:max-length="1000"
|
||||
></a-textarea>
|
||||
</a-modal> -->
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { FormInstance, SelectOptionData } from '@arco-design/web-vue';
|
||||
|
||||
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
||||
import MsParamsInput from '@/components/business/ms-params-input/index.vue';
|
||||
|
||||
// import paramDescInput from '@/views/api-test/components/paramDescInput.vue';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
const props = defineProps<{
|
||||
keyOptions: SelectOptionData[];
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(
|
||||
e: 'change',
|
||||
form: {
|
||||
matchAll: boolean;
|
||||
matchRules: Record<string, any>[];
|
||||
},
|
||||
isInit?: boolean
|
||||
): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const matchAll = defineModel<boolean>('matchAll', {
|
||||
required: true,
|
||||
});
|
||||
const matchRules = defineModel<Record<string, any>[]>('matchRules', {
|
||||
required: true,
|
||||
});
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
const formModel = ref({
|
||||
matchAll: matchAll.value,
|
||||
matchRules: matchRules.value,
|
||||
});
|
||||
|
||||
function handleDeleteItem(index: number) {
|
||||
matchRules.value.splice(index, 1);
|
||||
}
|
||||
|
||||
function addMatchRule(rowIndex: number) {
|
||||
if (rowIndex === matchRules.value.length - 1) {
|
||||
matchRules.value.push({
|
||||
key: '',
|
||||
value: '',
|
||||
description: '',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function emitChange(from: string, isInit?: boolean) {
|
||||
emit('change', formModel.value, isInit);
|
||||
}
|
||||
|
||||
const showQuickInputParam = ref(false);
|
||||
const activeQuickInputRecord = ref<any>({});
|
||||
const quickInputParamValue = ref('');
|
||||
|
||||
function quickInputParams(record: any) {
|
||||
activeQuickInputRecord.value = record;
|
||||
showQuickInputParam.value = true;
|
||||
quickInputParamValue.value = record.value;
|
||||
}
|
||||
|
||||
function clearQuickInputParam() {
|
||||
activeQuickInputRecord.value = {};
|
||||
quickInputParamValue.value = '';
|
||||
}
|
||||
|
||||
function applyQuickInputParam() {
|
||||
activeQuickInputRecord.value.value = quickInputParamValue.value;
|
||||
showQuickInputParam.value = false;
|
||||
addMatchRule(matchRules.value.findIndex((e) => e.id === activeQuickInputRecord.value.id));
|
||||
clearQuickInputParam();
|
||||
emitChange('applyQuickInputParam');
|
||||
}
|
||||
|
||||
// const showQuickInputDesc = ref(false);
|
||||
// const quickInputDescValue = ref('');
|
||||
|
||||
// function quickInputDesc(record: any) {
|
||||
// activeQuickInputRecord.value = record;
|
||||
// showQuickInputDesc.value = true;
|
||||
// quickInputDescValue.value = record.description;
|
||||
// }
|
||||
|
||||
// function clearQuickInputDesc() {
|
||||
// activeQuickInputRecord.value = {};
|
||||
// quickInputDescValue.value = '';
|
||||
// }
|
||||
|
||||
// function applyQuickInputDesc() {
|
||||
// activeQuickInputRecord.value.description = quickInputDescValue.value;
|
||||
// showQuickInputDesc.value = false;
|
||||
// addMatchRule(matchRules.value.findIndex((e) => e.id === activeQuickInputRecord.value.id));
|
||||
// clearQuickInputDesc();
|
||||
// emitChange('applyQuickInputDesc');
|
||||
// }
|
||||
|
||||
// function handleDescChange() {
|
||||
// emitChange('handleDescChange');
|
||||
// }
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -0,0 +1,304 @@
|
|||
<template>
|
||||
<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>
|
||||
{{ t('mockManagement.followDefinition') }}
|
||||
</div>
|
||||
<template v-if="!mockResponse.useApiResponse">
|
||||
<MsTab
|
||||
v-model:active-key="activeTab"
|
||||
:content-tab-list="responseCompositionTabList"
|
||||
class="no-content relative my-[8px] border-b"
|
||||
:show-badge="false"
|
||||
/>
|
||||
<div class="mt-[8px]">
|
||||
<template v-if="activeTab === ResponseComposition.BODY">
|
||||
<div class="mb-[8px] flex items-center justify-between">
|
||||
<a-radio-group
|
||||
v-model:model-value="mockResponse.body.bodyType"
|
||||
type="button"
|
||||
size="small"
|
||||
@change="(val) => emit('change')"
|
||||
>
|
||||
<a-radio
|
||||
v-for="item of ResponseBodyFormat"
|
||||
v-show="item !== ResponseBodyFormat.NONE"
|
||||
:key="item"
|
||||
:value="item"
|
||||
>
|
||||
{{ ResponseBodyFormat[item].toLowerCase() }}
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
<!-- <div v-if="mockResponse.body.bodyType === ResponseBodyFormat.JSON" class="ml-auto flex items-center">
|
||||
<a-radio-group
|
||||
v-model:model-value="mockResponse.body.jsonBody.enableJsonSchema"
|
||||
size="mini"
|
||||
@change="emit('change')"
|
||||
>
|
||||
<a-radio :value="false">Json</a-radio>
|
||||
<a-radio class="mr-0" :value="true"> Json Schema </a-radio>
|
||||
</a-radio-group>
|
||||
<div class="flex items-center gap-[8px]">
|
||||
<a-switch v-model:model-value="mockResponse.body.jsonBody.enableTransition" size="small" type="line" />
|
||||
{{ t('apiTestManagement.dynamicConversion') }}
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
[ResponseBodyFormat.JSON, ResponseBodyFormat.XML, ResponseBodyFormat.RAW].includes(
|
||||
mockResponse.body.bodyType
|
||||
)
|
||||
"
|
||||
>
|
||||
<!-- <MsJsonSchema
|
||||
v-if="mockResponse.body.jsonBody.enableJsonSchema"
|
||||
:data="mockResponse.body.jsonBody.jsonSchema"
|
||||
:columns="jsonSchemaColumns"
|
||||
/> -->
|
||||
<MsCodeEditor
|
||||
ref="responseEditorRef"
|
||||
v-model:model-value="currentBodyCode"
|
||||
:language="currentCodeLanguage"
|
||||
theme="vs"
|
||||
:show-full-screen="false"
|
||||
:show-theme-change="false"
|
||||
:show-language-change="false"
|
||||
:show-charset-change="false"
|
||||
show-code-format
|
||||
>
|
||||
</MsCodeEditor>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="mb-[16px] flex justify-between gap-[8px] bg-[var(--color-text-n9)] p-[12px]">
|
||||
<a-input
|
||||
v-model:model-value="mockResponse.body.binaryBody.description"
|
||||
:placeholder="t('common.desc')"
|
||||
:max-length="255"
|
||||
/>
|
||||
<MsAddAttachment
|
||||
v-model:file-list="fileList"
|
||||
mode="input"
|
||||
:multiple="false"
|
||||
:fields="{
|
||||
id: 'fileId',
|
||||
name: 'fileName',
|
||||
}"
|
||||
@change="handleFileChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<a-switch
|
||||
v-model:model-value="mockResponse.body.binaryBody.sendAsBody"
|
||||
class="mr-[8px]"
|
||||
size="small"
|
||||
type="line"
|
||||
></a-switch>
|
||||
<span>{{ t('apiTestDebug.sendAsMainText') }}</span>
|
||||
<a-tooltip position="right">
|
||||
<template #content>
|
||||
<div>{{ t('apiTestDebug.sendAsMainTextTip1') }}</div>
|
||||
<div>{{ t('apiTestDebug.sendAsMainTextTip2') }}</div>
|
||||
</template>
|
||||
<icon-question-circle
|
||||
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<paramTable
|
||||
v-else-if="activeTab === ResponseComposition.HEADER"
|
||||
:params="mockResponse.headers"
|
||||
:columns="columns"
|
||||
:default-param-item="defaultKeyValueParamItem"
|
||||
:selectable="false"
|
||||
@change="handleResponseTableChange"
|
||||
/>
|
||||
<a-select
|
||||
v-else
|
||||
v-model:model-value="mockResponse.statusCode"
|
||||
:options="statusCodeOptions"
|
||||
class="w-[200px]"
|
||||
@change="() => emit('change')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else class="mt-[8px]">
|
||||
<a-select
|
||||
v-model:model-value="mockResponse.apiResponseId"
|
||||
:options="mockResponseOptions"
|
||||
class="w-[150px]"
|
||||
></a-select>
|
||||
</div>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
||||
import { LanguageEnum } from '@/components/pure/ms-code-editor/types';
|
||||
import MsTab from '@/components/pure/ms-tab/index.vue';
|
||||
import { MsFileItem } from '@/components/pure/ms-upload/types';
|
||||
import MsAddAttachment from '@/components/business/ms-add-attachment/index.vue';
|
||||
import paramTable, { ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
|
||||
import { ResponseItem } from '@/views/api-test/components/requestComposition/response/edit.vue';
|
||||
|
||||
import { responseHeaderOption } from '@/config/apiTest';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { MockResponse } from '@/models/apiTest/mock';
|
||||
import { ResponseBodyFormat, ResponseComposition } from '@/enums/apiEnum';
|
||||
|
||||
import { defaultKeyValueParamItem, statusCodes } from '@/views/api-test/components/config';
|
||||
|
||||
const props = defineProps<{
|
||||
definitionResponses: ResponseItem[];
|
||||
uploadTempFileApi?: (...args: any) => Promise<any>; // 上传临时文件接口
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'change'): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const activeTab = ref<ResponseComposition>(ResponseComposition.BODY);
|
||||
const mockResponse = defineModel<MockResponse>('mockResponse', {
|
||||
required: true,
|
||||
});
|
||||
|
||||
const mockResponseOptions = computed(() =>
|
||||
props.definitionResponses.map((item) => ({
|
||||
label: `${t(item.label || item.name)}(${item.statusCode})`,
|
||||
value: item.id,
|
||||
}))
|
||||
);
|
||||
|
||||
const responseCompositionTabList = [
|
||||
{
|
||||
label: t('apiTestDebug.responseBody'),
|
||||
value: ResponseComposition.BODY,
|
||||
},
|
||||
{
|
||||
label: t('apiTestDebug.responseHeader'),
|
||||
value: ResponseComposition.HEADER,
|
||||
},
|
||||
{
|
||||
label: t('apiTestManagement.responseCode'),
|
||||
value: ResponseComposition.CODE,
|
||||
},
|
||||
];
|
||||
|
||||
const statusCodeOptions = statusCodes.map((e) => ({
|
||||
label: e.toString(),
|
||||
value: e,
|
||||
}));
|
||||
|
||||
const columns: ParamTableColumn[] = [
|
||||
{
|
||||
title: 'apiTestManagement.paramName',
|
||||
dataIndex: 'key',
|
||||
slotName: 'key',
|
||||
inputType: 'autoComplete',
|
||||
autoCompleteParams: responseHeaderOption,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.paramVal',
|
||||
dataIndex: 'value',
|
||||
slotName: 'value',
|
||||
isNormal: true,
|
||||
inputType: 'input',
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'operation',
|
||||
slotName: 'operation',
|
||||
width: 35,
|
||||
},
|
||||
];
|
||||
|
||||
function handleResponseTableChange(arr: any[]) {
|
||||
mockResponse.value.headers = [...arr];
|
||||
emit('change');
|
||||
}
|
||||
|
||||
// 当前显示的代码
|
||||
const currentBodyCode = computed({
|
||||
get() {
|
||||
if (mockResponse.value.body.bodyType === ResponseBodyFormat.JSON) {
|
||||
return mockResponse.value.body.jsonBody.jsonValue;
|
||||
}
|
||||
if (mockResponse.value.body.bodyType === ResponseBodyFormat.XML) {
|
||||
return mockResponse.value.body.xmlBody.value;
|
||||
}
|
||||
return mockResponse.value.body.rawBody.value;
|
||||
},
|
||||
set(val) {
|
||||
if (mockResponse.value.body.bodyType === ResponseBodyFormat.JSON) {
|
||||
mockResponse.value.body.jsonBody.jsonValue = val;
|
||||
} else if (mockResponse.value.body.bodyType === ResponseBodyFormat.XML) {
|
||||
mockResponse.value.body.xmlBody.value = val;
|
||||
} else {
|
||||
mockResponse.value.body.rawBody.value = val;
|
||||
}
|
||||
},
|
||||
});
|
||||
// 当前代码编辑器的语言
|
||||
const currentCodeLanguage = computed(() => {
|
||||
if (mockResponse.value.body.bodyType === ResponseBodyFormat.JSON) {
|
||||
return LanguageEnum.JSON;
|
||||
}
|
||||
if (mockResponse.value.body.bodyType === ResponseBodyFormat.XML) {
|
||||
return LanguageEnum.XML;
|
||||
}
|
||||
return LanguageEnum.PLAINTEXT;
|
||||
});
|
||||
|
||||
const fileList = ref<MsFileItem[]>([]);
|
||||
const loading = ref<boolean>(false);
|
||||
async function handleFileChange() {
|
||||
try {
|
||||
if (fileList.value[0] && fileList.value[0].local && fileList.value[0].file && props.uploadTempFileApi) {
|
||||
loading.value = true;
|
||||
const res = await props.uploadTempFileApi(fileList.value[0].file);
|
||||
mockResponse.value.body.binaryBody.file = {
|
||||
...fileList.value[0],
|
||||
fileId: res.data,
|
||||
fileName: fileList.value[0]?.name || '',
|
||||
fileAlias: fileList.value[0]?.name || '',
|
||||
local: true,
|
||||
};
|
||||
loading.value = false;
|
||||
} else if (fileList.value[0]) {
|
||||
mockResponse.value.body.binaryBody.file = {
|
||||
...fileList.value[0],
|
||||
fileId: fileList.value[0].uid,
|
||||
fileName: fileList.value[0]?.originalName || '',
|
||||
fileAlias: fileList.value[0]?.name || '',
|
||||
local: false,
|
||||
};
|
||||
} else {
|
||||
mockResponse.value.body.binaryBody.file = undefined;
|
||||
}
|
||||
emit('change');
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.response-head {
|
||||
@apply flex flex-wrap items-center justify-between border-b;
|
||||
|
||||
padding: 8px 16px;
|
||||
border-color: var(--color-text-n8);
|
||||
gap: 8px;
|
||||
}
|
||||
.arco-tabs-content {
|
||||
@apply hidden;
|
||||
}
|
||||
</style>
|
|
@ -66,8 +66,7 @@
|
|||
</template>
|
||||
</ms-base-table>
|
||||
</div>
|
||||
<mockDetailDrawer v-model:visible="mockDetailDrawerVisible" />
|
||||
<mockDebugDrawer v-model:visible="mockDebugDrawerVisible" />
|
||||
<mockDetailDrawer v-model:visible="mockDetailDrawerVisible" :definition-detail="props.definitionDetail" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -80,6 +79,7 @@
|
|||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
|
||||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||
import { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||
|
||||
import {
|
||||
deleteDefinitionMockMock,
|
||||
|
@ -97,14 +97,13 @@
|
|||
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||
|
||||
const mockDetailDrawer = defineAsyncComponent(() => import('./mockDetailDrawer.vue'));
|
||||
const mockDebugDrawer = defineAsyncComponent(() => import('./mockDebugDrawer.vue'));
|
||||
|
||||
const props = defineProps<{
|
||||
isApi?: boolean; // 接口定义详情的case tab下
|
||||
class?: string;
|
||||
activeModule: string;
|
||||
offspringIds: string[];
|
||||
protocol: string; // 查看的协议类型
|
||||
definitionDetail: RequestParam;
|
||||
readOnly?: boolean; // 是否是只读模式
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
|
@ -262,7 +261,7 @@
|
|||
);
|
||||
|
||||
watch(
|
||||
() => props.protocol,
|
||||
() => props.definitionDetail.protocol,
|
||||
() => {
|
||||
loadMockList();
|
||||
}
|
||||
|
|
|
@ -214,4 +214,7 @@ export default {
|
|||
'mockManagement.namePlaceholder': '请输入期望名称',
|
||||
'mockManagement.nameNotNull': '期望名称不能为空',
|
||||
'mockManagement.matchRule': '匹配规则',
|
||||
'mockManagement.saveAndContinue': '保存并继续创建',
|
||||
'mockManagement.paramNameNotNull': '参数名称不能为空',
|
||||
'mockManagement.followDefinition': '跟随 API 定义',
|
||||
};
|
||||
|
|
|
@ -99,19 +99,19 @@
|
|||
<apiMethodName v-if="checkStepShowMethod(step)" :method="step.config.method" />
|
||||
<div
|
||||
v-if="step.uniqueId === showStepNameEditInputStepId"
|
||||
class="name-warp absolute left-0 top-[-2px] z-10 w-[calc(100%-24px)]"
|
||||
class="name-warp absolute left-0 top-[-1px] z-10 w-[450px]"
|
||||
@click.stop
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="tempStepName"
|
||||
:placeholder="t('apiScenario.pleaseInputStepName')"
|
||||
:max-length="255"
|
||||
size="small"
|
||||
size="mini"
|
||||
@press-enter="applyStepNameChange(step)"
|
||||
@blur="applyStepNameChange(step)"
|
||||
/>
|
||||
</div>
|
||||
<a-tooltip :content="step.name">
|
||||
<a-tooltip v-else :content="step.name">
|
||||
<div class="step-name-container">
|
||||
<div class="one-line-text mr-[4px] max-w-[350px] font-medium text-[var(--color-text-1)]">
|
||||
{{ step.name }}
|
||||
|
@ -129,20 +129,20 @@
|
|||
<template v-else>
|
||||
<div
|
||||
v-if="step.uniqueId === showStepDescEditInputStepId"
|
||||
class="desc-warp absolute left-0 top-[-2px] z-10 w-[calc(100%-24px)]"
|
||||
class="desc-warp absolute left-0 top-[-1px] z-10 w-[450px]"
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="tempStepDesc"
|
||||
:default-value="step.name || t('apiScenario.pleaseInputStepDesc')"
|
||||
:placeholder="t('apiScenario.pleaseInputStepDesc')"
|
||||
:max-length="255"
|
||||
size="small"
|
||||
size="mini"
|
||||
@press-enter="applyStepDescChange(step)"
|
||||
@blur="applyStepDescChange(step)"
|
||||
@click.stop
|
||||
>
|
||||
<template #prefix>
|
||||
{{ t('common.desc') }}
|
||||
<div class="text-[12px] leading-[20px]">{{ t('common.desc') }}</div>
|
||||
</template>
|
||||
</a-input>
|
||||
</div>
|
||||
|
@ -1875,10 +1875,9 @@
|
|||
background-color: var(--color-text-n9) !important;
|
||||
}
|
||||
.step-node-content {
|
||||
@apply flex w-full flex-1 items-center;
|
||||
@apply flex w-full flex-1 flex-nowrap items-center;
|
||||
|
||||
gap: 8px;
|
||||
margin-right: 6px;
|
||||
}
|
||||
.step-name-container {
|
||||
@apply flex items-center;
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
ref="executeButtonRef"
|
||||
v-permission="['PROJECT_API_SCENARIO:READ+EXECUTE']"
|
||||
:execute-loading="activeScenarioTab.executeLoading"
|
||||
@execute="handleExecute"
|
||||
@execute="(type) => handleExecute(type)"
|
||||
@stop-debug="handleStopExecute"
|
||||
/>
|
||||
<a-button
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
v-model:model-value="record.value"
|
||||
:placeholder="t('common.pleaseSelect')"
|
||||
class="param-input w-full min-w-[250px]"
|
||||
:disabled="!record.enable || !hasAnyPermission(permissionsMap[props.group][props.moduleType].edit)"
|
||||
:disabled="!record.enable || !hasAnyPermission(permissionsMap[props.group][props.moduleType]?.edit)"
|
||||
@change="() => changeRunRules(record)"
|
||||
>
|
||||
<a-option v-for="item of syncFrequencyOptions" :key="item.value" :value="item.value">
|
||||
|
@ -112,12 +112,12 @@
|
|||
size="small"
|
||||
type="line"
|
||||
:before-change="() => handleBeforeEnableChange(record)"
|
||||
:disabled="!hasAnyPermission(permissionsMap[props.group][props.moduleType].edit)"
|
||||
:disabled="!hasAnyPermission(permissionsMap[props.group][props.moduleType]?.edit)"
|
||||
/>
|
||||
<a-divider direction="vertical" />
|
||||
<MsButton
|
||||
class="!mr-0"
|
||||
:disabled="!hasAnyPermission(permissionsMap[props.group][props.moduleType].edit)"
|
||||
:disabled="!hasAnyPermission(permissionsMap[props.group][props.moduleType]?.edit)"
|
||||
@click="delSchedule(record)"
|
||||
>{{ t('common.delete') }}
|
||||
</MsButton>
|
||||
|
@ -259,7 +259,7 @@
|
|||
},
|
||||
};
|
||||
const hasOperationPermission = computed(() =>
|
||||
hasAnyPermission([...permissionsMap[props.group][props.moduleType].edit])
|
||||
hasAnyPermission([...(permissionsMap[props.group][props.moduleType]?.edit || '')])
|
||||
);
|
||||
|
||||
const columns: MsTableColumn = [
|
||||
|
@ -411,12 +411,12 @@
|
|||
{
|
||||
label: 'project.taskCenter.batchEnable',
|
||||
eventTag: 'batchEnable',
|
||||
anyPermission: permissionsMap[props.group][props.moduleType].edit,
|
||||
anyPermission: permissionsMap[props.group][props.moduleType]?.edit,
|
||||
},
|
||||
{
|
||||
label: 'project.taskCenter.batchDisable',
|
||||
eventTag: 'batchDisable',
|
||||
anyPermission: permissionsMap[props.group][props.moduleType].edit,
|
||||
anyPermission: permissionsMap[props.group][props.moduleType]?.edit,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import ApiCase from './apiCase.vue';
|
||||
import ScheduledTask from './scheduledTask.vue';
|
||||
|
@ -33,13 +33,14 @@
|
|||
import type { ResourceTypeMapKey } from './utils';
|
||||
|
||||
const { t } = useI18n();
|
||||
const activeTab = ref<ResourceTypeMapKey>(TaskCenterEnum.API_CASE);
|
||||
|
||||
const props = defineProps<{
|
||||
group: 'system' | 'organization' | 'project';
|
||||
mode?: 'modal' | 'normal';
|
||||
}>();
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const realTabList = ref([
|
||||
{
|
||||
value: TaskCenterEnum.API_CASE,
|
||||
|
@ -75,7 +76,8 @@
|
|||
},
|
||||
]);
|
||||
|
||||
const activeTask = ref('real');
|
||||
const activeTask = ref(route.query.tab || 'real');
|
||||
const activeTab = ref<ResourceTypeMapKey>((route.query.type as ResourceTypeMapKey) || TaskCenterEnum.API_CASE);
|
||||
|
||||
const rightTabList = computed(() => {
|
||||
return activeTask.value === 'real' ? realTabList.value : timingTabList.value;
|
||||
|
|
|
@ -135,9 +135,9 @@
|
|||
{{ t(typeOptions.find((e) => e.value === record.type)?.label || '') }}
|
||||
</template>
|
||||
<template #content="{ record }">
|
||||
<div v-if="record.module === 'SYSTEM' || record.type === 'DELETE'" class="one-line-text">{{
|
||||
record.content
|
||||
}}</div>
|
||||
<div v-if="record.module === 'SYSTEM' || record.type === 'DELETE'" class="one-line-text">
|
||||
{{ record.content }}
|
||||
</div>
|
||||
<MsButton v-else @click="handleNameClick(record)">
|
||||
<div class="one-line-text">
|
||||
{{ record.content }}
|
||||
|
@ -186,6 +186,7 @@
|
|||
const props = defineProps<{
|
||||
mode: (typeof MENU_LEVEL)[number]; // 日志展示模式,系统/组织/项目
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const appStore = useAppStore();
|
||||
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||
import TaskCenter from '@/views/project-management/taskCenter/component/taskCom.vue';
|
||||
</script>
|
||||
|
|
Loading…
Reference in New Issue