diff --git a/src/catalogue/components/catalogue.tsx b/src/catalogue/components/catalogue.tsx new file mode 100644 index 000000000..8a961ffa1 --- /dev/null +++ b/src/catalogue/components/catalogue.tsx @@ -0,0 +1,203 @@ +import React, { useState } from 'react'; +import { Dropdown, DropdownProps, Form, Input, Tabs } from 'antd'; +import { BlockHeader, EllipsisText } from 'dt-react-component'; +import { IBlockHeaderProps } from 'dt-react-component/blockHeader'; + +import { ITreeNode } from '../useTreeData'; +import { CatalogIcon, CloseIcon, DragIcon, EllipsisIcon, SearchIcon } from './icon'; +import CatalogueTree, { ICatalogueTree } from './tree'; + +interface Tab { + readonly key: string; + readonly title: React.ReactNode; +} + +type readOnlyTab = readonly Tab[]; + +type TabKey = T[number]['key']; + +interface NormalCatalogueProps = {}> + extends Partial>, + ICatalogueTree { + showSearch?: boolean; + edit?: boolean; + placeholder?: string; + loading?: boolean; + onCancelSave?: (item: ITreeNode) => void; + overlay?: (item: ITreeNode) => DropdownProps['overlay']; + onSearch?: (value: string) => void; + onSave?: (data: ITreeNode, value: string) => Promise; +} +interface TabsCatalogueProps, T extends readOnlyTab> + extends NormalCatalogueProps { + tabList?: T; + activeTabKey?: TabKey; + defaultTabKey?: TabKey; + onTabChange?: (key: TabKey) => void; +} + +export type CatalogueProps = {}, T extends readOnlyTab = any> = + | TabsCatalogueProps + | NormalCatalogueProps; + +function isTabMode = {}, T extends readOnlyTab = any>( + props: CatalogueProps +): props is TabsCatalogueProps { + return 'tabList' in props; +} + +const Catalogue = = {}, T extends readOnlyTab = any>( + props: CatalogueProps +) => { + const { + title, + addonBefore = , + tooltip = false, + showSearch = false, + placeholder = '搜索目录名称', + addonAfter, + edit = true, + treeData, + draggable, + titleRender, + overlay, + onSearch, + onSave, + onCancelSave, + ...rest + } = props; + + const [tabSearch, setTabSearch] = useState(false); + + const [form] = Form.useForm(); + + const defaultTitleRender = (item: ITreeNode) => { + if (item.edit) { + return ( +
+ + form.setFields([{ name: 'catalog_input', errors: [] }])} + onClick={(e) => e.stopPropagation()} + onBlur={({ target }) => handleInputSubmit(item, target.value)} + onPressEnter={({ target }) => + handleInputSubmit(item, (target as any).value) + } + /> + +
+ ); + } + return ( +
+
+ +
+ {edit && renderNodeHover(item)} +
+ ); + }; + + const renderHeader = () => { + if (!title) return null; + return ( +
+ +
+ ); + }; + + const renderSearch = () => { + if (!showSearch || (isTabMode(props) && !tabSearch)) return null; + return ( +
+ + {isTabMode(props) && ( + setTabSearch(false)} /> + )} +
+ ); + }; + + const renderTab = () => { + if (!isTabMode(props) || tabSearch) return null; + const { activeTabKey, tabList, onTabChange } = props; + return ( + setTabSearch(true)} /> + } + activeKey={activeTabKey} + onChange={onTabChange} + > + {tabList?.map((tab: { key: string; title: React.ReactNode }) => ( + + ))} + + ); + }; + + const handleInputSubmit = (item: ITreeNode, value: string) => { + if (!value) { + return onCancelSave?.(item); + } + onSave?.(item, value).then((msg) => { + form.setFields([{ name: 'catalog_input', errors: msg ? [msg] : [] }]); + }); + }; + + const renderNodeHover = (item: ITreeNode) => { + return ( +
{ + e.stopPropagation(); + }} + > + {overlay && ( + + triggerNode.parentElement as HTMLElement + } + > + e.stopPropagation()} /> + + )} + {draggable && } +
+ ); + }; + + return ( +
+ {renderHeader()} + {renderSearch()} + {renderTab()} + +
+ ); +}; + +export default Catalogue; diff --git a/src/catalogue/components/icon.tsx b/src/catalogue/components/icon.tsx new file mode 100644 index 000000000..73203ed57 --- /dev/null +++ b/src/catalogue/components/icon.tsx @@ -0,0 +1,201 @@ +import React from 'react'; +import classNames from 'classnames'; + +interface IIcon { + className?: string; + style?: React.CSSProperties; + onClick?: React.MouseEventHandler; +} + +const FolderIcon = function ({ className, ...rest }: IIcon) { + return ( + + + + + + + + + + ); +}; + +const FolderOpenedIcon = function ({ className, ...rest }: IIcon) { + return ( + + + + + + + + + + ); +}; + +const DownTriangleIcon = function ({ className, ...rest }: IIcon) { + return ( + + + + + + + + ); +}; + +const CatalogIcon = function ({ className, ...rest }: IIcon) { + return ( + + + + + + + + ); +}; + +const DragIcon = function ({ className, ...rest }: IIcon) { + return ( + + + + + + + + ); +}; + +const EllipsisIcon = function ({ className, ...rest }: IIcon) { + return ( + + + + + + + + ); +}; + +const SearchIcon = function ({ className, ...rest }: IIcon) { + return ( + + + + + + ); +}; + +const CloseIcon = function ({ className, ...rest }: IIcon) { + return ( + + + + + + ); +}; + +export { + CatalogIcon, + CloseIcon, + DownTriangleIcon, + DragIcon, + EllipsisIcon, + FolderIcon, + FolderOpenedIcon, + SearchIcon, +}; diff --git a/src/catalogue/components/index.ts b/src/catalogue/components/index.ts deleted file mode 100644 index 8a3022552..000000000 --- a/src/catalogue/components/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { default as Tree } from './tree'; -export { type ITabsStatus, type ITreeDataItem } from './tree'; -export { default as TreeSelect } from './treeSelect'; diff --git a/src/catalogue/components/tree.tsx b/src/catalogue/components/tree.tsx new file mode 100644 index 000000000..cd2a2beec --- /dev/null +++ b/src/catalogue/components/tree.tsx @@ -0,0 +1,42 @@ +import React, { useMemo } from 'react'; +import { Spin, Tree, TreeProps } from 'antd'; +import { Empty } from 'dt-react-component'; + +import { ITreeNode } from '../useTreeData'; +import { loopTree } from '../utils'; +import { DownTriangleIcon } from './icon'; + +export interface ICatalogueTree = {}> + extends Omit { + loading?: boolean; + treeData: ITreeNode[]; + titleRender?: TreeProps>['titleRender']; +} + +const CatalogueTree = = {}>({ + treeData = [], + loading = false, + titleRender, + ...rest +}: ICatalogueTree) => { + const renderTreeData = useMemo(() => loopTree(treeData) || [], [treeData]); + + if (!renderTreeData.length) return ; + + return ( +
+ + > + showLine={{ showLeafIcon: false }} + switcherIcon={} + showIcon + treeData={renderTreeData} + titleRender={titleRender} + {...rest} + /> + +
+ ); +}; + +export default CatalogueTree; diff --git a/src/catalogue/components/tree/components/header/index.tsx b/src/catalogue/components/tree/components/header/index.tsx deleted file mode 100644 index 54890695e..000000000 --- a/src/catalogue/components/tree/components/header/index.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import * as React from 'react'; -import { MenuFoldOutlined, MenuUnfoldOutlined, ProfileFilled } from '@ant-design/icons'; - -import { ITreeProps } from '../../'; -import './style.scss'; - -export const prefixCls = 'dtTreeHeaderWrapper'; - -export const btnSlotProp = 'btnSlot'; - -export interface ITreeHeaderProps { - /** 目录标题 */ - title?: React.ReactNode; - /** 是否收起 */ - collapsed?: boolean; - /** 展开、收起的回调 */ - onCollapsed?: (collapsed: boolean) => void; - /** Header 右侧按扭组插槽【建议 icon 不超过3个,超出使用更多icon,下拉显示】,showHeader 为 true 时生效 */ - btnSlot?: React.ReactElement; - size?: ITreeProps['size']; -} - -export default (props: ITreeHeaderProps) => { - const { onCollapsed, btnSlot } = props; - const handleCollapsed = (flag: boolean) => (_: any) => { - onCollapsed?.(flag); - }; - return ( -
-
- - {props?.title ?? ''} -
-
- {btnSlot} - {props.collapsed ? ( - - ) : ( - - )} -
-
- ); -}; diff --git a/src/catalogue/components/tree/components/header/style.scss b/src/catalogue/components/tree/components/header/style.scss deleted file mode 100644 index b11ae6ed4..000000000 --- a/src/catalogue/components/tree/components/header/style.scss +++ /dev/null @@ -1,27 +0,0 @@ -.dtTreeHeaderWrapper { - display: flex; - align-items: center; - justify-content: space-between; - padding: 12px 12px 0; - margin-bottom: 8px; - &-left { - display: flex; - align-items: center; - &__catalogueIcon { - font-size: 20px; - margin-right: 4px; - color: #1D78FF; - } - &__title { - font-size: 14px; - } - } - &-right { - display: flex; - align-items: center; - } - &-menuFold { - font-size: 20px; - color: #AAA; - } -} diff --git a/src/catalogue/components/tree/components/index.ts b/src/catalogue/components/tree/components/index.ts deleted file mode 100644 index 7899000d7..000000000 --- a/src/catalogue/components/tree/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as Header } from './header'; diff --git a/src/catalogue/components/tree/helpers/index.tsx b/src/catalogue/components/tree/helpers/index.tsx deleted file mode 100644 index dfc12767b..000000000 --- a/src/catalogue/components/tree/helpers/index.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import * as React from 'react'; -import { FolderFilled, FolderOpenFilled } from '@ant-design/icons'; -import type { TreeProps } from 'antd'; -import type { DataNode } from 'antd/es/tree'; -import { ContextMenu } from 'dt-react-component'; - -import type { ITreeDataItem } from '..'; - -/** - * @description 根据 query 计算应该展开的 expendKeys - */ -export const getExpendKeysByQuery = ({ - tree, - searchStr, -}: { - /** 树数据 */ - tree: DataNode[]; - /** 搜索字符串 */ - searchStr: string; -}): React.Key[] => { - const keys: React.Key[] = []; - if (!searchStr) return []; - tree?.forEach?.((item) => { - if (item?.title?.toString()?.includes?.(searchStr)) { - keys.push(item.key); - } - if (item?.children && item?.children?.length) { - keys.push(...getExpendKeysByQuery({ tree: item?.children, searchStr })); - } - }); - return keys; -}; - -/** - * @description 文件 展开/收起样式 - */ -export const getIcon: DataNode['icon'] = ({ expanded, data }) => { - if (!data) return null; - if (!data?.hasOwnProperty('children')) return null; - const styles: React.CSSProperties = { - fontSize: 14, - color: '#1D78FF', - }; - if (expanded) return ; - return ; -}; - -/** - * @description 轮询 Tree 数据,赋值 搜索标识和leafIcon - */ -export const loopTree = ( - data: ITreeDataItem[] | undefined, - searchValue: string -): TreeProps['treeData'] => { - return data?.map((item: ITreeDataItem) => { - const strTitle = item?.title as string; - const index = strTitle?.indexOf?.(searchValue); - const beforeStr = strTitle?.substring?.(0, index); - const afterStr = strTitle?.slice?.(index + searchValue?.length); - const contextMenuData = - item?.contextMenuConfig?.data?.map?.((e: any) => ({ - ...e, - cb: () => { - item?.contextMenuConfig?.onClick(e, item); - }, - })) || []; - const title = - index > -1 ? ( - - {beforeStr} - {searchValue} - {afterStr} - - ) : ( - {strTitle} - ); - if (item.children) { - return { - icon: getIcon, - ...item, - title, - key: item.key, - children: loopTree(item.children, searchValue), - }; - } - return { ...item, title, key: item.key, children: undefined }; - }); -}; diff --git a/src/catalogue/components/tree/index.tsx b/src/catalogue/components/tree/index.tsx deleted file mode 100644 index d2f23c4bb..000000000 --- a/src/catalogue/components/tree/index.tsx +++ /dev/null @@ -1,330 +0,0 @@ -import * as React from 'react'; -import { useCallback, useEffect, useMemo, useReducer } from 'react'; -import { - CaretDownOutlined, - CloseOutlined, - MenuUnfoldOutlined, - SearchOutlined, -} from '@ant-design/icons'; -import type { TreeProps } from 'antd'; -import { Input, Spin, Tabs, Tree } from 'antd'; -import type { TabsProps } from 'antd/es/tabs'; -import type { DataNode } from 'antd/es/tree'; - -import type { ITreeHeaderProps } from './components/header'; -import { Header } from './components'; -import { getExpendKeysByQuery, loopTree } from './helpers'; -import './style.scss'; - -export const prefixCls = 'dtTreeWrapper'; - -/** ContextMenu item props */ -type ContextItemProps = { text: React.ReactNode; key: React.Key }; - -export enum ITabsStatus { - /** 搜索态 */ - search = 'search', - /** tab 切换态 */ - tabs = 'tabs', -} - -export interface ITreeDataItem extends DataNode { - /** Item ContextMenu 配置 */ - contextMenuConfig?: { - data: Array; - onClick: (e: ContextItemProps, item: ITreeDataItem) => void; - }; - children?: ITreeDataItem[]; -} - -export interface ITabsItem { - /** 选项卡头显示文字 */ - label: React.ReactNode; - /** 对应 activeKey */ - key: string; - /** 禁用某一项 */ - disabled?: boolean; - // [key: string]: any; -} - -export interface ITreeProps extends TreeProps, Pick { - /** 是否加载中 */ - loading?: boolean; - /** 是否展示头部组件 */ - showHeader?: boolean; - /** 头部文案 */ - treeTitle?: React.ReactNode; - /** 容器类名 */ - wrapperClassName?: string; - /** 容器行内样式 */ - wrapperStyle?: React.CSSProperties; - /** 尺寸大小,small 每一个 item 高度是 28px,middle 每一个 item 高度是 32px, large 未实现 ,默认为 small */ - size?: 'small' | 'middle'; - /** 点击搜索按钮回调 */ - onSearch?: ( - value: string, - e: - | React.ChangeEvent - | React.MouseEvent - | React.KeyboardEvent - | undefined - ) => void; - /** tabs 配置 */ - tabsProps?: TabsProps & { - /** 相当于 antd >=4.23.0 时的 items,目前只实现了 label、key、disabled > */ - items: ITabsItem[]; - }; - /** 与 TreeProps['treeData'] 类型相似,只是增加了 ContextMenu 配置 */ - treeData?: ITreeDataItem[]; - /** 默认展示 tabs 还是 search,仅 items 有值时生效 */ - defaultStatus?: ITabsStatus; - /** tabs or status 变化时的回调 */ - onStatusChange?: ( - status: ITabsStatus, - e: - | React.ChangeEvent - | React.MouseEvent - | React.KeyboardEvent - | undefined - ) => void; -} - -interface IState { - searchStr: string | undefined; - collapsed: boolean; - _expandedKeys: React.Key[]; - /** 搜索状态,默认 search 状态,若 tabsItems 有值则可以改动 */ - status: ITabsStatus; -} - -const initState: IState = { - searchStr: undefined, - collapsed: false, - _expandedKeys: [], - status: ITabsStatus.search, -}; - -enum Action { - UPDATE = 'update', - RESET = 'reset', -} - -interface IAction { - type: Action; - payload: Partial; -} - -const reducer = (state: IState, action: IAction) => { - switch (action.type) { - case Action.RESET: - return initState; - case Action.UPDATE: - return { ...state, ...action.payload }; - default: - return state; - } -}; - -const DtTree = (props: ITreeProps) => { - const { - showHeader, - treeTitle, - wrapperClassName, - wrapperStyle, - onSearch, - treeData, - tabsProps, - loading, - size, - btnSlot, - defaultStatus, - onStatusChange, - ...restProps - } = props; - const [{ searchStr, collapsed, _expandedKeys, status }, dispatch] = useReducer(reducer, { - ...initState, - status: defaultStatus || initState.status, - }); - - useEffect(() => { - dispatch({ type: Action.UPDATE, payload: { _expandedKeys: restProps.expandedKeys } }); - }, [restProps.expandedKeys]); - - const data: TreeProps['treeData'] = useMemo( - () => loopTree(treeData, searchStr as string), - [treeData, searchStr] - ); - - const handleSearch = useCallback( - ( - val: string, - e?: - | React.ChangeEvent - | React.MouseEvent - | React.KeyboardEvent - | undefined - ) => { - const newExpandedKeys = getExpendKeysByQuery({ - tree: treeData as DataNode[], - searchStr: val, - }); - dispatch({ - type: Action.UPDATE, - payload: { - _expandedKeys: newExpandedKeys, - searchStr: val, - }, - }); - onSearch?.(val, e); - }, - [treeData, onSearch] - ); - - const handleChange: React.ChangeEventHandler = useCallback( - (e) => { - const val = e?.target?.value; - const newKeys = getExpendKeysByQuery({ tree: treeData as DataNode[], searchStr: val }); - dispatch({ - type: Action.UPDATE, - payload: { - _expandedKeys: newKeys, - searchStr: val, - }, - }); - }, - [treeData] - ); - - const toggleCollapsed = useCallback((flag: boolean) => { - dispatch({ type: Action.UPDATE, payload: { collapsed: flag } }); - }, []); - - const onExpand = useCallback((newExpandedKeys: React.Key[]) => { - dispatch({ type: Action.UPDATE, payload: { _expandedKeys: newExpandedKeys } }); - }, []); - const handleChangeToSearch = useCallback( - (e: any) => { - dispatch({ - type: Action.UPDATE, - payload: { status: ITabsStatus.search }, - }); - onStatusChange?.(ITabsStatus.search, e); - }, - [onStatusChange] - ); - - const handleChangeToTabs = useCallback( - (e: any) => { - dispatch({ - type: Action.UPDATE, - payload: { status: ITabsStatus.tabs, searchStr: undefined }, - }); - onStatusChange?.(ITabsStatus.tabs, e); - }, - [onStatusChange] - ); - - const renderHeader = useCallback(() => { - if (!showHeader) return null; - return ( -
- ); - }, [showHeader, treeTitle, collapsed, size, btnSlot, toggleCollapsed]); - const renderTabsAndSearch = useCallback(() => { - const { items, ...restTabsProps } = tabsProps || {}; - if (status === ITabsStatus.tabs && items?.length) { - return ( - - } - size={size} - {...restTabsProps} - > - {items?.map((item) => ( - - ))} - - ); - } else if (status === ITabsStatus.search || !items?.length) { - return ( -
- { - handleSearch(e?.target?.value, e); - }} - /> - {items?.length ? ( - - ) : null} -
- ); - } - return null; - }, [ - handleSearch, - handleChange, - tabsProps, - status, - size, - handleChangeToSearch, - handleChangeToTabs, - ]); - if (collapsed) { - return ( - { - toggleCollapsed(false); - }} - /> - ); - } - return ( - -
- {renderHeader()} - {renderTabsAndSearch()} - -
-
- ); -}; - -DtTree.defaultProps = { - loading: false, - size: 'middle', - showHeader: true, - treeTitle: '标签目录', - collapsed: true, - showIcon: true, - showLine: { showLeafIcon: false }, - switcherIcon: , - defaultStatus: ITabsStatus.tabs, -} as ITreeProps; - -export default DtTree; diff --git a/src/catalogue/components/tree/style.scss b/src/catalogue/components/tree/style.scss deleted file mode 100644 index 3f32d63e9..000000000 --- a/src/catalogue/components/tree/style.scss +++ /dev/null @@ -1,128 +0,0 @@ -@import "../../consts.scss"; - -$smallSize: 24px; -$middleSize: 36px; - -.dtTreeWrapper { - background-color: $white; - &-icon { - &__unfold { - font-size: $font20; - color: $navy4; - } - &__search { - margin-left: 12px; - font-size: $font14; - color: $navy4; - } - &__close { - margin-left: 12px; - font-size: $font14; - color: $navy4; - } - } - &__search { - display: flex; - flex-direction: row; - align-items: center; - padding: 0 12px; - } - .ant-tree { - .ant-tree-node-content-wrapper.ant-tree-node-selected { - background-color: transparent; - } - &-list { - margin-top: 8px; - } - &-treenode { - width: 100%; - padding: 0 12px; - align-items: center; - .ant-tree-node-content-wrapper { - width: 100%; - padding: 0; - // TODO 会导致拖拽时遮挡 - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - &:hover { - background-color: transparent; - .ant-tree-switcher { - background-color: $navy7; - } - } - } - .ant-tree-switcher { - background: none; - } - &-selected { - position: relative; - background-color: $bg1; - &::before { - content: ""; - position: absolute; - left: 0; - width: 4px; - height: $smallSize; - background-color: $primary; - } - } - &:hover { - background-color: $navy7; - } - } - } - .ant-tabs { - padding: 0 12px; - &-nav { - margin-bottom: 0; - } - } - &-small { - font-size: $font14; - .ant-tree { - &-treenode { - .ant-tree-switcher { - width: $smallSize; - line-height: $smallSize; - } - &-selected { - &::before { - height: $smallSize; - } - } - } - } - } - &-middle { - font-size: $font16; - .ant-tree { - &-treenode { - .ant-tree-switcher { - // width: $middleSize; - line-height: $middleSize; - } - .ant-tree-node-content-wrapper { - min-height: $middleSize; - line-height: $middleSize; - .ant-tree-iconEle { - // width: $middleSize; - height: $middleSize; - line-height: $middleSize; - .anticon { - vertical-align: middle; - svg { - font-size: 18px; - } - } - } - } - &-selected { - &::before { - height: $middleSize; - } - } - } - } - } -} diff --git a/src/catalogue/components/treeSelect/index.tsx b/src/catalogue/components/treeSelect/index.tsx deleted file mode 100644 index fdfd9507c..000000000 --- a/src/catalogue/components/treeSelect/index.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react'; -import type { TreeSelectProps } from 'antd'; -import { TreeSelect } from 'antd'; - -export interface IProps extends TreeSelectProps {} - -export default (props: IProps) => ; diff --git a/src/catalogue/components/treeSelect/style.scss b/src/catalogue/components/treeSelect/style.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/catalogue/consts.scss b/src/catalogue/consts.scss deleted file mode 100644 index f0190104c..000000000 --- a/src/catalogue/consts.scss +++ /dev/null @@ -1,93 +0,0 @@ -// ==== 颜色 ==== -$bgColor: #F2F3F8; // 背景色 -$color1: #2E3943; // 主色 -$color2: #2491F7; // 第二色 - -// ==== 字体 ==== -$font1: 14px; -$font2: 16px; -$fontColor: #9FA9BA; -// ==== 页面大小 ==== -$minWidth:1260px; - -// ====== UI 5.0 颜色规范 ====== - -// DTinsight Blue - -/** 用于主按钮底色,描边按钮,可操作项,tab选中状态的底部线条,运行中的状态以及文字图标颜色 */ -$primary: #1D78FF; - -/** 用于主按钮底色,描边按钮,可操作项,tab选中状态的hover状态填充色 */ -$hover: #0A67F2; - -/** 用于主按钮底色,描边按钮,可操作项,tab选中状态的点击状态填充色 */ -$click: #005CE6; - -/** 用于主按钮底色,描边按钮,可操作项的不可用状态填充色 */ -$disable: #BBD6FF; - -/** 用于小模块切换背景,提示背景 */ -$bg1: #E8F1FF; - -/** 用于整个页面的底色 */ -$bg2: #F2F7FA; - -// DTinsight Navy - -/** 用于标题,主文字颜色 */ -$navy1: #3D446E; - -/** 用于表单标题备注说明文字颜色 */ -$navy2: #64698B; - -/** 用于分页器提示文字颜色 */ -$navy3: #8B8FA8; - -/** 用于文本框内的提示文字颜色 */ -$navy4: #B1B4C5; - -/** 用于表单边框颜色,图表线条颜色 */ -$navy5: #D8DAE2; - -/** 用于分割线、失效按钮状态颜色 */ -$navy6: #EBECF0; - -/** 用于模块标题背景色 */ -$navy7: #F5F5F8; - -/** 用于二级导航底色 */ -$navy8: #F9F9FA; - -/** 一级按钮文字颜色 */ -$white: #FFF; - -// Functional Color - -/** 用于成功状态颜色 */ -$green: #11D7B2; - -/** 用于失败状态、信息文字、必填项图标颜色 */ -$red: #F96C5B; - -/** 用于等待状态、警告提示颜色 */ -$yellow: #FBB310; - -/** 用于取消、冻结状态、未开始、未完成、未进行、未配置提示颜色 */ -$purple: #AC9DFF; - -// ====== UI 5.0 字体规范 ====== - -/** 主字体大小,应用于列表内容,下拉、选择、输入框,弹窗等控件文字 */ -$font12: 12px; - -/** 应用于左侧菜单栏字体,主标题字体,弹窗标题字体大小 */ -$font14: 14px; - -/** 应用于顶部导航字体和部分需要强调的标题 */ -$font16: 16px; - -/** 较少使用,应用于部分大标题 */ -$font20: 20px; - -/** 应用于数据统计概览页面等需突出数字 */ -$font40: 40px; diff --git a/src/catalogue/demos/async.tsx b/src/catalogue/demos/async.tsx new file mode 100644 index 000000000..57bf688f9 --- /dev/null +++ b/src/catalogue/demos/async.tsx @@ -0,0 +1,46 @@ +import React, { useEffect } from 'react'; +import { Catalogue } from 'dt-react-component'; + +import { useTreeData } from '../useTreeData'; +import { insertChildIntoNode } from '../utils'; + +interface DataNode { + title: string; + key: string; + isLeaf?: boolean; + children?: DataNode[]; +} + +const initTreeData: DataNode[] = [ + { title: 'Expand to load', key: '0' }, + { title: 'Expand to load', key: '1' }, + { title: 'Tree Node', key: '2', isLeaf: true }, +]; + +const App: React.FC = () => { + const treeData = useTreeData(); + + useEffect(() => { + treeData.onChange(initTreeData); + }, []); + + const onLoadData = ({ key, children }: any) => + new Promise((resolve) => { + if (children) { + resolve(); + return; + } + setTimeout(() => { + const newData = insertChildIntoNode(treeData.data, key, [ + { title: 'Child Node', key: `${key}-0` }, + { title: 'Child Node', key: `${key}-1` }, + ]); + treeData.onChange(newData); + resolve(); + }, 1000); + }); + + return ; +}; + +export default App; diff --git a/src/catalogue/demos/basic.tsx b/src/catalogue/demos/basic.tsx new file mode 100644 index 000000000..e09058c0a --- /dev/null +++ b/src/catalogue/demos/basic.tsx @@ -0,0 +1,75 @@ +import React, { useEffect } from 'react'; +import { Catalogue, EllipsisText } from 'dt-react-component'; + +import { useTreeData } from '../useTreeData'; + +const DEFAULT_DATA = [ + { + title: '0-0', + key: '0-0', + children: [ + { + title: ( + + ), + key: '0-0-0', + children: [ + { title: '0-0-0-0', key: '0-0-0-0' }, + { title: '0-0-0-1', key: '0-0-0-1' }, + { title: '0-0-0-2', key: '0-0-0-2' }, + ], + }, + { + title: '0-0-1', + key: '0-0-1', + children: [ + { title: '0-0-1-0', key: '0-0-1-0' }, + { title: '0-0-1-1', key: '0-0-1-1' }, + { title: '0-0-1-2', key: '0-0-1-2' }, + ], + }, + { + title: '0-0-2', + key: '0-0-2', + }, + ], + }, + { + title: '0-1', + key: '0-1', + children: [ + { title: '0-1-0-0', key: '0-1-0-0' }, + { title: '0-1-0-1', key: '0-1-0-1' }, + { title: '0-1-0-2', key: '0-1-0-2' }, + ], + }, + { + title: '0-2', + key: '0-2', + }, +]; + +export default () => { + const treeData = useTreeData(); + + useEffect(() => { + treeData.onChange(DEFAULT_DATA); + }, []); + + return ( +
+ +
+ ); +}; diff --git a/src/catalogue/demos/config.tsx b/src/catalogue/demos/config.tsx new file mode 100644 index 000000000..6cacf2f50 --- /dev/null +++ b/src/catalogue/demos/config.tsx @@ -0,0 +1,273 @@ +import React, { useEffect } from 'react'; +import { DeleteOutlined, EditOutlined, PlusSquareOutlined } from '@ant-design/icons'; +import { Menu } from 'antd'; +import { Catalogue, EllipsisText } from 'dt-react-component'; +import { cloneDeep } from 'lodash'; +import shortid from 'shortid'; + +import { CatalogueProps } from '../components/catalogue'; +import { ITreeNode, useTreeData } from '../useTreeData'; +import { + appendNodeByKey, + findNodeByKey, + findParentNodeByKey, + removeEditNode, + removeNodeByKey, + updateTreeNodeEdit, +} from '../utils'; + +const DEFAULT_DATA: ITreeNode[] = [ + { + title: '不可编辑的节点', + editable: false, + key: '0-0', + children: [ + { + title: ( + + ), + key: '0-0-0', + children: [ + { + title: '0-0-0-0', + key: '0-0-0-0', + }, + { title: '0-0-0-1', key: '0-0-0-1' }, + { title: '0-0-0-2', key: '0-0-0-2' }, + ], + }, + { + title: '0-0-1', + key: '0-0-1', + children: [ + { title: '0-0-1-0', key: '0-0-1-0' }, + { title: '0-0-1-1', key: '0-0-1-1' }, + { title: '0-0-1-2', key: '0-0-1-2' }, + ], + }, + { + title: '0-0-2', + key: '0-0-2', + }, + ], + }, + { + title: '不可新增的节点', + addable: false, + key: '0-1', + children: [ + { title: '0-1-0-0', key: '0-1-0-0' }, + { title: '0-1-0-1', key: '0-1-0-1' }, + { title: '0-1-0-2', key: '0-1-0-2' }, + ], + }, + { + title: '不可删除节点', + key: '0-2', + deletable: false, + }, +]; + +interface IData { + edit?: boolean; + addable?: boolean; + deletable?: boolean; + editable?: boolean; +} + +export default () => { + const treeData = useTreeData(); + + const handleEdit = (key: ITreeNode['key']) => { + const data = updateTreeNodeEdit(treeData.data, key); + treeData.onChange(data); + }; + + const handleAdd = (key: ITreeNode['key']) => { + const newExpandedKeys = treeData.expandedKeys ? [...treeData.expandedKeys] : []; + const data = appendNodeByKey(treeData.data, key); + if (!newExpandedKeys?.includes(key)) { + newExpandedKeys.push(key); + } + treeData.onExpand(newExpandedKeys); + treeData.onChange(data); + }; + + const handleSave = async (data: ITreeNode, value: string) => { + const newData = cloneDeep(treeData.data); + if (!data.key) { + newData.push({ + title: value, + key: shortid(), + }); + newData.shift(); + treeData.onChange(newData); + return; + } + if ((data.key as string).startsWith('new')) { + let node = findParentNodeByKey(newData, data.key); + if (!node) return; + if (node.children) { + const newChildren = node.children + .filter((item) => !item.edit) + .concat({ title: value, key: shortid() }); + node.children = newChildren; + } else { + node = { + ...node, + children: [ + { + title: value, + key: shortid(), + }, + ], + }; + } + treeData.onChange(newData); + return; + } + const node = findNodeByKey(newData, data.key); + if (node) { + node.title = value; + node.edit = false; + } + treeData.onChange(newData); + }; + + const handleCancelSave = () => { + const newData = removeEditNode(treeData.data); + treeData.onChange(newData); + }; + + const handleDelete = (data: ITreeNode) => { + let newData = cloneDeep(treeData.data); + newData = removeNodeByKey(newData, data.key); + treeData.onChange(newData); + }; + + const handleDrop: CatalogueProps['onDrop'] = (info) => { + const dropKey = info.node.key; + const dragKey = info.dragNode.key; + const dropPos = info.node.pos.split('-'); + const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]); // the drop position relative to the drop node, inside 0, top -1, bottom 1 + + const loop = ( + data: ITreeNode[], + key: React.Key, + callback: (node: ITreeNode, i: number, data: ITreeNode[]) => void + ) => { + for (let i = 0; i < data.length; i++) { + if (data[i].key === key) { + return callback(data[i], i, data); + } + if (data[i].children) { + loop(data[i].children!, key, callback); + } + } + }; + const data = [...treeData.data]; + + // Find dragObject + let dragObj: ITreeNode; + loop(data, dragKey, (item, index, arr) => { + arr.splice(index, 1); + dragObj = item; + }); + if (!info.dropToGap) { + // Drop on the content + loop(data, dropKey, (item) => { + item.children = item.children || []; + // where to insert. New item was inserted to the start of the array in this example, but can be anywhere + item.children.unshift(dragObj); + }); + } else { + let ar: ITreeNode[] = []; + let i: number; + loop(data, dropKey, (_item, index, arr) => { + ar = arr; + i = index; + }); + if (dropPosition === -1) { + // Drop on the top of the drop node + ar.splice(i!, 0, dragObj!); + } else { + // Drop on the bottom of the drop node + ar.splice(i! + 1, 0, dragObj!); + } + } + treeData.onChange(data); + }; + + useEffect(() => { + treeData.onChange(DEFAULT_DATA); + }, []); + + return ( +
+ + treeData.onChange([ + ...treeData.data, + { edit: true, title: '', key: '' }, + ]) + } + /> + } + title="标签目录" + showSearch + draggable + overlay={(item) => { + const { addable = true, editable = true, deletable = true } = item; + return ( + { + domEvent.stopPropagation(); + }} + > + addable && handleAdd(item.key)} + > + + 新建目录 + + editable && handleEdit(item.key)} + > + + 编辑 + + deletable && handleDelete(item)} + > + + 删除 + + + ); + }} + treeData={treeData.data} + expandedKeys={treeData.expandedKeys} + onExpand={treeData.onExpand} + onDragEnter={({ expandedKeys }) => treeData.onExpand(expandedKeys)} + onSave={handleSave} + onCancelSave={handleCancelSave} + onDrop={handleDrop} + /> +
+ ); +}; diff --git a/src/catalogue/demos/drag.tsx b/src/catalogue/demos/drag.tsx new file mode 100644 index 000000000..bb2a2c444 --- /dev/null +++ b/src/catalogue/demos/drag.tsx @@ -0,0 +1,262 @@ +import React, { useEffect } from 'react'; +import { DeleteOutlined, EditOutlined, PlusSquareOutlined } from '@ant-design/icons'; +import { Menu } from 'antd'; +import { Catalogue } from 'dt-react-component'; +import { cloneDeep } from 'lodash'; +import shortid from 'shortid'; + +import { CatalogueProps } from '../components/catalogue'; +import { ITreeNode, useTreeData } from '../useTreeData'; +import { + appendNodeByKey, + findNodeByKey, + findParentNodeByKey, + removeEditNode, + removeNodeByKey, + updateTreeNodeEdit, +} from '../utils'; + +interface IData { + edit?: boolean; + addable?: boolean; + deletable?: boolean; + editable?: boolean; +} + +const DEFAULT_DATA = [ + { + title: '0-0', + key: '0-0', + children: [ + { + title: '0-0-0', + key: '0-0-0', + children: [ + { title: '0-0-0-0', key: '0-0-0-0' }, + { title: '0-0-0-1', key: '0-0-0-1' }, + { title: '0-0-0-2', key: '0-0-0-2' }, + ], + }, + { + title: '0-0-1', + key: '0-0-1', + children: [ + { title: '0-0-1-0', key: '0-0-1-0' }, + { title: '0-0-1-1', key: '0-0-1-1' }, + { title: '0-0-1-2', key: '0-0-1-2' }, + ], + }, + { + title: '0-0-2', + key: '0-0-2', + }, + ], + }, + { + title: '0-1', + key: '0-1', + children: [ + { title: '0-1-0-0', key: '0-1-0-0' }, + { title: '0-1-0-1', key: '0-1-0-1' }, + { title: '0-1-0-2', key: '0-1-0-2' }, + ], + }, + { + title: '0-2', + key: '0-2', + }, +]; + +export default () => { + const treeData = useTreeData(); + + const handleEdit = (key: ITreeNode['key']) => { + const data = updateTreeNodeEdit(treeData.data, key); + treeData.onChange(data); + }; + + const handleAdd = (key: ITreeNode['key']) => { + const newExpandedKeys = treeData.expandedKeys ? [...treeData.expandedKeys] : []; + const data = appendNodeByKey(treeData.data, key); + if (!newExpandedKeys?.includes(key)) { + newExpandedKeys.push(key); + } + treeData.onExpand(newExpandedKeys); + treeData.onChange(data); + }; + + const handleSave = async (data: ITreeNode, value: string) => { + const newData = cloneDeep(treeData.data); + if (!data.key) { + newData.push({ + title: value, + key: shortid(), + }); + newData.shift(); + treeData.onChange(newData); + return; + } + if ((data.key as string).startsWith('new')) { + let node = findParentNodeByKey(newData, data.key); + if (!node) return; + if (node.children) { + const newChildren = node.children + .filter((item) => !item.edit) + .concat({ title: value, key: shortid() }); + node.children = newChildren; + } else { + node = { + ...node, + children: [ + { + title: value, + key: shortid(), + }, + ], + }; + } + treeData.onChange(newData); + return; + } + const node = findNodeByKey(newData, data.key); + if (node) { + node.title = value; + node.edit = false; + } + treeData.onChange(newData); + }; + + const handleCancelSave = () => { + const newData = removeEditNode(treeData.data); + treeData.onChange(newData); + }; + + const handleDelete = (data: ITreeNode) => { + let newData = cloneDeep(treeData.data); + newData = removeNodeByKey(newData, data.key); + treeData.onChange(newData); + }; + + const handleDrop: CatalogueProps['onDrop'] = (info) => { + const dropKey = info.node.key; + const dragKey = info.dragNode.key; + const dropPos = info.node.pos.split('-'); + const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]); // the drop position relative to the drop node, inside 0, top -1, bottom 1 + + const loop = ( + data: ITreeNode[], + key: React.Key, + callback: (node: ITreeNode, i: number, data: ITreeNode[]) => void + ) => { + for (let i = 0; i < data.length; i++) { + if (data[i].key === key) { + return callback(data[i], i, data); + } + if (data[i].children) { + loop(data[i].children!, key, callback); + } + } + }; + const data = [...treeData.data]; + + // Find dragObject + let dragObj: ITreeNode; + loop(data, dragKey, (item, index, arr) => { + arr.splice(index, 1); + dragObj = item; + }); + if (!info.dropToGap) { + // Drop on the content + loop(data, dropKey, (item) => { + item.children = item.children || []; + // where to insert. New item was inserted to the start of the array in this example, but can be anywhere + item.children.unshift(dragObj); + }); + } else { + let ar: ITreeNode[] = []; + let i: number; + loop(data, dropKey, (_item, index, arr) => { + ar = arr; + i = index; + }); + if (dropPosition === -1) { + // Drop on the top of the drop node + ar.splice(i!, 0, dragObj!); + } else { + // Drop on the bottom of the drop node + ar.splice(i! + 1, 0, dragObj!); + } + } + treeData.onChange(data); + }; + + useEffect(() => { + treeData.onChange(DEFAULT_DATA); + }, []); + + return ( +
+ + treeData.onChange([ + ...treeData.data, + { edit: true, title: '', key: '' }, + ]) + } + /> + } + title="标签目录" + showSearch + draggable + overlay={(item) => { + const { addable = true, editable = true, deletable = true } = item; + return ( + { + domEvent.stopPropagation(); + }} + > + addable && handleAdd(item.key)} + > + + 新建目录 + + editable && handleEdit(item.key)} + > + + 编辑 + + deletable && handleDelete(item)} + > + + 删除 + + + ); + }} + treeData={treeData.data} + expandedKeys={treeData.expandedKeys} + onExpand={treeData.onExpand} + onDragEnter={({ expandedKeys }) => treeData.onExpand(expandedKeys)} + onSave={handleSave} + onCancelSave={handleCancelSave} + onDrop={handleDrop} + /> +
+ ); +}; diff --git a/src/catalogue/demos/operator.tsx b/src/catalogue/demos/operator.tsx new file mode 100644 index 000000000..dc6c0f1ba --- /dev/null +++ b/src/catalogue/demos/operator.tsx @@ -0,0 +1,259 @@ +import React, { useEffect } from 'react'; +import { DeleteOutlined, EditOutlined, PlusSquareOutlined } from '@ant-design/icons'; +import { Menu } from 'antd'; +import { Catalogue } from 'dt-react-component'; +import { cloneDeep } from 'lodash'; +import shortid from 'shortid'; + +import { ITreeNode, useTreeData } from '../useTreeData'; +import { + appendNodeByKey, + findNodeByKey, + findParentNodeByKey, + removeEditNode, + removeNodeByKey, + updateTreeNodeEdit, +} from '../utils'; + +const DEFAULT_DATA = [ + { + title: '0-0', + key: '0-0', + addable: true, + deletable: true, + editable: true, + children: [ + { + title: '0-0-0', + key: '0-0-0', + addable: true, + deletable: true, + editable: true, + children: [ + { + title: '0-0-0-0', + key: '0-0-0-0', + addable: true, + deletable: true, + editable: true, + }, + { + title: '0-0-0-1', + key: '0-0-0-1', + addable: true, + deletable: true, + editable: true, + }, + { + title: '0-0-0-2', + key: '0-0-0-2', + addable: true, + deletable: true, + editable: true, + }, + ], + }, + { + title: '0-0-1', + key: '0-0-1', + children: [ + { + title: '0-0-1-0', + key: '0-0-1-0', + addable: true, + deletable: true, + editable: true, + }, + { + title: '0-0-1-1', + key: '0-0-1-1', + addable: true, + deletable: true, + editable: true, + }, + { + title: '0-0-1-2', + key: '0-0-1-2', + addable: true, + deletable: true, + editable: true, + }, + ], + addable: true, + deletable: true, + editable: true, + }, + { + title: '0-0-2', + key: '0-0-2', + addable: true, + deletable: true, + editable: true, + }, + ], + }, + { + title: '0-1', + key: '0-1', + addable: true, + deletable: true, + editable: true, + children: [ + { title: '0-1-0-0', key: '0-1-0-0', addable: true, deletable: true, editable: true }, + { title: '0-1-0-1', key: '0-1-0-1', addable: true, deletable: true, editable: true }, + { title: '0-1-0-2', key: '0-1-0-2', addable: true, deletable: true, editable: true }, + ], + }, + { + title: '0-2', + key: '0-2', + addable: true, + deletable: true, + editable: true, + }, +]; + +interface IData { + edit?: boolean; + addable?: boolean; + deletable?: boolean; + editable?: boolean; +} + +export default () => { + const treeData = useTreeData(); + + const handleEdit = (key: ITreeNode['key']) => { + const data = updateTreeNodeEdit(treeData.data, key); + treeData.onChange(data); + }; + + const handleAdd = (key: ITreeNode['key']) => { + const newExpandedKeys = treeData.expandedKeys ? [...treeData.expandedKeys] : []; + const data = appendNodeByKey(treeData.data, key); + if (!newExpandedKeys?.includes(key)) { + newExpandedKeys.push(key); + } + treeData.onExpand(newExpandedKeys); + treeData.onChange(data); + }; + + const handleSave = async (data: ITreeNode, value: string) => { + const newData = cloneDeep(treeData.data); + if (!data.key) { + newData.push({ + title: value, + key: shortid(), + }); + newData.shift(); + treeData.onChange(newData); + return; + } + if ((data.key as string).startsWith('new')) { + let node = findParentNodeByKey(newData, data.key); + if (!node) return; + if (node.children) { + const newChildren = node.children + .filter((item) => !item.edit) + .concat({ title: value, key: shortid() }); + node.children = newChildren; + } else { + node = { + ...node, + children: [ + { + title: value, + key: shortid(), + }, + ], + }; + } + treeData.onChange(newData); + return; + } + const node = findNodeByKey(newData, data.key); + if (node) { + node.title = value; + node.edit = false; + } + treeData.onChange(newData); + }; + + const handleCancelSave = () => { + const newData = removeEditNode(treeData.data); + treeData.onChange(newData); + }; + + const handleDelete = (data: ITreeNode) => { + let newData = cloneDeep(treeData.data); + newData = removeNodeByKey(newData, data.key); + treeData.onChange(newData); + }; + + useEffect(() => { + treeData.onChange(DEFAULT_DATA); + }, []); + + return ( +
+ + tooltip="嘿嘿,这是tooltip" + addonAfter={ + + treeData.onChange([ + ...treeData.data, + { edit: true, title: '', key: '' }, + ]) + } + /> + } + title="标签目录" + overlay={(item) => { + const { addable = true, editable = true, deletable = true } = item; + return ( + { + domEvent.stopPropagation(); + }} + > + addable && handleAdd(item.key)} + > + + 新建目录 + + editable && handleEdit(item.key)} + > + + 编辑 + + deletable && handleDelete(item)} + > + + 删除 + + + ); + }} + showSearch + treeData={treeData.data} + expandedKeys={treeData.expandedKeys} + onExpand={treeData.onExpand} + onSave={handleSave} + onCancelSave={handleCancelSave} + /> +
+ ); +}; diff --git a/src/catalogue/demos/simple.tsx b/src/catalogue/demos/simple.tsx new file mode 100644 index 000000000..b239ca95a --- /dev/null +++ b/src/catalogue/demos/simple.tsx @@ -0,0 +1,67 @@ +import React, { useEffect } from 'react'; +import { Catalogue, EllipsisText } from 'dt-react-component'; + +import { useTreeData } from '../useTreeData'; + +const DEFAULT_DATA = [ + { + title: '0-0', + key: '0-0', + children: [ + { + title: ( + + ), + key: '0-0-0', + children: [ + { title: '0-0-0-0', key: '0-0-0-0' }, + { title: '0-0-0-1', key: '0-0-0-1' }, + { title: '0-0-0-2', key: '0-0-0-2' }, + ], + }, + { + title: '0-0-1', + key: '0-0-1', + children: [ + { title: '0-0-1-0', key: '0-0-1-0' }, + { title: '0-0-1-1', key: '0-0-1-1' }, + { title: '0-0-1-2', key: '0-0-1-2' }, + ], + }, + { + title: '0-0-2', + key: '0-0-2', + }, + ], + }, + { + title: '0-1', + key: '0-1', + children: [ + { title: '0-1-0-0', key: '0-1-0-0' }, + { title: '0-1-0-1', key: '0-1-0-1' }, + { title: '0-1-0-2', key: '0-1-0-2' }, + ], + }, + { + title: '0-2', + key: '0-2', + }, +]; + +export default () => { + const treeData = useTreeData(); + + useEffect(() => { + treeData.onChange(DEFAULT_DATA); + }, []); + + return ( +
+ +
+ ); +}; diff --git a/src/catalogue/demos/tabs.tsx b/src/catalogue/demos/tabs.tsx new file mode 100644 index 000000000..ab3163ca8 --- /dev/null +++ b/src/catalogue/demos/tabs.tsx @@ -0,0 +1,89 @@ +import React, { useEffect, useState } from 'react'; +import { Catalogue, EllipsisText } from 'dt-react-component'; + +import { useTreeData } from '../useTreeData'; + +enum TreeType { + Api = 'api', + Index = 'index', +} + +const DEFAULT_DATA = (key: TreeType) => [ + { + title: `0-0-${key}`, + key: '0-0', + children: [ + { + title: ( + + ), + key: '0-0-0', + children: [ + { title: '0-0-0-0', key: '0-0-0-0' }, + { title: '0-0-0-1', key: '0-0-0-1' }, + { title: '0-0-0-2', key: '0-0-0-2' }, + ], + }, + { + title: '0-0-1', + key: '0-0-1', + children: [ + { title: '0-0-1-0', key: '0-0-1-0' }, + { title: '0-0-1-1', key: '0-0-1-1' }, + { title: '0-0-1-2', key: '0-0-1-2' }, + ], + }, + { + title: '0-0-2', + key: '0-0-2', + }, + ], + }, + { + title: `0-1-${key}`, + key: '0-1', + children: [ + { title: '0-1-0-0', key: '0-1-0-0' }, + { title: '0-1-0-1', key: '0-1-0-1' }, + { title: '0-1-0-2', key: '0-1-0-2' }, + ], + }, + { + title: `0-2-${key}`, + key: '0-2', + }, +]; + +export default () => { + const treeData = useTreeData(); + const [activeKey, setActiveKey] = useState(TreeType.Index); + + useEffect(() => { + treeData.onChange(DEFAULT_DATA(activeKey)); + }, [activeKey]); + + return ( +
+ setActiveKey(key)} + expandedKeys={treeData.expandedKeys} + onExpand={treeData.onExpand} + /> +
+ ); +}; diff --git a/src/catalogue/demos/tree/DefaultTree/index.tsx b/src/catalogue/demos/tree/DefaultTree/index.tsx deleted file mode 100644 index 610e9ae89..000000000 --- a/src/catalogue/demos/tree/DefaultTree/index.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import React, { useState } from 'react'; -import type { DataNode, TreeProps } from 'antd/es/tree'; -import { Catalogue } from 'dt-react-component'; -import { ITreeDataItem } from 'dt-react-component/catalogue/components/tree'; - -import { initTreeData } from '../data'; - -export const DefaultTree = () => { - const [dataSource, setDataSource] = useState(initTreeData); - const [selectedItems, setSelectedItems] = useState([]); - const handleDrop: TreeProps['onDrop'] = (info) => { - console.log(info); - const dropKey = info.node.key; - const dragKey = info.dragNode.key; - const dropPos = info.node.pos.split('-'); - const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]); - - const loop = ( - data: DataNode[], - key: React.Key, - callback: (node: DataNode, i: number, data: DataNode[]) => void - ) => { - for (let i = 0; i < data.length; i++) { - if (data[i].key === key) { - return callback(data[i], i, data); - } - if (data[i].children) { - loop(data[i].children!, key, callback); - } - } - }; - const data = [...dataSource]; - - // Find dragObject - let dragObj: DataNode; - loop(data, dragKey, (item, index, arr) => { - arr.splice(index, 1); - dragObj = item; - }); - - if (!info.dropToGap) { - // Drop on the content - loop(data, dropKey, (item) => { - item.children = item.children || []; - // where to insert 示例添加到头部,可以是随意位置 - item.children.unshift(dragObj); - }); - } else if ( - ((info.node as any).props.children || []).length > 0 && // Has children - (info.node as any).props.expanded && // Is expanded - dropPosition === 1 // On the bottom gap - ) { - loop(data, dropKey, (item) => { - item.children = item.children || []; - // where to insert 示例添加到头部,可以是随意位置 - item.children.unshift(dragObj); - // in previous version, we use item.children.push(dragObj) to insert the - // item to the tail of the children - }); - } else { - let ar: DataNode[] = []; - let i: number; - loop(data, dropKey, (_item, index, arr) => { - ar = arr; - i = index; - }); - if (dropPosition === -1) { - ar.splice(i!, 0, dragObj!); - } else { - ar.splice(i! + 1, 0, dragObj!); - } - } - setDataSource(data); - }; - const handleSelect: TreeProps['onSelect'] = (selectedKeys) => { - // const selectedKey = - const selectedItems: ITreeDataItem[] = []; - const loopTree = (tree: ITreeDataItem[]) => { - tree.forEach((item) => { - if (selectedKeys.includes(item.key)) { - selectedItems.push(item); - } - if (item?.children?.length) { - loopTree(item?.children); - } - }); - }; - loopTree(dataSource); - setSelectedItems(selectedItems); - console.log(selectedKeys, '--selectedKeys'); - }; - console.log(selectedItems, '--selectedItems'); - return ( -
- true }} - height={500} - wrapperStyle={{ width: 300 }} - onSearch={(v, e) => { - console.log(v, e, '--onSearch'); - }} - onDrop={handleDrop} - onSelect={handleSelect} - multiple - /> -

