Mingcedar

所属产品: Mingcedar CONTROL

自写页面 SDK 接口参考 | Mingcedar

SDK 为项目 HTML 提供实时 Tag、类型化读写、历史、报警、用户、权限、语言、导航、设备弹窗及表格接口。页面布局和业务计算由项目代码负责。

首次发布 最近更新

<p class="manual-start-link">第一次接入?<a href="/hqserver/manual/custom-page-tutorial/">从 SDK 入门教程开始</a>,本页用于查询接口参数与返回值。</p><div class="manual-doc-article manual-doc-article-section"><h2 id="custom-page-sdk">SDK: 自写页面 SDK 接口参考</h2>

原生 HTML/Vue 接入与公共 API 参考。Tag 读写沿用 PPT 的 ReadTag*/WriteTag*,语言由系统顶栏 LANG 切换。

<div class="doc-card-list doc-tutorial">

<article class="doc-card">

<h3 id="custom-page-sdk-item-1">自写页 SDK 负责什么</h3>

<p>SDK 为项目 HTML 提供实时 Tag、类型化读写、历史、报警、用户、权限、语言、导航、设备弹窗及表格接口。页面布局和业务计算由项目代码负责。</p>

<ul>

<li>适合:班次产量报表、能耗日报、设备健康页、质量追溯页、工单看板、客户展示页。</li>

<li>页面形式:普通 HTML、原生 ES Module、构建后的 Vue 页面;CSS、图表和组件随工程发布。</li>

<li>数据来源:声明范围内的 Tag、历史数据、报警数据和语言资源。</li>

<li>责任边界:Mingcedar 提供数据和权限边界,页面展示和业务计算由项目代码负责。</li>

<li>SDK 版本:1.5.0。</li>

</ul>

<p class="doc-step-result"><strong>完成标志</strong> 首次接入请看“SDK 快速上手”。除完整页面/组件外,片段均假设已取得 api,并已准备所需页面元素和项目渲染函数。</p>

<ul class="doc-related-links">

<li><a href="/hqserver/manual/custom-page-tutorial/">从读取一个 Tag 开始</a></li>

</ul>

</article>

<article class="doc-card">

<h3 id="custom-page-sdk-item-2">运行来源与权限边界</h3>

<p>项目页面在独立 Screen Origin 的 iframe 中运行,与 Portal 管理端隔离。宿主桥校验来源、页面窗口、会话及文档身份。页面可以使用本来源的 Cookie、存储、URL 参数和普通 Web Worker,不支持 Service Worker;无法取得 Portal Cookie、管理端 DOM 或配置接口。数据和写入仍受页面声明及服务端权限控制。</p>

</article>

<article class="doc-card">

<h3 id="custom-page-sdk-item-3">页面代码本身可以做什么</h3>

<p>自写页可使用 DOM、CSS、组件、图表库及浏览器和 iframe sandbox 允许的交互能力;SDK 管理 Mingcedar 宿主能力。</p>

<ul>

<li>支持普通 DOM 和组件逻辑,以及 <code>alert</code>、<code>confirm</code>、<code>prompt</code>。</li>

<li>支持 <code>window.open</code>、表单提交、浏览器导航和用户主动触发的下载;弹窗拦截和用户激活规则仍由浏览器决定。</li>

<li>支持 Clipboard API,但必须同时满足 HTTPS/安全上下文、用户激活、浏览器权限和企业策略。</li>

<li>不支持 Service Worker 注册;页面不能获得 Portal Cookie、登录 Token、Admin DOM、配置 API 或原始宿主服务对象。</li>

<li>页面不能直接轮询 PLC;SDK 不提供报警确认、搁置或关闭操作。</li>

<li><code>api.supports()</code> 只判断宿主桥能力,不判断浏览器 API 是否存在或获准。</li>

</ul>

</article>

<article class="doc-card">

<h3 id="custom-page-sdk-item-4">SDK 已自动加载,模块引入按需使用</h3>

<p>Runtime 自动为发布的 HTML/Vue 页面加载 SDK,直接 await HQVISU_CUSTOM_PAGE.ready() 即可,无需重复添加 script。</p>

<ul>

<li>偏好 ES Module 的页面可 import { ready } from &quot;/runtime/hqVisuDesigner/hq-custom-page.mjs&quot;;模块会复用已安装的 SDK。</li>

<li>仅在自备 Mock 等未自动加载的环境,才需要按 hq-common.js → hq-custom-page.js 手动加载;加载文件本身不会创建正式运行权限。</li>

<li>静态 Tag 声明仍需放在入口 HTML;无需安装 SDK npm 包。Vue/Vite 开发服务器和本地 file:// 不等于正式运行环境。</li>

</ul>

<div class="manual-code" data-code-language="javascript"><div class="manual-code-toolbar"><span>JavaScript</span><span class="manual-code-tools"><small>JavaScript</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: JavaScript">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#005CC5">HQVISU_CUSTOM_PAGE</span><span style="color:#24292E">.</span><span style="color:#6F42C1">ready</span><span style="color:#24292E">().</span><span style="color:#6F42C1">then</span><span style="color:#24292E">(</span><span style="color:#D73A49">async</span><span style="color:#24292E"> (</span><span style="color:#E36209">api</span><span style="color:#24292E">) </span><span style="color:#D73A49">=></span><span style="color:#24292E"> {</span></span>

<span class="line"><span style="color:#6A737D"> // Use the examples below here. Reuse this api object.</span></span>

<span class="line"><span style="color:#24292E">}).</span><span style="color:#6F42C1">catch</span><span style="color:#24292E">(</span><span style="color:#E36209">error</span><span style="color:#D73A49"> =></span><span style="color:#24292E"> console.</span><span style="color:#6F42C1">error</span><span style="color:#24292E">(error.message));</span></span></code></pre></div>

</article>

<article class="doc-card">

<h3 id="custom-page-sdk-item-5">点位声明写法</h3>

<p>服务端解析 HTML 的 JSON 块及受支持的数据声明,签发页面能力。Tag 须用完整点分编码,如 <code>&lt;device-code&gt;.&lt;temperature-tag-code&gt;</code>;位引用限规范的 <code>[0]</code>–<code>[31]</code>。无效或超限声明会影响能力签发。</p>

<ul>

<li>可用配置块 ID:<code>hqvisu-custom-page</code>、<code>hqvisu-custom-page-config</code>、<code>hqvisu-page-config</code>、<code>hqvisu-dependencies</code>。</li>

<li>可读字段别名:<code>readableTags</code>、<code>tags</code>、<code>tagNames</code>、<code>readTags</code>、<code>watchTags</code>。</li>

<li>可写字段别名:<code>writableTags</code>、<code>writeTags</code>;<code>outputs</code> 中的相同声明也会被收集。</li>

<li><code>dependencies</code> 下支持 <code>tags</code>、<code>tagNames</code>、<code>readableTags</code>、<code>readTags</code>、<code>writableTags</code>、<code>writeTags</code>。</li>

<li>字段值可以是数组,也可以是用换行、逗号或分号分隔的字符串。</li>

<li>位引用 <code>&lt;device-code&gt;.&lt;status-word-tag-code&gt;[3]</code> 只用于读取,例如 ReadTagBoolean(name, null)。写位用 WriteTagBit(&quot;&lt;device-code&gt;.&lt;status-word-tag-code&gt;&quot;, 3, &quot;set&quot;),父 Tag 同时声明可读、可写;不能把 [3] 传给任何写入方法。历史也查询父 Tag。</li>

<li>手册中的 <code>&lt;device-code&gt;</code>、<code>&lt;tag-code&gt;</code> 是占位符;发布前必须替换为项目实际编码,不能原样填写。</li>

<li>ready({ tags }) 只请求跟踪,不能扩大服务端可写、历史、报警或 Popup 范围;把需要操作且需要读回的 Tag 同时放进 readableTags 和 writableTags。</li>

</ul>

<div class="manual-code" data-code-language="html"><div class="manual-code-toolbar"><span>HTML</span><span class="manual-code-tools"><small>HTML</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: HTML">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#24292E">&#x3C;</span><span style="color:#22863A">script</span><span style="color:#6F42C1"> type</span><span style="color:#24292E">=</span><span style="color:#032F62">"application/json"</span><span style="color:#6F42C1"> id</span><span style="color:#24292E">=</span><span style="color:#032F62">"hqvisu-custom-page"</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E">{</span></span>

<span class="line"><span style="color:#24292E"> "readableTags": [</span></span>

<span class="line"><span style="color:#24292E"> "&#x3C;device-code>.&#x3C;temperature-tag-code>",</span></span>

<span class="line"><span style="color:#24292E"> "&#x3C;device-code>.&#x3C;pressure-tag-code>",</span></span>

<span class="line"><span style="color:#24292E"> "&#x3C;device-code>.&#x3C;run-state-tag-code>",</span></span>

<span class="line"><span style="color:#24292E"> "&#x3C;device-code>.&#x3C;output-tag-code>",</span></span>

<span class="line"><span style="color:#24292E"> "&#x3C;device-code>.&#x3C;energy-tag-code>",</span></span>

<span class="line"><span style="color:#24292E"> "&#x3C;device-code>.&#x3C;setpoint-tag-code>"</span></span>

<span class="line"><span style="color:#24292E"> ],</span></span>

<span class="line"><span style="color:#24292E"> "writableTags": ["&#x3C;device-code>.&#x3C;setpoint-tag-code>"]</span></span>

<span class="line"><span style="color:#24292E">}</span></span>

<span class="line"><span style="color:#24292E">&#x3C;/</span><span style="color:#22863A">script</span><span style="color:#24292E">></span></span></code></pre></div>

</article>

<article class="doc-card">

<h3 id="custom-page-sdk-item-6">动态精确使用:参数、循环和字符串组合得到 Tag</h3>

<p>运行时通过参数、循环或函数生成完整 Tag 时,可声明 <code>exact-on-use</code>,逐个请求精确 Tag。它不支持通配、目录枚举或无限权限;每个 Tag 仍校验格式、存在性、启用状态、页面会话及读写规则。</p>

<ul>

<li>liveTagRead.maxActiveTags 范围为 1–8000;示例按小页面配置 64 个读点、16 个写点。按实际需要设置,额度不代表性能承诺。</li>

<li><code>liveTagWrite.maxActiveTags</code> 的正式上限为 2000。</li>

<li>适合:从页面参数选择设备、遍历已知设备清单、函数拼接完整 Tag、组件按业务对象绑定 Tag。</li>

<li>不支持:按 <code>&lt;device-code&gt;.*</code> 搜索、读取 Tag 目录、枚举所有设备或绕过服务端逐项校验。</li>

<li>动态声明只扩展实时精确读写;历史、报警、设备弹窗和预取仍依据各自范围。</li>

<li>固定点位建议使用静态声明,便于首屏预取、初始订阅和范围管理。</li>

</ul>

<div class="manual-code" data-code-language="html"><div class="manual-code-toolbar"><span>HTML</span><span class="manual-code-tools"><small>HTML</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: HTML">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#24292E">&#x3C;</span><span style="color:#22863A">script</span><span style="color:#6F42C1"> type</span><span style="color:#24292E">=</span><span style="color:#032F62">"application/json"</span><span style="color:#6F42C1"> id</span><span style="color:#24292E">=</span><span style="color:#032F62">"hqvisu-custom-page"</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E">{</span></span>

<span class="line"><span style="color:#24292E"> "readableTags": ["&#x3C;device-code>.&#x3C;run-state-tag-code>"],</span></span>

<span class="line"><span style="color:#24292E"> "dependencies": {</span></span>

<span class="line"><span style="color:#24292E"> "runtimeAccess": {</span></span>

<span class="line"><span style="color:#24292E"> "liveTagRead": { "mode": "exact-on-use", "maxActiveTags": 64 },</span></span>

<span class="line"><span style="color:#24292E"> "liveTagWrite": { "mode": "exact-on-use", "maxActiveTags": 16 }</span></span>

<span class="line"><span style="color:#24292E"> }</span></span>

<span class="line"><span style="color:#24292E"> }</span></span>

<span class="line"><span style="color:#24292E">}</span></span>

<span class="line"><span style="color:#24292E">&#x3C;/</span><span style="color:#22863A">script</span><span style="color:#24292E">></span></span>

<span class="line"></span>

<span class="line"><span style="color:#24292E">const deviceCode = new URLSearchParams(location.search).get("device");</span></span>

<span class="line"><span style="color:#24292E">const temperatureTag = `${deviceCode}.&#x3C;</span><span style="color:#22863A">temperature-tag-code</span><span style="color:#24292E">>`;</span></span>

<span class="line"><span style="color:#24292E">api.track(temperatureTag);</span></span></code></pre></div>

</article>

<article class="doc-card">

<h3 id="custom-page-sdk-item-7">ready() 与资源生命周期</h3>

<p>HQVISU_CUSTOM_PAGE.ready(options?)、ready(callback) 或 ESM ready(options?) 返回 Promise&lt;PageApi&gt;。初始化成功表示桥已建立,不保证每个 Tag 已有 GOOD 数据。每个 HTML 文档共享一个 SDK;Vue 组件重复挂载应复用它。</p>

<ul>

<li>tags: string[] 追加跟踪点;普通页面优先静态声明。初始化错误用 try/catch 显示,不循环重试 ready。</li>

<li>subscribe、alarms.subscribe、language.onChange、bindJsonTable 都返回取消函数。组件卸载时逐项调用自己的取消函数。</li>

<li>destroy() 永久结束整个文档,后续同文档 ready() 返回 RUNTIME_PAGE_DESTROYED。普通 Vue unmount 不调用 destroy;整页永久结束时才调用。</li>

<li>pagehide 的 persisted=true 表示 BFCache 暂存,不永久销毁;异步挂载期间已卸载的组件不得再注册订阅。</li>

<li>timeoutMs 设置等待宿主的超时(示例 10000 ms),不是 PLC 读写超时;ready 成功后仍需检查各 Tag 的质量。</li>

</ul>

<div class="manual-code" data-code-language="javascript"><div class="manual-code-toolbar"><span>JavaScript</span><span class="manual-code-tools"><small>JavaScript</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: JavaScript">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> api</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> await</span><span style="color:#005CC5"> HQVISU_CUSTOM_PAGE</span><span style="color:#24292E">.</span><span style="color:#6F42C1">ready</span><span style="color:#24292E">();</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> stop</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> api.</span><span style="color:#6F42C1">subscribe</span><span style="color:#24292E">([</span><span style="color:#032F62">"&#x3C;device-code>.&#x3C;tag-code>"</span><span style="color:#24292E">], renderValue);</span></span>

<span class="line"><span style="color:#6A737D">// renderValue is your UI renderer.</span></span>

<span class="line"><span style="color:#24292E">window.</span><span style="color:#6F42C1">addEventListener</span><span style="color:#24292E">(</span><span style="color:#032F62">"pagehide"</span><span style="color:#24292E">, </span><span style="color:#E36209">event</span><span style="color:#D73A49"> =></span><span style="color:#24292E"> {</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (</span><span style="color:#D73A49">!</span><span style="color:#24292E">event.persisted) { </span><span style="color:#6F42C1">stop</span><span style="color:#24292E">(); api.</span><span style="color:#6F42C1">destroy</span><span style="color:#24292E">(); }</span></span>

<span class="line"><span style="color:#24292E">});</span></span></code></pre></div>

</article>

<article class="doc-card">

<h3 id="custom-page-sdk-item-8">SDK 全局对象 API</h3>

<p>全局对象为 <code>HQVISU_CUSTOM_PAGE</code>,兼容别名为 <code>HQVisuCustomPage</code>。以下成员可在 <code>ready()</code> 前访问;页面数据仍须等待宿主就绪。</p>

<ul>

<li><code>version</code>:返回字符串 <code>1.5.0</code>。</li>

<li><code>ready(options | callback)</code>:返回 <code>Promise&lt;PageApi&gt;</code>。</li>

<li><code>whenVisualReady({ timeoutMs? })</code>:等待宿主允许的首个画面快照就绪,返回 <code>Promise&lt;void&gt;</code>;一般页面使用 <code>ready()</code> 获取 api 即可。</li>

<li><code>destroy()</code>:永久结束当前文档的 SDK 连接;组件卸载时只取消自身订阅。</li>

<li><code>normalizeTagName(value)</code>:转成字符串、去首尾空白,并去掉包住整个值的一层单引号或双引号;返回字符串。</li>

<li><code>tagList(value)</code>:接受字符串、嵌套数组或单值,按换行/逗号/分号拆分,规范化、过滤空值和形如 <code>&lt;TagName&gt;</code> 的旧模板变量并去重;返回 <code>string[]</code>。</li>

<li><code>renderTable(target, rows, options)</code>:渲染轻量 HTML 表格;返回 <code>{ rowCount, columnCount }</code>。</li>

</ul>

<div class="manual-code" data-code-language="javascript"><div class="manual-code-toolbar"><span>JavaScript</span><span class="manual-code-tools"><small>JavaScript</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: JavaScript">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> tag</span><span style="color:#D73A49"> =</span><span style="color:#032F62"> "&#x3C;device-code>.&#x3C;tag-code>"</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> normalized</span><span style="color:#D73A49"> =</span><span style="color:#005CC5"> HQVISU_CUSTOM_PAGE</span><span style="color:#24292E">.</span><span style="color:#6F42C1">normalizeTagName</span><span style="color:#24292E">(tag);</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> tags</span><span style="color:#D73A49"> =</span><span style="color:#005CC5"> HQVISU_CUSTOM_PAGE</span><span style="color:#24292E">.</span><span style="color:#6F42C1">tagList</span><span style="color:#24292E">([tag, tag]);</span></span></code></pre></div>

</article>

<article class="doc-card">

<h3 id="custom-page-sdk-item-9">PageApi 根对象和能力矩阵</h3>

<p>使用 <code>ready()</code> 返回的根方法及 <code>api.auth</code>、<code>api.permissions</code>、<code>api.history</code>、<code>api.alarms</code> 等稳定接口。低层 <code>api.host</code> 的接口随传输方式变化。</p>

<ul>

<li>属性:<code>version</code>、<code>bridgeVersion</code>、<code>schemaRevision</code>、冻结的 <code>capabilities</code>、低层 <code>host</code>。</li>

<li>发现:<code>getCapabilities()</code> 返回可修改副本;<code>supports(name)</code> 返回布尔值,未知能力返回 false。</li>

<li>命名空间:<code>auth</code>、<code>permissions</code>、<code>language</code>、<code>history</code>、<code>alarms</code>、<code>navigation</code>、<code>dialog</code>。</li>

<li>根方法:<code>track</code>、<code>subscribe</code>、全部读取和写入方法、<code>queryHistory</code>、<code>translate</code> / <code>t</code>、<code>renderTable</code>、<code>bindJsonTable</code>、<code>destroy</code>。</li>

<li>能力名:<code>page.parameters</code>、<code>tag.read</code>、<code>tag.write</code>、<code>history.read</code>、<code>user.read</code>、<code>navigation</code>、<code>popup</code>、<code>alarm.read</code>、<code>log.write</code>。</li>

<li><code>supports()</code> 只说明当前传输暴露此能力,不代表用户有权限,也不会扩大页面范围。</li>

<li>log.write 是可选能力:supports(&quot;log.write&quot;) 为 false 时不能调用或宣称日志写入成功。PageApi 没有独立的 log.write 根接口。</li>

