SuperAPI AI Directory

jimureport

Data & analytics

View repository

Updated 10-15 22:41

About this skill

Jimu Report AI Generator

Does not involve "Online Report" (cgreport) or "Online Form" (cgform).

Temporary configuration file rules (mandatory)

All --config <xxx.json> passed to the script must be written to {系统临时目录}/{SKILL_NAME}/ , which the operating system cleans up automatically; neither the skill nor the script actively deletes this directory or files.

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() Auto-adaptation: Windows %TEMP%、Linux /tmp、macOS /var/folders/.../T(note that macOS is not /tmp)。
File names are recommended to use <表名>_<步骤>.json(such as sk_audit_create.json), no need to repeat the skill prefix, since the path already contains the skill name, making troubleshooting easier.

❌ Prohibited:

  • Writing to <skill>/tmp/ or the current working directory (pollutes the skill / user project)
  • Hardcoding /tmp、C:\Temp or any fixed path (not cross-platform)
  • After each step, actively rm / Remove-Item(the operating system will clean it up, making it a redundant tool call)
  • Actively os.path.exists() check (which itself is a tool call)
    (Using os.makedirs(…, exist_ok=True) to meet the requirement does not count as an active check)

Temporary files may be asynchronously cleaned up by the operating system, but still follow optimistic invocation + error remediation: only when the script returns FileNotFoundError or 配置文件不存在 , use the same content,under the same {系统临时目录}/{SKILL名称}/ path rewrite(before rewriting, still os.makedirs(skill_dir, exist_ok=True) to ensure the directory exists), and never change the path or fall back to the skill directory.

One-click script (must-read, covers all three types of scenarios)

Before writing custom JSON / Python, first check whether the user's requirement matches an existing script in the table below; if it matches,call it directly; do not reassemble JSON or Python:

User description (keywords) Call directly Default coverage
"all charts", "all charts", "chart collection", "test all dataset types", "SQL+API+JSON", "chart display" python scripts/generate_all_reports.py --base-url ... --token ... --name "..." --mysql-host ... --mysql-port ... --mysql-db ... --mysql-user ... --mysql-pwd ... 25 charts (SQL 12 + API 2 + JSON 4 + unbound 7), automatically create chart_demo_all table insert data + automatically create YApi mock + save once

Matching rules and prohibitions

  • Use immediately when keywords match: when the user says "generate all charts", "all charts test", "demonstrate all charts", "test the three dataset types SQL/API/JSON",the first reactionis generate_all_reports.py,do notgo back and write chart_entry/echarts templates yourself
  • Can finish in 3 seconds: measured ~3.1s end-to-end creation. After the script starts,do not wait in chunks, do not send AskUser for confirmation, directly Bash and wait for the result
  • Mock creation must use a unique path:create_mock encountering the same path willsilently overwrite existing interface data, polluting others' interfaces. When creating a new interface, a timestamp or sequence number must be appended to the end of the path (such as /sales_20260427), only reuse the original path when the user explicitly says "modify/update an existing interface"
  • saveDb serial: the script has been changed to serial calls save_db, avoiding jimu_report_db_field INSERT concurrency causing MySQL deadlock
  • When adding a new chart type: in the CHARTS list add one line, write one tpl_xxx function, no need to rewrite the main flow

Execution flow

Step zero (required): Token first

When the user message has no X-Access-Token,ask immediately, and only read any files after obtaining the token. Do not pre-read files while waiting for a reply—waiting time does not count toward the 3 minutes, file reading time does.

⚠️ Credentials must not be read from memory, ask the user directly: when any credentials such as database passwords, account passwords, etc. are needed,it is forbiddento read memory files to obtain them; you must ask the user directly in the conversation.

Step one (required): select the minimal file set according to the "Execution speed specification" table

Do not Glob examples/ first, directly check the "Execution speed specification" table below, and read only the specified files according to the scenario. It is forbidden to read additional files outside the table. Scenario matching takes priority over filename matching:multi-level-header.md is mainlycross-tab groupRight/dynamic, do not read it for vertical grouping + static multi-level headers (wasting ~30s reading an inapplicable example).

After reading the specified files, directly Write the JSON configuration → execute the CLI command → output the preview link.Complete in two steps, no extra actions allowed.

Report link format(output directly after successful creation; it is forbidden to call the interface to verify whether it exists):

  • Designer:http://{host}/jmreport/index/{report_id}?token={token}&tenantId=1
  • Preview:http://{host}/jmreport/view/{report_id}?token={token}&tenantId=1

Report name rules: when the user explicitly specifies a name, use it directly; when not specified, the AI automatically generates a name, and after generation it must call GET /jmreport/query/report/folder?pageNo=1&pageSize=10&reportType=&name={name}&token={token} to check whether it is duplicated; if there is a duplicate name, append a suffix (such as _2、_20260415)。

utils submodule quick reference(when you need to confirm a function signature, Grep the corresponding small file; it is forbidden to read the full jimureport_utils.py):

