vue3中如何使用iframe嵌入vue2頁面
vue3使用iframe嵌入vue2頁面
vue3中使用iframe嵌入vue2頁面或vue頁面和iframe頁面的相互傳參問題
記錄一個開發(fā)過程中遇到的問題,Vue3項(xiàng)目中嵌入了一個iframe,iframe里面的是一個vue2項(xiàng)目的頁面,是項(xiàng)目趕工計較著急,來不及把vue2集成到vue3中,這時候想到用Vue3和Vue2之間來回互調(diào)傳參,下面提供解決思路。
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("子頁面?zhèn)鬟^值event", event.data);
var datalist = event.data;
if (datalist) {
sendMessage();
}
}
// 調(diào)用vue中g(shù)etCurrentInstance方法(此參考V2和V3中this.$refs區(qū)別)
const currentInstance = getCurrentInstance();
function sendMessage() {
// currentInstance.ctx.$refs是vue3的寫法。vue2中是 this.$refs
if (currentInstance.ctx.$refs.iframeRef) {
//判斷他不為空,可根據(jù)自己的頁面判斷
currentInstance.ctx.$refs.iframeRef.contentWindow.postMessage(
// 向子頁面iframe傳遞
"aaa", //傳遞的參數(shù)
"http://10.1.0.238:88/#/workspace/formsPanel" //iframe的頁面地址
);
}
}
nextTick(() => {
window.addEventListener("message", handleMessage);
});
return{
handleMessage,
currentInstance,
sendMessage
}
}}
</script>Vue2 子組件(1)
mounted() {
//給父頁面發(fā)送消息
window.parent.postMessage(
"111",
"http://10.1.0.238:8080/#/application/function"
);
//監(jiān)聽父頁面發(fā)送過來的消息
window.addEventListener("message", this.handleMessageFromParent);
},
methods: {
// 接收父頁面發(fā)送的消息
handleMessageFromParent(event) {
console.log("父頁面?zhèn)鬟^值event", event.data);
},Vue2 子組件(2)
mounted() {
//給父頁面發(fā)送消息
window.parent.postMessage(
"111",
"http://10.1.0.238:8080/#/application/function"
);
window.addEventListener("message", (e) => {
//接收父頁面發(fā)送的消息
console.log("e",e)
})
},
methods: {vue項(xiàng)目嵌套(vue2嵌套vue3)
實(shí)現(xiàn)思路
- 產(chǎn)品測評通過 iframe 將產(chǎn)品庫加載進(jìn)來。
- 創(chuàng)建 template.vue 中間頁,使用path參數(shù)攜帶產(chǎn)品庫url,并通過傳遞用戶信息和路由信息實(shí)現(xiàn)產(chǎn)品庫自動登陸。
- 使用 postMessgae API 實(shí)現(xiàn)父子項(xiàng)目間的數(shù)據(jù)通信。
實(shí)現(xiàn)步驟
創(chuàng)建 Store/template 模塊,定義產(chǎn)品庫相關(guān)數(shù)據(jù)。
// src/store/modlues/template.ts
const state = {
sign: 'productLibrary', // 產(chǎn)品庫路由標(biāo)識
routePath: '/template', // 產(chǎn)品庫跳轉(zhuǎn)中間頁path
isFullFrame: false, // 是否設(shè)置產(chǎn)品庫Iframe全屏
NODE_ENV: process.env.NODE_ENV, // 環(huán)境變量
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;
},
}; 創(chuàng)建 template.vue 中間跳轉(zhuǎn)頁路由。
// src/router/modules/page-productLibrary-router.ts
{
path: '/template', // 對應(yīng)Store/template.ts 的 routePath
name: 'productLibrary', // 對應(yīng)Store/template.ts 的 sign
cn: '產(chǎn)品庫',
hidden: true,
component: () => import('@/views/productLibrary/template.vue'),
children: [],
}, 點(diǎn)擊左側(cè)菜單將符合條件的路由跳轉(zhuǎn)到產(chǎn)品庫。
// 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 監(jiān)聽路由并更新產(chǎn)品庫路由,同時傳遞用戶登陸信息實(shí)現(xiàn)自動登陸
// 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 = ''; // 產(chǎn)品庫跳轉(zhuǎn)路由
// 監(jiān)聽路由,更新產(chǎn)品庫路由
@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); // 設(shè)置產(chǎn)品庫跳轉(zhuǎn)路由
const productLibraryRoutes = this.getRoutes(); // 過濾產(chǎn)品庫路由
const permission = {
auth,
path,
userInfo: localStorage.getItem('userInfo'),
routes: productLibraryRoutes[0].childPermissionResList,
};
// 傳遞用戶登陸信息實(shí)現(xiàn)自動登陸
this.frames.contentWindow.postMessage(permission, '*');
}
getRoutes() {
const localRoutes = localStorage.getItem('routes') || '';
if (!localRoutes) return '';
const { sign } = this.$store.state.template; // 產(chǎn)品庫跳轉(zhuǎn)路由標(biāo)識
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> 產(chǎn)品庫獲取登陸信息及路由信息進(jìn)行模擬登陸操作。
// 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') || ''))
// 路由添加/標(biāo)記
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 })
}
})
} 產(chǎn)品庫向產(chǎn)品測試進(jìn)行通信
// src/utils/template.ts
// ifarame 內(nèi)嵌時向產(chǎn)品測評窗口進(jìn)行通信事件定義
const source = 'productLibrary' // 產(chǎn)品庫跳轉(zhuǎn)路由標(biāo)識
const posMessage = function (config) {
const message = Object.assign({ source }, config)
return new Promise((resolve) => {
top.postMessage(message, '*')
resolve(true)
})
}
// 是否設(shè)置全屏遮罩
export function postFrameCover(val) {
const config = {
fnName: 'setIframeCover',
params: val,
}
return posMessage(config)
}
// 跳轉(zhuǎn)產(chǎn)品測評頁面
export function postTopRouter(path) {
if (!path) return
const config = {
fnName: 'setRoute',
params: path,
}
return posMessage(config)
} 問題總結(jié)及解決方案
左側(cè)菜單路由高亮狀態(tài)
展示產(chǎn)品庫頁面時,對應(yīng)左側(cè)菜單路由要相應(yīng)高亮選中。
在監(jiān)聽路由跳轉(zhuǎn)時,判斷當(dāng)跳轉(zhuǎn)產(chǎn)品庫頁面時,取 query 中的 path 字段為 url 做高亮處理。
// src/components/Nav.vue
<script>
@Watch('$route', { immediate: true })
handlerRouteChange(to: Route): void {
this.active = to.path;
this.setProductRoute(to); // 判斷產(chǎn)品庫路由
}
setProductRoute(to) {
const { name = '', query = {} } = to;
const { sign } = this.$store.state.template; // 產(chǎn)品庫跳轉(zhuǎn)路由標(biāo)識
const isProductLibRoute = name === sign && query.path;
if (isProductLibRoute) {
this.active = query.path;
}
}
</script> 產(chǎn)品測評面包屑導(dǎo)航頁面路徑
展示產(chǎn)品庫頁面時,對應(yīng)上方面包屑導(dǎo)航要顯示正確的路由名稱。
在監(jiān)聽路由跳轉(zhuǎn)時,判斷當(dāng)跳轉(zhuǎn)產(chǎn)品庫頁面時,取 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;
}
// 獲取當(dāng)前路由path
getRoutePath(route) {
let { path: pathName, query } = route;
const { routePath } = this.$store.state.template;
// 如果產(chǎn)品庫的路由,修改為取query下的path為路由名稱
if (pathName === routePath) {
const { path = '' } = query;
if (path) return path;
} else {
return pathName;
}
}
</script> Dialog彈出窗遮罩覆蓋
當(dāng)點(diǎn)擊彈窗時,通知父窗口,同步修改外層樣式。
// 產(chǎn)品庫
<el-button @click="handleMask()">測試彈窗</el-button>
<script>
import { postFrameCover } from '@utils/template'
handleMask() {
postFrameCover(true).then(() => {
this.showDialog = true
})
}
</script>
// 產(chǎn)品測評 template.vue
setIframeCover(val) {
this.$store.commit('template/SET_isFullFrame', val);
}
// 產(chǎn)品測評 App.vue
// 修改左側(cè)菜單的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;
}
// 產(chǎn)品測評 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;
} 產(chǎn)品庫子項(xiàng)目跳轉(zhuǎn)產(chǎn)品測評頁面
// 產(chǎn)品庫
<el-button type="primary" @click="handleArticle()">跳轉(zhuǎn)產(chǎn)品測評文章詳情</el-button>
<script>
import { postTopRouter } from '@utils/template'
handleArticle() {
const route = {
path: '/article/articleAdd',
query: {
id: '795964024911646720',
},
}
postTopRouter(route)
}
</script> 產(chǎn)品庫判斷當(dāng)前項(xiàng)目是在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> 總結(jié)
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Vue使用wangeditor創(chuàng)建富文本編輯器的完整指南
WangEditor是一個開源的富文本編輯器,由阿里云開發(fā),它提供了一套簡潔易用的API和豐富的功能,如拖拽上傳圖片、插入表格、自定義表情等,適用于網(wǎng)頁和移動應(yīng)用中的內(nèi)容編輯場景,本文介紹了Vue使用wangeditor創(chuàng)建富文本編輯器的完整指南,需要的朋友可以參考下2024-08-08
Vue中使用event的坑及解決event is not defined
這篇文章主要介紹了Vue中使用event的坑及解決event is not defined,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03
在vue中,v-for的索引index在html中的使用方法
下面小編就為大家分享一篇在vue中,v-for的索引index在html中的使用方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-03-03
vue2 拖動排序 vuedraggable組件的實(shí)現(xiàn)
這篇文章主要介紹了vue2 拖動排序 vuedraggable組件的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
vue element-ui導(dǎo)航實(shí)現(xiàn)全屏/取消全屏功能
這篇文章主要介紹了vue element-ui導(dǎo)航實(shí)現(xiàn)全屏/取消全屏功能,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08
Vue中實(shí)現(xiàn)3D標(biāo)簽云的詳細(xì)代碼
本文通過實(shí)例代碼給大家介紹vue實(shí)現(xiàn)3D標(biāo)簽云的方法,代碼簡單易懂,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2021-07-07
Vue裝飾器中的vue-property-decorator?和?vux-class使用詳解
這篇文章主要介紹了Vue裝飾器中的vue-property-decorator?和?vux-class使用詳解,通過示例代碼給大家介紹的非常詳細(xì),對vue-property-decorator?和?vux-class的使用感興趣的朋友一起看看吧2022-08-08
Vue使用Pinia輕松實(shí)現(xiàn)狀態(tài)管理
pinia,一個基于Vue3的狀態(tài)管理庫,它可以幫助開發(fā)人員管理Vue應(yīng)用程序的狀態(tài),本文主要為大家介紹了Pinia的用法,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-06-06

