From 8f9723de90cd26c51d8afa607906408219dd82ff Mon Sep 17 00:00:00 2001 From: "kr.mao" Date: Tue, 14 Dec 2021 15:44:47 +0800 Subject: [PATCH 01/11] =?UTF-8?q?-=20=E6=94=AF=E6=8C=81=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E6=8E=A7=E5=88=B6=E6=98=AF=E5=90=A6=E5=B1=95=E5=BC=80,=20?= =?UTF-8?q?=E4=BC=98=E5=85=88=E7=BA=A7=E5=A4=A7=E4=BA=8E=20expandAll=20-?= =?UTF-8?q?=20=E6=94=AF=E6=8C=81=E5=BC=82=E6=AD=A5=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E5=AD=90=E8=8A=82=E7=82=B9=E6=95=B0=E6=8D=AE(=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E9=87=8F=E8=B6=85=E5=A4=A7=E6=97=B6=E5=8F=AF=E4=BB=A5?= =?UTF-8?q?=E4=BD=BF=E7=94=A8)=20-=20=E5=BC=82=E6=AD=A5=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E5=AD=90=E8=8A=82=E7=82=B9=E6=95=B0=E6=8D=AE=E6=97=B6=20'+'=20?= =?UTF-8?q?=E5=9B=BE=E6=A0=87=E5=8F=98=E4=B8=BA=20loading=20=E5=9C=86?= =?UTF-8?q?=E5=9C=88=E5=9B=BE=E6=A0=87=20-=20=E9=BB=98=E8=AE=A4=E5=B1=85?= =?UTF-8?q?=E4=B8=AD,=20=E4=BC=98=E5=85=88=E7=BA=A7=E4=BD=8E=E4=BA=8E=20st?= =?UTF-8?q?yle=20-=20renderNode=20=E8=87=AA=E5=AE=9A=E4=B9=89=E8=8C=83?= =?UTF-8?q?=E5=9B=B4=E6=89=A9=E5=A4=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 37 ++++++++++-------- src/OrgChart.module.less | 27 +++++++++++-- src/OrgChart.tsx | 2 +- src/components/DefaultOrgChart.tsx | 62 ++++++++++++++++++++++++------ src/interface.ts | 3 ++ 5 files changed, 98 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index b3217cc..affd9db 100644 --- a/README.md +++ b/README.md @@ -42,26 +42,29 @@ export default () => { ### NodeDataType -| 名称 | 类型 | 默认值 | 说明 | -| --------- | ------------------- | ------ | ---------- | -| key | string \| number | - | key | -| label | number | - | label | -| children | NodeDataType[] | - | 子节点集合 | -| className | string | - | 类名 | -| style | React.CSSProperties | - | 样式 | +| 名称 | 类型 | 默认值 | 说明 | +| ------------ | ------------------- | ------ | ------------------ | +| key | string \| number | - | key | +| label | number | - | label | +| expand | boolean | - | 控制展开/收缩 | +| loadChildren | boolean | - | 异步加载子节点数据 | +| children | NodeDataType[] | - | 子节点集合 | +| className | string | - | 类名 | +| style | React.CSSProperties | - | 样式 | ### OrgChartProps -| 名称 | 类型 | 默认值 | 说明 | -| ---------- | --------------------------------------------------------------------- | ------ | --------------------- | -| data | NodeDataType | - | 数据 | -| className | string | - | 类名 | -| style | React.CSSProperties | - | 样式 | -| expandAll | boolean | true | 是否展开所有子节点 | -| expandable | boolean | false | 是否允许子节点展开 | -| renderNode | (node: NodeDataType, originNode: React.ReactNode) => React.ReactNode; | - | 自定义渲染节点 | -| onExpand | (expanded: boolean, node: NodeDataType) => void | - | 展开/收起节点时的回调 | -| onClick | (node: NodeDataType) => void | - | 点击节点时的回调 | +| 名称 | 类型 | 默认值 | 说明 | +| ------------ | --------------------------------------------------------------------- | ------ | --------------------- | +| data | NodeDataType | - | 数据 | +| className | string | - | 类名 | +| style | React.CSSProperties | - | 样式 | +| expandAll | boolean | true | 是否展开所有子节点 | +| expandable | boolean | false | 是否允许子节点展开 | +| renderNode | (node: NodeDataType, originNode: React.ReactNode) => React.ReactNode; | - | 自定义渲染节点 | +| loadChildren | (data: NodeDataType) => Promise; | - | 异步加载子节点数据 | +| onExpand | (expanded: boolean, node: NodeDataType) => void | - | 展开/收起节点时的回调 | +| onClick | (node: NodeDataType) => void | - | 点击节点时的回调 | ## 支持 diff --git a/src/OrgChart.module.less b/src/OrgChart.module.less index 7186c39..5866547 100644 --- a/src/OrgChart.module.less +++ b/src/OrgChart.module.less @@ -59,6 +59,19 @@ } } + &-loading-children { + &::before { + content: ''; + width: 8px; + height: 8px; + border: 2px solid #fff; + border-radius: 50%; + position: absolute; + top: 4px; + left: 4px; + } + } + &:hover { background-color: darken(@line-color, 5%); } @@ -96,10 +109,18 @@ .node { display: inline-block; - border: 1px solid @node-color; - padding: 0.5rem; margin: 0 5px; - cursor: pointer; + + .node-content { + color: black; + text-align: center; + padding: 0.2rem 0.4rem; + border: 1px solid @node-color; + border-radius: 3px; + box-shadow: 0 1px 5px rgba(0, 0, 0, 0.15); + cursor: pointer; + white-space: nowrap; + } } } } diff --git a/src/OrgChart.tsx b/src/OrgChart.tsx index 7d11d20..7bd6731 100644 --- a/src/OrgChart.tsx +++ b/src/OrgChart.tsx @@ -10,7 +10,7 @@ const OrgChart = (props: OrgChartProps) => { return !!data ? (
diff --git a/src/components/DefaultOrgChart.tsx b/src/components/DefaultOrgChart.tsx index fc3d022..08e2fc0 100644 --- a/src/components/DefaultOrgChart.tsx +++ b/src/components/DefaultOrgChart.tsx @@ -4,16 +4,19 @@ import React from 'react'; const DefaultOrgChart = (props: OrgChartComponentProps) => { const { - data, expandAll = true, expandable = false, renderNode: customRenderNode, + loadChildren: customLoadChildren, onExpand, onClick, } = props; - const [expanded, setExpanded] = React.useState(false); - + const [data, setData] = React.useState(props.data); + const [expanded, setExpanded] = React.useState( + data?.expand ?? expandAll ?? false, + ); + const [loadingChildren, setLoadingChildren] = React.useState(false); const childrenLength = data.children?.length || 0; const colSpan: number = childrenLength * 2; @@ -50,8 +53,33 @@ const DefaultOrgChart = (props: OrgChartComponentProps) => { */ const handleExpandChange = () => { const newExpanded = !expanded; - setExpanded(newExpanded); - onExpand && onExpand(newExpanded, data); + if ( + !data.children && + data.loadChildren === true && + !!customLoadChildren && + newExpanded + ) { + setLoadingChildren(true); + Promise.resolve(customLoadChildren(data)) + .then((children) => { + setLoadingChildren(false); + const newData = data; + newData.children = children; + setData(newData); + setExpanded(newExpanded); + onExpand && onExpand(newExpanded, data); + }) + .catch((error) => { + setLoadingChildren(false); + console.log( + 'DefaultOrgChart handleExpandChange load children error', + error, + ); + }); + } else { + setExpanded(newExpanded); + onExpand && onExpand(newExpanded, data); + } }; /** @@ -61,16 +89,20 @@ const DefaultOrgChart = (props: OrgChartComponentProps) => { const renderVerticalLine = (): React.ReactNode => { return ( -
+
{expandable ? (
handleExpandChange()} - >
+ /> ) : null} ); @@ -105,7 +137,7 @@ const DefaultOrgChart = (props: OrgChartComponentProps) => { * @returns */ const renderChildren = (datas: NodeDataType[] = []): React.ReactNode => { - if (datas.length > 0) { + if ((datas?.length ?? 0) > 0) { return ( <> {renderVerticalLine()} @@ -123,13 +155,19 @@ const DefaultOrgChart = (props: OrgChartComponentProps) => { ); + } else if (data.loadChildren === true && !!customLoadChildren) { + return ( + <> + {renderVerticalLine()} + + ); } return; }; React.useEffect(() => { - setExpanded(expandAll); - }, [expandAll]); + setExpanded(data?.expand ?? expandAll ?? false); + }, [data?.expand, expandAll]); return ( diff --git a/src/interface.ts b/src/interface.ts index fb6736d..a1c21bb 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -3,6 +3,8 @@ export interface NodeDataType { label: string; children?: NodeDataType[]; className?: string; + expand?: boolean; + loadChildren?: boolean; style?: React.CSSProperties; } @@ -16,6 +18,7 @@ export interface OrgChartComponentProps { expandAll?: boolean; expandable?: boolean; renderNode?: RenderNode; + loadChildren?: (data: NodeDataType) => Promise; onExpand?: (expanded: boolean, node: NodeDataType) => void; onClick?: (node: NodeDataType) => void; } From 9f2e3d3b6c40ab5a4305bb9f3c1ce07e0961bbad Mon Sep 17 00:00:00 2001 From: "kr.mao" Date: Tue, 14 Dec 2021 18:27:33 +0800 Subject: [PATCH 02/11] =?UTF-8?q?=E5=BC=82=E6=AD=A5=E5=8A=A0=E8=BD=BD=20de?= =?UTF-8?q?mo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 支持横向滚动 - 默认居中 --- docs/examples/expandAsync.tsx | 106 ++++++++++++++++++++++++++++++++++ src/OrgChart.tsx | 2 +- 2 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 docs/examples/expandAsync.tsx diff --git a/docs/examples/expandAsync.tsx b/docs/examples/expandAsync.tsx new file mode 100644 index 0000000..3d18ea9 --- /dev/null +++ b/docs/examples/expandAsync.tsx @@ -0,0 +1,106 @@ +import React from 'react'; +import OrgChart, { NodeDataType } from '@twp0217/react-org-chart'; + +export default () => { + const data: NodeDataType = { + key: 0, + label: '科技有限公司', + expand: true, + children: [ + { + key: 1, + label: '研发部', + children: [ + { key: 11, label: '开发-前端' }, + { key: 12, label: '开发-后端' }, + { key: 13, label: 'UI设计' }, + { key: 14, label: '产品经理' }, + ], + }, + { + key: 2, + label: '销售部', + children: [ + { key: 21, label: '销售一部' }, + { key: 22, label: '销售二部' }, + ], + }, + { key: 3, label: '财务部' }, + { key: 4, label: '人事部', loadChildren: true }, + ], + }; + + const [expandAll, setExpandAll] = React.useState(false); + + return ( +
+
+ +
+
+
+ { + return new Promise((resolve, _reject) => { + setTimeout(() => { + resolve([ + { + key: 41, + label: '人事一部', + }, + { + key: 42, + label: '人事二部', + }, + { + key: 43, + label: '人事三部', + }, + { + key: 44, + label: '人事四部', + }, + { + key: 45, + label: '人事五部', + }, + { + key: 46, + label: '人事六部', + }, + { + key: 47, + label: '人事七部', + }, + { + key: 48, + label: '人事八部', + }, + { + key: 49, + label: '人事九部', + }, + ]); + }, 2000); + }); + }} + /> +
+
+ ); +}; diff --git a/src/OrgChart.tsx b/src/OrgChart.tsx index 7bd6731..7d11d20 100644 --- a/src/OrgChart.tsx +++ b/src/OrgChart.tsx @@ -10,7 +10,7 @@ const OrgChart = (props: OrgChartProps) => { return !!data ? (
From ab0795a0c0f0504785d4d188c88dad5c0305c621 Mon Sep 17 00:00:00 2001 From: "kr.mao" Date: Tue, 14 Dec 2021 18:39:02 +0800 Subject: [PATCH 03/11] =?UTF-8?q?=E7=BC=A9=E5=B0=8F=E6=8C=89=E9=92=AE=20z-?= =?UTF-8?q?index=20=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/OrgChart.module.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OrgChart.module.less b/src/OrgChart.module.less index 5866547..2875ff5 100644 --- a/src/OrgChart.module.less +++ b/src/OrgChart.module.less @@ -31,7 +31,7 @@ bottom: 0; margin-left: -@expand-icon-size / 2; margin-bottom: -@expand-icon-size / 2; - z-index: 99; + z-index: 9; cursor: pointer; &-expanded, From d29bb464923008c1b59c36e08a3975c44fb224a2 Mon Sep 17 00:00:00 2001 From: "kr.mao" Date: Tue, 14 Dec 2021 19:39:42 +0800 Subject: [PATCH 04/11] docs and loading icon border-box --- docs/demo/expandAsync.md | 7 +++++++ docs/examples/expandAsync.tsx | 5 +++-- src/OrgChart.module.less | 5 +++-- 3 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 docs/demo/expandAsync.md diff --git a/docs/demo/expandAsync.md b/docs/demo/expandAsync.md new file mode 100644 index 0000000..34e6eea --- /dev/null +++ b/docs/demo/expandAsync.md @@ -0,0 +1,7 @@ +--- +order: 4 +--- + +## expandAsync + + diff --git a/docs/examples/expandAsync.tsx b/docs/examples/expandAsync.tsx index 3d18ea9..9c6cabb 100644 --- a/docs/examples/expandAsync.tsx +++ b/docs/examples/expandAsync.tsx @@ -43,7 +43,7 @@ export default () => {
{ key: 49, label: '人事九部', }, + ``, ]); - }, 2000); + }, 2000000); }); }} /> diff --git a/src/OrgChart.module.less b/src/OrgChart.module.less index 2875ff5..998b61d 100644 --- a/src/OrgChart.module.less +++ b/src/OrgChart.module.less @@ -61,14 +61,15 @@ &-loading-children { &::before { + box-sizing: border-box; content: ''; width: 8px; height: 8px; border: 2px solid #fff; border-radius: 50%; position: absolute; - top: 4px; - left: 4px; + top: (@expand-icon-size - 8px) / 2; + left: (@expand-icon-size - 8px) / 2; } } From f5bf39a009713f25d4fe018e0243f26ddf4614eb Mon Sep 17 00:00:00 2001 From: "kr.mao" Date: Tue, 14 Dec 2021 19:45:01 +0800 Subject: [PATCH 05/11] docs fix about timeout --- docs/examples/expandAsync.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/examples/expandAsync.tsx b/docs/examples/expandAsync.tsx index 9c6cabb..800cb41 100644 --- a/docs/examples/expandAsync.tsx +++ b/docs/examples/expandAsync.tsx @@ -95,9 +95,8 @@ export default () => { key: 49, label: '人事九部', }, - ``, ]); - }, 2000000); + }, 3000); }); }} /> From d6621f9d711d8c779e29ef4e0b4d4d4f4c5e6b0b Mon Sep 17 00:00:00 2001 From: "kr.mao" Date: Tue, 21 Dec 2021 17:33:23 +0800 Subject: [PATCH 06/11] features 1. support expandableOnlyOneOnSameTime 2. support expanded path --- src/OrgChart.tsx | 1 + src/components/DefaultOrgChart.tsx | 110 +++++++++++++++++++++++++---- src/interface.ts | 17 ++++- 3 files changed, 114 insertions(+), 14 deletions(-) diff --git a/src/OrgChart.tsx b/src/OrgChart.tsx index 7d11d20..f51e2d3 100644 --- a/src/OrgChart.tsx +++ b/src/OrgChart.tsx @@ -2,6 +2,7 @@ import classNames from 'classnames'; import React from 'react'; import DefaultOrgChart from './components/DefaultOrgChart'; import { OrgChartProps } from './interface'; +// @ts-ignore import styles from './OrgChart.module.less'; const OrgChart = (props: OrgChartProps) => { diff --git a/src/components/DefaultOrgChart.tsx b/src/components/DefaultOrgChart.tsx index 08e2fc0..8c0aee8 100644 --- a/src/components/DefaultOrgChart.tsx +++ b/src/components/DefaultOrgChart.tsx @@ -1,6 +1,6 @@ import { NodeDataType, OrgChartComponentProps } from '../interface'; import classNames from 'classnames'; -import React from 'react'; +import React, { useReducer } from 'react'; const DefaultOrgChart = (props: OrgChartComponentProps) => { const { @@ -12,6 +12,8 @@ const DefaultOrgChart = (props: OrgChartComponentProps) => { onClick, } = props; + // eslint-disable-next-line + const [ignored, forceUpdate] = useReducer((x) => x + 1, 0); const [data, setData] = React.useState(props.data); const [expanded, setExpanded] = React.useState( data?.expand ?? expandAll ?? false, @@ -19,6 +21,8 @@ const DefaultOrgChart = (props: OrgChartComponentProps) => { const [loadingChildren, setLoadingChildren] = React.useState(false); const childrenLength = data.children?.length || 0; const colSpan: number = childrenLength * 2; + const expandableOnlyOneOnSameTime = + props.expandableOnlyOneOnSameTime ?? false; /** * 渲染节点 @@ -62,12 +66,22 @@ const DefaultOrgChart = (props: OrgChartComponentProps) => { setLoadingChildren(true); Promise.resolve(customLoadChildren(data)) .then((children) => { + console.log( + 'handleExpandChange expandableOnlyOneOnSameTime=', + expandableOnlyOneOnSameTime, + ', children=', + children, + ); setLoadingChildren(false); - const newData = data; - newData.children = children; - setData(newData); - setExpanded(newExpanded); - onExpand && onExpand(newExpanded, data); + + if (!expandableOnlyOneOnSameTime) { + const newData = data; + newData.children = children; + setData(newData); + } else { + data.children = children; + } + processExpanded(newExpanded); }) .catch((error) => { setLoadingChildren(false); @@ -77,8 +91,64 @@ const DefaultOrgChart = (props: OrgChartComponentProps) => { ); }); } else { + processExpanded(newExpanded); + } + }; + + const getExpandedPath = (newExpanded = expanded) => + (props.expandedPath ?? []).concat(newExpanded ? [data.label] : []); + + const processExpanded = (newExpanded: boolean) => { + if ( + !newExpanded || + !expandableOnlyOneOnSameTime || + (data?.children?.length ?? 0) <= 1 + ) { setExpanded(newExpanded); - onExpand && onExpand(newExpanded, data); + onExpand && onExpand(newExpanded, data, getExpandedPath(newExpanded)); + } else { + props?.setBrothersExpand?.( + (item) => (item.label === data.label ? newExpanded : false), + (processBySelf) => { + if (processBySelf) { + setExpanded(newExpanded); + } + }, + ); + onExpand && onExpand(newExpanded, data, getExpandedPath(newExpanded)); + } + }; + + const setChildrenExpand = ( + handleChildExpanded: (item: NodeDataType) => boolean, + handleExpandedBySelf: (processBySelf: boolean) => void, + ) => { + let changeCount = 0; + data?.children?.map((item) => { + let newExpand = handleChildExpanded(item); + if ((item.children?.length ?? 0 > 0) && item.expand !== newExpand) { + console.log( + 'setChildrenExpand item.label', + changeCount, + item.label, + item.expand, + newExpand, + ); + if (item.expand !== undefined || !newExpand) { + changeCount++; + } + item.expand = newExpand; + } + }); + if (changeCount > 1) { + console.log('setChildrenExpand forceUpdate changeCount', changeCount); + forceUpdate(); + } else { + console.log( + 'setChildrenExpand handleExpandedBySelf changeCount', + changeCount, + ); + handleExpandedBySelf(true); } }; @@ -133,11 +203,12 @@ const DefaultOrgChart = (props: OrgChartComponentProps) => { /** * 渲染子节点 - * @param datas + * @param children * @returns */ - const renderChildren = (datas: NodeDataType[] = []): React.ReactNode => { - if ((datas?.length ?? 0) > 0) { + const renderChildren = (children: NodeDataType[] = []): React.ReactNode => { + console.log('renderChildren', data); + if ((children?.length ?? 0) > 0) { return ( <>
{renderVerticalLine()} @@ -145,10 +216,23 @@ const DefaultOrgChart = (props: OrgChartComponentProps) => { {renderConnectLines()} - {datas.map((data) => { + {children.map((child) => { return ( - ); })} diff --git a/src/interface.ts b/src/interface.ts index a1c21bb..aed7cff 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -1,3 +1,8 @@ +/* eslint-disable */ +// noinspection TypeScriptUMDGlobal + +// noinspection TypeScriptUMDGlobal + export interface NodeDataType { key: string | number; label: string; @@ -17,9 +22,19 @@ export interface OrgChartComponentProps { data: NodeDataType; expandAll?: boolean; expandable?: boolean; + expandableOnlyOneOnSameTime?: boolean; + expandedPath?: string[]; renderNode?: RenderNode; + setBrothersExpand?: ( + handleChildExpanded: (item: NodeDataType) => boolean, + handleExpandedBySelf: (processBySelf: boolean) => void, + ) => void; loadChildren?: (data: NodeDataType) => Promise; - onExpand?: (expanded: boolean, node: NodeDataType) => void; + onExpand?: ( + expanded: boolean, + node: NodeDataType, + expandedPath: string[], + ) => void; onClick?: (node: NodeDataType) => void; } From acb81e8baea87e12128e6ee535a1889ee7ce82d3 Mon Sep 17 00:00:00 2001 From: "kr.mao" Date: Wed, 22 Dec 2021 17:24:56 +0800 Subject: [PATCH 07/11] features about keyMap and debugLog --- src/components/DefaultOrgChart.tsx | 122 ++++++++++++++++------------- src/interface.ts | 4 + 2 files changed, 72 insertions(+), 54 deletions(-) diff --git a/src/components/DefaultOrgChart.tsx b/src/components/DefaultOrgChart.tsx index 8c0aee8..85f892f 100644 --- a/src/components/DefaultOrgChart.tsx +++ b/src/components/DefaultOrgChart.tsx @@ -12,35 +12,60 @@ const DefaultOrgChart = (props: OrgChartComponentProps) => { onClick, } = props; + //region data with key map + const [data, setData] = React.useState(props.data); + const getValue = (key: string, fromData: NodeDataType = data) => + fromData[props.keyMap?.[key] ?? key]; + const setValue = (key: string, value: any, toData: NodeDataType = data) => { + toData[props.keyMap?.[key] ?? key] = value; + return toData; + }; + const label = getValue('label'); + const style = getValue('style'); + const className = getValue('className'); + const loadChildren = getValue('loadChildren'); + const expand = getValue('expand'); + const children = () => getValue('children'); + const childrenLength = () => children()?.length || 0; + const [colSpan, setColSpan] = React.useState(childrenLength() * 2); + //endregion + + if (props.debug === true) { + console.log('DefaultOrgChart props=', props); + console.log('DefaultOrgChart label=', label); + console.log('DefaultOrgChart style=', style); + console.log('DefaultOrgChart className=', className); + console.log('DefaultOrgChart loadChildren=', loadChildren); + console.log('DefaultOrgChart expand=', expand); + console.log('DefaultOrgChart children=', children()); + console.log('DefaultOrgChart childrenLength=', childrenLength()); + } + // eslint-disable-next-line const [ignored, forceUpdate] = useReducer((x) => x + 1, 0); - const [data, setData] = React.useState(props.data); const [expanded, setExpanded] = React.useState( - data?.expand ?? expandAll ?? false, + expand ?? expandAll ?? false, ); const [loadingChildren, setLoadingChildren] = React.useState(false); - const childrenLength = data.children?.length || 0; - const colSpan: number = childrenLength * 2; const expandableOnlyOneOnSameTime = props.expandableOnlyOneOnSameTime ?? false; /** * 渲染节点 * @param data - * @returns */ const renderNode = (data: NodeDataType): React.ReactNode => { const contentNode: React.ReactNode = ( -
- {data.label} +
+ {label}
); return (
{renderVerticalLine()} @@ -216,9 +227,12 @@ const DefaultOrgChart = (props: OrgChartComponentProps) => { {renderConnectLines()} - {children.map((child) => { + {children.map((child, index) => { return ( - ); - } else if (data.loadChildren === true && !!customLoadChildren) { + } else if (loadChildren === true && !!customLoadChildren) { return ( <> {renderVerticalLine()} @@ -250,14 +264,14 @@ const DefaultOrgChart = (props: OrgChartComponentProps) => { }; React.useEffect(() => { - setExpanded(data?.expand ?? expandAll ?? false); - }, [data?.expand, expandAll]); + setExpanded(expand ?? expandAll ?? false); + }, [data, expand, expandAll]); return (
- + + boolean, + handleExpandedBySelf: (processBySelf: boolean) => void, + ) => { + setChildrenExpand( + handleChildExpanded, + handleExpandedBySelf, + ); + }} + />
onClick && onClick(data)} > {!!customRenderNode @@ -58,37 +83,27 @@ const DefaultOrgChart = (props: OrgChartComponentProps) => { const handleExpandChange = () => { const newExpanded = !expanded; if ( - !data.children && - data.loadChildren === true && + (children()?.length ?? 0 <= 0) && + loadChildren === true && !!customLoadChildren && newExpanded ) { setLoadingChildren(true); Promise.resolve(customLoadChildren(data)) - .then((children) => { - console.log( - 'handleExpandChange expandableOnlyOneOnSameTime=', - expandableOnlyOneOnSameTime, - ', children=', - children, - ); + .then((newChildren) => { setLoadingChildren(false); - if (!expandableOnlyOneOnSameTime) { const newData = data; - newData.children = children; + setValue('children', newChildren, newData); setData(newData); } else { - data.children = children; + setValue('children', newChildren); } + setColSpan(childrenLength() * 2); processExpanded(newExpanded); }) - .catch((error) => { + .catch((ignore) => { setLoadingChildren(false); - console.log( - 'DefaultOrgChart handleExpandChange load children error', - error, - ); }); } else { processExpanded(newExpanded); @@ -96,19 +111,20 @@ const DefaultOrgChart = (props: OrgChartComponentProps) => { }; const getExpandedPath = (newExpanded = expanded) => - (props.expandedPath ?? []).concat(newExpanded ? [data.label] : []); + (props.expandedPath ?? []).concat(newExpanded ? [label] : []); const processExpanded = (newExpanded: boolean) => { if ( + !props.setBrothersExpand || !newExpanded || !expandableOnlyOneOnSameTime || - (data?.children?.length ?? 0) <= 1 + childrenLength() <= 1 ) { setExpanded(newExpanded); onExpand && onExpand(newExpanded, data, getExpandedPath(newExpanded)); } else { - props?.setBrothersExpand?.( - (item) => (item.label === data.label ? newExpanded : false), + props.setBrothersExpand?.( + (item) => (getValue('label', item) === label ? newExpanded : false), (processBySelf) => { if (processBySelf) { setExpanded(newExpanded); @@ -124,30 +140,25 @@ const DefaultOrgChart = (props: OrgChartComponentProps) => { handleExpandedBySelf: (processBySelf: boolean) => void, ) => { let changeCount = 0; - data?.children?.map((item) => { + children()?.map((item: NodeDataType) => { let newExpand = handleChildExpanded(item); - if ((item.children?.length ?? 0 > 0) && item.expand !== newExpand) { - console.log( - 'setChildrenExpand item.label', - changeCount, - item.label, - item.expand, - newExpand, - ); - if (item.expand !== undefined || !newExpand) { + let oldExpand = getValue('expand', item); + if ( + (getValue('children', item)?.length ?? 0 > 0) && + oldExpand !== newExpand + ) { + if ( + (!oldExpand && newExpand === true) || + (oldExpand === true && !newExpand) + ) { changeCount++; } - item.expand = newExpand; + setValue('expand', newExpand, item); } }); if (changeCount > 1) { - console.log('setChildrenExpand forceUpdate changeCount', changeCount); forceUpdate(); } else { - console.log( - 'setChildrenExpand handleExpandedBySelf changeCount', - changeCount, - ); handleExpandedBySelf(true); } }; @@ -206,9 +217,9 @@ const DefaultOrgChart = (props: OrgChartComponentProps) => { * @param children * @returns */ - const renderChildren = (children: NodeDataType[] = []): React.ReactNode => { - console.log('renderChildren', data); + const renderChildren = (children: NodeDataType[]): React.ReactNode => { if ((children?.length ?? 0) > 0) { + let uniqueTime = new Date().getTime(); return ( <>
+ {
{renderNode(data)} - {renderChildren(data.children)} + {renderChildren(children())}
); diff --git a/src/interface.ts b/src/interface.ts index aed7cff..a1cb43d 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -4,6 +4,8 @@ // noinspection TypeScriptUMDGlobal export interface NodeDataType { + [key: string]: any; + key: string | number; label: string; children?: NodeDataType[]; @@ -21,10 +23,12 @@ export type RenderNode = ( export interface OrgChartComponentProps { data: NodeDataType; expandAll?: boolean; + debug?: boolean; expandable?: boolean; expandableOnlyOneOnSameTime?: boolean; expandedPath?: string[]; renderNode?: RenderNode; + keyMap?: { [key: string]: string }; setBrothersExpand?: ( handleChildExpanded: (item: NodeDataType) => boolean, handleExpandedBySelf: (processBySelf: boolean) => void, From 8a83985faff9e887147d5966570e3da2efa7f9da Mon Sep 17 00:00:00 2001 From: "kr.mao" Date: Wed, 22 Dec 2021 18:17:23 +0800 Subject: [PATCH 08/11] features -> support filter data --- src/components/DefaultOrgChart.tsx | 5 ++++- src/interface.ts | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/DefaultOrgChart.tsx b/src/components/DefaultOrgChart.tsx index 85f892f..b2a9a07 100644 --- a/src/components/DefaultOrgChart.tsx +++ b/src/components/DefaultOrgChart.tsx @@ -25,7 +25,10 @@ const DefaultOrgChart = (props: OrgChartComponentProps) => { const className = getValue('className'); const loadChildren = getValue('loadChildren'); const expand = getValue('expand'); - const children = () => getValue('children'); + const children = () => + getValue('children')?.filter((item: NodeDataType) => { + return props.filter?.(item) ?? true; + }); const childrenLength = () => children()?.length || 0; const [colSpan, setColSpan] = React.useState(childrenLength() * 2); //endregion diff --git a/src/interface.ts b/src/interface.ts index a1cb43d..d832a14 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -40,6 +40,7 @@ export interface OrgChartComponentProps { expandedPath: string[], ) => void; onClick?: (node: NodeDataType) => void; + filter?: (node: NodeDataType) => boolean; } export interface OrgChartProps extends Partial { From d24490d2d9f0b5be0ab7ef11624a90449626258b Mon Sep 17 00:00:00 2001 From: "kr.mao" Date: Wed, 22 Dec 2021 18:19:24 +0800 Subject: [PATCH 09/11] optimized styles --- src/OrgChart.module.less | 41 ++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/src/OrgChart.module.less b/src/OrgChart.module.less index 998b61d..7f6a59d 100644 --- a/src/OrgChart.module.less +++ b/src/OrgChart.module.less @@ -1,7 +1,8 @@ -@node-color: #1890ff; -@line-width: 1px; +@node-color: #3870e1; +@line-width: 2px; @line-color: @node-color; -@expand-icon-size: 16px; +@expand-icon-size: 18px; +@expand-icon-background-color: #e8eefb; .orgChartContainer { display: inline-block; @@ -21,11 +22,13 @@ position: relative; .expand-icon { + box-sizing: content-box; display: inline-block; width: @expand-icon-size; height: @expand-icon-size; border-radius: 50%; - background-color: @line-color; + background-color: @expand-icon-background-color; + border: 1px solid @node-color; position: absolute; left: 50%; bottom: 0; @@ -37,25 +40,27 @@ &-expanded, &-collapsed { &::before { + box-sizing: border-box; content: ''; - width: 8px; + width: 10px; height: 2px; - background-color: #fff; + background-color: @node-color; position: absolute; - top: 7px; - left: 4px; + top: (@expand-icon-size - 2px) / 2; + left: (@expand-icon-size - 10px) / 2; } } &-collapsed { &::after { + box-sizing: border-box; content: ''; width: 2px; - height: 8px; - background-color: #fff; + height: 10px; + background-color: @node-color; position: absolute; - top: 4px; - left: 7px; + top: (@expand-icon-size - 10px) / 2; + left: (@expand-icon-size - 2px) / 2; } } @@ -63,18 +68,18 @@ &::before { box-sizing: border-box; content: ''; - width: 8px; - height: 8px; - border: 2px solid #fff; + width: 10px; + height: 10px; + border: 2px solid @node-color; border-radius: 50%; position: absolute; - top: (@expand-icon-size - 8px) / 2; - left: (@expand-icon-size - 8px) / 2; + top: (@expand-icon-size - 10px) / 2; + left: (@expand-icon-size - 10px) / 2; } } &:hover { - background-color: darken(@line-color, 5%); + background-color: darken(@expand-icon-background-color, 5%); } } From cc798f35878684509de926d291d2289ae435e52e Mon Sep 17 00:00:00 2001 From: "kr.mao" Date: Wed, 22 Dec 2021 19:40:48 +0800 Subject: [PATCH 10/11] tree line width dynamic --- src/OrgChart.module.less | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/OrgChart.module.less b/src/OrgChart.module.less index 7f6a59d..f2ee18e 100644 --- a/src/OrgChart.module.less +++ b/src/OrgChart.module.less @@ -91,18 +91,42 @@ height: 100%; background-color: @line-color; display: inline-block; + user-select: none; } - &.left { + .mixLeft( @a ) when (mod(@a,2)>0) { + border-right: @line-width solid @line-color; + } + .mixLeft( @a ) when (od(@a,2)<0) { border-right: @line-width solid @line-color; } + .mixLeft( @a ) when (mod(@a,2)=0) { + border-right: @line-width / 2 solid @line-color; + } - &.right { + &.left { + .mixLeft(@line-width); + user-select: none; + } + + .mixRight( @a ) when (mod(@a,2)<0) { + border-left: @line-width solid transparent; + } + .mixRight( @a ) when (mod(@a,2)>0) { border-left: @line-width solid transparent; } + .mixRight( @a ) when (mod(@a,2)=0) { + border-left: @line-width / 2 solid @line-color; + } + + &.right { + .mixRight(@line-width); + user-select: none; + } &.top { border-top: @line-width solid @line-color; + user-select: none; } } } From f68c5ee3e015d5084cc22b055a3d4bdcfd193e47 Mon Sep 17 00:00:00 2001 From: "kr.mao" Date: Thu, 23 Dec 2021 17:24:16 +0800 Subject: [PATCH 11/11] fix filter bug about dynamic colSpan --- src/components/DefaultOrgChart.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/components/DefaultOrgChart.tsx b/src/components/DefaultOrgChart.tsx index b2a9a07..038e6de 100644 --- a/src/components/DefaultOrgChart.tsx +++ b/src/components/DefaultOrgChart.tsx @@ -30,7 +30,7 @@ const DefaultOrgChart = (props: OrgChartComponentProps) => { return props.filter?.(item) ?? true; }); const childrenLength = () => children()?.length || 0; - const [colSpan, setColSpan] = React.useState(childrenLength() * 2); + const colSpan = () => childrenLength() * 2; //endregion if (props.debug === true) { @@ -65,7 +65,7 @@ const DefaultOrgChart = (props: OrgChartComponentProps) => { ); return ( - +
{ const handleExpandChange = () => { const newExpanded = !expanded; if ( - (children()?.length ?? 0 <= 0) && + childrenLength() <= 0 && loadChildren === true && !!customLoadChildren && newExpanded @@ -102,7 +102,6 @@ const DefaultOrgChart = (props: OrgChartComponentProps) => { } else { setValue('children', newChildren); } - setColSpan(childrenLength() * 2); processExpanded(newExpanded); }) .catch((ignore) => { @@ -172,7 +171,7 @@ const DefaultOrgChart = (props: OrgChartComponentProps) => { */ const renderVerticalLine = (): React.ReactNode => { return ( - +
{expandable ? (
{ */ const renderConnectLines = (): React.ReactNode[] => { const lines: React.ReactNode[] = []; - for (let index = 0; index < colSpan; index++) { + for (let index = 0; index < colSpan(); index++) { lines.push(