在新窗口中打开VueJS组件

3yhwsihp  于 2023-02-05  发布在  Vue.js
关注(0)|答案(4)|浏览(261)

我有一个基本的VueJS应用程序,只有一个页面。它不是一个SPA,我不使用vue-router。
我想实现一个按钮,当单击该按钮时,将执行window.open()函数,其中包含来自我的一个Vue组件的内容。
查看window.open()的文档,我看到URL的以下语句:
URL接受HTML页面、图像文件或浏览器支持的任何其他资源的路径或URL。
是否可以将组件作为window.open()的参数传递?

f4t66c6m

f4t66c6m1#

我能够使用an article about Portals in React的一些见解来创建一个Vue组件,该组件能够在新窗口中挂载其子组件,同时保持React性!

<window-portal>
  I appear in a new window!
</window-portal>

在这款codesandbox中试试吧!
此组件的代码如下所示:

<template>
  <div v-if="open">
    <slot />
  </div>
</template>

<script>
export default {
  name: 'window-portal',
  props: {
    open: {
      type: Boolean,
      default: false,
    }
  },
  data() {
    return {
      windowRef: null,
    }
  },
  watch: {
    open(newOpen) {
      if(newOpen) {
        this.openPortal();
      } else {
        this.closePortal();
      }
    }
  },
  methods: {
    openPortal() {
      this.windowRef = window.open("", "", "width=600,height=400,left=200,top=200");
      this.windowRef.addEventListener('beforeunload', this.closePortal);
      // magic!
      this.windowRef.document.body.appendChild(this.$el);
    },
    closePortal() {
      if(this.windowRef) {
        this.windowRef.close();
        this.windowRef = null;
        this.$emit('close');
      }
    }
  },
  mounted() {
    if(this.open) {
      this.openPortal();
    }
  },
  beforeDestroy() {
    if (this.windowRef) {
      this.closePortal();
    }
  }
}
</script>

关键是this.windowRef.document.body.appendChild(this.$el)线;该行有效地删除了与Vue组件关联的DOM元素(顶层<div>),并将其插入子窗口的主体。由于此元素与Vue通常更新的元素是 * 相同的引用 *,只是在不同的位置,因此所有内容Just Works - Vue将继续更新元素以响应数据绑定更改,尽管它被安装在一个新的窗口。我其实很惊讶这是多么简单!

b1payxdu

b1payxdu2#

你不能传递一个Vue组件,因为window.open不知道Vue。然而,你可以做的是创建一个显示你的组件的路由,并将这个路由的URL传递给window.open,给你一个包含你的组件的新窗口。但是,不同窗口中的组件之间的通信可能会很棘手。

3npbholx

3npbholx3#

例如,如果main vue声明为

var app = new Vue({...});

如果您只需要在新窗口中呈现少量数据,那么您可以从父窗口引用数据模型。

var app1 = window.opener.app;

var title = app.title;

var h1 = document.createElement("H1");

h1.innerHTML = title;

document.body.appendChild(h1);
q5lcpyga

q5lcpyga4#

我把Alex的贡献移植到了Composition API中,运行得很好。唯一的烦恼是创建的窗口忽略了大小和位置,可能是因为它是从全屏的Chrome应用程序启动的。

<script setup lang="ts">

import {ref, onMounted, onBeforeUnmount, watch, nextTick} from "vue";

const props = defineProps<{modelValue: boolean;}>();
const emit = defineEmits(["update:modelValue"]);

let windowRef: Window | null = null;
const portal = ref(null);

const copyStyles = (sourceDoc: Document, targetDoc: Document): void => {

    // eslint-disable-next-line unicorn/prefer-spread
    for(const styleSheet of Array.from(sourceDoc.styleSheets)) {
        if(styleSheet.cssRules) {
            // for <style> elements
            const nwStyleElement = sourceDoc.createElement("style");

            // eslint-disable-next-line unicorn/prefer-spread
            for(const cssRule of Array.from(styleSheet.cssRules)) {
                // write the text of each rule into the body of the style element
                nwStyleElement.append(sourceDoc.createTextNode(cssRule.cssText));
            }

            targetDoc.head.append(nwStyleElement);
        }
        else if(styleSheet.href) {
            // for <link> elements loading CSS from a URL
            const nwLinkElement = sourceDoc.createElement("link");

            nwLinkElement.rel = "stylesheet";
            nwLinkElement.href = styleSheet.href;
            targetDoc.head.append(nwLinkElement);
        }
    }
};

const openPortal = (): void => {

    nextTick().then((): void => {

        windowRef = window.open("", "", "width=600,height=400,left=200,top=200");
        if(!windowRef || !portal.value) return;
        windowRef.document.body.append(portal.value);
        copyStyles(window.document, windowRef.document);
        windowRef.addEventListener("beforeunload", closePortal);
    })
    .catch((error: Error) => console.error("Cannot instantiate portal", error.message));
};

const closePortal = (): void => {
      if(windowRef) {
        windowRef.close();
        windowRef = null;
        emit("update:modelValue", false);
      }
};

watch(props, () => {
    if(props.modelValue) {
        openPortal();
    }
    else {
        closePortal();
    }
});

onMounted(() => {
    if(props.modelValue) {
      openPortal();
    }
});
onBeforeUnmount(() => {
    if(windowRef) {
      closePortal();
    }
});
</script>

<template>
<div v-if="props.modelValue" ref="portal">
    <slot />
</div>
</template>

相关问题