feat(接口调试): 接口调试文件参数联调&问题修复

This commit is contained in:
baiqi 2024-02-21 18:43:10 +08:00 committed by Craftsman
parent d199f398ba
commit 716e5ead03
29 changed files with 780 additions and 278 deletions

View File

@ -4,6 +4,7 @@ import {
AddDebugModuleUrl, AddDebugModuleUrl,
DeleteDebugModuleUrl, DeleteDebugModuleUrl,
DeleteDebugUrl, DeleteDebugUrl,
DragDebugUrl,
ExecuteApiDebugUrl, ExecuteApiDebugUrl,
GetApiDebugDetailUrl, GetApiDebugDetailUrl,
GetDebugModuleCountUrl, GetDebugModuleCountUrl,
@ -11,6 +12,7 @@ import {
MoveDebugModuleUrl, MoveDebugModuleUrl,
UpdateApiDebugUrl, UpdateApiDebugUrl,
UpdateDebugModuleUrl, UpdateDebugModuleUrl,
UploadTempFileUrl,
} from '@/api/requrls/api-test/debug'; } from '@/api/requrls/api-test/debug';
import { import {
@ -21,7 +23,7 @@ import {
UpdateDebugModule, UpdateDebugModule,
UpdateDebugParams, UpdateDebugParams,
} from '@/models/apiTest/debug'; } from '@/models/apiTest/debug';
import { ModuleTreeNode, MoveModules } from '@/models/common'; import { DragSortParams, ModuleTreeNode, MoveModules } from '@/models/common';
// 获取模块树 // 获取模块树
export function getDebugModules() { export function getDebugModules() {
@ -53,6 +55,11 @@ export function getDebugModuleCount(data: { keyword: string }) {
return MSR.post({ url: GetDebugModuleCountUrl, data }); return MSR.post({ url: GetDebugModuleCountUrl, data });
} }
// 拖拽调试节点
export function dragDebug(data: DragSortParams) {
return MSR.post({ url: DragDebugUrl, data });
}
// 执行调试 // 执行调试
export function executeDebug(data: ExecuteRequestParams) { export function executeDebug(data: ExecuteRequestParams) {
return MSR.post<ExecuteRequestParams>({ url: ExecuteApiDebugUrl, data }); return MSR.post<ExecuteRequestParams>({ url: ExecuteApiDebugUrl, data });
@ -77,3 +84,8 @@ export function getDebugDetail(id: string) {
export function deleteDebug(id: string) { export function deleteDebug(id: string) {
return MSR.get({ url: DeleteDebugUrl, params: id }); return MSR.get({ url: DeleteDebugUrl, params: id });
} }
// 上传文件
export function uploadTempFile(file: File) {
return MSR.uploadFile({ url: UploadTempFileUrl }, { fileList: [file] }, 'file');
}

View File

@ -9,3 +9,5 @@ export const GetDebugModuleCountUrl = '/api/debug/module/count'; // 模块统计
export const AddDebugModuleUrl = '/api/debug/module/add'; // 添加模块 export const AddDebugModuleUrl = '/api/debug/module/add'; // 添加模块
export const GetDebugModulesUrl = '/api/debug/module/tree'; // 查询模块树 export const GetDebugModulesUrl = '/api/debug/module/tree'; // 查询模块树
export const DeleteDebugModuleUrl = '/api/debug/module/delete'; // 删除模块 export const DeleteDebugModuleUrl = '/api/debug/module/delete'; // 删除模块
export const DragDebugUrl = '/api/debug/edit/pos'; // 拖拽调试节点
export const UploadTempFileUrl = '/api/debug/upload/temp/file'; // 上传文件

View File

@ -0,0 +1,64 @@
<template>
<a-dropdown position="tl" trigger="hover">
<a-button size="mini" type="outline">
<template #icon> <icon-upload class="text-[14px] !text-[rgb(var(--primary-5))]" /> </template>
</a-button>
<template #content>
<a-upload
ref="uploadRef"
v-model:file-list="innerFileList"
:auto-upload="false"
:show-file-list="false"
:before-upload="beforeUpload"
@change="handleChange"
>
<template #upload-button>
<a-button size="small" type="text" class="ms-add-attachment-dropdown-btn">
<icon-upload class="mr-[8px]" />{{ t('ms.add.attachment.localUpload') }}
</a-button>
</template>
</a-upload>
<a-button size="small" type="text" class="ms-add-attachment-dropdown-btn" @click="emit('linkFile')">
<MsIcon type="icon-icon_link-copy_outlined" class="mr-[8px]" size="16" />
{{ t('ms.add.attachment.associateFile') }}
</a-button>
</template>
</a-dropdown>
</template>
<script setup lang="ts">
import { MsFileItem } from '@/components/pure/ms-upload/types';
import { useI18n } from '@/hooks/useI18n';
const emit = defineEmits<{
(e: 'upload', file: File): void;
(e: 'change', _fileList: MsFileItem[], fileItem: MsFileItem): void;
(e: 'linkFile'): void;
}>();
const { t } = useI18n();
const innerFileList = defineModel<MsFileItem[]>('fileList', {
required: true,
});
function beforeUpload(file: File) {
emit('upload', file);
}
function handleChange(_fileList: MsFileItem[], fileItem: MsFileItem) {
emit('change', _fileList, fileItem);
}
</script>
<style lang="less" scoped>
.ms-add-attachment-dropdown-btn {
padding-right: 8px;
padding-left: 8px;
color: var(--color-text-1) !important;
&:hover {
background: rgb(var(--primary-1)) !important;
}
}
</style>

View File

@ -1,12 +1,12 @@
<template> <template>
<a-form-item field="attachment" :label="t('caseManagement.featureCase.addAttachment')"> <a-form-item v-if="props.mode === 'button'" field="attachment" :label="t('caseManagement.featureCase.addAttachment')">
<div class="flex flex-col"> <div class="flex flex-col">
<div class="mb-1"> <div class="mb-1">
<a-dropdown position="tr" trigger="hover"> <a-dropdown position="tr" trigger="hover">
<a-button type="outline"> <a-button type="outline">
<template #icon> <icon-plus class="text-[14px]" /> </template <template #icon> <icon-plus class="text-[14px]" /> </template>
>{{ t('system.orgTemplate.addAttachment') }}</a-button {{ t('system.orgTemplate.addAttachment') }}
> </a-button>
<template #content> <template #content>
<a-upload <a-upload
ref="uploadRef" ref="uploadRef"
@ -17,61 +17,212 @@
@change="handleChange" @change="handleChange"
> >
<template #upload-button> <template #upload-button>
<a-button type="text" class="!text-[var(--color-text-1)]"> <a-button type="text" class="arco-dropdown-option !text-[var(--color-text-1)]">
<icon-upload />{{ t('caseManagement.featureCase.uploadFile') }}</a-button <icon-upload />{{ t('caseManagement.featureCase.uploadFile') }}
> </a-button>
</template> </template>
</a-upload> </a-upload>
<a-button type="text" class="!text-[var(--color-text-1)]" @click="associatedFile"> <a-button type="text" class="arco-dropdown-option !text-[var(--color-text-1)]" @click="associatedFile">
<MsIcon type="icon-icon_link-copy_outlined" size="16" />{{ <MsIcon type="icon-icon_link-copy_outlined" size="16" />
t('caseManagement.featureCase.associatedFile') {{ t('caseManagement.featureCase.associatedFile') }}
}}</a-button </a-button>
>
</template> </template>
</a-dropdown> </a-dropdown>
</div> </div>
<div class="!hover:bg-[rgb(var(--primary-1))] !text-[var(--color-text-4)]">{{ <div class="!hover:bg-[rgb(var(--primary-1))] !text-[var(--color-text-4)]">
t('system.orgTemplate.addAttachmentTip') {{ t('system.orgTemplate.addAttachmentTip') }}
}}</div> </div>
</div> </div>
</a-form-item> </a-form-item>
<template v-else>
<div v-if="props.multiple" class="flex w-full items-center gap-[4px]">
<dropdownMenu v-model:file-list="innerFileList" @link-file="associatedFile" @change="handleChange" />
<MsTagsInput
v-model:model-value="inputFiles"
:class="props.inputClass"
placeholder=" "
:max-tag-count="1"
readonly
>
<template #tag="{ data }">
<MsTag :closable="!data.label.includes('+')" class="m-0 p-0" @close="() => handleClose(data)">
{{ data.label }}
</MsTag>
</template>
</MsTagsInput>
</div>
<div v-else class="flex w-full items-center gap-[4px]">
<dropdownMenu v-model:file-list="innerFileList" @link-file="associatedFile" @change="handleChange" />
<a-input
v-model:model-value="inputFileName"
:class="props.inputClass"
allow-clear
readonly
@clear="handleFileClear"
>
</a-input>
</div>
</template>
<LinkFileDrawer
v-model:visible="showDrawer"
:get-tree-request="getModules"
:get-count-request="getModulesCount"
:get-list-request="getAssociatedFileListUrl"
:get-list-fun-params="getListFunParams"
:selector-type="props.multiple ? 'checkbox' : 'radio'"
@save="saveSelectAssociatedFile"
/>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useVModel } from '@vueuse/core'; import { useVModel } from '@vueuse/core';
import { TagData } from '@arco-design/web-vue';
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
import type { MsFileItem } from '@/components/pure/ms-upload/types'; import type { MsFileItem } from '@/components/pure/ms-upload/types';
import LinkFileDrawer from '@/components/business/ms-link-file/associatedFileDrawer.vue';
import dropdownMenu from './dropdownMenu.vue';
import { getAssociatedFileListUrl } from '@/api/modules/case-management/featureCase';
import { getModules, getModulesCount } from '@/api/modules/project-management/fileManagement';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { AssociatedList } from '@/models/caseManagement/featureCase';
import { TableQueryParams } from '@/models/common';
import { convertToFile } from '@/views/case-management/caseManagementFeature/components/utils';
const props = withDefaults(
defineProps<{
mode: 'button' | 'input';
fileList: MsFileItem[];
multiple?: boolean;
inputClass?: string;
fields: {
id: string;
name: string;
};
}>(),
{
mode: 'button',
multiple: true,
fields: () => ({
id: 'uid',
name: 'name',
}),
}
);
const emit = defineEmits<{
(e: 'update:fileList', fileList: MsFileItem[]): void;
(e: 'upload', file: File): void;
(e: 'change', _fileList: MsFileItem[], fileItem?: MsFileItem): void;
(e: 'linkFile'): void;
(e: 'deleteFile', fileId?: string | number): void;
}>();
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps<{
fileList: MsFileItem[];
}>();
const emit = defineEmits<{
(e: 'upload', file: File): void;
(e: 'change', _fileList: MsFileItem[], fileItem: MsFileItem): void;
(e: 'linkFile'): void;
(e: 'update:fileList', fileList: MsFileItem[]): void;
}>();
// const innerFileList = ref<MsFileItem[]>([]);
const innerFileList = useVModel(props, 'fileList', emit); const innerFileList = useVModel(props, 'fileList', emit);
const inputFileName = ref('');
const inputFiles = ref<TagData[]>([]);
const showDrawer = ref(false);
const getListFunParams = ref<TableQueryParams>({
combine: {
hiddenIds: [],
},
});
onBeforeMount(() => {
//
const defaultFiles = props.fileList.filter((item) => item) || [];
if (defaultFiles.length > 0) {
if (props.multiple) {
inputFiles.value = defaultFiles.map((item) => ({
//
value: item?.[props.fields.id] || '',
label: item?.[props.fields.name] || '',
}));
} else {
inputFileName.value = defaultFiles[0]?.[props.fields.name] || '';
}
getListFunParams.value.combine.hiddenIds = defaultFiles
.filter((item) => !item?.local)
.map((item) => item?.[props.fields.id] || '')
.filter((item) => item);
}
});
function beforeUpload(file: File) { function beforeUpload(file: File) {
emit('upload', file); emit('upload', file);
} }
function handleChange(_fileList: MsFileItem[], fileItem: MsFileItem) { function handleChange(_fileList: MsFileItem[], fileItem: MsFileItem) {
innerFileList.value = _fileList; innerFileList.value = _fileList.map((item) => ({ ...item, local: true }));
if (props.multiple) {
inputFiles.value = _fileList.map((item) => ({
value: item?.uid || '',
label: item?.name || '',
}));
} else {
inputFileName.value = fileItem.name || '';
}
emit('change', _fileList, fileItem); emit('change', _fileList, fileItem);
} }
function associatedFile() { function associatedFile() {
emit('linkFile'); // TODO:
if (props.mode === 'button') {
emit('linkFile');
} else {
showDrawer.value = true;
}
}
//
watch(
() => innerFileList.value,
() => {
getListFunParams.value.combine.hiddenIds = innerFileList.value
.filter((item) => !item.local)
.map((item) => item[props.fields.id] || item.uid);
},
{ deep: true, immediate: true }
);
//
function saveSelectAssociatedFile(fileData: AssociatedList[]) {
const fileResultList = fileData.map((fileInfo) => convertToFile(fileInfo));
if (props.mode === 'button') {
innerFileList.value.push(...fileResultList);
} else if (props.multiple) {
innerFileList.value.push(...fileResultList);
inputFiles.value.push(
...fileResultList.map((item) => ({
value: item?.uid || '',
label: item?.name || '',
}))
);
} else {
//
innerFileList.value = fileResultList;
inputFileName.value = fileResultList[0].name || '';
}
emit('change', innerFileList.value);
}
function handleClose(data: TagData) {
inputFiles.value = inputFiles.value.filter((item) => item.value !== data.value);
innerFileList.value = innerFileList.value.filter((item) => item[props.fields.id] !== data.value);
emit('deleteFile', data.value);
}
function handleFileClear() {
inputFileName.value = '';
inputFiles.value = [];
innerFileList.value = [];
emit('change', []);
} }
</script> </script>
<style scoped></style> <style lang="less" scoped></style>

View File

@ -0,0 +1,4 @@
export default {
'ms.add.attachment.localUpload': 'Local upload',
'ms.add.attachment.associateFile': 'Associate file',
};

View File

@ -0,0 +1,4 @@
export default {
'ms.add.attachment.localUpload': '本地上传',
'ms.add.attachment.associateFile': '关联文件',
};

View File

@ -52,7 +52,7 @@
</template> </template>
<a-input <a-input
v-if="model.type === 'input'" v-if="model.type === 'input'"
v-model="element[model.filed]" v-model:model-value="element[model.filed]"
class="flex-1" class="flex-1"
:placeholder="t(model.placeholder || '')" :placeholder="t(model.placeholder || '')"
:max-length="model.maxLength || 255" :max-length="model.maxLength || 255"
@ -61,11 +61,12 @@
/> />
<a-input-number <a-input-number
v-if="model.type === 'inputNumber'" v-if="model.type === 'inputNumber'"
v-model="element[model.filed]" v-model:model-value="element[model.filed]"
class="flex-1" class="flex-1"
:placeholder="t(model.placeholder || '')" :placeholder="t(model.placeholder || '')"
:min="model.min" :min="model.min"
:max="model.max || 9999999" :max="model.max || 9999999"
model-event="input"
allow-clear allow-clear
@change="emit('change')" @change="emit('change')"
/> />

View File

@ -17,7 +17,7 @@
:placeholder="t('project.commonScript.pleaseSelected')" :placeholder="t('project.commonScript.pleaseSelected')"
@change="changeHandler" @change="changeHandler"
> >
<a-option v-for="item of languages" :key="item.value"> <a-option v-for="item of languages" :key="item.value" :value="item.value">
<a-tooltip :content="item.text"> <a-tooltip :content="item.text">
{{ item.text }} {{ item.text }}
</a-tooltip> </a-tooltip>
@ -64,8 +64,8 @@
(e: 'update:languagesType', value: Language): void; (e: 'update:languagesType', value: Language): void;
(e: 'insert', code: string): void; (e: 'insert', code: string): void;
(e: 'formApiImport'): void; // api (e: 'formApiImport'): void; // api
(e: 'insertCommonScript'): void; // api (e: 'insertCommonScript'): void; //
(e: 'updateLanguages', value: Language): void; // api (e: 'updateLanguages', value: Language): void; //
}>(); }>();
const innerExpand = useVModel(props, 'expand', emit); const innerExpand = useVModel(props, 'expand', emit);
@ -73,11 +73,11 @@
const innerLanguageType = useVModel(props, 'languagesType', emit); const innerLanguageType = useVModel(props, 'languagesType', emit);
const languages = [ const languages = [
{ text: 'beanshellJSR223', value: 'beanshell-jsr233' }, { text: 'beanshellJSR223', value: RequestConditionScriptLanguage.BEANSHELL_JSR233 },
{ text: 'beanshell', value: 'beanshell' }, { text: 'beanshell', value: RequestConditionScriptLanguage.BEANSHELL },
{ text: 'python', value: 'python' }, { text: 'python', value: RequestConditionScriptLanguage.PYTHON },
{ text: 'groovy', value: 'groovy' }, { text: 'groovy', value: RequestConditionScriptLanguage.GROOVY },
{ text: 'javascript', value: 'javascript' }, { text: 'javascript', value: RequestConditionScriptLanguage.JAVASCRIPT },
]; ];
function expandedHandler() { function expandedHandler() {

View File

@ -14,9 +14,9 @@
{{ t('project.commonScript.clear') }}</MsTag {{ t('project.commonScript.clear') }}</MsTag
> >
</div> </div>
<MsTag class="cursor-pointer" theme="outline" @click="formatCoding">{{ <MsTag class="cursor-pointer" theme="outline" @click="formatCoding">
t('project.commonScript.formatting') {{ t('project.commonScript.formatting') }}
}}</MsTag> </MsTag>
</div> </div>
</div> </div>
<div v-if="props.showType === 'commonScript'" class="flex bg-[var(--color-bg-3)]"> <div v-if="props.showType === 'commonScript'" class="flex bg-[var(--color-bg-3)]">
@ -179,6 +179,13 @@ ${item.script}
function clearCode() { function clearCode() {
innerCodeValue.value = ''; innerCodeValue.value = '';
} }
defineExpose({
formatCoding,
insertHandler,
undoHandler,
clearCode,
});
</script> </script>
<style scoped lang="less"></style> <style scoped lang="less"></style>

View File

@ -4,11 +4,12 @@
:width="props.width" :width="props.width"
:footer="false" :footer="false"
class="ms-drawer" class="ms-drawer"
:show-full-screen="props.showFullScreen"
no-content-padding no-content-padding
unmount-on-close unmount-on-close
> >
<template #title> <template #title>
<div class="flex w-full items-center"> <div class="flex flex-1 items-center">
<a-tooltip :content="props.title" position="bottom"> <a-tooltip :content="props.title" position="bottom">
<div class="one-line-text max-w-[300px]"> <div class="one-line-text max-w-[300px]">
{{ props.title }} {{ props.title }}
@ -51,6 +52,7 @@
detailIndex: number; // detailIndex: number; //
tableData: any[]; // tableData: any[]; //
pagination: MsPaginationI; // pagination: MsPaginationI; //
showFullScreen?: boolean; //
pageChange: (page: number) => Promise<void>; // pageChange: (page: number) => Promise<void>; //
getDetailFunc: (id: string) => Promise<any>; // getDetailFunc: (id: string) => Promise<any>; //
}>(); }>();

View File

@ -1,7 +1,6 @@
<template> <template>
<MsDrawer <MsDrawer
v-model:visible="showDrawer" v-model:visible="showDrawer"
:mask="false"
:title="t('caseManagement.featureCase.associatedFile')" :title="t('caseManagement.featureCase.associatedFile')"
:ok-text="t('caseManagement.featureCase.associated')" :ok-text="t('caseManagement.featureCase.associated')"
:ok-loading="drawerLoading" :ok-loading="drawerLoading"
@ -72,6 +71,7 @@
:show-type="showType" :show-type="showType"
:get-list-request="props.getListRequest" :get-list-request="props.getListRequest"
:get-list-fun-params="props.getListFunParams" :get-list-fun-params="props.getListFunParams"
:selector-type="props.selectorType"
@init="handleModuleTableInit" @init="handleModuleTableInit"
/> />
</template> </template>
@ -103,6 +103,7 @@
getCountRequest: (params: any) => Promise<Record<string, any>>; // getCountRequest: (params: any) => Promise<Record<string, any>>; //
getListRequest: (params: TableQueryParams) => Promise<CommonList<AssociatedList>>; // getListRequest: (params: TableQueryParams) => Promise<CommonList<AssociatedList>>; //
getListFunParams: TableQueryParams; // id getListFunParams: TableQueryParams; // id
selectorType?: 'none' | 'checkbox' | 'radio';
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{

View File

@ -21,7 +21,7 @@
@press-enter="searchList" @press-enter="searchList"
/></div> /></div>
</div> </div>
<ms-base-table v-bind="propsRes" ref="tableRef" no-disable v-on="propsEvent"> <ms-base-table v-bind="propsRes" ref="tableRef" v-model:selected-key="selectedKey" no-disable v-on="propsEvent">
<template #name="{ record }"> <template #name="{ record }">
<MsTag <MsTag
v-if="record.fileType.toLowerCase() === 'jar'" v-if="record.fileType.toLowerCase() === 'jar'"
@ -83,6 +83,7 @@
showType: 'Module' | 'Storage'; // showType: 'Module' | 'Storage'; //
storageList: Repository[]; // storageList: Repository[]; //
getListFunParams: TableQueryParams; // getListFunParams: TableQueryParams; //
selectorType?: 'none' | 'checkbox' | 'radio';
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'init', params: FileListQueryParams): void; (e: 'init', params: FileListQueryParams): void;
@ -144,6 +145,7 @@
selectable: true, selectable: true,
showSelectAll: true, showSelectAll: true,
heightUsed: 300, heightUsed: 300,
selectorType: props.selectorType || 'checkbox',
}, },
(item) => { (item) => {
return { return {
@ -278,8 +280,6 @@
}; };
}); });
const tableSelected = ref<AssociatedList[]>([]);
const selectedIds = computed(() => { const selectedIds = computed(() => {
return [...propsRes.value.selectedKeys]; return [...propsRes.value.selectedKeys];
}); });
@ -287,8 +287,21 @@
watch( watch(
() => selectedIds.value, () => selectedIds.value,
() => { () => {
tableSelected.value = propsRes.value.data.filter((item: any) => selectedIds.value.indexOf(item.id) > -1); emit(
emit('update:selectFile', tableSelected.value); 'update:selectFile',
propsRes.value.data.filter((item: any) => selectedIds.value.indexOf(item.id) > -1)
);
}
);
const selectedKey = ref('');
watch(
() => selectedKey.value,
(key) => {
emit(
'update:selectFile',
propsRes.value.data.filter((item: any) => key === item.id)
);
} }
); );

