在WinCC Unified中使用自定义Web控件实现报表功能
基于西门子官方示例中的表格工具,扩展部分功能,初步实现报表功能。
1 西门子表格控件
官方的table控件采用了JavaScript库“Tabulator”(详见http://tabulator.info),并通过补充代码使其适用于WinCC Unified环境。
1.1 目录结构
用VS Code打开解压的文件夹,观察目录结构。
a. 文件名为“manifest.json”的文件,是控件与西门子WinCC进行交互的描述清单,包含了控件名称、版本、属性、方法、事件等的定义。
b. 文件名为“CWC_manifest_Schema.json”的文件,是“manifest.json”文件的注释和限制文件,一般不需要修改。可以用AI将文件中的"title"属性翻译成中文,这样在“manifest.json”文件中鼠标停留在标签上,会显示注释,程序更易读一些。
c. 名为“assets”的文件夹。可在此文件夹中以任意图形格式放置图标,该图标将在此控件的TIA门户中显示。
d. 名称为“control”的文件夹。此文件夹中用来放置“index.html”、“webcc.min.js”文件和其他附属库文件。“index.html”是控件的起始画面,可以在“manifest.json”文件中进行定义。“webcc.min.js”是WinCC与控件交互的底层逻辑,不能进行修改。其他附属库文件,西门子将JavaScript库“Tabulator”放置于此文件夹。
1.2 manifest.json文件
打开manifest.json文件,包含控件的描述和属性、方法、事件等接口的定义,是与WinCC进行交互的关键。此文件不允许有注释。
|
1 2 3 4 5 6 7 |
"contracts": { "api": { "methods": {},方法 "events": {},事件 "properties": {}属性 } } |
1.3 code.js文件
打开control目录下的code.js文件,里面有对manifest.json文件中定义的接口的具体实现。使用函数“WebCC.start();”开始连接配置。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
WebCC.start(function(result) { //可以把初始化的代码放到这里 if (result) { if (WebCC.isDesignMode) { //判断是否是设计模式 } else {} } else { console.log('connection failed'); } }, // contract (与manifest.json中的接口保持一致) { // 方法 methods: {}, // 事件 events: [], // 属性 properties: {} }, [], // connection timeout 10000); |
1.4 index.html文件
初始文件,可以看到引用了两个js文件。后续的程序修改中,我们修改code.js即可
|
1 2 |
<script src='./webcc.min.js'></script> <script src='./code.js'></script> |
2 报表控件
想实现报表控件基本功能:数据查询,数据显示,数据导出。
2.1 数据查询
我们要实现数据查询,并不能直接通过控件访问历史数据库,JS没有合适的库去做这个功能,而是需要通过WinCC的HMIRuntime.TagLogging.CreateLoggedTagSet()函数来实现。
所以我们要在控件中点击“查询”按钮来查询某一时间段的数据变化,那我们需要进行以下操作。
2.1.1 触发事件
事件需要在index.html、manifest.json和code.js里面进行修改。
index.html文件在<body>标签里面加入以下代码,当点击按钮的时候,就会调用"_getTableData()"函数。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<div id="controls" style="margin-bottom: 5px;"> <p> 起始时间: <input type="datetime-local" id="strdatepicker"> </p> <p> 结束时间: <input type="datetime-local" id="enddatepicker"> </p> <button onclick="_getTableData()"> 获取数据 </button> </div> |
code.js文件修改如下
|
1 |
events: ['GetTableData'] |
同时在最底部追加以下代码。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// 获取表格数据 function _getTableData() { if (!window.currentTableInstance) { console.warn("表格尚未初始化"); return; } const strdateInput = document.getElementById('strdatepicker').value.replace('T', ' '); const enddateInput = document.getElementById('enddatepicker').value.replace('T', ' '); TableHeaderData=["HMI_Tag_1:记录变量_1","HMI_Tag_2:记录变量_1","HMI_Tag_3:记录变量_1"]; //alert(strdateInput); //WebCC.Events.fire("GetTableData", TableHeaderData,strdateInput.toString("yyyy-MM-dd HH:mm:ss"), enddateInput.toString("yyyy-MM-dd HH:mm:ss")); var eventData = { headerdata: TableHeaderData, strdate: strdateInput ? strdateInput + ':00' : '', enddate: enddateInput ? enddateInput + ':59' : '' }; WebCC.Events.fire("GetTableData", eventData); } |
这里的变量名,是固定的值,后期可以设置为控件的属性值,方便使用者使用。WebCC.Events.fire是用来触发报表事件的函数,触发之后就可以在WinCC里面执行后续的代码。
这里需要注意一下,经过测试即便是传递了再多参数,实际上只有第一个参数能够传递成功。因为这里需要传递的是多个变量,包括起始时间、结束时间、变量的名称等信息,因此我们需要使用集合的方式进行数据传递。
别忘了修改manifest.json,修改如下
|
1 2 3 4 5 6 7 8 9 10 |
"events": { "GetTableData": { "arguments": { "TransData": { "type": "string" } }, "description": "获取表数据触发事件,需要在触发事件后调用SetTableData方法" } } |
2.1.2 查询数据
查询数据要放到WinCC里面执行。需要在报表控件的事件里面找到'GetTableData',在里面进行数据的查询。代码如下
|
1 2 3 4 5 6 7 8 |
//HMIRuntime.Trace("Time " + TransData.headerdata + " | " + TransData.strdate + " | " + TransData.enddate); let hdata=TransData.headerdata.toString(); let logTagSet = HMIRuntime.TagLogging.CreateLoggedTagSet(hdata.split(",")); let end = new Date(TransData.enddate); let begin = new Date(TransData.strdate); let loggedTagMap =await logTagSet.Read(begin, end, false); |
2.1.3 数据返回报表
数据的返回是以JSON文本的方式,但是因为logTagSet.Read()的返回值无法进行JSON序列化,所以只能手动转JSON文本。
|
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 |
const resultArray = []; for (let loggedTag of loggedTagMap) { // 提取单个标签的数据 const tagData = { Name: loggedTag.Name, Error: loggedTag.Error, Values: [] }; // 遍历 Values 集合 for (let loggedTagValue of loggedTag.Values) { tagData.Values.push({ Value: loggedTagValue.Value, Quality: loggedTagValue.Quality, TimeStamp: new Date(loggedTagValue.TimeStamp), Flags: loggedTagValue.Flags // 说明字段通常不需要传输,如需可加上 }); } resultArray.push(tagData); // 调试输出 //HMIRuntime.Trace("Name:" + loggedTag.Name); //for (let loggedTagValue of loggedTag.Values) { // HMIRuntime.Trace("Value:" + loggedTagValue.Value + " Quality:" + loggedTagValue.Quality + " TS:" + loggedTagValue.TimeStamp + " Flags:" + loggedTagValue.Flags); //} } // 序列化成完整 JSON 字符串,原样传给前端 const rawJson = JSON.stringify(resultArray); HMIRuntime.Trace(rawJson); await Screen.Items("Report table CustomWebControl_1").SetTableData(rawJson); |
2.2 数据显示
WinCC的表格示例控件已经能够很好进行数据显示。但是我们需要根据返回的JSON文本,转换成“Tabulator”库可以理解的代码。
我们需要添加一个方法SetTableData。需要在manifest.json和code.js里面进行修改。
code.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 |
/** * 【自动表格】设置表格数据方法 * @param {string} tableDataString - 表格数据的 JSON 字符串 */ SetTableData: function(tableDataJSON){ if(!tableDataJSON) return; // 1. 还原原始LoggedTagMap数据 const loggedTagMap = JSON.parse(tableDataJSON); // 2. 取出所有标签名(作为后续列) const tagNames = Object.keys(loggedTagMap); // 3. 用字典按时间聚合:key=时间字符串,value=行对象 const timeMap = {}; // 遍历每个标签 tagNames.forEach(tagName => { const tagResult = loggedTagMap[tagName]; // 遍历该标签的每一条历史值 tagResult.Values.forEach(item => { // 统一时间字符串作为唯一键 const timeKey = new Date(item.TimeStamp).toISOString(); // 不存在这个时间就新建一行,第一列固定time if(!timeMap[timeKey]){ timeMap[timeKey] = { time: timeKey }; } // 给当前标签字段赋值 timeMap[timeKey][tagName] = item.Value; }); }); // 4. 转成数组,作为表格数据源 tableData_ = Object.values(timeMap); // 5. 动态生成列样式配置 (Column Style) // 第一列固定为时间列 const dynamicColumns = [ { title: "Time", field: "time", sorter: "string", width: 200, hozAlign: "left" } ]; // 后续列为动态标签列 tagNames.forEach(tagName => { dynamicColumns.push({ title: tagName, field: tagName, sorter: "number", // 假设记录变量多为数值,可根据需要改为 "string" 或 "auto" hozAlign: "right", width: 150 }); }); // 6. 将生成的列配置赋值给临时变量 columnStyle_ = dynamicColumns; // 7. 重绘表格 drawTable(false); columnStyle_ = null; tableData_ = null; } |
别忘了在manifest.json声明一下
|
1 2 3 4 5 6 7 8 9 10 |
"methods": { "SetTableData": { "parameters": { "tableDataJSON": { "type": "string" } }, "description": "设置表数据方法,需要在GetTableData事件触发后调用" } } |
2.3 数据导出
JavaScript库“Tabulator”自带表格导出功能,但是需要附加几个js库。这几个库可以在网络上搜索到。
需要在index.html文件的<head>标签里面增加这几个js的引用
|
1 2 3 4 5 6 7 8 |
<!-- SheetJS (用于 XLSX 导出) --> <script src="xlsx.full.min.js"></script> <!-- jsPDF (用于 PDF 导出) --> <script src="jspdf.umd.min.js"></script> <!-- jsPDF AutoTable (用于 PDF 表格生成) --> <script src="jspdf.plugin.autotable.min.js"></script> |
同时在<body>标签里面增加以下代码,可以放到表格的前面。
|
1 2 3 4 5 6 7 8 9 10 11 |
<div id="controls" style="margin-bottom: 5px;"> <button onclick="_exportTable('csv', 'table_data')"> 导出 CSV </button> <button onclick="_exportTable('xlsx', 'table_data')"> 导出 Excel </button> <button onclick="_exportTable('pdf', 'table_data')"> 导出 PDF </button> </div> |
在code.js里面增加"_exportTable“函数。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
/** * 导出表格数据 * @param {string} type - 导出格式: 'csv', 'xlsx', 'pdf', 'json' * @param {string} filename - 文件名 (不含扩展名) */ function _exportTable(type, filename) { if (!window.currentTableInstance) { console.warn("表格尚未初始化"); return; } // 默认文件名 var name = filename || " TableExport"; if (window.currentTableInstance) { try { window.currentTableInstance.download(type, name + "." + type, {}); } catch (e) { console.error("导出失败:", e); alert("导出失败"); } } else { alert("表格未加载或 WebCC 未初始化"); } } |
根据jsPDF版本的不一样,有的时候window.jspdf.js会被加载成window.jspdf.jsPDF,这里需要加一个小代码确保jsPDF 可用。
|
1 2 3 4 5 6 7 8 9 |
// 确保 jsPDF 全局变量可用 (function() { if (typeof window.jsPDF === 'undefined') { if (window.jspdf && window.jspdf.jsPDF) { window.jsPDF = window.jspdf.jsPDF; //console.log("jsPDF 兼容性补丁已应用"); } } })(); |
相关链接
WinCCUnifiedV20_自定义网络控件-WinCC Unified V20 基本功能简介-系列课程-西门子1847工业学习平台官网
将用户自定义控件集成到 WinCC Unified 中(自定义 Web 控件) - ID: 109779176 - Industry Support Siemens