ToolJet Generate File 动作详解:动态构造 CSV、Text、PDF 文件并触发下载
Generate file 是 ToolJet 内置的一种客户端动作(Action),用于在运行时根据动态数据即时构造文件并直接触发浏览器下载,典型场景包括"一键导出表格数据为 CSV"、"生成纯文本报告"、"以 PDF 表格形式输出查询结果"等。本文基于官方文档 generate-file.md 的完整配置说明展开,并结合 前端事件调度实现、文件生成核心库 与 CSV 序列化实现,讲清楚每种文件类型对 Data 字段的格式要求、默认值行为以及底层生成原理,读完后可直接在 App Builder 和 RunJS 中正确使用该动作。
动作定位与执行链路
从官方文档描述看,该动作"允许你在运行时构造文件并让用户下载"。在前端源码中,它对应事件类型 generate-file,与 runQuery、showAlert、goToApp 等并列注册为可在 RunJS 中调用的动作之一(见 actions.js 中的动作清单)。
事件分发逻辑位于 eventsSlice.js:
case 'generate-file': {
const data = getResolvedValue(event.data, customVariables, moduleId) || [];
const fileName = getResolvedValue(event.fileName, customVariables, moduleId) || 'data.txt';
const fileType = getResolvedValue(event.fileType, customVariables, moduleId) || 'csv';
const fileData = {
csv: generateCSV,
plaintext: (plaintext) => plaintext,
pdf: (pdfData) => pdfData,
}fileType;
return generateFile(fileName, fileData, fileType);
}
这段代码揭示了三个源码级事实:
- 所有字段都经过变量解析:
data、fileName、fileType均通过getResolvedValue解析,因此支持{{...}}动态变量引用; - 存在隐式默认值:Data 为空时回退为
[],File name 为空时回退为data.txt,Type 为空时回退为csv; - 文本类型的内部标识是
plaintext而非text:虽然 UI 选项表中 Type 写作CSV、Text、PDF,但序列化映射表的键是csv/plaintext/pdf,这一点在 RunJS 中调用时尤为关键(见下文)。
配置项全解
官方文档给出了如下四个选项,本文逐一补充其在源码中的落地行为:
| Option | 说明 | 源码层面的行为 |
|---|---|---|
| Type | 要生成的文件类型:CSV、Text、PDF |
内部取值为 csv / plaintext / pdf,决定 Data 的序列化方式;未填时默认为 csv |
| File name | 生成文件的名称 | 未填时默认为 data.txt,支持变量插值 |
| Data | 用于构造文件的数据,格式随文件类型而定 | 为空时默认为 [],支持 {{...}} 动态表达式 |
| Debounce | 默认空;填入数字表示该毫秒数后才执行动作,如 300 |
事件节流配置,避免高频事件(如连续输入)反复触发文件下载 |
其中 Debounce 字段对交互密集型场景很有价值:例如在输入事件上挂载 generate file 时,填入 300 可使动作在用户停止触发 300ms 后才真正执行一次,避免产生大量重复下载。
三种文件类型的数据格式要求
CSV:对象数组,键即列头
使用 CSV 格式时,Data 字段应是一个对象数组,ToolJet 假定每个对象的键都相同,并将这些键作为 CSV 的列头。官方文档示例:
{{
[
{ name: 'John', email: 'john@tooljet.com' },
{ name: 'Sarah', email: 'sarah@tooljet.com' },
]
}}
生成结果为:
name,email
John,john@tooljet.com
Sarah,sarah@tooljet.com
这一行为由 generate-csv.js 实现,仅 5 行核心逻辑:
import Papa from 'papaparse';
export default function generateCSV(records) {
return Papa.unparse(records);
}
即底层直接委托给 papaparse 的 Papa.unparse 完成"对象数组 → CSV 文本"的序列化。这意味着字段中包含逗号、引号、换行符等字符时会按 RFC 4180 标准自动加引号转义,可直接粘贴到 Excel 等工具中解析。
Text:字符串;对象数组需先序列化
使用 Text 格式时,Data 字段应直接是一个字符串。如果数据源是对象数组(例如表格组件的当前页数据),必须先 stringify 再传入 Data 字段。官方文档给出的示例是:
{{JSON.stringify(components.table1.currentPageData)}}
对应源码中 plaintext 的序列化函数是恒等函数 (plaintext) => plaintext,即不做任何转换、原样写入文件——这也解释了为什么必须自行完成序列化:若直接传对象数组,Blob 会得到无意义的 [object Object] 内容。
PDF:字符串或对象数组,二选一
PDF 支持两种输入形态,行为差异在 generate-file.js 的 generatePDF 函数中清晰可见:
- 传入字符串:生成纯文本 PDF。源码中以
doc.text(value, x, y, { align: 'left', maxWidth: pageWidth - 2 * margin })逐行绘制,页边距margin = 10; - 传入对象数组:生成表格形态的 PDF。源码以数组第一个元素的键作为表头,调用 jspdf-autotable 的
doc.autoTable({ head: [columnNames], body: value.map((item) => Object.values(item)) })渲染出带行列的表格,并依据doc.lastAutoTable.finalY控制后续内容的起始位置; - 传入单个对象:同样渲染为表格,只是 body 只有一行数据;
- 其他类型(数字、布尔等):抛出
Invalid data type. Expected string, object, or array.错误。
底层生成机制:Blob 下载与 PDF 动态加载
generate-file.js 的 generateFile 主函数处理 CSV 与文本文件的下载:
const type = fileType === 'csv' ? 'text/csv' : 'text/plain';
const blob = new Blob([data], { type });
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveBlob(blob, filename);
} else {
const elem = window.document.createElement('a');
elem.href = window.URL.createObjectURL(blob);
elem.download = filename;
document.body.appendChild(elem);
elem.click();
document.body.removeChild(elem);
window.URL.revokeObjectURL(elem.href);
}
要点:
- CSV 使用
text/csvMIME 类型,其余(plaintext)使用text/plain; - 兼容旧版 IE/Edge 的
msSaveOrOpenBlob接口,现代浏览器走标准的"创建<a>元素 +URL.createObjectURL+ 模拟点击"流程,并在使用后revokeObjectURL释放内存,避免 Blob URL 泄漏; - 全程在浏览器端完成,数据不经过服务端中转,适合包含敏感数据的本地导出场景。
PDF 则采用动态 import 按需加载(await import('jspdf'),见 generate-file.js L22-L26),jspdf 体积较大,按需引入可避免首屏加载负担;同时对 ESM/CJS 两种导出形式都做了兼容取值(jsPDFNamespace.jsPDF || jsPDFNamespace.default)。
通过 RunJS 调用 generate file
除在组件事件(Events)中配置外,该动作同样可在 RunJS 代码中调用,官方文档在 run-action-from-runjs.md 中给出了签名与示例:
actions.generateFile('<fileName>', '<fileType>', '<data>')
三个实战示例(均出自该文档):
// 以表格当前页数据生成 CSV 文件 csvfile1
actions.generateFile('csvfile1', 'csv', '{{components.table1.currentPageData}}')
// 以字符串化后的表格数据生成文本文件 textfile1
actions.generateFile('textfile1', 'plaintext', '{{JSON.stringify(components.table1.currentPageData)}}')
// 以表格当前页数据生成 PDF 表格文件 Pdffile1
actions.generateFile('Pdffile1', 'pdf', '{{components.table1.currentPageData}}')
RunJS 桥接实现位于 eventsSlice.js L1298-L1310:内部先校验三个参数均非空(缺失时弹出 Action failed: fileName, fileType and data are required 错误提示),再构造 actionId: 'generate-file' 的事件对象并复用与 UI 完全相同的 executeAction 执行通道——这意味着 RunJS 调用与事件面板配置走的是同一条执行链路,行为完全一致。
使用要点小结
- 参数命名与文档一致:
fileName、fileType、data三个字段都支持{{...}}变量表达式,可引用查询结果、组件数据或全局变量; - 文本类型务必传字符串:UI 中 Type 选择
Text后,内部类型值是plaintext;若数据是对象数组,请在 Data 字段中显式JSON.stringify; - CSV 列头来自对象的键:确保数组中各对象键集一致,列头顺序即对象键的顺序;
- PDF 想要表格效果就传对象数组,想要纯文本就传字符串;
- 善用 Debounce:在高频触发的事件上配置毫秒级延迟,避免重复下载。
以上配置与行为均可在当前仓库中直接查证:文档主体见 generate-file.md,事件分发见 eventsSlice.js,文件生成核心见 generate-file.js 与 generate-csv.js,RunJS 调用说明见 run-action-from-runjs.md。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00