跳至主要内容

页面 SDK:快速上手

将 HTML 上传到“画面文件”并从运行菜单打开,Runtime 会自动加载 SDK,无需安装 npm 包或添加 script。调用一次 ready(),后续复用返回的 api。

开发指南

开发指南: 页面 SDK:快速上手

使用页面 SDK 读取 Tag、写入设定值并查询报警和趋势。示例适用于原生 HTML 和 Vue;发布前须替换项目点位并核对权限。

SDK 加载方式

将 HTML 上传到“画面文件”并从运行菜单打开,Runtime 会自动加载 SDK,无需安装 npm 包或添加 script。调用一次 ready(),后续复用返回的 api。

JavaScriptJavaScript
HQVISU_CUSTOM_PAGE.ready().then(async (api) => {
  // Use the examples below here. Reuse this api object.
}).catch(error => console.error(error.message));

完成标志 原生 HTML 将代码放在 body 末尾的 <script> 中,页面元素放在前面。按需选取下文片段放进同一 async 回调,复用 api。独立 Vite 服务器、file:// 和其它网站不提供此运行环境。

声明页面使用的 Tag

把这段声明放进入口 HTML 的 head。将 <device-code>.<tag-code> 替换成实际完整编码;只读页面把 writableTags 改成 []。Vue 也写在 index.html 中。

HTMLHTML
<script>window.HQVISU_PAGE_META = { layoutMode: "native" };</script>
<script type="application/json" id="hqvisu-custom-page">
{
  "readableTags": ["<device-code>.<tag-code>"],
  "writableTags": ["<device-code>.<tag-code>"]
}
</script>

完成标志 layoutMode: "native" 让普通 HTML/Vue 按窗口自适应布局。readableTags 是读范围,writableTags 是写范围;声明不会授予账号原本没有的控制权限。

读取一个 Tag

添加 <output id="value"></output>,用 ReadTagDouble 读数值,null 指定不可用时的返回值。整数、布尔和文本分别用 ReadTagInteger、ReadTagBoolean、ReadTagString。

JavaScriptJavaScript
const value = api.ReadTagDouble("<device-code>.<tag-code>", null);
document.getElementById("value").textContent = value ?? "—";

完成标志 读取同步返回已采集的当前值,不会每次调用都去读 PLC。null 显示为“—”,不能当成真实 0。

值变化时自动刷新

在 HTML 放一个 <output id="value"></output>,用 subscribe 通知页面刷新。无需 setInterval 反复查询。

JavaScriptJavaScript
const tag = "<device-code>.<tag-code>";
function renderValue() {
  const value = api.ReadTagDouble(tag, null);
  document.getElementById("value").textContent = value ?? "—";
}
renderValue();
const stop = api.subscribe([tag], renderValue);
// Call stop() when this view is removed.

完成标志 首次进入立即显示当前值,后续变化自动更新。组件退出时调用 stop()。

点击按钮写入一次 Tag

添加 <button id="write" disabled>写入</button> 和 <pre id="result"></pre>。下面写入一次 12.5;换成项目允许的目标值。目标需声明为可写、启用 Tag 控制,并由具有 tag:write 权限的账号操作。

JavaScriptJavaScript
const button = document.getElementById("write");
const output = document.getElementById("result");
button.disabled = !(api.supports("tag.write") && await api.auth.hasPermission("tag:write"));
button.onclick = async () => {
  if (button.disabled) return;
  const tag = "<device-code>.<tag-code>";
  if (!api.ReadTagQualityGood(tag)) { output.textContent = "Tag unavailable"; return; }
  if (!confirm("Write 12.5?")) return;
  button.disabled = true;
  try {
    const result = await api.WriteTagDouble(tag, 12.5, { reason: "Operator setpoint update" });
    const done = result?.accepted === true && result?.status === "done"
      && result?.outcomeUncertain !== true && result?.operatorVerificationRequired !== true
      && result?.transportOnly !== true;
    output.textContent = (done ? "Command completed; verify collected value: " : "Result unconfirmed; verify before another write: ")
      + JSON.stringify(result ?? null);
  } catch (error) {
    output.textContent = "Result unconfirmed: " + error.message + " " + (error.commandId || error.result?.commandId || "");
  }
  // Reconcile the result before another write. Never retry automatically.
};

