| 
10 | 10 |  <div :class="blockAreaCls">  | 
11 | 11 |  <div :style="{ height: `${topSpaceHeight}px` }"></div>  | 
12 | 12 |  <VTreeNode  | 
13 |  | - v-for="node in renderNodes"  | 
 | 13 | + v-for="node in (expandAnimation.ready.value ? expandAnimation.topNodes.value : renderNodes)"  | 
14 | 14 |  v-bind="treeNodeProps"  | 
15 | 15 |  :key="node[keyField]"  | 
16 | 16 |  :data="node"  | 
17 | 17 |  :getNode="getNode"  | 
18 | 18 |  :noSiblingNodeMap="noSiblingNodeMap"  | 
19 | 19 |  v-on="treeNodeListeners"  | 
20 |  | - :class="  | 
21 |  | - typeof nodeClassName === 'function'  | 
22 |  | - ? nodeClassName(node)  | 
23 |  | - : nodeClassName  | 
24 |  | - "  | 
 | 20 | + :class="getNodeClassName(node)"  | 
25 | 21 |  :style="{  | 
26 | 22 |  minHeight: `${nodeMinHeight}px`,  | 
27 | 23 |  }"  | 
 | 
30 | 26 |  @expand="handleNodeExpand"  | 
31 | 27 |  @node-drop="handleNodeDrop"  | 
32 | 28 |  />  | 
 | 29 | + <template v-if="expandAnimation.ready.value">  | 
 | 30 | + <Transition  | 
 | 31 | + name="vtree-expand-animation"  | 
 | 32 | + @after-enter="expandAnimation.onExpandAnimationFinish"  | 
 | 33 | + @after-leave="expandAnimation.onExpandAnimationFinish"  | 
 | 34 | + >  | 
 | 35 | + <div  | 
 | 36 | + v-show="expandAnimation.currentExpandState.value"  | 
 | 37 | + :style="{  | 
 | 38 | + display: 'grid',  | 
 | 39 | + }"  | 
 | 40 | + >  | 
 | 41 | + <div :style="{ overflow: 'hidden' }">  | 
 | 42 | + <VTreeNode  | 
 | 43 | + v-for="node in expandAnimation.middleNodes.value"  | 
 | 44 | + v-bind="treeNodeProps"  | 
 | 45 | + :key="node[keyField]"  | 
 | 46 | + :data="node"  | 
 | 47 | + :getNode="getNode"  | 
 | 48 | + :noSiblingNodeMap="noSiblingNodeMap"  | 
 | 49 | + :class="getNodeClassName(node)"  | 
 | 50 | + :style="{  | 
 | 51 | + minHeight: `${nodeMinHeight}px`,  | 
 | 52 | + }"  | 
 | 53 | + @check="handleNodeCheck"  | 
 | 54 | + @select="handleNodeSelect"  | 
 | 55 | + @expand="handleNodeExpand"  | 
 | 56 | + @node-drop="handleNodeDrop"  | 
 | 57 | + />  | 
 | 58 | + </div>  | 
 | 59 | + </div>  | 
 | 60 | + </Transition>  | 
 | 61 | + <VTreeNode  | 
 | 62 | + v-for="node in expandAnimation.bottomNodes.value"  | 
 | 63 | + v-bind="treeNodeProps"  | 
 | 64 | + :key="node[keyField]"  | 
 | 65 | + :data="node"  | 
 | 66 | + :getNode="getNode"  | 
 | 67 | + :noSiblingNodeMap="noSiblingNodeMap"  | 
 | 68 | + :class="getNodeClassName(node)"  | 
 | 69 | + :style="{  | 
 | 70 | + minHeight: `${nodeMinHeight}px`,  | 
 | 71 | + }"  | 
 | 72 | + @check="handleNodeCheck"  | 
 | 73 | + @select="handleNodeSelect"  | 
 | 74 | + @expand="handleNodeExpand"  | 
 | 75 | + @node-drop="handleNodeDrop"  | 
 | 76 | + />  | 
 | 77 | + </template>  | 
33 | 78 |  <div :style="{ height: `${bottomSpaceHeight}px` }"></div>  | 
34 | 79 |  </div>  | 
35 | 80 |  </div>  | 
@@ -153,6 +198,9 @@ export interface TreeProps {  | 
153 | 198 |  /** 连接线 */  | 
154 | 199 |  showLine?: boolean | ShowLine  | 
155 | 200 | 
  | 
 | 201 | + /** 是否启用过渡动画,目前仅控制展开收起 */  | 
 | 202 | + animation?: boolean  | 
 | 203 | +
  | 
156 | 204 |  /** 子节点缩进 */  | 
157 | 205 |  nodeIndent?: number,  | 
158 | 206 | 
  | 
@@ -225,6 +273,7 @@ import { useIframeResize } from '../hooks/useIframeResize'  | 
225 | 273 | import { usePublicTreeAPI } from '../hooks/usePublicTreeAPI'  | 
226 | 274 | import { FilterFunctionType } from '../store/tree-store'  | 
227 | 275 | import { pickReadonly } from '../utils'  | 
 | 276 | +import { useExpandAnimation } from '../hooks/useExpandAnimation'  | 
228 | 277 | 
  | 
229 | 278 | const props = withDefaults(defineProps<TreeProps>(), DEFAULT_TREE_PROPS)  | 
230 | 279 | 
  | 
@@ -306,6 +355,8 @@ const {  | 
306 | 355 |  scrollTo,  | 
307 | 356 | } = useVirtualList(nonReactive, props)  | 
308 | 357 | 
  | 
 | 358 | +const expandAnimation = useExpandAnimation(renderNodes, props)  | 
 | 359 | +
  | 
309 | 360 | const {  | 
310 | 361 |  unloadCheckedNodes,  | 
311 | 362 |  isRootLoading,  | 
@@ -347,6 +398,10 @@ const {  | 
347 | 398 |  updateRender,  | 
348 | 399 | })  | 
349 | 400 | 
  | 
 | 401 | +const getNodeClassName = (node: TreeNode) => {  | 
 | 402 | + return typeof props.nodeClassName === 'function' ? props.nodeClassName(node) : props.nodeClassName  | 
 | 403 | +}  | 
 | 404 | +
  | 
350 | 405 | const noSiblingNodeMap = computed(() => {  | 
351 | 406 |  const parentsOfFirstNode: TreeNode[] = []  | 
352 | 407 |  let nodeParent = renderNodes.value[0]?._parent  | 
@@ -421,6 +476,7 @@ const handleNodeSelect = (node: TreeNode): void => {  | 
421 | 476 |  nonReactive.store.setSelected(node[props.keyField], !node.selected)  | 
422 | 477 | }  | 
423 | 478 | const handleNodeExpand = (node: TreeNode): void => {  | 
 | 479 | + expandAnimation.updateBeforeExpand(node)  | 
424 | 480 |  nonReactive.store.setExpand(node[props.keyField], !node.expand)  | 
425 | 481 | }  | 
426 | 482 | const handleNodeDrop = (  | 
@@ -553,6 +609,7 @@ const emitSelectableInput = (  | 
553 | 609 | }  | 
554 | 610 | 
  | 
555 | 611 | onMounted(() => {  | 
 | 612 | + nonReactive.store.on('expand', expandAnimation.updateAfterExpand)  | 
556 | 613 |  nonReactive.store.on('visible-data-change', updateBlockNodes)  | 
557 | 614 |  nonReactive.store.on('render-data-change', updateRender)  | 
558 | 615 |  nonReactive.store.on(  | 
 | 
0 commit comments