Browse Source

feat: [Tree] support checkRelation #522 (#609)

feat: [Tree] support checkRelation #522

Co-authored-by: chenyuling <[email protected]>
boomboomchen 3 years ago
parent
commit
33646b6114

+ 88 - 0
content/navigation/tree/index-en-US.md

@@ -680,6 +680,93 @@ import { Tree } from '@douyinfe/semi-ui';
 };
 };
 ```
 ```
 
 
+### Checked RelationShip
+Version: >= 2.5.0
+
+When multiple selections are made, `checkRelation` can be used to set the type of node selection relationship, optional: 'related' (default), 'unRelated'. When the selection relationship is 'unRelated', it means that selections between nodes do not affect each other.
+
+```jsx live=true
+import React from 'react';
+import { Tree } from '@douyinfe/semi-ui';
+
+() => {
+    const treeData = [
+        {
+            label: 'Asia',
+            value: 'Asia',
+            key: '0',
+            children: [
+                {
+                    label: 'China',
+                    value: 'China',
+                    key: '0-0',
+                    children: [
+                        {
+                            label: 'Beijing',
+                            value: 'Beijing',
+                            key: '0-0-0',
+                        },
+                        {
+                            label: 'Shanghai',
+                            value: 'Shanghai',
+                            key: '0-0-1',
+                        },
+                        {
+                            label: 'Chengdu',
+                            value: 'Chengdu',
+                            key: '0-0-2',
+                        },
+                    ],
+                },
+                {
+                    label: 'Japan',
+                    value: 'Japan',
+                    key: '0-1',
+                    children: [
+                        {
+                            label: 'Osaka',
+                            value: 'Osaka',
+                            key: '0-1-0'
+                        }
+                    ]
+                },
+            ],
+        },
+        {
+            label: 'North America',
+            value: 'North America',
+            key: '1',
+            children: [
+                {
+                    label: 'United States',
+                    value: 'United States',
+                    key: '1-0'
+                },
+                {
+                    label: 'Canada',
+                    value: 'Canada',
+                    key: '1-1'
+                }
+            ]
+        }
+    ];
+    const style = {
+        width: 260,
+        height: 420,
+        border: '1px solid var(--semi-color-border)'
+    };
+    return (
+        <Tree
+            treeData={treeData}
+            multiple
+            checkRelation='unRelated'
+            defaultExpandAll
+            style={style}
+        />
+    );
+};
+```
+
 ### Default Expand All
 ### Default Expand All
 
 
 Both `defaultExpandAll` and `expandAll` can set the default expanded/collapsed state of `Tree`. The difference between the two is that `defaultExpandAll` only takes effect at initialization, while `expandAll` will not only take effect at initialization, but also when the data (`treeData`/`treeDataSimpleJson`) is dynamically updated, `expandAll` will still take effect.
 Both `defaultExpandAll` and `expandAll` can set the default expanded/collapsed state of `Tree`. The difference between the two is that `defaultExpandAll` only takes effect at initialization, while `expandAll` will not only take effect at initialization, but also when the data (`treeData`/`treeDataSimpleJson`) is dynamically updated, `expandAll` will still take effect.