</ul>

</article>

<article class="doc-card">

<h3 id="custom-page-sdk-item-10">当前用户和权限 API</h3>

<p>稳定会话结果为 <code>{ user, permissions }</code>。登录用户的 <code>user</code> 包含 <code>username</code>、<code>displayName</code>、<code>permissions</code>;匿名直接 Screen Gateway 返回 <code>user: null</code> 和空权限数组。SDK 不向页面暴露 Token、Cookie、角色或页面能力密钥。</p>

<ul>

<li><code>api.auth.getSession()</code>:返回会话快照;为兼容同步和异步宿主,统一写成 <code>await api.auth.getSession()</code>。</li>

<li>api.auth.getCurrentUser() / getUser() → 用户快照或 null;getUsername() → 用户名字符串;getDisplayName() → 显示名,无显示名时回退用户名。</li>

<li><code>api.auth.hasPermission(code)</code> / <code>api.permissions.has(code)</code>:返回 <code>Promise&lt;boolean&gt;</code>。</li>

<li><code>api.auth.requirePermission(code)</code> / <code>api.permissions.require(code)</code>:有权限时解析为 true,无权限时 Promise 拒绝。</li>

<li>上述方法由 PageApi.auth 提供。优先使用此公共封装,不直接依赖 api.host 中随传输变化的低层对象。</li>

</ul>

<div class="manual-code" data-code-language="javascript"><div class="manual-code-toolbar"><span>JavaScript</span><span class="manual-code-tools"><small>JavaScript</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: JavaScript">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> api</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> await</span><span style="color:#005CC5"> HQVISU_CUSTOM_PAGE</span><span style="color:#24292E">.</span><span style="color:#6F42C1">ready</span><span style="color:#24292E">();</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> session</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> await</span><span style="color:#24292E"> api.auth.</span><span style="color:#6F42C1">getSession</span><span style="color:#24292E">();</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> canWrite</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> api.</span><span style="color:#6F42C1">supports</span><span style="color:#24292E">(</span><span style="color:#032F62">"tag.write"</span><span style="color:#24292E">)</span></span>

<span class="line"><span style="color:#D73A49"> &#x26;&#x26;</span><span style="color:#D73A49"> await</span><span style="color:#24292E"> api.permissions.</span><span style="color:#6F42C1">has</span><span style="color:#24292E">(</span><span style="color:#032F62">"tag:write"</span><span style="color:#24292E">);</span></span>

<span class="line"></span>

<span class="line"><span style="color:#6F42C1">renderCurrentUser</span><span style="color:#24292E">(session.user);</span></span>

<span class="line"><span style="color:#6F42C1">setWriteControlsEnabled</span><span style="color:#24292E">(canWrite);</span></span></code></pre></div>

</article>

<article class="doc-card">

<h3 id="custom-page-sdk-item-11">读取接口:与 PPT 使用同一套名称</h3>

<p>读取同步返回当前已采集快照,不直接读取 PLC。类型化读取显式传 fallback 时,非 GOOD/缺质量或转换失败返回该 fallback;省略 fallback 不能代替质量检查。ReadTag 返回原始值,不做类型转换或质量过滤。需要区分真实 0 与不可用时,用 ReadTagDouble(name, null) 并同时显示质量。</p>

<ul>

<li><code>ReadTag(name, fallback?)</code> → 原始快照值;不进行类型转换。</li>

<li><code>ReadTagBoolean(name, fallback?)</code> → boolean;识别布尔、有限数值、true/false、1/0、on/off、yes/no;默认 false。</li>

<li><code>ReadTagInteger(name, fallback?)</code> → 向零截断的安全整数;默认 0。超出安全整数范围用原始值或字符串,不用 Number 强转。</li>

<li><code>ReadTagDouble(name, fallback?)</code> → 有限 number;默认 0。</li>

<li><code>ReadTagString(name, fallback?)</code> → 显示文本,displayValue 优先,数值遵循 precision/round;默认空字符串。GOOD 空字符串是有效值。</li>

<li><code>ReadTagJson(name, fallback?)</code> → 解析后的 JSON 值;默认 null,返回值不一定是对象,也可能为数组或标量。</li>

<li><code>ReadTagDateTime(name, fallback?)</code> → 有效 Date 或 fallback;默认 null。PLC 日历型的 UTC 字段规则见日期章节。</li>

<li><code>ReadCalculatedTag(name, fallback?)</code> → 计算 Tag 的 number/boolean,校验计算来源和 GOOD;默认 null。使用计算 Tag 稳定编码并声明页面读范围。</li>

<li><code>ReadTagQuality(name, fallback?)</code> → <code>&quot;good&quot; | &quot;bad&quot;</code>; <code>ReadTagQualityGood(name)</code> → <code>boolean</code>. 无质量时默认为 bad,不传 good 来掩盖缺失质量。</li>

<li><code>getMeta(name)</code> → 元数据对象;dataType、qualityDetail、timestamp、source 等字段按实际快照提供。stale/pending 等诊断细节不成为 ReadTagQuality 的第三种返回值。</li>

</ul>

<div class="manual-code" data-code-language="javascript"><div class="manual-code-toolbar"><span>JavaScript</span><span class="manual-code-tools"><small>JavaScript</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: JavaScript">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> name</span><span style="color:#D73A49"> =</span><span style="color:#032F62"> "&#x3C;device-code>.&#x3C;tag-code>"</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> raw</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> api.</span><span style="color:#6F42C1">ReadTag</span><span style="color:#24292E">(name);</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> quality</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> api.</span><span style="color:#6F42C1">ReadTagQuality</span><span style="color:#24292E">(name); </span><span style="color:#6A737D">// "good" | "bad"</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> usable</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> api.</span><span style="color:#6F42C1">ReadTagQualityGood</span><span style="color:#24292E">(name);</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> value</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> api.</span><span style="color:#6F42C1">ReadTagDouble</span><span style="color:#24292E">(name, </span><span style="color:#005CC5">null</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#24292E">document.</span><span style="color:#6F42C1">getElementById</span><span style="color:#24292E">(</span><span style="color:#032F62">"value"</span><span style="color:#24292E">).textContent </span><span style="color:#D73A49">=</span><span style="color:#24292E"> usable </span><span style="color:#D73A49">&#x26;&#x26;</span><span style="color:#24292E"> value </span><span style="color:#D73A49">!==</span><span style="color:#005CC5"> null</span><span style="color:#D73A49"> ?</span><span style="color:#6F42C1"> String</span><span style="color:#24292E">(value) </span><span style="color:#D73A49">:</span><span style="color:#032F62"> "—"</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> meta</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> api.</span><span style="color:#6F42C1">getMeta</span><span style="color:#24292E">(name); </span><span style="color:#6A737D">// source, dataType, ts/persistenceVersion when supplied</span></span>

<span class="line"><span style="color:#6A737D">// Choose the reader matching each Tag's type; do not cast one Tag to every type.</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> running</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> api.</span><span style="color:#6F42C1">ReadTagBoolean</span><span style="color:#24292E">(</span><span style="color:#032F62">"&#x3C;device-code>.&#x3C;run-state-tag-code>"</span><span style="color:#24292E">, </span><span style="color:#005CC5">null</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> count</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> api.</span><span style="color:#6F42C1">ReadTagInteger</span><span style="color:#24292E">(</span><span style="color:#032F62">"&#x3C;device-code>.&#x3C;count-tag-code>"</span><span style="color:#24292E">, </span><span style="color:#005CC5">null</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> label</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> api.</span><span style="color:#6F42C1">ReadTagString</span><span style="color:#24292E">(</span><span style="color:#032F62">"&#x3C;device-code>.&#x3C;text-tag-code>"</span><span style="color:#24292E">, </span><span style="color:#032F62">"—"</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> rows</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> api.</span><span style="color:#6F42C1">ReadTagJson</span><span style="color:#24292E">(</span><span style="color:#032F62">"&#x3C;device-code>.&#x3C;json-tag-code>"</span><span style="color:#24292E">, </span><span style="color:#005CC5">null</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> date</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> api.</span><span style="color:#6F42C1">ReadTagDateTime</span><span style="color:#24292E">(</span><span style="color:#032F62">"&#x3C;device-code>.&#x3C;datetime-tag-code>"</span><span style="color:#24292E">, </span><span style="color:#005CC5">null</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> calculated</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> api.</span><span style="color:#6F42C1">ReadCalculatedTag</span><span style="color:#24292E">(</span><span style="color:#032F62">"&#x3C;calculated-tag-code>"</span><span style="color:#24292E">, </span><span style="color:#005CC5">null</span><span style="color:#24292E">);</span></span></code></pre></div>

</article>

<article class="doc-card">

<h3 id="custom-page-sdk-item-12">订阅实时变化</h3>

<p><code>track(tags)</code> 规范化并跟踪 Tag,返回去重后的名称数组。<code>subscribe(tags, callback)</code> 返回幂等取消函数,回调为 <code>(items, api)</code>。先读取快照渲染首屏,再订阅变化。</p>

<ul>

<li>每个变化项包含 <code>tagName</code> 和 <code>value</code>;<code>fullCode</code>、<code>quality</code>、<code>timestamp</code>、<code>source</code> 等元信息按实际数据提供。</li>

<li>每页最多保持 8000 个活动唯一 Tag;无效、缺失、停用或拒绝的候选不会永久占用配额。</li>

<li>线协议每批最多 256 个 Tag,SDK 最多四批并发;一个大页面仍只有一个逻辑实时订阅和一个 WebSocket。</li>

<li>回调中可用第二个参数重新读取一致的当前快照;高频页面应集中订阅一组 Tag 后统一刷新 UI。</li>

<li>离开页面、切换数据范围或销毁组件时调用取消函数;<code>destroy()</code> 会集中清理剩余订阅。</li>

</ul>

<div class="manual-code" data-code-language="javascript"><div class="manual-code-toolbar"><span>JavaScript</span><span class="manual-code-tools"><small>JavaScript</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: JavaScript">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> tag</span><span style="color:#D73A49"> =</span><span style="color:#032F62"> "&#x3C;device-code>.&#x3C;process-tag-code>"</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#D73A49">function</span><span style="color:#6F42C1"> renderValue</span><span style="color:#24292E">() {</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> value</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> api.</span><span style="color:#6F42C1">ReadTagDouble</span><span style="color:#24292E">(tag, </span><span style="color:#005CC5">null</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#24292E"> document.</span><span style="color:#6F42C1">getElementById</span><span style="color:#24292E">(</span><span style="color:#032F62">"value"</span><span style="color:#24292E">).textContent </span><span style="color:#D73A49">=</span></span>

<span class="line"><span style="color:#24292E"> api.</span><span style="color:#6F42C1">ReadTagQualityGood</span><span style="color:#24292E">(tag) </span><span style="color:#D73A49">&#x26;&#x26;</span><span style="color:#24292E"> value </span><span style="color:#D73A49">!==</span><span style="color:#005CC5"> null</span><span style="color:#D73A49"> ?</span><span style="color:#6F42C1"> String</span><span style="color:#24292E">(value) </span><span style="color:#D73A49">:</span><span style="color:#032F62"> "—"</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#24292E">}</span></span>

<span class="line"><span style="color:#6F42C1">renderValue</span><span style="color:#24292E">();</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> stopRealtime</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> api.</span><span style="color:#6F42C1">subscribe</span><span style="color:#24292E">([tag], renderValue);</span></span>

<span class="line"><span style="color:#6A737D">// stopRealtime() when this view no longer needs the subscription.</span></span></code></pre></div>

</article>

<article class="doc-card">

<h3 id="custom-page-sdk-item-13">写入接口:参数、前置条件与返回值</h3>

<p>所有类型写入采用 (name, value, options?),统一用 await 接收结果:Host 可以同步返回、返回 Promise 或 undefined,不能直接假定存在 .then()。页面需具备 tag.write 能力、静态/合法动态写范围、账号 tag:write 权限、Tag 控制许可及有效质量;正式执行还会重新校验 Owner 和用户动作。</p>

<ul>

<li><code>WriteTagBoolean(name, value, options?)</code>:使用 boolean、0/1 或明确的布尔文本;&quot;false&quot; 表示 false,不用 JavaScript 真值规则。</li>

<li><code>WriteTagInteger(name, value, options?)</code>:number 必须是安全整数;BigInt 或十进制整数文本保留精度,设备实际范围由后端校验。</li>

<li><code>WriteTagDouble(name, value, options?)</code>:有限数值或合法数字文本;拒绝 NaN、Infinity 和空输入。</li>

<li><code>WriteTagString(name, value, options?)</code>:文本;空字符串是明确的写入值,仍需满足 PLC 编码与长度限制。</li>

<li><code>WriteTagJson(name, value, options?)</code>:传实际 JSON 对象/数组/值,不先 JSON.stringify 对象;恢复原始空文本应使用 WriteTagString。</li>

<li><code>WriteTagDateTime(name, value, options?)</code>:Date 或日期文本;PLC 日历元数据决定序列化规则,见下方日期章节。</li>

<li><code>WriteTag(name, value, options?)</code> / <code>WriteTag({ TagName, Value, ValueType, ... })</code>:通用入口,也接受 camelCase payload;false、0、空字符串和 JSON 值不会被丢弃。</li>

<li><code>WriteTagBit(name, bitIndex, bitAction, options?)</code>:name 为父 Tag,需 GOOD 质量及支持的 8/16/32 位整数类型;位号须在该类型位宽内且为 0–31,动作为 set/reset/toggle。空白位号非法。协议可能只支持部分动作;Modbus 需明确 FC22 支持,不支持 toggle,不用整字读改写代替。</li>

<li>options.reason 提供操作原因;typed 方法固定本次类型和调用来源。权限预检 await api.auth.hasPermission(&quot;tag:write&quot;),不要把 capability 名 tag.write 当权限码。</li>

<li>从用户点击处理函数发起;页面确认不替代服务配置的 Host 确认。禁止在定时器、加载或订阅中自动写入。</li>

</ul>

<div class="manual-code" data-code-language="javascript"><div class="manual-code-toolbar"><span>在 ready 回调内绑定写入按钮(id=&quot;write&quot;,结果 id=&quot;result&quot;)</span><span class="manual-code-tools"><small>JavaScript</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: 在 ready 回调内绑定写入按钮(id=&quot;write&quot;,结果 id=&quot;result&quot;)">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> button</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> document.</span><span style="color:#6F42C1">getElementById</span><span style="color:#24292E">(</span><span style="color:#032F62">"write"</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> output</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> document.</span><span style="color:#6F42C1">getElementById</span><span style="color:#24292E">(</span><span style="color:#032F62">"result"</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#24292E">button.disabled </span><span style="color:#D73A49">=</span><span style="color:#D73A49"> !</span><span style="color:#24292E">(api.</span><span style="color:#6F42C1">supports</span><span style="color:#24292E">(</span><span style="color:#032F62">"tag.write"</span><span style="color:#24292E">) </span><span style="color:#D73A49">&#x26;&#x26;</span><span style="color:#D73A49"> await</span><span style="color:#24292E"> api.auth.</span><span style="color:#6F42C1">hasPermission</span><span style="color:#24292E">(</span><span style="color:#032F62">"tag:write"</span><span style="color:#24292E">));</span></span>

