feat(测试计划&接口测试): 测试计划部分接口调整&接口测试报告展示布局优化调整

This commit is contained in:
xinxin.wu 2024-05-08 21:43:00 +08:00 committed by Craftsman
parent d4bd63ca04
commit 1a58e75e4c
16 changed files with 242 additions and 228 deletions

View File

@ -2,7 +2,11 @@ import MSR from '@/api/http/index';
import {
addTestPlanModuleUrl,
AddTestPlanUrl,
archivedPlanUrl,
batchDeletePlanUrl,
deletePlanUrl,
DeleteTestPlanModuleUrl,
getStatisticalCountUrl,
GetTestPlanListUrl,
GetTestPlanModuleCountUrl,
GetTestPlanModuleUrl,
@ -13,7 +17,7 @@ import {
import type { CreateOrUpdateModule, UpdateModule } from '@/models/caseManagement/featureCase';
import type { CommonList, MoveModules, TableQueryParams } from '@/models/common';
import { ModuleTreeNode } from '@/models/common';
import type { AddTestPlanParams, TestPlanItem } from '@/models/testPlan/testPlan';
import type { AddTestPlanParams, TestPlanItem, UseCountType } from '@/models/testPlan/testPlan';
// 获取模块树
export function getTestPlanModule(params: TableQueryParams) {
@ -41,7 +45,7 @@ export function deletePlanModuleTree(id: string) {
}
// 获取模块数量
export function getPlanModulesCounts(data: TableQueryParams) {
export function getPlanModulesCount(data: TableQueryParams) {
return MSR.post({ url: GetTestPlanModuleCountUrl, data });
}
@ -54,3 +58,19 @@ export function getTestPlanList(data: TableQueryParams) {
export function addTestPlan(data: AddTestPlanParams) {
return MSR.post({ url: AddTestPlanUrl, data });
}
// 批量删除测试计划
export function batchDeletePlan(data: TableQueryParams) {
return MSR.post({ url: batchDeletePlanUrl, data });
}
// 删除测试计划
export function deletePlan(id: string | undefined) {
return MSR.get({ url: `${deletePlanUrl}/${id}` });
}
// 获取统计数量
export function getStatisticalCount(id: string) {
return MSR.get<UseCountType>({ url: `${getStatisticalCountUrl}/${id}` });
}
// 归档
export function archivedPlan(id: string | undefined) {
return MSR.get({ url: `${archivedPlanUrl}/${id}` });
}

View File

@ -14,3 +14,11 @@ export const GetTestPlanModuleCountUrl = '/test-plan/module/count';
export const GetTestPlanListUrl = '/test-plan/page';
// 创建测试计划
export const AddTestPlanUrl = '/test-plan/add';
// 批量删除测试计划
export const batchDeletePlanUrl = '/test-plan/batch-delete';
// 删除测试计划
export const deletePlanUrl = '/test-plan/delete';
// 获取统计数量
export const getStatisticalCountUrl = '/test-plan/getCount';
// 归档
export const archivedPlanUrl = '/test-plan/archived';

View File

@ -1,8 +1,5 @@
<template>
<div
ref="fullRef"
class="flex h-full flex-col rounded-[var(--border-radius-small)] bg-[var(--color-fill-1)] p-[12px]"
>
<div ref="fullRef" class="flex flex-col rounded-[var(--border-radius-small)] bg-[var(--color-fill-1)] p-[12px]">
<div v-if="showTitleLine" class="mb-[8px] flex items-center justify-between">
<div class="flex flex-wrap gap-[4px]">
<a-select
@ -257,6 +254,32 @@
return editor.getValue();
}
const innerHeight = ref<string | number>();
function handleEditorMount() {
if (!props.isAdaptive) {
innerHeight.value = props.height;
return;
}
const editorElement = editor.getDomNode();
if (!editorElement) {
return;
}
//
const lineHeight = editor.getOption(monaco.editor.EditorOption.lineHeight);
//
const lineCount = editor.getModel()?.getLineCount() || 10;
// @desc 3 2412px
const height = (lineCount + 3) * lineHeight;
innerHeight.value = height > 300 ? `${height + 24}px` : '300px';
if (height > 1000) {
innerHeight.value = `1000px`;
}
editor.layout();
}
const init = () => {
// TODO:
// Object.keys(MsCodeEditorTheme).forEach((e) => {
@ -276,6 +299,7 @@
...props,
language: props.language.toLowerCase(),
theme: currentTheme.value,
selectOnLineNumbers: true,
});
//
@ -284,6 +308,8 @@
emit('update:modelValue', value);
emit('change', value);
});
handleEditorMount();
};
watch(
@ -294,6 +320,7 @@
if (newValue !== value) {
editor.setValue(newValue);
}
handleEditorMount();
}
},
{ immediate: true }
@ -356,6 +383,8 @@
redo,
format,
getEncodingCode,
innerHeight,
handleEditorMount,
};
},
});
@ -363,11 +392,12 @@
<style lang="less" scoped>
.ms-code-editor {
width: 100%;
height: v-bind(innerheight);
@apply z-10;
// TODO:
width: v-bind(width);
height: v-bind(height);
min-height: 200px;
// height: 100vh;
// &.MS-text[data-mode-id='plaintext'] {
// :deep(.mtk1) {
// color: rgb(var(--primary-5));

View File

@ -130,4 +130,9 @@ export const editorProps = {
type: String as PropType<string>,
default: '',
},
// 是否自适应 开启后按照代码高度计算代码器高度最大1000px 未开启则按照外侧传入容器高度
isAdaptive: {
type: Boolean as PropType<boolean>,
default: false,
},
};

