diff --git a/package.json b/package.json index 2e6fac3..f494b76 100644 --- a/package.json +++ b/package.json @@ -22,14 +22,21 @@ "antd": "^5.16.1", "antd-style": "^3.6.2", "axios": "^1.6.8", + "bunshi": "^2.1.4", "dayjs": "^1.11.10", + "fast-deep-equal": "^3.1.3", "i18next": "^23.11.2", "i18next-browser-languagedetector": "^7.2.1", "jotai": "^2.8.0", + "jotai-scope": "^0.5.1", "jotai-tanstack-query": "^0.8.5", + "re-resizable": "^6.9.11", "react": "^18.2.0", "react-dom": "^18.2.0", "react-i18next": "^14.1.0", + "react-layout-kit": "^1.9.0", + "react-rnd": "^10.4.2-test2", + "react-use": "^17.5.0", "wonka": "^6.3.4" }, "devDependencies": { diff --git a/src/App.tsx b/src/App.tsx index 7a9c2c3..fd2cbbb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,9 @@ -import ErrorPage from '@/components/error/error.tsx' -import { AppContextProvider } from '@/context.ts' + import { AppContextProvider } from '@/context.ts' import { initI18n } from '@/i18n.ts' import { appAtom, appStore, changeLanguage } from '@/store/system.ts' import { userMenuDataAtom } from '@/store/user.ts' import { IAppData } from '@/types' -import { ConfigProvider } from 'antd' +import { ConfigProvider } from '@/components/config-provider' import { Provider, useAtom, useAtomValue } from 'jotai' import './App.css' import { useEffect } from 'react' @@ -15,7 +14,7 @@ import PageLoading from '@/components/page-loading' function App() { const [ appData, ] = useAtom(appAtom) - const { data = [], isError, error, isLoading, refetch } = useAtomValue(userMenuDataAtom) + const { data = [], isLoading, refetch } = useAtomValue(userMenuDataAtom) useEffect(() => { initI18n() @@ -28,10 +27,6 @@ function App() { } }, [ appData.token ]) - if (isError) { - console.error(error) - return - } if (isLoading) { return diff --git a/src/components/breadcrumb/index.tsx b/src/components/breadcrumb/index.tsx index 236ab7b..e4deeb1 100644 --- a/src/components/breadcrumb/index.tsx +++ b/src/components/breadcrumb/index.tsx @@ -2,9 +2,9 @@ import { Breadcrumb, BreadcrumbProps, Dropdown } from 'antd' import { Link, useNavigate } from '@tanstack/react-router' import { DownOutlined } from '@ant-design/icons' import { getIcon } from '@/components/icon' -import { useCallback } from 'react' +import { memo, useCallback } from 'react' -export const PageBreadcrumb = (props: BreadcrumbProps & { +export const PageBreadcrumb = memo((props: BreadcrumbProps & { showIcon?: boolean; }) => { @@ -26,7 +26,7 @@ export const PageBreadcrumb = (props: BreadcrumbProps & { const items = route.children.map((item) => { return { ...item, - key: item.path, + key: item.path || item.name, label: item.name, } }) @@ -40,9 +40,10 @@ export const PageBreadcrumb = (props: BreadcrumbProps & { }} trigger={[ 'hover' ]}> { - !route.component ? {renderIcon(route.icon)}{route.name} + (!route.component || !route.path)? {renderIcon(route.icon)}{route.name} : {renderIcon(route.icon)}{route.name} @@ -53,14 +54,16 @@ export const PageBreadcrumb = (props: BreadcrumbProps & { ) } - return isLast ? ( + return isLast || !route.path ? ( {renderIcon(route.icon)}{route.name} ) : ( {renderIcon(route.icon)}{route.name} ) } + return ( <> ) -} +}) export default PageBreadcrumb \ No newline at end of file diff --git a/src/components/config-provider/index.tsx b/src/components/config-provider/index.tsx index 817b545..a17f7a4 100644 --- a/src/components/config-provider/index.tsx +++ b/src/components/config-provider/index.tsx @@ -1,61 +1,63 @@ -import { ConfigProvider as AntdConfigProvider } from 'antd' +import { ConfigProvider as AntdConfigProvider, ConfigProviderProps as AntdConfigProviderProps } from 'antd' import { AntdToken, ThemeAppearance, useAntdToken, useThemeMode } from 'antd-style' import type { OverrideToken } from 'antd/es/theme/interface' import type { FC, ReactNode } from 'react' import { ThemeProvider, createProAntdTheme, getProToken } from '@/theme' export const useProAntdTheme = (appearance: ThemeAppearance) => { - const token = useAntdToken() - const themeConfig = createProAntdTheme(appearance) - - const controlToken: Partial = { - colorBgContainer: token?.colorFillQuaternary, - colorBorder: 'transparent', - controlOutline: 'transparent', - } - - themeConfig.components = { - Input: controlToken, - InputNumber: controlToken, - Select: controlToken, - Tree: { - colorBgContainer: 'transparent', - }, - TreeSelect: controlToken, - } - - return themeConfig + const token = useAntdToken() + const themeConfig = createProAntdTheme(appearance) + + const controlToken: Partial = { + // colorBgContainer: token?.colorFillQuaternary, + // colorBorder: 'transparent', + // controlOutline: 'transparent', + } + + themeConfig.components = { + Input: controlToken, + InputNumber: controlToken, + Select: controlToken, + Tree: { + colorBgContainer: 'transparent', + directoryNodeSelectedBg: '#e1f0ff', + directoryNodeSelectedColor: 'rgb(22, 62, 124)', + }, + TreeSelect: controlToken, + } + + return themeConfig } -export interface ConfigProviderProps { - componentToken?: OverrideToken; - children: ReactNode; +export interface ConfigProviderProps extends AntdConfigProviderProps { + componentToken?: OverrideToken; + children: ReactNode; } -export const ConfigProvider: FC = ({ children, componentToken }) => { - const { appearance, themeMode } = useThemeMode() - const proTheme = useProAntdTheme(appearance) - proTheme.components = { ...proTheme.components, ...componentToken } - - return ( - - - {children} - - - ) +export const ConfigProvider: FC = ({ children, componentToken, ...props }) => { + const { appearance, themeMode } = useThemeMode() + const proTheme = useProAntdTheme(appearance) + proTheme.components = { ...proTheme.components, ...componentToken } + + return ( + + + {children} + + + ) } export const withProvider = (Component) => (props) => { - return ( - - - - ) + return ( + + + + ) } \ No newline at end of file diff --git a/src/components/draggable-panel/DraggablePanel.tsx b/src/components/draggable-panel/DraggablePanel.tsx new file mode 100644 index 0000000..b026f87 --- /dev/null +++ b/src/components/draggable-panel/DraggablePanel.tsx @@ -0,0 +1,182 @@ +import type { NumberSize, Size } from 're-resizable' +import type { CSSProperties, FC, ReactNode } from 'react' +import { memo } from 'react' +import type { Props as RndProps } from 'react-rnd' +import { withProvider } from '@/components/config-provider' +import { FixMode } from './FixMode' +import { FloatMode } from './FloatMode' + +export interface DraggablePanelProps { + /** + * 位置, + * 使用固定模式或者浮动窗口 + */ + mode?: 'fixed' | 'float'; + + /** + * 固定模式下面板的朝向,默认放置在右侧 + * @default right + */ + placement?: 'right' | 'left' | 'top' | 'bottom'; + + /** + * 最小宽度 + */ + minWidth?: number; + /** + * 最小高度 + */ + minHeight?: number; + /** + * 最大宽度 + */ + maxWidth?: number; + /** + * 最大高度 + */ + maxHeight?: number; + /** + * 控制可缩放区域 + */ + resize?: RndProps['enableResizing']; + /** + * 面板尺寸 + * + */ + size?: Partial; + onSizeChange?: (delta: NumberSize, size?: Size) => void; + /** + * 当用户在拖拽过程中触发 + * @param delta + * @param size + */ + onSizeDragging?: (delta: NumberSize, size?: Size) => void; + /** + * 是否可展开 + * @default true + */ + expandable?: boolean; + /** + * 当前是否是展开态 + */ + isExpand?: boolean; + /** + * 展开是否可以变更 + * @param expand + */ + onExpandChange?: (expand: boolean) => void; + /** + * 面板位置 + * 受控模式 + */ + position?: RndProps['position']; + /** + * 面板默认尺寸 + * 固定模式下: width 320px height 100% + * 浮动模式下:width 320px height 400px + */ + defaultSize?: Partial; + /** + * 面板默认位置悬浮模式下有效 + * @default [100,100] + */ + defaultPosition?: RndProps['position']; + /** + * 位置变更回调 + */ + onPositionChange?: (position: RndProps['position']) => void; + /** + * 样式 + */ + style?: CSSProperties; + /** + * 类名 + */ + className?: string; + /** + * 内容 + */ + children: ReactNode; +} + +const Draggable: FC = memo( + ({ + children, + className, + mode, + placement, + resize, + style, + position, + onPositionChange, + size, + defaultSize, + defaultPosition, + minWidth, + minHeight, + maxHeight, + maxWidth, + onSizeChange, + onSizeDragging, + expandable = true, + isExpand, + onExpandChange, + }) => { + switch (mode) { + case 'fixed': + default: + return ( + + {children} + + ) + case 'float': + return ( + + {children} + + ) + } + }, +) + +const WithProviderDraggable: FC = withProvider(Draggable) + +export { WithProviderDraggable as Draggable } \ No newline at end of file diff --git a/src/components/draggable-panel/FixMode.tsx b/src/components/draggable-panel/FixMode.tsx new file mode 100644 index 0000000..3fa9027 --- /dev/null +++ b/src/components/draggable-panel/FixMode.tsx @@ -0,0 +1,258 @@ +import type { Enable, NumberSize, Size } from 're-resizable' +import { HandleClassName, Resizable } from 're-resizable' +import type { CSSProperties, FC, ReactNode } from 'react' +import { memo, useMemo } from 'react' +import { Center } from 'react-layout-kit' +import type { Props as RndProps } from 'react-rnd' +import useControlledState from 'use-merge-value' + +import { DownOutlined, LeftOutlined, RightOutlined, UpOutlined } from '@ant-design/icons' +import { useStyle } from './style' + +export interface FixModePanelProps { + /** + * 位置, + * 使用固定模式或者浮动窗口 + */ + mode?: 'fixed' | 'float'; + + /** + * 固定模式下面板的朝向,默认放置在右侧 + * @default right + */ + placement: 'right' | 'left' | 'top' | 'bottom' | undefined; + + /** + * 最小宽度 + */ + minWidth?: number; + /** + * 最小高度 + */ + minHeight?: number; + + /** + * 最大宽度 + */ + maxWidth?: number; + /** + * 最大高度 + */ + maxHeight?: number; + /** + * 控制可缩放区域 + */ + resize?: RndProps['enableResizing']; + /** + * 面板尺寸 + * + */ + size?: Partial; + onSizeChange?: (delta: NumberSize, size?: Size) => void; + /** + * 当用户在拖拽过程中触发 + * @param delta + * @param size + */ + onSizeDragging?: (delta: NumberSize, size?: Size) => void; + /** + * 是否可展开 + * @default true + */ + expandable?: boolean; + /** + * 当前是否是展开态 + */ + isExpand?: boolean; + /** + * 展开是否可以变更 + * @param expand + */ + onExpandChange?: (expand: boolean) => void; + /** + * 面板位置 + * 受控模式 + */ + position?: RndProps['position']; + /** + * 面板默认尺寸 + * 固定模式下: width 320px height 100% + * 浮动模式下:width 320px height 400px + */ + defaultSize?: Partial; + /** + * 面板默认位置悬浮模式下有效 + * @default [100,100] + */ + defaultPosition?: RndProps['position']; + /** + * 位置变更回调 + */ + onPositionChange?: (position: RndProps['position']) => void; + /** + * 样式 + */ + style?: CSSProperties; + className?: string; + /** + * 内容 + */ + children: ReactNode; +} + +const DEFAULT_HEIGHT = 150 +const DEFAULT_WIDTH = 400 + +const reversePlacement = (placement: 'right' | 'left' | 'top' | 'bottom') => { + switch (placement) { + case 'bottom': + return 'top' + case 'top': + return 'bottom' + case 'right': + return 'left' + case 'left': + return 'right' + } +} + +const FixMode: FC = memo( + ({ + children, + placement = 'right', + resize, + style, + size, + defaultSize: customizeDefaultSize, + minWidth, + minHeight, + maxHeight, + maxWidth, + onSizeChange, + onSizeDragging, + expandable = true, + isExpand: expand, + onExpandChange, + className, + }) => { + const isVertical = placement === 'top' || placement === 'bottom' + + const { styles, cx } = useStyle() + + const [ isExpand, setIsExpand ] = useControlledState(true, { + value: expand, + onChange: onExpandChange, + }) + + // 只有配置了 resize 和 isExpand 属性后才可拖拽 + const canResizing = resize !== false && isExpand + + const resizeHandleClassNames: HandleClassName = useMemo(() => { + if (!canResizing) return {} + + return { + [reversePlacement(placement)]: styles[`${reversePlacement(placement)}Handle`], + } + }, [ canResizing, placement ]) + + const resizing = { + top: false, + bottom: false, + right: false, + left: false, + topRight: false, + bottomRight: false, + bottomLeft: false, + topLeft: false, + [reversePlacement(placement)]: true, + ...(resize as Enable), + } + + const defaultSize: Size = useMemo(() => { + if (isVertical) + return { + width: '100%', + height: DEFAULT_HEIGHT, + ...customizeDefaultSize, + } + + return { + width: DEFAULT_WIDTH, + height: '100%', + ...customizeDefaultSize, + } + }, [ isVertical ]) + + const sizeProps = isExpand + ? { + minWidth: typeof minWidth === 'number' ? Math.max(minWidth, 0) : 280, + minHeight: typeof minHeight === 'number' ? Math.max(minHeight, 0) : undefined, + maxHeight: typeof maxHeight === 'number' ? Math.max(maxHeight, 0) : undefined, + maxWidth: typeof maxWidth === 'number' ? Math.max(maxWidth, 0) : undefined, + defaultSize, + size: size as Size, + style, + } + : { + minWidth: 0, + minHeight: 0, + size: { width: 0, height: 0 }, + } + + const { Arrow, className: arrowPlacement } = useMemo(() => { + switch (placement) { + case 'top': + return { className: 'Bottom', Arrow: DownOutlined } + case 'bottom': + return { className: 'Top', Arrow: UpOutlined } + case 'right': + return { className: 'Left', Arrow: LeftOutlined } + case 'left': + return { className: 'Right', Arrow: RightOutlined } + } + }, [ styles, placement ]) + + return ( +
+ {expandable && ( +
{ + setIsExpand(!isExpand) + }} + style={{ opacity: isExpand ? undefined : 1 }} + > + +
+ )} + { + { + onSizeChange?.(delta, { + width: ref.style.width, + height: ref.style.height, + }) + }} + onResize={(_, _direction, ref, delta) => { + onSizeDragging?.(delta, { + width: ref.style.width, + height: ref.style.height, + }) + }} + > + {children} + + } +
+ ) + }, +) + +export { FixMode } \ No newline at end of file diff --git a/src/components/draggable-panel/FloatMode.tsx b/src/components/draggable-panel/FloatMode.tsx new file mode 100644 index 0000000..b163255 --- /dev/null +++ b/src/components/draggable-panel/FloatMode.tsx @@ -0,0 +1,182 @@ +import type { Enable, NumberSize, Size } from 're-resizable' +import { HandleClassName } from 're-resizable' +import type { CSSProperties, FC, ReactNode } from 'react' +import { memo, useMemo } from 'react' +import type { Position, Props as RndProps } from 'react-rnd' +import { Rnd } from 'react-rnd' + +import { useStyle } from './style' + +export interface FloatProps { + /** + * 位置, + * 使用固定模式或者浮动窗口 + */ + mode?: 'fixed' | 'float'; + + /** + * 面板的朝向,默认是 左右模式 + * @default horizontal + */ + direction?: 'vertical' | 'horizontal'; + + /** + * 最小宽度 + */ + minWidth?: number; + /** + * 最小高度 + */ + minHeight?: number; + /** + * 最大宽度 + */ + maxWidth?: number; + /** + * 最大高度 + */ + maxHeight?: number; + /** + * 控制可缩放区域 + */ + resize?: RndProps['enableResizing']; + /** + * 面板尺寸 + * + */ + size?: Partial; + onSizeChange?: (delta: NumberSize, size?: Size) => void; + /** + * 当用户在拖拽过程中触发 + * @param delta + * @param size + */ + onSizeDragging?: (delta: NumberSize, size?: Size) => void; + + canResizing?: boolean; + /** + * 面板位置 + * 受控模式 + */ + position?: RndProps['position']; + /** + * 面板默认尺寸 + * 固定模式下: width 320px height 100% + * 浮动模式下:width 320px height 400px + */ + defaultSize?: Partial; + /** + * 面板默认位置悬浮模式下有效 + * @default [100,100] + */ + defaultPosition?: RndProps['position']; + /** + * 位置变更回调 + */ + onPositionChange?: (position: RndProps['position']) => void; + /** + * 样式 + */ + style?: CSSProperties; + /** + * 类名 + */ + className?: string; + /** + * 内容 + */ + children: ReactNode; +} + +const DEFAULT_HEIGHT = 300 +const DEFAULT_WIDTH = 400 + +export const FloatMode: FC = memo( + ({ + children, + direction, + resize, + style, + position, + onPositionChange, + size, + defaultSize: customizeDefaultSize, + defaultPosition: customizeDefaultPosition, + minWidth = 280, + minHeight = 200, + maxHeight, + maxWidth, + canResizing, + }) => { + const { styles } = useStyle() + + const resizeHandleClassNames: HandleClassName = useMemo(() => { + if (!canResizing) return {} + + return { + right: styles.rightHandle, + left: styles.leftHandle, + top: styles.topHandle, + bottom: styles.bottomHandle, + } + }, [ canResizing, direction ]) + + const resizing = useMemo(() => { + if (canResizing) return resize + + return { + top: true, + bottom: true, + right: true, + left: true, + topRight: true, + bottomRight: true, + bottomLeft: true, + topLeft: true, + ...(resize as Enable), + } + }, [ canResizing, resize ]) + + const defaultSize: Size = { + width: DEFAULT_WIDTH, + height: DEFAULT_HEIGHT, + ...customizeDefaultSize, + } + + const defaultPosition: Position = { + x: 100, + y: 100, + ...customizeDefaultPosition, + } + + const sizeProps = { + minWidth: Math.max(minWidth, 0), + minHeight: Math.max(minHeight, 0), + maxHeight: maxHeight ? Math.max(maxHeight, 0) : undefined, + maxWidth: maxWidth ? Math.max(maxWidth, 0) : undefined, + defaultSize, + size: size as Size, + style, + } + + return ( + { + onPositionChange?.({ x: data.x, y: data.y }) + }} + bound={'parent'} + enableResizing={resizing} + {...sizeProps} + className={styles.float} + > + {children} + + ) + }, +) \ No newline at end of file diff --git a/src/components/draggable-panel/index.ts b/src/components/draggable-panel/index.ts new file mode 100644 index 0000000..82be0c1 --- /dev/null +++ b/src/components/draggable-panel/index.ts @@ -0,0 +1,3 @@ +export type { Position } from 'react-rnd'; +export { Draggable as DraggablePanel } from './DraggablePanel'; +export type { DraggablePanelProps } from './DraggablePanel'; \ No newline at end of file diff --git a/src/components/draggable-panel/style.ts b/src/components/draggable-panel/style.ts new file mode 100644 index 0000000..2c44bf2 --- /dev/null +++ b/src/components/draggable-panel/style.ts @@ -0,0 +1,183 @@ +import { createStyles } from '@/theme'; + +export const useStyle = createStyles(({ token, css, cx, prefixCls }) => { + const prefix = `${prefixCls}-${token?.proPrefix}-draggable-panel`; + const commonHandle = css` + position: relative; + + &::before { + position: absolute; + z-index: 50; + transition: all 0.3s ease-in-out; + content: ''; + } + + &:hover, + &:active { + &::before { + background: ${token.colorPrimary}; + } + } + `; + + const commonToggle = css` + position: absolute; + opacity: 0; + z-index: 1001; + transition: opacity 0.1s; + + border-radius: 4px; + cursor: pointer; + background: ${token.colorBgElevated}; + border-width: 1px; + border-style: solid; + color: ${token.colorTextTertiary}; + border-color: ${token.colorBorder}; + + &:hover { + color: ${token.colorTextSecondary}; + background: ${token.colorFillQuaternary}; + } + `; + + const offset = 17; + const toggleLength = 40; + const toggleShort = 16; + + return { + container: cx( + prefix, + css` + flex-shrink: 0; + position: relative; + border: 0 solid ${token.colorSplit}; + + &:hover { + .${prefix}-toggle { + opacity: 1; + } + } + `, + ), + toggleLeft: cx( + `${prefix}-toggle`, + `${prefix}-toggle-left`, + commonToggle, + css` + width: ${toggleShort}px; + height: ${toggleLength}px; + left: -${offset}px; + top: 50%; + margin-top: -20px; + border-radius: 4px 0 0 4px; + border-right-width: 0; + `, + ), + toggleRight: cx( + `${prefix}-toggle`, + `${prefix}-toggle-right`, + commonToggle, + css` + width: ${toggleShort}px; + height: ${toggleLength}px; + right: -${offset}px; + top: 50%; + margin-top: -20px; + border-radius: 0 4px 4px 0; + border-left-width: 0; + `, + ), + toggleTop: cx( + `${prefix}-toggle`, + `${prefix}-toggle-top`, + commonToggle, + css` + height: ${toggleShort}px; + width: ${toggleLength}px; + top: -${offset}px; + left: 50%; + margin-left: -20px; + border-radius: 4px 4px 0 0; + border-bottom-width: 0; + `, + ), + toggleBottom: cx( + `${prefix}-toggle`, + `${prefix}-toggle-bottom`, + commonToggle, + css` + height: 16px; + width: ${toggleLength}px; + bottom: -${offset}px; + left: 50%; + margin-left: -20px; + border-radius: 0 0 4px 4px; + border-top-width: 0; + `, + ), + fixed: cx( + `${prefix}-fixed`, + css` + background: ${token.colorBgContainer}; + overflow: hidden; + `, + ), + float: cx( + `${prefix}-float`, + css` + overflow: hidden; + border-radius: 8px; + background: ${token.colorBgElevated}; + box-shadow: ${token.boxShadowSecondary}; + z-index: 2000; + `, + ), + leftHandle: cx( + css` + ${commonHandle}; + + &::before { + left: 50%; + width: 2px; + height: 100%; + } + `, + `${prefix}-left-handle`, + ), + rightHandle: cx( + css` + ${commonHandle}; + &::before { + right: 50%; + width: 2px; + height: 100%; + } + `, + `${prefix}-right-handle`, + ), + topHandle: cx( + `${prefix}-top-handle`, + css` + ${commonHandle}; + + &::before { + top: 50%; + height: 2px; + width: 100%; + } + `, + ), + bottomHandle: cx( + `${prefix}-bottom-handle`, + css` + ${commonHandle}; + + &::before { + bottom: 50%; + height: 2px; + width: 100%; + } + `, + ), + }; +}); \ No newline at end of file diff --git a/src/components/icon/action/ActionIcon.tsx b/src/components/icon/action/ActionIcon.tsx index d08a4fe..5e1f90c 100644 --- a/src/components/icon/action/ActionIcon.tsx +++ b/src/components/icon/action/ActionIcon.tsx @@ -9,103 +9,106 @@ import { useStyles } from './style' * @description 继承自 `Button` 组件所有属性,除了 `title`, `type` 和 `size` */ export interface ActionIconProps extends Omit { - /** - * @title 鼠标类型 - */ - cursor?: CSSProperties['cursor']; - /** - * @title 动作提示 - */ - title?: TooltipProps['title']; - /** - * @title 提示位置 - */ - placement?: TooltipProps['placement']; - /** - * @title 图标 - */ - icon: ButtonProps['icon']; - /** - * @title 点击回调 - */ - onClick?: ButtonProps['onClick']; - /** - * @title 图标尺寸 - */ - size?: 'default' | 'large' | number; - /** - * @description 鼠标移入时候的延迟tooltip时间,默认 0.5 - * @default 0.5 - */ - tooltipDelay?: number; - /** - * @description 是否展示小箭头,默认不展示 - * @default false - */ - arrow?: boolean; + /** + * @title 鼠标类型 + */ + cursor?: CSSProperties['cursor']; + /** + * @title 动作提示 + */ + title?: TooltipProps['title']; + /** + * @title 提示位置 + */ + placement?: TooltipProps['placement']; + /** + * @title 图标 + */ + icon: ButtonProps['icon']; + /** + * @title 点击回调 + */ + onClick?: ButtonProps['onClick']; + /** + * @title 图标尺寸 + */ + size?: 'default' | 'large' | number; + /** + * @description 鼠标移入时候的延迟tooltip时间,默认 0.5 + * @default 0.5 + */ + tooltipDelay?: number; + /** + * @description 是否展示小箭头,默认不展示 + * @default false + */ + arrow?: boolean; + + bordered?: boolean; } const BaseActionIcon: FC = ({ - placement, - title, - icon, - cursor, - onClick, - className, - arrow = false, - size = 'default', - tooltipDelay = 0.5, - ...restProps + placement, + title, + icon, + cursor, + onClick, + className, + bordered = false, + arrow = false, + size = 'default', + tooltipDelay = 0.5, + ...restProps }) => { - const { styles, cx } = useStyles({ size }) + const { styles, cx } = useStyles({ size, bordered, className }) - const Icon = ( - - diff --git a/src/pages/login/style.ts b/src/pages/login/style.ts new file mode 100644 index 0000000..d893eb7 --- /dev/null +++ b/src/pages/login/style.ts @@ -0,0 +1,130 @@ +import { createStyles } from '@/theme' +import loginBg from '@/assets/login.png' + + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +export const useStyles = createStyles(({ token, css, cx, prefixCls }, props: any) => { + + const prefix = `${prefixCls}-${token.proPrefix}-login-page` + + + const container = css` + display: flex; + align-items: center; + height: 100vh; + background-image: url(${loginBg}); + background-repeat: no-repeat; + background-size: cover; + ` + const language = css` + position: absolute; + top: 10px; + right: 10px; + color: #fff; + font-size: 14px; + cursor: pointer; + ` + + + const loginBlock = css` + width: 100%; + height: 100%; + padding: 40px 0; + display: flex; + align-items: center; + justify-content: center; + ` + + const innerBlock = css` + width: 356px; + margin: 0 auto; + ` + + const logo = css` + height: 30px; + ` + + const infoLine = css` + display: flex; + align-items: center; + justify-content: space-between; + margin: 0; + ` + + const infoLeft = css` + color: #666; + font-size: 14px; + ` + + + const desc = css` + margin: 24px 0; + color: #999; + font-size: 16px; + cursor: pointer; + ` + const active = css` + color: #333; + font-weight: bold; + font-size: 24px; + ` + + const innerBeforeInput = css` + margin-left: 10px; + color: #999; + ` + + const line = css` + margin-left: 10px; + ` + + const innerAfterInput = css` + margin-right: 10px; + color: #999; + ` + + const lineR = css` + margin-right: 10px; + vertical-align: middle; + + ` + + const sendCode = css` + max-width: 65px; + margin-right: 10px; + ` + + const otherLogin = css` + color: #666; + font-size: 14px; + ` + + const icon = css` + margin-left: 10px; + ` + + const submitBtn = css` + width: 100%; + ` + + return { + container: cx(prefix, container, props?.className ?? ''), + language, + loginBlock, + innerBlock, + logo, + infoLine, + infoLeft, + desc, + active, + innerBeforeInput, + line: cx( innerBeforeInput, line), + innerAfterInput, + lineR: cx(innerAfterInput, lineR), + sendCode, + otherLogin, + icon, + submitBtn, + } +}) \ No newline at end of file diff --git a/src/pages/system/menus/components/BatchButton.tsx b/src/pages/system/menus/components/BatchButton.tsx new file mode 100644 index 0000000..2a55c67 --- /dev/null +++ b/src/pages/system/menus/components/BatchButton.tsx @@ -0,0 +1,32 @@ +import { Button, Popconfirm } from 'antd' +import { useAtomValue } from 'jotai' +import { batchIdsAtom, deleteMenuAtom } from '../store.ts' +import { useTranslation } from '@/i18n.ts' + +const BatchButton = () => { + + const { t } = useTranslation() + const { isPending, mutate, } = useAtomValue(deleteMenuAtom) + const ids = useAtomValue(batchIdsAtom) + if (ids.length === 0) { + return null + } + + return ( + { + mutate(ids as number[]) + }} + title={t('system.menus.batchDel.confirm', '确定要删除所选数据吗?')}> + + + ) +} + +export default BatchButton \ No newline at end of file diff --git a/src/pages/system/menus/components/ButtonTable.tsx b/src/pages/system/menus/components/ButtonTable.tsx new file mode 100644 index 0000000..dab5635 --- /dev/null +++ b/src/pages/system/menus/components/ButtonTable.tsx @@ -0,0 +1,97 @@ +import { EditableFormInstance, EditableProTable } from '@ant-design/pro-components' +import { useMemo, useRef, useState } from 'react' +import { IDataProps } from '@/types' +import { FormInstance } from 'antd/lib' + +type DataSourceType = { + id: number, + name: string, + code: string, +} + +const fixRowKey = (data: DataSourceType[]) => { + return data.map((item, index) => ({ ...item, id: item.id ?? index+1 })) +} + +const ButtonTable = (props: IDataProps & { + form: FormInstance +}) => { + + const { value, onChange } = props + const editorFormRef = useRef>() + const [ editableKeys, setEditableRowKeys ] = useState(() => { + return fixRowKey(value || []).map(item => item.id) + }) + + const values = fixRowKey(value || []) + + const columns = useMemo(() => { + + return [ + { + title: 'id', + dataIndex: 'id', + hideInTable: true, + }, + { + title: '名称', + dataIndex: 'label', + formItemProps: () => { + return { + rules: [ { required: true, message: '此项为必填项' } ], + } + }, + }, + { + title: '标识', + dataIndex: 'code', + formItemProps: () => { + return { + rules: [ { required: true, message: '此项为必填项' } ], + } + }, + }, + + { + title: '操作', + valueType: 'option', + width: 80, + + } + ] + + }, []) + + + return ( + + + rowKey="id" + value={values } + onChange={onChange} + editableFormRef={editorFormRef} + recordCreatorProps={ + { + newRecordType: 'dataSource', + record: () => { + return { id: ((value?? []).length + 1) } as DataSourceType + }, + } + } + editable={{ + type: 'multiple', + editableKeys, + actionRender: (_row, _config, defaultDoms) => { + return [ defaultDoms.delete ] + }, + onValuesChange: (_record, recordList) => { + onChange?.(recordList) + }, + onChange: setEditableRowKeys, + }} + columns={columns as any} + /> + ) +} + +export default ButtonTable \ No newline at end of file diff --git a/src/pages/system/menus/components/MenuTree.tsx b/src/pages/system/menus/components/MenuTree.tsx new file mode 100644 index 0000000..ff0679b --- /dev/null +++ b/src/pages/system/menus/components/MenuTree.tsx @@ -0,0 +1,103 @@ +import { Button, Empty, Spin, Tree } from 'antd' +import { PlusOutlined } from '@ant-design/icons' +import { MenuItem } from '@/types' +import { useStyle } from '../style.ts' +import { useTranslation } from '@/i18n.ts' +import { useSetAtom } from 'jotai' +import { batchIdsAtom, menuDataAtom, selectedMenuAtom } from '../store.ts' +import { FormInstance } from 'antd/lib' +import { useAtomValue } from 'jotai/index' +import { TreeNodeRender } from '../components/TreeNodeRender.tsx' +import { useRef } from 'react' +import { flattenTree } from '@/utils' +import { useDeepCompareEffect } from 'react-use' + +const MenuTree = ({ form }: { form: FormInstance }) => { + + + const { styles } = useStyle() + const { t } = useTranslation() + const setCurrentMenu = useSetAtom(selectedMenuAtom) + const setIds = useSetAtom(batchIdsAtom) + const { data = [], isLoading } = useAtomValue(menuDataAtom) + const flattenMenusRef = useRef([]) + + useDeepCompareEffect(() => { + + if (isLoading) return + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore array + if (data.length) { + // @ts-ignore flattenTree + flattenMenusRef.current = flattenTree(data as any) + // console.log(flattenMenusRef.current) + } + + return () => { + setCurrentMenu({} as MenuItem) + } + + }, [ data, isLoading ]) + + const renderEmpty = () => { + if ((data as any).length > 0 || isLoading) return null + return + + + } + + return ( + <> + + { + renderEmpty() + } + { + return () + }} + fieldNames={{ + title: 'title', + key: 'id' + }} + onSelect={(item) => { + const current = flattenMenusRef.current?.find((menu) => menu.id === item[0]) + setCurrentMenu(current as MenuItem) + form.setFieldsValue({ ...current }) + }} + onCheck={(item) => { + setIds(item as number[]) + }} + checkable={true} + showIcon={false} + /> + + + ) +} + +export default MenuTree \ No newline at end of file diff --git a/src/pages/system/menus/components/TreeNodeRender.tsx b/src/pages/system/menus/components/TreeNodeRender.tsx new file mode 100644 index 0000000..d857cd2 --- /dev/null +++ b/src/pages/system/menus/components/TreeNodeRender.tsx @@ -0,0 +1,66 @@ +import { memo } from 'react' +import { MenuItem } from '@/types' +import { Popconfirm, Space, TreeDataNode } from 'antd' +import { FormInstance } from 'antd/lib' +import { useTranslation } from '@/i18n.ts' +import { useStyle } from '../style.ts' +import { useAtomValue, useSetAtom } from 'jotai/index' +import { deleteMenuAtom, selectedMenuAtom } from '../store.ts' +import { PlusOutlined } from '@ant-design/icons' +import ActionIcon, { DeleteAction } from '@/components/icon/action' + +export const TreeNodeRender = memo(({ node, form }: { node: MenuItem & TreeDataNode, form: FormInstance }) => { + const { title } = node + const { t } = useTranslation() + const { styles } = useStyle() + const { mutate, } = useAtomValue(deleteMenuAtom) + + const setMenuData = useSetAtom(selectedMenuAtom) + + return ( +
+ {title as any} + + + } + title={t('system.menus.add', '添加')} + onClick={(e) => { + // console.log('add') + e.stopPropagation() + e.preventDefault() + + const menu = { + parent_id: node.id, + type: 'menu', + name: '', + title: '', + icon: '', + path: '', + component: '', + sort: 0, + id: 0, + } as MenuItem + setMenuData(menu) + form.setFieldsValue(menu) + + }}/> + { + mutate([ (node as any).id ]) + }} + > + { + e.stopPropagation() + e.stopPropagation() + }}/> + + + +
+ ) +}) \ No newline at end of file diff --git a/src/pages/system/menus/index.tsx b/src/pages/system/menus/index.tsx index 9cdb3b2..1b8a629 100644 --- a/src/pages/system/menus/index.tsx +++ b/src/pages/system/menus/index.tsx @@ -1,98 +1,155 @@ import { useTranslation } from '@/i18n.ts' -import { FlattenData } from '@/types' -import { IMenu } from '@/types/menus' -import { flattenTree } from '@/utils' import { PageContainer, ProCard } from '@ant-design/pro-components' -import { Button, Form, Input, Space, Tree } from 'antd' -import { useAtom, useAtomValue } from 'jotai' -import { useEffect, useRef } from 'react' -import { menuDataAtom, selectedMenuAtom, selectedMenuIdAtom } from './store.ts' -import { CloseOutlined, PlusOutlined } from '@ant-design/icons' +import { Button, Form, Input, message, Radio, TreeSelect } from 'antd' +import { useAtomValue } from 'jotai' +import { menuDataAtom, saveOrUpdateMenuAtom, selectedMenuAtom } from './store.ts' import { createLazyFileRoute } from '@tanstack/react-router' +import { NumberPicker } from '@formily/antd-v5' +import IconPicker from '@/components/icon/picker' +import ButtonTable from './components/ButtonTable.tsx' +import { Flexbox } from 'react-layout-kit' +import { DraggablePanel } from '@/components/draggable-panel' +import { useStyle } from './style.ts' +import { MenuItem } from '@/types' +import MenuTree from './components/MenuTree.tsx' +import BatchButton from '@/pages/system/menus/components/BatchButton.tsx' +import { useEffect } from 'react' + const Menus = () => { - const { t } = useTranslation() - const { data = [], isLoading } = useAtomValue(menuDataAtom) - const [ currentMenu, setCurrentMenu ] = useAtom(selectedMenuAtom) - const [ selectedKey, setSelectedKey ] = useAtom(selectedMenuIdAtom) - const flattenMenusRef = useRef[]>([]) - - useEffect(() => { - - if (data.length) { - flattenMenusRef.current = flattenTree(data) - console.log(flattenMenusRef.current) - } - - }, [ data ]) - - return ( - - - - + + + + + + { + return prevValues.id !== curValues.id + }}> + + + + + + + + + + ) } export const Route = createLazyFileRoute('/system/menus')({ - component: Menus + component: Menus }) diff --git a/src/pages/system/menus/store.ts b/src/pages/system/menus/store.ts index a76013c..a1db504 100644 --- a/src/pages/system/menus/store.ts +++ b/src/pages/system/menus/store.ts @@ -1,32 +1,66 @@ import systemServ from '@/service/system.ts' import { IPage, IPageResult, MenuItem } from '@/types' import { IMenu } from '@/types/menus' -import { atomWithQuery } from 'jotai-tanstack-query' +import { atomWithMutation, atomWithQuery, queryClientAtom } from 'jotai-tanstack-query' import { atom } from 'jotai/index' export const menuPageAtom = atom({}) -export const menuDataAtom = atomWithQuery>((get) => { +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore fix get +export const menuDataAtom = atomWithQuery>((get) => { - return { - queryKey: [ 'menus', get(menuPageAtom) ], - queryFn: async ({ queryKey: [ , page ] }) => { - return await systemServ.menus.list(page) - }, - select: (data) => { - return data.rows ?? [] + return { + queryKey: [ 'menus', get(menuPageAtom) ], + queryFn: async ({ queryKey: [ , page ] }) => { + return await systemServ.menus.list(page) + }, + select: (data) => { + return data.rows ?? [] + } } - } }) export const selectedMenuIdAtom = atom(0) -export const selectedMenuAtom = atom(undefined) +export const selectedMenuAtom = atom({} as MenuItem) export const byIdMenuAtom = atomWithQuery((get) => ({ - queryKey: [ 'selectedMenu', get(selectedMenuIdAtom) ], - queryFn: async ({ queryKey: [ , id ] }) => { - return await systemServ.menus.info(id as number) - }, - select: data => data.data, + queryKey: [ 'selectedMenu', get(selectedMenuIdAtom) ], + queryFn: async ({ queryKey: [ , id ] }) => { + return await systemServ.menus.info(id as number) + }, + select: data => data.data, })) + +export const saveOrUpdateMenuAtom = atomWithMutation((get) => { + + return { + mutationKey: [ 'updateMenu', get(selectedMenuIdAtom) ], + mutationFn: async (data: IMenu) => { + if (data.id === 0) { + return await systemServ.menus.add(data) + } + return await systemServ.menus.update(data) + }, + onSuccess: (data) => { + console.log(data) + //更新列表 + get(queryClientAtom).refetchQueries([ 'menus', get(menuPageAtom) ]) + } + } +}) + +export const batchIdsAtom = atom([]) + +export const deleteMenuAtom = atomWithMutation((get) => { + return { + mutationKey: [ 'deleteMenu', get(batchIdsAtom) ], + mutationFn: async (ids?: number[]) => { + return await systemServ.menus.batchDelete(ids ?? get(batchIdsAtom)) + }, + onSuccess: (data) => { + console.log(data) + } + } +}) \ No newline at end of file diff --git a/src/pages/system/menus/style.ts b/src/pages/system/menus/style.ts new file mode 100644 index 0000000..77582f2 --- /dev/null +++ b/src/pages/system/menus/style.ts @@ -0,0 +1,72 @@ +import { createStyles } from '@/theme' + +export const useStyle = createStyles(({ token, css, cx, prefixCls }) => { + const prefix = `${prefixCls}-${token?.proPrefix}-menu-page`; + + const tree = css` + .ant-tree { + overflow: auto; + height: 100%; + border-right: 1px solid ${token.colorBorder}; + background: ${token.colorBgContainer}; + + } + .ant-tree-directory .ant-tree-treenode-selected::before{ + background: ${token.colorBgTextHover}; + } + .ant-tree-treenode:before{ + border-radius: ${token.borderRadius}px; + } + ` + + const box = css` + flex: 1; + background: ${token.colorBgContainer}; + ` + + const form = css` + display: flex; + flex-wrap: wrap; + min-width: 500px; + ` + + const formSetting = css` + flex: 1; + + ` + + const formButtons = css` + width: 500px; + + ` + + const treeNode = css` + display: flex; + justify-content: space-between; + align-items: center; + + .actions{ + display: none; + padding: 0 10px; + } + + &:hover .actions{ { + display: flex; + } + + ` + const treeActions = css` + + ` + + return { + container: cx(prefix), + box, + tree, + form, + treeNode, + treeActions, + formSetting, + formButtons, + } +}) \ No newline at end of file diff --git a/src/request.ts b/src/request.ts index 9ac6de4..0f0aa56 100644 --- a/src/request.ts +++ b/src/request.ts @@ -43,6 +43,12 @@ request.interceptors.response.use((response: AxiosResponse) => { } return response.data case 401: + setToken('') + if (window.location.pathname === '/login') { + return + } + + // 401: 未登录 message.error('登录失败,跳转重新登录') // eslint-disable-next-line no-case-declarations @@ -53,14 +59,40 @@ request.interceptors.response.use((response: AxiosResponse) => { redirect = window.location.pathname + '?=' + search.toString() } window.location.href = `/login?redirect=${encodeURIComponent(redirect)}` - break + return default: message.error(response.data.message) return Promise.reject(response) } }, (error) => { - console.log('error', error) + // console.log('error', error) + const { response } = error + if (response) { + switch (response.status) { + case 401: + if (window.location.pathname === '/login') { + return + } + + setToken('') + // 401: 未登录 + message.error('登录失败,跳转重新登录') + // eslint-disable-next-line no-case-declarations + const search = new URLSearchParams(window.location.search) + // eslint-disable-next-line no-case-declarations + let redirect = window.location.pathname + if (search.toString() !== '') { + redirect = window.location.pathname + '?=' + search.toString() + } + window.location.href = `/login?redirect=${encodeURIComponent(redirect)}` + return + default: + message.error(response.data.message) + return Promise.reject(response) + } + } + return Promise.reject(error) }) diff --git a/src/service/base.ts b/src/service/base.ts index b271a3e..39e192d 100644 --- a/src/service/base.ts +++ b/src/service/base.ts @@ -6,13 +6,13 @@ export const createCURD = (api: string, options?: AxiosRequest return { list: (params?: TParams & IPage) => { - return request.post(`${api}/list`, { ...options, params }).then(data => data.data) + return request.post(`${api}/list`, { ...options, ...params }).then(data => data.data) }, add: (data: TParams) => { return request.post(`${api}/add`, data, options) }, - update: (id: number, data: TParams) => { - return request.put(`${api}/${id}`, data, options) + update: (data: TParams) => { + return request.post(`${api}/edit`, data, options) }, delete: (id: number) => { return request.delete(`${api}/delete`, { ...options, params: { id } }) diff --git a/src/store/system.ts b/src/store/system.ts index f961e4c..7767f13 100644 --- a/src/store/system.ts +++ b/src/store/system.ts @@ -1,11 +1,8 @@ -import { IAppData, MenuItem } from '@/types' -import { IMenu } from '@/types/menus' -import { atom, createStore } from 'jotai' -import { atomWithQuery } from 'jotai-tanstack-query' +import { IAppData } from '@/types' +import { createStore } from 'jotai' import { atomWithStorage } from 'jotai/utils' -import systemServ from '../service/system.ts' import { changeLanguage as setLang } from 'i18next' -import { AxiosResponse } from 'axios' +import { userMenuDataAtom } from '@/store/user.ts' /** * app全局状态 diff --git a/src/store/user.ts b/src/store/user.ts index ed4c0f6..d27ff75 100644 --- a/src/store/user.ts +++ b/src/store/user.ts @@ -1,47 +1,49 @@ import { appAtom } from '@/store/system.ts' import { AxiosResponse } from 'axios' import { atom } from 'jotai/index' -import { IAuth } from '@/types' +import { IAuth, MenuItem } from '@/types' import { LoginRequest } from '@/types/login' import { atomWithMutation, atomWithQuery, queryClientAtom } from 'jotai-tanstack-query' import systemServ from '@/service/system.ts' import { formatMenuData, isDev } from '@/utils' export const authAtom = atom({ - isLogin: false, - authKey: [] + isLogin: false, + authKey: [] }) const devLogin = { - username: 'SupperAdmin', - password: 'kk123456', - code: '123456' + username: 'SupperAdmin', + password: 'kk123456', + code: '123456' } export const loginFormAtom = atom({ - ...(isDev ? devLogin : {}) + ...(isDev ? devLogin : {}) } as LoginRequest) export const loginAtom = atomWithMutation(() => ({ - mutationKey: [ 'login' ], - mutationFn: async (params) => { - return await systemServ.login(params) - }, - onSuccess: () => { - // console.log('login success', data) - } + mutationKey: [ 'login' ], + mutationFn: async (params) => { + return await systemServ.login(params) + }, + onSuccess: () => { + // console.log('login success', data) + }, + retry: false, })) -export const userMenuDataAtom = atomWithQuery((get) => ({ - enabled: false, - queryKey: [ 'user_menus', get(appAtom).token ], - queryFn: async () => { - return await systemServ.user.menus() - }, - select: (data: AxiosResponse) => { - return formatMenuData(data.data.rows as any ?? []) - }, - initialData: () => { - const queryClient = get(queryClientAtom) - return queryClient.getQueryData([ 'user_menus', get(appAtom).token ]) - }, +export const userMenuDataAtom = atomWithQuery((get) => ({ + enabled: false, + queryKey: [ 'user_menus', get(appAtom).token ], + queryFn: async () => { + return await systemServ.user.menus() + }, + select: (data: AxiosResponse) => { + return formatMenuData(data.data.rows as any ?? []) + }, + initialData: () => { + const queryClient = get(queryClientAtom) + return queryClient.getQueryData([ 'user_menus', get(appAtom).token ]) + }, + retry: false, })) diff --git a/src/theme/themes/antdTheme.ts b/src/theme/themes/antdTheme.ts index 391f2d0..beb70b0 100644 --- a/src/theme/themes/antdTheme.ts +++ b/src/theme/themes/antdTheme.ts @@ -4,7 +4,7 @@ import { proDarkAlgorithm } from '@/theme' export const createProAntdTheme = (appearance: ThemeAppearance) => { const themeConfig: ThemeConfig = { - algorithm: [ theme.compactAlgorithm ], + algorithm: [ theme.defaultAlgorithm ], } if (appearance === 'dark') { diff --git a/src/theme/themes/token.ts b/src/theme/themes/token.ts index a615d4d..80a6d96 100644 --- a/src/theme/themes/token.ts +++ b/src/theme/themes/token.ts @@ -18,6 +18,7 @@ export const getProToken: GetCustomToken = () => ({ colorTypeBoolArray: '#D8C152', colorTypeNumberArray: '#239BEF', colorTypeStringArray: '#62AE8D', + }) export const themeToken = getProToken({} as any) \ No newline at end of file diff --git a/src/utils/index.ts b/src/utils/index.ts index 9c38268..6759fc1 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -8,86 +8,87 @@ export const isDev = import.meta.env.MODE === 'development' // 格式化菜单数据, 把children转换成routes export const formatMenuData = (data: IMenu[]) => { - const result: MenuItem[] = [] - for (const item of data) { - if (item.icon && typeof item.icon === 'string') { - item.icon = getIcon(item.icon as string, { size: '14', theme: 'filled' }) - } - if (!item.children || !item.children.length) { - result.push({ - ...item, - key: item.name, - name: item.title - }) - } else { - const { children, name, ...other } = item - result.push({ - ...other, - key: name, - name: other.title, - children: formatMenuData(children), - routes: formatMenuData(children), - }) + const result: MenuItem[] = [] + for (const item of data) { + if (item.icon && typeof item.icon === 'string') { + item.icon = getIcon(item.icon as string, { size: '14', theme: 'filled' }) + } + if (!item.children || !item.children.length) { + result.push({ + ...item, + key: item.name, + name: item.title + }) + } else { + const { children, name, ...other } = item + result.push({ + ...other, + key: name, + name: other.title, + children: formatMenuData(children), + routes: formatMenuData(children), + }) + } } - } - return result + return result } //把MenuItem[]转换成antd树形结构 export const formatterMenuData = (data: MenuItem[]): TreeDataNode[] => { - const result: TreeDataNode[] = [] - for (const item of data) { - if (item.children && item.children.length) { - const { children, ...other } = item - result.push({ - ...other, - key: item.id!, - title: item.name!, - children: formatterMenuData(children), - }) - } else { - result.push({ - ...item, - key: item.id!, - title: item.name!, - }) + const result: TreeDataNode[] = [] + for (const item of data) { + if (item.children && item.children.length) { + const { children, ...other } = item + result.push({ + ...other, + key: item.id!, + title: item.name!, + children: formatterMenuData(children), + }) + } else { + result.push({ + ...item, + key: item.id!, + title: item.name!, + }) + } } - } - return result + return result } //把tree转成平铺数组 const defaultTreeFieldNames: FiledNames = { - key: 'id', - title: 'title', - children: 'children' + key: 'id', + title: 'title', + children: 'children' } -export function flattenTree(tree: TreeItem[], fieldNames: { - key: string; - title: string; - children: string -} = defaultTreeFieldNames): FlattenData[] { - const result: FlattenData[] = [] +export function flattenTree(tree: TreeItem[], fieldNames?: FiledNames) { + const result: FlattenData[] = [] - function flattenRecursive(item: TreeItem, level: number) { - const data: FlattenData = { - ...item, - key: item[fieldNames.key], - title: item[fieldNames.title], - level, + if (!fieldNames) { + fieldNames = defaultTreeFieldNames } - result.push(data) + function flattenRecursive(item: TreeItem, level: number, fieldNames: FiledNames) { - if (item.children) { - item.children.forEach((child) => flattenRecursive(child, level + 1)) + const data: FlattenData = { + ...item, + key: item[fieldNames.key!], + title: item[fieldNames.title!], + level, + } + const children = item[fieldNames.children!] + if (children) { + children.forEach((child) => flattenRecursive(child, level + 1, fieldNames)) + data.children = children + } + result.push(data) } - } - tree.forEach((item) => flattenRecursive(item, 0)) + tree.forEach((item) => flattenRecursive(item, 0, fieldNames)) - return result + return result } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 768623f..21d19f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -486,7 +486,7 @@ "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-syntax-typescript" "^7.24.1" -"@babel/runtime@^7.10.1", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.7", "@babel/runtime@^7.18.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.0", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.6", "@babel/runtime@^7.23.9", "@babel/runtime@^7.24.1", "@babel/runtime@^7.24.4": +"@babel/runtime@^7", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.7", "@babel/runtime@^7.18.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.0", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.6", "@babel/runtime@^7.23.9", "@babel/runtime@^7.24.1", "@babel/runtime@^7.24.4": version "7.24.4" resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.24.4.tgz#de795accd698007a66ba44add6cc86542aff1edd" integrity sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA== @@ -607,7 +607,7 @@ "@emotion/weak-memoize" "^0.3.1" stylis "4.2.0" -"@emotion/css@^11.11.2": +"@emotion/css@^11", "@emotion/css@^11.11.2": version "11.11.2" resolved "https://registry.npmmirror.com/@emotion/css/-/css-11.11.2.tgz#e5fa081d0c6e335352e1bc2b05953b61832dca5a" integrity sha512-VJxe1ucoMYMS7DkiMdC2T7PWNbrEI0a39YRiyDvK2qq4lXwjRbVP/z4lpG+odCsRzadlR+1ywwrTzhdm5HNdew== @@ -984,7 +984,7 @@ resolved "https://registry.npmmirror.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": version "1.4.15" resolved "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== @@ -1282,6 +1282,11 @@ resolved "https://registry.npmmirror.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== +"@types/js-cookie@^2.2.6": + version "2.2.7" + resolved "https://registry.npmmirror.com/@types/js-cookie/-/js-cookie-2.2.7.tgz#226a9e31680835a6188e887f3988e60c04d3f6a3" + integrity sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA== + "@types/json-schema@^7.0.15": version "7.0.15" resolved "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" @@ -1434,6 +1439,11 @@ "@types/babel__core" "^7.20.5" react-refresh "^0.14.0" +"@xobotyi/scrollbar-width@^1.9.5": + version "1.9.5" + resolved "https://registry.npmmirror.com/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz#80224a6919272f405b87913ca13b92929bdf3c4d" + integrity sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ== + acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -1654,6 +1664,11 @@ bundle-require@^4.0.1: dependencies: load-tsconfig "^0.2.3" +bunshi@^2.1.4: + version "2.1.4" + resolved "https://registry.npmmirror.com/bunshi/-/bunshi-2.1.4.tgz#1b7b99294ce101eb7167c6d98d79129b0cd2bf73" + integrity sha512-8kHpSsfFQu3Dj6nuVP5qr6wwqeXHbdu/hqrT1hdv6N4KcIQ0amHJYR0iSgjYhxazS6NlQb6JacP1/A7EJ+ZRAg== + callsites@^3.0.0: version "3.1.0" resolved "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -1714,6 +1729,11 @@ client-only@^0.0.1: resolved "https://registry.npmmirror.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1" integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== +clsx@^1.1.1: + version "1.2.1" + resolved "https://registry.npmmirror.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" + integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== + clsx@^2.1.0: version "2.1.0" resolved "https://registry.npmmirror.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb" @@ -1785,7 +1805,7 @@ convert-source-map@^2.0.0: resolved "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== -copy-to-clipboard@^3.3.3: +copy-to-clipboard@^3.3.1, copy-to-clipboard@^3.3.3: version "3.3.3" resolved "https://registry.npmmirror.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz#55ac43a1db8ae639a4bd99511c148cdd1b83a1b0" integrity sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA== @@ -1817,7 +1837,22 @@ cross-spawn@^7.0.2: shebang-command "^2.0.0" which "^2.0.1" -csstype@^3.0.2, csstype@^3.1.3: +css-in-js-utils@^3.1.0: + version "3.1.0" + resolved "https://registry.npmmirror.com/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz#640ae6a33646d401fc720c54fc61c42cd76ae2bb" + integrity sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A== + dependencies: + hyphenate-style-name "^1.0.3" + +css-tree@^1.1.2: + version "1.1.3" + resolved "https://registry.npmmirror.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== + dependencies: + mdn-data "2.0.14" + source-map "^0.6.1" + +csstype@^3.0.2, csstype@^3.1.2, csstype@^3.1.3: version "3.1.3" resolved "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== @@ -1909,6 +1944,13 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" +error-stack-parser@^2.0.6: + version "2.1.4" + resolved "https://registry.npmmirror.com/error-stack-parser/-/error-stack-parser-2.1.4.tgz#229cb01cdbfa84440bfa91876285b94680188286" + integrity sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ== + dependencies: + stackframe "^1.3.4" + esbuild@^0.20.1: version "0.20.2" resolved "https://registry.npmmirror.com/esbuild/-/esbuild-0.20.2.tgz#9d6b2386561766ee6b5a55196c6d766d28c87ea1" @@ -2084,6 +2126,26 @@ fast-levenshtein@^2.0.6: resolved "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-loops@^1.1.3: + version "1.1.3" + resolved "https://registry.npmmirror.com/fast-loops/-/fast-loops-1.1.3.tgz#ce96adb86d07e7bf9b4822ab9c6fac9964981f75" + integrity sha512-8EZzEP0eKkEEVX+drtd9mtuQ+/QrlfW/5MlwcwK5Nds6EkZ/tRzEexkzUY2mIssnAyVLT+TKHuRXmFNNXYUd6g== + +fast-memoize@^2.5.1: + version "2.5.2" + resolved "https://registry.npmmirror.com/fast-memoize/-/fast-memoize-2.5.2.tgz#79e3bb6a4ec867ea40ba0e7146816f6cdce9b57e" + integrity sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw== + +fast-shallow-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz#d4dcaf6472440dcefa6f88b98e3251e27f25628b" + integrity sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw== + +fastest-stable-stringify@^2.0.2: + version "2.0.2" + resolved "https://registry.npmmirror.com/fastest-stable-stringify/-/fastest-stable-stringify-2.0.2.tgz#3757a6774f6ec8de40c4e86ec28ea02417214c76" + integrity sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q== + fastq@^1.6.0: version "1.17.1" resolved "https://registry.npmmirror.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" @@ -2281,6 +2343,11 @@ html-tokenize@^2.0.0: readable-stream "~1.0.27-1" through2 "~0.4.1" +hyphenate-style-name@^1.0.3: + version "1.0.4" + resolved "https://registry.npmmirror.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d" + integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ== + i18next-browser-languagedetector@^7.2.1: version "7.2.1" resolved "https://registry.npmmirror.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.1.tgz#1968196d437b4c8db847410c7c33554f6c448f6f" @@ -2326,6 +2393,14 @@ inherits@2, inherits@~2.0.1, inherits@~2.0.3: resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +inline-style-prefixer@^7.0.0: + version "7.0.0" + resolved "https://registry.npmmirror.com/inline-style-prefixer/-/inline-style-prefixer-7.0.0.tgz#991d550735d42069f528ac1bcdacd378d1305442" + integrity sha512-I7GEdScunP1dQ6IM2mQWh6v0mOYdYmH3Bp31UecKdrcUgcURTcctSe1IECdUznSHKSmsHtjrT3CwCPI1pyxfUQ== + dependencies: + css-in-js-utils "^3.1.0" + fast-loops "^1.1.3" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -2382,6 +2457,11 @@ isexe@^2.0.0: resolved "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +jotai-scope@^0.5.1: + version "0.5.1" + resolved "https://registry.npmmirror.com/jotai-scope/-/jotai-scope-0.5.1.tgz#a0eee96435262ff195901c420ca55b411639d8ef" + integrity sha512-GKaqtCj1Hv36nyl63PVm+s5jo+hQqz+wAb81iqA8VuUXp5ot4eXGOZt4Hc66lFZMP0N/yEOISMlazprmDI6kFA== + jotai-tanstack-query@^0.8.5: version "0.8.5" resolved "https://registry.npmmirror.com/jotai-tanstack-query/-/jotai-tanstack-query-0.8.5.tgz#12616f18a7623cc3786f3e2d7b1e1b2dc60394b2" @@ -2392,6 +2472,11 @@ jotai@^2.8.0: resolved "https://registry.npmmirror.com/jotai/-/jotai-2.8.0.tgz#5a6585cd5576c400c2c5f8e157b83ad2ba70b2ab" integrity sha512-yZNMC36FdLOksOr8qga0yLf14miCJlEThlp5DeFJNnqzm2+ZG7wLcJzoOyij5K6U6Xlc5ljQqPDlJRgqW0Y18g== +js-cookie@^2.2.1: + version "2.2.1" + resolved "https://registry.npmmirror.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" + integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -2516,6 +2601,11 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +mdn-data@2.0.14: + version "2.0.14" + resolved "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" + integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== + merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" @@ -2585,6 +2675,20 @@ multipipe@^1.0.2: duplexer2 "^0.1.2" object-assign "^4.1.0" +nano-css@^5.6.1: + version "5.6.1" + resolved "https://registry.npmmirror.com/nano-css/-/nano-css-5.6.1.tgz#964120cb1af6cccaa6d0717a473ccd876b34c197" + integrity sha512-T2Mhc//CepkTa3X4pUhKgbEheJHYAxD0VptuqFhDbGMUWVV2m+lkNiW/Ieuj35wrfC8Zm0l7HvssQh7zcEttSw== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.15" + css-tree "^1.1.2" + csstype "^3.1.2" + fastest-stable-stringify "^2.0.2" + inline-style-prefixer "^7.0.0" + rtl-css-js "^1.16.1" + stacktrace-js "^2.0.2" + stylis "^4.3.0" + nanoid@^3.3.7: version "3.3.7" resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" @@ -2775,7 +2879,7 @@ process-nextick-args@~2.0.0: resolved "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -prop-types@^15.5.10: +prop-types@^15.5.10, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -3176,6 +3280,18 @@ rc-virtual-list@^3.11.1, rc-virtual-list@^3.5.1, rc-virtual-list@^3.5.2: rc-resize-observer "^1.0.0" rc-util "^5.36.0" +re-resizable@6.9.6: + version "6.9.6" + resolved "https://registry.npmmirror.com/re-resizable/-/re-resizable-6.9.6.tgz#b95d37e3821481b56ddfb1e12862940a791e827d" + integrity sha512-0xYKS5+Z0zk+vICQlcZW+g54CcJTTmHluA7JUUgvERDxnKAnytylcyPsA+BSFi759s5hPlHmBRegFrwXs2FuBQ== + dependencies: + fast-memoize "^2.5.1" + +re-resizable@^6.9.11: + version "6.9.11" + resolved "https://registry.npmmirror.com/re-resizable/-/re-resizable-6.9.11.tgz#f356e27877f12d926d076ab9ad9ff0b95912b475" + integrity sha512-a3hiLWck/NkmyLvGWUuvkAmN1VhwAz4yOhS6FdMTaxCUVN9joIWkT11wsO68coG/iEYuwn+p/7qAmfQzRhiPLQ== + react-dom@^18.2.0: version "18.2.0" resolved "https://registry.npmmirror.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" @@ -3184,6 +3300,14 @@ react-dom@^18.2.0: loose-envify "^1.1.0" scheduler "^0.23.0" +react-draggable@4.4.5: + version "4.4.5" + resolved "https://registry.npmmirror.com/react-draggable/-/react-draggable-4.4.5.tgz#9e37fe7ce1a4cf843030f521a0a4cc41886d7e7c" + integrity sha512-OMHzJdyJbYTZo4uQE393fHcqqPYsEtkjfMgvCHr6rejT+Ezn4OZbNyGH50vv+SunC1RMvwOTSWkEODQLzw1M9g== + dependencies: + clsx "^1.1.1" + prop-types "^15.8.1" + react-i18next@^14.1.0: version "14.1.0" resolved "https://registry.npmmirror.com/react-i18next/-/react-i18next-14.1.0.tgz#44da74fbffd416f5d0c5307ef31735cf10cc91d9" @@ -3202,6 +3326,14 @@ react-is@^18.2.0: resolved "https://registry.npmmirror.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== +react-layout-kit@^1.9.0: + version "1.9.0" + resolved "https://registry.npmmirror.com/react-layout-kit/-/react-layout-kit-1.9.0.tgz#db04c27a048d76243c0a4dfb107f7aa0428e9c5f" + integrity sha512-YjFXGaWTemwagfdmqz1VxTATXCpQmDfW/giHOSQDrGsddyBC6MEFdj+kNHYY9WpRamQDjyzAIkOQaJ1KxmcC2g== + dependencies: + "@babel/runtime" "^7" + "@emotion/css" "^11" + react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.npmmirror.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" @@ -3212,6 +3344,15 @@ react-refresh@^0.14.0: resolved "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== +react-rnd@^10.4.2-test2: + version "10.4.2-test2" + resolved "https://registry.npmmirror.com/react-rnd/-/react-rnd-10.4.2-test2.tgz#09e6bcb8fca0c40e60829b89380bd64a017cb4fb" + integrity sha512-gIlqsvhAM3mNz+QKa7n/fOkChHwU5Hq0hiSq4d+kq9Q7mW0KO9jp+M9ufHGuPykbWv1LST5ybrxPVyddN7oXZg== + dependencies: + re-resizable "6.9.6" + react-draggable "4.4.5" + tslib "2.3.1" + react-sticky-box@^1.0.2: version "1.0.2" resolved "https://registry.npmmirror.com/react-sticky-box/-/react-sticky-box-1.0.2.tgz#7e72a0f237bdf8270cec9254337f49519a411174" @@ -3219,6 +3360,31 @@ react-sticky-box@^1.0.2: dependencies: resize-observer-polyfill "^1.5.1" +react-universal-interface@^0.6.2: + version "0.6.2" + resolved "https://registry.npmmirror.com/react-universal-interface/-/react-universal-interface-0.6.2.tgz#5e8d438a01729a4dbbcbeeceb0b86be146fe2b3b" + integrity sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw== + +react-use@^17.5.0: + version "17.5.0" + resolved "https://registry.npmmirror.com/react-use/-/react-use-17.5.0.tgz#1fae45638828a338291efa0f0c61862db7ee6442" + integrity sha512-PbfwSPMwp/hoL847rLnm/qkjg3sTRCvn6YhUZiHaUa3FA6/aNoFX79ul5Xt70O1rK+9GxSVqkY0eTwMdsR/bWg== + dependencies: + "@types/js-cookie" "^2.2.6" + "@xobotyi/scrollbar-width" "^1.9.5" + copy-to-clipboard "^3.3.1" + fast-deep-equal "^3.1.3" + fast-shallow-equal "^1.0.0" + js-cookie "^2.2.1" + nano-css "^5.6.1" + react-universal-interface "^0.6.2" + resize-observer-polyfill "^1.5.1" + screenfull "^5.1.0" + set-harmonic-interval "^1.0.1" + throttle-debounce "^3.0.1" + ts-easing "^0.2.0" + tslib "^2.1.0" + react@^18.2.0: version "18.2.0" resolved "https://registry.npmmirror.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" @@ -3323,6 +3489,13 @@ rollup@^4.13.0: "@rollup/rollup-win32-x64-msvc" "4.14.2" fsevents "~2.3.2" +rtl-css-js@^1.16.1: + version "1.16.1" + resolved "https://registry.npmmirror.com/rtl-css-js/-/rtl-css-js-1.16.1.tgz#4b48b4354b0ff917a30488d95100fbf7219a3e80" + integrity sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg== + dependencies: + "@babel/runtime" "^7.1.2" + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -3347,6 +3520,11 @@ scheduler@^0.23.0: dependencies: loose-envify "^1.1.0" +screenfull@^5.1.0: + version "5.2.0" + resolved "https://registry.npmmirror.com/screenfull/-/screenfull-5.2.0.tgz#6533d524d30621fc1283b9692146f3f13a93d1ba" + integrity sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA== + scroll-into-view-if-needed@^3.1.0: version "3.1.0" resolved "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz#fa9524518c799b45a2ef6bbffb92bcad0296d01f" @@ -3366,6 +3544,11 @@ semver@^7.6.0: dependencies: lru-cache "^6.0.0" +set-harmonic-interval@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/set-harmonic-interval/-/set-harmonic-interval-1.0.1.tgz#e1773705539cdfb80ce1c3d99e7f298bb3995249" + integrity sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g== + shallowequal@^1.1.0: version "1.1.0" resolved "https://registry.npmmirror.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" @@ -3393,11 +3576,50 @@ source-map-js@^1.2.0: resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== +source-map@0.5.6: + version "0.5.6" + resolved "https://registry.npmmirror.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" + integrity sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA== + source-map@^0.5.7: version "0.5.7" resolved "https://registry.npmmirror.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== +source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +stack-generator@^2.0.5: + version "2.0.10" + resolved "https://registry.npmmirror.com/stack-generator/-/stack-generator-2.0.10.tgz#8ae171e985ed62287d4f1ed55a1633b3fb53bb4d" + integrity sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ== + dependencies: + stackframe "^1.3.4" + +stackframe@^1.3.4: + version "1.3.4" + resolved "https://registry.npmmirror.com/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310" + integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw== + +stacktrace-gps@^3.0.4: + version "3.1.2" + resolved "https://registry.npmmirror.com/stacktrace-gps/-/stacktrace-gps-3.1.2.tgz#0c40b24a9b119b20da4525c398795338966a2fb0" + integrity sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ== + dependencies: + source-map "0.5.6" + stackframe "^1.3.4" + +stacktrace-js@^2.0.2: + version "2.0.2" + resolved "https://registry.npmmirror.com/stacktrace-js/-/stacktrace-js-2.0.2.tgz#4ca93ea9f494752d55709a081d400fdaebee897b" + integrity sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg== + dependencies: + error-stack-parser "^2.0.6" + stack-generator "^2.0.5" + stacktrace-gps "^3.0.4" + statuses@~1.5.0: version "1.5.0" resolved "https://registry.npmmirror.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" @@ -3437,7 +3659,7 @@ stylis@4.2.0: resolved "https://registry.npmmirror.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== -stylis@^4.0.13: +stylis@^4.0.13, stylis@^4.3.0: version "4.3.1" resolved "https://registry.npmmirror.com/stylis/-/stylis-4.3.1.tgz#ed8a9ebf9f76fe1e12d462f5cc3c4c980b23a7eb" integrity sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ== @@ -3474,6 +3696,11 @@ text-table@^0.2.0: resolved "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +throttle-debounce@^3.0.1: + version "3.0.1" + resolved "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-3.0.1.tgz#32f94d84dfa894f786c9a1f290e7a645b6a19abb" + integrity sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg== + throttle-debounce@^5.0.0: version "5.0.0" resolved "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-5.0.0.tgz#a17a4039e82a2ed38a5e7268e4132d6960d41933" @@ -3529,7 +3756,17 @@ ts-api-utils@^1.3.0: resolved "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== -tslib@^2.0.0, tslib@^2.0.3: +ts-easing@^0.2.0: + version "0.2.0" + resolved "https://registry.npmmirror.com/ts-easing/-/ts-easing-0.2.0.tgz#c8a8a35025105566588d87dbda05dd7fbfa5a4ec" + integrity sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ== + +tslib@2.3.1: + version "2.3.1" + resolved "https://registry.npmmirror.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + +tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0: version "2.6.2" resolved "https://registry.npmmirror.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==