完成标志 整数、布尔用 WriteTagInteger、WriteTagBoolean。本例每页实例仅提交一次,随后禁用按钮;done 仍须核对后续正常采集值。刷新会重置按钮,下次操作前须核对上次结果;需跨刷新阻断时,保存待核对状态。

查询当前报警(一次)

在 HTML 添加 <div id="alarms"></div> 和 <p id="alarm-status"></p>。getActive 查询当前报警,renderTable 把 items 显示成表格。

JavaScriptJavaScript
const alarms = await api.alarms.getActive({ limit: 100 });
api.renderTable("#alarms", alarms.items);
document.getElementById("alarm-status").textContent = alarms.hasMore ? "More records available" : "";

完成标志 本例仅查询一次。实时列表用 alarms.subscribe 通知重新查询,不直接追加回调数据;退出时取消订阅。空 items 表示当前范围无报警,hasMore=true 表示仍有记录。其它设备或分组须配置报警范围。

查看最近一小时的历史报警

添加 <div id="alarm-history"></div> 和 <p id="history-status"></p>。历史报警用 alarms.queryHistory 查询,显示在独立表格中。

JavaScriptJavaScript
const alarmEnd = new Date();
const alarmHistory = await api.alarms.queryHistory({
  start: new Date(alarmEnd.getTime() - 3600000).toISOString(),
  end: alarmEnd.toISOString(),
  limit: 100
});
api.renderTable("#alarm-history", alarmHistory.items);
document.getElementById("history-status").textContent = alarmHistory.hasMore ? "More records available" : "";

查询趋势数据

先为 Tag 启用趋势归档,添加 <div id="trend"></div> 和 <p id="trend-status"></p>。下面把最近一小时的分钟平均值显示成表格;将同一份 points 交给图表组件即可画曲线。

JavaScriptJavaScript
const trendEnd = new Date();
const trend = await api.history.query({
  tags: ["<device-code>.<tag-code>"],
  start: new Date(trendEnd.getTime() - 3600000).toISOString(),
  end: trendEnd.toISOString(), interval: "1m", aggregate: "avg"
});
const points = (trend.items[0]?.points || []).map(point => ({
  time: point.t, value: point.quality === "good" ? point.value : null
}));
api.renderTable("#trend", points);
document.getElementById("trend-status").textContent = trend.effectiveInterval;

完成标志 无归档时返回空数据;异常质量用 null 保留曲线缺口。effectiveInterval 是实际采样间隔。

打开设备弹窗

先在“弹窗模板”保存模板,添加 <button id="open-popup">打开设备</button>,再绑定点击。替换设备编码、模板编码和已声明的 Tag。

JavaScriptJavaScript
document.getElementById("open-popup").onclick = () => {
  try {
    api.dialog.openDevicePopup({
      plc: "<device-code>",
      templateCode: "<popup-template-code>",
      focusTagNames: ["<device-code>.<tag-code>"]
    });
  } catch (error) { console.error(error.message); }
};

完成标志 弹窗中的显示、读写、报警和趋势由模板配置决定。调用返回不代表弹窗内写入已完成;实际核对弹窗与目标值。

跳转到其它页面

按目标选择下面一个调用,放在按钮的点击处理函数中。相对路径以当前画面为起点;在 assets 根内可以跳到上级、下级和其它目录。

JavaScriptJavaScript
// Use one of these calls in your button's click handler:
api.navigation.gotoPage("details/equipment.html");   // Child page
api.navigation.gotoPage("../overview.html");         // Parent page
api.navigation.gotoPage("/assets/<folder>/index.html"); // Another folder
api.navigation.openUrl("https://<server-host>/manual", { openInNewWindow: true });

跟随系统切换中英文

系统顶栏 LANG 已负责切换,不必再加语言按钮。给标题加 id="title",读取当前语言并订阅变化。

JavaScriptJavaScript
function renderText() {
  const zh = api.language.current().startsWith("zh");
  document.getElementById("title").textContent = zh ? "设备总览" : "Equipment overview";
}
renderText();
const stopLanguage = api.language.onChange(renderText);
// Call stopLanguage() when this view is removed.