@@ -1815,6 +1902,7 @@ import { IconFixedStroked, IconSectionStroked, IconAbsoluteStroked, IconInnerSec
 | autoExpandParent | Toggle whether to expand parent node automatically | boolean | false | 0.34.0 |
 | autoExpandParent | Toggle whether to expand parent node automatically | boolean | false | 0.34.0 |
 | autoExpandWhenDragEnter | Toggle whether allow autoExpand when drag enter node | boolean | true | 1.8.0 | 
 | autoExpandWhenDragEnter | Toggle whether allow autoExpand when drag enter node | boolean | true | 1.8.0 | 
 | blockNode           | Toggle whether to display node as row     | boolean                     | true    | - |
 | blockNode           | Toggle whether to display node as row     | boolean                     | true    | - |
+| checkRelation | In multiple, the relationship between the checked states of the nodes, optional: 'related'、'unRelated' | string | 'related' | 2.5.0 |
 | className           | Class name| string                      | -       | - |
 | className           | Class name| string                      | -       | - |
 | defaultExpandAll    | Set whether to expand all nodes during initialization. And if the subsequent data (`treeData`/`treeDataSimpleJson`) changes, this api cannot affect the default expansion of the node. If you need this, you can use `expandAll`    | boolean                     | false   | - |
 | defaultExpandAll    | Set whether to expand all nodes during initialization. And if the subsequent data (`treeData`/`treeDataSimpleJson`) changes, this api cannot affect the default expansion of the node. If you need this, you can use `expandAll`    | boolean                     | false   | - |
 | defaultExpandedKeys | Keys of default expanded nodes. Direct child nodes will be displayed.     | string\[]                   | -       | - |
 | defaultExpandedKeys | Keys of default expanded nodes. Direct child nodes will be displayed.     | string\[]                   | -       | - |

+ 88 - 0
content/navigation/tree/index.md

@@ -728,6 +728,93 @@ import { Tree } from '@douyinfe/semi-ui';
 };
 };
 ```
 ```
 
 
+### 节点选中关系
+版本:>= 2.5.0
+
+多选时,可以使用 `checkRelation` 来设置节点选中关系的类型,可选:'related'(默认)、'unRelated'。当选中关系为 'unRelated',意味着节点之间的选中互不影响。
+
+```jsx live=true
+import React from 'react';
+import { Tree } from '@douyinfe/semi-ui';
+
+() => {
+    const treeData = [
+        {
+            label: 'Asia',
+            value: 'Asia',
+            key: '0',
+            children: [
+                {
+                    label: 'China',
+                    value: 'China',
+                    key: '0-0',
+                    children: [
+                        {
+                            label: 'Beijing',
+                            value: 'Beijing',
+                            key: '0-0-0',
+                        },
+                        {
+                            label: 'Shanghai',
+                            value: 'Shanghai',
+                            key: '0-0-1',
+                        },
+                        {
+                            label: 'Chengdu',
+                            value: 'Chengdu',
+                            key: '0-0-2',
+                        },
+                    ],
+                },
+                {
+                    label: 'Japan',
+                    value: 'Japan',
+                    key: '0-1',
+                    children: [
+                        {
+                            label: 'Osaka',
+                            value: 'Osaka',
+                            key: '0-1-0'
+                        }
+                    ]
+                },
+            ],
+        },
+        {
+            label: 'North America',
+            value: 'North America',
+            key: '1',
+            children: [
+                {
+                    label: 'United States',
+                    value: 'United States',
+                    key: '1-0'
+                },
+                {
+                    label: 'Canada',
+                    value: 'Canada',
+                    key: '1-1'
+                }
+            ]
+        }
+    ];
+    const style = {
+        width: 260,
+        height: 420,
+        border: '1px solid var(--semi-color-border)'
+    };
+    return (
+        <Tree
+            treeData={treeData}
+            multiple
+            checkRelation='unRelated'
+            defaultExpandAll
+            style={style}
+        />
+    );
+};
+```
+
 ### 默认展开
 ### 默认展开
 
 
 `defaultExpandAll` 和 `expandAll` 均可以设置 `Tree` 的默认展开/收起状态。二者的区别是,`defaultExpandAll` 只在初始化时生效,而 `expandAll` 不仅会在初始化时生效,当数据(`treeData`/`treeDataSimpleJson`)发生动态更新时,`expandAll` 也仍然生效。
 `defaultExpandAll` 和 `expandAll` 均可以设置 `Tree` 的默认展开/收起状态。二者的区别是,`defaultExpandAll` 只在初始化时生效,而 `expandAll` 不仅会在初始化时生效,当数据(`treeData`/`treeDataSimpleJson`)发生动态更新时,`expandAll` 也仍然生效。