- {selectedItems?.length - ? `选中了 ${selectedItems?.map?.((item) => item?.title)?.join?.('、')}` - : 'Content'} -

-
- ); -}; - -export default DefaultTree; diff --git a/src/catalogue/demos/tree/NoHeaderTree/index.tsx b/src/catalogue/demos/tree/NoHeaderTree/index.tsx deleted file mode 100644 index 42ca783a5..000000000 --- a/src/catalogue/demos/tree/NoHeaderTree/index.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React, { useState } from 'react'; -import type { TreeProps } from 'antd/es'; -import { Catalogue } from 'dt-react-component'; -import { ITreeDataItem } from 'dt-react-component/catalogue/components/tree'; - -import { initTreeData } from '../data'; - -export const NoHeaderTree = () => { - const [dataSource] = useState(initTreeData); - const [selectedItems, setSelectedItems] = useState([]); - const handleSelect: TreeProps['onSelect'] = (selectedKeys) => { - // const selectedKey = - const selectedItems: ITreeDataItem[] = []; - const loopTree = (tree: ITreeDataItem[]) => { - tree.forEach((item) => { - if (selectedKeys.includes(item.key)) { - selectedItems.push(item); - } - if (item?.children?.length) { - loopTree(item?.children); - } - }); - }; - loopTree(dataSource); - setSelectedItems(selectedItems); - console.log(selectedKeys, '--selectedKeys'); - }; - console.log(selectedItems, '--selectedItems'); - return ( -
- -

- {selectedItems?.length - ? `选中了 ${selectedItems?.map?.((item) => item?.title)?.join?.('、')}` - : 'Content'} -

-
- ); -}; - -export default NoHeaderTree; diff --git a/src/catalogue/demos/tree/SmallTree/index.tsx b/src/catalogue/demos/tree/SmallTree/index.tsx deleted file mode 100644 index c986cbfd8..000000000 --- a/src/catalogue/demos/tree/SmallTree/index.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import React, { useState } from 'react'; -import type { DataNode, TreeProps } from 'antd/es/tree'; -import { Catalogue } from 'dt-react-component'; -import { ITreeDataItem } from 'dt-react-component/catalogue/components/tree'; - -import { initTreeData, sleep } from '../data'; - -export const SmallTree = () => { - const [dataSource, setDataSource] = useState(initTreeData); - const [selectedItems, setSelectedItems] = useState([]); - const handleDrop: TreeProps['onDrop'] = (info) => { - console.log(info); - const dropKey = info.node.key; - const dragKey = info.dragNode.key; - const dropPos = info.node.pos.split('-'); - const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]); - - const loop = ( - data: DataNode[], - key: React.Key, - callback: (node: DataNode, i: number, data: DataNode[]) => void - ) => { - for (let i = 0; i < data.length; i++) { - if (data[i].key === key) { - return callback(data[i], i, data); - } - if (data[i].children) { - loop(data[i].children!, key, callback); - } - } - }; - const data = [...dataSource]; - - // Find dragObject - let dragObj: DataNode; - loop(data, dragKey, (item, index, arr) => { - arr.splice(index, 1); - dragObj = item; - }); - - if (!info.dropToGap) { - // Drop on the content - loop(data, dropKey, (item) => { - item.children = item.children || []; - // where to insert 示例添加到头部,可以是随意位置 - item.children.unshift(dragObj); - }); - } else if ( - ((info.node as any).props.children || []).length > 0 && // Has children - (info.node as any).props.expanded && // Is expanded - dropPosition === 1 // On the bottom gap - ) { - loop(data, dropKey, (item) => { - item.children = item.children || []; - // where to insert 示例添加到头部,可以是随意位置 - item.children.unshift(dragObj); - // in previous version, we use item.children.push(dragObj) to insert the - // item to the tail of the children - }); - } else { - let ar: DataNode[] = []; - let i: number; - loop(data, dropKey, (_item, index, arr) => { - ar = arr; - i = index; - }); - if (dropPosition === -1) { - ar.splice(i!, 0, dragObj!); - } else { - ar.splice(i! + 1, 0, dragObj!); - } - } - setDataSource(data); - }; - const handleLoadData: TreeProps['loadData'] = (node) => { - console.log(node, '--node'); - // node.children = [{ }]; - return sleep(1500, []); - }; - const handleSelect: TreeProps['onSelect'] = (selectedKeys) => { - // const selectedKey = - const selectedItems: ITreeDataItem[] = []; - const loopTree = (tree: ITreeDataItem[]) => { - tree.forEach((item) => { - if (selectedKeys.includes(item.key)) { - selectedItems.push(item); - } - if (item?.children?.length) { - loopTree(item?.children); - } - }); - }; - loopTree(dataSource); - setSelectedItems(selectedItems); - console.log(selectedKeys, '--selectedKeys'); - }; - console.log(selectedItems, '--selectedItems'); - return ( -
- true }} - height={500} - loadData={handleLoadData} - // wrapperStyle={{ width: 500 }} - onSearch={(v, e) => { - console.log(v, e, '--onSearch'); - }} - onDrop={handleDrop} - onSelect={handleSelect} - /> -

