mcpbeat

Jimureport

jeecgboot/jimureport

积木报表生成器 — 自然语言描述报表需求或提供截图,自动生成积木报表(支持数据报表、打印报表、分组报表、循环报表、数据填报等全类型)。Use when user says "积木报表", "jmreport", "Excel报表", "数据填报", "可视化报表", "打印报表", "分组报表", "循环报表", "按照截图生成报表", "创建积木报表", "做一个可视化报表", "积木设计器", "create jimureport", "visual report". Also triggers when user describes report requirements involving Excel-like layouts, data binding with #{}, or multi-sheet reports, or provides a screenshot to generate a report.

249k tokens
context cost
the whole folder, loaded on every use
71
files
ships runnable scripts
0
copies elsewhere
how many repositories repackaged it
209
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/jeecgboot/skills --skill jimureport

What comes with it

633 227 bytes besides the instruction
examples/cell-format-display-styles.md
examples/chart-linkage.md
examples/horizontal-group.md
examples/master-sub-table.md
examples/multi-level-header-advanced.md
examples/multi-level-header-api.md
examples/multi-level-header-basic.md
examples/multi-level-header.md
examples/multi-source-table.md
examples/object-dataset.md
examples/param-query.md
examples/report-drilling.md
examples/vertical-group-custom-sort.md
examples/vertical-group-subtotal-example.md
references/cell-config.md
references/chart-canonical-configs.md
references/chart-components.md
references/chart-echarts-props.md
references/chart-echarts-templates.md
references/chart-types-quickref.md
references/dataset-advanced-datasource.md
references/dataset-advanced-file.md
references/dataset-advanced-shared.md
references/dataset-advanced.md
references/dataset-core.md
references/dataset-types.md
references/expressions.md
references/fillform.md
references/horizontal-grouping.md
references/import-export.md
references/json-dataset-cells.md
references/loopblock-grouping.md
references/mastersub-report.md
references/misc-config.md
references/mock-apis.md
references/multi-sheet.md
references/pitfalls.md
references/query-controls.md
references/query-params.md
references/quick-ref.md

What it tells the agent to use

found in the instruction text
Bash runs shell commands — read the instruction before connecting

The instruction itself

20 sections, as written by the author

积木报表 AI 生成器

> 不涉及「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 / 用户项目)
  • 硬编码 /tmpC:\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_field INSERT 并发引发 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.dbfcolumns 为空则不写入绑定元数据,报表预览完全无数据(即使数据集 records=N)。customRows 只控制视觉布局,columns 必须填数据集的实际字段(至少一个)|

| 用户未指定的可选参数自行填值(如 customEditConf.eventParamsfreezerpbarbackground 等) | 只写用户明确给出的字段,其余可选参数一律省略,不得自造默认值 |

| 调 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.jsonpython 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.loadUnexpected 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.columnscustomRows 同时存在时,columns 仍不可为空数组 []

> creator 依赖 columns 来生成 designerObj 中的 queryInfo.dbf(数据集绑定元数据)。

> 若 columns: []queryInfo.dbf 不会写入,报表预览将完全空白(无数据),即使数据集已保存成功(records=N)。

> 正确做法columns 填入数据集的实际字段(哪怕只有一个),customRows 再覆盖视觉布局;两者独立互不影响。

配置 E:含钻取(drilling)——无需任何额外 Python 脚本

drillinglinkages 键已内置于 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 条件。两种方式不能混用

样式规范(必读约定,无需用户提醒)

  • 所有表格报表必须带 border:优先用 from jimureport_utils import make_styles 的 5 种预置样式(索引 0-4)
  • col0 始终留白 30px 不加边框:标题行从 col1 开始合并(merges 写 B1:F1 不含 A 列)。make_styles() 预置样式 0-4 均含边框,col0 必须单独追加无边框样式
  styles = make_styles()
  styles.append({"align": "center"})  # index 5:col0 专用,无边框
  # 所有行的 col0 统一用 style: 5
  "0": {"text": "", "style": 5}
  • 自定义样式时 border 必须嵌套(不能 ** 展开到顶层),color(文字色)与 font 平级放 style 顶层
  • ⚠️ 凡是自定义 styles 数组或修改单元格配置,必须先读 references/styling.md,不得凭经验猜属性层级(color 在顶层而非 font 内是高频踩坑点,引擎静默忽略错误写法)