Function to confirm Which file to read
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(named constants, magic numbers are forbidden) 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
Forbidden Replace with
Read the full jimureport_utils.py Grep/Read the corresponding submodule according to the table above (each 25-175 lines)
Grep/Read jimureport_gen.py(for any reason) api_dataset/group/standard The signatures and parameters of functions such as are fully given in the SKILL.md call examples (base_url defaults to http://192.168.1.6:8085/jmreport,无需传),"不确定参数"不构成读源码的理由,直接信任文档
Grep/Read jimureport_creator.py Confirm whether a certain feature is supported(such as fieldList searchMode, paramList, etc.) The JSON configuration templates and forbidden table in SKILL.md already cover all scenarios; directly write the JSON configuration and execute it, it is forbidden to read the source code for verification
Grep/Read jimureport_dataset.py View the parse_sql implementation parse_sql Accepts SQL containing FreeMarker conditions; the server evaluates with empty parameters and then parses the field list, so just call it directly, no need to look at the source code
Find DB credentials Use the configuration in memory or ask the user
Running python with the Bash tool on Windows Switch to the PowerShell tool to run python xxx.py / python -c "...", returned synchronously (see "Windows execution environment" below)
Call external API to validate fields Write the script directly based on the fields provided by the user, without pre-calling the API
Unauthorized calls to create_mock() or init_yapi() ⛔ When the user has provided the API URL, directly save_db(api_url=URL), strictly prohibited from calling create_mock() / init_yapi() / any YApi login or validation operation; only call it when the user explicitly says "help me create a mock API" or has not provided a URL at all; URL known = use it directly, no validation, no asking, no login
sleep + cat polling output Bash commands are always backgrounded on Windows; if you still need to use Bash,you must use TaskOutput(task_id, block=true) to wait for the result, polling with sleep/cat is prohibited
After the report is created, call the API to verify whether it exists /save returns success:true means success; directly output the design/preview link, no need to query the report list
Hand-write border styles({"style":1} or any non-array format) Must use make_styles() to get the styles list, which already has the correct built-in ["thin","#d8d8d8"] array format; hand-writing borders is strictly prohibited and will cause the entire table to render blank
customRows paired with empty columns: [] creator relies on columns to generate queryInfo.dbf;columns is empty then binding metadata is not written, report previewhas absolutely no data(even if the dataset records=N)。customRows only controls visual layout,columns must be filled with the actual fields of the dataset (at least one)
Optional parameters not specified by the user are filled in by yourself(such as customEditConf.eventParams、freeze、rpbar、background , etc.) Only write the fields explicitly given by the user; all other optional parameters are omitted, and you must not invent default values
Call report_urls() use the tool function as a dict (such as urls['designer']) report_urls(report_id, base_url, token, tenant) returns tuple (preview_url, design_url), not a dict; and the first parameter is report_id, not base_url.Directly concatenate the string according to "Report link format" in this section, do not call this function
Report creation uses a custom .py script ⛔ must use a JSON configuration file + jimureport_creator.py CLI: only write xxx.json → python jimureport_creator.py --api-base URL --token TOKEN --config xxx.json. For API dataset reports that also need to create a YApi mock, use jimureport_gen.api_dataset(..., mock_data=[...], mock_path="/xxx_日期") to complete in one step; it internally automatically generates JSON and calls the CLI, with no need to hand-write a Python script. User feedback (reconfirmed on 2026-05-22): "only generate JSON from now on".⚠️ Typical error scenario 1: the user requests custom styles, and the AI thinks the advanced function does not support styles so it hand-writes rows/cols/styles/save_db/base_save——a serious violation.⚠️ Typical error scenario 2: the scenario is complex (including drill-down + charts + table creation), and the AI directly writes drill_demo.py and other full-process Python scripts——a serious violation. Correct approach: the report structure part always writes JSON; steps not supported by JSON such as table creation/link creation are written as minimal standalone PowerShell inline; the two parts are kept separate and not mixed.
PowerShell Out-File -Encoding utf8 write JSON produces UTF-8 BOM causing json.load to report Unexpected UTF-8 BOM。Must use:[System.IO.File]::WriteAllText($path, $content, (New-Object System.Text.UTF8Encoding $false))

Windows execution environment (mandatory rule; violating it makes users complain that "execution is too slow")

Symptom: Windows' Bash tool treats the python / python -c / skill script as a long command and automatically run_in_background, the tool immediately returns a background ID, and the actual output must wait for the completion notification—inflating a millisecond-level call to several seconds, which historically dragged a single report from 1 minute to 18 minutes multiple times.

Rule:

  • Windows(platform=win32) → Use PowerShell tool to directly execute python xxx.py, returning synchronously. It is forbidden to use the Bash tool to run python (it will be backgrounded).
  • Linux / macOS → Use the Bash tool to directly call python xxx.py。
  • On any platform, do not use curl: cross-platform inconsistency; under Windows Bash it is likewise backgrounded.

Mandatory checks before script execution (2 items):

  1. ✅ Under Windows use PowerShell tool to execute python xxx.py,not Bash tool
  2. ✅ The script's first line already has the encoding declaration:import sys; sys.stdout.reconfigure(encoding='utf-8')(to prevent GBK crash retries)

Correct Windows example:

PowerShell: python <skill_base_dir>/scripts/xxx.py --base-url ... --token ...

<skill_base_dir> is the directory where this SKILL.md is located; at runtime replace it with the actual path, and it is forbidden to hardcode C:/Users/...。

Incorrect Windows example:

Bash: python generate_all_reports.py ...    ← 返回 "Command running in background with ID: xxx"
Bash: curl -X POST ...                       ← 同上

Historical lesson: Previously, because Bash + python was used by default, users repeatedly complained "execution is too slow / generation is so slow". The root cause is that the Bash tool backgrounds python on Windows, not limited to curl. Another common retry cause: the script lacks an encoding declaration, causing UnicodeEncodeError: 'gbk' codec, and adding the second check can eliminate it. Typical symptom: After report generation completes, it still waits about 2 minutes before ending—this is a direct manifestation of Bash backgrounding: the script has finished but the tool is waiting for the background completion notification. If this symptom occurs, immediately confirm whether the Bash tool was mistakenly used; switching to PowerShell will eliminate it.

Prerequisites

The user must provide X-Access-Token。

SQL dataset data source selection (must be executed when the user has not specified dbSource )

Correct process (must be executed every time, cannot be skipped):

  1. Call GET /jmreport/initDataSource to get the data source list
  2. According to the database name provided by the user (e.g. jeecg-boot-cr)exact match name field, take its id
  3. If not found, then ask the user

⚠️ It is forbidden to skip the query by directly using the data source ID stored in memory: the ID in memory may have expired or been rebuilt; you must query every time and then match by name to get the current valid ID. Memory is only used to remember the database name, not to remember the ID. ⚠️ It is forbidden to pull everything and then iterate and guess: when there is a clear database name, match directly by name; do not rely on fuzzy rules like containing "积木".

Handle according to the following rules:

Returned result Handling method
result is an empty array Tell the user they need to first add a data source in 积木报表, and stop creation
result is non-empty, and there exists name an item containing "积木" Automatically select that item, and use its id as db_source and pass it to save_db
result is non-empty, with no item containing "积木" List all data source names, ask the user which one to choose, and continue only after waiting for a reply

Interface return fields: each item contains id(passed to db_source) and name(displayed to the user).

Call directly in the script(It is forbidden to rewrite this logic manually in the script):

from jimureport_utils import resolve_db_source
# 用户未指定数据源时:
db_source = resolve_db_source(session)   # 自动选含「积木」的;无则抛 RuntimeError 列出清单

RuntimeError The message already contains the data source list; after capturing it, simply pass it on to the user.

Context takes precedence: If in this conversation it has already been determined via resolve_db_source or the user's reply that db_source, then subsequent reports in the same session directly reuse it,do not repeatedly call initDataSource。 ⚠️ Do not perform a full fetch and then iterate to guess: do not fetch all 30+ data sources and then rely on fuzzy name matching; instead, look up precisely by the database name provided by the user, or directly read memory. Full fetching is wasteful + prone to selecting the wrong one.

API dataset prior inquiry (when the user has not provided an API address, you must ask first)

When the user has not given an API address,you must first ask:

How should the interface be created?

  • mock interface: Create a mock interface via YApi (see the "YApi Mock Data Source" section below)
  • local code: Please provide the local JeecgBoot project path, and I will write the Controller directly into the project

Processing rules after receiving the reply:

User choice Processing method
mock interface Follow the process in the "YApi Mock Data Source" section, use yapi_mock.py to create a mock interface, and fill the returned mock URL into the dataset
local code Ask for the project path (e.g. D:\path\to\jeecg-boot), generate only a Controller written into the project, and return static data ({"data": [...]}),do not generate Entity / Mapper / Service / SQL

🚀 CLI creation (one command)

python /scripts/jimureport_creator.py \
  --api-base http://BASE_URL --token TOKEN --config /path/to/config.json

Config A: SQL ordinary/grouped report

{
  "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 optional attributes:group:true(grouping) / funcname:"SUM"(aggregation) / subtotalText:"小计"

Config B: SQL + chart

{
  "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

Config C: JSON dataset (dbCode must be a string!)

{
  "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}]}
}

Do not use a pure numeric dbCode(e.g. gen_code()); the JSON dataset template engine cannot parse it.

When writing bound fields with an f-string, braces must be escaped:f"#{{{db_code}.{field}}}" → generate #{db_code.field}. If written as f"#{db_code}.{field}#" then the braces are consumed by Python, becoming #db_code.field#(format error, extra at the end #, data will not render).

Config D: custom rows (complex multi-level headers)

When build_table_rows cannot satisfy the requirement (e.g. four-level merged headers), pass customRows + customMerges to skip automatic construction:

{
  "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 When both and customRows exist at the same time,columns still cannot be an empty array [] creator depends on columns to generate the designerObj in queryInfo.dbf(dataset binding metadata). If columns: [],queryInfo.dbf will not be written, the report preview will becompletely blank (no data), even if the dataset has been saved successfully (records=N)。 Correct approach:columns : fill in the dataset's actual fields into columns (even if there is only one),customRows then override the visual layout; the two are independent and do not affect each other.

Config E: with drilling (drilling)——no additional Python script is required

drilling and linkages keys are alreadybuilt inIn creator: creator automatically calls /link/saveAndEdit and fills linkIds back into cells / chart extData; the whole process requires only one JSON file.

{
  "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 field description:

Field Description
linkType "0" report drill-through / "1" network link
targetReportId When linkType=0, fill in the target report ID; when linkType=1, leave it empty
targetUrl When linkType=1, fill in the external URL
ejectType "0" new window / "1" current window
source.type "cell" cell trigger / "chart" chart trigger
source.field For cell, fill in the dataset field name (creator automatically matches the data row cell containing that field and fills it back linkIds)
params[].paramValue For cell drill-through, fill in the field name; for chart drill-through, fill in name(X axis) / value(Y axis) / seriesName

order of cross-report drill-through: first create the subreport to get detail_id, then fill detail_id into the targetReportIdof the main report JSON. Run the CLI on the two JSON files separately; no additional Python script is needed.

Configuration F: NoSQL datasets (ES / MongoDB / Redis)

dbType Fill "es"/"mongo"/"mongodb"/"redis", creator automatically adds the Calcite schema prefix;dbSource the data source ID is required.

{"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}
 ]}
}
Field Description
esIndex ES-specific shorthand, automatically generates SELECT * FROM es.{索引名}; with it, dbDynSql
mongoCollection MongoDB-specific shorthand, automatically generates SELECT * FROM mongo.{集合名}
dbDynSql explicit SQL; when ES/Mongo lacks the es./mongo. prefix, it is automatically completed; for Redis, it is passed through as is

If an ES field name conflicts with a Calcite reserved word (position/date/type/value etc.), escape it with backticks or switch to the esIndex shorthand (SELECT * no field names need to be listed, naturally avoiding this). See pitfalls.md §Database Data Sources.

Modify an existing report

# 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 fixes must use a patch script; rerunning the creation script is forbidden In any case where an existing report is found to have problems (whether reported by the user or discovered by the AI itself), the correct approach is to write a separate patch script (get_report → modify local fields → base_save write back; for dataset errors, use update_db),It must not be modified and the creation script rerun. Rerunning the creation script will generate a report with a new ID; the original report (including already configured permissions, share links, and reference relationships) will not be fixed, and junk reports will be produced.

Report category

JimuReport reports are divided into two major categories, with data reports as the default:

Category Description designerObj key fields
Data report(default) Display-oriented report, rendered from dataset queries submitForm not set or is 0
Data entry report Fill in data on the report and submit it to the backend submitForm: 1

Data report type determination

User description Data binding Dataset configuration
Detail/list #{db.field} isList:"1" isPage:"1"
Form print/single record ${db.field} isList:"0" isPage:"0"
Group by XX #{db.group(field)} isPage:"0"
Cross table #{db.groupRight(field)} + #{db.dynamic(field)} isPage:"0"

Cell binding field name retrieval

Write #{dbCode.fieldName} before binding,Do nothandwrite field names based on SQL aliases.

Directly use the parse_sql returned by fieldName(recommended, fastest):

fl = parse_sql(session, sql)
fields = [f["fieldName"] for f in fl]
# MySQL 将所有别名转小写,AS totalAmount → totalamount,直接用即可

/field/tree/{reportId} is the fallback (requires the report to be first /save before it can be called),parse_sql has already returned the same real field names, so no extra call is needed.

Performance Optimization (Recommended Template for a Single Report)

For a single-report single-dataset scenario, the following 3-step process totals ≤ 5 HTTP requests (including the 1 for an existing data source). Measured end-to-end ~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=[]))
Stage Original HTTP Current HTTP Description
Data Source 3 (query + save + query again) 1-2 ensure_datasource merged
First placeholder /save 1 0 parse_and_save_dataset directly call saveDb on the orphan report_id
Parse SQL 1 1 —
Save Dataset 1 (merged in ③) —
Final /save 1 1 First creation + write layout
Total (data source already exists) 7 4 Saves 3 HTTP requests

Key Principle: saveDb accepts a report_id(orphan) that does not yet exist on the server; when /save later creates the report for the first time with this id, the dataset will be bound correctly. Verified by testing. addDataSource returns result: true(does not return an id); after creation you must query once more;/initDataSource without a signature is faster than /getDataSourceByPage .

Legacy Path Still Available (Kept for Compatibility)

parallel_init_and_parse is no longer recommended but is retained — old scripts need no changes. New scripts should always use parse_and_save_dataset。

API Dataset Fast Path (2 HTTP, measured ~0.5s)

API datasets do not require queryFieldBySql, fields are defined manually, and the entire process requires only 2 HTTP requests:

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",   # 交叉/分组报表需要
))
Legacy Process (3 HTTP) New Process (2 HTTP)
POST /save with an empty report → get report_id gen_id() generated locally (0 HTTP)
POST /saveDb POST /saveDb (same)
POST /save with the complete design POST /save with the complete design (same)

