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
fileSaveAsApi?: (params: TransferFileParams) => Promise<string>; //
fileModuleOptionsApi?: (...args) => Promise<any>; //
fileModuleOptionsApi?: (...args: any[]) => Promise<any>; //
}>(),
{
mode: 'button',

View File

@ -108,7 +108,7 @@
</MsButton>
<template #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 :key="CommonScriptStatusEnum.PASSED" :value="CommonScriptStatusEnum.PASSED">
<commonScriptStatus :status="CommonScriptStatusEnum.PASSED" />

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -137,22 +137,49 @@
</a-button>
</template>
<!-- 接口调试支持快捷保存 -->
<a-button
<template
v-else-if="
requestVModel.isNew
? props.permissionMap && hasAnyPermission([props.permissionMap.create])
: props.permissionMap && hasAnyPermission([props.permissionMap.update])
"
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>
<!-- 接口调试-可保存或保存为新接口定义 -->
<a-dropdown-button
v-if="
props.permissionMap &&
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>
@ -412,6 +439,11 @@
</a-form-item>
</a-form>
</a-modal>
<saveAsApiModal
v-if="tempApiDetail"
v-model:visible="saveNewApiModalVisible"
:detail="tempApiDetail"
></saveAsApiModal>
<addDependencyDrawer
v-if="props.isDefinition"
v-model:visible="showAddDependencyDrawer"
@ -439,6 +471,7 @@
import setting from './setting.vue';
import apiMethodName from '@/views/api-test/components/apiMethodName.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 { getPluginScript, getProtocolList } from '@/api/modules/api-test/common';
@ -451,7 +484,7 @@
import { filterTree, getGenerateId, parseQueryParams } from '@/utils';
import { scrollIntoView } from '@/utils/dom';
import { registerCatchSaveShortcut, removeCatchSaveShortcut } from '@/utils/event';
import { hasAnyPermission } from '@/utils/permission';
import { hasAllPermission, hasAnyPermission } from '@/utils/permission';
import {
ExecuteApiRequestFullParams,
@ -462,7 +495,6 @@
} from '@/models/apiTest/common';
import { AddApiCaseParams } from '@/models/apiTest/management';
import { ModuleTreeNode, TransferFileParams } from '@/models/common';
import { EnvConfig } from '@/models/projectManagement/environmental';
import {
RequestAuthType,
RequestBodyFormat,
@ -542,6 +574,7 @@
execute: string;
create: string;
update: string;
saveASApi?: string;
};
}>();
const emit = defineEmits(['addDone', 'execute']);
@ -846,7 +879,7 @@
initPluginScript();
} else {
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
requestVModel.value.method = RequestMethods.GET;
}
@ -1562,12 +1595,25 @@
const apiBaseFormRef = ref<InstanceType<typeof apiBaseForm>>();
const isUrlError = ref(false);
const tempApiDetail = ref<RequestParam>();
const saveNewApiModalVisible = ref(false);
function handleSelect(value: string | number | Record<string, any> | undefined) {
if (requestVModel.value.url === '' && requestVModel.value.protocol === 'HTTP') {
isUrlError.value = true;
return;
}
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) => {
if (errors) {
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',
update: 'PROJECT_API_DEBUG:READ+UPDATE',
create: 'PROJECT_API_DEBUG:READ+ADD',
saveASApi: 'PROJECT_API_DEFINITION:READ+ADD',
}"
@add-done="handleDebugAddDone"
/>
@ -91,6 +92,64 @@
</MsCodeEditor>
</div>
</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>
<script lang="ts" setup>
@ -107,6 +166,7 @@
import MsEditableTab from '@/components/pure/ms-editable-tab/index.vue';
import { TabItem } from '@/components/pure/ms-editable-tab/types';
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 apiMethodName from '@/views/api-test/components/apiMethodName.vue';
import debug, { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
@ -121,8 +181,10 @@
updateDebug,
uploadTempFile,
} from '@/api/modules/api-test/debug';
import { getModuleTreeOnlyModules } from '@/api/modules/api-test/management';
import { useI18n } from '@/hooks/useI18n';
import useLeaveTabUnSaveCheck from '@/hooks/useLeaveTabUnSaveCheck';
import useAppStore from '@/store/modules/app';
import { parseCurlScript } from '@/utils';
import { hasAnyPermission } from '@/utils/permission';
@ -138,7 +200,9 @@
import { defaultBodyParams, defaultResponse } from '../components/config';
import { parseRequestBodyFiles } from '../components/utils';
import type { FormInstance } from '@arco-design/web-vue';
const appStore = useAppStore();
const route = useRoute();
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(() => {
if (route.query.id) {
openApiTab(route.query.id as string);

View File

@ -205,4 +205,5 @@ export default {
'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',
'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.extractValueTitleTip': '输入按列存储中的列名和对应的数值如提取name列的第一个值则输入name_1',
'apiTestDebug.responseRepeatMessage': '名称重复,请重新输入',
'apiTestDebug.saveAsApi': '另存为接口',
};

View File

@ -143,7 +143,7 @@
@change="handleUrlChange"
>
<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>
</a-input>
</a-input-group>
@ -390,13 +390,19 @@
import { scrollIntoView } from '@/utils/dom';
import {
EnableKeyValueParam,
ExecuteApiRequestFullParams,
ExecuteBody,
ExecuteConditionConfig,
ExecutePluginRequestParams,
ExecuteRequestCommonParam,
ExecuteRequestFormBody,
PluginConfig,
RequestResult,
RequestTaskResult,
} from '@/models/apiTest/common';
import { ScenarioStepFileParams, ScenarioStepItem } from '@/models/apiTest/scenario';
import type { EnvConfig } from '@/models/projectManagement/environmental';
import {
RequestAuthType,
RequestBodyFormat,
@ -429,7 +435,8 @@
// );
export interface RequestCustomAttr {
type: 'api';
type?: 'api';
label: string;
name: string;
stepId: string | number; // id
resourceId: string | number; // id
@ -450,7 +457,7 @@
};
}
export type RequestParam = ExecuteApiRequestFullParams & {
export type RequestParam = (ExecuteApiRequestFullParams | ExecutePluginRequestParams) & {
response?: RequestTaskResult;
customizeRequest?: boolean;
customizeRequestEnvEnable?: boolean;
@ -489,6 +496,7 @@
const loading = defineModel<boolean>('detailLoading', { default: false });
const defaultApiParams: RequestParam = {
label: '',
name: '',
type: 'api',
stepId: '',
@ -824,7 +832,7 @@
nextTick(() => {
if (fApi.value) {
fApi.value.nextRefresh(() => {
const form = {};
const form: Record<string, any> = {};
controlPluginFormFields().forEach((key) => {
form[key] = formData[key];
});
@ -891,7 +899,7 @@
initPluginScript();
} else {
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
requestVModel.value.method = RequestMethods.GET;
}
@ -1082,12 +1090,12 @@
async function execute(executeType?: 'localExec' | 'serverExec') {
requestVModel.value.executeLoading = true;
if (isHttpProtocol.value) {
emit('execute', makeRequestParams(executeType), executeType);
emit('execute', makeRequestParams(executeType) as RequestParam, executeType);
} else {
//
fApi.value?.validate(async (valid) => {
if (valid === true) {
emit('execute', makeRequestParams(executeType), executeType);
emit('execute', makeRequestParams(executeType) as RequestParam, executeType);
} else {
requestVModel.value.activeTab = RequestComposition.PLUGIN;
nextTick(() => {
@ -1103,7 +1111,7 @@
emit('stopDebug');
}
function initErrorMessageInfoItem(key) {
function initErrorMessageInfoItem(key: string) {
if (requestVModel.value.errorMessageInfo && !requestVModel.value.errorMessageInfo[key]) {
requestVModel.value.errorMessageInfo[key] = {};
}
@ -1181,7 +1189,7 @@
showMessage();
return;
}
emit('addStep', cloneDeep(makeRequestParams()));
emit('addStep', cloneDeep(makeRequestParams()) as RequestParam);
}
function handleSave() {
@ -1202,7 +1210,7 @@
function handleClose() {
// applyStep
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) {
// api
// queryrest
['headers', 'query', 'rest'].forEach((type) => {
props.request?.[type]?.forEach((item) => {
if (!item.key.length) return;
const index = requestVModel.value[type]?.findIndex((itemReq) => itemReq.key === item.key);
if (index > -1) {
requestVModel.value[type][index].value = item.value;
(['headers', 'query', 'rest'] as (keyof RequestParam)[]).forEach((type: keyof RequestParam) => {
(props.request?.[type] as (EnableKeyValueParam | ExecuteRequestCommonParam)[])?.forEach(
(item: EnableKeyValueParam) => {
if (!item.key.length) return;
const index = (
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) => {
props.request?.body?.[type].formValues.forEach((item) => {
(['formDataBody', 'wwwFormBody'] as (keyof ExecuteBody)[]).forEach((type: keyof ExecuteBody) => {
(props.request?.body?.[type] as ExecuteRequestFormBody).formValues.forEach((item) => {
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) {
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 class="flex items-center gap-[16px] p-[16px] pb-[8px]">
<a-input
v-if="activeStep?.stepType && _stepType.isQuoteCase"
v-if="_stepType.isQuoteCase || activeStep?.isQuoteScenarioStep"
v-model:model-value="requestVModel.name"
:max-length="255"
:show-word-limit="isEditableApi"
@ -365,6 +365,7 @@
const hasLocalExec = inject<Ref<boolean>>('hasLocalExec');
const defaultApiParams: RequestParam = {
label: '',
name: '',
type: 'api',
stepId: '',

View File

@ -389,69 +389,11 @@
</div>
</template>
</a-modal>
<a-modal
<saveAsApiModal
v-if="tempApiDetail"
v-model:visible="saveNewApiModalVisible"
:title="t('common.save')"
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="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>
:detail="tempApiDetail"
></saveAsApiModal>
<a-modal
v-model:visible="saveCaseModalVisible"
:title="t('apiTestManagement.saveAsCase')"
@ -519,14 +461,10 @@
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 saveAsApiModal from '@/views/api-test/components/saveAsApiModal.vue';
import { localExecuteApiDebug } from '@/api/modules/api-test/common';
import {
addCase,
addDefinition,
getDefinitionDetail,
getModuleTreeOnlyModules,
} from '@/api/modules/api-test/management';
import { addCase, getDefinitionDetail } from '@/api/modules/api-test/management';
import { debugScenario, getScenarioDetail, getScenarioStep } from '@/api/modules/api-test/scenario';
import { getSocket } from '@/api/modules/project-management/commonScript';
import { useI18n } from '@/hooks/useI18n';
@ -547,6 +485,7 @@
ExecuteApiRequestFullParams,
ExecuteConditionProcessor,
ExecutePluginRequestParams,
RequestResult,
} from '@/models/apiTest/common';
import { AddApiCaseParams } from '@/models/apiTest/management';
import {
@ -554,14 +493,13 @@
CreateStepAction,
Scenario,
ScenarioStepConfig,
ScenarioStepDetail,
ScenarioStepDetails,
ScenarioStepFileParams,
ScenarioStepItem,
} from '@/models/apiTest/scenario';
import { EnvConfig } from '@/models/projectManagement/environmental';
import {
RequestCaseStatus,
RequestDefinitionStatus,
ScenarioAddStepActionType,
ScenarioExecuteStatus,
ScenarioStepRefType,
@ -571,7 +509,7 @@
import type { RequestParam } from '../common/customApiDrawer.vue';
import updateStepStatus from '../utils';
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 getStepType from '@/views/api-test/scenario/components/common/stepType/utils';
import { defaultStepItemCommon } from '@/views/api-test/scenario/components/config';
@ -899,11 +837,13 @@
stepDetails.value[step.id] = {
...res,
stepId: step.id,
protocol: step.config.protocol,
method: step.config.method,
protocol: step.config.protocol || '',
method: step.config.method || '',
...parseRequestBodyResult,
};
scenario.value.stepFileParam[step.id] = {
...parseRequestBodyResult,
};
scenario.value.stepFileParam[step.id] = parseRequestBodyResult;
} catch (error) {
// eslint-disable-next-line no-console
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 saveModalForm = ref({
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 tempApiDetail = ref<RequestParam>();
const saveCaseModalVisible = ref(false);
const saveCaseLoading = ref(false);
@ -1209,8 +1033,13 @@
break;
case 'saveAsApi':
activeStep.value = node as ScenarioStepItem;
initApiModuleTree((stepDetails.value[node.id] as RequestParam)?.protocol);
saveModalForm.value.path = (stepDetails.value[node.id] as RequestParam)?.url;
const detail = stepDetails.value[activeStep.value.id] as RequestParam;
const fileParams = scenario.value.stepFileParam[activeStep.value.id];
tempApiDetail.value = {
...detail,
uploadFileIds: fileParams?.uploadFileIds || [],
linkFileIds: fileParams?.linkFileIds || [],
};
saveNewApiModalVisible.value = true;
break;
case 'saveAsCase':
@ -1284,7 +1113,7 @@
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');
if (realStep) {
Object.keys($event).forEach((key) => {
@ -1399,7 +1228,7 @@
if (data.msgType === 'EXEC_RESULT') {
if (step.reportId === data.reportId) {
// tabtab
data.taskResult.requestResults.forEach((result) => {
data.taskResult.requestResults.forEach((result: RequestResult) => {
if (_scenario.stepResponses[result.stepId] === undefined) {
_scenario.stepResponses[result.stepId] = [];
}
@ -1466,7 +1295,7 @@
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, node.uniqueId, 'uniqueId');
if (realStep) {
realStep.reportId = getGenerateId();
const _stepDetails = {};
const _stepDetails: Record<string, any> = {};
const stepFileParam = scenario.value.stepFileParam[realStep.id];
traverseTree(
realStep,
@ -1579,7 +1408,9 @@
//
if (realStep.parent?.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);
} else {
//
@ -1930,10 +1761,10 @@
}
const showQuickInput = ref(false);
const quickInputParamValue = ref('');
const quickInputParamValue = ref<any>('');
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');
if (realStep) {
activeStep.value = realStep as ScenarioStepItem;

View File

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

View File

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

View File

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

View File

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

View File

@ -71,39 +71,38 @@
:title="t('system.authorized.authorityChecking')"
:ok-text="t('system.authorized.authorization')"
:ok-loading="drawerLoading"
:width="480"
:width="680"
@confirm="confirmHandler"
@cancel="cancelHandler"
>
<a-form ref="authFormRef" :model="authorizedForm" layout="vertical">
<a-row class="grid-demo">
<a-form-item
:label="t('system.authorized.license')"
field="licenseCode"
asterisk-position="end"
<a-form-item
:label="t('system.authorized.license')"
field="licenseCode"
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') }]"
:validate-trigger="['input']"
>
<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') }]"
:max-length="1000"
></a-textarea>
</a-form-item>
</a-row>
:max-length="1000"
></a-textarea>
</a-form-item>
</a-form>
</MsDrawer>
</MsCard>
@ -260,7 +259,4 @@
@apply flex flex-col justify-between;
}
}
:deep(.ms-upload-area) {
width: 446px;
}
</style>

View File

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