<span class="line"><span style="color:#24292E">button.</span><span style="color:#6F42C1">onclick</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> async</span><span style="color:#24292E"> () </span><span style="color:#D73A49">=></span><span style="color:#24292E"> {</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (button.disabled) </span><span style="color:#D73A49">return</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> tag</span><span style="color:#D73A49"> =</span><span style="color:#032F62"> "&#x3C;device-code>.&#x3C;tag-code>"</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (</span><span style="color:#D73A49">!</span><span style="color:#24292E">api.</span><span style="color:#6F42C1">ReadTagQualityGood</span><span style="color:#24292E">(tag)) { output.textContent </span><span style="color:#D73A49">=</span><span style="color:#032F62"> "Tag unavailable"</span><span style="color:#24292E">; </span><span style="color:#D73A49">return</span><span style="color:#24292E">; }</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (</span><span style="color:#D73A49">!</span><span style="color:#6F42C1">confirm</span><span style="color:#24292E">(</span><span style="color:#032F62">"Write 12.5?"</span><span style="color:#24292E">)) </span><span style="color:#D73A49">return</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#24292E"> button.disabled </span><span style="color:#D73A49">=</span><span style="color:#005CC5"> true</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#D73A49"> try</span><span style="color:#24292E"> {</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> result</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> await</span><span style="color:#24292E"> api.</span><span style="color:#6F42C1">WriteTagDouble</span><span style="color:#24292E">(tag, </span><span style="color:#005CC5">12.5</span><span style="color:#24292E">, { reason: </span><span style="color:#032F62">"Operator setpoint update"</span><span style="color:#24292E"> });</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> done</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> result?.accepted </span><span style="color:#D73A49">===</span><span style="color:#005CC5"> true</span><span style="color:#D73A49"> &#x26;&#x26;</span><span style="color:#24292E"> result?.status </span><span style="color:#D73A49">===</span><span style="color:#032F62"> "done"</span></span>

<span class="line"><span style="color:#D73A49"> &#x26;&#x26;</span><span style="color:#24292E"> result?.outcomeUncertain </span><span style="color:#D73A49">!==</span><span style="color:#005CC5"> true</span><span style="color:#D73A49"> &#x26;&#x26;</span><span style="color:#24292E"> result?.operatorVerificationRequired </span><span style="color:#D73A49">!==</span><span style="color:#005CC5"> true</span></span>

<span class="line"><span style="color:#D73A49"> &#x26;&#x26;</span><span style="color:#24292E"> result?.transportOnly </span><span style="color:#D73A49">!==</span><span style="color:#005CC5"> true</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#24292E"> output.textContent </span><span style="color:#D73A49">=</span><span style="color:#24292E"> (done </span><span style="color:#D73A49">?</span><span style="color:#032F62"> "Command completed; verify collected value: "</span><span style="color:#D73A49"> :</span><span style="color:#032F62"> "Result unconfirmed; verify before another write: "</span><span style="color:#24292E">)</span></span>

<span class="line"><span style="color:#D73A49"> +</span><span style="color:#005CC5"> JSON</span><span style="color:#24292E">.</span><span style="color:#6F42C1">stringify</span><span style="color:#24292E">(result </span><span style="color:#D73A49">??</span><span style="color:#005CC5"> null</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#24292E"> } </span><span style="color:#D73A49">catch</span><span style="color:#24292E"> (error) {</span></span>

<span class="line"><span style="color:#24292E"> output.textContent </span><span style="color:#D73A49">=</span><span style="color:#032F62"> "Result unconfirmed: "</span><span style="color:#D73A49"> +</span><span style="color:#24292E"> error.message </span><span style="color:#D73A49">+</span><span style="color:#032F62"> " "</span><span style="color:#D73A49"> +</span><span style="color:#24292E"> (error.commandId </span><span style="color:#D73A49">||</span><span style="color:#24292E"> error.result?.commandId </span><span style="color:#D73A49">||</span><span style="color:#032F62"> ""</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#24292E"> }</span></span>

<span class="line"><span style="color:#6A737D"> // Reconcile the result before another write. Never retry automatically.</span></span>

<span class="line"><span style="color:#24292E">};</span></span></code></pre></div>

</article>

<article class="doc-card">

<h3 id="custom-page-sdk-item-14">判断命令结果,再核对正常采集读回</h3>

<p>accepted=true 只表示受理。status、commandId、journalId 和不确定性字段共同描述命令;currentValue 可能是 runtime-write 即时回显,不能当成后续正常采集证据。代码中 nextValue、reason 由页面已验证的输入提供。</p>

<ul>

<li>done:命令完成,仍核对正常采集来源、较新时间/版本及质量后再认定设备值到位;不要把目标值直接覆盖实时显示。</li>

<li>proxied:可能是转发结果,继续检查实际完成证据;sent 或 transportOnly=true 仅证明传输,不能显示“PLC 已执行”。</li>

<li>readback_failed/readback_mismatch/latest_superseded:显示待核对,不把命令回显作为正确采集值。</li>

<li>pending_verification/outcome_uncertain、超时、断线、undefined 或未知状态:保持操作阻断,记录 commandId,核对命令记录与实际值,不自动重试。</li>

<li>accepted=false、cancelled、failed、page_changed:未得到成功完成证据,分别显示真实结果。</li>

</ul>

<div class="manual-code" data-code-language="javascript"><div class="manual-code-toolbar"><span>JavaScript</span><span class="manual-code-tools"><small>JavaScript</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: JavaScript">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#6A737D">// Call from the existing async click handler, after input/quality/permission checks.</span></span>

<span class="line"><span style="color:#D73A49">try</span><span style="color:#24292E"> {</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> result</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> await</span><span style="color:#24292E"> api.</span><span style="color:#6F42C1">WriteTagDouble</span><span style="color:#24292E">(</span><span style="color:#032F62">"&#x3C;device-code>.&#x3C;setpoint-tag-code>"</span><span style="color:#24292E">, nextValue, { reason });</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> confirmed</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> result?.accepted </span><span style="color:#D73A49">===</span><span style="color:#005CC5"> true</span><span style="color:#D73A49"> &#x26;&#x26;</span><span style="color:#24292E"> result?.status </span><span style="color:#D73A49">===</span><span style="color:#032F62"> "done"</span></span>

<span class="line"><span style="color:#D73A49"> &#x26;&#x26;</span><span style="color:#24292E"> result?.outcomeUncertain </span><span style="color:#D73A49">!==</span><span style="color:#005CC5"> true</span><span style="color:#D73A49"> &#x26;&#x26;</span><span style="color:#24292E"> result?.operatorVerificationRequired </span><span style="color:#D73A49">!==</span><span style="color:#005CC5"> true</span></span>

<span class="line"><span style="color:#D73A49"> &#x26;&#x26;</span><span style="color:#24292E"> result?.transportOnly </span><span style="color:#D73A49">!==</span><span style="color:#005CC5"> true</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#24292E"> document.</span><span style="color:#6F42C1">getElementById</span><span style="color:#24292E">(</span><span style="color:#032F62">"write-result"</span><span style="color:#24292E">).textContent </span><span style="color:#D73A49">=</span></span>

<span class="line"><span style="color:#24292E"> (confirmed </span><span style="color:#D73A49">?</span><span style="color:#032F62"> "Command completed; verify normal collected state: "</span><span style="color:#D73A49"> :</span><span style="color:#032F62"> "Verify before another write: "</span><span style="color:#24292E">)</span></span>

<span class="line"><span style="color:#D73A49"> +</span><span style="color:#005CC5"> JSON</span><span style="color:#24292E">.</span><span style="color:#6F42C1">stringify</span><span style="color:#24292E">(result </span><span style="color:#D73A49">??</span><span style="color:#005CC5"> null</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#6A737D"> // Do not overwrite the live value with nextValue or result.currentValue.</span></span>

<span class="line"><span style="color:#6A737D"> // Reconcile the command record and a later GOOD collected sample before another operation.</span></span>

<span class="line"><span style="color:#24292E">} </span><span style="color:#D73A49">catch</span><span style="color:#24292E"> (error) {</span></span>

<span class="line"><span style="color:#24292E"> document.</span><span style="color:#6F42C1">getElementById</span><span style="color:#24292E">(</span><span style="color:#032F62">"write-result"</span><span style="color:#24292E">).textContent </span><span style="color:#D73A49">=</span></span>

<span class="line"><span style="color:#032F62"> "Result unconfirmed; do not retry automatically. "</span><span style="color:#D73A49"> +</span><span style="color:#24292E"> error.message;</span></span>

<span class="line"><span style="color:#24292E">}</span></span></code></pre></div>

<p class="doc-step-result"><strong>完成标志</strong> PageApi 暂无公共命令记录查询方法。保留回执或错误中的 commandId,由具备权限的管理端/运维集成核对命令记录,并通过 subscribe 观察后续 GOOD 正常采集值和 getMeta 元信息。未确认时保持禁用;核对完成后才由页面允许新的人工操作,不能仅按固定延时、刷新页面或值相等自动解锁。</p>

</article>

<article class="doc-card">

<h3 id="custom-page-sdk-item-15">查询历史和趋势</h3>

<p><code>api.history.query(request)</code> 与根别名 <code>api.queryHistory(request)</code> 都返回 <code>Promise&lt;HistoryQueryResult&gt;</code>。请求只查询历史库,不直接轮询 PLC;所有 Tag 必须具有页面历史读取范围。</p>

<ul>

<li>请求字段:<code>tags</code>、<code>start</code>、<code>end</code>、<code>interval</code>、<code>aggregate</code>,可选 <code>points</code>。</li>

<li><code>tags</code> 必填,提交数组最多 16 项,重复项也计入请求上限;服务端随后去重。历史不支持 <code>[0]</code> 至 <code>[31]</code> 位引用,必须查询父 Tag。</li>

<li><code>start</code>、<code>end</code> 必须是 RFC 3339 时间,最大范围 366 天。</li>

<li><code>interval</code> 支持 <code>m</code>、<code>h</code>、<code>d</code>、<code>w</code> 单位,例如 <code>15m</code>、<code>1h</code>、<code>1d</code>、<code>1w</code>,最小 1 分钟。</li>

<li><code>aggregate</code> 支持 <code>avg</code>、<code>min</code>、<code>max</code>、<code>sum</code>、<code>count</code>、<code>first</code>、<code>last</code>、<code>delta</code>、<code>raw</code>。</li>

<li><code>delta</code> 适合电表、水表、产量累积量等报表口径。</li>

<li>下例查询最近 7 天;目标 Tag 须已启用历史记录,页面才可能返回数据。</li>

<li><code>points</code> 省略或为 0 时默认 20000,最大 20000,且不能小于去重后的 Tag 数。</li>

</ul>

<div class="manual-code" data-code-language="javascript"><div class="manual-code-toolbar"><span>JavaScript</span><span class="manual-code-tools"><small>JavaScript</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: JavaScript">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> weekEnd</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> new</span><span style="color:#6F42C1"> Date</span><span style="color:#24292E">();</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> weekStart</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> new</span><span style="color:#6F42C1"> Date</span><span style="color:#24292E">(weekEnd.</span><span style="color:#6F42C1">getTime</span><span style="color:#24292E">() </span><span style="color:#D73A49">-</span><span style="color:#005CC5"> 7</span><span style="color:#D73A49"> *</span><span style="color:#005CC5"> 24</span><span style="color:#D73A49"> *</span><span style="color:#005CC5"> 60</span><span style="color:#D73A49"> *</span><span style="color:#005CC5"> 60</span><span style="color:#D73A49"> *</span><span style="color:#005CC5"> 1000</span><span style="color:#24292E">);</span></span>

<span class="line"></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> weekEnergy</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> await</span><span style="color:#24292E"> api.history.</span><span style="color:#6F42C1">query</span><span style="color:#24292E">({</span></span>

<span class="line"><span style="color:#24292E"> tags: [</span></span>

<span class="line"><span style="color:#032F62"> "&#x3C;primary-device-code>.&#x3C;energy-tag-code>"</span><span style="color:#24292E">,</span></span>

<span class="line"><span style="color:#032F62"> "&#x3C;secondary-device-code>.&#x3C;energy-tag-code>"</span></span>

<span class="line"><span style="color:#24292E"> ],</span></span>

<span class="line"><span style="color:#24292E"> start: weekStart.</span><span style="color:#6F42C1">toISOString</span><span style="color:#24292E">(),</span></span>

<span class="line"><span style="color:#24292E"> end: weekEnd.</span><span style="color:#6F42C1">toISOString</span><span style="color:#24292E">(),</span></span>

<span class="line"><span style="color:#24292E"> interval: </span><span style="color:#032F62">"1d"</span><span style="color:#24292E">,</span></span>

<span class="line"><span style="color:#24292E"> aggregate: </span><span style="color:#032F62">"delta"</span><span style="color:#24292E">,</span></span>

<span class="line"><span style="color:#24292E"> points: </span><span style="color:#005CC5">20000</span></span>

<span class="line"><span style="color:#24292E">});</span></span>

<span class="line"></span>

<span class="line"><span style="color:#6F42C1">renderEnergyReport</span><span style="color:#24292E">(weekEnergy.items, {</span></span>

<span class="line"><span style="color:#24292E"> interval: weekEnergy.effectiveInterval,</span></span>

<span class="line"><span style="color:#24292E"> truncated: weekEnergy.truncated</span></span>

<span class="line"><span style="color:#24292E">});</span></span></code></pre></div>

</article>

<article class="doc-card">

<h3 id="custom-page-sdk-item-16">历史返回结果怎么用</h3>

<p>结果结构为 <code>{ items, effectiveInterval, rangeStart, rangeEnd, truncated }</code>。<code>items</code> 按请求 Tag 返回序列;每项为 <code>{ tag, tagId, displayName, points }</code>,每个点为 <code>{ t, value, quality }</code>。</p>

<ul>

<li><code>tag</code> 是完整 Tag 编码;<code>tagId</code> 是服务器标识;<code>displayName</code> 可能为空。</li>

<li><code>points[].t</code> 是 RFC 3339 时间;<code>value</code> 是原始值或聚合值;<code>quality</code> 是该点质量。</li>

<li><code>effectiveInterval</code> 是服务器实际使用的间隔;点数预算不足时可能比请求间隔更粗。</li>

<li><code>rangeStart</code>、<code>rangeEnd</code> 是实际采用的 UTC/RFC 3339 范围。</li>

<li><code>truncated</code> 为 true 表示聚合间隔被放粗,或原始数据因点数预算而截断。显示实际间隔和结果不完整提示;需要完整原始记录时缩短时间范围后分段查询。</li>

<li>读取序列时使用 <code>series.points || []</code> 处理空数据;页面显示空态,不把无记录当成数值 0。</li>

</ul>

<div class="manual-code" data-code-language="javascript"><div class="manual-code-toolbar"><span>JavaScript</span><span class="manual-code-tools"><small>JavaScript</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: JavaScript">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#D73A49">for</span><span style="color:#24292E"> (</span><span style="color:#D73A49">const</span><span style="color:#005CC5"> series</span><span style="color:#D73A49"> of</span><span style="color:#24292E"> weekEnergy.items </span><span style="color:#D73A49">||</span><span style="color:#24292E"> []) {</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> chartPoints</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> (series.points </span><span style="color:#D73A49">||</span><span style="color:#24292E"> []).</span><span style="color:#6F42C1">map</span><span style="color:#24292E">((</span><span style="color:#E36209">point</span><span style="color:#24292E">) </span><span style="color:#D73A49">=></span><span style="color:#24292E"> ({</span></span>

<span class="line"><span style="color:#24292E"> time: </span><span style="color:#D73A49">new</span><span style="color:#6F42C1"> Date</span><span style="color:#24292E">(point.t),</span></span>

<span class="line"><span style="color:#24292E"> value: point.quality </span><span style="color:#D73A49">===</span><span style="color:#032F62"> "good"</span><span style="color:#D73A49"> ?</span><span style="color:#24292E"> point.value </span><span style="color:#D73A49">:</span><span style="color:#005CC5"> null</span><span style="color:#24292E">,</span></span>

<span class="line"><span style="color:#24292E"> quality: point.quality</span></span>

<span class="line"><span style="color:#24292E"> }));</span></span>

<span class="line"><span style="color:#6F42C1"> drawEnergySeries</span><span style="color:#24292E">(series.displayName </span><span style="color:#D73A49">||</span><span style="color:#24292E"> series.tag, chartPoints);</span></span>

<span class="line"><span style="color:#24292E">}</span></span>

<span class="line"><span style="color:#6F42C1">showActualInterval</span><span style="color:#24292E">(weekEnergy.effectiveInterval, weekEnergy.truncated </span><span style="color:#D73A49">===</span><span style="color:#005CC5"> true</span><span style="color:#24292E">);</span></span></code></pre></div>

</article>

<article class="doc-card">

<h3 id="custom-page-sdk-item-17">报警范围声明</h3>

<p>报警范围由服务器根据页面声明签发,客户端查询参数不能扩大。可在 JSON 配置的 <code>alarm</code>、<code>alarmScope</code> 或 <code>pageAlarm</code> 下声明维度;页面可读 Tag 也会自动加入报警 Tag 范围。</p>

<ul>

<li>支持维度:<code>tags</code>、<code>plcCodes</code>、<code>functionGroupCodes</code>、<code>deviceGroupCodes</code>、<code>signalGroupCodes</code>。</li>

<li>没有任何报警范围时,活动和历史报警查询返回空范围。</li>

<li>动态 <code>liveTagRead</code> 不自动扩大报警范围;需要报警的 Tag 或分组必须静态声明。</li>

<li>服务端会用签发能力覆盖查询中的范围字段,页面不能通过附加 JSON 字段查看更多报警。</li>

<li>组编码需带设备前缀;空范围表示未绑定,不是全部报警。showWindow 控制页面右侧报警栏,与设备操作 Popup 是两项功能。</li>

</ul>

<div class="manual-code" data-code-language="html"><div class="manual-code-toolbar"><span>HTML</span><span class="manual-code-tools"><small>HTML</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: HTML">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#24292E">&#x3C;</span><span style="color:#22863A">script</span><span style="color:#6F42C1"> type</span><span style="color:#24292E">=</span><span style="color:#032F62">"application/json"</span><span style="color:#6F42C1"> id</span><span style="color:#24292E">=</span><span style="color:#032F62">"hqvisu-custom-page"</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E">{</span></span>

<span class="line"><span style="color:#24292E"> "readableTags": ["&#x3C;device-code>.&#x3C;temperature-tag-code>"],</span></span>

<span class="line"><span style="color:#24292E"> "alarmScope": {</span></span>

<span class="line"><span style="color:#24292E"> "plcCodes": ["&#x3C;device-code>"],</span></span>

<span class="line"><span style="color:#24292E"> "functionGroupCodes": ["&#x3C;device-code>.&#x3C;function-group-code>"],</span></span>

<span class="line"><span style="color:#24292E"> "deviceGroupCodes": [],</span></span>

<span class="line"><span style="color:#24292E"> "signalGroupCodes": []</span></span>

<span class="line"><span style="color:#24292E"> }</span></span>

<span class="line"><span style="color:#24292E">}</span></span>

<span class="line"><span style="color:#24292E">&#x3C;/</span><span style="color:#22863A">script</span><span style="color:#24292E">></span></span></code></pre></div>

</article>

<article class="doc-card">

<h3 id="custom-page-sdk-item-18">查询和订阅报警</h3>

<p>报警接口提供同步快照、异步查询和变化订阅。<code>getActive()</code>、<code>getActiveAlarms()</code> 和 <code>queryHistory(request)</code> 均返回 <code>Promise&lt;AlarmQueryResult&gt;</code> 分页结果。</p>

<ul>

<li><code>getSnapshot()</code> 同步返回宿主已有快照;不同宿主可能返回数组,也可能返回 <code>{ alarms }</code> 或 <code>{ activeAlarms }</code> 包装对象。</li>

<li>查询字段:<code>start</code>、<code>end</code>、<code>severity</code>、<code>state</code>、<code>source</code> / <code>q</code>、<code>limit</code>、<code>offset</code>、<code>cursorTs</code>、<code>cursorId</code>。</li>

<li>历史查询省略时间时默认最近 24 小时;单次最大跨度 24 小时。</li>

<li>下例查询最近 1 小时;该时段无报警时历史结果为空。</li>

<li><code>limit</code> 省略或为 0 时是 500,最大也是 500;超过 500 会被截到 500。<code>offset</code> 必须非负。</li>

<li>结果为 <code>{ items, count, hasMore, nextCursorTs, nextCursorId }</code>;有下一页时优先把两个 nextCursor 字段传回下一次查询。</li>

<li><code>subscribe(callback)</code> 返回幂等取消函数,回调为 <code>(items, api)</code>;不同宿主可能传完整快照或变化项,应把它作为刷新信号,不要盲目追加。</li>

<li>getActive(request?) 接受上述过滤/分页参数;getActiveAlarms() 是无参便捷入口。</li>

</ul>

<div class="manual-code" data-code-language="javascript"><div class="manual-code-toolbar"><span>JavaScript</span><span class="manual-code-tools"><small>JavaScript</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: JavaScript">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#D73A49">function</span><span style="color:#6F42C1"> alarmItemsFromSnapshot</span><span style="color:#24292E">(</span><span style="color:#E36209">snapshot</span><span style="color:#24292E">) {</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (Array.</span><span style="color:#6F42C1">isArray</span><span style="color:#24292E">(snapshot)) </span><span style="color:#D73A49">return</span><span style="color:#24292E"> snapshot;</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (Array.</span><span style="color:#6F42C1">isArray</span><span style="color:#24292E">(snapshot?.alarms)) </span><span style="color:#D73A49">return</span><span style="color:#24292E"> snapshot.alarms;</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (Array.</span><span style="color:#6F42C1">isArray</span><span style="color:#24292E">(snapshot?.activeAlarms)) </span><span style="color:#D73A49">return</span><span style="color:#24292E"> snapshot.activeAlarms;</span></span>

<span class="line"><span style="color:#D73A49"> return</span><span style="color:#24292E"> [];</span></span>

<span class="line"><span style="color:#24292E">}</span></span>

<span class="line"></span>

<span class="line"><span style="color:#6F42C1">renderAlarmSummary</span><span style="color:#24292E">(</span><span style="color:#6F42C1">alarmItemsFromSnapshot</span><span style="color:#24292E">(api.alarms.</span><span style="color:#6F42C1">getSnapshot</span><span style="color:#24292E">()));</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> activeResult</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> await</span><span style="color:#24292E"> api.alarms.</span><span style="color:#6F42C1">getActiveAlarms</span><span style="color:#24292E">();</span></span>

<span class="line"><span style="color:#6F42C1">renderAlarmSummary</span><span style="color:#24292E">(activeResult.items </span><span style="color:#D73A49">||</span><span style="color:#24292E"> []);</span></span>

<span class="line"></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> alarmEnd</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> new</span><span style="color:#6F42C1"> Date</span><span style="color:#24292E">();</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> alarmStart</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> new</span><span style="color:#6F42C1"> Date</span><span style="color:#24292E">(alarmEnd.</span><span style="color:#6F42C1">getTime</span><span style="color:#24292E">() </span><span style="color:#D73A49">-</span><span style="color:#005CC5"> 60</span><span style="color:#D73A49"> *</span><span style="color:#005CC5"> 60</span><span style="color:#D73A49"> *</span><span style="color:#005CC5"> 1000</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> historyResult</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> await</span><span style="color:#24292E"> api.alarms.</span><span style="color:#6F42C1">queryHistory</span><span style="color:#24292E">({</span></span>

<span class="line"><span style="color:#24292E"> start: alarmStart.</span><span style="color:#6F42C1">toISOString</span><span style="color:#24292E">(),</span></span>

<span class="line"><span style="color:#24292E"> end: alarmEnd.</span><span style="color:#6F42C1">toISOString</span><span style="color:#24292E">(),</span></span>

<span class="line"><span style="color:#24292E"> severity: [</span><span style="color:#032F62">"critical"</span><span style="color:#24292E">, </span><span style="color:#032F62">"high"</span><span style="color:#24292E">],</span></span>

<span class="line"><span style="color:#24292E"> state: [</span><span style="color:#032F62">"active"</span><span style="color:#24292E">, </span><span style="color:#032F62">"recovered"</span><span style="color:#24292E">],</span></span>

<span class="line"><span style="color:#24292E"> limit: </span><span style="color:#005CC5">200</span></span>

<span class="line"><span style="color:#24292E">});</span></span>

<span class="line"></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> stopAlarms</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> api.alarms.</span><span style="color:#6F42C1">subscribe</span><span style="color:#24292E">(() </span><span style="color:#D73A49">=></span><span style="color:#6F42C1"> scheduleActiveAlarmRefresh</span><span style="color:#24292E">());</span></span></code></pre></div>

</article>

<article class="doc-card">

<h3 id="custom-page-sdk-item-19">报警项和分页结果字段</h3>

<p>用 id 标识事件,用 state 和时间字段显示状态。可选字段缺失时留空;缺少翻译时回退 messageText/displayName,不显示内部 key。</p>

<ul>

<li>身份:<code>id</code>、<code>deviceId</code>、<code>deviceCode</code>、<code>deviceName</code>、<code>tagId</code>、<code>tagCode</code>、<code>fullCode</code>、<code>displayName</code>。</li>

<li>内容:<code>messageText</code>、<code>alarmKind</code>、<code>severity</code>、<code>state</code>、<code>displayValue</code>、<code>quality</code>。</li>

<li>时间:<code>activeTs</code>、<code>recoverTs</code>、<code>closeTs</code>、<code>ackTs</code>、<code>shelvedUntil</code>、<code>serverTs</code>。</li>

<li>标志:<code>suppressed</code>;部分数据还可能带 <code>ackUserId</code>、<code>areaCode</code>、<code>objectIdentifier</code>、<code>groupCode</code>。</li>

<li><code>count</code> 是本页实际返回数量,<code>hasMore</code> 表示还有下一页;下一页使用 <code>nextCursorTs</code> 和 <code>nextCursorId</code> 可避免 offset 翻页期间数据变化造成重复或遗漏。</li>

<li>SDK 提供报警读取和订阅,不提供报警确认、搁置或关闭操作。</li>

</ul>

</article>

<article class="doc-card">

<h3 id="custom-page-sdk-item-20">导航 API:上级、下级、assets 根与 URL</h3>

<p>自写页 api.navigation.gotoPage 与 PPT hqVisuDesigner.openPage 共用解析规则。无须预先枚举所有目标。页面映射用于名称与面包屑;明确路径不会被同名映射截走。</p>

<ul>

<li><code>gotoPage(target, options?)</code>:./、../、子级、同级路径相对当前画面 URL 解析;本地无扩展名补 .html。解析后在 assets 根内即可跨页面目录。</li>

<li>assets/&lt;folder&gt;/... 与 /assets/&lt;folder&gt;/... 指向当前 Runtime Unit 的用户画面根;发布画面或内置画面也可进入这个明确根目标。它们的其它本地相对路径仍保留原包边界。</li>

<li><code>openUrl(target, options?)</code>:同一目标规则;外站明确写 http://、https:// 或 //,不把带点的目录名猜成域名。query/hash 保留。</li>

<li><code>gotoAndon(target, options?)</code>:请求宿主安灯/全屏窗口。</li>

<li><code>gotoScreen(screenCode)</code>:按画面菜单编码跳转,不是任意文件路径。</li>

<li>options:openInNewWindow、frameTarget、parameters:[{key,value}]。参数最多64个,key≤256字符、value≤2048字符;目标用 URLSearchParams 读取。</li>

<li>合法 HTTP(S) 地址允许导航;第三方是否允许 iframe 嵌入由其 CSP/X-Frame-Options 决定。Portal 管理路由用顶层/新窗口,不作为 Screen 嵌入。外站不获得 Tag 或 Portal 身份。</li>

<li>越出 assets 根、javascript:/data:/file:、反斜杠和歧义编码路径拒绝。每个目标 HTML 必须有自己的静态数据范围。返回 true 是分派结果,必须看到目标页面才能判断加载成功。</li>

</ul>

<div class="manual-code" data-code-language="javascript"><div class="manual-code-toolbar"><span>JavaScript</span><span class="manual-code-tools"><small>JavaScript</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: JavaScript">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#6A737D">// Inside the ready() scope; the HTML contains the corresponding buttons.</span></span>

<span class="line"><span style="color:#24292E">document.</span><span style="color:#6F42C1">getElementById</span><span style="color:#24292E">(</span><span style="color:#032F62">"parent"</span><span style="color:#24292E">).</span><span style="color:#6F42C1">onclick</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> () </span><span style="color:#D73A49">=></span></span>

<span class="line"><span style="color:#24292E"> api.navigation.</span><span style="color:#6F42C1">gotoPage</span><span style="color:#24292E">(</span><span style="color:#032F62">"../overview.html?view=summary#status"</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#24292E">document.</span><span style="color:#6F42C1">getElementById</span><span style="color:#24292E">(</span><span style="color:#032F62">"child"</span><span style="color:#24292E">).</span><span style="color:#6F42C1">onclick</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> () </span><span style="color:#D73A49">=></span></span>

<span class="line"><span style="color:#24292E"> api.navigation.</span><span style="color:#6F42C1">gotoPage</span><span style="color:#24292E">(</span><span style="color:#032F62">"details/equipment"</span><span style="color:#24292E">, { parameters: [{ key: </span><span style="color:#032F62">"device"</span><span style="color:#24292E">, value: </span><span style="color:#032F62">"&#x3C;device-code>"</span><span style="color:#24292E"> }] });</span></span>

<span class="line"><span style="color:#24292E">document.</span><span style="color:#6F42C1">getElementById</span><span style="color:#24292E">(</span><span style="color:#032F62">"other-package"</span><span style="color:#24292E">).</span><span style="color:#6F42C1">onclick</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> () </span><span style="color:#D73A49">=></span></span>

<span class="line"><span style="color:#24292E"> api.navigation.</span><span style="color:#6F42C1">gotoPage</span><span style="color:#24292E">(</span><span style="color:#032F62">"/assets/&#x3C;page-folder>/overview.html"</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#24292E">document.</span><span style="color:#6F42C1">getElementById</span><span style="color:#24292E">(</span><span style="color:#032F62">"external"</span><span style="color:#24292E">).</span><span style="color:#6F42C1">onclick</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> () </span><span style="color:#D73A49">=></span></span>

<span class="line"><span style="color:#24292E"> api.navigation.</span><span style="color:#6F42C1">openUrl</span><span style="color:#24292E">(</span><span style="color:#032F62">"https://&#x3C;server-host>/manual"</span><span style="color:#24292E">, { openInNewWindow: </span><span style="color:#005CC5">true</span><span style="color:#24292E"> });</span></span>

<span class="line"><span style="color:#6A737D">// In the destination page:</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> device</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> new</span><span style="color:#6F42C1"> URLSearchParams</span><span style="color:#24292E">(location.search).</span><span style="color:#6F42C1">get</span><span style="color:#24292E">(</span><span style="color:#032F62">"device"</span><span style="color:#24292E">);</span></span></code></pre></div>

</article>

<article class="doc-card">

<h3 id="custom-page-sdk-item-21">设备弹窗 API</h3>

<p><code>api.dialog.openDevicePopup(payload)</code> 请求宿主打开正式设备弹窗,<code>api.dialog.close()</code> 请求关闭当前宿主弹窗。调用前检查 <code>api.supports(&quot;popup&quot;)</code>,并从真实点击事件中调用。返回 true 只表示请求已分派。</p>

<ul>

<li><code>plc</code> 或 <code>deviceCode</code> 必填,且该 PLC 至少有一个 Tag 位于页面可读范围。</li>

<li><code>focusTagNames</code> 可选,最多 64 个;每个 Tag 都必须在页面可读范围。</li>

<li><code>templateCode</code> / <code>id</code> 可指定弹窗模板。</li>

<li><code>diagnosisControl</code>、<code>groupRightIndex</code> 是可选弹窗上下文字段。</li>

<li>设备弹窗不能扩大页面读写范围,也不等于页面获得 Popup 内部服务对象。</li>

<li>先在“弹窗模板”配置正确的绑定 Tag、控制项、报警及趋势区域;弹窗中的真实写入仍使用账号权限、Tag 控制标志、质量与 Host 确认。</li>

</ul>

<div class="manual-code" data-code-language="javascript"><div class="manual-code-toolbar"><span>JavaScript</span><span class="manual-code-tools"><small>JavaScript</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: JavaScript">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#6A737D">// Add &#x3C;button id="open-popup">Open equipment&#x3C;/button></span></span>

<span class="line"><span style="color:#6A737D">// and &#x3C;button id="close-popup">Close&#x3C;/button> to the HTML.</span></span>

<span class="line"><span style="color:#6A737D">// Place this code inside the existing ready() scope.</span></span>

<span class="line"><span style="color:#24292E">document.</span><span style="color:#6F42C1">getElementById</span><span style="color:#24292E">(</span><span style="color:#032F62">"open-popup"</span><span style="color:#24292E">).disabled </span><span style="color:#D73A49">=</span><span style="color:#D73A49"> !</span><span style="color:#24292E">api.</span><span style="color:#6F42C1">supports</span><span style="color:#24292E">(</span><span style="color:#032F62">"popup"</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#24292E">document.</span><span style="color:#6F42C1">getElementById</span><span style="color:#24292E">(</span><span style="color:#032F62">"open-popup"</span><span style="color:#24292E">).</span><span style="color:#6F42C1">onclick</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> () </span><span style="color:#D73A49">=></span><span style="color:#24292E"> {</span></span>

<span class="line"><span style="color:#D73A49"> try</span><span style="color:#24292E"> {</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> dispatched</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> api.dialog.</span><span style="color:#6F42C1">openDevicePopup</span><span style="color:#24292E">({</span></span>

<span class="line"><span style="color:#24292E"> plc: </span><span style="color:#032F62">"&#x3C;device-code>"</span><span style="color:#24292E">,</span></span>

<span class="line"><span style="color:#24292E"> templateCode: </span><span style="color:#032F62">"&#x3C;popup-template-code>"</span><span style="color:#24292E">,</span></span>

<span class="line"><span style="color:#24292E"> focusTagNames: [</span><span style="color:#032F62">"&#x3C;device-code>.&#x3C;setpoint-tag-code>"</span><span style="color:#24292E">]</span></span>

<span class="line"><span style="color:#24292E"> });</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (dispatched </span><span style="color:#D73A49">!==</span><span style="color:#005CC5"> true</span><span style="color:#24292E">) </span><span style="color:#D73A49">throw</span><span style="color:#D73A49"> new</span><span style="color:#6F42C1"> Error</span><span style="color:#24292E">(</span><span style="color:#032F62">"Popup request was not dispatched"</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#6A737D"> // Verify the actual drawer and target Tag; true is not a load-complete receipt.</span></span>

<span class="line"><span style="color:#24292E"> } </span><span style="color:#D73A49">catch</span><span style="color:#24292E"> (error) { document.</span><span style="color:#6F42C1">getElementById</span><span style="color:#24292E">(</span><span style="color:#032F62">"status"</span><span style="color:#24292E">).textContent </span><span style="color:#D73A49">=</span><span style="color:#24292E"> error.message; }</span></span>

<span class="line"><span style="color:#24292E">};</span></span>

<span class="line"><span style="color:#24292E">document.</span><span style="color:#6F42C1">getElementById</span><span style="color:#24292E">(</span><span style="color:#032F62">"close-popup"</span><span style="color:#24292E">).</span><span style="color:#6F42C1">onclick</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> () </span><span style="color:#D73A49">=></span><span style="color:#24292E"> api.dialog.</span><span style="color:#6F42C1">close</span><span style="color:#24292E">();</span></span></code></pre></div>

</article>

<article class="doc-card">

<h3 id="custom-page-sdk-item-22">完整监控页面(按需查看)</h3>

<p>完整 HTML 包含实时值、单次设定值写入、活动报警和历史趋势。发布前替换 Tag 及声明,按设备要求调整输入范围。Runtime 自动提供 SDK。</p>

<details class="manual-code-disclosure"><summary>展开完整源代码</summary><div class="manual-code" data-code-language="html"><div class="manual-code-toolbar"><span>HTML</span><span class="manual-code-tools"><small>HTML</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: HTML">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#24292E">&#x3C;!</span><span style="color:#22863A">doctype</span><span style="color:#6F42C1"> html</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E">&#x3C;</span><span style="color:#22863A">html</span><span style="color:#6F42C1"> lang</span><span style="color:#24292E">=</span><span style="color:#032F62">"en"</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E">&#x3C;</span><span style="color:#22863A">head</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">meta</span><span style="color:#6F42C1"> charset</span><span style="color:#24292E">=</span><span style="color:#032F62">"utf-8"</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">meta</span><span style="color:#6F42C1"> name</span><span style="color:#24292E">=</span><span style="color:#032F62">"viewport"</span><span style="color:#6F42C1"> content</span><span style="color:#24292E">=</span><span style="color:#032F62">"width=device-width, initial-scale=1"</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">title</span><span style="color:#24292E">>Mingcedar equipment monitor&#x3C;/</span><span style="color:#22863A">title</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">script</span><span style="color:#6F42C1"> type</span><span style="color:#24292E">=</span><span style="color:#032F62">"application/json"</span><span style="color:#6F42C1"> id</span><span style="color:#24292E">=</span><span style="color:#032F62">"hqvisu-custom-page"</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> {"readableTags":["&#x3C;device-code>.&#x3C;process-tag-code>","&#x3C;device-code>.&#x3C;setpoint-tag-code>"],</span></span>

<span class="line"><span style="color:#24292E"> "writableTags":["&#x3C;device-code>.&#x3C;setpoint-tag-code>"]}</span></span>

<span class="line"><span style="color:#24292E"> &#x3C;/</span><span style="color:#22863A">script</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">script</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> window.</span><span style="color:#005CC5">HQVISU_PAGE_META</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> { layoutMode: </span><span style="color:#032F62">"native"</span><span style="color:#24292E"> };</span></span>

<span class="line"><span style="color:#24292E"> window.</span><span style="color:#005CC5">HQVISU_PAGE_NAME</span><span style="color:#D73A49"> =</span><span style="color:#032F62"> "equipment-monitor"</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#24292E"> window.</span><span style="color:#005CC5">HQVISU_PAGE_ALARM</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> {</span></span>

<span class="line"><span style="color:#24292E"> showWindow: </span><span style="color:#005CC5">true</span><span style="color:#24292E">, tags: [</span><span style="color:#032F62">"&#x3C;device-code>.&#x3C;process-tag-code>"</span><span style="color:#24292E">],</span></span>

<span class="line"><span style="color:#24292E"> plcCodes: [], functionGroupCodes: [], deviceGroupCodes: [], signalGroupCodes: []</span></span>

<span class="line"><span style="color:#24292E"> };</span></span>

<span class="line"><span style="color:#24292E"> &#x3C;/</span><span style="color:#22863A">script</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">style</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#22863A"> body</span><span style="color:#24292E"> { </span><span style="color:#005CC5">font</span><span style="color:#24292E">: </span><span style="color:#005CC5">16</span><span style="color:#D73A49">px</span><span style="color:#24292E">/</span><span style="color:#005CC5">1.6</span><span style="color:#005CC5"> system-ui</span><span style="color:#24292E">; </span><span style="color:#005CC5">margin</span><span style="color:#24292E">: </span><span style="color:#005CC5">24</span><span style="color:#D73A49">px</span><span style="color:#24292E">; </span><span style="color:#005CC5">color</span><span style="color:#24292E">: </span><span style="color:#005CC5">#16263c</span><span style="color:#24292E">; }</span></span>

<span class="line"><span style="color:#22863A"> section</span><span style="color:#24292E"> { </span><span style="color:#005CC5">margin-block</span><span style="color:#24292E">: </span><span style="color:#005CC5">24</span><span style="color:#D73A49">px</span><span style="color:#24292E">; } </span><span style="color:#22863A">button</span><span style="color:#24292E">, </span><span style="color:#22863A">input</span><span style="color:#24292E"> { </span><span style="color:#005CC5">font</span><span style="color:#24292E">: </span><span style="color:#005CC5">inherit</span><span style="color:#24292E">; </span><span style="color:#005CC5">padding</span><span style="color:#24292E">: </span><span style="color:#005CC5">8</span><span style="color:#D73A49">px</span><span style="color:#24292E">; }</span></span>

<span class="line"><span style="color:#22863A"> table</span><span style="color:#24292E"> { </span><span style="color:#005CC5">border-collapse</span><span style="color:#24292E">: </span><span style="color:#005CC5">collapse</span><span style="color:#24292E">; </span><span style="color:#005CC5">width</span><span style="color:#24292E">: </span><span style="color:#005CC5">100</span><span style="color:#D73A49">%</span><span style="color:#24292E">; }</span></span>

<span class="line"><span style="color:#22863A"> th</span><span style="color:#24292E">, </span><span style="color:#22863A">td</span><span style="color:#24292E"> { </span><span style="color:#005CC5">text-align</span><span style="color:#24292E">: </span><span style="color:#005CC5">left</span><span style="color:#24292E">; </span><span style="color:#005CC5">padding</span><span style="color:#24292E">: </span><span style="color:#005CC5">8</span><span style="color:#D73A49">px</span><span style="color:#24292E">; </span><span style="color:#005CC5">border-bottom</span><span style="color:#24292E">: </span><span style="color:#005CC5">1</span><span style="color:#D73A49">px</span><span style="color:#005CC5"> solid</span><span style="color:#005CC5"> #d9e1eb</span><span style="color:#24292E">; }</span></span>

<span class="line"><span style="color:#22863A"> pre</span><span style="color:#24292E"> { </span><span style="color:#005CC5">white-space</span><span style="color:#24292E">: </span><span style="color:#005CC5">pre-wrap</span><span style="color:#24292E">; </span><span style="color:#005CC5">overflow-wrap</span><span style="color:#24292E">: anywhere; }</span></span>

<span class="line"><span style="color:#24292E"> &#x3C;/</span><span style="color:#22863A">style</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E">&#x3C;/</span><span style="color:#22863A">head</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E">&#x3C;</span><span style="color:#22863A">body</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">h1</span><span style="color:#24292E">>Equipment monitor&#x3C;/</span><span style="color:#22863A">h1</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">p</span><span style="color:#6F42C1"> id</span><span style="color:#24292E">=</span><span style="color:#032F62">"status"</span><span style="color:#6F42C1"> role</span><span style="color:#24292E">=</span><span style="color:#032F62">"status"</span><span style="color:#24292E">>Connecting to Mingcedar…&#x3C;/</span><span style="color:#22863A">p</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">section</span><span style="color:#24292E">>&#x3C;</span><span style="color:#22863A">h2</span><span style="color:#24292E">>Live value&#x3C;/</span><span style="color:#22863A">h2</span><span style="color:#24292E">>&#x3C;</span><span style="color:#22863A">output</span><span style="color:#6F42C1"> id</span><span style="color:#24292E">=</span><span style="color:#032F62">"value"</span><span style="color:#24292E">>Waiting for data&#x3C;/</span><span style="color:#22863A">output</span><span style="color:#24292E">>&#x3C;/</span><span style="color:#22863A">section</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">section</span><span style="color:#24292E">>&#x3C;</span><span style="color:#22863A">h2</span><span style="color:#24292E">>Setpoint&#x3C;/</span><span style="color:#22863A">h2</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">label</span><span style="color:#24292E">>Target (0–100) &#x3C;</span><span style="color:#22863A">input</span><span style="color:#6F42C1"> id</span><span style="color:#24292E">=</span><span style="color:#032F62">"setpoint"</span><span style="color:#6F42C1"> type</span><span style="color:#24292E">=</span><span style="color:#032F62">"number"</span><span style="color:#6F42C1"> min</span><span style="color:#24292E">=</span><span style="color:#032F62">"0"</span><span style="color:#6F42C1"> max</span><span style="color:#24292E">=</span><span style="color:#032F62">"100"</span><span style="color:#6F42C1"> step</span><span style="color:#24292E">=</span><span style="color:#032F62">"0.1"</span><span style="color:#24292E">>&#x3C;/</span><span style="color:#22863A">label</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">button</span><span style="color:#6F42C1"> id</span><span style="color:#24292E">=</span><span style="color:#032F62">"write"</span><span style="color:#6F42C1"> disabled</span><span style="color:#24292E">>Write setpoint&#x3C;/</span><span style="color:#22863A">button</span><span style="color:#24292E">>&#x3C;</span><span style="color:#22863A">pre</span><span style="color:#6F42C1"> id</span><span style="color:#24292E">=</span><span style="color:#032F62">"write-result"</span><span style="color:#24292E">>&#x3C;/</span><span style="color:#22863A">pre</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;/</span><span style="color:#22863A">section</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">section</span><span style="color:#24292E">>&#x3C;</span><span style="color:#22863A">h2</span><span style="color:#24292E">>Active alarms&#x3C;/</span><span style="color:#22863A">h2</span><span style="color:#24292E">>&#x3C;</span><span style="color:#22863A">button</span><span style="color:#6F42C1"> id</span><span style="color:#24292E">=</span><span style="color:#032F62">"alarms"</span><span style="color:#6F42C1"> disabled</span><span style="color:#24292E">>Refresh alarms&#x3C;/</span><span style="color:#22863A">button</span><span style="color:#24292E">>&#x3C;</span><span style="color:#22863A">div</span><span style="color:#6F42C1"> id</span><span style="color:#24292E">=</span><span style="color:#032F62">"alarm-list"</span><span style="color:#24292E">>&#x3C;/</span><span style="color:#22863A">div</span><span style="color:#24292E">>&#x3C;</span><span style="color:#22863A">p</span><span style="color:#6F42C1"> id</span><span style="color:#24292E">=</span><span style="color:#032F62">"alarm-status"</span><span style="color:#24292E">>&#x3C;/</span><span style="color:#22863A">p</span><span style="color:#24292E">>&#x3C;/</span><span style="color:#22863A">section</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">section</span><span style="color:#24292E">>&#x3C;</span><span style="color:#22863A">h2</span><span style="color:#24292E">>Last hour&#x3C;/</span><span style="color:#22863A">h2</span><span style="color:#24292E">>&#x3C;</span><span style="color:#22863A">button</span><span style="color:#6F42C1"> id</span><span style="color:#24292E">=</span><span style="color:#032F62">"history"</span><span style="color:#6F42C1"> disabled</span><span style="color:#24292E">>Query historical trend&#x3C;/</span><span style="color:#22863A">button</span><span style="color:#24292E">>&#x3C;</span><span style="color:#22863A">div</span><span style="color:#6F42C1"> id</span><span style="color:#24292E">=</span><span style="color:#032F62">"trend"</span><span style="color:#24292E">>&#x3C;/</span><span style="color:#22863A">div</span><span style="color:#24292E">>&#x3C;</span><span style="color:#22863A">p</span><span style="color:#6F42C1"> id</span><span style="color:#24292E">=</span><span style="color:#032F62">"trend-status"</span><span style="color:#24292E">>&#x3C;/</span><span style="color:#22863A">p</span><span style="color:#24292E">>&#x3C;/</span><span style="color:#22863A">section</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">script</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> (</span><span style="color:#D73A49">async</span><span style="color:#D73A49"> function</span><span style="color:#24292E"> () {</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#6F42C1"> element</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> (</span><span style="color:#E36209">id</span><span style="color:#24292E">) </span><span style="color:#D73A49">=></span><span style="color:#24292E"> document.</span><span style="color:#6F42C1">getElementById</span><span style="color:#24292E">(id);</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> processTag</span><span style="color:#D73A49"> =</span><span style="color:#032F62"> "&#x3C;device-code>.&#x3C;process-tag-code>"</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> setpointTag</span><span style="color:#D73A49"> =</span><span style="color:#032F62"> "&#x3C;device-code>.&#x3C;setpoint-tag-code>"</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> api</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> await</span><span style="color:#005CC5"> HQVISU_CUSTOM_PAGE</span><span style="color:#24292E">.</span><span style="color:#6F42C1">ready</span><span style="color:#24292E">({timeoutMs: </span><span style="color:#005CC5">10000</span><span style="color:#24292E">});</span></span>

<span class="line"><span style="color:#D73A49"> let</span><span style="color:#6F42C1"> stopValues</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> () </span><span style="color:#D73A49">=></span><span style="color:#24292E"> {};</span></span>

<span class="line"><span style="color:#D73A49"> let</span><span style="color:#6F42C1"> stopAlarms</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> () </span><span style="color:#D73A49">=></span><span style="color:#24292E"> {};</span></span>

<span class="line"><span style="color:#6F42C1"> element</span><span style="color:#24292E">(</span><span style="color:#032F62">"status"</span><span style="color:#24292E">).textContent </span><span style="color:#D73A49">=</span><span style="color:#032F62"> "Connected"</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#24292E"> window.</span><span style="color:#6F42C1">addEventListener</span><span style="color:#24292E">(</span><span style="color:#032F62">"pagehide"</span><span style="color:#24292E">, </span><span style="color:#E36209">event</span><span style="color:#D73A49"> =></span><span style="color:#24292E"> {</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (</span><span style="color:#D73A49">!</span><span style="color:#24292E">event.persisted) { </span><span style="color:#6F42C1">stopValues</span><span style="color:#24292E">(); </span><span style="color:#6F42C1">stopAlarms</span><span style="color:#24292E">(); api.</span><span style="color:#6F42C1">destroy</span><span style="color:#24292E">(); }</span></span>

<span class="line"><span style="color:#24292E"> });</span></span>

<span class="line"><span style="color:#D73A49"> function</span><span style="color:#6F42C1"> refreshValue</span><span style="color:#24292E">() {</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> quality</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> api.</span><span style="color:#6F42C1">ReadTagQuality</span><span style="color:#24292E">(processTag);</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> value</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> api.</span><span style="color:#6F42C1">ReadTagDouble</span><span style="color:#24292E">(processTag, </span><span style="color:#005CC5">null</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#6F42C1"> element</span><span style="color:#24292E">(</span><span style="color:#032F62">"value"</span><span style="color:#24292E">).textContent </span><span style="color:#D73A49">=</span><span style="color:#24292E"> quality </span><span style="color:#D73A49">===</span><span style="color:#032F62"> "good"</span><span style="color:#D73A49"> &#x26;&#x26;</span><span style="color:#24292E"> value </span><span style="color:#D73A49">!==</span><span style="color:#005CC5"> null</span></span>

<span class="line"><span style="color:#D73A49"> ?</span><span style="color:#6F42C1"> String</span><span style="color:#24292E">(value) </span><span style="color:#D73A49">:</span><span style="color:#032F62"> "Unavailable — quality: "</span><span style="color:#D73A49"> +</span><span style="color:#24292E"> quality;</span></span>

<span class="line"><span style="color:#24292E"> }</span></span>

<span class="line"><span style="color:#24292E"> stopValues </span><span style="color:#D73A49">=</span><span style="color:#24292E"> api.</span><span style="color:#6F42C1">subscribe</span><span style="color:#24292E">([processTag, setpointTag], refreshValue);</span></span>

<span class="line"><span style="color:#6F42C1"> refreshValue</span><span style="color:#24292E">();</span></span>

<span class="line"><span style="color:#6F42C1"> element</span><span style="color:#24292E">(</span><span style="color:#032F62">"status"</span><span style="color:#24292E">).textContent </span><span style="color:#D73A49">=</span><span style="color:#032F62"> "Connected. Reads remain subject to data quality."</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#6F42C1"> element</span><span style="color:#24292E">(</span><span style="color:#032F62">"write"</span><span style="color:#24292E">).disabled </span><span style="color:#D73A49">=</span><span style="color:#D73A49"> !</span><span style="color:#24292E">(api.</span><span style="color:#6F42C1">supports</span><span style="color:#24292E">(</span><span style="color:#032F62">"tag.write"</span><span style="color:#24292E">) </span><span style="color:#D73A49">&#x26;&#x26;</span><span style="color:#D73A49"> await</span><span style="color:#24292E"> api.permissions.</span><span style="color:#6F42C1">has</span><span style="color:#24292E">(</span><span style="color:#032F62">"tag:write"</span><span style="color:#24292E">));</span></span>

<span class="line"><span style="color:#6F42C1"> element</span><span style="color:#24292E">(</span><span style="color:#032F62">"write"</span><span style="color:#24292E">).</span><span style="color:#6F42C1">onclick</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> async</span><span style="color:#24292E"> () </span><span style="color:#D73A49">=></span><span style="color:#24292E"> {</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> input</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> element</span><span style="color:#24292E">(</span><span style="color:#032F62">"setpoint"</span><span style="color:#24292E">).value.</span><span style="color:#6F42C1">trim</span><span style="color:#24292E">();</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> value</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> Number</span><span style="color:#24292E">(input);</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (</span><span style="color:#D73A49">!</span><span style="color:#24292E">input </span><span style="color:#D73A49">||</span><span style="color:#D73A49"> !</span><span style="color:#24292E">Number.</span><span style="color:#6F42C1">isFinite</span><span style="color:#24292E">(value) </span><span style="color:#D73A49">||</span><span style="color:#24292E"> value </span><span style="color:#D73A49">&#x3C;</span><span style="color:#005CC5"> 0</span><span style="color:#D73A49"> ||</span><span style="color:#24292E"> value </span><span style="color:#D73A49">></span><span style="color:#005CC5"> 100</span><span style="color:#24292E">) {</span></span>

<span class="line"><span style="color:#6F42C1"> element</span><span style="color:#24292E">(</span><span style="color:#032F62">"write-result"</span><span style="color:#24292E">).textContent </span><span style="color:#D73A49">=</span><span style="color:#032F62"> "Enter a number between 0 and 100."</span><span style="color:#24292E">; </span><span style="color:#D73A49">return</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#24292E"> }</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (</span><span style="color:#D73A49">!</span><span style="color:#24292E">api.</span><span style="color:#6F42C1">ReadTagQualityGood</span><span style="color:#24292E">(setpointTag)) {</span></span>

<span class="line"><span style="color:#6F42C1"> element</span><span style="color:#24292E">(</span><span style="color:#032F62">"write-result"</span><span style="color:#24292E">).textContent </span><span style="color:#D73A49">=</span><span style="color:#032F62"> "Setpoint quality is not good."</span><span style="color:#24292E">; </span><span style="color:#D73A49">return</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#24292E"> }</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (</span><span style="color:#D73A49">!</span><span style="color:#6F42C1">confirm</span><span style="color:#24292E">(</span><span style="color:#032F62">"Write setpoint "</span><span style="color:#D73A49"> +</span><span style="color:#24292E"> value </span><span style="color:#D73A49">+</span><span style="color:#032F62"> "?"</span><span style="color:#24292E">)) </span><span style="color:#D73A49">return</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#6F42C1"> element</span><span style="color:#24292E">(</span><span style="color:#032F62">"write"</span><span style="color:#24292E">).disabled </span><span style="color:#D73A49">=</span><span style="color:#005CC5"> true</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#D73A49"> try</span><span style="color:#24292E"> {</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> result</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> await</span><span style="color:#24292E"> api.</span><span style="color:#6F42C1">WriteTagDouble</span><span style="color:#24292E">(setpointTag, value, {reason: </span><span style="color:#032F62">"Operator setpoint update"</span><span style="color:#24292E">});</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> done</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> result?.accepted </span><span style="color:#D73A49">===</span><span style="color:#005CC5"> true</span><span style="color:#D73A49"> &#x26;&#x26;</span><span style="color:#24292E"> result?.status </span><span style="color:#D73A49">===</span><span style="color:#032F62"> "done"</span><span style="color:#D73A49"> &#x26;&#x26;</span><span style="color:#24292E"> result?.outcomeUncertain </span><span style="color:#D73A49">!==</span><span style="color:#005CC5"> true</span><span style="color:#D73A49"> &#x26;&#x26;</span><span style="color:#24292E"> result?.operatorVerificationRequired </span><span style="color:#D73A49">!==</span><span style="color:#005CC5"> true</span><span style="color:#D73A49"> &#x26;&#x26;</span><span style="color:#24292E"> result?.transportOnly </span><span style="color:#D73A49">!==</span><span style="color:#005CC5"> true</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#6F42C1"> element</span><span style="color:#24292E">(</span><span style="color:#032F62">"write-result"</span><span style="color:#24292E">).textContent </span><span style="color:#D73A49">=</span><span style="color:#24292E"> (done </span><span style="color:#D73A49">?</span><span style="color:#032F62"> "Command completed; verify normal collected state: "</span><span style="color:#D73A49"> :</span><span style="color:#032F62"> "Verify before another write: "</span><span style="color:#24292E">) </span><span style="color:#D73A49">+</span><span style="color:#005CC5"> JSON</span><span style="color:#24292E">.</span><span style="color:#6F42C1">stringify</span><span style="color:#24292E">(result </span><span style="color:#D73A49">??</span><span style="color:#005CC5"> null</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#24292E"> } </span><span style="color:#D73A49">catch</span><span style="color:#24292E"> (error) {</span></span>

<span class="line"><span style="color:#6F42C1"> element</span><span style="color:#24292E">(</span><span style="color:#032F62">"write-result"</span><span style="color:#24292E">).textContent </span><span style="color:#D73A49">=</span><span style="color:#032F62"> "Result not confirmed. Check actual value and command records. "</span><span style="color:#D73A49"> +</span><span style="color:#24292E"> error.message;</span></span>

<span class="line"><span style="color:#24292E"> }</span></span>

<span class="line"><span style="color:#6A737D"> // Keep the button disabled after submission; refresh only after result reconciliation.</span></span>

<span class="line"><span style="color:#24292E"> };</span></span>

<span class="line"><span style="color:#D73A49"> let</span><span style="color:#24292E"> alarmPending </span><span style="color:#D73A49">=</span><span style="color:#005CC5"> false</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#D73A49"> async</span><span style="color:#D73A49"> function</span><span style="color:#6F42C1"> refreshAlarms</span><span style="color:#24292E">() {</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (alarmPending) </span><span style="color:#D73A49">return</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#24292E"> alarmPending </span><span style="color:#D73A49">=</span><span style="color:#005CC5"> true</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#D73A49"> try</span><span style="color:#24292E"> {</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> result</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> await</span><span style="color:#24292E"> api.alarms.</span><span style="color:#6F42C1">getActive</span><span style="color:#24292E">({limit: </span><span style="color:#005CC5">100</span><span style="color:#24292E">, offset: </span><span style="color:#005CC5">0</span><span style="color:#24292E">});</span></span>

<span class="line"><span style="color:#24292E"> api.</span><span style="color:#6F42C1">renderTable</span><span style="color:#24292E">(</span><span style="color:#6F42C1">element</span><span style="color:#24292E">(</span><span style="color:#032F62">"alarm-list"</span><span style="color:#24292E">), result.items, {columns: [</span></span>

<span class="line"><span style="color:#24292E"> {key:</span><span style="color:#032F62">"fullCode"</span><span style="color:#24292E">,label:</span><span style="color:#032F62">"Tag"</span><span style="color:#24292E">}, {key:</span><span style="color:#032F62">"messageText"</span><span style="color:#24292E">,label:</span><span style="color:#032F62">"Message"</span><span style="color:#24292E">},</span></span>

<span class="line"><span style="color:#24292E"> {key:</span><span style="color:#032F62">"severity"</span><span style="color:#24292E">,label:</span><span style="color:#032F62">"Severity"</span><span style="color:#24292E">}, {key:</span><span style="color:#032F62">"state"</span><span style="color:#24292E">,label:</span><span style="color:#032F62">"State"</span><span style="color:#24292E">}</span></span>

<span class="line"><span style="color:#24292E"> ]});</span></span>

<span class="line"><span style="color:#6F42C1"> element</span><span style="color:#24292E">(</span><span style="color:#032F62">"alarm-status"</span><span style="color:#24292E">).textContent </span><span style="color:#D73A49">=</span><span style="color:#24292E"> result.hasMore </span><span style="color:#D73A49">?</span><span style="color:#032F62"> "Showing first 100 alarms; narrow the filter or request the next page."</span><span style="color:#D73A49"> :</span><span style="color:#032F62"> "Current active alarms: "</span><span style="color:#D73A49"> +</span><span style="color:#24292E"> result.count;</span></span>

<span class="line"><span style="color:#24292E"> } </span><span style="color:#D73A49">catch</span><span style="color:#24292E"> (error) { </span><span style="color:#6F42C1">element</span><span style="color:#24292E">(</span><span style="color:#032F62">"alarm-status"</span><span style="color:#24292E">).textContent </span><span style="color:#D73A49">=</span><span style="color:#24292E"> error.message; }</span></span>

<span class="line"><span style="color:#D73A49"> finally</span><span style="color:#24292E"> { alarmPending </span><span style="color:#D73A49">=</span><span style="color:#005CC5"> false</span><span style="color:#24292E">; }</span></span>

<span class="line"><span style="color:#24292E"> }</span></span>

<span class="line"><span style="color:#6F42C1"> element</span><span style="color:#24292E">(</span><span style="color:#032F62">"alarms"</span><span style="color:#24292E">).disabled </span><span style="color:#D73A49">=</span><span style="color:#D73A49"> !</span><span style="color:#24292E">api.</span><span style="color:#6F42C1">supports</span><span style="color:#24292E">(</span><span style="color:#032F62">"alarm.read"</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#6F42C1"> element</span><span style="color:#24292E">(</span><span style="color:#032F62">"alarms"</span><span style="color:#24292E">).onclick </span><span style="color:#D73A49">=</span><span style="color:#24292E"> refreshAlarms;</span></span>

<span class="line"><span style="color:#24292E"> stopAlarms </span><span style="color:#D73A49">=</span><span style="color:#24292E"> api.</span><span style="color:#6F42C1">supports</span><span style="color:#24292E">(</span><span style="color:#032F62">"alarm.read"</span><span style="color:#24292E">) </span><span style="color:#D73A49">?</span><span style="color:#24292E"> api.alarms.</span><span style="color:#6F42C1">subscribe</span><span style="color:#24292E">(refreshAlarms) </span><span style="color:#D73A49">:</span><span style="color:#24292E"> () </span><span style="color:#D73A49">=></span><span style="color:#24292E"> {};</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (api.</span><span style="color:#6F42C1">supports</span><span style="color:#24292E">(</span><span style="color:#032F62">"alarm.read"</span><span style="color:#24292E">)) </span><span style="color:#D73A49">await</span><span style="color:#6F42C1"> refreshAlarms</span><span style="color:#24292E">();</span></span>

<span class="line"><span style="color:#6F42C1"> element</span><span style="color:#24292E">(</span><span style="color:#032F62">"history"</span><span style="color:#24292E">).disabled </span><span style="color:#D73A49">=</span><span style="color:#D73A49"> !</span><span style="color:#24292E">api.</span><span style="color:#6F42C1">supports</span><span style="color:#24292E">(</span><span style="color:#032F62">"history.read"</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#6F42C1"> element</span><span style="color:#24292E">(</span><span style="color:#032F62">"history"</span><span style="color:#24292E">).</span><span style="color:#6F42C1">onclick</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> async</span><span style="color:#24292E"> () </span><span style="color:#D73A49">=></span><span style="color:#24292E"> {</span></span>

<span class="line"><span style="color:#6F42C1"> element</span><span style="color:#24292E">(</span><span style="color:#032F62">"history"</span><span style="color:#24292E">).disabled </span><span style="color:#D73A49">=</span><span style="color:#005CC5"> true</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#D73A49"> try</span><span style="color:#24292E"> {</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> end</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> new</span><span style="color:#6F42C1"> Date</span><span style="color:#24292E">();</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> result</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> await</span><span style="color:#24292E"> api.history.</span><span style="color:#6F42C1">query</span><span style="color:#24292E">({tags:[processTag],</span></span>

<span class="line"><span style="color:#24292E"> start:</span><span style="color:#D73A49">new</span><span style="color:#6F42C1"> Date</span><span style="color:#24292E">(end.</span><span style="color:#6F42C1">getTime</span><span style="color:#24292E">()</span><span style="color:#D73A49">-</span><span style="color:#005CC5">3600000</span><span style="color:#24292E">).</span><span style="color:#6F42C1">toISOString</span><span style="color:#24292E">(), end:end.</span><span style="color:#6F42C1">toISOString</span><span style="color:#24292E">(),</span></span>

<span class="line"><span style="color:#24292E"> interval:</span><span style="color:#032F62">"1m"</span><span style="color:#24292E">, aggregate:</span><span style="color:#032F62">"avg"</span><span style="color:#24292E">, points:</span><span style="color:#005CC5">1000</span><span style="color:#24292E">});</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> rows</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> (result.items </span><span style="color:#D73A49">||</span><span style="color:#24292E"> []).</span><span style="color:#6F42C1">flatMap</span><span style="color:#24292E">(</span><span style="color:#E36209">series</span><span style="color:#D73A49"> =></span><span style="color:#24292E"> (series.points </span><span style="color:#D73A49">||</span><span style="color:#24292E"> []).</span><span style="color:#6F42C1">map</span><span style="color:#24292E">(</span><span style="color:#E36209">point</span><span style="color:#D73A49"> =></span><span style="color:#24292E"> ({</span></span>

<span class="line"><span style="color:#24292E"> tag:series.tag, time:point.t, value:point.quality </span><span style="color:#D73A49">===</span><span style="color:#032F62"> "good"</span><span style="color:#D73A49"> ?</span><span style="color:#24292E"> point.value </span><span style="color:#D73A49">:</span><span style="color:#005CC5"> null</span><span style="color:#24292E">, quality:point.quality</span></span>

<span class="line"><span style="color:#24292E"> })));</span></span>

<span class="line"><span style="color:#24292E"> api.</span><span style="color:#6F42C1">renderTable</span><span style="color:#24292E">(</span><span style="color:#6F42C1">element</span><span style="color:#24292E">(</span><span style="color:#032F62">"trend"</span><span style="color:#24292E">), rows);</span></span>

<span class="line"><span style="color:#6F42C1"> element</span><span style="color:#24292E">(</span><span style="color:#032F62">"trend-status"</span><span style="color:#24292E">).textContent </span><span style="color:#D73A49">=</span><span style="color:#24292E"> rows.</span><span style="color:#005CC5">length</span></span>

<span class="line"><span style="color:#D73A49"> ?</span><span style="color:#032F62"> "Interval: "</span><span style="color:#D73A49"> +</span><span style="color:#24292E"> result.effectiveInterval </span><span style="color:#D73A49">+</span><span style="color:#24292E"> (result.truncated </span><span style="color:#D73A49">?</span><span style="color:#032F62"> " (adjusted to point budget)"</span><span style="color:#D73A49"> :</span><span style="color:#032F62"> ""</span><span style="color:#24292E">)</span></span>

<span class="line"><span style="color:#D73A49"> :</span><span style="color:#032F62"> "No archived values in this time range."</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#24292E"> } </span><span style="color:#D73A49">catch</span><span style="color:#24292E"> (error) { </span><span style="color:#6F42C1">element</span><span style="color:#24292E">(</span><span style="color:#032F62">"trend-status"</span><span style="color:#24292E">).textContent </span><span style="color:#D73A49">=</span><span style="color:#24292E"> error.message; }</span></span>

<span class="line"><span style="color:#D73A49"> finally</span><span style="color:#24292E"> { </span><span style="color:#6F42C1">element</span><span style="color:#24292E">(</span><span style="color:#032F62">"history"</span><span style="color:#24292E">).disabled </span><span style="color:#D73A49">=</span><span style="color:#005CC5"> false</span><span style="color:#24292E">; }</span></span>

<span class="line"><span style="color:#24292E"> };</span></span>

<span class="line"><span style="color:#24292E"> })().</span><span style="color:#6F42C1">catch</span><span style="color:#24292E">(</span><span style="color:#E36209">error</span><span style="color:#D73A49"> =></span><span style="color:#24292E"> { document.</span><span style="color:#6F42C1">getElementById</span><span style="color:#24292E">(</span><span style="color:#032F62">"status"</span><span style="color:#24292E">).textContent </span><span style="color:#D73A49">=</span><span style="color:#24292E"> error.message; });</span></span>