完整规范 + col0 留白模板代码 → references/styling.md

图表类型速查

含图表时读 references/chart-types-quickref.md(系统名称 → chartType 对照 + series 字段取值 + 地图数据语义规则)。

完整 ECharts 配置模板 → references/chart-echarts-templates.md

执行速度规范(3分钟内完成)

开始前强制检查(每次执行前过一遍):

  • ✅ 数据源ID已从 memory 读取,未调 initDataSource(除非 memory 确实没有)
  • ✅ 数据库驱动用 pymysql,不是 mysql.connector(该项目环境只有 pymysql)
  • ✅ 样式用 customStyles 时,border 格式从已读的示例文件中复用,不额外读 styling.md

文件读取按场景取最小集,只读表中指定的文件,不得额外读其他文件

| 场景 | 读取文件(精确,不多读) |

|------|---------|

| 普通表格 / JSON数据集(无分组) | 只读 references/pitfalls.md(遇到报错按症状索引跳节,用 offset 只读对应节) |

| 纵向分组(含小计/聚合) | references/pitfalls.md §数据集保存(offset=15, limit=40)+ examples/vertical-group-subtotal-example.md |

| 纵向分组 + 自定义排序(textOrders) | 只读 examples/vertical-group-custom-sort.mdAPI 数据集场景见下方「已知坑点」中的 API+mock+textOrders 条目 |

| customGroup(自定义横向分组,每条记录→一列) | 只读 references/horizontal-grouping.md(§5.3.2 已内嵌完整 JSON 模板 + 所有关键属性;无需再读 examples/horizontal-group.md) |

| groupRight + dynamic(交叉表,列头横向动态展开) | references/horizontal-grouping.md + examples/horizontal-group.md 示例1 |

| groupRight + dynamic + 跟随分组扩展统计行(rightFollowExten) | 只读 references/horizontal-grouping.md §「跟随分组扩展统计行」节 |

| 含条码/二维码 / 补全空白行 / 自定义编辑单元格 | 只读 references/cell-config.md |

| 含查询控件(paramList) | references/query-params.md § 0 + examples/param-query.md |

| 含图表(无联动/钻取) | references/chart-types-quickref.md(选 chartType)→ references/chart-canonical-configs.md + references/chart-echarts-templates.md图表显示条数/排序/数据过滤需求必须先查 references/chart-echarts-props.md §9,禁止改数据集类型或切片数据 |

| 含图表联动 | 只读 examples/chart-linkage.md(已含完整 extData/linkType=2/paramValue="name"/parameter 结构,不需要再读 canonical-configs);chartType 不确定时才额外读 references/chart-types-quickref.md(共 ≤2 个文件)。严禁读 pitfalls.md:该文件 26742 tokens 必超限报错,联动场景也不需要它。⚠️ API 数据集图表联动复用场景直接用 scripts/create_chart_linkage.pychart_linkage() 的 linkages 参数不生效,必须 patch(见「已知坑点」) |

| 含报表钻取 | references/report-drilling.md + examples/report-drilling.md |

| 含表达式 | 只读 references/expressions.md |

| 数据填报报表 | 只读 references/fillform.md |

| 套打报表${db.field} 单条绑定,背景图覆盖整张表单) | 只读 references/misc-config.md §套打imgList配置(完整代码模板);坑点摘要见 references/pitfalls.md §套打背景图(offset=292, limit=10) |

| 主子报表(套打式或循环块式,子表纵向展开,eri≥40) | references/mastersub-report.md + examples/master-sub-table.md |

