vue3中如何使用iframe嵌入vue2页面
作者:小何同学要加油
这篇文章主要介绍了vue3中如何使用iframe嵌入vue2页面问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
vue3使用iframe嵌入vue2页面
vue3中使用iframe嵌入vue2页面或vue页面和iframe页面的相互传参问题
记录一个开发过程中遇到的问题,Vue3项目中嵌入了一个iframe,iframe里面的是一个vue2项目的页面,是项目赶工计较着急,来不及把vue2集成到vue3中,这时候想到用Vue3和Vue2之间来回互调传参,下面提供解决思路。
Vue3 父组件
<template> <div class="content"> <iframe id="unityiframe" ref="iframeRef" name="iframeContain" seamless scrolling="yes" :src="srcUrl" style="width: 100%; height: 70vh; border: 0" > </iframe> </div> </template>
<script> import { reactive, toRefs, onMounted, ref,watch, nextTick, getCurrentInstance,} from "vue"; export default { setup() { //接受子组件穿过来的值 function handleMessage(event) { console.log("子页面传过值event", event.data); var datalist = event.data; if (datalist) { sendMessage(); } } // 调用vue中getCurrentInstance方法(此参考V2和V3中this.$refs区别) const currentInstance = getCurrentInstance(); function sendMessage() { // currentInstance.ctx.$refs是vue3的写法。vue2中是 this.$refs if (currentInstance.ctx.$refs.iframeRef) { //判断他不为空,可根据自己的页面判断 currentInstance.ctx.$refs.iframeRef.contentWindow.postMessage( // 向子页面iframe传递 "aaa", //传递的参数 "http://10.1.0.238:88/#/workspace/formsPanel" //iframe的页面地址 ); } } nextTick(() => { window.addEventListener("message", handleMessage); }); return{ handleMessage, currentInstance, sendMessage } }} </script>
Vue2 子组件(1)
mounted() { //给父页面发送消息 window.parent.postMessage( "111", "http://10.1.0.238:8080/#/application/function" ); //监听父页面发送过来的消息 window.addEventListener("message", this.handleMessageFromParent); }, methods: { // 接收父页面发送的消息 handleMessageFromParent(event) { console.log("父页面传过值event", event.data); },
Vue2 子组件(2)
mounted() { //给父页面发送消息 window.parent.postMessage( "111", "http://10.1.0.238:8080/#/application/function" ); window.addEventListener("message", (e) => { //接收父页面发送的消息 console.log("e",e) }) }, methods: {
vue项目嵌套(vue2嵌套vue3)
实现思路
- 产品测评通过 iframe 将产品库加载进来。
- 创建 template.vue 中间页,使用path参数携带产品库url,并通过传递用户信息和路由信息实现产品库自动登陆。
- 使用 postMessgae API 实现父子项目间的数据通信。
实现步骤
创建 Store/template 模块,定义产品库相关数据。
// src/store/modlues/template.ts const state = { sign: 'productLibrary', // 产品库路由标识 routePath: '/template', // 产品库跳转中间页path isFullFrame: false, // 是否设置产品库Iframe全屏 NODE_ENV: process.env.NODE_ENV, // 环境变量 ENV_URLS: { development: 'http://127.0.0.1:3000', test: 'http://cms-test.shenlanbao.com', uat: 'http://cms-uat.shenlanbao.com', production: 'http://cms.shenlanbao.com', }, }; const mutations: MutationTree<any> = { SET_isFullFrame(state, val) { state.isFullFrame = val; }, };
创建 template.vue 中间跳转页路由。
// src/router/modules/page-productLibrary-router.ts { path: '/template', // 对应Store/template.ts 的 routePath name: 'productLibrary', // 对应Store/template.ts 的 sign cn: '产品库', hidden: true, component: () => import('@/views/productLibrary/template.vue'), children: [], },
点击左侧菜单将符合条件的路由跳转到产品库。
// src/components/common/MenuItem.vue <script> methods: { navigateTo(e): void { const { frontPermission, url } = e; const { sign, routePath } = this.$store.state.template; if (frontPermission.includes(sign)) { if (!url) return; this.$router.push({ path: routePath, query: { path: url }, }); } else { if (this.$route.path === url) return; this.$router.push({ path: url }); } }, } </script>
template.vue 监听路由并更新产品库路由,同时传递用户登陆信息实现自动登陆
// src/views/productLibrary/template.vue <div :class="{ wrapper: true, hidemenu: $store.state.template.isFullFrame }"> <iframe :src="src" frameborder="0" class="sub-frame" ref="frames" @load="frameLoad" ></iframe> </div> <script lang="ts"> @Component export default class Index extends Vue { @Ref() readonly frames; src: string = ''; // 产品库跳转路由 // 监听路由,更新产品库路由 @Watch('$route') toggleRoute(data) { this.$nextTick(() => { this.frameLoad(); }); } frameLoad() { const { path = '' } = this.$route.query; const auth = localStorage.getItem('auth') || ''; if (!path || !auth) return; this.setSrc(path); // 设置产品库跳转路由 const productLibraryRoutes = this.getRoutes(); // 过滤产品库路由 const permission = { auth, path, userInfo: localStorage.getItem('userInfo'), routes: productLibraryRoutes[0].childPermissionResList, }; // 传递用户登陆信息实现自动登陆 this.frames.contentWindow.postMessage(permission, '*'); } getRoutes() { const localRoutes = localStorage.getItem('routes') || ''; if (!localRoutes) return ''; const { sign } = this.$store.state.template; // 产品库跳转路由标识 const routes: any[] = JSON.parse(localRoutes) || []; return routes.filter((i) => { return i.frontPermission === sign && i.childPermissionResList; }); } setSrc(path) { const { NODE_ENV, ENV_URLS } = this.$store.state.template; const domain = ENV_URLS[NODE_ENV]; if (path && domain) this.src = `${domain}${path}`; } } </script>
产品库获取登陆信息及路由信息进行模拟登陆操作。
// index.html <body> <div id="app"></div> <script type="module" src="/src/main.ts"></script> <script> window.addEventListener( 'message', function (event) { const { data } = event if (data.auth) { window.localStorage.setItem('auth', data.auth) window.localStorage.setItem('routes', JSON.stringify(data.routes)) window.localStorage.setItem('userInfo', data.userInfo) window.localStorage.setItem('path', data.path) } }, false, ) </script> </body> // src/router/routerGuards.ts export function createRouterGuards(routers: Router) { routers.beforeEach(async (to, _, next) => { const isFrame = JSON.parse(localStorage.getItem('isFrame') || '') // 是否是 iframe 加载 if (!isFrame) { // 正常登陆操作 } else { const hasToken = localStorage.getItem('auth') || '' const { addRouter } = store.state.global if (hasToken && addRouter) { next() return } localStorage.setItem('auth', localStorage.getItem('auth') || '') await store.commit('global/setRoutes', JSON.parse(localStorage.getItem('routes') || '')) await store.commit('global/setUserInfo', JSON.parse(localStorage.getItem('userInfo') || '')) // 路由添加/标记 store.commit('global/isAddRouter', true) const getNewRouter = store.getters['global/getNewRouter'] getNewRouter.forEach((route: Iroute) => { routers.addRoute(route) }) const path = localStorage.getItem('path') to.path = path const newTo = { ...to, name: to.name || undefined, } next({ ...newTo, replace: true }) } }) }
产品库向产品测试进行通信
// src/utils/template.ts // ifarame 内嵌时向产品测评窗口进行通信事件定义 const source = 'productLibrary' // 产品库跳转路由标识 const posMessage = function (config) { const message = Object.assign({ source }, config) return new Promise((resolve) => { top.postMessage(message, '*') resolve(true) }) } // 是否设置全屏遮罩 export function postFrameCover(val) { const config = { fnName: 'setIframeCover', params: val, } return posMessage(config) } // 跳转产品测评页面 export function postTopRouter(path) { if (!path) return const config = { fnName: 'setRoute', params: path, } return posMessage(config) }
问题总结及解决方案
左侧菜单路由高亮状态
展示产品库页面时,对应左侧菜单路由要相应高亮选中。
在监听路由跳转时,判断当跳转产品库页面时,取 query 中的 path 字段为 url 做高亮处理。
// src/components/Nav.vue <script> @Watch('$route', { immediate: true }) handlerRouteChange(to: Route): void { this.active = to.path; this.setProductRoute(to); // 判断产品库路由 } setProductRoute(to) { const { name = '', query = {} } = to; const { sign } = this.$store.state.template; // 产品库跳转路由标识 const isProductLibRoute = name === sign && query.path; if (isProductLibRoute) { this.active = query.path; } } </script>
产品测评面包屑导航页面路径
展示产品库页面时,对应上方面包屑导航要显示正确的路由名称。
在监听路由跳转时,判断当跳转产品库页面时,取 query 中的 path 字段为 url 做高亮处理。
// src/components/common/BaseHeard.vue <script> @Watch('$route') $routeWatch(to: Iobj) { this.getBar(to); } getBar(route) { let pathName = this.getRoutePath(route); let name = ''; let barList: Array<Iobj> = []; let pathArr = pathName.split('/').filter((_) => _); let newArr = [ ...this.getNewRouter, ...(this.$router as any).options.routes, ]; for (let i of pathArr) { name = name + `/${i}`; for (let j of newArr) { if (j.path === name) { barList.push({ cn: j.cn, path: j.path, }); break; } } } this.arr = barList; } // 获取当前路由path getRoutePath(route) { let { path: pathName, query } = route; const { routePath } = this.$store.state.template; // 如果产品库的路由,修改为取query下的path为路由名称 if (pathName === routePath) { const { path = '' } = query; if (path) return path; } else { return pathName; } } </script>
Dialog弹出窗遮罩覆盖
当点击弹窗时,通知父窗口,同步修改外层样式。
// 产品库 <el-button @click="handleMask()">测试弹窗</el-button> <script> import { postFrameCover } from '@utils/template' handleMask() { postFrameCover(true).then(() => { this.showDialog = true }) } </script> // 产品测评 template.vue setIframeCover(val) { this.$store.commit('template/SET_isFullFrame', val); } // 产品测评 App.vue // 修改左侧菜单的z-index,使遮罩在最顶层 <nav class="nav" :class="{ hidemenu: $store.state.template.isFullFrame }"> <app-nav /> </nav> .hidemenu { background: rgba(0, 0, 0, 0.5); z-index: 100; } // 产品测评 template.vue <div :class="{ wrapper: true, hidemenu: $store.state.template.isFullFrame }"> <iframe :src="src" frameborder="0" class="sub-frame" ref="frames" @load="frameLoad" ></iframe> </div> .hidemenu { background: rgba(0, 0, 0, 0.5); z-index: 100; }
产品库子项目跳转产品测评页面
// 产品库 <el-button type="primary" @click="handleArticle()">跳转产品测评文章详情</el-button> <script> import { postTopRouter } from '@utils/template' handleArticle() { const route = { path: '/article/articleAdd', query: { id: '795964024911646720', }, } postTopRouter(route) } </script>
产品库判断当前项目是在iframe中还是web中
// index.html <head> <script> // 判断是在iframe还是web let isFrame = false if (window.frames.length != parent.frames.length) isFrame = true window.localStorage.setItem('isFrame', isFrame) </script> </head>
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。