<span class="line"><span style="color:#24292E"> &#x3C;/</span><span style="color:#22863A">script</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E">&#x3C;/</span><span style="color:#22863A">body</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E">&#x3C;/</span><span style="color:#22863A">html</span><span style="color:#24292E">></span></span></code></pre></div></details>

</article>

<article class="doc-card">

<h3 id="custom-page-sdk-item-23">renderTable 与 bindJsonTable</h3>

<p><code>api.renderTable</code> 与全局 <code>HQVISU_CUSTOM_PAGE.renderTable</code> 将数组、<code>{ rows }</code> 或 <code>{ items }</code> 渲染为 HTML 表格,返回 <code>{ rowCount, columnCount }</code>。<code>bindJsonTable</code> 首次渲染后订阅 JSON Tag,返回取消函数。</p>

<ul>

<li><code>target</code> 可以是 CSS 选择器或 DOM 元素。</li>

<li><code>options.columns</code> 可传字符串数组,也可传 <code>{ key, label }</code> 对象数组。</li>

<li>未提供 columns 时,SDK 会根据前 25 行数据推断列。</li>

<li><code>options.className</code> 可指定表格 class,页面自行写 CSS。</li>

<li>单元格通过 <code>textContent</code> 写入,不把 Tag 内容当 HTML 执行;目标元素不存在时返回 <code>{ rowCount: 0, columnCount: 0 }</code>。</li>

