feat(接口场景): 场景大框架&接口定义列表执行

This commit is contained in:
baiqi 2024-03-13 17:56:00 +08:00 committed by 刘瑞斌
parent f8343ea765
commit 1c86838b8b
25 changed files with 463 additions and 90 deletions

View File

@ -32,7 +32,12 @@
</slot>
</div>
</div>
<MsButton type="text" class="more-btn" @click="toggleExpand">
<MsButton
v-if="props.simpleShowCount !== undefined && props.simpleShowCount > 0"
type="text"
class="more-btn"
@click="toggleExpand"
>
<div v-if="isExpand" class="flex items-center gap-[4px]">
{{ t('msDetailCard.collapse') }}
<icon-up :size="14" />

View File

@ -59,11 +59,41 @@ export const pathMap: PathMapItem[] = [
],
},
{
key: 'API_TEST_MANAGEMENT', // 接口测试-接口管理
key: 'API_TEST_MANAGEMENT', // 接口测试-接口定义
locale: 'menu.apiTest.management',
route: RouteEnum.API_TEST_MANAGEMENT,
permission: [],
level: MENU_LEVEL[2],
children: [
{
key: 'API_TEST_MANAGEMENT_MODULE', // 接口测试-接口定义-模块
locale: 'common.module',
route: RouteEnum.API_TEST_MANAGEMENT,
permission: [],
level: MENU_LEVEL[2],
},
{
key: 'API_TEST_MANAGEMENT_DEFINITION', // 接口测试-接口定义
locale: 'menu.apiTest.management.definition',
route: RouteEnum.API_TEST_MANAGEMENT,
permission: [],
level: MENU_LEVEL[2],
},
{
key: 'API_TEST_MANAGEMENT_MOCK', // 接口测试-接口定义-mock
locale: 'MOCK',
route: RouteEnum.API_TEST_MANAGEMENT,
permission: [],
level: MENU_LEVEL[2],
},
{
key: 'API_TEST_MANAGEMENT_CASE', // 接口测试-接口定义-case
locale: 'CASE',
route: RouteEnum.API_TEST_MANAGEMENT,
permission: [],
level: MENU_LEVEL[2],
},
],
},
{
key: 'API_TEST_REPORT', // 接口测试-接口测试报告

View File

@ -213,3 +213,19 @@ export enum RequestCaseStatus {
PROCESSING = 'PROCESSING',
DONE = 'DONE',
}
// 创建接口场景组成部分
export enum ScenarioCreateComposition {
STEP = 'STEP',
PARAMS = 'PARAMS',
PRE_POST = 'PRE_POST',
ASSERTION = 'ASSERTION',
SETTING = 'SETTING',
}
// 接口场景详情组成部分
export enum ScenarioDetailComposition {
BASE_INFO = 'BASE_INFO',
EXECUTE_HISTORY = 'EXECUTE_HISTORY',
CHANGE_HISTORY = 'CHANGE_HISTORY',
DEPENDENCY = 'DEPENDENCY',
QUOTE = 'QUOTE',
}

View File

@ -27,6 +27,7 @@ export default {
'menu.apiTest.debug': 'API debug',
'menu.apiTest.debug.debug': 'Debug',
'menu.apiTest.management': 'API Management',
'menu.apiTest.management.definition': 'API Definition',
'menu.apiTest.scenario': 'API Scenario',
'menu.apiTest.report': 'API Report',
'menu.uiTest': 'UI Test',

View File

@ -27,6 +27,7 @@ export default {
'menu.apiTest.debug': '接口调试',
'menu.apiTest.debug.debug': '调试',
'menu.apiTest.management': '接口管理',
'menu.apiTest.management.definition': '接口定义',
'menu.apiTest.api': 'API列表',
'menu.apiTest.scenario': '接口场景',
'menu.apiTest.report': '接口报告',

View File

@ -483,6 +483,7 @@
</template>
<script setup lang="ts">
// TODO:
import { FormInstance, Message, SelectOptionData } from '@arco-design/web-vue';
import { cloneDeep, debounce } from 'lodash-es';
@ -562,6 +563,7 @@
mode?: 'definition' | 'debug';
executeLoading: boolean; // loading
isCopy?: boolean; //
isExecute?: boolean; //
}
export type RequestParam = ExecuteApiRequestFullParams & {
responseDefinition?: ResponseItem[];
@ -603,18 +605,6 @@
const temporaryResponseMap = {}; // websockettab
const isInitPluginForm = ref(false);
watch(
() => props.request.id,
() => {
if (temporaryResponseMap[props.request.reportId]) {
//
requestVModel.value.response = temporaryResponseMap[props.request.reportId];
requestVModel.value.executeLoading = false;
delete temporaryResponseMap[props.request.reportId];
}
}
);
function handleActiveDebugChange() {
if (!loading.value || (!isHttpProtocol.value && isInitPluginForm.value)) {
// change
@ -878,25 +868,6 @@
handleActiveDebugChange();
}
watch(
() => requestVModel.value.id,
async () => {
if (requestVModel.value.protocol !== 'HTTP') {
requestVModel.value.activeTab = RequestComposition.PLUGIN;
if (protocolOptions.value.length === 0) {
//
await initProtocolList();
}
initPluginScript();
} else {
initProtocolList();
}
},
{
immediate: true,
}
);
/**
* 处理url输入框变化解析成参数表格
*/
@ -994,35 +965,6 @@
}
}
const reportId = ref('');
const websocket = ref<WebSocket>();
/**
* 开启websocket监听接收执行结果
*/
function debugSocket(executeType?: 'localExec' | 'serverExec') {
websocket.value = getSocket(
reportId.value,
executeType === 'localExec' ? '/ws/debug' : '',
executeType === 'localExec' ? localExecuteUrl.value : ''
);
websocket.value.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
if (data.msgType === 'EXEC_RESULT') {
if (requestVModel.value.reportId === data.reportId) {
// tabtab
requestVModel.value.response = data.taskResult;
requestVModel.value.executeLoading = false;
} else {
// tab
temporaryResponseMap[data.reportId] = data.taskResult;
}
} else if (data.msgType === 'EXEC_END') {
// websocket
websocket.value?.close();
}
});
}
const saveModalVisible = ref(false);
const saveModalForm = ref({
name: '',
@ -1061,6 +1003,38 @@
return conditionCopy;
}
const reportId = ref('');
const websocket = ref<WebSocket>();
/**
* 开启websocket监听接收执行结果
*/
function debugSocket(executeType?: 'localExec' | 'serverExec') {
websocket.value = getSocket(
reportId.value,
executeType === 'localExec' ? '/ws/debug' : '',
executeType === 'localExec' ? localExecuteUrl.value : ''
);
websocket.value.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
if (data.msgType === 'EXEC_RESULT') {
if (requestVModel.value.reportId === data.reportId) {
// tabtab
requestVModel.value.response = data.taskResult;
requestVModel.value.executeLoading = false;
requestVModel.value.isExecute = false;
} else {
// tab
temporaryResponseMap[data.reportId] = data.taskResult;
}
} else if (data.msgType === 'EXEC_END') {
// websocket
websocket.value?.close();
requestVModel.value.executeLoading = false;
requestVModel.value.isExecute = false;
}
});
}
/**
* 生成请求参数
* @param executeType 执行类型执行时传入
@ -1214,6 +1188,34 @@
requestVModel.value.executeLoading = false;
}
watch(
() => requestVModel.value.id,
async () => {
if (requestVModel.value.protocol !== 'HTTP') {
requestVModel.value.activeTab = RequestComposition.PLUGIN;
if (protocolOptions.value.length === 0) {
//
await initProtocolList();
}
await initPluginScript();
} else {
await initProtocolList();
}
if (props.request.isExecute && !requestVModel.value.executeLoading) {
//
execute(isPriorityLocalExec.value ? 'localExec' : 'serverExec');
} else if (temporaryResponseMap[props.request.reportId]) {
//
requestVModel.value.response = temporaryResponseMap[props.request.reportId];
requestVModel.value.executeLoading = false;
delete temporaryResponseMap[props.request.reportId];
}
},
{
immediate: true,
}
);
async function updateRequest() {
try {
saveLoading.value = true;
@ -1266,7 +1268,6 @@
requestVModel.value.label = res.name;
requestVModel.value.url = res.path;
requestVModel.value.path = res.path;
console.log('requestVModel.value', requestVModel.value);
if (!props.isDefinition) {
saveModalVisible.value = false;
}

View File

@ -265,7 +265,12 @@
document.querySelector(`#renameSpan${_tab.id}`)?.dispatchEvent(new Event('click'));
break;
case 'copy':
addResponseTab({ ..._tab, label: `${_tab.label || _tab.name}-Copy`, name: `${_tab.label || _tab.name}-Copy` });
addResponseTab({
..._tab,
id: new Date().getTime(),
label: `copy-${t(_tab.label || _tab.name)}`,
name: `copy-${t(_tab.label || _tab.name)}`,
});
break;
case 'delete':
_tab.showPopConfirm = true;

View File

@ -89,6 +89,9 @@
</template>
<script lang="ts" setup>
/**
* @description 接口测试-接口调试
*/
import { onBeforeRouteLeave } from 'vue-router';
import { cloneDeep } from 'lodash-es';

