From eca5d3aa57096005e0d720f66ca88c2e6aaf20ec Mon Sep 17 00:00:00 2001 From: Feynman Date: Tue, 8 Sep 2026 00:31:50 +0800 Subject: [PATCH] fix(TAP-12860): optimize source tree and sync virtual tree --- .../src/virtual-tree/VirtualTree.vue | 83 +- .../src/virtual-tree/VirtualTreeNode.vue | 127 +- .../src/virtual-tree/composables/useCheck.ts | 148 +-- .../virtual-tree/composables/useDragNode.ts | 235 ++-- .../src/virtual-tree/composables/useFilter.ts | 76 +- .../src/virtual-tree/composables/useTree.ts | 269 ++-- packages/component/src/virtual-tree/types.ts | 159 +-- packages/ldp/src/Dashboard.vue | 7 +- packages/ldp/src/Source.vue | 1095 +++++++++-------- packages/ldp/src/Target.vue | 16 +- 10 files changed, 1057 insertions(+), 1158 deletions(-) diff --git a/packages/component/src/virtual-tree/VirtualTree.vue b/packages/component/src/virtual-tree/VirtualTree.vue index d02e38ac39..7ba61ea869 100644 --- a/packages/component/src/virtual-tree/VirtualTree.vue +++ b/packages/component/src/virtual-tree/VirtualTree.vue @@ -11,11 +11,7 @@ import { TreeOptionsEnum, treeProps, } from 'element-plus/es/components/tree-v2/src/virtual-tree' -import { - definePropType, - mutable, - type Nullable, -} from 'element-plus/es/utils/index.mjs' +import { definePropType, mutable } from 'element-plus/es/utils/index.mjs' import { computed, getCurrentInstance, provide, ref, useSlots } from 'vue' import { useDragNodeHandler } from './composables/useDragNode' import { useTree } from './composables/useTree' @@ -36,10 +32,10 @@ const props = defineProps({ mutable({ children: TreeOptionsEnum.CHILDREN, label: TreeOptionsEnum.LABEL, - disabled: TreeOptionsEnum.DISABLED, value: TreeOptionsEnum.KEY, - isLeaf: undefined, - } as const), + disabled: TreeOptionsEnum.DISABLED, + class: TreeOptionsEnum.CLASS, + }), }, draggable: { type: Boolean, @@ -49,50 +45,58 @@ const props = defineProps({ allowDrop: Function, }) -// const props = defineProps(treeProps) - -const emit = defineEmits(treeEmits) - +const emit = defineEmits({ + ...treeEmits, + 'node-drag-start': (node: any, event: DragEvent) => Boolean(node && event), + 'node-drag-enter': (draggingNode: any, dropNode: any, event: DragEvent) => + Boolean(draggingNode && dropNode && event), + 'node-drag-leave': (draggingNode: any, dropNode: any, event: DragEvent) => + Boolean(draggingNode && dropNode && event), + 'node-drag-over': (draggingNode: any, dropNode: any, event: DragEvent) => + Boolean(draggingNode && dropNode && event), + 'node-drag-end': ( + draggingNode: any, + _dropNode: any, + _dropType: any, + event: DragEvent, + ) => Boolean(draggingNode && event), + 'node-drop': ( + draggingNode: any, + _dropNode: any, + _dropType: any, + event: DragEvent, + ) => Boolean(draggingNode && _dropNode && _dropType && event), +}) const slots = useSlots() - const treeNodeSize = computed(() => props.itemSize) - -const el$ = ref>(null) -const dropIndicator$ = ref>(null) -const nodeRef = ref>(null) +const el$ = ref(null) +const dropIndicator$ = ref(null) provide(ROOT_TREE_INJECTION_KEY, { ctx: { - emit, + emit: emit as any, slots, }, props, instance: getCurrentInstance()!, }) -provide('RootTree', { - ctx: { - emit, - slots, - }, - props, - instance: getCurrentInstance(), -} as any) provide(formItemContextKey, undefined) + const { t } = useLocale() const ns = useNamespace('tree') const { flattenTree, isNotEmpty, + listRef, toggleExpand, - isExpanded, isIndeterminate, isChecked, isDisabled, isCurrent, isForceHiddenExpandIcon, handleNodeClick, + handleNodeDrop, handleNodeCheck, - // expose toggleCheckbox, getCurrentNode, getCurrentKey, @@ -109,7 +113,9 @@ const { expandNode, collapseNode, setExpandedKeys, -} = useTree(props, emit) + scrollToNode, + scrollTo, +} = useTree(props, emit as any) defineExpose({ toggleCheckbox, @@ -128,12 +134,14 @@ defineExpose({ expandNode, collapseNode, setExpandedKeys, + scrollToNode, + scrollTo, }) const { dragState } = useDragNodeHandler({ props, ctx: { - emit, + emit: emit as any, slots, }, el$, @@ -149,20 +157,21 @@ const { dragState } = useDragNodeHandler({ >
- {{ - emptyText ?? t('el.tree.emptyText') - }} + + {{ + emptyText ?? t('el.tree.emptyText') + }} +
-
-import { CaretRight, Loading } from '@element-plus/icons-vue' +import { CaretRight } from '@element-plus/icons-vue' import { ElCheckbox, ElIcon, @@ -12,9 +12,8 @@ import { treeNodeEmits, treeNodeProps, } from 'element-plus/es/components/tree-v2/src/virtual-tree' -// import ElNodeContent from 'element-plus/es/components/tree-v2/src/tree-node-content' -import ElNodeContent from 'element-plus/es/components/tree/src/tree-node-content.mjs' -import { computed, getCurrentInstance, inject, provide, ref } from 'vue' +import ElNodeContent from 'element-plus/es/components/tree-v2/src/tree-node-content.mjs' +import { computed, inject, ref } from 'vue' import { dragEventsKey } from './composables/useDragNode' defineOptions({ @@ -23,70 +22,96 @@ defineOptions({ const props = defineProps(treeNodeProps) const emit = defineEmits(treeNodeEmits) - const tree = inject(ROOT_TREE_INJECTION_KEY) const ns = useNamespace('tree') -const instance = getCurrentInstance() -const node$ = ref>(null) +const node$ = ref(null) +const dragEvents = inject(dragEventsKey) -provide('NodeInstance', instance) +const indent = computed(() => tree?.props.indent ?? 16) +const icon = computed(() => tree?.props.icon ?? CaretRight) +const draggable = computed(() => Boolean((tree?.props as any)?.draggable)) -const loading = ref(false) +const getNodeClass = (node: any) => { + const nodeClassFunc = (tree?.props as any)?.props?.class + if (!nodeClassFunc) return {} -const indent = computed(() => { - return tree?.props.indent ?? 16 -}) + const className = + typeof nodeClassFunc === 'function' + ? nodeClassFunc(node.data, node) + : nodeClassFunc + return typeof className === 'string' ? { [className]: true } : className +} -const icon = computed(() => { - return tree?.props.icon ?? CaretRight -}) +const handleClick = (event: MouseEvent) => { + emit('click', props.node, event) +} -const handleClick = (e: MouseEvent) => { - emit('click', props.node, e) +const handleDrop = (event: DragEvent) => { + emit('drop', props.node, event) + dragEvents?.treeNodeDrop({ + event, + treeNode: { + node: props.node, + $el: node$.value, + }, + }) } + const handleExpandIconClick = () => { - emit('toggle', props.node, instance) + emit('toggle', props.node) } + const handleCheckChange = (value: CheckboxValueType) => { emit('check', props.node, value) } + const handleContextMenu = (event: Event) => { - if (tree?.instance?.vnode?.props?.onNodeContextmenu) { + if (tree?.instance?.vnode?.props?.['onNodeContextmenu']) { event.stopPropagation() event.preventDefault() } tree?.ctx.emit(NODE_CONTEXTMENU, event, props.node?.data, props.node) } -const dragEvents = inject(dragEventsKey) - const handleDragStart = (event: DragEvent) => { - console.log('handleDragStart', event) - if (!tree.props.draggable) return - dragEvents.treeNodeDragStart({ event, treeNode: props }) + if (!draggable.value) return + dragEvents?.treeNodeDragStart({ + event, + treeNode: { + node: props.node, + $el: node$.value, + }, + }) } const handleDragOver = (event: DragEvent) => { + if (!draggable.value) return event.preventDefault() - if (!tree.props.draggable) return - dragEvents.treeNodeDragOver({ + dragEvents?.treeNodeDragOver({ event, - treeNode: { $el: node$.value, node: props.node }, + treeNode: { + node: props.node, + $el: node$.value, + }, }) } -const handleDrop = (event: DragEvent) => { +const handleDragEnter = (event: DragEvent) => { + if (!draggable.value) return event.preventDefault() + dragEvents?.treeNodeDragOver({ + event, + treeNode: { + node: props.node, + $el: node$.value, + }, + }) } const handleDragEnd = (event: DragEvent) => { - if (!tree.props.draggable) return - dragEvents.treeNodeDragEnd(event) + if (!draggable.value) return + dragEvents?.treeNodeDragEnd(event) } - -defineExpose({ - loading, -}) - + diff --git a/packages/component/src/virtual-tree/composables/useCheck.ts b/packages/component/src/virtual-tree/composables/useCheck.ts index 2144d6dbf4..c299cd1aa8 100644 --- a/packages/component/src/virtual-tree/composables/useCheck.ts +++ b/packages/component/src/virtual-tree/composables/useCheck.ts @@ -1,12 +1,11 @@ -import { getCurrentInstance, nextTick, ref, watch } from 'vue' import { NODE_CHECK, NODE_CHECK_CHANGE, - SetOperationEnum, -} from '../virtual-tree' +} from 'element-plus/es/components/tree-v2/src/virtual-tree' +import { getCurrentInstance, nextTick, ref, watch, type Ref } from 'vue' + import type { Tree, TreeKey, TreeNode, TreeNodeData, TreeProps } from '../types' import type { CheckboxValueType } from 'element-plus' -import type { Ref } from 'vue' export function useCheck(props: TreeProps, tree: Ref) { const checkedKeys = ref>(new Set()) @@ -16,40 +15,41 @@ export function useCheck(props: TreeProps, tree: Ref) { watch( [() => tree.value, () => props.defaultCheckedKeys], () => { - return nextTick(() => { - _setCheckedKeys(props.defaultCheckedKeys) + nextTick(() => { + setCheckedKeysFromProps(props.defaultCheckedKeys || []) }) }, - { - immediate: true, - }, + { immediate: true }, ) - const updateCheckedKeys = () => { - if (!tree.value || !props.showCheckbox || props.checkStrictly) { + const updateCheckedKeys = (deep = false) => { + if (!tree.value || !props.showCheckbox || (props.checkStrictly && !deep)) { return } + const { levelTreeNodeMap, maxLevel } = tree.value const checkedKeySet = checkedKeys.value const indeterminateKeySet = new Set() - // It is easier to determine the indeterminate state by - // traversing from bottom to top - // leaf nodes not have indeterminate status and can be skipped - for (let level = maxLevel - 1; level >= 1; --level) { + + for (let level = maxLevel; level >= 1; --level) { const nodes = levelTreeNodeMap.get(level) if (!nodes) continue + nodes.forEach((node) => { + let isEffectivelyChecked = + !node.isLeaf || Boolean(node.disabled) || checkedKeySet.has(node.key) const children = node.children if (children) { - // Whether all child nodes are selected let allChecked = true - // Whether a child node is selected let hasChecked = false + for (const childNode of children) { - const key = childNode.key - if (checkedKeySet.has(key)) { + if (!childNode.isEffectivelyChecked) { + isEffectivelyChecked = false + } + if (checkedKeySet.has(childNode.key)) { hasChecked = true - } else if (indeterminateKeySet.has(key)) { + } else if (indeterminateKeySet.has(childNode.key)) { allChecked = false hasChecked = true break @@ -57,6 +57,7 @@ export function useCheck(props: TreeProps, tree: Ref) { allChecked = false } } + if (allChecked) { checkedKeySet.add(node.key) } else if (hasChecked) { @@ -67,13 +68,15 @@ export function useCheck(props: TreeProps, tree: Ref) { indeterminateKeySet.delete(node.key) } } + + node.isEffectivelyChecked = isEffectivelyChecked }) } + indeterminateKeys.value = indeterminateKeySet } const isChecked = (node: TreeNode) => checkedKeys.value.has(node.key) - const isIndeterminate = (node: TreeNode) => indeterminateKeys.value.has(node.key) @@ -81,26 +84,30 @@ export function useCheck(props: TreeProps, tree: Ref) { node: TreeNode, isChecked: CheckboxValueType, nodeClick = true, + immediateUpdate = true, + deep = false, ) => { const checkedKeySet = checkedKeys.value - const toggle = (node: TreeNode, checked: CheckboxValueType) => { - checkedKeySet[checked ? SetOperationEnum.ADD : SetOperationEnum.DELETE]( - node.key, - ) - const children = node.children - if (!props.checkStrictly && children) { - children.forEach((childNode) => { - if (!childNode.disabled) { + const children = node.children + if ((!props.checkStrictly || deep) && nodeClick && children?.length) { + isChecked = children.some((child) => !child.isEffectivelyChecked) + } + + const toggle = (currentNode: TreeNode, checked: CheckboxValueType) => { + if (checked) checkedKeySet.add(currentNode.key) + else checkedKeySet.delete(currentNode.key) + if ((!props.checkStrictly || deep) && currentNode.children) { + currentNode.children.forEach((childNode) => { + if (!childNode.disabled || childNode.children) { toggle(childNode, checked) } }) } } + toggle(node, isChecked) - updateCheckedKeys() - if (nodeClick) { - afterNodeCheck(node, isChecked) - } + if (immediateUpdate) updateCheckedKeys(deep) + if (nodeClick) afterNodeCheck(node, isChecked) } const afterNodeCheck = (node: TreeNode, checked: CheckboxValueType) => { @@ -115,7 +122,6 @@ export function useCheck(props: TreeProps, tree: Ref) { emit(NODE_CHECK_CHANGE, node.data, checked) } - // expose function getCheckedKeys(leafOnly = false): TreeKey[] { return getChecked(leafOnly).checkedKeys } @@ -132,77 +138,58 @@ export function useCheck(props: TreeProps, tree: Ref) { return getHalfChecked().halfCheckedNodes } - function getChecked(leafOnly = false): { - checkedKeys: TreeKey[] - checkedNodes: TreeNodeData[] - } { + function getChecked(leafOnly = false) { const checkedNodes: TreeNodeData[] = [] const keys: TreeKey[] = [] - if (tree?.value && props.showCheckbox) { - const { treeNodeMap } = tree.value - checkedKeys.value.forEach((key) => { - const node = treeNodeMap.get(key) - if (node && (!leafOnly || (leafOnly && node.isLeaf))) { - keys.push(key) + if (tree.value && props.showCheckbox) { + tree.value.treeNodeMap.forEach((node) => { + if (checkedKeys.value.has(node.key) && (!leafOnly || node.isLeaf)) { + keys.push(node.key) checkedNodes.push(node.data) } }) } - return { - checkedKeys: keys, - checkedNodes, - } + return { checkedKeys: keys, checkedNodes } } - function getHalfChecked(): { - halfCheckedKeys: TreeKey[] - halfCheckedNodes: TreeNodeData[] - } { + function getHalfChecked() { const halfCheckedNodes: TreeNodeData[] = [] const halfCheckedKeys: TreeKey[] = [] - if (tree?.value && props.showCheckbox) { - const { treeNodeMap } = tree.value - indeterminateKeys.value.forEach((key) => { - const node = treeNodeMap.get(key) - if (node) { - halfCheckedKeys.push(key) + if (tree.value && props.showCheckbox) { + tree.value.treeNodeMap.forEach((node) => { + if (indeterminateKeys.value.has(node.key)) { + halfCheckedKeys.push(node.key) halfCheckedNodes.push(node.data) } }) } - return { - halfCheckedNodes, - halfCheckedKeys, - } + return { halfCheckedKeys, halfCheckedNodes } } function setCheckedKeys(keys: TreeKey[]) { checkedKeys.value.clear() indeterminateKeys.value.clear() - _setCheckedKeys(keys) + nextTick(() => { + setCheckedKeysFromProps(keys) + }) } - function setChecked(key: TreeKey, isChecked: boolean) { - if (tree?.value && props.showCheckbox) { - const node = tree.value.treeNodeMap.get(key) - if (node) { - toggleCheckbox(node, isChecked, false) - } - } + function setChecked(key: TreeKey, isChecked: boolean, deep = false) { + if (!tree.value || !props.showCheckbox) return + const node = tree.value.treeNodeMap.get(key) + if (node) toggleCheckbox(node, isChecked, false, true, deep) } - function _setCheckedKeys(keys: TreeKey[]) { - if (tree?.value) { - const { treeNodeMap } = tree.value - if (props.showCheckbox && treeNodeMap && keys) { - for (const key of keys) { - const node = treeNodeMap.get(key) - if (node && !isChecked(node)) { - toggleCheckbox(node, true, false) - } - } + function setCheckedKeysFromProps(keys: TreeKey[]) { + if (!tree.value || !props.showCheckbox) return + + for (const key of keys) { + const node = tree.value.treeNodeMap.get(key) + if (node && !isChecked(node)) { + toggleCheckbox(node, true, false, false) } } + updateCheckedKeys() } return { @@ -210,7 +197,6 @@ export function useCheck(props: TreeProps, tree: Ref) { toggleCheckbox, isChecked, isIndeterminate, - // expose getCheckedKeys, getCheckedNodes, getHalfCheckedKeys, diff --git a/packages/component/src/virtual-tree/composables/useDragNode.ts b/packages/component/src/virtual-tree/composables/useDragNode.ts index 80ac8afa4c..bba4eb2b06 100644 --- a/packages/component/src/virtual-tree/composables/useDragNode.ts +++ b/packages/component/src/virtual-tree/composables/useDragNode.ts @@ -1,211 +1,216 @@ import { useNamespace } from 'element-plus' import { addClass, removeClass } from 'element-plus/es/utils/index.mjs' -// @ts-nocheck -import { provide, ref } from 'vue' -import type { NodeDropType } from '../types' -import type Node from './node' -import type { InjectionKey } from 'vue' - -interface TreeNode { - node: Node - $el?: HTMLElement +import { provide, ref, type InjectionKey } from 'vue' +import type { NodeDropType, TreeNode } from '../types' + +interface DragTreeNode { + node: TreeNode + $el?: HTMLElement | null } interface DragOptions { event: DragEvent - treeNode: TreeNode + treeNode: DragTreeNode +} + +interface DragState { + showDropIndicator: boolean + draggingNode: DragTreeNode | null + dropNode: DragTreeNode | null + allowDrop: boolean + dropType: NodeDropType +} + +interface DragContext { + emit: (event: string, ...args: any[]) => void + slots: Record } export interface DragEvents { treeNodeDragStart: (options: DragOptions) => void treeNodeDragOver: (options: DragOptions) => void + treeNodeDrop: (options: DragOptions) => void treeNodeDragEnd: (event: DragEvent) => void } export const dragEventsKey: InjectionKey = Symbol('dragEvents') -export function useDragNodeHandler({ props, ctx, el$, dropIndicator$ }) { +function isDescendant(parent: TreeNode, node: TreeNode) { + let current = node.parent + while (current) { + if (current.key === parent.key) return true + current = current.parent + } + return false +} + +export function useDragNodeHandler({ + props, + ctx, + el$, + dropIndicator$, +}: { + props: any + ctx: DragContext + el$: { value: HTMLElement | null } + dropIndicator$: { value: HTMLElement | null } +}) { const ns = useNamespace('tree') - const dragState = ref({ + const dragState = ref({ showDropIndicator: false, draggingNode: null, dropNode: null, allowDrop: true, - dropType: null, + dropType: 'none', }) + const clearDropState = () => { + const dropNode = dragState.value.dropNode + if (dropNode?.$el) removeClass(dropNode.$el, ns.is('drop-inner')) + dragState.value.showDropIndicator = false + dragState.value.draggingNode = null + dragState.value.dropNode = null + dragState.value.allowDrop = true + dragState.value.dropType = 'none' + } + const treeNodeDragStart = ({ event, treeNode }: DragOptions) => { if ( typeof props.allowDrag === 'function' && !props.allowDrag(treeNode.node) ) { event.preventDefault() - return false + return } - event.dataTransfer.effectAllowed = 'move' - // wrap in try catch to address IE's error when first param is 'text/plain' + if (event.dataTransfer) event.dataTransfer.effectAllowed = 'move' try { - // setData is required for draggable to work in FireFox - // the content has to be '' so dragging a node out of the tree won't open a new tab in FireFox - event.dataTransfer.setData('text/plain', '') - } catch {} + event.dataTransfer?.setData('text/plain', '') + } catch { + // Firefox requires drag data for draggable elements. + } + dragState.value.draggingNode = treeNode ctx.emit('node-drag-start', treeNode.node, event) } const treeNodeDragOver = ({ event, treeNode }: DragOptions) => { - console.log('treeNode', treeNode) - const dropNode = treeNode + const draggingNode = dragState.value.draggingNode + if (!draggingNode) return + const oldDropNode = dragState.value.dropNode - if (oldDropNode && oldDropNode.node.id !== dropNode.node.id) { + if (oldDropNode?.node.key !== treeNode.node.key && oldDropNode?.$el) { removeClass(oldDropNode.$el, ns.is('drop-inner')) + ctx.emit('node-drag-leave', draggingNode.node, oldDropNode.node, event) } - const draggingNode = dragState.value.draggingNode - if (!draggingNode || !dropNode) return let dropPrev = true let dropInner = true let dropNext = true - let userAllowDropInner = true if (typeof props.allowDrop === 'function') { - dropPrev = props.allowDrop(draggingNode.node, dropNode.node, 'prev') - userAllowDropInner = dropInner = props.allowDrop( - draggingNode.node, - dropNode.node, - 'inner', - ) - dropNext = props.allowDrop(draggingNode.node, dropNode.node, 'next') - } - event.dataTransfer.dropEffect = - dropInner || dropPrev || dropNext ? 'move' : 'none' - if ( - (dropPrev || dropInner || dropNext) && - oldDropNode?.node.id !== dropNode.node.id - ) { - if (oldDropNode) { - ctx.emit('node-drag-leave', draggingNode.node, oldDropNode.node, event) - } - ctx.emit('node-drag-enter', draggingNode.node, dropNode.node, event) + dropPrev = props.allowDrop(draggingNode.node, treeNode.node, 'prev') + dropInner = props.allowDrop(draggingNode.node, treeNode.node, 'inner') + dropNext = props.allowDrop(draggingNode.node, treeNode.node, 'next') } - if (dropPrev || dropInner || dropNext) { - dragState.value.dropNode = dropNode - } - - if (dropNode.node.nextSibling === draggingNode.node) { - dropNext = false - } - if (dropNode.node.previousSibling === draggingNode.node) { - dropPrev = false - } - if (dropNode.node.contains?.(draggingNode.node, false)) { - dropInner = false - } if ( - draggingNode.node === dropNode.node || - draggingNode.node.contains?.(dropNode.node) + draggingNode.node.key === treeNode.node.key || + isDescendant(draggingNode.node, treeNode.node) ) { dropPrev = false dropInner = false dropNext = false } - // find target node without children, just calc content node height - const targetPosition = dropNode.$el - .querySelector(`.${ns.be('node', 'content')}`) - .getBoundingClientRect() - const treePosition = el$.value.getBoundingClientRect() + const target = treeNode.$el?.querySelector( + `.${ns.be('node', 'content')}`, + ) as HTMLElement | null + const treeElement = el$.value + if (!target || !treeElement) return - let dropType: NodeDropType + const targetPosition = target.getBoundingClientRect() + const treePosition = treeElement.getBoundingClientRect() + const distance = event.clientY - targetPosition.top const prevPercent = dropPrev ? (dropInner ? 0.25 : dropNext ? 0.45 : 1) : -1 const nextPercent = dropNext ? (dropInner ? 0.75 : dropPrev ? 0.55 : 0) : 1 - let indicatorTop = -9999 - const distance = event.clientY - targetPosition.top + let dropType: NodeDropType = 'none' if (distance < targetPosition.height * prevPercent) { dropType = 'before' } else if (distance > targetPosition.height * nextPercent) { dropType = 'after' } else if (dropInner) { dropType = 'inner' - } else { - dropType = 'none' } - const iconPosition = dropNode.$el - .querySelector(`.${ns.be('node', 'expand-icon')}`) - .getBoundingClientRect() - const dropIndicator = dropIndicator$.value - if (dropType === 'before') { - indicatorTop = iconPosition.top - treePosition.top - } else if (dropType === 'after') { - indicatorTop = iconPosition.bottom - treePosition.top + const icon = treeNode.$el?.querySelector( + `.${ns.be('node', 'expand-icon')}`, + ) as HTMLElement | null + if (icon && dropIndicator$.value) { + let indicatorTop = -9999 + if (dropType === 'before') { + indicatorTop = icon.getBoundingClientRect().top - treePosition.top + } else if (dropType === 'after') { + indicatorTop = icon.getBoundingClientRect().bottom - treePosition.top + } + const iconPosition = icon.getBoundingClientRect() + dropIndicator$.value.style.top = `${indicatorTop}px` + dropIndicator$.value.style.left = `${iconPosition.right - treePosition.left}px` } - dropIndicator.style.top = `${indicatorTop}px` - dropIndicator.style.left = `${iconPosition.right - treePosition.left}px` - if (dropType === 'inner') { - addClass(dropNode.$el, ns.is('drop-inner')) - } else { - removeClass(dropNode.$el, ns.is('drop-inner')) + if (dropType === 'inner' && treeNode.$el) { + addClass(treeNode.$el, ns.is('drop-inner')) + } else if (treeNode.$el) { + removeClass(treeNode.$el, ns.is('drop-inner')) } + const canDrop = dropType !== 'none' + if (event.dataTransfer) { + event.dataTransfer.dropEffect = canDrop ? 'move' : 'none' + } + dragState.value.dropNode = canDrop ? treeNode : null + dragState.value.allowDrop = canDrop + dragState.value.dropType = dropType dragState.value.showDropIndicator = dropType === 'before' || dropType === 'after' - dragState.value.allowDrop = - dragState.value.showDropIndicator || userAllowDropInner - dragState.value.dropType = dropType - ctx.emit('node-drag-over', draggingNode.node, dropNode.node, event) + + if (oldDropNode?.node.key !== treeNode.node.key && canDrop) { + ctx.emit('node-drag-enter', draggingNode.node, treeNode.node, event) + } + ctx.emit('node-drag-over', draggingNode.node, treeNode.node, event) } - const treeNodeDragEnd = (event: DragEvent) => { - const { draggingNode, dropType, dropNode } = dragState.value + const treeNodeDrop = ({ event }: DragOptions) => { event.preventDefault() + if (!dragState.value.allowDrop || !event.dataTransfer) return event.dataTransfer.dropEffect = 'move' + } - if (draggingNode && dropNode) { - const draggingNodeCopy = { data: draggingNode.node.data } - if (dropType !== 'none') { - draggingNode.node.remove() - } - if (dropType === 'before') { - dropNode.node.parent.insertBefore(draggingNodeCopy, dropNode.node) - } else if (dropType === 'after') { - dropNode.node.parent.insertAfter(draggingNodeCopy, dropNode.node) - } else if (dropType === 'inner') { - dropNode.node.insertChild(draggingNodeCopy) - } - if (dropType !== 'none') { - store.value.registerNode(draggingNodeCopy) - } - - removeClass(dropNode.$el, ns.is('drop-inner')) + const treeNodeDragEnd = (event: DragEvent) => { + const { draggingNode, dropNode, dropType, allowDrop } = dragState.value + event.preventDefault() + if (event.dataTransfer) event.dataTransfer.dropEffect = 'move' + if (draggingNode) { ctx.emit( 'node-drag-end', draggingNode.node, - dropNode.node, + dropNode?.node || null, dropType, event, ) - if (dropType !== 'none') { + if (dropNode && allowDrop && dropType !== 'none') { ctx.emit('node-drop', draggingNode.node, dropNode.node, dropType, event) } } - if (draggingNode && !dropNode) { - ctx.emit('node-drag-end', draggingNode.node, null, dropType, event) - } - dragState.value.showDropIndicator = false - dragState.value.draggingNode = null - dragState.value.dropNode = null - dragState.value.allowDrop = true + clearDropState() } provide(dragEventsKey, { treeNodeDragStart, treeNodeDragOver, + treeNodeDrop, treeNodeDragEnd, }) diff --git a/packages/component/src/virtual-tree/composables/useFilter.ts b/packages/component/src/virtual-tree/composables/useFilter.ts index d39895f14e..89bf273c6b 100644 --- a/packages/component/src/virtual-tree/composables/useFilter.ts +++ b/packages/component/src/virtual-tree/composables/useFilter.ts @@ -1,71 +1,61 @@ -import { isFunction } from '@vue/shared' -import { computed, ref } from 'vue' +import { computed, ref, type Ref } from 'vue' +import { isFunction } from 'element-plus/es/utils/index.mjs' + import type { Tree, TreeKey, TreeNode, TreeProps } from '../types' -import type { Ref } from 'vue' -// When the data volume is very large using filter will cause lag -// I haven't found a better way to optimize it for now -// Maybe this problem should be left to the server side export function useFilter(props: TreeProps, tree: Ref) { - const hiddenNodeKeySet = ref>(new Set([])) - const hiddenExpandIconKeySet = ref>(new Set([])) + const hiddenNodeKeySet = ref>(new Set()) + const hiddenExpandIconKeySet = ref>(new Set()) - const filterable = computed(() => { - return isFunction(props.filterMethod) - }) + const filterable = computed(() => isFunction(props.filterMethod)) function doFilter(query: string) { - if (!filterable.value) { - return - } - const expandKeySet = new Set() - const hiddenExpandIconKeys = hiddenExpandIconKeySet.value + if (!filterable.value) return + + const expandedKeys = new Set() const hiddenKeys = hiddenNodeKeySet.value + const hiddenExpandIconKeys = hiddenExpandIconKeySet.value const family: TreeNode[] = [] const nodes = tree.value?.treeNodes || [] const filter = props.filterMethod + hiddenKeys.clear() - function traverse(nodes: TreeNode[]) { - nodes.forEach((node) => { + hiddenExpandIconKeys.clear() + + function traverse(currentNodes: TreeNode[]) { + currentNodes.forEach((node) => { family.push(node) - if (filter?.(query, node.data)) { + if (filter?.(query, node.data, node)) { family.forEach((member) => { - expandKeySet.add(member.key) + expandedKeys.add(member.key) + member.expanded = true }) - } else if (node.isLeaf) { - hiddenKeys.add(node.key) - } - const children = node.children - if (children) { - traverse(children) + } else { + node.expanded = false + if (node.isLeaf) hiddenKeys.add(node.key) } + + if (node.children) traverse(node.children) + if (!node.isLeaf) { - if (!expandKeySet.has(node.key)) { + if (!expandedKeys.has(node.key)) { hiddenKeys.add(node.key) - } else if (children) { - // If all child nodes are hidden, then the expand icon will be hidden - let allHidden = true - for (const childNode of children) { - if (!hiddenKeys.has(childNode.key)) { - allHidden = false - break - } - } - if (allHidden) { - hiddenExpandIconKeys.add(node.key) - } else { - hiddenExpandIconKeys.delete(node.key) - } + } else if (node.children) { + const allHidden = node.children.every((child) => + hiddenKeys.has(child.key), + ) + if (allHidden) hiddenExpandIconKeys.add(node.key) } } family.pop() }) } + traverse(nodes) - return expandKeySet + return expandedKeys } - function isForceHiddenExpandIcon(node: TreeNode): boolean { + function isForceHiddenExpandIcon(node: TreeNode) { return hiddenExpandIconKeySet.value.has(node.key) } diff --git a/packages/component/src/virtual-tree/composables/useTree.ts b/packages/component/src/virtual-tree/composables/useTree.ts index 367f6001d0..5b8e3cf145 100644 --- a/packages/component/src/virtual-tree/composables/useTree.ts +++ b/packages/component/src/virtual-tree/composables/useTree.ts @@ -1,24 +1,17 @@ -import { useCheck } from 'element-plus/es/components/tree-v2/src/composables/useCheck' -import { useFilter } from 'element-plus/es/components/tree-v2/src/composables/useFilter' +import { computed, ref, shallowRef, watch, type SetupContext } from 'vue' +import { isObject } from 'element-plus/es/utils/index.mjs' import { CURRENT_CHANGE, NODE_CLICK, NODE_COLLAPSE, + NODE_DROP, NODE_EXPAND, TreeOptionsEnum, type treeEmits, } from 'element-plus/es/components/tree-v2/src/virtual-tree' -import { isObject } from 'element-plus/es/utils/index.mjs' -import { - computed, - nextTick, - ref, - shallowRef, - watch, - type ComponentInternalInstance, - type SetupContext, -} from 'vue' -// import type { treeEmits } from 'element-plus/es/components/tree-v2/src/virtual-tree' +import { useCheck } from './useCheck' +import { useFilter } from './useFilter' + import type { CheckboxValueType } from 'element-plus' import type { Tree, @@ -27,35 +20,21 @@ import type { TreeNode, TreeNodeData, TreeProps, -} from 'element-plus/es/components/tree-v2/src/types' +} from '../types' + +type ListInstance = { + scrollToItem: (index: number, strategy?: any) => void + scrollTo: (offset: number) => void +} export function useTree( props: TreeProps, emit: SetupContext['emit'], ) { - const expandedKeySet = ref>(new Set(props.defaultExpandedKeys)) + const expandedKeySet = ref>(new Set()) const currentKey = ref() const tree = shallowRef() - - watch( - () => props.currentNodeKey, - (key) => { - currentKey.value = key - }, - { - immediate: true, - }, - ) - - watch( - () => props.data, - (data: TreeData) => { - setData(data) - }, - { - immediate: true, - }, - ) + const listRef = ref() const { isIndeterminate, @@ -86,50 +65,42 @@ export function useTree( const labelKey = computed(() => { return props.props?.label || TreeOptionsEnum.LABEL }) - const isLeafKey = computed(() => { - return props.props?.isLeaf - }) + const isLeafKey = computed(() => (props.props as any)?.isLeaf) const flattenTree = computed(() => { const expandedKeys = expandedKeySet.value const hiddenKeys = hiddenNodeKeySet.value const flattenNodes: TreeNode[] = [] - const nodes = (tree.value && tree.value.treeNodes) || [] - function traverse() { - const stack: TreeNode[] = [] - for (let i = nodes.length - 1; i >= 0; --i) { - stack.push(nodes[i]) - } - while (stack.length) { - const node = stack.pop() - if (!node) continue - if (!hiddenKeys.has(node.key)) { - flattenNodes.push(node) - } - // Only "visible" nodes will be rendered - if (expandedKeys.has(node.key)) { - const children = node.children - if (children) { - const length = children.length - for (let i = length - 1; i >= 0; --i) { - stack.push(children[i]) - } - } + const nodes = tree.value?.treeNodes || [] + + const stack: TreeNode[] = [] + for (let i = nodes.length - 1; i >= 0; --i) { + const node = nodes[i] + if (node) stack.push(node) + } + while (stack.length) { + const node = stack.pop() + if (!node || hiddenKeys.has(node.key)) continue + + flattenNodes.push(node) + if (node.children && expandedKeys.has(node.key)) { + for (let i = node.children.length - 1; i >= 0; --i) { + const child = node.children[i] + if (child) stack.push(child) } } } - traverse() + return flattenNodes }) - const isNotEmpty = computed(() => { - return flattenTree.value.length > 0 - }) + const isNotEmpty = computed(() => flattenTree.value.length > 0) function createTree(data: TreeData): Tree { - const treeNodeMap: Map = new Map() - const levelTreeNodeMap: Map = new Map() + const treeNodeMap = new Map() + const levelTreeNodeMap = new Map() let maxLevel = 1 + function traverse( nodes: TreeData, level = 1, @@ -147,7 +118,10 @@ export function useTree( node.parent = parent const children = getChildren(rawNode) node.disabled = getDisabled(rawNode) - node.isLeaf = getIsLeaf(rawNode, children) + node.isLeaf = isLeafKey.value + ? Boolean(rawNode[isLeafKey.value]) + : !children || children.length === 0 + node.expanded = expandedKeySet.value.has(value) if (children && children.length) { node.children = traverse(children, level + 1, node) } @@ -158,79 +132,88 @@ export function useTree( } levelTreeNodeMap.get(level)?.push(node) } - if (level > maxLevel) { - maxLevel = level - } + if (level > maxLevel) maxLevel = level return siblings } - const treeNodes: TreeNode[] = traverse(data) + return { treeNodeMap, levelTreeNodeMap, maxLevel, - treeNodes, + treeNodes: traverse(data), } } function filter(query: string) { const keys = doFilter(query) - if (keys) { - expandedKeySet.value = keys - } + if (keys) expandedKeySet.value = keys } function getChildren(node: TreeNodeData): TreeNodeData[] { - return node[childrenKey.value] + return node[childrenKey.value] || [] } function getKey(node: TreeNodeData): TreeKey { - if (!node) { - return '' - } - return node[valueKey.value] + return node ? node[valueKey.value] : '' } function getDisabled(node: TreeNodeData): boolean { - return node[disabledKey.value] + return Boolean(node[disabledKey.value]) } function getLabel(node: TreeNodeData): string { return node[labelKey.value] } - function getIsLeaf(node: TreeNodeData, children: TreeNodeData[]): boolean { - return isLeafKey.value - ? node[isLeafKey.value] - : !children || children.length === 0 - } - - function toggleExpand( - node: TreeNode, - nodeInstance: ComponentInternalInstance, - ) { - const expandedKeys = expandedKeySet.value - if (expandedKeys.has(node.key)) { - collapseNode(node, nodeInstance) + function toggleExpand(node: TreeNode) { + if (expandedKeySet.value.has(node.key)) { + collapseNode(node) } else { - expandNode(node, nodeInstance) + expandNode(node) } } function setExpandedKeys(keys: TreeKey[]) { - expandedKeySet.value = new Set(keys) + if (!tree.value) return + + const nextExpandedKeys = new Set() + const nodeMap = tree.value.treeNodeMap + + expandedKeySet.value.forEach((key) => { + const node = nodeMap.get(key) + if (node) node.expanded = false + }) + + keys.forEach((key) => { + let node = nodeMap.get(key) + while (node && !nextExpandedKeys.has(node.key)) { + nextExpandedKeys.add(node.key) + node.expanded = true + node = node.parent + } + }) + + expandedKeySet.value = nextExpandedKeys } - function handleNodeClick(node: TreeNode, e: MouseEvent) { - emit(NODE_CLICK, node.data, node, e) + function handleNodeClick(node: TreeNode, event: MouseEvent) { handleCurrentChange(node) - if (props.expandOnClickNode) { - toggleExpand(node) - } - if (props.showCheckbox && props.checkOnClickNode && !node.disabled) { + emit(NODE_CLICK, node.data, node, event) + if (props.expandOnClickNode) toggleExpand(node) + + if ( + props.showCheckbox && + (props.checkOnClickNode || (node.isLeaf && props.checkOnClickLeaf)) && + !node.disabled + ) { toggleCheckbox(node, !isChecked(node), true) } } + function handleNodeDrop(node: TreeNode, event: DragEvent) { + emit(NODE_DROP, node.data, node, event) + } + function handleCurrentChange(node: TreeNode) { if (!isCurrent(node)) { currentKey.value = node.key @@ -242,41 +225,38 @@ export function useTree( toggleCheckbox(node, checked) } - function expandNode(node: TreeNode, nodeInstance: ComponentInternalInstance) { + function expandNode(node: TreeNode) { const keySet = expandedKeySet.value if (tree.value && props.accordion) { - // whether only one node among the same level can be expanded at one time const { treeNodeMap } = tree.value keySet.forEach((key) => { - const treeNode = treeNodeMap.get(key) - if (node && node.level === treeNode?.level) { + const sibling = treeNodeMap.get(key) + if (node.level === sibling?.level) { keySet.delete(key) + sibling.expanded = false } }) } + keySet.add(node.key) - emit(NODE_EXPAND, node.data, node, nodeInstance) + const currentNode = getNode(node.key) + if (currentNode) { + currentNode.expanded = true + emit(NODE_EXPAND, currentNode.data, currentNode) + } } - function collapseNode( - node: TreeNode, - nodeInstance: ComponentInternalInstance, - ) { + function collapseNode(node: TreeNode) { expandedKeySet.value.delete(node.key) - emit(NODE_COLLAPSE, node.data, node, nodeInstance) - } - - function isExpanded(node: TreeNode): boolean { - return expandedKeySet.value.has(node.key) - } - - function isDisabled(node: TreeNode): boolean { - return !!node.disabled + const currentNode = getNode(node.key) + if (currentNode) { + currentNode.expanded = false + emit(NODE_COLLAPSE, currentNode.data, currentNode) + } } function isCurrent(node: TreeNode): boolean { - const current = currentKey.value - return current !== undefined && current === node.key + return currentKey.value !== undefined && currentKey.value === node.key } function getCurrentNode(): TreeNodeData | undefined { @@ -288,12 +268,12 @@ export function useTree( return currentKey.value } - function setCurrentKey(key: TreeKey): void { + function setCurrentKey(key: TreeKey) { currentKey.value = key } function setData(data: TreeData) { - nextTick(() => (tree.value = createTree(data))) + tree.value = createTree(data) } function getNode(data: TreeKey | TreeNodeData) { @@ -301,23 +281,58 @@ export function useTree( return tree.value?.treeNodeMap.get(key) } + function scrollToNode(key: TreeKey, strategy: any = 'auto') { + const node = getNode(key) + if (node && listRef.value) { + listRef.value.scrollToItem(flattenTree.value.indexOf(node), strategy) + } + } + + function scrollTo(offset: number) { + listRef.value?.scrollTo(offset) + } + + watch( + () => props.currentNodeKey, + (key) => { + currentKey.value = key + }, + { immediate: true }, + ) + + watch( + () => props.defaultExpandedKeys, + (keys) => { + setExpandedKeys(keys || []) + }, + ) + + watch( + () => props.data, + (data: TreeData | undefined) => { + setData(data || []) + setExpandedKeys(props.defaultExpandedKeys || []) + }, + { immediate: true }, + ) + return { tree, flattenTree, isNotEmpty, + listRef, getKey, getChildren, toggleExpand, toggleCheckbox, - isExpanded, isChecked, isIndeterminate, - isDisabled, + isDisabled: (node: TreeNode) => Boolean(node.disabled), isCurrent, isForceHiddenExpandIcon, handleNodeClick, + handleNodeDrop, handleNodeCheck, - // expose getCurrentNode, getCurrentKey, setCurrentKey, @@ -333,5 +348,7 @@ export function useTree( expandNode, collapseNode, setExpandedKeys, + scrollToNode, + scrollTo, } } diff --git a/packages/component/src/virtual-tree/types.ts b/packages/component/src/virtual-tree/types.ts index 3a43bbcdd0..c2d56750d0 100644 --- a/packages/component/src/virtual-tree/types.ts +++ b/packages/component/src/virtual-tree/types.ts @@ -1,151 +1,16 @@ -import type Node from './model/node' -import type TreeStore from './model/tree-store' -import type { - Component, - ComponentInternalInstance, - h, - Ref, - SetupContext, - VNode, -} from 'vue' +import type { TreeOptionProps as ElementPlusTreeOptionProps } from 'element-plus/es/components/tree-v2/src/types' -export interface RootTreeType { - ctx: SetupContext - props: TreeComponentProps - store: Ref - root: Ref - currentNode: Ref - instance: ComponentInternalInstance -} - -export declare type hType = typeof h -export declare type TreeData = TreeNodeData[] -export declare type TreeKey = string | number - -export declare interface FakeNode { - data: TreeNodeData -} - -export declare interface TreeNodeData { - [key: string]: any -} - -export declare interface TreeNodeLoadedDefaultProps { - checked?: boolean -} - -export declare interface TreeNodeChildState { - all: boolean - none: boolean - allWithoutDisable: boolean - half: boolean -} - -export declare interface TreeNodeOptions { - data: TreeNodeData - store: TreeStore - parent?: Node -} - -export declare interface TreeStoreNodesMap { - [key: string]: Node -} - -export declare interface TreeOptionProps { - children?: string - label?: string | ((data: TreeNodeData, node: Node) => string) - disabled?: string | ((data: TreeNodeData, node: Node) => boolean) - isLeaf?: string | ((data: TreeNodeData, node: Node) => boolean) - class?: ( - data: TreeNodeData, - node: Node, - ) => string | { [key: string]: boolean } | string -} - -export declare interface TreeStoreOptions { - key: TreeKey - data: TreeData - lazy: boolean - props: TreeOptionProps - load: LoadFunction - currentNodeKey: TreeKey - checkStrictly: boolean - checkDescendants: boolean - defaultCheckedKeys: TreeKey[] - defaultExpandedKeys: TreeKey[] - autoExpandParent: boolean - defaultExpandAll: boolean - filterNodeMethod: FilterNodeMethodFunction -} - -export declare interface TreeOptionProps { - children?: string - label?: string | ((data: TreeNodeData, node: Node) => string) - disabled?: string | ((data: TreeNodeData, node: Node) => boolean) - isLeaf?: string | ((data: TreeNodeData, node: Node) => boolean) - class?: ( - data: TreeNodeData, - node: Node, - ) => string | { [key: string]: boolean } | string -} - -export declare type RenderContentFunction = ( - h: hType, - context: RenderContentContext, -) => VNode | VNode[] - -export declare interface RenderContentContext { - _self: ComponentInternalInstance - node: Node - data: TreeNodeData - store: TreeStore -} - -export declare type AllowDragFunction = (node: Node) => boolean -export declare type AllowDropType = 'inner' | 'prev' | 'next' -export declare type AllowDropFunction = ( - draggingNode: Node, - dropNode: Node, - type: AllowDropType, -) => boolean -export declare type LoadFunction = ( - rootNode: Node, - loadedCallback: (data: TreeData) => void, -) => void -export declare type FilterValue = any -export declare type FilterNodeMethodFunction = ( - value: FilterValue, - data: TreeNodeData, - child: Node, -) => boolean +export type { + Tree, + TreeData, + TreeKey, + TreeNode, + TreeNodeData, + TreeProps, +} from 'element-plus/es/components/tree-v2/src/types' -export declare interface TreeComponentProps { - data: TreeData - emptyText: string - renderAfterExpand: boolean - nodeKey: string - checkStrictly: boolean - expandOnClickNode: boolean - defaultExpandAll: boolean - checkOnClickNode: boolean - checkDescendants: boolean - autoExpandParent: boolean - defaultCheckedKeys: TreeKey[] - defaultExpandedKeys: TreeKey[] - currentNodeKey: TreeKey - renderContent: RenderContentFunction - showCheckbox: boolean - draggable: boolean - allowDrag: AllowDragFunction - allowDrop: AllowDropFunction - props: TreeOptionProps - lazy: boolean - highlightCurrent: boolean - load: LoadFunction - filterNodeMethod: FilterNodeMethodFunction - accordion: boolean - indent: number - icon: string | Component +export type TreeOptionProps = ElementPlusTreeOptionProps & { + isLeaf?: string } -export declare type NodeDropType = 'before' | 'after' | 'inner' | 'none' +export type NodeDropType = 'before' | 'after' | 'inner' | 'none' diff --git a/packages/ldp/src/Dashboard.vue b/packages/ldp/src/Dashboard.vue index 0e73d2d799..c6f3211a8e 100644 --- a/packages/ldp/src/Dashboard.vue +++ b/packages/ldp/src/Dashboard.vue @@ -400,11 +400,12 @@ export default { if (el.ldpType === 'target') { if (el.type === 'apiserverLineage') { const { table, modules = {} } = el || {} - const { appName, name } = Object.values(modules)[0] || {} + const { appName, name, id } = Object.values(modules)[0] || {} keywordOptions[el.ldpType].push({ table, appName, - serverName: name, + apiName: name, + apiId: id, type: el.type, }) } @@ -449,7 +450,7 @@ export default { const sDom = sourceNode.dom || map[sourceNode.ldpType](sourceNode) const tDom = targetNode.dom || map[targetNode.ldpType](targetNode) // 过滤掉source节点连线到source节点的情况 - if (targetNode.ldpType !== 'source') { + if (targetNode.ldpType !== 'source' && sDom && tDom) { el.leaderLine = new LeaderLine(sDom, tDom, { color: 'var(--color-primary)', dash: { diff --git a/packages/ldp/src/Source.vue b/packages/ldp/src/Source.vue index 8d42842bf5..35f0195537 100644 --- a/packages/ldp/src/Source.vue +++ b/packages/ldp/src/Source.vue @@ -1,4 +1,4 @@ - @@ -439,20 +582,21 @@ export default defineComponent({ id="btn-add-source" :disabled="highlightBoard" @click="handleAdd" - >add + add + search-outline + > + search-outline -
- - -
-
- - - + +
+ +
diff --git a/packages/ldp/src/Target.vue b/packages/ldp/src/Target.vue index 7580c4072e..c4221c154f 100644 --- a/packages/ldp/src/Target.vue +++ b/packages/ldp/src/Target.vue @@ -183,16 +183,20 @@ export default { const result = [] this.searchKeywordList.forEach((item) => { if (item.type === 'apiserverLineage') { - // item的数据结构:appName,serverName,table,type const appList = cloneDeep( this.list.filter((item) => item.LDP_TYPE === 'app'), ) - const findApp = appList.find((t) => t.value === item.appName) - const findServer = findApp?.modules?.find( - (t) => t.name === item.serverName, - ) + let findApp = null + let findServer = null + for (const app of appList) { + findServer = app.modules?.find((t) => t.id === item.apiId) + if (findServer) { + findApp = app + break + } + } if (!findServer) return - const findOne = result.find((t) => t.value === item.appName) + const findOne = result.find((t) => t.id === findApp.id) if (findOne) { findOne.modules.push(findServer) } else {