View File

@ -8,6 +8,9 @@
v-model:selected-keys="innerSelectedKeys" v-model:selected-keys="innerSelectedKeys"
:data="treeData" :data="treeData"
class="ms-tree" class="ms-tree"
:allow-drop="handleAllowDrop"
@drag-start="onDragStart"
@drag-end="onDragEnd"
@drop="onDrop" @drop="onDrop"
@select="select" @select="select"
@check="checked" @check="checked"
@ -117,6 +120,7 @@
| 'right' | 'right'
| 'rt' | 'rt'
| 'rb'; // tooltip | 'rb'; // tooltip
allowDrop?: (dropNode: MsTreeNodeData, dropPosition: -1 | 0 | 1, dragNode?: MsTreeNodeData | null) => boolean; //
filterMoreActionFunc?: (items: ActionsItem[], node: MsTreeNodeData) => ActionsItem[]; // filterMoreActionFunc?: (items: ActionsItem[], node: MsTreeNodeData) => ActionsItem[]; //
}>(), }>(),
{ {
@ -265,6 +269,23 @@
}); });
} }
const tempDragNode = ref<MsTreeNodeData | null>(null);
function handleAllowDrop({ dropNode, dropPosition }: { dropNode: MsTreeNodeData; dropPosition: -1 | 0 | 1 }) {
if (props.allowDrop) {
return props.allowDrop(dropNode, dropPosition, tempDragNode.value);
}
return true;
}
function onDragStart(e, node: MsTreeNodeData) {
tempDragNode.value = node;
}
function onDragEnd() {
tempDragNode.value = null;
}
/** /**
* 处理拖拽结束 * 处理拖拽结束
*/ */

