feat(接口定义): 模块树操作与右侧数据同步
This commit is contained in:
parent
dac0d93608
commit
49090a2312
|
@ -78,7 +78,7 @@
|
|||
updateApiNodeApi?: (params: { id: string; name: string }) => Promise<any>;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['update:visible', 'close', 'addFinish', 'renameFinish', 'updateDescFinish']);
|
||||
const emit = defineEmits(['update:visible', 'close', 'addFinish', 'renameFinish']);
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
|
|
|
@ -262,6 +262,9 @@
|
|||
...defaultProps,
|
||||
});
|
||||
activeApiTab.value = apiTabs.value[apiTabs.value.length - 1];
|
||||
if (!defaultProps) {
|
||||
definitionActiveKey.value = 'definition';
|
||||
}
|
||||
}
|
||||
|
||||
const apiTableRef = ref<InstanceType<typeof apiTable>>();
|
||||
|
@ -326,7 +329,6 @@
|
|||
}
|
||||
|
||||
function handleAddDone() {
|
||||
definitionActiveKey.value = 'preview'; // 保存完毕后切换到预览页
|
||||
if (typeof refreshModuleTree === 'function') {
|
||||
refreshModuleTree();
|
||||
}
|
||||
|
|
|
@ -92,9 +92,9 @@
|
|||
|
||||
const apiRef = ref<InstanceType<typeof api>>();
|
||||
|
||||
function newTab(apiInfo?: ModuleTreeNode | string) {
|
||||
function newTab(apiInfo?: ModuleTreeNode | string, isCopy?: boolean, isExecute?: boolean) {
|
||||
if (apiInfo) {
|
||||
apiRef.value?.openApiTab(apiInfo);
|
||||
apiRef.value?.openApiTab(apiInfo, isCopy, isExecute);
|
||||
} else {
|
||||
apiRef.value?.addApiTab();
|
||||
}
|
||||
|
@ -105,10 +105,22 @@
|
|||
id: 'all',
|
||||
label: t('apiTestManagement.allApi'),
|
||||
closable: false,
|
||||
} as RequestParam,
|
||||
moduleId: 'root',
|
||||
} as unknown as RequestParam,
|
||||
]);
|
||||
const activeApiTab = ref<RequestParam>(apiTabs.value[0] as RequestParam);
|
||||
|
||||
// 监听模块树的激活节点变化,记录表格数据的模块 id
|
||||
watch(
|
||||
() => props.activeModule,
|
||||
(val) => {
|
||||
const defaultTab = apiTabs.value.find((item) => item.id === 'all');
|
||||
if (defaultTab) {
|
||||
defaultTab.moduleId = val;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 下拉框切换
|
||||
function currentTabChange(val: any) {
|
||||
apiTabs.value[0].label = val === 'api' ? t('apiTestManagement.allApi') : t('case.allCase');
|
||||
|
@ -117,7 +129,8 @@
|
|||
watch(
|
||||
() => activeApiTab.value.id,
|
||||
() => {
|
||||
if (typeof setActiveApi === 'function') {
|
||||
if (typeof setActiveApi === 'function' && !activeApiTab.value.isNew) {
|
||||
// 打开的 tab 是接口详情的 tab 才需要同步设置模块树的激活节点
|
||||
setActiveApi(activeApiTab.value);
|
||||
}
|
||||
}
|
||||
|
@ -166,6 +179,53 @@
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步模块树的接口信息更新操作
|
||||
*/
|
||||
function handleApiUpdateFromModuleTree(newInfo: { id: string; name: string; moduleId?: string; [key: string]: any }) {
|
||||
apiTabs.value = apiTabs.value.map((item) => {
|
||||
if (item.id === newInfo.id) {
|
||||
item.label = newInfo.name;
|
||||
item.name = newInfo.name;
|
||||
if (newInfo.moduleId) {
|
||||
item.moduleId = newInfo.moduleId;
|
||||
}
|
||||
}
|
||||
return item;
|
||||
});
|
||||
if (activeApiTab.value.id === newInfo.id) {
|
||||
activeApiTab.value.label = newInfo.name;
|
||||
activeApiTab.value.name = newInfo.name;
|
||||
if (newInfo.moduleId) {
|
||||
activeApiTab.value.moduleId = newInfo.moduleId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步模块树的接口信息删除操作
|
||||
* @param id 接口 id
|
||||
* @param isModule 是否是删除模块
|
||||
*/
|
||||
function handleDeleteApiFromModuleTree(id: string, isModule = false) {
|
||||
if (isModule) {
|
||||
// 删除整个模块
|
||||
apiTabs.value = apiTabs.value.filter((item) => {
|
||||
if (activeApiTab.value.id === item.id) {
|
||||
// 删除的是当前激活的 tab, 切换到第一个 tab
|
||||
[activeApiTab.value] = apiTabs.value;
|
||||
}
|
||||
return item.moduleId !== id || item.id === 'all';
|
||||
});
|
||||
} else {
|
||||
// 删除单个 api
|
||||
apiTabs.value = apiTabs.value.filter((item) => item.id !== id);
|
||||
if (activeApiTab.value.id === id) {
|
||||
[activeApiTab.value] = apiTabs.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
initEnvList();
|
||||
});
|
||||
|
@ -176,6 +236,8 @@
|
|||
defineExpose({
|
||||
newTab,
|
||||
refreshApiTable,
|
||||
handleApiUpdateFromModuleTree,
|
||||
handleDeleteApiFromModuleTree,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
:all-names="rootModulesName"
|
||||
parent-id="NONE"
|
||||
:add-module-api="addModule"
|
||||
@add-finish="initModules"
|
||||
@add-finish="handleAddFinish"
|
||||
>
|
||||
<span id="addModulePopSpan"></span>
|
||||
</popConfirm>
|
||||
|
@ -122,7 +122,7 @@
|
|||
:parent-id="nodeData.id"
|
||||
:add-module-api="addModule"
|
||||
@close="resetFocusNodeKey"
|
||||
@add-finish="() => initModules()"
|
||||
@add-finish="handleAddFinish"
|
||||
>
|
||||
<MsButton type="icon" size="mini" class="ms-tree-node-extra__btn !mr-0" @click="setFocusNodeKey(nodeData)">
|
||||
<MsIcon type="icon-icon_add_outlined" size="14" class="text-[var(--color-text-4)]" />
|
||||
|
@ -139,7 +139,7 @@
|
|||
:update-module-api="updateModule"
|
||||
:update-api-node-api="updateDefinition"
|
||||
@close="resetFocusNodeKey"
|
||||
@rename-finish="initModules"
|
||||
@rename-finish="handleRenameFinish"
|
||||
>
|
||||
<span :id="`renameSpan${nodeData.id}`" class="relative"></span>
|
||||
</popConfirm>
|
||||
|
@ -151,6 +151,7 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeMount, ref, watch } from 'vue';
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
import { Message, SelectOptionData } from '@arco-design/web-vue';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
|
@ -164,6 +165,7 @@
|
|||
import { getProtocolList } from '@/api/modules/api-test/common';
|
||||
import {
|
||||
addModule,
|
||||
deleteDefinition,
|
||||
deleteModule,
|
||||
getModuleCount,
|
||||
getModuleTree,
|
||||
|
@ -199,11 +201,22 @@
|
|||
trash: false,
|
||||
}
|
||||
);
|
||||
const emit = defineEmits(['init', 'newApi', 'import', 'folderNodeSelect', 'clickApiNode', 'changeProtocol']);
|
||||
const emit = defineEmits([
|
||||
'init',
|
||||
'newApi',
|
||||
'import',
|
||||
'folderNodeSelect',
|
||||
'clickApiNode',
|
||||
'changeProtocol',
|
||||
'updateApiNode',
|
||||
'deleteNode',
|
||||
'execute',
|
||||
]);
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
const { openModal } = useModal();
|
||||
const { copy, isSupported } = useClipboard();
|
||||
|
||||
const moduleProtocol = ref('HTTP');
|
||||
const moduleProtocolOptions = ref<SelectOptionData[]>([]);
|
||||
|
@ -483,8 +496,11 @@
|
|||
function deleteFolder(node: MsTreeNodeData) {
|
||||
openModal({
|
||||
type: 'error',
|
||||
title: t('apiTestDebug.deleteFolderTipTitle', { name: node.name }),
|
||||
content: t('apiTestDebug.deleteFolderTipContent'),
|
||||
title:
|
||||
node.type === 'API'
|
||||
? t('apiTestDebug.deleteDebugTipTitle', { name: node.name })
|
||||
: t('apiTestDebug.deleteFolderTipTitle', { name: node.name }),
|
||||
content: node.type === 'API' ? t('apiTestDebug.deleteDebugTipContent') : t('apiTestDebug.deleteFolderTipContent'),
|
||||
okText: t('apiTestDebug.deleteConfirm'),
|
||||
okButtonProps: {
|
||||
status: 'danger',
|
||||
|
@ -492,8 +508,13 @@
|
|||
maskClosable: false,
|
||||
onBeforeOk: async () => {
|
||||
try {
|
||||
await deleteModule(node.id);
|
||||
if (node.type === 'API') {
|
||||
await deleteDefinition(node.id);
|
||||
} else {
|
||||
await deleteModule(node.id);
|
||||
}
|
||||
Message.success(t('apiTestDebug.deleteSuccess'));
|
||||
emit('deleteNode', node.id, node.type === 'MODULE');
|
||||
await initModules();
|
||||
initModuleCount();
|
||||
} catch (error) {
|
||||
|
@ -514,6 +535,15 @@
|
|||
renameFolderTitle.value = '';
|
||||
}
|
||||
|
||||
function share(id: string) {
|
||||
if (isSupported) {
|
||||
copy(`${window.location.href}&dId=${id}`);
|
||||
Message.success(t('apiTestManagement.shareUrlCopied'));
|
||||
} else {
|
||||
Message.error(t('common.copyNotSupport'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理树节点更多按钮事件
|
||||
* @param item
|
||||
|
@ -529,6 +559,12 @@
|
|||
renamePopVisible.value = true;
|
||||
document.querySelector(`#renameSpan${node.id}`)?.dispatchEvent(new Event('click'));
|
||||
break;
|
||||
case 'share':
|
||||
share(node.id);
|
||||
break;
|
||||
case 'execute':
|
||||
emit('execute', node.id);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -577,9 +613,10 @@
|
|||
projectId: appStore.currentProjectId,
|
||||
moveMode: dropPositionMap[dropPosition],
|
||||
moveId: dragNode.id,
|
||||
targetId: dropNode.type === 'MODULE' ? dragNode.id : dropNode.id,
|
||||
targetId: dropNode.type === 'MODULE' ? dragNode.id : dropNode.id, // 释放节点是模块,则传入当前拖动的 API 的id;释放节点是 API 节点的话就传入释放节点的 id
|
||||
moduleId: dropNode.type === 'API' ? dropNode.parentId : dropNode.id, // 释放节点是 API,则传入它所属模块id;模块的话直接是模块id
|
||||
});
|
||||
emit('updateApiNode', { ...dragNode, moduleId: dropNode.type === 'API' ? dropNode.parentId : dropNode.id });
|
||||
}
|
||||
Message.success(t('apiTestDebug.moduleMoveSuccess'));
|
||||
} catch (error) {
|
||||
|
@ -592,6 +629,16 @@
|
|||
}
|
||||
}
|
||||
|
||||
async function handleAddFinish() {
|
||||
await initModules();
|
||||
initModuleCount();
|
||||
}
|
||||
|
||||
function handleRenameFinish(newName: string, id: string) {
|
||||
emit('updateApiNode', { name: newName, id });
|
||||
initModules();
|
||||
}
|
||||
|
||||
function moreActionsClose() {
|
||||
if (!renamePopVisible.value) {
|
||||
// 当下拉菜单关闭时,若不是触发重命名气泡显示,则清空聚焦节点 key
|
||||
|
|
|
@ -2,24 +2,33 @@
|
|||
<MsCard simple no-content-padding>
|
||||
<MsSplitBox :size="0.25" :max="0.5">
|
||||
<template #first>
|
||||
<div class="p-[16px]">
|
||||
<moduleTree
|
||||
ref="moduleTreeRef"
|
||||
:active-node-id="activeApi?.id"
|
||||
@init="handleModuleInit"
|
||||
@new-api="newApi"
|
||||
@import="importDrawerVisible = true"
|
||||
@folder-node-select="handleNodeSelect"
|
||||
@click-api-node="handleApiNodeClick"
|
||||
@change-protocol="handleProtocolChange"
|
||||
/>
|
||||
</div>
|
||||
<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')">
|
||||
<MsIcon type="icon-icon_delete-trash_outlined" class="folder-icon" />
|
||||
<div class="folder-name mx-[4px]">{{ t('caseManagement.featureCase.recycle') }}</div>
|
||||
<div class="flex flex-col">
|
||||
<div class="p-[16px]">
|
||||
<moduleTree
|
||||
ref="moduleTreeRef"
|
||||
:active-node-id="activeNodeId"
|
||||
@init="handleModuleInit"
|
||||
@new-api="newApi"
|
||||
@import="importDrawerVisible = true"
|
||||
@folder-node-select="handleNodeSelect"
|
||||
@click-api-node="handleApiNodeClick"
|
||||
@change-protocol="handleProtocolChange"
|
||||
@update-api-node="handleUpdateApiNode"
|
||||
@delete-node="handleDeleteApiFromModuleTree"
|
||||
@execute="handleExecute"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<a-divider class="!my-0 !mb-0" />
|
||||
<div class="case">
|
||||
<div
|
||||
class="flex items-center px-[20px]"
|
||||
:class="getActiveClass('recycle')"
|
||||
@click="setActiveFolder('recycle')"
|
||||
>
|
||||
<MsIcon type="icon-icon_delete-trash_outlined" class="folder-icon" />
|
||||
<div class="folder-name mx-[4px]">{{ t('caseManagement.featureCase.recycle') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -79,7 +88,7 @@
|
|||
const importDrawerVisible = ref(false);
|
||||
const offspringIds = ref<string[]>([]);
|
||||
const protocol = ref('HTTP');
|
||||
const activeApi = ref<RequestParam>();
|
||||
const activeNodeId = ref<string | number>('all');
|
||||
const moduleTreeRef = ref<InstanceType<typeof moduleTree>>();
|
||||
const managementRef = ref<InstanceType<typeof management>>();
|
||||
|
||||
|
@ -103,7 +112,12 @@
|
|||
}
|
||||
|
||||
function setActiveApi(params: RequestParam) {
|
||||
activeApi.value = params;
|
||||
if (params.id === 'all') {
|
||||
// 切换到全部 tab 时需设置为上次激活的 api 节点的模块
|
||||
activeNodeId.value = params.moduleId;
|
||||
} else {
|
||||
activeNodeId.value = params.id;
|
||||
}
|
||||
}
|
||||
|
||||
function handleProtocolChange(val: string) {
|
||||
|
@ -119,6 +133,18 @@
|
|||
managementRef.value?.refreshApiTable();
|
||||
}
|
||||
|
||||
function handleUpdateApiNode(newInfo: { id: string; name: string; moduleId?: string; [key: string]: any }) {
|
||||
managementRef.value?.handleApiUpdateFromModuleTree(newInfo);
|
||||
}
|
||||
|
||||
function handleDeleteApiFromModuleTree(id: string, isModule?: boolean) {
|
||||
managementRef.value?.handleDeleteApiFromModuleTree(id, isModule);
|
||||
}
|
||||
|
||||
function handleExecute(id: string) {
|
||||
managementRef.value?.newTab(id, false, true);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (route.query.dId) {
|
||||
// 携带 dId 参数,自动打开接口定义详情 tab
|
||||
|
|
|
@ -186,14 +186,14 @@
|
|||
const virtualListProps = computed(() => {
|
||||
if (props.readOnly) {
|
||||
return {
|
||||
height: 'calc(60vh - 343px)',
|
||||
height: 'calc(60vh - 325px)',
|
||||
threshold: 200,
|
||||
fixedSize: true,
|
||||
buffer: 15, // 缓冲区默认 10 的时候,虚拟滚动的底部 padding 计算有问题
|
||||
};
|
||||
}
|
||||
return {
|
||||
height: 'calc(100vh - 343px)',
|
||||
height: 'calc(100vh - 325px)',
|
||||
threshold: 200,
|
||||
fixedSize: true,
|
||||
buffer: 15, // 缓冲区默认 10 的时候,虚拟滚动的底部 padding 计算有问题
|
||||
|
|
|
@ -20,23 +20,23 @@
|
|||
<div v-if="activeApiTab.id === 'all'" class="pageWrap">
|
||||
<MsSplitBox :size="300" :max="0.5">
|
||||
<template #first>
|
||||
<div class="p-[24px] pb-0">
|
||||
<div class="feature-case h-[100%]">
|
||||
<div class="flex h-full flex-col">
|
||||
<div class="p-[16px]">
|
||||
<scenarioModuleTree
|
||||
ref="scenarioModuleTreeRef"
|
||||
:is-show-scenario="isShowScenario"
|
||||
@folder-node-select="handleNodeSelect"
|
||||
@init="handleModuleInit"
|
||||
></scenarioModuleTree>
|
||||
<div class="b-0 absolute w-[88%]">
|
||||
<a-divider class="!my-0 !mb-2" />
|
||||
<div class="case h-[38px]">
|
||||
<div class="flex items-center" :class="getActiveClass('recycle')" @click="redirectRecycle()">
|
||||
<MsIcon type="icon-icon_delete-trash_outlined" class="folder-icon" />
|
||||
<div class="folder-name mx-[4px]">{{ t('apiScenario.tree.recycleBin') }}</div>
|
||||
<!-- <div class="folder-count">({{ recycleModulesCount.all || 0 }})</div></div-->
|
||||
<div class="folder-count">({{ 0 }})</div></div
|
||||
>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<a-divider margin="0" />
|
||||
<div class="case">
|
||||
<div class="flex items-center px-[20px]" :class="getActiveClass('recycle')" @click="redirectRecycle()">
|
||||
<MsIcon type="icon-icon_delete-trash_outlined" class="folder-icon" />
|
||||
<div class="folder-name mx-[4px]">{{ t('apiScenario.tree.recycleBin') }}</div>
|
||||
<!-- <div class="folder-count">({{ recycleModulesCount.all || 0 }})</div></div-->
|
||||
<div class="folder-count">({{ 0 }})</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue