feat(接口测试): 导入导出

This commit is contained in:
baiqi 2024-09-02 15:33:30 +08:00 committed by Craftsman
parent 1ea2d56b07
commit 6c82da4de5
19 changed files with 1907 additions and 4514 deletions

View File

@ -107,8 +107,8 @@ import {
ApiCaseExecuteHistoryParams,
ApiCasePageParams,
ApiDefinitionBatchDeleteParams,
type ApiDefinitionBatchExportParams,
ApiDefinitionBatchMoveParams,
ApiDefinitionBatchParams,
ApiDefinitionBatchUpdateParams,
ApiDefinitionCreateParams,
ApiDefinitionDeleteParams,
@ -593,6 +593,6 @@ export function getCaseReportDetail(reportId: string, stepId: string) {
}
// 导出定义
export function exportApiDefinition(data: ApiDefinitionBatchParams, type: string) {
export function exportApiDefinition(data: ApiDefinitionBatchExportParams, type: string) {
return MSR.post({ url: `${ExportDefinitionUrl}/${type}`, data });
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 519 KiB

After

Width:  |  Height:  |  Size: 267 KiB

View File

@ -129,6 +129,7 @@ export interface MsTableProps<T> {
paginationSize?: 'small' | 'mini' | 'medium' | 'large';
// 行选择器禁用配置
rowSelectionDisabledConfig?: MsTableRowSelectionDisabledConfig;
sorter?: Record<string, any>; // 排序
[key: string]: any;
}

View File

@ -399,6 +399,7 @@ export default function useTableProps<T>(
sortItem.value = sortObj;
setTableDraggable(Object.keys(sortItem.value).length === 0);
loadList();
propsRes.value.sorter = sortObj;
},
// 筛选触发
@ -452,6 +453,7 @@ export default function useTableProps<T>(
// 重置排序
resetSort: () => {
sortItem.value = {};
propsRes.value.sorter = {};
},
// 重置筛选
clearSelector: () => {

View File

@ -76,6 +76,14 @@ export const FileIconMap: FileIconMapping = {
[UploadStatus.init]: 'icon-a-icon_file-json',
[UploadStatus.done]: 'icon-a-icon_file-json',
},
jmx: {
[UploadStatus.init]: 'icon-a-icon_file-JMX',
[UploadStatus.done]: 'icon-a-icon_file-JMX',
},
har: {
[UploadStatus.init]: 'icon-icon_file_har',
[UploadStatus.done]: 'icon-icon_file_har',
},
};
/**

View File

@ -80,11 +80,10 @@ export enum ApiScenarioStatus {
// 接口导入支持格式
export enum RequestImportFormat {
SWAGGER = 'Swagger3',
// MeterSphere = 'MeterSphere',
// Postman= 'Postman',
// Plugin = 'Plugin',
// Jmeter = 'Jmeter',
// Har = 'Har',
MeterSphere = 'MeterSphere',
Postman = 'Postman',
Jmeter = 'Jmeter',
Har = 'Har',
}
// 接口导入方式
export enum RequestImportType {

View File

@ -16,6 +16,8 @@ export enum UploadAcceptEnum {
none = 'none',
unknown = 'unknown',
json = '.json',
jmx = '.jmx',
har = '.har',
}
export enum UploadStatus {

View File

@ -183,6 +183,12 @@ export interface mockParams {
export interface ApiDefinitionBatchParams extends BatchApiParams {
protocols: string[];
}
// 批量导出定义参数
export interface ApiDefinitionBatchExportParams extends ApiDefinitionBatchParams {
exportApiCase: boolean;
exportApiMock: boolean;
sort: Record<string, any>;
}
// 批量更新定义参数
export interface ApiDefinitionBatchUpdateParams extends ApiDefinitionBatchParams {
type?: string;

View File

@ -2,42 +2,34 @@
<div>
<MsDrawer
v-model:visible="visible"
width="100%"
:popup-container="props.popupContainer"
:width="960"
:title="t('apiTestManagement.importApi')"
:closable="false"
:ok-disabled="disabledConfirm"
:ok-text="t('common.import')"
:ok-loading="importLoading"
disabled-width-drag
desc
no-title
@confirm="confirmImport"
@cancel="cancelImport"
>
<div class="flex items-center justify-between p-[12px_8px]">
<div class="font-medium text-[var(--color-text-1)]">{{ t('apiTestManagement.importApi') }}</div>
<a-radio-group v-model:model-value="importForm.type" type="button">
<a-radio :value="RequestImportType.API">{{ t('apiTestManagement.fileImport') }}</a-radio>
<a-radio :value="RequestImportType.SCHEDULE">{{ t('apiTestManagement.timeImport') }}</a-radio>
</a-radio-group>
</div>
<div
v-if="importType === 'file'"
class="my-[16px] flex items-center gap-[16px] rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] p-[16px]"
>
<div v-if="importType === 'file'" class="mb-[16px] flex items-center gap-[16px]">
<div
v-for="item of platformList"
:key="item.value"
:class="`import-item ${importForm.platform === item.value ? 'import-item--active' : ''}`"
@click="() => setActiveImportFormat(item.value)"
>
<div class="flex h-[24px] w-[24px] items-center justify-center rounded-[var(--border-radius-small)] bg-white">
<MsIcon :type="item.icon" :class="`text-[${item.iconColor}]`" :size="18" />
</div>
<div class="text-[var(--color-text-1)]">{{ item.name }}</div>
</div>
</div>
<a-form ref="importFormRef" :model="importForm" layout="vertical">
<a-form-item :label="t('apiTestManagement.importType')">
<a-radio-group v-model:model-value="importForm.type" type="button">
<a-radio :value="RequestImportType.API">{{ t('apiTestManagement.fileImport') }}</a-radio>
<a-radio :value="RequestImportType.SCHEDULE">{{ t('apiTestManagement.timeImport') }}</a-radio>
</a-radio-group>
</a-form-item>
<template v-if="importForm.type === RequestImportType.API">
<a-form-item :label="t('apiTestManagement.belongModule')">
<a-tree-select
@ -57,56 +49,58 @@
</template>
</a-tree-select>
</a-form-item>
<a-form-item>
<template #label>
<div class="flex items-center gap-[2px]">
{{ t('apiTestManagement.importMode') }}
<a-tooltip position="right">
<icon-question-circle
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
size="16"
/>
<template #content>
<div>{{ t('apiTestManagement.importModeTip1') }}</div>
<div>{{ t('apiTestManagement.importModeTip2') }}</div>
<div>{{ t('apiTestManagement.importModeTip3') }}</div>
<div>{{ t('apiTestManagement.importModeTip4') }}</div>
<div class="h-[22px] w-full"></div>
<div>{{ t('apiTestManagement.importModeTip5') }}</div>
<div>{{ t('apiTestManagement.importModeTip6') }}</div>
<div>{{ t('apiTestManagement.importModeTip7') }}</div>
</template>
</a-tooltip>
</div>
</template>
<a-select v-model:model-value="importForm.coverData" class="w-[240px]">
<a-option :value="true">{{ t('apiTestManagement.cover') }}</a-option>
<a-option :value="false">{{ t('apiTestManagement.uncover') }}</a-option>
</a-select>
<a-form-item :label="t('apiTestManagement.importMode')">
<a-radio-group v-model:model-value="importForm.coverData">
<a-radio :value="true">
<div class="flex items-center gap-[2px]">
{{ t('apiTestManagement.cover') }}
<a-tooltip position="right">
<icon-question-circle
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
size="16"
/>
<template #content>
<div>{{ t('apiTestManagement.importModeTip1') }}</div>
<div>{{ t('apiTestManagement.importModeTip2') }}</div>
<div>{{ t('apiTestManagement.importModeTip3') }}</div>
<div>{{ t('apiTestManagement.importModeTip4') }}</div>
</template>
</a-tooltip>
</div>
</a-radio>
<a-radio :value="false">
<div class="flex items-center gap-[2px]">
{{ t('apiTestManagement.uncover') }}
<a-tooltip position="right">
<icon-question-circle
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
size="16"
/>
<template #content>
<div>{{ t('apiTestManagement.importModeTip5') }}</div>
<div>{{ t('apiTestManagement.importModeTip6') }}</div>
<div>{{ t('apiTestManagement.importModeTip7') }}</div>
</template>
</a-tooltip>
</div>
</a-radio>
</a-radio-group>
</a-form-item>
<a-collapse v-model:active-key="moreSettingActive" :bordered="false" :show-expand-icon="false">
<a-collapse-item :key="1">
<template #header>
<MsButton
type="text"
@click="() => (moreSettingActive.length > 0 ? (moreSettingActive = []) : (moreSettingActive = [1]))"
>
{{ t('apiTestDebug.moreSetting') }}
<icon-down v-if="moreSettingActive.length > 0" class="text-rgb(var(--primary-5))" />
<icon-right v-else class="text-rgb(var(--primary-5))" />
</MsButton>
</template>
<div class="mt-[16px]">
<a-checkbox v-model:model-value="importForm.syncCase" class="mr-[24px]">
{{ t('apiTestManagement.syncImportCase') }}
</a-checkbox>
<a-checkbox v-if="importForm.coverData" v-model:model-value="importForm.coverModule">
{{ t('apiTestManagement.syncUpdateDirectory') }}
</a-checkbox>
</div>
</a-collapse-item>
</a-collapse>
<a-form-item :label="t('apiTestManagement.importType')" class="mt-[8px]">
<div v-if="importForm.coverData" class="mb-[16px] flex items-center gap-[4px]">
<a-switch v-model:model-value="importForm.coverModule" size="small" />
{{ t('apiTestManagement.syncUpdateDirectory') }}
</div>
<div
v-if="[RequestImportFormat.MeterSphere, RequestImportFormat.Postman].includes(importForm.platform)"
class="mb-[16px] flex items-center gap-[4px]"
>
<a-switch v-model:model-value="importForm.syncCase" size="small" />
{{ t('apiTestManagement.syncImportCase') }}
</div>
<a-form-item
v-if="importForm.platform === RequestImportFormat.SWAGGER"
:label="t('apiTestManagement.importMethod')"
>
<a-radio-group v-model:model-value="importType" type="button">
<a-radio value="file">{{ t('apiTestManagement.fileImport') }}</a-radio>
<a-radio value="swaggerUrl">{{ t('apiTestManagement.urlImport') }}</a-radio>
@ -115,20 +109,23 @@
<MsUpload
v-if="importType === 'file'"
v-model:file-list="fileList"
accept="json"
:accept="uploadAccept"
:auto-upload="false"
draggable
size-unit="MB"
class="w-full"
>
<template #subText>
<div class="flex">
<div v-if="importForm.platform === RequestImportFormat.SWAGGER" class="flex">
{{ t('apiTestManagement.importSwaggerFileTip1') }}
<span class="text-[rgb(var(--warning-6))]" @click.stop="openLink">{{
t('apiTestManagement.importSwaggerFileTip2')
}}</span>
<span class="text-[rgb(var(--warning-6))]" @click.stop="openLink">
{{ t('apiTestManagement.importSwaggerFileTip2') }}
</span>
{{ t('apiTestManagement.importSwaggerFileTip3') }}
</div>
<div v-else-if="importForm.platform === RequestImportFormat.Postman" class="flex">
{{ t('apiTestManagement.importPostmanFileTip') }}
</div>
</template>
</MsUpload>
<template v-else>
@ -329,7 +326,6 @@
import MsButton from '@/components/pure/ms-button/index.vue';
import MsCronSelect from '@/components/pure/ms-cron-select/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import type { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
@ -356,7 +352,6 @@
const props = defineProps<{
visible: boolean;
moduleTree: ModuleTreeNode[];
popupContainer?: string;
activeModule: string;
}>();
const emit = defineEmits(['update:visible', 'done']);
@ -372,8 +367,22 @@
{
name: 'Swagger',
value: RequestImportFormat.SWAGGER,
icon: 'icon-icon_swagger',
iconColor: 'rgb(var(--success-7))',
},
{
name: 'Postman',
value: RequestImportFormat.Postman,
},
{
name: 'Har',
value: RequestImportFormat.Har,
},
{
name: 'Jmeter',
value: RequestImportFormat.Jmeter,
},
{
name: 'MeterSphere',
value: RequestImportFormat.MeterSphere,
},
];
const fileList = ref<MsFileItem[]>([]);
@ -395,6 +404,18 @@
};
const importForm = ref({ ...defaultForm });
const importFormRef = ref<FormInstance>();
const uploadAccept = computed(() => {
if ([RequestImportFormat.SWAGGER, RequestImportFormat.Postman].includes(importForm.value.platform)) {
return 'json';
}
if (importForm.value.platform === RequestImportFormat.Har) {
return 'har';
}
if (importForm.value.platform === RequestImportFormat.Jmeter) {
return 'jmx';
}
return 'json';
});
watch(
() => visible.value,
@ -426,6 +447,9 @@
function setActiveImportFormat(format: RequestImportFormat) {
importForm.value.platform = format;
if (format !== RequestImportFormat.SWAGGER) {
importType.value = 'file';
}
}
function cancelImport() {
@ -647,9 +671,10 @@
@apply flex cursor-pointer items-center bg-white;
padding: 8px;
gap: 6px;
width: 200px;
border: 1px solid var(--color-text-n8);
border-radius: var(--border-radius-small);
gap: 6px;
}
.import-item--active {
border: 1px solid rgb(var(--primary-5));

View File

@ -252,6 +252,42 @@
@folder-node-select="folderNodeSelect"
/>
</a-modal>
<a-modal
v-model:visible="showExportModal"
:title="t('common.export')"
title-align="start"
class="ms-modal-upload ms-modal-medium"
:width="400"
>
<div class="mb-[16px] flex gap-[8px]">
<div
v-for="item of platformList"
:key="item.value"
:class="`import-item ${exportPlatform === item.value ? 'import-item--active' : ''}`"
@click="exportPlatform = item.value"
>
<div class="text-[var(--color-text-1)]">{{ item.name }}</div>
</div>
</div>
<div class="mb-[16px] flex items-center gap-[4px]">
<a-switch v-model:model-value="exportApiCase" size="small" />
{{ t('apiTestManagement.exportCase') }}
</div>
<div class="flex items-center gap-[4px]">
<a-switch v-model:model-value="exportApiMock" size="small" />
{{ t('apiTestManagement.exportMock') }}
</div>
<template #footer>
<div class="flex justify-end">
<a-button type="secondary" :disabled="exportLoading" @click="cancelExport">
{{ t('common.cancel') }}
</a-button>
<a-button class="ml-3" type="primary" :loading="exportLoading" @click="exportApi">
{{ t('common.export') }}
</a-button>
</div>
</template>
</a-modal>
</template>
<script setup lang="ts">
@ -294,7 +330,7 @@
import { ProtocolItem } from '@/models/apiTest/common';
import { ApiDefinitionDetail, ApiDefinitionGetModuleParams } from '@/models/apiTest/management';
import { DragSortParams } from '@/models/common';
import { RequestDefinitionStatus, RequestMethods } from '@/enums/apiEnum';
import { RequestDefinitionStatus, RequestImportFormat, RequestMethods } from '@/enums/apiEnum';
import { CacheTabTypeEnum } from '@/enums/cacheTabEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum';
@ -385,7 +421,7 @@
};
});
});
const apiTableRef = ref();
let columns: MsTableColumn = [
{
title: 'ID',
@ -568,12 +604,6 @@
{
label: 'common.export',
eventTag: 'export',
children: [
{
label: 'apiTestManagement.swagger.export',
eventTag: 'exportSwagger',
},
],
permission: ['PROJECT_API_DEFINITION:READ+EXPORT'],
},
{
@ -939,27 +969,60 @@
selectedModuleKeys.value = keys;
}
const showExportModal = ref(false);
const platformList = [
{
name: 'Swagger',
value: RequestImportFormat.SWAGGER,
},
{
name: 'MeterSphere',
value: RequestImportFormat.MeterSphere,
},
];
const exportPlatform = ref(RequestImportFormat.SWAGGER);
const exportApiCase = ref(false);
const exportApiMock = ref(false);
const exportLoading = ref(false);
function cancelExport() {
showExportModal.value = false;
exportPlatform.value = RequestImportFormat.SWAGGER;
}
/**
* 导出接口
*/
async function exportApi(type: string, record?: ApiDefinitionDetail, params?: BatchActionQueryParams) {
const result = await exportApiDefinition(
{
selectIds: tableSelected.value as string[],
selectAll: !!params?.selectAll,
excludeIds: params?.excludeIds || [],
condition: {
keyword: keyword.value,
filter: propsRes.value.filter,
async function exportApi() {
try {
exportLoading.value = true;
const result = await exportApiDefinition(
{
selectIds: tableSelected.value as string[],
selectAll: !!batchParams.value?.selectAll,
excludeIds: batchParams.value?.excludeIds || [],
condition: {
keyword: keyword.value,
filter: propsRes.value.filter,
},
projectId: appStore.currentProjectId,
moduleIds: await getModuleIds(),
protocols: props.selectedProtocols,
exportApiCase: exportApiCase.value,
exportApiMock: exportApiMock.value,
sort: propsRes.value.sorter || {},
},
projectId: appStore.currentProjectId,
moduleIds: await getModuleIds(),
protocols: props.selectedProtocols,
},
type
);
const res = await getProjectInfo(appStore.currentProjectId);
downloadByteFile(new Blob([JSON.stringify(result)]), `Swagger_Api_${res.name}.json`);
exportPlatform.value
);
const res = await getProjectInfo(appStore.currentProjectId);
downloadByteFile(new Blob([JSON.stringify(result)]), `Swagger_Api_${res.name}.json`);
showExportModal.value = false;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
exportLoading.value = false;
}
}
/**
@ -970,8 +1033,8 @@
tableSelected.value = params?.selectedIds || [];
batchParams.value = params;
switch (event.eventTag) {
case 'exportSwagger':
exportApi('swagger', undefined, params);
case 'export':
showExportModal.value = true;
break;
case 'delete':
deleteApi(undefined, true, params);
@ -1020,7 +1083,6 @@
}
}
const apiTableRef = ref();
watch(
() => requestMethodsOptions.value,
() => {
@ -1030,6 +1092,19 @@
</script>
<style lang="less" scoped>
.import-item {
@apply flex cursor-pointer items-center bg-white;
padding: 8px;
width: 150px;
border: 1px solid var(--color-text-n8);
border-radius: var(--border-radius-small);
gap: 6px;
}
.import-item--active {
border: 1px solid rgb(var(--primary-5));
background-color: rgb(var(--primary-1));
}
:deep(.param-input:not(.arco-input-focus, .arco-select-view-focus)) {
&:not(:hover) {
border-color: transparent !important;

View File

@ -1,48 +1,30 @@
<template>
<div>
<template v-if="!props.isModal">
<div class="mb-[8px] flex items-center gap-[8px]">
<a-input
v-model:model-value="moduleKeyword"
:placeholder="props.isModal ? t('apiTestManagement.moveSearchTip') : t('apiTestManagement.searchTip')"
allow-clear
/>
<template v-if="!props.readOnly && !props.trash">
<a-dropdown-button
v-if="hasAllPermission(['PROJECT_API_DEFINITION:READ+ADD', 'PROJECT_API_DEFINITION:READ+IMPORT'])"
type="primary"
@click="handleSelect('newApi')"
>
{{ t('common.newCreate') }}
<template #icon>
<icon-down />
</template>
<template #content>
<a-doption value="import" @click="handleSelect('import')">
{{ t('apiTestManagement.importApi') }}
</a-doption>
</template>
</a-dropdown-button>
<a-button
v-else-if="
!hasAnyPermission(['PROJECT_API_DEFINITION:READ+ADD']) &&
hasAnyPermission(['PROJECT_API_DEFINITION:READ+IMPORT'])
"
type="primary"
@click="handleSelect('import')"
>
{{ t('apiTestManagement.importApi') }}
</a-button>
<a-button
v-else
v-permission="['PROJECT_API_DEFINITION:READ+ADD']"
type="primary"
@click="handleSelect('newApi')"
>
{{ t('apiTestManagement.newApi') }}
</a-button>
</template>
<div v-if="!props.readOnly && !props.trash" class="mb-[8px] flex items-center gap-[8px]">
<a-button
v-permission="['PROJECT_API_DEFINITION:READ+ADD']"
type="primary"
long
@click="handleSelect('newApi')"
>
{{ t('apiTestManagement.newApi') }}
</a-button>
<a-button
v-permission="['PROJECT_API_DEFINITION:READ+IMPORT']"
type="outline"
long
@click="handleSelect('import')"
>
{{ t('apiTestManagement.importApi') }}
</a-button>
</div>
<a-input
v-model:model-value="moduleKeyword"
:placeholder="props.isModal ? t('apiTestManagement.moveSearchTip') : t('apiTestManagement.searchTip')"
class="mb-[8px]"
allow-clear
/>
<TreeFolderAll
v-if="!props.readOnly"
ref="treeFolderAllRef"
@ -193,7 +175,7 @@
import useAppStore from '@/store/modules/app';
import { characterLimit, mapTree } from '@/utils';
import { getLocalStorage } from '@/utils/local-storage';
import { hasAllPermission, hasAnyPermission } from '@/utils/permission';
import { hasAnyPermission } from '@/utils/permission';
import { ApiDefinitionGetModuleParams } from '@/models/apiTest/management';
import { ModuleTreeNode } from '@/models/common';

View File

@ -79,14 +79,17 @@ export default {
'apiTestManagement.cover': 'Cover',
'apiTestManagement.uncover': 'Do Not Cover',
'apiTestManagement.moreSetting': 'More Settings',
'apiTestManagement.importType': 'Import Type',
'apiTestManagement.importMethod': 'Import Method',
'apiTestManagement.urlImport': 'URL Import',
'apiTestManagement.swagger.export': 'Export Swagger3.0(Only supports HTTP protocol)',
'apiTestManagement.exportCase': 'Synchronous export use case',
'apiTestManagement.exportMock': 'Export Mock Synchronously',
'apiTestManagement.syncImportCase': 'Sync Import API Cases',
'apiTestManagement.syncUpdateDirectory': 'Sync Update API Directory',
'apiTestManagement.importSwaggerFileTip1': 'Supports Swagger 3.0 version JSON files,',
'apiTestManagement.importSwaggerFileTip2': '2.0 files can be converted to 3.0 on the official website',
'apiTestManagement.importSwaggerFileTip3': 'with a size limit of 50MB',
'apiTestManagement.importPostmanFileTip':
'Postman only supports file import, and only supports json files in v2.1 format',
'apiTestManagement.urlImportPlaceholder': 'Please enter OpenAPI/URL',
'apiTestManagement.swaggerURLRequired': 'Swagger URL cannot be empty',
'apiTestManagement.basicAuth': 'Basic Auth',

View File

@ -1,6 +1,7 @@
export default {
'apiTestManagement.newApi': '新建请求',
'apiTestManagement.newApi': '新建接口',
'apiTestManagement.importApi': '导入接口',
'apiTestManagement.importType': '导入类型',
'apiTestManagement.fileImport': '文件导入',
'apiTestManagement.timeImport': '定时导入',
'apiTestManagement.timeTask': '定时任务',
@ -74,14 +75,16 @@ export default {
'apiTestManagement.cover': '覆盖',
'apiTestManagement.uncover': '不覆盖',
'apiTestManagement.moreSetting': '更多设置',
'apiTestManagement.importType': '导入方式',
'apiTestManagement.importMethod': '导入方式',
'apiTestManagement.urlImport': 'URL 导入',
'apiTestManagement.swagger.export': '导出 Swagger3.0 格式(仅支持HTTP协议)',
'apiTestManagement.exportCase': '同步导出用例',
'apiTestManagement.exportMock': '同步导出 Mock',
'apiTestManagement.syncImportCase': '同步导入接口用例',
'apiTestManagement.syncUpdateDirectory': '同步更新接口所在目录',
'apiTestManagement.importSwaggerFileTip1': '支持 Swagger 3.0 版本的 json 文件,',
'apiTestManagement.importSwaggerFileTip2': '2.0 文件可以在官网一键转换 3.0',
'apiTestManagement.importSwaggerFileTip3': ',大小不超过 50M',
'apiTestManagement.importPostmanFileTip': 'Postman仅支持文件导入且只支持v2.1格式的json文件',
'apiTestManagement.urlImportPlaceholder': '请输入OpenAPI/URL',
'apiTestManagement.swaggerURLRequired': 'SwaggerURL 不能为空',
'apiTestManagement.basicAuth': 'Basic Auth 认证',