| 分栏报表(loopTime 横向循环,eri=卡片实际行数,禁止缓冲行) | 只读 references/loopblock-grouping.md §5(offset=106) |

| 分版(并列独立展开,zonedEdition/zonedEditionList) | 只读 references/loopblock-grouping.md §5.5(offset=219,limit=60) |

| 多源报表(多个数据集来自不同表且有共同关联字段;或用户要求同一行同时绑定主子两表字段) | 只读 examples/multi-source-table.md |

| 样式细节(边框/留白/优先级) | 只读 references/styling.md |

| 自定义 styles 数组(含 color / bgcolor / font 任意属性) | 必读 references/styling.md(color 文字色必须放 style 顶层,不能放 font 内;违反则静默失效) |

| 修改已有报表单元格配置(style / merge / text / direction 等) | 必读 references/styling.md,确认属性层级后再动手 |

| 文件数据集(Excel/CSV/JSON,含用户未提供文件需自动创建的场景) | 只读 references/dataset-advanced-file.md(§前置自动创建 + §4.0 上传流程) |

| 数据源管理(MongoDB/Redis/MySQL等 addDataSource) | 只读 references/dataset-advanced.md § 1 |

| 报表组合(创建/更新/删除) | 只读 references/report-group.md |

| 有参考报表 ID | get_report 照搬,跳过所有文件 |

| 需要示例数据/字段未确定 | 只读 references/mock-apis.md 选接口 |

> pitfalls.md 读取规则:仅「普通表格/无分组」行要求全节读取;其他场景遇到运行时报错时,按 pitfalls.md 顶部症状索引找对应节,用 Read(offset=N, limit=40) 只读该节(不要全读,9 个节每节约 20 行)。⚠️ pitfalls.md 共 26742 tokens,任何场景都禁止整文件读取(必超 25000 token 限制报错);联动/图表场景更是完全不需要读它。

> API 数据集 + customGroup 快速路径(用户提供了 API URL + token 时,不超过 3 步):

> 1. 读 references/horizontal-grouping.md §5.3.2 拿到模板