Measured: regional province sales crosstab report, 3 steps ~3s → 2 steps ~0.5s (verified 2026-04-22). Applicable scenarios: all API dataset reports (crosstab, grouped, and detail reports).

Special Notes on MongoDB / NoSQL Data Sources

  • testConnection only detects TCP connectivity, and does not verify username or password. It returning success does not mean the credentials are correct.
  • Actual authentication occurs at queryFieldBySql / preview. If credentials are wrong, an error will be reported at these two steps. Exception authenticating。
  • Prohibitedin the creation script, trying trial-and-error loops with multiple formats (standard separated / connection string / multiple authSource) — it wastes 3-6 seconds for nothing. Try only the one format the user provided; on failure, immediately report an error and have the user check the MongoDB server db.getUsers()。

Performance Optimization (Multi-Dataset / Multi-Report Scenario)

Core principle: parallelize everything that can be parallelized, and eliminate serial waiting.

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()
Optimization Point Savings
parse_sql directly get field names, skipping first_save + field/tree saves 2 requests per report
parallel_parse_sqls N serial → 1 round of parallel
parallel_save_dbs N serial → 1 parallel round
parallel_create_links N serial → 1 parallel round
Multiple reports /save parallel M serial → 1 parallel round

Row/column indexing rules

  • All 0-indexed, column A (col0) left blank, data starts from col1 (column B)
  • merge: [extraRows, extraCols], 0 = occupies only itself
  • merges uses Excel notation:"B2:F2"(UI row number = code row number + 1)

Grouped summary

User wording Implementation
"Total row" Add below the data rows =SUM(列号)
"Group subtotal" subtotal:"groupField" + funcname:"SUM" + subtotalText:"Subtotal"
Only says "group" Use only group() + aggregate:"group"

🚨 When the group column has subtotal:"groupField" enabled, all numeric columns must default to aggregate:"select" + funcname:"SUM"— otherwise the numeric cells in subtotal/total rows will all be blank; on the UI, "total label shown + numeric columns blank" is an absolutely unacceptable compromise state. Either do not show the total row (change the group column to subtotal:"-1"、subtotalText:""), or default all numeric columns to sum. AI must not simply copy the template's "group column with subtotal, numeric columns bare text" pattern.

