perf: multiple menu
This commit is contained in:
parent
c1f8d5b032
commit
98b00ae86d
@ -28,6 +28,7 @@ curl --location --request POST 'https://{{host}}/api/admin/initv4911' \
|
|||||||
1. 商业版支持图片知识库。
|
1. 商业版支持图片知识库。
|
||||||
2. 工作流中增加节点搜索功能。
|
2. 工作流中增加节点搜索功能。
|
||||||
3. 工作流中,子流程版本控制,可选择“保持最新版本”,无需手动更新。
|
3. 工作流中,子流程版本控制,可选择“保持最新版本”,无需手动更新。
|
||||||
|
4. 增加更多审计操作日志。
|
||||||
|
|
||||||
## ⚙️ 优化
|
## ⚙️ 优化
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,7 @@ export const ApiDatasetTypeMap: Record<
|
|||||||
`${DatasetTypeEnum}`,
|
`${DatasetTypeEnum}`,
|
||||||
{
|
{
|
||||||
icon: string;
|
icon: string;
|
||||||
|
avatar: string;
|
||||||
label: any;
|
label: any;
|
||||||
collectionLabel: string;
|
collectionLabel: string;
|
||||||
courseUrl?: string;
|
courseUrl?: string;
|
||||||
@ -24,18 +25,21 @@ export const ApiDatasetTypeMap: Record<
|
|||||||
> = {
|
> = {
|
||||||
[DatasetTypeEnum.apiDataset]: {
|
[DatasetTypeEnum.apiDataset]: {
|
||||||
icon: 'core/dataset/externalDatasetOutline',
|
icon: 'core/dataset/externalDatasetOutline',
|
||||||
|
avatar: 'core/dataset/externalDatasetColor',
|
||||||
label: i18nT('dataset:api_file'),
|
label: i18nT('dataset:api_file'),
|
||||||
collectionLabel: i18nT('common:File'),
|
collectionLabel: i18nT('common:File'),
|
||||||
courseUrl: '/docs/guide/knowledge_base/api_dataset/'
|
courseUrl: '/docs/guide/knowledge_base/api_dataset/'
|
||||||
},
|
},
|
||||||
[DatasetTypeEnum.feishu]: {
|
[DatasetTypeEnum.feishu]: {
|
||||||
icon: 'core/dataset/feishuDatasetOutline',
|
icon: 'core/dataset/feishuDatasetOutline',
|
||||||
|
avatar: 'core/dataset/feishuDatasetColor',
|
||||||
label: i18nT('dataset:feishu_dataset'),
|
label: i18nT('dataset:feishu_dataset'),
|
||||||
collectionLabel: i18nT('common:File'),
|
collectionLabel: i18nT('common:File'),
|
||||||
courseUrl: '/docs/guide/knowledge_base/lark_dataset/'
|
courseUrl: '/docs/guide/knowledge_base/lark_dataset/'
|
||||||
},
|
},
|
||||||
[DatasetTypeEnum.yuque]: {
|
[DatasetTypeEnum.yuque]: {
|
||||||
icon: 'core/dataset/yuqueDatasetOutline',
|
icon: 'core/dataset/yuqueDatasetOutline',
|
||||||
|
avatar: 'core/dataset/yuqueDatasetColor',
|
||||||
label: i18nT('dataset:yuque_dataset'),
|
label: i18nT('dataset:yuque_dataset'),
|
||||||
collectionLabel: i18nT('common:File'),
|
collectionLabel: i18nT('common:File'),
|
||||||
courseUrl: '/docs/guide/knowledge_base/yuque_dataset/'
|
courseUrl: '/docs/guide/knowledge_base/yuque_dataset/'
|
||||||
@ -45,6 +49,7 @@ export const DatasetTypeMap: Record<
|
|||||||
`${DatasetTypeEnum}`,
|
`${DatasetTypeEnum}`,
|
||||||
{
|
{
|
||||||
icon: string;
|
icon: string;
|
||||||
|
avatar: string;
|
||||||
label: any;
|
label: any;
|
||||||
collectionLabel: string;
|
collectionLabel: string;
|
||||||
courseUrl?: string;
|
courseUrl?: string;
|
||||||
@ -53,22 +58,26 @@ export const DatasetTypeMap: Record<
|
|||||||
...ApiDatasetTypeMap,
|
...ApiDatasetTypeMap,
|
||||||
[DatasetTypeEnum.folder]: {
|
[DatasetTypeEnum.folder]: {
|
||||||
icon: 'common/folderFill',
|
icon: 'common/folderFill',
|
||||||
|
avatar: 'common/folderFill',
|
||||||
label: i18nT('dataset:folder_dataset'),
|
label: i18nT('dataset:folder_dataset'),
|
||||||
collectionLabel: i18nT('common:Folder')
|
collectionLabel: i18nT('common:Folder')
|
||||||
},
|
},
|
||||||
[DatasetTypeEnum.dataset]: {
|
[DatasetTypeEnum.dataset]: {
|
||||||
icon: 'core/dataset/commonDatasetOutline',
|
icon: 'core/dataset/commonDatasetOutline',
|
||||||
|
avatar: 'core/dataset/commonDatasetColor',
|
||||||
label: i18nT('dataset:common_dataset'),
|
label: i18nT('dataset:common_dataset'),
|
||||||
collectionLabel: i18nT('common:File')
|
collectionLabel: i18nT('common:File')
|
||||||
},
|
},
|
||||||
[DatasetTypeEnum.websiteDataset]: {
|
[DatasetTypeEnum.websiteDataset]: {
|
||||||
icon: 'core/dataset/websiteDatasetOutline',
|
icon: 'core/dataset/websiteDatasetOutline',
|
||||||
|
avatar: 'core/dataset/websiteDatasetColor',
|
||||||
label: i18nT('dataset:website_dataset'),
|
label: i18nT('dataset:website_dataset'),
|
||||||
collectionLabel: i18nT('common:Website'),
|
collectionLabel: i18nT('common:Website'),
|
||||||
courseUrl: '/docs/guide/knowledge_base/websync/'
|
courseUrl: '/docs/guide/knowledge_base/websync/'
|
||||||
},
|
},
|
||||||
[DatasetTypeEnum.externalFile]: {
|
[DatasetTypeEnum.externalFile]: {
|
||||||
icon: 'core/dataset/externalDatasetOutline',
|
icon: 'core/dataset/externalDatasetOutline',
|
||||||
|
avatar: 'core/dataset/externalDatasetColor',
|
||||||
label: i18nT('dataset:external_file'),
|
label: i18nT('dataset:external_file'),
|
||||||
collectionLabel: i18nT('common:File')
|
collectionLabel: i18nT('common:File')
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,10 +36,12 @@ export type Props = {
|
|||||||
label?: string;
|
label?: string;
|
||||||
width?: number | string;
|
width?: number | string;
|
||||||
offset?: [number, number];
|
offset?: [number, number];
|
||||||
Button: React.ReactNode;
|
Trigger: React.ReactNode;
|
||||||
trigger?: 'hover' | 'click';
|
trigger?: 'hover' | 'click';
|
||||||
size?: MenuSizeType;
|
size?: MenuSizeType;
|
||||||
placement?: PlacementWithLogical;
|
placement?: PlacementWithLogical;
|
||||||
|
hasArrow?: boolean;
|
||||||
|
onClose?: () => void;
|
||||||
menuList: MenuItemData[];
|
menuList: MenuItemData[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -189,32 +191,32 @@ const sizeMapStyle: Record<
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const MenuItem = React.forwardRef<
|
const MenuItem = ({
|
||||||
HTMLDivElement,
|
item,
|
||||||
{
|
size,
|
||||||
item: MenuItemData['children'][number];
|
onClose
|
||||||
size: MenuSizeType;
|
}: {
|
||||||
onClose: () => void;
|
item: MenuItemData['children'][number];
|
||||||
}
|
size: MenuSizeType;
|
||||||
>((props, ref) => {
|
onClose: () => void;
|
||||||
const { item, size, onClose } = props;
|
}) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
ref={ref}
|
|
||||||
px={3}
|
px={3}
|
||||||
py={2}
|
py={2}
|
||||||
cursor="pointer"
|
cursor="pointer"
|
||||||
|
borderRadius="md"
|
||||||
_hover={{
|
_hover={{
|
||||||
bg: 'primary.50',
|
bg: 'primary.50',
|
||||||
color: 'primary.600'
|
color: 'primary.600'
|
||||||
}}
|
}}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
if (item.onClick) {
|
||||||
if (item.onClick && !item.menuList) {
|
|
||||||
onClose();
|
|
||||||
item.onClick();
|
item.onClick();
|
||||||
}
|
}
|
||||||
|
if (!item.menuList) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex alignItems="center" w="100%">
|
<Flex alignItems="center" w="100%">
|
||||||
@ -242,41 +244,53 @@ const MenuItem = React.forwardRef<
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
});
|
};
|
||||||
|
|
||||||
MenuItem.displayName = 'MenuItem';
|
const MultipleMenu = (props: Props) => {
|
||||||
|
const {
|
||||||
|
width = 'auto',
|
||||||
|
trigger = 'hover',
|
||||||
|
size = 'sm',
|
||||||
|
offset,
|
||||||
|
Trigger,
|
||||||
|
menuList,
|
||||||
|
hasArrow = false,
|
||||||
|
placement = 'bottom-start'
|
||||||
|
} = props;
|
||||||
|
|
||||||
const MyMenu = ({
|
|
||||||
width = 'auto',
|
|
||||||
trigger = 'hover',
|
|
||||||
size = 'sm',
|
|
||||||
offset,
|
|
||||||
Button,
|
|
||||||
menuList,
|
|
||||||
placement = 'bottom-start'
|
|
||||||
}: Props) => {
|
|
||||||
const { isPc } = useSystem();
|
const { isPc } = useSystem();
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
|
||||||
const formatTrigger = !isPc ? 'click' : trigger;
|
const formatTrigger = !isPc ? 'click' : trigger;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box ref={ref} display="inline-block">
|
<MyPopover
|
||||||
<MyPopover
|
placement={placement}
|
||||||
placement={placement}
|
offset={offset}
|
||||||
offset={offset || [0, 5]}
|
hasArrow={hasArrow}
|
||||||
hasArrow
|
trigger={formatTrigger}
|
||||||
trigger={formatTrigger}
|
w={width}
|
||||||
w={width}
|
zIndex={999}
|
||||||
zIndex={999}
|
closeOnBlur={false}
|
||||||
closeOnBlur={false}
|
autoFocus={false}
|
||||||
autoFocus={false}
|
Trigger={Trigger}
|
||||||
Trigger={Button}
|
>
|
||||||
>
|
{({ onClose }) => {
|
||||||
{({ onClose }) => (
|
const onCloseFn = () => {
|
||||||
<Box bg="white" minW="150px" maxW="300px" p="6px" borderRadius="md" boxShadow="md">
|
onClose();
|
||||||
|
props?.onClose?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
bg="white"
|
||||||
|
maxW="300px"
|
||||||
|
p="6px"
|
||||||
|
border={'1px solid #fff'}
|
||||||
|
boxShadow={'3'}
|
||||||
|
borderRadius={'md'}
|
||||||
|
>
|
||||||
{menuList.map((group, i) => (
|
{menuList.map((group, i) => (
|
||||||
<Box key={i}>
|
<Box key={i}>
|
||||||
{i !== 0 && <MyDivider {...sizeMapStyle[size].dividerStyle} />}
|
{i !== 0 && <MyDivider h={'1.5px'} {...sizeMapStyle[size].dividerStyle} />}
|
||||||
{group.label && (
|
{group.label && (
|
||||||
<Box fontSize="sm" px={3} py={1} color="myGray.500">
|
<Box fontSize="sm" px={3} py={1} color="myGray.500">
|
||||||
{group.label}
|
{group.label}
|
||||||
@ -286,54 +300,21 @@ const MyMenu = ({
|
|||||||
return (
|
return (
|
||||||
<Box key={index}>
|
<Box key={index}>
|
||||||
{item.menuList ? (
|
{item.menuList ? (
|
||||||
<MyPopover
|
<MultipleMenu
|
||||||
placement="right-start"
|
{...props}
|
||||||
offset={[10, 10]}
|
placement={'left'}
|
||||||
hasArrow
|
trigger={'hover'}
|
||||||
trigger={formatTrigger}
|
menuList={item.menuList}
|
||||||
w={'auto'}
|
onClose={onCloseFn}
|
||||||
zIndex={1000}
|
|
||||||
closeOnBlur={false}
|
|
||||||
autoFocus={false}
|
|
||||||
Trigger={
|
Trigger={
|
||||||
<Box position="relative">
|
<Box>
|
||||||
<MenuItem item={item} size={size} onClose={onClose} />
|
<MenuItem item={item} size={size} onClose={onCloseFn} />
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
>
|
hasArrow
|
||||||
{({ onClose: onCloseSubmenu }) => {
|
/>
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
bg="white"
|
|
||||||
minW="150px"
|
|
||||||
maxW="300px"
|
|
||||||
p="6px"
|
|
||||||
borderRadius="md"
|
|
||||||
boxShadow="md"
|
|
||||||
position="relative"
|
|
||||||
zIndex={1001}
|
|
||||||
>
|
|
||||||
{item.menuList?.map((subGroup, subI) => (
|
|
||||||
<Box key={subI}>
|
|
||||||
{subGroup.children.map((subItem, subIndex) => (
|
|
||||||
<MenuItem
|
|
||||||
key={subIndex}
|
|
||||||
item={subItem}
|
|
||||||
size={size}
|
|
||||||
onClose={() => {
|
|
||||||
onClose();
|
|
||||||
onCloseSubmenu();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</MyPopover>
|
|
||||||
) : (
|
) : (
|
||||||
<MenuItem item={item} size={size} onClose={onClose} />
|
<MenuItem item={item} size={size} onClose={onCloseFn} />
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@ -341,10 +322,10 @@ const MyMenu = ({
|
|||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
);
|
||||||
</MyPopover>
|
}}
|
||||||
</Box>
|
</MyPopover>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(MyMenu);
|
export default React.memo(MultipleMenu);
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Menu,
|
Menu,
|
||||||
MenuList,
|
MenuList,
|
||||||
@ -18,9 +18,20 @@ import { useSystem } from '../../../hooks/useSystem';
|
|||||||
import Avatar from '../Avatar';
|
import Avatar from '../Avatar';
|
||||||
|
|
||||||
export type MenuItemType = 'primary' | 'danger' | 'gray' | 'grayBg';
|
export type MenuItemType = 'primary' | 'danger' | 'gray' | 'grayBg';
|
||||||
|
|
||||||
export type MenuSizeType = 'sm' | 'md' | 'xs' | 'mini';
|
export type MenuSizeType = 'sm' | 'md' | 'xs' | 'mini';
|
||||||
|
|
||||||
|
export type MenuItemData = {
|
||||||
|
label?: string;
|
||||||
|
children: Array<{
|
||||||
|
isActive?: boolean;
|
||||||
|
type?: MenuItemType;
|
||||||
|
icon?: IconNameType | string;
|
||||||
|
label: string | React.ReactNode;
|
||||||
|
description?: string;
|
||||||
|
onClick?: () => any;
|
||||||
|
menuItemStyles?: MenuItemProps;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
export type Props = {
|
export type Props = {
|
||||||
width?: number | string;
|
width?: number | string;
|
||||||
offset?: [number, number];
|
offset?: [number, number];
|
||||||
@ -29,18 +40,7 @@ export type Props = {
|
|||||||
size?: MenuSizeType;
|
size?: MenuSizeType;
|
||||||
|
|
||||||
placement?: PlacementWithLogical;
|
placement?: PlacementWithLogical;
|
||||||
menuList: {
|
menuList: MenuItemData[];
|
||||||
label?: string;
|
|
||||||
children: {
|
|
||||||
isActive?: boolean;
|
|
||||||
type?: MenuItemType;
|
|
||||||
icon?: IconNameType | string;
|
|
||||||
label: string | React.ReactNode;
|
|
||||||
description?: string;
|
|
||||||
onClick?: () => any;
|
|
||||||
menuItemStyles?: MenuItemProps;
|
|
||||||
}[];
|
|
||||||
}[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const typeMapStyle: Record<MenuItemType, { styles: MenuItemProps; iconColor?: string }> = {
|
const typeMapStyle: Record<MenuItemType, { styles: MenuItemProps; iconColor?: string }> = {
|
||||||
|
|||||||
@ -43,11 +43,11 @@ const MyPopover = ({
|
|||||||
initialFocusRef={firstFieldRef}
|
initialFocusRef={firstFieldRef}
|
||||||
onOpen={() => {
|
onOpen={() => {
|
||||||
onOpen();
|
onOpen();
|
||||||
onOpenFunc && onOpenFunc();
|
onOpenFunc?.();
|
||||||
}}
|
}}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
onClose();
|
onClose();
|
||||||
onCloseFunc && onCloseFunc();
|
onCloseFunc?.();
|
||||||
}}
|
}}
|
||||||
placement={placement}
|
placement={placement}
|
||||||
offset={offset}
|
offset={offset}
|
||||||
|
|||||||
@ -50,7 +50,7 @@ const CreateModal = ({
|
|||||||
defaultValues: {
|
defaultValues: {
|
||||||
parentId,
|
parentId,
|
||||||
type: type || DatasetTypeEnum.dataset,
|
type: type || DatasetTypeEnum.dataset,
|
||||||
avatar: DatasetTypeMap[type].icon,
|
avatar: DatasetTypeMap[type].avatar,
|
||||||
name: '',
|
name: '',
|
||||||
intro: '',
|
intro: '',
|
||||||
vectorModel:
|
vectorModel:
|
||||||
@ -95,7 +95,7 @@ const CreateModal = ({
|
|||||||
w={'20px'}
|
w={'20px'}
|
||||||
h={'20px'}
|
h={'20px'}
|
||||||
borderRadius={'xs'}
|
borderRadius={'xs'}
|
||||||
src={DatasetTypeMap[type].icon}
|
src={DatasetTypeMap[type].avatar}
|
||||||
pr={'10px'}
|
pr={'10px'}
|
||||||
/>
|
/>
|
||||||
{t('common:core.dataset.Create dataset', { name: t(DatasetTypeMap[type].label) })}
|
{t('common:core.dataset.Create dataset', { name: t(DatasetTypeMap[type].label) })}
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import List from '@/pageComponents/dataset/list/List';
|
|||||||
import { DatasetsContext } from './context';
|
import { DatasetsContext } from './context';
|
||||||
import DatasetContextProvider from './context';
|
import DatasetContextProvider from './context';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
import MyMenu from '@fastgpt/web/components/common/MyMenu/secondary';
|
import MultipleMenu from '@fastgpt/web/components/common/MyMenu/Multiple';
|
||||||
import { AddIcon } from '@chakra-ui/icons';
|
import { AddIcon } from '@chakra-ui/icons';
|
||||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
@ -138,10 +138,9 @@ const Dataset = () => {
|
|||||||
? folderDetail.permission.hasWritePer
|
? folderDetail.permission.hasWritePer
|
||||||
: userInfo?.team?.permission.hasDatasetCreatePer) && (
|
: userInfo?.team?.permission.hasDatasetCreatePer) && (
|
||||||
<Box pl={[0, 4]}>
|
<Box pl={[0, 4]}>
|
||||||
<MyMenu
|
<MultipleMenu
|
||||||
size="md"
|
size="md"
|
||||||
offset={[0, 10]}
|
Trigger={
|
||||||
Button={
|
|
||||||
<Button variant={'primary'} px="0">
|
<Button variant={'primary'} px="0">
|
||||||
<Flex alignItems={'center'} px={5}>
|
<Flex alignItems={'center'} px={5}>
|
||||||
<AddIcon mr={2} />
|
<AddIcon mr={2} />
|
||||||
@ -163,11 +162,7 @@ const Dataset = () => {
|
|||||||
label: t('dataset:website_dataset'),
|
label: t('dataset:website_dataset'),
|
||||||
description: t('dataset:website_dataset_desc'),
|
description: t('dataset:website_dataset_desc'),
|
||||||
onClick: () => onSelectDatasetType(DatasetTypeEnum.websiteDataset)
|
onClick: () => onSelectDatasetType(DatasetTypeEnum.websiteDataset)
|
||||||
}
|
},
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
children: [
|
|
||||||
{
|
{
|
||||||
icon: 'core/dataset/otherDataset',
|
icon: 'core/dataset/otherDataset',
|
||||||
label: t('dataset:other_dataset'),
|
label: t('dataset:other_dataset'),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user