首页 > 解决方案 > 更改 Vue 组件的所有权

问题描述

我正在尝试制作一个布局创建者,可以将一个小部件从画廊拖到屏幕上。为此,我制作了两个组件 -LayoutWidgetsLayoutCanvas. 这两个组件都有vue-grid-layouts作为子级。我的想法是,当将项目从画廊拖到画布时,项目组件将被重新设置为画布布局(这是从原始 JS 角度接近它,.appendChild可用于重新排列 DOM),同时仍然保持其拖动状态并保留事件侦听器等。Vue 的 VDom 有什么办法做到这一点吗?这是我当前的包装器组件:

<template>
    <ResizablePanes class="resizer-root">
        <LayoutCanvas ref="canvas"
                      style="flex-grow:2"
                      @dragging="dragEvent($event, 'canvas')"/>
        <LayoutWidgets ref="widgets"
                       style="flex-grow:1"
                       @dragging="dragEvent($event, 'widgets')"/>
    </ResizablePanes>
</template>
<script>
import LayoutCanvas from './LayoutCanvas.vue';
import LayoutWidgets from './LayoutWidgets.vue';
import ResizablePanes from './ResizablePanes.vue';
export default {
    components: {
        LayoutCanvas,
        LayoutWidgets,
        ResizablePanes,
    },
    data: () => ({
        dragging: null,
        dragParent: null,
        dragReceiverRefs: ['canvas', 'widgets'],
    }),
    computed: {
        dragReceivers() {
            return this.dragReceiverRefs
                .filter(x => x != this.dragParent)
                .map(x => ({
                    grid:this.$refs[x].$refs.grid,
                    bBox:this.$refs[x].$refs.grid.$el.getBoundingClientRect()
                }))
        }
    },
    methods: {
        dragEvent(item, ref) {
            if (!item) {
                this.dragging = null;
                this.dragParent = null;
                window.removeEventListener('mousemove', this.dragHover);
            } else {
                this.dragging = item;
                this.dragParent = ref;
                window.addEventListener('mousemove', this.dragHover);
            }
        },
        dragHover(e) {
            const receiver = this.dragReceivers.find(x => 
                x.bBox.left <= e.clientX &&
                x.bBox.top <= e.clientY &&
                x.bBox.right >= e.clientX &&
                x.bBox.bottom >= e.clientY
            );
            // need help here:
            receiver.grid.appendChild(this.dragging);
            //
        }
    }
}
</script>

标签: javascriptvue.jsvue-component

解决方案


好吧,这是一个不必要的难题,但我已经设法创建了一个解决方案,如果将来有人发现这个问题,我会在这里发布。

请注意,在此代码中:

  • moveNode是要移动的 Vue 组件
  • oldGrandParent是包含其prop定义的此父级的祖先
  • newGrandParent是它被移动到的等效祖先
  • newParent是将持有它的直接父级 Vue 组件
// New data object
const newLayout = { x:0, y:0, w:moveNode.w * 2, h:moveNode.h, i:Date.now() };
// Remove VNode from old parent
const vNode = moveNode.$parent._vnode.children.splice(moveNode.$parent._vnode.children.indexOf(moveNode.$vnode), 1)[0];
// Rekey it
vNode.key = newLayout.i;
// Move HTMLElement
newParent._vnode.elm.appendChild(vNode.elm);
// Move VNode
newParent._vnode.children.push(vNode);
// Clean up old parent
moveNode.eventBus.$emit('dragEvent', 'dragend')
// Re-initialise Node for new parent (using copied source code - I don't think there's an easier way to do this)
this.rebindEventBus(moveNode,  Object.getOwnPropertyDescriptor(newParent, "eventBus"));
// Continue cleaning up old parent
moveNode.$parent.$children.splice(moveNode.$parent.$children.indexOf(moveNode), 1)
oldGrandParent.layout.splice(oldGrandParent.layout.findIndex(x => x.i == moveNode.i), 1);
// Give Vue instance new parent
moveNode.$parent = newParent;
// Add new data object to new parent, triggering reactivity
newGrandParent.layout.push(newLayout);
// Call Nodes "mounted" hook to finish re-parenting
this.$nextTick(function() { moveNode.$options.mounted.slice().reverse()[0].call(moveNode); });
// Reparenting complete!

推荐阅读