funcname aggregate function values (⚠️ the following strings must be used strictly; a typo will make it ineffective)

User requirement funcname value
Total / Sum "SUM"
Average / Average value "AVERAGE" (❌ not "AVG")
Maximum "MAX"
Minimum "MIN"
Count "COUNT"
No aggregation (group column placeholder) "-1"

Comparison of group column vs aggregate column attributes

Attribute Group column (group) Aggregate column (select)
aggregate "group" "select"
subtotal "groupField" "-1"
funcname "-1" "SUM" / "AVERAGE" / "MAX" / "MIN" / "COUNT"
subtotalText Subtotal row label text Subtotal row label text

Query parameters (paramList)

Read when query controls are included references/query-params.md § 0 (including SQL FreeMarker conditions, widgetType/searchMode comparison table, date range splitting rules).

Field query vs report parameter query (required reading rules)

Field query (fieldList searchFlag) Report parameter query (paramList)
SQL Pure SELECT,Do not add WHERE / FreeMarker conditions Must add <#if isNotEmpty(x)>... conditions
fieldList searchFlag=1 + searchMode + dictCode etc. No need to set searchFlag
paramList Not needed paramList must be configured
querySetting No need to set Configure izOpenQueryBar as needed

Core rule: For field queries, the JimuReport engine automatically handles filtering, and SQL stays pure; only when report parameters are used should FreeMarker WHERE conditions be added to SQL. The two methodscannot be mixed。

Style specifications (required conventions, no user reminder needed)

  • All table reports must have border: Prefer from jimureport_utils import make_styles 's 5 preset styles (indices 0-4)
  • col0 is always left blank at 30px without borders: The title row starts merging from col1 (merges written as B1:F1 excluding column A).make_styles() The preset styles 0-4 all contain borders,col0 must have a borderless style appended separately:
    styles = make_styles()
    styles.append({"align": "center"})  # index 5:col0 专用,无边框
    # 所有行的 col0 统一用 style: 5
    "0": {"text": "", "style": 5}
    
  • When customizing styles border must be nested (cannot ** be expanded to the top level),color(text color) and font are placed at the same level at the top level of style
  • ⚠️ Whenever customizing the styles array or modifying cell configuration, you must first read references/styling.md, and must not guess the property hierarchy from experience (color being at the top level rather than inside font is a high-frequency pitfall; the engine silently ignores incorrect syntax)

Complete specification + col0 blank template code → references/styling.md

Chart type quick reference

Read when charts are included references/chart-types-quickref.md(system name → chartType mapping + series field values + map data semantic rules). Complete ECharts configuration template → references/chart-echarts-templates.md。

Execution speed specification (complete within 3 minutes)

Mandatory check before starting (go through it before every execution):

  1. ✅ Data source ID has been read from memory, and initDataSourcewas not called (unless memory really does not have it)
  2. ✅ Database driver uses pymysql, not mysql.connector(this project environment only has pymysql)
  3. ✅ When using customStyles for styles, reuse the border format from the already-read example file; do not additionally read the styling.md

File reading takes the minimal set by scenario,only read the files specified in the table; do not read other files additionally:

Scenario Files to read (precise, no extra reads)
Ordinary table / JSON dataset (no grouping) Read only references/pitfalls.md(when encountering an error, jump by symptom index; use offset to read only the corresponding section)
Vertical grouping (including subtotals/aggregation) references/pitfalls.md §Dataset save (offset=15, limit=40) + examples/vertical-group-subtotal-example.md
Vertical grouping + custom sorting (textOrders) Read only examples/vertical-group-custom-sort.md;API dataset scenarioSee the API+mock+textOrders entry in "Known pitfalls" below
customGroup (custom horizontal grouping, each record → one column) Read only references/horizontal-grouping.md(§5.3.2 already has the complete JSON template embedded + all key properties; no need to read examples/horizontal-group.md again)
groupRight + dynamic (cross table, column headers expand dynamically horizontally) references/horizontal-grouping.md + examples/horizontal-group.md Example 1
groupRight + dynamic + follow-group extended statistics row (rightFollowExten) Read only references/horizontal-grouping.md §"Follow-group extended statistics row" section
Contains barcode/QR code / fill blank rows / custom editable cells Read only references/cell-config.md
Contains query controls (paramList) references/query-params.md § 0 + examples/param-query.md
Contains charts (no linkage/drill-down) references/chart-types-quickref.md(choose chartType) → references/chart-canonical-configs.md + references/chart-echarts-templates.md;Chart display count/sorting/data filtering requirements must first check references/chart-echarts-props.md §9; do not change the dataset type or slice data
Contains chart linkage Read only examples/chart-linkage.md(already includes the complete extData/linkType=2/paramValue="name"/parameter structure; no need to read canonical-configs again); only additionally read when chartType is uncertain references/chart-types-quickref.md(total ≤2 files).Strictly forbidden to read pitfalls.md: This file has 26742 tokens and will definitely exceed the limit and error out; linked scenarios don't need it either.⚠️ For the API dataset chart linkage reuse scenario, directly use scripts/create_chart_linkage.py;chart_linkage() 's linkages parameterdoes not take effect, must patch (see "Known Pitfalls")
includes report drill-down references/report-drilling.md + examples/report-drilling.md
includes expressions read-only references/expressions.md
data entry report read-only references/fillform.md
overlay printing report(${db.field} single binding, background image covers the entire form) read-only references/misc-config.md §overlay printing imgList configuration (complete code template); for a summary of pitfalls see references/pitfalls.md §overlay printing background image (offset=292, limit=10)
master-detail report (overlay-printing style or loop-block style, detail table expands vertically, eri≥40) references/mastersub-report.md + examples/master-sub-table.md
column report (loopTime horizontal looping, eri=actual number of card rows, buffer rows prohibited) read-only references/loopblock-grouping.md §5(offset=106)
zoned edition (parallel independent expansion, zonedEdition/zonedEditionList) read-only references/loopblock-grouping.md §5.5(offset=219,limit=60)
multi-source report(multiple datasets come from different tables and have a common related field; or the user requires the same row to bind fields from both the master and detail tables simultaneously) read-only examples/multi-source-table.md
style details (borders/whitespace/priority) read-only references/styling.md
custom styles array (including any color / bgcolor / font properties) must-read references/styling.md(color text color must be placed at the top level of style, not inside font; otherwise it silently fails)
modify existing report cell configuration (style / merge / text / direction, etc.) must-read references/styling.md, confirm the property hierarchy before making changes
file dataset (Excel/CSV/JSON, including scenarios where the user has not provided a file and it needs to be created automatically) read-only references/dataset-advanced-file.md(§pre-auto-creation + §4.0 upload process)
data source management (MongoDB/Redis/MySQL, etc. addDataSource) read-only references/dataset-advanced.md § 1
report combination (create/update/delete) read-only references/report-group.md
has reference report ID get_report copy directly, skip all files
sample data/fields needed but not yet determined read-only references/mock-apis.md select interface

pitfalls.md reading rules: only the "ordinary table/no grouping" row requiresthe entire sectionto be read; for other scenarios, when a runtime error occurs, find the corresponding section by the symptom index at the top of pitfalls.md, and use Read(offset=N, limit=40) to read only that section (do not read the whole thing; each of the 9 sections is about 20 lines).⚠️ pitfalls.md has a total of 26742 tokens; reading the entire file is prohibited in any scenario (it will definitely exceed the 25000 token limit and error out); linkage/chart scenarios do not need to read it at all.