> 2. 在脚本里调 parse_api(session, api_url) 获取字段名(1 次 HTTP,合并进脚本,不单独执行

> 3. 替换模板中的字段名 → PowerShell 执行脚本

自定义图表脚本规范(含 SQL/API 数据集)

> 写自定义图表脚本前必须先确认以下规范,杜绝反复调试。

# ① BASE_URL 必须含 /jmreport
session = Session("http://host:port/jmreport", TOKEN)

# ② make_designer:首次 /save 时 report_id 传空字符串
designer = make_designer("", REPORT_NAME)
# !! 拿到 report_id 后,最终保存前必须重新构造 designer(带真实 id),否则单元格数据不会写入 !!
# designer = make_designer(report_id, REPORT_NAME)  ← 最终保存前用这个

# ③ rows 必须带 "len" key
rows = {"len": 200, "1": {"height": 25, "cells": {}}, ...}

# ④ base_save 前两个参数是位置参数,不可用关键字
resp = session.request("/save", base_save("", designer, rows=rows, cols=cols, styles=styles, merges=[], chartList=[]))

# ⑤ 首次 /save 返回的 result 是 dict,取 id
report_id = resp["result"]["id"]

# ⑥ save_db 位置参数顺序(第4个是 db_name,不是 db_ch_name)
db_id = save_db(session, report_id, "db_code", "中文名", sql_or_empty, field_list,
                db_type="1", api_url=mock_url, ...)   # API 数据集
# ⚠️ JSON数据集(db_type="3")json_data 必须包裹 {"data":[...]},不能传裸数组:
#    json_data=json.dumps({"data": raw_list}, ensure_ascii=False)

# ⑦ 图表回填:
#   SQL  → parallel_fill_charts(session, chart_list)
#   API  → parallel_fill_charts(session, chart_list)
#   JSON → parallel_fill_charts(session, chart_list, json_records_map={dbCode: records, ...})
#          JSON 数据集无服务端查询接口,必须通过 json_records_map 传入原始 records 本地填充。
#          不传 json_records_map 则 JSON 数据集图表 config 保持空 data:[],预览显示空白。
# 完整代码见 references/chart-echarts-templates.md § SQL/API 图表数据回填

# ⑧ 最终保存:get_report 返回的 design 可直接 **展开
designer2, design2 = get_report(session, report_id)
design2["chartList"] = filled_charts
session.request("/save", base_save(report_id, designer2, **design2))

已知坑点(必读)

> 写脚本前先 Read references/pitfalls.md,避免踩坑重试浪费时间。

> 写循环块报表前必须先 Read references/loopblock-grouping.md,里面有完整配置模板,禁止从头自己写。

> ⚠️ API 数据集有 paramList 时,apiUrl 末尾必须加 ?paramName=${paramName},否则联动/钻取参数传不进去。多参数用 & 连接。写完 save_db 后立即目视检查 api_url 字符串含占位符。

> ⚠️ 交叉表(groupRight+dynamic)底部跟随分组扩展统计行:必须用独立静态行 + rightFollowExten:"follow" + Excel公式(如 =MAX(G6)),dynamic 单元格的 funcname 必须设为 "-1"禁止设为 "MAX"/"MIN"/"SUM"——那不会生成跟随横向扩展的统计行。非分组维度字段(如姓名、性别)用 aggregate:"select",不用 group()。详见 references/horizontal-grouping.md §「跟随分组扩展统计行」。

> ⚠️ 样式 patch 禁止跳过 col "0":creator 生成的分组/标准报表,标题格可能在 rows["1"]["cells"]["0"](从 col 0 开始合并所有列)。patch 脚本遍历 cells 时不得 if ck == '0': continue,否则标题样式永远不会被更新。正确做法:遍历所有 col,判断是否有 merge 且 text 不以 #{ 开头即为标题格,直接更新 style。

> ⚠️ mock 路径冲突导致重试create_mock() 遇到路径已存在会报 RuntimeError。mock_path 末尾必须用时间戳import time; mock_path=f"/xxx_{int(time.time())}")而非固定日期,确保唯一。只有用户明确说"更新已有接口"时才可复用原路径。

> ✅ API 数据集 dbType 修正与图表 dataType 已内置 creator(2026-05-23)jimureport_creator.py 现在在保存 API 数据集(dbType:"1")后自动调用 update_db 修正 dbType/apiUrl,并在 /save 前按数据集实际类型(sql/api/json/javabean/files)自动设置图表 extData 的 dataType无需再手动 patch,直接写 JSON config 即可。支持所有类型:SQL→"sql"、API→"api"、JSON→"json"、JavaBean→"javabean"、文件→"files"、共享→"sql"。

> ⚠️ 条码/二维码必须用 display 内联,禁止 displayConfig 浮层:单元格只写 "display":"barcode" / "display":"qrcode"不加 "config" 字段,不在 payload 里传 displayConfigdisplayConfig + config 方式会创建 floating overlay 叠加层,覆盖表头和其他行,布局完全错乱(2026-05-22 实测)。

> ⚠️ create + patch 必须合并成一次 PowerShell 调用:需要在创建报表后立即 patch(如添加条码/二维码)时,不得分两次 PowerShell 调用。正确做法是在同一个 Python 脚本中:调 api_dataset() → 解析 stdout 取 report_id → 立刻调 get_report + base_save 完成 patch。分两步会把秒级操作拖成分钟级,且中间报错需人工干预。

> ⚠️ report_id 解析正则用 report_id= 锚定,不要靠格式猜jimureport_creator.py stdout 固定输出 report_id=<id> 格式,直接用 re.search(r'report_id[=:\s]+(\S+)', stdout) 提取,比按 ID 长度/字符集猜格式更可靠。

