积木报表 AI 生成器
SkillFiles & storageJimuReport generator — describe report requirements in natural language or provide a screenshot to automatically generate JimuReport reports (supports all types: data reports, print reports, grouped reports, loop reports, data entry forms, etc.). Use when user says "积木报表", "jmreport", "Excel报表", "数据
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the 积木报表 AI 生成器 skill
What this skill tells your AI
The instructions your AI receives, as published by jeecgboot/skills in jimureport/SKILL.md and read by ahel’s review.
不涉及「Online 报表」(cgreport)或「Online 表单」(cgform)。
临时配置文件规则(强制)
所有传给脚本的 --config <xxx.json> 必须写到 {系统临时目录}/{SKILL_NAME}/ 下,由操作系统自动清理;skill 与脚本均不主动删除该目录或文件。
import tempfile, os, json
SKILL_NAME = "<SKILL_NAME>" # 请替换为实际的技能名称
skill_dir = os.path.join(tempfile.gettempdir(), SKILL_NAME)
os.makedirs(skill_dir, exist_ok=True) # 确保目录存在,不主动检查
config_path = os.path.join(skill_dir, 'sk_audit_create.json') # 示例文件名
with open(config_path, 'w', encoding='utf-8') as f:
json.dump(cfg, f, ensure_ascii=False, indent=2)
tempfile.gettempdir() 自动适配:Windows %TEMP%、Linux /tmp、macOS /var/folders/.../T(注意 macOS 并非 /tmp)。
文件名建议使用 <表名>_<步骤>.json(如 sk_audit_create.json),无需重复技能前缀,因路径已包含技能名称,便于排错。
❌ 禁止:
- 写到
<skill>/tmp/或当前工作目录(污染 skill / 用户项目) - 硬编码
/tmp、C:\Temp或任何固定路径(不跨平台) - 每步完成后主动
rm/Remove-Item(操作系统会清理,属多余 tool call) - 主动
os.path.exists()检查(其本身即为一次 tool call) (使用os.makedirs(…, exist_ok=True)满足需求,不算主动检查)
临时文件可能被操作系统异步清理,但仍遵循 乐观调用 + 报错补救:仅当脚本返回 FileNotFoundError 或 配置文件不存在 时,使用相同内容、在相同的 {系统临时目录}/{SKILL名称}/ 路径下重写(重写前仍需 os.makedirs(skill_dir, exist_ok=True) 确保目录存在),切勿更换路径或回退至 skill 目录。
一键脚本(必看,覆盖三类全场景)
写自定义 JSON / Python 之前,先看用户需求是否命中下表现成脚本,命中则直接调用,禁止重新组装 JSON 或 Python:
| 用户描述(关键词) | 直接调用 | 默认覆盖 |
|---|---|---|
| 「全图表」「所有图表」「图表大全」「测试所有数据集类型」「SQL+API+JSON」「图表展示」 | python scripts/generate_all_reports.py --base-url ... --token ... --name "..." --mysql-host ... --mysql-port ... --mysql-db ... --mysql-user ... --mysql-pwd ... | 25 个图表(SQL 12 + API 2 + JSON 4 + 不绑 7),自动建 chart_demo_all 表插数据 + 自动创建 YApi mock + 一次保存 |
命中规则与禁止事项
- 关键词命中即用:用户说「生成全部图表」「全图表测试」「演示所有图表」「测试 SQL/API/JSON 三种数据集」时,第一反应就是
generate_all_reports.py,不要回头自己写 chart_entry/echarts 模板 - 3 秒能跑完:实测 ~3.1s 端到端创建。脚本启动后不要分块等待、不要发 AskUser 求确认,直接
Bash等结果 - Mock 新建必须用唯一路径:
create_mock遇到同路径会静默覆盖已有接口数据,污染他人接口。创建新接口时必须在路径末尾追加时间戳或序号(如/sales_20260427),只有用户明确说"修改/更新已有接口"时才可复用原路径 - saveDb 串行:脚本已改为串行调用
save_db,避免jimu_report_db_fieldINSERT 并发引发 MySQL deadlock - 新增图表类型时:在
CHARTS列表加一行,写一个tpl_xxx函数即可,无需重写主流程
执行流程
第零步(必须):Token 优先
用户消息里没有 X-Access-Token 时,立刻询问,拿到 token 后再读任何文件。等待回复期间不要预读文件——等待时间不计入 3 分钟,文件读取时间计入。
⚠️ 凭证禁止读记忆,直接问用户:需要数据库密码、账号密码等任何凭证时,禁止读取 memory 文件获取,必须直接在对话中问用户。
第一步(必须):按「执行速度规范」表选最小文件集
不要先 Glob examples/,直接查下方「执行速度规范」表,按场景只读指定文件。禁止在表外额外读文件。 场景匹配优先于文件名匹配:
multi-level-header.md主要是交叉表 groupRight/dynamic,纵向分组+静态多级表头不要读它(浪费 ~30s 读不适用示例)。
读完指定文件后直接 Write JSON 配置 → 执行 CLI 命令 → 输出预览链接。两步完成,禁止多余动作。
报表链接格式(创建成功后直接输出,禁止调接口验证是否存在):
- 设计器:
http://{host}/jmreport/index/{report_id}?token={token}&tenantId=1- 预览:
http://{host}/jmreport/view/{report_id}?token={token}&tenantId=1
报表名称规则:用户明确指定名称时直接使用;未指定时 AI 自动生成名称,生成后须调
GET /jmreport/query/report/folder?pageNo=1&pageSize=10&reportType=&name={name}&token={token}检查是否重复,有同名则追加后缀(如_2、_20260415)。
utils 子模块速查(需确认某函数签名时,Grep 对应小文件,禁止读全量 jimureport_utils.py):
| 需要确认的函数 | 读哪个文件 |
|---|---|
| Session、gen_id/code/layer、col_letter、_compute_sign | jimureport_core.py |
| parse_api、parse_sql、save_db、update_db、parse_and_save_dataset、parallel_parse/save/api | jimureport_dataset.py |
| make_designer、base_save、get_report、report_urls、print_summary | jimureport_report.py |
| make_styles、STYLE_BASE/DATA/HEADER/TITLE/LINK(命名常量,禁止用魔法数字) | jimureport_styles.py |
| chart_entry、virtual_row、build_chart_layout、update_chart_config、parallel_fill_charts、pick_chart_axes | jimureport_chart.py |
| create_link、parallel_create_links | jimureport_link.py |
| ensure_datasource、find_datasource、get_ds_connection、query_mysql、execute_ds | jimureport_datasource.py |
| 禁止 | 替代 |
|---|---|
| 读全量 jimureport_utils.py | 按上表 Grep/Read 对应子模块(各 25-175 行) |
Grep/Read jimureport_gen.py(任何原因) | api_dataset/group/standard 等函数的签名和参数在 SKILL.md 调用示例中已完整给出(base_url 默认 http://192.168.1.6:8085/jmreport,无需传),"不确定参数"不构成读源码的理由,直接信任文档 |
Grep/Read jimureport_creator.py 确认是否支持某功能(如 fieldList searchMode、paramList 等) | SKILL.md 的 JSON 配置模板和禁止表已覆盖所有场景,直接写 JSON 配置执行,禁止读源码验证 |
Grep/Read jimureport_dataset.py 查看 parse_sql 实现 | parse_sql 接受含 FreeMarker 条件的 SQL,服务端以空参评估后解析字段列表,直接调用即可,无需看源码 |
| 找 DB 凭证 | 用 memory 中的配置或问用户 |
| Windows 下 Bash tool 跑 python | 改用 PowerShell tool 跑 python xxx.py / python -c "...",同步返回(详见下方「Windows 执行环境」) |
| 调外部 API 验证字段 | 直接按用户提供的字段写脚本,不预调 API |
擅自调用 create_mock() 或 init_yapi() | ⛔ 用户已提供接口 URL 时,直接 save_db(api_url=URL),严禁调用 create_mock() / init_yapi() / 任何 YApi 登录或验证操作;只有用户明确说"帮我创建 mock 接口"或完全未提供 URL 时才调用;URL 已知 = 直接用,不验证、不询问、不登录 |
sleep + cat 轮询输出 | Bash 命令在 Windows 始终被后台化;若仍要走 Bash,必须用 TaskOutput(task_id, block=true) 等待结果,禁止用 sleep/cat 轮询 |
| 报表创建后调接口验证是否存在 | /save 返回 success:true 即成功,直接输出设计/预览链接,无需查报表列表 |
手写 border 样式({"style":1} 或任何非数组格式) | 必须用 make_styles() 获取 styles 列表,它已内置正确的 ["thin","#d8d8d8"] 数组格式;手写 border 一律禁止,会导致整表渲染空白 |
customRows 配合空 columns: [] | creator 靠 columns 生成 queryInfo.dbf;columns 为空则不写入绑定元数据,报表预览完全无数据(即使数据集 records=N)。customRows 只控制视觉布局,columns 必须填数据集的实际字段(至少一个) |
用户未指定的可选参数自行填值(如 customEditConf.eventParams、freeze、rpbar、background 等) | 只写用户明确给出的字段,其余可选参数一律省略,不得自造默认值 |
调 report_urls() 工具函数当作 dict 用(如 urls['designer']) | report_urls(report_id, base_url, token, tenant) 返回 tuple (preview_url, design_url),不是 dict;且第一个参数是 report_id 不是 base_url。直接按本节"报表链接格式"拼字符串,不要调用此函数 |
| 报表创建走自定义 .py 脚本 | ⛔ 必须用 JSON 配置文件 + jimureport_creator.py CLI:只写 xxx.json → python jimureport_creator.py --api-base URL --token TOKEN --config xxx.json。需要同时创建 YApi mock 的 API 数据集报表,用 jimureport_gen.api_dataset(..., mock_data=[...], mock_path="/xxx_日期") 一步完成,内部自动生成 JSON 并调 CLI,无需手写 Python 脚本。用户反馈(2026-05-22 再次确认):"以后只生成JSON"。⚠️ 典型错误场景1:用户要求自定义样式,AI 认为高级函数不支持样式就手写 rows/cols/styles/save_db/base_save——严重违规。⚠️ 典型错误场景2:场景复杂(含钻取+图表+建表),AI 直接写 drill_demo.py 等全流程 Python 脚本——严重违规。正确做法:报表结构部分始终写 JSON;建表/链接创建等 JSON 不支持的步骤写成最小独立 PowerShell inline;两部分分开,不混写。 |
PowerShell Out-File -Encoding utf8 写 JSON | 产生 UTF-8 BOM 导致 json.load 报 Unexpected UTF-8 BOM。必须用:[System.IO.File]::WriteAllText($path, $content, (New-Object System.Text.UTF8Encoding $false)) |
Windows 执行环境(强制规则,违反会让用户吐槽"执行太慢")
现象:Windows 的 Bash tool 会把 python / python -c / skill 脚本当作长命令自动 run_in_background,tool 立即返回 background ID,真正输出要等完成通知——把毫秒级调用放大到数秒,历史上多次让单报表从 1 分钟拖到 18 分钟。
规则:
- Windows(platform=win32) → 用 PowerShell tool 直接执行
python xxx.py,同步返回。禁止用 Bash tool 跑 python(会被后台化)。 - Linux / macOS → 用 Bash tool 直接调用
python xxx.py。 - 任何平台都不用
curl:跨平台不一致,Windows Bash 下同样被后台化。
脚本执行前强制检查(2 项):
- ✅ Windows 下用 PowerShell tool 执行
python xxx.py,不是 Bash tool - ✅ 脚本第一行已加编码声明:
import sys; sys.stdout.reconfigure(encoding='utf-8')(防 GBK 崩溃重试)
Windows 正确示例:
PowerShell: python <skill_base_dir>/scripts/xxx.py --base-url ... --token ...
<skill_base_dir>是本 SKILL.md 所在目录,运行时用实际路径替换,禁止写死C:/Users/...。
Windows 错误示例:
Bash: python generate_all_reports.py ... ← 返回 "Command running in background with ID: xxx"
Bash: curl -X POST ... ← 同上
历史教训:曾因默认走 Bash + python 被用户连续吐槽"执行太慢了 / 生成这么慢"。根因是 Bash tool 在 Windows 对 python 会后台化,不限于 curl。另一常见重试原因:脚本缺编码声明导致
UnicodeEncodeError: 'gbk' codec,加第2项检查可消除。 典型症状:报表生成完成后仍等待约 2 分钟才结束——这是 Bash 后台化的直接表现:脚本已跑完但 tool 在等 background 完成通知。遇到此现象立即确认是否误用了 Bash tool,改 PowerShell 即可消除。
前置条件
用户须提供 X-Access-Token。
SQL 数据集数据源选取(用户未指定 dbSource 时必须执行)
正确流程(必须每次执行,不可跳过):
- 调
GET /jmreport/initDataSource获取数据源列表 - 按用户提供的数据库名(如
jeecg-boot-cr)精确匹配name字段,取其id - 找不到再询问用户
⚠️ 禁止用 memory 中存的数据源ID直接跳过查询:memory 里的ID可能已过期或被重建,必须每次查询后按名字匹配拿到当前有效ID。memory 只用于记住数据库名,不用于记住ID。 ⚠️ 禁止全量拉取后遍历猜测:有明确数据库名时直接按名字匹配,不要靠含"积木"等模糊规则。
按以下规则处理:
| 返回结果 | 处理方式 |
|---|---|
result 为空数组 | 告知用户需要先在积木报表中新增数据源,停止创建 |
result 非空,存在 name 含"积木"的项 | 自动选该项,将其 id 作为 db_source 传入 save_db |
result 非空,无含"积木"的项 | 列出所有数据源名称,询问用户选哪个,等待回复后再继续 |
接口返回字段:每项包含 id(传给 db_source)和 name(展示给用户)。
脚本中直接调用(禁止在脚本里重新手写此逻辑):
from jimureport_utils import resolve_db_source
# 用户未指定数据源时:
db_source = resolve_db_source(session) # 自动选含「积木」的;无则抛 RuntimeError 列出清单
RuntimeError 消息已包含数据源列表,捕获后直接转告用户即可。
上下文优先:本次对话中已经通过
resolve_db_source或用户回复确定过db_source,后续同一会话的报表直接复用,不得重复调用initDataSource。 ⚠️ 禁止全量拉取后遍历猜测:不要拉取全部30+数据源再靠名字模糊匹配,应按用户提供的数据库名精确查找,或直接读 memory。全量拉取是浪费 + 容易选错。
API 数据集前置询问(用户未提供 API 地址时必须先问)
用户未给出 API 地址时,必须先询问:
请问接口用哪种方式创建?
- mock 接口:通过 YApi 创建 mock 接口(参见下方「YApi Mock 数据源」章节)
- 本地代码:请提供本地 JeecgBoot 项目路径,我直接把 Controller 写入项目
收到答复后的处理规则:
| 用户选择 | 处理方式 |
|---|---|
| mock 接口 | 按「YApi Mock 数据源」章节流程,用 yapi_mock.py 创建 mock 接口,返回 mock URL 填入数据集 |
| 本地代码 | 询问项目路径(如 D:\path\to\jeecg-boot),只生成 Controller 写入项目,返回静态数据({"data": [...]}),不生成 Entity / Mapper / Service / SQL |
🚀 CLI 创建(一条命令)
python /scripts/jimureport_creator.py \
--api-base http://BASE_URL --token TOKEN --config /path/to/config.json
配置 A:SQL 普通/分组报表
{
"action": "create", "reportName": "报表名称", "theme": "blue",
"datasets": [{"dbCode":"ds1","dbChName":"数据集","dbDynSql":"SELECT col1,col2 FROM t ORDER BY col1","dbSource":"","isPage":"0"}],
"table": {"datasetCode":"ds1","title":"报表名称","columns":[
{"field":"col1","title":"列1","width":120,"group":true},
{"field":"col2","title":"列2","width":100,"funcname":"SUM"}
]}
}
columns 可选属性:
group:true(分组) /funcname:"SUM"(聚合) /subtotalText:"小计"
配置 B:SQL + 图表
{
"action":"create","reportName":"名称","layout":"chart_bottom",
"datasets":[
{"dbCode":"dt","dbChName":"表格","dbDynSql":"SELECT ...","isPage":"1"},
{"dbCode":"dc","dbChName":"图表","dbDynSql":"SELECT x AS name,y AS value,'' AS type FROM ...","isPage":"0"}
],
"table":{"datasetCode":"dt","title":"名称","columns":[...]},
"chart":{"datasetCode":"dc","chartType":"bar.simple","title":"图表","width":"650","height":"380"}
}
layout:
chart_bottom/chart_top/chart_right/chart_only
配置 C:JSON 数据集(dbCode 必须字符串!)
{
"action":"create","reportName":"名称",
"datasets":[{"dbCode":"my_data","dbChName":"数据","dbType":"3","isList":"1","isPage":"0",
"jsonData":[{"name":"张三","age":"25"}],
"fieldList":[["name","姓名"],["age","年龄"]]}],
"table":{"datasetCode":"my_data","title":"名称","columns":[
{"field":"name","title":"姓名","width":100},{"field":"age","title":"年龄","width":80}]}
}
禁止纯数字 dbCode(如 gen_code()),JSON 数据集模板引擎无法解析。
f-string 写绑定字段时必须转义花括号:
f"#{{{db_code}.{field}}}"→ 生成#{db_code.field}。若写成f"#{db_code}.{field}#"则花括号被 Python 吃掉,变成#db_code.field#(格式错误,末尾多#,数据不渲染)。
配置 D:自定义 rows(复杂多级表头)
build_table_rows 无法满足时(如四级合并表头),传 customRows + customMerges 跳过自动构建:
{
"action":"create","reportName":"名称",
"datasets":[{"dbCode":"ds1","dbType":"3","jsonData":[...],"fieldList":[...]}],
"table":{"datasetCode":"ds1","columns":[{"field":"f1","title":"F1","width":100}]},
"groupField":"ds1.group_field",
"customRows":{"1":{"cells":{"1":{"text":"标题","style":0,"merge":[0,5]}},"height":40}},
"customMerges":["B2:G2"],
"customStyles":[{"align":"center","font":{"size":16,"bold":true}},{"align":"center","font":{"bold":true,"color":"#FFF"},"bgcolor":"#4472C4"},{"align":"center","valign":"middle"}],
"customCols":{"0":{"width":27},"1":{"width":100},"len":100}
}
⚠️
table.columns与customRows同时存在时,columns仍不可为空数组[]creator 依赖columns来生成designerObj中的queryInfo.dbf(数据集绑定元数据)。 若columns: [],queryInfo.dbf不会写入,报表预览将完全空白(无数据),即使数据集已保存成功(records=N)。 正确做法:columns填入数据集的实际字段(哪怕只有一个),customRows再覆盖视觉布局;两者独立互不影响。
配置 E:含钻取(drilling)——无需任何额外 Python 脚本
drilling 和 linkages 键已内置于 creator:creator 自动调用 /link/saveAndEdit 并把 linkIds 回填到 cells / chart extData,全程只需一个 JSON 文件。
{
"action": "create",
"reportName": "主报表(三种钻取演示)",
"theme": "blue",
"layout": "chart_bottom",
"datasets": [
{
"dbCode": "sales", "dbChName": "学校汇总",
"dbDynSql": "SELECT leibie AS name, SUM(jine) AS value, '' AS type, 'jeecg.com' AS website FROM school_demo GROUP BY leibie ORDER BY value DESC",
"dbSource": "1161942757348524032", "isPage": "0"
}
],
"table": {
"datasetCode": "sales", "title": "学校汇总报表",
"columns": [
{"field": "name", "title": "类别(点击→子报表)", "width": 140},
{"field": "value", "title": "总金额", "width": 120},
{"field": "website", "title": "官网(点击→jeecg.com)","width": 180}
]
},
"chart": {
"datasetCode": "sales", "chartType": "bar.simple",
"title": "各类别总金额(点击柱子钻取)", "width": "560", "height": "360"
},
"drilling": [
{
"name": "钻取1-报表钻取报表",
"linkType": "0",
"targetReportId": "<detail_report_id>",
"ejectType": "0",
"source": {"type": "cell", "field": "name"},
"params": [
{"paramName": "leibie", "paramValue": "name", "fieldName": "name", "dbCode": "sales", "tableIndex": 0}
]
},
{
"name": "钻取2-图表钻取报表",
"linkType": "0",
"targetReportId": "<detail_report_id>",
"ejectType": "0",
"source": {"type": "chart"},
"params": [
{"paramName": "leibie", "paramValue": "name", "fieldName": "", "dbCode": "sales", "tableIndex": 0}
]
},
{
"name": "钻取3-网络钻取",
"linkType": "1",
"targetUrl": "http://jeecg.com",
"ejectType": "0",
"source": {"type": "cell", "field": "website"},
"params": [
{"paramName": "school", "paramValue": "name", "fieldName": "name", "dbCode": "sales", "tableIndex": 0}
]
}
]
}
drilling 字段说明:
| 字段 | 说明 |
|---|---|
linkType | "0" 报表钻取 / "1" 网络链接 |
targetReportId | linkType=0 时填目标报表 ID;linkType=1 时留空 |
targetUrl | linkType=1 时填外部 URL |
ejectType | "0" 新窗口 / "1" 当前窗口 |
source.type | "cell" 单元格触发 / "chart" 图表触发 |
source.field | cell 时填数据集字段名(creator 自动匹配含该字段的数据行单元格并回填 linkIds) |
params[].paramValue | 单元格钻取填字段名;图表钻取填 name(X轴)/ value(Y轴)/ seriesName |
跨报表钻取的顺序:先创建子报表拿到
detail_id,再把detail_id填入主报表 JSON 的targetReportId,两个 JSON 分别跑 CLI 即可,无需额外 Python 脚本。
配置 F:NoSQL 数据集(ES / MongoDB / Redis)
dbType 填 "es"/"mongo"/"mongodb"/"redis",creator 自动补 Calcite schema 前缀;dbSource 必填数据源 ID。
{"action":"create","reportName":"名称",
"datasets":[
{"dbCode":"esDs", "dbChName":"ES员工", "dbType":"es", "esIndex":"jmreport_test_employee","dbSource":"<ES数据源ID>", "isPage":"1"},
{"dbCode":"mongoDs", "dbChName":"Mongo订单", "dbType":"mongo", "mongoCollection":"orders", "dbSource":"<Mongo数据源ID>", "isPage":"1"},
{"dbCode":"redisDs", "dbChName":"Redis缓存", "dbType":"redis", "dbDynSql":"SELECT * FROM cache", "dbSource":"<Redis数据源ID>", "isPage":"0"}
],
"table":{"datasetCode":"esDs","title":"员工列表","columns":[
{"field":"emp_id","title":"工号","width":80},{"field":"emp_name","title":"姓名","width":100}
]}
}
| 字段 | 说明 |
|---|---|
esIndex | ES 专用简写,自动生成 SELECT * FROM es.{索引名};有它可省略 dbDynSql |
mongoCollection | MongoDB 专用简写,自动生成 SELECT * FROM mongo.{集合名} |
dbDynSql | 显式 SQL;ES/Mongo 缺 es./mongo. 前缀时自动补全;Redis 原样传入 |
ES 字段名若与 Calcite 保留字冲突(
position/date/type/value等),需反引号转义或改用esIndex简写(SELECT *无需列出字段名,天然规避)。详见 pitfalls.md §数据库数据源。
修改已有报表
# get_report → 改 design → base_save(**design 展开,get_report 返回的 design 是安全的)
designer, design = get_report(session, report_id)
design["rows"]["3"]["cells"]["1"]["text"] = "新值"
design["chartList"] = filled_charts # 如有图表回填,直接替换 chartList
session.request("/save", base_save(report_id, designer, **design))
# ↑ get_report 返回的 design 只含 base_save 接受的 key,**design 展开无冲突
# 注意:手动拼的 design dict 禁止 **展开,必须显式列出 rows/cols/styles/merges/chartList
⚠️ Bug 修复必须用 patch 脚本,禁止重跑创建脚本 任何情况下发现已有报表存在问题(无论是用户反馈还是 AI 自己发现),正确做法是写一个独立 patch 脚本(
get_report→ 改局部字段 →base_save回写;数据集错误用update_db),不得修改并重新执行创建脚本。重跑创建脚本会生成新 ID 的报表,原报表(含已配置的权限、分享链接、引用关系)不会被修复,且产生垃圾报表。
报表大类
积木报表分为两大类,默认为数据报表:
| 大类 | 说明 | designerObj 关键字段 |
|---|---|---|
| 数据报表(默认) | 展示型报表,从数据集查询渲染 | submitForm 不设置或为 0 |
| 填报报表 | 在报表上填写数据并提交到后端 | submitForm: 1 |
数据报表类型判断
| 用户描述 | 数据绑定 | 数据集配置 |
|---|---|---|
| 明细/列表 | #{db.field} | isList:"1" isPage:"1" |
| 套打/单条 | ${db.field} | isList:"0" isPage:"0" |
| 按XX分组 | #{db.group(field)} | isPage:"0" |
| 交叉表 | #{db.groupRight(field)} + #{db.dynamic(field)} | isPage:"0" |
单元格绑定字段名获取
写 #{dbCode.fieldName} 绑定前,不得凭 SQL 别名手写字段名。
直接用 parse_sql 返回的 fieldName(推荐,最快):
fl = parse_sql(session, sql)
fields = [f["fieldName"] for f in fl]
# MySQL 将所有别名转小写,AS totalAmount → totalamount,直接用即可
/field/tree/{reportId}是备用方案(需报表先/save存在才能调),parse_sql已返回同样的真实字段名,无需多一次调用。
性能优化(单报表推荐模板)
单报表单数据集场景,以下 3-step 流程总 HTTP ≤ 5 次(含数据源已存在的 1 次)。实测端到端 ~0.8s。
from jimureport_utils import (
Session, gen_id, make_designer, make_styles, base_save, report_urls,
ensure_datasource, parse_and_save_dataset, # ← 推荐新路径
)
session = Session(BASE_URL, TOKEN)
# ① 确保数据源存在(1-2 HTTP,已存在时只 1 次)
ds_id = ensure_datasource(
session, name="mongodb", db_type="mongodb",
db_url="<db_host>:27017/<db_name>",
db_username="qqyun", db_password="qqyun188"
)
# ② 预生成 report_id(客户端,0 HTTP)
report_id = gen_id()
# ③ parse_sql + saveDb 组合(2 HTTP,report_id 允许尚不存在)
sql = f"select * from mongo.{COLLECTION}"
field_list, db_id = parse_and_save_dataset(
session, report_id, DB_CODE, "中文名", sql,
db_source=ds_id, is_list="1", is_page="1"
)
# ④ 构建 rows/cols/styles 后,首次 /save —— 一步创建报表 + 写入布局(1 HTTP)
designer = make_designer(report_id, REPORT_NAME)
session.request("/save", base_save(report_id, designer,
rows=rows, cols=cols, styles=styles, merges=merges, chartList=[]))
| 阶段 | 原来 HTTP | 现在 HTTP | 说明 |
|---|---|---|---|
| 数据源 | 3(查+存+再查) | 1-2 | ensure_datasource 合并 |
| 首次占位 /save | 1 | 0 | parse_and_save_dataset 直接对 orphan report_id 调 saveDb |
| 解析 SQL | 1 | 1 | — |
| 保存数据集 | 1 | (合并在 ③) | — |
| 最终 /save | 1 | 1 | 首次创建 + 写入布局 |
| 合计(已存在数据源) | 7 | 4 | 省 3 次 HTTP |
关键原理:saveDb 接受尚不存在于服务端的
report_id(orphan),后续 /save 以此 id 首次创建报表时,数据集会正确绑定。实测验证通过。addDataSource返回result: true(不返回 id),新建后必须再查一次;/initDataSource无签名比/getDataSourceByPage快。
仍可用的旧路径(保留兼容)
parallel_init_and_parse 已不推荐但保留 —— 旧脚本无需修改。新脚本一律用 parse_and_save_dataset。
API 数据集快速路径(2 HTTP,实测 ~0.5s)
API 数据集无需 queryFieldBySql,字段手动定义,整个流程只需 2 次 HTTP:
from jimureport_utils import Session, gen_id, make_designer, base_save, save_db
session = Session(BASE_URL, TOKEN)
# ① 客户端生成 report_id(0 HTTP)
report_id = gen_id()
# ② saveDb:orphan report_id 合法,直接绑定(1 HTTP)
field_list = [
{"fieldName": "f1", "fieldText": "字段1", "widgetType": "String", "orderNum": 0, "tableIndex": 0, "extJson": "", "dictCode": ""},
# ... 其余字段
]
save_db(session, report_id, DB_CODE, "数据集名称",
API_URL, field_list,
db_type="1", api_url=API_URL, api_method="0",
is_list="1", is_page="0")
# ③ /save:首次创建报表 + 完整设计一步完成(1 HTTP)
designer = make_designer(report_id, REPORT_NAME)
session.request("/save", base_save(
report_id, designer,
rows=rows, cols=cols, styles=styles, merges=merges, chartList=[],
isGroup=True, groupField=f"{DB_CODE}.group_field", # 交叉/分组报表需要
))
| 旧流程(3 HTTP) | 新流程(2 HTTP) |
|---|---|
| POST /save 空报表 → 取 report_id | gen_id() 本地生成(0 HTTP) |
| POST /saveDb | POST /saveDb(同) |
| POST /save 完整设计 | POST /save 完整设计(同) |
实测:区域省份销售额交叉报表,3 步 ~3s → 2 步 ~0.5s(2026-04-22 验证)。 适用场景:所有 API 数据集报表(交叉表、分组表、明细表均可)。
MongoDB / NoSQL 数据源特别说明
testConnection仅检测 TCP 连通,不验证账号密码。它返回 success 不代表凭证正确。- 真实鉴权发生在
queryFieldBySql/ 预览时。凭证错会在这两步报Exception authenticating。 - 禁止在创建脚本里尝试多种格式(标准分离 / 连接串 / 多 authSource)的试错循环 —— 白白浪费 3-6 秒。只试用户给的一种,失败立刻报错让用户检查 MongoDB 服务端
db.getUsers()。
性能优化(多数据集 / 多报表场景)
核心原则:能并行的全部并行,消灭串行等待。
from jimureport_utils import parallel_parse_sqls, parallel_save_dbs, parallel_create_links
from concurrent.futures import ThreadPoolExecutor
# ① 并行解析所有 SQL(一轮完成)
fl_a, fl_b, fl_c = parallel_parse_sqls(session, [
{"sql": sql_a}, {"sql": sql_b}, {"sql": sql_c},
])
# ② 并行保存所有数据集(一轮完成)
db_id_a, db_id_b, db_id_c = parallel_save_dbs(session, [
{"report_id": rid, "db_code": "dsA", "sql": sql_a, "field_list": fl_a, ...},
{"report_id": rid, "db_code": "dsB", "sql": sql_b, "field_list": fl_b, ...},
{"report_id": rid, "db_code": "dsC", "sql": sql_c, "field_list": fl_c, ...},
])
# ③ 并行创建所有钻取/联动(一轮完成)
link1, link2, link3 = parallel_create_links(session, [
{"report_id": rid, "link_name": "钻取1", "link_type": "0", ...},
{"report_id": rid, "link_name": "钻取2", "link_type": "0", ...},
{"report_id": rid, "link_name": "联动1", "link_type": "2", ...},
])
# ④ 多张报表最终 /save 并行
with ThreadPoolExecutor(max_workers=2) as ex:
f1 = ex.submit(lambda: session.request("/save", base_save(rid1, d1, ...)))
f2 = ex.submit(lambda: session.request("/save", base_save(rid2, d2, ...)))
f1.result(); f2.result()
| 优化点 | 节省 |
|---|---|
parse_sql 直接取字段名,跳过 first_save + field/tree | 每张报表省 2 次请求 |
parallel_parse_sqls | N 次串行 → 1 轮并行 |
parallel_save_dbs | N 次串行 → 1 轮并行 |
parallel_create_links | N 次串行 → 1 轮并行 |
多报表 /save 并行 | M 次串行 → 1 轮并行 |
行列索引规则
- 全部 0-indexed,A列(col0)留空,数据从 col1(B列)开始
- merge:
[extraRows, extraCols],0=只占自身 - merges 用 Excel 记法:
"B2:F2"(UI行号 = code行号+1)
分组汇总
| 用户说法 | 实现 |
|---|---|
| "合计行" | 数据行下方加 =SUM(列号) |
| "分组小计" | subtotal:"groupField" + funcname:"SUM" + subtotalText:"小计" |
| 只说"分组" | 只用 group() + aggregate:"group" |
🚨 分组列开
subtotal:"groupField"时,所有数值列必须默认aggregate:"select"+funcname:"SUM"——否则小计/合计行的数值单元格全部空白,UI 上"显示了合计标签 + 数值列空白"是绝对不允许的折中状态。要嘛不显示合计行(分组列改subtotal:"-1"、subtotalText:""),要嘛数值列全部默认求和。AI 不得只搬模板里"分组列带 subtotal、数值列裸 text"的写法。
funcname 聚合函数值(⚠️ 必须严格使用以下字符串,写错则不生效)
| 用户需求 | funcname 值 |
|---|---|
| 合计 / 求和 | "SUM" |
| 平均 / 平均值 | "AVERAGE" (❌ 不是 "AVG") |
| 最大值 | "MAX" |
| 最小值 | "MIN" |
| 计数 | "COUNT" |
| 不聚合(分组列占位) | "-1" |
分组列 vs 聚合列属性对比
| 属性 | 分组列(group) | 聚合列(select) |
|---|---|---|
aggregate | "group" | "select" |
subtotal | "groupField" | "-1" |
funcname | "-1" | "SUM" / "AVERAGE" / "MAX" / "MIN" / "COUNT" |
subtotalText | 小计行标签文字 | 小计行标签文字 |
查询参数(paramList)
含查询控件时读 references/query-params.md § 0(含 SQL FreeMarker 条件、widgetType/searchMode 对照表、日期范围拆分规则)。
字段查询 vs 报表参数查询(必读规则)
| 字段查询(fieldList searchFlag) | 报表参数查询(paramList) | |
|---|---|---|
| SQL | 纯 SELECT,不加 WHERE / FreeMarker 条件 | 必须加 <#if isNotEmpty(x)>... 条件 |
| fieldList | searchFlag=1 + searchMode + dictCode 等 | 无需设置 searchFlag |
| paramList | 不需要 | 必须配置 paramList |
| querySetting | 无需设置 | 按需配置 izOpenQueryBar |
核心规则:字段查询时 JimuReport 引擎自动处理过滤,SQL 保持纯净;只有使用报表参数时才在 SQL 中添加 FreeMarker WHERE 条件。两种方式不能混用。
样式规范(必读约定,无需用户提醒)
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 230
- Forks
- 67
- Last commit
- Jul 2026
Advanced
- Catalog kind
- skill
- Gateway key
jimureport- Source
- github.com/jeecgboot/skills