feat(接口测试): 接口场景模块树开发

--task=1014597 --user=宋天阳 接口测试-接口场景-模块树(增,删,改,查,移动) https://www.tapd.cn/55049933/s/1473433
This commit is contained in:
song-tianyang 2024-03-12 16:49:30 +08:00 committed by 建国
parent 6857dd764c
commit 1253919add
11 changed files with 870 additions and 0 deletions

View File

@ -0,0 +1,47 @@
import MSR from '@/api/http/index';
import {
AddModuleUrl,
DeleteModuleUrl,
GetModuleCountUrl,
GetModuleTreeUrl,
MoveModuleUrl,
UpdateModuleUrl,
} from '@/api/requrls/api-test/scenario';
import { ApiScenarioGetModuleParams, ApiScenarioModuleUpdateParams } from '@/models/apiTest/scenario';
import {
AddModuleParams,
ModuleTreeNode,
MoveModules,
} from '@/models/common';
// 更新模块
export function updateModule(data: ApiScenarioModuleUpdateParams) {
return MSR.post({ url: UpdateModuleUrl, data });
}
// 获取模块树
export function getModuleTree(data: ApiScenarioGetModuleParams) {
return MSR.post<ModuleTreeNode[]>({ url: GetModuleTreeUrl, data });
}
// 移动模块
export function moveModule(data: MoveModules) {
return MSR.post({ url: MoveModuleUrl, data });
}
// 获取模块统计数量
export function getModuleCount(data: ApiScenarioGetModuleParams) {
return MSR.post({ url: GetModuleCountUrl, data });
}
// 添加模块
export function addModule(data: AddModuleParams) {
return MSR.post({ url: AddModuleUrl, data });
}
// 删除模块
export function deleteModule(id: string) {
return MSR.get({ url: DeleteModuleUrl, params: id });
}

View File

@ -0,0 +1,30 @@
export const UpdateModuleUrl = '/api/scenario/module/update'; // 更新模块
export const GetModuleTreeUrl = '/api/scenario/module/tree'; // 查找模块
export const MoveModuleUrl = '/api/scenario/module/move'; // 移动模块
export const GetModuleCountUrl = '/api/scenario/module/count'; // 获取模块统计数量
export const AddModuleUrl = '/api/scenario/module/add'; // 添加模块
export const DeleteModuleUrl = '/api/scenario/module/delete'; // 删除模块
// export const GetEnvModuleUrl = '/api/scenario/module/env/tree'; // 获取环境的模块树
// export const DefinitionPageUrl = '/api/scenario/page'; // 接口定义列表
// export const AddDefinitionUrl = '/api/scenario/add'; // 添加接口定义
// export const UpdateDefinitionUrl = '/api/scenario/update'; // 更新接口定义
// export const GetDefinitionDetailUrl = '/api/scenario/get-detail'; // 获取接口定义详情
// export const TransferFileUrl = '/api/scenario/transfer'; // 文件转存
// export const TransferFileModuleOptionUrl = '/api/scenario/transfer/options'; // 文件转存目录
// export const UploadTempFileUrl = '/api/scenario/upload/temp/file'; // 临时文件上传
// export const DefinitionMockPageUrl = '/api/scenario/mock/page'; // mock列表
// export const UpdateMockStatusUrl = '/api/scenario/mock/enable/'; // 更新mock状态
// export const DeleteMockUrl = '/api/scenario/mock/delete'; // 刪除mock
// export const DeleteDefinitionUrl = '/api/scenario/delete-to-gc'; // 删除接口定义
// export const ImportDefinitionUrl = '/api/scenario/import'; // 导入接口定义
// export const SortDefinitionUrl = '/api/scenario/edit/pos'; // 接口定义拖拽
// export const BatchUpdateDefinitionUrl = '/api/scenario/batch-update'; // 批量更新接口定义
// export const BatchMoveDefinitionUrl = '/api/scenario/batch-move'; // 批量移动接口定义
// export const BatchDeleteDefinitionUrl = '/api/scenario/batch/delete-to-gc'; // 批量删除接口定义
// export const UpdateDefinitionScheduleUrl = '/api/scenario/schedule/update'; // 接口定义-定时同步-更新
// export const CheckDefinitionScheduleUrl = '/api/scenario/schedule/check'; // 接口定义-定时同步-检查 url 是否存在
// export const AddDefinitionScheduleUrl = '/api/scenario/schedule/add'; // 接口定义-定时同步-添加
// export const SwitchDefinitionScheduleUrl = '/api/scenario/schedule/switch'; // 接口定义-定时同步-开启关闭
// export const GetDefinitionScheduleUrl = '/api/scenario/schedule/get'; // 接口定义-定时同步-查询
// export const DeleteDefinitionScheduleUrl = '/api/scenario/schedule/delete'; // 接口定义-定时同步-删除
// export const DebugDefinitionUrl = '/api/scenario/debug'; // 接口定义-调试