@@ -1833,6 +1920,7 @@ import { IconFixedStroked, IconSectionStroked, IconAbsoluteStroked, IconInnerSec
 | autoExpandParent | 是否自动展开父节点,默认为 false,当组件初次挂载时为 true | boolean | false | 0.34.0 |
 | autoExpandParent | 是否自动展开父节点,默认为 false,当组件初次挂载时为 true | boolean | false | 0.34.0 |
 | autoExpandWhenDragEnter | 是否允许拖拽到节点上时自动展开改节点 | boolean | true | 1.8.0 | 
 | autoExpandWhenDragEnter | 是否允许拖拽到节点上时自动展开改节点 | boolean | true | 1.8.0 | 
 | blockNode | 行显示节点 | boolean | true | - |
 | blockNode | 行显示节点 | boolean | true | - |
+| checkRelation | 多选时,节点之间选中状态的关系,可选:'related'、'unRelated' | string | 'related' | 2.5.0 |
 | className | 类名 | string | - | - |
 | className | 类名 | string | - | - |
 | defaultExpandAll | 设置在初始化时是否展开所有节点。而如果后续数据(`treeData`/`treeDataSimpleJson`)发生改变,这个 api 是无法影响节点的默认展开情况的,如果有这个需要可以使用 `expandAll` | boolean | false | - |
 | defaultExpandAll | 设置在初始化时是否展开所有节点。而如果后续数据(`treeData`/`treeDataSimpleJson`)发生改变,这个 api 是无法影响节点的默认展开情况的,如果有这个需要可以使用 `expandAll` | boolean | false | - |
 | defaultExpandedKeys | 默认展开的节点,显示其直接子级 | string\[] | - | - |
 | defaultExpandedKeys | 默认展开的节点,显示其直接子级 | string\[] | - | - |

+ 56 - 17
packages/semi-foundation/tree/foundation.ts

@@ -178,6 +178,8 @@ export interface Virtualize {
     width?: number | string;
     width?: number | string;
 }
 }
 
 
+export type CheckRelation = 'related' | 'unRelated';
+
 export interface BasicTreeProps {
 export interface BasicTreeProps {
     autoExpandParent?: boolean;
     autoExpandParent?: boolean;
     autoExpandWhenDragEnter?: boolean;
     autoExpandWhenDragEnter?: boolean;
@@ -233,6 +235,7 @@ export interface BasicTreeProps {
     value?: BasicValue;
     value?: BasicValue;
     virtualize?: Virtualize;
     virtualize?: Virtualize;
     icon?: any;
     icon?: any;
+    checkRelation?: CheckRelation;
     'aria-label'?: string;
     'aria-label'?: string;
 }
 }
 
 
@@ -252,6 +255,8 @@ export interface BasicTreeInnerData {
     checkedKeys: Set<string>;
     checkedKeys: Set<string>;
     /* Half-selected node when multiple selection */
     /* Half-selected node when multiple selection */
     halfCheckedKeys: Set<string>;
     halfCheckedKeys: Set<string>;
+    /* real selected nodes in multiple selection */
+    realCheckedKeys: Set<string>;
     /* Animation node */
     /* Animation node */
     motionKeys: Set<string>;
     motionKeys: Set<string>;
     /* Animation type */
     /* Animation type */
@@ -358,6 +363,7 @@ export default class TreeFoundation extends BaseFoundation<TreeAdapter, BasicTre
             selectedKeys = [],
             selectedKeys = [],
             checkedKeys = new Set([]),
             checkedKeys = new Set([]),
             halfCheckedKeys = new Set([]),
             halfCheckedKeys = new Set([]),
+            realCheckedKeys = new Set([]),
             keyEntities = {},
             keyEntities = {},
             filteredKeys = new Set([]),
             filteredKeys = new Set([]),
             inputValue = '',
             inputValue = '',
@@ -366,19 +372,29 @@ export default class TreeFoundation extends BaseFoundation<TreeAdapter, BasicTre
             filteredExpandedKeys = new Set([]),
             filteredExpandedKeys = new Set([]),
             disabledKeys = new Set([]),
             disabledKeys = new Set([]),
         } = this.getStates();
         } = this.getStates();
