一、 背景
一个项目为vue2,一个项目为vue3,两个不同的项目实现iframe嵌入,并实现通信
二、方案
iframe跨域时,iframe组件之间常用的通信,主要是H5的possmessage方法
三、案例代码
父页面-vue2(端口号为127.0.0.1:8080)
<template>
<div>
<div class="container">
<iframe
ref="iframeId"
id="iframeId"
src="http://127.0.0.1:8081"
frameborder="0"
border="0"
hspace="0"
vspace="0"
scrolling="yes"
height="100%"
width="100%"
></iframe>
</div>
</div>
</template>
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator';
@Component({
components: {},
})
export default class FeatureService extends Vue {
// 定义一个方法,用于发送消息到iframe
private postMessageToIframe() {
// 设置延时,确保iframe已经加载完成
setTimeout(() => {
// 定义要发送的数据
const iframeInfo = { isIframeParent: true };
// 定义目标源
const targetOrigin = 'http://127.0.0.1:8081';
// 获取iframe引用
const iframe = this.$refs.iframeId;
// 向iframe发送消息
iframe.contentWindow.postMessage(
JSON.stringify(iframeInfo),
targetOrigin
);
}, 500);
}
private mounted() {
// 在组件挂载后,发送消息到iframe
this.postMessageToIframe();
window.addEventListener('message', this.handleMessage, false);
}
private handleMessage(event: MessageEvent) {
// 通过origin对消息进行过滤,避免遭到XSS攻击
if (event.origin === 'http://127.0.0.1:8081') {
console.log('子页面传输过来参数', event.data);
}
}
}
</script>
<style lang="less" scoped>
.container {
width: 100%;
margin-top: -40px;
height: 100vh;
overflow: hidden;
#iframeId {
width: 100%;
display: block;
}
}
</style>
子页面-vue3, (端口号为127.0.0.1:8081)
<template>
<div>
<!-- 顶部导航 -->
<TopMenu v-if="!iframeParentInfo.isIframeParent" />
<div class="demo-sidebar-container demo-full-width">
<!-- 侧边栏 -->
<SiderBar v-if="!iframeParentInfo.isIframeParent" />
<!-- 内容容器 -->
<ContentBox />
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
const iframeParentInfo = ref({})
const messageHandler = (e) => {
// 通过origin对消息进行过滤,避免遭到XSS攻击
if (e.origin !== 'http://127.0.0.1:8080') return
if (typeof e.data === 'string') {
parseData(e.data)
}
}
const parseData = (data) => {
try {
iframeParentInfo.value = JSON.parse(data)
console.log('父页面传输过来参数', data)
} catch (error) {
console.error('解析JSON出错', error)
iframeParentInfo.value = {}
}
}
onMounted(() => {
// 获取 父向 子(iframe) 传递的信息
window.addEventListener('message', messageHandler)
// 子(iframe)向父传递信息
window.parent.postMessage('Hello Parent!', '*')
})
</script>
四、案例效果
五、补充
ps: 以上案例可能会发现 父窗口能拿到子iframe的信息,而子iframe拿不到父窗口的信息
解决方案
this.$nextTick
在 Vue 完成下一次 DOM 更新后,添加一个事件监听器,用于监听 message 事件,并在事件触发时调用 this.handleMessage 方法处理事件。
案例优化
父窗口
<template>
<div>
<div class="container">
<iframe
ref="iframeId"
id="iframeId"
src="http://127.0.0.1:8081"
frameborder="0"
border="0"
hspace="0"
vspace="0"
scrolling="yes"
height="100%"
width="100%"
></iframe>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {},
})
export default class FeatureService extends Vue {
// 定义一个方法,用于发送消息到iframe
private postMessageToIframe() {
// 定义要发送的数据
const iframeInfo = { isIframeParent: true };
// 定义目标源,对应iframe子窗口的地址
const targetOrigin = 'http://127.0.0.1:8081';
// 获取iframe引用
const iframe = this.$refs.iframeId as HTMLIFrameElement;
// 向iframe发送消息
iframe?.contentWindow?.postMessage(
JSON.stringify(iframeInfo),
targetOrigin,
);
}
private created() {
// 在组件挂载后,发送消息到iframe
this.$nextTick(() => {
window.addEventListener('message', this.handleMessage, false);
});
}
private handleMessage(event: MessageEvent) {
// 通过origin对消息进行过滤,避免遭到XSS攻击,对应iframe子窗口的地址
if (event.origin === 'http://127.0.0.1:8081') {
console.log('子页面传输过来参数', event.data);
this.postMessageToIframe();
}
}
private unmounted() {
window.removeEventListener('message', (e) => this.handleMessage(e));
}
}
</script>
<style lang="less" scoped>
.container {
width: 100%;
margin-top: -40px;
height: 100vh;
overflow: hidden;
#iframeId {
width: 100%;
display: block;
}
}
</style>
子窗口
<template>
<div>
<!-- 顶部导航 -->
<TopMenu v-if="!iframeParentInfo.isIframeParent" />
<div class="demo-sidebar-container demo-full-width">
{{ iframeParentInfo.isIframeParent }}
<!-- 侧边栏 -->
<SiderBar v-if="!iframeParentInfo.isIframeParent" />
<!-- 内容容器 -->
<ContentBox />
</div>
</div>
</template>
<script setup lang="ts">
import ContentBox from '@/common/components/ContentBox.vue'
import SiderBar from '@/common/components/SiderBar.vue'
import TopMenu from '@/common/components/TopMenu.vue'
import { useFeatureStore, useKVBankStore } from '@/stores'
import { onMounted, onUnmounted, ref } from 'vue'
import useRentManageStore from './views/authorityManage/rentManage/store'
// isIframeParent为false
const iframeParentInfo = ref({ isIframeParent: false })
// 存储信息
const featureStore = useFeatureStore()
const messageHandler = (e: any) => {
console.log('子=====e', e)
// 通过origin对消息进行过滤,避免遭到XSS攻击 e.origin对应父窗口的地址
if (e.origin !== 'http://127.0.0.1:8080') return
if (typeof e.data === 'string') {
parseData(e.data)
}
}
const parseData = (data: any) => {
console.log('---4222--', data)
try {
iframeParentInfo.value = JSON.parse(data)
console.log('父页面传输过来参数', data)
} catch (error) {
console.error('解析JSON出错', error)
iframeParentInfo.value = { isIframeParent: false }
}
featureStore.getFeatureInfo(iframeParentInfo.value)
}
onMounted(() => {
// 获取 父向 子(iframe) 传递的信息
window.addEventListener('message', messageHandler, false)
console.log('子==window.location.location', window.location)
// 子(iframe)向父传递信息
window.parent.postMessage('Hello Parent!', '*')
})
onUnmounted(() => {
window.removeEventListener('message', messageHandler)
})
</script>