跳转至

报表、图表与可视化

本章解决两类问题:一是用 统计图(.chart 把业务数据画成 ECharts 图表;二是给 报表 提供动态 SQL、存储过程、自定义数据源或整张报表内容。两者落点不同,脚本返回值契约也完全不同——一个返回 ECharts option 对象,一个返回 SQL 字符串 / DRDataSource / DynamicReports Report。混用就报错。

本章场景一览:

场景 落点 / 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[].typeline/bar/pie)。两者不是一套字符串,不要互相套用。

触发时机与上下文

  • 触发:打开统计图页 / 首页 Widget 渲染时执行一次脚本。
  • 可用环境变量WebUser(当前登录用户)、CurrentDocument(如果挂在文档上下文)、参数表(查询表单通过 commonFilterCondition 注入)。
  • 数据查询:可用 queryByDSNamefindBySQL,或源素材中常见的 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; 结尾。
  • 混淆 chartNameseries[].typechartName=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

触发时机与上下文

  • 触发:报表打开、取数时执行。
  • 可用环境变量WebUserCurrentDocument(通常指绑定的查询表单文档)。

返回值契约

类型 含义
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;
})()

代码说明

变体与扩展

  • 限定域:跨域报表记得加 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,不是 JS Number;报表引擎按 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 形式,属性即"报表内容脚本")。

触发时机与上下文

  • 触发:报表打开时执行一次。
  • 可用环境变量WebUserCurrentDocument

返回值契约

类型 含义
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.dynamicreportsnet.sf.jasperreports,平台封装在 Packages.cn.myapps.report.*。脚本本质是 Java API 调用,链式构造 Report
  • DRDataSource(这里)vs DRDataSource(「报表存储过程 / 自定义数据源」):注意此处的 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()IntegerbigDecimalType()BigDecimal,类型不一致报表可能直接报错或丢数据。
  • 脚本规模:DynamicReports 调用链较长,建议把 createDataSource() 拆成具名函数,主体只保留链式 report() 调用,便于阅读。

本章速查

想做什么 落点 返回值
画 ECharts 图(折/柱/饼/堆叠…) .chartscripttextCHART:SCRIPT option 对象(须 return
报表按条件动态查数 报表 SQL 数据源脚本(REPORT:DATASOURCE_SQL SQL 字符串
报表跑存储过程 报表存储过程脚本(REPORT:DATASOURCE_PROCEDURE call ... 字符串
报表用内存数据集 报表自定义数据源(REPORT:DATASOURCE_CUSTOM DRDataSource 对象
报表整张内容(含图表) 报表内容脚本 DynamicReports Report 对象

一句话区分:.chart return option报表数据源 return SQL/DRDataSource报表内容 return Report——三套契约互不通用。