-        const { treeNodeFilterProp } = this.getProps();
+        const { treeNodeFilterProp, checkRelation } = this.getProps();
         const entity = keyEntities[key];
         const entity = keyEntities[key];
         const notExist = !entity;
         const notExist = !entity;
         if (notExist) {
         if (notExist) {
             return null;
             return null;
         }
         }
+        // if checkRelation is invalid, the checked status of node will be false
+        let realChecked = false;
+        let realHalfChecked = false;
+        if (checkRelation === 'related') {
+            realChecked = checkedKeys.has(key);
+            realHalfChecked = halfCheckedKeys.has(key);
+        } else if (checkRelation === 'unRelated') {
+            realChecked = realCheckedKeys.has(key);
+            realHalfChecked = false;
+        }
         const isSearching = Boolean(inputValue);
         const isSearching = Boolean(inputValue);
         const treeNodeProps: BasicTreeNodeProps = {
         const treeNodeProps: BasicTreeNodeProps = {
             eventKey: key,
             eventKey: key,
             expanded: isSearching ? filteredExpandedKeys.has(key) : expandedKeys.has(key),
             expanded: isSearching ? filteredExpandedKeys.has(key) : expandedKeys.has(key),
             selected: selectedKeys.includes(key),
             selected: selectedKeys.includes(key),
-            checked: checkedKeys.has(key),
-            halfChecked: halfCheckedKeys.has(key),
+            checked: realChecked,
+            halfChecked: realHalfChecked,
             pos: String(entity ? entity.pos : ''),
             pos: String(entity ? entity.pos : ''),
             level: entity.level,
             level: entity.level,
             filtered: filteredKeys.has(key),
             filtered: filteredKeys.has(key),
@@ -402,11 +418,16 @@ export default class TreeFoundation extends BaseFoundation<TreeAdapter, BasicTre
         this._adapter.notifyChange(value as BasicValue);
         this._adapter.notifyChange(value as BasicValue);
     }
     }
 
 
