fix(all): 修复bugs

This commit is contained in:
baiqi 2024-04-11 16:00:10 +08:00 committed by 刘瑞斌
parent b094ebf8eb
commit 2f5e5ada03
31 changed files with 279 additions and 165 deletions

View File

@ -431,7 +431,7 @@
}
}
.arco-checkbox-indeterminate .arco-checkbox-icon {
border-color: rgba(var(--primary-7));
border: 1px solid rgba(var(--primary-7)) !important;
background-color: rgba(var(--primary-1));
&::after {
background-color: rgb(var(--primary-7));

View File

@ -32,6 +32,7 @@
:read-only="props.disabled"
:show-full-screen="false"
:show-theme-change="false"
@change="() => emit('change')"
>
<template #rightBox>
<MsScriptMenu
@ -99,6 +100,9 @@
showHeader: true,
}
);
const emit = defineEmits<{
(e: 'change'): void;
}>();
const { t } = useI18n();

View File

@ -33,8 +33,7 @@
v-if="attrs.selectorType === 'checkbox'"
:total="attrs.showPagination ? (attrs.msPagination as MsPaginationI).total : (attrs.data as MsTableDataItem<TableData>[]).length"
:selected-keys="props.selectedKeys"
:selector-status="props.selectorStatus"
:exclude-keys="[...props.excludeKeys]"
:exclude-keys="Array.from(props.excludeKeys)"
:current-data="attrs.data as Record<string,any>[]"
:show-select-all="!!attrs.showPagination && props.showSelectorAll"
:disabled="(attrs.data as []).length === 0"
@ -785,3 +784,20 @@
}
}
</style>
<style lang="less">
.arco-table-filters-content {
@apply overflow-hidden;
max-width: 300px;
.arco-checkbox-group {
@apply flex w-full flex-col;
.arco-checkbox {
@apply w-full;
.arco-checkbox-label {
@apply flex-1 overflow-hidden;
}
}
}
}
</style>

View File

@ -20,7 +20,7 @@
</template>
<script lang="ts" setup>
import MsIcon from '../ms-icon-font/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import { useI18n } from '@/hooks/useI18n';
@ -41,7 +41,6 @@
currentData: MsTableDataItem<Record<string, any>>[];
showSelectAll: boolean;
disabled: boolean;
selectorStatus: SelectAllEnum;
excludeKeys: string[];
}>(),
{
@ -52,32 +51,31 @@
}
);
const selectAllStatus = ref<SelectAllEnum>(SelectAllEnum.NONE);
const checked = computed({
get: () => {
if (props.selectorStatus !== 'all') {
return props.selectedKeys.size > 0 && props.selectedKeys.size === props.total;
}
if (props.selectorStatus === 'all') {
return !props.excludeKeys?.length
? true
: props.selectedKeys.size > 0 && props.selectedKeys.size === props.total;
}
//
return (
selectAllStatus.value === SelectAllEnum.ALL ||
(props.selectedKeys.size > 0 && props.selectedKeys.size === props.total)
);
},
set: (value) => {
return value;
},
});
const indeterminate = computed(() => {
// 0
if (props.selectorStatus === 'current') {
return props.selectedKeys.size > 0 && props.selectedKeys.size < props.total;
}
if (props.selectorStatus === 'all') {
return !props.excludeKeys?.length ? false : props.selectedKeys.size > 0 && props.selectedKeys.size < props.total;
}
// key 0
return (
props.excludeKeys.length > 0 ||
(selectAllStatus.value !== SelectAllEnum.ALL &&
props.selectedKeys.size > 0 &&
props.selectedKeys.size < props.total)
);
});
const handleSelect = (v: string | number | Record<string, any> | undefined) => {
selectAllStatus.value = v as SelectAllEnum;
emit('change', v as SelectAllEnum);
};

View File

@ -415,7 +415,13 @@ export default function useTableProps<T>(
if (v === SelectAllEnum.NONE) {
// 清空选中项
resetSelector();
} else {
} else if (v === SelectAllEnum.CURRENT) {
// 先清空选中项,再选中当前页面所有数据
resetSelector();
collectIds(data as MsTableDataItem<T>[], rowKey);
} else if (v === SelectAllEnum.ALL) {
// 全选所有页的时候先清空排除项,再选中所有数据
propsRes.value.excludeKeys.clear();
collectIds(data as MsTableDataItem<T>[], rowKey);
}
},

View File

@ -151,6 +151,7 @@
innerInputValue.value = '';
tagsLength.value += 1;
emit('update:modelValue', innerModelValue.value);
emit('change', innerModelValue.value);
}
emit('blur');
}

View File

