关键词:SpringReport、数据大屏、Excel 报表、双 Y 轴、下钻传参、免登录分享 适合读者:正在用或准备用 SpringReport 做报表/大屏的后端、数据、实施同学。
写在前面
最近用 SpringReport 给产线做了一套「可动率分析」:5 个大屏页面 + 5 个 Excel 导出模板,要求很硬--- ---
- 不允许改源代码;
- 不允许直接连数据库(一切走平台接口);
- 页面要能逐级下钻,最后跳到 MES 系统;
- Excel 要能正式打印导出发给客户。
两周下来,我对这套平台的判断可以压成一句话:
界面能做出"像样的画面",但要做"精确控制",必须落到组件 JSON;两者都改不了的,就得靠 URL 传参绕。
下面把踩过的坑、验证过的写法、以及平台目前的能力边界一次讲清,能帮你少走几天弯路。
一、先建立一张"能力边界表"
SpringReport 的交付物有三类:Excel 展示报表 (单元格)、数据大屏 (自由布局 + VChart)、数据集(SQL/API)。
上手之前建议先明确:某需求属于哪一层?
| 需求 | 界面可配 | 需要改组件 JSON | 只能改源码 |
|---|---|---|---|
| 换图表类型、颜色、图例、标签位置 | ✅ | ||
| 布局、层级、背景、边框 | ✅ | ||
| 数据集 + 参数(字符串/数值/日期/下拉) | ✅ | ||
| 图表下钻(带参跳转) | ✅ | ||
| 双 Y 轴 | ✅ | ||
| 轴刻度颜色、隐藏轴标题 | ✅ | ||
| 饼图标签引导线 / 防重叠 | ✅ | ||
| tooltip 配色、参考线 | ✅ | ||
| 下钻带业务字段名(对接外部系统) | ✅ + 数据集别名 | ||
| 页面内改日期 → 数据刷新 | ✅ | ||
| 下钻在当前页打开(不新标签) | ✅ | ||
| 文本链接带动态参数 | ✅ |
这张表决定了你后面 80% 的决策。先查表,再动手,能省掉大量试错。
二、坑 1:双 Y 轴,面板里真的没有
做柏拉图(柱状 + 累计占比折线)时,你会遇到:柱子数值几百,累计占比 0~100,折线被压成一条直线。
面板里能加轴、能改标题,但没有任何地方能把"某个系列"绑到"某根轴" 。
答案在 VChart 的 axes[].seriesIndex,只能改组件 JSON:
json
{
"axes": [
{ "orient": "bottom" },
{ "orient": "left", "seriesIndex": [0], "min": 0, "max": 250 },
{ "orient": "right", "seriesIndex": [1], "min": 0, "max": 100 }
]
}
两个细节:
seriesIndex:[0]是柱子,[1]是折线;右轴记得min:0,max:100,否则刻度会被数据带着跑;series[].axes是无效写法,别被某些文档带偏。
三、坑 2:坐标轴刻度一白一彩,改不了颜色
左右轴默认配色不同,放在深色大屏上很花。面板只能改字体大小/标题,颜色要写 JSON:
json
"axes": [{
"orient": "left",
"label": { "visible": true, "style": { "fill": "#FFFFFF", "fontSize": 13 } },
"title": { "visible": false }
}]
顺手把 title.visible 关掉,画面会干净很多。
四、坑 3:饼图标签挤成一坨
数据倾斜时(比如最大项占 95%,其余五项各占 1%),标签全堆在同一角度。
先开外置 + 引导线:
json
"label": {
"visible": true,
"position": "outside",
"line": { "visible": true, "style": { "lineWidth": 1, "stroke": "#9BB7E0" } },
"style": { "fontSize": 13, "fill": "#FFFFFF" }
},
"labelLayout": { "hideOverlap": true, "align": "labelLine" }
但要说实话 :hideOverlap 在"95% + 多个 1%"这种极端分布下压不住。工程上的正解是------别让饼图承担精度:标签只标大项,明细交给右侧表格。
五、坑 4:下钻能点,但参数带不过去
大屏下钻的机制很简单:
ini
url = drillLink + '&' + 数据行里的字段名 + '=' + 值;
关键点:参数只能取"数据集返回的列"。 你想让 URL 出现 lineCode=AS0020,数据集里就得有 lineCode 这一列。
于是我把业务字段做成别名列,一步到位:
sql
SELECT
DATE_FORMAT(r.work_day,'%Y-%m-%d') AS 工作日,
r.line_code AS 产线,
r.availability AS 可动率,
r.line_code AS lineCode, -- 下钻用
#{start_time} AS startTime, -- 下钻用
concat(#{start_time}, ' 08:30:00') AS beginTime,
concat(DATE_ADD(#{end_time}, INTERVAL 1 DAY), ' 08:29:59') AS endTime
FROM daily_rate r
然后在组件里配:
json
"isDrill": true,
"drillLink": "http://host/mes/pro/defectRecord?", // 注意只写到 ?
"drillParam": "lineCode,beginTime,endTime"
三个血泪教训:
drillLink千万不要写查询串 。我一开始写成?lineId=4&badTypeMode=${bad_type_mode},结果 URL 变成badTypeMode=%24%7Bbad_type_mode%7D&badTypeMode=1------ 占位符原样发出,还和真实参数重复;- 目标页要有同名参数才接得住;
- 折线图只有圆点能点 ,线身不可点,把
point.style.size放大到 14 会好点很多。
六、坑 5:日期控件能选,但数据不变
这是最容易被业务当成 bug 的一点:抽屉里选好日期 → 点查询 → 请求确实重发了,但参数还是空。
原因链:控件值只写进控件临时状态,没回写 component.params;而取数是 Object.assign(组件参数, URL参数, 表单参数)------组件这一层从来没拿到新值。
结论:日期区间只能靠 URL 传。
ini
.../screenView?tplId=xxx&sendRequest=1&start_time=2026-10-16&end_time=2026-10-26
配套做法:把默认区间写进入口链接,SQL 里再兜一层:
sql
CASE WHEN #{start_time} = '' OR #{start_time} IS NULL
THEN DATE_SUB(CURDATE(), INTERVAL 6 DAY)
ELSE #{start_time} END
七、坑 6:#{} 和 ${} 的引号规则不一样
这个坑能让 SQL 直接报语法错,且报错信息很不友好。
| 写法 | 替换方式 | 要不要加引号 |
|---|---|---|
#{x} |
预编译,值自带引号 | ❌ 不加('#{x}' → ''值'') |
${x} |
字符串拼接 | ✅ 字符串要加、数字不加 |
| 表名后缀 | 只能 ${} |
qt_over_station_${line_id} |
还有个隐藏坑:通过接口建数据集时,SQL 里用 ${} 会被当模板表达式解析 ,直接 模版解析错误:Encountered ...。所以接口建库一律用 #{}。
八、坑 7:点查询后,抽屉不关
现象:数据刷新了,但筛选抽屉赖着不走。
翻源码(screenView.js)找到真相:
kotlin
refreshComponentData() {
for (let i = 0; i < this.components.length; i++) {
const el = this.components[i];
if (el.type != 'picture') {
this.$refs['draggable'].$refs[el.id][0].initData(...); // ← 任一组件取不到 ref 就抛错
}
}
this.closeSearch(); // ← 只有全部跑完才会执行
}
只要有一个组件拿不到 ref,循环中断,closeSearch() 永远不执行。 实践中元凶是表单类组件 (formsDate/formsButton 在查看模式下不渲染),以及非规范的自定义组件 id。
解法:页面里不要留 forms 组件;自定义组件 id 统一改成 32 位十六进制。
九、坑 8:表格永远"暂无数据"
同一个数据集,饼图有数,表格空白。原因:表格组件要自己声明参数,不声明就传空参数给 SQL。
记住三件事:
- 表格参数与图表同名同类型逐个补齐;
tableColumn里的key必须等于数据集列名;- 列宽用百分比,合计 100。
十、坑 9:Excel 报表建不出来(50001)
调 reportTpl/insert 一直 50001,日志里是空指针。翻后端发现:
css
for (int i = 0; i < model.getDataSource().size(); i++) { ... } // dataSource 为 null → NPE
建 Excel 模板必须带 dataSource 数组:
bash
{ "tplCode": "exp_xxx", "tplName": "导出-xxx", "tplType": 2,
"reportType": <目录真实id>, "merchantNo": "SR00000000", "isExample": 2,
"dataSource": ["<datasourceId>"] }
另外:tplType 必须 2,reportType 要填目录 id (不是字符串 "1")。
十一、坑 10:大屏导出的 Excel,参数带不过去
这是架构级限制:文本组件的 href 是静态字符串。
xml
<!-- textComponent.vue -->
<a :href="component.href" target="_blank">
写 href="...?lineId=${line_id}" 不会替换,只会编码成 %24%7Bline_id%7D 发出去。
目前三条路:
| 方案 | 可行性 | 代价 |
|---|---|---|
| 静态 href(写死默认参数) | 能用 | 不跟随筛选 |
| 图表下钻当"导出按钮" | 能带参 | 每页多一个组件 + 一次取数 |
| 外层 iframe 外壳页拼 URL | 能带参、还能同页跳转 | 需同源部署一个 html |
我最后选了外壳页,顺手解决了"下钻总开新标签"的问题:
javascript
// 同源 iframe 内接管跳转
view.contentWindow.open = function (url) { view.src = String(url); return null; };
document.querySelectorAll('a[target="_blank"]').forEach(a => a.target = '_self');
十二、顺带提给产品的 6 个需求
调研完最大的感受:这些不是使用姿势问题,是产品缺口。按性价比排序:
| 优先级 | 需求 | 解决什么 |
|---|---|---|
| P1 | 下钻「打开方式」:新标签 / 当前页 / 父 iframe | 多级下钻开一堆标签 |
| P1 | 参数控件值回写请求(表单组件可注册为筛选项) | 页面内筛选形同虚设 |
| P1 | 刷新函数 ref 判空 + try/catch + 完成后关抽屉 | 点了没反应 / 抽屉不关 |
| P2 | 文本/按钮 href 支持参数占位 | 导出、外链不能带当前筛选 |
| P2 | 图表面板加「轴-系列绑定」+ 轴刻度色 | 不用再改 JSON |
| P2 | 折线下钻支持线身热区 | 点击成功率 |
十三、总结
- SpringReport 是能交付的:不改源码、不碰数据库,也做出了 5 页大屏 + 5 个 Excel 模板 + MES 跳转;
- 但要有"JSON 能力" :双轴、轴样式、标签策略、外部系统参数名,全都靠改组件 JSON 实现;
- 交互层要绕:日期靠 URL、同页跳转靠 iframe 外壳、导出靠拼参;
- 配置规范比技巧重要 :
#{}不加引号、组件 id 用 32 位十六进制、表格必声明参数、日期不填默认值------这四条能避免你 70% 的疑难杂症。
最后给一句判断标准,省得你反复试:
界面能配颜色,就别写 JSON;能写 JSON,就别改源码;改源码都解决不了的,用"外壳页 + URL 传参"绕过。
如果这篇对你有帮助,欢迎点赞收藏;也欢迎在评论区聊聊你踩过的 SpringReport 坑。