API dataset + customGroup quick path(when the user provides an API URL + token, no more than 3 steps):

  1. read references/horizontal-grouping.md §5.3.2 to get the template
  2. call it in the script parse_api(session, api_url) to get field names (1 HTTP request, merged into the script,not executed separately)
  3. Replace field names in the template → PowerShell execution script

Custom chart script specification (including SQL/API datasets)

Before writing a custom chart script, you must first confirm the following specifications to avoid repeated debugging.

# ① 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))

Known pitfalls (must read)

Before writing a script, first Read references/pitfalls.md, to avoid wasting time on pitfalls and retries. Before writing a loop block report, you must first Read references/loopblock-grouping.md, which contains a complete configuration template; writing it from scratch yourself is prohibited. ⚠️ When an API dataset has paramList ,apiUrl at the end you must append ?paramName=${paramName}, otherwise linkage/drill-down parameters cannot be passed in. Connect multiple parameters with & . After writing save_db , immediately visually check that the api_url string contains placeholders. ⚠️ Crosstab (groupRight+dynamic) bottom row that follows group expansion statistics: must usean independent static row + rightFollowExten:"follow" + Excel formula(e.g. =MAX(G6)), the dynamic cell's funcname must be set to "-1",Prohibitedset to "MAX"/"MIN"/"SUM"—that will not generate a statistics row that follows horizontal expansion. For non-grouping dimension fields (such as name, gender), use aggregate:"select", not group(). See references/horizontal-grouping.md § "Statistics row following group expansion". ⚠️ Style patch must not skip col "0": In grouping/standard reports generated by creator, the title cell may be in rows["1"]["cells"]["0"](merged across all columns starting from col 0). When the patch script iterates over cells, itmust not if ck == '0': continue, otherwise the title style will never be updated. Correct approach: iterate over all cols, determine whether there is merge and the text does not start with #{ ; that is the title cell, and directly update its style. ⚠️ mock path conflict causes retries:create_mock() will throw a RuntimeError when the path already exists. mock_path must use at the endtimestamp(import time; mock_path=f"/xxx_{int(time.time())}") rather than a fixed date, to ensure uniqueness. The original path may be reused only when the user explicitly says "update an existing interface". ✅ API dataset dbType correction and chart dataType are now built into creator (2026-05-23):jimureport_creator.py now automatically callsdbType:"1"after saving an API dataset ( update_db to correct dbType/apiUrl, and before /save automatically sets the chart extData's dataType。according to the dataset's actual type (sql/api/json/javabean/files), so there is no need to manually patch anymore; just write the JSON config directly. Supports all types: SQL→"sql", API→"api", JSON→"json", JavaBean→"javabean", file→"files", shared→"sql". ⚠️ Barcodes/QR codes must use display inline; displayConfig floating layers are prohibited: In the cell write only "display":"barcode" / "display":"qrcode",without adding "config" field, do not pass it in the payload displayConfig。displayConfig + config ; this method will create a floating overlay layer that covers the header and other rows, completely disrupting the layout (actually tested on 2026-05-22). ⚠️ create + patch must be merged into a single PowerShell call: When you need to patch immediately after creating the report (such as adding a barcode/QR code),must not be split into two PowerShell calls. The correct approach is in the same Python script: call api_dataset() → parse stdout to get report_id → immediately call get_report + base_save to complete the patch. Splitting into two steps drags a second-level operation into a minute-level one, and any error in between requires manual intervention. ⚠️ The report_id parsing regex uses report_id= for anchoring, don't rely on guessing the format:jimureport_creator.py stdout has a fixed output of report_id=<id> format, directly use re.search(r'report_id[=:\s]+(\S+)', stdout) to extract, which is more reliable than guessing the format by ID length/character set. ⚠️ api_dataset() may return CompletedProcess instead of a report_id string: after calling, you must do a compatibility check, otherwise the subsequent get_report(session, report_id) will send the whole object as an ID in the request, causing a 400 error and forcing a split into a second PowerShell execution (seconds become minutes). The standard way:

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

⚠️ Do not grep the function signature:api_dataset、run_config、DEFAULT_BASE_URL The complete examples of parameter formats such as these are already in SKILL.md; there is no need to grep/read jimureport_gen.py to confirm. Grepping the function signature wastes 3-5 tool calls each time; just trust the documentation. ⚠️ chart_linkage() 's linkages parameter is silently ignored:jimureport_gen.chart_linkage() accepts the linkages parameter butdoes not handle it—the function only creates the report layout and does not call /link/saveAndEdit, and linkIds are not written to cells/chartList either. Correct approach:chart_linkage() after creating the report, you must add a patch step: ① call /link/saveAndEdit to create the linkage record and get link_id; ②get_report fetch the design; ③ write linkIds+display:"link"to the cell, and write linkIds;④base_save back to the triggering chart's extData. Already packaged as scripts/create_chart_linkage.py, call it directly.create_chart_linkage.py is a provided tool script, calling it directly is fine; but if it fails midway (e.g. list_mocks() cannot find the create interface),do notrewrite a full-process Python script to replace it—the correct approach is: use the known interface ID to directly make up the call to set_advmock (minimal PowerShell inline), change the report structure via JSON config + creator CLI, and write a separate minimal PowerShell inline for the linkage patch, keeping the two parts strictly separate. ⚠️ chart_linkage() The dataset keys passed to creator must be camelCase: keys in datasets use dbCode/apiUrl/fieldList/paramList, keys in table/charts use datasetCode/chartType. Using db_code/api_url/field_list and other snake_case will cause KeyError: 'dbCode'(creator reads JSON fields directly and does not convert). ⚠️ yapi_mock does not expose a session object, use list_mocks() to get the interface ID:_session/session and other attributes do not exist, don't waste time using inspect.getmembers to find the session. Correct approach:init_yapi() after logging in, directly from yapi_mock import list_mocks; ifaces = list_mocks() get the interface list, then match by path to find _id。 ⚠️ mastersub() The correct signature is (token, name, master_sql, sub_sql, master_cols, sub_cols, *, link_field, sub_param, db_source):sub_sql in position 4,master_cols in position 5, the order cannot be swapped. After swapping, creator will pass the master_cols list as SQL to queryFieldBySql, reporting "signature verification failed". ⚠️ mastersub() only generates overlay-style layouts, and does not support loop-block style (master-detail loop): after calling, creator reports "no table or chart configured" because mastersub's JSON template goes through ${} single-value binding path and cannot generate loopBlockList.Correct approach: abandon mastersub(), switch to the following 3 steps: ①parse_and_save_dataset Save the primary and sub datasets; ② manually build rows (add to all cells "loopBlock":1, including merged empty cells in the header row, buffer row eri≥40); ③base_save pass in loopBlockList=[{"sci":1,"eci":N,"sri":0,"eri":40,"index":1,"db":MAIN_CODE}], then call /link/saveAndEdit linkType="4". See the complete code template in references/mastersub-report.md §II. ⚠️ The master-sub cyclic report JSON config does not support loopBlockList, and the creator will report "no table or chart configured": The JSON config + creator CLI path is invalid for cyclic block-style master-sub reports. The correct approach has two steps: ① first run the JSON config to let the creator finish saving the datasets (the datasets part executes normally; the layout step error can be ignored; report_id has been generated); ② then use a minimal PowerShell inline call to base_save(including manual rows + loopBlockList) + /link/saveAndEdit. The two steps are merged into a single PowerShell call. ⚠️ All chart config xAxis/yAxis must be an object {...}; arrays [{...}]are forbidden (even if there is only one axis): Although the array format can render normally (ECharts accepts it), it makes the designer's "Data" panel's getChartDataList when reading options['xAxis'].data get undefined and throw Cannot read properties of undefined (reading 'length'), causing selectChart to crash before handleChartExtData → the chart's "Data Type" dropdown stays at the default value "Api dataset", making it look as if the JSON/SQL dataset was treated as an API dataset.jimureport_creator.build_echarts_config was fixed to the object format on 2026-05-26; when hand-writing configs or patching, the object format must likewise be used. To fix an existing report:get_report → for each chart, change cfg['xAxis']=cfg['xAxis'][0](same for yAxis) → base_save and write back. ⚠️ In chart linkage, the "trigger chart"'s xAxis must be in object format, otherwise clicking cannot trigger the linkage:chart_entry() In the ECharts config of the generated bar chart, xAxis is in array format [{type:'category',data:[]}](the standard multi-axis format), but the frontend refreshChart() when updating the X-axis label writes chartOptions['xAxis']['data'] = names—performing this operation on an array merely adds a custom property to the array object, andxAxis[0].data remains empty. Result: when a bar is clicked, the ECharts click event's event.name = "", the linkage parameter check if(param["name"]) is false, and the target chart never refreshes.Fix method: After the report is created, for each bar chart that "serves as a linkage trigger source" (extData has linkIds), change the config's xAxis/yAxis from array to object:

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))