<li><code>bindJsonTable(target, tagName, options)</code> 接受数组 JSON、<code>{ rows }</code> 或 <code>{ items }</code>;空值或解析失败时渲染空表。它直接显示原始快照,不按 Tag 质量过滤;需要区分失效数据时,使用 subscribe,在回调中检查 ReadTagQualityGood 后再调用 renderTable。</li>

<li>复杂排序、分页、冻结列和导出 Excel 应由项目页面或第三方表格组件实现。</li>

</ul>

<div class="manual-code" data-code-language="javascript"><div class="manual-code-toolbar"><span>JavaScript</span><span class="manual-code-tools"><small>JavaScript</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: JavaScript">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#24292E">api.</span><span style="color:#6F42C1">renderTable</span><span style="color:#24292E">(</span><span style="color:#032F62">"#quality-table"</span><span style="color:#24292E">, {</span></span>

<span class="line"><span style="color:#24292E"> rows: [</span></span>

<span class="line"><span style="color:#24292E"> { tag: </span><span style="color:#032F62">"&#x3C;device-code>.&#x3C;temperature-tag-code>"</span><span style="color:#24292E">, value: </span><span style="color:#032F62">"72.4"</span><span style="color:#24292E">, quality: </span><span style="color:#032F62">"good"</span><span style="color:#24292E"> },</span></span>

<span class="line"><span style="color:#24292E"> { tag: </span><span style="color:#032F62">"&#x3C;device-code>.&#x3C;pressure-tag-code>"</span><span style="color:#24292E">, value: </span><span style="color:#032F62">"0.68"</span><span style="color:#24292E">, quality: </span><span style="color:#032F62">"good"</span><span style="color:#24292E"> }</span></span>

<span class="line"><span style="color:#24292E"> ]</span></span>

<span class="line"><span style="color:#24292E">}, {</span></span>

<span class="line"><span style="color:#24292E"> className: </span><span style="color:#032F62">"report-table"</span><span style="color:#24292E">,</span></span>

<span class="line"><span style="color:#24292E"> columns: [</span></span>

<span class="line"><span style="color:#24292E"> { key: </span><span style="color:#032F62">"tag"</span><span style="color:#24292E">, label: </span><span style="color:#032F62">"点位"</span><span style="color:#24292E"> },</span></span>

<span class="line"><span style="color:#24292E"> { key: </span><span style="color:#032F62">"value"</span><span style="color:#24292E">, label: </span><span style="color:#032F62">"当前值"</span><span style="color:#24292E"> },</span></span>

<span class="line"><span style="color:#24292E"> { key: </span><span style="color:#032F62">"quality"</span><span style="color:#24292E">, label: </span><span style="color:#032F62">"质量"</span><span style="color:#24292E"> }</span></span>

<span class="line"><span style="color:#24292E"> ]</span></span>

<span class="line"><span style="color:#24292E">});</span></span>