View File

@ -62,4 +62,15 @@ export interface SwitchListModel {
tooltipPosition: 'top' | 'tl' | 'tr' | 'bottom' | 'bl' | 'br' | 'left' | 'lt' | 'lb' | 'right' | 'rt' | 'rb';
}
// 获取统计数量
export interface UseCountType {
id: string;
passRate: string; // 通过率
functionalCaseCount: number; // 功能用例数
apiCaseCount: number; // 接口用例数
apiScenarioCount: number; // 接口场景数
bugCount: number; // Bug数量
testProgress: string; // 测试进度
}
export default {};

View File

@ -1,5 +1,5 @@
<template>
<div class="response flex min-w-[300px] flex-col">
<div class="response flex h-full min-w-[300px] flex-col">
<div :class="['response-head', activeLayout === 'vertical' ? 'border-t' : '']">
<slot name="titleLeft">
<div class="flex items-center justify-between">

View File

@ -136,6 +136,7 @@
<style lang="less" scoped>
.response-container {
margin-top: 8px;
height: calc(100% - 48px);
}
:deep(.arco-table-th) {
background-color: var(--color-text-n9);

View File

@ -32,6 +32,7 @@
show-language-change
show-charset-change
read-only
is-adaptive
>
<template #rightTitle>
<a-button type="outline" class="arco-btn-outline--secondary p-[0_8px]" size="mini" @click="emits('copy')">

View File

@ -9,6 +9,7 @@
:show-language-change="false"
:show-charset-change="false"
read-only
is-adaptive
>
</MsCodeEditor>
</template>

View File

@ -1,99 +1,101 @@
<template>
<div class="flex h-[calc(100%-64px)] flex-col" @click.stop="() => {}">
<div v-if="isShowLoopControl" class="my-4 flex items-center justify-start" @click.stop="() => {}">
<a-pagination
v-model:page-size="controlPageSize"
v-model:current="controlCurrent"
:total="controlTotal"
size="mini"
show-total
:show-jumper="controlTotal > 5"
@change="loadControlLoop"
/>
<!-- <loopPagination v-model:current-loop="controlCurrent" :loop-total="controlTotal" /> -->
</div>
<div class="mt-4 flex w-full items-center justify-between rounded bg-[var(--color-text-n9)] p-4">
<div class="font-medium">
<span
:class="{ 'text-[rgb(var(--primary-5))]': activeType === 'ResContent' }"
@click.stop="setActiveType('ResContent')"
>{{ t('report.detail.api.resContent') }}</span
>
<span
v-if="total > 0"
:class="{ 'text-[rgb(var(--primary-5))]': activeType === 'SubRequest' }"
@click.stop="setActiveType('SubRequest')"
>
<a-divider direction="vertical" :margin="8"></a-divider>
{{ t('report.detail.api.subRequest') }}</span
>
<div class="flex flex-col" @click.stop="() => {}">
<div class="response-header">
<div v-if="isShowLoopControl" class="my-4 flex items-center justify-start" @click.stop="() => {}">
<a-pagination
v-model:page-size="controlPageSize"
v-model:current="controlCurrent"
:total="controlTotal"
size="mini"
show-total
:show-jumper="controlTotal > 5"
@change="loadControlLoop"
/>
<!-- <loopPagination v-model:current-loop="controlCurrent" :loop-total="controlTotal" /> -->
</div>
<div class="flex flex-row gap-6 text-center">
<a-popover position="left" content-class="response-popover-content">
<div
v-if="activeStepDetailCopy?.content?.responseResult.responseCode"
class="one-line-text max-w-[200px]"
:style="{ color: statusCodeColor }"
<div class="flex w-full items-center justify-between rounded bg-[var(--color-text-n9)] p-4">
<div class="font-medium">
<span
:class="{ 'text-[rgb(var(--primary-5))]': activeType === 'ResContent' }"
@click.stop="setActiveType('ResContent')"
>{{ t('report.detail.api.resContent') }}</span
>
{{ activeStepDetailCopy?.content?.responseResult.responseCode || '-' }}
</div>
<template #content>
<div class="flex items-center gap-[8px] text-[14px]">
<div class="text-[var(--color-text-4)]">{{ t('apiTestDebug.statusCode') }}</div>
<div :style="{ color: statusCodeColor }">
{{ activeStepDetailCopy?.content?.responseResult.responseCode || '-' }}
</div>
</div>
</template>
</a-popover>
<a-popover position="left" content-class="w-[400px]">
<div v-if="timingInfo?.responseTime" class="one-line-text text-[rgb(var(--success-7))]">
{{ timingInfo?.responseTime || 0 }} ms
</div>
<template #content>
<div class="mb-[8px] flex items-center gap-[8px] text-[14px]">
<div class="text-[var(--color-text-4)]">{{ t('apiTestDebug.responseTime') }}</div>
<div class="text-[rgb(var(--success-7))]"> {{ timingInfo?.responseTime }} ms </div>
</div>
<responseTimeLine v-if="timingInfo" :response-timing="timingInfo" />
</template>
</a-popover>
<a-popover position="left" content-class="response-popover-content">
<div
v-if="activeStepDetail?.content?.responseResult.responseSize"
class="one-line-text text-[rgb(var(--success-7))]"
<span
v-if="total > 0"
:class="{ 'text-[rgb(var(--primary-5))]': activeType === 'SubRequest' }"
@click.stop="setActiveType('SubRequest')"
>
{{ activeStepDetail?.content?.responseResult.responseSize || '-' }} bytes
</div>
<template #content>
<div class="flex items-center gap-[8px] text-[14px]">
<div class="text-[var(--color-text-4)]">{{ t('apiTestDebug.responseSize') }}</div>
<div class="one-line-text text-[rgb(var(--success-7))]">
{{ activeStepDetail?.content?.responseResult.responseSize }} bytes
</div>
<a-divider direction="vertical" :margin="8"></a-divider>
{{ t('report.detail.api.subRequest') }}</span
>
</div>
<div class="flex flex-row gap-6 text-center">
<a-popover position="left" content-class="response-popover-content">
<div
v-if="activeStepDetailCopy?.content?.responseResult.responseCode"
class="one-line-text max-w-[200px]"
:style="{ color: statusCodeColor }"
>
{{ activeStepDetailCopy?.content?.responseResult.responseCode || '-' }}
</div>
</template>
</a-popover>
<a-popover position="left" content-class="response-popover-content">
<div v-if="props.showType && props.showType !== 'CASE'" class="one-line-text max-w-[150px]">{{
props.environmentName
}}</div>
<template #content>
<div v-if="props.showType && props.showType !== 'CASE'" class="one-line-text">{{
<template #content>
<div class="flex items-center gap-[8px] text-[14px]">
<div class="text-[var(--color-text-4)]">{{ t('apiTestDebug.statusCode') }}</div>
<div :style="{ color: statusCodeColor }">
{{ activeStepDetailCopy?.content?.responseResult.responseCode || '-' }}
</div>
</div>
</template>
</a-popover>
<a-popover position="left" content-class="w-[400px]">
<div v-if="timingInfo?.responseTime" class="one-line-text text-[rgb(var(--success-7))]">
{{ timingInfo?.responseTime || 0 }} ms
</div>
<template #content>
<div class="mb-[8px] flex items-center gap-[8px] text-[14px]">
<div class="text-[var(--color-text-4)]">{{ t('apiTestDebug.responseTime') }}</div>
<div class="text-[rgb(var(--success-7))]"> {{ timingInfo?.responseTime }} ms </div>
</div>
<responseTimeLine v-if="timingInfo" :response-timing="timingInfo" />
</template>
</a-popover>
<a-popover position="left" content-class="response-popover-content">
<div
v-if="activeStepDetail?.content?.responseResult.responseSize"
class="one-line-text text-[rgb(var(--success-7))]"
>
{{ activeStepDetail?.content?.responseResult.responseSize || '-' }} bytes
</div>
<template #content>
<div class="flex items-center gap-[8px] text-[14px]">
<div class="text-[var(--color-text-4)]">{{ t('apiTestDebug.responseSize') }}</div>
<div class="one-line-text text-[rgb(var(--success-7))]">
{{ activeStepDetail?.content?.responseResult.responseSize }} bytes
</div>
</div>
</template>
</a-popover>
<a-popover position="left" content-class="response-popover-content">
<div v-if="props.showType && props.showType !== 'CASE'" class="one-line-text max-w-[150px]">{{
props.environmentName
}}</div>
</template>
</a-popover>
<template #content>
<div v-if="props.showType && props.showType !== 'CASE'" class="one-line-text">{{
props.environmentName
}}</div>
</template>
</a-popover>
</div>
</div>
<div v-if="activeType === 'SubRequest'" class="my-4 flex justify-start">
<MsPagination
v-model:page-size="pageSize"
v-model:current="current"
:total="total"
size="mini"
@change="loadLoop"
/>
</div>
</div>
<div v-if="activeType === 'SubRequest'" class="my-4 flex justify-start">
<MsPagination
v-model:page-size="pageSize"
v-model:current="current"
:total="total"
size="mini"
@change="loadLoop"
/>
</div>
<!-- 平铺 -->
<a-spin v-if="props.mode === 'tiled'" class="w-full" :loading="loading">
@ -108,8 +110,8 @@
</Suspense>
</a-spin>
<!-- 响应内容tab -->
<div v-else class="h-full">
<a-spin :loading="loading" class="h-full w-full pb-1">
<div v-else>
<a-spin :loading="loading" class="w-full pb-1">
<result
v-model:active-tab="activeTab"
:request-result="activeStepDetailCopy?.content"
@ -371,4 +373,10 @@
});
</script>
<style scoped lang="less"></style>
<style scoped lang="less">
.response-header {
position: sticky;
top: 0;
z-index: 9999999;
}
</style>

View File

@ -29,10 +29,10 @@
</div>
<transition name="fade">
<div v-show="!expandIds.includes(item.value) && isShowContent(item.value)" class="expandContent">
<div v-if="item.value === ResponseComposition.BODY" class="res-item">
<div v-if="item.value === ResponseComposition.BODY">
<ResBody ref="resBodyRef" :request-result="props.requestResult" @copy="copyScript" />
</div>
<div v-if="!expandIds.includes(item.value) && item.value === ResponseComposition.CONSOLE" class="res-item">
<div v-if="!expandIds.includes(item.value) && item.value === ResponseComposition.CONSOLE">
<ResConsole :console="props.console?.trim()" />
</div>
<div v-if="!expandIds.includes(item.value) && item.value === ResponseComposition.HEADER" class="">
@ -49,7 +49,7 @@
</div>
</div>
</transition>
<a-divider v-if="isShowContent(item.value)" type="dashed" :margin="0" class="!mb-4"></a-divider>
<a-divider v-if="isShowContent(item.value)" type="dashed" :margin="0"></a-divider>
</div>
</div>
</div>
@ -155,10 +155,13 @@
.tiledList {
@apply px-4;
.menu-list-wrapper {
@apply mt-4;
.menu-list {
height: 32px;
// border-bottom: 1px dashed var(--color-text-n8);
position: sticky;
top: 50px;
z-index: 999999;
height: 40px;
line-height: 40px;
background: white;
@apply flex items-start justify-between px-4;
.menu-title {
@apply font-medium;
@ -166,9 +169,6 @@
}
.expandContent {
background: var(--color-text-n9);
.res-item {
height: 210px;
}
}
}
}

View File

@ -7,6 +7,7 @@
:title="props.scenarioDetail?.name"
show-full-screen
:unmount-on-close="true"
no-content-padding
>
<template #headerLeft>
<ConditionStatus
@ -15,7 +16,7 @@
:status="props.scenarioDetail?.stepType"
/>
</template>
<div>
<div class="px-[12px]">
<StepDetailContent
mode="tiled"
:show-type="props.showType"
@ -102,4 +103,7 @@
padding-left: 0 !important;
}
}
:deep(.arco-drawer-body) {
padding: 0 16px;
}
</style>