> ⚠️ api_dataset() 可能返回 CompletedProcess 而非 report_id 字符串:调用后必须做兼容判断,否则后续 get_report(session, report_id) 会把整个对象当 ID 发请求导致 400 报错,被迫拆成第二次 PowerShell 执行(秒级变分钟级)。标准写法:

> `python

> result = api_dataset(...)

> if hasattr(result, 'stdout'):

> import re

> report_id = re.search(r'report_id[=:\s]+(\S+)', result.stdout).group(1)

> else:

> report_id = result

> `

> ⚠️ 禁止 grep 函数签名api_datasetrun_configDEFAULT_BASE_URL 等参数格式在 SKILL.md 已有完整示例,不需要 grep/read jimureport_gen.py 确认。grep 函数签名每次浪费 3-5 次 tool call,直接信任文档。

> ⚠️ chart_linkage()linkages 参数被静默忽略jimureport_gen.chart_linkage() 接受 linkages 参数但不处理它——函数只创建报表布局,不调用 /link/saveAndEdit,linkIds 也不会写入 cells/chartList。正确做法:chart_linkage() 创建报表后,必须补一个 patch 步骤:①调 /link/saveAndEdit 创建联动记录拿到 link_id;②get_report 取设计;③向 cell 写 linkIds+display:"link"、向触发图表 extData 写 linkIds;④base_save 回写。已封装为 scripts/create_chart_linkage.py,直接调用。create_chart_linkage.py 是提供的工具脚本,直接调用没问题;但若中途失败(如 list_mocks() 找不到新建接口),禁止重新写一个替代它的全流程 Python 脚本——正确做法是:用已知接口 ID 直接补调 set_advmock (最小 PowerShell inline),报表结构改走 JSON config + creator CLI,联动 patch 另写最小 PowerShell inline,两部分严格分开。

> ⚠️ chart_linkage() 传给 creator 的数据集键必须 camelCase:datasets 里的键用 dbCode/apiUrl/fieldList/paramList,table/charts 里的键用 datasetCode/chartType。用 db_code/api_url/field_list 等 snake_case 会导致 KeyError: 'dbCode'(creator 直接读 JSON 字段,不做转换)。

> ⚠️ yapi_mock 不暴露 session 对象,用 list_mocks() 获取接口 ID_session/session 等属性均不存在,不要花时间用 inspect.getmembers 找 session。正确做法:init_yapi() 登录后直接 from yapi_mock import list_mocks; ifaces = list_mocks() 获取接口列表,再按 path 匹配找 _id

> ⚠️ mastersub() 正确签名是 (token, name, master_sql, sub_sql, master_cols, sub_cols, *, link_field, sub_param, db_source)sub_sql 在第4位,master_cols 在第5位,顺序不可对调。调换后 creator 会把 master_cols 列表当 SQL 传给 queryFieldBySql,报「签名校验失败」。

> ⚠️ mastersub() 只生成套打式布局,不支持循环块式(主子循环):调用后 creator 报「未配置 table 或 chart」是因为 mastersub 的 JSON 模板走 ${} 单值绑定路径,无法生成 loopBlockList。正确做法:放弃 mastersub(),改用以下 3 步:①parse_and_save_dataset 保存主子两个数据集;②手动构建 rows(所有单元格加 "loopBlock":1,包括标题行的合并空单元格,缓冲行 eri≥40);③base_save 传入 loopBlockList=[{"sci":1,"eci":N,"sri":0,"eri":40,"index":1,"db":MAIN_CODE}],再调 /link/saveAndEdit linkType="4"。完整代码模板见 references/mastersub-report.md §二。

> ⚠️ 主子循环报表 JSON config 不支持 loopBlockList,creator 会报「未配置 table 或 chart」:JSON config + creator CLI 路径对循环块式主子报表无效。正确做法分两步:①先跑 JSON config 让 creator 完成数据集保存(datasets 部分正常执行,layout 步骤报错可忽略,report_id 已生成);②再用最小 PowerShell inline 调 base_save(含手动 rows + loopBlockList)+ /link/saveAndEdit。两步合并成一次 PowerShell 调用完成。

