fix(全局): bug修复

This commit is contained in:
baiqi 2024-04-19 14:57:16 +08:00 committed by 刘瑞斌
parent 40dded74b3
commit 43877c59a6
24 changed files with 551 additions and 314 deletions

View File

@ -218,7 +218,7 @@
}; };
fileSaveAsSourceId?: string | number; // id fileSaveAsSourceId?: string | number; // id
fileSaveAsApi?: (params: TransferFileParams) => Promise<string>; // fileSaveAsApi?: (params: TransferFileParams) => Promise<string>; //
fileModuleOptionsApi?: (...args) => Promise<any>; // fileModuleOptionsApi?: (...args: any[]) => Promise<any>; //
}>(), }>(),
{ {
mode: 'button', mode: 'button',

View File

@ -108,7 +108,7 @@
</MsButton> </MsButton>
<template #content> <template #content>
<div class="arco-table-filters-content"> <div class="arco-table-filters-content">
<div class="ml-[6px] flex items-center justify-start px-[6px] py-[2px]"> <div class="py-[2px]] flex w-full items-center justify-start overflow-hidden px-[12px]">
<a-checkbox-group v-model:model-value="statusFilters" direction="vertical" size="small"> <a-checkbox-group v-model:model-value="statusFilters" direction="vertical" size="small">
<a-checkbox :key="CommonScriptStatusEnum.PASSED" :value="CommonScriptStatusEnum.PASSED"> <a-checkbox :key="CommonScriptStatusEnum.PASSED" :value="CommonScriptStatusEnum.PASSED">
<commonScriptStatus :status="CommonScriptStatusEnum.PASSED" /> <commonScriptStatus :status="CommonScriptStatusEnum.PASSED" />

View File

@ -119,7 +119,7 @@
import type { Description } from '@/components/pure/ms-description/index.vue'; import type { Description } from '@/components/pure/ms-description/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue'; import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import useFullScreen from '@/hooks/useFullScreen'; import useFullScreen, { UseFullScreen } from '@/hooks/useFullScreen';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { characterLimit } from '@/utils'; import { characterLimit } from '@/utils';
import { getMaxZIndexLayer } from '@/utils/dom'; import { getMaxZIndexLayer } from '@/utils/dom';
@ -171,6 +171,7 @@
const { t } = useI18n(); const { t } = useI18n();
const visible = ref(props.visible); const visible = ref(props.visible);
const fullScreen = ref<UseFullScreen>();
watch( watch(
() => props.visible, () => props.visible,
@ -188,12 +189,14 @@
}; };
const handleCancel = () => { const handleCancel = () => {
fullScreen.value?.exitFullscreen();
visible.value = false; visible.value = false;
emit('update:visible', false); emit('update:visible', false);
emit('cancel'); emit('cancel');
}; };
const handleClose = () => { const handleClose = () => {
fullScreen.value?.exitFullscreen();
visible.value = false; visible.value = false;
emit('update:visible', false); emit('update:visible', false);
emit('close'); emit('close');
@ -246,7 +249,6 @@
} }
}; };
const fullScreen = ref();
watch( watch(
() => visible.value, () => visible.value,
(val) => { (val) => {

View File

@ -796,6 +796,12 @@
@apply overflow-hidden; @apply overflow-hidden;
max-width: 300px; max-width: 300px;
.arco-table-filters-content-list {
@apply overflow-y-auto;
.ms-scroll-bar();
max-height: 400px;
}
.arco-checkbox-group { .arco-checkbox-group {
@apply flex w-full flex-col; @apply flex w-full flex-col;
.arco-checkbox { .arco-checkbox {

View File

@ -1,12 +1,18 @@
import { mergeStyles } from '@/utils/dom'; import { mergeStyles } from '@/utils/dom';
export interface UseFullScreen {
isFullScreen: Ref<boolean>;
toggleFullScreen: () => void;
exitFullscreen: () => void;
}
/** /**
* hook * hook
* @param domRef dom ref * @param domRef dom ref
*/ */
export default function useFullScreen( export default function useFullScreen(
domRef: Ref<HTMLElement | null | undefined> | HTMLElement | Element | null | undefined domRef: Ref<HTMLElement | null | undefined> | HTMLElement | Element | null | undefined
) { ): UseFullScreen {
const isFullScreen = ref(false); const isFullScreen = ref(false);
const originalStyle = ref(''); const originalStyle = ref('');
@ -41,5 +47,6 @@ export default function useFullScreen(
return { return {
isFullScreen, isFullScreen,
toggleFullScreen, toggleFullScreen,
exitFullscreen,
}; };
} }

View File

@ -322,7 +322,7 @@ export interface ExecuteAssertionConfig {
} }
// 执行请求-共用配置子项 // 执行请求-共用配置子项
export interface ExecuteCommonChild { export interface ExecuteCommonChild {
polymorphicName: 'MsCommonElement'; // 协议多态名称写死MsCommonElement polymorphicName: string; // 协议多态名称写死MsCommonElement
assertionConfig: ExecuteAssertionConfig; assertionConfig: ExecuteAssertionConfig;
postProcessorConfig: ExecuteConditionConfig; // 后置处理器配置 postProcessorConfig: ExecuteConditionConfig; // 后置处理器配置
preProcessorConfig: ExecuteConditionConfig; // 前置处理器配置 preProcessorConfig: ExecuteConditionConfig; // 前置处理器配置
@ -358,7 +358,7 @@ export interface ExecuteApiRequestFullParams {
authConfig: ExecuteAuthConfig; authConfig: ExecuteAuthConfig;
body: ExecuteBody; body: ExecuteBody;
headers: EnableKeyValueParam[]; headers: EnableKeyValueParam[];
method: RequestMethods; method: RequestMethods | string;
otherConfig: ExecuteOtherConfig; otherConfig: ExecuteOtherConfig;
path: string; path: string;
query: ExecuteRequestCommonParam[]; query: ExecuteRequestCommonParam[];
@ -409,7 +409,7 @@ export interface RequestResult {
body: string; body: string;
headers: string; headers: string;
url: string; url: string;
method: string; method: RequestMethods | string;
responseResult: ResponseResult; responseResult: ResponseResult;
isSuccessful?: boolean; isSuccessful?: boolean;
console?: string; console?: string;

View File

@ -6,7 +6,7 @@ import { ExecuteApiRequestFullParams, ExecutePluginRequestParams } from './commo
export interface SaveDebugParams { export interface SaveDebugParams {
name: string; name: string;
protocol: string; protocol: string;
method: RequestMethods; method: RequestMethods | string;
path: string; path: string;
projectId: string; projectId: string;
moduleId: string; moduleId: string;
@ -47,7 +47,7 @@ export interface DebugDetail {
id: string; id: string;
name: string; name: string;
protocol: string; protocol: string;
method: string; method: RequestMethods | string;
path: string; path: string;
projectId: string; projectId: string;
moduleId: string; moduleId: string;

View File

@ -60,7 +60,7 @@ export interface ApiDefinitionDetail extends ApiDefinitionCreateParams {
id: string; id: string;
name: string; name: string;
protocol: string; protocol: string;
method: RequestMethods; method: RequestMethods | string;
path: string; path: string;
num: number; num: number;
pos: number; pos: number;
@ -314,7 +314,7 @@ export interface ApiCaseDetail extends ExecuteRequestParams {
environmentId: string; environmentId: string;
environmentName: string; environmentName: string;
follow: boolean; follow: boolean;
method: RequestMethods; method: RequestMethods | string;
path: string; path: string;
tags: string[]; tags: string[];
passRate: string; passRate: string;

View File

@ -1,4 +1,5 @@
import { RequestResult } from '@/models/apiTest/common'; import { RequestResult } from '@/models/apiTest/common';
import type { RequestMethods } from '@/enums/apiEnum';
export interface LegendData { export interface LegendData {
label: string; label: string;
@ -55,7 +56,7 @@ export interface StepContent {
cookies: string; cookies: string;
body: string; body: string;
status: string; status: string;
method: string; method: RequestMethods | string;
assertionTotal: number; assertionTotal: number;
passAssertionsTotal: number; passAssertionsTotal: number;
subRequestResults: ResponseResult[]; subRequestResults: ResponseResult[];

View File

@ -62,7 +62,7 @@ export interface ApiScenarioScheduleConfig {
export interface ApiScenarioTableItem { export interface ApiScenarioTableItem {
id: string; id: string;
name: string; name: string;
method: string; method: RequestMethods | string;
path: string; path: string;
num: number; num: number;
pos: number; pos: number;
@ -328,7 +328,7 @@ export type ScenarioStepDetail = Partial<
LoopStepDetail & LoopStepDetail &
ScenarioStepConfig & { ScenarioStepConfig & {
protocol: string; protocol: string;
method: RequestMethods; method: RequestMethods | string;
} }
>; >;
// 场景步骤项 // 场景步骤项
@ -367,13 +367,13 @@ export interface ScenarioStepItem {
} }
// 场景步骤文件参数 // 场景步骤文件参数
export interface ScenarioStepFileParams { export interface ScenarioStepFileParams {
uploadFileIds: string[]; uploadFileIds?: string[];
linkFileIds: string[]; linkFileIds?: string[];
deleteFileIds?: string[]; deleteFileIds?: string[];
unLinkFileIds?: string[]; unLinkFileIds?: string[];
} }
// 场景步骤详情 // 场景步骤详情
export type ScenarioStepDetails = RequestParam | CaseRequestParam | ExecuteConditionProcessor; export type ScenarioStepDetails = Partial<RequestParam | CaseRequestParam | ExecuteConditionProcessor>;
// 场景 // 场景
export interface Scenario { export interface Scenario {
id?: string | number; id?: string | number;

View File

@ -137,22 +137,49 @@
</a-button> </a-button>
</template> </template>
<!-- 接口调试支持快捷保存 --> <!-- 接口调试支持快捷保存 -->
<a-button <template
v-else-if=" v-else-if="
requestVModel.isNew requestVModel.isNew
? props.permissionMap && hasAnyPermission([props.permissionMap.create]) ? props.permissionMap && hasAnyPermission([props.permissionMap.create])
: props.permissionMap && hasAnyPermission([props.permissionMap.update]) : props.permissionMap && hasAnyPermission([props.permissionMap.update])
" "
type="secondary"
:disabled="isHttpProtocol && !requestVModel.url"
:loading="saveLoading"
@click="handleSaveShortcut"
> >
<div class="flex items-center"> <!-- 接口调试-可保存或保存为新接口定义 -->
{{ t('common.save') }} <a-dropdown-button
<div class="text-[var(--color-text-4)]">(<icon-command size="14" />+S)</div> v-if="
</div> props.permissionMap &&
</a-button> props.permissionMap.saveASApi &&
hasAllPermission([props.permissionMap.create, props.permissionMap.saveASApi])
"
:disabled="(isHttpProtocol && !requestVModel.url) || saveLoading"
@click="handleSaveShortcut"
>
<div class="flex items-center">
{{ t('common.save') }}
<div class="text-[var(--color-text-4)]">(<icon-command size="14" />+S)</div>
</div>
<template #icon>
<icon-down />
</template>
<template #content>
<a-doption value="saveAsApi" @click="() => handleSelect('saveAsApi')">
{{ t('apiTestDebug.saveAsApi') }}
</a-doption>
</template>
</a-dropdown-button>
<a-button
v-else
type="secondary"
:disabled="isHttpProtocol && !requestVModel.url"
:loading="saveLoading"
@click="handleSaveShortcut"
>
<div class="flex items-center">
{{ t('common.save') }}
<div class="text-[var(--color-text-4)]">(<icon-command size="14" />+S)</div>
</div>
</a-button>
</template>
</div> </div>
</div> </div>
</div> </div>
@ -412,6 +439,11 @@
</a-form-item> </a-form-item>
</a-form> </a-form>
</a-modal> </a-modal>
<saveAsApiModal
v-if="tempApiDetail"
v-model:visible="saveNewApiModalVisible"
:detail="tempApiDetail"
></saveAsApiModal>
<addDependencyDrawer <addDependencyDrawer
v-if="props.isDefinition" v-if="props.isDefinition"
v-model:visible="showAddDependencyDrawer" v-model:visible="showAddDependencyDrawer"
@ -439,6 +471,7 @@
import setting from './setting.vue'; import setting from './setting.vue';
import apiMethodName from '@/views/api-test/components/apiMethodName.vue'; import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
import apiMethodSelect from '@/views/api-test/components/apiMethodSelect.vue'; import apiMethodSelect from '@/views/api-test/components/apiMethodSelect.vue';
import saveAsApiModal from '@/views/api-test/components/saveAsApiModal.vue';
import apiBaseForm from '@/views/api-test/management/components/management/api/apiBaseForm.vue'; import apiBaseForm from '@/views/api-test/management/components/management/api/apiBaseForm.vue';
import { getPluginScript, getProtocolList } from '@/api/modules/api-test/common'; import { getPluginScript, getProtocolList } from '@/api/modules/api-test/common';
@ -451,7 +484,7 @@
import { filterTree, getGenerateId, parseQueryParams } from '@/utils'; import { filterTree, getGenerateId, parseQueryParams } from '@/utils';
import { scrollIntoView } from '@/utils/dom'; import { scrollIntoView } from '@/utils/dom';
import { registerCatchSaveShortcut, removeCatchSaveShortcut } from '@/utils/event'; import { registerCatchSaveShortcut, removeCatchSaveShortcut } from '@/utils/event';
import { hasAnyPermission } from '@/utils/permission'; import { hasAllPermission, hasAnyPermission } from '@/utils/permission';
import { import {
ExecuteApiRequestFullParams, ExecuteApiRequestFullParams,
@ -462,7 +495,6 @@
} from '@/models/apiTest/common'; } from '@/models/apiTest/common';
import { AddApiCaseParams } from '@/models/apiTest/management'; import { AddApiCaseParams } from '@/models/apiTest/management';
import { ModuleTreeNode, TransferFileParams } from '@/models/common'; import { ModuleTreeNode, TransferFileParams } from '@/models/common';
import { EnvConfig } from '@/models/projectManagement/environmental';
import { import {
RequestAuthType, RequestAuthType,
RequestBodyFormat, RequestBodyFormat,
@ -542,6 +574,7 @@
execute: string; execute: string;
create: string; create: string;
update: string; update: string;
saveASApi?: string;
}; };
}>(); }>();
const emit = defineEmits(['addDone', 'execute']); const emit = defineEmits(['addDone', 'execute']);
@ -846,7 +879,7 @@
initPluginScript(); initPluginScript();
} else { } else {
requestVModel.value.activeTab = RequestComposition.HEADER; requestVModel.value.activeTab = RequestComposition.HEADER;
if (!Object.values(RequestMethods).includes(requestVModel.value.method)) { if (!Object.values(RequestMethods).includes(requestVModel.value.method as RequestMethods)) {
// HTTP GET // HTTP GET
requestVModel.value.method = RequestMethods.GET; requestVModel.value.method = RequestMethods.GET;
} }
@ -1562,12 +1595,25 @@
const apiBaseFormRef = ref<InstanceType<typeof apiBaseForm>>(); const apiBaseFormRef = ref<InstanceType<typeof apiBaseForm>>();
const isUrlError = ref(false); const isUrlError = ref(false);
const tempApiDetail = ref<RequestParam>();
const saveNewApiModalVisible = ref(false);
function handleSelect(value: string | number | Record<string, any> | undefined) { function handleSelect(value: string | number | Record<string, any> | undefined) {
if (requestVModel.value.url === '' && requestVModel.value.protocol === 'HTTP') { if (requestVModel.value.url === '' && requestVModel.value.protocol === 'HTTP') {
isUrlError.value = true; isUrlError.value = true;
return; return;
} }
isUrlError.value = false; isUrlError.value = false;
if (value === 'saveAsApi') {
const params = makeRequestParams();
tempApiDetail.value = {
...params,
...params.request,
polymorphicName: params.request.polymorphicName,
};
saveNewApiModalVisible.value = true;
return;
}
apiBaseFormRef.value?.formRef?.validate(async (errors) => { apiBaseFormRef.value?.formRef?.validate(async (errors) => {
if (errors) { if (errors) {
requestVModel.value.activeTab = RequestComposition.BASE_INFO; requestVModel.value.activeTab = RequestComposition.BASE_INFO;

View File

@ -0,0 +1,219 @@
<template>
<a-modal
v-model:visible="visible"
:title="t('apiTestDebug.saveAsApi')"
class="ms-modal-form"
title-align="start"
body-class="!p-0"
>
<a-form ref="saveModalFormRef" :model="saveModalForm" layout="vertical">
<a-form-item
field="name"
:label="t('apiTestDebug.requestName')"
:rules="[{ required: true, message: t('apiTestDebug.requestNameRequired') }]"
asterisk-position="end"
>
<a-input
v-model:model-value="saveModalForm.name"
:max-length="255"
:placeholder="t('apiTestDebug.requestNamePlaceholder')"
/>
</a-form-item>
<a-form-item
v-if="props.detail.protocol === 'HTTP'"
field="path"
:label="t('apiTestDebug.requestUrl')"
:rules="[{ required: true, message: t('apiTestDebug.requestUrlRequired') }]"
asterisk-position="end"
>
<a-input
v-model:model-value="saveModalForm.path"
:max-length="255"
:placeholder="t('apiTestDebug.commonPlaceholder')"
/>
</a-form-item>
<a-form-item :label="t('apiTestDebug.requestModule')" class="mb-0">
<a-tree-select
v-model:modelValue="saveModalForm.moduleId"
:data="apiModuleTree"
:field-names="{ title: 'name', key: 'id', children: 'children' }"
:tree-props="{
virtualListProps: {
height: 200,
threshold: 200,
},
}"
allow-search
/>
</a-form-item>
</a-form>
<template #footer>
<div class="flex items-center justify-between">
<div class="flex items-center gap-[4px]">
<a-checkbox v-model:model-value="saveModalForm.saveApiAsCase"></a-checkbox>
{{ t('apiScenario.syncSaveAsCase') }}
</div>
<div class="flex items-center gap-[12px]">
<a-button type="secondary" :disabled="saveLoading" @click="handleSaveApiCancel">
{{ t('common.cancel') }}
</a-button>
<a-button type="primary" :loading="saveLoading" @click="handleSaveApi">{{ t('common.confirm') }}</a-button>
</div>
</div>
</template>
</a-modal>
</template>
<script setup lang="ts">
import { Message } from '@arco-design/web-vue';
import { MsTreeNodeData } from '@/components/business/ms-tree/types';
import { addCase, addDefinition, getModuleTreeOnlyModules } from '@/api/modules/api-test/management';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import { AddApiCaseParams } from '@/models/apiTest/management';
import { RequestCaseStatus, RequestDefinitionStatus } from '@/enums/apiEnum';
import { defaultResponseItem } from '@/views/api-test/components/config';
import type { RequestParam as ApiDefinitionRequestParam } from '@/views/api-test/components/requestComposition/index.vue';
import type { RequestParam } from '@/views/api-test/scenario/components/common/customApiDrawer.vue';
import type { FormInstance } from '@arco-design/web-vue';
const props = defineProps<{
detail: RequestParam | ApiDefinitionRequestParam;
}>();
const appStore = useAppStore();
const { t } = useI18n();
const visible = defineModel<boolean>('visible', {
required: true,
});
const saveModalForm = ref({
name: '',
path: '',
moduleId: 'root',
saveApiAsCase: false,
});
const saveModalFormRef = ref<FormInstance>();
const saveLoading = ref(false);
const apiModuleTree = ref<MsTreeNodeData[]>([]);
async function initApiModuleTree(protocol: string) {
try {
apiModuleTree.value = await getModuleTreeOnlyModules({
keyword: '',
protocol,
projectId: appStore.currentProjectId,
moduleIds: [],
});
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
}
}
async function saveApiAsCase(id: string) {
let url;
let path = '';
try {
url = new URL(saveModalForm.value.path);
path = url.pathname + url.search + url.hash;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
path = saveModalForm.value.path;
}
const params: AddApiCaseParams = {
name: saveModalForm.value.name,
projectId: appStore.currentProjectId,
environmentId: appStore.currentEnvConfig?.id || '',
apiDefinitionId: id,
request: {
...props.detail,
url: path,
},
priority: 'P0',
status: RequestCaseStatus.PROCESSING,
tags: [],
uploadFileIds: props.detail.uploadFileIds || [],
linkFileIds: props.detail.linkFileIds || [],
};
await addCase(params);
}
/**
* 保存请求
* @param isSaveCase 是否需要保存用例
*/
async function realSaveAsApi() {
try {
saveLoading.value = true;
let url;
let path = '';
try {
url = new URL(saveModalForm.value.path);
path = url.pathname + url.search + url.hash;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
path = saveModalForm.value.path;
}
const res = await addDefinition({
...saveModalForm.value,
path,
projectId: appStore.currentProjectId,
tags: [],
description: '',
status: RequestDefinitionStatus.PROCESSING,
customFields: [],
versionId: '',
environmentId: appStore.currentEnvConfig?.id || '',
request: {
...props.detail,
url: path,
path,
},
uploadFileIds: props.detail.uploadFileIds || [],
linkFileIds: props.detail.linkFileIds || [],
response: [defaultResponseItem],
method: props.detail.method,
protocol: props.detail.protocol,
});
if (saveModalForm.value.saveApiAsCase) {
await saveApiAsCase(res.id);
}
Message.success(t('common.saveSuccess'));
visible.value = false;
saveLoading.value = false;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
saveLoading.value = false;
}
}
function handleSaveApiCancel() {
saveModalFormRef.value?.resetFields();
saveModalForm.value.saveApiAsCase = false;
visible.value = false;
}
function handleSaveApi() {
saveModalFormRef.value?.validate(async (errors) => {
if (!errors) {
await realSaveAsApi();
handleSaveApiCancel();
}
});
}
onBeforeMount(() => {
saveModalForm.value.path = props.detail.url || props.detail.path;
initApiModuleTree(props.detail.protocol);
});
</script>
<style lang="less" scoped></style>

View File

@ -51,6 +51,7 @@
execute: 'PROJECT_API_DEBUG:READ+EXECUTE', execute: 'PROJECT_API_DEBUG:READ+EXECUTE',
update: 'PROJECT_API_DEBUG:READ+UPDATE', update: 'PROJECT_API_DEBUG:READ+UPDATE',
create: 'PROJECT_API_DEBUG:READ+ADD', create: 'PROJECT_API_DEBUG:READ+ADD',
saveASApi: 'PROJECT_API_DEFINITION:READ+ADD',
}" }"
@add-done="handleDebugAddDone" @add-done="handleDebugAddDone"
/> />
@ -91,6 +92,64 @@
</MsCodeEditor> </MsCodeEditor>
</div> </div>
</MsDrawer> </MsDrawer>
<!-- <a-modal
v-model:visible="saveAsApiModalVisible"
:title="t('common.save')"
:ok-loading="saveLoading"
class="ms-modal-form"
title-align="start"
body-class="!p-0"
@before-ok="handleSave"
@cancel="handleCancel"
>
<a-form ref="saveModalFormRef" :model="saveModalForm" layout="vertical">
<a-form-item
field="name"
:label="t('apiTestDebug.requestName')"
:rules="[{ required: true, message: t('apiTestDebug.requestNameRequired') }]"
asterisk-position="end"
>
<a-input
v-model:model-value="saveModalForm.name"
:max-length="255"
:placeholder="t('apiTestDebug.requestNamePlaceholder')"
/>
</a-form-item>
<a-form-item
v-if="isHttpProtocol"
field="path"
:label="t('apiTestDebug.requestUrl')"
:rules="[{ required: true, message: t('apiTestDebug.requestUrlRequired') }]"
asterisk-position="end"
>
<a-input
v-model:model-value="saveModalForm.path"
:max-length="255"
:placeholder="t('apiTestDebug.commonPlaceholder')"
/>
</a-form-item>
<a-form-item :label="t('apiTestDebug.requestModule')" class="mb-0">
<a-tree-select
v-model:modelValue="saveModalForm.moduleId"
:data="apiModules as ModuleTreeNode[]"
:field-names="{ title: 'name', key: 'id', children: 'children' }"
:tree-props="{
virtualListProps: {
height: 200,
threshold: 200,
},
}"
allow-search
>
<template #tree-slot-title="node">
<a-tooltip :content="`${node.name}`" position="tl">
<div class="one-line-text w-[300px] text-[var(--color-text-1)]">{{ node.name }}</div>
</a-tooltip>
</template>
</a-tree-select>
</a-form-item>
</a-form>
</a-modal> -->
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -107,6 +166,7 @@
import MsEditableTab from '@/components/pure/ms-editable-tab/index.vue'; import MsEditableTab from '@/components/pure/ms-editable-tab/index.vue';
import { TabItem } from '@/components/pure/ms-editable-tab/types'; import { TabItem } from '@/components/pure/ms-editable-tab/types';
import MsSplitBox from '@/components/pure/ms-split-box/index.vue'; import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
import moduleTree from './components/moduleTree.vue'; import moduleTree from './components/moduleTree.vue';
import apiMethodName from '@/views/api-test/components/apiMethodName.vue'; import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
import debug, { RequestParam } from '@/views/api-test/components/requestComposition/index.vue'; import debug, { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
@ -121,8 +181,10 @@
updateDebug, updateDebug,
uploadTempFile, uploadTempFile,
} from '@/api/modules/api-test/debug'; } from '@/api/modules/api-test/debug';
import { getModuleTreeOnlyModules } from '@/api/modules/api-test/management';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useLeaveTabUnSaveCheck from '@/hooks/useLeaveTabUnSaveCheck'; import useLeaveTabUnSaveCheck from '@/hooks/useLeaveTabUnSaveCheck';
import useAppStore from '@/store/modules/app';
import { parseCurlScript } from '@/utils'; import { parseCurlScript } from '@/utils';
import { hasAnyPermission } from '@/utils/permission'; import { hasAnyPermission } from '@/utils/permission';
@ -138,7 +200,9 @@
import { defaultBodyParams, defaultResponse } from '../components/config'; import { defaultBodyParams, defaultResponse } from '../components/config';
import { parseRequestBodyFiles } from '../components/utils'; import { parseRequestBodyFiles } from '../components/utils';
import type { FormInstance } from '@arco-design/web-vue';
const appStore = useAppStore();
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
@ -354,6 +418,42 @@
} }
} }
const saveAsApiModalVisible = ref(false);
const saveModalForm = ref({
id: '',
name: '',
path: '',
moduleId: 'root',
});
const saveModalFormRef = ref<FormInstance>();
const saveLoading = ref(false);
const apiModules = ref<ModuleTreeNode[]>([]);
watch(
() => saveAsApiModalVisible.value,
(val) => {
if (!val) {
saveModalFormRef.value?.resetFields();
}
}
);
/**
async function openSaveAsApiModal(node: MsTreeNodeData) {
try {
const [modules] = await getModuleTreeOnlyModules({
keyword: '',
protocol: '',
projectId: appStore.currentProjectId,
moduleIds: [],
});
apiModules.value = modules;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
}
}
*/
onMounted(() => { onMounted(() => {
if (route.query.id) { if (route.query.id) {
openApiTab(route.query.id as string); openApiTab(route.query.id as string);

View File

@ -205,4 +205,5 @@ export default {
'apiTestDebug.extractValueTitleTip': 'apiTestDebug.extractValueTitleTip':
'Enter the column name and corresponding value in column storage. If you want to extract the first value of the name column, enter name_1', 'Enter the column name and corresponding value in column storage. If you want to extract the first value of the name column, enter name_1',
'apiTestDebug.responseRepeatMessage': 'The name is duplicated, please re-enter it.', 'apiTestDebug.responseRepeatMessage': 'The name is duplicated, please re-enter it.',
'apiTestDebug.saveAsApi': 'Save as Api',
}; };

View File

@ -191,4 +191,5 @@ export default {
'apiTestDebug.regexMatchRules': '表达式匹配规则', 'apiTestDebug.regexMatchRules': '表达式匹配规则',
'apiTestDebug.extractValueTitleTip': '输入按列存储中的列名和对应的数值如提取name列的第一个值则输入name_1', 'apiTestDebug.extractValueTitleTip': '输入按列存储中的列名和对应的数值如提取name列的第一个值则输入name_1',
'apiTestDebug.responseRepeatMessage': '名称重复,请重新输入', 'apiTestDebug.responseRepeatMessage': '名称重复,请重新输入',
'apiTestDebug.saveAsApi': '另存为接口',
}; };

View File

@ -143,7 +143,7 @@
@change="handleUrlChange" @change="handleUrlChange"
> >
<template v-if="showEnvPrefix" #prefix> <template v-if="showEnvPrefix" #prefix>
{{ appStore.currentEnvConfig?.httpConfig.find((e) => e.type === 'NONE')?.url }} {{ (appStore.currentEnvConfig as EnvConfig)?.httpConfig.find((e) => e.type === 'NONE')?.url }}
</template> </template>
</a-input> </a-input>
</a-input-group> </a-input-group>
@ -390,13 +390,19 @@
import { scrollIntoView } from '@/utils/dom'; import { scrollIntoView } from '@/utils/dom';
import { import {
EnableKeyValueParam,
ExecuteApiRequestFullParams, ExecuteApiRequestFullParams,
ExecuteBody,
ExecuteConditionConfig, ExecuteConditionConfig,
ExecutePluginRequestParams,
ExecuteRequestCommonParam,
ExecuteRequestFormBody,
PluginConfig, PluginConfig,
RequestResult, RequestResult,
RequestTaskResult, RequestTaskResult,
} from '@/models/apiTest/common'; } from '@/models/apiTest/common';
import { ScenarioStepFileParams, ScenarioStepItem } from '@/models/apiTest/scenario'; import { ScenarioStepFileParams, ScenarioStepItem } from '@/models/apiTest/scenario';
import type { EnvConfig } from '@/models/projectManagement/environmental';
import { import {
RequestAuthType, RequestAuthType,
RequestBodyFormat, RequestBodyFormat,
@ -429,7 +435,8 @@
// ); // );
export interface RequestCustomAttr { export interface RequestCustomAttr {
type: 'api'; type?: 'api';
label: string;
name: string; name: string;
stepId: string | number; // id stepId: string | number; // id
resourceId: string | number; // id resourceId: string | number; // id
@ -450,7 +457,7 @@
}; };
} }
export type RequestParam = ExecuteApiRequestFullParams & { export type RequestParam = (ExecuteApiRequestFullParams | ExecutePluginRequestParams) & {
response?: RequestTaskResult; response?: RequestTaskResult;
customizeRequest?: boolean; customizeRequest?: boolean;
customizeRequestEnvEnable?: boolean; customizeRequestEnvEnable?: boolean;
@ -489,6 +496,7 @@
const loading = defineModel<boolean>('detailLoading', { default: false }); const loading = defineModel<boolean>('detailLoading', { default: false });
const defaultApiParams: RequestParam = { const defaultApiParams: RequestParam = {
label: '',
name: '', name: '',
type: 'api', type: 'api',
stepId: '', stepId: '',
@ -824,7 +832,7 @@
nextTick(() => { nextTick(() => {
if (fApi.value) { if (fApi.value) {
fApi.value.nextRefresh(() => { fApi.value.nextRefresh(() => {
const form = {}; const form: Record<string, any> = {};
controlPluginFormFields().forEach((key) => { controlPluginFormFields().forEach((key) => {
form[key] = formData[key]; form[key] = formData[key];
}); });
@ -891,7 +899,7 @@
initPluginScript(); initPluginScript();
} else { } else {
requestVModel.value.activeTab = RequestComposition.HEADER; requestVModel.value.activeTab = RequestComposition.HEADER;
if (!Object.values(RequestMethods).includes(requestVModel.value.method)) { if (!Object.values(RequestMethods).includes(requestVModel.value.method as RequestMethods)) {
// HTTP GET // HTTP GET
requestVModel.value.method = RequestMethods.GET; requestVModel.value.method = RequestMethods.GET;
} }
@ -1082,12 +1090,12 @@
async function execute(executeType?: 'localExec' | 'serverExec') { async function execute(executeType?: 'localExec' | 'serverExec') {
requestVModel.value.executeLoading = true; requestVModel.value.executeLoading = true;
if (isHttpProtocol.value) { if (isHttpProtocol.value) {
emit('execute', makeRequestParams(executeType), executeType); emit('execute', makeRequestParams(executeType) as RequestParam, executeType);
} else { } else {
// //
fApi.value?.validate(async (valid) => { fApi.value?.validate(async (valid) => {
if (valid === true) { if (valid === true) {
emit('execute', makeRequestParams(executeType), executeType); emit('execute', makeRequestParams(executeType) as RequestParam, executeType);
} else { } else {
requestVModel.value.activeTab = RequestComposition.PLUGIN; requestVModel.value.activeTab = RequestComposition.PLUGIN;
nextTick(() => { nextTick(() => {
@ -1103,7 +1111,7 @@
emit('stopDebug'); emit('stopDebug');
} }
function initErrorMessageInfoItem(key) { function initErrorMessageInfoItem(key: string) {
if (requestVModel.value.errorMessageInfo && !requestVModel.value.errorMessageInfo[key]) { if (requestVModel.value.errorMessageInfo && !requestVModel.value.errorMessageInfo[key]) {
requestVModel.value.errorMessageInfo[key] = {}; requestVModel.value.errorMessageInfo[key] = {};
} }
@ -1181,7 +1189,7 @@
showMessage(); showMessage();
return; return;
} }
emit('addStep', cloneDeep(makeRequestParams())); emit('addStep', cloneDeep(makeRequestParams()) as RequestParam);
} }
function handleSave() { function handleSave() {
@ -1202,7 +1210,7 @@
function handleClose() { function handleClose() {
// applyStep // applyStep
if (!requestVModel.value.isNew) { if (!requestVModel.value.isNew) {
emit('applyStep', cloneDeep(makeRequestParams())); emit('applyStep', cloneDeep(makeRequestParams()) as RequestParam);
} }
} }
@ -1237,21 +1245,28 @@
if (_stepType.value.isQuoteApi && props.request && isHttpProtocol.value && !props.step?.isQuoteScenarioStep) { if (_stepType.value.isQuoteApi && props.request && isHttpProtocol.value && !props.step?.isQuoteScenarioStep) {
// api // api
// queryrest // queryrest
['headers', 'query', 'rest'].forEach((type) => { (['headers', 'query', 'rest'] as (keyof RequestParam)[]).forEach((type: keyof RequestParam) => {
props.request?.[type]?.forEach((item) => { (props.request?.[type] as (EnableKeyValueParam | ExecuteRequestCommonParam)[])?.forEach(
if (!item.key.length) return; (item: EnableKeyValueParam) => {
const index = requestVModel.value[type]?.findIndex((itemReq) => itemReq.key === item.key); if (!item.key.length) return;
if (index > -1) { const index = (
requestVModel.value[type][index].value = item.value; requestVModel.value[type] as (EnableKeyValueParam | ExecuteRequestCommonParam)[]
)?.findIndex((itemReq) => itemReq.key === item.key);
if (index > -1) {
(requestVModel.value[type] as (EnableKeyValueParam | ExecuteRequestCommonParam)[])[index].value =
item.value;
}
} }
}); );
}); });
['formDataBody', 'wwwFormBody'].forEach((type) => { (['formDataBody', 'wwwFormBody'] as (keyof ExecuteBody)[]).forEach((type: keyof ExecuteBody) => {
props.request?.body?.[type].formValues.forEach((item) => { (props.request?.body?.[type] as ExecuteRequestFormBody).formValues.forEach((item) => {
if (!item.key.length) return; if (!item.key.length) return;
const index = requestVModel.value.body[type].formValues.findIndex((itemReq) => itemReq.key === item.key); const index = (requestVModel.value.body[type] as ExecuteRequestFormBody).formValues.findIndex(
(itemReq) => itemReq.key === item.key
);
if (index > -1) { if (index > -1) {
requestVModel.value.body[type].formValues[index].value = item.value; (requestVModel.value.body[type] as ExecuteRequestFormBody).formValues[index].value = item.value;
} }
}); });
}); });

View File

@ -69,7 +69,7 @@
<div v-show="!pluginError || isHttpProtocol" class="flex h-full flex-col"> <div v-show="!pluginError || isHttpProtocol" class="flex h-full flex-col">
<div class="flex items-center gap-[16px] p-[16px] pb-[8px]"> <div class="flex items-center gap-[16px] p-[16px] pb-[8px]">
<a-input <a-input
v-if="activeStep?.stepType && _stepType.isQuoteCase" v-if="_stepType.isQuoteCase || activeStep?.isQuoteScenarioStep"
v-model:model-value="requestVModel.name" v-model:model-value="requestVModel.name"
:max-length="255" :max-length="255"
:show-word-limit="isEditableApi" :show-word-limit="isEditableApi"
@ -365,6 +365,7 @@
const hasLocalExec = inject<Ref<boolean>>('hasLocalExec'); const hasLocalExec = inject<Ref<boolean>>('hasLocalExec');
const defaultApiParams: RequestParam = { const defaultApiParams: RequestParam = {
label: '',
name: '', name: '',
type: 'api', type: 'api',
stepId: '', stepId: '',

View File

@ -389,69 +389,11 @@
</div> </div>
</template> </template>
</a-modal> </a-modal>
<a-modal <saveAsApiModal
v-if="tempApiDetail"
v-model:visible="saveNewApiModalVisible" v-model:visible="saveNewApiModalVisible"
:title="t('common.save')" :detail="tempApiDetail"
class="ms-modal-form" ></saveAsApiModal>
title-align="start"
body-class="!p-0"
>
<a-form ref="saveModalFormRef" :model="saveModalForm" layout="vertical">
<a-form-item
field="name"
:label="t('apiTestDebug.requestName')"
:rules="[{ required: true, message: t('apiTestDebug.requestNameRequired') }]"
asterisk-position="end"
>
<a-input
v-model:model-value="saveModalForm.name"
:max-length="255"
:placeholder="t('apiTestDebug.requestNamePlaceholder')"
/>
</a-form-item>
<a-form-item
v-if="activeStep?.config.protocol === 'HTTP'"
field="path"
:label="t('apiTestDebug.requestUrl')"
:rules="[{ required: true, message: t('apiTestDebug.requestUrlRequired') }]"
asterisk-position="end"
>
<a-input
v-model:model-value="saveModalForm.path"
:max-length="255"
:placeholder="t('apiTestDebug.commonPlaceholder')"
/>
</a-form-item>
<a-form-item :label="t('apiTestDebug.requestModule')" class="mb-0">
<a-tree-select
v-model:modelValue="saveModalForm.moduleId"
:data="apiModuleTree"
:field-names="{ title: 'name', key: 'id', children: 'children' }"
:tree-props="{
virtualListProps: {
height: 200,
threshold: 200,
},
}"
allow-search
/>
</a-form-item>
</a-form>
<template #footer>
<div class="flex items-center justify-between">
<div class="flex items-center gap-[4px]">
<a-checkbox v-model:model-value="saveModalForm.saveApiAsCase"></a-checkbox>
{{ t('apiScenario.syncSaveAsCase') }}
</div>
<div class="flex items-center gap-[12px]">
<a-button type="secondary" :disabled="saveLoading" @click="handleSaveApiCancel">
{{ t('common.cancel') }}
</a-button>
<a-button type="primary" :loading="saveLoading" @click="handleSaveApi">{{ t('common.confirm') }}</a-button>
</div>
</div>
</template>
</a-modal>
<a-modal <a-modal
v-model:visible="saveCaseModalVisible" v-model:visible="saveCaseModalVisible"
:title="t('apiTestManagement.saveAsCase')" :title="t('apiTestManagement.saveAsCase')"
@ -519,14 +461,10 @@
import waitTimeContent from './stepNodeComposition/waitTimeContent.vue'; import waitTimeContent from './stepNodeComposition/waitTimeContent.vue';
import apiMethodName from '@/views/api-test/components/apiMethodName.vue'; import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
import { RequestParam as CaseRequestParam } from '@/views/api-test/components/requestComposition/index.vue'; import { RequestParam as CaseRequestParam } from '@/views/api-test/components/requestComposition/index.vue';
import saveAsApiModal from '@/views/api-test/components/saveAsApiModal.vue';
import { localExecuteApiDebug } from '@/api/modules/api-test/common'; import { localExecuteApiDebug } from '@/api/modules/api-test/common';
import { import { addCase, getDefinitionDetail } from '@/api/modules/api-test/management';
addCase,
addDefinition,
getDefinitionDetail,
getModuleTreeOnlyModules,
} from '@/api/modules/api-test/management';
import { debugScenario, getScenarioDetail, getScenarioStep } from '@/api/modules/api-test/scenario'; import { debugScenario, getScenarioDetail, getScenarioStep } from '@/api/modules/api-test/scenario';
import { getSocket } from '@/api/modules/project-management/commonScript'; import { getSocket } from '@/api/modules/project-management/commonScript';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
@ -547,6 +485,7 @@
ExecuteApiRequestFullParams, ExecuteApiRequestFullParams,
ExecuteConditionProcessor, ExecuteConditionProcessor,
ExecutePluginRequestParams, ExecutePluginRequestParams,
RequestResult,
} from '@/models/apiTest/common'; } from '@/models/apiTest/common';
import { AddApiCaseParams } from '@/models/apiTest/management'; import { AddApiCaseParams } from '@/models/apiTest/management';
import { import {
@ -554,14 +493,13 @@
CreateStepAction, CreateStepAction,
Scenario, Scenario,
ScenarioStepConfig, ScenarioStepConfig,
ScenarioStepDetail,
ScenarioStepDetails, ScenarioStepDetails,
ScenarioStepFileParams, ScenarioStepFileParams,
ScenarioStepItem, ScenarioStepItem,
} from '@/models/apiTest/scenario'; } from '@/models/apiTest/scenario';
import { EnvConfig } from '@/models/projectManagement/environmental';
import { import {
RequestCaseStatus, RequestCaseStatus,
RequestDefinitionStatus,
ScenarioAddStepActionType, ScenarioAddStepActionType,
ScenarioExecuteStatus, ScenarioExecuteStatus,
ScenarioStepRefType, ScenarioStepRefType,
@ -571,7 +509,7 @@
import type { RequestParam } from '../common/customApiDrawer.vue'; import type { RequestParam } from '../common/customApiDrawer.vue';
import updateStepStatus from '../utils'; import updateStepStatus from '../utils';
import useCreateActions from './createAction/useCreateActions'; import useCreateActions from './createAction/useCreateActions';
import { casePriorityOptions, caseStatusOptions, defaultResponseItem } from '@/views/api-test/components/config'; import { casePriorityOptions, caseStatusOptions } from '@/views/api-test/components/config';
import { parseRequestBodyFiles } from '@/views/api-test/components/utils'; import { parseRequestBodyFiles } from '@/views/api-test/components/utils';
import getStepType from '@/views/api-test/scenario/components/common/stepType/utils'; import getStepType from '@/views/api-test/scenario/components/common/stepType/utils';
import { defaultStepItemCommon } from '@/views/api-test/scenario/components/config'; import { defaultStepItemCommon } from '@/views/api-test/scenario/components/config';
@ -899,11 +837,13 @@
stepDetails.value[step.id] = { stepDetails.value[step.id] = {
...res, ...res,
stepId: step.id, stepId: step.id,
protocol: step.config.protocol, protocol: step.config.protocol || '',
method: step.config.method, method: step.config.method || '',
...parseRequestBodyResult,
};
scenario.value.stepFileParam[step.id] = {
...parseRequestBodyResult, ...parseRequestBodyResult,
}; };
scenario.value.stepFileParam[step.id] = parseRequestBodyResult;
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(error); console.log(error);
@ -912,124 +852,8 @@
} }
} }
const apiModuleTree = ref<MsTreeNodeData[]>([]);
async function initApiModuleTree(protocol: string) {
try {
apiModuleTree.value = await getModuleTreeOnlyModules({
keyword: '',
protocol,
projectId: appStore.currentProjectId,
moduleIds: [],
});
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
}
}
const saveNewApiModalVisible = ref(false); const saveNewApiModalVisible = ref(false);
const saveModalForm = ref({ const tempApiDetail = ref<RequestParam>();
name: '',
path: '',
moduleId: 'root',
saveApiAsCase: false,
});
const saveModalFormRef = ref<FormInstance>();
const saveLoading = ref(false);
async function saveApiAsCase(id: string) {
if (activeStep.value) {
const detail = stepDetails.value[activeStep.value.id] as RequestParam;
const fileParams = scenario.value.stepFileParam[activeStep.value.id];
const url = new URL(saveModalForm.value.path);
const path = url.pathname + url.search + url.hash;
const params: AddApiCaseParams = {
name: saveModalForm.value.name,
projectId: appStore.currentProjectId,
environmentId: appStore.currentEnvConfig?.id || '',
apiDefinitionId: id,
request: {
...detail,
url: path,
},
priority: 'P0',
status: RequestCaseStatus.PROCESSING,
tags: [],
uploadFileIds: fileParams?.uploadFileIds || [],
linkFileIds: fileParams?.linkFileIds || [],
};
await addCase(params);
}
}
/**
* 保存请求
* @param isSaveCase 是否需要保存用例
*/
async function realSaveAsApi() {
try {
saveLoading.value = true;
if (activeStep.value) {
let url;
let path = '';
try {
url = new URL(saveModalForm.value.path);
path = url.pathname + url.search + url.hash;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
path = saveModalForm.value.path;
}
const detail = stepDetails.value[activeStep.value.id] as RequestParam;
const fileParams = scenario.value.stepFileParam[activeStep.value.id];
const res = await addDefinition({
...saveModalForm.value,
path,
projectId: appStore.currentProjectId,
tags: [],
description: '',
status: RequestDefinitionStatus.PROCESSING,
customFields: [],
versionId: '',
environmentId: appStore.currentEnvConfig?.id || '',
request: {
...detail,
url: path,
path,
},
uploadFileIds: fileParams?.uploadFileIds || [],
linkFileIds: fileParams?.linkFileIds || [],
response: [defaultResponseItem],
method: detail?.method,
protocol: detail?.protocol,
});
if (saveModalForm.value.saveApiAsCase) {
await saveApiAsCase(res.id);
}
Message.success(t('common.saveSuccess'));
saveNewApiModalVisible.value = false;
saveLoading.value = false;
}
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
saveLoading.value = false;
}
}
function handleSaveApiCancel() {
saveModalFormRef.value?.resetFields();
saveNewApiModalVisible.value = false;
}
function handleSaveApi() {
saveModalFormRef.value?.validate(async (errors) => {
if (!errors) {
await realSaveAsApi();
handleSaveApiCancel();
}
});
}
const saveCaseModalVisible = ref(false); const saveCaseModalVisible = ref(false);
const saveCaseLoading = ref(false); const saveCaseLoading = ref(false);
@ -1209,8 +1033,13 @@
break; break;
case 'saveAsApi': case 'saveAsApi':
activeStep.value = node as ScenarioStepItem; activeStep.value = node as ScenarioStepItem;
initApiModuleTree((stepDetails.value[node.id] as RequestParam)?.protocol); const detail = stepDetails.value[activeStep.value.id] as RequestParam;
saveModalForm.value.path = (stepDetails.value[node.id] as RequestParam)?.url; const fileParams = scenario.value.stepFileParam[activeStep.value.id];
tempApiDetail.value = {
...detail,
uploadFileIds: fileParams?.uploadFileIds || [],
linkFileIds: fileParams?.linkFileIds || [],
};
saveNewApiModalVisible.value = true; saveNewApiModalVisible.value = true;
break; break;
case 'saveAsCase': case 'saveAsCase':
@ -1284,7 +1113,7 @@
scenario.value.unSaved = !!tempStepDesc.value; scenario.value.unSaved = !!tempStepDesc.value;
} }
function handleStepContentChange($event, step: ScenarioStepItem) { function handleStepContentChange($event: Record<string, any>, step: ScenarioStepItem) {
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.uniqueId, 'uniqueId'); const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.uniqueId, 'uniqueId');
if (realStep) { if (realStep) {
Object.keys($event).forEach((key) => { Object.keys($event).forEach((key) => {
@ -1399,7 +1228,7 @@
if (data.msgType === 'EXEC_RESULT') { if (data.msgType === 'EXEC_RESULT') {
if (step.reportId === data.reportId) { if (step.reportId === data.reportId) {
// tabtab // tabtab
data.taskResult.requestResults.forEach((result) => { data.taskResult.requestResults.forEach((result: RequestResult) => {
if (_scenario.stepResponses[result.stepId] === undefined) { if (_scenario.stepResponses[result.stepId] === undefined) {
_scenario.stepResponses[result.stepId] = []; _scenario.stepResponses[result.stepId] = [];
} }
@ -1466,7 +1295,7 @@
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, node.uniqueId, 'uniqueId'); const realStep = findNodeByKey<ScenarioStepItem>(steps.value, node.uniqueId, 'uniqueId');
if (realStep) { if (realStep) {
realStep.reportId = getGenerateId(); realStep.reportId = getGenerateId();
const _stepDetails = {}; const _stepDetails: Record<string, any> = {};
const stepFileParam = scenario.value.stepFileParam[realStep.id]; const stepFileParam = scenario.value.stepFileParam[realStep.id];
traverseTree( traverseTree(
realStep, realStep,
@ -1579,7 +1408,9 @@
// //
if (realStep.parent?.children) { if (realStep.parent?.children) {
// children // children
const index = realStep.parent.children.findIndex((item) => item.uniqueId === realStep.uniqueId); const index = realStep.parent.children.findIndex(
(item: ScenarioStepItem) => item.uniqueId === realStep.uniqueId
);
realStep.parent.children.splice(index, 1, newStep); realStep.parent.children.splice(index, 1, newStep);
} else { } else {
// //
@ -1930,10 +1761,10 @@
} }
const showQuickInput = ref(false); const showQuickInput = ref(false);
const quickInputParamValue = ref(''); const quickInputParamValue = ref<any>('');
const quickInputDataKey = ref(''); const quickInputDataKey = ref('');
function setQuickInput(step: ScenarioStepItem, dataKey: string) { function setQuickInput(step: ScenarioStepItem, dataKey: keyof ScenarioStepDetail) {
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.uniqueId, 'uniqueId'); const realStep = findNodeByKey<ScenarioStepItem>(steps.value, step.uniqueId, 'uniqueId');
if (realStep) { if (realStep) {
activeStep.value = realStep as ScenarioStepItem; activeStep.value = realStep as ScenarioStepItem;

View File

@ -39,6 +39,7 @@
<assertion <assertion
v-if="activeKey === ScenarioCreateComposition.ASSERTION" v-if="activeKey === ScenarioCreateComposition.ASSERTION"
v-model:assertion-config="scenario.scenarioConfig.assertionConfig" v-model:assertion-config="scenario.scenarioConfig.assertionConfig"
@change="scenario.unSaved = true"
/> />
<template #title> <template #title>
<div class="flex items-center"> <div class="flex items-center">

View File

@ -8,35 +8,40 @@
</a-button> </a-button>
<template #content> <template #content>
<div class="arco-table-filters-content"> <div class="arco-table-filters-content">
<div class="ml-[6px] flex w-full items-center justify-start overflow-hidden px-[6px] py-[2px]"> <div class="arco-table-filters-content-list">
<a-checkbox-group <div class="flex w-full items-center justify-start overflow-hidden px-[12px] py-[2px]">
v-if="props.mode === 'static' && props.list?.length" <a-checkbox-group
v-model:model-value="innerStatusFilters" v-if="props.mode === 'static' && props.list?.length"
direction="vertical" v-model:model-value="innerStatusFilters"
size="small" direction="vertical"
> size="small"
<a-checkbox
v-for="(item, index) of props.list"
:key="item[props.valueKey || 'value']"
:value="item[props.valueKey || 'value']"
> >
<a-tooltip :content="item[props.labelKey || 'text']" :mouse-enter-delay="300"> <a-checkbox
<div class="one-line-text"> v-for="(item, index) of props.list"
<slot name="item" :item="item" :index="index"></slot> :key="item[props.valueKey || 'value']"
</div> :value="item[props.valueKey || 'value']"
</a-tooltip> >
</a-checkbox> <a-tooltip :content="item[props.labelKey || 'text']" :mouse-enter-delay="300">
</a-checkbox-group> <div class="one-line-text">
<slot name="item" :item="item" :index="index"></slot>
</div>
</a-tooltip>
</a-checkbox>
</a-checkbox-group>
</div>
<div v-if="props.mode === 'remote'" class="w-[200px] p-[12px] pb-0">
<MsUserSelector
v-model="innerStatusFilters"
:load-option-params="props.loadOptionParams"
:type="props.type"
:placeholder="props.placeholderText"
/>
</div>
</div> </div>
<div v-if="props.mode === 'remote'" class="w-[200px] p-4 pb-0"> <div
<MsUserSelector class="flex items-center border-t border-[var(--color-text-n8)] p-[12px]"
v-model="innerStatusFilters" :class="[props.mode === 'static' ? 'justify-between' : 'justify-end']"
:load-option-params="props.loadOptionParams" >
:type="props.type"
:placeholder="props.placeholderText"
/>
</div>
<div class="flex items-center p-4" :class="[props.mode === 'static' ? 'justify-between' : 'justify-end']">
<a-button size="mini" class="mr-[8px]" @click="resetFilter"> <a-button size="mini" class="mr-[8px]" @click="resetFilter">
{{ t('common.reset') }} {{ t('common.reset') }}
</a-button> </a-button>

View File

@ -4,8 +4,8 @@ export default {
'login.form.password.errMsg': '密码不能为空', 'login.form.password.errMsg': '密码不能为空',
'login.form.login.errMsg': '登录出错,请刷新重试', 'login.form.login.errMsg': '登录出错,请刷新重试',
'login.form.login.success': '欢迎使用', 'login.form.login.success': '欢迎使用',
'login.form.userName.placeholder': '请输入邮箱登录', 'login.form.userName.placeholder': '请输入邮箱',
'login.form.userName.placeholderOther': '请输入账号登录', 'login.form.userName.placeholderOther': '请输入账号',
'login.form.password.placeholder': '请输入密码', 'login.form.password.placeholder': '请输入密码',
'login.form.rememberPassword': '记住密码', 'login.form.rememberPassword': '记住密码',
'login.form.forgetPassword': '忘记密码', 'login.form.forgetPassword': '忘记密码',

View File

@ -131,7 +131,7 @@
mode="fileUpdateDesc" mode="fileUpdateDesc"
:title="t('project.fileManagement.desc')" :title="t('project.fileManagement.desc')"
:field-config="{ :field-config="{
field: detail.desc, field: detail.description,
placeholder: t('project.fileManagement.descPlaceholder'), placeholder: t('project.fileManagement.descPlaceholder'),
maxLength: 1000, maxLength: 1000,
isTextArea: true, isTextArea: true,

View File

@ -71,39 +71,38 @@
:title="t('system.authorized.authorityChecking')" :title="t('system.authorized.authorityChecking')"
:ok-text="t('system.authorized.authorization')" :ok-text="t('system.authorized.authorization')"
:ok-loading="drawerLoading" :ok-loading="drawerLoading"
:width="480" :width="680"
@confirm="confirmHandler" @confirm="confirmHandler"
@cancel="cancelHandler" @cancel="cancelHandler"
> >
<a-form ref="authFormRef" :model="authorizedForm" layout="vertical"> <a-form ref="authFormRef" :model="authorizedForm" layout="vertical">
<a-row class="grid-demo"> <a-form-item
<a-form-item :label="t('system.authorized.license')"
:label="t('system.authorized.license')" field="licenseCode"
field="licenseCode" asterisk-position="end"
asterisk-position="end" required
:validate-trigger="['input']"
:rules="[{ required: true, message: t('system.authorized.LicenseIsRequired') }]"
>
<MsUpload
v-model:file-list="fileList"
accept="none"
:is-limit="false"
:show-sub-text="false"
:show-file-list="false"
:auto-upload="false"
/>
<a-textarea
v-model="authorizedForm.licenseCode"
class="mt-4"
:placeholder="t('system.authorized.licenseCode')"
:auto-size="{
minRows: 3,
}"
:rules="[{ required: true, message: t('system.authorized.LicenseIsRequired') }]" :rules="[{ required: true, message: t('system.authorized.LicenseIsRequired') }]"
:validate-trigger="['input']" :max-length="1000"
> ></a-textarea>
<MsUpload </a-form-item>
v-model:file-list="fileList"
accept="none"
:is-limit="false"
:show-sub-text="false"
:show-file-list="false"
:auto-upload="false"
/>
<a-textarea
v-model="authorizedForm.licenseCode"
class="mt-4"
:placeholder="t('system.authorized.licenseCode')"
:auto-size="{
minRows: 3,
}"
:rules="[{ required: true, message: t('system.authorized.LicenseIsRequired') }]"
:max-length="1000"
></a-textarea>
</a-form-item>
</a-row>
</a-form> </a-form>
</MsDrawer> </MsDrawer>
</MsCard> </MsCard>
@ -260,7 +259,4 @@
@apply flex flex-col justify-between; @apply flex flex-col justify-between;
} }
} }
:deep(.ms-upload-area) {
width: 446px;
}
</style> </style>

View File

@ -373,7 +373,6 @@
/** /**
* @description 系统设置-资源池详情 * @description 系统设置-资源池详情
*/ */
import { computed, onBeforeMount, Ref, ref, watch, watchEffect } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { FormInstance, Message, SelectOptionData } from '@arco-design/web-vue'; import { FormInstance, Message, SelectOptionData } from '@arco-design/web-vue';
import { cloneDeep, isEmpty } from 'lodash-es'; import { cloneDeep, isEmpty } from 'lodash-es';
@ -467,6 +466,9 @@
orgOptions.value = await getSystemOrgOption(); orgOptions.value = await getSystemOrgOption();
if (!isXpack.value) { if (!isXpack.value) {
useList.value = useList.value.filter((item) => item.value === 'API'); useList.value = useList.value.filter((item) => item.value === 'API');
nextTick(() => {
setIsSave(true); //
});
} }
}); });
@ -489,6 +491,9 @@
orgIds: orgIdNameMap?.map((e) => e.id) || [], orgIds: orgIdNameMap?.map((e) => e.id) || [],
}, },
}; };
nextTick(() => {
setIsSave(true); //
});
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(error); console.log(error);