diff --git a/src/tree/_example/draggable.vue b/src/tree/_example/draggable.vue index 763d6c7d9..c01b35e27 100644 --- a/src/tree/_example/draggable.vue +++ b/src/tree/_example/draggable.vue @@ -7,6 +7,7 @@ transition expand-all draggable + :allow-drop="handleAllowDrop" @drag-start="handleDragStart" @drag-end="handleDragEnd" @drag-over="handleDragOver" @@ -71,7 +72,7 @@ export default { }, { value: '2.2', - label: '2.2', + label: '2.2 不允许拖放为 2.2 的子节点', }, ], }, @@ -94,6 +95,12 @@ export default { handleDrop(ctx) { console.log('handleDrop', ctx); }, + handleAllowDrop(ctx) { + const { dropNode, dropPosition } = ctx; + if (dropNode.value === '2.2' && dropPosition === 0) { + return false; + } + }, }, }; diff --git a/src/tree/hooks/useDragHandle.ts b/src/tree/hooks/useDragHandle.ts index 11a501bdd..90388cc8d 100644 --- a/src/tree/hooks/useDragHandle.ts +++ b/src/tree/hooks/useDragHandle.ts @@ -2,6 +2,7 @@ import { TreeNode } from '../adapt'; import { TreeProps, TypDragEventState, TypeTreeState, TypeDragHandle, } from '../tree-types'; +import { DragPosition } from './useDraggable'; import { emitEvent } from '../util'; export default function useDragHandle(state: TypeTreeState) { @@ -54,12 +55,21 @@ export default function useDragHandle(state: TypeTreeState) { const { dragEvent, node, dropPosition } = state; if (node.value === dragNode.value || node.getParents().some((_node) => _node.value === dragNode.value)) return; + const ctx = { + dropNode: node.getModel(), + dragNode: dragNode.getModel(), + dropPosition, + e: dragEvent, + }; + + if (props.allowDrop(ctx) === false) return; + const nodes = store.getNodes() as TreeNode[]; nodes.some((_node) => { if (_node.value === node.value) { - if (dropPosition === 0) { + if (dropPosition === DragPosition.Inside) { dragNode.appendTo(store, _node); - } else if (dropPosition < 0) { + } else if (dropPosition === DragPosition.Before) { node.insertBefore(dragNode); } else { node.insertAfter(dragNode); @@ -68,12 +78,7 @@ export default function useDragHandle(state: TypeTreeState) { } return false; }); - const ctx = { - dropNode: node.getModel(), - dragNode: dragNode.getModel(), - dropPosition, - e: dragEvent, - }; + emitEvent>(props, context, 'drop', ctx); }; diff --git a/src/tree/hooks/useDraggable.ts b/src/tree/hooks/useDraggable.ts index 1ff09deb7..76c51ed5a 100644 --- a/src/tree/hooks/useDraggable.ts +++ b/src/tree/hooks/useDraggable.ts @@ -8,6 +8,12 @@ export interface TypeDragStates { dropPosition: number; } +export enum DragPosition { + Before = -1, + Inside = 0, + After = 1, +} + type TypeDrag = 'dragStart' | 'dragOver' | 'dragLeave' | 'dragEnd' | 'drop'; export default function useDraggable(state: TypeTreeItemState) { @@ -15,7 +21,7 @@ export default function useDraggable(state: TypeTreeItemState) { const dragStates = reactive({ isDragOver: false, isDragging: false, - dropPosition: 0, + dropPosition: DragPosition.Inside, }); const updateDropPosition = (dragEvent: DragEvent) => { @@ -29,11 +35,11 @@ export default function useDraggable(state: TypeTreeItemState) { const diff = pageY - offsetY; if (diff < gapHeight) { - dragStates.dropPosition = -1; + dragStates.dropPosition = DragPosition.Before; } else if (diff < rect.height - gapHeight) { - dragStates.dropPosition = 0; + dragStates.dropPosition = DragPosition.Inside; } else { - dragStates.dropPosition = 1; + dragStates.dropPosition = DragPosition.After; } }; @@ -45,13 +51,13 @@ export default function useDraggable(state: TypeTreeItemState) { switch (status) { case 'dragStart': dragStates.isDragging = true; - dragStates.dropPosition = 0; + dragStates.dropPosition = DragPosition.Inside; drag.handleDragStart?.({ node, dragEvent }); break; case 'dragEnd': dragStates.isDragging = false; dragStates.isDragOver = false; - dragStates.dropPosition = 0; + dragStates.dropPosition = DragPosition.Inside; throttleUpdateDropPosition.cancel(); drag.handleDragEnd?.({ node, dragEvent }); break; @@ -62,7 +68,7 @@ export default function useDraggable(state: TypeTreeItemState) { break; case 'dragLeave': dragStates.isDragOver = false; - dragStates.dropPosition = 0; + dragStates.dropPosition = DragPosition.Inside; throttleUpdateDropPosition.cancel(); drag.handleDragLeave?.({ node, dragEvent }); break; diff --git a/src/tree/props.ts b/src/tree/props.ts index b1029fc6d..6498e713a 100644 --- a/src/tree/props.ts +++ b/src/tree/props.ts @@ -21,6 +21,10 @@ export default { defaultActived: { type: Array as PropType, }, + /** 判断节点是否可以执行 drop 操作,泛型 `T` 表示树节点 TS 类型 */ + allowDrop: { + type: Function as PropType, + }, /** 是否允许在过滤时节点折叠节点 */ allowFoldNodeOnFilter: Boolean, /** 透传属性到 checkbox 组件。参考 checkbox 组件 API */ diff --git a/src/tree/tree.en-US.md b/src/tree/tree.en-US.md index 80e87f811..7715218c0 100644 --- a/src/tree/tree.en-US.md +++ b/src/tree/tree.en-US.md @@ -1,6 +1,7 @@ :: BASE_DOC :: ## API + ### Tree Props name | type | default | description | required @@ -8,6 +9,7 @@ name | type | default | description | required activable | Boolean | false | make nodes can be highlight | N activeMultiple | Boolean | false | \- | N actived | Array | - | `.sync` is supported。Typescript:`Array` | N +allowDrop | Function | - | Determine whether the node can execute the drop operation。Typescript:`(context: { e: DragEvent; dragNode: TreeNodeModel; dropNode: TreeNodeModel; dropPosition: number; }) => boolean` | N allowFoldNodeOnFilter | Boolean | false | \- | N checkProps | Object | - | Typescript:`CheckboxProps`,[Checkbox API Documents](./checkbox?tab=api)。[see more ts definition](https://github.com/Tencent/tdesign-vue/tree/develop/src/tree/type.ts) | N checkStrictly | Boolean | false | \- | N @@ -81,6 +83,7 @@ getPath | `(value: TreeNodeValue)` | `TreeNodeModel[]` | required getTreeData | `(value?: TreeNodeValue)` | `Array` | required。get tree struct data insertAfter | `(value: TreeNodeValue, newData: T)` | \- | required insertBefore | `(value: TreeNodeValue, newData: T)` | \- | required +refresh | \- | \- | required。refresh tree state, used in tree search remove | `(value: TreeNodeValue)` | \- | required scrollTo | `(scrollToParams: ScrollToElementParams)` | \- | support scrolling to a specific node when virtual scrolling setItem | `(value: TreeNodeValue, options: TreeNodeState)` | \- | required diff --git a/src/tree/tree.md b/src/tree/tree.md index a54645c45..7a5a665ea 100644 --- a/src/tree/tree.md +++ b/src/tree/tree.md @@ -1,13 +1,15 @@ :: BASE_DOC :: ## API + ### Tree Props -名称 | 类型 | 默认值 | 说明 | 必传 +名称 | 类型 | 默认值 | 描述 | 必传 -- | -- | -- | -- | -- activable | Boolean | false | 节点是否可高亮 | N activeMultiple | Boolean | false | 是否允许多个节点同时高亮 | N actived | Array | - | 高亮的节点值。支持语法糖 `.sync`。TS 类型:`Array` | N +allowDrop | Function | - | 判断节点是否可以执行 drop 操作,泛型 `T` 表示树节点 TS 类型。TS 类型:`(context: { e: DragEvent; dragNode: TreeNodeModel; dropNode: TreeNodeModel; dropPosition: number; }) => boolean` | N allowFoldNodeOnFilter | Boolean | false | 是否允许在过滤时节点折叠节点 | N checkProps | Object | - | 透传属性到 checkbox 组件。参考 checkbox 组件 API。TS 类型:`CheckboxProps`,[Checkbox API Documents](./checkbox?tab=api)。[详细类型定义](https://github.com/Tencent/tdesign-vue/tree/develop/src/tree/type.ts) | N checkStrictly | Boolean | false | 父子节点选中状态不再关联,可各自选中或取消 | N @@ -81,13 +83,14 @@ getPath | `(value: TreeNodeValue)` | `TreeNodeModel[]` | 必需。自下而 getTreeData | `(value?: TreeNodeValue)` | `Array` | 必需。获取某节点的全部树形结构;参数为空,则表示获取整棵树的结构数据,泛型 `T` 表示树节点 TS 类型 insertAfter | `(value: TreeNodeValue, newData: T)` | \- | 必需。插入新节点到指定节点后面,泛型 `T` 表示树节点 TS 类型 insertBefore | `(value: TreeNodeValue, newData: T)` | \- | 必需。插入新节点到指定节点前面,泛型 `T` 表示树节点 TS 类型 +refresh | \- | \- | 必需。刷新树节点状态,可用于搜索场景刷新 remove | `(value: TreeNodeValue)` | \- | 必需。移除指定节点 scrollTo | `(scrollToParams: ScrollToElementParams)` | \- | 虚拟滚动场景下 支持指定滚动到具体的节点 setItem | `(value: TreeNodeValue, options: TreeNodeState)` | \- | 必需。设置节点状态 ### TreeNodeState -名称 | 类型 | 默认值 | 说明 | 必传 +名称 | 类型 | 默认值 | 描述 | 必传 -- | -- | -- | -- | -- activable | Boolean | false | 节点是否允许被激活 | N actived | Boolean | false | 节点是否被激活 | N @@ -105,7 +108,7 @@ visible | Boolean | false | 节点是否可视 | N ### TreeNodeModel -名称 | 类型 | 默认值 | 说明 | 必传 +名称 | 类型 | 默认值 | 描述 | 必传 -- | -- | -- | -- | -- actived | Boolean | - | 必需。当前节点是否处于高亮激活态 | Y checked | Boolean | - | 必需。当前节点是否被选中 | Y @@ -138,7 +141,7 @@ setData | `(data: T)` | \- | 必需。设置节点数据,数据变化可自动 ### TScroll -名称 | 类型 | 默认值 | 说明 | 必传 +名称 | 类型 | 默认值 | 描述 | 必传 -- | -- | -- | -- | -- bufferSize | Number | 20 | 表示除可视区域外,额外渲染的行数,避免快速滚动过程中,新出现的内容来不及渲染从而出现空白 | N isFixedRowHeight | Boolean | false | 表示每行内容是否同一个固定高度,仅在 `scroll.type` 为 `virtual` 时有效,该属性设置为 `true` 时,可用于简化虚拟滚动内部计算逻辑,提升性能,此时则需要明确指定 `scroll.rowHeight` 属性的值 | N diff --git a/src/tree/type.ts b/src/tree/type.ts index e0de332c8..0908b4980 100644 --- a/src/tree/type.ts +++ b/src/tree/type.ts @@ -26,6 +26,15 @@ export interface TdTreeProps { * 高亮的节点值,非受控属性 */ defaultActived?: Array; + /** + * 判断节点是否可以执行 drop 操作,泛型 `T` 表示树节点 TS 类型 + */ + allowDrop?: (context: { + e: DragEvent; + dragNode: TreeNodeModel; + dropNode: TreeNodeModel; + dropPosition: number; + }) => boolean; /** * 是否允许在过滤时节点折叠节点 * @default false @@ -277,6 +286,10 @@ export interface TreeInstanceFunctions void; + /** + * 刷新树节点状态,可用于搜索场景刷新 + */ + refresh: () => void; /** * 移除指定节点 */ diff --git a/test/snap/__snapshots__/csr.test.js.snap b/test/snap/__snapshots__/csr.test.js.snap index df8be2179..c983b8fac 100644 --- a/test/snap/__snapshots__/csr.test.js.snap +++ b/test/snap/__snapshots__/csr.test.js.snap @@ -121779,12 +121779,12 @@ exports[`csr snapshot test > csr test ./src/tree/_example/draggable.vue 1`] = ` /> - 2.2 + 2.2 不允许拖放为 2.2 的子节点 diff --git a/test/snap/__snapshots__/ssr.test.js.snap b/test/snap/__snapshots__/ssr.test.js.snap index 1f3028364..232903a6c 100644 --- a/test/snap/__snapshots__/ssr.test.js.snap +++ b/test/snap/__snapshots__/ssr.test.js.snap @@ -1237,7 +1237,7 @@ exports[`ssr snapshot test > renders ./src/tree/_example/debug-vscroll.vue corre exports[`ssr snapshot test > renders ./src/tree/_example/disabled.vue correctly 1`] = `"
是否禁用整个 tree:
可选:
可激活:
"`; -exports[`ssr snapshot test > renders ./src/tree/_example/draggable.vue correctly 1`] = `"
1
1.1
1.1.1
1.1.1.1
1.1.1.2
1.1.2
1.1.2.1
1.1.2.2
2
2.1
2.2
"`; +exports[`ssr snapshot test > renders ./src/tree/_example/draggable.vue correctly 1`] = `"
1
1.1
1.1.1
1.1.1.1
1.1.1.2
1.1.2
1.1.2.1
1.1.2.2
2
2.1
2.2 不允许拖放为 2.2 的子节点
"`; exports[`ssr snapshot test > renders ./src/tree/_example/empty.vue correctly 1`] = `"

默认为空状态

暂无数据

设置 empty 属性为指定字符串

😊 空数据(string)

jsx 形式

😊 空数据( empty props )

slot 形式

😊 空数据(slot)
"`;