feat(接口管理): api回收站
This commit is contained in:
parent
e07b33f1d8
commit
6857dd764c
|
@ -3,8 +3,10 @@ import {
|
|||
AddDefinitionScheduleUrl,
|
||||
AddDefinitionUrl,
|
||||
AddModuleUrl,
|
||||
BatchCleanOutApiUrl,
|
||||
BatchDeleteDefinitionUrl,
|
||||
BatchMoveDefinitionUrl,
|
||||
BatchRecoverApiUrl,
|
||||
BatchUpdateDefinitionUrl,
|
||||
CheckDefinitionScheduleUrl,
|
||||
DebugDefinitionUrl,
|
||||
|
@ -15,15 +17,19 @@ import {
|
|||
DeleteDefinitionUrl,
|
||||
DeleteMockUrl,
|
||||
DeleteModuleUrl,
|
||||
DeleteRecycleApiUrl,
|
||||
GetDefinitionDetailUrl,
|
||||
GetDefinitionScheduleUrl,
|
||||
GetEnvModuleUrl,
|
||||
GetModuleCountUrl,
|
||||
GetModuleOnlyTreeUrl,
|
||||
GetModuleTreeUrl,
|
||||
GetTrashModuleCountUrl,
|
||||
GetTrashModuleTreeUrl,
|
||||
ImportDefinitionUrl,
|
||||
MoveModuleUrl,
|
||||
OperationHistoryUrl,
|
||||
RecoverDefinitionUrl,
|
||||
RecoverOperationHistoryUrl,
|
||||
SaveOperationHistoryUrl,
|
||||
SortDefinitionUrl,
|
||||
|
@ -44,6 +50,7 @@ import {
|
|||
ApiDefinitionBatchMoveParams,
|
||||
ApiDefinitionBatchUpdateParams,
|
||||
ApiDefinitionCreateParams,
|
||||
ApiDefinitionDeleteParams,
|
||||
ApiDefinitionDetail,
|
||||
ApiDefinitionGetEnvModuleParams,
|
||||
ApiDefinitionGetModuleParams,
|
||||
|
@ -52,6 +59,7 @@ import {
|
|||
ApiDefinitionPageParams,
|
||||
ApiDefinitionUpdateModuleParams,
|
||||
ApiDefinitionUpdateParams,
|
||||
BatchRecoverApiParams,
|
||||
CheckScheduleParams,
|
||||
CreateImportApiDefinitionScheduleParams,
|
||||
DefinitionHistoryItem,
|
||||
|
@ -254,3 +262,36 @@ export function updateMockStatusPage(id: string) {
|
|||
export function deleteDefinitionMockMock(data: mockParams) {
|
||||
return MSR.post({ url: DeleteMockUrl, data });
|
||||
}
|
||||
|
||||
/**
|
||||
* 回收站
|
||||
*/
|
||||
// 回收站-恢复接口定义
|
||||
export function recoverDefinition(data: ApiDefinitionDeleteParams) {
|
||||
return MSR.post({ url: RecoverDefinitionUrl, data });
|
||||
}
|
||||
|
||||
// 回收站-彻底删除接口定义
|
||||
export function deleteRecycleApiList(id: string) {
|
||||
return MSR.get({ url: DeleteRecycleApiUrl, params: id });
|
||||
}
|
||||
|
||||
// 回收站-批量恢复接口定义
|
||||
export function batchRecoverDefinition(data: BatchRecoverApiParams) {
|
||||
return MSR.post({ url: BatchRecoverApiUrl, data });
|
||||
}
|
||||
|
||||
// 回收站-批量彻底删除接口定义
|
||||
export function batchCleanOutDefinition(data: BatchRecoverApiParams) {
|
||||
return MSR.post({ url: BatchCleanOutApiUrl, data });
|
||||
}
|
||||
|
||||
// 回收站-模块树
|
||||
export function getTrashModuleTree(data: ApiDefinitionGetModuleParams) {
|
||||
return MSR.post<ModuleTreeNode[]>({ url: GetTrashModuleTreeUrl, data });
|
||||
}
|
||||
|
||||
// 获取回收站模块统计数量
|
||||
export function getTrashModuleCount(data: ApiDefinitionGetModuleParams) {
|
||||
return MSR.post({ url: GetTrashModuleCountUrl, data });
|
||||
}
|
||||
|
|
|
@ -46,3 +46,13 @@ export const DeleteMockUrl = '/api/definition/mock/delete'; // 刪除mock
|
|||
* 接口引用关系
|
||||
*/
|
||||
export const DefinitionReferenceUrl = '/api/definition/get-reference'; // 获取接口引用关系
|
||||
|
||||
/**
|
||||
* api回收站
|
||||
*/
|
||||
export const RecoverDefinitionUrl = '/api/definition/recover'; // 回收站-接口定义-恢复
|
||||
export const DeleteRecycleApiUrl = '/api/definition/delete/'; // 回收站-接口定义-彻底删除
|
||||
export const BatchRecoverApiUrl = '/api/definition/batch-recover'; // 回收站-接口定义-批量恢复
|
||||
export const BatchCleanOutApiUrl = '/api/definition/batch/delete'; // 回收站-接口定义-批量彻底删除
|
||||
export const GetTrashModuleTreeUrl = '/api/definition/module/trash/tree'; // 回收站查找模块
|
||||
export const GetTrashModuleCountUrl = '/api/definition/module/trash/count'; // 获取回收站模块统计数量
|
||||
|
|
|
@ -2,6 +2,7 @@ export enum ApiTestRouteEnum {
|
|||
API_TEST = 'apiTest',
|
||||
API_TEST_DEBUG_MANAGEMENT = 'apiTestDebug',
|
||||
API_TEST_MANAGEMENT = 'apiTestManagement',
|
||||
API_TEST_MANAGEMENT_RECYCLE = 'apiTestManagementRecycle',
|
||||
API_TEST_REPORT = 'apiTestReport',
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ export default {
|
|||
'menu.apiTest.debug': '接口调试',
|
||||
'menu.apiTest.debug.debug': '调试',
|
||||
'menu.apiTest.management': '接口管理',
|
||||
'menu.apiTest.api': 'API列表',
|
||||
'menu.apiTest.report': '接口报告',
|
||||
'menu.uiTest': 'UI测试',
|
||||
'menu.workstation': '工作台',
|
||||
|
|
|
@ -262,3 +262,17 @@ export interface RecoverDefinitionParams {
|
|||
export interface DefinitionReferencePageParams extends TableQueryParams {
|
||||
resourceId: string;
|
||||
}
|
||||
|
||||
// 回收站-恢复接口定义参数
|
||||
export interface ApiDefinitionDeleteParams {
|
||||
id: string;
|
||||
projectId: string;
|
||||
protocol: string;
|
||||
deleteAll?: boolean;
|
||||
}
|
||||
|
||||
// 回收站-批量恢复接口定义参数
|
||||
export interface BatchRecoverApiParams extends ApiDefinitionBatchParams {
|
||||
projectId: string;
|
||||
moduleIds?: string[];
|
||||
}
|
||||
|
|
|
@ -43,6 +43,26 @@ const ApiTest: AppRouteRecordRaw = {
|
|||
isTopMenu: true,
|
||||
},
|
||||
},
|
||||
// 接口定义回收站
|
||||
{
|
||||
path: 'recycle',
|
||||
name: ApiTestRouteEnum.API_TEST_MANAGEMENT_RECYCLE,
|
||||
component: () => import('@/views/api-test/management/recycle.vue'),
|
||||
meta: {
|
||||
locale: 'common.recycle',
|
||||
roles: ['FUNCTIONAL_CASE:READ'],
|
||||
breadcrumbs: [
|
||||
{
|
||||
name: ApiTestRouteEnum.API_TEST_MANAGEMENT,
|
||||
locale: 'menu.apiTest.api',
|
||||
},
|
||||
{
|
||||
name: ApiTestRouteEnum.API_TEST_MANAGEMENT_RECYCLE,
|
||||
locale: 'common.recycle',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'report',
|
||||
name: ApiTestRouteEnum.API_TEST_REPORT,
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
/>
|
||||
<div class="mb-[8px] flex items-center gap-[8px]">
|
||||
<a-input v-model:model-value="moduleKeyword" :placeholder="t('apiTestManagement.searchTip')" allow-clear />
|
||||
<a-dropdown v-if="!props.readOnly" @select="handleSelect">
|
||||
<a-dropdown v-if="!props.readOnly && !props.trash" @select="handleSelect">
|
||||
<a-button type="primary">{{ t('apiTestManagement.newApi') }}</a-button>
|
||||
<template #content>
|
||||
<a-doption value="newApi">{{ t('apiTestManagement.newApi') }}</a-doption>
|
||||
|
@ -28,7 +28,7 @@
|
|||
</div>
|
||||
<div class="ml-auto flex items-center">
|
||||
<a-tooltip
|
||||
v-if="!props.readOnly"
|
||||
v-if="!props.readOnly && !props.trash"
|
||||
:content="isExpandApi ? t('apiTestManagement.collapseApi') : t('apiTestManagement.expandApi')"
|
||||
>
|
||||
<MsButton type="icon" status="secondary" class="!mr-0 p-[4px]" @click="changeApiExpand">
|
||||
|
@ -40,7 +40,7 @@
|
|||
<MsIcon :type="isExpandAll ? 'icon-icon_folder_collapse1' : 'icon-icon_folder_expansion1'" />
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
<template v-if="!props.readOnly">
|
||||
<template v-if="!props.readOnly && !props.trash">
|
||||
<a-dropdown @select="handleSelect">
|
||||
<MsButton type="icon" class="!mr-0 p-[2px]">
|
||||
<MsIcon
|
||||
|
@ -167,6 +167,8 @@
|
|||
getModuleCount,
|
||||
getModuleTree,
|
||||
getModuleTreeOnlyModules,
|
||||
getTrashModuleCount,
|
||||
getTrashModuleTree,
|
||||
moveModule,
|
||||
sortDefinition,
|
||||
updateDefinition,
|
||||
|
@ -187,11 +189,13 @@
|
|||
readOnly?: boolean; // 是否是只读模式
|
||||
activeNodeId?: string | number; // 当前选中节点 id
|
||||
isModal?: boolean; // 是否弹窗模式,只读且只可见模块树
|
||||
trash?: boolean; // 是否是回收站
|
||||
}>(),
|
||||
{
|
||||
activeModule: 'all',
|
||||
readOnly: false,
|
||||
isModal: false,
|
||||
trash: false,
|
||||
}
|
||||
);
|
||||
const emit = defineEmits(['init', 'newApi', 'import', 'folderNodeSelect', 'clickApiNode', 'changeProtocol']);
|
||||
|
@ -312,6 +316,7 @@
|
|||
(action) => action.eventTag === undefined || !['execute', 'share'].includes(action.eventTag)
|
||||
);
|
||||
const apiActions = folderMoreActions.filter((action) => action.eventTag !== 'shareModule');
|
||||
|
||||
function filterMoreActionFunc(actions, node) {
|
||||
if (node.type === 'MODULE') {
|
||||
return moduleActions;
|
||||
|
@ -333,7 +338,15 @@
|
|||
try {
|
||||
loading.value = true;
|
||||
let res;
|
||||
if (isExpandApi.value && !props.readOnly) {
|
||||
if (props.trash) {
|
||||
res = await getTrashModuleTree({
|
||||
// 回收站下的模块
|
||||
keyword: moduleKeyword.value,
|
||||
protocol: moduleProtocol.value,
|
||||
projectId: appStore.currentProjectId,
|
||||
moduleIds: [],
|
||||
});
|
||||
} else if (isExpandApi.value && !props.readOnly) {
|
||||
// 查看模块及模块下的请求
|
||||
res = await getModuleTree({
|
||||
keyword: moduleKeyword.value,
|
||||
|
@ -392,12 +405,22 @@
|
|||
|
||||
async function initModuleCount() {
|
||||
try {
|
||||
const res = await getModuleCount({
|
||||
keyword: moduleKeyword.value,
|
||||
protocol: moduleProtocol.value,
|
||||
projectId: appStore.currentProjectId,
|
||||
moduleIds: [],
|
||||
});
|
||||
let res;
|
||||
if (props.trash) {
|
||||
res = await getTrashModuleCount({
|
||||
keyword: moduleKeyword.value,
|
||||
protocol: moduleProtocol.value,
|
||||
projectId: appStore.currentProjectId,
|
||||
moduleIds: [],
|
||||
});
|
||||
} else {
|
||||
res = await getModuleCount({
|
||||
keyword: moduleKeyword.value,
|
||||
protocol: moduleProtocol.value,
|
||||
projectId: appStore.currentProjectId,
|
||||
moduleIds: [],
|
||||
});
|
||||
}
|
||||
modulesCount.value = res;
|
||||
folderTree.value = mapTree<ModuleTreeNode>(folderTree.value, (node) => {
|
||||
return {
|
||||
|
@ -597,23 +620,29 @@
|
|||
|
||||
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,
|
||||
|
@ -622,6 +651,7 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(#root ~ .arco-tree-node-drag-icon) {
|
||||
@apply hidden;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,487 @@
|
|||
<template>
|
||||
<div :class="['p-[16px_22px]', props.class]">
|
||||
<div class="mb-[16px] flex items-center justify-end">
|
||||
<div class="flex items-center gap-[8px]">
|
||||
<a-input-search
|
||||
v-model:model-value="keyword"
|
||||
:placeholder="t('apiTestManagement.searchPlaceholder')"
|
||||
allow-clear
|
||||
class="mr-[8px] w-[240px]"
|
||||
@search="loadApiList"
|
||||
@press-enter="loadApiList"
|
||||
/>
|
||||
<a-button type="outline" class="arco-btn-outline--secondary !p-[8px]" @click="loadApiList">
|
||||
<template #icon>
|
||||
<icon-refresh class="text-[var(--color-text-4)]" />
|
||||
</template>
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<ms-base-table
|
||||
v-bind="propsRes"
|
||||
:action-config="batchActions"
|
||||
:first-column-width="44"
|
||||
no-disable
|
||||
filter-icon-align-left
|
||||
v-on="propsEvent"
|
||||
@selected-change="handleTableSelect"
|
||||
@batch-action="handleTableBatch"
|
||||
>
|
||||
<template #methodFilter="{ columnConfig }">
|
||||
<a-trigger
|
||||
v-model:popup-visible="methodFilterVisible"
|
||||
trigger="click"
|
||||
@popup-visible-change="handleFilterHidden"
|
||||
>
|
||||
<MsButton type="text" class="arco-btn-text--secondary ml-[10px]" @click="methodFilterVisible = true">
|
||||
{{ t(columnConfig.title as string) }}
|
||||
<icon-down :class="methodFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
|
||||
</MsButton>
|
||||
<template #content>
|
||||
<div class="arco-table-filters-content">
|
||||
<div class="flex items-center justify-center px-[6px] py-[2px]">
|
||||
<a-checkbox-group v-model:model-value="methodFilters" direction="vertical" size="small">
|
||||
<a-checkbox v-for="key of RequestMethods" :key="key" :value="key">
|
||||
<apiMethodName :method="key" />
|
||||
</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-trigger>
|
||||
</template>
|
||||
<template #statusFilter="{ columnConfig }">
|
||||
<a-trigger
|
||||
v-model:popup-visible="statusFilterVisible"
|
||||
trigger="click"
|
||||
@popup-visible-change="handleFilterHidden"
|
||||
>
|
||||
<MsButton type="text" class="arco-btn-text--secondary ml-[10px]" @click="statusFilterVisible = true">
|
||||
{{ t(columnConfig.title as string) }}
|
||||
<icon-down :class="statusFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
|
||||
</MsButton>
|
||||
<template #content>
|
||||
<div class="arco-table-filters-content">
|
||||
<div class="flex items-center justify-center px-[6px] py-[2px]">
|
||||
<a-checkbox-group v-model:model-value="statusFilters" direction="vertical" size="small">
|
||||
<a-checkbox v-for="val of Object.values(RequestDefinitionStatus)" :key="val" :value="val">
|
||||
<apiStatus :status="val" />
|
||||
</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-trigger>
|
||||
</template>
|
||||
<template #deleteUserName="{ record }">
|
||||
<span type="text" class="px-0">{{ record.updateUserName || '-' }}</span>
|
||||
</template>
|
||||
<template #method="{ record }">
|
||||
<apiMethodName :method="record.method" is-tag />
|
||||
</template>
|
||||
<template #status="{ record }">
|
||||
<apiStatus :status="record.status" />
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<MsButton type="text" class="!mr-0" @click="recover(record)">
|
||||
{{ t('apiTestManagement.recycle.batchRecover') }}
|
||||
</MsButton>
|
||||
<a-divider direction="vertical" :margin="8"></a-divider>
|
||||
<MsButton type="text" class="!mr-0" @click="cleanOut(record)">
|
||||
{{ t('apiTestManagement.recycle.batchCleanOut') }}
|
||||
</MsButton>
|
||||
<a-divider direction="vertical" :margin="8"></a-divider>
|
||||
</template>
|
||||
</ms-base-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { FormInstance, Message } from '@arco-design/web-vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import type { BatchActionParams, BatchActionQueryParams, MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
||||
import apiStatus from '@/views/api-test/components/apiStatus.vue';
|
||||
|
||||
import {
|
||||
batchCleanOutDefinition,
|
||||
batchRecoverDefinition,
|
||||
deleteRecycleApiList,
|
||||
getDefinitionPage,
|
||||
recoverDefinition,
|
||||
} from '@/api/modules/api-test/management';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useModal from '@/hooks/useModal';
|
||||
import useTableStore from '@/hooks/useTableStore';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { characterLimit } from '@/utils';
|
||||
|
||||
import { ApiDefinitionDetail, BatchRecoverApiParams } from '@/models/apiTest/management';
|
||||
import { RequestDefinitionStatus, RequestMethods } from '@/enums/apiEnum';
|
||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||
|
||||
const props = defineProps<{
|
||||
class?: string;
|
||||
activeModule: string;
|
||||
offspringIds: string[];
|
||||
protocol: string; // 查看的协议类型
|
||||
readOnly?: boolean; // 是否是只读模式
|
||||
}>();
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
const { openModal } = useModal();
|
||||
|
||||
const folderTreePathMap = inject('folderTreePathMap');
|
||||
const keyword = ref('');
|
||||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'num',
|
||||
slotName: 'num',
|
||||
sortIndex: 1,
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
fixed: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.apiName',
|
||||
dataIndex: 'name',
|
||||
showTooltip: true,
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.apiType',
|
||||
dataIndex: 'method',
|
||||
slotName: 'method',
|
||||
titleSlotName: 'methodFilter',
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.apiStatus',
|
||||
dataIndex: 'status',
|
||||
slotName: 'status',
|
||||
titleSlotName: 'statusFilter',
|
||||
width: 130,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.path',
|
||||
dataIndex: 'path',
|
||||
showTooltip: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'common.tag',
|
||||
dataIndex: 'tags',
|
||||
isTag: true,
|
||||
isStringTag: true,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.version',
|
||||
dataIndex: 'versionName',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.deleteTime',
|
||||
dataIndex: 'deleteTime',
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.deleteUser',
|
||||
slotName: 'deleteUserName',
|
||||
dataIndex: 'deleteUser',
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: 'common.operation',
|
||||
slotName: 'action',
|
||||
dataIndex: 'operation',
|
||||
fixed: 'right',
|
||||
width: 150,
|
||||
},
|
||||
];
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
|
||||
getDefinitionPage,
|
||||
{
|
||||
columns: props.readOnly ? columns : [],
|
||||
scroll: { x: '100%' },
|
||||
tableKey: props.readOnly ? undefined : TableKeyEnum.API_TEST,
|
||||
showSetting: !props.readOnly,
|
||||
selectable: true,
|
||||
showSelectAll: !props.readOnly,
|
||||
draggable: props.readOnly ? undefined : { type: 'handle', width: 32 },
|
||||
heightUsed: 374,
|
||||
},
|
||||
(item) => ({
|
||||
...item,
|
||||
fullPath: folderTreePathMap?.[item.moduleId],
|
||||
deleteTime: dayjs(item.deleteTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||
})
|
||||
);
|
||||
const batchActions = {
|
||||
baseAction: [
|
||||
{
|
||||
label: 'apiTestManagement.recycle.batchRecover',
|
||||
eventTag: 'batchRecover',
|
||||
// permission: ['FUNCTIONAL_CASE:READ+DELETE'],
|
||||
},
|
||||
{
|
||||
label: 'apiTestManagement.recycle.batchCleanOut',
|
||||
eventTag: 'batchCleanOut',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const methodFilterVisible = ref(false);
|
||||
const methodFilters = ref(Object.keys(RequestMethods));
|
||||
const statusFilterVisible = ref(false);
|
||||
const statusFilters = ref(Object.keys(RequestDefinitionStatus));
|
||||
const moduleIds = computed(() => {
|
||||
if (props.activeModule === 'all') {
|
||||
return [];
|
||||
}
|
||||
return [props.activeModule];
|
||||
});
|
||||
const tableQueryParams = ref<any>();
|
||||
|
||||
function loadApiList() {
|
||||
const params = {
|
||||
keyword: keyword.value,
|
||||
projectId: appStore.currentProjectId,
|
||||
moduleIds: moduleIds.value,
|
||||
deleted: true,
|
||||
protocol: props.protocol,
|
||||
filter: { status: statusFilters.value, method: methodFilters.value },
|
||||
};
|
||||
setLoadListParams(params);
|
||||
loadList();
|
||||
tableQueryParams.value = {
|
||||
...params,
|
||||
current: propsRes.value.msPagination?.current,
|
||||
pageSize: propsRes.value.msPagination?.pageSize,
|
||||
};
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.activeModule,
|
||||
() => {
|
||||
resetSelector();
|
||||
loadApiList();
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.protocol,
|
||||
() => {
|
||||
resetSelector();
|
||||
loadApiList();
|
||||
}
|
||||
);
|
||||
|
||||
function handleFilterHidden(val: boolean) {
|
||||
if (!val) {
|
||||
loadApiList();
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
loadApiList();
|
||||
});
|
||||
|
||||
const tableSelected = ref<(string | number)[]>([]);
|
||||
const batchParams = ref<BatchActionQueryParams>({
|
||||
selectedIds: [],
|
||||
selectAll: false,
|
||||
excludeIds: [],
|
||||
currentSelectCount: 0,
|
||||
});
|
||||
|
||||
/**
|
||||
* 处理表格选中
|
||||
*/
|
||||
function handleTableSelect(arr: (string | number)[]) {
|
||||
tableSelected.value = arr;
|
||||
}
|
||||
|
||||
const showBatchModal = ref(false);
|
||||
const batchFormRef = ref<FormInstance>();
|
||||
const batchForm = ref({
|
||||
attr: '',
|
||||
value: '',
|
||||
values: [],
|
||||
});
|
||||
|
||||
function cancelBatch() {
|
||||
showBatchModal.value = false;
|
||||
batchFormRef.value?.resetFields();
|
||||
batchForm.value = {
|
||||
attr: '',
|
||||
value: '',
|
||||
values: [],
|
||||
};
|
||||
}
|
||||
|
||||
// 批量操作参数
|
||||
function getBatchParams(): BatchRecoverApiParams {
|
||||
return {
|
||||
excludeIds: batchParams.value.excludeIds,
|
||||
selectAll: batchParams.value.selectAll,
|
||||
selectIds: batchParams.value.selectedIds as string[],
|
||||
moduleIds: props.activeModule === 'all' ? [] : [props.activeModule],
|
||||
projectId: appStore.currentProjectId,
|
||||
protocol: props.protocol,
|
||||
condition: {
|
||||
keyword: keyword.value,
|
||||
filter: propsRes.value.filter,
|
||||
combine: batchParams.value.condition,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// 批量恢复
|
||||
async function batchRecover() {
|
||||
try {
|
||||
await batchRecoverDefinition(getBatchParams());
|
||||
Message.success(t('apiTestManagement.recycle.recoveredSuccessfully'));
|
||||
resetSelector();
|
||||
loadApiList();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
// 批量彻底删除
|
||||
async function batchCleanOut() {
|
||||
const title = t('apiTestManagement.recycle.batchDeleteApiTip', {
|
||||
count: batchParams.value.currentSelectCount || tableSelected.value.length,
|
||||
});
|
||||
openModal({
|
||||
type: 'error',
|
||||
title,
|
||||
content: t('apiTestManagement.recycle.cleanOutDeleteOnRecycleTip'),
|
||||
okText: t('common.confirmDelete'),
|
||||
cancelText: t('common.cancel'),
|
||||
okButtonProps: {
|
||||
status: 'danger',
|
||||
},
|
||||
onBeforeOk: async () => {
|
||||
try {
|
||||
await batchCleanOutDefinition(getBatchParams());
|
||||
Message.success(t('common.deleteSuccess'));
|
||||
resetSelector();
|
||||
loadApiList();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
},
|
||||
hideCancel: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理表格选中后批量操作
|
||||
* @param event 批量操作事件对象
|
||||
*/
|
||||
function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
|
||||
batchParams.value = { ...params };
|
||||
switch (event.eventTag) {
|
||||
case 'batchRecover':
|
||||
batchRecover();
|
||||
break;
|
||||
case 'batchCleanOut':
|
||||
batchCleanOut();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 列表彻底删除
|
||||
async function cleanOut(record: ApiDefinitionDetail) {
|
||||
openModal({
|
||||
type: 'error',
|
||||
title: t('apiTestManagement.recycle.completedDeleteCaseTitle', { name: characterLimit(record.name) }),
|
||||
content: t('apiTestManagement.recycle.cleanOutDeleteOnRecycleTip'),
|
||||
okText: t('common.confirmDelete'),
|
||||
cancelText: t('common.cancel'),
|
||||
okButtonProps: {
|
||||
status: 'danger',
|
||||
},
|
||||
onBeforeOk: async () => {
|
||||
try {
|
||||
await deleteRecycleApiList(record.id);
|
||||
Message.success(t('common.deleteSuccess'));
|
||||
resetSelector();
|
||||
loadApiList();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
},
|
||||
hideCancel: false,
|
||||
});
|
||||
}
|
||||
|
||||
// 列表恢复
|
||||
async function recover(record: ApiDefinitionDetail) {
|
||||
try {
|
||||
await recoverDefinition({
|
||||
id: record.id,
|
||||
projectId: record.projectId,
|
||||
protocol: props.protocol,
|
||||
});
|
||||
Message.success(t('apiTestManagement.recycle.recoveredSuccessfully'));
|
||||
resetSelector();
|
||||
loadApiList();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
loadApiList,
|
||||
});
|
||||
|
||||
const tableStore = useTableStore();
|
||||
await tableStore.initColumn(TableKeyEnum.API_TEST, columns, 'drawer', true);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.param-input:not(.arco-input-focus, .arco-select-view-focus)) {
|
||||
&:not(:hover) {
|
||||
border-color: transparent !important;
|
||||
|
||||
.arco-input::placeholder {
|
||||
@apply invisible;
|
||||
}
|
||||
|
||||
.arco-select-view-icon {
|
||||
@apply invisible;
|
||||
}
|
||||
|
||||
.arco-select-view-value {
|
||||
color: var(--color-text-brand);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,55 @@
|
|||
<template>
|
||||
<a-tabs v-model:active-key="activeTab" animation lazy-load class="ms-api-tab-nav">
|
||||
<a-tab-pane key="api" title="API" class="ms-api-tab-pane">
|
||||
<api
|
||||
ref="apiRef"
|
||||
:module-tree="props.moduleTree"
|
||||
:active-module="props.activeModule"
|
||||
:offspring-ids="props.offspringIds"
|
||||
:protocol="protocol"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="case" title="CASE" class="ms-api-tab-pane"></a-tab-pane>
|
||||
</a-tabs>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import api from './api/apiTable.vue';
|
||||
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
import { ModuleTreeNode } from '@/models/common';
|
||||
|
||||
const props = defineProps<{
|
||||
activeModule: string;
|
||||
offspringIds: string[];
|
||||
protocol: string;
|
||||
moduleTree: ModuleTreeNode[]; // 模块树
|
||||
}>();
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
||||
const activeTab = ref('api');
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ms-api-tab-nav {
|
||||
@apply h-full;
|
||||
|
||||
:deep(.arco-tabs-content) {
|
||||
height: calc(100% - 51px);
|
||||
|
||||
.arco-tabs-content-list {
|
||||
@apply h-full;
|
||||
|
||||
.arco-tabs-pane {
|
||||
@apply h-full;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.arco-tabs-nav) {
|
||||
border-bottom: 1px solid var(--color-text-n8);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -14,16 +14,15 @@
|
|||
@change-protocol="handleProtocolChange"
|
||||
/>
|
||||
</div>
|
||||
<!-- <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('caseManagement.featureCase.recycle') }}</div>
|
||||
<div class="folder-count">({{ recycleModulesCount.all || 0 }})</div></div
|
||||
>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="b-0 absolute w-full p-[24px]">
|
||||
<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('caseManagement.featureCase.recycle') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #second>
|
||||
<div class="relative flex h-full flex-col">
|
||||
|
@ -54,7 +53,7 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { provide } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||
|
@ -63,10 +62,14 @@
|
|||
import management from './components/management/index.vue';
|
||||
import moduleTree from './components/moduleTree.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { ModuleTreeNode } from '@/models/common';
|
||||
import { ApiTestRouteEnum } from '@/enums/routeEnum';
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
const activeModule = ref<string>('all');
|
||||
const folderTree = ref<ModuleTreeNode[]>([]);
|
||||
const folderTreePathMap = ref<Record<string, any>>({});
|
||||
|
@ -120,10 +123,88 @@
|
|||
}
|
||||
});
|
||||
|
||||
// 获取激活用例类型样式
|
||||
const getActiveClass = (type: string) => {
|
||||
return activeModule.value === type ? 'folder-text case-active' : 'folder-text';
|
||||
};
|
||||
|
||||
// 设置当前激活用例类型公共用例|全部用例|回收站
|
||||
const setActiveFolder = (type: string) => {
|
||||
if (type === 'recycle') {
|
||||
router.push({
|
||||
name: ApiTestRouteEnum.API_TEST_MANAGEMENT_RECYCLE,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/** 向子孙组件提供方法和值 */
|
||||
provide('setActiveApi', setActiveApi);
|
||||
provide('refreshModuleTree', refreshModuleTree);
|
||||
provide('folderTreePathMap', folderTreePathMap.value);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style lang="less" scoped>
|
||||
.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>
|
||||
|
|
|
@ -18,6 +18,13 @@ export default {
|
|||
'apiTestManagement.moveSearchTip': 'Please enter the module name to search',
|
||||
'apiTestManagement.noMatchModule': 'No matching module/api yet',
|
||||
'apiTestManagement.execute': 'Execute',
|
||||
'apiTestManagement.recycle.batchRecover': 'Recover',
|
||||
'apiTestManagement.recycle.recoveredSuccessfully': 'recovery was successful',
|
||||
'apiTestManagement.recycle.batchCleanOut': 'Completely delete',
|
||||
'apiTestManagement.recycle.completedDeleteCaseTitle': 'Confirm complete deletion {name}?',
|
||||
'apiTestManagement.recycle.cleanOutDeleteOnRecycleTip':
|
||||
'After deletion, the API cannot be restored. Please operate with caution!',
|
||||
'apiTestManagement.recycle.batchDeleteApiTip': 'Are you sure to completely delete the selected {count} interfaces?',
|
||||
'apiTestManagement.share': 'Share API',
|
||||
'apiTestManagement.shareModule': 'Share module',
|
||||
'apiTestManagement.doc': 'Document',
|
||||
|
@ -32,6 +39,8 @@ export default {
|
|||
'apiTestManagement.version': 'Version',
|
||||
'apiTestManagement.createTime': 'Creation time',
|
||||
'apiTestManagement.updateTime': 'Update time',
|
||||
'apiTestManagement.deleteTime': 'Delete time',
|
||||
'apiTestManagement.deleteUser': 'Delete user',
|
||||
'apiTestManagement.deprecate': 'Deprecated',
|
||||
'apiTestManagement.processing': 'Processing',
|
||||
'apiTestManagement.debugging': 'Debugging',
|
||||
|
|
|
@ -18,6 +18,12 @@ export default {
|
|||
'apiTestManagement.moveSearchTip': '请输入模块名称搜索',
|
||||
'apiTestManagement.noMatchModule': '暂无匹配的模块/接口',
|
||||
'apiTestManagement.execute': '执行',
|
||||
'apiTestManagement.recycle.batchRecover': '恢复',
|
||||
'apiTestManagement.recycle.recoveredSuccessfully': '恢复成功',
|
||||
'apiTestManagement.recycle.batchCleanOut': '彻底删除',
|
||||
'apiTestManagement.recycle.completedDeleteCaseTitle': '确认彻底删除 {name} 吗?',
|
||||
'apiTestManagement.recycle.cleanOutDeleteOnRecycleTip': '删除后,API无法恢复,请谨慎操作!',
|
||||
'apiTestManagement.recycle.batchDeleteApiTip': '确认彻底删除已选中的 {count} 个接口吗?',
|
||||
'apiTestManagement.share': '分享 API',
|
||||
'apiTestManagement.shareModule': '分享模块',
|
||||
'apiTestManagement.doc': '文档',
|
||||
|
@ -32,6 +38,8 @@ export default {
|
|||
'apiTestManagement.version': '版本',
|
||||
'apiTestManagement.createTime': '创建时间',
|
||||
'apiTestManagement.updateTime': '更新时间',
|
||||
'apiTestManagement.deleteTime': '删除时间',
|
||||
'apiTestManagement.deleteUser': '删除人',
|
||||
'apiTestManagement.deprecate': '已废弃',
|
||||
'apiTestManagement.processing': '进行中',
|
||||
'apiTestManagement.debugging': '联调中',
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
<template>
|
||||
<MsCard :min-width="1180" simple no-content-padding>
|
||||
<MsSplitBox :size="0.25" :max="0.5">
|
||||
<template #first>
|
||||
<div class="p-[24px]">
|
||||
<moduleTree
|
||||
ref="moduleTreeRef"
|
||||
:active-node-id="activeApi?.id"
|
||||
:trash="true"
|
||||
@init="handleModuleInit"
|
||||
@folder-node-select="handleNodeSelect"
|
||||
@change-protocol="handleProtocolChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #second>
|
||||
<div class="relative flex h-full flex-col">
|
||||
<management
|
||||
ref="managementRef"
|
||||
:module-tree="folderTree"
|
||||
:active-module="activeModule"
|
||||
:offspring-ids="offspringIds"
|
||||
:protocol="protocol"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</MsSplitBox>
|
||||
</MsCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { provide } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||
import { RequestParam } from '../components/requestComposition/index.vue';
|
||||
import moduleTree from './components/moduleTree.vue';
|
||||
import management from './components/recycle/index.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { ModuleTreeNode } from '@/models/common';
|
||||
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
const activeModule = ref<string>('all');
|
||||
const folderTree = ref<ModuleTreeNode[]>([]);
|
||||
const folderTreePathMap = ref<Record<string, any>>({});
|
||||
const offspringIds = ref<string[]>([]);
|
||||
const protocol = ref('HTTP');
|
||||
const activeApi = ref<RequestParam>();
|
||||
const moduleTreeRef = ref<InstanceType<typeof moduleTree>>();
|
||||
const managementRef = ref<InstanceType<typeof management>>();
|
||||
|
||||
function handleModuleInit(tree, _protocol: string, pathMap: Record<string, any>) {
|
||||
folderTree.value = tree;
|
||||
protocol.value = _protocol;
|
||||
folderTreePathMap.value = pathMap;
|
||||
}
|
||||
|
||||
function handleNodeSelect(keys: string[], _offspringIds: string[]) {
|
||||
[activeModule.value] = keys;
|
||||
offspringIds.value = _offspringIds;
|
||||
}
|
||||
|
||||
function handleProtocolChange(val: string) {
|
||||
protocol.value = val;
|
||||
}
|
||||
|
||||
function refreshModuleTree() {
|
||||
moduleTreeRef.value?.refresh();
|
||||
}
|
||||
|
||||
/** 向子孙组件提供方法和值 */
|
||||
provide('refreshModuleTree', refreshModuleTree);
|
||||
provide('folderTreePathMap', folderTreePathMap.value);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
Loading…
Reference in New Issue