news 2026/9/10 20:45:45

ToolJet 多标签页批量打印:用 Tabs 组件与 JavaScript 查询实现一键导出 PDF 报告

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ToolJet 多标签页批量打印:用 Tabs 组件与 JavaScript 查询实现一键导出 PDF 报告

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)对外暴露了currentTabcurrentTabTitleisVisibleisDisabledisLoading等变量,其中:

  • 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 clickSet variable设置一个键为lastSelectedTab的变量,值为{{components.tabs1.currentTab}},用于保存点击按钮前当前选中的标签页 id
On clickRun 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 中executeActionrunOnlyIf的判断逻辑)。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 APIdocument.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按钮即可测试:

  1. 浏览器会打开一个新标签页/窗口;
  2. 其中包含按顺序纵向排布的所有标签页内容(每个标签页约占 100vh 高度);
  3. 触发浏览器打印对话框,选择「另存为 PDF」即可下载包含全部标签页数据的 PDF 文档;
  4. 打印窗口关闭后,应用内的标签页会自动恢复到点击按钮前用户所在的标签页(lastSelectedTab)。

小结

本方案的核心设计可以归纳为三点:

  • 循环控制viewTabstabIndex变量 +Run only if条件实现循环,配合 Tabs 组件的Set current tab动作驱动逐页切换;
  • 内容收集getTabsHTML通过 DOM 读取当前激活标签页的innerHTML,并以 100vh 错位 div 实现多页纵向堆叠;
  • 输出打印printPDF将收集到的样式与 HTML 写入新窗口,以横向页面输出,并在完成后清理tabIndextabsHtml变量、恢复原标签页。

你可以据此模式扩展:把循环上限从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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 20:44:31

Laravel接口绑定与依赖注入实战:从原理到应用与排查

做 Laravel 开发这几年&#xff0c;我在不少项目里见过同一种代码&#xff1a;控制器里直接 new UserService() &#xff0c;Repository 写死在类里面&#xff0c;等哪一天想把存储层从 MySQL 换成缓存&#xff0c;或者想把支付渠道从微信换成支付宝&#xff0c;才发现改文件…

作者头像 李华
网站建设 2026/9/10 20:40:24

百考通得力助手:AI赋能期刊论文写作

在学术研究领域&#xff0c;期刊论文的撰写是成果输出的关键环节&#xff0c;却也让众多科研工作者与学生倍感压力&#xff1a;选题迷茫、逻辑梳理困难、格式规范复杂、内容提炼耗时&#xff0c;严重拖慢了学术成果的发表节奏。百考通&#xff08;https://www.baikaotongai.com…

作者头像 李华
网站建设 2026/9/10 20:39:57

数据资产入表:现状、痛点与选型指南

1. 数据资产入表的行业现状与核心痛点 当前企业数据资产管理正面临从粗放式向精细化转型的关键阶段。根据Gartner最新调研显示&#xff0c;超过78%的CIO将"数据资产价值量化"列为年度战略优先级&#xff0c;但实际落地过程中普遍存在三大典型困境&#xff1a; 首先是…

作者头像 李华
网站建设 2026/9/10 20:35:26

CANN/GE ACL获取输出名称API

aclmdlGetOutputNameByIndex 【免费下载链接】ge GE&#xff08;Graph Engine&#xff09;是面向昇腾的图编译器和执行器&#xff0c;提供了计算图优化、多流并行、内存复用和模型下沉等技术手段&#xff0c;加速模型执行效率&#xff0c;减少模型内存占用。 GE 提供对 PyTorch…

作者头像 李华
网站建设 2026/9/10 20:31:57

论文初稿的引言怎么搭?按研究背景拆解

论文引言写不清&#xff0c;多数时候不是文笔问题&#xff0c;而是研究背景里的三层信息没有各就各位。把背景拆成现象层、问题层、意义层之后&#xff0c;该摆事实的地方摆事实&#xff0c;该切出缺口的地方切出缺口&#xff0c;引言自然就立得住。知学术AIPaperGPT 提供免费智…

作者头像 李华