This patch should be executed immediately after the linkIds backfill step, merged into the same PowerShell inline. ⚠️ Correct process for API dataset + mock + textOrders custom sorting: The report structure must be in the JSON config; the mock creation + report creation + patch must not be wrapped in the same custom .py script. Correct four steps (merged into a single PowerShell call): ① minimal inline script (only containing init_yapi + create_mock) → capture mockUrl; ② write the JSON config (including mockUrl, columns use standard properties, not including textOrders); ③ run the jimureport_creator.py CLI → capture report_id; ④ minimal inline script (get_report → iterate over cells to add textOrders → base_save). ②③④ execute sequentially within the same PowerShell call; ① can be split out or merged, either is fine.

⚠️ Multi-file datasets (dbType=5) must be split into two steps; merging into one custom Python script is prohibited: file upload + saveDb are prerequisite steps that cannot be expressed in JSON config, but the report layout (rows/cols/styles) must still go through JSON + creator CLI. Correct split:

  • Step 1 (minimal PowerShell inline): upload files one by one (session.upload('/source/datasource/files/add', ...))→ GET /source/datasource/files/get get datasource_id and table name → build JOIN SQL → queryFieldBySql parse fields → saveDb first step (no id) → get db_id → saveDb second step (with id, only then is fieldList truly persisted) → output report_id、dbCode
  • Step 2(JSON config + creator CLI): use Step 1's report_id and dbCode to write the JSON layout → python jimureport_creator.py --config xxx.json

Calcite reserved word handling:type、count when used as an Excel column name, must be escaped with backticks in SQL and given an alias (e.g. b.`type` AS prod_type,a.`count` AS sales_count); double quotes (b."type") are parsed as string literals in Calcite, causing SQL语法错误。

⚠️ File dataset Excel/CSV must contain at least one row of data:/source/dataset/files/single/save needs to parse the file to infer field types; a pure header (only row1) will return {"success":false,"message":"解析失败,请检查文件格式符合要求"}. When automatically creating files, row1=header, row2=one row of reasonable sample data (can be fake data); sample data does not affect report display.

⚠️ Table creation scripts must INSERT with explicit column names, and DROP before creating:INSERT INTO t VALUES (...) when column names are not written, if the target table already exists and the column count/order differs, MySQL reports Column count doesn't match, forcing another PowerShell call to retry, directly adding one round trip (ideally 2 times → actually 3 times). Correct way: always use INSERT INTO t (col1, col2, ...) VALUES (...) explicit column names; at the start of the table creation script first DROP TABLE IF EXISTS, ensuring success in one go:

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)

⚠️ Chart extData must have both id and chartId; missing id will cause the chart not to render: the chart extData generated by creator sometimes only writes chartId and omits id; both must equal layer_id. When patching an existing report, if extData is found to lack the id field, it must be filled in:ext['id'] = c['layer_id']. Symptom: the chart area on the preview page is blank, with no error or reporting Cannot read properties of undefined。

⚠️ pie.simple's extData.series must be ""; writing "type" incorrectly will report TypeError: for a single-series pie chart, the series field must be an empty string; creator occasionally sets it incorrectly to "type". During frontend initialization it groups by the series field name; if the data has no type column then undefined[0] reports TypeError: Cannot read properties of undefined (reading '0'). When patching, check and correct:ext['series'] = ''。

⚠️ Patching a chart background color requires setting two places at the same time: the chart container background is chartList[n]['backgroud'](a typo in the framework source code; the actual field name is indeed backgroud, not background), and the ECharts canvas background is config['backgroundColor']. Setting only one of them gives an incomplete effect. Correct way:

c['backgroud'] = {'enabled': True, 'color': '#e8f5e9', 'image': ''}
cfg = json.loads(c['config'])
cfg['backgroundColor'] = '#e8f5e9'
c['config'] = json.dumps(cfg, ensure_ascii=False)

Report creation method comparison (required reading)

Jimu Report has three creation levels;prefer choosing from top to bottom:

Level Method Applicable scenario
① JSON + CLI(Only recommended) Write xxx.json → python jimureport_creator.py --api-base URL --token TOKEN --config xxx.json all reports, JSON files visible and reproducible
② Python wrapper functions from jimureport_gen import group, api_dataset, ... Internally auto-generates JSON + calls CLI; use this approach when YApi mock also needs to be created

⛔ Do not write custom Python scripts to create reports(User 2026-05-22 clarified again:from now on only generate JSON). The report structure must be written in a JSON config file; API dataset + mock go through jimureport_gen.api_dataset()(internally also JSON+CLI). Steps that JSON cannot express, such as table creation,/link/saveAndEdit etc., should be written as minimal standalone PowerShell inline;must notmix report creation logic into Python scripts. ⚠️ Typical error scenario 3: Chart linkage scenario (table + bar chart + pie chart), because list_mocks() failed midway, the AI wrote mock creation + chart_linkage() call + linkIds patch all into one chart_linkage_patch.py custom script — serious violation. Correct approach: ① When mock does not have advmock set, call in a separate PowerShell inline line set_advmock(已知ID); ② Write report structure as JSON config → creator CLI; ③ Linkage patch (/link/saveAndEdit + get_report + base_save) written as another minimal PowerShell inline; the three steps are each independent and not mixed into the same large Python script.

jimureport_gen.py function quick reference

jimureport_gen.py is a Python wrapper for jimureport_creator.py and internally auto-writes JSON + calls CLI.