<span class="line"></span>

<span class="line"><span style="color:#6A737D">// JSON Tag 内容变化时自动刷新表格</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> stopTable</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> api.</span><span style="color:#6F42C1">bindJsonTable</span><span style="color:#24292E">(</span><span style="color:#032F62">"#event-table"</span><span style="color:#24292E">, </span><span style="color:#032F62">"&#x3C;device-code>.&#x3C;event-rows-tag-code>"</span><span style="color:#24292E">, {</span></span>

<span class="line"><span style="color:#24292E"> columns: [</span><span style="color:#032F62">"time"</span><span style="color:#24292E">, </span><span style="color:#032F62">"type"</span><span style="color:#24292E">, </span><span style="color:#032F62">"message"</span><span style="color:#24292E">]</span></span>

<span class="line"><span style="color:#24292E">});</span></span></code></pre></div>

</article>

<article class="doc-card">

<h3 id="custom-page-sdk-item-24">多语言:跟随系统顶栏 LANG</h3>

<p>统一使用系统顶栏 LANG,不另设语言按钮。先按当前语言渲染,再订阅变化;只翻译显示文案,保留接口数据、Tag 原值和命令状态码。</p>

<ul>

<li><code>language.current()</code> / <code>getLocale()</code> → string。</li>

<li><code>language.translate(key, fallback?)</code> / <code>t(key, fallback?)</code>;根 api.translate/api.t 同义。缺少 key 返回 fallback,未给 fallback 返回 key。业务翻译 key 在系统多语言配置中维护。</li>

<li><code>language.onChange((locale, translations) =&gt; ...)</code> / <code>subscribe(...)</code> → 取消函数,不主动发送初值。</li>

<li><code>language.setLocale(locale)</code> → string | Promise&lt;string&gt;;影响系统语言,不能当组件局部状态。页面内的语言设置应与系统顶栏保持一致。</li>

<li>面包屑名称来自静态页面导航声明;公开 PageApi 没有动态改名接口,不重复握手来刷新。</li>

</ul>

<div class="manual-code" data-code-language="javascript"><div class="manual-code-toolbar"><span>JavaScript</span><span class="manual-code-tools"><small>JavaScript</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: JavaScript">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#6A737D">// The system top-bar LANG is the language selector. Do not add a second one.</span></span>

<span class="line"><span style="color:#D73A49">function</span><span style="color:#6F42C1"> renderText</span><span style="color:#24292E">() {</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> locale</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> api.language.</span><span style="color:#6F42C1">current</span><span style="color:#24292E">();</span></span>

<span class="line"><span style="color:#24292E"> document.documentElement.lang </span><span style="color:#D73A49">=</span><span style="color:#24292E"> locale;</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> fallback</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> locale.</span><span style="color:#6F42C1">startsWith</span><span style="color:#24292E">(</span><span style="color:#032F62">"zh"</span><span style="color:#24292E">) </span><span style="color:#D73A49">?</span><span style="color:#032F62"> "设备总览"</span><span style="color:#D73A49"> :</span><span style="color:#032F62"> "Equipment overview"</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#24292E"> document.</span><span style="color:#6F42C1">getElementById</span><span style="color:#24292E">(</span><span style="color:#032F62">"title"</span><span style="color:#24292E">).textContent </span><span style="color:#D73A49">=</span><span style="color:#24292E"> api.</span><span style="color:#6F42C1">t</span><span style="color:#24292E">(</span><span style="color:#032F62">"equipment.overview.title"</span><span style="color:#24292E">, fallback);</span></span>

<span class="line"><span style="color:#24292E">}</span></span>

<span class="line"><span style="color:#6F42C1">renderText</span><span style="color:#24292E">(); </span><span style="color:#6A737D">// onChange does not supply an initial callback.</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> stopLanguage</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> api.language.</span><span style="color:#6F42C1">onChange</span><span style="color:#24292E">(renderText);</span></span>

<span class="line"><span style="color:#6A737D">// Call stopLanguage() in this page/component's existing cleanup handler.</span></span></code></pre></div>

</article>

<article class="doc-card">

<h3 id="custom-page-sdk-item-25">使用 Mock Host 做本地页面开发</h3>

<p>随版本提供的 <code>runtime-page-sdk-mock-host-v1.js</code> 可在没有 Mingcedar 的浏览器中提供开发数据。先创建 Mock Host,再加载 <code>hq-common.js</code> 和 <code>hq-custom-page.js</code>。Mock 只帮助验证页面渲染、订阅和异常态,不模拟真实安全、权限、审计、写入确认、历史库或 PLC。</p>

<ul>

<li><code>createHQVisuMockHost(options)</code> 返回一个开发宿主对象。</li>

<li><code>options.values</code>、<code>meta</code>、<code>user</code>、<code>alarms</code>、<code>locale</code> 设置初始状态。</li>

<li><code>queryHistory</code>、<code>queryAlarmHistory</code> 可提供自定义查询结果。</li>

<li><code>host.__mock.setTag(name, value, meta)</code> 推送 Tag 变化;<code>setAlarms(items)</code> 推送报警快照。</li>

<li>上线验收必须回到 Mingcedar Runtime,重新验证页面能力、普通用户权限、写入确认和真实查询限制。</li>

<li>本地 Mock 使用配套开发文件;正式部署从 Runtime 提供的 SDK URL 加载,不要将本地开发用的 hq-common.js 当作独立 npm 包部署。</li>

</ul>

<div class="manual-code" data-code-language="html"><div class="manual-code-toolbar"><span>HTML</span><span class="manual-code-tools"><small>HTML</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: HTML">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#24292E">&#x3C;</span><span style="color:#22863A">script</span><span style="color:#6F42C1"> src</span><span style="color:#24292E">=</span><span style="color:#032F62">"./runtime-page-sdk-mock-host-v1.js"</span><span style="color:#24292E">>&#x3C;/</span><span style="color:#22863A">script</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E">&#x3C;</span><span style="color:#22863A">script</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E">window.</span><span style="color:#005CC5">HQVISU_HOST</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> createHQVisuMockHost</span><span style="color:#24292E">({</span></span>

<span class="line"><span style="color:#24292E"> values: { </span><span style="color:#032F62">"&#x3C;device-code>.&#x3C;process-tag-code>"</span><span style="color:#24292E">: </span><span style="color:#005CC5">72.4</span><span style="color:#24292E"> },</span></span>

<span class="line"><span style="color:#24292E"> meta: { </span><span style="color:#032F62">"&#x3C;device-code>.&#x3C;process-tag-code>"</span><span style="color:#24292E">: { quality: </span><span style="color:#032F62">"good"</span><span style="color:#24292E"> } },</span></span>

<span class="line"><span style="color:#24292E"> user: {</span></span>

<span class="line"><span style="color:#24292E"> username: </span><span style="color:#032F62">"&#x3C;operator-name>"</span><span style="color:#24292E">,</span></span>

<span class="line"><span style="color:#24292E"> displayName: </span><span style="color:#032F62">"班次操作员"</span><span style="color:#24292E">,</span></span>

<span class="line"><span style="color:#24292E"> permissions: []</span></span>

<span class="line"><span style="color:#24292E"> }</span></span>

<span class="line"><span style="color:#24292E">});</span></span>

<span class="line"><span style="color:#24292E">&#x3C;/</span><span style="color:#22863A">script</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E">&#x3C;</span><span style="color:#22863A">script</span><span style="color:#6F42C1"> src</span><span style="color:#24292E">=</span><span style="color:#032F62">"./hq-common.js"</span><span style="color:#24292E">>&#x3C;/</span><span style="color:#22863A">script</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E">&#x3C;</span><span style="color:#22863A">script</span><span style="color:#6F42C1"> src</span><span style="color:#24292E">=</span><span style="color:#032F62">"./hq-custom-page.js"</span><span style="color:#24292E">>&#x3C;/</span><span style="color:#22863A">script</span><span style="color:#24292E">></span></span></code></pre></div>

</article>

<article class="doc-card">

<h3 id="custom-page-sdk-item-26">PPT 与自写页:只使用同名公共读写接口</h3>

<p>自写页调用 api.ReadTagInteger / api.WriteTagInteger,PPT 调用 ReadTagInteger / WriteTagInteger;大小写、解析、质量 fallback、日期和结果判断一致。升级时替换旧名称。</p>

<ul>

<li>读取:ReadTag、ReadCalculatedTag、ReadTagBoolean/Integer/Double/String/Json/DateTime、ReadTagQuality、ReadTagQualityGood。</li>

