diff --git a/frontend/src/components/business/ms-add-attachment/index.vue b/frontend/src/components/business/ms-add-attachment/index.vue index 491b31f516..95834bd90a 100644 --- a/frontend/src/components/business/ms-add-attachment/index.vue +++ b/frontend/src/components/business/ms-add-attachment/index.vue @@ -42,10 +42,15 @@ :class="props.inputClass" placeholder=" " :max-tag-count="1" + :size="props.inputSize" readonly > - handleClose(data)"> + {{ data.label }} @@ -56,6 +61,7 @@ - + diff --git a/frontend/src/components/business/ms-case-associate/index.vue b/frontend/src/components/business/ms-case-associate/index.vue index 09af7b1494..8fe2d990c5 100644 --- a/frontend/src/components/business/ms-case-associate/index.vue +++ b/frontend/src/components/business/ms-case-associate/index.vue @@ -619,7 +619,6 @@ () => { if (innerVisible.value) { searchCase(); - resetSelector(); initModules(); } } diff --git a/frontend/src/components/business/ms-tree/index.vue b/frontend/src/components/business/ms-tree/index.vue index 6e26b31aa1..25b1771385 100644 --- a/frontend/src/components/business/ms-tree/index.vue +++ b/frontend/src/components/business/ms-tree/index.vue @@ -96,7 +96,7 @@ blockNode?: boolean; // 是否块级节点 showLine?: boolean; // 是否展示连接线 defaultExpandAll?: boolean; // 是否默认展开所有节点 - selectable?: boolean; // 是否可选中 + selectable?: boolean | ((node: MsTreeNodeData, info: { level: number; isLeaf: boolean }) => boolean); // 是否可选中 fieldNames?: MsTreeFieldNames; // 自定义字段名 focusNodeKey?: string | number; // 聚焦的节点 key selectedKeys?: Array; // 选中的节点 key diff --git a/frontend/src/components/pure/ms-tags-input/index.vue b/frontend/src/components/pure/ms-tags-input/index.vue index 2ea5e265a2..4f62255f23 100644 --- a/frontend/src/components/pure/ms-tags-input/index.vue +++ b/frontend/src/components/pure/ms-tags-input/index.vue @@ -11,6 +11,7 @@ :max-tag-count="props.maxTagCount" :readonly="props.readonly" :class="props.class" + :size="props.size" @press-enter="tagInputEnter" @blur="tagInputBlur" @clear="emit('clear')" @@ -50,6 +51,7 @@ maxLength?: number; readonly?: boolean; class?: string; + size?: 'small' | 'large' | 'medium' | 'mini'; }>(), { retainInputValue: true, @@ -57,6 +59,7 @@ allowClear: true, maxLength: 64, class: '', + size: 'medium', } ); const emit = defineEmits(['update:modelValue', 'update:inputValue', 'change', 'clear']); diff --git a/frontend/src/locale/en-US/common.ts b/frontend/src/locale/en-US/common.ts index 91a986ec15..878e961cef 100644 --- a/frontend/src/locale/en-US/common.ts +++ b/frontend/src/locale/en-US/common.ts @@ -117,4 +117,5 @@ export default { 'common.validateSuccess': 'Validate success', 'common.to': 'To', 'common.tip': 'Tips', + 'common.stay': 'Stay', }; diff --git a/frontend/src/locale/zh-CN/common.ts b/frontend/src/locale/zh-CN/common.ts index 5bc8142a44..16ac5c0fbb 100644 --- a/frontend/src/locale/zh-CN/common.ts +++ b/frontend/src/locale/zh-CN/common.ts @@ -120,4 +120,5 @@ export default { 'common.validateSuccess': '验证成功', 'common.to': '至', 'common.tip': '温馨提示', + 'common.stay': '留下', }; diff --git a/frontend/src/models/apiTest/debug.ts b/frontend/src/models/apiTest/debug.ts index 4917d452df..39beae2385 100644 --- a/frontend/src/models/apiTest/debug.ts +++ b/frontend/src/models/apiTest/debug.ts @@ -64,6 +64,8 @@ export type ExecuteRequestFormBodyFormValue = ExecuteRequestCommonParam & { fileId: string; fileName: string; local: boolean; // 是否是本地上传的文件 + fileAlias: string; // 文件别名 + delete: boolean; // 是否删除 [key: string]: any; // 用于前端渲染时填充的自定义信息,后台无此字段 }[]; contentType?: RequestContentTypeEnum & string; @@ -258,8 +260,14 @@ export interface ExecuteCommonChild { // 执行请求-认证配置 export interface ExecuteAuthConfig { authType: RequestAuthType; - password: string; - userName: string; + basicAuth: { + password: string; + userName: string; + }; + digestAuth: { + password: string; + userName: string; + }; } // 执行请求- body 配置-文本格式的 body export interface ExecuteValueBody { @@ -321,7 +329,7 @@ export interface SaveDebugParams { export interface UpdateDebugParams extends Partial { id: string; deleteFileIds?: string[]; - unLinkRefIds?: string[]; + unLinkFileIds?: string[]; } // 更新模块入参 export interface UpdateDebugModule { diff --git a/frontend/src/views/api-test/components/apiMethodName.vue b/frontend/src/views/api-test/components/apiMethodName.vue index d0ce7986d7..e3353a13da 100644 --- a/frontend/src/views/api-test/components/apiMethodName.vue +++ b/frontend/src/views/api-test/components/apiMethodName.vue @@ -1,7 +1,12 @@ {{ props.method }} @@ -9,14 +14,23 @@ diff --git a/frontend/src/views/api-test/components/requestComposition/body.vue b/frontend/src/views/api-test/components/requestComposition/body.vue index e17af9b1d1..dda1b5cb64 100644 --- a/frontend/src/views/api-test/components/requestComposition/body.vue +++ b/frontend/src/views/api-test/components/requestComposition/body.vue @@ -57,7 +57,10 @@ v-model:file-list="fileList" mode="input" :multiple="false" - :default-file-list="[innerParams.binaryBody.file]" + :fields="{ + id: 'fileId', + name: 'fileName', + }" @change="handleFileChange" /> @@ -111,6 +114,7 @@ import { requestBodyTypeMap } from '@/config/apiTest'; import { useI18n } from '@/hooks/useI18n'; + import useAppStore from '@/store/modules/app'; import { ExecuteBody, ExecuteRequestFormBodyFormValue } from '@/models/apiTest/debug'; import { RequestBodyFormat, RequestContentTypeEnum, RequestParamsType } from '@/enums/apiEnum'; @@ -127,6 +131,7 @@ (e: 'change'): void; }>(); + const appStore = useAppStore(); const { t } = useI18n(); const innerParams = useVModel(props, 'params', emit); @@ -155,6 +160,7 @@ if (!props.uploadTempFileApi) return; try { if (fileList.value[0]?.local) { + appStore.showLoading(); const res = await props.uploadTempFileApi(fileList.value[0].file); innerParams.value.binaryBody.file = { ...fileList.value[0], @@ -162,6 +168,7 @@ fileName: fileList.value[0]?.name || '', local: true, }; + appStore.hideLoading(); } else { innerParams.value.binaryBody.file = { ...fileList.value[0], diff --git a/frontend/src/views/api-test/components/requestComposition/index.vue b/frontend/src/views/api-test/components/requestComposition/index.vue index 9ef43a78cb..83f2b46f39 100644 --- a/frontend/src/views/api-test/components/requestComposition/index.vue +++ b/frontend/src/views/api-test/components/requestComposition/index.vue @@ -1,8 +1,17 @@ - + + + + + + - + handleActiveDebugProtocolChange(val as string)" /> - + + + + {{ requestVModel.label }} + + e.value === requestVModel.value.protocol)?.pluginId; + if (!pluginId) { + Message.warning(t('apiTestDebug.noPluginTip')); + pluginError.value = true; + return; + } + pluginError.value = false; if (pluginScriptMap.value[requestVModel.value.protocol] !== undefined) { setPluginFormData(); // 已经初始化过 @@ -448,9 +475,7 @@ } try { pluginLoading.value = true; - const res = await getPluginScript( - protocolOptions.value.find((e) => e.value === requestVModel.value.protocol)?.pluginId || '' - ); + const res = await getPluginScript(pluginId); pluginScriptMap.value[requestVModel.value.protocol] = res; setPluginFormData(); } catch (error) { @@ -578,6 +603,7 @@ temporaryResponseMap[data.reportId] = data.taskResult; } } + websocket.value?.close(); }); } @@ -606,39 +632,20 @@ ); function makeRequestParams() { - const { formDataBody, wwwFormBody, binaryBody } = requestVModel.value.body; + const { formDataBody, wwwFormBody } = 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 parseRequestBodyResult; let requestParams; if (isHttpProtocol.value) { + const realFormDataBodyValues = formDataBody.formValues.filter((e, i) => i !== formDataBody.formValues.length - 1); // 去掉最后一行空行 + const realWwwFormBodyValues = wwwFormBody.formValues.filter((e, i) => i !== wwwFormBody.formValues.length - 1); // 去掉最后一行空行 + parseRequestBodyResult = parseRequestBodyFiles( + requestVModel.value.body, + requestVModel.value.uploadFileIds, + requestVModel.value.linkFileIds + ); requestParams = { authConfig: requestVModel.value.authConfig, body: { @@ -672,10 +679,11 @@ id: requestVModel.value.id.toString(), reportId: reportId.value, environmentId: '', - name: saveModalForm.value.name || requestVModel.value.name, + name: requestVModel.value.isNew ? saveModalForm.value.name : requestVModel.value.name, + moduleId: requestVModel.value.isNew ? saveModalForm.value.moduleId : requestVModel.value.moduleId, request: { ...requestParams, - name: saveModalForm.value.name || requestVModel.value.name, + name: requestVModel.value.isNew ? saveModalForm.value.name : requestVModel.value.name, children: [ { polymorphicName: 'MsCommonElement', // 协议多态名称,写死MsCommonElement @@ -689,8 +697,7 @@ }, ], }, - uploadFileIds, - linkFileIds, + ...parseRequestBodyResult, projectId: appStore.currentProjectId, }; } @@ -709,8 +716,6 @@ // eslint-disable-next-line no-console console.log(error); requestVModel.value.executeLoading = false; - } finally { - websocket.value?.close(); } } else { // 插件需要校验动态表单 @@ -723,8 +728,6 @@ // eslint-disable-next-line no-console console.log(error); requestVModel.value.executeLoading = false; - } finally { - websocket.value?.close(); } } else { requestVModel.value.activeTab = RequestComposition.PLUGIN; @@ -741,7 +744,6 @@ saveLoading.value = true; await props.updateApi({ ...makeRequestParams(), - ...saveModalForm.value, protocol: requestVModel.value.protocol, method: isHttpProtocol.value ? requestVModel.value.method : requestVModel.value.protocol, deleteFileIds: [], // TODO:删除文件集合 @@ -749,8 +751,7 @@ }); Message.success(t('common.updateSuccess')); requestVModel.value.unSaved = false; - requestVModel.value.name = saveModalForm.value.name; - requestVModel.value.label = saveModalForm.value.name; + emit('addDone'); } catch (error) { // eslint-disable-next-line no-console console.log(error); @@ -778,13 +779,13 @@ requestVModel.value.unSaved = false; requestVModel.value.name = saveModalForm.value.name; requestVModel.value.label = saveModalForm.value.name; + saveLoading.value = false; + saveModalVisible.value = false; + done(true); + emit('addDone'); } else { updateDebug(); } - saveLoading.value = false; - saveModalVisible.value = false; - done(true); - emit('addDone'); } catch (error) { saveLoading.value = false; } diff --git a/frontend/src/views/api-test/components/utils.ts b/frontend/src/views/api-test/components/utils.ts new file mode 100644 index 0000000000..052b9a6b78 --- /dev/null +++ b/frontend/src/views/api-test/components/utils.ts @@ -0,0 +1,102 @@ +import { ExecuteBody } from '@/models/apiTest/debug'; +import { RequestParamsType } from '@/enums/apiEnum'; + +export default {}; + +export interface ParseResult { + uploadFileIds: string[]; + linkFileIds: string[]; + deleteFileIds: string[]; + unLinkFileIds: string[]; +} + +/** + * 解析接口请求 body 内的文件列表 + * @param body body 参数对象 + */ +export function parseRequestBodyFiles( + body: ExecuteBody, + saveUploadFileIds?: string[], + saveLinkFileIds?: string[] +): ParseResult { + const { formDataBody, binaryBody } = body; + const uploadFileIds = new Set(); // 存储本地上传的文件 id 集合 + const linkFileIds = new Set(); // 存储关联文件 id 集合 + const tempSaveUploadFileIds = new Set(); // 临时存储 body 内已保存的上传文件 id 集合,用于对比 saveUploadFileIds 以判断有哪些文件被删除 + const tempSaveLinkFileIds = new Set(); // 临时存储 body 内已保存的关联文件 id 集合,用于对比 saveLinkFileIds 以判断有哪些文件被取消关联 + // 获取上传文件和关联文件 + 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.local) { + // 本地上传的文件 + if (saveUploadFileIds) { + // 如果有已保存的上传文件id集合 + if (saveUploadFileIds.includes(file.fileId)) { + // 当前文件是已保存的文件,存入 tempSaveUploadFileIds + tempSaveUploadFileIds.add(file.fileId); + } else { + // 当前文件不是已保存的文件,存入 uploadFileIds + uploadFileIds.add(file.fileId); + } + } else { + // 没有已保存的文件id集合,直接存入 uploadFileIds + uploadFileIds.add(file.fileId); + } + } else if (saveLinkFileIds) { + // 如果有已保存的关联文件id集合 + if (saveLinkFileIds.includes(file.fileId)) { + // 当前文件是已保存的文件,存入 + tempSaveLinkFileIds.add(file.fileId); + } else { + // 当前文件不是已保存的文件,存入 uploadFileIds + linkFileIds.add(file.fileId); + } + } else { + // 关联的文件 + linkFileIds.add(file.fileId); + } + } + } + } + } + if (binaryBody && binaryBody.file) { + const { fileId } = binaryBody.file; + if (binaryBody.file?.local) { + if (saveUploadFileIds) { + // 如果有已保存的上传文件id集合 + if (saveUploadFileIds.includes(fileId)) { + // 当前文件是已保存的文件,存入 tempSaveUploadFileIds + tempSaveUploadFileIds.add(fileId); + } else { + // 当前文件不是已保存的文件,存入 uploadFileIds + uploadFileIds.add(fileId); + } + } else { + // 没有已保存的文件id集合,直接存入 uploadFileIds + uploadFileIds.add(fileId); + } + } else if (saveLinkFileIds) { + // 如果有已保存的关联文件id集合 + if (saveLinkFileIds.includes(fileId)) { + // 当前文件是已保存的文件,存入 + tempSaveLinkFileIds.add(fileId); + } else { + // 当前文件不是已保存的文件,存入 uploadFileIds + linkFileIds.add(fileId); + } + } else { + // 关联的文件 + linkFileIds.add(fileId); + } + } + return { + uploadFileIds: Array.from(uploadFileIds), + linkFileIds: Array.from(linkFileIds), + deleteFileIds: saveUploadFileIds?.filter((id) => !tempSaveUploadFileIds.has(id)) || [], // 存储对比已保存的文件后,需要删除的文件 id 集合 + unLinkFileIds: saveLinkFileIds?.filter((id) => !tempSaveLinkFileIds.has(id)) || [], // 存储对比已保存的文件后,需要取消关联的文件 id 集合 + }; +} diff --git a/frontend/src/views/api-test/debug/components/moduleTree.vue b/frontend/src/views/api-test/debug/components/moduleTree.vue index fb99d22b7b..ad778727ae 100644 --- a/frontend/src/views/api-test/debug/components/moduleTree.vue +++ b/frontend/src/views/api-test/debug/components/moduleTree.vue @@ -36,6 +36,7 @@ (); - const emit = defineEmits(['init', 'clickApiNode', 'newApi', 'import', 'renameFinish']); + const emit = defineEmits(['init', 'clickApiNode', 'newApi', 'import', 'renameFinish', 'deleteFinish']); const appStore = useAppStore(); const { t } = useI18n(); @@ -178,9 +180,24 @@ const moduleKeyword = ref(''); const folderTree = ref([]); + const selectedKeys = ref<(string | number)[]>([]); const focusNodeKey = ref(''); const loading = ref(false); + watch( + () => props.activeNodeId, + (val) => { + if (val) { + selectedKeys.value = [val]; + } + } + ); + + function nodeSelectable(node: MsTreeNodeData) { + // 只有 api 节点可选中 + return node.type === 'API'; + } + function setFocusNodeKey(node: MsTreeNodeData) { focusNodeKey.value = node.id || ''; } @@ -261,7 +278,9 @@ try { await deleteDebugModule(node.id); Message.success(t('apiTestDebug.deleteSuccess')); - initModules(); + emit('deleteFinish', node); + await initModules(); + initModuleCount(); } catch (error) { // eslint-disable-next-line no-console console.log(error); @@ -297,7 +316,9 @@ try { await deleteDebug(node.id); Message.success(t('apiTestDebug.deleteSuccess')); - initModules(); + emit('deleteFinish', node); + await initModules(); + initModuleCount(); } catch (error) { // eslint-disable-next-line no-console console.log(error); diff --git a/frontend/src/views/api-test/debug/index.vue b/frontend/src/views/api-test/debug/index.vue index f3be965221..64d913349d 100644 --- a/frontend/src/views/api-test/debug/index.vue +++ b/frontend/src/views/api-test/debug/index.vue @@ -5,11 +5,13 @@ (folderTree = val)" @new-api="addDebugTab" @click-api-node="openApiTab" @import="importDrawerVisible = true" @rename-finish="handleRenameFinish" + @delete-finish="handleDeleteFinish" /> @@ -77,6 +79,7 @@ diff --git a/frontend/src/views/api-test/debug/locale/en-US.ts b/frontend/src/views/api-test/debug/locale/en-US.ts index 30bc11628d..60b52b0f62 100644 --- a/frontend/src/views/api-test/debug/locale/en-US.ts +++ b/frontend/src/views/api-test/debug/locale/en-US.ts @@ -184,4 +184,8 @@ export default { 'apiTestDebug.importByCURL': 'Import cURL', 'apiTestDebug.importByCURLTip': 'Supports quick import of packet capture data from tools such as Chrome, Charles or Fiddler', + 'apiTestDebug.noPluginTip': 'The plug-in has been uninstalled and plug-in details cannot be viewed.', + 'apiTestDebug.noPlugin': 'Plugin has been uninstalled', + 'apiTestDebug.unsavedLeave': + 'The content of some tabs has not been saved. The unsaved content will be lost after leaving. Are you sure you want to leave?', }; diff --git a/frontend/src/views/api-test/debug/locale/zh-CN.ts b/frontend/src/views/api-test/debug/locale/zh-CN.ts index e3cdb6e931..43bd55b57e 100644 --- a/frontend/src/views/api-test/debug/locale/zh-CN.ts +++ b/frontend/src/views/api-test/debug/locale/zh-CN.ts @@ -173,4 +173,7 @@ export default { 'apiTestDebug.closeOther': '关闭其他请求', 'apiTestDebug.importByCURL': '导入 cURL', 'apiTestDebug.importByCURLTip': '支持快速导入 Chrome、Charles 或 Fiddler 等工具中的抓包数据', + 'apiTestDebug.noPluginTip': '该插件已卸载,无法查看插件详情', + 'apiTestDebug.noPlugin': '插件已卸载', + 'apiTestDebug.unsavedLeave': '有标签页的内容未保存,离开后后未保存的内容将丢失,确定要离开吗?', }; diff --git a/frontend/src/views/api-test/management/components/management/api/index.vue b/frontend/src/views/api-test/management/components/management/api/index.vue index f4da034b18..c29b570c4f 100644 --- a/frontend/src/views/api-test/management/components/management/api/index.vue +++ b/frontend/src/views/api-test/management/components/management/api/index.vue @@ -192,8 +192,14 @@ linkFileIds: [], authConfig: { authType: RequestAuthType.NONE, - userName: '', - password: '', + basicAuth: { + userName: '', + password: '', + }, + digestAuth: { + userName: '', + password: '', + }, }, children: [ { diff --git a/frontend/src/views/project-management/messageManagement/components/messageList.vue b/frontend/src/views/project-management/messageManagement/components/messageList.vue index e45676b454..6c421847ea 100644 --- a/frontend/src/views/project-management/messageManagement/components/messageList.vue +++ b/frontend/src/views/project-management/messageManagement/components/messageList.vue @@ -48,7 +48,7 @@ + @@ -122,6 +128,7 @@ import MsBaseTable from '@/components/pure/ms-table/base-table.vue'; import type { MsTableColumn } from '@/components/pure/ms-table/type'; import useTable from '@/components/pure/ms-table/useTable'; + import MsTagGroup from '@/components/pure/ms-tag/ms-tag-group.vue'; import MsSelect from '@/components/business/ms-select'; import MessagePreview from './messagePreview.vue'; @@ -133,6 +140,7 @@ } from '@/api/modules/project-management/messageManagement'; import { useI18n } from '@/hooks/useI18n'; import useAppStore from '@/store/modules/app'; + import { hasAnyPermission } from '@/utils/permission'; import type { MessageItem, ProjectRobotConfig, Receiver, RobotItem } from '@/models/projectManagement/message'; import { ProjectManagementRouteEnum } from '@/enums/routeEnum';