报表、图表与可视化¶
本章解决两类问题:一是用 统计图(
.chart) 把业务数据画成 ECharts 图表;二是给 报表 提供动态 SQL、存储过程、自定义数据源或整张报表内容。两者落点不同,脚本返回值契约也完全不同——一个返回 EChartsoption对象,一个返回 SQL 字符串 /DRDataSource/ DynamicReportsReport。混用就报错。
本章场景一览:
| 场景 | 落点 / Label | 返回值 |
|---|---|---|
| ECharts 图表脚本(return option) | CHART:SCRIPT |
ECharts option 对象 |
| 报表 SQL 数据源 | REPORT:DATASOURCE_SQL |
String(SQL 语句) |
| 报表存储过程 / 自定义数据源 | REPORT:DATASOURCE_PROCEDURE / REPORT:DATASOURCE_CUSTOM |
String(call 语句) / DRDataSource 对象 |
| 报表内容脚本(3D 柱状图等) | 报表内容脚本 | DynamicReports Report 对象 |
写之前先看一眼全书 GraalVM 差异;本章所有示例均为 GraalVM JS 合法写法。
ECharts 图表脚本:查库拼装 option 并 return¶
业务目标¶
把表单数据按维度聚合(如「班级 → 各科成绩」「订单状态 → 数量」),在前端统计图组件里以折线/柱/饼图呈现。脚本负责**查库 + 组装 ECharts option 对象并 return**——运行时拿到的返回值交给 ECharts 渲染,而不是把静态 JSON 文本写进 scripttext。
写在哪儿¶
统计图域 → 统计图设计器 → 基本属性 → 脚本("选择样例"按钮旁)→ 脚本编辑器;落盘到 .chart 文件的 scripttext 字段(CDATA 包裹),Label CHART:SCRIPT。
同一
.chart还有chartName字段(设计器"选择样例"时写入,样例类型键,如lineChart/barChart/pieChart),它**只是占位样例的标识**;真正决定图表形态的是脚本里series[].type(line/bar/pie)。两者不是一套字符串,不要互相套用。
触发时机与上下文¶
- 触发:打开统计图页 / 首页 Widget 渲染时执行一次脚本。
- 可用环境变量:
WebUser(当前登录用户)、CurrentDocument(如果挂在文档上下文)、参数表(查询表单通过commonFilterCondition注入)。 - 数据查询:可用
queryByDSName、findBySQL,或源素材中常见的DesignTimeServiceFactory.resolve(...).queryDataSourceSQL(...)(具名数据源查询,旧示例多用此路径)。
返回值契约¶
| 类型 | 含义 |
|---|---|
ECharts option 对象 |
必须 return;包含 title / tooltip / legend / xAxis / yAxis / series 等标准 ECharts 字段。前端拿到后直接交给 ECharts 实例 setOption。 |
**不要**只把一段静态 option JSON 当作脚本内容贴进去——平台不会自动
return;必须是(function () { ... return option; })()形式。
示例代码¶
查「班级人数登记表」,按姓名聚合语文/数学成绩,画双折线 + 最大/最小/平均标线:
(function () {
var nameList = [];
var chineseList = [];
var mathsList = [];
try {
var sql = "select * from tlk_班级人数登记表";
// 具名数据源查询(产品文档原写法,沿用 Packages 引用)
var service = Packages.cn.myapps.designtime.common.service.DesignTimeServiceFactory
.resolve("cn.myapps.designtime.datasource.service.DataSourceDesignTimeService");
var datas = service.queryDataSourceSQL("obpm2功能示例", sql, "HdPeBqYwJyFyjHkhZu3");
if (datas != null) {
for (var it = datas.iterator(); it.hasNext();) {
var row = it.next(); // java.util.Map
nameList.push(new String(row.get("item_姓名")));
chineseList.push(Number(row.get("item_语文")));
mathsList.push(Number(row.get("item_数学")));
}
}
} catch (e) {
// 查询失败时退化为空数据,前端渲染空图,不抛异常打断页面
}
var option = {
title: { text: "个人成绩对比图" },
tooltip: { trigger: "axis" },
legend: { data: ["语文成绩", "数学成绩"] },
xAxis: {
type: "category",
boundaryGap: false,
data: nameList
},
yAxis: {
type: "value",
axisLabel: { formatter: "{value}" }
},
series: [
{
name: "语文成绩",
type: "line",
data: chineseList,
markPoint: {
data: [
{ type: "max", name: "最大值" },
{ type: "min", name: "最小值" }
]
},
markLine: {
data: [{ type: "average", name: "平均值" }]
}
},
{
name: "数学成绩",
type: "line",
data: mathsList,
markPoint: {
data: [
{ type: "max", name: "最大值" },
{ type: "min", name: "最小值" }
]
},
markLine: {
data: [{ type: "average", name: "平均值" }]
}
}
]
};
return option;
})()
代码说明¶
- IIFE +
return option:所有图表脚本的硬契约。即使 option 完全静态,也要写成(function () { var option = {...}; return option; })()。 Packages.cn.myapps.designtime...:源素材沿用的具名数据源服务调用(详见iscript-guid/functions→ java-class-functions);简单场景可直接用queryByDSName(dsName, sql)或findBySQL(sql)替代。datas.iterator()/it.hasNext():返回值是java.util.List,遍历用迭代器或下标.get(i),长度用.size();**不要**写成datas.length(参见 GraalVM 差异 → Java List vs JS 数组)。row.get("item_姓名"):每行是java.util.Map,键为表单字段列名(业务列前缀ITEM_,平台默认小写)。Java 值包一层new String(...)/Number(...)转成 JS 原生类型,避免后续 ECharts 比较时踩 Java 字符串的坑。try/catch包查询:数据源异常时退化为空数组,保证图表渲染不阻塞整个页面。
变体与扩展¶
- 饼图:把
series[0].type改成"pie",data改为[{ name: "...", value: N }, ...],去掉xAxis/yAxis。常用于状态/分类占比统计。 - 柱状图 / 堆叠柱:
type: "bar",多系列同stack字段即堆叠。 - 查询表单参数:
.chart通过commonFilterCondition绑定查询表单字段;脚本里用getParameter("__FIELD_UUID")取值后再拼 SQL(参见iscript-guid/functions→ system-functions)。 - ** Widget 渲染**:首页 Widget 配置中选择某个
.chart时,复用同一份scripttext,无需重写。
常见坑¶
- 忘记
return:贴了一段静态 JSON 进scripttext,保存后图表空白。脚本必须以return option;结尾。 - 混淆
chartName与series[].type:chartName=lineChart(设计器样例键)不等于series[].type="line"(ECharts 类型字符串);两者各管各的,新建图表以脚本里的series[].type为准。 - 把 Java List 当 JS 数组:
queryByDSName等返回java.util.List,取长度用.size()、取元素用.get(i),不要写.length/[i](详见 GraalVM 差异)。 - 业务表列名:表名
TLK_+ 表单 name;列名ITEM_+ 字段名(数据库实际大小写以设计器生成为准)。 getDomainid()域过滤:跨域数据查询时记得加where domainid='" + getDomainid() + "',否则会读到其它租户的数据。
报表 SQL 数据源:按查询条件动态拼 SQL¶
业务目标¶
报表打开时根据当前文档(查询表单)里用户填的查询条件,动态拼出 SQL 交给报表引擎取数。固定 SQL 无法表达"条件为空则不加过滤"这类需求,脚本拼串才能灵活。
写在哪儿¶
报表域 → 报表设计器 → 基本 → SQL 数据源 → "数据源 SQL 脚本";Label REPORT:DATASOURCE_SQL。
触发时机与上下文¶
- 触发:报表打开、取数时执行。
- 可用环境变量:
WebUser、CurrentDocument(通常指绑定的查询表单文档)。
返回值契约¶
| 类型 | 含义 |
|---|---|
| String | 一条完整的 SQL 查询语句;报表引擎直接执行。 |
示例代码¶
按查询表单的三个条件(项目名/代号/阶段)模糊匹配,未填则不加该过滤:
(function () {
var doc = getCurrentDocument();
var ProjectName = doc.getItemValueAsString("S_ProjectName");
var ProjectCode = doc.getItemValueAsString("S_ProjectCode");
var ProjectPhase = doc.getItemValueAsString("S_ProjectPhase");
var sql = "select t.* from ("
+ " select domainid, domainid as id, item_selectplatform, item_platform,"
+ " item_projectname, item_projectcode, item_ProjectPhase,"
+ " item_ProductionPlace, count(*) as item_count"
+ " from tlk_sample_vehicle_plan s where 1 = 1"
+ " group by domainid, item_selectplatform, item_platform,"
+ " item_projectname, item_projectcode, item_projectphase,"
+ " item_ProductionPlace"
+ ") t where 1 = 1";
if (isNotNull(ProjectName)) {
sql += " and item_projectname like '%" + ProjectName + "%'";
}
if (isNotNull(ProjectCode)) {
sql += " and item_projectcode like '%" + ProjectCode + "%'";
}
if (isNotNull(ProjectPhase)) {
sql += " and item_ProjectPhase like '%" + ProjectPhase + "%'";
}
return sql;
})()
代码说明¶
getCurrentDocument().getItemValueAsString(...):从绑定的查询表单取字段值(iscript-guid/api→ doc、iscript-guid/functions→ doc-functions)。isNotNull(v):平台提供的非空判断,比v !== "" && v != null更简洁;为数字时不为 0,为字符串时长度大于 0(iscript-guid/functions→ string-functions)。- 基础 SQL 用
where 1 = 1起头:方便后续按条件and ...拼接,避免分支处理where/and关键字。
变体与扩展¶
- 限定域:跨域报表记得加
and domainid = '" + getDomainid() + "'。 - 限定当前用户:
and item_申请人 = '" + getWebUser().getId() + "'(iscript-guid/api→ curruser)。 - 排序/分页:分页参数从 URL 取:
var page = getParameter("_page");。
常见坑¶
- SQL 注入:直接字符串拼接用户输入有风险;内部报表可控场景可接受,对外暴露的报表建议先做白名单校验(如只允许字母数字汉字)。
- 字符串比较:判空请用
isNotNull(v)或v == null || v === "",禁止"".equals(v)(详见 GraalVM 差异)。 - 忘记
return:返回undefined报表取数为空。 - 列名大小写:业务列以设计器实际生成为准(通常
ITEM_+ 字段名,全大写或保留原大小写视数据库而定)。
报表存储过程 / 自定义数据源¶
两种属性同构、低频场景,合并速查。落点都在 报表设计器 → 基本 → SQL 数据源 下,二选一:
| 场景 | Label | 属性位置 | 返回值契约 |
|---|---|---|---|
| 存储过程数据源 | REPORT:DATASOURCE_PROCEDURE |
"存储过程数据源"脚本框 | String:调用存储过程的 SQL,如 call report_proc(...) |
| 自定义数据源 | REPORT:DATASOURCE_CUSTOM |
"自定义数据源"脚本框 | DRDataSource 对象:手工构造列与行的内存数据集 |
存储过程数据源示例¶
(function () {
// 报表引擎把整段字符串当作 SQL 直接执行;call 语法以目标数据库为准
return "call report_proc('" + getDomainid() + "')";
})()
自定义数据源示例(DRDataSource)¶
(function () {
// 系统自定义类(产品文档原写法,沿用 Packages 引用)
var DRDataSource = Packages.cn.myapps.runtime.report.model.DRDataSource;
var Double = Packages.java.lang.Double;
// 构造函数:第一个参数起为列名
var dataSource = new DRDataSource("A", "B", "C", "D", "E");
dataSource.add("chain", new Double(350), new Double(300), new Double(200), new Double(200));
dataSource.add("abel", new Double(300), new Double(500), new Double(200), new Double(600));
dataSource.add("quan", new Double(450), new Double(250), new Double(300), new Double(200));
return dataSource; // 返回 DRDataSource 实例,不是 SQL 字符串
})()
说明与坑¶
- 两种返回值不能混:存储过程返回 SQL 字符串,自定义数据源返回
DRDataSource对象;写错位置或类型,报表取数为空或抛类型异常。 DRDataSource构造参数:第一个参数起即列名;列数与add(...)的实参数必须一致,否则报表渲染列错位。- Java 数值类型:示例里的
new Double(...)是java.lang.Double,不是 JSNumber;报表引擎按 Java 类型读列,混用可能丢精度。 Packages.cn.myapps...:源素材沿用此引用方式,新建脚本若需迁移,可改为Java.type('java.lang.Double')(详见 GraalVM 差异 → Java.type);DRDataSource是平台类,仍建议保留Packages.cn.myapps.runtime.report.model.DRDataSource。
报表内容脚本:DynamicReports 构图(3D 柱状图等)¶
业务目标¶
整张报表的内容(标题、列、汇总图表)都用脚本生成——典型场景是报表底部带一张 3D 柱状图 或其它 DynamicReports 支持的图表类型。返回的不是 SQL、不是 ECharts option,而是一个 DynamicReports Report 对象,由报表引擎直接渲染。
写在哪儿¶
报表域 → 报表设计器 → 内容 → 脚本;落点为报表内容脚本(无 XXX:YYY Label 形式,属性即"报表内容脚本")。
触发时机与上下文¶
- 触发:报表打开时执行一次。
- 可用环境变量:
WebUser、CurrentDocument。
返回值契约¶
| 类型 | 含义 |
|---|---|
DynamicReports Report 对象 |
通过 DynamicReports.report()...setDataSource(...) 链式构造;引擎渲染为带表格 + 图表的报表。 |
示例代码¶
带 Bar3DChart 的报表:表格三列(Item / Quantity / Unit price)+ 汇总区 3D 柱状图:
(function () {
// 第三方报表库类(源素材沿用 Packages 引用)
var FontBuilder = Packages.net.sf.dynamicreports.report.builder.style.FontBuilder;
var DynamicReports = Packages.net.sf.dynamicreports.report.builder.DynamicReports;
var Templates = Packages.cn.myapps.report.examples.Templates;
var DRDataSource = Packages.net.sf.dynamicreports.report.datasource.DRDataSource;
var BigDecimal = Packages.java.math.BigDecimal;
var Integer = Packages.java.lang.Integer;
function createDataSource() {
var ds = new DRDataSource("item", "quantity", "unitprice");
ds.add("Tablet", new Integer(350), new BigDecimal(300));
ds.add("Laptop", new Integer(300), new BigDecimal(500));
ds.add("Smartphone", new Integer(450), new BigDecimal(250));
return ds;
}
var boldFont = DynamicReports.stl.fontArialBold().setFontSize(12);
var itemColumn = DynamicReports.col.column("Item", "item", DynamicReports.type.stringType());
var quantityColumn = DynamicReports.col.column("Quantity", "quantity", DynamicReports.type.integerType());
var unitPriceColumn = DynamicReports.col.column("Unit price","unitprice",DynamicReports.type.bigDecimalType());
return DynamicReports.report()
.setTemplate(Templates.reportTemplate)
.columns(itemColumn, quantityColumn, unitPriceColumn)
.title(Templates.createTitleComponent("Bar3DChart"))
.summary(
DynamicReports.cht.bar3DChart()
.setTitle("Bar 3D chart")
.setTitleFont(boldFont)
.setCategory(itemColumn)
.series(
DynamicReports.cht.serie(quantityColumn),
DynamicReports.cht.serie(unitPriceColumn)
)
.setCategoryAxisFormat(DynamicReports.cht.axisFormat().setLabel("Item"))
)
.pageFooter(Templates.footerComponent)
.setDataSource(createDataSource());
})()
代码说明¶
- DynamicReports / JasperReports:底层为
net.sf.dynamicreports与net.sf.jasperreports,平台封装在Packages.cn.myapps.report.*。脚本本质是 Java API 调用,链式构造Report。 DRDataSource(这里)vsDRDataSource(「报表存储过程 / 自定义数据源」):注意此处的DRDataSource来自net.sf.dynamicreports.report.datasource,与「报表存储过程 / 自定义数据源」的cn.myapps.runtime.report.model.DRDataSource是**两个不同包下的同名类**——构造参数与用法不同,不要混用。DynamicReports.col.column(...):定义列;第二参数是dataSource里的字段名,必须与DRDataSource构造时传入的列名一致。bar3DChart()/serie(...):在summary区放图表;可换lineChart()/pieChart()/stackedBarChart()等同类 API。
变体与扩展¶
- 换图表类型:
DynamicReports.cht.lineChart()/.pieChart()/.stackedBarChart()/.xyBlockChart()等;series 与 category 维度按图表类型调整。 - 多图表:
summary(...)接收可变参数,可叠多个图表。 - 样式:
DynamicReports.stl.fontArialBold()/.columnTitleStyle(...)调字体、边框、背景色。
常见坑¶
- 返回值类型不是 ECharts option:这里返回的是 DynamicReports
Report,**不要**和「ECharts 图表脚本:查库拼装 option 并 r」的CHART:SCRIPT(return ECharts option)混——两者落点、运行引擎、返回类型完全不同。 - 类引用:源素材使用
Packages.net.sf.dynamicreports.*与Packages.cn.myapps.report.*,**保留原写法**即可;不建议改成Java.type(...),因为部分内部类经Packages解析更稳。 new Integer(...)/new BigDecimal(...):Java 数值类型按列类型严格匹配;列声明integerType()给Integer、bigDecimalType()给BigDecimal,类型不一致报表可能直接报错或丢数据。- 脚本规模:DynamicReports 调用链较长,建议把
createDataSource()拆成具名函数,主体只保留链式report()调用,便于阅读。
本章速查¶
| 想做什么 | 落点 | 返回值 |
|---|---|---|
| 画 ECharts 图(折/柱/饼/堆叠…) | .chart → scripttext(CHART:SCRIPT) |
option 对象(须 return) |
| 报表按条件动态查数 | 报表 SQL 数据源脚本(REPORT:DATASOURCE_SQL) |
SQL 字符串 |
| 报表跑存储过程 | 报表存储过程脚本(REPORT:DATASOURCE_PROCEDURE) |
call ... 字符串 |
| 报表用内存数据集 | 报表自定义数据源(REPORT:DATASOURCE_CUSTOM) |
DRDataSource 对象 |
| 报表整张内容(含图表) | 报表内容脚本 | DynamicReports Report 对象 |
一句话区分:
.chartreturn option,报表数据源 return SQL/DRDataSource,报表内容 return Report——三套契约互不通用。