<li>写入:WriteTag、WriteTagBoolean/Integer/Double/String/Json/DateTime、WriteTagBit。</li>

<li>读取同步返回当前快照;写入使用 await 并检查实际结果。封装业务方法时,应保留质量检查、权限校验和命令结果处理。</li>

</ul>

<div class="manual-code" data-code-language="javascript"><div class="manual-code-toolbar"><span>JavaScript</span><span class="manual-code-tools"><small>JavaScript</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: JavaScript">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> api</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> await</span><span style="color:#005CC5"> HQVISU_CUSTOM_PAGE</span><span style="color:#24292E">.</span><span style="color:#6F42C1">ready</span><span style="color:#24292E">();</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> value</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> api.</span><span style="color:#6F42C1">ReadTagInteger</span><span style="color:#24292E">(</span><span style="color:#032F62">"&#x3C;device-code>.&#x3C;tag-code>"</span><span style="color:#24292E">, </span><span style="color:#005CC5">null</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#6A737D">// PPT script reads the same name without the api prefix:</span></span>

<span class="line"><span style="color:#6A737D">// const value = ReadTagInteger("&#x3C;device-code>.&#x3C;tag-code>", null);</span></span></code></pre></div>

</article>

<article class="doc-card">

<h3 id="custom-page-sdk-item-27">错误处理和交付检查</h3>

<p>读取失败使用 fallback 与质量提示;初始化、写入、历史和报警用 await/try-catch。写入可能没有返回值,undefined 不是成功。记录 error.code、error.message 和存在的 commandId,不记录权限密钥。</p>

<ul>

<li>用管理员和普通用户各打开一次页面,确认权限边界符合预期。</li>

<li>检查一个未声明 Tag,确认页面显示拒绝或空态,不出现白屏。</li>

<li>断开或模拟 bad/stale 质量,确认页面不会把异常值当正常值展示。</li>

<li>验证历史查询时间范围、聚合口径、单位、小数位和图表说明。</li>

<li>验证写入动作有确认、失败提示,并按项目要求记录操作原因。</li>

<li>验证 <code>outcome_uncertain</code> / <code>pending_verification</code> 会进入阻断提示,且没有自动重试。</li>

<li>组件退出时取消自身 Tag、报警、语言和表格订阅。只有整页永久离开(pagehide 且 !event.persisted)才调用 api.destroy();BFCache 暂存与 Vue 卸载不销毁整个文档。销毁后不要复用订阅或未完成请求。</li>

<li>打开浏览器控制台,确认没有 SDK 初始化失败、404、JSON 解析错误或未捕获 Promise。</li>

<li>常见错误:INVALID_ARGUMENT(输入)、CAPABILITY_DENIED(页面范围/能力)、PERMISSION_DENIED(账号权限)、NOT_FOUND、TIMEOUT、UNAVAILABLE、RUNTIME_WRITE_OUTCOME_UNCERTAIN、RUNTIME_PAGE_DESTROYED。分清页面未就绪、服务离线和 Tag 质量,不显示一个笼统成功提示。</li>

</ul>

<div class="manual-code" data-code-language="javascript"><div class="manual-code-toolbar"><span>JavaScript</span><span class="manual-code-tools"><small>JavaScript</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: JavaScript">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#D73A49">try</span><span style="color:#24292E"> {</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> history</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> await</span><span style="color:#24292E"> api.history.</span><span style="color:#6F42C1">query</span><span style="color:#24292E">({</span></span>

<span class="line"><span style="color:#24292E"> tags: [</span><span style="color:#032F62">"&#x3C;device-code>.&#x3C;output-tag-code>"</span><span style="color:#24292E">],</span></span>

<span class="line"><span style="color:#24292E"> start,</span></span>

<span class="line"><span style="color:#24292E"> end,</span></span>

<span class="line"><span style="color:#24292E"> interval: </span><span style="color:#032F62">"1h"</span><span style="color:#24292E">,</span></span>

<span class="line"><span style="color:#24292E"> aggregate: </span><span style="color:#032F62">"sum"</span></span>

<span class="line"><span style="color:#24292E"> });</span></span>

<span class="line"><span style="color:#6F42C1"> renderTrend</span><span style="color:#24292E">(history.items </span><span style="color:#D73A49">||</span><span style="color:#24292E"> []);</span></span>

<span class="line"><span style="color:#24292E">} </span><span style="color:#D73A49">catch</span><span style="color:#24292E"> (err) {</span></span>

<span class="line"><span style="color:#6F42C1"> renderEmptyState</span><span style="color:#24292E">(</span><span style="color:#032F62">"历史查询失败:"</span><span style="color:#D73A49"> +</span><span style="color:#24292E"> err.message);</span></span>

<span class="line"><span style="color:#24292E">}</span></span></code></pre></div>

</article>

<article class="doc-card">

<h3 id="sdk-native-module">原生 HTML 模块:一个完整可复制页面</h3>

<p>保存为 equipment-overview.html,替换声明与脚本中的 Tag。mjs 仅导出 ready 和默认对象 {ready};ReadTagInteger 等方法从 await ready() 获取。页面自适应窗口并跟随 LANG。</p>

<details class="manual-code-disclosure"><summary>展开完整源代码</summary><div class="manual-code" data-code-language="html"><div class="manual-code-toolbar"><span>HTML</span><span class="manual-code-tools"><small>HTML</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: HTML">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#24292E">&#x3C;!</span><span style="color:#22863A">doctype</span><span style="color:#6F42C1"> html</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E">&#x3C;</span><span style="color:#22863A">html</span><span style="color:#6F42C1"> lang</span><span style="color:#24292E">=</span><span style="color:#032F62">"en"</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E">&#x3C;</span><span style="color:#22863A">head</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">meta</span><span style="color:#6F42C1"> charset</span><span style="color:#24292E">=</span><span style="color:#032F62">"utf-8"</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">meta</span><span style="color:#6F42C1"> name</span><span style="color:#24292E">=</span><span style="color:#032F62">"viewport"</span><span style="color:#6F42C1"> content</span><span style="color:#24292E">=</span><span style="color:#032F62">"width=device-width, initial-scale=1"</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">title</span><span style="color:#24292E">>Mingcedar equipment overview&#x3C;/</span><span style="color:#22863A">title</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">script</span><span style="color:#24292E">>window.</span><span style="color:#005CC5">HQVISU_PAGE_META</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> { layoutMode: </span><span style="color:#032F62">"native"</span><span style="color:#24292E"> };&#x3C;/</span><span style="color:#22863A">script</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">script</span><span style="color:#6F42C1"> type</span><span style="color:#24292E">=</span><span style="color:#032F62">"application/json"</span><span style="color:#6F42C1"> id</span><span style="color:#24292E">=</span><span style="color:#032F62">"hqvisu-custom-page"</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> {"readableTags":["&#x3C;device-code>.&#x3C;process-tag-code>"],"writableTags":[]}</span></span>

<span class="line"><span style="color:#24292E"> &#x3C;/</span><span style="color:#22863A">script</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E">&#x3C;/</span><span style="color:#22863A">head</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E">&#x3C;</span><span style="color:#22863A">body</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">h1</span><span style="color:#6F42C1"> id</span><span style="color:#24292E">=</span><span style="color:#032F62">"title"</span><span style="color:#24292E">>Equipment overview&#x3C;/</span><span style="color:#22863A">h1</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">output</span><span style="color:#6F42C1"> id</span><span style="color:#24292E">=</span><span style="color:#032F62">"value"</span><span style="color:#24292E">>—&#x3C;/</span><span style="color:#22863A">output</span><span style="color:#24292E">>&#x3C;</span><span style="color:#22863A">p</span><span style="color:#6F42C1"> id</span><span style="color:#24292E">=</span><span style="color:#032F62">"status"</span><span style="color:#6F42C1"> role</span><span style="color:#24292E">=</span><span style="color:#032F62">"status"</span><span style="color:#24292E">>&#x3C;/</span><span style="color:#22863A">p</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">script</span><span style="color:#6F42C1"> type</span><span style="color:#24292E">=</span><span style="color:#032F62">"module"</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#D73A49"> import</span><span style="color:#24292E"> { ready } </span><span style="color:#D73A49">from</span><span style="color:#032F62"> "/runtime/hqVisuDesigner/hq-custom-page.mjs"</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> tag</span><span style="color:#D73A49"> =</span><span style="color:#032F62"> "&#x3C;device-code>.&#x3C;process-tag-code>"</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#6F42C1"> element</span><span style="color:#D73A49"> =</span><span style="color:#E36209"> id</span><span style="color:#D73A49"> =></span><span style="color:#24292E"> document.</span><span style="color:#6F42C1">getElementById</span><span style="color:#24292E">(id);</span></span>

<span class="line"><span style="color:#D73A49"> let</span><span style="color:#24292E"> page, stopValue, stopLanguage, disposed </span><span style="color:#D73A49">=</span><span style="color:#005CC5"> false</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#24292E"> window.</span><span style="color:#6F42C1">addEventListener</span><span style="color:#24292E">(</span><span style="color:#032F62">"pagehide"</span><span style="color:#24292E">, </span><span style="color:#E36209">event</span><span style="color:#D73A49"> =></span><span style="color:#24292E"> {</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (event.persisted) </span><span style="color:#D73A49">return</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#24292E"> disposed </span><span style="color:#D73A49">=</span><span style="color:#005CC5"> true</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#6F42C1"> stopValue</span><span style="color:#24292E">?.(); </span><span style="color:#6F42C1">stopLanguage</span><span style="color:#24292E">?.(); page?.</span><span style="color:#6F42C1">destroy</span><span style="color:#24292E">();</span></span>

<span class="line"><span style="color:#24292E"> });</span></span>

<span class="line"><span style="color:#D73A49"> try</span><span style="color:#24292E"> {</span></span>

<span class="line"><span style="color:#24292E"> page </span><span style="color:#D73A49">=</span><span style="color:#D73A49"> await</span><span style="color:#6F42C1"> ready</span><span style="color:#24292E">({ tags: [tag] });</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (</span><span style="color:#D73A49">!</span><span style="color:#24292E">disposed) {</span></span>

<span class="line"><span style="color:#D73A49"> function</span><span style="color:#6F42C1"> render</span><span style="color:#24292E">() {</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> zh</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> page.language.</span><span style="color:#6F42C1">current</span><span style="color:#24292E">().</span><span style="color:#6F42C1">startsWith</span><span style="color:#24292E">(</span><span style="color:#032F62">"zh"</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#24292E"> document.documentElement.lang </span><span style="color:#D73A49">=</span><span style="color:#24292E"> page.language.</span><span style="color:#6F42C1">current</span><span style="color:#24292E">();</span></span>

<span class="line"><span style="color:#6F42C1"> element</span><span style="color:#24292E">(</span><span style="color:#032F62">"title"</span><span style="color:#24292E">).textContent </span><span style="color:#D73A49">=</span><span style="color:#24292E"> zh </span><span style="color:#D73A49">?</span><span style="color:#032F62"> "设备总览"</span><span style="color:#D73A49"> :</span><span style="color:#032F62"> "Equipment overview"</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> quality</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> page.</span><span style="color:#6F42C1">ReadTagQuality</span><span style="color:#24292E">(tag);</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> value</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> page.</span><span style="color:#6F42C1">ReadTagDouble</span><span style="color:#24292E">(tag, </span><span style="color:#005CC5">null</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#6F42C1"> element</span><span style="color:#24292E">(</span><span style="color:#032F62">"value"</span><span style="color:#24292E">).textContent </span><span style="color:#D73A49">=</span><span style="color:#24292E"> quality </span><span style="color:#D73A49">===</span><span style="color:#032F62"> "good"</span><span style="color:#D73A49"> &#x26;&#x26;</span><span style="color:#24292E"> value </span><span style="color:#D73A49">!==</span><span style="color:#005CC5"> null</span><span style="color:#D73A49"> ?</span><span style="color:#6F42C1"> String</span><span style="color:#24292E">(value) </span><span style="color:#D73A49">:</span><span style="color:#032F62"> "—"</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#6F42C1"> element</span><span style="color:#24292E">(</span><span style="color:#032F62">"status"</span><span style="color:#24292E">).textContent </span><span style="color:#D73A49">=</span><span style="color:#24292E"> (zh </span><span style="color:#D73A49">?</span><span style="color:#032F62"> "数据质量:"</span><span style="color:#D73A49"> :</span><span style="color:#032F62"> "Quality: "</span><span style="color:#24292E">) </span><span style="color:#D73A49">+</span><span style="color:#24292E"> quality;</span></span>

<span class="line"><span style="color:#24292E"> }</span></span>

<span class="line"><span style="color:#6F42C1"> render</span><span style="color:#24292E">();</span></span>

<span class="line"><span style="color:#24292E"> stopValue </span><span style="color:#D73A49">=</span><span style="color:#24292E"> page.</span><span style="color:#6F42C1">subscribe</span><span style="color:#24292E">([tag], render);</span></span>

<span class="line"><span style="color:#24292E"> stopLanguage </span><span style="color:#D73A49">=</span><span style="color:#24292E"> page.language.</span><span style="color:#6F42C1">onChange</span><span style="color:#24292E">(render);</span></span>

<span class="line"><span style="color:#24292E"> }</span></span>

<span class="line"><span style="color:#24292E"> } </span><span style="color:#D73A49">catch</span><span style="color:#24292E"> (error) {</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (</span><span style="color:#D73A49">!</span><span style="color:#24292E">disposed) </span><span style="color:#6F42C1">element</span><span style="color:#24292E">(</span><span style="color:#032F62">"status"</span><span style="color:#24292E">).textContent </span><span style="color:#D73A49">=</span><span style="color:#24292E"> (error.code </span><span style="color:#D73A49">?</span><span style="color:#24292E"> error.code </span><span style="color:#D73A49">+</span><span style="color:#032F62"> ": "</span><span style="color:#D73A49"> :</span><span style="color:#032F62"> ""</span><span style="color:#24292E">) </span><span style="color:#D73A49">+</span><span style="color:#24292E"> error.message;</span></span>

<span class="line"><span style="color:#24292E"> }</span></span>

<span class="line"><span style="color:#24292E"> &#x3C;/</span><span style="color:#22863A">script</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E">&#x3C;/</span><span style="color:#22863A">body</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E">&#x3C;/</span><span style="color:#22863A">html</span><span style="color:#24292E">></span></span></code></pre></div></details>

</article>

<article class="doc-card">

<h3 id="sdk-vue-entry">Vue 接入 1:在入口 HTML 声明范围</h3>

<p>在现有 Vue 3/Vite 项目中使用以下 index.html;Vue 本身由项目依赖打包,无需 CDN。readableTags 和 writableTags 中的编码必须与组件 prop 一致。SSR 环境只在 onMounted 后调用 ready,否则报 RUNTIME_PAGE_BROWSER_REQUIRED。</p>

<div class="manual-code" data-code-language="html"><div class="manual-code-toolbar"><span>HTML</span><span class="manual-code-tools"><small>HTML</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: HTML">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#6A737D">&#x3C;!-- Vite project root: index.html --></span></span>

<span class="line"><span style="color:#24292E">&#x3C;!</span><span style="color:#22863A">doctype</span><span style="color:#6F42C1"> html</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E">&#x3C;</span><span style="color:#22863A">html</span><span style="color:#6F42C1"> lang</span><span style="color:#24292E">=</span><span style="color:#032F62">"en"</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E">&#x3C;</span><span style="color:#22863A">head</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">meta</span><span style="color:#6F42C1"> charset</span><span style="color:#24292E">=</span><span style="color:#032F62">"utf-8"</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">meta</span><span style="color:#6F42C1"> name</span><span style="color:#24292E">=</span><span style="color:#032F62">"viewport"</span><span style="color:#6F42C1"> content</span><span style="color:#24292E">=</span><span style="color:#032F62">"width=device-width, initial-scale=1"</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">title</span><span style="color:#24292E">>Mingcedar equipment monitor&#x3C;/</span><span style="color:#22863A">title</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">script</span><span style="color:#24292E">>window.</span><span style="color:#005CC5">HQVISU_PAGE_META</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> { layoutMode: </span><span style="color:#032F62">"native"</span><span style="color:#24292E"> };&#x3C;/</span><span style="color:#22863A">script</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">script</span><span style="color:#6F42C1"> type</span><span style="color:#24292E">=</span><span style="color:#032F62">"application/json"</span><span style="color:#6F42C1"> id</span><span style="color:#24292E">=</span><span style="color:#032F62">"hqvisu-custom-page"</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> {"readableTags":["&#x3C;device-code>.&#x3C;setpoint-tag-code>"],</span></span>

<span class="line"><span style="color:#24292E"> "writableTags":["&#x3C;device-code>.&#x3C;setpoint-tag-code>"]}</span></span>

<span class="line"><span style="color:#24292E"> &#x3C;/</span><span style="color:#22863A">script</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E">&#x3C;/</span><span style="color:#22863A">head</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E">&#x3C;</span><span style="color:#22863A">body</span><span style="color:#24292E">>&#x3C;</span><span style="color:#22863A">div</span><span style="color:#6F42C1"> id</span><span style="color:#24292E">=</span><span style="color:#032F62">"app"</span><span style="color:#24292E">>&#x3C;/</span><span style="color:#22863A">div</span><span style="color:#24292E">>&#x3C;</span><span style="color:#22863A">script</span><span style="color:#6F42C1"> type</span><span style="color:#24292E">=</span><span style="color:#032F62">"module"</span><span style="color:#6F42C1"> src</span><span style="color:#24292E">=</span><span style="color:#032F62">"/src/main.js"</span><span style="color:#24292E">>&#x3C;/</span><span style="color:#22863A">script</span><span style="color:#24292E">>&#x3C;/</span><span style="color:#22863A">body</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E">&#x3C;/</span><span style="color:#22863A">html</span><span style="color:#24292E">></span></span></code></pre></div>

</article>

<article class="doc-card">

<h3 id="sdk-vue-main">Vue 接入 2:挂载组件</h3>

<p>保存为 src/main.js。把下面 prop、入口声明和组件绑定统一为已批准的数值设定 Tag。只读项目去掉可写声明与操作区域。</p>

<div class="manual-code" data-code-language="javascript"><div class="manual-code-toolbar"><span>JavaScript</span><span class="manual-code-tools"><small>JavaScript</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: JavaScript">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#6A737D">// src/main.js — use the Vue dependency installed in your project.</span></span>

<span class="line"><span style="color:#D73A49">import</span><span style="color:#24292E"> { createApp } </span><span style="color:#D73A49">from</span><span style="color:#032F62"> "vue"</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#D73A49">import</span><span style="color:#24292E"> EquipmentPanel </span><span style="color:#D73A49">from</span><span style="color:#032F62"> "./EquipmentPanel.vue"</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#6F42C1">createApp</span><span style="color:#24292E">(EquipmentPanel, {</span></span>

<span class="line"><span style="color:#24292E"> tagName: </span><span style="color:#032F62">"&#x3C;device-code>.&#x3C;setpoint-tag-code>"</span></span>

<span class="line"><span style="color:#24292E">}).</span><span style="color:#6F42C1">mount</span><span style="color:#24292E">(</span><span style="color:#032F62">"#app"</span><span style="color:#24292E">);</span></span></code></pre></div>

</article>

<article class="doc-card">