- {selectedItems?.length - ? `选中了 ${selectedItems?.map?.((item) => item?.title)?.join?.('、')}` - : 'Content'} -

-
- ); -}; - -export default SmallTree; diff --git a/src/catalogue/demos/tree/WithBtnSlotTree/index.tsx b/src/catalogue/demos/tree/WithBtnSlotTree/index.tsx deleted file mode 100644 index faed6ebc1..000000000 --- a/src/catalogue/demos/tree/WithBtnSlotTree/index.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import React, { useState } from 'react'; -import { DeleteOutlined, FormOutlined, PlusSquareOutlined } from '@ant-design/icons'; -import { Space, TreeProps } from 'antd'; -import { Catalogue } from 'dt-react-component'; -import { ITreeDataItem } from 'dt-react-component/catalogue/components/tree'; - -import { initTreeData } from '../data'; - -export const WithBtnSlotTree = () => { - const [dataSource] = useState(initTreeData); - const [selectedItems, setSelectedItems] = useState([]); - const handleSelect: TreeProps['onSelect'] = (selectedKeys) => { - // const selectedKey = - const selectedItems: ITreeDataItem[] = []; - const loopTree = (tree: ITreeDataItem[]) => { - tree.forEach((item) => { - if (selectedKeys.includes(item.key)) { - selectedItems.push(item); - } - if (item?.children?.length) { - loopTree(item?.children); - } - }); - }; - loopTree(dataSource); - setSelectedItems(selectedItems); - console.log(selectedKeys, '--selectedKeys'); - }; - console.log(selectedItems, '--selectedItems'); - return ( -
- - - - - - } - /> -

- {selectedItems?.length - ? `选中了 ${selectedItems?.map?.((item) => item?.title)?.join?.('、')}` - : 'Content'} -

-
- ); -}; - -export default WithBtnSlotTree; diff --git a/src/catalogue/demos/tree/WithCheckboxTree/index.tsx b/src/catalogue/demos/tree/WithCheckboxTree/index.tsx deleted file mode 100644 index fe4d9cd10..000000000 --- a/src/catalogue/demos/tree/WithCheckboxTree/index.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import React, { useState } from 'react'; -import { TreeProps } from 'antd'; -import { Catalogue } from 'dt-react-component'; -import { ITreeDataItem } from 'dt-react-component/catalogue/components/tree'; - -import { initTreeData } from '../data'; - -export const WithCheckboxTree = () => { - const [dataSource] = useState(initTreeData); - const [selectedItems, setSelectedItems] = useState([]); - const [checkedItems, setCheckedItems] = useState([]); - const handleSelect: TreeProps['onSelect'] = (selectedKeys) => { - console.log(selectedKeys, '--selectedKeys'); - const selectedItems: ITreeDataItem[] = []; - const loopTree = (tree: ITreeDataItem[]) => { - tree.forEach((item) => { - if (selectedKeys.includes(item.key)) { - selectedItems.push(item); - } - if (item?.children?.length) { - loopTree(item?.children); - } - }); - }; - loopTree(dataSource); - setSelectedItems(selectedItems); - }; - const handleCheck: TreeProps['onCheck'] = (checkedKeys, info) => { - console.log(info, '--info'); - console.log(checkedKeys, '--checkedKeys'); - const checkedItems: ITreeDataItem[] = []; - const loopTree = (tree: ITreeDataItem[]) => { - tree.forEach((item) => { - if (checkedKeys?.includes?.(item.key)) { - checkedItems.push(item); - } - if (item?.children?.length) { - loopTree(item?.children); - } - }); - }; - loopTree(dataSource); - setCheckedItems(checkedItems); - }; - console.log(selectedItems, '--selectedItems'); - console.log(checkedItems, '--checkedItems'); - return ( -
- true }} - height={500} - // wrapperStyle={{ width: 500 }} - onSearch={(v, e) => { - console.log(v, e, '--onSearch'); - }} - checkable - onSelect={handleSelect} - onCheck={handleCheck} - /> -

