vxiao

悟已往之不谏,知来者之可追。实迷途其未远,觉今是而昨非。

0%

跨iframe通信

场景

门户应用通过嵌套 iframe 访问其他应用,不同页面间需确立通信机制。例如任务处理完成后页签关闭,同时刷新任务列表。

思路

通过 window.postMessage(H5 跨文档通信 API)实现跨 iframe 通信

实现

1.各页面在加载时 向门户声明“当前页面信息”,如

1
2
3
{
id: 'taskcenter'
}

id – 页面的唯一标示

2.应用页面向门户告知,执行的方法

1
2
3
4
5
{
to: 'ofs'
message: 'refreshData'
data: ''
}

to: - 目标页面为模糊匹配

message –与业务模块约定的方法名

data – 传递的数据

3.门户分发到该页面的目标页面

只通知,不监听方法执行成功或失败

代码

新门户通信 JS-SDK

index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/*
* @Description: 新门户API
* @Author: guof
* @Date: 2019-7-1
* @Remark: .vue通过vue原型链调用(组件中使用) .js文件直接import新门户JS-SDK(路由中使用)
*/

import LOPTL from './loptl-jssdk'

/**
* 是否在新门户页签中显示
*/
let isInNewPortalTab = () => {
return LOPTL.isInNewPortalTab()
}
/**
* 打开新页面
* @param {Object} tab 页签属性
*/
let openNewPage = tab => {
LOPTL.openNewPage(tab)
}

/**
* 关闭当前页面
*/
let closeCurrPage = () => {
LOPTL.closeCurrPage()
}

export default {
install(Vue, options) {
Vue.prototype.$LOPTL = LOPTL
Vue.prototype.$isInNewPortalTab = isInNewPortalTab
Vue.prototype.$openNewPage = openNewPage
Vue.prototype.$closeCurrPage = closeCurrPage
}
}

loptl-jssdk.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
/*
* @Description: 新门户通信JS-SDK
* @Author: guof
* @Date: 2019-8-21
*/

/**
* 是否在新门户页签中显示
*/
let isInNewPortalTab = () => {
return self !== top
}
/**
* 监听(仅初始化时调用一次)
*/
let listen = callback => {
window.addEventListener('message', function(event) {
if (typeof callback === 'function') {
// 消息
let data = event.data || {}
// source – 来源窗口的id | message – 操作 | data – 数据
callback(data.message, data.data)
}
})
}
/**
* 初始化当前页面标识
*/
let init = id => {
if (isInNewPortalTab()) {
window.parent.postMessage({ id: id }, '*')
}
}
/**
* 打开新页面
* @param {Object} tab 页签属性
*/
let openNewPage = tab => {
// 无效页签
if (Object.prototype.toString.call(tab) !== '[object Object]') {
console.error('无效的新页面参数.')
return 0
}
// 缺少参数
if (!tab.title || !tab.url) {
console.error('新页面缺少参数.')
return 0
}
// 在新门户页签内显示
if (isInNewPortalTab()) {
window.parent.postMessage(tab, '*')
}
// 单独窗口显示
if (!isInNewPortalTab()) {
window.open(tab.url, tab.name)
}
}
/**
* 关闭当前页面
*/
let closeCurrPage = () => {
// 在新门户页签内显示
if (isInNewPortalTab()) {
window.parent.postMessage('close', '*')
return 0
}
// 单独窗口显示
if (!isInNewPortalTab()) {
window.close()
}
}
/**
* 刷新目标窗口
*/
let refresh = (to, data) => {
let msg = {
to: to,
message: 'refresh',
data: data
}
if (isInNewPortalTab()) {
window.parent.postMessage(msg, '*')
}
}
/**
* 关闭当前页面并且刷新目标窗口
* @param {Array} to 目标窗口集合
* @param {Object} data 数据
*/
let refreshAndCloseTab = (to, data) => {
let msg = [
{
to: to,
message: 'refresh',
data: data
},
{
to: ['portal'],
message: 'close',
data: {}
}
]
if (isInNewPortalTab()) {
window.parent.postMessage(msg, '*')
} else {
window.close()
}
}
/**
* 门户提示消息
*/
let showMessage = param => {
if (!param) {
return false
}
let msg = {
to: ['portal'],
message: 'showMessage',
data: param
}
if (isInNewPortalTab()) {
window.parent.postMessage(msg, '*')
} else {
alert(param.message)
}
}
/**
* 门户便签消息
*/
let showNotive = param => {
if (!param) {
return false
}
let msg = {
to: ['portal'],
message: 'showNotive',
data: param
}
if (isInNewPortalTab()) {
window.parent.postMessage(msg, '*')
} else {
alert(param.message)
}
}
/**
* 自定义通信事件
* @param {Array} to 目标窗口集合
* @param {String} message 操作方法
* @param {Object} data 数据
*/
let send = (to, message, data) => {
let msg = {
to: to,
message: message,
data: data
}
if (isInNewPortalTab()) {
window.parent.postMessage(msg, '*')
}
}
/**
* 多个消息通信事件
* @param {Array} msgList 消息集合
*/
let sendMore = msgList => {
if (isInNewPortalTab()) {
window.parent.postMessage(msgList, '*')
}
}
export default {
isInNewPortalTab,
init,
listen,
openNewPage,
closeCurrPage,
showMessage,
showNotive,
send,
sendMore,
refreshAndCloseTab,
refresh
}

新门户消息分发

存在代码冗余 为兼容之前不完整的通信方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
listen () {
window.addEventListener('message', (event) => {
// 无效的事件包
if (this.isInvalidEvent(event)) {
return false
}
let actions = {
'1': this.closeCurrTab,
'2': this.openNewTab,
'3': this.initTabTarget,
'4': this.sendEventMsg,
'5': this.sendEventPackage
}
// 判断操作类型
let op = this.getPageOperation(event)
// 执行操作
if (typeof (actions[op]) === 'function') {
actions[op](event)
}
})
},
/**
* 获取页签操作
*/
getPageOperation (event) {
let data = event.data || {}
// 关闭页签
if (data === 'close' || data.op === 'close') {
return '1'
}
// 打开新页签
if (data.url) {
return '2'
}
// 应用注册
if (data.id) {
return '3'
}
// 自定义事件
if (data.message) {
return '4'
}
// 事件组合
if (Array.isArray(data)) {
return '5'
}
},
/**
* 自定义事件通信
*/
sendEventMsg (event) {
let eventData = event.data || {}
let targetNameList = eventData.to
// 数组
if (!Array.isArray(targetNameList)) {
return false
}
targetNameList.forEach(targetName => {
this.sendMsgToWindow(targetName, eventData, event)
})
},
/**
* 自定义事件包
*/
sendEventPackage (event) {
let eventData = event.data || {}
// 多事件遍历
eventData.forEach(item => {
let targetNameList = item.to
if (Array.isArray(targetNameList)) {
// 发送窗口遍历
targetNameList.forEach(targetName => {
this.sendMsgToWindow(targetName, item, event)
})
}
})
},
/**
* 向窗口发送事件
*/
sendMsgToWindow (targetName, eventData, event) {
// 门户内页签消息
if (targetName !== 'portal') {
// ...寻找iframe代码省略 可根据id
iframe.contentWindow.postMessage({ message: eventData.message, data: eventData.data }, '*')
}
}

如何判断message来源哪个iframe?

event.source与iframe.contentWindow比较

1
2
3
4
5
window.addEventListener("message", (event) => {
if(event.source === iframe.contentWindow){
//iframe.contentWindow为源窗口
}
})

::: warning
考虑安全性 可判断event.origin是否为可信域名
:::