<h3 id="sdk-vue-component">Vue 接入 3:读取、单次写入、LANG 与卸载</h3>

<p>保存为 src/EquipmentPanel.vue。卸载时只取消组件订阅;若等待期间已卸载,停止注册。本例提交后保持按钮禁用,刷新或重新挂载会重置状态。再次写入前核对上次命令和正常采集值;需跨刷新阻断时,保存并恢复待核对状态。</p>

<details class="manual-code-disclosure"><summary>展开完整源代码</summary><div class="manual-code" data-code-language="vue"><div class="manual-code-toolbar"><span>Vue</span><span class="manual-code-tools"><small>Vue</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: Vue">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#24292E">&#x3C;</span><span style="color:#22863A">script</span><span style="color:#6F42C1"> setup</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#D73A49">import</span><span style="color:#24292E"> { onMounted, onUnmounted, ref } </span><span style="color:#D73A49">from</span><span style="color:#032F62"> "vue"</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> props</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> defineProps</span><span style="color:#24292E">({ tagName: { type: String, required: </span><span style="color:#005CC5">true</span><span style="color:#24292E"> } });</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> tag</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> props.tagName; </span><span style="color:#6A737D">// Remount with a new key when changing this Tag.</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> value</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> ref</span><span style="color:#24292E">(</span><span style="color:#005CC5">null</span><span style="color:#24292E">), </span><span style="color:#005CC5">quality</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> ref</span><span style="color:#24292E">(</span><span style="color:#032F62">"bad"</span><span style="color:#24292E">), </span><span style="color:#005CC5">locale</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> ref</span><span style="color:#24292E">(</span><span style="color:#032F62">"en-US"</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> target</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> ref</span><span style="color:#24292E">(</span><span style="color:#032F62">""</span><span style="color:#24292E">), </span><span style="color:#005CC5">message</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> ref</span><span style="color:#24292E">(</span><span style="color:#032F62">""</span><span style="color:#24292E">), </span><span style="color:#005CC5">canWrite</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> ref</span><span style="color:#24292E">(</span><span style="color:#005CC5">false</span><span style="color:#24292E">), </span><span style="color:#005CC5">submitted</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> ref</span><span style="color:#24292E">(</span><span style="color:#005CC5">false</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#D73A49">let</span><span style="color:#24292E"> page, stopValue, stopLanguage, disposed </span><span style="color:#D73A49">=</span><span style="color:#005CC5"> false</span><span style="color:#24292E">, allowed </span><span style="color:#D73A49">=</span><span style="color:#005CC5"> false</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#D73A49">const</span><span style="color:#6F42C1"> text</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> (</span><span style="color:#E36209">zh</span><span style="color:#24292E">, </span><span style="color:#E36209">en</span><span style="color:#24292E">) </span><span style="color:#D73A49">=></span><span style="color:#24292E"> locale.value.</span><span style="color:#6F42C1">startsWith</span><span style="color:#24292E">(</span><span style="color:#032F62">"zh"</span><span style="color:#24292E">) </span><span style="color:#D73A49">?</span><span style="color:#24292E"> zh </span><span style="color:#D73A49">:</span><span style="color:#24292E"> en;</span></span>

<span class="line"><span style="color:#D73A49">function</span><span style="color:#6F42C1"> render</span><span style="color:#24292E">() {</span></span>

<span class="line"><span style="color:#24292E"> value.value </span><span style="color:#D73A49">=</span><span style="color:#24292E"> page.</span><span style="color:#6F42C1">ReadTagDouble</span><span style="color:#24292E">(tag, </span><span style="color:#005CC5">null</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#24292E"> quality.value </span><span style="color:#D73A49">=</span><span style="color:#24292E"> page.</span><span style="color:#6F42C1">ReadTagQuality</span><span style="color:#24292E">(tag);</span></span>

<span class="line"><span style="color:#24292E"> canWrite.value </span><span style="color:#D73A49">=</span><span style="color:#24292E"> allowed </span><span style="color:#D73A49">&#x26;&#x26;</span><span style="color:#24292E"> quality.value </span><span style="color:#D73A49">===</span><span style="color:#032F62"> "good"</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#24292E">}</span></span>

<span class="line"><span style="color:#6F42C1">onMounted</span><span style="color:#24292E">(</span><span style="color:#D73A49">async</span><span style="color:#24292E"> () </span><span style="color:#D73A49">=></span><span style="color:#24292E"> {</span></span>

<span class="line"><span style="color:#D73A49"> try</span><span style="color:#24292E"> {</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> sdkUrl</span><span style="color:#D73A49"> =</span><span style="color:#032F62"> "/runtime/hqVisuDesigner/hq-custom-page.mjs"</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#24292E"> { </span><span style="color:#005CC5">ready</span><span style="color:#24292E"> } </span><span style="color:#D73A49">=</span><span style="color:#D73A49"> await</span><span style="color:#6F42C1"> import</span><span style="color:#24292E">(</span><span style="color:#6A737D">/* @vite-ignore */</span><span style="color:#24292E"> sdkUrl);</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (disposed) </span><span style="color:#D73A49">return</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> api</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> await</span><span style="color:#6F42C1"> ready</span><span style="color:#24292E">({ tags: [tag] });</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (disposed) </span><span style="color:#D73A49">return</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#24292E"> page </span><span style="color:#D73A49">=</span><span style="color:#24292E"> api;</span></span>

<span class="line"><span style="color:#24292E"> locale.value </span><span style="color:#D73A49">=</span><span style="color:#24292E"> page.language.</span><span style="color:#6F42C1">current</span><span style="color:#24292E">();</span></span>

<span class="line"><span style="color:#24292E"> stopLanguage </span><span style="color:#D73A49">=</span><span style="color:#24292E"> page.language.</span><span style="color:#6F42C1">onChange</span><span style="color:#24292E">(</span><span style="color:#E36209">next</span><span style="color:#D73A49"> =></span><span style="color:#24292E"> { </span><span style="color:#D73A49">if</span><span style="color:#24292E"> (</span><span style="color:#D73A49">!</span><span style="color:#24292E">disposed) locale.value </span><span style="color:#D73A49">=</span><span style="color:#24292E"> next; });</span></span>

<span class="line"><span style="color:#6F42C1"> render</span><span style="color:#24292E">();</span></span>

<span class="line"><span style="color:#24292E"> stopValue </span><span style="color:#D73A49">=</span><span style="color:#24292E"> page.</span><span style="color:#6F42C1">subscribe</span><span style="color:#24292E">([tag], render);</span></span>

<span class="line"><span style="color:#24292E"> allowed </span><span style="color:#D73A49">=</span><span style="color:#24292E"> page.</span><span style="color:#6F42C1">supports</span><span style="color:#24292E">(</span><span style="color:#032F62">"tag.write"</span><span style="color:#24292E">) </span><span style="color:#D73A49">&#x26;&#x26;</span><span style="color:#D73A49"> await</span><span style="color:#24292E"> page.auth.</span><span style="color:#6F42C1">hasPermission</span><span style="color:#24292E">(</span><span style="color:#032F62">"tag:write"</span><span style="color:#24292E">) </span><span style="color:#D73A49">===</span><span style="color:#005CC5"> true</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (</span><span style="color:#D73A49">!</span><span style="color:#24292E">disposed) </span><span style="color:#6F42C1">render</span><span style="color:#24292E">();</span></span>

<span class="line"><span style="color:#24292E"> } </span><span style="color:#D73A49">catch</span><span style="color:#24292E"> (error) {</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (</span><span style="color:#D73A49">!</span><span style="color:#24292E">disposed) message.value </span><span style="color:#D73A49">=</span><span style="color:#24292E"> error.message;</span></span>

<span class="line"><span style="color:#24292E"> }</span></span>

<span class="line"><span style="color:#24292E">});</span></span>

<span class="line"><span style="color:#6F42C1">onUnmounted</span><span style="color:#24292E">(() </span><span style="color:#D73A49">=></span><span style="color:#24292E"> {</span></span>

<span class="line"><span style="color:#24292E"> disposed </span><span style="color:#D73A49">=</span><span style="color:#005CC5"> true</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#6F42C1"> stopValue</span><span style="color:#24292E">?.(); </span><span style="color:#6F42C1">stopLanguage</span><span style="color:#24292E">?.();</span></span>

<span class="line"><span style="color:#6A737D"> // Do not destroy the shared document SDK on component unmount.</span></span>

<span class="line"><span style="color:#24292E">});</span></span>

<span class="line"><span style="color:#D73A49">async</span><span style="color:#D73A49"> function</span><span style="color:#6F42C1"> write</span><span style="color:#24292E">() {</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (submitted.value </span><span style="color:#D73A49">||</span><span style="color:#D73A49"> !</span><span style="color:#24292E">canWrite.value) </span><span style="color:#D73A49">return</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> input</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> String</span><span style="color:#24292E">(target.value).</span><span style="color:#6F42C1">trim</span><span style="color:#24292E">(), </span><span style="color:#005CC5">next</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> Number</span><span style="color:#24292E">(input);</span></span>

<span class="line"><span style="color:#6A737D"> // Replace 0–100 with the approved engineering range for this Tag.</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (</span><span style="color:#D73A49">!</span><span style="color:#24292E">input </span><span style="color:#D73A49">||</span><span style="color:#D73A49"> !</span><span style="color:#24292E">Number.</span><span style="color:#6F42C1">isFinite</span><span style="color:#24292E">(next) </span><span style="color:#D73A49">||</span><span style="color:#24292E"> next </span><span style="color:#D73A49">&#x3C;</span><span style="color:#005CC5"> 0</span><span style="color:#D73A49"> ||</span><span style="color:#24292E"> next </span><span style="color:#D73A49">></span><span style="color:#005CC5"> 100</span><span style="color:#24292E">) {</span></span>

<span class="line"><span style="color:#24292E"> message.value </span><span style="color:#D73A49">=</span><span style="color:#6F42C1"> text</span><span style="color:#24292E">(</span><span style="color:#032F62">"请输入 0–100 的数值"</span><span style="color:#24292E">, </span><span style="color:#032F62">"Enter a number from 0 to 100"</span><span style="color:#24292E">); </span><span style="color:#D73A49">return</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#24292E"> }</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (</span><span style="color:#D73A49">!</span><span style="color:#24292E">page.</span><span style="color:#6F42C1">ReadTagQualityGood</span><span style="color:#24292E">(tag)) </span><span style="color:#D73A49">return</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (</span><span style="color:#D73A49">!</span><span style="color:#24292E">window.</span><span style="color:#6F42C1">confirm</span><span style="color:#24292E">(tag </span><span style="color:#D73A49">+</span><span style="color:#032F62"> ": "</span><span style="color:#D73A49"> +</span><span style="color:#24292E"> next)) </span><span style="color:#D73A49">return</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#24292E"> submitted.value </span><span style="color:#D73A49">=</span><span style="color:#005CC5"> true</span><span style="color:#24292E">;</span></span>

<span class="line"><span style="color:#D73A49"> try</span><span style="color:#24292E"> {</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> result</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> await</span><span style="color:#24292E"> page.</span><span style="color:#6F42C1">WriteTagDouble</span><span style="color:#24292E">(tag, next, { reason: </span><span style="color:#032F62">"Operator setpoint update"</span><span style="color:#24292E"> });</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (</span><span style="color:#D73A49">!</span><span style="color:#24292E">disposed) message.value </span><span style="color:#D73A49">=</span><span style="color:#6F42C1"> text</span><span style="color:#24292E">(</span><span style="color:#032F62">"命令回执(需核对正常采集值):"</span><span style="color:#24292E">, </span><span style="color:#032F62">"Command receipt (verify normal collected value): "</span><span style="color:#24292E">)</span></span>

<span class="line"><span style="color:#D73A49"> +</span><span style="color:#005CC5"> JSON</span><span style="color:#24292E">.</span><span style="color:#6F42C1">stringify</span><span style="color:#24292E">(result </span><span style="color:#D73A49">??</span><span style="color:#005CC5"> null</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#24292E"> } </span><span style="color:#D73A49">catch</span><span style="color:#24292E"> (error) {</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (</span><span style="color:#D73A49">!</span><span style="color:#24292E">disposed) message.value </span><span style="color:#D73A49">=</span><span style="color:#6F42C1"> text</span><span style="color:#24292E">(</span><span style="color:#032F62">"结果未确认,核对后再操作:"</span><span style="color:#24292E">, </span><span style="color:#032F62">"Result unconfirmed; reconcile before another operation: "</span><span style="color:#24292E">) </span><span style="color:#D73A49">+</span><span style="color:#24292E"> error.message;</span></span>

<span class="line"><span style="color:#24292E"> }</span></span>

<span class="line"><span style="color:#6A737D"> // Keep submission blocked until the operator reconciles command and collected state.</span></span>

<span class="line"><span style="color:#24292E">}</span></span>

<span class="line"><span style="color:#24292E">&#x3C;/</span><span style="color:#22863A">script</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E">&#x3C;</span><span style="color:#22863A">template</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">section</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">h1</span><span style="color:#24292E">>{{ text("设备设定值", "Equipment setpoint") }}&#x3C;/</span><span style="color:#22863A">h1</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">p</span><span style="color:#24292E">>{{ tag }}: {{ quality === "good" ? value : "—" }} · {{ quality }}&#x3C;/</span><span style="color:#22863A">p</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">label</span><span style="color:#24292E">>{{ text("目标值(0–100)", "Target (0–100)") }} &#x3C;</span><span style="color:#22863A">input</span><span style="color:#6F42C1"> v-model</span><span style="color:#24292E">=</span><span style="color:#032F62">"target"</span><span style="color:#6F42C1"> type</span><span style="color:#24292E">=</span><span style="color:#032F62">"number"</span><span style="color:#6F42C1"> step</span><span style="color:#24292E">=</span><span style="color:#032F62">"0.1"</span><span style="color:#24292E">>&#x3C;/</span><span style="color:#22863A">label</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">button</span><span style="color:#6F42C1"> :disabled</span><span style="color:#24292E">=</span><span style="color:#032F62">"!canWrite || submitted"</span><span style="color:#6F42C1"> @click</span><span style="color:#24292E">=</span><span style="color:#032F62">"write"</span><span style="color:#24292E">>{{ text("写入一次", "Write once") }}&#x3C;/</span><span style="color:#22863A">button</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;</span><span style="color:#22863A">pre</span><span style="color:#6F42C1"> role</span><span style="color:#24292E">=</span><span style="color:#032F62">"status"</span><span style="color:#24292E">>{{ message }}&#x3C;/</span><span style="color:#22863A">pre</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E"> &#x3C;/</span><span style="color:#22863A">section</span><span style="color:#24292E">></span></span>

<span class="line"><span style="color:#24292E">&#x3C;/</span><span style="color:#22863A">template</span><span style="color:#24292E">></span></span></code></pre></div></details>

</article>

<article class="doc-card">

<h3 id="sdk-vue-deploy">Vue 接入 4:构建和发布目录</h3>

<p>在 Vite 配置中保留现有 Vue 插件,设置 base: &quot;./&quot;,让构建产物可放进 assets 的任意子目录。页面 SDK 使用 /runtime/ 绝对服务路径,不跟随资源相对基址。</p>

<ol>

<li>运行项目的 npm run build,检查 dist/index.html 仍含 hqvisu-custom-page 静态 JSON 声明。</li>

<li>将 dist 的内容按原层级上传到“画面文件”同一个目标目录,包含 index.html 与构建 assets/*.js、CSS。不能只上传 .vue 源文件或只上传 index.html。</li>

<li>运行菜单选这个目录的 index.html,检查 SDK 就绪、Tag GOOD、LANG 切换、组件离开再进入及真实操作结果。</li>

</ol>

<div class="manual-code" data-code-language="javascript"><div class="manual-code-toolbar"><span>JavaScript</span><span class="manual-code-tools"><small>JavaScript</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: JavaScript">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#6A737D">// In the existing vite.config.js:</span></span>

<span class="line"><span style="color:#D73A49">export</span><span style="color:#D73A49"> default</span><span style="color:#6F42C1"> defineConfig</span><span style="color:#24292E">({ plugins: [</span><span style="color:#6F42C1">vue</span><span style="color:#24292E">()], base: </span><span style="color:#032F62">"./"</span><span style="color:#24292E"> });</span></span></code></pre></div>

</article>

<article class="doc-card">

<h3 id="sdk-calendar-values">日期与大整数:避免悄悄改变数据</h3>

<p>DATE_AND_TIME、DTL、LDT 元数据表示 PLC 无时区日历。ReadTagDateTime 返回以 UTC 字段承载该日历的 Date,不是真实 UTC 时刻;用 getUTC* 读取字段。WriteTagDateTime(Date) 写回相同 UTC 字段、不带 Z。其它时刻型 Date 使用 ISO 时刻语义。</p>

<ul>

<li>例如 PLC 日历 08:30 在上海和其它时区都应保持 08:30;不要直接用本地 getters 或 toLocaleString 解释这个日历容器。</li>

<li>Date 仅保留毫秒;DTL/LDT 亚毫秒精度需保留原始文本。日期有效性、PLC 年份和编码范围仍由设备类型校验。</li>

<li>超过 JavaScript 安全整数范围的数值,用 ReadTag/ReadTagString 保留文本,并用 WriteTagInteger 的十进制文本或 BigInt 参数;不要先经过 Number。</li>

</ul>

<div class="manual-code" data-code-language="javascript"><div class="manual-code-toolbar"><span>JavaScript</span><span class="manual-code-tools"><small>JavaScript</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: JavaScript">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> date</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> api.</span><span style="color:#6F42C1">ReadTagDateTime</span><span style="color:#24292E">(</span><span style="color:#032F62">"&#x3C;device-code>.&#x3C;datetime-tag-code>"</span><span style="color:#24292E">, </span><span style="color:#005CC5">null</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#D73A49">if</span><span style="color:#24292E"> (date) {</span></span>

<span class="line"><span style="color:#D73A49"> const</span><span style="color:#005CC5"> fields</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> [date.</span><span style="color:#6F42C1">getUTCFullYear</span><span style="color:#24292E">(), date.</span><span style="color:#6F42C1">getUTCMonth</span><span style="color:#24292E">() </span><span style="color:#D73A49">+</span><span style="color:#005CC5"> 1</span><span style="color:#24292E">, date.</span><span style="color:#6F42C1">getUTCDate</span><span style="color:#24292E">(),</span></span>

<span class="line"><span style="color:#24292E"> date.</span><span style="color:#6F42C1">getUTCHours</span><span style="color:#24292E">(), date.</span><span style="color:#6F42C1">getUTCMinutes</span><span style="color:#24292E">(), date.</span><span style="color:#6F42C1">getUTCSeconds</span><span style="color:#24292E">()];</span></span>

<span class="line"><span style="color:#24292E"> document.</span><span style="color:#6F42C1">getElementById</span><span style="color:#24292E">(</span><span style="color:#032F62">"calendar"</span><span style="color:#24292E">).textContent </span><span style="color:#D73A49">=</span><span style="color:#24292E"> fields.</span><span style="color:#6F42C1">join</span><span style="color:#24292E">(</span><span style="color:#032F62">" / "</span><span style="color:#24292E">);</span></span>

<span class="line"><span style="color:#24292E">}</span></span></code></pre></div>

</article>

</div>

</div>