-

- {selectedItems?.length - ? `选中了 ${selectedItems?.map?.((item) => item?.title)?.join?.('、')}` - : '未选中'} -

-

- {checkedItems?.length - ? `勾选了 ${checkedItems?.map((item) => item?.title)?.join('、')}` - : null} -

-

-
- ); -}; - -export default WithCheckboxTree; diff --git a/src/catalogue/demos/tree/WithTabsTree/index.tsx b/src/catalogue/demos/tree/WithTabsTree/index.tsx deleted file mode 100644 index 5aa6457f7..000000000 --- a/src/catalogue/demos/tree/WithTabsTree/index.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import React, { useState } from 'react'; -import { DeleteOutlined, FormOutlined, PlusSquareOutlined } from '@ant-design/icons'; -import { Space, TreeProps } from 'antd'; -import { Catalogue } from 'dt-react-component'; -import { ITabsStatus, ITreeDataItem } from 'dt-react-component/catalogue/components/tree'; - -import { initTreeData } from '../data'; - -export const WithTabsTree = () => { - const [dataSource, setDataSource] = useState(initTreeData); - const [selectedItems, setSelectedItems] = useState([]); - - const handleSelect: TreeProps['onSelect'] = (selectedKeys) => { - console.log(selectedKeys, '--selectedKeys'); - const selectedItems: ITreeDataItem[] = []; - const loopTree = (tree: ITreeDataItem[]) => { - tree.forEach((item) => { - if (selectedKeys.includes(item.key)) { - selectedItems.push(item); - } - if (item?.children?.length) { - loopTree(item?.children); - } - }); - }; - loopTree(dataSource); - setSelectedItems(selectedItems); - }; - console.log(selectedItems, '--selectedItems'); - return ( -
- { - console.log(activeKey, e, 'onTabClick'); - if (activeKey === 'project_1') { - setDataSource(initTreeData.slice(0, 2)); - } else if (activeKey === 'project_2') { - setDataSource(initTreeData.slice(3)); - } else if (activeKey === 'all') { - setDataSource(initTreeData); - } - }, - }} - onStatusChange={(status) => { - if (status === ITabsStatus.search) { - setDataSource(initTreeData); - } - }} - btnSlot={ - - - - - - } - /> -

