From 526e563270e5e1e0b1b2633c2a9c3074b5b09cda Mon Sep 17 00:00:00 2001 From: baiqi Date: Thu, 11 Apr 2024 18:41:32 +0800 Subject: [PATCH] =?UTF-8?q?fix(all):=20=E4=BF=AE=E5=A4=8Dbugs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/modules/api-test/common.ts | 15 ++ frontend/src/api/requrls/api-test/common.ts | 2 + frontend/src/components/pure/navbar/index.vue | 6 +- frontend/src/models/apiTest/scenario.ts | 1 + .../api-test/components/condition/content.vue | 11 +- .../api-test/components/condition/list.vue | 3 +- .../views/api-test/components/paramTable.vue | 2 +- .../response/result/assertionTable.vue | 23 +- .../components/management/case/caseDetail.vue | 16 +- .../components/common/customApiDrawer.vue | 2 +- .../scenario/components/step/index.vue | 28 ++- .../scenario/components/step/stepTree.vue | 200 +++++++++--------- .../api-test/scenario/components/utils.ts | 6 +- .../src/views/api-test/scenario/index.vue | 34 ++- .../views/api-test/scenario/locale/en-US.ts | 29 ++- .../views/api-test/scenario/locale/zh-CN.ts | 16 +- .../src/views/login/components/login-form.vue | 10 +- .../system/config/components/pageConfig.vue | 39 ++-- .../setting/system/config/locale/en-US.ts | 2 +- .../setting/system/config/locale/zh-CN.ts | 8 +- 20 files changed, 258 insertions(+), 195 deletions(-) diff --git a/frontend/src/api/modules/api-test/common.ts b/frontend/src/api/modules/api-test/common.ts index d43565af09..2dff888d28 100644 --- a/frontend/src/api/modules/api-test/common.ts +++ b/frontend/src/api/modules/api-test/common.ts @@ -6,6 +6,8 @@ import { GetPluginScriptUrl, GetProtocolListUrl, LocalExecuteApiDebugUrl, + StopExecuteUrl, + StopLocalExecuteUrl, } from '@/api/requrls/api-test/common'; import { @@ -16,6 +18,7 @@ import { ProtocolItem, } from '@/models/apiTest/common'; import { EnvConfig, EnvironmentItem } from '@/models/projectManagement/environmental'; +import { ScenarioStepType } from '@/enums/apiEnum'; // 获取协议列表 export function getProtocolList(organizationId: string) { @@ -46,3 +49,15 @@ export function getEnvList(projectId: string) { export function getEnvironment(envId: string) { return MSR.get({ url: GetEnvironmentUrl, params: envId }); } + +// 停止本地执行 +export function stopLocalExecute(host: string, id: string | number, type?: ScenarioStepType) { + return MSR.post({ + url: type ? `${host}${StopLocalExecuteUrl}/${type}/${id}` : `${host}${StopLocalExecuteUrl}/${id}`, + }); +} + +// 停止执行 +export function stopExecute(id: string | number, type?: ScenarioStepType) { + return MSR.get({ url: type ? `${StopExecuteUrl}/${type}/${id}` : `${StopExecuteUrl}/${id}` }); +} diff --git a/frontend/src/api/requrls/api-test/common.ts b/frontend/src/api/requrls/api-test/common.ts index 03d08a3298..d7210ba818 100644 --- a/frontend/src/api/requrls/api-test/common.ts +++ b/frontend/src/api/requrls/api-test/common.ts @@ -4,3 +4,5 @@ export const GetPluginScriptUrl = '/api/test/plugin/script'; // 获取插件配 export const LocalExecuteApiDebugUrl = '/api/debug'; // 本地执行调试 export const GetEnvListUrl = '/api/test/env-list'; // 获取接口测试环境列表 export const GetEnvironmentUrl = '/api/test/environment'; // 获取接口测试环境详情 +export const StopExecuteUrl = '/task/center/api/project/stop'; // 停止执行 +export const StopLocalExecuteUrl = '/api/stop'; // 停止本地执行 diff --git a/frontend/src/components/pure/navbar/index.vue b/frontend/src/components/pure/navbar/index.vue index e2721a436e..ac918fdd0d 100644 --- a/frontend/src/components/pure/navbar/index.vue +++ b/frontend/src/components/pure/navbar/index.vue @@ -4,7 +4,11 @@
-
{{ props.name }}
+ +
+ {{ props.name }} +
+
diff --git a/frontend/src/models/apiTest/scenario.ts b/frontend/src/models/apiTest/scenario.ts index c3dcf00e6c..b4013f5829 100644 --- a/frontend/src/models/apiTest/scenario.ts +++ b/frontend/src/models/apiTest/scenario.ts @@ -408,6 +408,7 @@ export interface Scenario { stepResponses: Record>; // 步骤响应集合,key 为步骤 id,value 为步骤响应内容 isExecute?: boolean; // 是否从列表执行进去场景详情 isDebug?: boolean; // 是否调试,区分执行场景和批量调试步骤 + executeType?: 'localExec' | 'serverExec'; // 执行类型 } // 场景详情 export interface ScenarioDetail extends Scenario { diff --git a/frontend/src/views/api-test/components/condition/content.vue b/frontend/src/views/api-test/components/condition/content.vue index 574fcf0557..07e3491c2b 100644 --- a/frontend/src/views/api-test/components/condition/content.vue +++ b/frontend/src/views/api-test/components/condition/content.vue @@ -87,8 +87,8 @@ :max-length="255" :disabled="props.disabled" size="small" - @press-enter="isShowEditScriptNameInput = false" - @blur="isShowEditScriptNameInput = false" + @press-enter="handleScriptNameInputBlur" + @blur="handleScriptNameInputBlur" @change="() => emit('change')" /> @@ -689,6 +689,13 @@ if (!result){ }, ]; + function handleScriptNameInputBlur() { + if (condition.value.name === '') { + condition.value.name = t('apiTestDebug.preconditionScriptName'); + } + isShowEditScriptNameInput.value = false; + } + const showQuoteDrawer = ref(false); function saveQuoteScriptHandler(item: any) { // TODO:any diff --git a/frontend/src/views/api-test/components/condition/list.vue b/frontend/src/views/api-test/components/condition/list.vue index 77848b4848..66d1b852c0 100644 --- a/frontend/src/views/api-test/components/condition/list.vue +++ b/frontend/src/views/api-test/components/condition/list.vue @@ -222,6 +222,7 @@ try { deleteListItem(item); } catch (error) { + // eslint-disable-next-line no-console console.log(error); } }, @@ -238,4 +239,4 @@ background: white !important; box-shadow: 0 0 0 1px var(--color-text-n8); } - \ No newline at end of file + diff --git a/frontend/src/views/api-test/components/paramTable.vue b/frontend/src/views/api-test/components/paramTable.vue index c14c967a28..4bdfef1dc6 100644 --- a/frontend/src/views/api-test/components/paramTable.vue +++ b/frontend/src/views/api-test/components/paramTable.vue @@ -841,7 +841,7 @@ if ( !filterKeyValParams(arr, props.defaultParamItem).lastDataIsDefault && !props.isTreeTable && - !filterKeyValParams(arr, arr[arr.length - 2]).lastDataIsDefault // 为了判断最后俩行是否一致(因为下拉框切换会新增一行一样的数据,此时最后一条数据与默认数据是不一样的) + !filterKeyValParams([arr[arr.length - 2], arr[arr.length - 1]], arr[arr.length - 1]).lastDataIsDefault // 为了判断最后俩行是否一致(因为下拉框切换会新增一行一样的数据,此时最后一条数据与默认数据是不一样的) ) { addTableLine(arr.length - 1, false, true); } diff --git a/frontend/src/views/api-test/components/requestComposition/response/result/assertionTable.vue b/frontend/src/views/api-test/components/requestComposition/response/result/assertionTable.vue index f2494691e4..6438b0883d 100644 --- a/frontend/src/views/api-test/components/requestComposition/response/result/assertionTable.vue +++ b/frontend/src/views/api-test/components/requestComposition/response/result/assertionTable.vue @@ -1,19 +1,18 @@ diff --git a/frontend/src/views/api-test/management/components/management/case/caseDetail.vue b/frontend/src/views/api-test/management/components/management/case/caseDetail.vue index 938aa3b691..38e6089a31 100644 --- a/frontend/src/views/api-test/management/components/management/case/caseDetail.vue +++ b/frontend/src/views/api-test/management/components/management/case/caseDetail.vue @@ -104,7 +104,7 @@ import TabCaseDependency from '@/views/api-test/management/components/management/case/tabContent/tabCaseDependency.vue'; import TabCaseExecuteHistory from '@/views/api-test/management/components/management/case/tabContent/tabCaseExecuteHistory.vue'; - import { localExecuteApiDebug } from '@/api/modules/api-test/common'; + import { localExecuteApiDebug, stopExecute, stopLocalExecute } from '@/api/modules/api-test/common'; import { debugCase, deleteCase, runCase, toggleFollowCase } from '@/api/modules/api-test/management'; import { getSocket } from '@/api/modules/project-management/commonScript'; import useModal from '@/hooks/useModal'; @@ -112,7 +112,7 @@ import { ProtocolItem } from '@/models/apiTest/common'; import { EnvConfig } from '@/models/projectManagement/environmental'; - import { RequestMethods } from '@/enums/apiEnum'; + import { RequestMethods, ScenarioStepType } from '@/enums/apiEnum'; import { defaultResponse } from '@/views/api-test/components/config'; @@ -299,7 +299,17 @@ executeCase.value = false; } } - function stopDebug() { + async function stopDebug() { + try { + if (caseDetail.value.frontendDebug) { + await stopLocalExecute(executeRef.value?.localExecuteUrl || '', reportId.value, ScenarioStepType.API_CASE); + } else { + await stopExecute(reportId.value, ScenarioStepType.API_CASE); + } + } catch (error) { + // eslint-disable-next-line no-console + console.log(error); + } websocket.value?.close(); caseDetail.value.executeLoading = false; executeCase.value = false; diff --git a/frontend/src/views/api-test/scenario/components/common/customApiDrawer.vue b/frontend/src/views/api-test/scenario/components/common/customApiDrawer.vue index a3fe751ad1..c5f10426ef 100644 --- a/frontend/src/views/api-test/scenario/components/common/customApiDrawer.vue +++ b/frontend/src/views/api-test/scenario/components/common/customApiDrawer.vue @@ -82,7 +82,7 @@ :scenario-id="scenarioId" @replace="handleReplace" /> - + {{ t('common.delete') }} diff --git a/frontend/src/views/api-test/scenario/components/step/index.vue b/frontend/src/views/api-test/scenario/components/step/index.vue index 6f4a8b3a63..3aeaabfe9f 100644 --- a/frontend/src/views/api-test/scenario/components/step/index.vue +++ b/frontend/src/views/api-test/scenario/components/step/index.vue @@ -142,6 +142,7 @@ import { getScenarioDetail } from '@/api/modules/api-test/scenario'; import { useI18n } from '@/hooks/useI18n'; + import useModal from '@/hooks/useModal'; import { deleteNodes, filterTree, getGenerateId, mapTree, traverseTree } from '@/utils'; import { countNodes } from '@/utils/tree'; @@ -156,6 +157,7 @@ }>(); const { t } = useI18n(); + const { openModal } = useModal(); const scenario = defineModel('scenario', { required: true, @@ -268,12 +270,26 @@ } function batchDelete() { - deleteNodes(scenario.value.steps, checkedKeys.value, (node) => !node.isQuoteScenarioStep, 'uniqueId'); - Message.success(t('common.deleteSuccess')); - if (scenario.value.steps.length === 0) { - checkedAll.value = false; - indeterminate.value = false; - } + openModal({ + type: 'error', + title: t('common.tip'), + content: t('apiScenario.deleteStepConfirmWithChildren'), + okText: t('common.confirmDelete'), + cancelText: t('common.cancel'), + okButtonProps: { + status: 'danger', + }, + maskClosable: false, + onBeforeOk: async () => { + deleteNodes(scenario.value.steps, checkedKeys.value, (node) => !node.isQuoteScenarioStep, 'uniqueId'); + Message.success(t('common.deleteSuccess')); + if (scenario.value.steps.length === 0) { + checkedAll.value = false; + indeterminate.value = false; + } + }, + hideCancel: false, + }); } const showScenarioReportVisible = ref(false); diff --git a/frontend/src/views/api-test/scenario/components/step/stepTree.vue b/frontend/src/views/api-test/scenario/components/step/stepTree.vue index 16efd420dd..4eb436c5f1 100644 --- a/frontend/src/views/api-test/scenario/components/step/stepTree.vue +++ b/frontend/src/views/api-test/scenario/components/step/stepTree.vue @@ -328,7 +328,7 @@ - +
{{ t('apiScenario.sourceScenario') }} @@ -530,6 +530,7 @@ import { debugScenario, getScenarioStep } from '@/api/modules/api-test/scenario'; import { getSocket } from '@/api/modules/project-management/commonScript'; import { useI18n } from '@/hooks/useI18n'; + import useModal from '@/hooks/useModal'; import useAppStore from '@/store/modules/app'; import { deleteNode, @@ -594,6 +595,7 @@ const appStore = useAppStore(); const { t } = useI18n(); + const { openModal } = useModal(); const steps = defineModel('steps', { required: true, @@ -779,69 +781,45 @@ }); const showScenarioConfig = ref(false); const scenarioConfigParamTip = computed(() => { - if ( - scenarioConfigForm.value.useOriginScenarioParam && - !scenarioConfigForm.value.useOriginScenarioParamPreferential && - !scenarioConfigForm.value.enableScenarioEnv - ) { - // 使用当前场景参数-空值 - return t('apiScenario.currentScenarioAndNull'); + if (!scenarioConfigForm.value.useOriginScenarioParam && !scenarioConfigForm.value.enableScenarioEnv) { + // 非使用原场景参数-非选择源场景环境 + return t('apiScenario.notSource'); } - if ( - scenarioConfigForm.value.useOriginScenarioParam && - !scenarioConfigForm.value.useOriginScenarioParamPreferential && - scenarioConfigForm.value.enableScenarioEnv - ) { - // 使用当前场景参数-空值-且选择源场景环境 - return t('apiScenario.currentScenarioAndNullAndSourceEnv'); + if (!scenarioConfigForm.value.useOriginScenarioParam && scenarioConfigForm.value.enableScenarioEnv) { + // 非使用原场景参数-选择源场景环境 + return t('apiScenario.notSourceParamAndSourceEnv'); } if ( scenarioConfigForm.value.useOriginScenarioParam && scenarioConfigForm.value.useOriginScenarioParamPreferential && !scenarioConfigForm.value.enableScenarioEnv ) { - // 使用当前场景参数-原场景参数 - return t('apiScenario.currentScenarioAndSourceScenario'); + // 使用原场景参数-优先使用原场景参数 + return t('apiScenario.sourceParamAndSource'); } if ( scenarioConfigForm.value.useOriginScenarioParam && scenarioConfigForm.value.useOriginScenarioParamPreferential && scenarioConfigForm.value.enableScenarioEnv ) { - // 使用当前场景参数-原场景参数-且选择源场景环境 - return t('apiScenario.currentScenarioAndSourceScenarioAndSourceEnv'); + // 使用原场景参数-优先使用原场景参数-选择源场景环境 + return t('apiScenario.sourceParamAndSourceEnv'); } if ( - !scenarioConfigForm.value.useOriginScenarioParam && + scenarioConfigForm.value.useOriginScenarioParam && !scenarioConfigForm.value.useOriginScenarioParamPreferential && !scenarioConfigForm.value.enableScenarioEnv ) { - // 使用原场景参数-空值 - return t('apiScenario.sourceScenarioAndNull'); + // 使用原场景参数-优先使用当前场景参数 + return t('apiScenario.currentParamAndSource'); } if ( - !scenarioConfigForm.value.useOriginScenarioParam && + scenarioConfigForm.value.useOriginScenarioParam && !scenarioConfigForm.value.useOriginScenarioParamPreferential && scenarioConfigForm.value.enableScenarioEnv ) { - // 使用原场景参数-空值-且选择源场景环境 - return t('apiScenario.sourceScenarioAndNullAndSourceEnv'); - } - if ( - !scenarioConfigForm.value.useOriginScenarioParam && - scenarioConfigForm.value.useOriginScenarioParamPreferential && - !scenarioConfigForm.value.enableScenarioEnv - ) { - // 使用原场景参数-当前场景参数 - return t('apiScenario.sourceScenarioAndCurrentScenario'); - } - if ( - !scenarioConfigForm.value.useOriginScenarioParam && - scenarioConfigForm.value.useOriginScenarioParamPreferential && - scenarioConfigForm.value.enableScenarioEnv - ) { - // 使用原场景参数-当前场景参数-且选择源场景环境 - return t('apiScenario.sourceScenarioAndCurrentScenarioAndSourceEnv'); + // 使用原场景参数-优先使用当前场景参数-选择源场景环境 + return t('apiScenario.currentParamAndSourceEnv'); } }); @@ -1153,7 +1131,7 @@ })[0] ), name: `copy_${node.name}`, - copyFromStepId: node.id, + copyFromStepId: stepDetail ? node.id : node.copyFromStepId, sort: node.sort + 1, isNew: true, id, @@ -1174,8 +1152,25 @@ showScenarioConfig.value = true; break; case 'delete': - deleteNode(steps.value, node.uniqueId, 'uniqueId'); - scenario.value.unSaved = true; + openModal({ + type: 'error', + title: t('common.tip'), + content: + node.children && node.children.length > 0 + ? t('apiScenario.deleteStepConfirmWithChildren') + : t('apiScenario.deleteStepConfirm', { name: node.name }), + okText: t('common.confirmDelete'), + cancelText: t('common.cancel'), + okButtonProps: { + status: 'danger', + }, + maskClosable: false, + onBeforeOk: async () => { + deleteNode(steps.value, node.uniqueId, 'uniqueId'); + scenario.value.unSaved = true; + }, + hideCancel: false, + }); break; case 'saveAsApi': activeStep.value = node as ScenarioStepItem; @@ -1321,7 +1316,7 @@ // 复制 api、引用 api、自定义 api打开抽屉 activeStep.value = step; if ( - (stepDetails.value[step.id] === undefined && step.copyFromStepId && !step.isNew) || + (stepDetails.value[step.id] === undefined && step.copyFromStepId) || (stepDetails.value[step.id] === undefined && !step.isNew) ) { // 查看场景详情时,详情映射中没有对应数据,初始化步骤详情(复制的步骤没有加载详情前就被复制,打开复制后的步骤就初始化被复制步骤的详情) @@ -1332,7 +1327,7 @@ activeStep.value = step; if ( _stepType.isCopyCase && - ((stepDetails.value[step.id] === undefined && step.copyFromStepId && !step.isNew) || + ((stepDetails.value[step.id] === undefined && step.copyFromStepId) || (stepDetails.value[step.id] === undefined && !step.isNew)) ) { // 只有复制的 case 需要查看步骤详情,引用的无法更改所以不需要在此初始化详情 @@ -1358,16 +1353,11 @@ /** * 开启websocket监听,接收执行结果 */ - function debugSocket( - step: ScenarioStepItem, - _scenario: Scenario, - reportId: string | number, - executeType?: 'localExec' | 'serverExec' - ) { + function debugSocket(step: ScenarioStepItem, _scenario: Scenario, reportId: string | number) { websocketMap[reportId] = getSocket( reportId || '', - executeType === 'localExec' ? '/ws/debug' : '', - executeType === 'localExec' ? localExecuteUrl?.value : '' + scenario.value.executeType === 'localExec' ? '/ws/debug' : '', + scenario.value.executeType === 'localExec' ? localExecuteUrl?.value : '' ); websocketMap[reportId].addEventListener('message', (event) => { const data = JSON.parse(event.data); @@ -1396,21 +1386,20 @@ } async function realExecute( - executeParams: Pick, - executeType?: 'localExec' | 'serverExec' + executeParams: Pick ) { const [currentStep] = executeParams.steps; try { currentStep.isExecuting = true; currentStep.executeStatus = ScenarioExecuteStatus.EXECUTING; - debugSocket(currentStep, scenario.value, executeParams.reportId, executeType); // 开启websocket + debugSocket(currentStep, scenario.value, executeParams.reportId); // 开启websocket const res = await debugScenario({ id: scenario.value.id || '', grouped: false, environmentId: currentEnvConfig?.value?.id || '', projectId: appStore.currentProjectId, scenarioConfig: scenario.value.scenarioConfig, - frontendDebug: executeType === 'localExec', + frontendDebug: scenario.value.executeType === 'localExec', ...executeParams, steps: mapTree(executeParams.steps, (node) => { return { @@ -1420,7 +1409,7 @@ }; }), }); - if (executeType === 'localExec' && localExecuteUrl?.value) { + if (scenario.value.executeType === 'localExec' && localExecuteUrl?.value) { await localExecuteApiDebug(localExecuteUrl.value, res); } } catch (error) { @@ -1461,17 +1450,15 @@ return step.enable || step.uniqueId === realStep.uniqueId; } ); - realExecute( - { - steps: [realStep as ScenarioStepItem], - stepDetails: _stepDetails, - reportId: realStep.reportId, - stepFileParam: { - [realStep.id]: stepFileParam, - }, + scenario.value.executeType = isPriorityLocalExec?.value ? 'localExec' : 'serverExec'; + realExecute({ + steps: [realStep as ScenarioStepItem], + stepDetails: _stepDetails, + reportId: realStep.reportId, + stepFileParam: { + [realStep.id]: stepFileParam, }, - isPriorityLocalExec?.value ? 'localExec' : 'serverExec' - ); + }); } } @@ -1488,19 +1475,17 @@ realStep.executeStatus = ScenarioExecuteStatus.EXECUTING; const stepFileParam = scenario.value.stepFileParam[realStep.id]; request.executeLoading = true; - realExecute( - { - steps: [realStep as ScenarioStepItem], - stepDetails: { - [realStep.id]: request, - }, - reportId: realStep.reportId, - stepFileParam: { - [realStep.uniqueId]: stepFileParam, - }, + scenario.value.executeType = executeType; + realExecute({ + steps: [realStep as ScenarioStepItem], + stepDetails: { + [realStep.id]: request, }, - executeType - ); + reportId: realStep.reportId, + stepFileParam: { + [realStep.uniqueId]: stepFileParam, + }, + }); } else { // 步骤列表找不到该步骤,说明是新建的自定义请求还未保存,则临时创建一个步骤进行调试(不保存步骤信息) const reportId = getGenerateId(); @@ -1519,26 +1504,23 @@ reportId, uniqueId: request.stepId, }; - realExecute( - { - steps: [activeStep.value], - stepDetails: { - [request.stepId]: request, - }, - reportId, - stepFileParam: { - [request.stepId]: { - uploadFileIds: request.uploadFileIds || [], - linkFileIds: request.linkFileIds || [], - }, + realExecute({ + steps: [activeStep.value], + stepDetails: { + [request.stepId]: request, + }, + reportId, + stepFileParam: { + [request.stepId]: { + uploadFileIds: request.uploadFileIds || [], + linkFileIds: request.linkFileIds || [], }, }, - executeType - ); + }); } } - function handleStopExecute(step?: ScenarioStepItem) { + async function handleStopExecute(step?: ScenarioStepItem) { if (step?.reportId) { const realStep = findNodeByKey(steps.value, step.uniqueId, 'uniqueId'); websocketMap[step.reportId].close(); @@ -1759,12 +1741,26 @@ */ function deleteStep(step?: ScenarioStepItem) { if (step) { - customCaseDrawerVisible.value = false; - customApiDrawerVisible.value = false; - deleteNode(steps.value, step.uniqueId, 'uniqueId'); - activeStep.value = undefined; - scenario.value.unSaved = true; - Message.success(t('common.deleteSuccess')); + openModal({ + type: 'error', + title: t('common.tip'), + content: t('apiScenario.deleteStepConfirm', { name: step.name }), + okText: t('common.confirmDelete'), + cancelText: t('common.cancel'), + okButtonProps: { + status: 'danger', + }, + maskClosable: false, + onBeforeOk: async () => { + customCaseDrawerVisible.value = false; + customApiDrawerVisible.value = false; + deleteNode(steps.value, step.uniqueId, 'uniqueId'); + activeStep.value = undefined; + scenario.value.unSaved = true; + Message.success(t('common.deleteSuccess')); + }, + hideCancel: false, + }); } } diff --git a/frontend/src/views/api-test/scenario/components/utils.ts b/frontend/src/views/api-test/scenario/components/utils.ts index 1750f67f56..624b245fbe 100644 --- a/frontend/src/views/api-test/scenario/components/utils.ts +++ b/frontend/src/views/api-test/scenario/components/utils.ts @@ -74,7 +74,11 @@ export default function updateStepStatus( // 非逻辑控制器直接更改本身状态 if (stepResponses[node.uniqueId] && stepResponses[node.uniqueId].length > 0) { // 存在多个请求结果说明是循环控制器下的步骤,需要判断其子步骤的执行结果 - if (stepResponses[node.uniqueId].some((report) => !report.isSuccessful)) { + if ( + stepResponses[node.uniqueId].some( + (report) => !report.isSuccessful && report.status !== ScenarioExecuteStatus.FAKE_ERROR + ) + ) { node.executeStatus = ScenarioExecuteStatus.FAILED; } else if ( stepResponses[node.uniqueId].some((report) => report.status === ScenarioExecuteStatus.FAKE_ERROR) diff --git a/frontend/src/views/api-test/scenario/index.vue b/frontend/src/views/api-test/scenario/index.vue index 794228f181..f6b3513d9c 100644 --- a/frontend/src/views/api-test/scenario/index.vue +++ b/frontend/src/views/api-test/scenario/index.vue @@ -118,7 +118,7 @@ import executeButton from '@/views/api-test/components/executeButton.vue'; import ScenarioTable from '@/views/api-test/scenario/components/scenarioTable.vue'; - import { localExecuteApiDebug } from '@/api/modules/api-test/common'; + import { localExecuteApiDebug, stopExecute, stopLocalExecute } from '@/api/modules/api-test/common'; import { addScenario, debugScenario, @@ -246,14 +246,14 @@ activeScenarioTab.value.stepResponses = {}; activeScenarioTab.value.reportId = executeParams.reportId; // 存储报告ID debugSocket(activeScenarioTab.value, executeType, localExecuteUrl); // 开启websocket + activeScenarioTab.value.isDebug = !isExecute; let res; if (isExecute && executeType !== 'localExec' && !activeScenarioTab.value.isNew) { // 执行场景且非本地执行且非未保存场景 - activeScenarioTab.value.isDebug = false; res = await executeScenario({ id: activeScenarioTab.value.id, grouped: false, - environmentId: currentEnvConfig.value?.id || '', + environmentId: activeScenarioTab.value.environmentId || '', projectId: appStore.currentProjectId, scenarioConfig: activeScenarioTab.value.scenarioConfig, ...executeParams, @@ -266,11 +266,10 @@ }), }); } else { - activeScenarioTab.value.isDebug = true; res = await debugScenario({ id: activeScenarioTab.value.id, grouped: false, - environmentId: currentEnvConfig.value?.id || '', + environmentId: activeScenarioTab.value.environmentId || '', projectId: appStore.currentProjectId, scenarioConfig: activeScenarioTab.value.scenarioConfig, stepFileParam: activeScenarioTab.value.stepFileParam, @@ -329,7 +328,24 @@ ); } - function handleStopExecute() { + async function handleStopExecute() { + if (!activeScenarioTab.value.isDebug) { + // 调试模式不需要调停止执行接口 + try { + if (activeScenarioTab.value.executeType === 'localExec') { + await stopLocalExecute( + executeButtonRef.value?.localExecuteUrl || '', + activeScenarioTab.value.reportId, + ScenarioStepType.API_SCENARIO + ); + } else { + await stopExecute(activeScenarioTab.value.reportId, ScenarioStepType.API_SCENARIO); + } + } catch (error) { + // eslint-disable-next-line no-console + console.log(error); + } + } websocketMap[activeScenarioTab.value.reportId]?.close(); activeScenarioTab.value.executeLoading = false; setStepExecuteStatus(activeScenarioTab.value); @@ -479,7 +495,7 @@ }; }), projectId: appStore.currentProjectId, - environmentId: currentEnvConfig.value?.id || '', + environmentId: activeScenarioTab.value.environmentId || '', }); const scenarioDetail = await getScenarioDetail(res.id); scenarioDetail.stepDetails = {}; @@ -519,7 +535,7 @@ } else { await updateScenario({ ...activeScenarioTab.value, - environmentId: currentEnvConfig.value?.id || '', + environmentId: activeScenarioTab.value.environmentId || '', steps: mapTree(activeScenarioTab.value.steps, (node) => { return { ...node, @@ -554,7 +570,7 @@ if (isLoadedTabIndex > -1 && action !== 'copy') { // 如果点击的请求在tab中已经存在,则直接切换到该tab activeScenarioTab.value = scenarioTabs.value[isLoadedTabIndex]; - // requestCompositionRef里监听的是id,所以id相等的时候需要单独调执行 + // tab子组件里监听的是id变化,所以id相等的时候需要单独调执行 if (action === 'execute') { handleExecute(executeButtonRef.value?.isPriorityLocalExec ? 'localExec' : 'serverExec'); } diff --git a/frontend/src/views/api-test/scenario/locale/en-US.ts b/frontend/src/views/api-test/scenario/locale/en-US.ts index 6d24bd3a3a..19cc6fff2f 100644 --- a/frontend/src/views/api-test/scenario/locale/en-US.ts +++ b/frontend/src/views/api-test/scenario/locale/en-US.ts @@ -170,21 +170,17 @@ export default { 'apiScenario.sourceScenarioParams': 'Source Scenario Parameters', 'apiScenario.sourceScenarioEnv': 'Use source Scenario Environment', 'apiScenario.valuePriority': 'Value Priority:', - 'apiScenario.currentScenarioAndNull': - 'Current Step Parameters > Current Scenario Parameters > Current Environment Parameters > Empty Value', - 'apiScenario.currentScenarioAndNullAndSourceEnv': - 'Current Step Parameters > Current Scenario Parameters > Source Environment Parameters > Empty Value', - 'apiScenario.currentScenarioAndSourceScenario': - 'Current Step Parameters > Current Scenario Parameters > Current Environment Parameters > Source Scenario Parameters', - 'apiScenario.currentScenarioAndSourceScenarioAndSourceEnv': - 'Current Step Parameters > Current Scenario Parameters > Source Scenario Parameters > Source Environment Parameters', - 'apiScenario.sourceScenarioAndNull': 'Source Scenario Parameters > Empty Value', - 'apiScenario.sourceScenarioAndNullAndSourceEnv': - 'Source Scenario Parameters > Source Environment Parameters > Empty Value', - 'apiScenario.sourceScenarioAndCurrentScenario': - 'Source Scenario Parameters > Current Step Parameters > Current Scenario Parameters > Current Environment Parameters', - 'apiScenario.sourceScenarioAndCurrentScenarioAndSourceEnv': - 'Source Scenario Parameters > Source Environment Parameters > Current Step Parameters > Current Scenario Parameters', + 'apiScenario.notSource': 'Current Step Parameters > Current Scenario Parameters > Current Environment Parameters', + 'apiScenario.notSourceParamAndSourceEnv': + 'Current Step Parameters > Current Scenario Parameters > Original Environment Parameters', + 'apiScenario.currentParamAndSource': + 'Current Step Parameters > Current Scenario Parameters > Current Environment Parameters > Original Scenario Parameters', + 'apiScenario.currentParamAndSourceEnv': + 'Current Step Parameters > Current Scenario Parameters > Original Scenario Parameters > Original Environment Parameters', + 'apiScenario.sourceParamAndSource': + 'Original Scenario Parameters > Current Step Parameters > Current Scenario Parameters > Current Environment Parameters', + 'apiScenario.sourceParamAndSourceEnv': + 'Original Scenario Parameters > Original Environment Parameters > Current Step Parameters > Current Scenario Parameters', 'apiScenario.fullQuoteTip': 'Full Quote: Follows the original step content and step status changes. Step status cannot be adjusted.', 'apiScenario.stepQuoteTip': @@ -193,6 +189,9 @@ export default { 'apiScenario.setSuccess': 'Set Successful', 'apiScenario.pleaseInputUrl': 'Please enter URL', 'apiScenario.syncSaveAsCase': 'Synchronously add test interface case', + 'apiScenario.deleteStepConfirm': 'Are you sure you want to delete {name}?', + 'apiScenario.deleteStepConfirmWithChildren': + 'Are you sure you want to delete the selected step and all substeps within the step?', // Execution History 'apiScenario.executeHistory.searchPlaceholder': 'Search by ID or name', 'apiScenario.executeHistory.num': 'No.', diff --git a/frontend/src/views/api-test/scenario/locale/zh-CN.ts b/frontend/src/views/api-test/scenario/locale/zh-CN.ts index 08cbf4a2c9..83044045cd 100644 --- a/frontend/src/views/api-test/scenario/locale/zh-CN.ts +++ b/frontend/src/views/api-test/scenario/locale/zh-CN.ts @@ -169,20 +169,20 @@ export default { 'apiScenario.sourceScenarioParams': '使用原场景参数', 'apiScenario.sourceScenarioEnv': '使用原场景环境', 'apiScenario.valuePriority': '取值优先级:', - 'apiScenario.currentScenarioAndNull': '当前步骤参数 > 当前场景参数 > 当前环境参数 > 空值', - 'apiScenario.currentScenarioAndNullAndSourceEnv': '当前步骤参数 > 当前场景参数 > 原环境参数 > 空值', - 'apiScenario.currentScenarioAndSourceScenario': '当前步骤参数 > 当前场景参数 > 当前环境参数 > 原场景参数', - 'apiScenario.currentScenarioAndSourceScenarioAndSourceEnv': '当前步骤参数 > 当前场景参数 > 原场景参数> 原环境参数', - 'apiScenario.sourceScenarioAndNull': '原场景参数 > 空值', - 'apiScenario.sourceScenarioAndNullAndSourceEnv': '原场景参数 > 原环境参数 > 空值', - 'apiScenario.sourceScenarioAndCurrentScenario': '原场景参数 > 当前步骤参数 > 当前场景参数 > 当前环境参数', - 'apiScenario.sourceScenarioAndCurrentScenarioAndSourceEnv': '原场景参数 > 原环境参数 > 当前步骤参数 > 当前场景参数', + 'apiScenario.notSource': '当前步骤参数 > 当前场景参数 > 当前环境参数', + 'apiScenario.notSourceParamAndSourceEnv': '当前步骤参数 > 当前场景参数 > 原环境参数', + 'apiScenario.currentParamAndSource': '当前步骤参数 > 当前场景参数 > 当前环境参数 > 原场景参数', + 'apiScenario.currentParamAndSourceEnv': '当前步骤参数 > 当前场景参数 > 原场景参数 > 原环境参数', + 'apiScenario.sourceParamAndSource': '原场景参数 > 当前步骤参数 > 当前场景参数 > 当前环境参数', + 'apiScenario.sourceParamAndSourceEnv': '原场景参数 > 原环境参数 > 当前步骤参数 > 当前场景参数', 'apiScenario.fullQuoteTip': '完全引用:跟随原步骤内容及步骤状态变化,步骤状态不可调整', 'apiScenario.stepQuoteTip': '步骤引用:仅跟随原步骤内容变化,步骤状态可调整', 'apiScenario.sourceScenarioEnvTip': '使用原场景运行环境,包含环境参数', 'apiScenario.setSuccess': '设置成功', 'apiScenario.pleaseInputUrl': '请输入 url', 'apiScenario.syncSaveAsCase': '同步添加测试接口用例', + 'apiScenario.deleteStepConfirm': '确认删除 {name} 吗?', + 'apiScenario.deleteStepConfirmWithChildren': '确认删除所选步骤以及步骤内所有子步骤?', // 执行历史 'apiScenario.executeHistory.searchPlaceholder': '通过ID或名称搜索', 'apiScenario.executeHistory.num': '序号', diff --git a/frontend/src/views/login/components/login-form.vue b/frontend/src/views/login/components/login-form.vue index f3d0c69894..7be77c84b1 100644 --- a/frontend/src/views/login/components/login-form.vue +++ b/frontend/src/views/login/components/login-form.vue @@ -5,14 +5,16 @@
- {{ t(innerSlogan || '') || t('login.form.title') }} + + {{ t(innerSlogan || '') || t('login.form.title') }} +
-
{{ - t('login.form.accountLogin') - }}
+
+ {{ t('login.form.accountLogin') }} +
- +
{{ t('system.config.page.theme') }} @@ -32,7 +32,7 @@
- +
{{ t('system.config.page.loginPageConfig') }}
@@ -194,6 +194,7 @@ v-model:model-value="pageConfig.slogan" :placeholder="t('system.config.page.sloganPlaceholder')" :max-length="255" + :disabled="!hasAnyPermission(['SYSTEM_PARAMETER_SETTING_DISPLAY:READ+UPDATE'])" > @@ -202,6 +203,7 @@ v-model:model-value="pageConfig.title" :placeholder="t('system.config.page.titlePlaceholder')" :max-length="255" + :disabled="!hasAnyPermission(['SYSTEM_PARAMETER_SETTING_DISPLAY:READ+UPDATE'])" > @@ -212,7 +214,7 @@
- +
{{ t('system.config.page.platformConfig') }}
@@ -273,8 +275,8 @@ @@ -316,6 +319,7 @@ v-model:model-value="pageConfig.helpDoc" :placeholder="t('system.config.page.helpDocPlaceholder')" :max-length="255" + :disabled="!hasAnyPermission(['SYSTEM_PARAMETER_SETTING_DISPLAY:READ+UPDATE'])" > @@ -329,9 +333,9 @@ class="fixed bottom-0 right-[16px] z-[999] flex justify-between bg-white p-[24px] shadow-[0_-1px_4px_rgba(2,2,2,0.1)]" :style="{ width: `calc(100% - ${menuWidth + 16}px)` }" > - {{ - t('system.config.page.resetAll') - }} + + {{ t('system.config.page.resetAll') }} + {{ t('system.config.page.save') }} @@ -359,6 +363,7 @@ import useAppStore from '@/store/modules/app'; import { sleep } from '@/utils'; import { scrollIntoView } from '@/utils/dom'; + import { hasAnyPermission } from '@/utils/permission'; import { setCustomTheme, setPlatformColor, watchStyle, watchTheme } from '@/utils/theme'; import type { FormInstance, ValidatedError } from '@arco-design/web-vue'; @@ -373,7 +378,7 @@ const menuWidth = computed(() => { return appStore.menuCollapse ? collapsedWidth : appStore.menuWidth; }); - const pageloading = ref(false); + const pageLoading = ref(false); const pageConfig = ref({ ...appStore.pageConfig }); const loginPageFullRef = ref(null); const platformPageFullRef = ref(null); @@ -554,39 +559,41 @@ */ async function save() { try { - pageloading.value = true; + pageLoading.value = true; await savePageConfig(makeParams()); Message.success(t('system.config.page.saveSuccess')); await sleep(300); window.location.reload(); } catch (error) { + // eslint-disable-next-line no-console console.log(error); } finally { - pageloading.value = false; + pageLoading.value = false; } } /** * 保存前校验 */ - function beforeSave() { + async function beforeSave() { try { - loginConfigFormRef.value?.validate((errors: Record | undefined) => { + await loginConfigFormRef.value?.validate((errors: Record | undefined) => { if (errors) { throw new Error('登录页表单校验不通过'); } }); - platformConfigFormRef.value?.validate((errors: Record | undefined) => { + await platformConfigFormRef.value?.validate((errors: Record | undefined) => { if (errors) { throw new Error('平台页表单校验不通过'); } }); save(); } catch (error) { + // eslint-disable-next-line no-console console.log(error); + const errDom = document.querySelector('.arco-form-item-message'); + scrollIntoView(errDom, { block: 'center' }); } - const errDom = document.querySelector('.arco-form-item-message'); - scrollIntoView(errDom, { block: 'center' }); } onBeforeUnmount(() => { diff --git a/frontend/src/views/setting/system/config/locale/en-US.ts b/frontend/src/views/setting/system/config/locale/en-US.ts index 6a1b7212c3..3c3e0dcc78 100644 --- a/frontend/src/views/setting/system/config/locale/en-US.ts +++ b/frontend/src/views/setting/system/config/locale/en-US.ts @@ -85,7 +85,7 @@ export default { 'system.config.page.platformNamePlaceholder': 'Please enter a platform name', 'system.config.page.platformNameRequired': 'Platform name cannot be empty', 'system.config.page.platformNameTip': - 'The general product name of the whole site, the recommended number of words is 8', + 'The general product name of the whole site, the recommended number of words is 7', 'system.config.page.helpDoc': 'Help document', 'system.config.page.helpDocPlaceholder': 'Please enter the address of the help document', 'system.config.page.helpDocTip': 'Help document jump link can be set, the default is official help document', diff --git a/frontend/src/views/setting/system/config/locale/zh-CN.ts b/frontend/src/views/setting/system/config/locale/zh-CN.ts index dbcb45440f..d64a6bca13 100644 --- a/frontend/src/views/setting/system/config/locale/zh-CN.ts +++ b/frontend/src/views/setting/system/config/locale/zh-CN.ts @@ -60,7 +60,7 @@ export default { 'system.config.page.replace': '替换图片', 'system.config.page.iconTip': '顶部网站显示的 icon;建议使用 SVG 或 PNG 格式透明背景图片,宽高 18px;图片大小仅支持 200 KB 以内', - 'system.config.page.loginLogo': '登录 logo', + 'system.config.page.loginLogo': '登录 Logo', 'system.config.page.loginLogoTip': '登录页面右侧 logo;建议使用 SVG 或 PNG 格式透明背景图片,高度不小于 48px;图片大小仅支持 200 KB 以内', 'system.config.page.loginBg': '登录背景图', @@ -69,7 +69,7 @@ export default { 'system.config.page.slogan': 'Slogan', 'system.config.page.sloganPlaceholder': '请输入 Slogan', 'system.config.page.sloganRequired': 'Slogan不能为空', - 'system.config.page.sloganTip': '产品logo下的 slogan', + 'system.config.page.sloganTip': '产品Logo下的 slogan', 'system.config.page.title': '网站名称', 'system.config.page.titlePlaceholder': '请输入网站名称', 'system.config.page.titleTip': '显示在网页tab的平台名称', @@ -77,11 +77,11 @@ export default { 'system.config.page.platformConfig': '平台设置', 'system.config.page.platformLogo': '平台 Logo', 'system.config.page.platformLogoTip': - '平台页面顶部显示的 logo;建议使用 SVG 或 PNG 格式透明背景图片,高度不小于 32px;图片大小仅支持 200 KB 以内', + '平台页面顶部显示的 Logo;建议使用 SVG 或 PNG 格式透明背景图片,高度不小于 32px;图片大小仅支持 200 KB 以内', 'system.config.page.platformName': '平台名称', 'system.config.page.platformNamePlaceholder': '请输入平台名称', 'system.config.page.platformNameRequired': '平台名称不能为空', - 'system.config.page.platformNameTip': '全站通用产品名称,建议字数 8', + 'system.config.page.platformNameTip': '全站通用产品名称,建议字数 7', 'system.config.page.helpDoc': '帮助文档', 'system.config.page.helpDocPlaceholder': '请输入帮助文档地址', 'system.config.page.helpDocTip': '可设置帮助文档跳转链接,默认为官方帮助文档',