HMI 设计器教程 (完整版)
本文档以 菜鸟教程风格 编写, 每个概念包含: 解释 + 参数表 + 代码示例 + 常见错误。适合从零开始学习 Darra HMI 设计, 也可作为日常开发速查手册。
1. HMI 设计入门
1.1 HMI 在工业自动化中的作用
HMI (Human Machine Interface, 人机界面) 是操作员与 PLC 控制系统之间的桥梁。在 DarraRT 中, HMI 负责:
| 功能 | 说明 | 示例 |
|---|---|---|
| 状态监控 | 实时显示 PLC 变量值 | 温度、压力、转速、液位 |
| 操作控制 | 向 PLC 写入数据 | 启动/停止电机、设定目标值 |
| 报警管理 | 显示、确认、归档报警 | 超温报警、急停触发 |
| 数据趋势 | 历史数据曲线可视化 | 过去 2 小时温度趋势 |
| 配方管理 | 切换工艺参数组 | 不同产品规格的参数集 |
| 权限控制 | 限定操作员可执行的操作 | 操作员只能启停, 工程师能改参数 |
HMI 与 PLC 的关系:
操作员 ──操作──→ HMI 界面 ──写入──→ PLC 变量 ──控制──→ 现场设备
↑ │
└──────────── 读取 ──────────────┘
关键设计原则:
- HMI 只是"显示层", 不执行控制逻辑 (控制逻辑在 PLC 中)
- HMI 挂掉不影响 PLC 扫描运行
- 一个工程可同时有 LocalHMI (本地全屏) 和 WebHMI (远程浏览器) 两份独立界面
1.2 Darra HMI 架构 (WebView2 + WinForms)
Darra HMI 使用 纯 Web 技术栈 (HTML + CSS + JavaScript), 通过 WebSocket 与 PLC Service 实时通信。
┌────────────────────────────────────────────────────────────────┐
│ LocalHMI (Darra.PLC.HMI.exe) │
│ WinForms + WebView2 全屏外壳 │
│ F11 全屏 / F5 刷新 / ESC 退出 / 自动重连 │
└──────────────────────────┬─────────────────────────────────────┘
│ 加载
▼
┌────────────────────────────────────────────────────────────────┐
│ HmiWebServer (端口 18823) │
│ /local/{route} LocalHMI 页面 │
│ /web/{route} WebHMI 页面 │
│ /static/* 静态资源 (alpine.js, darra-plc.js 等) │
│ /ws WebSocket (变量订阅/推送/写入) │
│ /api/hmi/* 管理 API (登录/DB描述/列页面) │
└──────────────────────────┬─────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────┐
│ Darra.PLC.Service (永驻进程) │
│ HmiBroadcaster 变量推送节流合批 │
│ HmiVariableBridge PLC ↔ JSON 变量读写桥 │
│ HmiPythonBridge HMI 调 Python 脚本 │
│ HmiAuthProvider 登录/角色/Token │
└──────────────────────────┬─────────────────────────────────────┘
│ (共享内存)
▼
┌────────────────────────────────────────────────────────────────┐
│ PLC 运行时 (内核态, 扫描周期 1-100ms) │
└────────────────────────────────────────────────────────────────┘
LocalHMI vs WebHMI 对比:
| 属性 | LocalHMI | WebHMI |
|---|---|---|
| 访问方式 | 本地 WebView2 全屏 | 浏览器 http://ip:18823/web/... |
| 外壳 | Darra.PLC.HMI.exe | 无, 浏览器直接访问 |
| 典型终端 | 工控机 / 触摸屏 / 工位 Panel | 移动端 / 办公电脑 / 外部大屏 |
| 认证 | 可选 (局域网直接登录) | 强制 (RequireAuth = true) |
| 写入白名单 | 通常宽松 | 强烈建议启用 |
| 页面路由 | /local/{route} | /web/{route} |
1.3 三种制作方式
Darra HMI 提供三种制作方式, 可根据场景灵活选择:
| 方式 | 适用场景 | 难度 | 推荐度 |
|---|---|---|---|
| 拖拽式 (FreeForm) | 快速搭建, 非开发者 | ⭐ | ⭐⭐⭐⭐⭐ |
| 代码式 (Code) | 复杂逻辑, 自定义样式 | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 混合模式 | 先拖后补逻辑 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
经验: 90% 的页面用拖拽搭骨架, 再用代码式补 JS 逻辑。这是最有效率的工作流。
1.4 第一个 HMI 项目
步骤 1: 新建 HMI 项目
- 在 IDE 中打开或新建一个 PLC 项目
- 左侧项目树 → 右键 "HMI 设计" 节点 → "新建 LocalHMI 项目" (或 WebHMI 项目)
- 输入项目名称, 选择设计尺寸 (默认 1920×1080)
步骤 2: 添加第一个页面
- 在 HMI 项目节点上右键 → "新建 HMI 页面"
- 设置页面名称:
首页, 路由:/, 勾选 "设为首页" - 双击页面节点打开设计器
步骤 3: 拖入控件
- 左侧 Block 面板找到 "基础控件 → 按钮"
- 拖拽到画布上
- 右侧 Trait 面板设置:
var:M0.0(绑定到 PLC 位变量)mode:toggle(切换模式)- 控件的文本:
启动/停止
步骤 4: 添加指示灯
- 拖入 "基础控件 → LED" 到画布
- 设置
var:M0.0(与按钮绑定同一变量) - 设置
color-on:#00FF00(绿色) - 设置
color-off:#666666(灰色)
步骤 5: 添加数值显示
- 拖入 "基础控件 → Numeric" 到画布
- 设置
var:DB1.Temperature - 设置
format:0.00 - 设置
unit:℃
步骤 6: 保存并预览
- Ctrl+S 保存项目
- 菜单
运行 → 启动 PLC Service - 点击设计器顶部工具栏的 "预览" 按钮
- 浏览器打开
http://localhost:18823/local/
完整页面代码 (如果你用代码模式):
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<title>我的第一个 HMI 页面</title>
<link rel="stylesheet" href="/static/darra-hmi.css" />
<script src="/static/alpine.min.js" defer></script>
<script src="/static/darra-plc.js"></script>
</head>
<body class="theme-modern">
<h1>1# 反应釜监控</h1>
<!-- 启动/停止按钮, 绑定 M0.0 -->
<darra-button var="M0.0" mode="toggle">启动/停止</darra-button>
<!-- 运行指示灯, 与按钮同一变量 -->
<darra-led var="M0.0" color-on="#00FF00" color-off="#666"></darra-led>
<!-- 温度显示 -->
<div>
温度: <darra-numeric var="DB1.Temperature" format="0.00" unit="℃"></darra-numeric>
</div>
<!-- 压力显示 -->
<div>
压力: <darra-numeric var="DB1.Pressure" format="0.00" unit="MPa"></darra-numeric>
</div>
</body>
</html>
常见错误: 预览时看不到控件
| 错误 | 原因 | 解决 |
|---|---|---|
| 画布空白 | Service 未启动 | 运行 → 启动 PLC Service |
| 控件显示 "未定义" | 变量名拼写错误 | 检查 var 属性是否与 PLC 变量名一致 |
| 按钮点击无反应 | 变量不在写入白名单 | 在项目设置中配置 WriteWhitelist |
| 右下角显示 "断开" | 端口 18823 被防火墙拦截 | 检查防火墙规则 |
2. HMI 控件大全
Darra HMI 提供 36 个自定义 HTML 元素 (<darra-*>), 分为 11 大类。每个控件包含: 属性表 + 示例 + 常见错误。
2.1 基础控件
<darra-button> — 按钮
按钮用于触发 PLC 操作, 支持两种模式: toggle (切换) 和 momentary (点动)。
属性表:
| 属性 | 必填 | 默认值 | 说明 |
|---|---|---|---|
var | 是 | — | 绑定的 PLC 位变量 (如 M0.0) |
mode | 否 | toggle | toggle 翻转 / momentary 按下即松 (200ms 后自动置 0) |
label | 否 | 子节点文本 | 按钮文字 |
color | 否 | — | 按下时的颜色 |
disabled | 否 | false | 是否禁用 |
size | 否 | medium | small / medium / large |
示例:
<!-- 切换按钮: 点击翻转 M0.0 的状态 -->
<darra-button var="M0.0" mode="toggle">启动/停止</darra-button>
<!-- 点动按钮: 按下时 M0.1 为 true, 松开后 200ms 自动变 false -->
<darra-button var="M0.1" mode="momentary">点动</darra-button>
<!-- 禁用按钮: 当 M10.0 为 true 时禁用 -->
<darra-button var="M0.2" disabled="M10.0">危险操作</darra-button>
<!-- 大按钮, 触摸屏用 -->
<darra-button var="M0.3" mode="toggle" size="large">紧急停止</darra-button>
常见错误:
| 错误 | 原因 | 解决 |
|---|---|---|
| 按钮点击后变量不变 | 变量不在 WriteWhitelist | 在项目设置中添加该变量到白名单 |
| 点动模式一直为 true | Service 版本低 | 升级到 v1.0.5+ |
| 按钮文字不显示 | 忘了写子节点文本 | 在标签中写文本: <darra-button>启动</darra-button> |
<darra-led> — 指示灯
指示灯用于显示 PLC 位的 ON/OFF 状态, 带颜色和闪烁功能。
属性表:
| 属性 | 必填 | 默认值 | 说明 |
|---|---|---|---|
var | 是 | — | 绑定的 PLC 位变量 |
color-on | 否 | #00FF00 | ON 状态的颜色 |
color-off | 否 | #FF0000 | OFF 状态的颜色 |
size | 否 | 40px | 圆形直径 |
blink | 否 | false | 为 true 时 ON 状态闪烁 |
label | 否 | — | 指示灯下方的标签文字 |
shape | 否 | circle | circle 圆形 / square 方形 |
示例:
<!-- 基本指示灯: M0.0 为 true 时绿色, false 时红色 -->
<darra-led var="M0.0" color-on="#00FF00" color-off="#FF0000"></darra-led>
<!-- 运行指示灯, 带闪烁 -->
<darra-led var="M10.0" color-on="#00FF00" color-off="#666" blink="true"></darra-led>
<!-- 故障指示灯, 红色闪烁, 大方块 -->
<darra-led var="M10.1" color-on="#FF0000" color-off="#666" size="64px" blink="true" shape="square" label="故障"></darra-led>
<!-- 黄色警告灯 -->
<darra-led var="M10.2" color-on="#FFD700" color-off="#666" label="警告"></darra-led>
常见错误:
| 错误 | 原因 | 解决 |
|---|---|---|
| 指示灯一直灰色 | 变量未订阅或变量名拼错 | 检查 var 拼写, 看 F12 网络 WS 帧 |
| 闪烁不生效 | blink 属性设为字符串 "false" | 用 blink="true" 而非 blink="false" |
<darra-numeric> — 数值显示
用于显示 PLC 的数值变量 (INT, DINT, REAL 等), 支持格式化和单位。
属性表:
| 属性 | 必填 | 默认值 | 说明 |
|---|---|---|---|
var | 是 | — | 绑定的 PLC 数值变量 |
format | 否 | 0 | 显示格式 (0, 0.0, 0.00, #,##0.00) |
unit | 否 | "" | 单位后缀 |
color | 否 | #333 | 文本颜色 |
font-size | 否 | 32px | 字体大小 |
prefix | 否 | — | 数值前缀 (如 ~) |
decimals | 否 | — | 小数位数 (优先级低于 format) |
示例:
<!-- 简单数值显示 -->
<darra-numeric var="DB1.Temperature" format="0.0" unit="℃"></darra-numeric>
<!-- 大字号 KPI 显示 -->
<darra-numeric var="DB100.OEE" format="0.0%" unit="" font-size="72px" color="#38bdf8"></darra-numeric>
<!-- 带前缀的数值 -->
<darra-numeric var="DB1.Pressure" format="0.00" unit="MPa" prefix="~"></darra-numeric>
<!-- 转速显示, 千分位 -->
<darra-numeric var="DB3.RPM" format="#,##0" unit="r/min"></darra-numeric>
常见错误:
| 错误 | 原因 | 解决 |
|---|---|---|
显示 0 或 NaN | 变量未绑定或 PLC 未运行 | 确认 PLC 正在运行, 变量地址正确 |
| 格式不生效 | format 值写错 | 使用 0.0 (小数点后一位) 或 0.00 (两位) |
| 单位显示在数值上方 | 样式问题 | 在 CSS 中加 display: inline-flex |
<darra-input> — 输入框 (双向绑定)
输入框支持双向数据绑定: 用户输入的值会自动写入 PLC 变量。
属性表:
| 属性 | 必填 | 默认值 | 说明 |
|---|---|---|---|
var | 是 | — | 绑定的 PLC 可写变量 |
type | 否 | number | number / text / password |
min | 否 | — | 最小值 (type=number 时) |
max | 否 | — | 最大值 |
step | 否 | — | 步进值 |
placeholder | 否 | "" | 占位提示文字 |
readonly | 否 | false | 是否只读 |
示例:
<!-- 数值输入, 设定目标温度 -->
<darra-input var="DB2.Setpoint" min="0" max="300" step="0.1"></darra-input>
<!-- 文本输入, 产品批号 -->
<darra-input var="DB2.BatchNo" type="text" placeholder="输入批号"></darra-input>
<!-- 只读显示 (与 numeric 不同, 它显示原始值) -->
<darra-input var="DB1.Temperature" readonly="true"></darra-input>
常见错误:
| 错误 | 原因 | 解决 |
|---|---|---|
| 输入后数值不变 | 变量不在白名单 | 检查 WriteWhitelist |
| 输入框显示 NaN | 变量类型不匹配 | 确保 PLC 变量是数值类型 |
| 输入小数自动取整 | 忘了设置 step | 加 step="0.1" |
| 输入框无焦点光标 | 误用了 PopupContainerEdit | 确认使用的是 <darra-input> 而非其他控件 |
<darra-label> — 静态文字
显示静态文本, 不绑定 PLC 变量。
属性表:
| 属性 | 必填 | 默认值 | 说明 |
|---|---|---|---|
text | 否 | 子节点文本 | 显示的文字 |
font-size | 否 | 16px | 字体大小 |
color | 否 | 继承主题色 | 文字颜色 |
bold | 否 | false | 是否加粗 |
align | 否 | left | left / center / right |
示例:
<darra-label>反应釜 #1</darra-label>
<darra-label text="温度监控" font-size="24px" bold="true" color="#1E90FF"></darra-label>
2.2 输入控件
<darra-input> — 输入框 (见 2.1 基础控件)
<darra-slider> — 滑块
滑块用于直观地调整数值, 适合频率、速度等连续调节。
属性表:
| 属性 | 必填 | 默认值 | 说明 |
|---|---|---|---|
var | 是 | — | 绑定的 PLC 可写变量 |
min | 否 | 0 | 最小值 |
max | 否 | 100 | 最大值 |
step | 否 | 1 | 步进值 |
orientation | 否 | horizontal | horizontal / vertical |
show-value | 否 | true | 是否显示当前值 |
color | 否 | 主题主色 | 滑块颜色 |
示例:
<!-- 水平滑块, 调节电机频率 -->
<darra-slider var="DB2.Frequency" min="0" max="50" step="0.5"></darra-slider>
<!-- 垂直滑块, 调节阀门开度 -->
<darra-slider var="DB2.ValveOpen" min="0" max="100" step="1" orientation="vertical"></darra-slider>
常见错误:
| 错误 | 原因 | 解决 |
|---|---|---|
| 拖动滑块时写入太频繁 | 需要防抖 | 在脚本中加 debounce(300ms) |
| 滑块位置不更新 | 变量是只读的 | 确认变量可写, 且在白名单中 |
<darra-switch> — 开关
开关控件, 适合启停控制。
属性表:
| 属性 | 必填 | 默认值 | 说明 |
|---|---|---|---|
var | 是 | — | 绑定的 PLC 位变量 |
label-on | 否 | ON | 开启时的标签 |
label-off | 否 | OFF | 关闭时的标签 |
color-on | 否 | #22C55E | 开启颜色 |
size | 否 | medium | small / medium / large |
示例:
<darra-switch var="M0.0" label-on="运行" label-off="停止"></darra-switch>
<darra-dropdown> — 下拉框
下拉框用于从多个预设值中选择一个写入 PLC。
属性表:
| 属性 | 必填 | 默认值 | 说明 |
|---|---|---|---|
var | 是 | — | 绑定的 PLC 变量 (写入选中的值) |
options | 是 | — | 选项列表, 格式: 值1:标签1,值2:标签2 |
placeholder | 否 | 请选择 | 占位文字 |
示例:
<!-- 选择运行模式 -->
<darra-dropdown var="DB2.Mode" options="0:手动,1:半自动,2:全自动"></darra-dropdown>
<!-- 选择配方 -->
<darra-dropdown var="DB2.RecipeID" options="1:碳钢3mm,2:不锈钢2mm,3:铝合金4mm"></darra-dropdown>
2.3 容器控件
<darra-panel> — 面板容器
面板用于将多个控件组合在一起, 带标题和边框。
属性表:
| 属性 | 必填 | 默认值 | 说明 |
|---|---|---|---|
title | 否 | — | 面板标题 |
border | 否 | 1px solid #ccc | 边框样式 |
collapsible | 否 | false | 是否可折叠 |
collapsed | 否 | false | 初始折叠状态 |
width | 否 | auto | 面板宽度 |
height | 否 | auto | 面板高度 |
示例:
<darra-panel title="1# 反应釜" border="1px solid #1E90FF" collapsible="true">
<darra-led var="M0.0" color-on="#00FF00" color-off="#666"></darra-led>
<darra-numeric var="DB1.Temperature" format="0.0" unit="℃"></darra-numeric>
<darra-numeric var="DB1.Pressure" format="0.0" unit="MPa"></darra-numeric>
</darra-panel>
<darra-group-box> — 分组框
分组框与面板类似, 但视觉更简洁 (仅边框+标题)。
属性表:
| 属性 | 必填 | 默认值 | 说明 |
|---|---|---|---|
title | 否 | — | 分组标题 |
border | 否 | 1px solid #ddd | 边框样式 |
示例:
<darra-group-box title="供水系统">
<darra-tank var="DB1.Level" max="100"></darra-tank>
<darra-pipe flow-var="DB1.Flow" direction="right"></darra-pipe>
</darra-group-box>
<darra-tab-container> — 标签页容器
标签页用于在有限空间内展示多个页面内容。
属性表:
| 属性 | 必填 | 默认值 | 说明 |
|---|---|---|---|
active-tab | 否 | 第一个 Tab | 当前激活的 Tab 索引 |
position | 否 | top | top / bottom / left / right |
示例:
<darra-tab-container>
<darra-tab title="概览">
<darra-numeric var="DB1.Temperature" unit="℃"></darra-numeric>
</darra-tab>
<darra-tab title="趋势">
<darra-trend vars="DB1.Temperature,DB1.Pressure" max-points="300"></darra-trend>
</darra-tab>
<darra-tab title="报警">
<darra-alarm-list max-rows="10"></darra-alarm-list>
</darra-tab>
</darra-tab-container>
<darra-table> — 表格
表格控件用于显示结构化数据。
属性表:
| 属性 | 必填 | 默认值 | 说明 |
|---|---|---|---|
var | 是 | — | 绑定的 PLC 数组变量 |
columns | 是 | — | 列定义, 格式: 字段1:标题1,字段2:标题2 |
page-size | 否 | 20 | 每页行数 |
sortable | 否 | true | 是否可排序 |
filterable | 否 | false | 是否可筛选 |
示例:
<darra-table var="DB100.OrderList" columns="id:订单号,product:产品,qty:数量,state:状态" page-size="10"></darra-table>
2.4 数据控件
<darra-dx-grid> — 数据表格 (DevExtreme DataGrid)
高级表格控件, 支持排序、筛选、分页、行编辑、列拖拽等企业级功能。
属性表:
| 属性 | 必填 | 默认值 | 说明 |
|---|---|---|---|
var | 是 | — | 绑定的 PLC 数组变量 |
columns | 是 | — | 列定义 字段:标题:宽度 |
paging | 否 | 20 | 每页行数 |
filter-row | 否 | false | 是否启用筛选行 |
editing | 否 | false | 是否启用行编辑 |
selection-mode | 否 | single | single / multiple / none |
height | 否 | auto | 表格高度 |
示例:
<darra-dx-grid var="DB100.OrderList"
columns="id:编号:80,name:产品名称:200,qty:数量:100,state:状态:120"
paging="20"
filter-row="true"
selection-mode="multiple"></darra-dx-grid>
常见错误:
| 错误 | 原因 | 解决 |
|---|---|---|
| 表格为空白 | 变量未绑定或数据未更新 | 确认 PLC 变量有数据, 查看 F12 网络 |
| 列标题显示字段名 | columns 格式错误 | 格式为 字段:标题 而非 字段:标题:宽度 |
<darra-trend> — 趋势图 (Chart.js)
趋势图用于实时显示变量的历史变化曲线。
属性表:
| 属性 | 必填 | 默认值 | 说明 |
|---|---|---|---|
vars | 是 | — | 逗号分隔的多个变量名 |
max-points | 否 | 200 | 窗口内最大点数 |
colors | 否 | 自动 | 每条曲线的颜色, 逗号分隔 |
update-rate | 否 | 500 | 采样周期 (ms) |
unit | 否 | — | Y 轴单位 |
height | 否 | 300px | 图表高度 |
show-legend | 否 | true | 是否显示图例 |
show-grid | 否 | true | 是否显示网格线 |
示例:
<!-- 双变量趋势图 -->
<darra-trend vars="DB1.Temperature,DB1.Pressure"
max-points="300"
colors="#38bdf8,#fbbf24"
unit="℃ / MPa"
height="400px"></darra-trend>
<!-- 单变量趋势, 快速刷新 -->
<darra-trend vars="DB3.RPM" max-points="600" update-rate="100" height="200px"></darra-trend>
常见错误:
| 错误 | 原因 | 解决 |
|---|---|---|
| 趋势图不更新 | 变量未订阅 | 确认 vars 属性中的变量名拼写正确 |
| 曲线挤在一起看不清 | 量程差太大 | 考虑用双 Y 轴或分开显示 |
| 图表卡顿 | 点数太多 | 减少 max-points 或增大 update-rate |
<darra-bar-chart> — 柱状图
属性表:
| 属性 | 必填 | 默认值 | 说明 |
|---|---|---|---|
vars | 是 | — | 逗号分隔的变量名 |
labels | 否 | 变量名 | 柱状图标签 |
type | 否 | bar | bar / stacked-bar / horizontal-bar |
height | 否 | 300px | 图表高度 |
示例:
<darra-bar-chart vars="DB4.Production_Q1,DB4.Production_Q2,DB4.Production_Q3,DB4.Production_Q4"
labels="Q1,Q2,Q3,Q4"
type="bar"
height="400px"></darra-bar-chart>
<darra-pie-chart> — 饼图
属性表:
| 属性 | 必填 | 默认值 | 说明 |
|---|---|---|---|
vars | 是 | — | 逗号分隔的变量名 |
labels | 否 | 变量名 | 扇区标签 |
type | 否 | pie | pie / doughnut |
height | 否 | 300px | 图表高度 |
示例:
<darra-pie-chart vars="DB4.GoodQty,DB4.DefectQty,DB4.ReworkQty"
labels="合格,缺陷,返工"
type="doughnut"
height="300px"></darra-pie-chart>
<darra-gauge> — 仪表盘
仪表盘用于显示单个数值在量程范围内的位置, 带警告/严重阈值区域。
属性表:
| 属性 | 必填 | 默认值 | 说明 |
|---|---|---|---|
var | 是 | — | 数值变量 |
min | 否 | 0 | 量程最小值 |
max | 否 | 100 | 量程最大值 |
warn | 否 | — | 警告阈值 (显示黄色区域) |
critical | 否 | — | 严重阈值 (显示红色区域) |
unit | 否 | "" | 单位 |
size | 否 | medium | small / medium / large |
示例:
<darra-gauge var="DB1.Pressure" min="0" max="10" unit="MPa"
warn="7" critical="9"></darra-gauge>
<darra-progress-bar> — 进度条
属性表:
| 属性 | 必填 | 默认值 | 说明 |
|---|---|---|---|
var | 是 | — | 进度变量 |
max | 否 | 100 | 满量程 |
color | 否 | #1E90FF | 进度条颜色 |
show-label | 否 | true | 是否显示百分比 |
height | 否 | 24px | 高度 |
示例:
<darra-progress-bar var="DB1.Progress" max="100" color="#22C55E"></darra-progress-bar>
2.5 报警控件
<darra-alarm-list> — 报警列表
报警列表自动显示 Service 推送的实时报警, 支持级别筛选、排序、确认。
属性表:
| 属性 | 必填 | 默认值 | 说明 |
|---|---|---|---|
max-rows | 否 | 10 | 最大显示行数 |
level-filter | 否 | warn,error | 过滤级别 info,warn,error |
sort | 否 | time desc | 排序方式 |
allow-ack | 否 | false | 是否允许在列表中确认 |
show-time | 否 | true | 是否显示时间列 |
height | 否 | auto | 列表高度 |
示例:
<!-- 报警面板, 显示所有级别, 可确认 -->
<darra-alarm-list max-rows="20" level-filter="warn,error" allow-ack="true" height="400px"></darra-alarm-list>
<!-- 底部报警条, 只显示错误 -->
<darra-alarm-list max-rows="3" level-filter="error" show-time="false"></darra-alarm-list>
<darra-alarm-popup> — 报警弹窗
当新报警到达时自动弹出窗口, 支持声音和闪烁。
属性表:
| 属性 | 必填 | 默认值 | 说明 |
|---|---|---|---|
level-filter | 否 | error | 触发弹窗的级别 |
sound | 否 | true | 是否播放声音 |
auto-close | 否 | false | 是否自动关闭 |
auto-close-sec | 否 | 10 | 自动关闭秒数 |
示例:
<darra-alarm-popup level-filter="error" sound="true" auto-close="true" auto-close-sec="15"></darra-alarm-popup>
2.6 导航控件
<darra-nav-button> — 页面导航按钮
属性表:
| 属性 | 必填 | 默认值 | 说明 |
|---|---|---|---|
route | 是 | — | 目标路由, 如 /reactor |
label | 否 | 子节点文本 | 按钮文字 |
icon | 否 | — | 图标 URL |
示例:
<darra-nav-button route="/">首页</darra-nav-button>
<darra-nav-button route="/reactor">反应釜</darra-nav-button>
<darra-nav-button route="/alarm">报警中心</darra-nav-button>
<darra-menu> — 菜单
属性表:
| 属性 | 必填 | 默认值 | 说明 |
|---|---|---|---|
items | 是 | — | 菜单项, 格式: 标签1:路由1,标签2:路由2 |
position | 否 | top | top / left / right |
mode | 否 | horizontal | horizontal / vertical |
示例:
<darra-menu items="首页:/,反应釜:/reactor,报警:/alarm,趋势:/trend" position="top" mode="horizontal"></darra-menu>
2.7 工业控件
<darra-tank> — 储罐 (含液位动画)
属性表:
| 属性 | 必填 | 默认值 | 说明 |
|---|---|---|---|
var | 是 | — | 液位变量 (0 - max) |
max | 否 | 100 | 满量程 |
color | 否 | #1E90FF | 液体颜色 |
show-label | 否 | false | 显示百分比 |
width | 否 | 120px | 宽度 |
height | 否 | 200px | 高度 |
示例:
<darra-tank var="DB1.Level" max="100" color="#22C55E" show-label="true"></darra-tank>
<darra-motor> — 电机
属性表:
| 属性 | 必填 | 默认值 | 说明 |
|---|---|---|---|
run-var | 是 | — | 运行状态位变量 |
fault-var | 否 | — | 故障位变量 |
speed-var | 否 | — | 速度变量 (控制旋转快慢) |
size | 否 | medium | small / medium / large |
示例:
<darra-motor run-var="M10.0" fault-var="M10.1" speed-var="DB3.RPM"></darra-motor>
<darra-valve> — 阀门
属性表:
| 属性 | 必填 | 默认值 | 说明 |
|---|---|---|---|
open-var | 是 | — | 开到位位变量 |
closed-var | 否 | — | 关到位位变量 |
position-var | 否 | — | 开度变量 (0-100%) |
示例:
<darra-valve open-var="M11.0" closed-var="M11.1"></darra-valve>
<darra-pipe> — 管道
属性表:
| 属性 | 必填 | 默认值 | 说明 |
|---|---|---|---|
flow-var | 是 | — | 流量变量 (>0 时显示流动动画) |
direction | 否 | right | left / right / up / down |
color | 否 | #888 | 管道颜色 |
示例:
<darra-pipe flow-var="DB1.Flow" direction="right" color="#4FC3F7"></darra-pipe>
3. 数据绑定
数据绑定是 HMI 与 PLC 通信的核心机制。Darra HMI 提供 变量绑定、属性绑定、表达式绑定 三种方式。
3.1 变量绑定
方式一: 控件属性绑定 (最常用)
在 <darra-*> 控件的 var 属性中直接指定 PLC 变量地址:
<darra-numeric var="DB1.Temperature" format="0.0" unit="℃"></darra-numeric>
<darra-led var="M0.0" color-on="#00FF00" color-off="#666"></darra-led>
PLC 变量地址语法:
| 语法 | 类型 | 示例 |
|---|---|---|
M<byte>.<bit> | 位 (BOOL) | M0.0, M10.3 |
MB<offset> | 字节 (BYTE) | MB10 |
MW<offset> | 字 (WORD/INT) | MW100 |
MD<offset> | 双字 (DWORD/REAL) | MD200 |
I<byte>.<bit> | 输入位 | I0.0 |
Q<byte>.<bit> | 输出位 | Q0.0 |
DB<n>.<field> | 数据块字段 | DB1.Temperature |
DB<n>.<field>.<sub> | 嵌套结构 | DB1.Motor.Speed |
方式二: darra.bind() API 绑定
在页面脚本中通过 JavaScript 手动绑定:
// 绑定单变量
darra.bind('DB1.Temperature', (value, meta) => {
document.getElementById('temp-display').textContent = value.toFixed(2) + ' ℃'
// meta.timestamp: 服务端采集时间
// meta.quality: 'good' | 'bad' | 'cached'
// meta.type: 'number' | 'boolean' | 'string'
})
// 绑定整个 DB 块
darra.bindDB('DB1', (values) => {
// values = { Temperature: 25.3, Pressure: 1.2, Level: 80.5 }
})
// 绑定多个变量组
darra.bindGroup(['DB1.T1', 'DB1.T2', 'M0.0'], (values) => {
// values = { 'DB1.T1': 25, 'DB1.T2': 26, 'M0.0': true }
})
方式三: 订阅 API
// 订阅变量变化, 返回取消订阅函数
const unsubscribe = darra.subscribeVariable('DB1.Temperature', (value, meta) => {
console.log(`温度: ${value} ℃`)
})
// 不再需要时取消订阅
unsubscribe()
// 订阅整个 DB
darra.subscribeDB('DB1', (values) => {
console.log('DB1 更新:', values)
})
3.2 属性绑定
除了 var 属性绑定数值外, 还可以绑定其他 HTML 属性。
使用 x-bind (Alpine.js)
<!-- 绑定 CSS class -->
<div x-bind:class="{ 'running': isRunning, 'stopped': !isRunning }"
x-data="{ isRunning: false }"
x-init="darra.bind('M0.0', v => isRunning = v)">
<!-- 绑定 style -->
<div x-bind:style="`background: ${level > 80 ? 'red' : 'green'}`"
x-data="{ level: 0 }"
x-init="darra.bind('DB1.Level', v => level = v)">
使用 Darra.bindElement API
// 绑定元素的 textContent
const el = document.getElementById('temp-display')
Darra.bindElement(el, 'DB1.Temperature')
// 绑定元素的任意属性
Darra.bindElementAttribute(el, 'M0.0', 'class')
// M0.0 = true → el.className = 'on'
// M0.0 = false → el.className = 'off'
// 绑定 CSS 属性 (带阈值映射)
Darra.bindElementStyle(el, 'DB1.Level', 'width', {
min: 0,
max: 100,
unit: '%'
})
// DB1.Level = 75 → el.style.width = '75%'
3.3 表达式绑定
Alpine.js 支持在 HTML 属性中写 内联表达式, 可以做运算、格式化、条件判断。
数学运算
<div x-data="{ t: 0, r: 0 }"
x-init="darra.bind('DB1.TempC', v => t = v); darra.bind('DB1.Ratio', v => r = v)">
<!-- 单位换算: C → F -->
华氏温度: <span x-text="(t * 1.8 + 32).toFixed(1)"></span> °F
<!-- 百分比 -->
利用率: <span x-text="(r * 100).toFixed(1)"></span> %
<!-- 分段条件 -->
状态: <span x-text="t < 20 ? '低' : t < 60 ? '正常' : '高'"></span>
</div>
条件渲染
<div x-data="{ level: 0 }" x-init="darra.bind('DB1.Level', v => level = v)">
<div x-show="level < 20" style="color:red">液位低</div>
<div x-show="level >= 20 && level <= 80" style="color:green">正常</div>
<div x-show="level > 80" style="color:orange">液位高</div>
</div>
<!-- x-if: 条件满足时才会创建 DOM -->
<template x-if="level > 95">
<div class="alarm-panel">
<h2>紧急: 液位超限</h2>
<darra-button var="M0.0" mode="momentary">排空</darra-button>
</div>
</template>
列表循环
<div x-data="{ axes: [
{ id:0, name:'X', pos:0 },
{ id:1, name:'Y', pos:0 },
{ id:2, name:'Z', pos:0 }
] }"
x-init="
axes.forEach(a => {
darra.bind(`DB50.Axis${a.id}.Pos`, v => a.pos = v)
})">
<template x-for="axis in axes" :key="axis.id">
<div>
<span x-text="axis.name"></span>轴: <span x-text="axis.pos.toFixed(3)"></span> mm
</div>
</template>
</div>
3.4 数据源配置
写入白名单
所有写入操作必须经过 WriteWhitelist 白名单校验:
{
"WriteWhitelist": [
"M0.*",
"DB2.Setpoint*",
"MW200"
]
}
不在白名单的写入会被 Service 拒绝, 返回 error 事件。
全局数据源
darra 全局对象提供了几个核心数据源方法:
// 读取当前缓存值 (同步, 不走网络)
const t = darra.get('DB1.Temperature')
// 查询 DB 结构 (异步, 发 HTTP 请求)
const fields = await darra.describeDB('DB1')
// fields = [{ name: 'Temperature', type: 'REAL', comment: '温度' }, ...]
// 写入变量
darra.write('M0.0', true)
darra.write('DB2.Setpoint', 75.5)
// 批量写入 (性能更好)
darra.writeGroup({
'DB2.Setpoint': 75.5,
'M0.1': false,
'MW100': 1234
})
3.5 数组绑定
对于数组类型变量, 需要在控件中指定数组下标:
<!-- 绑定数组第一个元素 -->
<darra-numeric var="DB10.ArrayData[0]" format="0.0"></darra-numeric>
<!-- 循环遍历数组 -->
<div x-data="{ data: [] }" x-init="
for (let i = 0; i < 10; i++) {
darra.bind(`DB10.ArrayData[${i}]`, v => data[i] = v)
}">
<template x-for="(val, idx) in data" :key="idx">
<div>元素 [<span x-text="idx"></span>]: <span x-text="val"></span></div>
</template>
</div>
3.6 绑定更新模式
| 模式 | 说明 | 适用场景 |
|---|---|---|
| 推送 (默认) | Service 主动推送变量变化, 默认 100ms 节流 | 实时监控, 默认推荐 |
| 轮询 | HMI 定时调用 darra.get() 读取缓存 | 低频更新, 不需要实时 |
| 事件驱动 | 通过 darra.on('alarm', ...) 等事件接收 | 报警、登录等异步事件 |
推送节流配置:
darra.init({
updateRate: 100, // 推送节流 ms (越小越实时, 越大越省带宽)
reconnect: true,
reconnectInterval: 3000
})
4. 事件与脚本
4.1 事件类型
Darra HMI 支持以下事件类型:
| 事件类型 | 来源 | 示例 |
|---|---|---|
| DOM 事件 | 浏览器原生 | click, input, change, keydown |
| PLC 事件 | Service 推送 | ready, offline, alarm, error |
| 变量事件 | 值变化触发 | bind 回调, subscribe 回调 |
| 自定义事件 | 用户代码触发 | CustomEvent, darra.bus |
| 页面生命周期 | 页面切换 | pageEnter, pageLeave |
| 定时器 | JavaScript | setInterval, setTimeout |
4.2 DOM 事件 (Alpine 语法)
使用 Alpine 的 @event 语法绑定 DOM 事件:
<!-- 点击事件 -->
<button @click="darra.write('M0.0', true)">启动</button>
<!-- 输入事件, 带防抖 -->
<input @input.debounce.500ms="handleInput($event.target.value)" />
<!-- 键盘事件 -->
<div @keydown.escape="closeDialog()" tabindex="0">按 ESC 关闭</div>
<!-- 全局快捷键 (Ctrl+S 保存) -->
<div @keydown.window.ctrl.s.prevent="save()">全局保存</div>
<!-- 鼠标事件 -->
<div @mouseenter="hover = true" @mouseleave="hover = false"
:class="hover ? 'highlighted' : ''">悬停高亮</div>
Alpine 事件修饰符:
| 修饰符 | 说明 |
|---|---|
.prevent | 阻止默认行为 |
.stop | 阻止冒泡 |
.debounce.300ms | 防抖 300ms |
.throttle.200ms | 节流 200ms |
.outside | 元素外点击 |
.window | 挂在 window 对象上 |
.once | 只触发一次 |
.ctrl / .shift / .alt | 组合键 |
4.3 PLC 事件 (darra.on)
使用 darra.on() 监听 Service 推送事件:
// 连接状态
darra.on('ready', () => {
console.log('WebSocket 已连接')
document.querySelector('.offline-banner').style.display = 'none'
})
darra.on('offline', () => {
console.warn('连接断开')
document.querySelector('.offline-banner').style.display = 'block'
// 断开时禁用所有写入按钮
document.querySelectorAll('button.writable').forEach(b => b.disabled = true)
})
// 写入失败
darra.on('error', (err) => {
// err = { type: 'write', var: 'DB2.Setpoint', error: 'not_in_whitelist' }
showToast(`写入失败: ${err.var} (${err.error})`, 'error')
})
// 报警事件
darra.on('alarm', (alarm) => {
// alarm = { level: 'error', source: '伺服2', message: '过载', timestamp: 1713429123000 }
playSound('alarm.mp3')
addToAlarmList(alarm)
})
// 登录成功
darra.on('auth_ok', (msg) => {
console.log('登录成功:', msg.user, '角色:', msg.role)
location.href = '/local/'
})
4.4 HMI 中编写 SCL 脚本
HMI 的 JsCode 中可以编写 JavaScript 逻辑, 通过 darra.* API 访问 PLC 变量。SCL 是 PLC 端的编程语言, HMI 端使用 JavaScript。
变量读写
// 读取 PLC 变量 (从缓存)
const temp = darra.get('DB1.Temperature')
// 写入 PLC 变量 (异步)
darra.write('M0.0', true)
darra.write('DB2.Setpoint', 75.5)
// 批量写入
darra.writeGroup({
'DB2.Setpoint': 80.0,
'M0.1': false
})
页面导航
// 跳转到指定页面
darra.navigateTo('/reactor')
darra.navigateTo('/alarm')
// 获取当前路由
const route = darra.getCurrentRoute() // '/reactor'
// 显示弹出窗口
darra.showPopup('<h2>确认停车?</h2><button onclick="darra.closePopup(true)">确认</button>', {
width: 400,
height: 300,
modal: true,
title: '停车确认'
})
// 关闭弹出窗口
darra.closePopup()
系统函数
// 获取系统信息
const info = darra.getSystemInfo()
// { version: '1.0.5', runtimeMode: 'Production', plcCycleTime: 5, ... }
// 获取报警列表
const alarms = darra.getAlarms({ acknowledged: false, severity: 'Critical' })
// 确认报警
darra.acknowledgeAlarm('alarm-001', '已确认, 安排处理')
// 获取用户信息
const user = darra.getUserInfo()
// { username: 'wang', role: 'engineer', displayName: '王工' }
// 登出
darra.logout()
// 切换主题
darra.setTheme('industrial')
// 显示 Toast 通知
darra.showToast('保存成功', { type: 'success', duration: 3000 })
4.5 完整脚本示例
示例: 一键停机脚本
// 在 JsCode 中定义
async function emergencyStop() {
// 二次确认
const confirmed = await showConfirmDialog('确认切入紧急停机?')
if (!confirmed) return
// 批量写入 PLC 变量
darra.writeGroup({
'M0.0': false, // 主启动 OFF
'M0.1': true, // 急停 ON
'DB2.SetpointSpeed': 0,
'DB2.SetpointTemp': 25
})
// 记录审计日志
await fetch('/api/hmi/audit', {
method: 'POST',
body: JSON.stringify({ action: 'emergency_stop', ts: Date.now() })
})
// UI 反馈
document.body.classList.add('emergency-mode')
darra.showToast('已进入紧急停机', { type: 'error', duration: 0 })
}
示例: 阈值检测脚本
// 温度超限检测
let overTempFired = false
darra.bind('DB1.Temp', (v) => {
if (v > 80 && !overTempFired) {
overTempFired = true
darra.showToast('温度超限!', { type: 'warning' })
darra.write('M0.5', true) // 触发报警输出
} else if (v < 75 && overTempFired) {
overTempFired = false
darra.write('M0.5', false) // 复位报警
}
})
示例: 定时轮询脚本
// 每 30 秒查询一次历史 KPI (历史数据用 REST, 实时数据用订阅)
const pollTimer = setInterval(async () => {
const kpi = await fetch('/api/hmi/kpi/today').then(r => r.json())
updateKpiPanel(kpi)
}, 30000)
// 页面关闭时清理定时器
window.addEventListener('beforeunload', () => clearInterval(pollTimer))
5. 页面管理
5.1 页面创建与配置
创建新页面
- 在 HMI 项目节点上右键 → "新建 HMI 页面"
- 填写:
- 页面名称: 显示在页面标签上
- 路由 (Route): 访问路径, 如
/reactor, 首页设为/ - 设为首页: 勾选后将作为默认加载页面
页面配置属性
{
"Name": "反应釜监控",
"Route": "/reactor",
"IsHome": false,
"HtmlCode": "<!DOCTYPE html>...",
"CssCode": "body { ... }",
"JsCode": "// 页面脚本",
"Elements": [
{
"Id": "el_001",
"Type": "Numeric",
"X": 100, "Y": 50,
"Width": 200, "Height": 60,
"VariableBinding": "DB1.Temperature",
"Properties": { "format": "0.0", "unit": "℃" }
}
]
}
5.2 页面路由 (多页 SPA)
Darra HMI 支持多页架构, 每页一个独立路由:
| 路由 | 页面 | 访问 URL |
|---|---|---|
/ | 首页 | /local/ |
/reactor | 反应釜 | /local/reactor |
/alarm | 报警中心 | /local/alarm |
/trend | 趋势分析 | /local/trend |
/recipe | 配方管理 | /local/recipe |
页面导航方式
<!-- 使用导航按钮 -->
<darra-nav-button route="/reactor">反应釜</darra-nav-button>
<!-- 使用菜单 -->
<darra-menu items="首页:/,反应釜:/reactor,报警:/alarm"></darra-menu>
<!-- 使用 JavaScript 导航 -->
<button @click="darra.navigateTo('/reactor')">反应釜</button>
5.3 Popup 弹窗
Popup 弹窗用于在当前页面之上显示临时内容, 如确认对话框、参数编辑等。
// 打开弹窗
const popup = darra.showPopup(`
<div style="padding:20px;text-align:center;">
<h2>确认停机?</h2>
<p>当前批次: BATCH-001</p>
<button onclick="darra.closePopup(true)" style="background:red;color:white;padding:10px 20px;">确认停机</button>
<button onclick="darra.closePopup(false)" style="margin-left:10px;padding:10px 20px;">取消</button>
</div>
`, {
width: 400,
height: 250,
modal: true,
title: '操作确认',
closeOnEsc: false
})
// 关闭弹窗
darra.closePopup()
darra.closePopup(true) // 带返回值
5.4 页面生命周期
每个页面有完整的生命周期:
页面加载 (Load)
│
▼
pageEnter 事件触发
│
├── 首次: 执行 JsCode, 初始化 Alpine
│
└── Resume (从其他页面返回): 不重新加载, 保留状态
│
▼
正常运行 (绑定变量, 监听事件)
│
▼
pageLeave 事件触发 (离开页面时)
│
├── Suspend: 保留 DOM 状态, 隐藏
│
└── Destroy: 清理订阅, 释放内存 (多页内存压力大时)
页面生命周期脚本:
// 页面进入时
darra.on('pageEnter', ({ route, prevRoute }) => {
console.log(`进入页面: ${route}, 来自: ${prevRoute}`)
if (route === '/reactor') {
// 初始化反应釜页面
initReactorPage()
}
})
// 页面离开时
darra.on('pageLeave', ({ route, nextRoute }) => {
console.log(`离开页面: ${route}, 前往: ${nextRoute}`)
// 清理定时器
clearInterval(pageTimer)
// 取消所有订阅
darra.unsubscribeAll()
})
// 页面完全加载
document.addEventListener('DOMContentLoaded', () => {
console.log('DOM 已加载')
})
// Alpine 初始化前 (注册自定义指令/魔法的好时机)
document.addEventListener('alpine:init', () => {
Alpine.magic('darra', () => ({
get: (v) => darra.get(v),
write: (v, val) => darra.write(v, val)
}))
})
5.5 多页面切换动画
/* 页面淡入动画 */
body {
animation: page-in 0.4s ease;
}
@keyframes page-in {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
/* 页面切换时显示加载状态 */
body.loading {
opacity: 0.5;
pointer-events: none;
}
body.loading::after {
content: '加载中...';
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24px;
}
6. 动画与交互
Darra HMI 动画全部基于 CSS transition / animation / keyframes, 不需要 JS 动画库。90% 场景优先用 CSS, 由 GPU 处理, 流畅且省电。
6.1 三种动画技术
| 技术 | 适合 | 性能 |
|---|---|---|
transition | 属性渐变 (width/color/transform) | 极高 |
animation + @keyframes | 循环/复杂序列 (旋转/闪烁/流动) | 高 |
| JS 动画 (requestAnimationFrame) | 极端自定义 (轨迹曲线) | 中 (占 CPU) |
6.2 属性动画
位置/大小变化
.box {
transition: all 0.5s ease;
}
.box.expanded {
width: 400px;
height: 300px;
}
旋转动画
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.motor.running {
animation: spin 1s linear infinite;
}
.motor.slow { animation-duration: 3s; }
.motor.fast { animation-duration: 0.3s; }
透明度动画
.fade-in {
transition: opacity 0.5s ease;
}
.fade-in.hidden {
opacity: 0;
pointer-events: none;
}
颜色动画
.alarm-indicator {
transition: background-color 0.3s ease;
}
.alarm-indicator.warning { background-color: #F59E0B; }
.alarm-indicator.danger { background-color: #EF4444; }
6.3 状态动画
报警闪烁
@keyframes alarm-blink {
0%, 49% { background: #EF4444; }
50%,100% { background: #7f1d1d; }
}
.alarm-active {
animation: alarm-blink 0.5s step-end infinite;
color: #fff;
}
LED 呼吸灯
@keyframes pulse {
0%,100% { opacity: 1; box-shadow: 0 0 8px currentColor; }
50% { opacity: .5; box-shadow: 0 0 20px currentColor; }
}
.led.on { animation: pulse 2s ease-in-out infinite; }
6.4 触摸交互
触摸滑动
<div @touchstart.passive="startX = $event.touches[0].clientX"
@touchend="
let dx = $event.changedTouches[0].clientX - startX;
if (dx > 50) prevPage();
if (dx < -50) nextPage();
">
</div>
长按操作
<script>
let longPressTimer
function startLongPress() {
longPressTimer = setTimeout(() => {
darra.showToast('长按触发', { type: 'warning' })
darra.write('M0.0', true)
}, 2000)
}
function cancelLongPress() {
clearTimeout(longPressTimer)
}
</script>
<button @mousedown="startLongPress()" @mouseup="cancelLongPress()"
@mouseleave="cancelLongPress()"
@touchstart="startLongPress()" @touchend="cancelLongPress()">
长按 2 秒启动
</button>
6.5 动画示例
旋转风扇
@keyframes fan-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.fan {
width: 100px; height: 100px;
border-radius: 50%;
background: conic-gradient(from 0deg, #4FC3F7, #0288D1, #4FC3F7, #0288D1, #4FC3F7);
}
.fan.running {
animation: fan-spin 2s linear infinite;
}
.fan.fast {
animation-duration: 0.5s;
}
<div class="fan" :class="{ 'running': speed > 0, 'fast': speed > 500 }"
x-data="{ speed: 0 }"
x-init="darra.bind('DB3.FanSpeed', v => speed = v)"></div>
液位动画
.tank {
width: 120px; height: 200px;
border: 3px solid #333;
overflow: hidden;
position: relative;
}
.tank .liquid {
position: absolute;
left: 0; right: 0; bottom: 0;
background: linear-gradient(180deg, #4FC3F7, #1E88E5);
height: 0;
transition: height 0.5s ease;
}
.tank .liquid::before {
content: '';
position: absolute;
top: -10px; left: 0; right: 0;
height: 10px;
background: inherit;
border-radius: 50%;
animation: wave 2s ease-in-out infinite;
}
@keyframes wave {
0%,100% { transform: translateX(0) scaleY(1); }
50% { transform: translateX(-5px) scaleY(0.8); }
}
<div class="tank" x-data="{ level: 0 }"
x-init="darra.bind('DB1.Level', v => level = v)">
<div class="liquid" :style="`height: ${level}%`"></div>
</div>
6.6 动画性能最佳实践
| 做法 | 说明 |
|---|---|
只动 transform / opacity | 这两个属性走 GPU 合成层, 不触发 layout/paint |
用 will-change 提示 | .el { will-change: transform; } (不滥用) |
| 关 Chart.js 动画 | chart.update('none') 跳过动画 |
| 降频处理 | 60FPS 太高, 节流到 20FPS |
| 动画降级 | @media (prefers-reduced-motion: reduce) 禁用动画 |
7. 主题与样式
7.1 39 种预设主题
Darra HMI 提供 39 种预设主题, 分为 7 大类别:
现代风格 (6 种)
| 主题名 | 说明 | 适用场景 |
|---|---|---|
modern | 现代深色, 蓝紫主色 | 数据大屏、车间总览 |
modern-light | 现代浅色 | 办公区监控 |
ocean | 海洋蓝渐变 | 水处理、船舶 |
aurora | 极光绿渐变 | 新能源、环保 |
sunset | 暖橙渐变 | 冶金、热处理 |
graphite | 石墨灰, 极简 | 高端设备、展厅 |
工业风格 (8 种)
| 主题名 | 说明 | 适用场景 |
|---|---|---|
industrial | 经典工业, 浅色绿底黑字 | 通用 SCADA |
industrial-dark | 工业深色 | 昏暗机房 |
steel | 冷钢灰 | 冶金、重工 |
petrochemical | 石化蓝 | 化工、炼油 |
pharma | 医药白, 洁净 | 医药、食品 |
water | 水务蓝 | 水处理、泵站 |
energy | 能源绿 | 电力、光伏 |
logistics | 物流橙 | 仓储、分拣 |
触摸屏风格 (5 种)
| 主题名 | 说明 |
|---|---|
touch-panel | 通用触摸, 48px 最小触摸目标 |
touch-panel-dark | 触摸深色 |
touch-heavy | 重工业触摸 (戴手套操作) |
kiosk | 自助查询机, 大字 |
mobile | 手机竖屏优化 |
数字孪生风格 (5 种)
| 主题名 | 说明 |
|---|---|
digital-twin | 暗色发光, 适合 3D 场景 |
cyberpunk | 赛博朋克, 霓虹紫 |
hologram | 全息蓝, 透明效果 |
matrix | 矩阵绿, 数据可视化 |
glassmorphism | 毛玻璃效果 |
无障碍与辅助 (3 种)
| 主题名 | 说明 |
|---|---|
high-contrast | 高对比度白底黑字 |
high-contrast-dark | 高对比度黑底白字 |
colorblind-safe | 色盲安全配色 (蓝-橙主色) |
7.2 主题切换
运行时切换
// JS 切换
darra.setTheme('industrial')
darra.setTheme('brandRed') // 自定义主题
// 获取当前主题
console.log(darra.getTheme()) // 'industrial'
按钮切换
<button @click="darra.setTheme('modern')">现代</button>
<button @click="darra.setTheme('industrial')">工业</button>
<button @click="darra.setTheme('digital-twin')">数字孪生</button>
按页面自动切换
darra.on('pageEnter', ({ route }) => {
switch (route) {
case '/': darra.setTheme('modern'); break
case '/alarm': darra.setTheme('industrial'); break
case '/3d': darra.setTheme('digital-twin'); break
}
})
7.3 自定义主题
方式一: 覆盖 CSS 变量 (推荐)
在 HMIProjectDef.CustomSkins 中定义:
{
"CustomSkins": [
{
"Name": "brand-blue",
"DisplayName": "公司蓝",
"BaseTheme": "modern",
"Variables": {
"--darra-primary": "#005A9E",
"--darra-bg": "#0A1628",
"--darra-panel-bg": "#112240",
"--darra-text": "#E6F1FF",
"--darra-success": "#22C55E",
"--darra-warning": "#F59E0B",
"--darra-danger": "#EF4444",
"--darra-radius": "6px",
"--darra-font": "'MiSans', 'Segoe UI', sans-serif"
}
}
],
"ActiveSkin": "brand-blue"
}
方式二: 完整 CSS 文件
创建 custom/brand-red.css:
body.theme-brand-red {
--darra-bg: #1A0A0A;
--darra-panel-bg: #2D1515;
--darra-text: #FFE6E6;
--darra-primary: #D7263D;
--darra-success: #22C55E;
--darra-warning: #F59E0B;
--darra-danger: #FF0000;
--darra-radius: 4px;
--darra-font: 'MiSans', sans-serif;
}
然后在 CustomSkins 中引用:
{
"Name": "brand-red",
"DisplayName": "品牌红",
"CssFile": "custom/brand-red.css"
}
7.4 核心 CSS 变量参考
| 变量 | 默认值 (modern) | 用途 |
|---|---|---|
--darra-bg | #1A1D23 | 页面底色 |
--darra-panel-bg | #23272E | 面板/卡片底色 |
--darra-text | #E6E8EB | 主文本颜色 |
--darra-primary | #4F7CFF | 主色 (按钮/链接/图表主线) |
--darra-success | #22C55E | 成功 (绿 LED/运行态) |
--darra-warning | #F59E0B | 警告 (黄/橙色) |
--darra-danger | #EF4444 | 危险 (红 LED/停止/故障) |
--darra-font | 'MiSans','Segoe UI',sans-serif | 字体族 |
--darra-radius | 8px | 默认圆角 |
--darra-shadow | 0 4px 16px rgba(0,0,0,0.3) | 默认阴影 |
7.5 暗色/浅色模式
// 根据系统偏好自动切换
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
darra.setTheme('modern') // 暗色
} else {
darra.setTheme('modern-light') // 浅色
}
// 监听系统主题变化
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
darra.setTheme(e.matches ? 'modern' : 'modern-light')
})
8. 报警管理
8.1 报警配置
报警在 PLC 侧定义, 通过 UDT 结构体声明:
TYPE T_Alarm :
STRUCT
Code : DINT; (* 唯一编号 *)
Severity : INT; (* 0=Info, 1=Warn, 2=Error, 3=Fatal *)
Active : BOOL;
Acked : BOOL;
Message : STRING[128];
Source : STRING[32]; (* 设备/工位 *)
FirstTime : DT;
LastTime : DT;
END_STRUCT
END_TYPE
报警级别:
| 级别 | 值 | 颜色 | 典型场景 |
|---|---|---|---|
| Info | 0 | 蓝 | 启停/换型/换班 |
| Warn | 1 | 黄 | 温度接近上限 |
| Error | 2 | 橙 | 工艺偏差/单工位故障 |
| Fatal | 3 | 红 | 急停/设备损坏 |
8.2 报警显示
报警列表
<darra-alarm-list max-rows="20"
level-filter="warn,error,fatal"
allow-ack="true"
sort="severity,time"
height="400px"></darra-alarm-list>
报警弹窗
<darra-alarm-popup level-filter="error,fatal"
sound="true"
auto-close="true"
auto-close-sec="15"></darra-alarm-popup>
自定义报警显示
darra.on('alarm', (alarm) => {
// alarm: { code, severity, message, source, timestamp, acked }
const row = document.createElement('div')
row.className = `alarm-row severity-${alarm.severity}`
row.innerHTML = `
<span class="time">${new Date(alarm.timestamp).toLocaleTimeString()}</span>
<span class="badge">${['INFO','WARN','ERROR','FATAL'][alarm.severity]}</span>
<span class="source">${alarm.source}</span>
<span class="msg">${alarm.message}</span>
`
document.getElementById('alarm-container').prepend(row)
})
8.3 报警确认
// 手动确认
darra.acknowledgeAlarm('alarm-001', '已确认, 安排处理')
// 批量确认
darra.getAlarms({ acknowledged: false }).forEach(alarm => {
darra.acknowledgeAlarm(alarm.id, '批量确认')
})
8.4 报警历史
// 获取历史报警
const history = darra.getAlarms({
startTime: Date.now() - 7 * 86400000, // 最近 7 天
acknowledged: true,
limit: 100
})
8.5 报警推送
浏览器通知
if (Notification.permission === 'granted') {
new Notification('[ERROR] 1# 反应釜超温', {
body: '当前温度: 85.3℃, 阈值: 80℃',
icon: '/static/img/alarm-error.png',
requireInteraction: true
})
}
语音播报
function speakAlarm(alarm) {
if (!window.speechSynthesis) return
const label = ['信息', '警告', '错误', '致命'][alarm.severity]
const msg = new SpeechSynthesisUtterance(`${label}: ${alarm.source} ${alarm.message}`)
msg.lang = 'zh-CN'
msg.rate = 0.9
speechSynthesis.cancel()
speechSynthesis.speak(msg)
}
9. 配方管理
9.1 配方概念
配方 (Recipe) = 一组命名的工艺参数集合。每个配方项绑定一个 PLC 变量地址, 激活配方时将参数值写入对应的 PLC 地址。
配方组: "焊接参数"
├── 配方: "碳钢 3mm"
│ ├── 焊接电流: 180A → DB1.SetCurrent
│ ├── 焊接电压: 22V → DB1.SetVoltage
│ └── 送丝速度: 8.5m/min → DB1.WireSpeed
│
├── 配方: "不锈钢 2mm"
│ ├── 焊接电流: 120A
│ └── ...
9.2 配方面板
<darra-recipe-panel
group="焊接参数"
recipe="碳钢_3mm"
show-history="true"
show-compare="true"
height="500px"></darra-recipe-panel>
9.3 配方下发
// 手动下发
darra.loadRecipe('焊接参数', '碳钢_3mm')
.then(() => showToast('配方加载成功', { type: 'success' }))
.catch(err => showToast(`配方加载失败: ${err}`, { type: 'error' }))
// 自动选择配方
const productCode = darra.get('DB1.ProductCode')
let recipeName
switch (productCode) {
case 1: recipeName = '碳钢_3mm'; break
case 2: recipeName = '不锈钢_2mm'; break
default: return
}
darra.loadRecipe('焊接参数', recipeName)
9.4 配方导入/导出
// 导出为 JSON
const json = darra.exportRecipe('焊接参数', '碳钢_3mm', 'json')
// 导出为 CSV
const csv = darra.exportRecipeGroup('焊接参数', 'csv')
// 导入
darra.importRecipe(jsonData, { overwrite: false, validate: true })
10. 安全与权限
10.1 用户认证
启用登录认证:
{
"RequireAuth": true,
"AuthSettings": {
"SessionTimeout": 480,
"MaxLoginAttempts": 5,
"LockoutDuration": 15,
"PasswordMinLength": 8
}
}
登录页面:
<darra-login-panel redirect-on-success="/"></darra-login-panel>
10.2 角色权限
| 角色 | 权限范围 | 典型用户 |
|---|---|---|
viewer | 只读 | 参观者、质量巡检 |
operator | 读写变量、确认报警、切换配方 | 产线操作员 |
engineer | 修改配方、调整参数、编辑 HMI | 工艺工程师 |
admin | 所有权限 | 车间主管、IT |
权限门控控件
<!-- 仅 admin 可见 -->
<darra-permission-gate role="admin">
<darra-button var="M100.0">紧急停车</darra-button>
</darra-permission-gate>
<!-- 多角色 -->
<darra-permission-gate role="admin,engineer">
<button @click="showEditDialog()">编辑参数</button>
</darra-permission-gate>
脚本权限检查
const user = darra.getUserInfo()
// { username: 'wang', role: 'engineer', permissions: ['read', 'write', 'recipe_edit'] }
if (!user.permissions.includes('write')) {
document.getElementById('setpoint-input').disabled = true
}
10.3 屏幕锁定
{
"ScreenLock": {
"Enabled": true,
"Timeout": 5,
"LockOnSleep": true
}
}
// 手动锁定
darra.lockScreen()
// 解锁
darra.unlockScreen(password)
// 重置空闲计时器
darra.resetIdleTimer()
10.4 审计日志
// 获取最近审计日志
const logs = darra.getAuditLogs({ limit: 100 })
// 按条件筛选
const todayLogs = darra.getAuditLogs({
startTime: Date.now() - 86400000,
category: 'write',
user: 'wang'
})
11. 发布与部署
11.1 编译 HMI 项目
HMI 发布 = 保存项目 → 启动 HmiWebServer → 访问端口 18823。没有传统"打包 MSI"的步骤。
- 在 IDE 中 Ctrl+S 保存项目
- 菜单
运行 → 启动 PLC Service - 看到日志:
[Info] [HmiWebServer] 监听 http://+:18823
11.2 发布到运行时
# 启动 Service (带项目)
Darra.PLC.Service.exe --project=myproject.plc
# 启动 LocalHMI 外壳
Darra.PLC.HMI.exe --fullscreen --kiosk
# 或指定 URL
Darra.PLC.HMI.exe --url=http://localhost:18823/local/ --fullscreen --no-esc-exit
11.3 移动端适配
同一局域网下:
- 查 Service 主机 IP:
ipconfig | findstr IPv4 - 手机浏览器输入
http://192.168.1.100:18823/web/ - 添加到主屏 → 创建 PWA 应用图标
11.4 多屏显示
{
"MultiScreen": {
"Enabled": true,
"Screens": [
{ "Id": "main", "Route": "/", "Monitor": 0 },
{ "Id": "alarm", "Route": "/alarm", "Monitor": 1 },
{ "Id": "trend", "Route": "/trend", "Monitor": 2 }
]
}
}
11.5 发布前检查清单
- 所有
<darra-*>的var属性指向真实 PLC 变量 -
DesignWidth/Height匹配真实屏幕 -
WriteWhitelist只包含必须的变量 (最小权限) - WebHMI 必须启用
RequireAuth = true - 断开 PLC 验证: UI 不崩, 显示"断开重连"
- 触摸屏测试: 按钮 ≥ 44×44px
- 移动端测试: 屏幕旋转不遮挡输入框
12. 实际应用示例
12.1 电机控制面板
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<title>电机控制</title>
<link rel="stylesheet" href="/static/darra-hmi.css" />
<script src="/static/alpine.min.js" defer></script>
<script src="/static/darra-plc.js"></script>
</head>
<body class="theme-industrial">
<div style="padding:20px;max-width:600px;margin:0 auto;">
<h1>1# 主电机控制</h1>
<!-- 电机状态 -->
<div x-data="{ running: false, speed: 0, fault: false }"
x-init="
darra.bind('M10.0', v => running = v);
darra.bind('DB3.RPM', v => speed = v);
darra.bind('M10.1', v => fault = v)">
<!-- 电机指示 -->
<darra-motor run-var="M10.0" fault-var="M10.1" speed-var="DB3.RPM"></darra-motor>
<!-- 运行状态 -->
<div style="margin:16px 0;">
状态: <span x-text="fault ? '故障' : running ? '运行中' : '停止'"
:style="fault ? 'color:red' : running ? 'color:green' : 'color:gray'"></span>
</div>
<!-- 转速 -->
<div style="margin:16px 0;">
当前转速: <darra-numeric var="DB3.RPM" format="#,##0" unit="r/min"></darra-numeric>
</div>
<!-- 控制按钮 -->
<div style="display:flex;gap:12px;margin:16px 0;">
<darra-button var="M10.0" mode="toggle" :disabled="fault">启动/停止</darra-button>
<darra-button var="M10.2" mode="momentary" color="#FF0000">急停</darra-button>
<darra-button var="M10.3" mode="momentary" :disabled="!fault">故障复位</darra-button>
</div>
<!-- 速度设定 -->
<div style="margin:16px 0;">
<label>目标转速: <darra-input var="DB3.SetRPM" min="0" max="3000" step="10"></darra-input></label>
</div>
<darra-slider var="DB3.SetRPM" min="0" max="3000" step="10"></darra-slider>
</div>
</div>
</body>
</html>
12.2 报警监控页面
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<title>报警中心</title>
<link rel="stylesheet" href="/static/darra-hmi.css" />
<script src="/static/alpine.min.js" defer></script>
<script src="/static/darra-plc.js"></script>
</head>
<body class="theme-industrial">
<div style="padding:20px;">
<h1>报警中心</h1>
<!-- 报警统计 -->
<div style="display:flex;gap:20px;margin:16px 0;" x-data="{
active: 0, unacked: 0, critical: 0
}" x-init="
darra.on('alarm', () => {
const alarms = darra.getAlarms()
active = alarms.filter(a => !a.acknowledged).length
unacked = alarms.filter(a => !a.acknowledged).length
critical = alarms.filter(a => a.severity === 'Critical' && !a.acknowledged).length
})">
<div style="padding:16px;background:var(--darra-panel-bg);border-radius:8px;">
<div>活动报警</div>
<div style="font-size:48px;font-weight:bold;" x-text="active"></div>
</div>
<div style="padding:16px;background:var(--darra-panel-bg);border-radius:8px;color:#F59E0B;">
<div>未确认</div>
<div style="font-size:48px;font-weight:bold;" x-text="unacked"></div>
</div>
<div style="padding:16px;background:var(--darra-panel-bg);border-radius:8px;color:#EF4444;">
<div>严重报警</div>
<div style="font-size:48px;font-weight:bold;" x-text="critical"></div>
</div>
</div>
<!-- 报警列表 -->
<darra-alarm-list max-rows="50" level-filter="warn,error,fatal" allow-ack="true" height="500px"></darra-alarm-list>
<!-- 报警弹窗 -->
<darra-alarm-popup level-filter="error,fatal" sound="true"></darra-alarm-popup>
</div>
</body>
</html>
12.3 数据仪表盘
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<title>车间总览</title>
<link rel="stylesheet" href="/static/darra-hmi.css" />
<link rel="stylesheet" href="/static/devextreme/dx.common.css" />
<link rel="stylesheet" href="/static/devextreme/dx.dark.css" />
<script src="/static/alpine.min.js" defer></script>
<script src="/static/darra-plc.js"></script>
<script src="/static/chart.umd.js"></script>
<script src="/static/devextreme/dx.all.js"></script>
<script src="/static/darra-devextreme.js"></script>
</head>
<body class="theme-modern">
<div style="display:grid;grid-template-columns:repeat(12,1fr);grid-template-rows:80px 1fr 1fr 80px;gap:16px;padding:24px;height:100vh;background:#0f172a;">
<!-- 标题栏 -->
<header style="grid-column:1/-1;display:flex;align-items:center;justify-content:space-between;border-bottom:2px solid #334155;">
<h1 style="font-size:48px;margin:0;background:linear-gradient(90deg,#38bdf8,#818cf8);-webkit-background-clip:text;color:transparent;">1 号车间生产总览</h1>
<div x-data="{ now: new Date().toLocaleString('zh-CN') }" x-init="setInterval(() => now = new Date().toLocaleString('zh-CN'), 1000)">
<span x-text="now"></span>
</div>
</header>
<!-- KPI 卡片 -->
<div style="grid-column:1/4;grid-row:2/4;background:rgba(30,41,59,0.7);border-radius:12px;padding:24px;display:flex;flex-direction:column;justify-content:center;">
<div style="font-size:22px;color:#94a3b8;">实时 OEE</div>
<div style="font-size:72px;font-weight:700;color:#38bdf8;" x-data="{ v: 0 }" x-init="darra.bind('DB100.OEE', x => v = x)">
<span x-text="(v * 100).toFixed(1)"></span>%
</div>
</div>
<div style="grid-column:4/7;grid-row:2/4;background:rgba(30,41,59,0.7);border-radius:12px;padding:24px;">
<div style="font-size:22px;color:#94a3b8;">今日产量</div>
<div style="font-size:72px;font-weight:700;color:#22C55E;" x-data="{ v: 0 }" x-init="darra.bind('DB100.TodayQty', x => v = x)">
<span x-text="v.toLocaleString()"></span> 件
</div>
</div>
<div style="grid-column:7/10;grid-row:2/4;background:rgba(30,41,59,0.7);border-radius:12px;padding:24px;">
<div style="font-size:22px;color:#94a3b8;">合格率</div>
<div style="font-size:72px;font-weight:700;color:#22C55E;" x-data="{ v: 0 }" x-init="darra.bind('DB100.QualityRate', x => v = x)">
<span x-text="(v * 100).toFixed(2)"></span>%
</div>
</div>
<div style="grid-column:10/-1;grid-row:2/4;background:rgba(30,41,59,0.7);border-radius:12px;padding:24px;">
<div style="font-size:22px;color:#94a3b8;">设备在线</div>
<div style="font-size:72px;font-weight:700;color:#38bdf8;" x-data="{ n: 0, t: 30 }" x-init="darra.bind('DB100.OnlineCount', x => n = x)">
<span x-text="n"></span>/<span x-text="t"></span>
</div>
</div>
<!-- 报警条 -->
<footer style="grid-column:1/-1;grid-row:4;background:rgba(30,41,59,0.7);border-radius:12px;padding:8px 16px;">
<darra-alarm-list max-rows="3" level-filter="error"></darra-alarm-list>
</footer>
</div>
</body>
</html>
12.4 配方管理页面
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<title>配方管理</title>
<link rel="stylesheet" href="/static/darra-hmi.css" />
<script src="/static/alpine.min.js" defer></script>
<script src="/static/darra-plc.js"></script>
</head>
<body class="theme-industrial">
<div style="padding:20px;max-width:1000px;margin:0 auto;">
<h1>焊接参数 - 配方管理</h1>
<p>当前激活配方: <span x-data="{ active: '' }" x-init="
const a = darra.getActiveRecipe();
if (a) active = a.recipe;
" x-text="active || '无'"></span></p>
<darra-recipe-panel
group="焊接参数"
show-history="true"
show-compare="true"
height="500px"></darra-recipe-panel>
<!-- 参数显示 -->
<div style="margin-top:20px;padding:16px;background:var(--darra-panel-bg);border-radius:8px;">
<h3>当前参数</h3>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div>焊接电流: <darra-numeric var="DB1.SetCurrent" format="0.0" unit="A"></darra-numeric></div>
<div>焊接电压: <darra-numeric var="DB1.SetVoltage" format="0.0" unit="V"></darra-numeric></div>
<div>送丝速度: <darra-numeric var="DB1.WireSpeed" format="0.0" unit="m/min"></darra-numeric></div>
<div>气体流量: <darra-numeric var="DB1.GasFlow" format="0.0" unit="L/min"></darra-numeric></div>
</div>
</div>
</div>
</body>
</html>
13. 调试与排错
13.1 浏览器开发者工具
| 工具 | 快捷键 | 用途 |
|---|---|---|
| 元素检查 | F12 或 Ctrl+Shift+I | 查看 DOM 结构和样式 |
| 控制台 Console | Ctrl+Shift+J | 查看 JS 错误和日志 |
| 网络 Network | F12 → Network 标签 | 查看 WebSocket 帧和 HTTP 请求 |
| 性能 Performance | F12 → Performance | 分析页面卡顿原因 |
13.2 常见问题排错表
连接问题
| 现象 | 原因 | 解决 |
|---|---|---|
| 右下角显示"断开" | Service 未启动或端口未监听 | 运行 → 启动 PLC Service, 确认 18823 端口 |
| 连接断断续续 | 网络不稳定或防火墙拦截 | 检查防火墙规则, 确保 WebSocket 端口放行 |
| 重连后值还是旧值 | 缓存未失效 | 按 Ctrl+F5 强刷 |
| 外网访问连不上 | 端口映射未配置 | 配置路由器端口映射, 启用 HTTPS 反代 |
控件问题
| 现象 | 原因 | 解决 |
|---|---|---|
| 控件显示空白 | JS 未加载 | 检查 /static/darra-controls.js 返回 200 |
| 变量值显示 NaN | 变量地址拼写错误 | 确认 var 属性与 PLC 变量名一致 |
| 按钮点击无反应 | 变量不在写入白名单 | 在项目设置中添加白名单 |
| 趋势图不更新 | 变量名拼写错误 | 检查 vars 属性, 看 F12 网络 WS 帧 |
| 滑块拖动太卡 | 写入频率太高 | 加 debounce(300ms) |
样式问题
| 现象 | 原因 | 解决 |
|---|---|---|
| 控件叠在一起 | 未设置 position 或布局 | 在画布中调整位置, 或使用 CSS Grid |
| 主题切换后颜色不变 | CSS 变量未更新 | 检查 ExtraCss 是否覆盖了 --darra-* |
| 字体不显示 | 字体文件未部署 | 确认字体文件在 /static/fonts/ 目录 |
| 触摸屏按钮太小 | 未使用触摸主题 | 切换 touch-panel 主题 |
脚本问题
| 现象 | 原因 | 解决 |
|---|---|---|
darra 对象未定义 | JS 文件未加载 | 确认 <script src="/static/darra-plc.js"></script> 存在 |
bind 回调不触发 | 变量未订阅成功 | F12 Network → WS → 看 subscribe 帧是否发出 |
| 写入失败 | 白名单未通过或变量只读 | 监听 error 事件, 检查 WriteWhitelist |
| 定时器不清理 | 页面切换时未取消 | 在 pageLeave 中 clearInterval |
13.3 WebSocket 调试
// 查看所有 WebSocket 通信
darra.on('ready', () => console.log('WebSocket 已连接'))
darra.on('offline', () => console.warn('WebSocket 断开'))
darra.on('error', (err) => console.error('WebSocket 错误:', err))
// 查看当前连接状态
console.log(darra.getConnectionStatus())
// 'connected' | 'connecting' | 'offline' | 'reconnecting'
13.4 变量调试
// 查看所有已订阅的变量
console.log(darra._subscribedVars)
// 查看某个变量的缓存值
console.log(darra.get('DB1.Temperature'))
// 查看 DB 结构
darra.describeDB('DB1').then(fields => {
console.table(fields)
})
13.5 性能调优
| 问题 | 优化方法 |
|---|---|
| 页面加载慢 | 减少首次加载的控件数量, 使用懒加载 |
| 趋势图卡顿 | 减少 max-points, 增大 update-rate, 关动画 |
| 变量更新延迟 | 降低 updateRate (默认 100ms, 可调至 50ms) |
| 内存持续增长 | 在 pageLeave 中调用 unsubscribeAll() 和 clearInterval |
| 触摸屏响应慢 | 使用 touch-panel 主题, 大按钮减少 DOM 层级 |
13.6 日志查看
# IDE 日志
%AppData%/DarraPLC/logs/ide_*.log
# Service 日志
<Service目录>/logs/service_*.log
# HMI 运行时日志
F12 Console 面板
附录: 速查手册
控件速查
| 控件 | 标签 | 绑定属性 | 主要用途 |
|---|---|---|---|
| 按钮 | <darra-button> | var | 启停控制 |
| 指示灯 | <darra-led> | var | 状态显示 |
| 数值显示 | <darra-numeric> | var | 数值监控 |
| 输入框 | <darra-input> | var | 参数输入 |
| 滑块 | <darra-slider> | var | 连续调节 |
| 开关 | <darra-switch> | var | 开关控制 |
| 下拉框 | <darra-dropdown> | var | 模式选择 |
| 仪表盘 | <darra-gauge> | var | 量程显示 |
| 趋势图 | <darra-trend> | vars | 实时曲线 |
| 进度条 | <darra-progress-bar> | var | 进度显示 |
| 储罐 | <darra-tank> | var | 液位显示 |
| 电机 | <darra-motor> | run-var | 电机状态 |
| 阀门 | <darra-valve> | open-var | 阀门状态 |
| 报警列表 | <darra-alarm-list> | — | 报警显示 |
| 导航按钮 | <darra-nav-button> | route | 页面跳转 |
| 面板 | <darra-panel> | — | 容器分组 |
| 数据表格 | <darra-dx-grid> | var | 表格数据 |
| 登录面板 | <darra-login-panel> | — | 用户登录 |
API 速查
| 函数 | 说明 |
|---|---|
darra.bind(var, cb) | 绑定变量 |
darra.bindDB(db, cb) | 绑定整个 DB |
darra.bindGroup(vars, cb) | 绑定多变量组 |
darra.write(var, val) | 写入变量 |
darra.writeGroup(vals) | 批量写入 |
darra.get(var) | 读取缓存 |
darra.on(event, cb) | 监听事件 |
darra.navigateTo(route) | 页面导航 |
darra.showPopup(html, opts) | 显示弹窗 |
darra.setTheme(name) | 切换主题 |
darra.getSystemInfo() | 系统信息 |
darra.getAlarms(filter) | 获取报警 |
darra.acknowledgeAlarm(id) | 确认报警 |
darra.getUserInfo() | 用户信息 |
darra.logout() | 登出 |
darra.showToast(msg, opts) | 显示通知 |
地址速查
| 地址 | 说明 |
|---|---|
http://localhost:18823/local/ | LocalHMI 首页 |
http://localhost:18823/web/ | WebHMI 首页 |
ws://localhost:18823/ws | WebSocket 地址 |
http://localhost:18823/api/hmi/db/DB1 | DB 结构查询 API |
本文档由 Darra 文档团队维护, 最后更新: 2026-07-26。