在 Vue 中使用同一套 API

Vue 3 项目在 index.html 中添加布局和 Tag 声明,再保存并挂载以下完整只读组件。onMounted 后取得 api;发布到 Runtime 后自动加载 SDK。

VueVue
<script setup>
import { ref, onMounted, onUnmounted } from "vue";
const value = ref(null), error = ref("");
let stop, disposed = false;
onMounted(async () => {
  try {
    const api = await window.HQVISU_CUSTOM_PAGE.ready();
    if (disposed) return;
    const tag = "<device-code>.<tag-code>";
    const refresh = () => { value.value = api.ReadTagDouble(tag, null); };
    refresh();
    stop = api.subscribe([tag], refresh);
  } catch (err) { if (!disposed) error.value = err.message; }
});
onUnmounted(() => { disposed = true; stop?.(); });
</script>
<template>
  <p v-if="error">{{ error }}</p>
  <output v-else>{{ value ?? "—" }}</output>
</template>

完成标志 设置 Vite base: "./",按原目录上传 dist 内容。卸载只取消组件订阅,不调用 api.destroy()。本例 api 位于 onMounted 内;按钮调用须在组件作用域保存 api,见完整 Vue 写入示例。

完整 HTML:替换点位后发布

需要从零开始时复制这个完整只读页,替换两处 Tag 编码。它使用 Runtime 自动提供的 SDK。

展开完整源代码
HTMLHTML
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Mingcedar live value</title>
<script type="application/json" id="hqvisu-custom-page">
{"readableTags":["<device-code>.<process-tag-code>"],"writableTags":[]}
</script>
<script>window.HQVISU_PAGE_META = { layoutMode: "native" };</script>
<h1>Live value</h1>
<p id="status" role="status">Connecting…</p>
<output id="value">Waiting for data</output>
<script>
(async () => {
  const tag = "<device-code>.<process-tag-code>";
  const status = document.getElementById("status");
  const output = document.getElementById("value");
  const api = await HQVISU_CUSTOM_PAGE.ready({timeoutMs: 10000});
  function refresh() {
    const quality = api.ReadTagQuality(tag);
    const value = api.ReadTagDouble(tag, null);
    status.textContent = "Connected — quality: " + quality;
    output.textContent = quality === "good" && value !== null
      ? String(value) : "Unavailable";
  }
  const stop = api.subscribe([tag], refresh);
  refresh();
  window.addEventListener("pagehide", event => {
    if (!event.persisted) { stop(); api.destroy(); }
  });
})().catch(error => {
  document.getElementById("status").textContent = error.message;
});
</script>
</html>

发布到运行页面

  1. 配置中心 → 画面文件:上传 HTML;Vue 上传构建后 dist 的内容并保留目录。
  2. 配置中心 → 菜单:选择“页面(直接打开 HTML)”,把入口指向刚上传的 HTML。
  3. 从运行菜单打开。先确认 Tag 值,再验证需要的操作;修改后覆盖发布并刷新。

常见问题与排查

  • HQVISU_CUSTOM_PAGE 未定义:确认从当前 Runtime 的运行菜单打开;独立网页不会自动提供 SDK。
  • 读到 null:核对静态声明、完整 Tag 编码及数据质量。
  • 写入不可用:核对 writableTags、账号权限和 Tag 控制标志;未知结果先核对,不重发。
  • 其他数据类型和选项见 API 参考。

页面交付信息

开发和验收自写页面时,准备实际 Tag 编码与类型、允许的写入值、页面功能及目标运行菜单。入口 HTML 中的声明须与页面代码一致。

  • 记录页面使用的 Tag、报警和历史范围;写入点同时明确账号权限、控制许可和设备允许值。
  • 从运行菜单打开页面,核对 SDK 初始化、GOOD 质量、系统 LANG 切换和组件卸载后的订阅清理。
  • 验证写入回执与后续正常采集值;结果未确认时保留命令标识,下一次操作前先核对。
  • 缺少实际编码、类型或授权范围时,先补全配置再发布。

再次点击图片或按 Esc 关闭