View File

@ -3,6 +3,8 @@ export enum ApiTestRouteEnum {
API_TEST_DEBUG_MANAGEMENT = 'apiTestDebug', API_TEST_DEBUG_MANAGEMENT = 'apiTestDebug',
API_TEST_MANAGEMENT = 'apiTestManagement', API_TEST_MANAGEMENT = 'apiTestManagement',
API_TEST_MANAGEMENT_RECYCLE = 'apiTestManagementRecycle', API_TEST_MANAGEMENT_RECYCLE = 'apiTestManagementRecycle',
API_TEST_SCENARIO = 'apiTestScenario',
API_TEST_SCENARIO_RECYCLE = 'apiTestScenarioRecycle',
API_TEST_REPORT = 'apiTestReport', API_TEST_REPORT = 'apiTestReport',
} }

View File

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

View File

@ -28,6 +28,7 @@ export default {
'menu.apiTest.debug.debug': '调试', 'menu.apiTest.debug.debug': '调试',
'menu.apiTest.management': '接口管理', 'menu.apiTest.management': '接口管理',
'menu.apiTest.api': 'API列表', 'menu.apiTest.api': 'API列表',
'menu.apiTest.scenario': '接口场景',
'menu.apiTest.report': '接口报告', 'menu.apiTest.report': '接口报告',
'menu.uiTest': 'UI测试', 'menu.uiTest': 'UI测试',
'menu.workstation': '工作台', 'menu.workstation': '工作台',

View File

@ -0,0 +1,81 @@
import { RequestDefinitionStatus, RequestImportFormat, RequestImportType } from '@/enums/apiEnum';
import { BatchApiParams, ModuleTreeNode, TableQueryParams } from '../common';
import { ExecuteRequestParams, ResponseDefinition } from './common';
// 场景-更新模块参数
export interface ApiScenarioModuleUpdateParams {
id: string;
name: string;
}
// 场景-获取模块树参数
export interface ApiScenarioGetModuleParams {
keyword: string;
searchMode?: 'AND' | 'OR';
filter?: Record<string, any>;
combine?: Record<string, any>;
moduleIds: string[];
projectId: string;
versionId?: string;
refId?: string;
}
// 环境-选中的模块
export interface SelectedModule {
// 选中的模块
moduleId: string;
containChildModule: boolean; // 是否包含新增子模块
disabled: boolean;
}
// 环境-模块树
export interface EnvModule {
moduleTree: ModuleTreeNode[];
selectedModules: SelectedModule[];
}
// 定义列表查询参数
export interface ApiScenarioPageParams extends TableQueryParams {
id: string;
name: string;
protocol: string;
projectId: string;
versionId: string;
refId: string;
moduleIds: string[];
deleted: boolean;
}
export interface mockParams {
id: string;
projectId: string;
}
// 批量更新定义参数
export interface ApiScenarioBatchUpdateParams extends BatchApiParams {
type?: string;
append?: boolean;
method?: string;
versionId?: string;
tags?: string[];
customField?: Record<string, any>;
}
// 批量移动定义参数
export interface ApiScenarioBatchMoveParams extends BatchApiParams {
moduleId: string | number;
}
// 批量删除定义参数
export interface ApiScenarioBatchDeleteParams extends BatchApiParams {
deleteAll: boolean;
}
// 场景-定时同步-更新参数
export interface UpdateScheduleParams {
id: string;
taskId: string;
}

View File

@ -63,6 +63,28 @@ const ApiTest: AppRouteRecordRaw = {
], ],
}, },
}, },
{
path: 'scenario',
name: ApiTestRouteEnum.API_TEST_SCENARIO,
component: () => import('@/views/api-test/scenario/index.vue'),
meta: {
locale: 'menu.apiTest.scenario',
roles: ['*'],
isTopMenu: true,
},
},
{
path: 'scenarioRecycle',
name: ApiTestRouteEnum.API_TEST_SCENARIO_RECYCLE,
component: () => import('@/views/api-test/scenario/index.vue'),
meta: {
locale: 'menu.apiTest.scenario',
roles: ['*'],
isTopMenu: false,
},
},
{ {
path: 'report', path: 'report',
name: ApiTestRouteEnum.API_TEST_REPORT, name: ApiTestRouteEnum.API_TEST_REPORT,

View File

@ -0,0 +1,500 @@
<template>
<div>
<div class="mb-[8px] flex items-center gap-[8px]">
<a-input
v-model:model-value="moduleKeyword"
:placeholder="t('apiScenario.tree.selectorPlaceholder')"
allow-clear
/>
<a-dropdown v-if="!props.readOnly" @select="handleSelect">
<a-button type="primary">{{ t('apiScenario.createScenario') }}</a-button>
<template #content>
<a-doption value="newScenario">{{ t('apiScenario.createScenario') }}</a-doption>
<a-doption value="import">
{{ t('apiScenario.importScenario') }}
</a-doption>
</template>
</a-dropdown>
</div>
<div class="folder" @click="setActiveFolder('all')">
<div :class="allFolderClass">
<MsIcon type="icon-icon_folder_filled1" class="folder-icon" />
<div class="folder-name">{{ t('apiScenario.tree.folder.allScenario') }}</div>
<div class="folder-count">({{ allScenarioCount }})</div>
</div>
<div class="ml-auto flex items-center">
<a-tooltip :content="isExpandAll ? t('common.collapseAll') : t('common.expandAll')">
<MsButton type="icon" status="secondary" class="!mr-0 p-[4px]" @click="changeExpand">
<MsIcon :type="isExpandAll ? 'icon-icon_folder_collapse1' : 'icon-icon_folder_expansion1'" />
</MsButton>
</a-tooltip>
<template v-if="!props.readOnly">
<a-dropdown @select="handleSelect">
<MsButton type="icon" class="!mr-0 p-[2px]">
<MsIcon
type="icon-icon_create_planarity"
size="18"
class="text-[rgb(var(--primary-5))] hover:text-[rgb(var(--primary-4))]"
/>
</MsButton>
<template #content>
<a-doption value="newScenario">{{ t('apiScenario.createScenario') + '(暂未实现)' }}</a-doption>
<a-doption value="addModule">{{ t('apiScenario.createSubModule') }}</a-doption>
</template>
</a-dropdown>
<popConfirm
mode="add"
:all-names="rootModulesName"
parent-id="NONE"
:add-module-api="addModule"
@add-finish="initModules"
>
<span id="addModulePopSpan"></span>
</popConfirm>
</template>
</div>
</div>
<a-divider class="my-[8px]" />
<a-spin class="w-full" :loading="loading">
<MsTree
v-model:focus-node-key="focusNodeKey"
v-model:selected-keys="selectedKeys"
:data="folderTree"
:keyword="moduleKeyword"
:node-more-actions="folderMoreActions"
:default-expand-all="isExpandAll"
:expand-all="isExpandAll"
:empty-text="t('apiScenario.tree.noMatchModule')"
:virtual-list-props="virtualListProps"
:field-names="{
title: 'name',
key: 'id',
children: 'children',
count: 'count',
}"
:draggable="!props.readOnly"
block-node
title-tooltip-position="left"
@select="folderNodeSelect"
@more-action-select="handleFolderMoreSelect"
@more-actions-close="moreActionsClose"
@drop="handleDrop"
>
<template #title="nodeData">
<div :id="nodeData.id" class="inline-flex w-full">
<div class="one-line-text w-[calc(100%-32px)] text-[var(--color-text-1)]">{{ nodeData.name }}</div>
<div class="ml-[4px] text-[var(--color-text-4)]">({{ nodeData.count || 0 }})</div>
</div>
</template>
<template v-if="!props.readOnly" #extra="nodeData">
<!-- 默认模块的 id 是root默认模块不可编辑不可添加子模块 -->
<popConfirm
v-if="nodeData.id !== 'root' && nodeData.type === 'MODULE'"
mode="add"
:all-names="(nodeData.children || []).map((e: ModuleTreeNode) => e.name || '')"
:parent-id="nodeData.id"
:add-module-api="addModule"
@close="resetFocusNodeKey"
@add-finish="() => initModules()"
>
<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)]" />
</MsButton>
</popConfirm>
<popConfirm
v-if="nodeData.id !== 'root'"
mode="rename"
:node-type="nodeData.type"
:parent-id="nodeData.id"
:node-id="nodeData.id"
:field-config="{ field: renameFolderTitle }"
:all-names="(nodeData.children || []).map((e: ModuleTreeNode) => e.name || '')"
:update-module-api="updateModule"
@close="resetFocusNodeKey"
@rename-finish="initModules"
>
<span :id="`renameSpan${nodeData.id}`" class="relative"></span>
</popConfirm>
</template>
</MsTree>
</a-spin>
</div>
</template>
<script setup lang="ts">
import { computed, onBeforeMount, ref, watch } from 'vue';
import { Message } from '@arco-design/web-vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import MsTree from '@/components/business/ms-tree/index.vue';
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
import popConfirm from '@/views/api-test/components/popConfirm.vue';
import {
addModule,
deleteModule,
getModuleCount,
getModuleTree,
moveModule,
updateModule,
} from '@/api/modules/api-test/scenario';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import useAppStore from '@/store/modules/app';
import { mapTree } from '@/utils';
import { ModuleTreeNode } from '@/models/common';
const props = withDefaults(
defineProps<{
isExpandAll?: boolean; //
isShowScenario?: boolean; //
activeModule?: string | number; // key
readOnly?: boolean; //
activeNodeId?: string | number; // id
}>(),
{
activeModule: 'all',
readOnly: false,
}
);
const emit = defineEmits(['init', 'newScenario', 'import', 'folderNodeSelect', 'clickScenario', 'changeProtocol']);
const appStore = useAppStore();
const { t } = useI18n();
const { openModal } = useModal();
function handleSelect(value: string | number | Record<string, any> | undefined) {
switch (value) {
case 'newScenario':
emit('newScenario');
break;
case 'import':
emit('import');
break;
case 'addModule':
document.querySelector('#addModulePopSpan')?.dispatchEvent(new Event('click'));
break;
default:
break;
}
}
const virtualListProps = computed(() => {
if (props.readOnly) {
return {
height: 'calc(60vh - 343px)',
threshold: 200,
fixedSize: true,
buffer: 15, // 10 padding
};
}
return {
height: 'calc(100vh - 343px)',
threshold: 200,
fixedSize: true,
buffer: 15, // 10 padding
};
});
const moduleKeyword = ref('');
const folderTree = ref<ModuleTreeNode[]>([]);
const focusNodeKey = ref<string | number>('');
const selectedKeys = ref<Array<string | number>>([props.activeModule]);
const allFolderClass = computed(() =>
selectedKeys.value[0] === 'all' ? 'folder-text folder-text--active' : 'folder-text'
);
const loading = ref(false);
function setActiveFolder(id: string) {
selectedKeys.value = [id];
emit('folderNodeSelect', selectedKeys.value, []);
}
watch(
() => props.activeNodeId,
(val) => {
if (val) {
selectedKeys.value = [val];
}
}
);
function setFocusNodeKey(node: MsTreeNodeData) {
focusNodeKey.value = node.id || '';
}
const folderMoreActions: ActionsItem[] = [
{
label: 'common.rename',
eventTag: 'rename',
},
{
isDivider: true,
},
{
label: 'common.delete',
eventTag: 'delete',
danger: true,
},
];
const modulesCount = ref<Record<string, number>>({});
const allScenarioCount = computed(() => modulesCount.value.all || 0);
const isExpandAll = ref(props.isExpandAll);
const rootModulesName = ref<string[]>([]); //
/**
* 初始化模块树
* @param isSetDefaultKey 是否设置第一个节点为选中节点
*/
async function initModules(isSetDefaultKey = false) {
try {
loading.value = true;
const res = await getModuleTree({
keyword: moduleKeyword.value,
projectId: appStore.currentProjectId,
moduleIds: [],
});
const nodePathObj: Record<string, any> = {};
if (props.readOnly) {
folderTree.value = mapTree<ModuleTreeNode>(res, (e, fullPath) => {
//
nodePathObj[e.id] = {
path: e.path,
fullPath,
};
return {
...e,
hideMoreAction: true,
draggable: false,
disabled: e.id === selectedKeys.value[0],
};
});
} else {
folderTree.value = mapTree<ModuleTreeNode>(res, (e, fullPath) => {
//
nodePathObj[e.id] = {
path: e.path,
fullPath,
};
return {
...e,
hideMoreAction: e.id === 'root',
};
});
}
if (isSetDefaultKey) {
selectedKeys.value = [folderTree.value[0].id];
}
emit('init', folderTree.value, nodePathObj);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
loading.value = false;
}
}
async function initModuleCount() {
try {
const res = await getModuleCount({
keyword: moduleKeyword.value,
projectId: appStore.currentProjectId,
moduleIds: [],
});
modulesCount.value = res;
folderTree.value = mapTree<ModuleTreeNode>(folderTree.value, (node) => {
return {
...node,
count: res[node.id] || 0,
draggable: !props.readOnly,
disabled: props.readOnly ? node.id === selectedKeys.value[0] : false,
};
});
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
watch(
() => props.isExpandAll,
(val) => {
isExpandAll.value = val;
}
);
function changeExpand() {
isExpandAll.value = !isExpandAll.value;
}
/**
* 处理文件夹树节点选中事件
*/
function folderNodeSelect(_selectedKeys: (string | number)[], node: MsTreeNodeData) {
const offspringIds: string[] = [];
if (props.isShowScenario) {
mapTree(node.children || [], (e) => {
offspringIds.push(e.id);
return e;
});
}
emit('folderNodeSelect', _selectedKeys, offspringIds);
}
/**
* 删除文件夹
* @param node 节点信息
*/
function deleteFolder(node: MsTreeNodeData) {
openModal({
type: 'error',
title: t('apiScenario.module.deleteTipTitle', { name: node.name }),
content: t('apiScenario.module.deleteTipContent'),
okText: t('apiScenario.deleteConfirm'),
okButtonProps: {
status: 'danger',
},
maskClosable: false,
onBeforeOk: async () => {
try {
await deleteModule(node.id);
Message.success(t('apiScenario.deleteSuccess'));
await initModules();
initModuleCount();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
},
hideCancel: false,
});
}
const renamePopVisible = ref(false);
const renameFolderTitle = ref(''); //
function resetFocusNodeKey() {
focusNodeKey.value = '';
renamePopVisible.value = false;
renameFolderTitle.value = '';
}
/**
* 处理树节点更多按钮事件
* @param item
*/
function handleFolderMoreSelect(item: ActionsItem, node: MsTreeNodeData) {
switch (item.eventTag) {
case 'delete':
deleteFolder(node);
resetFocusNodeKey();
break;
case 'rename':
renameFolderTitle.value = node.name || '';
renamePopVisible.value = true;
document.querySelector(`#renameSpan${node.id}`)?.dispatchEvent(new Event('click'));
break;
default:
break;
}
}
/**
* 处理文件夹树节点拖拽事件
* @param tree 树数据
* @param dragNode 拖拽节点
* @param dropNode 释放节点
* @param dropPosition 释放位置
*/
async function handleDrop(
tree: MsTreeNodeData[],
dragNode: MsTreeNodeData,
dropNode: MsTreeNodeData,
dropPosition: number
) {
if (dragNode.id === 'root' || (dragNode.type === 'MODULE' && dropNode.id === 'root')) {
//
return;
}
try {
loading.value = true;
await moveModule({
dragNodeId: dragNode.id as string,
dropNodeId: dropNode.id || '',
dropPosition,
});
Message.success(t('apiScenario.moveSuccess'));
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
loading.value = false;
await initModules();
initModuleCount();
}
}
function moreActionsClose() {
if (!renamePopVisible.value) {
// key
resetFocusNodeKey();
}
}
onBeforeMount(async () => {
await initModules();
initModuleCount();
});
async function refresh() {
await initModules();
initModuleCount();
}
defineExpose({
refresh,
});
</script>
<style lang="less" scoped>
.folder {
@apply flex cursor-pointer items-center justify-between;
padding: 8px 4px;
border-radius: var(--border-radius-small);
&:hover {
background-color: rgb(var(--primary-1));
}
.folder-text {
@apply flex flex-1 cursor-pointer items-center;
.folder-icon {
margin-right: 4px;
color: var(--color-text-4);
}
.folder-name {
color: var(--color-text-1);
}
.folder-count {
margin-left: 4px;
color: var(--color-text-4);
}
}
.folder-text--active {
.folder-icon,
.folder-name,
.folder-count {
color: rgb(var(--primary-5));
}
}
}
:deep(#root ~ .arco-tree-node-drag-icon) {
@apply hidden;
}
</style>

View File

@ -0,0 +1,147 @@
<template>
<div class="rounded-2xl bg-white">
<div class="p-[24px] pb-[16px]">
<span>场景列表接口(标签页配置未实现)</span>
</div>
<a-divider class="!my-0" />
<div class="pageWrap">
<MsSplitBox :size="300" :max="0.5">
<template #first>
<div class="p-[24px] pb-0">
<div class="feature-case h-[100%]">
<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="setActiveFolder('recycle')">
<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>
</div>
</div>
</template>
<template #second>
<div class="p-[24px]">
<!-- <apiTable ref="apiTableRef" :active-module="activeFolder" :offspring-ids="offspringIds" />-->
</div>
</template>
</MsSplitBox>
</div>
</div>
</template>
<script setup lang="ts">
/**
* @description 接口测试-接口场景主页
*/
import { computed, ref } from 'vue';
import { Message } from '@arco-design/web-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 ApiTable from '@/views/api-test/management/components/management/api/apiTable.vue';
import { useI18n } from '@/hooks/useI18n';
import router from '@/router';
import { ModuleTreeNode } from '@/models/common';
import { ApiTestRouteEnum } from '@/enums/routeEnum';
const { t } = useI18n();
const folderTree = ref<ModuleTreeNode[]>([]);
const folderTreePathMap = ref<Record<string, any>>({});
const activeFolder = ref<string>('all');
const activeModule = ref<string>('all');
const offspringIds = ref<string[]>([]);
const addSubVisible = ref(false);
const isShowScenario = ref(false);
//
const getActiveClass = (type: string) => {
return activeFolder.value === type ? 'folder-text case-active' : 'folder-text';
};
const scenarioModuleTreeRef = ref();
function handleModuleInit(tree, _protocol: string, pathMap: Record<string, any>) {
folderTree.value = tree;
folderTreePathMap.value = pathMap;
}
function handleNodeSelect(keys: string[], _offspringIds: string[]) {
[activeModule.value] = keys;
offspringIds.value = _offspringIds;
}
</script>
<style scoped lang="less">
.pageWrap {
min-width: 1000px;
height: calc(100vh - 166px);
border-radius: var(--border-radius-large);
@apply bg-white;
.case {
padding: 8px 4px;
border-radius: var(--border-radius-small);
@apply flex cursor-pointer items-center justify-between;
&:hover {
background-color: rgb(var(--primary-1));
}
.folder-icon {
margin-right: 4px;
color: var(--color-text-4);
}
.folder-name {
color: var(--color-text-1);
}
.folder-count {
margin-left: 4px;
color: var(--color-text-4);
}
.case-active {
.folder-icon,
.folder-name,
.folder-count {
color: rgb(var(--primary-5));
}
}
.back {
margin-right: 8px;
width: 20px;
height: 20px;
border: 1px solid #ffffff;
background: linear-gradient(90deg, rgb(var(--primary-9)) 3.36%, #ffffff 100%);
box-shadow: 0 0 7px rgb(15 0 78 / 9%);
.arco-icon {
color: rgb(var(--primary-5));
}
@apply flex cursor-pointer items-center rounded-full;
}
}
}
.recycle {
@apply absolute bottom-0 bg-white pb-4;
:deep(.arco-divider-horizontal) {
margin: 8px 0;
}
.recycle-bin {
@apply bottom-0 flex items-center bg-white;
.recycle-count {
margin-left: 4px;
color: var(--color-text-4);
}
}
}
</style>

View File

@ -0,0 +1,20 @@
export default {
'apiScenario.createScenario': 'Create scenario',
'apiScenario.importScenario': 'Import scenario',
'apiScenario.tree.selectorPlaceholder': 'Please enter the module name',
'apiScenario.tree.folder.allScenario': 'All scenarios',
'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',
};

View File

@ -0,0 +1,19 @@
export default {
'apiScenario.createScenario': '新建场景',
'apiScenario.importScenario': '导入场景',
'apiScenario.tree.selectorPlaceholder': '请输入模块名称',
'apiScenario.tree.folder.allScenario': '全部场景',
'apiScenario.tree.showLeafNodeScenario': '显示子目录场景',
'apiScenario.tree.recycleBin': '回收站',
'apiScenario.tree.noMatchModule': '暂无匹配的模块/场景',
'apiScenario.createSubModule': '新建子模块',
'apiScenario.module.deleteTipTitle': '是否删除 {name} 模块?',
'apiScenario.module.deleteTipContent': '删除后,会同步删除模块下的所有场景,请谨慎操作.',
'apiScenario.deleteConfirm': '确认删除',
'apiScenario.deleteSuccess': '删除成功',
'apiScenario.moveSuccess': '移动成功',
};