定义:iframe 元素会创建包含另外一个文档的内联框架(即行内框架),通常我们使用iframe直接在页面嵌套iframe标签指定src就可以了。使用iframe可以实现自由操作iframe和父框架的内容(DOM). 但前提条件是同域. 如果跨域顶多只能实现页面跳转window.location.href.
<iframe
class="scada-content" // 框架作为普通元素的样式
src // 内框架的地址,可以是页面地址,也可以是图片地址
id="sacda-content"
ref="scadaIframeRef">
</iframe>
获取iframe的内容(dom节点提供的方式)
(1)iframe.contentWindow, 获取iframe的window对象(vue框架中iframe元素从而访问)
(2)iframe.contentDocument, 获取iframe的document对象
(3)通过iframe的name属性调用iframe window.frames[iframeName]
获取父元素内容(在同域下可以获取父元素内容)
window.parent 获取上一级的window对象,如果还是iframe则是该iframe的window对象
window.top 获取最顶级容器的window对象,即,就是你打开页面的文档
window.self 返回自身window的引用。可以理解 window===window.self(脑残)
例子:项目中iframe的使用
<iframe
class="scada-content" // 框架作为普通元素的样式
src // 内框架的地址,可以是页面地址,也可以是图片地址,动态设置src
id="sacda-content"
ref="scadaIframeRef">
</iframe>
iframe安全问题
// 在前端领域,我们可以通过window.top来防止我们页面被嵌套
if(window != window.top){
window.top.location.href = correctURL;
}
// 通过window.location.host来检测是否跨域了,限定你的网页不能嵌套在任意网页内。如果你想引用同域的框架的话,可以判断域名
if (top.location.host != window.location.host) {
top.location.href = window.location.href;
}
例子:项目中iframe结合scada的使用
// 在index|preview中全局引入
<script src="/js/scadaConfig.js"></script>
public/js/scadaConfig 文件(组态封装初始化)
function Scada(iframe) {
this.iframe = iframe, //初始化后的iframe元素
this.initOption();//初始化配置项
};
Scada.init = function (iframe) {//初始化实例,iframe为一个iframe元素
if (iframe instanceof HTMLElement && iframe.tagName == 'IFRAME') { // 确保传的是iframe元素
//生成一个独一无二的标记
const sign = 'scada_' + new Date().getTime() + Math.random().toString(36).substr(2);
iframe.setAttribute('sign', sign), // 给iframe设置新属性sign
window[sign] = new Scada(iframe); // 将Scada生成的实例放在window属性上
return window[sign];
} else {
alert('您初始化的不是一个iframe元素');
}
};
Scada.prototype = {
_option: {//默认配置项
serve: '', //工程服务器
project: [],//工程名称
page: '', //(string)页面路径,存放Configure.html或Run.html的路径
/* shapes: {
attrs: {
w: 1500, //(number)画布宽度
ratio: 1500 / 900, //(number)画布宽高比
},
models: [] //(array)图形数据
}, */
dataLists: {},//数据列表
values: {},//实时数据
callback_saveScada: function () {},
saveTemplate: function () {},
copyTemplate: function () {},
showMenu: function (x, y, chain) {},
setValue: function (data, success) {},
bind: function () {},
time: new Date(), //当共享一个iframe实例切换显示多个组态工程时,由于获取组态数据是通过监听serve及project的变化,当serve及project不变时仍需重新加载,在setOption中设置此字段为当前时间
},
initOption: function () {//初始化配置项
this.option = {};
for (var attr in this._option) {
var value = this._option[attr];
this.option[attr] = typeof (value) == 'function' ? value : JSON.parse(JSON.stringify(value));
};
},
setOption:function(option, bool) {//外界传入配置项
option = option || {};
//是否初始化配置项
if (bool) this.initOption();
//判断是否为对象
if (Object.prototype.toString.call(option) != '[object Object]') {
alert('option只能是对象格式!');
return;
};
//更新option
for (var attr in option) {
if (!option[attr]) continue;
//非函数的属性值进行深拷贝
if (typeof (option[attr]) == 'function') {
this.option[attr] = option[attr];
} else {
var value = JSON.parse(JSON.stringify(option[attr]));
if (attr == 'page' && this.option[attr] != value) {
this.iframe.setAttribute('src', option[attr]);
};
this.option[attr] = value;
};
};
}
};
项目中使用
第一次运行挂在完成后的运行
<iframe class="scada-content" src id="sacda-content" ref="scadaIframeRef"></iframe> // 插入内联框架
// 挂在后调用start函数
onMounted(() => {
start()
})
const start = async () => {
state.scadRun = window.Scada.init(scadaIframeRef.value) // 调用组态初始化方法并将iframe元素传入。返回scada实例,此时已初始化配置(例如serve project 等,但还没赋值具体值,具体赋值可利用构造函数Scada原型上的setOption方法)
try {
// 第一次获取懒加载树
const res = await getLazyLoadTree(`?nodeCode=`) // 第一次请求是nodecode是无值
if (res.code == 200) {
state.noEquipmentTree = JSON.parse(JSON.stringify(res.data)) // 拿接口data数据存储在noEquipmentTree变量中
// 默认展开到的层级
const level = 2
let nodeStack = [...state.noEquipmentTree]
while (nodeStack.length > 0) {
const currentNode = nodeStack.pop() // 第一层级的第一项
// 设置默认展开的节点
state.lazyLoadExpandedKey.push(currentNode.node) // 默认展开的节点的 key 的数组
if (currentNode.level < level) { // 若是第一层级则将二层的也连接?????
nodeStack = nodeStack.concat(currentNode.progenies)
}
}
} else {
ElMessage.error('获取懒加载树失败')
}
} catch (error) {
}
}
// 搜索树节点懒加载
// 节点懒加载
const loadNode = (node, resolve) => { // 加载子树数据的方法,仅当 lazy 属性为true 时生效
if (node.level > 0) {
if (node.level < 3) {
return resolve(node.data.progenies || [])
}
getLazyLoadTree(`?nodeCode=${node.data.code}`).then((res) => {
if (res.data) {
resolve(res.data)
} else {
resolve([])
}
})
}
}
// 搜索树过滤方法,通过监听input输入,调用tree实例的filter方法触发定义的过滤方法,第一个参数就是实例filter第一参数
const filterNode = (value, data) => {
if (!value) return true
return data.name.indexOf(value) !== -1
}
watch(() => state.filterPartText, (val) => {
treeRef.value.filter(val)
}, {deep: true})
// 当点击搜索树的处理 (节点被点击时的回调node-click 参数传递给 data 属性的数组中该节点所对应的对象、节点对应的 Node、节点组件本身)
// 获取节点路径,返回数组['node1','node2','node3'...]
const getNodePath = (node) => {
let path = []
const loop = (node) => {
path.push(Array.isArray(node.data) ? node.data[0].name : node.data.name)
if (node.parent && node.level >= 2) {//等于1的时候不可能往上走了
loop(node.parent)
}
}
loop(node)
return path.reverse()
}
const handleNodeClick = async (data, node) => {
state.currentNode = node // 更新当前节点
state.activeNodeRelatedId = data.tuple // 存储当前的tuple字段
state.activeNodeRelatedModel = data.model // 存储model字段
state.activeId = ''
// 设置绑定数据的树需要展开的节点
state.treeExpandedKey.splice(0, 1, data.code)
let projectArray = []
const res = await getScadaByRelationCode({ // 先查询节点是否已绑定组态
relatedId: data.tuple,
relatedModel: data.model,
})
// 已绑定,使用已绑定的路径
if (res.data) {
projectArray = res.data.code.split('/') // 转数组
state.activeId = res.data.id
} else {
projectArray = getNodePath(node) // 未绑定,则使用当前节点的默认路径,返回第一级节点到当前的路径name
}
// 通过获取组态数据判断组态是否存在该组态工程目录(调用后端接口传路径看组态是否在该组态工程目录)
fetchScada({action: 'getScada', project: projectArray}).then((res) => {
const {type} = res.data.notice
const projectTree = getSingleBranchTree(projectArray) // 获取到点击节点的单分支树
// 不存在,则创建工程目录(给后端记录)
if (type === 'error') {
fetchScada({action: 'batchAdd', projects: [projectTree]}).then((res) => {
initScada(projectArray) // 单分支树数组
})
} else {
initScada(projectArray) // 单分支树数组,初始化组态
}
})
}
// 初始化组态
const initScada = (projectArray) => {
timer1 = setTimeout(() => {
state.scadRun = window.Scada.init(scadaIframeRef.value) // 返回scada实例,此时已初始化配置
// 具体赋值可利用构造函数Scada原型上的setOption方法
state.scadRun.setOption(
{
serve: '/scada-backend/',
project: projectArray, // 当前点击的路径数组
page: '/scada-front/Configure.html',
bind: initBindDialog,
// 保存
callback_saveScada: callback_saveScada,
saveTemplate: saveTemplateHandler,
copyTemplate: copyTemplate,
},
)
// 获取组态中已绑定的数据,根据设备编码请求设备信息,并更新组态右侧已绑定数据信息(将设备属性列表更新至组态选项中的dataList中)
fetchScada({action: 'getUsed', project: projectArray})
.then((res) => {
const usedData = res.data.data
if (usedData.length > 0) {
// 设备编码数组
const equipCodeArray = usedData.map((item) => {
return item[0]
})
// 获取已绑定的设备的属性信息
return getEquipInfoList({equipIds: equipCodeArray.join(',')})
}
return Promise.reject('没有绑定数据')
})
.then((res) => {
const equipInfoList = res.data
if (equipInfoList.length > 0) {
const dataList = {}
/* 遍历设备属性列表,组装dataList,
格式:
{
'["EC0303-000001","11200"]': {
name: ['通风机1号', '控制方式'],
write: 0,
unit: '度',
}
} */
equipInfoList.forEach((item) => {
item.attributes.forEach((element) => {
const keyArray = [item.id + '', element.code]
const nameArray = [item.name, element.name]
dataList[JSON.stringify(keyArray)] = {name: nameArray}
})
dataList[JSON.stringify([item.id + '', 'devState'])] = {
name: [item.name, '设备监控状态'],
}
})
Object.assign(state.scadRun.option.dataLists, dataList)
} else {
return Promise.reject('没有设备信息')
}
})
.catch((err) => {
console.warn(err)
})
}, 0)
}
// 保存组态
const callback_saveScada = async () => {
const projectId = state.scadRun.option.project.join('/')
const projectName = state.scadRun.option.project[state.scadRun.option.project.length - 1]
const apiParams = {
id: state.activeId,
code: projectId,
name: projectName,
relatedId: state.activeNodeRelatedId,
relatedModel: state.activeNodeRelatedModel,
}
const res = await createScada(apiParams)
if (res.code == 200) {
state.activeId = res.data
ElMessage.success('保存成功')
} else {
ElMessage.error(res.msg)
}
}
// 保存模板
const saveTemplateHandler = (project) => {
if (!state.activeId) {
ElMessage.warning('请先保存组态')
return
}
ElMessageBox.confirm('确认将该组态设为模板吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(async () => {
const res = await setTemplate(state.activeId)
if (res.code == 200) {
ElMessage.success('设置成功')
} else {
ElMessage.error(res.msg)
}
})
.catch(() => { });
}
// 复制(点击导入模板)
const copyTemplate = (resolve) => {
let currentNode = treeRef.value.getCurrentNode()
copyDialogRef.value.openDialog(currentNode, resolve)
}
// 获取节点单分支树,返回对象{"name":"苏州市","children":[{"name":"苏州监控中心","children":[{"name":"苏州园区综合管廊","children":[{"name":"分区016"}]}]}]}
const getSingleBranchTree = (path) => {
let obj = {}
let arr = JSON.parse(JSON.stringify(path))
arr.reverse()
let y
arr.forEach((item, index) => {
if (index == 0) {
y = {name: item}
} else {
y = {name: item, children: [y]}
}
})
return y
}
树组件后面的小链接按钮功能
<i class="el-icon-paperclip" @click.stop="createRelation(node,data)"></i>
const createRelation = async (node, data) => {
// 先查询节点是否已绑定组态
const res = await getScadaByRelationCode({
relatedId: data.tuple,
relatedModel: data.model,
})
let form = {}
// 已绑定
if (res.data) {
form.projectId = res.data.code
form.id = res.data.id
} else {
form.projectId = ''
form.id = ''
}
form.projectName = data.name
form.relatedId = data.tuple
form.relatedModel = data.model
bindDialogRef.value.openDialog(form) // 触发弹框
}
弹框功能
// 复制弹框 CopyDialog (1)点击复制组态时调用,参数当前节点。弹框功能:选择模板及展示信息及一些操作
const copyTemplate = (resolve) => {
let currentNode = treeRef.value.getCurrentNode()
copyDialogRef.value.openDialog(currentNode, resolve) // 调用节点
}
// BindDialog:组态工程绑定(1)点击树种小链接按钮时调用。弹框功能:选择组态树形结构及组态的相关方法调用
// BindDataDialog:在初始化组态的bind方法中调用。弹框功能:关联设备信号的相关功能及操作