@ -317,8 +317,8 @@ export interface LoopStepDetail extends StepDetailsCommon {
}
// 场景引用配置
export interface ScenarioStepConfig {
useCurrentScenarioParam: boolean; // 是否优先使用当前场景参数
useBothScenarioParam: boolean; // 是否当前场景参数和源场景参数都应用(勾选非空值时为 true
useOriginScenarioParam: boolean; // 是否优先使用当前场景参数
useOriginScenarioParamPreferential: boolean; // 是否当前场景参数和源场景参数都应用(勾选非空值时为 true
enableScenarioEnv: boolean; // 是否应用源场景环境
}
// 场景步骤详情
@ -436,8 +436,7 @@ export interface ApiScenarioDebugRequest {
reportId: string | number;
steps: ScenarioStepItem[];
projectId: string;
uploadFileIds: string[];
linkFileIds: string[];
stepFileParam: Record<string, ScenarioStepFileParams>;
frontendDebug?: boolean;
}

View File

@ -89,6 +89,7 @@
size="small"
@press-enter="isShowEditScriptNameInput = false"
@blur="isShowEditScriptNameInput = false"
@change="() => emit('change')"
/>
</div>
<div class="flex items-center justify-between px-[12px] pt-[12px]">
@ -195,6 +196,7 @@
:disabled="props.disabled"
show-type="commonScript"
:show-header="false"
@change="() => emit('change')"
/>
</div>
</div>
@ -230,6 +232,7 @@
:columns="scriptColumns"
:height-used="heightUsed"
:selectable="false"
@change="() => emit('change')"
/>
<MsCodeEditor
v-else-if="commonScriptShowType === 'scriptContent' && condition.commonScriptInfo"
@ -703,6 +706,7 @@ if (!result){
};
scriptParams.value = (condition.value.commonScriptInfo?.params as any[]) || [];
showQuoteDrawer.value = false;
Message.success(t('apiTestDebug.introduceSourceApplySuccess'));
}
const showAddScriptDrawer = ref(false);

View File

@ -798,6 +798,16 @@
// 便
if (key) {
nextLine[key] = lastLineData[key];
// Content-Type contentType
if (nextLine.contentType) {
if (lastLineData[key] === 'file') {
nextLine.contentType = RequestContentTypeEnum.OCTET_STREAM;
} else if (lastLineData[key] === 'json') {
nextLine.contentType = RequestContentTypeEnum.JSON;
} else {
nextLine.contentType = RequestContentTypeEnum.TEXT;
}
}
}
});
paramsData.value.push(nextLine);

View File