View File

@ -9,6 +9,7 @@ export interface MsTreeFieldNames extends TreeFieldNames {
export type MsTreeNodeData = { export type MsTreeNodeData = {
hideMoreAction?: boolean; // 隐藏更多操作 hideMoreAction?: boolean; // 隐藏更多操作
parentId?: string;
[key: string]: any; [key: string]: any;
} & TreeNodeData; } & TreeNodeData;

View File

@ -94,6 +94,9 @@
top: 12, top: 12,
bottom: 12, bottom: 12,
}, },
minimap: {
enabled: false, //
},
...props, ...props,
}); });

View File

@ -10,8 +10,10 @@
:unique-value="props.uniqueValue" :unique-value="props.uniqueValue"
:max-tag-count="props.maxTagCount" :max-tag-count="props.maxTagCount"
:readonly="props.readonly" :readonly="props.readonly"
:class="props.class"
@press-enter="tagInputEnter" @press-enter="tagInputEnter"
@blur="tagInputBlur" @blur="tagInputBlur"
@clear="emit('clear')"
> >
<template v-if="$slots.prefix" #prefix> <template v-if="$slots.prefix" #prefix>
<slot name="prefix"></slot> <slot name="prefix"></slot>
@ -47,15 +49,17 @@
maxTagCount?: number; maxTagCount?: number;
maxLength?: number; maxLength?: number;
readonly?: boolean; readonly?: boolean;
class?: string;
}>(), }>(),
{ {
retainInputValue: true, retainInputValue: true,
uniqueValue: true, uniqueValue: true,
allowClear: true, allowClear: true,
maxLength: 64, maxLength: 64,
class: '',
} }
); );
const emit = defineEmits(['update:modelValue', 'update:inputValue', 'change']); const emit = defineEmits(['update:modelValue', 'update:inputValue', 'change', 'clear']);
const { t } = useI18n(); const { t } = useI18n();

