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 { import {
addTestPlanModuleUrl, addTestPlanModuleUrl,
AddTestPlanUrl, AddTestPlanUrl,
archivedPlanUrl,
batchDeletePlanUrl,
deletePlanUrl,
DeleteTestPlanModuleUrl, DeleteTestPlanModuleUrl,
getStatisticalCountUrl,
GetTestPlanListUrl, GetTestPlanListUrl,
GetTestPlanModuleCountUrl, GetTestPlanModuleCountUrl,
GetTestPlanModuleUrl, GetTestPlanModuleUrl,
@ -13,7 +17,7 @@ import {
import type { CreateOrUpdateModule, UpdateModule } from '@/models/caseManagement/featureCase'; import type { CreateOrUpdateModule, UpdateModule } from '@/models/caseManagement/featureCase';
import type { CommonList, MoveModules, TableQueryParams } from '@/models/common'; import type { CommonList, MoveModules, TableQueryParams } from '@/models/common';
import { ModuleTreeNode } 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) { 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 }); return MSR.post({ url: GetTestPlanModuleCountUrl, data });
} }
@ -54,3 +58,19 @@ export function getTestPlanList(data: TableQueryParams) {
export function addTestPlan(data: AddTestPlanParams) { export function addTestPlan(data: AddTestPlanParams) {
return MSR.post({ url: AddTestPlanUrl, data }); 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 GetTestPlanListUrl = '/test-plan/page';
// 创建测试计划 // 创建测试计划
export const AddTestPlanUrl = '/test-plan/add'; 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> <template>
<div <div ref="fullRef" class="flex flex-col rounded-[var(--border-radius-small)] bg-[var(--color-fill-1)] p-[12px]">
ref="fullRef"
class="flex h-full 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 v-if="showTitleLine" class="mb-[8px] flex items-center justify-between">
<div class="flex flex-wrap gap-[4px]"> <div class="flex flex-wrap gap-[4px]">
<a-select <a-select
@ -257,6 +254,32 @@
return editor.getValue(); 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 = () => { const init = () => {
// TODO: // TODO:
// Object.keys(MsCodeEditorTheme).forEach((e) => { // Object.keys(MsCodeEditorTheme).forEach((e) => {
@ -276,6 +299,7 @@
...props, ...props,
language: props.language.toLowerCase(), language: props.language.toLowerCase(),
theme: currentTheme.value, theme: currentTheme.value,
selectOnLineNumbers: true,
}); });
// //
@ -284,6 +308,8 @@
emit('update:modelValue', value); emit('update:modelValue', value);
emit('change', value); emit('change', value);
}); });
handleEditorMount();
}; };
watch( watch(
@ -294,6 +320,7 @@
if (newValue !== value) { if (newValue !== value) {
editor.setValue(newValue); editor.setValue(newValue);
} }
handleEditorMount();
} }
}, },
{ immediate: true } { immediate: true }
@ -356,6 +383,8 @@
redo, redo,
format, format,
getEncodingCode, getEncodingCode,
innerHeight,
handleEditorMount,
}; };
}, },
}); });
@ -363,11 +392,12 @@
<style lang="less" scoped> <style lang="less" scoped>
.ms-code-editor { .ms-code-editor {
width: 100%;
height: v-bind(innerheight);
@apply z-10; @apply z-10;
// TODO: // height: 100vh;
width: v-bind(width);
height: v-bind(height);
min-height: 200px;
// &.MS-text[data-mode-id='plaintext'] { // &.MS-text[data-mode-id='plaintext'] {
// :deep(.mtk1) { // :deep(.mtk1) {
// color: rgb(var(--primary-5)); // color: rgb(var(--primary-5));

View File

@ -130,4 +130,9 @@ export const editorProps = {
type: String as PropType<string>, type: String as PropType<string>,
default: '', 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'; 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 {}; export default {};

View File

@ -1,5 +1,5 @@
<template> <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' : '']"> <div :class="['response-head', activeLayout === 'vertical' ? 'border-t' : '']">
<slot name="titleLeft"> <slot name="titleLeft">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">

View File

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

View File

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

View File

@ -1,5 +1,6 @@
<template> <template>
<div class="flex h-[calc(100%-64px)] flex-col" @click.stop="() => {}"> <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="() => {}"> <div v-if="isShowLoopControl" class="my-4 flex items-center justify-start" @click.stop="() => {}">
<a-pagination <a-pagination
v-model:page-size="controlPageSize" v-model:page-size="controlPageSize"
@ -12,7 +13,7 @@
/> />
<!-- <loopPagination v-model:current-loop="controlCurrent" :loop-total="controlTotal" /> --> <!-- <loopPagination v-model:current-loop="controlCurrent" :loop-total="controlTotal" /> -->
</div> </div>
<div class="mt-4 flex w-full items-center justify-between rounded bg-[var(--color-text-n9)] p-4"> <div class="flex w-full items-center justify-between rounded bg-[var(--color-text-n9)] p-4">
<div class="font-medium"> <div class="font-medium">
<span <span
:class="{ 'text-[rgb(var(--primary-5))]': activeType === 'ResContent' }" :class="{ 'text-[rgb(var(--primary-5))]': activeType === 'ResContent' }"
@ -95,6 +96,7 @@
@change="loadLoop" @change="loadLoop"
/> />
</div> </div>
</div>
<!-- 平铺 --> <!-- 平铺 -->
<a-spin v-if="props.mode === 'tiled'" class="w-full" :loading="loading"> <a-spin v-if="props.mode === 'tiled'" class="w-full" :loading="loading">
<Suspense> <Suspense>
@ -108,8 +110,8 @@
</Suspense> </Suspense>
</a-spin> </a-spin>
<!-- 响应内容tab --> <!-- 响应内容tab -->
<div v-else class="h-full"> <div v-else>
<a-spin :loading="loading" class="h-full w-full pb-1"> <a-spin :loading="loading" class="w-full pb-1">
<result <result
v-model:active-tab="activeTab" v-model:active-tab="activeTab"
:request-result="activeStepDetailCopy?.content" :request-result="activeStepDetailCopy?.content"
@ -371,4 +373,10 @@
}); });
</script> </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> </div>
<transition name="fade"> <transition name="fade">
<div v-show="!expandIds.includes(item.value) && isShowContent(item.value)" class="expandContent"> <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" /> <ResBody ref="resBodyRef" :request-result="props.requestResult" @copy="copyScript" />
</div> </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()" /> <ResConsole :console="props.console?.trim()" />
</div> </div>
<div v-if="!expandIds.includes(item.value) && item.value === ResponseComposition.HEADER" class=""> <div v-if="!expandIds.includes(item.value) && item.value === ResponseComposition.HEADER" class="">
@ -49,7 +49,7 @@
</div> </div>
</div> </div>
</transition> </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> </div>
</div> </div>
@ -155,10 +155,13 @@
.tiledList { .tiledList {
@apply px-4; @apply px-4;
.menu-list-wrapper { .menu-list-wrapper {
@apply mt-4;
.menu-list { .menu-list {
height: 32px; position: sticky;
// border-bottom: 1px dashed var(--color-text-n8); top: 50px;
z-index: 999999;
height: 40px;
line-height: 40px;
background: white;
@apply flex items-start justify-between px-4; @apply flex items-start justify-between px-4;
.menu-title { .menu-title {
@apply font-medium; @apply font-medium;
@ -166,9 +169,6 @@
} }
.expandContent { .expandContent {
background: var(--color-text-n9); background: var(--color-text-n9);
.res-item {
height: 210px;
}
} }
} }
} }

View File

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

View File

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

View File

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

View File

@ -95,7 +95,7 @@
> >
</template> </template>
<template #statusFilter="{ columnConfig }"> <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"> <a-button type="text" class="arco-btn-text--secondary" @click="statusFilterVisible = true">
{{ t(columnConfig.title as string) }} {{ t(columnConfig.title as string) }}
<icon-down :class="statusFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" /> <icon-down :class="statusFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
@ -242,7 +242,7 @@
@save="handleMoveOrCopy" @save="handleMoveOrCopy"
/> />
<ScheduledModal v-model:visible="showScheduledTaskModal" /> <ScheduledModal v-model:visible="showScheduledTaskModal" />
<ActionModal v-model:visible="showStatusDeleteModal" :record="activeRecord" /> <ActionModal v-model:visible="showStatusDeleteModal" :record="activeRecord" @success="fetchData()" />
<BatchEditModal <BatchEditModal
v-model:visible="showEditModel" v-model:visible="showEditModel"
:batch-params="batchParams" :batch-params="batchParams"
@ -274,13 +274,12 @@
import StatusProgress from './statusProgress.vue'; import StatusProgress from './statusProgress.vue';
import statusTag from '@/views/case-management/caseReview/components/statusTag.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 { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal'; import useModal from '@/hooks/useModal';
import { useAppStore, useTableStore } from '@/store'; import { useAppStore, useTableStore } from '@/store';
import { characterLimit } from '@/utils'; import { characterLimit } from '@/utils';
import { ReviewStatus } from '@/models/caseManagement/caseReview';
import type { planStatusType, TestPlanItem } from '@/models/testPlan/testPlan'; import type { planStatusType, TestPlanItem } from '@/models/testPlan/testPlan';
import { ColumnEditTypeEnum, TableKeyEnum } from '@/enums/tableEnum'; import { ColumnEditTypeEnum, TableKeyEnum } from '@/enums/tableEnum';
import { testPlanTypeEnum } from '@/enums/testPlanEnum'; import { testPlanTypeEnum } from '@/enums/testPlanEnum';
@ -537,110 +536,8 @@
eventTag: 'delete', 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, getTestPlanList,
{ {
tableKey: TableKeyEnum.TEST_PLAN_ALL_TABLE, tableKey: TableKeyEnum.TEST_PLAN_ALL_TABLE,
@ -821,6 +718,19 @@
onBeforeOk: async () => { onBeforeOk: async () => {
try { try {
const { selectedIds, selectAll, excludeIds } = batchParams.value; 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')); Message.success(t('common.deleteSuccess'));
fetchData(); fetchData();
} catch (error) { } catch (error) {
@ -908,6 +818,7 @@
}, },
onBeforeOk: async () => { onBeforeOk: async () => {
try { try {
await archivedPlan(record.id);
Message.success(t('common.batchArchiveSuccess')); Message.success(t('common.batchArchiveSuccess'));
fetchData(); fetchData();
} catch (error) { } catch (error) {

View File

@ -105,12 +105,12 @@
import TestPlanTree from './components/testPlanTree.vue'; import TestPlanTree from './components/testPlanTree.vue';
import CreateAndEditPlanDrawer from './createAndEditPlanDrawer.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 { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import type { CaseModuleQueryParams, CreateOrUpdateModule, ValidateInfo } from '@/models/caseManagement/featureCase'; import type { CreateOrUpdateModule } from '@/models/caseManagement/featureCase';
import type { ModuleTreeNode } from '@/models/common'; import { ModuleTreeNode, TableQueryParams } from '@/models/common';
import Message from '@arco-design/web-vue/es/message'; 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); const showPlanDrawer = ref(false);
function handleSelect(value: string | number | Record<string, any> | undefined) { function handleSelect(value: string | number | Record<string, any> | undefined) {