feat(接口调试): 接口调试文件参数联调&问题修复
This commit is contained in:
parent
d199f398ba
commit
716e5ead03
|
@ -4,6 +4,7 @@ import {
|
|||
AddDebugModuleUrl,
|
||||
DeleteDebugModuleUrl,
|
||||
DeleteDebugUrl,
|
||||
DragDebugUrl,
|
||||
ExecuteApiDebugUrl,
|
||||
GetApiDebugDetailUrl,
|
||||
GetDebugModuleCountUrl,
|
||||
|
@ -11,6 +12,7 @@ import {
|
|||
MoveDebugModuleUrl,
|
||||
UpdateApiDebugUrl,
|
||||
UpdateDebugModuleUrl,
|
||||
UploadTempFileUrl,
|
||||
} from '@/api/requrls/api-test/debug';
|
||||
|
||||
import {
|
||||
|
@ -21,7 +23,7 @@ import {
|
|||
UpdateDebugModule,
|
||||
UpdateDebugParams,
|
||||
} from '@/models/apiTest/debug';
|
||||
import { ModuleTreeNode, MoveModules } from '@/models/common';
|
||||
import { DragSortParams, ModuleTreeNode, MoveModules } from '@/models/common';
|
||||
|
||||
// 获取模块树
|
||||
export function getDebugModules() {
|
||||
|
@ -53,6 +55,11 @@ export function getDebugModuleCount(data: { keyword: string }) {
|
|||
return MSR.post({ url: GetDebugModuleCountUrl, data });
|
||||
}
|
||||
|
||||
// 拖拽调试节点
|
||||
export function dragDebug(data: DragSortParams) {
|
||||
return MSR.post({ url: DragDebugUrl, data });
|
||||
}
|
||||
|
||||
// 执行调试
|
||||
export function executeDebug(data: ExecuteRequestParams) {
|
||||
return MSR.post<ExecuteRequestParams>({ url: ExecuteApiDebugUrl, data });
|
||||
|
@ -77,3 +84,8 @@ export function getDebugDetail(id: string) {
|
|||
export function deleteDebug(id: string) {
|
||||
return MSR.get({ url: DeleteDebugUrl, params: id });
|
||||
}
|
||||
|
||||
// 上传文件
|
||||
export function uploadTempFile(file: File) {
|
||||
return MSR.uploadFile({ url: UploadTempFileUrl }, { fileList: [file] }, 'file');
|
||||
}
|
||||
|
|
|
@ -9,3 +9,5 @@ export const GetDebugModuleCountUrl = '/api/debug/module/count'; // 模块统计
|
|||
export const AddDebugModuleUrl = '/api/debug/module/add'; // 添加模块
|
||||
export const GetDebugModulesUrl = '/api/debug/module/tree'; // 查询模块树
|
||||
export const DeleteDebugModuleUrl = '/api/debug/module/delete'; // 删除模块
|
||||
export const DragDebugUrl = '/api/debug/edit/pos'; // 拖拽调试节点
|
||||
export const UploadTempFileUrl = '/api/debug/upload/temp/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>
|
|
@ -1,12 +1,12 @@
|
|||
<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="mb-1">
|
||||
<a-dropdown position="tr" trigger="hover">
|
||||
<a-button type="outline">
|
||||
<template #icon> <icon-plus class="text-[14px]" /> </template
|
||||
>{{ t('system.orgTemplate.addAttachment') }}</a-button
|
||||
>
|
||||
<template #icon> <icon-plus class="text-[14px]" /> </template>
|
||||
{{ t('system.orgTemplate.addAttachment') }}
|
||||
</a-button>
|
||||
<template #content>
|
||||
<a-upload
|
||||
ref="uploadRef"
|
||||
|
@ -17,61 +17,212 @@
|
|||
@change="handleChange"
|
||||
>
|
||||
<template #upload-button>
|
||||
<a-button type="text" class="!text-[var(--color-text-1)]">
|
||||
<icon-upload />{{ t('caseManagement.featureCase.uploadFile') }}</a-button
|
||||
>
|
||||
<a-button type="text" class="arco-dropdown-option !text-[var(--color-text-1)]">
|
||||
<icon-upload />{{ t('caseManagement.featureCase.uploadFile') }}
|
||||
</a-button>
|
||||
</template>
|
||||
</a-upload>
|
||||
<a-button type="text" class="!text-[var(--color-text-1)]" @click="associatedFile">
|
||||
<MsIcon type="icon-icon_link-copy_outlined" size="16" />{{
|
||||
t('caseManagement.featureCase.associatedFile')
|
||||
}}</a-button
|
||||
>
|
||||
<a-button type="text" class="arco-dropdown-option !text-[var(--color-text-1)]" @click="associatedFile">
|
||||
<MsIcon type="icon-icon_link-copy_outlined" size="16" />
|
||||
{{ t('caseManagement.featureCase.associatedFile') }}
|
||||
</a-button>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
<div class="!hover:bg-[rgb(var(--primary-1))] !text-[var(--color-text-4)]">{{
|
||||
t('system.orgTemplate.addAttachmentTip')
|
||||
}}</div>
|
||||
<div class="!hover:bg-[rgb(var(--primary-1))] !text-[var(--color-text-4)]">
|
||||
{{ t('system.orgTemplate.addAttachmentTip') }}
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
<script setup lang="ts">
|
||||
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 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 { 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 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 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) {
|
||||
emit('upload', file);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
<style scoped></style>
|
||||
<style lang="less" scoped></style>
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
export default {
|
||||
'ms.add.attachment.localUpload': 'Local upload',
|
||||
'ms.add.attachment.associateFile': 'Associate file',
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
export default {
|
||||
'ms.add.attachment.localUpload': '本地上传',
|
||||
'ms.add.attachment.associateFile': '关联文件',
|
||||
};
|
|
@ -52,7 +52,7 @@
|
|||
</template>
|
||||
<a-input
|
||||
v-if="model.type === 'input'"
|
||||
v-model="element[model.filed]"
|
||||
v-model:model-value="element[model.filed]"
|
||||
class="flex-1"
|
||||
:placeholder="t(model.placeholder || '')"
|
||||
:max-length="model.maxLength || 255"
|
||||
|
@ -61,11 +61,12 @@
|
|||
/>
|
||||
<a-input-number
|
||||
v-if="model.type === 'inputNumber'"
|
||||
v-model="element[model.filed]"
|
||||
v-model:model-value="element[model.filed]"
|
||||
class="flex-1"
|
||||
:placeholder="t(model.placeholder || '')"
|
||||
:min="model.min"
|
||||
:max="model.max || 9999999"
|
||||
model-event="input"
|
||||
allow-clear
|
||||
@change="emit('change')"
|
||||
/>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
:placeholder="t('project.commonScript.pleaseSelected')"
|
||||
@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">
|
||||
{{ item.text }}
|
||||
</a-tooltip>
|
||||
|
@ -64,8 +64,8 @@
|
|||
(e: 'update:languagesType', value: Language): void;
|
||||
(e: 'insert', code: string): void;
|
||||
(e: 'formApiImport'): void; // 从api 定义导入
|
||||
(e: 'insertCommonScript'): void; // 从api 定义导入
|
||||
(e: 'updateLanguages', value: Language): void; // 从api 定义导入
|
||||
(e: 'insertCommonScript'): void; // 插入公共脚本
|
||||
(e: 'updateLanguages', value: Language): void; // 更新语言类型
|
||||
}>();
|
||||
|
||||
const innerExpand = useVModel(props, 'expand', emit);
|
||||
|
@ -73,11 +73,11 @@
|
|||
const innerLanguageType = useVModel(props, 'languagesType', emit);
|
||||
|
||||
const languages = [
|
||||
{ text: 'beanshellJSR223', value: 'beanshell-jsr233' },
|
||||
{ text: 'beanshell', value: 'beanshell' },
|
||||
{ text: 'python', value: 'python' },
|
||||
{ text: 'groovy', value: 'groovy' },
|
||||
{ text: 'javascript', value: 'javascript' },
|
||||
{ text: 'beanshellJSR223', value: RequestConditionScriptLanguage.BEANSHELL_JSR233 },
|
||||
{ text: 'beanshell', value: RequestConditionScriptLanguage.BEANSHELL },
|
||||
{ text: 'python', value: RequestConditionScriptLanguage.PYTHON },
|
||||
{ text: 'groovy', value: RequestConditionScriptLanguage.GROOVY },
|
||||
{ text: 'javascript', value: RequestConditionScriptLanguage.JAVASCRIPT },
|
||||
];
|
||||
|
||||
function expandedHandler() {
|
||||
|
|
|
@ -14,9 +14,9 @@
|
|||
{{ t('project.commonScript.clear') }}</MsTag
|
||||
>
|
||||
</div>
|
||||
<MsTag class="cursor-pointer" theme="outline" @click="formatCoding">{{
|
||||
t('project.commonScript.formatting')
|
||||
}}</MsTag>
|
||||
<MsTag class="cursor-pointer" theme="outline" @click="formatCoding">
|
||||
{{ t('project.commonScript.formatting') }}
|
||||
</MsTag>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="props.showType === 'commonScript'" class="flex bg-[var(--color-bg-3)]">
|
||||
|
@ -179,6 +179,13 @@ ${item.script}
|
|||
function clearCode() {
|
||||
innerCodeValue.value = '';
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
formatCoding,
|
||||
insertHandler,
|
||||
undoHandler,
|
||||
clearCode,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
||||
|
|
|
@ -4,11 +4,12 @@
|
|||
:width="props.width"
|
||||
:footer="false"
|
||||
class="ms-drawer"
|
||||
:show-full-screen="props.showFullScreen"
|
||||
no-content-padding
|
||||
unmount-on-close
|
||||
>
|
||||
<template #title>
|
||||
<div class="flex w-full items-center">
|
||||
<div class="flex flex-1 items-center">
|
||||
<a-tooltip :content="props.title" position="bottom">
|
||||
<div class="one-line-text max-w-[300px]">
|
||||
{{ props.title }}
|
||||
|
@ -51,6 +52,7 @@
|
|||
detailIndex: number; // 详情 下标
|
||||
tableData: any[]; // 表格数据
|
||||
pagination: MsPaginationI; // 分页器对象
|
||||
showFullScreen?: boolean; // 是否显示全屏按钮
|
||||
pageChange: (page: number) => Promise<void>; // 分页变更函数
|
||||
getDetailFunc: (id: string) => Promise<any>; // 获取详情的请求函数
|
||||
}>();
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<template>
|
||||
<MsDrawer
|
||||
v-model:visible="showDrawer"
|
||||
:mask="false"
|
||||
:title="t('caseManagement.featureCase.associatedFile')"
|
||||
:ok-text="t('caseManagement.featureCase.associated')"
|
||||
:ok-loading="drawerLoading"
|
||||
|
@ -72,6 +71,7 @@
|
|||
:show-type="showType"
|
||||
:get-list-request="props.getListRequest"
|
||||
:get-list-fun-params="props.getListFunParams"
|
||||
:selector-type="props.selectorType"
|
||||
@init="handleModuleTableInit"
|
||||
/>
|
||||
</template>
|
||||
|
@ -103,6 +103,7 @@
|
|||
getCountRequest: (params: any) => Promise<Record<string, any>>; // 获取左侧树模块数量请求
|
||||
getListRequest: (params: TableQueryParams) => Promise<CommonList<AssociatedList>>; // 获取表格请求
|
||||
getListFunParams: TableQueryParams; // 关联表去重id
|
||||
selectorType?: 'none' | 'checkbox' | 'radio';
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
@press-enter="searchList"
|
||||
/></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 }">
|
||||
<MsTag
|
||||
v-if="record.fileType.toLowerCase() === 'jar'"
|
||||
|
@ -83,6 +83,7 @@
|
|||
showType: 'Module' | 'Storage'; // 展示类型
|
||||
storageList: Repository[]; // 存储库列表
|
||||
getListFunParams: TableQueryParams; // 表格额外去重参数
|
||||
selectorType?: 'none' | 'checkbox' | 'radio';
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'init', params: FileListQueryParams): void;
|
||||
|
@ -144,6 +145,7 @@
|
|||
selectable: true,
|
||||
showSelectAll: true,
|
||||
heightUsed: 300,
|
||||
selectorType: props.selectorType || 'checkbox',
|
||||
},
|
||||
(item) => {
|
||||
return {
|
||||
|
@ -278,8 +280,6 @@
|
|||
};
|
||||
});
|
||||
|
||||
const tableSelected = ref<AssociatedList[]>([]);
|
||||
|
||||
const selectedIds = computed(() => {
|
||||
return [...propsRes.value.selectedKeys];
|
||||
});
|
||||
|
@ -287,8 +287,21 @@
|
|||
watch(
|
||||
() => selectedIds.value,
|
||||
() => {
|
||||
tableSelected.value = propsRes.value.data.filter((item: any) => selectedIds.value.indexOf(item.id) > -1);
|
||||
emit('update:selectFile', tableSelected.value);
|
||||
emit(
|
||||
'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)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -8,6 +8,9 @@
|
|||
v-model:selected-keys="innerSelectedKeys"
|
||||
:data="treeData"
|
||||
class="ms-tree"
|
||||
:allow-drop="handleAllowDrop"
|
||||
@drag-start="onDragStart"
|
||||
@drag-end="onDragEnd"
|
||||
@drop="onDrop"
|
||||
@select="select"
|
||||
@check="checked"
|
||||
|
@ -117,6 +120,7 @@
|
|||
| 'right'
|
||||
| 'rt'
|
||||
| 'rb'; // 标题 tooltip 的位置
|
||||
allowDrop?: (dropNode: MsTreeNodeData, dropPosition: -1 | 0 | 1, dragNode?: MsTreeNodeData | null) => boolean; // 是否允许放置
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理拖拽结束
|
||||
*/
|
||||
|
|
|
@ -9,6 +9,7 @@ export interface MsTreeFieldNames extends TreeFieldNames {
|
|||
|
||||
export type MsTreeNodeData = {
|
||||
hideMoreAction?: boolean; // 隐藏更多操作
|
||||
parentId?: string;
|
||||
[key: string]: any;
|
||||
} & TreeNodeData;
|
||||
|
||||
|
|
|
@ -94,6 +94,9 @@
|
|||
top: 12,
|
||||
bottom: 12,
|
||||
},
|
||||
minimap: {
|
||||
enabled: false, // 将代码块预览隐藏
|
||||
},
|
||||
...props,
|
||||
});
|
||||
|
||||
|
|
|
@ -10,8 +10,10 @@
|
|||
:unique-value="props.uniqueValue"
|
||||
:max-tag-count="props.maxTagCount"
|
||||
:readonly="props.readonly"
|
||||
:class="props.class"
|
||||
@press-enter="tagInputEnter"
|
||||
@blur="tagInputBlur"
|
||||
@clear="emit('clear')"
|
||||
>
|
||||
<template v-if="$slots.prefix" #prefix>
|
||||
<slot name="prefix"></slot>
|
||||
|
@ -47,15 +49,17 @@
|
|||
maxTagCount?: number;
|
||||
maxLength?: number;
|
||||
readonly?: boolean;
|
||||
class?: string;
|
||||
}>(),
|
||||
{
|
||||
retainInputValue: true,
|
||||
uniqueValue: true,
|
||||
allowClear: true,
|
||||
maxLength: 64,
|
||||
class: '',
|
||||
}
|
||||
);
|
||||
const emit = defineEmits(['update:modelValue', 'update:inputValue', 'change']);
|
||||
const emit = defineEmits(['update:modelValue', 'update:inputValue', 'change', 'clear']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
export default {};
|
||||
|
||||
export const dropPositionMap: Record<string, any> = {
|
||||
'-1': 'BEFORE',
|
||||
'0': 'APPEND',
|
||||
'1': 'AFTER',
|
||||
};
|
|
@ -38,7 +38,7 @@ export interface ResponseTiming {
|
|||
}
|
||||
// key-value参数信息
|
||||
export interface KeyValueParam {
|
||||
id: string; // id用于前端渲染,后台无此字段
|
||||
id?: string; // id用于前端渲染,后台无此字段
|
||||
key: string;
|
||||
value: string;
|
||||
[key: string]: any; // 用于前端渲染时填充的自定义信息,后台无此字段
|
||||
|
@ -63,6 +63,8 @@ export type ExecuteRequestFormBodyFormValue = ExecuteRequestCommonParam & {
|
|||
files?: {
|
||||
fileId: string;
|
||||
fileName: string;
|
||||
local: boolean; // 是否是本地上传的文件
|
||||
[key: string]: any; // 用于前端渲染时填充的自定义信息,后台无此字段
|
||||
}[];
|
||||
contentType?: RequestContentTypeEnum & string;
|
||||
};
|
||||
|
@ -75,6 +77,8 @@ export interface ExecuteBinaryBody {
|
|||
file?: {
|
||||
fileId: string;
|
||||
fileName: string;
|
||||
local: boolean; // 是否是本地上传的文件
|
||||
[key: string]: any; // 用于前端渲染时填充的自定义信息,后台无此字段
|
||||
};
|
||||
}
|
||||
// 接口请求json-body参数集合信息
|
||||
|
@ -296,7 +300,8 @@ export interface ExecuteRequestParams {
|
|||
id?: string;
|
||||
reportId: string;
|
||||
environmentId: string;
|
||||
tempFileIds: string[];
|
||||
uploadFileIds: string[];
|
||||
linkFileIds: string[];
|
||||
request: ExecuteHTTPRequestFullParams | ExecutePluginRequestParams;
|
||||
projectId: string;
|
||||
}
|
||||
|
|
|
@ -65,3 +65,12 @@ export interface ModuleTreeNode {
|
|||
parentId: string;
|
||||
path: string;
|
||||
}
|
||||
// 拖拽排序
|
||||
export type MoveMode = 'BEFORE' | 'AFTER' | 'APPEND';
|
||||
export interface DragSortParams {
|
||||
projectId: string;
|
||||
targetId: string;
|
||||
moveMode: MoveMode; // 拖拽类型
|
||||
moveId: string;
|
||||
moduleId?: string;
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
<MsIcon type="icon-icon_edit_outlined" class="edit-script-name-icon" @click="showEditScriptNameInput" />
|
||||
</div>
|
||||
</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>
|
||||
<template #content>
|
||||
<div class="mb-[8px] flex items-center justify-between">
|
||||
|
@ -64,7 +64,7 @@
|
|||
</a-popover>
|
||||
</div>
|
||||
<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>
|
||||
<MsIcon type="icon-icon_undo_outlined" class="text-var(--color-text-4)" size="12" />
|
||||
</template>
|
||||
|
@ -87,6 +87,7 @@
|
|||
<div class="h-[calc(100%-24px)] min-h-[300px]">
|
||||
<MsScriptDefined
|
||||
v-if="condition.script !== undefined && condition.scriptLanguage !== undefined"
|
||||
ref="scriptDefinedRef"
|
||||
v-model:code="condition.script"
|
||||
v-model:language="condition.scriptLanguage"
|
||||
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() {
|
||||
condition.value.enable = false;
|
||||
condition.value.script = '';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -124,6 +124,18 @@
|
|||
@input="(val) => addTableLine(val, 'value')"
|
||||
/>
|
||||
</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
|
||||
v-else
|
||||
v-model:value="record.value"
|
||||
|
@ -141,7 +153,7 @@
|
|||
class="param-input param-input-number"
|
||||
@change="(val) => addTableLine(val, 'minLength')"
|
||||
/>
|
||||
<div class="mx-[4px]">~</div>
|
||||
<div class="mx-[4px]">{{ t('common.to') }}</div>
|
||||
<a-input-number
|
||||
v-model:model-value="record.maxLength"
|
||||
:placeholder="t('apiTestDebug.paramMax')"
|
||||
|
@ -317,7 +329,7 @@
|
|||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||
import MsTagsGroup from '@/components/pure/ms-tag/ms-tag-group.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 { useI18n } from '@/hooks/useI18n';
|
||||
|
@ -325,6 +337,9 @@
|
|||
|
||||
import { RequestBodyFormat, RequestContentTypeEnum, RequestParamsType } from '@/enums/apiEnum';
|
||||
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 & {
|
||||
isNormal?: boolean; // 用于 value 列区分是普通输入框还是 MsParamsInput
|
||||
|
@ -356,6 +371,7 @@
|
|||
showSelectorAll?: boolean; // 是否显示全选
|
||||
isSimpleSetting?: boolean; // 是否简单Column设置
|
||||
response?: string; // 响应内容
|
||||
uploadTempFileApi?: (...args) => Promise<any>; // 上传临时文件接口
|
||||
}>(),
|
||||
{
|
||||
params: () => [],
|
||||
|
@ -513,6 +529,34 @@
|
|||
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 activeQuickInputRecord = ref<any>({});
|
||||
const quickInputParamValue = ref('');
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
:show-setting="true"
|
||||
:table-key="TableKeyEnum.API_TEST_DEBUG_FORM_DATA"
|
||||
:default-param-item="defaultParamItem"
|
||||
:upload-temp-file-api="props.uploadTempFileApi"
|
||||
@change="handleParamTableChange"
|
||||
/>
|
||||
<paramTable
|
||||
|
@ -47,15 +48,21 @@
|
|||
/>
|
||||
<div v-else-if="innerParams.bodyType === RequestBodyFormat.BINARY">
|
||||
<div class="mb-[16px] flex justify-between gap-[8px] bg-[var(--color-text-n9)] p-[12px]">
|
||||
<!--TODO:文件上传&关联组件-->
|
||||
<a-input
|
||||
v-model:model-value="innerParams.binaryBody.description"
|
||||
:placeholder="t('common.desc')"
|
||||
:max-length="255"
|
||||
/>
|
||||
<MsAddAttachment
|
||||
v-model:file-list="fileList"
|
||||
mode="input"
|
||||
:multiple="false"
|
||||
:default-file-list="[innerParams.binaryBody.file]"
|
||||
@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> -->
|
||||
<!-- <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>
|
||||
|
@ -67,7 +74,7 @@
|
|||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
<div v-else class="flex h-[calc(100%-100px)]">
|
||||
<MsCodeEditor
|
||||
|
@ -97,13 +104,15 @@
|
|||
|
||||
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
||||
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 paramTable, { type ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
|
||||
|
||||
import { requestBodyTypeMap } from '@/config/apiTest';
|
||||
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 { TableKeyEnum } from '@/enums/tableEnum';
|
||||
|
||||
|
@ -111,6 +120,7 @@
|
|||
params: ExecuteBody;
|
||||
layout: 'horizontal' | 'vertical';
|
||||
secondBoxHeight: number;
|
||||
uploadTempFileApi?: (...args) => Promise<any>; // 上传临时文件接口
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:params', value: any[]): void;
|
||||
|
@ -120,7 +130,7 @@
|
|||
const { t } = useI18n();
|
||||
|
||||
const innerParams = useVModel(props, 'params', emit);
|
||||
const defaultParamItem = {
|
||||
const defaultParamItem: ExecuteRequestFormBodyFormValue = {
|
||||
key: '',
|
||||
value: '',
|
||||
paramType: RequestParamsType.STRING,
|
||||
|
@ -131,58 +141,102 @@
|
|||
encode: false,
|
||||
enable: true,
|
||||
contentType: RequestContentTypeEnum.TEXT,
|
||||
files: [],
|
||||
};
|
||||
const fileList = ref<any[]>(
|
||||
innerParams.value.binaryBody && innerParams.value.binaryBody.file ? [innerParams.value.binaryBody.file] : []
|
||||
);
|
||||
|
||||
const columns = computed<ParamTableColumn[]>(() => [
|
||||
{
|
||||
title: 'apiTestDebug.paramName',
|
||||
dataIndex: 'key',
|
||||
slotName: 'key',
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.paramType',
|
||||
dataIndex: 'paramType',
|
||||
slotName: 'paramType',
|
||||
hasRequired: true,
|
||||
typeOptions: Object.values(RequestParamsType).map((val) => ({
|
||||
label: val,
|
||||
value: val,
|
||||
})),
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.paramValue',
|
||||
dataIndex: 'value',
|
||||
slotName: 'value',
|
||||
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,
|
||||
},
|
||||
]);
|
||||
async function handleFileChange(files: MsFileItem[]) {
|
||||
if (files.length === 0) {
|
||||
innerParams.value.binaryBody.file = undefined;
|
||||
return;
|
||||
}
|
||||
if (!props.uploadTempFileApi) return;
|
||||
try {
|
||||
if (fileList.value[0]?.local) {
|
||||
const res = await props.uploadTempFileApi(fileList.value[0].file);
|
||||
innerParams.value.binaryBody.file = {
|
||||
...fileList.value[0],
|
||||
fileId: res.data,
|
||||
fileName: fileList.value[0]?.name || '',
|
||||
local: true,
|
||||
};
|
||||
} else {
|
||||
innerParams.value.binaryBody.file = {
|
||||
...fileList.value[0],
|
||||
fileId: fileList.value[0].uid,
|
||||
fileName: fileList.value[0]?.name || '',
|
||||
local: false,
|
||||
};
|
||||
}
|
||||
emit('change');
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
const typeOptions = computed(() => {
|
||||
const fullOptions = Object.values(RequestParamsType).map((val) => ({
|
||||
label: val,
|
||||
value: val,
|
||||
}));
|
||||
if (innerParams.value.bodyType === RequestBodyFormat.FORM_DATA) {
|
||||
return fullOptions;
|
||||
}
|
||||
return fullOptions.filter((item) => item.value !== RequestParamsType.FILE && item.value !== RequestParamsType.JSON);
|
||||
});
|
||||
const columns = computed<ParamTableColumn[]>(() => {
|
||||
return [
|
||||
{
|
||||
title: 'apiTestDebug.paramName',
|
||||
dataIndex: 'key',
|
||||
slotName: 'key',
|
||||
},
|
||||
{
|
||||
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);
|
||||
|
||||
|
|
|
@ -4,13 +4,18 @@
|
|||
<div class="mb-[8px] flex items-center justify-between">
|
||||
<div class="flex flex-1">
|
||||
<a-select
|
||||
v-if="requestVModel.isNew"
|
||||
v-model:model-value="requestVModel.protocol"
|
||||
:options="protocolOptions"
|
||||
:loading="protocolLoading"
|
||||
:disabled="!requestVModel.isNew"
|
||||
class="mr-[4px] w-[90px]"
|
||||
@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">
|
||||
<apiMethodSelect
|
||||
v-model:model-value="requestVModel.method"
|
||||
|
@ -50,7 +55,7 @@
|
|||
<a-doption value="saveAsCase">{{ t('apiTestManagement.saveAsCase') }}</a-doption>
|
||||
</template>
|
||||
</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">
|
||||
{{ t('common.save') }}
|
||||
<div class="text-[var(--color-text-4)]">(<icon-command size="14" />+S)</div>
|
||||
|
@ -115,6 +120,7 @@
|
|||
v-model:params="requestVModel.body"
|
||||
:layout="activeLayout"
|
||||
:second-box-height="secondBoxHeight"
|
||||
:upload-temp-file-api="props.uploadTempFileApi"
|
||||
@change="handleActiveDebugChange"
|
||||
/>
|
||||
<debugQuery
|
||||
|
@ -223,6 +229,7 @@
|
|||
import precondition from './precondition.vue';
|
||||
import response from './response.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 { getPluginScript, getProtocolList } from '@/api/modules/api-test/management';
|
||||
|
@ -263,6 +270,7 @@
|
|||
executeApi: (...args) => Promise<any>; // 执行接口
|
||||
createApi: (...args) => Promise<any>; // 创建接口
|
||||
updateApi: (...args) => Promise<any>; // 更新接口
|
||||
uploadTempFileApi?: (...args) => Promise<any>; // 上传临时文件接口
|
||||
}>();
|
||||
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 saveModalForm = ref({
|
||||
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() {
|
||||
if (!requestVModel.value.isNew) {
|
||||
// 更新接口不需要弹窗,直接更新保存
|
||||
updateDebug();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (!isHttpProtocol.value) {
|
||||
// 插件需要校验动态表单
|
||||
|
@ -738,51 +838,6 @@
|
|||
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(() => {
|
||||
initProtocolList();
|
||||
initLocalConfig();
|
||||
|
|
|
@ -33,14 +33,14 @@
|
|||
/>
|
||||
</a-form-item>
|
||||
</div>
|
||||
<a-form-item :label="t('apiTestDebug.certificateAlias')">
|
||||
<!-- <a-form-item :label="t('apiTestDebug.certificateAlias')">
|
||||
<a-input
|
||||
v-model:model-value="settingForm.certificateAlias"
|
||||
:max-length="255"
|
||||
:placeholder="t('apiTestDebug.commonPlaceholder')"
|
||||
class="w-[450px]"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form-item> -->
|
||||
<a-form-item :label="t('apiTestDebug.redirect')">
|
||||
<a-radio
|
||||
v-model:model-value="settingForm.followRedirects"
|
||||
|
|
|
@ -55,9 +55,11 @@
|
|||
children: 'children',
|
||||
count: 'count',
|
||||
}"
|
||||
:draggable="true"
|
||||
:selectable="false"
|
||||
block-node
|
||||
title-tooltip-position="left"
|
||||
:allow-drop="allowDrop"
|
||||
@more-action-select="handleFolderMoreSelect"
|
||||
@more-actions-close="moreActionsClose"
|
||||
@drop="handleDrop"
|
||||
|
@ -124,12 +126,15 @@
|
|||
import {
|
||||
deleteDebug,
|
||||
deleteDebugModule,
|
||||
dragDebug,
|
||||
getDebugModuleCount,
|
||||
getDebugModules,
|
||||
moveDebugModule,
|
||||
} from '@/api/modules/api-test/debug';
|
||||
import { dropPositionMap } from '@/config/common';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useModal from '@/hooks/useModal';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { mapTree } from '@/utils';
|
||||
|
||||
import { ModuleTreeNode } from '@/models/common';
|
||||
|
@ -139,6 +144,7 @@
|
|||
}>();
|
||||
const emit = defineEmits(['init', 'clickApiNode', 'newApi', 'import', 'renameFinish']);
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
const { openModal } = useModal();
|
||||
|
||||
|
@ -204,6 +210,7 @@
|
|||
return {
|
||||
...e,
|
||||
hideMoreAction: e.id === 'root',
|
||||
draggable: e.id !== 'root',
|
||||
};
|
||||
});
|
||||
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 树数据
|
||||
|
@ -339,11 +358,21 @@
|
|||
) {
|
||||
try {
|
||||
loading.value = true;
|
||||
await moveDebugModule({
|
||||
dragNodeId: dragNode.id as string,
|
||||
dropNodeId: dropNode.id || '',
|
||||
dropPosition,
|
||||
});
|
||||
if (dragNode.type === 'MODULE') {
|
||||
await moveDebugModule({
|
||||
dragNodeId: dragNode.id as string,
|
||||
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, // 释放节点是 API,则传入它所属模块id;模块的话直接是模块id
|
||||
});
|
||||
}
|
||||
Message.success(t('apiTestDebug.moduleMoveSuccess'));
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
:create-api="addDebug"
|
||||
:update-api="updateDebug"
|
||||
:execute-api="executeDebug"
|
||||
:upload-temp-file-api="uploadTempFile"
|
||||
@add-done="handleDebugAddDone"
|
||||
/>
|
||||
</div>
|
||||
|
@ -88,7 +89,7 @@
|
|||
import apiMethodName from '@/views/api-test/components/apiMethodName.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 { parseCurlScript } from '@/utils';
|
||||
|
||||
|
|
|
@ -100,7 +100,7 @@ export default {
|
|||
'apiTestDebug.deleteDebugTipContent': 'Deletion cannot be restored, please proceed with caution!',
|
||||
'apiTestDebug.deleteConfirm': 'Confirm delete',
|
||||
'apiTestDebug.deleteSuccess': 'Successfully deleted',
|
||||
'apiTestDebug.moduleMoveSuccess': 'Module moved successfully',
|
||||
'apiTestDebug.moduleMoveSuccess': 'Moved successfully',
|
||||
'apiTestDebug.sqlSourceName': 'Data source name',
|
||||
'apiTestDebug.driver': 'Drive',
|
||||
'apiTestDebug.username': 'Username',
|
||||
|
|
|
@ -94,7 +94,7 @@ export default {
|
|||
'apiTestDebug.deleteDebugTipContent': '删除后无法恢复,请谨慎操作!',
|
||||
'apiTestDebug.deleteConfirm': '确认删除',
|
||||
'apiTestDebug.deleteSuccess': '删除成功',
|
||||
'apiTestDebug.moduleMoveSuccess': '模块移动成功',
|
||||
'apiTestDebug.moduleMoveSuccess': '移动成功',
|
||||
'apiTestDebug.sqlSourceName': '数据源名称',
|
||||
'apiTestDebug.driver': '驱动',
|
||||
'apiTestDebug.username': '用户名',
|
||||
|
|
|
@ -79,7 +79,8 @@
|
|||
:show-fill-icon="false"
|
||||
/>
|
||||
</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-input
|
||||
v-model:model-value="form.testResourceDTO.loadTestImage"
|
||||
|
@ -98,9 +99,9 @@
|
|||
@fill="fillHeapByDefault"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<template v-if="isCheckedUI">
|
||||
</template> -->
|
||||
<!--TODO:暂无UI测试-->
|
||||
<!-- <template v-if="isCheckedUI">
|
||||
<a-form-item
|
||||
:label="t('system.resourcePool.uiGrid')"
|
||||
field="testResourceDTO.uiGrid"
|
||||
|
@ -131,7 +132,7 @@
|
|||
class="w-[160px]"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template> -->
|
||||
|
||||
<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">
|
||||
|
|
Loading…
Reference in New Issue