View File

@ -0,0 +1,7 @@
export default {};
export const dropPositionMap: Record<string, any> = {
'-1': 'BEFORE',
'0': 'APPEND',
'1': 'AFTER',
};

View File

@ -38,7 +38,7 @@ export interface ResponseTiming {
} }
// key-value参数信息 // key-value参数信息
export interface KeyValueParam { export interface KeyValueParam {
id: string; // id用于前端渲染后台无此字段 id?: string; // id用于前端渲染后台无此字段
key: string; key: string;
value: string; value: string;
[key: string]: any; // 用于前端渲染时填充的自定义信息,后台无此字段 [key: string]: any; // 用于前端渲染时填充的自定义信息,后台无此字段
@ -63,6 +63,8 @@ export type ExecuteRequestFormBodyFormValue = ExecuteRequestCommonParam & {
files?: { files?: {
fileId: string; fileId: string;
fileName: string; fileName: string;
local: boolean; // 是否是本地上传的文件
[key: string]: any; // 用于前端渲染时填充的自定义信息,后台无此字段
}[]; }[];
contentType?: RequestContentTypeEnum & string; contentType?: RequestContentTypeEnum & string;
}; };
@ -75,6 +77,8 @@ export interface ExecuteBinaryBody {
file?: { file?: {
fileId: string; fileId: string;
fileName: string; fileName: string;
local: boolean; // 是否是本地上传的文件
[key: string]: any; // 用于前端渲染时填充的自定义信息,后台无此字段
}; };
} }
// 接口请求json-body参数集合信息 // 接口请求json-body参数集合信息
@ -296,7 +300,8 @@ export interface ExecuteRequestParams {
id?: string; id?: string;
reportId: string; reportId: string;
environmentId: string; environmentId: string;
tempFileIds: string[]; uploadFileIds: string[];
linkFileIds: string[];
request: ExecuteHTTPRequestFullParams | ExecutePluginRequestParams; request: ExecuteHTTPRequestFullParams | ExecutePluginRequestParams;
projectId: string; projectId: string;
} }

View File

@ -65,3 +65,12 @@ export interface ModuleTreeNode {
parentId: string; parentId: string;
path: string; path: string;
} }
// 拖拽排序
export type MoveMode = 'BEFORE' | 'AFTER' | 'APPEND';
export interface DragSortParams {
projectId: string;
targetId: string;
moveMode: MoveMode; // 拖拽类型
moveId: string;
moduleId?: string;
}

View File