> ⚠️ 所有图表 config 的 xAxis/yAxis 必须是对象 {...},禁止数组 [{...}](即使只有一个轴):数组格式虽能正常渲染(ECharts 接受),但会让设计器「数据」面板的 getChartDataList 在读 options['xAxis'].data 时取到 undefinedCannot read properties of undefined (reading 'length'),导致 selectCharthandleChartExtData 之前崩溃 → 图表「数据类型」下拉框停留在默认值「Api数据集」,看起来像 JSON/SQL 数据集被当成了 API 数据集。jimureport_creator.build_echarts_config 已于 2026-05-26 修正为对象格式;手写 config 或 patch 时同样必须用对象。修复已有报表:get_report → 对每个 chart 把 cfg['xAxis']=cfg['xAxis'][0](yAxis 同)→ base_save 回写。

> ⚠️ 图表联动中「触发图表」的 xAxis 必须是对象格式,否则点击无法触发联动chart_entry() 生成的柱状图 ECharts config 里 xAxis 是数组格式 [{type:'category',data:[]}](标准多轴格式),但前端 refreshChart() 更新 X 轴标签时写的是 chartOptions['xAxis']['data'] = names——对数组执行此操作只是给数组对象加了个自定义属性,xAxis[0].data 仍为空。结果:点击柱子时 ECharts click event 的 event.name = "",联动参数检查 if(param["name"]) 为 false,目标图表永远不刷新。修复方法:报表创建完成后,对每个「充当联动触发源」的柱状图(extData 里有 linkIds),把 config 里的 xAxis/yAxis 从数组改为对象:

> `python

> import json

> designer, design = get_report(session, report_id)

> for c in design.get("chartList", []):

> ext = c.get("extData", {})

> if ext.get("linkIds"): # 该图表是联动触发源

> cfg = json.loads(c["config"])

> if isinstance(cfg.get("xAxis"), list) and cfg["xAxis"]:

> cfg["xAxis"] = cfg["xAxis"][0]

> if isinstance(cfg.get("yAxis"), list) and cfg["yAxis"]:

> cfg["yAxis"] = cfg["yAxis"][0]

> c["config"] = json.dumps(cfg, ensure_ascii=False)

> session.request("/save", base_save(report_id, designer, **design))

> `

> 此 patch 应紧接 linkIds 回填步骤之后执行,合并在同一个 PowerShell inline 里。

> ⚠️ API 数据集 + mock + textOrders 自定义排序的正确流程:报表结构必须在 JSON config,不得把 mock 创建 + 报表创建 + patch 包进同一个自定义 .py 脚本。正确四步(合并成一次 PowerShell 调用):①最小 inline 脚本(只含 init_yapi + create_mock)→ 捕获 mockUrl;②写 JSON config(含 mockUrl,columns 用标准属性,不含 textOrders);③运行 jimureport_creator.py CLI → 捕获 report_id;④最小 inline 脚本(get_report → 遍历 cells 加 textOrdersbase_save)。②③④ 在同一次 PowerShell 调用内顺序执行,①可拆出或合并均可。

>

> ⚠️ 多文件数据集(dbType=5)必须拆成两步,禁止合并成一个自定义 Python 脚本:文件上传 + saveDb 是 JSON config 无法表达的前置步骤,但报表布局(rows/cols/styles)仍必须走 JSON + creator CLI。正确拆分:

> - Step 1(最小 PowerShell inline):逐个上传文件(session.upload('/source/datasource/files/add', ...))→ GET /source/datasource/files/get 获取 datasource_id 和表名 → 构建 JOIN SQL → queryFieldBySql 解析字段 → saveDb 第一步(无 id)→ 拿 db_idsaveDb 第二步(带 id,fieldList 才真正持久化) → 输出 report_iddbCode

> - Step 2(JSON config + creator CLI):用 Step 1 的 report_iddbCode 写 JSON layout → python jimureport_creator.py --config xxx.json

