feat(接口场景): 脚本操作&api、case 文件处理&交互优化

This commit is contained in:
baiqi 2024-03-29 18:00:45 +08:00 committed by Craftsman
parent 7048ec91e2
commit 56ad892491
30 changed files with 390 additions and 244 deletions

View File

@ -49,7 +49,7 @@
"@tiptap/vue-3": "^2.1.13",
"@types/color": "^3.0.4",
"@types/node": "^20.11.16",
"@vueuse/core": "^10.7.2",
"@vueuse/core": "^10.9.0",
"@xmldom/xmldom": "^0.8.10",
"ace-builds": "^1.24.2",
"ahooks-vue": "^0.15.1",

View File

@ -53,7 +53,7 @@
onBeforeMount(async () => {
try {
await appStore.initSystemVersion(); //
appStore.initSystemVersion(); //
// license
if (appStore.packageType === 'enterprise') {
licenseStore.getValidateLicense();
@ -115,7 +115,7 @@
setLocalStorage('salt', publicKey);
};
onMounted(async () => {
onBeforeMount(async () => {
await getPublicKey();
if (WHITE_LIST.find((el) => el.path === window.location.hash.split('#')[1]) === undefined) {
await checkIsLogin();

View File

@ -35,6 +35,7 @@ import {
UpdateScenarioUrl,
} from '@/api/requrls/api-test/scenario';
import { ExecuteConditionProcessor } from '@/models/apiTest/common';
import {
ApiScenarioBatchDeleteParams,
ApiScenarioBatchEditParams,
@ -55,6 +56,9 @@ import {
} from '@/models/apiTest/scenario';
import { AddModuleParams, CommonList, ModuleTreeNode, MoveModules, TransferFileParams } from '@/models/common';
import type { RequestParam as CaseRequestParam } from '@/views/api-test/components/requestComposition/index.vue';
import type { RequestParam } from '@/views/api-test/scenario/components/common/customApiDrawer.vue';
// 更新模块
export function updateModule(data: ApiScenarioModuleUpdateParams) {
return MSR.post({ url: UpdateModuleUrl, data });
@ -207,7 +211,10 @@ export function getScenarioDetail(id: string) {
// 获取场景步骤详情
export function getScenarioStep(stepId: string | number) {
return MSR.get({ url: GetScenarioStepUrl, params: stepId });
return MSR.get<Partial<RequestParam & CaseRequestParam & ExecuteConditionProcessor>>({
url: GetScenarioStepUrl,
params: stepId,
});
}
// 文件转存

View File

@ -68,31 +68,31 @@
arrow-class="hidden"
:popup-offset="0"
>
<MsTagsInput
v-model:model-value="inputFiles"
:disabled="props.disabled"
:input-class="props.inputClass"
placeholder=" "
:max-tag-count="1"
:size="props.inputSize"
readonly
class="!w-[calc(100%-28px)]"
>
<template v-if="alreadyDeleteFiles.length > 0" #prefix>
<icon-exclamation-circle-fill class="!text-[rgb(var(--warning-6))]" :size="18" />
</template>
<template #tag="{ data }">
<MsTag
:size="props.tagSize"
class="m-0 border-none p-0"
:self-style="{ backgroundColor: 'transparent !important' }"
:closable="data.value !== '__arco__more'"
@close="handleClose(data)"
>
{{ data.value === '__arco__more' ? data.label.replace('...', '') : data.label }}
</MsTag>
</template>
</MsTagsInput>
<div class="!w-[calc(100%-28px)]">
<MsTagsInput
v-model:model-value="inputFiles"
:input-class="props.inputClass"
placeholder=" "
:max-tag-count="1"
:size="props.inputSize"
readonly
>
<template v-if="alreadyDeleteFiles.length > 0" #prefix>
<icon-exclamation-circle-fill class="!text-[rgb(var(--warning-6))]" :size="18" />
</template>
<template #tag="{ data }">
<MsTag
:size="props.tagSize"
class="m-0 border-none p-0"
:self-style="{ backgroundColor: 'transparent !important' }"
:closable="data.value !== '__arco__more'"
@close="handleClose(data)"
>
{{ data.value === '__arco__more' ? data.label.replace('...', '') : data.label }}
</MsTag>
</template>
</MsTagsInput>
</div>
<template #content>
<div class="flex w-[200px] flex-col gap-[8px]">
<template v-if="alreadyDeleteFiles.length > 0">
@ -379,6 +379,14 @@
function handleSaveFileFinish(fileId: string) {
if (savingFile.value) {
inputFiles.value = inputFiles.value.map((e) => {
if (e.value === savingFile.value?.fileId || e.value === savingFile.value?.uid) {
// uidfileId uidfileId
e.value = fileId;
e.local = false;
}
return e;
});
savingFile.value.fileId = fileId;
savingFile.value.local = false;
}

View File

@ -23,12 +23,12 @@
<div class="relative w-full">
<MsCodeEditor
ref="codeEditorRef"
v-model:model-value="innerCodeValue"
v-model:model-value="code"
title=""
:width="expandMenu ? '100%' : '68%'"
height="460px"
theme="vs"
:language="innerLanguagesType"
:language="language"
:read-only="props.disabled"
:show-full-screen="false"
:show-theme-change="false"
@ -36,7 +36,7 @@
<template #rightBox>
<MsScriptMenu
v-model:expand="expandMenu"
v-model:languagesType="innerLanguagesType"
v-model:languagesType="language"
:disabled="props.disabled"
@insert="insertHandler"
@form-api-import="formApiImport"
@ -48,7 +48,7 @@
</div>
<MsCodeEditor
v-else
v-model:model-value="executionResultValue"
v-model:model-value="executionResult"
title=""
width="100%"
height="calc(100vh - 155px)"
@ -60,7 +60,7 @@
/>
<InsertCommonScript
v-model:visible="showInsertDrawer"
:script-language="innerLanguagesType"
:script-language="language"
:enable-radio-selected="props.enableRadioSelected"
@save="saveHandler"
/>
@ -73,7 +73,6 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useVModel } from '@vueuse/core';
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
import { Language, LanguageEnum } from '@/components/pure/ms-code-editor/types';
@ -93,43 +92,27 @@
defineProps<{
showType: 'commonScript' | 'executionResult'; //
disabled?: boolean;
language: Language;
code: string;
enableRadioSelected?: boolean;
executionResult?: string; //
showHeader?: boolean;
}>(),
{
showHeader: true,
}
);
const emit = defineEmits<{
(e: 'update:language', value: Language): void;
(e: 'update:code', value: string): void;
}>();
const { t } = useI18n();
const projectId = ref<string>(appStore.currentProjectId);
const innerLanguagesType = useVModel(props, 'language', emit);
const executionResultValue = useVModel(props, 'executionResult', emit);
watch(
() => innerLanguagesType.value,
(val) => {
emit('update:language', val);
}
);
const innerCodeValue = useVModel(props, 'code', emit);
watch(
() => props.code,
(val) => {
innerCodeValue.value = val;
}
);
const language = defineModel<Language>('language', {
required: true,
});
const executionResult = defineModel<string>('executionResult', {
default: '',
});
const code = defineModel<string>('code', {
required: true,
});
const expandMenu = ref<boolean>(false);
@ -153,8 +136,8 @@
const confirmLoading = ref<boolean>(false);
function insertHandler(code: string) {
codeEditorRef.value?.insertContent(code);
function insertHandler(_code: string) {
codeEditorRef.value?.insertContent(_code);
}
function saveHandler(data: CommonScriptItem[]) {
@ -185,7 +168,7 @@ ${item.script}
}
function clearCode() {
innerCodeValue.value = '';
code.value = '';
}
defineExpose({
@ -193,7 +176,7 @@ ${item.script}
insertHandler,
undoHandler,
clearCode,
executionResultValue,
executionResult,
});
</script>

View File

@ -90,6 +90,7 @@
v-permission="props.okPermission || []"
type="secondary"
:loading="props.okLoading"
:disabled="okDisabled"
@click="handleContinue"
>
{{ t(props.saveContinueText || 'ms.drawer.saveContinue') }}

View File

@ -16,6 +16,7 @@
@press-enter="tagInputEnter"
@blur="tagInputBlur"
@clear="emit('clear')"
@click="emit('click')"
>
<template v-if="$slots.prefix" #prefix>
<slot name="prefix"></slot>
@ -66,7 +67,7 @@
size: 'medium',
}
);
const emit = defineEmits(['update:modelValue', 'update:inputValue', 'change', 'clear', 'blur']);
const emit = defineEmits(['update:modelValue', 'update:inputValue', 'change', 'clear', 'blur', 'click']);
const { t } = useI18n();

View File

@ -11,12 +11,17 @@ export default function useOpenNewPage() {
const appStore = useAppStore();
const router = useRouter();
function openNewPage(name: RouteRecordName | undefined, query = {}) {
function openNewPage(name: RouteRecordName | undefined, query: Record<string, any> = {}) {
const pId = query.pId || appStore.currentProjectId;
if (pId) {
// 如果传入参数指定了项目 id则使用传入的项目 id
delete query.pId;
}
const queryParams = new URLSearchParams(query).toString();
window.open(
`${window.location.origin}#${router.resolve({ name }).fullPath}?orgId=${appStore.currentOrgId}&projectId=${
appStore.currentProjectId
}&${queryParams}`,
`${window.location.origin}#${router.resolve({ name }).fullPath}?orgId=${
appStore.currentOrgId
}&pId=${pId}&${queryParams}`,
'_blank'
);
}

View File

@ -21,9 +21,12 @@ import {
ExecuteApiRequestFullParams,
ExecuteAssertionItem,
ExecuteConditionConfig,
ExecuteConditionProcessor,
RequestResult,
ResponseDefinition,
} from './common';
import type { RequestParam as CaseRequestParam } from '@/views/api-test/components/requestComposition/index.vue';
import type { RequestParam } from '@/views/api-test/scenario/components/common/customApiDrawer.vue';
// 场景-更新模块参数
export interface ApiScenarioModuleUpdateParams {
@ -190,7 +193,7 @@ export interface ScenarioHistoryItem {
createUserName: string;
versionName: string;
}
// 场景步骤-自定义请求
export type CustomApiStep = ExecuteApiRequestFullParams & {
protocol: string;
activeTab: RequestComposition;
@ -241,6 +244,7 @@ export interface Variable {
commonVariables: CommonVariable[];
csvVariables: CsvVariable[];
}
// 场景配置
export interface ScenarioConfig {
variable: Variable;
preProcessorConfig: ExecuteConditionConfig;
@ -298,11 +302,13 @@ export interface LoopStepDetail extends StepDetailsCommon {
msCountController: CountController;
whileController: WhileController;
}
// 场景引用配置
export interface ScenarioStepConfig {
useCurrentScenarioParam: boolean; // 是否优先使用当前场景参数
useBothScenarioParam: boolean; // 是否当前场景参数和源场景参数都应用(勾选非空值时为 true
enableScenarioEnv: boolean; // 是否应用源场景环境
}
// 场景步骤详情
export type ScenarioStepDetail = Partial<
CustomApiStepDetail &
ConditionStepDetail &
@ -312,14 +318,16 @@ export type ScenarioStepDetail = Partial<
method: RequestMethods;
}
>;
// 场景步骤项
export interface ScenarioStepItem {
id: string | number;
sort: number;
name: string;
enable: boolean; // 是否启用
copyFromStepId?: string; // 如果步骤是复制的这个字段是复制的步骤id如果复制的步骤也是复制的并且没有加载过详情则这个 id 是最原始的 被复制的步骤 id
resourceId?: string; // 详情或者引用的类型才有
resourceNum?: string; // 详情或者引用的类型才有
resourceId?: string; // 详情或者引用、复制的类型才有
resourceNum?: string; // 详情或者引用、复制的类型才有
originProjectId?: string; // 如果步骤是复制的这个字段是复制的资源的所在的项目id
stepType: ScenarioStepType;
refType: ScenarioStepRefType;
config: ScenarioStepDetail; // 存储步骤列表需要展示的信息
@ -342,6 +350,15 @@ export interface ScenarioStepItem {
isQuoteScenarioStep?: boolean; // 是否是引用场景下的步骤(不分是不是完全引用,只要是引用类型就是),不可修改引用 api 的参数值
isRefScenarioStep?: boolean; // 是否是完全引用的场景下的步骤,是的话不允许启用禁用
}
// 场景步骤文件参数
export interface ScenarioStepFileParams {
uploadFileIds: string[];
linkFileIds: string[];
deleteFileIds?: string[];
unLinkFileIds?: string[];
}
// 场景步骤详情
export type ScenarioStepDetails = RequestParam | CaseRequestParam | ExecuteConditionProcessor;
// 场景
export interface Scenario {
id?: string | number;
@ -357,7 +374,8 @@ export interface Scenario {
environmentId?: string;
scenarioConfig: ScenarioConfig;
steps: ScenarioStepItem[];
stepDetails: Record<string, ScenarioStepDetail>;
stepDetails: Record<string, ScenarioStepDetails>; // case、api、脚本操作抽屉的详情结构
stepFileParam: Record<string, ScenarioStepFileParams>;
follow?: boolean;
uploadFileIds: string[];
linkFileIds: string[];
@ -375,6 +393,7 @@ export interface Scenario {
isExecute?: boolean; // 是否从列表执行进去场景详情
isDebug?: boolean; // 是否调试,区分执行场景和批量调试步骤
}
// 场景详情
export interface ScenarioDetail extends Scenario {
stepTotal: number;
requestPassRate: string;
@ -390,7 +409,7 @@ export interface ScenarioDetail extends Scenario {
updateTime: number;
updateUser: string;
}
// 场景-执行请求参数
export interface ApiScenarioDebugRequest {
id: string | number; // 场景 id
grouped: boolean;
@ -420,7 +439,7 @@ export interface ApiScenarioUpdateDTO extends Partial<Scenario> {
deleteFileIds?: string[];
unLinkFileIds?: string[];
}
// 导入系统请求时获取步骤列表的分组入参
export interface GetSystemRequestTypeParams {
moduleIds?: (string | number)[];
selectedIds: (string | number)[];
@ -429,7 +448,7 @@ export interface GetSystemRequestTypeParams {
protocol?: string;
versionId?: string;
}
// 导入系统请求时获取步骤列表的入参
export interface GetSystemRequestParams {
apiRequest?: GetSystemRequestTypeParams;
caseRequest?: GetSystemRequestTypeParams;

View File

@ -1,4 +1,3 @@
import { useAppStore } from '@/store';
import { clearToken, hasToken, isLoginExpires } from '@/utils/auth';
import NProgress from 'nprogress'; // progress bar

View File

@ -2,8 +2,6 @@ import { defineStore } from 'pinia';
import { getLicenseInfo } from '@/api/modules/setting/authorizedManagement';
import useAppStore from '../app';
const useLicenseStore = defineStore('license', {
persist: true,
state: (): { status: string | null } => ({
@ -21,7 +19,6 @@ const useLicenseStore = defineStore('license', {
},
// license校验
async getValidateLicense() {
const appStore = useAppStore();
try {
const result = await getLicenseInfo();
if (!result || !result.status || !result.license || !result.license.count) {

View File

@ -59,8 +59,8 @@ const useUserStore = defineStore('user', {
} {
const appStore = useAppStore();
state.userRoleRelations?.forEach(ug => {
state.userRolePermissions?.forEach(gp => {
state.userRoleRelations?.forEach((ug) => {
state.userRolePermissions?.forEach((gp) => {
if (gp.userRole.id === ug.roleId) {
ug.userRolePermissions = gp.userRolePermissions;
ug.userRole = gp.userRole;
@ -151,13 +151,8 @@ const useUserStore = defineStore('user', {
setToken(res.sessionId, res.csrfToken);
this.setInfo(res);
const { orgId, pId } = getHashParameters();
// 如果访问页面的时候携带了组织 ID和项目 ID则不设置
if (!orgId || forceSet) {
appStore.setCurrentOrgId(res.lastOrganizationId || '');
}
if (!pId || forceSet) {
appStore.setCurrentProjectId(res.lastProjectId || '');
}
appStore.setCurrentOrgId(forceSet ? res.lastOrganizationId || '' : orgId || '');
appStore.setCurrentProjectId(forceSet ? res.lastProjectId || '' : pId || '');
return true;
} catch (err) {
// eslint-disable-next-line no-console

View File

@ -86,7 +86,7 @@
</div>
<div class="flex items-center justify-between px-[12px] pt-[12px]">
<div class="flex items-center">
<a-tooltip :content="condition.name">
<a-tooltip v-if="condition.name" :content="condition.name">
<div class="script-name-container">
<div class="one-line-text mr-[4px] max-w-[110px] font-medium text-[var(--color-text-1)]">
{{ condition.name }}

View File

@ -72,6 +72,7 @@
isPriorityLocalExec,
execute,
localExecuteUrl,
hasLocalExec,
});
</script>

View File

@ -279,6 +279,9 @@
gap: 12px;
}
.code-container {
@apply overflow-y-auto;
.ms-scroll-bar();
padding: 12px;
max-height: 400px;
border-radius: var(--border-radius-small);

View File

@ -778,21 +778,24 @@
emitChange('addTableLine addLineDisabled', isInit);
return;
}
if (
rowIndex === paramsData.value.length - 1 &&
(paramsData.value[rowIndex].key ||
paramsData.value[rowIndex].projectId ||
paramsData.value[rowIndex].header ||
paramsData.value[rowIndex].variableName ||
paramsData.value[rowIndex].expression)
) {
if (rowIndex === paramsData.value.length - 1) {
// Don't change this!!!
//
const id = new Date().getTime().toString();
paramsData.value.push({
const lastLineData = paramsData.value[rowIndex]; //
const selectColumnKeys = props.columns.filter((e) => e.typeOptions).map((e) => e.dataIndex); //
const nextLine = {
id,
...cloneDeep(props.defaultParamItem), //
enable: true, //
} as any);
} as any;
selectColumnKeys.forEach((key) => {
// 便
if (key) {
nextLine[key] = lastLineData[key];
}
});
paramsData.value.push(nextLine);
}
emitChange('addTableLine', isInit);
handleMustContainColChange(true);

View File

@ -108,7 +108,6 @@
import { cloneDeep } from 'lodash-es';
import { TabItem } from '@/components/pure/ms-editable-tab/types';
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import caseTable from '../case/caseTable.vue';
// import MsFormCreate from '@/components/pure/ms-form-create/formCreate.vue';
import apiTable from './apiTable.vue';
@ -230,15 +229,15 @@
{
polymorphicName: 'MsCommonElement', // MsCommonElement
assertionConfig: {
enableGlobal: false,
enableGlobal: true,
assertions: [],
},
postProcessorConfig: {
enableGlobal: false,
enableGlobal: true,
processors: [],
},
preProcessorConfig: {
enableGlobal: false,
enableGlobal: true,
processors: [],
},
},

View File

@ -169,15 +169,15 @@
{
polymorphicName: 'MsCommonElement', // MsCommonElement
assertionConfig: {
enableGlobal: false,
enableGlobal: true,
assertions: [],
},
postProcessorConfig: {
enableGlobal: false,
enableGlobal: true,
processors: [],
},
preProcessorConfig: {
enableGlobal: false,
enableGlobal: true,
processors: [],
},
},

View File

@ -63,15 +63,14 @@ export default {
'apiTestManagement.importMode': 'Import mode',
'apiTestManagement.importModeTip1': 'Cover:',
'apiTestManagement.importModeTip2':
'1.If the request type + path is the same for the same interface, if the request parameter content is inconsistent, it will be overwritten.',
'1.The same interface already exists in the system (the request type + path are consistent). If the request parameter content is inconsistent, the original interface of the system will be overwritten.',
'apiTestManagement.importModeTip3':
'2.The same interface request type + path are the same, and the request parameter content is the same and does not change.',
'apiTestManagement.importModeTip4':
'3.If the interface is not the same and the request type + path is the same, add it',
'2.The same interface already exists in the system (request type + path are consistent), if the content of the request parameters is consistent, no changes will be made.',
'apiTestManagement.importModeTip4': '3.Interfaces that do not exist in the system are added.',
'apiTestManagement.importModeTip5': 'Not covered:',
'apiTestManagement.importModeTip6':
'1.If the same interface request type + path are the same, no changes will be made.',
'apiTestManagement.importModeTip7': '2.If the request type + path are not the same for the same interface, add a new',
'1.If the same interface already exists in the system (the request type + path is the same), no changes will be made.',
'apiTestManagement.importModeTip7': '2.Interfaces that do not exist in the system are added.',
'apiTestManagement.cover': 'Cover',
'apiTestManagement.uncover': 'Not covered',
'apiTestManagement.moreSetting': 'More settings',

View File

@ -60,12 +60,12 @@ export default {
'apiTestManagement.belongModule': '所属模块',
'apiTestManagement.importMode': '导入模式',
'apiTestManagement.importModeTip1': '覆盖:',
'apiTestManagement.importModeTip2': '1.同一接口请求类型+路径一致,请求参数内容不一致则覆盖',
'apiTestManagement.importModeTip3': '2.同一接口请求类型+路径一致,请求参数内容一致不做变更',
'apiTestManagement.importModeTip4': '3.非同一接口,请求类型+路径一致,则新增',
'apiTestManagement.importModeTip2': '1.系统已存在的同一接口请求类型+路径一致,请求参数内容不一致则覆盖系统原接口',
'apiTestManagement.importModeTip3': '2.系统已存在的同一接口请求类型+路径一致,请求参数内容一致不做变更',
'apiTestManagement.importModeTip4': '3.系统不存在的接口,则新增',
'apiTestManagement.importModeTip5': '不覆盖:',
'apiTestManagement.importModeTip6': '1.同一接口请求类型+路径一致,则不做变更',
'apiTestManagement.importModeTip7': '2.非同一接口请求类型+路径一致,则新增',
'apiTestManagement.importModeTip6': '1.系统已存在的同一接口请求类型+路径一致,则不做变更',
'apiTestManagement.importModeTip7': '2.系统不存在的接口,则新增',
'apiTestManagement.cover': '覆盖',
'apiTestManagement.uncover': '不覆盖',
'apiTestManagement.moreSetting': '更多设置',

View File

@ -5,6 +5,7 @@
no-content-padding
:show-continue="true"
:footer="requestVModel.isNew === true"
:ok-disabled="requestVModel.executeLoading || (isHttpProtocol && !requestVModel.url)"
@confirm="handleSave"
@continue="handleContinue"
@close="handleClose"
@ -20,7 +21,7 @@
</div>
<div
v-if="!props.step || props.step?.stepType === ScenarioStepType.CUSTOM_REQUEST"
class="ml-auto flex items-center gap-[16px]"
class="customApiDrawer-title-right ml-auto flex items-center gap-[16px]"
>
<div class="text-[14px] font-normal text-[var(--color-text-4)]">
{{ t('apiScenario.env', { name: currentEnvConfig?.name }) }}
@ -28,6 +29,7 @@
<a-select
v-model:model-value="requestVModel.customizeRequestEnvEnable"
class="w-[150px]"
popup-container=".customApiDrawer-title-right"
@change="handleUseEnvChange"
>
<template #prefix>
@ -318,7 +320,7 @@
RequestResult,
RequestTaskResult,
} from '@/models/apiTest/common';
import { ScenarioStepItem } from '@/models/apiTest/scenario';
import { ScenarioStepFileParams, ScenarioStepItem } from '@/models/apiTest/scenario';
import { EnvConfig } from '@/models/projectManagement/environmental';
import {
RequestAuthType,
@ -366,6 +368,8 @@
unSaved: boolean;
uploadFileIds: string[];
linkFileIds: string[];
deleteFileIds?: string[];
unLinkFileIds?: string[];
}
export type RequestParam = ExecuteApiRequestFullParams & {
@ -384,6 +388,7 @@
update: string;
};
stepResponses?: Record<string | number, RequestResult>;
fileParams?: ScenarioStepFileParams;
}>();
const emit = defineEmits<{
@ -399,6 +404,7 @@
//
const scenarioId = inject<string | number>('scenarioId');
const currentEnvConfig = inject<Ref<EnvConfig>>('currentEnvConfig');
const hasLocalExec = inject<Ref<boolean>>('isPriorityLocalExec');
const isPriorityLocalExec = inject<Ref<boolean>>('isPriorityLocalExec');
const visible = defineModel<boolean>('visible', { required: true });
@ -439,15 +445,15 @@
{
polymorphicName: 'MsCommonElement', // MsCommonElement
assertionConfig: {
enableGlobal: false,
enableGlobal: true,
assertions: [],
},
postProcessorConfig: {
enableGlobal: false,
enableGlobal: true,
processors: [],
},
preProcessorConfig: {
enableGlobal: false,
enableGlobal: true,
processors: [],
},
},
@ -656,8 +662,6 @@
}
}
const hasLocalExec = ref(false); // api
const pluginScriptMap = ref<Record<string, PluginConfig>>({}); //
const temporaryPluginFormMap: Record<string, any> = {}; // API
const pluginLoading = ref(false);
@ -909,8 +913,8 @@
).validParams;
parseRequestBodyResult = parseRequestBodyFiles(
requestVModel.value.body,
requestVModel.value.uploadFileIds, //
requestVModel.value.linkFileIds //
props.fileParams?.uploadFileIds || requestVModel.value.uploadFileIds, // api requestVModel
props.fileParams?.linkFileIds || requestVModel.value.linkFileIds // api requestVModel
);
requestParams = {
authConfig: requestVModel.value.authConfig,

View File

@ -266,7 +266,7 @@
import { scrollIntoView } from '@/utils/dom';
import { ExecuteConditionConfig, PluginConfig, RequestResult } from '@/models/apiTest/common';
import { ScenarioStepItem } from '@/models/apiTest/scenario';
import { ScenarioStepFileParams, ScenarioStepItem } from '@/models/apiTest/scenario';
import {
RequestAuthType,
RequestBodyFormat,
@ -298,6 +298,7 @@
const props = defineProps<{
request?: RequestParam; //
stepResponses?: Record<string | number, RequestResult>;
fileParams?: ScenarioStepFileParams;
}>();
const emit = defineEmits<{
(e: 'applyStep', request: RequestParam): void;
@ -351,15 +352,15 @@
{
polymorphicName: 'MsCommonElement', // MsCommonElement
assertionConfig: {
enableGlobal: false,
enableGlobal: true,
assertions: [],
},
postProcessorConfig: {
enableGlobal: false,
enableGlobal: true,
processors: [],
},
preProcessorConfig: {
enableGlobal: false,
enableGlobal: true,
processors: [],
},
},
@ -797,11 +798,14 @@
defaultBodyParamsItem,
isExecute
).validParams;
parseRequestBodyResult = parseRequestBodyFiles(
requestVModel.value.body,
requestVModel.value.uploadFileIds, //
requestVModel.value.linkFileIds //
);
if (activeStep.value?.refType === ScenarioStepRefType.COPY) {
// case
parseRequestBodyResult = parseRequestBodyFiles(
requestVModel.value.body,
props.fileParams?.uploadFileIds || requestVModel.value.uploadFileIds, // case requestVModel
props.fileParams?.linkFileIds || requestVModel.value.linkFileIds // case requestVModel
);
}
requestParams = {
authConfig: requestVModel.value.authConfig,
body: {
@ -878,8 +882,8 @@
}
function handleClose() {
// applyStep
if (!requestVModel.value.isNew) {
// applyStep case
if (!requestVModel.value.isNew && activeStep.value?.refType === ScenarioStepRefType.COPY) {
emit('applyStep', cloneDeep(makeRequestParams()));
}
}

View File

@ -198,7 +198,13 @@
* 获取复制或引用的步骤数据
* @param refType 复制或引用
*/
async function getScenarioSteps(refType: ScenarioStepRefType.COPY | ScenarioStepRefType.REF) {
async function getScenarioSteps(
refType: ScenarioStepRefType.COPY | ScenarioStepRefType.REF,
other: {
api: MsTableDataItem<ApiDefinitionDetail>[];
case: MsTableDataItem<ApiCaseDetail>[];
}
) {
const scenarioMap: Record<string, MsTableDataItem<ApiScenarioTableItem>[]> = {};
// id
selectedScenarios.value.forEach((e) => {
@ -236,18 +242,19 @@
return {
...node,
copyFromStepId: node.id,
originProjectId: node.projectId,
id: getGenerateId(),
};
}),
name: `copy-${e.name}`,
copyFromStepId: e.resourceId,
originProjectId: e.projectId,
};
});
emit(
'copy',
cloneDeep({
api: selectedApis.value,
case: selectedCases.value,
api: other.api,
case: other.case,
scenario: fullScenarioArr,
})
);
@ -260,18 +267,20 @@
return {
...node,
copyFromStepId: node.id,
originProjectId: node.projectId,
id: getGenerateId(),
isQuoteScenarioStep: true,
isRefScenarioStep: true, //
};
}),
originProjectId: e.projectId,
};
});
emit(
'quote',
cloneDeep({
api: selectedApis.value,
case: selectedCases.value,
api: other.api,
case: other.case,
scenario: fullScenarioArr,
})
);
@ -286,14 +295,25 @@
}
async function handleCopy() {
const copyApis = selectedApis.value.map((e) => ({
...e,
originProjectId: e.projectId,
}));
const copyCases = selectedCases.value.map((e) => ({
...e,
originProjectId: e.projectId,
}));
if (selectedScenarios.value.length > 0) {
await getScenarioSteps(ScenarioStepRefType.COPY);
await getScenarioSteps(ScenarioStepRefType.COPY, {
api: copyApis,
case: copyCases,
});
} else {
emit(
'copy',
cloneDeep({
api: selectedApis.value,
case: selectedCases.value,
api: copyApis,
case: copyCases,
scenario: selectedScenarios.value,
})
);
@ -302,14 +322,25 @@
}
async function handleQuote() {
const quoteApis = selectedApis.value.map((e) => ({
...e,
originProjectId: e.projectId,
}));
const quoteCases = selectedCases.value.map((e) => ({
...e,
originProjectId: e.projectId,
}));
if (selectedScenarios.value.length > 0) {
await getScenarioSteps(ScenarioStepRefType.REF);
await getScenarioSteps(ScenarioStepRefType.REF, {
api: quoteApis,
case: quoteCases,
});
} else {
emit(
'quote',
cloneDeep({
api: selectedApis.value,
case: selectedCases.value,
api: quoteApis,
case: quoteCases,
scenario: selectedScenarios.value,
})
);

View File

@ -5,9 +5,12 @@
:width="960"
no-content-padding
disabled-width-drag
:footer="!props.detail"
@close="handleClose"
@cancel="handleDrawerCancel"
>
<div class="ml-[16px] mt-[10px]">
<!-- <stepTypeVue v-if="props.step" :step="props.step" /> -->
{{ t('apiScenario.scriptOperationName') }}
</div>
<div class="ml-[16px] mt-[3px] max-w-[70%]">
@ -19,9 +22,9 @@
/>
</div>
<div class="mt-[10px] flex h-[calc(100%-40px)] gap-[8px]">
<conditionContent v-model:data="activeItem" :is-build-in="true" :is-format="true" />
<conditionContent v-if="visible" v-model:data="activeItem" :is-build-in="true" :is-format="true" />
</div>
<template #footer>
<template v-if="!props.detail" #footer>
<a-button type="secondary" @click="handleDrawerCancel">
{{ t('common.cancel') }}
</a-button>
@ -36,19 +39,29 @@
</template>
<script setup lang="ts">
import { cloneDeep } from 'lodash-es';
import { LanguageEnum } from '@/components/pure/ms-code-editor/types';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import conditionContent from '@/views/api-test/components/condition/content.vue';
// import stepTypeVue from './stepType/stepType.vue';
import { useI18n } from '@/hooks/useI18n';
import { ExecuteConditionProcessor } from '@/models/apiTest/common';
import { ScenarioStepItem } from '@/models/apiTest/scenario';
import { RequestConditionProcessor } from '@/enums/apiEnum';
const conditionContent = defineAsyncComponent(() => import('@/views/api-test/components/condition/content.vue'));
const props = defineProps<{
script?: ExecuteConditionProcessor;
detail?: ExecuteConditionProcessor;
step?: ScenarioStepItem;
name?: string;
}>();
const emit = defineEmits<{
(e: 'add', name: string, scriptProcessor: ExecuteConditionProcessor): void;
(e: 'save', name: string, scriptProcessor: ExecuteConditionProcessor): void;
}>();
const defaultScript = {
processorType: RequestConditionProcessor.SCRIPT,
@ -56,44 +69,45 @@
script: '',
scriptLanguage: LanguageEnum.BEANSHELL_JSR233,
commonScriptInfo: {},
} as ExecuteConditionProcessor;
const scriptName = ref(props.name || '');
const activeItem = ref(props.script || defaultScript);
polymorphicName: 'MsScriptElement',
} as unknown as ExecuteConditionProcessor;
const scriptName = ref('');
const activeItem = ref<ExecuteConditionProcessor>(cloneDeep(defaultScript));
const { t } = useI18n();
const visible = defineModel<boolean>('visible', { required: true });
const emit = defineEmits<{
(e: 'save', name: string, scriptProcessor: ExecuteConditionProcessor): void;
}>();
function resetField() {
scriptName.value = '';
activeItem.value = {
processorType: RequestConditionProcessor.SCRIPT,
enableCommonScript: false,
script: '',
scriptLanguage: LanguageEnum.BEANSHELL_JSR233,
commonScriptInfo: {},
} as ExecuteConditionProcessor;
}
watch(
() => visible.value,
(val) => {
if (val) {
scriptName.value = props.name || '';
activeItem.value = cloneDeep(props.detail || defaultScript);
}
}
);
function handleDrawerCancel() {
resetField();
visible.value = false;
}
function saveAndContinue() {
emit('save', scriptName.value, activeItem.value);
resetField();
emit('add', scriptName.value, activeItem.value);
}
function save() {
emit('save', scriptName.value, activeItem.value);
resetField();
emit('add', scriptName.value, activeItem.value);
visible.value = false;
}
function handleClose() {
if (props.detail) {
emit('save', scriptName.value, activeItem.value);
}
scriptName.value = '';
activeItem.value = defaultScript as unknown as ExecuteConditionProcessor;
}
</script>
<style lang="less" scoped></style>

View File

@ -94,11 +94,11 @@ export const defaultScenario: Scenario = {
csvVariables: [],
},
preProcessorConfig: {
enableGlobal: false,
enableGlobal: true,
processors: [],
},
postProcessorConfig: {
enableGlobal: false,
enableGlobal: true,
processors: [],
},
assertionConfig: {
@ -114,6 +114,7 @@ export const defaultScenario: Scenario = {
},
steps: [],
stepDetails: {},
stepFileParam: {},
executeTime: 0,
executeSuccessCount: 0,
executeFailCount: 0,

View File

@ -116,9 +116,9 @@ export default function useCreateActions() {
if (item.id || item.resourceId) {
// 引用复制接口、用例、场景时的源资源信息
resourceField = {
resourceId: item.id || item.resourceId,
resourceNum: item.num,
resourceName: item.name,
resourceId: item.id || item.resourceId, // 场景会调接口获取信息所以有resourceId接口、用例没有下同
resourceNum: item.num || item.resourceNum,
resourceName: item.name || item.resourceName,
};
}
if (item.protocol) {
@ -141,6 +141,7 @@ export default function useCreateActions() {
children: item.children || [],
stepType,
refType,
originProjectId: item.originProjectId,
copyFromStepId: item.copyFromStepId,
...resourceField,
name: name || item.name,

View File

@ -1,13 +1,20 @@
<template>
<div class="flex items-center gap-[4px]">
<!-- <a-popover position="bl" content-class="detail-popover" arrow-class="hidden">
<a-popover
v-if="
[ScenarioStepType.API, ScenarioStepType.API_CASE, ScenarioStepType.API_SCENARIO].includes(props.data.stepType)
"
position="bl"
content-class="detail-popover"
arrow-class="hidden"
>
<MsIcon type="icon-icon-draft" class="text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]" />
<template #content>
<div class="flex flex-col gap-[16px]">
<div>
<div class="mb-[2px] text-[var(--color-text-4)]">{{ t('apiScenario.belongProject') }}</div>
<div class="text-[14px] text-[var(--color-text-1)]">
{{ props.data.belongProjectName }}
<!-- {{ props.data.belongProjectName }} -->
</div>
</div>
<div>
@ -18,9 +25,9 @@
</div>
</div>
</template>
</a-popover> -->
<!-- <MsTag
v-if="props.data.projectId !== appStore.currentProjectId"
</a-popover>
<MsTag
v-if="props.data.originProjectId !== appStore.currentProjectId"
theme="outline"
size="small"
:self-style="{
@ -30,22 +37,23 @@
}"
>
{{ t('apiScenario.crossProject') }}
</MsTag> -->
</MsTag>
</div>
</template>
<script setup lang="ts">
// import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import { useI18n } from '@/hooks/useI18n';
// import useOpenNewPage from '@/hooks/useOpenNewPage';
import useOpenNewPage from '@/hooks/useOpenNewPage';
import useAppStore from '@/store/modules/app';
import { ScenarioStepItem } from '@/models/apiTest/scenario';
// import { ApiTestRouteEnum } from '@/enums/routeEnum';
import { ScenarioStepType } from '@/enums/apiEnum';
import { ApiTestRouteEnum } from '@/enums/routeEnum';
// import getStepType from '@/views/api-test/scenario/components/common/stepType/utils';
import getStepType from '@/views/api-test/scenario/components/common/stepType/utils';
const props = defineProps<{
data: ScenarioStepItem;
@ -53,27 +61,36 @@
const appStore = useAppStore();
const { t } = useI18n();
// const { openNewPage } = useOpenNewPage();
const { openNewPage } = useOpenNewPage();
// function goDetail() {
// const _stepType = getStepType(props.data);
// switch (true) {
// case _stepType.isCopyApi:
// case _stepType.isQuoteApi:
// openNewPage(ApiTestRouteEnum.API_TEST_MANAGEMENT, { dId: props.data.id, pId: props.data.projectId });
// break;
// case _stepType.isCopyScenario:
// case _stepType.isQuoteScenario:
// openNewPage(ApiTestRouteEnum.API_TEST_SCENARIO, { sId: props.data.id, pId: props.data.projectId });
// break;
// case _stepType.isQuoteCase:
// case _stepType.isCopyCase:
// openNewPage(ApiTestRouteEnum.API_TEST_MANAGEMENT, { cId: props.data.id, pId: props.data.projectId });
// break;
// default:
// break;
// }
// }
function goDetail() {
const _stepType = getStepType(props.data);
switch (true) {
case _stepType.isCopyApi:
case _stepType.isQuoteApi:
openNewPage(ApiTestRouteEnum.API_TEST_MANAGEMENT, {
pId: props.data.originProjectId,
dId: props.data.resourceId,
});
break;
case _stepType.isCopyScenario:
case _stepType.isQuoteScenario:
openNewPage(ApiTestRouteEnum.API_TEST_SCENARIO, {
pId: props.data.originProjectId,
sId: props.data.resourceId,
});
break;
case _stepType.isQuoteCase:
case _stepType.isCopyCase:
openNewPage(ApiTestRouteEnum.API_TEST_MANAGEMENT, {
pId: props.data.originProjectId,
cId: props.data.resourceId,
});
break;
default:
break;
}
}
</script>
<style lang="less" scoped>

View File

@ -88,7 +88,7 @@
<!-- 步骤差异内容按步骤类型展示不同组件 -->
<component
:is="getStepContent(step)"
:data="step.config"
:data="checkStepIsApi(step) || step.stepType === ScenarioStepType.API_SCENARIO ? step : step.config"
:step-id="step.id"
@quick-input="setQuickInput(step, $event)"
@change="handleStepContentChange($event, step)"
@ -240,7 +240,8 @@
</createStepActions>
<customApiDrawer
v-model:visible="customApiDrawerVisible"
:request="currentStepDetail"
:request="currentStepDetail as unknown as RequestParam"
:file-params="currentStepFileParams"
:step="activeStep"
:step-responses="scenario.stepResponses"
@add-step="addCustomApiStep"
@ -251,7 +252,8 @@
<customCaseDrawer
v-model:visible="customCaseDrawerVisible"
:active-step="activeStep"
:request="currentStepDetail"
:request="currentStepDetail as unknown as RequestParam"
:file-params="currentStepFileParams"
:step-responses="scenario.stepResponses"
@apply-step="applyApiStep"
@delete-step="deleteCaseStep(activeStep)"
@ -265,11 +267,12 @@
@quote="handleImportApiApply('quote', $event)"
/>
<scriptOperationDrawer
v-if="scriptOperationDrawerVisible"
v-model:visible="scriptOperationDrawerVisible"
:script="currentStepDetail"
:detail="currentStepDetail as unknown as ExecuteConditionProcessor"
:step="activeStep"
:name="activeStep?.name"
@save="addScriptStep"
@add="addScriptStep"
@save="saveScriptStep"
/>
<a-modal
v-model:visible="showQuickInput"
@ -406,7 +409,6 @@
import waitTimeContent from './stepNodeComposition/waitTimeContent.vue';
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
import { RequestParam as CaseRequestParam } from '@/views/api-test/components/requestComposition/index.vue';
import responseResult from '@/views/api-test/components/requestComposition/response/index.vue';
import { localExecuteApiDebug } from '@/api/modules/api-test/common';
import { debugScenario, getScenarioStep } from '@/api/modules/api-test/scenario';
@ -429,6 +431,8 @@
CreateStepAction,
Scenario,
ScenarioStepConfig,
ScenarioStepDetails,
ScenarioStepFileParams,
ScenarioStepItem,
} from '@/models/apiTest/scenario';
import { EnvConfig } from '@/models/projectManagement/environmental';
@ -442,6 +446,7 @@
import type { RequestParam } from '../common/customApiDrawer.vue';
import useCreateActions from './createAction/useCreateActions';
import { parseRequestBodyFiles } from '@/views/api-test/components/utils';
import getStepType from '@/views/api-test/scenario/components/common/stepType/utils';
import { defaultStepItemCommon } from '@/views/api-test/scenario/components/config';
@ -451,6 +456,9 @@
const customCaseDrawer = defineAsyncComponent(() => import('../common/customCaseDrawer.vue'));
const importApiDrawer = defineAsyncComponent(() => import('../common/importApiDrawer/index.vue'));
const scriptOperationDrawer = defineAsyncComponent(() => import('../common/scriptOperationDrawer.vue'));
const responseResult = defineAsyncComponent(
() => import('@/views/api-test/components/requestComposition/response/index.vue')
);
const props = defineProps<{
stepKeyword: string;
@ -470,13 +478,14 @@
required: true,
});
//
const stepDetails = defineModel<Record<string, any>>('stepDetails', {
const stepDetails = defineModel<Record<string, ScenarioStepDetails>>('stepDetails', {
required: true,
});
const scenario = defineModel<Scenario>('scenario', {
required: true,
});
const isPriorityLocalExec = inject<Ref<boolean>>('isPriorityLocalExec');
const localExecuteUrl = inject<Ref<string>>('localExecuteUrl');
const currentEnvConfig = inject<Ref<EnvConfig>>('currentEnvConfig');
const selectedKeys = ref<(string | number)[]>([]); //
@ -510,11 +519,10 @@
* 根据步骤类型获取步骤内容组件
*/
function getStepContent(step: ScenarioStepItem) {
const _stepType = getStepType(step);
if (_stepType.isQuoteApi || _stepType.isQuoteCase || _stepType.isQuoteScenario) {
return quoteContent;
}
switch (step.stepType) {
case ScenarioStepType.API:
case ScenarioStepType.API_CASE:
case ScenarioStepType.API_SCENARIO:
case ScenarioStepType.CUSTOM_REQUEST:
return quoteContent;
case ScenarioStepType.LOOP_CONTROLLER:
@ -741,11 +749,16 @@
case 'copy':
const id = getGenerateId();
const stepDetail = stepDetails.value[node.id];
const stepFileParam = scenario.value.stepFileParam[node.id];
const { isQuoteScenario } = getStepType(node as ScenarioStepItem);
if (stepDetail) {
//
stepDetails.value[id] = cloneDeep(stepDetail);
}
if (stepFileParam) {
//
scenario.value.stepFileParam[id] = cloneDeep(stepFileParam);
}
insertNodes<ScenarioStepItem>(
steps.value,
node.id,
@ -754,11 +767,16 @@
mapTree<ScenarioStepItem>(node, (childNode) => {
const childId = getGenerateId();
const childStepDetail = stepDetails.value[node.id];
const childStepFileParam = scenario.value.stepFileParam[node.id];
let childCopyFromStepId = childNode.id;
if (childStepDetail) {
//
stepDetails.value[childId] = cloneDeep(childStepDetail);
}
if (childStepFileParam) {
//
scenario.value.stepFileParam[id] = cloneDeep(childStepFileParam);
}
if (!isQuoteScenario) {
// id
if (childStepDetail || (childNode.isNew && childNode.stepRefType === ScenarioStepRefType.REF)) {
@ -892,24 +910,33 @@
const customApiDrawerVisible = ref(false);
const scriptOperationDrawerVisible = ref(false);
const activeCreateAction = ref<CreateStepAction>(); //
const currentStepDetail = computed<any>(() => {
// TODO:
const currentStepDetail = computed<ScenarioStepDetails | undefined>(() => {
if (activeStep.value) {
return stepDetails.value[activeStep.value.id];
}
return undefined;
});
const currentStepFileParams = computed<ScenarioStepFileParams | undefined>(() => {
if (activeStep.value) {
return scenario.value.stepFileParam[activeStep.value.id];
}
});
async function getStepDetail(step: ScenarioStepItem) {
try {
appStore.showLoading();
const res = await getScenarioStep(step.copyFromStepId || step.id);
let parseRequestBodyResult;
if (step.config.protocol === 'HTTP' && res.body) {
parseRequestBodyResult = parseRequestBodyFiles(res.body); // id
}
stepDetails.value[step.id] = {
...res,
stepId: step.id,
protocol: step.config.protocol,
method: step.config.method,
...parseRequestBodyResult,
};
scenario.value.stepFileParam[step.id] = parseRequestBodyResult;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
@ -947,6 +974,13 @@
customCaseDrawerVisible.value = true;
} else if (step.stepType === ScenarioStepType.SCRIPT) {
activeStep.value = step;
if (
(stepDetails.value[step.id] === undefined && step.copyFromStepId) ||
(stepDetails.value[step.id] === undefined && !step.isNew)
) {
//
await getStepDetail(step);
}
scriptOperationDrawerVisible.value = true;
}
}
@ -971,16 +1005,11 @@
/**
* 开启websocket监听接收执行结果
*/
function debugSocket(
step: ScenarioStepItem,
reportId: string | number,
executeType?: 'localExec' | 'serverExec',
localExecuteUrl?: string
) {
function debugSocket(step: ScenarioStepItem, reportId: string | number, executeType?: 'localExec' | 'serverExec') {
websocketMap[reportId] = getSocket(
reportId || '',
executeType === 'localExec' ? '/ws/debug' : '',
executeType === 'localExec' ? localExecuteUrl : ''
executeType === 'localExec' ? localExecuteUrl?.value : ''
);
websocketMap[reportId].addEventListener('message', (event) => {
const data = JSON.parse(event.data);
@ -1022,13 +1051,12 @@
ApiScenarioDebugRequest,
'steps' | 'stepDetails' | 'reportId' | 'uploadFileIds' | 'linkFileIds'
>,
executeType?: 'localExec' | 'serverExec',
localExecuteUrl?: string
executeType?: 'localExec' | 'serverExec'
) {
const [currentStep] = executeParams.steps;
try {
currentStep.isExecuting = true;
debugSocket(currentStep, executeParams.reportId, executeType, localExecuteUrl); // websocket
debugSocket(currentStep, executeParams.reportId, executeType); // websocket
const res = await debugScenario({
id: scenario.value.id || '',
grouped: false,
@ -1044,8 +1072,8 @@
};
}),
});
if (executeType === 'localExec' && localExecuteUrl) {
await localExecuteApiDebug(localExecuteUrl, res);
if (executeType === 'localExec' && localExecuteUrl?.value) {
await localExecuteApiDebug(localExecuteUrl.value, res);
}
} catch (error) {
// eslint-disable-next-line no-console
@ -1073,15 +1101,16 @@
}
const stepDetail = stepDetails.value[realStep.id];
delete scenario.value.stepResponses[realStep.id]; //
const stepFileParam = scenario.value.stepFileParam[realStep.id];
realExecute(
{
steps: [realStep as ScenarioStepItem],
stepDetails: {
[realStep.id]: stepDetails.value[realStep.id],
[realStep.id]: stepDetail,
},
reportId: realStep.reportId,
uploadFileIds: stepDetail?.uploadFileIds || [],
linkFileIds: stepDetail?.linkFileIds || [],
uploadFileIds: stepFileParam?.uploadFileIds || [],
linkFileIds: stepFileParam?.linkFileIds || [],
},
isPriorityLocalExec?.value ? 'localExec' : 'serverExec'
);
@ -1245,6 +1274,12 @@
customizeRequest: true,
customizeRequestEnvEnable: request.customizeRequestEnvEnable,
};
scenario.value.stepFileParam[request.stepId] = {
linkFileIds: request.linkFileIds,
uploadFileIds: request.uploadFileIds,
deleteFileIds: request.deleteFileIds,
unLinkFileIds: request.unLinkFileIds,
};
emit('updateResource', request.uploadFileIds, request.linkFileIds);
if (activeStep.value && activeCreateAction.value) {
handleCreateStep(
@ -1287,7 +1322,13 @@
}
if (activeStep.value) {
request.isNew = false;
stepDetails.value[activeStep.value?.id] = request;
stepDetails.value[activeStep.value.id] = request;
scenario.value.stepFileParam[activeStep.value?.id] = {
linkFileIds: request.linkFileIds,
uploadFileIds: request.uploadFileIds,
deleteFileIds: request.deleteFileIds,
unLinkFileIds: request.unLinkFileIds,
};
emit('updateResource', request.uploadFileIds, request.linkFileIds);
activeStep.value = undefined;
}
@ -1337,6 +1378,14 @@
scenario.value.unSaved = true;
}
function saveScriptStep(name: string, scriptProcessor: ExecuteConditionProcessor) {
if (activeStep.value) {
stepDetails.value[activeStep.value.id] = cloneDeep(scriptProcessor);
activeStep.value.name = name;
activeStep.value = undefined;
}
}
/**
* 释放允许拖拽步骤到释放的节点内
* @param dropNode 释放节点

View File

@ -529,6 +529,7 @@
try {
appStore.showLoading();
const res = await getScenarioDetail(typeof record === 'string' ? record : record.id);
res.stepFileParam = {};
res.stepDetails = {};
if (!res.steps) {
res.steps = [];
@ -551,11 +552,15 @@
}
});
const hasLocalExec = computed(() => executeButtonRef.value?.hasLocalExec);
const localExecuteUrl = computed(() => executeButtonRef.value?.localExecuteUrl);
const isPriorityLocalExec = computed(() => executeButtonRef.value?.isPriorityLocalExec);
const scenarioId = computed(() => activeScenarioTab.value.id);
const scenarioExecuteLoading = computed(() => activeScenarioTab.value.executeLoading);
//
provide('isPriorityLocalExec', readonly(isPriorityLocalExec));
provide('hasLocalExec', readonly(hasLocalExec));
provide('localExecuteUrl', readonly(localExecuteUrl));
provide('currentEnvConfig', readonly(currentEnvConfig));
provide('scenarioId', scenarioId);
provide('scenarioExecuteLoading', scenarioExecuteLoading);

View File

@ -808,11 +808,11 @@
:deep(.apiFieldIdClass) {
margin-bottom: 0;
}
:deep(.systemFieldWrapper) {
.arco-form-item {
margin-bottom: 0;
}
padding: 8px;
border: 1px solid transparent;
border-radius: 6px;
@ -821,11 +821,11 @@
background: var(--color-text-n9);
}
}
.tagWrapper {
.arco-form-item {
margin-bottom: 0;
}
padding: 8px;
border: 1px solid transparent;
border-radius: 6px;