fix(场景步骤): 场景配置&样式优化

This commit is contained in:
baiqi 2024-03-28 13:51:51 +08:00 committed by 刘瑞斌
parent f69672938f
commit f04bd1c0da
9 changed files with 152 additions and 29 deletions

View File

@ -14,6 +14,7 @@ import {
DeleteScenarioUrl,
ExecuteHistoryUrl,
ExecuteScenarioUrl,
FollowScenarioUrl,
GetModuleCountUrl,
GetModuleTreeUrl,
GetScenarioStepUrl,
@ -238,3 +239,8 @@ export function executeScenario(data: ApiScenarioDebugRequest) {
export function getSystemRequest(data: GetSystemRequestParams) {
return MSR.post<ApiScenarioTableItem[]>({ url: GetSystemRequestUrl, data });
}
// 关注/取消关注接口场景
export function followScenario(id: string | number) {
return MSR.get({ url: FollowScenarioUrl, params: id });
}

View File

@ -16,6 +16,7 @@ export const ScenarioTransferModuleOptionsUrl = '/api/scenario/transfer/options'
export const DebugScenarioUrl = '/api/scenario/debug'; // 接口场景调试(不保存报告)
export const ExecuteScenarioUrl = '/api/scenario/run'; // 接口场景执行(保存报告)
export const GetSystemRequestUrl = '/api/scenario/get/system-request'; // 获取导入的系统请求数据
export const FollowScenarioUrl = '/api/scenario/follow'; // 关注/取消关注接口场景
export const BatchRecycleScenarioUrl = '/api/scenario/batch-operation/delete-gc'; // 批量删除接口场景
export const BatchMoveScenarioUrl = '/api/scenario/batch-operation/move'; // 批量移动接口场景
export const BatchCopyScenarioUrl = '/api/scenario/batch-operation/copy'; // 批量复制接口场景

View File

@ -82,14 +82,18 @@
<a-input
v-model:model-value="requestVModel.url"
:max-length="255"
:placeholder="t('apiTestDebug.urlPlaceholder')"
:placeholder="showEnvPrefix ? t('apiScenario.pleaseInputUrl') : t('apiTestDebug.urlPlaceholder')"
allow-clear
class="hover:z-10"
:style="isUrlError ? 'border: 1px solid rgb(var(--danger-6);z-index: 10' : ''"
:disabled="_stepType.isQuoteApi"
@input="() => (isUrlError = false)"
@change="handleUrlChange"
/>
>
<template v-if="showEnvPrefix" #prefix>
{{ currentEnvConfig?.httpConfig.find((e) => e.type === 'NONE')?.url }}
</template>
</a-input>
</a-input-group>
</div>
<div>
@ -128,6 +132,7 @@
</div>
<div class="px-[16px]">
<MsTab
v-if="requestVModel.activeTab"
v-model:active-key="requestVModel.activeTab"
:content-tab-list="contentTabList"
:get-text-func="getTabBadge"
@ -472,6 +477,11 @@
}
return t('apiScenario.customApi');
});
const showEnvPrefix = computed(
() =>
requestVModel.value.customizeRequestEnvEnable &&
currentEnvConfig?.value.httpConfig.find((e) => e.type === 'NONE')?.url
);
watch(
() => props.stepResponses,
@ -1007,7 +1017,7 @@
}
requestVModel.value = {
executeLoading: false,
activeTab: res.protocol === 'HTTP' ? RequestComposition.HEADER : RequestComposition.PLUGIN,
activeTab: contentTabList.value[0].value,
unSaved: false,
isNew: false,
label: res.name,
@ -1018,6 +1028,7 @@
name: res.name, // requestnamenull
resourceId: res.id,
stepId: props.step?.id || '',
responseActiveTab: ResponseComposition.BODY,
...parseRequestBodyResult,
};
if (_stepType.value.isQuoteApi && props.request && isHttpProtocol.value) {
@ -1050,8 +1061,8 @@
}
}
nextTick(() => {
requestVModel.value.activeTab = contentTabList.value[0].value;
// loading
requestVModel.value.activeTab = contentTabList.value[0].value;
loading.value = false;
});
} catch (error) {
@ -1073,6 +1084,8 @@
requestVModel.value = cloneDeep({
...defaultApiParams,
...props.request,
activeTab: contentTabList.value[0].value,
responseActiveTab: ResponseComposition.BODY,
isNew: false,
});
if (_stepType.value.isQuoteApi) {

View File

@ -261,6 +261,7 @@
...node,
copyFromStepId: node.id,
config: {
...node.config,
isRefScenarioStep: true, //
},
id: getGenerateId(),

View File

@ -131,7 +131,7 @@
v-permission="['PROJECT_API_SCENARIO:READ+EXECUTE']"
type="text"
class="!mr-0"
@click="openScenarioTab(record)"
@click="openScenarioTab(record, 'execute')"
>
{{ t('apiScenario.execute') }}
</MsButton>
@ -140,7 +140,7 @@
v-permission="['PROJECT_API_SCENARIO:READ+ADD']"
type="text"
class="!mr-0"
@click="openScenarioTab(record, true)"
@click="openScenarioTab(record, 'copy')"
>
{{ t('common.copy') }}
</MsButton>
@ -353,7 +353,7 @@
readOnly?: boolean; //
}>();
const emit = defineEmits<{
(e: 'openScenario', record: ApiScenarioTableItem, isCopy?: boolean): void;
(e: 'openScenario', record: ApiScenarioTableItem, action?: 'copy' | 'execute'): void;
(e: 'refreshModuleTree', params: any): void;
}>();
@ -943,8 +943,8 @@
}
}
function openScenarioTab(record: ApiScenarioTableItem, isCopy = false) {
emit('openScenario', record, isCopy);
function openScenarioTab(record: ApiScenarioTableItem, action?: 'copy' | 'execute') {
emit('openScenario', record, action);
}
defineExpose({

View File

@ -325,10 +325,18 @@
<a-radio :value="false">{{ t('apiScenario.sourceScenario') }}</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="" class="hidden-item">
<a-form-item :label="t('apiScenario.currentScenarioTip')">
<a-radio-group v-model:model-value="scenarioConfigForm.useBothScenarioParam">
<a-radio :value="false">{{ t('apiScenario.empty') }}</a-radio>
<a-radio :value="true">{{ t('apiScenario.sourceScenarioEnv') }}</a-radio>
<a-radio :value="true">
{{
t(
scenarioConfigForm.useCurrentScenarioParam
? 'apiScenario.sourceScenarioParams'
: 'apiScenario.currentScenarioParams'
)
}}
</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="" class="hidden-item !mb-0">
@ -352,11 +360,8 @@
<div class="text-[var(--color-text-4)]">
{{ t('apiScenario.valuePriority') }}
</div>
<div v-if="scenarioConfigForm.useCurrentScenarioParam" class="text-[var(--color-text-1)]">
{{ t('apiScenario.currentScenarioAndNull') }}
</div>
<div v-else class="text-[var(--color-text-1)]">
{{ t('apiScenario.sourceScenarioAndNull') }}
<div v-if="scenarioConfigParamTip" class="text-[var(--color-text-1)]">
{{ scenarioConfigParamTip }}
</div>
</div>
<div class="flex items-center gap-[12px]">
@ -614,7 +619,74 @@
useCurrentScenarioParam: true,
});
const showScenarioConfig = ref(false);
const scenarioConfigParamTip = computed(() => {
if (
scenarioConfigForm.value.useCurrentScenarioParam &&
!scenarioConfigForm.value.useBothScenarioParam &&
!scenarioConfigForm.value.enableScenarioEnv
) {
// 使-
return t('apiScenario.currentScenarioAndNull');
}
if (
scenarioConfigForm.value.useCurrentScenarioParam &&
!scenarioConfigForm.value.useBothScenarioParam &&
scenarioConfigForm.value.enableScenarioEnv
) {
// 使--
return t('apiScenario.currentScenarioAndNullAndSourceEnv');
}
if (
scenarioConfigForm.value.useCurrentScenarioParam &&
scenarioConfigForm.value.useBothScenarioParam &&
!scenarioConfigForm.value.enableScenarioEnv
) {
// 使-
return t('apiScenario.currentScenarioAndSourceScenario');
}
if (
scenarioConfigForm.value.useCurrentScenarioParam &&
scenarioConfigForm.value.useBothScenarioParam &&
scenarioConfigForm.value.enableScenarioEnv
) {
// 使--
return t('apiScenario.currentScenarioAndSourceScenarioAndSourceEnv');
}
if (
!scenarioConfigForm.value.useCurrentScenarioParam &&
!scenarioConfigForm.value.useBothScenarioParam &&
!scenarioConfigForm.value.enableScenarioEnv
) {
// 使-
return t('apiScenario.sourceScenarioAndNull');
}
if (
!scenarioConfigForm.value.useCurrentScenarioParam &&
!scenarioConfigForm.value.useBothScenarioParam &&
scenarioConfigForm.value.enableScenarioEnv
) {
// 使--
return t('apiScenario.sourceScenarioAndNullAndSourceEnv');
}
if (
!scenarioConfigForm.value.useCurrentScenarioParam &&
scenarioConfigForm.value.useBothScenarioParam &&
!scenarioConfigForm.value.enableScenarioEnv
) {
// 使-
return t('apiScenario.sourceScenarioAndCurrentScenario');
}
if (
!scenarioConfigForm.value.useCurrentScenarioParam &&
scenarioConfigForm.value.useBothScenarioParam &&
scenarioConfigForm.value.enableScenarioEnv
) {
// 使--
return t('apiScenario.sourceScenarioAndCurrentScenarioAndSourceEnv');
}
});
//
function cancelScenarioConfig() {
showScenarioConfig.value = false;
scenarioConfigForm.value = {
@ -625,11 +697,12 @@
};
}
//
function saveScenarioConfig() {
if (activeStep.value) {
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, activeStep.value.id, 'id');
if (realStep) {
realStep.refType = scenarioConfigForm.value.refType;
realStep.refType = scenarioConfigForm.value.refType; //
realStep.config = {
...realStep.config,
...scenarioConfigForm.value,
@ -1443,7 +1516,7 @@
.arco-tree-node-title {
@apply !cursor-pointer bg-white;
padding: 12px 4px;
padding: 8px 4px;
&:hover {
background-color: var(--color-text-n9) !important;
}

View File

@ -1,7 +1,7 @@
<template>
<div class="h-full w-full overflow-hidden">
<div class="px-[24px] pt-[16px]">
<MsDetailCard :title="`【${scenario.num}】${scenario.name}`" :description="description">
<MsDetailCard :title="`【${scenario.num}】${scenario.name}`" :description="description" class="!py-[8px]">
<template #titleAppend>
<apiStatus :status="scenario.status" size="small" />
</template>
@ -137,6 +137,8 @@
import step from '../components/step/index.vue';
import apiStatus from '@/views/api-test/components/apiStatus.vue';
import { followScenario } from '@/api/modules/api-test/scenario';
import { ApiScenarioDebugRequest, Scenario, ScenarioDetail } from '@/models/apiTest/scenario';
import { ScenarioDetailComposition } from '@/enums/apiEnum';
@ -184,6 +186,8 @@
async function toggleFollowReview() {
try {
followLoading.value = true;
await followScenario(scenario.value.id || '');
scenario.value.follow = !scenario.value.follow;
Message.success(scenario.value.follow ? t('common.unFollowSuccess') : t('common.followSuccess'));
emit('updateFollow');
} catch (error) {
@ -196,7 +200,7 @@
function share() {
if (isSupported) {
copy(`${window.location.href}&dId=${scenario.value.id}`);
copy(`${window.location.href}&sId=${scenario.value.id}`);
Message.success(t('common.copySuccess'));
} else {
Message.error(t('common.copyNotSupport'));

View File

@ -147,13 +147,27 @@
]);
const activeScenarioTab = ref<ScenarioParams>(scenarioTabs.value[0] as ScenarioParams);
function newTab(defaultScenarioInfo?: Scenario, isCopy = false) {
function newTab(defaultScenarioInfo?: Scenario, action?: 'copy' | 'execute') {
if (defaultScenarioInfo) {
const isCopy = action === 'copy';
let copySteps = defaultScenarioInfo.steps;
if (isCopy) {
copySteps = mapTree(defaultScenarioInfo.steps, (node) => {
return {
...node,
copyFromStepId: node.id,
id: getGenerateId(),
};
});
}
scenarioTabs.value.push({
...defaultScenarioInfo,
steps: copySteps,
id: isCopy ? getGenerateId() : defaultScenarioInfo.id || '',
label: isCopy ? `copy-${defaultScenarioInfo.name}` : defaultScenarioInfo.name,
isNew: false,
name: isCopy ? `copy-${defaultScenarioInfo.name}` : defaultScenarioInfo.name,
isNew: isCopy,
isExecute: action === 'execute',
stepResponses: {},
});
} else {
@ -279,7 +293,7 @@
}
}
async function openScenarioTab(record: ApiScenarioTableItem | string, isCopy?: boolean) {
async function openScenarioTab(record: ApiScenarioTableItem | string, action?: 'copy' | 'execute') {
try {
appStore.showLoading();
const res = await getScenarioDetail(typeof record === 'string' ? record : record.id);
@ -287,7 +301,7 @@
if (!res.steps) {
res.steps = [];
}
newTab(res, isCopy);
newTab(res, action);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);

View File

@ -142,18 +142,29 @@ export default {
'apiScenario.quoteMode': '引用模式',
'apiScenario.fullQuote': '完全引用',
'apiScenario.stepQuote': '步骤引用',
'apiScenario.runRule': '运行取值规则设置',
'apiScenario.currentScenario': '当前场景参数',
'apiScenario.sourceScenario': '源场景参数',
'apiScenario.runRule': '参数取值规则',
'apiScenario.currentScenario': '优先当前场景参数',
'apiScenario.sourceScenario': '优先源场景参数',
'apiScenario.currentScenarioTip': '当前场景参数不存在时,取',
'apiScenario.sourceScenarioTip': '源场景参数不存在时,取',
'apiScenario.empty': '空值',
'apiScenario.currentScenarioParams': '当前场景参数',
'apiScenario.sourceScenarioParams': '源场景参数',
'apiScenario.sourceScenarioEnv': '源场景环境',
'apiScenario.valuePriority': '取值优先级:',
'apiScenario.currentScenarioAndNull': '步骤参数>场景参数>环境参数>空值',
'apiScenario.sourceScenarioAndNull': '源场景参数>源场景环境>步骤参数>当前场景参数>当前环境参数',
'apiScenario.currentScenarioAndNull': '当前步骤参数 > 当前场景参数 > 当前环境参数 > 空值',
'apiScenario.currentScenarioAndNullAndSourceEnv': '当前步骤参数 > 当前场景参数 > 源环境参数 > 空值',
'apiScenario.currentScenarioAndSourceScenario': '当前步骤参数 > 当前场景参数 > 当前环境参数 > 源场景参数',
'apiScenario.currentScenarioAndSourceScenarioAndSourceEnv': '当前步骤参数 > 当前场景参数 > 源场景参数> 源环境参数',
'apiScenario.sourceScenarioAndNull': '源场景参数 > 空值',
'apiScenario.sourceScenarioAndNullAndSourceEnv': '源场景参数 > 源环境参数 > 空值',
'apiScenario.sourceScenarioAndCurrentScenario': '源场景参数 > 当前步骤参数 > 当前场景参数 > 当前环境参数',
'apiScenario.sourceScenarioAndCurrentScenarioAndSourceEnv': '源场景参数 > 源环境参数 > 当前步骤参数 > 当前场景参数',
'apiScenario.fullQuoteTip': '完全引用:跟随源步骤内容及步骤状态变化,步骤状态不可调整',
'apiScenario.stepQuoteTip': '步骤引用:仅跟随源步骤内容变化,步骤状态可调整',
'apiScenario.sourceScenarioEnvTip': '运行环境,含环境参数',
'apiScenario.setSuccess': '设置成功',
'apiScenario.pleaseInputUrl': '请输入 url',
// 执行历史
'apiScenario.executeHistory.searchPlaceholder': '通过ID或名称搜索',
'apiScenario.executeHistory.num': '序号',