>

> Calcite 保留字处理typecount 作为 Excel 列名时,SQL 中必须用反引号转义并起别名(如 b.type AS prod_type a.count AS sales_count );双引号(b."type")在 Calcite 中会被解析为字符串字面量导致 SQL语法错误

>

> ⚠️ 文件数据集 Excel/CSV 必须包含至少一行数据/source/dataset/files/single/save 需要解析文件推断字段类型,纯表头(只有 row1)会返回 {"success":false,"message":"解析失败,请检查文件格式符合要求"}。自动创建文件时,row1=表头,row2=一行合理的示例数据(可以是假数据),示例数据不影响报表展示。

>

> ⚠️ 建表脚本必须显式列名 INSERT,且先 DROP 再建INSERT INTO t VALUES (...) 不写列名时,若目标表已存在且列数/顺序不同,MySQL 报 Column count doesn't match,被迫再发一次 PowerShell 调用重试,直接多一个来回(理想 2 次 → 实际 3 次)。正确写法:始终用 INSERT INTO t (col1, col2, ...) VALUES (...) 显式列名,建表脚本开头先 DROP TABLE IF EXISTS,保证一次成功:

> `python

> cur.execute("DROP TABLE IF EXISTS my_table")

> cur.execute("CREATE TABLE my_table (id VARCHAR(32) PRIMARY KEY, ...)")

> cur.execute("INSERT INTO my_table (id, name, ...) VALUES (%s, %s, ...)", row)

> `

>

> ⚠️ 图表 extData 必须同时有 idchartId,缺 id 会导致图表不渲染:creator 生成的图表 extData 有时只写了 chartId 而漏掉 id,两者均须等于 layer_id。patch 已有报表时若发现 extData 没有 id 字段,必须补填:ext['id'] = c['layer_id']。症状:预览页图表区域空白,无报错或报 Cannot read properties of undefined

>

> ⚠️ pie.simple 的 extData.series 必须为 "",错写 "type" 会报 TypeError:单系列饼图 series 字段必须是空字符串,creator 偶尔会错误地设为 "type"。前端初始化时会按 series 字段名分组,数据里没有 type 列则 undefined[0]TypeError: Cannot read properties of undefined (reading '0')。patch 时检查并修正:ext['series'] = ''

>

> ⚠️ patch 图表背景色需同时设两处:图表容器背景是 chartList[n]['backgroud'](框架源码拼写错误,实际字段名就是 backgroud,不是 background),ECharts 画布背景是 config['backgroundColor']。只设其中一处效果不完整。正确写法:

> `python

> c['backgroud'] = {'enabled': True, 'color': '#e8f5e9', 'image': ''}

> cfg = json.loads(c['config'])

> cfg['backgroundColor'] = '#e8f5e9'

> c['config'] = json.dumps(cfg, ensure_ascii=False)

> `

报表创建方式对比(必读)

积木报表有三种创建层级,优先从上往下选

| 层级 | 方式 | 适用场景 |

|------|------|---------|

| ① JSON + CLI(唯一推荐) | 写 xxx.jsonpython jimureport_creator.py --api-base URL --token TOKEN --config xxx.json | 所有报表,JSON 文件可见、可复现 |

| ② Python 封装函数 | from jimureport_gen import group, api_dataset, ... | 内部自动生成 JSON + 调 CLI,需要同时建 YApi mock 时用此方式 |

> ⛔ 禁止写自定义 Python 脚本创建报表(用户 2026-05-22 再次明确:以后只生成JSON)。报表结构必须写 JSON 配置文件,API dataset + mock 走 jimureport_gen.api_dataset()(内部也是 JSON+CLI)。建表、/link/saveAndEdit 等 JSON 无法表达的步骤写成最小独立 PowerShell inline,不得把报表创建逻辑混进 Python 脚本。

