前端的iframe、scada组态(电力系统常用)

定义: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方法中调用。弹框功能:关联设备信号的相关功能及操作
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值