ToolJet 多标签页批量打印:用 Tabs 组件与 JavaScript 查询实现一键导出 PDF 报告
【免费下载链接】ToolJetOpen-source foundation of ToolJet AI - the enterprise app generation platform for internal tools, dashboards, business applications, workflows and AI agents. Build visually, from a prompt, or from Claude Code, Codex and Cursor over MCP 🚀项目地址: https://gitcode.com/GitHub_Trending/to/ToolJet
本指南以 ToolJet 2.50.0-LTS 版本文档为基础,完整讲解如何在 ToolJet 应用中实现「一次点击,打印/导出所有标签页数据」的能力,典型场景是发票批量打印、多记录报告导出。读完本文,你将掌握 Tabs 组件与currentTab暴露变量的联动方式、actions.setVariable/unsetVariable变量操作的用法,以及利用事件处理器串联多条 JavaScript 查询、实现循环遍历标签页并最终生成 PDF 的完整方案。
应用界面设计(UI of the App)
在 ToolJet 首页点击Create new app按钮上的省略号,从模板中选择一个「每个记录对应一组标签页」的应用。本指南以Lead Management System(线索管理系统)应用为例进行讲解。
示例应用包含以下关键组件:
- Tabs 组件(tabs1):每个标签页代表一类线索记录。示例应用共有 4 个标签页,id 依次为 0 到 4(0、1、2、3)。
- 按钮组件:默认的Create Lead按钮之外,再添加一个Download PDF按钮,用于一次性打印所有标签页的数据。该按钮需要配置两个事件,具体细节见下文。
从 Tabs 组件的源码可以看到,ToolJet 的 Tabs 组件(frontend/src/AppBuilder/WidgetManager/widgets/tabs.js)对外暴露了currentTab、currentTabTitle、isVisible、isDisabled、isLoading等变量,其中:
currentTab:当前激活标签页的 id(数字);currentTabTitle:当前激活标签页的标题。
后续脚本中使用的{{components.tabs1.currentTab}}、{{variables.tabIndex}}正是读取了这些暴露变量与自定义变量。Tabs 组件还注册了setTab(显示名Set current tab)等可被事件系统调用的动作,这是实现「程序化切换标签页」的基础。
从数据库加载数据(Load Data from Database)
- 数据来源使用lead_management_system表;
- 在fetchLeads查询中,
Table name参数选择lead_management_system; Operations参数选择List rows;- 点击查询面板中的Run按钮加载数据。
数据成功加载到各标签页、应用运行符合预期后,即可进入下一步。
打印多个标签页的数据(Printing Data from Multiple Tabs)
实现思路是:创建若干条JavaScript 查询,通过**事件处理器(Event Handler)**将这些查询按顺序串联执行,循环遍历所有标签页、逐个收集 HTML,最后统一输出到打印窗口生成 PDF。
在创建查询之前,先给Download PDF按钮添加两个事件:
| 事件 | 动作 | 说明 |
|---|---|---|
| On click | Set variable | 设置一个键为lastSelectedTab的变量,值为{{components.tabs1.currentTab}},用于保存点击按钮前当前选中的标签页 id |
| On click | Run query | 选择名为viewTabs的查询,在按钮被点击时运行 |
注意:viewTabs查询将在下文创建,因此需要先完成查询创建,再回到按钮上补充该事件。
创建查询(Creating Queries)
整个方案由三条 JavaScript 查询协作完成:viewTabs(循环控制)、getTabsHTML(收集当前标签页 HTML)、printPDF(输出打印窗口)。下面逐条讲解。
viewTabs 查询(循环控制)
viewTabs是一条 JavaScript 查询,负责循环遍历所有标签页。它通过setVariable动作维护一个tabIndex变量,记录「当前要打印哪个标签页」。示例应用中循环条件为tabIndex < 4(即 id 0 到 3 共 4 个标签页)。
if ((variables?.tabIndex ?? undefined) == undefined) { await actions.setVariable("tabIndex", "0"); // 若 tabIndex 未设置,则初始化为 0 } else if (parseInt(variables.tabIndex) < 4){ await actions.setVariable("tabIndex", (parseInt(variables.tabIndex) + 1).toString()); // 否则将 tabIndex 加 1 }这段代码的逻辑:首次执行时(tabIndex未定义)将其置为"0";后续执行时只要当前值小于 4 就自增 1,从而驱动循环推进。需要注意的是变量值以字符串形式存储,比较与自增时都需要parseInt转换。
该查询需要配置 3 个事件:
事件 1(切换标签页)
- 在viewTabs查询中点击New event handler,事件类型选择
Query Success; - Action选择
Control component; - 在事件的Run only if参数中填写:
{{parseInt(variables.tabIndex) < 4}}——仅当条件为真(tabIndex 小于 4)时才执行该事件; - 在ACTION OPTIONS中,Action选择
Set current tab; - Id参数填写:
{{variables.tabIndex}}——把当前标签页切换到tabIndex变量中存储的 id 对应的标签页。
事件系统在真正执行动作前会先解析Run only if条件,只有条件成立才继续分发动作(见 frontend/src/AppBuilder/_stores/slices/eventsSlice.js 中executeAction对runOnlyIf的判断逻辑)。Set current tab对应 Tabs 组件注册的setTab动作,其参数id就是要切换到的标签页 id(见 frontend/src/AppBuilder/WidgetManager/widgets/tabs.js)。
事件 2(收集当前标签页 HTML)
- 第二个事件同样是
Query Success事件; - Action选择
Run Query; - Run Only If参数填写:
{{parseInt(variables.tabIndex) < 4}}——仅在循环未结束时执行; - Query选择
getTabsHTML; - 为该事件处理添加100 毫秒的Debounce(防抖)。
注意:getTabsHTML查询将在下文创建,需先创建查询再回填此事件。防抖参数由事件执行器统一支持——
executeAction本身就被debounce包裹(见 frontend/src/AppBuilder/_stores/slices/eventsSlice.js),此处为事件级叠加的 100ms 防抖,用于等待标签页切换后的渲染完成,避免取到未更新的 DOM。
事件 3(触发最终打印)
- 第三个事件同样为
Query Success事件; - Action选择
Run Query; - Run Only If参数填写:
{{parseInt(variables.tabIndex) === 4}}——仅当tabIndex等于 4(循环的最后一次迭代)时执行,此时所有标签页的 HTML 都已收集完毕; - Query选择
printPDF。
注意:printPDF查询将在下文创建,需先创建查询再回填此事件。
三条事件共同构成完整的循环链条:事件 1 切页 → 事件 2 收集 HTML → getTabsHTML 成功后又回跑 viewTabs,如此往复,直到tabIndex达到 4 时由事件 3 触发打印。
getTabsHTML 查询(收集当前标签页 HTML)
getTabsHTML是一条 JavaScript 查询,负责读取当前标签页的 HTML 内容,并追加保存到tabsHtml数组变量中,最终累积全部标签页的 HTML。
actions.setVariable( // 设置 tabsHtml 变量 "tabsHtml", [...(variables?.tabsHtml ?? [])].concat([ // 将当前标签页的 html 追加到 tabsHtml 变量中 ((variables?.tabIndex ?? -1) > 0 ? `<div style="top: ${ // 为当前标签页的 html 添加高度为 100vh 的 div variables?.tabIndex ?? -1 }00vh; position: absolute;">` // 便于将所有标签页的数据一次性打印出来 : "") + document.getElementsByClassName("widget-" + components.tabs1.id)[0] // 获取当前标签页的 html .innerHTML + "</div>", // 将当前标签页的 html 追加到 tabsHtml 变量 ]) );代码要点:
tabsHtml以数组形式累积各标签页的 HTML;[...(variables?.tabsHtml ?? [])]保证在变量未初始化时也能正常工作;- 通过 DOM API
document.getElementsByClassName("widget-" + components.tabs1.id)[0].innerHTML抓取 Tabs 组件(tabs1)当前激活标签页的实际渲染内容; - 当
tabIndex > 0时,会在 HTML 前包裹一个position: absolute的<div>,其top为${tabIndex}00vh——利用每个标签页高度约 100vh 的错位排版,把多个标签页的内容纵向堆叠到同一张纸上,从而实现「多页内容一次打印」。
该查询需要配置 1 个事件:
事件 1(回跑循环)
- 事件类型为
Query Success; - Action选择
Run Query; - Query参数选择
viewTabs——即getTabsHTML成功执行后,立即再次运行viewTabs,驱动循环进入下一轮。
注意:创建完getTabsHTML后,需回到viewTabs查询的事件 2,把getTabsHTML填入事件处理器的 Query 参数中。
printPDF 查询(生成可打印文档)
printPDF是一条 JavaScript 查询,它把tabsHtml变量中累积的全部 HTML 写入一个新建的浏览器窗口,从而允许用户将该窗口打印/另存为包含所有标签页内容的 PDF 文档。
var printContents = variables.tabsHtml; // 获取所有标签页的 html(来自 tabsHtml 变量) var winPrint = window.open("", "", "width=900,height=650"); // 打开一个新的打印窗口 var styles = document.querySelectorAll('link, style'); var stylesHtml = ""; for (var i = 0; i < styles.length; i++) { stylesHtml += styles[i].outerHTML; } // 收集当前页面的样式 stylesHtml += '<style>@page { size: landscape; }</style>'; // 为打印页面添加横向(landscape)方向 winPrint.document.write( "<html><head>" + stylesHtml + "</head><body>" ); // 将样式写入打印页面 for (var j = 0; j < printContents.length; j++) { winPrint.document.write(printContents[j]); } // 将所有标签页的 html 写入打印页面 winPrint.document.write("</body></html>"); // 完成文档并打印 winPrint.document.close(); winPrint.focus(); winPrint.print(); winPrint.close();代码要点:
- 遍历当前页面的
<link>与<style>标签(document.querySelectorAll('link, style')),将其outerHTML拼接进打印窗口,确保打印出的内容保留应用的主题样式; - 额外注入
@page { size: landscape; },将打印页面方向设置为横向,更适合多列表格类报告; - 使用
window.open("", "", "width=900,height=650")打开新窗口,依次write全部标签页 HTML,最后依次调用close()、focus()、print()、close()触发浏览器原生打印对话框。
该查询需要配置 3 个事件(均为Query Success):
事件 1(清理 tabIndex)
- 事件类型选择
Query Success; - Action选择
Unset variable; - 在ACTION OPTIONS中将Key设置为
tabIndex——打印成功后清除循环变量,避免下次点击时状态残留。
事件 2(清理 tabsHtml)
- 事件类型选择
Query Success; - Action选择
Unset variable; - 在ACTION OPTIONS中将Key设置为
tabsHtml——打印成功后清除累积的 HTML 数据,确保下一次点击重新收集。
事件 3(恢复原标签页)
- 事件类型选择
Query Success; - Action选择
Control component; - Component参数选择
tabs1; - Action选择
Set current tab; - Id参数填写:
{{variables.lastSelectedTab}}——把当前标签页恢复为点击Download PDF之前用户所在的那个标签页。
这组「清理 + 恢复」动作保证了打印流程结束后应用状态回到初始状态,用户可以重复点击按钮而不会出现变量残留或标签页错位。Unset variable动作对应事件系统里的unset-custom-variable分支,底层调用unsetVariable(key, moduleId)从应用变量中移除指定键(见 frontend/src/AppBuilder/_stores/slices/eventsSlice.js);而Set variable动作对应set-custom-variable分支,调用setVariable(key, value, moduleId)写入变量(见 frontend/src/AppBuilder/_stores/slices/eventsSlice.js)。
注意:创建完printPDF后,需回到viewTabs查询的事件 3,把printPDF填入
Query Success事件处理器的 Query 参数中。
运行与验证
所有查询创建并回填事件后,点击Download PDF按钮即可测试:
- 浏览器会打开一个新标签页/窗口;
- 其中包含按顺序纵向排布的所有标签页内容(每个标签页约占 100vh 高度);
- 触发浏览器打印对话框,选择「另存为 PDF」即可下载包含全部标签页数据的 PDF 文档;
- 打印窗口关闭后,应用内的标签页会自动恢复到点击按钮前用户所在的标签页(
lastSelectedTab)。
小结
本方案的核心设计可以归纳为三点:
- 循环控制:
viewTabs用tabIndex变量 +Run only if条件实现循环,配合 Tabs 组件的Set current tab动作驱动逐页切换; - 内容收集:
getTabsHTML通过 DOM 读取当前激活标签页的innerHTML,并以 100vh 错位 div 实现多页纵向堆叠; - 输出打印:
printPDF将收集到的样式与 HTML 写入新窗口,以横向页面输出,并在完成后清理tabIndex、tabsHtml变量、恢复原标签页。
你可以据此模式扩展:把循环上限从4改为任意标签页数量、为每个标签页定制打印样式,或将window.print()替换为其他导出逻辑,从而将「多标签页一键打印」复用到发票、报表、工单等多种业务场景。
【免费下载链接】ToolJetOpen-source foundation of ToolJet AI - the enterprise app generation platform for internal tools, dashboards, business applications, workflows and AI agents. Build visually, from a prompt, or from Claude Code, Codex and Cursor over MCP 🚀项目地址: https://gitcode.com/GitHub_Trending/to/ToolJet
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考