import sys
sys.path.insert(0, r'<skill_base_dir>/scripts')
from jimureport_gen import group, api_dataset, standard  # 按需 import
Function Report type Key parameters
standard(token, name, sql, columns) Normal detail table db_source, is_page, chart
group(token, name, sql, columns) Vertical grouping/subtotal Add to group column group=True, subtotalText="合计"
api_dataset(token, name, field_list, columns, *, api_url=...) API dataset (existing URL) report_type="group" can be changed to grouping mode
api_dataset(token, name, field_list, columns, *, mock_data=[...], mock_path=f"/xxx_{int(time.time())}") API dataset (auto-create mock) Automatically create YApi mock interface then create report, in one step; mock_path must use a timestamp, fixed dates are forbidden
horizontal_group(token, name, sql, *, group_field, row_fields, value_fields) Cross table statistics_rows append statistics row
param_query(token, name, sql, columns, param_list) With query conditions param_list includes searchMode
multilevel(token, name, sql, columns, header_groups) Multi-level header header_groups=[{"title":"分组","span":3}]
mastersub(token, name, master_sql, sub_sql, master_cols, sub_cols, *, link_field, sub_param, db_source) Master-detail tableOverlay printing(${}single-value paging) ⚠️ Limited to overlay printing style only; loop block style (master-detail loop)must not use this function, see "Known pitfalls"
loopblock(token, name, sql, columns, *, group_label) Loop block group_label is the group field name
fillform(token, name, sql, columns, *, key_field) Data entry enable_add/edit/delete
drilling(token, name, sql, columns, drilling_list) Report drill-down drilling_list includes params
chart_linkage(token, name, datasets, table, charts, linkages) Chart linkage ⚠️ linkages parameters are silently ignored, the function only creates the report layout and does not handle linkage; the datasets key must be camelCase (dbCode/apiUrl/fieldList); ✅ API dataset chart dataType has been automatically corrected by creator, no manual patch needed. For the complete process see scripts/create_chart_linkage.py
json_dataset(token, name, records, field_list, columns) JSON static dataset field_list=[["field","名称"]]
template_print(token, name, sql, cells, bg_image) Overlay printing bg_image={"localPath":...,"cols":7,"rows":15}
file_dataset(token, name, file_path, columns) Excel/CSV file columns 's field uses pinyin;Excel/CSV must have at least one row of data (pure header reports "parse failed"), when automatically creating the file add a row of sample data at row2
multisource(token, name, sections) Multi-source horizontal concatenation sections Contains multiple datasets + columns
multisheet(token, name, sheets) Multiple Sheets sheets Corresponds to the multisheet type configuration

Call example (API dataset grouped report, automatically creates mock):

from jimureport_gen import api_dataset
api_dataset(
    token="xxx",
    name="学生信息统计表",
    field_list=[["grade","年级"],["class_name","班级"],["name","姓名"],
                ["gender","性别"],["age","年龄"],["total_score","总成绩"]],
    columns=[
        {"field":"grade",       "title":"年级",   "width":120, "group":True, "subtotalText":"合计"},
        {"field":"class_name",  "title":"班级",   "width":120, "group":True, "subtotalText":"小计"},
        {"field":"name",        "title":"姓名",   "width":100},
        {"field":"gender",      "title":"性别",   "width":80},
        {"field":"age",         "title":"年龄",   "width":80},
        {"field":"total_score", "title":"总成绩", "width":100, "funcname":"AVERAGE"},
    ],
    mock_data=[
        {"grade":"一年级","class_name":"一班","name":"张三","gender":"男","age":7,"total_score":285},
        # ...更多数据
    ],
    mock_path=f"/student_info_{int(time.time())}",   # 必须用时间戳,禁止固定日期
    report_type="group",
)

Tool scripts (use as needed)

Operation Command
Query report python report_tools.py --base-url URL --token T list [-k 关键词]
Report details python report_tools.py --base-url URL --token T detail <id>
Delete/copy python report_tools.py --base-url URL --token T delete/copy <id>
Query report group list python report_tools.py --base-url URL --token T group-list [-k 关键词]
Create/update report group python report_tools.py --base-url URL --token T group-save --name "名称" --ids "id1,id2" [--names "报表1,报表2"] [--descr "描述"] [--group-id <id>]
Delete report group python report_tools.py --base-url URL --token T group-delete <组合ID>
Share python report_export.py share --name 名称
Export python report_export.py export <id> --format pdf
Create scheduled export task python report_export.py schedule-export --name 任务名 --reports '[{"reportId":"id","exportType":"PDF","name":"报表名"}]' --cron "0 0 8 * * ?" [--email xxx@qq.com] [--begin "YYYY-MM-DD HH:mm:ss"] [--end "YYYY-MM-DD HH:mm:ss"]
Modify scheduled export task Same as above, append --job-id <任务ID>(Creation and modification share the same API)
Query scheduled export task list python report_export.py job-list [--name 关键词](API is /auto/export/job/query,/list returns 404)
Delete scheduled export task python report_export.py job-delete <job_id>
Run scheduled export task immediately python report_export.py job-run <job_id>(returns execution batch number)
Start scheduled export task python report_export.py job-start <job_id>
Stop scheduled export task python report_export.py job-stop <job_id>
Change chart type python chart_tools.py change-type <id> bar.multi
Create YApi Mock python yapi_mock.py list
Multi-file dataset report(Excel/CSV JOIN) python multi_file_report.py --token T --files a.xlsx b.xlsx --name "名称" [--join-on product_id] [--db-code myds] [--base-url URL]

YApi Mock data source