-

- {selectedItems?.length - ? `选中了 ${selectedItems?.map?.((item) => item?.title)?.join?.('、')}` - : '未选中'} -

-

-
- ); -}; - -export default WithTabsTree; diff --git a/src/catalogue/demos/tree/data.ts b/src/catalogue/demos/tree/data.ts deleted file mode 100644 index a2693a7e4..000000000 --- a/src/catalogue/demos/tree/data.ts +++ /dev/null @@ -1,554 +0,0 @@ -import type { ITreeDataItem } from '../../components/tree'; - -export const initTreeData: ITreeDataItem[] = [ - { - key: '1', - title: '文件夹1', - contextMenuConfig: { - data: [ - { text: '日志', key: 'log' }, - { text: '自定义操作', key: 'customOperation' }, - ], - onClick: (a, b) => { - console.log(a, b, '--8'); - }, - }, - children: [ - { - key: '1.1', - title: '很长很长很长很长很长很长很长很长很长很长很长的文件夹名称', - contextMenuConfig: { - data: [ - { text: '新建文件夹', key: 'newFolder' }, - { text: '删除', key: 'deleteFolder' }, - ], - onClick: (a, b) => { - console.log(a, b, '--13'); - }, - }, - children: [ - { - key: '1.1.1', - title: '文件2', - contextMenuConfig: { - data: [ - { text: '编辑', key: 'editFile' }, - { text: '删除', key: 'deleteFile' }, - { text: '克隆', key: 'cloneFile' }, - ], - onClick: (a, b) => { - console.log(a, b, '--23'); - }, - }, - }, - { - key: '1.1.2', - title: '文件3', - }, - { - key: '1.1.3', - title: '文件4', - }, - { - key: '1.1.4', - title: '文件5', - }, - { - key: '1.1.5', - title: '文件6', - }, - { - key: '1.1.6', - title: '文件7', - }, - ], - }, - { - key: '1.2', - title: '文件夹8', - children: [ - { - key: '1.2.1', - title: '文件9', - }, - ], - }, - ], - }, - { - key: '2', - title: '文件夹10', - children: [ - { - key: '2.1', - title: '文件夹11', - children: [ - { - key: '2.1.1', - title: '文件12', - }, - { - key: '2.1.2', - title: '文件13', - }, - ], - }, - { - key: '2.2', - title: '文件夹14', - children: [ - { - key: '2.2.1', - title: '文件15', - }, - { - key: '2.2.2', - title: '文件16', - }, - { - key: '2.2.3', - title: '文件17', - }, - { - key: '2.2.4', - title: '文件18', - }, - { - key: '2.2.5', - title: '文件19', - }, - { - key: '2.2.6', - title: '文件20', - }, - { - key: '2.2.7', - title: '文件21', - }, - ], - }, - ], - }, - { - key: '3', - title: '文件夹22', - children: [ - { - key: '3.1', - title: '文件夹23', - children: [ - { - key: '3.1.1', - title: '文件24', - }, - { - key: '3.1.2', - title: '文件25', - }, - ], - }, - { - key: '3.2', - title: '文件夹26', - children: [ - { - key: '3.2.1', - title: '文件27', - }, - ], - }, - ], - }, - { - key: '4', - title: '4', - children: [ - { - key: '4.1', - title: '文件夹28', - children: [ - { - key: '4.1.1', - title: '文件29', - }, - { - key: '4.1.2', - title: '文件30', - }, - ], - }, - { - key: '4.2', - title: '文件夹31', - children: [ - { - key: '4.2.1', - title: '文件32', - }, - ], - }, - ], - }, - { - key: '521751', - title: '文件夹33', - children: [ - { - key: '5.1', - title: '文件夹34', - children: [ - { - key: '5.1.1', - title: '文件35', - }, - { - key: '5.1.2', - title: '文件36', - }, - { - key: '5.1.3', - title: '文件37', - }, - { - key: '5.1.4', - title: '文件38', - }, - ], - }, - { - key: '5.2', - title: '文件夹39', - children: [ - { - key: '5.2.12', - title: '文件40', - }, - ], - }, - { - key: '5.3', - title: '文件夹41', - children: [ - { - key: '5.2.131', - title: '文件42', - }, - { - key: '5.2.2', - title: '文件43', - }, - { - key: '5.2.3', - title: '文件44', - }, - ], - }, - ], - }, - { - key: '6123123', - title: '文件夹45', - children: [ - { - key: '6.1', - title: '文件夹46', - children: [ - { - key: '6.1.1', - title: '文件47', - }, - { - key: '6.1.2', - title: '文件48', - }, - { - key: '6.1.3', - title: '文件49', - }, - { - key: '6.1.4', - title: '文件50', - }, - ], - }, - { - key: '6.2', - title: '文件夹51', - children: [ - { - key: '6.2.1123', - title: '文件52', - }, - ], - }, - { - key: '6.3', - title: '文件夹53', - children: [ - { - key: '6.2.1867234', - title: '文件54', - }, - { - key: '6.2.2', - title: '文件55', - }, - { - key: '6.2.3', - title: '文件56', - }, - ], - }, - ], - }, - { - key: '721751', - title: '文件夹57', - children: [ - { - key: '7.1', - title: '文件夹58', - children: [ - { - key: '7.1.1', - title: '文件59', - }, - { - key: '7.1.2', - title: '文件60', - }, - { - key: '7.1.3', - title: '文件61', - }, - { - key: '7.1.4', - title: '文件62', - }, - ], - }, - { - key: '7.2', - title: '文件夹63', - children: [ - { - key: '7.2.31878', - title: '文件64', - }, - ], - }, - { - key: '7.3', - title: '文件夹65', - children: [ - { - key: '7.2.12451', - title: '文件66', - }, - { - key: '7.2.2', - title: '文件67', - }, - { - key: '7.2.3', - title: '文件68', - }, - ], - }, - ], - }, - { - key: '821751', - title: '文件夹69', - children: [ - { - key: '8.1', - title: '文件夹70', - children: [ - { - key: '8.1.1', - title: '文件71', - }, - { - key: '8.1.2', - title: '文件72', - }, - { - key: '8.1.3', - title: '文件73', - }, - { - key: '8.1.4', - title: '文件74', - }, - ], - }, - { - key: '8.2', - title: '文件夹75', - children: [ - { - key: '8.2.1', - title: '文件76', - }, - ], - }, - { - key: '8.3', - title: '文件夹77', - children: [ - { - key: '8.2.878231', - title: '文件78', - }, - { - key: '8.2.2', - title: '文件79', - }, - { - key: '8.2.3', - title: '文件80', - }, - ], - }, - ], - }, - { - key: '99921266', - title: '文件夹81', - // type: 'x', - children: [ - { - key: '9.1', - title: '测试很长很长很长很长很长很长很长很长的文件夹名称', - children: [ - { - key: '9.1.1', - title: '文件82', - }, - { - key: '9.1.2', - title: '文件83', - }, - { - key: '9.1.3', - title: '文件84', - }, - { - key: '9.1.4', - title: '文件85', - }, - { - key: '9.1.5', - title: '文件86', - }, - { - key: '9.1.6', - title: '文件87', - }, - ], - }, - { - key: '9.2', - title: '文件夹88', - children: [ - { - key: '9.2.1', - title: '文件89', - }, - ], - }, - ], - }, - { - key: '10', - title: '文件夹90', - children: [ - { - key: '10.1', - title: '文件夹91', - children: [ - { - key: '10.1.1', - title: '文件92', - }, - { - key: '10.1.2', - title: '文件93', - }, - ], - }, - { - key: '10.2', - title: '文件夹94', - children: [ - { - key: '10.2.1', - title: '文件95', - }, - { - key: '10.2.2', - title: '文件96', - }, - { - key: '10.2.3', - title: '文件97', - }, - { - key: '10.2.4', - title: '文件98', - }, - { - key: '10.2.5', - title: '文件99', - }, - { - key: '10.2.6', - title: '文件100', - }, - { - key: '10.2.7', - title: '文件101', - }, - ], - }, - ], - }, - { - key: '118326832101dd', - title: '文件夹102', - children: [ - { - key: '11.13', - title: '文件夹103', - children: [ - { - key: '11.41.1', - title: '文件104', - }, - { - key: '11.71.2', - title: '文件105', - }, - ], - }, - { - key: '11.2', - title: '文件夹106', - children: [ - { - key: '11.82.1', - title: '文件107', - }, - ], - }, - ], - }, -]; - -export type ISleep = (t: number, d?: any) => Promise; - -export const sleep: ISleep = (t = 1500, data) => - new Promise((resolve) => { - setTimeout(() => resolve(data), t); - }); diff --git a/src/catalogue/demos/treeSelect/NormalTreeSelect/index.tsx b/src/catalogue/demos/treeSelect/NormalTreeSelect/index.tsx deleted file mode 100644 index e97dc7ce8..000000000 --- a/src/catalogue/demos/treeSelect/NormalTreeSelect/index.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React, { ReactNode, useState } from 'react'; -import { TreeSelectProps } from 'antd'; -import { Catalogue } from 'dt-react-component'; - -import { initTreeSelectData } from '../data'; - -export const NormalTreeSelect = () => { - const [dataSource] = useState(initTreeSelectData); - const [selectedLabelList, setSelectedLabelList] = useState([]); - const handleChange: TreeSelectProps['onChange'] = (value, labelList, _) => { - setSelectedLabelList(labelList); - }; - console.log(selectedLabelList, '--selectedLabelList'); - return ( -
- -

-

- {selectedLabelList?.length - ? `选中了 ${selectedLabelList?.join?.('、')}` - : '未选中'} -

-

-
- ); -}; - -export default NormalTreeSelect; diff --git a/src/catalogue/demos/treeSelect/data.ts b/src/catalogue/demos/treeSelect/data.ts deleted file mode 100644 index 60cb7c307..000000000 --- a/src/catalogue/demos/treeSelect/data.ts +++ /dev/null @@ -1,186 +0,0 @@ -import type { TreeSelectProps } from 'antd'; - -export const initTreeSelectData: TreeSelectProps['treeData'] = [ - { - value: '1', - title: '1', - children: [ - { - value: '1.1', - title: '1.1', - children: [ - { - value: '1.1.1', - title: '1.1.1', - isLeaf: true, - }, - { - value: '1.1.2', - title: '1.1.2', - isLeaf: true, - }, - { - value: '1.1.3', - title: '1.1.3', - isLeaf: true, - }, - { - value: '1.1.4', - title: '1.1.4', - isLeaf: true, - }, - { - value: '1.1.5', - title: '1.1.5', - isLeaf: true, - }, - { - value: '1.1.6', - title: '1.1.6', - isLeaf: true, - }, - ], - }, - { - value: '1.2', - title: '1.2', - children: [ - { - value: '1.2.1', - title: '1.2.1', - isLeaf: true, - }, - ], - }, - ], - }, - { - value: '2', - title: '2', - children: [ - { - value: '2.1', - title: '2.1', - children: [ - { - value: '2.1.1', - title: '2.1.1', - isLeaf: true, - }, - { - value: '2.1.2', - title: '2.1.2', - isLeaf: true, - }, - ], - }, - { - value: '2.2', - title: '2.2', - children: [ - { - value: '2.2.1', - title: '2.2.1', - isLeaf: true, - }, - { - value: '2.2.2', - title: '2.2.2', - isLeaf: true, - }, - { - value: '2.2.3', - title: '2.2.3', - isLeaf: true, - }, - { - value: '2.2.4', - title: '2.2.4', - isLeaf: true, - }, - { - value: '2.2.5', - title: '2.2.5', - isLeaf: true, - }, - { - value: '2.2.6', - title: '2.2.6', - isLeaf: true, - }, - { - value: '2.2.7', - title: '2.2.7', - isLeaf: true, - }, - ], - }, - ], - }, - { - value: '3', - title: '3', - children: [ - { - value: '3.1', - title: '3.1', - children: [ - { - value: '3.1.1', - title: '3.1.1', - isLeaf: true, - }, - { - value: '3.1.2', - title: '3.1.2', - isLeaf: true, - }, - ], - }, - { - value: '3.2', - title: '3.2', - children: [ - { - value: '3.2.1', - title: '3.2.1', - isLeaf: true, - }, - ], - }, - ], - }, - { - value: '4', - title: '4', - children: [ - { - value: '4.1', - title: '4.1', - children: [ - { - value: '4.1.1', - title: '4.1.1', - isLeaf: true, - }, - { - value: '4.1.2', - title: '4.1.2', - isLeaf: true, - }, - ], - }, - { - value: '4.2', - title: '4.2', - children: [ - { - value: '4.2.1', - title: '4.2.1', - isLeaf: true, - }, - ], - }, - ], - }, -]; diff --git a/src/catalogue/index.md b/src/catalogue/index.md index 0550cc1f5..d99351311 100644 --- a/src/catalogue/index.md +++ b/src/catalogue/index.md @@ -1,65 +1,57 @@ --- -title: Catalogue 目录 +title: Catalogue 目录树 group: 组件 toc: content demo: cols: 2 --- -# Catalogue 目录 +# Catalogue 目录树 + +用于展示目录树 ## 何时使用 -目录树 +适合使用在需要展示目录树的地方 ## 示例 -### Catalogue.Tree - - - - - - - - - - - - - -### Catalogue.TreeSelect - - +带有 Title 纯展示的目录树 +无 Title 纯展示操作项的目录树 +可操作的目录树 +可拖拽的目录树 +配置操作项的目录树 +配置操作项的目录树 +异步加载数据 ## API -### Catalog.Tree - -| 参数 | 说明 | 类型 | 默认值 | -| ---------------- | -------------------------------------------------------------------------------------- | ------------------------------------- | ------------------ | -| loading | 是否加载中 | `boolean` | `false` | -| showHeader | 是否展示头部组件 | `boolean` | `true` | -| treeTitle | 头部文案 | `React.ReactNode` | - | -| wrapperClassName | 容器类名 | `string` | - | -| wrapperStyle | 容器行内样式 | `React.CSSProperties` | - | -| size | 尺寸大小,small 每一个 item 高度是 24px,middle 每一个 item 高度是 36px,默认为 middle | `small \| middle` | `middle` | -| onSearch | 点击搜索按钮回调 | `(value: string, e) => void` | - | -| tabsProps | tabs 配置 | `TabsProps & { items: ITabsItem[]; }` | - | -| treeData | `数据源,与 TreeProps['treeData'] 类型相似,只是增加了 ContextMenu 配置` | `ITreeDataItem[]` | - | -| defaultStatus | `默认展示 tabs 还是 search,仅 items 有值时生效` | `ITabsStatus` | `ITabsStatus.tabs` | -| onStatusChange | `tabs or status 变化时的回调` | `(status: ITabsStatus, e) => void` | - | - -其余属性均继承自 `Tree` 组件,参考 Tree API - -### Catalog.TreeSelect +### Catalogue -即 `TreeSelect` 组件,参考 TreeSelect API - -
-
-
+| 参数 | 说明 | 类型 | 默认值 | +| ----------- | ------------------------------ | ------------------------------------------------------------- | -------------- | +| loading | 目录树是否加载中 | `boolean` | `false` | +| overlay | 目录树的操作配置 | `(item: ITreeNode) => DropdownProps['overlay']` | - | +| treeData | 目录树是否加载中 | `ITreeNode[]` | `[]` | +| showSearch | 是否展示搜索框 | `boolean` | `false` | +| edit | 目录是否可以操作 | `boolean` | `true` | +| placeholder | 搜索框的默认展示内容 | `string` | `搜索目录名称` | +| onSearch | 点击搜索图标、清除图标时的回调 | `(value: string) => void` | - | +| onSave | 新增/编辑时的回调 | `(data: ITreeNode, value: string) => Promise` | - | +| onChange | 触发新增/编辑的回调 | `(node?: ITreeNode, inputMode?: InputMode) => void` | - | :::info -其余属性参考 Ant Design 的 Tree、TreeSelect 组件 +其余参数继承 antd4.x 的 `Omit`
+继承 dt-react-component 的 `Pick`
+[TreeProps](https://ant.design/components/tree-cn#tree-props),[IBlockHeaderProps](https://dtstack.github.io/dt-react-component/components/block-header#blockheader) ::: + +### ITreeNode + +| 参数 | 说明 | 类型 | 默认值 | +| --------- | -------------- | ----------------------- | ------ | +| inputMode | 节点输入框类型 | `add \| edit \| append` | - | +| editable | 是否可编辑 | `boolean` | `true` | +| deletable | 是否可删除 | `boolean` | `true` | +| addable | 是否可增加 | `boolean` | `true` | +| children | 子节点 | `ITreeNode[]` | - | diff --git a/src/catalogue/index.scss b/src/catalogue/index.scss new file mode 100644 index 000000000..3daefe3f5 --- /dev/null +++ b/src/catalogue/index.scss @@ -0,0 +1,163 @@ +.dt-catalogue { + position: relative; + height: 100%; + width: 100%; + display: flex; + flex-direction: column; + background-color: #FFF; + &__header { + padding: 12px 12px 0; + } + &__search { + display: flex; + padding: 0 12px 6px; + align-items: center; + .close { + font-size: 16px; + color: #B1B4C5; + cursor: pointer; + margin-left: 46px; + } + } + .dt-catalogue__tree { + padding: 2px 0 24px; + } + &__tree { + height: 100%; + width: 100%; + flex: 1; + min-height: 0; + overflow-y: auto; + .ant-spin-nested-loading, + .ant-spin-container { + height: 100%; + } + .ant-tree-drop-indicator { + background: none; + border-bottom: 1px dashed #1D78FF; + bottom: 0 !important; + &::after { + border: none; + } + } + .ant-tree-treenode { + width: 100%; + display: flex; + align-items: center; + &:hover { + background-color: #F5F5F8; + .tree__title--operation { + display: flex; + align-items: center; + gap: 4px; + color: #B1B4C5; + } + } + .ant-tree-node-content-wrapper { + display: flex; + align-items: center; + flex: 1; + min-width: 0; + &:hover { + background: none; + } + .ant-tree-iconEle { + display: flex; + align-items: center; + width: 20px; + &:empty { + display: none; + } + } + .ant-tree-title { + flex: 1; + min-width: 0; + } + } + &.ant-tree-treenode-selected { + background-color: #E8F1FF; + position: relative; + &::before { + position: absolute; + content: ""; + height: 100%; + width: 4px; + background-color: #1D78FF; + } + } + .ant-tree-switcher { + background: none; + } + .ant-tree-checkbox { + margin: 0; + } + .ant-tree-indent-unit::before { + height: 100%; + border-right: 1px solid #D8DAE2; + } + .ant-tree-switcher-leaf-line { + &::before { + height: 100%; + } + &::after { + height: 50%; + } + } + } + .ant-tree { + height: 100%; + .ant-tree-treenode-leaf-last .ant-tree-switcher-leaf-line::before { + height: 50% !important; + } + .ant-tree-node-content-wrapper.ant-tree-node-selected { + background: none; + } + } + .tree { + &__title { + display: flex; + align-items: center; + &--text { + flex: 1; + min-width: 0; + } + &--operation { + display: none; + .ant-dropdown-menu-title-content { + display: flex; + gap: 8px; + align-items: center; + .dt-catalogue__icon { + color: #B1B4C5; + } + } + } + &--input { + margin-right: 12px; + .ant-form-item { + margin-bottom: 0; + } + } + } + } + } + &__tabs { + margin-bottom: 6px; + height: fit-content; + &.ant-tabs-top > .ant-tabs-nav { + margin: 0; + padding: 0 16px; + } + .ant-tabs-extra-content { + .search { + font-size: 16px; + color: #B1B4C5; + cursor: pointer; + } + } + } + &__icon { + display: flex; + align-items: center; + } +} diff --git a/src/catalogue/index.tsx b/src/catalogue/index.tsx index 36b8cb614..dcaf017b9 100644 --- a/src/catalogue/index.tsx +++ b/src/catalogue/index.tsx @@ -1,17 +1,16 @@ -import React from 'react'; +import Catalogue from './components/catalogue'; +import CatalogueTree from './components/tree'; +import './index.scss'; -import type { ITreeProps } from './components/tree'; -import { Tree, TreeSelect } from './components'; - -export type { ITreeDataItem, ITreeProps } from './components/tree'; -export type { ITreeHeaderProps } from './components/tree/components/header'; -export { getExpendKeysByQuery, getIcon, loopTree } from './components/tree/helpers'; - -function Catalogue(props: ITreeProps) { - return ; +type OriginInputType = typeof Catalogue; +interface CatalogueInterface extends OriginInputType { + Tree: typeof CatalogueTree; } -Catalogue.Tree = Tree; -Catalogue.TreeSelect = TreeSelect; +const WrapperCatalogue = Catalogue; +(WrapperCatalogue as CatalogueInterface).Tree = CatalogueTree; + +export * from './components/catalogue'; +export * from './components/tree'; -export default Catalogue; +export default WrapperCatalogue as CatalogueInterface; diff --git a/src/catalogue/useTreeData.tsx b/src/catalogue/useTreeData.tsx new file mode 100644 index 000000000..b22bcca5a --- /dev/null +++ b/src/catalogue/useTreeData.tsx @@ -0,0 +1,30 @@ +import { useState } from 'react'; +import { DataNode } from 'antd/lib/tree'; + +import { CatalogueProps } from './components/catalogue'; + +export type ITreeNode = Omit & { + title?: React.ReactNode; + children?: ITreeNode[]; +} & U; + +export const useTreeData = = {}>(): { + data: ITreeNode[]; + expandedKeys: CatalogueProps['expandedKeys']; + onChange: (node: ITreeNode[]) => void; + onExpand: React.Dispatch>; +} => { + const [data, setData] = useState[]>([]); + const [expandedKeys, setExpandedKeys] = useState([]); + + const onChange = (data: ITreeNode[]) => { + setData(data); + }; + + return { + data, + expandedKeys, + onChange, + onExpand: setExpandedKeys, + }; +}; diff --git a/src/catalogue/utils.tsx b/src/catalogue/utils.tsx new file mode 100644 index 000000000..69f88c132 --- /dev/null +++ b/src/catalogue/utils.tsx @@ -0,0 +1,191 @@ +import React from 'react'; + +import { CatalogueProps } from './components/catalogue'; +import { FolderIcon, FolderOpenedIcon } from './components/icon'; +import { ITreeNode } from './useTreeData'; + +export const getIcon: ITreeNode['icon'] = ({ selected, expanded }) => { + const styles: React.CSSProperties = { + fontSize: 16, + color: '#1D78FF', + }; + + if (expanded || selected) return ; + return ; +}; + +/** + * @description 轮询 Tree 数据,赋值搜索标识和leafIcon + */ +export const loopTree = >( + data: CatalogueProps['treeData'] +): CatalogueProps['treeData'] => { + return data?.map((item) => { + if (item.children) { + return { + icon: getIcon, + ...item, + children: loopTree(item.children), + }; + } + return { + icon: getIcon, + ...item, + children: undefined, + }; + }); +}; + +/** + * 查找 key 对应的节点 + * @param data 遍历的数组 + * @param key 当前 key 值 + * @returns 找到的对应节点 + */ +export const findNodeByKey = ( + data: ITreeNode[], + key: ITreeNode['key'] +): ITreeNode | null => { + for (const node of data) { + if (node.key === key) { + return node; + } + if (node.children) { + const result = findNodeByKey(node.children, key); + if (result) return result; + } + } + return null; +}; + +/** + * 更新 key 对应节点为编辑状态 + * @param data 遍历的数组 + * @param key 当前 key 值 + * @returns 更新之后 data + */ +export const updateTreeNodeEdit = ( + data: ITreeNode[], + key: ITreeNode['key'] +): ITreeNode[] => { + return data.map((node) => { + if (node.key === key) { + return { ...node, edit: true }; + } + if (node.children) { + return { ...node, children: updateTreeNodeEdit(node.children, key) }; + } + return node; + }); +}; + +/** + * 查找 key 对应的父级节点 + * @param data 遍历的数组 + * @param key 当前 key 值 + * @returns 当前找到父级节点 + */ +export const findParentNodeByKey = ( + data: ITreeNode[], + key: ITreeNode['key'] +): ITreeNode | null => { + for (const node of data) { + if (node.children?.some((child) => child.key === key)) { + return node; + } + // 如果有子节点,递归查找 + if (node.children) { + const parent = findParentNodeByKey(node.children, key); + if (parent) return parent; + } + } + return null; +}; + +/** + * 在 key 节点中添加子节点 + * @param data 遍历的数组 + * @param key 当前 key 值 + * @returns 插入新数据之后的 data + */ +export const appendNodeByKey = ( + data: ITreeNode[], + key: ITreeNode['key'] +): ITreeNode[] => { + const newNode = { key: 'new_', title: '', edit: true }; + return data.map((node) => { + if (node.key === key) { + const updatedChildren = node.children ? [...node.children, newNode] : [newNode]; + return { ...node, children: updatedChildren }; + } + if (node.children) { + return { ...node, children: appendNodeByKey(node.children, key) }; + } + return node; + }); +}; + +/** + * 移除 key 节点 + * @param data 遍历的数组 + * @param key 当前 key 值 + * @returns 删除数据之后的 data + */ +export const removeNodeByKey = ( + data: ITreeNode[], + key: ITreeNode['key'] +): ITreeNode[] => { + return data + .filter((node) => node.key !== key) + .map((node) => { + if (node.children) { + return { + ...node, + children: removeNodeByKey(node.children, key), + }; + } + return node; + }); +}; + +/** + * 移除 edit 为 true 的节点 + * @param treeData + * @returns 移除之后的数据 + */ +export const removeEditNode = ( + treeData: ITreeNode[] +): ITreeNode[] => { + return treeData + .filter((node) => !node.edit) + .map((node) => ({ + ...node, + children: node.children ? removeEditNode(node.children) : undefined, + })); +}; + +/** + * + */ + +export const insertChildIntoNode = ( + treeData: ITreeNode[], + key: ITreeNode['key'], + children: ITreeNode[] +): ITreeNode[] => { + return treeData.map((node) => { + if (node.key === key) { + return { + ...node, + children, + }; + } + if (node.children) { + return { + ...node, + children: insertChildIntoNode(node.children, key, children), + }; + } + return node; + }); +};