View File

@ -1,6 +1,6 @@
<template>
<div :class="['p-[0_16px_16px_16px]', props.class]">
<div class="mb-[16px] flex items-center justify-between">
<div class="mb-[16px] flex items-center justify-end">
<div class="flex items-center gap-[8px]">
<a-input-search
v-model:model-value="keyword"
@ -110,7 +110,7 @@
</a-select>
</template>
<template #action="{ record }">
<MsButton type="text" class="!mr-0">
<MsButton type="text" class="!mr-0" @click="executeDefinition(record)">
{{ t('apiTestManagement.execute') }}
</MsButton>
<a-divider direction="vertical" :margin="8"></a-divider>
@ -277,7 +277,7 @@
readOnly?: boolean; //
}>();
const emit = defineEmits<{
(e: 'openApiTab', record: ApiDefinitionDetail): void;
(e: 'openApiTab', record: ApiDefinitionDetail, isExecute?: boolean): void;
(e: 'openCopyApiTab', record: ApiDefinitionDetail): void;
}>();
@ -759,6 +759,10 @@
emit('openCopyApiTab', record);
}
function executeDefinition(record: ApiDefinitionDetail) {
emit('openApiTab', record, true);
}
//
async function handleTableDragSort(params: DragSortParams) {
try {

View File

@ -6,7 +6,7 @@
:active-module="props.activeModule"
:offspring-ids="props.offspringIds"
:protocol="props.protocol"
@open-api-tab="openApiTab"
@open-api-tab="(record, isExecute) => openApiTab(record, false, isExecute)"
@open-copy-api-tab="openApiTab($event, true)"
/>
</div>
@ -107,7 +107,6 @@
moduleTree: ModuleTreeNode[]; //
protocol: string;
}>();
const emit = defineEmits(['addDone']);
const refreshModuleTree: (() => Promise<any>) | undefined = inject('refreshModuleTree');
@ -157,6 +156,7 @@
},
rawBody: { value: '' },
};
//
const defaultResponse: RequestTaskResult = {
requestResults: [
{
@ -182,7 +182,7 @@
},
],
console: '',
}; //
};
const defaultDefinitionParams: RequestParam = {
id: initDefaultId,
moduleId: props.activeModule === 'all' ? 'root' : props.activeModule,
@ -276,20 +276,24 @@
);
const loading = ref(false);
async function openApiTab(apiInfo: ModuleTreeNode | ApiDefinitionDetail | string, isCopy = false) {
async function openApiTab(apiInfo: ModuleTreeNode | ApiDefinitionDetail | string, isCopy = false, isExecute = false) {
const isLoadedTabIndex = apiTabs.value.findIndex(
(e) => e.id === (typeof apiInfo === 'string' ? apiInfo : apiInfo.id)
);
if (isLoadedTabIndex > -1 && !isCopy) {
// tabtab
activeApiTab.value = apiTabs.value[isLoadedTabIndex] as RequestParam;
activeApiTab.value = {
...(apiTabs.value[isLoadedTabIndex] as RequestParam),
isExecute,
mode: isExecute ? 'debug' : 'definition',
};
return;
}
try {
loading.value = true;
const res = await getDefinitionDetail(typeof apiInfo === 'string' ? apiInfo : apiInfo.id);
const name = isCopy ? `${res.name}-copy` : res.name;
definitionActiveKey.value = isCopy ? 'definition' : 'preview';
const name = isCopy ? `copy-${res.name}` : res.name;
definitionActiveKey.value = isCopy || isExecute ? 'definition' : 'preview';
let parseRequestBodyResult;
if (res.protocol === 'HTTP') {
parseRequestBodyResult = parseRequestBodyFiles(res.request.body); // id
@ -305,6 +309,9 @@
isNew: isCopy,
unSaved: isCopy,
isCopy,
id: isCopy ? new Date().getTime() : res.id,
isExecute,
mode: isExecute ? 'debug' : 'definition',
...parseRequestBodyResult,
});
nextTick(() => {

View File

@ -14,7 +14,7 @@
@change-protocol="handleProtocolChange"
/>
</div>
<div class="b-0 absolute w-full p-[9px]">
<div class="w-full p-[8px]">
<a-divider class="!my-0 !mb-0" />
<div class="case h-[38px]">
<div class="flex items-center" :class="getActiveClass('recycle')" @click="setActiveFolder('recycle')">
@ -52,6 +52,9 @@
</template>
<script lang="ts" setup>
/**
* @description 接口测试-接口管理
*/
import { provide } from 'vue';
import { useRoute, useRouter } from 'vue-router';

View File

@ -0,0 +1,7 @@
<template>
<div> assertion </div>
</template>
<script setup lang="ts"></script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,7 @@
<template>
<div> changeHistory </div>
</template>
<script setup lang="ts"></script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,7 @@
<template>
<div> dependency </div>
</template>
<script setup lang="ts"></script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,7 @@
<template>
<div> executeHistory </div>
</template>
<script setup lang="ts"></script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,7 @@
<template>
<div> params </div>
</template>
<script setup lang="ts"></script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,7 @@
<template>
<div> prePost </div>
</template>
<script setup lang="ts"></script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,7 @@
<template>
<div> quote </div>
</template>
<script setup lang="ts"></script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,7 @@
<template>
<div> setting </div>
</template>
<script setup lang="ts"></script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,7 @@
<template>
<div> step </div>
</template>
<script setup lang="ts"></script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,7 @@
<template>
<div> create </div>
</template>
<script setup lang="ts"></script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,180 @@
<template>
<div class="h-full w-full overflow-hidden">
<div class="px-[24px] pt-[16px]">
<MsDetailCard :title="`【${previewDetail.num}】${previewDetail.name}`" :description="description">
<template #titleAppend>
<apiStatus :status="previewDetail.status" size="small" />
</template>
<template #titleRight>
<a-button
type="outline"
:loading="followLoading"
size="mini"
class="arco-btn-outline--secondary mr-[4px] !bg-transparent"
@click="toggleFollowReview"
>
<div class="flex items-center gap-[4px]">
<MsIcon
:type="previewDetail.follow ? 'icon-icon_collect_filled' : 'icon-icon_collection_outlined'"
:class="`${previewDetail.follow ? 'text-[rgb(var(--warning-6))]' : 'text-[var(--color-text-4)]'}`"
:size="14"
/>
{{ t(previewDetail.follow ? 'common.forked' : 'common.fork') }}
</div>
</a-button>
<a-button type="outline" size="mini" class="arco-btn-outline--secondary !bg-transparent" @click="share">
<div class="flex items-center gap-[4px]">
<MsIcon type="icon-icon_share1" class="text-[var(--color-text-4)]" :size="14" />
{{ t('common.share') }}
</div>
</a-button>
</template>
<template #type="{ value }">
<apiMethodName :method="value as RequestMethods" tag-size="small" is-tag />
</template>
</MsDetailCard>
</div>
<div class="h-[calc(100%-124px)]">
<a-tabs v-model:active-key="activeKey" class="h-full" animation lazy-load>
<a-tab-pane
:key="ScenarioDetailComposition.BASE_INFO"
:title="t('apiScenario.baseInfo')"
class="px-[24px] py-[16px]"
>
BASE_INFO
</a-tab-pane>
<a-tab-pane :key="ScenarioCreateComposition.STEP" :title="t('apiScenario.step')" class="px-[24px] py-[16px]">
<step v-if="activeKey === ScenarioCreateComposition.STEP" />
</a-tab-pane>
<a-tab-pane
:key="ScenarioCreateComposition.PARAMS"
:title="t('apiScenario.params')"
class="px-[24px] py-[16px]"
>
<params v-if="activeKey === ScenarioCreateComposition.PARAMS" />
</a-tab-pane>
<a-tab-pane
:key="ScenarioCreateComposition.PRE_POST"
:title="t('apiScenario.prePost')"
class="px-[24px] py-[16px]"
>
<prePost v-if="activeKey === ScenarioCreateComposition.PRE_POST" />
</a-tab-pane>
<a-tab-pane
:key="ScenarioCreateComposition.ASSERTION"
:title="t('apiScenario.assertion')"
class="px-[24px] py-[16px]"
>
<assertion v-if="activeKey === ScenarioCreateComposition.ASSERTION" />
</a-tab-pane>
<a-tab-pane
:key="ScenarioDetailComposition.EXECUTE_HISTORY"
:title="t('apiScenario.executeHistory')"
class="px-[24px] py-[16px]"
>
<executeHistory v-if="activeKey === ScenarioDetailComposition.EXECUTE_HISTORY" />
</a-tab-pane>
<a-tab-pane
:key="ScenarioDetailComposition.CHANGE_HISTORY"
:title="t('apiScenario.changeHistory')"
class="px-[24px] py-[16px]"
>
<changeHistory v-if="activeKey === ScenarioDetailComposition.CHANGE_HISTORY" />
</a-tab-pane>
<a-tab-pane
:key="ScenarioDetailComposition.DEPENDENCY"
:title="t('apiScenario.dependency')"
class="px-[24px] py-[16px]"
>
<dependency v-if="activeKey === ScenarioDetailComposition.DEPENDENCY" />
</a-tab-pane>
<a-tab-pane :key="ScenarioDetailComposition.QUOTE" :title="t('apiScenario.quote')" class="px-[24px] py-[16px]">
<quote v-if="activeKey === ScenarioDetailComposition.QUOTE" />
</a-tab-pane>
<a-tab-pane :key="ScenarioCreateComposition.SETTING" :title="t('common.setting')" class="px-[24px] py-[16px]">
<setting v-if="activeKey === ScenarioCreateComposition.SETTING" />
</a-tab-pane>
</a-tabs>
</div>
</div>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import { useClipboard } from '@vueuse/core';
import { Message } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es';
import MsDetailCard from '@/components/pure/ms-detail-card/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
import apiStatus from '@/views/api-test/components/apiStatus.vue';
import { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
import { RequestMethods, ScenarioCreateComposition, ScenarioDetailComposition } from '@/enums/apiEnum';
//
const step = defineAsyncComponent(() => import('../components/step/index.vue'));
const params = defineAsyncComponent(() => import('../components/params.vue'));
const prePost = defineAsyncComponent(() => import('../components/prePost.vue'));
const assertion = defineAsyncComponent(() => import('../components/assertion.vue'));
const executeHistory = defineAsyncComponent(() => import('../components/executeHistory.vue'));
const changeHistory = defineAsyncComponent(() => import('../components/changeHistory.vue'));
const dependency = defineAsyncComponent(() => import('../components/dependency.vue'));
const quote = defineAsyncComponent(() => import('../components/quote.vue'));
const setting = defineAsyncComponent(() => import('../components/setting.vue'));
const props = defineProps<{
detail: RequestParam;
}>();
const emit = defineEmits(['updateFollow']);
const { copy, isSupported } = useClipboard();
const { t } = useI18n();
const previewDetail = ref<RequestParam>(cloneDeep(props.detail));
const description = computed(() => [
{
key: 'type',
locale: 'something.type',
value: 'type',
},
{
key: 'path',
locale: 'something.path',
value: 'path',
},
]);
const followLoading = ref(false);
async function toggleFollowReview() {
try {
followLoading.value = true;
Message.success(previewDetail.value.follow ? t('common.unFollowSuccess') : t('common.followSuccess'));
emit('updateFollow');
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
followLoading.value = false;
}
}
function share() {
if (isSupported) {
copy(`${window.location.href}&dId=${previewDetail.value.id}`);
Message.success(t('common.copySuccess'));
} else {
Message.error(t('common.copyNotSupport'));
}
}
const activeKey = ref<ScenarioCreateComposition | ScenarioDetailComposition>(ScenarioDetailComposition.BASE_INFO);
</script>
<style lang="less" scoped>
:deep(.arco-tabs-content) {
@apply pt-0;
}
</style>

View File

@ -1,10 +1,23 @@
<template>
<div class="rounded-2xl bg-white">
<div class="p-[24px] pb-[16px]">
<span>场景列表接口(标签页配置未实现)</span>
<MsCard no-content-padding simple>
<div class="p-[24px_24px_8px_24px]">
<MsEditableTab
v-model:active-tab="activeApiTab"
v-model:tabs="apiTabs"
class="flex-1 overflow-hidden"
@add="newTab"
>
<template #label="{ tab }">
<a-tooltip :content="tab.label" :mouse-enter-delay="500">
<div class="one-line-text max-w-[144px]">
{{ tab.label }}
</div>
</a-tooltip>
</template>
</MsEditableTab>
</div>
<a-divider class="!my-0" />
<div class="pageWrap">
<div v-if="activeApiTab.id === 'all'" class="pageWrap">
<MsSplitBox :size="300" :max="0.5">
<template #first>
<div class="p-[24px] pb-0">
@ -36,7 +49,8 @@
</template>
</MsSplitBox>
</div>
</div>
<detail v-else :detail="activeApiTab"></detail>
</MsCard>
</template>
<script setup lang="ts">
@ -46,9 +60,12 @@
import { ref } from 'vue';
import MsCard from '@/components/pure/ms-card/index.vue';
import MsEditableTab from '@/components/pure/ms-editable-tab/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
import scenarioModuleTree from './components/scenarioModuleTree.vue';
import detail from './detail/index.vue';
import ApiTable from '@/views/api-test/management/components/management/api/apiTable.vue';
import { useI18n } from '@/hooks/useI18n';
@ -56,6 +73,25 @@
import { ModuleTreeNode } from '@/models/common';
const { t } = useI18n();
const apiTabs = ref<any[]>([
{
id: 'all',
label: t('apiScenario.allScenario'),
closable: false,
},
]);
const activeApiTab = ref<any>(apiTabs.value[0]);
function newTab() {
apiTabs.value.push({
id: `newTab${apiTabs.value.length}`,
label: `New Tab ${apiTabs.value.length}`,
closable: true,
});
activeApiTab.value = apiTabs.value[apiTabs.value.length - 1];
}
const folderTree = ref<ModuleTreeNode[]>([]);
const folderTreePathMap = ref<Record<string, any>>({});
const activeFolder = ref<string>('all');
@ -86,8 +122,7 @@
<style scoped lang="less">
.pageWrap {
min-width: 1000px;
height: calc(100vh - 166px);
height: calc(100% - 65px);
border-radius: var(--border-radius-large);
@apply bg-white;
.case {

View File

@ -1,4 +1,5 @@
export default {
'apiScenario.allScenario': 'All scenarios',
'apiScenario.createScenario': 'Create scenario',
'apiScenario.importScenario': 'Import scenario',
'apiScenario.tree.selectorPlaceholder': 'Please enter the module name',
@ -6,15 +7,20 @@ export default {
'apiScenario.tree.showLeafNodeScenario': 'Show subdirectory scenarios',
'apiScenario.tree.recycleBin': 'Recycle bin',
'apiScenario.tree.noMatchModule': 'No matching module/scene yet',
'apiScenario.createSubModule': 'Create sub-module',
'apiScenario.module.deleteTipTitle': 'Delete {name} module?',
'apiScenario.module.deleteTipContent':
'After deletion, all scenarios under the module will be deleted synchronously. Please operate with caution.',
'apiScenario.deleteConfirm': 'Confirm',
'apiScenario.deleteSuccess': 'Success',
'apiScenario.moveSuccess': 'Success',
'apiScenario.baseInfo': 'Base info',
'apiScenario.step': 'Step',
'apiScenario.params': 'Params',
'apiScenario.prePost': 'Pre/Post',
'apiScenario.assertion': 'Assertion',
'apiScenario.executeHistory': 'Execute history',
'apiScenario.changeHistory': 'Change history',
'apiScenario.dependency': 'Dependencies',
'apiScenario.quote': 'Reference',
};

View File

@ -1,4 +1,5 @@
export default {
'apiScenario.allScenario': '全部场景',
'apiScenario.createScenario': '新建场景',
'apiScenario.importScenario': '导入场景',
'apiScenario.tree.selectorPlaceholder': '请输入模块名称',
@ -6,14 +7,19 @@ export default {
'apiScenario.tree.showLeafNodeScenario': '显示子目录场景',
'apiScenario.tree.recycleBin': '回收站',
'apiScenario.tree.noMatchModule': '暂无匹配的模块/场景',
'apiScenario.createSubModule': '新建子模块',
'apiScenario.module.deleteTipTitle': '是否删除 {name} 模块?',
'apiScenario.module.deleteTipContent': '删除后,会同步删除模块下的所有场景,请谨慎操作.',
'apiScenario.deleteConfirm': '确认删除',
'apiScenario.deleteSuccess': '删除成功',
'apiScenario.moveSuccess': '移动成功',
'apiScenario.baseInfo': '基本信息',
'apiScenario.step': '步骤',
'apiScenario.params': '参数',
'apiScenario.prePost': '前/后置',
'apiScenario.assertion': '断言',
'apiScenario.executeHistory': '执行历史',
'apiScenario.changeHistory': '变更历史',
'apiScenario.dependency': '依赖关系',
'apiScenario.quote': '引用关系',
};