> ⚠️ 典型错误场景3:图表联动场景(table + 柱图 + 饼图),因 list_mocks() 中途失败,AI 把 mock 创建 + chart_linkage() 调用 + linkIds patch 全写进一个 chart_linkage_patch.py 自定义脚本——严重违规。正确做法:①mock 未设 advmock 时,单独一行 PowerShell inline 调 set_advmock(已知ID);②报表结构写 JSON config → creator CLI;③联动 patch(/link/saveAndEdit + get_report + base_save)写成另一个最小 PowerShell inline;三步各自独立,不混入同一个大 Python 脚本。

jimureport_gen.py 函数速查

jimureport_gen.pyjimureport_creator.py 的 Python 封装,内部自动写 JSON + 调 CLI。

import sys
sys.path.insert(0, r'<skill_base_dir>/scripts')
from jimureport_gen import group, api_dataset, standard  # 按需 import

| 函数 | 报表类型 | 关键参数 |

|------|---------|---------|

| standard(token, name, sql, columns) | 普通明细表 | db_source, is_page, chart |

| group(token, name, sql, columns) | 纵向分组/小计 | 分组列加 group=True, subtotalText="合计" |

| api_dataset(token, name, field_list, columns, *, api_url=...) | API 数据集(已有URL) | report_type="group" 可改为分组模式 |

| api_dataset(token, name, field_list, columns, *, mock_data=[...], mock_path=f"/xxx_{int(time.time())}") | API 数据集(自动建 mock) | 自动创建 YApi mock 接口再建报表,一步完成;mock_path 必须用时间戳,禁止固定日期 |

| horizontal_group(token, name, sql, *, group_field, row_fields, value_fields) | 交叉表 | statistics_rows 追加统计行 |

| param_query(token, name, sql, columns, param_list) | 带查询条件 | param_list 含 searchMode |

| multilevel(token, name, sql, columns, header_groups) | 多级表头 | header_groups=[{"title":"分组","span":3}] |

| mastersub(token, name, master_sql, sub_sql, master_cols, sub_cols, *, link_field, sub_param, db_source) | 主子表套打${}单值翻页)| ⚠️ 仅限套打式;循环块式(主子循环)禁止用此函数,见「已知坑点」 |

| loopblock(token, name, sql, columns, *, group_label) | 循环块 | group_label 为分组字段名 |

| fillform(token, name, sql, columns, *, key_field) | 数据填报 | enable_add/edit/delete |

| drilling(token, name, sql, columns, drilling_list) | 报表钻取 | drilling_list 含 params |

| chart_linkage(token, name, datasets, table, charts, linkages) | 图表联动 | ⚠️ linkages 参数被静默忽略,函数只创建报表布局不处理联动;datasets 键必须 camelCase(dbCode/apiUrl/fieldList);✅ API 数据集图表 dataType 已由 creator 自动修正,无需手动 patch。完整流程见 scripts/create_chart_linkage.py |

| json_dataset(token, name, records, field_list, columns) | JSON 静态数据集 | field_list=[["field","名称"]] |

| template_print(token, name, sql, cells, bg_image) | 套打 | bg_image={"localPath":...,"cols":7,"rows":15} |

| file_dataset(token, name, file_path, columns) | Excel/CSV 文件 | columns 的 field 用拼音;Excel/CSV 必须有至少一行数据(纯表头报"解析失败"),自动创建文件时 row2 加一行示例数据 |

| multisource(token, name, sections) | 多源横向拼接 | sections 含多个数据集 + columns |

| multisheet(token, name, sheets) | 多 Sheet | sheets 对应 multisheet type 配置 |

调用示例(API 数据集分组报表,自动建 mock)

from jimureport_gen import api_dataset
api_dataset(
    token="xxx",
    name="学生信息统计表",
    field_list=[["grade","年级"],["class_name","班级"],["name","姓名"],
                ["gender","性别"],["age","年龄"],["total_score","总成绩"]],
    columns=[

How to use it

Copy the folder

Take jeecgboot/jimureport from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.