-    notifyMultipleChange(key: string[] | string, e: any) {
+    notifyMultipleChange(key: string[], e: any) {
         const { keyEntities } = this.getStates();
         const { keyEntities } = this.getStates();
-        const { leafOnly } = this.getProps();
+        const { leafOnly, checkRelation } = this.getProps();
         let value;
         let value;
-        const keyList = normalizeKeyList(key, keyEntities, leafOnly);
+        let keyList = [];
+        if (checkRelation === 'related') {
+            keyList = normalizeKeyList(key, keyEntities, leafOnly);
+        } else if (checkRelation === 'unRelated') {
+            keyList = key;
+        }
         if (this.getProp('onChangeWithObject')) {
         if (this.getProp('onChangeWithObject')) {
             value = keyList.map((itemKey: string) => keyEntities[itemKey].data);
             value = keyList.map((itemKey: string) => keyEntities[itemKey].data);
         } else {
         } else {
@@ -421,7 +442,7 @@ export default class TreeFoundation extends BaseFoundation<TreeAdapter, BasicTre
         if (this.getProp('treeDataSimpleJson')) {
         if (this.getProp('treeDataSimpleJson')) {
             this.notifyJsonChange(key, e);
             this.notifyJsonChange(key, e);
         } else if (isMultiple) {
         } else if (isMultiple) {
-            this.notifyMultipleChange(key, e);
+            this.notifyMultipleChange(key as string[], e);
         } else {
         } else {
             let value;
             let value;
             if (this.getProp('onChangeWithObject')) {
             if (this.getProp('onChangeWithObject')) {
@@ -565,18 +586,36 @@ export default class TreeFoundation extends BaseFoundation<TreeAdapter, BasicTre
     * Handle the selection event in the case of multiple selection
     * Handle the selection event in the case of multiple selection
     */
     */
     handleMultipleSelect(e: any, treeNode: BasicTreeNodeProps) {
     handleMultipleSelect(e: any, treeNode: BasicTreeNodeProps) {
-        const { disableStrictly } = this.getProps();
+        const { disableStrictly, checkRelation } = this.getProps();
+        const { realCheckedKeys } = this.getStates();
         // eventKey: The key value of the currently clicked node
         // eventKey: The key value of the currently clicked node
         const { checked, eventKey, data } = treeNode;
         const { checked, eventKey, data } = treeNode;
-        // Find the checked state of the current node
-        const targetStatus = disableStrictly ? this.calcChekcedStatus(!checked, eventKey) : !checked;
-        const { checkedKeys, halfCheckedKeys } = disableStrictly ?
-            this.calcNonDisabedCheckedKeys(eventKey, targetStatus) :
-            this.calcCheckedKeys(eventKey, targetStatus);
-        this._adapter.notifySelect(eventKey, targetStatus, data);
-        this.notifyChange([...checkedKeys], e);
-        if (!this._isControlledComponent()) {
-            this._adapter.updateState({ checkedKeys, halfCheckedKeys });
+        if (checkRelation === 'related') {
+            // Find the checked state of the current node
+            const targetStatus = disableStrictly ? this.calcChekcedStatus(!checked, eventKey) : !checked;
+            const { checkedKeys, halfCheckedKeys } = disableStrictly ?
+                this.calcNonDisabedCheckedKeys(eventKey, targetStatus) :
+                this.calcCheckedKeys(eventKey, targetStatus);
+            this._adapter.notifySelect(eventKey, targetStatus, data);
+            this.notifyChange([...checkedKeys], e);
+            if (!this._isControlledComponent()) {
+                this._adapter.updateState({ checkedKeys, halfCheckedKeys });
+            }
+        } else if (checkRelation === 'unRelated') {
+            const newRealCheckedKeys: Set<string> = new Set(realCheckedKeys);
+            let targetStatus: boolean;
+            if (realCheckedKeys.has(eventKey)) {
+                newRealCheckedKeys.delete(eventKey);
+                targetStatus = false;
+            } else {
+                newRealCheckedKeys.add(eventKey);
+                targetStatus = true;
+            }
+            this._adapter.notifySelect(eventKey, targetStatus, data);
+            this.notifyChange([...newRealCheckedKeys], e);
+            if (!this._isControlledComponent()) {
+                this._adapter.updateState({ realCheckedKeys: newRealCheckedKeys });
+            }
         }
         }
     }
     }
 
 

+ 94 - 0
packages/semi-ui/tree/__test__/treeMultiple.test.js

@@ -357,6 +357,100 @@ describe('Tree', () => {
         ).toEqual(true);
         ).toEqual(true);
     });
     });
 
 
+    it('unRelated', () => {
+        const spyOnChange = sinon.spy(() => { });
+        const tree = getTree({
+            defaultExpandAll: true,
+            onChange: spyOnChange,
+            checkRelation: 'unRelated',
+        });
+        const nodelevel2 = tree.find(`.${BASE_CLASS_PREFIX}-tree-option.${BASE_CLASS_PREFIX}-tree-option-level-2`);
+        const selectedNode = nodelevel2.at(0);
+        selectedNode.simulate('click');
+        expect(spyOnChange.calledOnce).toBe(true);
+        expect(spyOnChange.calledWithMatch(['Zhongguo'])).toEqual(true);
+        // Note: selectedNode cannot be used directly here. selectedNode is the original node in the unselected state
+        expect(
+            tree
+            .find(`.${BASE_CLASS_PREFIX}-tree-option.${BASE_CLASS_PREFIX}-tree-option-level-2`)
+            .at(0)
+            .exists(`.${BASE_CLASS_PREFIX}-checkbox-checked`)
+        ).toEqual(true);
+        const nodelevel3 = tree.find(`.${BASE_CLASS_PREFIX}-tree-option.${BASE_CLASS_PREFIX}-tree-option-level-3`);
+        expect(
+            nodelevel3
+            .exists(`.${BASE_CLASS_PREFIX}-checkbox-unChecked` )
+        ).toEqual(true);
+        expect(
+            nodelevel3
+            .at(1)
+            .exists(`.${BASE_CLASS_PREFIX}-checkbox-unChecked` )
+        ).toEqual(true);  
+    });
+
+    it('unRelated + value', () => {
+        const tree = getTree({
+            defaultExpandAll: true,
+            checkRelation: 'unRelated',
+            value: 'Zhongguo'
+        });
+        expect(
+            tree
+            .find(`.${BASE_CLASS_PREFIX}-tree-option.${BASE_CLASS_PREFIX}-tree-option-level-2`)
+            .at(0)
+            .exists(`.${BASE_CLASS_PREFIX}-checkbox-checked`)
+        ).toEqual(true);
+        const nodelevel3 = tree.find(`.${BASE_CLASS_PREFIX}-tree-option.${BASE_CLASS_PREFIX}-tree-option-level-3`);
+        expect(
+            nodelevel3
+            .exists(`.${BASE_CLASS_PREFIX}-checkbox-unChecked` )
+        ).toEqual(true);
+        expect(
+            nodelevel3
+            .at(1)
+            .exists(`.${BASE_CLASS_PREFIX}-checkbox-unChecked` )
+        ).toEqual(true);  
+    });
+
+    it('unRelated + defaultValue', () => {
+        const tree = getTree({
+            defaultExpandAll: true,
+            checkRelation: 'unRelated',
+            defaultValue: 'Zhongguo'
+        });
+        expect(
+            tree
+            .find(`.${BASE_CLASS_PREFIX}-tree-option.${BASE_CLASS_PREFIX}-tree-option-level-2`)
+            .at(0)
+            .exists(`.${BASE_CLASS_PREFIX}-checkbox-checked`)
+        ).toEqual(true);
+        const nodelevel3 = tree.find(`.${BASE_CLASS_PREFIX}-tree-option.${BASE_CLASS_PREFIX}-tree-option-level-3`);
+        expect(
+            nodelevel3
+            .exists(`.${BASE_CLASS_PREFIX}-checkbox-unChecked` )
+        ).toEqual(true);
+        expect(
+            nodelevel3
+            .at(1)
+            .exists(`.${BASE_CLASS_PREFIX}-checkbox-unChecked` )
+        ).toEqual(true);  
+    });
+
+    it('unRelated + onSelect', () => {
+        const spyOnSelect = sinon.spy(() => { });
+        const tree = getTree({
+            defaultExpandAll: true,
+            onSelect: spyOnSelect,
+            checkRelation: 'unRelated',
+        });
+        const nodelevel2 = tree.find(`.${BASE_CLASS_PREFIX}-tree-option.${BASE_CLASS_PREFIX}-tree-option-level-2`);
+        const selectedNode = nodelevel2.at(0);
+        selectedNode.simulate('click');
+        expect(spyOnSelect.calledOnce).toBe(true);
+        // onSelect first args is key, not value
+        expect(spyOnSelect.calledWithMatch('zhongguo')).toEqual(true);
+    });
+
     it('controlled: leaf values show correct', () => {
     it('controlled: leaf values show correct', () => {
         let tree = getTree({
         let tree = getTree({
             value: 'Beijing'
             value: 'Beijing'

+ 169 - 0
packages/semi-ui/tree/_story/tree.stories.js

@@ -2170,3 +2170,172 @@ export const RenderFullLabelWithDraggable = () => {
 RenderFullLabelWithDraggable.story = {
 RenderFullLabelWithDraggable.story = {
   name: 'renderFullLabel with draggable',
   name: 'renderFullLabel with draggable',
 };
 };
+
+export const CheckRelationDemo = () => {
+  const treeData = [
+    {
+        label: 'Asia',
+        value: 'Asia',
+        key: '0',
+        children: [
+            {
+                label: 'China',
+                value: 'China',
+                key: '0-0',
+                children: [
+                    {
+                        label: 'Beijing',
+                        value: 'Beijing',
+                        key: '0-0-0',
+                    },
+                    {
+                        label: 'Shanghai',
+                        value: 'Shanghai',
+                        key: '0-0-1',
+                    },
+                    {
+                        label: 'Chengdu',
+                        value: 'Chengdu',
+                        key: '0-0-2',
+                    },
+                ],
+            },
+            {
+                label: 'Japan',
+                value: 'Japan',
+                key: '0-1',
+                children: [
+                    {
+                        label: 'Osaka',
+                        value: 'Osaka',
+                        key: '0-1-0'
+                    }
+                ]
+            },
+        ],
+    },
+    {
+        label: 'North America',
+        value: 'North America',
+        key: '1',
+        children: [
+            {
+                label: 'United States',
+                value: 'United States',
+                key: '1-0'
+            },
+            {
+                label: 'Canada',
+                value: 'Canada',
+                key: '1-1'
+            }
+        ]
+    }
+  ];
+  const [value, setValue] = useState('China');
+  const [value2, setValue2] = useState();
+  const [value3, setValue3] = useState();
+  const style = {
+    width: 260,
+    height: 420,
+    border: '1px solid var(--semi-color-border)'
+  };
+  const handleChange = value => {
+    console.log(value);
+    setValue(value);
+  };
+  const handleChange2 = value => {
+    console.log(value);
+    setValue2(value);
+  };
+  const handleChange3 = value => {
+    console.log(value);
+    setValue3(value);
+  };
+  return (
+    <>
+      <div>checkRelation='unRelated'</div>
+      <Tree
+        treeData={treeData}
+        multiple
+        checkRelation='unRelated'
+        defaultExpandAll
+        style={style}
+      />
+      <br /><br />
+      <div>checkRelation='unRelated' + 中国节点为 disabled</div>
+      <Tree
+        treeData={treeData1}
+        multiple
+        checkRelation='unRelated'
+        defaultExpandAll
+        style={style}
+      />
+      <br /><br />
+      <div>checkRelation='unRelated' + 中国节点为 disabled + 严格禁用</div>
+      <Tree
+        treeData={treeData1}
+        multiple
+        checkRelation='unRelated'
+        defaultExpandAll
+        disableStrictly
+        style={style}
+      />
+      <br /><br />
+      <div>checkRelation='unRelated' + defaultValue 为 China</div>
+      <Tree
+        treeData={treeData}
+        multiple
+        checkRelation='unRelated'
+        defaultExpandAll
+        style={style}
+        defaultValue='China'
+      />
+      <br /><br />
+      <div>checkRelation='unRelated' + 受控 + value 初始为 China</div>
+      <Tree
+        treeData={treeData}
+        multiple
+        checkRelation='unRelated'
+        defaultExpandAll
+        style={style}
+        value={value}
+        onChange={handleChange}
+      />
+      <br /><br />
+      <div>checkRelation='unRelated' + 受控 + onChangeWithObject</div>
+      <Tree
+        treeData={treeData}
+        multiple
+        checkRelation='unRelated'
+        defaultExpandAll
+        style={style}
+        value={value2}
+        onChangeWithObject
+        onChange={handleChange2}
+      />
+      <br /><br />
+      <div>checkRelation='unRelated' + 受控 + leafOnly,此时 leafOnly 失效</div>
+      <Tree
+        leafOnly
+        treeData={treeData}
+        multiple
+        checkRelation='unRelated'
+        defaultExpandAll
+        style={style}
+        value={value3}
+        onChange={handleChange3}
+      />
+      <br /><br />
+      <div>checkRelation='unRelated' + onSelect </div>
+      <Tree
+        treeData={treeData}
+        multiple
+        checkRelation='unRelated'
+        defaultExpandAll
+        style={style}
+        onSelect={(value,status,node)=>console.log('select', value, status, node)}
+      />
+    </>
+  );
+};

+ 12 - 5
packages/semi-ui/tree/index.tsx

@@ -112,6 +112,7 @@ class Tree extends BaseComponent<TreeProps, TreeState> {
         onDragStart: PropTypes.func,
         onDragStart: PropTypes.func,
         onDrop: PropTypes.func,
         onDrop: PropTypes.func,
         labelEllipsis: PropTypes.bool,
         labelEllipsis: PropTypes.bool,
+        checkRelation: PropTypes.string,
         'aria-label': PropTypes.string,
         'aria-label': PropTypes.string,
     };
     };
 
 
@@ -133,6 +134,7 @@ class Tree extends BaseComponent<TreeProps, TreeState> {
         disableStrictly: false,
         disableStrictly: false,
         draggable: false,
         draggable: false,
         autoExpandWhenDragEnter: true,
         autoExpandWhenDragEnter: true,
+        checkRelation: 'related',
     };
     };
 
 
     static TreeNode: typeof TreeNode;
     static TreeNode: typeof TreeNode;
@@ -152,6 +154,7 @@ class Tree extends BaseComponent<TreeProps, TreeState> {
             selectedKeys: [],
             selectedKeys: [],
             checkedKeys: new Set(),
             checkedKeys: new Set(),
             halfCheckedKeys: new Set(),
             halfCheckedKeys: new Set(),
+            realCheckedKeys: new Set([]),
             motionKeys: new Set([]),
             motionKeys: new Set([]),
             motionType: 'hide',
             motionType: 'hide',
             expandedKeys: new Set(props.expandedKeys),
             expandedKeys: new Set(props.expandedKeys),
@@ -409,10 +412,14 @@ class Tree extends BaseComponent<TreeProps, TreeState> {
             }
             }
 
 
             if (checkedKeyValues) {
             if (checkedKeyValues) {
-                const { checkedKeys, halfCheckedKeys } = calcCheckedKeys(checkedKeyValues, keyEntities);
+                if (props.checkRelation === 'unRelated') {
+                    newState.realCheckedKeys = new Set(checkedKeyValues);
+                } else if (props.checkRelation === 'related') {
+                    const { checkedKeys, halfCheckedKeys } = calcCheckedKeys(checkedKeyValues, keyEntities);
 
 
-                newState.checkedKeys = checkedKeys;
-                newState.halfCheckedKeys = halfCheckedKeys;
+                    newState.checkedKeys = checkedKeys;
+                    newState.halfCheckedKeys = halfCheckedKeys;
+                }
             }
             }
         }
         }
 
 
@@ -422,7 +429,7 @@ class Tree extends BaseComponent<TreeProps, TreeState> {
         }
         }
 
 
         // update disableStrictly
         // update disableStrictly
-        if (treeData && props.disableStrictly) {
+        if (treeData && props.disableStrictly && props.checkRelation === 'related') {
             newState.disabledKeys = calcDisabledKeys(keyEntities);
             newState.disabledKeys = calcDisabledKeys(keyEntities);
         }
         }
 
 
@@ -706,7 +713,7 @@ class Tree extends BaseComponent<TreeProps, TreeState> {
         const ariaAttr = {
         const ariaAttr = {
             role: noData ? 'none' : 'tree'
             role: noData ? 'none' : 'tree'
         };
         };
-        if (ariaAttr.role === 'tree'){
+        if (ariaAttr.role === 'tree') {
             ariaAttr['aria-multiselectable'] = multiple ? true : false;
             ariaAttr['aria-multiselectable'] = multiple ? true : false;
         }
         }
         return (
         return (