View File

@ -499,10 +499,6 @@
height: 1px;
background: var(--color-text-n8);
}
.foldContent {
height: 100%;
height: 1000px;
}
:deep(.step-tree-node-title) {
width: 100%;
}

View File

@ -44,6 +44,7 @@
import { useVModel } from '@vueuse/core';
import { Message } from '@arco-design/web-vue';
import { archivedPlan, deletePlan } from '@/api/modules/test-plan/testPlan';
import { useI18n } from '@/hooks/useI18n';
import { characterLimit } from '@/utils';
@ -59,6 +60,7 @@
const emit = defineEmits<{
(e: 'update:visible', val: boolean): void;
(e: 'success'): void;
}>();
const showModalVisible = useVModel(props, 'visible', emit);
@ -68,11 +70,21 @@
}
const confirmLoading = ref<boolean>(false);
function confirmHandler(isDelete: boolean) {
async function confirmHandler(isDelete: boolean) {
try {
confirmLoading.value = true;
if (isDelete) {
await deletePlan(props.record?.id);
} else {
await archivedPlan(props.record?.id);
}
Message.success(isDelete ? t('common.deleteSuccess') : t('common.batchArchiveSuccess'));
emit('success');
} catch (error) {
console.log(error);
} finally {
confirmLoading.value = false;
}
}

View File

@ -95,7 +95,7 @@
>
</template>
<template #statusFilter="{ columnConfig }">
<a-trigger v-model:popup-visible="statusFilterVisible" @popup-visible-change="handleFilterHidden">
<a-trigger v-model:popup-visible="statusFilterVisible" trigger="click" @popup-visible-change="handleFilterHidden">
<a-button type="text" class="arco-btn-text--secondary" @click="statusFilterVisible = true">
{{ t(columnConfig.title as string) }}
<icon-down :class="statusFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
@ -242,7 +242,7 @@
@save="handleMoveOrCopy"
/>
<ScheduledModal v-model:visible="showScheduledTaskModal" />
<ActionModal v-model:visible="showStatusDeleteModal" :record="activeRecord" />
<ActionModal v-model:visible="showStatusDeleteModal" :record="activeRecord" @success="fetchData()" />
<BatchEditModal
v-model:visible="showEditModel"
:batch-params="batchParams"
@ -274,13 +274,12 @@
import StatusProgress from './statusProgress.vue';
import statusTag from '@/views/case-management/caseReview/components/statusTag.vue';
import { getTestPlanList, getTestPlanModule } from '@/api/modules/test-plan/testPlan';
import { archivedPlan, batchDeletePlan, getTestPlanList, getTestPlanModule } from '@/api/modules/test-plan/testPlan';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import { useAppStore, useTableStore } from '@/store';
import { characterLimit } from '@/utils';
import { ReviewStatus } from '@/models/caseManagement/caseReview';
import type { planStatusType, TestPlanItem } from '@/models/testPlan/testPlan';
import { ColumnEditTypeEnum, TableKeyEnum } from '@/enums/tableEnum';
import { testPlanTypeEnum } from '@/enums/testPlanEnum';
@ -537,110 +536,8 @@
eventTag: 'delete',
},
];
// TODO
const data = [
{
id: '100944',
projectId: 'string',
num: '100944',
name: '系统示例',
status: 'COMPLETED',
tags: ['string'],
schedule: 'string',
createUser: 'string',
createTime: 'string',
moduleName: 'string',
moduleId: 'string',
passCount: 0,
unPassCount: 0,
reviewedCount: 0,
underReviewedCount: 0,
childrenCount: 2,
statusDetail: {
tolerance: 100,
UNPENDING: 100,
RUNNING: 30,
SUCCESS: 30,
ERROR: 30,
executionProgress: '100%',
},
useCaseCount: {
caseCount: 3,
apiCount: 3,
scenarioCount: 3,
},
// children: [
// {
// id: '100945',
// projectId: 'string',
// num: '100945',
// name: '',
// status: 'COMPLETED',
// tags: ['string'],
// schedule: 'string',
// createUser: 'string',
// createTime: 'string',
// moduleName: 'string',
// moduleId: 'string',
// testPlanItem: [],
// testPlanGroupId: 'string',
// passCount: 0,
// unPassCount: 0,
// reviewedCount: 0,
// underReviewedCount: 0,
// childrenCount: 0,
// useCaseCount: {
// caseCount: 3,
// apiCount: 3,
// scenarioCount: 3,
// },
// statusDetail: {
// tolerance: 100,
// UNPENDING: 100,
// RUNNING: 30,
// SUCCESS: 30,
// ERROR: 30,
// executionProgress: '100%',
// },
// },
// {
// id: '100955',
// projectId: 'string',
// num: '100955',
// name: '',
// status: 'COMPLETED',
// tags: ['string'],
// schedule: 'string',
// createUser: 'string',
// createTime: 'string',
// moduleName: 'string',
// moduleId: 'string',
// testPlanItem: [],
// testPlanGroupId: 'string',
// passCount: 0,
// unPassCount: 0,
// reviewedCount: 0,
// underReviewedCount: 0,
// childrenCount: 0,
// useCaseCount: {
// caseCount: 3,
// apiCount: 3,
// scenarioCount: 3,
// },
// statusDetail: {
// tolerance: 100,
// UNPENDING: 100,
// RUNNING: 30,
// SUCCESS: 30,
// ERROR: 30,
// executionProgress: '100%',
// },
// },
// ],
},
];
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector, setProps } = useTable(
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
getTestPlanList,
{
tableKey: TableKeyEnum.TEST_PLAN_ALL_TABLE,
@ -821,6 +718,19 @@
onBeforeOk: async () => {
try {
const { selectedIds, selectAll, excludeIds } = batchParams.value;
await batchDeletePlan({
projectId: appStore.currentProjectId,
selectIds: selectedIds || [],
excludeIds: excludeIds || [],
moduleIds: props.activeFolder === 'all' ? [] : [props.activeFolder, ...props.offspringIds],
condition: {
keyword: keyword.value,
filter: {},
combine: batchParams.value.condition,
},
selectAll: !!selectAll,
type: showType.value,
});
Message.success(t('common.deleteSuccess'));
fetchData();
} catch (error) {
@ -908,6 +818,7 @@
},
onBeforeOk: async () => {
try {
await archivedPlan(record.id);
Message.success(t('common.batchArchiveSuccess'));
fetchData();
} catch (error) {

View File

@ -105,12 +105,12 @@
import TestPlanTree from './components/testPlanTree.vue';
import CreateAndEditPlanDrawer from './createAndEditPlanDrawer.vue';
import { createPlanModuleTree } from '@/api/modules/test-plan/testPlan';
import { createPlanModuleTree, getPlanModulesCount } from '@/api/modules/test-plan/testPlan';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import type { CaseModuleQueryParams, CreateOrUpdateModule, ValidateInfo } from '@/models/caseManagement/featureCase';
import type { ModuleTreeNode } from '@/models/common';
import type { CreateOrUpdateModule } from '@/models/caseManagement/featureCase';
import { ModuleTreeNode, TableQueryParams } from '@/models/common';
import Message from '@arco-design/web-vue/es/message';
@ -202,7 +202,13 @@
/**
* 刷新模块树的统计数量
*/
function initModulesCount(params: any) {}
async function initModulesCount(params: TableQueryParams) {
try {
modulesCount.value = await getPlanModulesCount(params);
} catch (error) {
console.log(error);
}
}
const showPlanDrawer = ref(false);
function handleSelect(value: string | number | Record<string, any> | undefined) {