@ -31,7 +31,7 @@
<MsIcon type="icon-icon_edit_outlined" class="edit-script-name-icon" @click="showEditScriptNameInput" /> <MsIcon type="icon-icon_edit_outlined" class="edit-script-name-icon" @click="showEditScriptNameInput" />
</div> </div>
</a-tooltip> </a-tooltip>
<a-popover class="h-auto" position="top"> <a-popover class="h-auto" position="right">
<div class="text-[rgb(var(--primary-5))]">{{ t('apiTestDebug.scriptEx') }}</div> <div class="text-[rgb(var(--primary-5))]">{{ t('apiTestDebug.scriptEx') }}</div>
<template #content> <template #content>
<div class="mb-[8px] flex items-center justify-between"> <div class="mb-[8px] flex items-center justify-between">
@ -64,7 +64,7 @@
</a-popover> </a-popover>
</div> </div>
<div class="flex items-center gap-[8px]"> <div class="flex items-center gap-[8px]">
<a-button type="outline" class="arco-btn-outline--secondary p-[0_8px]" size="mini"> <a-button type="outline" class="arco-btn-outline--secondary p-[0_8px]" size="mini" @click="undoScript">
<template #icon> <template #icon>
<MsIcon type="icon-icon_undo_outlined" class="text-var(--color-text-4)" size="12" /> <MsIcon type="icon-icon_undo_outlined" class="text-var(--color-text-4)" size="12" />
</template> </template>
@ -87,6 +87,7 @@
<div class="h-[calc(100%-24px)] min-h-[300px]"> <div class="h-[calc(100%-24px)] min-h-[300px]">
<MsScriptDefined <MsScriptDefined
v-if="condition.script !== undefined && condition.scriptLanguage !== undefined" v-if="condition.script !== undefined && condition.scriptLanguage !== undefined"
ref="scriptDefinedRef"
v-model:code="condition.script" v-model:code="condition.script"
v-model:language="condition.scriptLanguage" v-model:language="condition.scriptLanguage"
show-type="commonScript" show-type="commonScript"
@ -402,8 +403,14 @@ org.apache.http.client.method . . . '' at line number 2
} }
} }
const scriptDefinedRef = ref<InstanceType<typeof MsScriptDefined>>();
function undoScript() {
scriptDefinedRef.value?.undoHandler();
}
function clearScript() { function clearScript() {
condition.value.enable = false; condition.value.script = '';
} }
/** /**

View File

@ -124,6 +124,18 @@
@input="(val) => addTableLine(val, 'value')" @input="(val) => addTableLine(val, 'value')"
/> />
</a-popover> </a-popover>
<MsAddAttachment
v-else-if="record.paramType === RequestParamsType.FILE"
v-model:file-list="record.files"
mode="input"
:multiple="true"
:fields="{
id: 'fileId',
name: 'fileName',
}"
input-class="param-input"
@change="(files) => handleFileChange(files, record)"
/>
<MsParamsInput <MsParamsInput
v-else v-else
v-model:value="record.value" v-model:value="record.value"
@ -141,7 +153,7 @@
class="param-input param-input-number" class="param-input param-input-number"
@change="(val) => addTableLine(val, 'minLength')" @change="(val) => addTableLine(val, 'minLength')"
/> />
<div class="mx-[4px]"></div> <div class="mx-[4px]">{{ t('common.to') }}</div>
<a-input-number <a-input-number
v-model:model-value="record.maxLength" v-model:model-value="record.maxLength"
:placeholder="t('apiTestDebug.paramMax')" :placeholder="t('apiTestDebug.paramMax')"
@ -317,7 +329,7 @@
import { ActionsItem } from '@/components/pure/ms-table-more-action/types'; import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import MsTagsGroup from '@/components/pure/ms-tag/ms-tag-group.vue'; import MsTagsGroup from '@/components/pure/ms-tag/ms-tag-group.vue';
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue'; import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
import MsParamsInput from '@/components/business/ms-params-input/index.vue'; import { MsFileItem } from '@/components/pure/ms-upload/types';
import paramDescInput from './paramDescInput.vue'; import paramDescInput from './paramDescInput.vue';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
@ -325,6 +337,9 @@
import { RequestBodyFormat, RequestContentTypeEnum, RequestParamsType } from '@/enums/apiEnum'; import { RequestBodyFormat, RequestContentTypeEnum, RequestParamsType } from '@/enums/apiEnum';
import { SelectAllEnum, TableKeyEnum } from '@/enums/tableEnum'; import { SelectAllEnum, TableKeyEnum } from '@/enums/tableEnum';
//
const MsAddAttachment = defineAsyncComponent(() => import('@/components/business/ms-add-attachment/index.vue'));
const MsParamsInput = defineAsyncComponent(() => import('@/components/business/ms-params-input/index.vue'));
export type ParamTableColumn = MsTableColumnData & { export type ParamTableColumn = MsTableColumnData & {
isNormal?: boolean; // value MsParamsInput isNormal?: boolean; // value MsParamsInput
@ -356,6 +371,7 @@
showSelectorAll?: boolean; // showSelectorAll?: boolean; //
isSimpleSetting?: boolean; // Column isSimpleSetting?: boolean; // Column
response?: string; // response?: string; //
uploadTempFileApi?: (...args) => Promise<any>; //
}>(), }>(),
{ {
params: () => [], params: () => [],
@ -513,6 +529,34 @@
emit('change', propsRes.value.data); emit('change', propsRes.value.data);
} }
async function handleFileChange(files: MsFileItem[], record: Record<string, any>) {
try {
if (props.uploadTempFileApi && files.length === 1) {
//
const fileItem = files[0];
const res = await props.uploadTempFileApi(fileItem.file);
record.files = [
{
...fileItem,
fileId: res.data,
fileName: fileItem.name || '',
local: true,
},
];
} else {
record.files = files.map((e) => ({
...e,
fileId: e.uid || e.fileId || '',
fileName: e.name || e.fileName || '',
}));
}
emit('change', propsRes.value.data);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
const showQuickInputParam = ref(false); const showQuickInputParam = ref(false);
const activeQuickInputRecord = ref<any>({}); const activeQuickInputRecord = ref<any>({});
const quickInputParamValue = ref(''); const quickInputParamValue = ref('');

View File

@ -32,6 +32,7 @@
:show-setting="true" :show-setting="true"
:table-key="TableKeyEnum.API_TEST_DEBUG_FORM_DATA" :table-key="TableKeyEnum.API_TEST_DEBUG_FORM_DATA"
:default-param-item="defaultParamItem" :default-param-item="defaultParamItem"
:upload-temp-file-api="props.uploadTempFileApi"
@change="handleParamTableChange" @change="handleParamTableChange"
/> />
<paramTable <paramTable
@ -47,15 +48,21 @@
/> />
<div v-else-if="innerParams.bodyType === RequestBodyFormat.BINARY"> <div v-else-if="innerParams.bodyType === RequestBodyFormat.BINARY">
<div class="mb-[16px] flex justify-between gap-[8px] bg-[var(--color-text-n9)] p-[12px]"> <div class="mb-[16px] flex justify-between gap-[8px] bg-[var(--color-text-n9)] p-[12px]">
<!--TODO:文件上传&关联组件-->
<a-input <a-input
v-model:model-value="innerParams.binaryBody.description" v-model:model-value="innerParams.binaryBody.description"
:placeholder="t('common.desc')" :placeholder="t('common.desc')"
:max-length="255" :max-length="255"
/> />
<MsAddAttachment
v-model:file-list="fileList"
mode="input"
:multiple="false"
:default-file-list="[innerParams.binaryBody.file]"
@change="handleFileChange"
/>
</div> </div>
<div class="flex items-center"> <!-- <div class="flex items-center">
<!-- <a-switch v-model:model-value="innerParams.binarySend" class="mr-[8px]" size="small" type="line"></a-switch> --> <a-switch v-model:model-value="innerParams.binarySend" class="mr-[8px]" size="small" type="line"></a-switch>
<span>{{ t('apiTestDebug.sendAsMainText') }}</span> <span>{{ t('apiTestDebug.sendAsMainText') }}</span>
<a-tooltip position="right"> <a-tooltip position="right">
<template #content> <template #content>
@ -67,7 +74,7 @@
size="16" size="16"
/> />
</a-tooltip> </a-tooltip>
</div> </div> -->
</div> </div>
<div v-else class="flex h-[calc(100%-100px)]"> <div v-else class="flex h-[calc(100%-100px)]">
<MsCodeEditor <MsCodeEditor
@ -97,13 +104,15 @@
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue'; import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
import { LanguageEnum } from '@/components/pure/ms-code-editor/types'; import { LanguageEnum } from '@/components/pure/ms-code-editor/types';
import { MsFileItem } from '@/components/pure/ms-upload/types';
import MsAddAttachment from '@/components/business/ms-add-attachment/index.vue';
import batchAddKeyVal from '@/views/api-test/components/batchAddKeyVal.vue'; import batchAddKeyVal from '@/views/api-test/components/batchAddKeyVal.vue';
import paramTable, { type ParamTableColumn } from '@/views/api-test/components/paramTable.vue'; import paramTable, { type ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
import { requestBodyTypeMap } from '@/config/apiTest'; import { requestBodyTypeMap } from '@/config/apiTest';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { ExecuteBody } from '@/models/apiTest/debug'; import { ExecuteBody, ExecuteRequestFormBodyFormValue } from '@/models/apiTest/debug';
import { RequestBodyFormat, RequestContentTypeEnum, RequestParamsType } from '@/enums/apiEnum'; import { RequestBodyFormat, RequestContentTypeEnum, RequestParamsType } from '@/enums/apiEnum';
import { TableKeyEnum } from '@/enums/tableEnum'; import { TableKeyEnum } from '@/enums/tableEnum';
@ -111,6 +120,7 @@
params: ExecuteBody; params: ExecuteBody;
layout: 'horizontal' | 'vertical'; layout: 'horizontal' | 'vertical';
secondBoxHeight: number; secondBoxHeight: number;
uploadTempFileApi?: (...args) => Promise<any>; //
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:params', value: any[]): void; (e: 'update:params', value: any[]): void;
@ -120,7 +130,7 @@
const { t } = useI18n(); const { t } = useI18n();
const innerParams = useVModel(props, 'params', emit); const innerParams = useVModel(props, 'params', emit);
const defaultParamItem = { const defaultParamItem: ExecuteRequestFormBodyFormValue = {
key: '', key: '',
value: '', value: '',
paramType: RequestParamsType.STRING, paramType: RequestParamsType.STRING,
@ -131,58 +141,102 @@
encode: false, encode: false,
enable: true, enable: true,
contentType: RequestContentTypeEnum.TEXT, contentType: RequestContentTypeEnum.TEXT,
files: [],
}; };
const fileList = ref<any[]>(
innerParams.value.binaryBody && innerParams.value.binaryBody.file ? [innerParams.value.binaryBody.file] : []
);
const columns = computed<ParamTableColumn[]>(() => [ async function handleFileChange(files: MsFileItem[]) {
{ if (files.length === 0) {
title: 'apiTestDebug.paramName', innerParams.value.binaryBody.file = undefined;
dataIndex: 'key', return;
slotName: 'key', }
}, if (!props.uploadTempFileApi) return;
{ try {
title: 'apiTestDebug.paramType', if (fileList.value[0]?.local) {
dataIndex: 'paramType', const res = await props.uploadTempFileApi(fileList.value[0].file);
slotName: 'paramType', innerParams.value.binaryBody.file = {
hasRequired: true, ...fileList.value[0],
typeOptions: Object.values(RequestParamsType).map((val) => ({ fileId: res.data,
label: val, fileName: fileList.value[0]?.name || '',
value: val, local: true,
})), };
width: 120, } else {
}, innerParams.value.binaryBody.file = {
{ ...fileList.value[0],
title: 'apiTestDebug.paramValue', fileId: fileList.value[0].uid,
dataIndex: 'value', fileName: fileList.value[0]?.name || '',
slotName: 'value', local: false,
width: 240, };
}, }
{ emit('change');
title: 'apiTestDebug.paramLengthRange', } catch (error) {
dataIndex: 'lengthRange', // eslint-disable-next-line no-console
slotName: 'lengthRange', console.log(error);
align: 'center', }
width: 200, }
},
{ const typeOptions = computed(() => {
title: 'apiTestDebug.encode', const fullOptions = Object.values(RequestParamsType).map((val) => ({
dataIndex: 'encode', label: val,
slotName: 'encode', value: val,
titleSlotName: 'encodeTitle', }));
width: 80, if (innerParams.value.bodyType === RequestBodyFormat.FORM_DATA) {
}, return fullOptions;
{ }
title: 'apiTestDebug.desc', return fullOptions.filter((item) => item.value !== RequestParamsType.FILE && item.value !== RequestParamsType.JSON);
dataIndex: 'description', });
slotName: 'description', const columns = computed<ParamTableColumn[]>(() => {
}, return [
{ {
title: '', title: 'apiTestDebug.paramName',
slotName: 'operation', dataIndex: 'key',
fixed: 'right', slotName: 'key',
format: innerParams.value.bodyType, },
width: innerParams.value.bodyType === RequestBodyFormat.FORM_DATA ? 90 : 50, {
}, title: 'apiTestDebug.paramType',
]); dataIndex: 'paramType',
slotName: 'paramType',
hasRequired: true,
typeOptions: typeOptions.value,
width: 120,
},
{
title: 'apiTestDebug.paramValue',
dataIndex: 'value',
slotName: 'value',
multiple: true,
width: 240,
},
{
title: 'apiTestDebug.paramLengthRange',
dataIndex: 'lengthRange',
slotName: 'lengthRange',
align: 'center',
width: 200,
},
{
title: 'apiTestDebug.encode',
dataIndex: 'encode',
slotName: 'encode',
titleSlotName: 'encodeTitle',
width: 80,
},
{
title: 'apiTestDebug.desc',
dataIndex: 'description',
slotName: 'description',
},
{
title: '',
slotName: 'operation',
fixed: 'right',
format: innerParams.value.bodyType,
width: innerParams.value.bodyType === RequestBodyFormat.FORM_DATA ? 90 : 50,
},
];
});
const heightUsed = ref<number | undefined>(undefined); const heightUsed = ref<number | undefined>(undefined);

View File

@ -4,13 +4,18 @@
<div class="mb-[8px] flex items-center justify-between"> <div class="mb-[8px] flex items-center justify-between">
<div class="flex flex-1"> <div class="flex flex-1">
<a-select <a-select
v-if="requestVModel.isNew"
v-model:model-value="requestVModel.protocol" v-model:model-value="requestVModel.protocol"
:options="protocolOptions" :options="protocolOptions"
:loading="protocolLoading" :loading="protocolLoading"
:disabled="!requestVModel.isNew"
class="mr-[4px] w-[90px]" class="mr-[4px] w-[90px]"
@change="(val) => handleActiveDebugProtocolChange(val as string)" @change="(val) => handleActiveDebugProtocolChange(val as string)"
/> />
<apiMethodName
v-else
:method="(requestVModel.protocol as RequestMethods)"
class="mr-[16px] flex h-[30px] items-center"
/>
<a-input-group v-if="isHttpProtocol" class="flex-1"> <a-input-group v-if="isHttpProtocol" class="flex-1">
<apiMethodSelect <apiMethodSelect
v-model:model-value="requestVModel.method" v-model:model-value="requestVModel.method"
@ -50,7 +55,7 @@
<a-doption value="saveAsCase">{{ t('apiTestManagement.saveAsCase') }}</a-doption> <a-doption value="saveAsCase">{{ t('apiTestManagement.saveAsCase') }}</a-doption>
</template> </template>
</a-dropdown> </a-dropdown>
<a-button v-else type="secondary" @click="handleSaveShortcut"> <a-button v-else type="secondary" :loading="saveLoading" @click="handleSaveShortcut">
<div class="flex items-center"> <div class="flex items-center">
{{ t('common.save') }} {{ t('common.save') }}
<div class="text-[var(--color-text-4)]">(<icon-command size="14" />+S)</div> <div class="text-[var(--color-text-4)]">(<icon-command size="14" />+S)</div>
@ -115,6 +120,7 @@
v-model:params="requestVModel.body" v-model:params="requestVModel.body"
:layout="activeLayout" :layout="activeLayout"
:second-box-height="secondBoxHeight" :second-box-height="secondBoxHeight"
:upload-temp-file-api="props.uploadTempFileApi"
@change="handleActiveDebugChange" @change="handleActiveDebugChange"
/> />
<debugQuery <debugQuery
@ -223,6 +229,7 @@
import precondition from './precondition.vue'; import precondition from './precondition.vue';
import response from './response.vue'; import response from './response.vue';
import debugSetting from './setting.vue'; import debugSetting from './setting.vue';
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
import apiMethodSelect from '@/views/api-test/components/apiMethodSelect.vue'; import apiMethodSelect from '@/views/api-test/components/apiMethodSelect.vue';
import { getPluginScript, getProtocolList } from '@/api/modules/api-test/management'; import { getPluginScript, getProtocolList } from '@/api/modules/api-test/management';
@ -263,6 +270,7 @@
executeApi: (...args) => Promise<any>; // executeApi: (...args) => Promise<any>; //
createApi: (...args) => Promise<any>; // createApi: (...args) => Promise<any>; //
updateApi: (...args) => Promise<any>; // updateApi: (...args) => Promise<any>; //
uploadTempFileApi?: (...args) => Promise<any>; //
}>(); }>();
const emit = defineEmits(['addDone']); const emit = defineEmits(['addDone']);
@ -573,107 +581,6 @@
}); });
} }
function makeRequestParams() {
const polymorphicName = protocolOptions.value.find(
(e) => e.value === requestVModel.value.protocol
)?.polymorphicName; //
let requestParams;
if (isHttpProtocol.value) {
requestParams = {
authConfig: requestVModel.value.authConfig,
body: {
...requestVModel.value.body,
binaryBody: undefined,
formDataBody: {
formValues: requestVModel.value.body.formDataBody.formValues.filter(
(e, i) => i !== requestVModel.value.body.formDataBody.formValues.length - 1
), //
},
wwwFormBody: {
formValues: requestVModel.value.body.wwwFormBody.formValues.filter(
(e, i) => i !== requestVModel.value.body.wwwFormBody.formValues.length - 1
), //
},
}, // TODO:binaryBody
headers: requestVModel.value.headers.filter((e, i) => i !== requestVModel.value.headers.length - 1), //
method: requestVModel.value.method,
otherConfig: requestVModel.value.otherConfig,
path: requestVModel.value.url,
query: requestVModel.value.query.filter((e, i) => i !== requestVModel.value.query.length - 1), //
rest: requestVModel.value.rest.filter((e, i) => i !== requestVModel.value.rest.length - 1), //
url: requestVModel.value.url,
polymorphicName,
};
} else {
requestParams = {
...fApi.value?.formData(),
polymorphicName,
};
}
reportId.value = getGenerateId();
requestVModel.value.reportId = reportId.value; // ID
debugSocket(); // websocket
return {
id: requestVModel.value.id.toString(),
reportId: reportId.value,
environmentId: '',
tempFileIds: [],
request: {
...requestParams,
children: [
{
polymorphicName: 'MsCommonElement', // MsCommonElement
assertionConfig: {
// TODO:
enableGlobal: false,
assertions: [],
},
postProcessorConfig: requestVModel.value.children[0].postProcessorConfig,
preProcessorConfig: requestVModel.value.children[0].preProcessorConfig,
},
],
},
projectId: appStore.currentProjectId,
};
}
/**
* 执行调试
* @param val 执行类型
*/
async function execute(execuetType?: 'localExec' | 'serverExec') {
// TODO:&
if (isHttpProtocol.value) {
try {
requestVModel.value.executeLoading = true;
await props.executeApi(makeRequestParams());
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
requestVModel.value.executeLoading = false;
}
} else {
//
fApi.value?.validate(async (valid) => {
if (valid === true) {
try {
requestVModel.value.executeLoading = true;
await props.executeApi(makeRequestParams());
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
requestVModel.value.executeLoading = false;
}
} else {
requestVModel.value.activeTab = RequestComposition.PLUGIN;
nextTick(() => {
scrollIntoView(document.querySelector('.arco-form-item-message'), { block: 'center' });
});
}
});
}
}
const saveModalVisible = ref(false); const saveModalVisible = ref(false);
const saveModalForm = ref({ const saveModalForm = ref({
name: '', name: '',
@ -698,7 +605,200 @@
} }
); );
function makeRequestParams() {
const { formDataBody, wwwFormBody, binaryBody } = requestVModel.value.body;
const polymorphicName = protocolOptions.value.find(
(e) => e.value === requestVModel.value.protocol
)?.polymorphicName; //
const realFormDataBodyValues = formDataBody.formValues.filter((e, i) => i !== formDataBody.formValues.length - 1); //
const realWwwFormBodyValues = wwwFormBody.formValues.filter((e, i) => i !== wwwFormBody.formValues.length - 1); //
const uploadFileIds: string[] = [];
const linkFileIds: string[] = [];
//
for (let i = 0; i < formDataBody.formValues.length; i++) {
const item = formDataBody.formValues[i];
if (item.paramType === RequestParamsType.FILE) {
if (item.files) {
for (let j = 0; j < item.files.length; j++) {
const file = item.files[j];
if (file.isLocal) {
uploadFileIds.push(file.fileId);
} else {
linkFileIds.push(file.fileId);
}
}
}
}
}
if (binaryBody) {
if (binaryBody.file?.isLocal) {
uploadFileIds.push(binaryBody.file.fileId);
} else if (binaryBody.file?.fileId) {
linkFileIds.push(binaryBody.file.fileId);
}
}
let requestParams;
if (isHttpProtocol.value) {
requestParams = {
authConfig: requestVModel.value.authConfig,
body: {
...requestVModel.value.body,
formDataBody: {
formValues: realFormDataBodyValues,
},
wwwFormBody: {
formValues: realWwwFormBodyValues,
},
}, // TODO:binaryBody
headers: requestVModel.value.headers.filter((e, i) => i !== requestVModel.value.headers.length - 1), //
method: requestVModel.value.method,
otherConfig: requestVModel.value.otherConfig,
path: requestVModel.value.url,
query: requestVModel.value.query.filter((e, i) => i !== requestVModel.value.query.length - 1), //
rest: requestVModel.value.rest.filter((e, i) => i !== requestVModel.value.rest.length - 1), //
url: requestVModel.value.url,
polymorphicName,
};
} else {
requestParams = {
...fApi.value?.formData(),
polymorphicName,
};
}
reportId.value = getGenerateId();
requestVModel.value.reportId = reportId.value; // ID
debugSocket(); // websocket
return {
id: requestVModel.value.id.toString(),
reportId: reportId.value,
environmentId: '',
name: saveModalForm.value.name || requestVModel.value.name,
request: {
...requestParams,
name: saveModalForm.value.name || requestVModel.value.name,
children: [
{
polymorphicName: 'MsCommonElement', // MsCommonElement
assertionConfig: {
// TODO:
enableGlobal: false,
assertions: [],
},
postProcessorConfig: requestVModel.value.children[0].postProcessorConfig,
preProcessorConfig: requestVModel.value.children[0].preProcessorConfig,
},
],
},
uploadFileIds,
linkFileIds,
projectId: appStore.currentProjectId,
};
}
/**
* 执行调试
* @param val 执行类型
*/
async function execute(execuetType?: 'localExec' | 'serverExec') {
// TODO:&
if (isHttpProtocol.value) {
try {
requestVModel.value.executeLoading = true;
await props.executeApi(makeRequestParams());
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
requestVModel.value.executeLoading = false;
} finally {
websocket.value?.close();
}
} else {
//
fApi.value?.validate(async (valid) => {
if (valid === true) {
try {
requestVModel.value.executeLoading = true;
await props.executeApi(makeRequestParams());
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
requestVModel.value.executeLoading = false;
} finally {
websocket.value?.close();
}
} else {
requestVModel.value.activeTab = RequestComposition.PLUGIN;
nextTick(() => {
scrollIntoView(document.querySelector('.arco-form-item-message'), { block: 'center' });
});
}
});
}
}
async function updateDebug() {
try {
saveLoading.value = true;
await props.updateApi({
...makeRequestParams(),
...saveModalForm.value,
protocol: requestVModel.value.protocol,
method: isHttpProtocol.value ? requestVModel.value.method : requestVModel.value.protocol,
deleteFileIds: [], // TODO:
unLinkRefIds: [], // TODO:
});
Message.success(t('common.updateSuccess'));
requestVModel.value.unSaved = false;
requestVModel.value.name = saveModalForm.value.name;
requestVModel.value.label = saveModalForm.value.name;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
saveLoading.value = false;
}
}
async function handleSave(done: (closed: boolean) => void) {
saveModalFormRef.value?.validate(async (errors) => {
if (!errors) {
try {
saveLoading.value = true;
if (requestVModel.value.isNew) {
//
const res = await props.createApi({
...makeRequestParams(),
...saveModalForm.value,
protocol: requestVModel.value.protocol,
method: isHttpProtocol.value ? requestVModel.value.method : requestVModel.value.protocol,
});
requestVModel.value.id = res.id;
requestVModel.value.isNew = false;
Message.success(t('common.saveSuccess'));
requestVModel.value.unSaved = false;
requestVModel.value.name = saveModalForm.value.name;
requestVModel.value.label = saveModalForm.value.name;
} else {
updateDebug();
}
saveLoading.value = false;
saveModalVisible.value = false;
done(true);
emit('addDone');
} catch (error) {
saveLoading.value = false;
}
}
});
done(false);
}
async function handleSaveShortcut() { async function handleSaveShortcut() {
if (!requestVModel.value.isNew) {
//
updateDebug();
return;
}
try { try {
if (!isHttpProtocol.value) { if (!isHttpProtocol.value) {
// //
@ -738,51 +838,6 @@
saveModalFormRef.value?.resetFields(); saveModalFormRef.value?.resetFields();
} }
async function handleSave(done: (closed: boolean) => void) {
saveModalFormRef.value?.validate(async (errors) => {
if (!errors) {
try {
saveLoading.value = true;
if (requestVModel.value.isNew) {
//
const res = await props.createApi({
...makeRequestParams(),
...saveModalForm.value,
protocol: requestVModel.value.protocol,
method: isHttpProtocol.value ? requestVModel.value.method : requestVModel.value.protocol,
uploadFileIds: [],
linkFileIds: [],
});
requestVModel.value.id = res.id;
requestVModel.value.isNew = false;
} else {
await props.updateApi({
...makeRequestParams(),
...saveModalForm.value,
protocol: requestVModel.value.protocol,
method: isHttpProtocol.value ? requestVModel.value.method : requestVModel.value.protocol,
uploadFileIds: [],
linkFileIds: [],
deleteFileIds: [], // TODO:
unLinkRefIds: [], // TODO:
});
}
saveLoading.value = false;
saveModalVisible.value = false;
done(true);
requestVModel.value.unSaved = false;
requestVModel.value.name = saveModalForm.value.name;
requestVModel.value.label = saveModalForm.value.name;
emit('addDone');
Message.success(requestVModel.value.isNew ? t('common.saveSuccess') : t('common.updateSuccess'));
} catch (error) {
saveLoading.value = false;
}
}
});
done(false);
}
onBeforeMount(() => { onBeforeMount(() => {
initProtocolList(); initProtocolList();
initLocalConfig(); initLocalConfig();

View File

@ -33,14 +33,14 @@
/> />
</a-form-item> </a-form-item>
</div> </div>
<a-form-item :label="t('apiTestDebug.certificateAlias')"> <!-- <a-form-item :label="t('apiTestDebug.certificateAlias')">
<a-input <a-input
v-model:model-value="settingForm.certificateAlias" v-model:model-value="settingForm.certificateAlias"
:max-length="255" :max-length="255"
:placeholder="t('apiTestDebug.commonPlaceholder')" :placeholder="t('apiTestDebug.commonPlaceholder')"
class="w-[450px]" class="w-[450px]"
/> />
</a-form-item> </a-form-item> -->
<a-form-item :label="t('apiTestDebug.redirect')"> <a-form-item :label="t('apiTestDebug.redirect')">
<a-radio <a-radio
v-model:model-value="settingForm.followRedirects" v-model:model-value="settingForm.followRedirects"

View File

@ -55,9 +55,11 @@
children: 'children', children: 'children',
count: 'count', count: 'count',
}" }"
:draggable="true"
:selectable="false" :selectable="false"
block-node block-node
title-tooltip-position="left" title-tooltip-position="left"
:allow-drop="allowDrop"
@more-action-select="handleFolderMoreSelect" @more-action-select="handleFolderMoreSelect"
@more-actions-close="moreActionsClose" @more-actions-close="moreActionsClose"
@drop="handleDrop" @drop="handleDrop"
@ -124,12 +126,15 @@
import { import {
deleteDebug, deleteDebug,
deleteDebugModule, deleteDebugModule,
dragDebug,
getDebugModuleCount, getDebugModuleCount,
getDebugModules, getDebugModules,
moveDebugModule, moveDebugModule,
} from '@/api/modules/api-test/debug'; } from '@/api/modules/api-test/debug';
import { dropPositionMap } from '@/config/common';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal'; import useModal from '@/hooks/useModal';
import useAppStore from '@/store/modules/app';
import { mapTree } from '@/utils'; import { mapTree } from '@/utils';
import { ModuleTreeNode } from '@/models/common'; import { ModuleTreeNode } from '@/models/common';
@ -139,6 +144,7 @@
}>(); }>();
const emit = defineEmits(['init', 'clickApiNode', 'newApi', 'import', 'renameFinish']); const emit = defineEmits(['init', 'clickApiNode', 'newApi', 'import', 'renameFinish']);
const appStore = useAppStore();
const { t } = useI18n(); const { t } = useI18n();
const { openModal } = useModal(); const { openModal } = useModal();
@ -204,6 +210,7 @@
return { return {
...e, ...e,
hideMoreAction: e.id === 'root', hideMoreAction: e.id === 'root',
draggable: e.id !== 'root',
}; };
}); });
rootModulesName.value = folderTree.value.map((e) => e.name || ''); rootModulesName.value = folderTree.value.map((e) => e.name || '');
@ -324,6 +331,18 @@
} }
} }
function allowDrop(dropNode: MsTreeNodeData, dropPosition: number, dragNode?: MsTreeNodeData | null) {
if (dropNode.type === 'API' && dropPosition === 0) {
// API
return false;
}
if (dropNode.type === 'MODULE' && dragNode?.type === 'API' && dropPosition !== 0) {
// API
return false;
}
return true;
}
/** /**
* 处理文件夹树节点拖拽事件 * 处理文件夹树节点拖拽事件
* @param tree 树数据 * @param tree 树数据
@ -339,11 +358,21 @@
) { ) {
try { try {
loading.value = true; loading.value = true;
await moveDebugModule({ if (dragNode.type === 'MODULE') {
dragNodeId: dragNode.id as string, await moveDebugModule({
dropNodeId: dropNode.id || '', dragNodeId: dragNode.id as string,
dropPosition, dropNodeId: dropNode.id || '',
}); dropPosition,
});
} else {
await dragDebug({
projectId: appStore.currentProjectId,
moveMode: dropPositionMap[dropPosition],
moveId: dropNode.id,
targetId: dragNode.id,
moduleId: dropNode.type === 'API' ? dropNode.parentId : dropNode.id, // APIidid
});
}
Message.success(t('apiTestDebug.moduleMoveSuccess')); Message.success(t('apiTestDebug.moduleMoveSuccess'));
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console

View File

@ -36,6 +36,7 @@
:create-api="addDebug" :create-api="addDebug"
:update-api="updateDebug" :update-api="updateDebug"
:execute-api="executeDebug" :execute-api="executeDebug"
:upload-temp-file-api="uploadTempFile"
@add-done="handleDebugAddDone" @add-done="handleDebugAddDone"
/> />
</div> </div>
@ -88,7 +89,7 @@
import apiMethodName from '@/views/api-test/components/apiMethodName.vue'; import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
import debug, { RequestParam } from '@/views/api-test/components/requestComposition/index.vue'; import debug, { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
import { addDebug, executeDebug, getDebugDetail, updateDebug } from '@/api/modules/api-test/debug'; import { addDebug, executeDebug, getDebugDetail, updateDebug, uploadTempFile } from '@/api/modules/api-test/debug';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { parseCurlScript } from '@/utils'; import { parseCurlScript } from '@/utils';

View File

@ -100,7 +100,7 @@ export default {
'apiTestDebug.deleteDebugTipContent': 'Deletion cannot be restored, please proceed with caution!', 'apiTestDebug.deleteDebugTipContent': 'Deletion cannot be restored, please proceed with caution!',
'apiTestDebug.deleteConfirm': 'Confirm delete', 'apiTestDebug.deleteConfirm': 'Confirm delete',
'apiTestDebug.deleteSuccess': 'Successfully deleted', 'apiTestDebug.deleteSuccess': 'Successfully deleted',
'apiTestDebug.moduleMoveSuccess': 'Module moved successfully', 'apiTestDebug.moduleMoveSuccess': 'Moved successfully',
'apiTestDebug.sqlSourceName': 'Data source name', 'apiTestDebug.sqlSourceName': 'Data source name',
'apiTestDebug.driver': 'Drive', 'apiTestDebug.driver': 'Drive',
'apiTestDebug.username': 'Username', 'apiTestDebug.username': 'Username',

View File

@ -94,7 +94,7 @@ export default {
'apiTestDebug.deleteDebugTipContent': '删除后无法恢复,请谨慎操作!', 'apiTestDebug.deleteDebugTipContent': '删除后无法恢复,请谨慎操作!',
'apiTestDebug.deleteConfirm': '确认删除', 'apiTestDebug.deleteConfirm': '确认删除',
'apiTestDebug.deleteSuccess': '删除成功', 'apiTestDebug.deleteSuccess': '删除成功',
'apiTestDebug.moduleMoveSuccess': '模块移动成功', 'apiTestDebug.moduleMoveSuccess': '移动成功',
'apiTestDebug.sqlSourceName': '数据源名称', 'apiTestDebug.sqlSourceName': '数据源名称',
'apiTestDebug.driver': '驱动', 'apiTestDebug.driver': '驱动',
'apiTestDebug.username': '用户名', 'apiTestDebug.username': '用户名',

View File

@ -79,7 +79,8 @@
:show-fill-icon="false" :show-fill-icon="false"
/> />
</a-form-item> </a-form-item>
<template v-if="isCheckedPerformance"> <!--TODO:暂无性能测试-->
<!-- <template v-if="isCheckedPerformance">
<a-form-item :label="t('system.resourcePool.mirror')" field="testResourceDTO.loadTestImage" class="form-item"> <a-form-item :label="t('system.resourcePool.mirror')" field="testResourceDTO.loadTestImage" class="form-item">
<a-input <a-input
v-model:model-value="form.testResourceDTO.loadTestImage" v-model:model-value="form.testResourceDTO.loadTestImage"
@ -98,9 +99,9 @@
@fill="fillHeapByDefault" @fill="fillHeapByDefault"
/> />
</a-form-item> </a-form-item>
</template> </template> -->
<!--TODO:暂无UI测试-->
<template v-if="isCheckedUI"> <!-- <template v-if="isCheckedUI">
<a-form-item <a-form-item
:label="t('system.resourcePool.uiGrid')" :label="t('system.resourcePool.uiGrid')"
field="testResourceDTO.uiGrid" field="testResourceDTO.uiGrid"
@ -131,7 +132,7 @@
class="w-[160px]" class="w-[160px]"
></a-input-number> ></a-input-number>
</a-form-item> </a-form-item>
</template> </template> -->
<a-form-item v-if="isShowTypeItem" :label="t('system.resourcePool.type')" field="type" class="form-item"> <a-form-item v-if="isShowTypeItem" :label="t('system.resourcePool.type')" field="type" class="form-item">
<a-radio-group v-model:model-value="form.type" type="button" @change="changeResourceType"> <a-radio-group v-model:model-value="form.type" type="button" @change="changeResourceType">