@ -136,6 +136,7 @@
disabledParamValue?: boolean; //
disabledExceptParam?: boolean; //
secondBoxHeight: number;
isDrawer?: boolean;
uploadTempFileApi?: (file: File) => Promise<any>; //
fileSaveAsSourceId?: string | number; // id
fileSaveAsApi?: (params: TransferFileParams) => Promise<string>; //
@ -267,7 +268,8 @@
watch(
() => props.layout,
(val) => {
heightUsed.value = val === 'horizontal' ? 428 : 430 + props.secondBoxHeight;
const otherHeight = props.isDrawer ? 328 : 430;
heightUsed.value = val === 'horizontal' ? otherHeight : otherHeight + props.secondBoxHeight;
},
{
immediate: true,
@ -278,7 +280,7 @@
() => props.secondBoxHeight,
(val) => {
if (props.layout === 'vertical') {
heightUsed.value = 430 + val;
heightUsed.value = (props.isDrawer ? 328 : 430) + val;
}
},
{

View File

@ -43,6 +43,7 @@
disabledParamValue?: boolean; //
disabledExceptParam?: boolean; //
typeTitle?: string;
isDrawer?: boolean;
}>();
const emit = defineEmits<{
(e: 'update:selectedKeys', value: string[]): void;
@ -83,9 +84,9 @@
const heightUsed = computed(() => {
if (props.layout === 'horizontal') {
return 428;
return props.isDrawer ? 328 : 428;
}
return 428 + props.secondBoxHeight;
return (props.isDrawer ? 328 : 428) + props.secondBoxHeight;
});
const scroll = computed(() => (props.layout === 'horizontal' ? { x: '700px' } : { x: '100%' }));

View File

@ -548,10 +548,11 @@
const isHttpProtocol = computed(() => requestVModel.value.protocol === 'HTTP');
const temporaryResponseMap = {}; // websockettab
const isInitPluginForm = ref(false);
const isSwitchingContent = ref(false); //
function handleActiveDebugChange() {
if (!loading.value || (!isHttpProtocol.value && isInitPluginForm.value)) {
// change
if ((!isSwitchingContent.value && !loading.value) || (!isHttpProtocol.value && isInitPluginForm.value)) {
// change
requestVModel.value.unSaved = true;
}
}
@ -1225,6 +1226,10 @@
watch(
() => requestVModel.value.id,
async () => {
isSwitchingContent.value = true; //
nextTick(() => {
isSwitchingContent.value = false; //
});
if (requestVModel.value.protocol !== 'HTTP') {
requestVModel.value.activeTab = RequestComposition.PLUGIN;
if (protocolOptions.value.length === 0) {

View File

@ -57,7 +57,7 @@
}
//
if (props.isScenario) {
return [RequestConditionProcessor.SCRIPT, RequestConditionProcessor.SQL];
return [RequestConditionProcessor.SCRIPT, RequestConditionProcessor.SQL, RequestConditionProcessor.TIME_WAITING];
}
//
return [RequestConditionProcessor.SCRIPT, RequestConditionProcessor.TIME_WAITING];

View File

@ -53,6 +53,7 @@
disabledParamValue?: boolean; //
disabledExceptParam?: boolean; //
secondBoxHeight: number;
isDrawer?: boolean;
}>();
const emit = defineEmits<{
(e: 'update:params', value: any[]): void;
@ -122,7 +123,8 @@
watch(
() => props.layout,
(val) => {
heightUsed.value = val === 'horizontal' ? 428 : 428 + props.secondBoxHeight;
const otherHeight = props.isDrawer ? 328 : 430;
heightUsed.value = val === 'horizontal' ? otherHeight : otherHeight + props.secondBoxHeight;
},
{
immediate: true,
@ -133,7 +135,7 @@
() => props.secondBoxHeight,
(val) => {
if (props.layout === 'vertical') {
heightUsed.value = 428 + val;
heightUsed.value = (props.isDrawer ? 328 : 430) + val;
}
},
{

View File

@ -4,7 +4,7 @@
class="flex items-center justify-between gap-[24px] text-[14px]"
>
<a-tooltip :content="props.requestResult.fakeErrorCode">
<executeStatus :status="props.requestResult.status" size="small" class="ml-[4px]" />
<executeStatus :status="finalStatus" size="small" class="ml-[4px]" />
</a-tooltip>
<a-popover position="left" content-class="response-popover-content">
<div class="one-line-text max-w-[200px]" :style="{ color: statusCodeColor }">
@ -52,6 +52,7 @@
import { useI18n } from '@/hooks/useI18n';
import { RequestResult } from '@/models/apiTest/common';
import { ScenarioExecuteStatus } from '@/enums/apiEnum';
const props = defineProps<{
requestResult?: RequestResult;
@ -99,6 +100,12 @@
}
return '';
});
const finalStatus = computed(() => {
if (props.requestResult?.fakeErrorCode) {
return ScenarioExecuteStatus.FAKE_ERROR;
}
return props.requestResult?.isSuccessful ? ScenarioExecuteStatus.SUCCESS : ScenarioExecuteStatus.FAILED;
});
</script>
<style lang="less">

View File

@ -53,6 +53,7 @@
secondBoxHeight: number;
disabledParamValue?: boolean; //
disabledExceptParam?: boolean; //
isDrawer?: boolean;
}>();
const emit = defineEmits<{
(e: 'update:params', value: any[]): void;
@ -123,7 +124,8 @@
watch(
() => props.layout,
(val) => {
heightUsed.value = val === 'horizontal' ? 428 : 428 + props.secondBoxHeight;
const otherHeight = props.isDrawer ? 328 : 430;
heightUsed.value = val === 'horizontal' ? otherHeight : otherHeight + props.secondBoxHeight;
},
{
immediate: true,
@ -134,7 +136,7 @@
() => props.secondBoxHeight,
(val) => {
if (props.layout === 'vertical') {
heightUsed.value = 428 + val;
heightUsed.value = (props.isDrawer ? 328 : 430) + val;
}
},
{

View File

@ -1,7 +1,5 @@
import { cloneDeep, isEqual } from 'lodash-es';
import { RequestParam } from './requestComposition/index.vue';
import { ExecuteBody } from '@/models/apiTest/common';
import { RequestParamsType } from '@/enums/apiEnum';
@ -11,6 +9,7 @@ import {
defaultKeyValueParamItem,
defaultRequestParamsItem,
} from './config';
import type { RequestParam } from './requestComposition/index.vue';
export interface ParseResult {
uploadFileIds: string[];

View File

@ -348,7 +348,6 @@
sortDefinition,
updateDefinition,
} from '@/api/modules/api-test/management';
import { getProjectOptions } from '@/api/modules/project-management/projectMember';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import useTableStore from '@/hooks/useTableStore';

View File

@ -23,13 +23,31 @@
v-if="props.step && [ScenarioStepType.API, ScenarioStepType.CUSTOM_REQUEST].includes(props.step?.stepType)"
:step="props.step"
/>
<a-tooltip :content="title" position="bottom">
<a-tooltip v-if="!isShowEditStepNameInput" :content="title" position="bottom">
<div class="flex items-center gap-[4px]">
<div class="one-line-text">
{{ title }}
</div>
<MsIcon
type="icon-icon_edit_outlined"
class="cursor-pointer hover:text-[rgb(var(--primary-5))]"
@click="isShowEditStepNameInput = true"
/>
</div>
</a-tooltip>
</div>
<div class="ml-auto flex items-center gap-[16px]">
<a-input
v-if="isShowEditStepNameInput"
ref="stepNameInputRef"
v-model:model-value="requestVModel.name"
class="flex-1"
:placeholder="t('apiScenario.pleaseInputStepName')"
:max-length="255"
show-word-limit
@press-enter="updateStepName"
@blur="updateStepName"
/>
<div v-show="!isShowEditStepNameInput" class="ml-auto flex items-center gap-[16px]">
<div
v-if="!props.step || props.step?.stepType === ScenarioStepType.CUSTOM_REQUEST"
class="customApiDrawer-title-right flex items-center gap-[16px]"
@ -184,7 +202,7 @@
class="no-content relative mt-[8px] border-b"
/>
</div>
<div ref="splitContainerRef" class="h-[calc(100%-87px)]">
<div ref="splitContainerRef" class="h-[calc(100%-97px)]">
<MsSplitBox
ref="verticalSplitBoxRef"
v-model:size="splitBoxSize"
@ -228,6 +246,7 @@
:disabled-except-param="!isEditableApi"
:layout="activeLayout"
:second-box-height="secondBoxHeight"
is-drawer
@change="handleActiveDebugChange"
/>
<httpBody
@ -241,6 +260,7 @@
:file-save-as-source-id="scenarioId"
:file-save-as-api="transferFile"
:file-module-options-api="getTransferOptions"
is-drawer
@change="handleActiveDebugChange"
/>
<httpQuery
@ -250,6 +270,7 @@
:disabled-param-value="!isEditableApi && !isEditableParamValue"
:disabled-except-param="!isEditableApi"
:second-box-height="secondBoxHeight"
is-drawer
@change="handleActiveDebugChange"
/>
<httpRest
@ -259,6 +280,7 @@
:disabled-param-value="!isEditableApi && !isEditableParamValue"
:disabled-except-param="!isEditableApi"
:second-box-height="secondBoxHeight"
is-drawer
@change="handleActiveDebugChange"
/>
<precondition
@ -542,9 +564,9 @@
_stepType.value.isQuoteApi ||
props.step?.stepType === ScenarioStepType.CUSTOM_REQUEST
) {
return props.step?.name;
return requestVModel.value.name || props.step?.name;
}
return t('apiScenario.customApi');
return requestVModel.value.name || t('apiScenario.customApi');
});
//
const showEnvPrefix = computed(
@ -589,16 +611,22 @@
const isEditableParamValue = computed(() => !props.step?.isQuoteScenarioStep && _stepType.value.isQuoteApi);
// HTTP
const isHttpProtocol = computed(() => requestVModel.value.protocol === 'HTTP');
const isInitPluginForm = ref(false);
const isSwitchingContent = ref(false); //
function handleActiveDebugChange() {
if (!loading.value || (!isHttpProtocol.value && isInitPluginForm.value)) {
if ((!isSwitchingContent.value && !loading.value) || (!isHttpProtocol.value && isInitPluginForm.value)) {
// change
requestVModel.value.unSaved = true;
}
}
const isShowEditStepNameInput = ref(false);
function updateStepName() {
isShowEditStepNameInput.value = false;
}
// tabKey
const commonContentTabKey = [
RequestComposition.PRECONDITION,
@ -1159,6 +1187,16 @@
emit('replace', newStep);
}
watch(
() => props.request?.stepId,
() => {
isSwitchingContent.value = true;
},
{
immediate: true,
}
);
watch(
() => visible.value,
async (val) => {
@ -1194,6 +1232,9 @@
});
}
requestVModel.value.activeTab = contentTabList.value[0].value;
nextTick(() => {
isSwitchingContent.value = false;
});
}
},
{

View File

@ -32,7 +32,11 @@
<a-tooltip :content="activeStep?.name">
<div class="one-line-text max-w-[300px]"> {{ characterLimit(activeStep?.name) }}</div>
</a-tooltip>
<MsIcon type="icon-icon_edit_outlined" class="edit-script-name-icon" @click="showEditScriptNameInput" />
<MsIcon
type="icon-icon_edit_outlined"
class="cursor-pointer hover:text-[rgb(var(--primary-5))]"
@click="showEditScriptNameInput"
/>
</div>
<div
v-if="activeStep && !activeStep.isQuoteScenarioStep && activeStep.resourceId"
@ -117,7 +121,7 @@
class="relative mt-[8px] border-b"
/>
</div>
<div ref="splitContainerRef" class="h-[calc(100%-87px)]">
<div ref="splitContainerRef" class="h-[calc(100%-97px)]">
<MsSplitBox
ref="verticalSplitBoxRef"
v-model:size="splitBoxSize"
@ -466,6 +470,7 @@
const isEditableApi = computed(() => !activeStep.value?.isQuoteScenarioStep && _stepType.value.isCopyCase);
const isHttpProtocol = computed(() => requestVModel.value.protocol === 'HTTP');
const isInitPluginForm = ref(false);
const isSwitchingContent = ref(false); //
const loading = ref(false);
function handleActiveDebugChange() {
@ -991,6 +996,16 @@
emit('replace', newStep);
}
watch(
() => props.request?.stepId,
() => {
isSwitchingContent.value = true;
},
{
immediate: true,
}
);
watch(
() => visible.value,
async (val) => {
@ -1010,6 +1025,9 @@
await initQuoteCaseDetail();
}
handleActiveDebugProtocolChange(requestVModel.value.protocol);
nextTick(() => {
isSwitchingContent.value = false;
});
}
}
);

View File

@ -24,7 +24,13 @@
/>
</div>
<div class="mt-[10px] flex flex-1 gap-[8px]">
<conditionContent v-if="visible" v-model:data="activeItem" :disabled="isReadonly" :is-build-in="true" />
<conditionContent
v-if="visible"
v-model:data="activeItem"
:disabled="isReadonly"
:is-build-in="true"
@change="unSaved = true"
/>
</div>
<div v-if="currentResponse?.console" class="p-[8px]">
<div class="mb-[8px] font-medium text-[var(--color-text-1)]">{{ t('apiScenario.executionResult') }}</div>
@ -72,7 +78,7 @@
}>();
const emit = defineEmits<{
(e: 'add', name: string, scriptProcessor: ExecuteConditionProcessor): void;
(e: 'save', name: string, scriptProcessor: ExecuteConditionProcessor): void;
(e: 'save', name: string, scriptProcessor: ExecuteConditionProcessor, unSaved: boolean): void;
}>();
const defaultScript = {
@ -90,6 +96,7 @@
const visible = defineModel<boolean>('visible', { required: true });
const isReadonly = computed(() => props.step?.isQuoteScenarioStep);
const unSaved = ref(false);
const currentLoop = ref(1);
const currentResponse = computed(() => {
if (props.step?.uniqueId) {
@ -131,7 +138,7 @@
function handleClose() {
if (props.detail) {
emit('save', scriptName.value, activeItem.value);
emit('save', scriptName.value, activeItem.value, unSaved.value);
}
scriptName.value = '';
activeItem.value = defaultScript as unknown as ExecuteConditionProcessor;

View File

@ -54,8 +54,8 @@ export const defaultTimeController = {
// 场景配置
export const defaultScenarioStepConfig: ScenarioStepConfig = {
enableScenarioEnv: false,
useBothScenarioParam: false,
useCurrentScenarioParam: true,
useOriginScenarioParamPreferential: true,
useOriginScenarioParam: false,
};
export const defaultStepItemCommon = {

View File

@ -27,40 +27,6 @@
</div>
</a-tooltip>
</div>
<div style="font-weight: bold">
{{ t('apiScenario.setting.run.config') }}
</div>
<div class="mb-[16px] mt-[10px] flex items-center gap-[8px]">
<a-switch v-model:model-value="form.enableStepWait" type="line" size="small" @change="emit('change')" />
{{ t('apiScenario.setting.step.waitTime') }}
<a-tooltip :content="t('apiScenario.setting.waitTime.tip')">
<div>
<MsIcon
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
type="icon-icon-maybe_outlined"
/>
</div>
</a-tooltip>
</div>
<a-form-item v-if="form.stepWaitTime !== null || form.stepWaitTime !== undefined" class="flex-1">
<template #label>
<div class="flex items-center">
{{ t('apiScenario.setting.waitTime') }}
<div class="text-[var(--color-text-brand)]">(ms)</div>
</div>
</template>
<a-input-number
v-model:model-value="form.stepWaitTime"
mode="button"
:step="100"
:min="0"
:max="600000"
model-event="input"
class="w-[160px]"
@blur="handleInputChange"
/>
</a-form-item>
<a-form-item class="flex-1">
<template #label>

View File

@ -317,35 +317,49 @@
<a-radio :value="ScenarioStepRefType.PARTIAL_REF">{{ t('apiScenario.stepQuote') }}</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item :label="t('apiScenario.runRule')">
<a-radio-group v-model:model-value="scenarioConfigForm.useCurrentScenarioParam" type="button">
<a-radio :value="true">{{ t('apiScenario.currentScenario') }}</a-radio>
<a-radio :value="false">{{ t('apiScenario.sourceScenario') }}</a-radio>
</a-radio-group>
<a-form-item label="" class="hidden-item">
<div class="flex items-center gap-[8px]">
<a-switch
v-model:model-value="scenarioConfigForm.useOriginScenarioParam"
class="ml-[6px]"
size="small"
></a-switch>
{{ t('apiScenario.sourceScenarioParams') }}
</div>
</a-form-item>
<a-form-item
:label="
scenarioConfigForm.useCurrentScenarioParam
? t('apiScenario.currentScenarioTip')
: t('apiScenario.sourceScenarioTip')
"
>
<a-radio-group v-model:model-value="scenarioConfigForm.useBothScenarioParam">
<a-radio :value="false">{{ t('apiScenario.empty') }}</a-radio>
<a-form-item v-show="scenarioConfigForm.useOriginScenarioParam" class="hidden-item">
<a-radio-group v-model:model-value="scenarioConfigForm.useOriginScenarioParamPreferential" type="button">
<a-radio :value="true">
{{
t(
scenarioConfigForm.useCurrentScenarioParam
? 'apiScenario.sourceScenarioParams'
: 'apiScenario.currentScenarioParams'
)
}}
<div class="flex items-center gap-[4px]">
{{ t('apiScenario.sourceScenario') }}
<a-tooltip :content="t('apiScenario.sourceScenarioTip')" position="right">
<icon-question-circle
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
size="16"
/>
</a-tooltip>
</div>
</a-radio>
<a-radio :value="false">
<div class="flex items-center gap-[4px]">
{{ t('apiScenario.currentScenario') }}
<a-tooltip :content="t('apiScenario.currentScenarioTip')" position="right">
<icon-question-circle
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
size="16"
/>
</a-tooltip>
</div>
</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="" class="hidden-item !mb-0">
<div class="flex items-center gap-[8px]">
<a-checkbox v-model:model-value="scenarioConfigForm.enableScenarioEnv" class="ml-[6px]"></a-checkbox>
<a-switch
v-model:model-value="scenarioConfigForm.enableScenarioEnv"
class="ml-[6px]"
size="small"
></a-switch>
<div class="flex items-center gap-[4px]">
{{ t('apiScenario.sourceScenarioEnv') }}
<a-tooltip :content="t('apiScenario.sourceScenarioEnvTip')" position="right">
@ -760,70 +774,70 @@
>({
refType: ScenarioStepRefType.REF,
enableScenarioEnv: false,
useBothScenarioParam: false,
useCurrentScenarioParam: true,
useOriginScenarioParamPreferential: true,
useOriginScenarioParam: false,
});
const showScenarioConfig = ref(false);
const scenarioConfigParamTip = computed(() => {
if (
scenarioConfigForm.value.useCurrentScenarioParam &&
!scenarioConfigForm.value.useBothScenarioParam &&
scenarioConfigForm.value.useOriginScenarioParam &&
!scenarioConfigForm.value.useOriginScenarioParamPreferential &&
!scenarioConfigForm.value.enableScenarioEnv
) {
// 使-
return t('apiScenario.currentScenarioAndNull');
}
if (
scenarioConfigForm.value.useCurrentScenarioParam &&
!scenarioConfigForm.value.useBothScenarioParam &&
scenarioConfigForm.value.useOriginScenarioParam &&
!scenarioConfigForm.value.useOriginScenarioParamPreferential &&
scenarioConfigForm.value.enableScenarioEnv
) {
// 使--
return t('apiScenario.currentScenarioAndNullAndSourceEnv');
}
if (
scenarioConfigForm.value.useCurrentScenarioParam &&
scenarioConfigForm.value.useBothScenarioParam &&
scenarioConfigForm.value.useOriginScenarioParam &&
scenarioConfigForm.value.useOriginScenarioParamPreferential &&
!scenarioConfigForm.value.enableScenarioEnv
) {
// 使-
return t('apiScenario.currentScenarioAndSourceScenario');
}
if (
scenarioConfigForm.value.useCurrentScenarioParam &&
scenarioConfigForm.value.useBothScenarioParam &&
scenarioConfigForm.value.useOriginScenarioParam &&
scenarioConfigForm.value.useOriginScenarioParamPreferential &&
scenarioConfigForm.value.enableScenarioEnv
) {
// 使--
return t('apiScenario.currentScenarioAndSourceScenarioAndSourceEnv');
}
if (
!scenarioConfigForm.value.useCurrentScenarioParam &&
!scenarioConfigForm.value.useBothScenarioParam &&
!scenarioConfigForm.value.useOriginScenarioParam &&
!scenarioConfigForm.value.useOriginScenarioParamPreferential &&
!scenarioConfigForm.value.enableScenarioEnv
) {
// 使-
return t('apiScenario.sourceScenarioAndNull');
}
if (
!scenarioConfigForm.value.useCurrentScenarioParam &&
!scenarioConfigForm.value.useBothScenarioParam &&
!scenarioConfigForm.value.useOriginScenarioParam &&
!scenarioConfigForm.value.useOriginScenarioParamPreferential &&
scenarioConfigForm.value.enableScenarioEnv
) {
// 使--
return t('apiScenario.sourceScenarioAndNullAndSourceEnv');
}
if (
!scenarioConfigForm.value.useCurrentScenarioParam &&
scenarioConfigForm.value.useBothScenarioParam &&
!scenarioConfigForm.value.useOriginScenarioParam &&
scenarioConfigForm.value.useOriginScenarioParamPreferential &&
!scenarioConfigForm.value.enableScenarioEnv
) {
// 使-
return t('apiScenario.sourceScenarioAndCurrentScenario');
}
if (
!scenarioConfigForm.value.useCurrentScenarioParam &&
scenarioConfigForm.value.useBothScenarioParam &&
!scenarioConfigForm.value.useOriginScenarioParam &&
scenarioConfigForm.value.useOriginScenarioParamPreferential &&
scenarioConfigForm.value.enableScenarioEnv
) {
// 使--
@ -837,8 +851,8 @@
scenarioConfigForm.value = {
refType: ScenarioStepRefType.REF,
enableScenarioEnv: false,
useBothScenarioParam: false,
useCurrentScenarioParam: true,
useOriginScenarioParamPreferential: true,
useOriginScenarioParam: false,
};
}
@ -1382,10 +1396,7 @@
}
async function realExecute(
executeParams: Pick<
ApiScenarioDebugRequest,
'steps' | 'stepDetails' | 'reportId' | 'uploadFileIds' | 'linkFileIds'
>,
executeParams: Pick<ApiScenarioDebugRequest, 'steps' | 'stepDetails' | 'reportId' | 'stepFileParam'>,
executeType?: 'localExec' | 'serverExec'
) {
const [currentStep] = executeParams.steps;
@ -1455,8 +1466,9 @@
steps: [realStep as ScenarioStepItem],
stepDetails: _stepDetails,
reportId: realStep.reportId,
uploadFileIds: stepFileParam?.uploadFileIds || [],
linkFileIds: stepFileParam?.linkFileIds || [],
stepFileParam: {
[realStep.id]: stepFileParam,
},
},
isPriorityLocalExec?.value ? 'localExec' : 'serverExec'
);
@ -1474,6 +1486,7 @@
delete scenario.value.stepResponses[realStep.uniqueId]; //
realStep.reportId = getGenerateId();
realStep.executeStatus = ScenarioExecuteStatus.EXECUTING;
const stepFileParam = scenario.value.stepFileParam[realStep.id];
request.executeLoading = true;
realExecute(
{
@ -1482,15 +1495,15 @@
[realStep.id]: request,
},
reportId: realStep.reportId,
uploadFileIds: request.uploadFileIds || [],
linkFileIds: request.linkFileIds || [],
stepFileParam: {
[realStep.uniqueId]: stepFileParam,
},
},
executeType
);
} else {
//
const reportId = getGenerateId();
delete scenario.value.stepResponses[request.stepId]; //
request.executeLoading = true;
activeStep.value = {
id: request.stepId,
@ -1513,9 +1526,13 @@
[request.stepId]: request,
},
reportId,
stepFileParam: {
[request.stepId]: {
uploadFileIds: request.uploadFileIds || [],
linkFileIds: request.linkFileIds || [],
},
},
},
executeType
);
}
@ -1668,7 +1685,7 @@
handleCreateStep(
{
stepType: ScenarioStepType.CUSTOM_REQUEST,
name: t('apiScenario.customApi'),
name: request.name || t('apiScenario.customApi'),
config: {
protocol: request.protocol,
method: request.method,
@ -1694,7 +1711,7 @@
sort: steps.value.length + 1,
stepType: ScenarioStepType.CUSTOM_REQUEST,
refType: ScenarioStepRefType.DIRECT,
name: t('apiScenario.customApi'),
name: request.name || t('apiScenario.customApi'),
projectId: appStore.currentProjectId,
});
}
@ -1731,6 +1748,7 @@
...activeStep.value.config,
method: request.method,
};
activeStep.value.name = request.name;
emit('updateResource', request.uploadFileIds, request.linkFileIds);
activeStep.value = undefined;
}
@ -1788,11 +1806,14 @@
scenario.value.unSaved = true;
}
function saveScriptStep(name: string, scriptProcessor: ExecuteConditionProcessor) {
function saveScriptStep(name: string, scriptProcessor: ExecuteConditionProcessor, unSaved = false) {
if (activeStep.value) {
stepDetails.value[activeStep.value.id] = cloneDeep(scriptProcessor);
activeStep.value.name = name;
activeStep.value = undefined;
if (unSaved) {
scenario.value.unSaved = true;
}
}
}

View File

@ -32,6 +32,7 @@
:scenario="scenario as ScenarioDetail"
:module-tree="props.moduleTree"
class="w-[30%]"
@change="scenario.unSaved = true"
/>
</a-tab-pane>
<a-tab-pane

View File

@ -256,9 +256,8 @@
environmentId: currentEnvConfig.value?.id || '',
projectId: appStore.currentProjectId,
scenarioConfig: activeScenarioTab.value.scenarioConfig,
uploadFileIds: activeScenarioTab.value.uploadFileIds,
linkFileIds: activeScenarioTab.value.linkFileIds,
...executeParams,
stepFileParam: activeScenarioTab.value.stepFileParam,
steps: mapTree(executeParams.steps, (node) => {
return {
...node,
@ -274,8 +273,7 @@
environmentId: currentEnvConfig.value?.id || '',
projectId: appStore.currentProjectId,
scenarioConfig: activeScenarioTab.value.scenarioConfig,
uploadFileIds: activeScenarioTab.value.uploadFileIds,
linkFileIds: activeScenarioTab.value.linkFileIds,
stepFileParam: activeScenarioTab.value.stepFileParam,
frontendDebug: executeType === 'localExec',
...executeParams,
steps: mapTree(executeParams.steps, (node) => {

View File

@ -167,7 +167,7 @@ export default {
'apiScenario.empty': 'Empty Value',
'apiScenario.currentScenarioParams': 'Current Scenario Parameters',
'apiScenario.sourceScenarioParams': 'Source Scenario Parameters',
'apiScenario.sourceScenarioEnv': 'Source Scenario Environment',
'apiScenario.sourceScenarioEnv': 'Use source Scenario Environment',
'apiScenario.valuePriority': 'Value Priority:',
'apiScenario.currentScenarioAndNull':
'Current Step Parameters > Current Scenario Parameters > Current Environment Parameters > Empty Value',

View File

@ -160,12 +160,12 @@ export default {
'apiScenario.runRule': '参数取值规则',
'apiScenario.currentScenario': '优先当前场景参数',
'apiScenario.sourceScenario': '优先原场景参数',
'apiScenario.currentScenarioTip': '当前场景参数不存在时,取',
'apiScenario.sourceScenarioTip': '原场景参数不存在时,取',
'apiScenario.sourceScenarioTip': '优先使用原场景参数,没有则取当前场景参数',
'apiScenario.currentScenarioTip': '优先使用当前场景参数,没有则取原场景参数',
'apiScenario.empty': '空值',
'apiScenario.currentScenarioParams': '当前场景参数',
'apiScenario.sourceScenarioParams': '原场景参数',
'apiScenario.sourceScenarioEnv': '原场景环境',
'apiScenario.sourceScenarioParams': '使用原场景参数',
'apiScenario.sourceScenarioEnv': '使用原场景环境',
'apiScenario.valuePriority': '取值优先级:',
'apiScenario.currentScenarioAndNull': '当前步骤参数 > 当前场景参数 > 当前环境参数 > 空值',
'apiScenario.currentScenarioAndNullAndSourceEnv': '当前步骤参数 > 当前场景参数 > 原环境参数 > 空值',
@ -177,7 +177,7 @@ export default {
'apiScenario.sourceScenarioAndCurrentScenarioAndSourceEnv': '原场景参数 > 原环境参数 > 当前步骤参数 > 当前场景参数',
'apiScenario.fullQuoteTip': '完全引用:跟随原步骤内容及步骤状态变化,步骤状态不可调整',
'apiScenario.stepQuoteTip': '步骤引用:仅跟随原步骤内容变化,步骤状态可调整',
'apiScenario.sourceScenarioEnvTip': '运行环境,含环境参数',
'apiScenario.sourceScenarioEnvTip': '使用原场景运行环境,含环境参数',
'apiScenario.setSuccess': '设置成功',
'apiScenario.pleaseInputUrl': '请输入 url',
'apiScenario.syncSaveAsCase': '同步添加测试接口用例',

View File

@ -1,23 +1,25 @@
<template>
<a-trigger v-model:popup-visible="innerVisible" trigger="click" @popup-visible-change="handleFilterHidden">
<a-button type="text" class="arco-btn-text--secondary p-[8px_4px]" @click.stop="innerVisible = true">
<a-button type="text" class="arco-btn-text--secondary p-[8px_4px]" size="small" @click.stop="innerVisible = true">
<div class="font-medium">
{{ t(props.title) }}
</div>
<icon-down :class="innerVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
</a-button>
<template #content>
<div class="arco-table-filters-content max-w-[400px]">
<div class="ml-[6px] flex items-center justify-start px-[6px] py-[2px]">
<div class="arco-table-filters-content">
<div class="ml-[6px] flex w-full items-center justify-start overflow-hidden px-[6px] py-[2px]">
<a-checkbox-group v-model:model-value="innerStatusFilters" direction="vertical" size="small">
<a-checkbox
v-for="(item, index) of props.list"
:key="item[props.valueKey || 'value']"
:value="item[props.valueKey || 'value']"
>
<div class="one-line-text max-w-[300px]">
<a-tooltip :content="item[props.labelKey || 'text']" :mouse-enter-delay="300">
<div class="one-line-text">
<slot name="item" :item="item" :index="index"></slot>
</div>
</a-tooltip>
</a-checkbox>
</a-checkbox-group>
</div>
@ -41,12 +43,17 @@
const { t } = useI18n();
export interface FilterListItem {
[key: string]: any;
}
const props = defineProps<{
visible: boolean;
title: string;
statusFilters: string[];
list: any[];
list: FilterListItem[];
valueKey?: string;
labelKey?: string;
}>();
const emit = defineEmits<{