Web 开发中用 H5DW 替代 DataWindow
H5DW 是什么
H5DW(HTML5 DataWindow)是 PowerBuilder DataWindow 的 JavaScript 实现,由 DataStore(数据处理)和 DataWindow(界面呈现)两个核心类组成,DataWindow 继承 DataStore,所有 DataStore 方法都能用。它保留了数据窗口的概念模型——带区、行流、公式、过滤排序、编辑回写、打印——只是把定义载体从 .srd 换成了浏览器能直接吃的形式。
npm install h5dw
<script src="http://www.satrda.com/h5dw/satreport.js"></script>
<link href="http://www.satrda.com/h5dw/satreport.css" rel="stylesheet">
<div id="datawindow" style="width:500px;height:400px"></div>
<script>
let dw = new DataWindow('#datawindow');
</script>
不需要 Vue、不需要构建工具,纯 HTML 页面即可承载;也支持 ES Module / CommonJS / TypeScript。
DataWindow 报表可以直接加载
原样的数据窗口定义不用重写就能跑:
- 数据窗口原样导入:引擎内置 PB 语法解析器,把
.srd文本交给parseDW()就得到报表定义,赋给dw.dataObject即可渲染;ds.create("release 8; ...")同样接受语法字符串。 - 设计器里对应「导入数据窗口」入口:按 BOM / gb2312 读文件 →
window.parseDW→ 直接进画布编辑。 - 服务端侧两条路都通:用 SatServer 在服务端把数据窗口语法解析成 H5DW 报表格式返回,或者把数据窗口原样下发给前端、由 H5DW 自己解析;自实现服务端只需返回报表 JSON 或语法文本。
- 运行时改属性也沿用 PB 口径:
describe()/modify()用的是数据窗口属性串语法,dw.describe('colname.edit.style')、dw.describe('datawindow.table.sort')都能取到值;setSQLSelect()/getSQLSelect()兼容setsqlselect写法。
// 拿到 .srd 文本,直接装载
const res = await fetch('/h5dw/syntax?key=d_order');
dw.dataObject = parseDW(await res.text()); // 数据窗口语法 → 报表定义
await dw.retrieve();
界面可以一比一还原
同一份定义,画出来的东西是一致的:
- 网格 + 带区的行流模型(
header/detail/header.N/trailer.N/pageHeader/reportFooterN…),列宽、行高、合并区、冻结区、字体字号加粗、前后景色、边框线型、对齐与垂直对齐、format显示格式,全部按定义绘制,而不是"套一层表格 CSS 近似一下"。 - 浮动对象(图片、矩形/椭圆/线段/箭头、条码、二维码、图表、文本框)按锚点
(ri, ci, dx, dy)精确定位,锚在detail的对象随行扩展;文本框支持内容超行时的行级伸展,伸展后下方整体下移,与 PB 的 band 自适应语义一致。 - 屏幕、打印预览、实际打印、PDF、HTML、Excel 走同一套布局计算:分页断点、页眉页脚逐页重复、打印锚定页底的结果在各通道一致,导出 Excel 还能保留合并与原生文本框形状。
也就是说:老系统里那张凭证、那张处方笺在 PB 里长什么样,在浏览器里就是什么样,业务人员用类 Excel 的设计器改版式,会 Excel 就能改。
支持百万级数据
官方口径:100 万行数据流畅显示与编辑,支持百万级数据。做法是「计算全量、绘制局部」:
- 布局与绘制只处理可视区 滚动时重新计算可见行范围,非可视行不参与绘制;
- HTML 组件单元格与浮动对象采用虚拟创建:只为可视区域内的锚定行创建 DOM 实例,滚出可视区即销毁,DOM 数量不随行数线性增长;
- 支持虚拟滚动,支持100万行级数据;
- 公式结果缓存、分组求值缓存、行高缓存,避免滚动/重排时反复求值;
再加上多数据源(datasets)、区块循环(blocks)、子报表、增删查改、过滤排序、打印这些能力,一块画布就把「大数据网格 + 精细打印 + 自定义设计」三件事一起解决了。
主要语法对照
方法
| 目的 | PowerBuilder | H5DW |
|---|---|---|
| 装载定义 | dw.Object.DataObject = "d_order" |
dw.dataObject = 报表JSON / parseDW(srd文本) |
| 检索 | dw.Retrieve(ll_rc) |
await dw.retrieve(arg1, arg2) |
| 更新 | dw.Update() |
await dw.update() |
| 行数 | dw.RowCount() |
dw.rowCount() |
| 取值 | dw.GetItemString(1,"name") |
dw.getItem(1, 'name') |
| 赋值 | dw.SetItem(1,"name","x") |
dw.setItem(1, 'name', 'x') |
| 插/删行 | dw.InsertRow(0) / dw.DeleteRow(1) |
dw.insertRow(0) / dw.deleteRow(1) |
| 当前行 | dw.GetRow() / dw.SetRow(2) |
dw.getRow() / dw.setRow(2) |
| 过滤 | dw.SetFilter("…") + dw.Filter() |
dw.setFilter("name like '小%' and age > 18") + dw.filter() |
| 排序 | dw.SetSort("name A") + dw.Sort() |
dw.setSort("name A, price D") + dw.sort()(A 升 D 降) |
| 查找 | dw.Find("price>100", 1, 10) |
dw.find('price>100', 1, 10) |
| 求值 | dw.Evaluate("price*qty") |
dw.evaluate("price * quantity", 1)(对第 1 行) |
| 取属性 | dw.Describe("d.id.Color") |
dw.describe('id.color') |
| 改属性 | dw.Modify("d.id.Color='255'") |
dw.modify("…") |
| 改 SQL | dw.SetSQLSelect(s) |
dw.setSQLSelect(sql) / dw.getSQLSelect() |
| 共享数据 | ds2.ShareData(ds1) |
dw.shareData(other)(+ shareDataOff()) |
| 子数据窗口 | dw.GetChild("dw", ds) |
dw.getChild(name) |
| 状态标志 | dw.SetItemStatus(...) |
dw.setItemStatus(row, col, buffer, status) / getItemStatus(...) / resetUpdate() |
| 缓冲区行操作 | RowsCopy / RowsMove / RowsDiscard |
rowsCopy() / rowsMove() / rowsDiscard() |
| 选择行 | dw.SelectRow(1, TRUE) |
dw.selectRow(1, true) / isSelected(1) / getSelectedRows() |
| 提交变更 | dw.GetChanges(blb) |
dw.getChanges() / dw.setChanges(data) |
| 接受编辑文本 | dw.AcceptText() |
dw.acceptText() / dw.setText(value) |
| 清屏 | dw.Reset() |
dw.reset() |
| 打印 | dw.Print() |
dw.print({ paperSize: 'A4', orientation: 'portrait' }) |
行号一律从 1 开始,列可以用名称字符串,也可以用数字索引。
事件
| PowerBuilder | H5DW |
|---|---|
ItemChanged 事件 |
dw.on('ItemChanged', (evt, row, cell, text) => {}) |
Clicked |
dw.on('Clicked', (evt, row, cell) => {}) |
RowFocusChanged |
dw.on('RowFocusChanged', (evt, row) => {}) |
DoubleClick |
dw.on('DoubleClicked', ...) |
EDNEdited / 键事件 |
EditChanged / KeyDown / ButtonClicked / CellRender / DropDownSelected / FilterChanged |
| 事件写在 PB 脚本里 | 报表级脚本挂 dataObject.eventScript,自定义函数挂 dataObject.functions;on_EventName 命名自动注册 |
主动派发用 dw.fire('ItemChanged', ...),解绑用 dw.off(name, fn)。
公式与绑定
| 用途 | PowerBuilder | H5DW |
|---|---|---|
| 取字段 | t1.name(列表达式) |
#{name} |
| 拼接 | '客户:' + company_name |
#{'客户:' + CompanyName} |
| 运算 | price * qty |
#{price * qty} |
| 条件 | if(price>=100,'高价','低价') |
#{if(价格 >= 100, '高价', '低价')} |
| 分组汇总 | sum(amt for group 1) |
#{sum(amt for group 1)} |
| 计数/平均/极值 | count / avg / max / min |
#{count(城市 for group 3)}、#{avg(价格 for group 2)}、#{max(金额 for group 1)} |
| 全局汇总 | Sum(amt For All) |
#{sum(amt for all)} |
| 交叉表汇总 | — | #{crosstabsum('amount')},动态列标题 @字段名 |
| 多数据源字段 | 子数据窗口取数 | #{key1.name}(具名数据源加 key 前缀,主数据源不加前缀) |
表达式里字段名、if()、mid() 等函数与 PB 一致,dw.evaluate() 可对任意行单独求值。
更多功能
- 支持拖拽控件和Excel两种方式编辑,所见即所得
- 支持导入导出Excel模板
在线示例
一句话总结
概念是原来那套概念,定义可以原样导进来,界面按原样画出来,百万行也照刷照改——想给老数据窗口找个 Web 落点,H5DW 走的就是这条最短的路。

浙公网安备 33010602011771号