自写页 SDK:在受控范围内开发项目页面
SDK 负责把宿主机中的受控数据能力交给自写 HTML 页面。它提供实时 Tag 快照、Tag 订阅、写入、历史查询、报警查询、报警订阅、多语言和表格辅助方法。SDK 提供数据能力和权限边界,不限定页面形态;项目页面可以按现场需要组织为报表、看板、诊断页、质量页、能耗页或客户专用页面。
SDK: 自写页 SDK:在受控范围内开发项目页面
自写页 SDK 面向部署在 HQControl 运行端的项目 HTML 页面。页面在 sandbox iframe 中运行,通过 `HQVISU_CUSTOM_PAGE.ready()` 获取宿主桥 API,在声明范围内读取、订阅、写入 Tag,并查询历史、趋势、报警和语言数据。项目可以用它开发班次报表、能耗汇总、设备诊断、客户看板等自定义页面;页面 UI、布局、计算口径和交互由项目 HTML 自行实现。1. 自写页 SDK 负责什么
SDK 负责把宿主机中的受控数据能力交给自写 HTML 页面。它提供实时 Tag 快照、Tag 订阅、写入、历史查询、报警查询、报警订阅、多语言和表格辅助方法。SDK 提供数据能力和权限边界,不限定页面形态;项目页面可以按现场需要组织为报表、看板、诊断页、质量页、能耗页或客户专用页面。
- 适合:班次产量报表、能耗日报、设备健康页、质量追溯页、工单看板、客户展示页。
- 页面形式:普通 HTML,可包含独立 CSS、JavaScript、图表库和组件逻辑。
- 数据来源:声明范围内的 Tag、历史数据、报警数据和语言资源。
- 责任边界:HQControl 提供数据和权限边界,页面展示和业务计算由项目代码负责。
- SDK 版本:当前手册按
hq-custom-page.js的 SDK 1.3.0 能力编写。
2. 运行模型和安全边界
自写页由 HQControl 以 sandbox iframe 方式加载。页面不直接继承主应用内部对象,也不绕过登录、权限、Tag 范围和服务端校验。页面只能等待宿主注入 HQVISU_HOST,再通过 HQVISU_CUSTOM_PAGE.ready() 拿到 SDK API。
- 声明范围是安全边界:未在页面声明的 Tag 不能作为正常读写目标。
- 读取、历史、报警都受
readableTags范围限制。 - 写入必须同时满足
writableTags声明和当前用户tag:write权限。 - 自写页没有可信内建画面的全部内部能力;导航、弹窗等主应用内部能力不应写成 SDK 承诺。
- 在普通浏览器直接打开自写页文件时没有宿主桥,
ready()会超时失败。
3. 最小接入步骤
开发自写页时先写声明,再引入 SDK,最后在 ready() 之后访问数据。不要在 ready() 解析前读取 HQVISU_HOST,此时宿主桥可能还没有注入。
- 在 HTML 中放置
type="application/json"、id="hqvisu-custom-page"的声明块。 - 声明
readableTags和需要写入的writableTags,只写页面真实需要的点位。 - 引入
/runtime/hqVisuDesigner/hq-custom-page.js。 - 调用
HQVISU_CUSTOM_PAGE.ready(),在回调或await之后使用 SDK API。 - 先做实时值和质量显示,再增加历史、报警、写入和表格。
- 上线前在 HQControl 运行端打开页面,不以本地文件直接打开作为验收结果。
<!doctype html>
<html>
<head><meta charset="utf-8"><title>产线报表</title></head>
<body>
<section id="app">正在连接 HQControl...</section>
<script type="application/json" id="hqvisu-custom-page">
{
"readableTags": ["Line1.Temperature", "Line1.RunState", "Line1.Output", "Line1.Energy"],
"writableTags": ["Line1.Setpoint"]
}
</script>
<script src="/runtime/hqVisuDesigner/hq-custom-page.js"></script>
<script>
HQVISU_CUSTOM_PAGE.ready().then((api) => {
document.getElementById("app").textContent =
"当前温度:" + api.readNumber("Line1.Temperature", 0).toFixed(1) + " ℃";
}).catch((err) => {
document.getElementById("app").textContent = "SDK 初始化失败:" + err.message;
});
</script>
</body>
</html>
4. 点位声明写法
服务端会解析自写页 HTML 中的 JSON 声明块,并据此签发页面能力。点位必须使用 设备.点位 形式的完整点分编码,例如 Line1.Temperature。不符合格式的声明会被忽略。
- 可用配置块 ID:
hqvisu-custom-page、hqvisu-custom-page-config、hqvisu-page-config、hqvisu-dependencies。 - 可读字段别名:
readableTags、tags、tagNames、readTags、watchTags。 - 可写字段别名:
writableTags、writeTags、outputs。 - 字段值可以是数组,也可以是用换行、逗号或分号分隔的字符串。
- SDK 1.2.0 之后,配置块中声明的可读点会自动纳入订阅范围;
ready({ tags: [...] })可用于额外兼容或追加订阅。
<script type="application/json" id="hqvisu-custom-page">
{
"readableTags": [
"Line1.Temperature",
"Line1.Pressure",
"Line1.RunState",
"Line1.Output",
"Line1.Energy"
],
"writableTags": ["Line1.Setpoint"]
}
</script>
5. ready() 与生命周期
ready() 等待宿主桥就绪,默认超时 5 秒。它返回 Promise,也支持传回调。页面不在 HQControl 运行端打开时,常见错误是 HQVISU_HOST is not available. Open this page from the HQ runtime shell.。
ready(options):返回Promise<api>。ready(callback):宿主就绪后执行回调。timeoutMs:等待宿主桥就绪超时,默认 5000ms。writeTimeoutMs、historyTimeoutMs、alarmsTimeoutMs:写入、历史和报警请求超时配置。destroy():页面卸载或重建时取消 SDK 订阅,避免旧回调继续运行。
const api = await HQVISU_CUSTOM_PAGE.ready({
timeoutMs: 5000,
historyTimeoutMs: 15000,
alarmsTimeoutMs: 15000
});
window.addEventListener("beforeunload", () => {
api.destroy();
});
6. 读取实时值和质量
读取方法是同步的,返回宿主机推送到页面的最近一次快照,不会每次调用都向服务器发请求。所有读取都建议传 fallback,避免页面初始态、断线或未声明点位导致显示 undefined。
read(tag, fallback):读取原始值。readNumber/readTagDouble:读取数字,无法转换时返回 fallback。readTagInteger:读取整数。readBoolean/readTagBoolean:读取布尔,支持 true/false、1/0、yes/no、on/off。readString、readJson、readTagDateTime:读取字符串、JSON 和时间。readTagQuality、readTagQualityGood:读取质量,质量不是 good 时应显示异常提示。getMeta(tag):读取点位元信息,例如质量、显示名、数据类型等。
const temp = api.readNumber("Line1.Temperature", 0);
const running = api.readBoolean("Line1.RunState", false);
const qualityGood = api.readTagQualityGood("Line1.Temperature");
renderKpi({
title: "当前温度",
value: temp.toFixed(1),
unit: "℃",
state: qualityGood ? "normal" : "bad-quality",
subtitle: running ? "产线运行中" : "产线停止"
});
7. 订阅实时变化
subscribe(tags, callback) 会订阅声明范围内的 Tag 变化,并返回取消订阅函数。回调参数是变化项数组,每个 item 包含 tagName、fullCode、value、quality 等字段。只想让快照开始进入页面、暂时不需要回调时,可使用 track(tags)。
- 订阅点位应来自页面声明范围。
- 回调里可以重新调用
readNumber、readBoolean等方法读取当前快照。 - 离开页面、切换报表或销毁组件时要调用取消订阅函数。
- 高频页面应集中订阅一组 Tag,再统一刷新 UI,避免每个组件重复订阅。
const stopRealtime = api.subscribe([
"Line1.Temperature",
"Line1.RunState",
"Line1.Output"
], (items, host) => {
updateRealtimeCards({
temperature: host.readNumber("Line1.Temperature", 0),
running: host.readBoolean("Line1.RunState", false),
output: host.readNumber("Line1.Output", 0)
});
});
// 不再需要实时刷新时
// stopRealtime();
8. 写入 Tag
写入方法是异步 Promise。写入会走宿主机服务端校验:点位必须在 writableTags 内,当前用户必须具备写入权限,Tag 自身也要允许控制。自写页写入是程序化动作,页面应自行做好确认、防误触和失败提示。
writeTag(tag, value, options):按原值写入,可用options.valueType指定类型。writeTagBoolean、writeTagInteger、writeTagDouble、writeTagString、writeTagJson、writeTagDateTime:类型化写入。- 常见失败:点位未声明、用户无权限、点位不存在、参数非法、写入太频繁、宿主不支持或请求超时。
- 涉及设定值、复位、确认类操作时,页面必须先让用户确认,再调用写入。
async function increaseSetpoint() {
const current = api.readNumber("Line1.Setpoint", 0);
const next = current + 1;
if (!confirm(`确认把设定值写为 ${next.toFixed(1)} ?`)) return;
try {
const result = await api.writeTagDouble("Line1.Setpoint", next, {
reason: "custom page setpoint adjustment"
});
showToast("写入已提交:" + (result.status || "accepted"));
} catch (err) {
showToast("写入失败:" + err.message, "error");
}
}
9. 查询历史和趋势
history.query() 用于按时间范围、间隔和聚合方式取历史数据。接口返回历史数据结果,页面可用返回结果绘制趋势图、能耗报表、班次统计或质量分析。
tags最多 16 个,且必须在readableTags范围内。start、end使用可解析时间,范围最多 366 天。interval可使用15m、1h、1d、1w等,最小 1 分钟。aggregate支持avg、min、max、sum、count、first、last、delta、raw。delta适合电表、水表、产量累积量等报表口径。- 返回点数默认/最大 20000;超出预算时宿主可能放粗间隔,并通过
effectiveInterval、truncated表示实际结果。
const weekEnergy = await api.history.query({
tags: ["Line1.Energy", "Line2.Energy"],
start: "2026-07-01T00:00:00+08:00",
end: "2026-07-08T00:00:00+08:00",
interval: "1d",
aggregate: "delta",
points: 20000
});
renderEnergyReport(weekEnergy.items, {
interval: weekEnergy.effectiveInterval,
truncated: weekEnergy.truncated
});
10. 查询和订阅报警
报警接口用于查询当前活动报警、分页查询历史报警,以及订阅报警变化。报警范围按页面 readableTags 的整体范围确定;如果页面没有声明可读 Tag,报警查询应返回空范围。
alarms.getActive():获取当前活动报警快照。alarms.queryHistory(payload):按时间、等级、状态、来源、分页查询历史报警。alarms.subscribe(callback):订阅报警变化,返回取消订阅函数。- 历史报警默认可按最近 24 小时查询,单次跨度和分页大小受宿主限制。
- 报警等级通常按
critical、high、medium、low、info展示;状态通常是active或recovered。
const active = await api.alarms.getActive();
renderAlarmSummary(active.items || []);
const history = await api.alarms.queryHistory({
start: "2026-07-07T00:00:00+08:00",
end: "2026-07-08T00:00:00+08:00",
severity: ["critical", "high"],
state: ["active", "recovered"],
limit: 200,
offset: 0
});
const stopAlarms = api.alarms.subscribe((items) => {
renderActiveAlarmList(items);
});
11. 报表页的数据组织方式
项目报表页由自写 HTML 页面基于 SDK 数据能力实现。页面把实时值、历史查询、报警列表和自有样式组织在同一业务视图中;建议先确定报表口径,再封装数据函数,最后实现 UI,使每个指标都有明确来源和计算口径。
- 实时 KPI:用
readNumber/subscribe展示当前产量、温度、运行状态、质量。 - 趋势曲线:用
history.query获取小时、天、周维度的数据,再交给页面图表库渲染。 - 能耗或产量报表:累积表使用
aggregate: "delta",普通模拟量使用avg/min/max。 - 报警摘要:用
alarms.getActive()和queryHistory()汇总当前风险和历史记录。 - 数据表格:简单表格可用
renderTable,复杂表格可由项目组件渲染。 - 异常态:空数据、越权、质量 bad/stale/pending、查询超时都要有页面提示。
async function renderShiftReport(api) {
const live = {
output: api.readNumber("Line1.Output", 0),
energy: api.readNumber("Line1.Energy", 0),
running: api.readBoolean("Line1.RunState", false),
quality: api.readTagQuality("Line1.Output", "pending")
};
const trend = await api.history.query({
tags: ["Line1.Output"],
start: "2026-07-08T08:00:00+08:00",
end: "2026-07-08T20:00:00+08:00",
interval: "1h",
aggregate: "sum"
});
const alarms = await api.alarms.getActive();
drawKpiCards(live);
drawOutputTrend(trend.items || []);
api.renderTable("#alarm-table", alarms.items || [], {
columns: [
{ key: "severity", label: "等级" },
{ key: "messageText", label: "报警内容" },
{ key: "activeTs", label: "发生时间" }
]
});
}
12. renderTable 与 bindJsonTable
renderTable(target, rows, options) 是轻量表格辅助,不是报表系统。它会把数组、{ rows: [...] } 或 { items: [...] } 渲染成 HTML table。bindJsonTable(target, tagName, options) 会读取 JSON Tag,并在 Tag 更新时自动重渲染表格。
target可以是 CSS 选择器或 DOM 元素。options.columns可传字符串数组,也可传{ key, label }对象数组。- 未提供 columns 时,SDK 会根据前 25 行数据推断列。
options.className可指定表格 class,页面自行写 CSS。- 复杂排序、分页、冻结列和导出 Excel 应由项目页面或第三方表格组件实现。
api.renderTable("#quality-table", {
rows: [
{ tag: "Line1.Temperature", value: "72.4", quality: "good" },
{ tag: "Line1.Pressure", value: "0.68", quality: "good" }
]
}, {
className: "report-table",
columns: [
{ key: "tag", label: "点位" },
{ key: "value", label: "当前值" },
{ key: "quality", label: "质量" }
]
});
// JSON Tag 内容变化时自动刷新表格
const stopTable = api.bindJsonTable("#event-table", "Line1.EventRows", {
columns: ["time", "type", "message"]
});
13. 多语言
自写页可以跟随 HQControl 当前语言切换。系统翻译包中的 key 可通过 language.translate() 读取;页面内的业务文案,也可以根据 language.current() 自行切换。
language.current()/getLocale():读取当前语言码。language.translate(key, fallback)/t(key, fallback):读取系统翻译包。language.onChange(callback):语言变化时重渲染页面,返回取消函数。language.setLocale(locale):切换运行时语言,会影响整个运行时。
function renderTitle() {
const locale = api.language.current();
document.getElementById("title").textContent =
locale === "en" ? "Line A Shift Report" : "A 线班次报表";
}
renderTitle();
const stopLanguage = api.language.onChange(renderTitle);
14. 错误处理和交付检查
同步读取使用 fallback 兜底;写入、历史和报警是 Promise,必须用 try/catch 或 .catch() 处理。交付时应同时验证权限、质量、空数据、超时和控制台错误。
- 用管理员和普通用户各打开一次页面,确认权限边界符合预期。
- 故意移除一个未声明 Tag,确认页面有清晰错误或空态,不出现白屏。
- 断开或模拟 bad/stale 质量,确认页面不会把异常值当正常值展示。
- 验证历史查询时间范围、聚合口径、单位、小数位和图表说明。
- 验证写入动作有确认、失败提示和审计原因。
- 打开浏览器控制台,确认没有 SDK 初始化失败、404、JSON 解析错误或未捕获 Promise。
try {
const history = await api.history.query({
tags: ["Line1.Output"],
start,
end,
interval: "1h",
aggregate: "sum"
});
renderTrend(history.items || []);
} catch (err) {
renderEmptyState("历史查询失败:" + err.message);
}
图示用于说明自写 HTML 页面如何把实时快照、历史查询、报警摘要和表格辅助组织为项目报表页;页面样式和业务计算由项目代码实现。