When a report needs an API data source, use the built-in YApi platform (https://api.jeecg.com)创建 mock API. Login credential handling flow (mandatory; violation causes execution failure and retry):

  1. Before starting, you must first read the YApi credential record in memory (reference_yapi_credentials.md) and must not skip it
  2. If present, write it directly into the script;If not present or init_yapi authentication fails → ask the user for a new password, and immediately update memory once obtained
  3. After the user provides it, immediately save it to memory (reference type); read it directly next time without asking again
  4. init_yapi() credentials must be passed explicitly:init_yapi(email=EMAIL, password=PWD), otherwise it will fail with "email cannot be empty"
  5. api_dataset() When mock_data is present, the credentials must be passed directly to api_dataset():api_dataset(..., yapi_email=EMAIL, yapi_password=PWD)— internally it will call init_yapiagain; calling init_yapi() separately in advance does not take effect and will report "email cannot be empty"
import sys
sys.path.insert(0, '<skill目录>/scripts')
from yapi_mock import init_yapi, create_mock

init_yapi()  # 自动登录

mock_url = create_mock(
    path='/sales',       # 路径后缀,不含 basepath(/claude)
    title='销售数据',
    data=[
        {"month": "一月", "amount": 12000},
        {"month": "二月", "amount": 15000},
    ]
)
# mock_url = https://api.jeecg.com/mock/57/claude/sales

Fixed parameters:project_id=57,catid=1157,basepath=/claude

Path rules: Only write the suffix in the API path (e.g. /sales), full URL = https://api.jeecg.com/mock/57/claude/sales

⚠️ Create vs Modify (strictly distinguish to prevent polluting others' data):

User intent Approach
Create API (default) Append a timestamp or sequence number to the path, e.g. /sales_20260427, to force creation of a new API
Explicitly say "modify/update existing API" Directly use the original path to call create_mock() or update_mock()

create_mock() encountering the same path willsilently overwrite existing API data, which will destroy others' APIs in shared projects. By default, a unique path must be used.

Pagination rule: Self-built mock APIdoes not need pagination,data and directly returns the complete array without adding pageNo / pageSize parameters.

If the user explicitly requires the mock to support pagination(A few scenarios, such as demonstrating the "pagination + field query" linkage), requires using set_advmock to write advanced scripts, and strictly follow the response field semantics below — total is the total number of pages,count is the total number of records, and the two must not be confused:

// set_advmock 脚本中:
var total = Math.ceil(count / pageSize);   // 总页数(≠ 总条数)
mockJson = {
  "data": paged,        // 当前页记录数组
  "count": filteredLen, // 总条数(过滤后)
  "total": total,       // 总页数
  "pageNo": pageNo,
  "pageSize": pageSize
};

assigning the total number of records to total will cause the total number of pages displayed by the JimuReport paginator to be incorrect. See references/dataset-core.md §3.3。

When using a mock URL as an API dataset in JimuReport, call save_db(..., db_type="1", api_url=mock_url) and it is enough;dbCode is a custom code (such as sales_data), unrelated to the URL.

API dataset query condition parameter passing rules: see the "Known Pitfalls" section (including code examples) and references/dataset-core.md § 3.2.2。

fieldList must include orderNum(starting from 0), otherwise the dataset's "sort" column is empty and the field order is uncertain:

{"fieldName": "col1", "fieldText": "列1", "fieldType": "String", "orderNum": 0},
{"fieldName": "col2", "fieldText": "列2", "fieldType": "String", "orderNum": 1},

Query the list of APIs already created by AI

init_yapi()  # 先登录,自动带上 Cookie

# 列出 project_id=57 下已创建的所有接口(需登录态)
# GET https://api.jeecg.com/api/interface/list?page=1&limit=20&project_id=57
# 返回字段:_id(接口ID), path(路径), title(名称), method, up_time
# 完整 mock URL = https://api.jeecg.com/mock/57{path}

Command-line equivalent:python yapi_mock.py list(automatically logs in and lists all APIs in pages)

Advanced Mock script (dynamically returns different data based on parameters)

Automatic determination rules (AI must comply):

⛔ Prerequisite: the following rules take effect only when the user has not provided any API URL. If the user has already provided a URL → directly parse_api → save_db, and calling is prohibited throughout create_mock / init_yapi, regardless of whether the report has linkage/drill-down.

Scenario Approach
User has already provided an API URL(regardless of whether there is linkage) ⛔ Creating a mock is prohibited, directly parse_api(url) → save_db(api_url=url)
User has not provided a URL; the report has linkage/drill-down, and the dataset needs to be filtered by parameters (such as ?category=手机) create_mock create the interface → set_advmock write the script, and in the script return by params.xxx branch
User has not provided a URL, static display, and no parameter-based filtering is needed Use only create_mock, do not call set_advmock

⚠️ The correct API is /api/plugin/advmock/save, and it is prohibited to use /api/interface/up 's isOpen/script field — that field does not take effect.

from yapi_mock import init_yapi, set_advmock

init_yapi(email='...', password='...')

# 先用 create_mock / list_mocks 拿到接口 ID,再调:
set_advmock(
    iface_id='5933',   # 接口 _id(字符串)
    script=r"""
if(params.leibie=='手机'){
    mockJson = {"data": [{"name":"苹果手机","value":25500}]}
}else if(params.leibie=='电脑'){
    mockJson = {"data": [{"name":"MacBook","value":47500}]}
}else{
    mockJson = {"data": [...]}   // 兜底默认值,无参数时返回
}
""",
    enable=True,
)

Script rules:

  • Use params.xxx to read URL query parameters
  • Use mockJson = {...} to assign the response body (not return)
  • A fallback else branch must be written to ensure there is data when no parameters are present
  • Field names must exactly match the JimuReport dataset's axisX/axisY(or fieldName)

Common configuration quick reference

Freeze rows and columns (freeze)

base_save(report_id, designer, ..., freeze="A3", freezeLineColor="red")
freeze value Effect
"A1" Do not freeze (default)
"A2" Freeze row 1
"A3" Freeze the first 2 rows
"C1" Freeze the first 2 columns
"C3" Freeze both the first 2 rows and the first 2 columns

Report background color (background)

# 启用背景色(在 base_save override 里传,或 patch 时改 design["background"])
base_save(report_id, designer, ..., background={"enabled": True, "color": "#e8f5e9"})

# 禁用背景色
base_save(report_id, designer, ..., background=False)

⚠️ bgColor is written in make_designer(**extra) inInvalid; it must be passed as a top-level override of base_save . When patching an existing report:design["background"] = {"enabled": True, "color": "#hex"} → base_save(id, designer, **design)

Preview page toolbar (rpbar)

btnList / childrenBtnList stores the button numbers of "Display"; deleting numbers from the list hides them,[] = hide all. rpbar must be a dict object, cannot json.dumps()。

base_save(report_id, designer, ..., rpbar={
    "show": True, "pageSize": "",
    "btnList": [1,2,3,4,5,6,7,8,9],
    "childrenBtnList": [7.1,7.2,7.3,7.4,8.1,8.2,8.3,8.4,8.5,8.6]
})
Main button Sub button
1 First page 5 Next page 7.1 Default print 8.1 Export Excel
2 Previous page 6 Last page 7.2 Print current page 8.2 Large data Excel
3 Current/total pages 7 Print 7.3 Paginated scaled print 8.3 Export PDF
4 Rows per page 8 Export 7.4 Overall scaled print 8.4 Export PDF image
9 Clarity 8.5 Export image / 8.6 Export WORD

Chart extData required fields

Field Description
chartId / id = layer_id (both must be filled)
chartType bar.simple / pie.normal / line.simple etc.
dataType "sql" / "api" / "json" / "files" / "javabean"
apiStatus "1"=dynamic SQL / "0"=static JSON
dataId save_db() return value
dbCode Dataset code
axisX / axisY X/Y axis field names
series Grouping field name, pass for single series ""

Drill-down/linkage linkType reference

linkType Description reportId paramValue
"0" Report drill-down Target report ID Field name
"1" Web link Current report ID Field name
"2" Table→Chart Linkage Current report ID Field name
"2" Chart→Chart Linkage Current report ID name/value/seriesName
"4" Main-sub report Current report ID —
  • Cell trigger:cell["linkIds"] = link_id + cell["display"] = "link"
  • Chart trigger:extData["linkIds"] = link_id(placed inside extData, not at the top level)
  • Linkage must have linkChartId(target chart layer_id); the target chart paramList must set a paramValue default

Image cell (local image insertion)

⚠️ Do not use base64Img for large images (>20KB): base64 encoding bloats the JSON to MB scale, making save/load extremely slow.

Correct approach: first upload to get a URL, then use display:"img" to reference:

from report_tools import upload_image  # 封装好的上传函数
from jimureport_utils import Session

session = Session(BASE_URL, TOKEN)
msg, _ = upload_image(session, r"C:\path\to\image.png")
# OSS 场景 msg 已是完整 https URL;本地存储需拼前缀
img_url = msg if msg.startswith("http") else f"{BASE_URL}/img/{msg}"

Cell configuration (customRows or patch method):

{"text": "http://...oss-url.../image.png", "display": "img", "merge": [extraRows, extraCols]}

base64Img Only for very small icons (< 20KB). Overprint background images also use upload_image, which has been internally corrected to the /upload endpoint (2026-05-25).

searchMode mapping

searchMode Control
1 Input box
2 Range query
3 Dropdown multi-select
4 Dropdown single-select
5 Fuzzy query
6 Dropdown tree
8 Time control