jeecg-onlchart について
jeecg-onlchartは、チャートの要望をJeecgBootのOnlineチャート設定に変換し、API経由で作成・編集するスキルです。SQL、JSON、APIのデータ形式に対応し、変更の実行前にユーザー確認を求めます。
主な機能
- 01チャート要望を設定に変換
- 02SQL・JSON・APIの項目解析
- 03項目の表示・検索設定を推定
- 04棒・折れ線・円グラフの選択
- 05API経由でチャートを作成・編集
- 06必要に応じてメニューと権限を設定
活用シーン
- 01自然言語の要望からデータチャートを作成
- 02既存チャートの項目や種類を変更
- 03SQL検索結果を可視化用に設定
- 04確認後にチャートを公開しメニューを生成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jeecgboot/ skills --skill jeecg-onlchartインストール後、Claude Code で/jeecg-onlchartを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
docs/skill-usage-guide.mdscripts/onlchart_api.pyyapi_mock.py詳細
JeecgBoot Online グラフ AI 自動生成ツール
自然言語によるグラフ要件の説明を Online グラフの設定に変換し、API 経由で JeecgBoot システム内にグラフを自動作成/編集します。
重要:このスキルは「Online グラフ」(SQL 駆動のデータ可視化グラフ)を扱います。「Online レポート」(cgreport データ一覧)や「Online フォーム」(cgform)は対象外です。
前提条件
ユーザーは以下の情報を提供する必要があります(または AI が案内して確認します)。
- API アドレス:JeecgBoot バックエンドのアドレス(例:
https://boot3.jeecg.com/jeecgboot) - X-Access-Token:JWT ログイントークン(ブラウザーの F12 から取得)
ユーザーが提供していない場合は、次のように案内します。
JeecgBoot バックエンドのアドレスと X-Access-Token(ブラウザーで F12 → Network → 任意のリクエストの Request Headers からコピー)を提供してください。
対話フロー
Step 0: 操作タイプを判断する
| ユーザーの意図を示すキーワード | 操作タイプ |
|---|---|
| 作成/新規作成/作って/グラフを生成 | グラフの新規作成 → Step 1A |
| グラフを変更/フィールドを変更/グラフタイプを変更 | グラフの編集 → Step 1B |
Step 1A: グラフの新規作成 — 要件を解析する
ユーザーの説明から以下の情報を抽出します。
| 情報 | 必須 | デフォルト値 | 例 |
|---|---|---|---|
| グラフコード (code) | はい | snake_case で自動生成 | tj_user_sex |
| グラフ名 (name) | はい | ユーザーが指定 | "男女比を集計" |
| SQL 文 (cgrSql) | はい | 要件から推測するか、ユーザーが指定 | select count(*) cout, sex from sys_user group by sex |
| X 軸フィールド (xaxisField) | はい | SQL から推測 | sex |
| Y 軸フィールド (yaxisField) | はい | SQL から推測 | cout |
| グラフタイプ (graphType) | はい | bar | bar、line、pie、line,bar |
| 表示テンプレート (displayTemplate) | いいえ | tab | tab、single、double |
| データソース (dbSource) | いいえ | 空(デフォルトのデータソース) | second_db |
| データタイプ (dataType) | いいえ | sql | sql |
X/Y 軸の推測ルール:
- X 軸 (xaxisField):通常は分類/ディメンションフィールドです(例:sex、dept、month、category)
- Y 軸 (yaxisField):通常はメジャー/集計フィールドです(例:count、sum、avg の結果)
Step 1B: グラフの編集 — 既存の設定を取得する
- ユーザーからグラフ ID またはコードを受け取ります
- API 経由で既存のグラフ設定を取得します(API 一覧を参照)
- 既存の設定を表示し、ユーザーの要件に応じて変更します
Step 2: フィールドを解析する(dataType に応じてインターフェースを選択)
2A. SQL タイプ — parseSql インターフェースを再利用
GET /online/cgreport/head/parseSql?sql={urlEncodedSql}&dbKey={dbKey}
sql:URL エンコードされた SQL 文dbKey:データソースコード。デフォルトのデータソースの場合は省略できます
戻り値の構造:
{
"success": true,
"result": {
"fields": [
{ "fieldName": "cout", "fieldTxt": "cout", "fieldType": "String", "isShow": 1, "orderNum": 1 }
],
"params": []
}
}
注意:parseSql が返す
isShowは数値(0/1)ですが、グラフインターフェースでは文字列"Y"/"N"が必要なため、変換する必要があります。
2B. JSON/API タイプ — parseField インターフェースを使用
POST /online/graphreport/head/parseField?type=JSON
POST /online/graphreport/head/parseField?type=API
リクエストボディ(JSON タイプでは JSON 文字列を、API タイプではインターフェース URL を渡します):
{ "data": "[{\"month\":\"01\",\"amount\":1000}]" }
戻り値の構造(parseSql と同じ形式):
{
"success": true,
"result": {
"fields": [
{ "fieldName": "month", "fieldTxt": "month", "fieldType": "String", "isShow": 1 },
{ "fieldName": "amount", "fieldTxt": "amount", "fieldType": "Integer", "isShow": 1 }
],
"params": []
}
}
JSON/API タイプでは
dbSourceの設定は不要です。cgrSqlフィールドには JSON 文字列または API URL を格納します。
API データ形式の要件
API インターフェースの戻り値データは、{"data": [...]} 構造で必ずラップする必要があります。そうしないとグラフを解析できません。
// ✓ 正确
{"data": [{"name": "一月", "value": 120}, {"name": "二月", "value": 200}]}
// ✗ 错误(裸数组,图表无法识别)
[{"name": "一月", "value": 120}]
parseField が失敗した場合の処理
parseField?type=API を使用するには、JeecgBoot サーバーからその URL にアクセスできる必要があります。サーバーが外部ネットワークにアクセスできず失敗する場合は、parseField をスキップして items を手動で構築します(フィールドが既知であれば直接定義できます)。
YApi Mock を使用して API データソースを作成する
プロジェクトには YApi Mock プラットフォーム(https://api.jeecg.com)が組み込まれており、API データソースとして使用するモックインターフェースをすばやく作成できます。
scripts/yapi_mock.py スクリプトを使用して操作します。認証情報は以下のルールに従って取得し、ハードコードは禁止です。
- まず、現在のコンテキスト(システムプロンプト、memory、グローバル設定など、任意の情報源)から YApi のメールアドレスとパスワードを探します
- コンテキスト内で見つからない場合は、必ずユーザーに尋ねます。
データソースの作成に YApi Mock を使用するため、ログイン用のメールアドレスとパスワードを提供してください(プラットフォームのアドレス:https://api.jeecg.com)。
yapi_mock.pyは内部でhttp.cookiejar.CookieJar + build_openerを使用してセッションを管理しており、Python 3.6 / 3.9 / 3.12 以降のすべてのバージョンに対応しています。init_yapi(email, password)を直接呼び出してください。
import sys
sys.path.insert(0, '<skill目录>/scripts')
from yapi_mock import init_yapi, create_mock
# 凭证由 Claude 从 memory 读取后注入,不得硬编码
init_yapi(email='<email>', password='<password>')
# 创建 mock 接口并写入数据,返回完整 mock URL
mock_url = create_mock(
path='/staff', # 路径后缀,不含 basepath(/claude)
title='职员信息',
data=[
{"name": "张三", "salary": 18000},
{"name": "李四", "salary": 15000},
]
)
print(mock_url) # https://api.jeecg.com/mock/57/claude/staff
パスのルール(重要):プロジェクトの basepath は /claude です。インターフェースパスには末尾部分のみを指定します。
| 指定する path | 完全な mock URL |
|---|---|
/staff | https://api.jeecg.com/mock/57/claude/staff |
/line | https://api.jeecg.com/mock/57/claude/line |
Step 3: フィールドを自動設定する
3.1 フィールド属性のマッピング(グラフとレポートの違い)
重要な違い:グラフのフィールドでは、数値の 0/1 ではなく文字列 "Y"/"N" を使用します。
| 属性 | グラフ (graphreport) | レポート (cgreport) | 説明 |
|---|---|---|---|
| 関連するヘッダー ID | graphreportHeadId | cgrheadId | フィールド名が異なります |
| 表示するかどうか | isShow: "Y"/"N" | isShow: 0/1 | 型が異なります |
| 合計するかどうか | isTotal: "Y"/"N" | isTotal: "0"/"1" または null | 型が異なります |
| 検索するかどうか | searchFlag: "Y"/"N" | isSearch: 0/1 | フィールド名と型が異なります |
| 検索モード | searchMode | searchMode | 同じです |
| 辞書 | dictCode | dictCode | 同じです |
| 並び順 | orderNum | orderNum | 同じです |
3.2 フィールド表示名 (fieldTxt)
parseSql が返す fieldTxt はデフォルトで fieldName と同じです。AI は意味に基づいて中国語に翻訳する必要があります。
| フィールド名のパターン | 推測される中国語名 |
|---|---|
| count / cout / cnt | 数量/人数/回数 |
| sum / total / amount | 合計/総額 |
| avg / average | 平均値 |
| sex | 性別 |
| dept / department | 部門 |
| status | ステータス |
| type / category | タイプ/分類 |
| month / year / date | 月/年/日付 |
| name / title | 名前 |
| age | 年齢 |
| salary | 給与 |
3.3 表示するかどうか (isShow)
| ルール | isShow |
|---|---|
| すべてのフィールド(デフォルト) | "Y"(グラフのフィールドは通常少ないため、すべて表示します) |
| id/主キー項目 | "N" |
3.4 検索するかどうか (searchFlag) + 検索モード (searchMode)
| フィールドタイプ | searchFlag | searchMode |
|---|---|---|
| 分類/ディメンションフィールド | "Y" | single |
| 日付/時刻フィールド | "Y" | group |
| メジャー/集計フィールド | "N" | null |
3.5 是否合计 (isTotal)
| 规则 | isTotal |
|---|---|
| 度量/聚合字段 | "Y" |
| 维度/分类字段 | "N" |
3.6 字典配置 (dictCode) — 列表数据值替换显示
作用:列表(明细表格区域)中,将数据库存储的原始值替换为可读文本显示。
例:性别字段数据库存
1/2,配置字典后显示为"男"/"女"。
支持两种方式:
方式一:系统字典编码
填写系统字典的 dictCode,由系统字典表自动解析值 → 文本。
"dictCode": "sex"
常用系统字典编码:
| dictCode | 说明 |
|---|---|
sex | 性别(1=男,2=女) |
priority | 优先级 |
valid_status | 有效状态 |
yn | 是/否 |
方式二:字典 SQL
在 dictCode 处直接写一条 SELECT 语句,动态替换显示值。SQL 必须返回两列:value(数据库存的值)和 text(展示的文本)。
"dictCode": "SELECT id as value, name as text FROM sys_category WHERE pid = '1'"
"dictCode": "SELECT code as value, name as text FROM sys_depart ORDER BY depart_order"
注意:字典 SQL 每次渲染都会执行查询,数据量大时建议加
WHERE条件限制范围。
Step 4: 图表类型选择
根据数据特征推荐图表类型:
| 数据场景 | 推荐 graphType | 说明 |
|---|---|---|
| 分类对比(如男女人数) | bar | 柱状图 |
| 趋势变化(如月度销售) | line | 折线图 |
| 占比分布(如部门比例) | pie | 饼图 |
| 趋势+对比(如月度销售对比) | line,bar | 组合图表 |
| 纯数据明细 | table | 数据表格(只渲染在底部明细区,不占图表位) |
graphType 支持逗号分隔多种类型,如 "bar,pie" 会同时生成柱状图和饼图两个区域。
组合图表配置(折线+柱状同坐标系):
graphType:"line,bar"(逗号分隔多种类型)isCombination:"combination"(标记为组合图表)- 非组合图表
isCombination为 null 或不传
Step 5: 展示摘要并确认
必须展示以下内容,等待用户确认后再执行:
## Online 图表配置摘要
- 图表编码:tj_user_sex
- 图表名称:统计男女比例
- 图表类型:bar(柱状图)
- X 轴字段:sex(性别)
- Y 轴字段:cout(人数)
- 数据源:默认
- 目标环境:https://boot3.jeecg.com/jeecgboot
### SQL 语句
select count(*) cout, sex from sys_user group by sex
### 字段配置
| 序号 | 字段名 | 显示名称 | 类型 | 显示 | 查询 | 字典 | 合计 |
|------|--------|---------|------|------|------|------|------|
| 0 | cout | 人数 | String | Y | N | - | Y |
| 1 | sex | 性别 | String | Y | N | sex | N |
### 参数
(无)
确认以上配置?(y/n)
Step 6: 校验编码可用性(仅新增时)
用户确认后,新增图表前必须先校验 code 是否已被占用:
GET /sys/duplicate/check?tableName=onl_graphreport_head&fieldName=code&fieldVal={code}
返回结构:
{ "success": true, "result": true } // true = 可用(未重复)
{ "success": true, "result": false } // false = 已存在,需换一个 code
若 result 为 false,提示用户更换编码,不继续执行创建。
Python 示例:
encoded_code = urllib.parse.quote(report_code)
check = api_request(f'/sys/duplicate/check?tableName=onl_graphreport_head&fieldName=code&fieldVal={encoded_code}')
if not check.get('result'):
print(f'图表编码 "{report_code}" 已存在,请换一个编码')
exit(1)
print(f'编码 "{report_code}" 可用,继续创建...')
Step 7: 调用 API 创建/编辑图表
用户确认且编码校验通过后执行。
6.1 新增图表 — 请求结构
POST /online/graphreport/head/add
{
"dbSource": "",
"name": "统计男女比例",
"code": "tj_user_sex",
"displayTemplate": "tab",
"xaxisField": "sex",
"yaxisField": "cout",
"dataType": "sql",
"graphType": "bar",
"cgrSql": "select count(*) cout, sex from sys_user group by sex",
"onlGraphreportItemList": [
{
"id": "前端生成的长数字ID",
"cgrheadId": null,
"fieldName": "cout",
"fieldTxt": "人数",
"fieldWidth": null,
"fieldType": "String",
"searchMode": null,
"isOrder": null,
"isSearch": null,
"dictCode": null,
"fieldHref": null,
"isShow": "Y",
"orderNum": 0,
"replaceVal": null,
"isTotal": null,
"createBy": null,
"createTime": null,
"updateBy": null,
"updateTime": null,
"groupTitle": null
}
],
"paramsList": []
}
注意(add 接口):add 时 items 中的关联ID字段名为
cgrheadId(值为 null),虽然查询/编辑时返回的是graphreportHeadId。
6.2 编辑图表 — 请求结构
PUT /online/graphreport/head/edit
{
"id": "1290934362649460737",
"name": "统计男女比例",
"code": "tj_user_bysex",
"cgrSql": "select count(*) cout, sex from sys_user group by sex",
"xaxisField": "sex",
"yaxisField": "cout",
"yaxisText": "yaxis_text",
"content": null,
"extendJs": null,
"graphType": "line,bar",
"isCombination": "combination",
"displayTemplate": "tab",
"dataType": "sql",
"dbSource": "",
"tenantId": 0,
"lowAppId": null,
"onlGraphreportItemList": [
{
"id": "1290934166687383554",
"graphreportHeadId": "1290934362649460737",
"fieldName": "cout",
"fieldTxt": "人数",
"isShow": "Y",
"isTotal": "N",
"searchFlag": "N",
"searchMode": null,
"dictCode": "",
"fieldHref": null,
"fieldType": "String",
"orderNum": 0,
"replaceVal": null,
"createBy": "admin",
"createTime": "2020-08-05 17:03:06",
"updateBy": null,
"updateTime": null
}
],
"paramsList": []
}
add 与 edit 字段差异:
| 字段 | add | edit | 说明 |
|---|---|---|---|
id (head) | 不传 | 必传 | 图表头ID |
yaxisText | 不传 | 可选 | Y轴标签文字 |
content | 不传 | 可选 | 自定义内容 |
extendJs | 不传 | 可选 | 扩展JS |
isCombination | 不传 | 可选 | 组合图表标记 |
tenantId | 不传 | 传回原值 | 租户ID |
| Item 关联ID字段 | cgrheadId: null | graphreportHeadId: headId | 字段名不同 |
Item isShow | "Y"/"N" | "Y"/"N" | 一致 |
Item searchFlag | 不存在,用 isSearch | searchFlag: "Y"/"N" | add 和 edit 可能不同 |
6.3 字段 ID 生成规则
- add 时使用雪花ID格式(19位数字字符串),如
"2033369959277633538" - 可用 Python 的
str(int(time.time() * 1000) * 1000000 + random.randint(100000, 999999))近似生成
6.4 使用 Python 调用 API
重要限制:
- Windows 环境下 curl 发送中文/长 JSON 会出错,必须使用 Python
- 禁止使用
python3 -c "..."内联方式 - 必须先用 Write 工具写入
.py临时文件,再用 Bash 执行,最后删除临时文件
推荐方式:使用 onlchart_api.py 封装脚本(与 jeecg-onlreport 的 onlreport_api.py 同模式)
脚本路径: skill 加载时开头已提供 Base directory for this skill: <skill_base_dir>,scripts 目录即 <skill_base_dir>\scripts。
import sys
sys.path.insert(0, r'<skill_base_dir>\scripts')
from onlchart_api import init_api, build_item, build_param, create_chart
init_api('<api_base>', '<token>')
items = [
build_item('sex', '性别', search_flag='Y', search_mode='single', dict_code='sex', order_num=0),
build_item('cout', '人数', is_total='Y', order_num=1),
]
# 默认只创建图表,不挂菜单、不授权
# 如需发布,改用 create_and_publish() 或单独调用 publish_chart()
result = create_chart(
code='tj_user_sex',
name='用户性别分布',
sql='SELECT sex, count(*) cout FROM sys_user GROUP BY sex',
x='sex', y='cout',
graph_type='bar',
items=items,
)
可用函数一览:
| 函数 | 说明 |
|---|---|
init_api(api_base, token) | 初始化连接(必须最先调用) |
build_item(field_name, ...) | 构建字段配置 |
build_param(param_name, ...) | 构建自定义参数 |
parse_sql(sql, db_key) | 解析 SQL 字段 |
check_code_available(code) | 校验编码是否可用 |
create_chart(code, name, sql, x, y, items, ...) | 创建图表 |
edit_chart(head_id, ...) | 编辑图表 |
publish_chart(head_id, name, role_code) | 挂载菜单 + 授权角色 |
create_and_publish(code, name, sql, x, y, items, ...) | 一键全流程 |
list_charts() | 查询图表列表 |
query_chart(head_id) | 查询图表详情 |
get_chart_id_by_code(code) | 按编码查 head_id |
低级手写脚本模板(不推荐,优先使用上方封装):
import urllib.request
import json
import time
import random
import ssl
import urllib.parse
API_BASE = '{用户提供的后端地址}'
TOKEN = '{用户提供的 X-Access-Token}'
# 忽略SSL验证(开发环境)
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
def api_request(path, data=None, method=None):
"""发送 API 请求"""
url = f'{API_BASE}{path}'
headers = {
'X-Access-Token': TOKEN,
'Content-Type': 'application/json; charset=UTF-8'
}
if data is not None:
json_data = json.dumps(data, ensure_ascii=False).encode('utf-8')
if method is None:
method = 'POST'
req = urllib.request.Request(url, data=json_data, headers=headers, method=method)
else:
if method is None:
method = 'GET'
req = urllib.request.Request(url, headers=headers, method=method)
resp = urllib.request.urlopen(req, context=ctx)
return json.loads(resp.read().decode('utf-8'))
def gen_id():
"""生成雪花ID格式的字符串(19位数字)"""
return str(int(time.time() * 1000) * 1000000 + random.randint(100000, 999999))
# ====== Step 1: 调用 parseSql 解析字段 ======
sql = "select count(*) cout, sex from sys_user group by sex"
encoded_sql = urllib.parse.quote(sql, safe='')
parse_result = api_request(f'/online/cgreport/head/parseSql?sql={encoded_sql}')
print('解析结果:', json.dumps(parse_result, ensure_ascii=False, indent=2))
if not parse_result.get('success'):
print('SQL 解析失败:', parse_result.get('message'))
exit(1)
# ====== Step 2: 构造字段配置 ======
items = [
{
"id": gen_id(), "cgrheadId": None,
"fieldName": "cout", "fieldTxt": "人数",
"fieldWidth": None, "fieldType": "String",
"searchMode": None, "isOrder": None, "isSearch": None,
"dictCode": None, "fieldHref": None,
"isShow": "Y", "orderNum": 0,
"replaceVal": None, "isTotal": None,
"createBy": None, "createTime": None,
"updateBy": None, "updateTime": None, "groupTitle": None
},
{
"id": gen_id(), "cgrheadId": None,
"fieldName": "sex", "fieldTxt": "性别",
"fieldWidth": None, "fieldType": "String",
"searchMode": None, "isOrder": None, "isSearch": None,
"dictCode": "sex", "fieldHref": None,
"isShow": "Y", "orderNum": 1,
"replaceVal": None, "isTotal": None,
"createBy": None, "createTime": None,
"updateBy": None, "updateTime": None, "groupTitle": None
}
]
# ====== Step 3: 构造请求 ======
graph_data = {
"dbSource": "",
"name": "统计男女比例",
"code": "tj_user_sex",
"displayTemplate": "tab",
"xaxisField": "sex",
"yaxisField": "cout",
"dataType": "sql",
"graphType": "bar",
"cgrSql": sql,
"onlGraphreportItemList": items,
"paramsList": []
}
# ====== Step 4: 调用 add API 创建图表 ======
result = api_request('/online/graphreport/head/add', graph_data)
print('创建结果:', json.dumps(result, ensure_ascii=False, indent=2))
if result.get('success'):
print('\n图表创建成功!')
else:
print('\n创建失败:', result.get('message'))
编辑图表脚本差异:
# 编辑时用 PUT 方法,且 items 使用 graphreportHeadId
graph_data = {
"id": "existing_head_id",
"name": "统计男女比例",
"code": "tj_user_bysex",
"cgrSql": sql,
"xaxisField": "sex",
"yaxisField": "cout",
"yaxisText": "",
"content": None,
"extendJs": None,
"graphType": "line,bar",
"isCombination": "combination",
"displayTemplate": "tab",
"dataType": "sql",
"dbSource": "",
"tenantId": 0,
"lowAppId": None,
"onlGraphreportItemList": [
{
"id": "existing_item_id",
"graphreportHeadId": "existing_head_id",
"fieldName": "cout", "fieldTxt": "人数",
"isShow": "Y", "isTotal": "N",
"searchFlag": "N", "searchMode": None,
"dictCode": "", "fieldHref": None,
"fieldType": "String", "orderNum": 0,
"replaceVal": None
}
],
"paramsList": []
}
result = api_request('/online/graphreport/head/edit', graph_data, method='PUT')
Step 8: 生成菜单 SQL(可选)
默认不创建菜单、不授权。 只有用户明确说「挂菜单」「授权」「发布」时才执行此步骤。
图表创建成功后,可生成菜单 SQL:
重要:菜单 URL 使用图表的 head_id,不是 code。 路由格式:
/online/graphreport/chart/{head_id}
# 查询刚创建的图表,获取 head_id
list_result = api_request(f'/online/graphreport/head/list?code={urllib.parse.quote(report_code)}')
if list_result.get('success') and list_result['result']['records']:
head_id = list_result['result']['records'][0]['id']
report_name = list_result['result']['records'][0]['name']
print(f'\n图表 ID: {head_id}')
print(f'\n### 菜单 SQL(可选执行)')
print(f"""
INSERT INTO sys_permission (
id, parent_id, name, url, component, component_name,
is_route, is_leaf, keep_alive, hidden, hide_tab, description,
del_flag, rule_flag, status, internal_or_external,
perms_type, sort_no, menu_type, route_redirect
) VALUES (
'{head_id}', NULL, '{report_name}',
'/online/graphreport/chart/{head_id}',
'super/online/graphreport/auto/GraphreportAutoChart',
NULL,
1, 1, 0, 0, 0, NULL,
0, 0, '1', 0,
'0', 1.0, 1, NULL
);
""")
Step 9: 输出结果
本地环境自动执行菜单 SQL 规则:
如果 API_BASE 以 http://127.0.0.1 或 http://localhost 开头(不区分大小写),在生成菜单 SQL 后,自动通过 Bash 工具执行 MySQL 命令插入菜单:
# 先检查是否已存在,避免重复插入
mysql -h127.0.0.1 -P3306 -uroot -proot jeecgboot3 -e "SELECT id FROM sys_permission WHERE id='{head_id}'"
# 不存在则执行插入
mysql -h127.0.0.1 -P3306 -uroot -proot jeecgboot3 -e "INSERT INTO sys_permission (...) VALUES (...);"
- 如果 MySQL 执行失败,回退为输出 SQL 让用户手动执行,不中断整体流程
- 数据库连接参数默认
mysql -h127.0.0.1 -P3306 -uroot -proot jeecgboot3,与 jeecg-codegen 保持一致
## Online 图表创建成功
- 图表编码:{code}
- 图表名称:{name}
- 图表类型:{graphType}
- X 轴:{xaxisField}
- Y 轴:{yaxisField}
- 字段数量:{N} 个
- 目标环境:{API_BASE}
- 菜单 SQL:{已自动执行 ✓ / 需手动执行}
### 菜单 SQL
INSERT INTO sys_permission (...) VALUES (...);
### 后续操作
1. 打开 JeecgBoot 后台 → Online图表
2. 找到该图表,点击「功能测试」预览效果
3. 如菜单未自动执行,手动执行上方 SQL 或在后台手动添加
4. 可在「编辑」中调整图表类型、字段等配置
高级功能
组合图表
支持在同一图表中展示多种图表类型:
{
"graphType": "line,bar",
"isCombination": "combination"
}
组合图表会在同一坐标系中同时展示折线和柱状图。
Y 轴标签 (yaxisText)
自定义 Y 轴显示文字:
{
"yaxisText": "人数(单位:人)"
}
JS 增强 (extendJs)
全局变量
| 变量 | 说明 |
|---|---|
headId | 当前图表的 ID |
onClick | 用于添加点击事件的全局对象 |
添加点击事件
通过 onClick.图表类型 = function(event) {} 的方式挂载,不同图表类型分别定义:
// 柱状图点击
onClick.bar = function(event) {
this.$message.success('点击了柱状图!')
}
// 折线图点击
onClick.line = function(event) {
this.$message.info('点击了折线图!')
}
// 饼图点击
onClick.pie = function(event) {
this.$message.info('点击了饼图!')
}
this 上下文(可用 API)
extendJs 中函数体的 this 指向一个固定对象(源码 onClickThis),包含以下可用属性:
| 属性 | 类型 | 说明 |
|---|---|---|
this.$message | Ant Design Message | 轻提示(顶部小条,自动消失) |
this.$confirm | Modal confirm | 确认对话框(需传入 options 对象) |
this.$info | Modal.info | 信息对话框 |
this.$success | Modal.success | 成功对话框 |
this.$error | Modal.error | 错误对话框 |
this.$warning | Modal.warning | 警告对话框 |
this.$router | Vue Router | 路由跳转 |
this.$http | defHttp (Axios) | HTTP 请求 |
$message — 轻提示(最常用):
this.$message.success('操作成功') // 绿色
this.$message.info('提示信息') // 蓝色
this.$message.warning('注意!') // 黄色
this.$message.error('发生错误') // 红色
this.$message.loading('加载中...') // 转圈
$confirm / $info / $success / $error / $warning — 模态对话框:
// $confirm:带确认/取消按钮的对话框
this.$confirm({
iconType: 'warning',
title: '确认操作',
content: '职业:' + event.name + ',确认查看详情?',
onOk: () => {
this.$router.push('/detail?occupation=' + event.name)
},
onCancel: () => {}
})
// $info / $success / $error / $warning:纯展示对话框(只有关闭按钮)
this.$success({ title: '成功', content: '数值:' + event.value })
this.$info({ title: '信息', content: event.name + ' 的平均薪资:' + event.value })
$router — 路由跳转:
// 跳转到系统内部页面
this.$router.push('/system/user')
// 带参数跳转
this.$router.push({ path: '/online/cgreport/xxx', query: { occupation: event.name } })
$http — HTTP 请求(defHttp,Axios 封装):
onClick.bar = function(event) {
var self = this // 保存 this 引用,供回调中使用
self.$message.loading('查询中...')
self.$http.get({
url: '/sys/user/list',
params: { occupation: event.name }
}).then(function(res) {
self.$message.success('查到 ' + res.total + ' 条记录')
})
}
⚠️ 注意:
$http回调(.then/.catch)中this已不是onClickThis,必须提前用var self = this保存引用,或改用箭头函数。
事件参数 (event)
Online 图表底层使用 ECharts,
event是标准 ECharts 事件对象(不是自定义对象)。 直接打印event会看到[object Object],需要读取具体属性。
通用参数(柱状图 / 折线图 / 饼图均有):
| 参数 | 类型 | 说明 |
|---|---|---|
event.name | string | X 轴类目值(柱/线)或扇区名称(饼) |
event.value | number | Y 轴数值(柱/线)或扇区数值(饼) |
event.seriesName | string | 系列名称(Y 轴字段的 fieldTxt) |
event.seriesType | string | 系列类型:'bar'、'line'、'pie' |
event.dataIndex | number | 数据在数组中的索引(从 0 开始) |
event.color | string | 当前元素颜色(十六进制) |
饼图额外参数:
| 参数 | 类型 | 说明 |
|---|---|---|
event.percent | number | ECharts 自动计算的百分比(如 66.7),可直接使用 |
onClick.bar = function(event) {
// event.name = X轴类目,event.value = Y轴值,event.seriesName = 系列名
this.$message.info('X轴: ' + event.name + ',值: ' + event.value)
}
onClick.line = function(event) {
// ECharts 折线图:只有点击数据点才触发,点击连接线不触发事件
this.$router.push('/detail?name=' + event.name)
}
onClick.pie = function(event) {
// event.name = 扇区名称(字典翻译后的文本,如"男"/"女")
// event.percent = ECharts 自动计算的百分比
this.$message.info(
'点击了:' + event.name
+ ',值:' + event.value
+ ',占比:' + event.percent.toFixed(1) + '%'
)
}
组合图(line,bar)区分系列:
// 组合图注册两个事件,通过 event.seriesName 区分点击的是哪条系列
onClick.bar = function(event) {
this.$message.success('[柱] ' + event.name + ':' + event.value)
}
onClick.line = function(event) {
this.$message.info('[线] ' + event.name + ':' + event.value)
}
自定义内容 (content)
用于自定义渲染模板或说明内容。
AIGC — AI 自动生成图表
系统内置 AI 生成能力,可基于已有的 Online 表单(cgform)表,通过自然语言描述需求自动生成图表配置:
POST /online/graphreport/head/api/aigc?cgformTableName={表名}&prompt={需求描述}
cgformTableName:Online 表单的数据库表名(如oa_leave)prompt:用自然语言描述图表需求(如 "统计各部门请假次数")- 超时建议设为 60000ms
返回结构:
{
"success": true,
"result": {
"code": "dept_leave_count",
"name": "各部门请假次数",
"cgrSql": "SELECT dept, COUNT(*) as cnt FROM oa_leave GROUP BY dept",
"graphType": "bar",
"xaxisField": "dept",
"yaxisField": "cnt",
"displayTemplate": "tab",
"onlGraphreportItemList": [...]
}
}
AIGC 返回的配置可直接作为 add 接口的请求体使用,通常无需修改即可创建。
参数化查询
图表支持两种参数语法,注意区分:
自定义参数:${参数名}
在 SQL 中用 ${} 拼接 WHERE 条件,{} 内的文本就是参数名,需与 paramsList 中配置的 paramName 完全一致。
文本类型参数必须加引号:
-- 文本字段加引号
SELECT count(*) cnt, dept FROM sys_user
WHERE status = '${status}' AND dept = '${dept}'
GROUP BY dept
-- 数字字段不加引号
SELECT * FROM order WHERE amount > ${min_amount}
对应的 paramsList 配置:
[
{"paramName": "status", "paramTxt": "状态", "paramValue": "1", "orderNum": 1},
{"paramName": "dept", "paramTxt": "部门", "paramValue": "", "orderNum": 2},
{"paramName": "min_amount", "paramTxt": "最低金额", "paramValue": "0", "orderNum": 3}
]
使用方式:
- URL 传参:
/online/graphreport/chart/{head_id}?status=1&dept=研发部 - 菜单配置时直接在 URL 上设置固定值
- 未传参则使用
paramValue中配置的默认值
系统变量:#{变量名}
系统变量用 #{} 语法(不是 ${}),由系统自动注入当前登录用户信息,无需配置到 paramsList:
SELECT * FROM sys_user
WHERE org_code = '#{sys_org_code}'
AND create_by = '#{sys_user_code}'
AND create_time >= '#{sys_date}'
可用系统变量:
| 变量名 | 说明 | 版本要求 |
|---|---|---|
sys_user_code | 当前登录用户账号 | - |
sys_user_name | 当前登录用户真实姓名 | - |
sys_date | 当前系统日期 | - |
sys_time | 当前系统时间 | - |
sys_org_code | 当前登录用户部门编号 | - |
tenant_id | 当前登录用户租户 ID | v3.4.5+ |
sys_base_path | Java 服务 basePath(仅 API 模式支持) | v2.4.4+ |
混用示例(自定义参数 + 系统变量):
SELECT dept, count(*) cnt FROM sys_user WHERE org_code = '#{sys_org_code}' AND status = '${status}' GROUP BY dept
动态数据源
查询非默认数据源的数据:
{
"dbSource": "second_db"
}
API の完全な一覧
| 操作 | 方法 | パス | 説明 |
|---|---|---|---|
| SQL 解析(レポートを再利用) | GET | /online/cgreport/head/parseSql?sql={encodedSql}&dbKey={dbKey} | SQL の型フィールドを解析 |
| JSON/API フィールド解析 | POST | /online/graphreport/head/parseField?type=JSON|API | JSON または API URL のフィールドを解析 |
| コードの重複チェック | GET | /sys/duplicate/check?tableName=onl_graphreport_head&fieldName=code&fieldVal={code} | result=true は使用可能、false は既存 |
| グラフの追加 | POST | /online/graphreport/head/add | グラフを作成 |
| グラフの編集 | PUT | /online/graphreport/head/edit | グラフを変更 |
| 一覧の検索 | GET | /online/graphreport/head/list?code={code} | グラフ一覧を検索(ページネーション) |
| 詳細の検索 | GET | /online/graphreport/head/queryById?id={headId} | ID でグラフヘッダーを検索 |
| フィールド一覧の検索 | GET | /online/graphreport/head/queryOnlGraphreportItemByMainId?headId={headId} | グラフに関連付けられたフィールドを検索 |
| 単体削除 | DELETE | /online/graphreport/head/delete?id={id} | グラフを削除 |
| 一括削除 | DELETE | /online/graphreport/head/deleteBatch?ids={id1,id2} | 複数のグラフを削除 |
| グラフデータの取得 | GET | /online/graphreport/api/getChartsData?id={id}¶ms={json} | グラフ表示時にレンダリングデータを取得 |
| パラメーター設定の取得 | GET | /online/graphreport/params/listByHeadId?headId={id} | グラフ表示時にパラメーター一覧を取得 |
| AI によるグラフ生成 | POST | /online/graphreport/head/api/aigc?cgformTableName={表名}&prompt={需求} | AIGC で設定を自動生成。タイムアウトは 60 秒 |
| Excel のエクスポート | GET | /online/graphreport/head/exportXls | グラフ設定をエクスポート |
| Excel のインポート | POST | /online/graphreport/head/importExcel | グラフ設定をインポート |
他の Skill との違い
| Skill | 成果物 | 適用シーン |
|---|---|---|
jeecg-graphreport | Online グラフ設定(SQL 駆動、データ可視化) | 棒グラフ、折れ線グラフ、円グラフなどのデータ可視化 |
jeecg-onlreport | Online レポート設定(SQL 駆動、データ一覧) | データ検索レポート、統計一覧、データエクスポート |
jeecg-onlform | Online フォーム設定(メタデータ駆動、CRUD) | データ入力管理フォーム |
jeecg-codegen | Java + Vue3 コード + SQL | カスタムビジネスロジックが必要なモジュール |
jeecg-desform | デザイナーフォーム JSON | データ収集、承認フォーム |
よくある質問
Q1:Y 軸のデータが乱れる/グラフの表示に異常がある
現象:棒グラフや折れ線グラフの Y 軸の値の順序が乱れたり、正しく表示されなかったりします。
原因:Y 軸フィールドの fieldType が数値型ではありません。parseSql はデフォルトですべてのフィールドを String として解析するため、レンダリング時に文字列として並べ替えられ(例:"10" < "9")、表示が乱れます。
解決策:Y 軸フィールドの fieldType を適切な数値型に変更します。
| フィールド値の特徴 | 設定する fieldType |
|---|---|
| 整数(count、人数、数量) | Integer |
| 小数/金額/平均値 | BigDecimal |
| 長整数 | Long |
例:
# 错误:Y 轴字段 fieldType 是 String,图表会乱序
build_item('monthly_salary', '月薪', field_type='String', ...)
# 正确:数值字段必须用数值类型
build_item('monthly_salary', '月薪', field_type='BigDecimal', ...)
build_item('cnt', '数量', field_type='Integer', ...)
build_item('avg_age', '平均年龄', field_type='BigDecimal', ...)
注意:
build_item()のデフォルトはfield_type='String'です。Y 軸の数値フィールドには、正しい型を手動で指定する必要があります。
Q2:YApi Mock の作成後、後続リクエストで「ログインしてください」と表示される
現象:init_yapi() の呼び出しは成功しますが、直後の create_mock() で「ログインしてください...」と表示されます。
原因:旧バージョンの init_yapi() は resp.headers.get('Set-Cookie') でレスポンスヘッダーを手動で解析していました。Python 3.12 では、この方法は最初の Set-Cookie 値しか返しません。YApi のログインには _yapi_token と _yapi_uid の 2 つの cookie が必要なため、手動解析では片方が失われます。
解決策:yapi_mock.py は CookieJar + build_opener 方式にリファクタリング済みです。標準ライブラリがすべての Set-Cookie レスポンスヘッダーを自動で処理するため、init_yapi(email, password) を直接呼び出せばよく、追加の処理は不要です。Python 3.6/3.9/3.12 以降に対応しています。
エラー処理
| エラー | 解決策 |
|---|---|
| トークンの期限切れ(401/認証失敗) | X-Access-Token を再取得するようユーザーに案内します |
图表编码已存在/duplicate check の戻り値が false | 別の code に変更するか、edit を使って編集します |
| parseSql の失敗 | SQL 構文が正しいか、テーブルが存在するか確認します |
| Y 軸のデータが乱れる | Y 軸フィールドの fieldType は数値型(Integer/BigDecimal/Long)である必要があります。String は指定できません |
SQL注入风险 | SQL で DROP/DELETE/UPDATE などの危険なステートメントを使用しないでください |
| 文字化け | Python urllib を使用していることを確認します(curl は使用しないでください) |
実測記録
実測 1:グラフの追加(2026-03-16 検証済み)
テストシーン:「システムログインユーザー統計分析」グラフを作成し、sys_user テーブルのユーザー数を性別ごとに集計します。
設定パラメーター:
- グラフコード:
tj_login_user - グラフ名:システムログインユーザー統計分析
- グラフの種類:
bar(棒グラフ) - X 軸:
sex(性別) - Y 軸:
cout(人数)
SQL:
select count(*) cout, sex from sys_user group by sex
Step 1 — parseSql による解析:
リクエスト:
GET /online/cgreport/head/parseSql?sql=select%20count(*)%20cout%2C%20sex%20from%20sys_user%20group%20by%20sex
戻り値(成功):
{
"success": true,
"code": 200,
"result": {
"fields": [
{
"id": "2033372375880409089",
"fieldName": "cout",
"fieldTxt": "cout",
"fieldType": "String",
"isShow": 1,
"orderNum": 1
},
{
"id": "2033372375880409090",
"fieldName": "sex",
"fieldTxt": "sex",
"fieldType": "String",
"isShow": 1,
"orderNum": 2
}
],
"params": []
}
}
Step 2 — add によるグラフの作成:
リクエスト:
POST /online/graphreport/head/add
リクエストボディ:
{
"dbSource": "",
"name": "系统登录用户统计分析",
"code": "tj_login_user",
"displayTemplate": "tab",
"xaxisField": "sex",
"yaxisField": "cout",
"dataType": "sql",
"graphType": "bar",
"cgrSql": "select count(*) cout, sex from sys_user group by sex",
"onlGraphreportItemList": [
{
"id": "1773628737000000123456",
"cgrheadId": null,
"fieldName": "cout",
"fieldTxt": "人数",
"fieldWidth": null,
"fieldType": "String",
"searchMode": null,
"isOrder": null,
"isSearch": null,
"dictCode": null,
"fieldHref": null,
"isShow": "Y",
"orderNum": 0,
"replaceVal": null,
"isTotal": null,
"createBy": null,
"createTime": null,
"updateBy": null,
"updateTime": null,
"groupTitle": null
},
{
"id": "1773628737000000654321",
"cgrheadId": null,
"fieldName": "sex",
"fieldTxt": "性别",
"fieldWidth": null,
"fieldType": "String",
"searchMode": null,
"isOrder": null,
"isSearch": null,
"dictCode": "sex",
"fieldHref": null,
"isShow": "Y",
"orderNum": 1,
"replaceVal": null,
"isTotal": null,
"createBy": null,
"createTime": null,
"updateBy": null,
"updateTime": null,
"groupTitle": null
}
],
"paramsList": []
}
戻り値(成功):
{
"success": true,
"message": "添加成功!",
"code": 200,
"result": null,
"timestamp": 1773628737956
}
主な確認事項:
- parseSql が返す
fieldTxtはデフォルトでfieldNameと同じ値(例:"cout")です。AI で中国語に翻訳する必要があります(例:「人数」) - parseSql が返す
isShowは数値の1です。add 時に文字列"Y"に変換する必要があります - parseSql が返す
fieldTypeはすべて"String"です。意味に応じて修正する必要があります - add 時、items の
orderNumは 0 から始めても正常に動作します - add 時、items 内の関連 ID フィールド名は
cgrheadId(値は null)であり、graphreportHeadIdではありません - gen_id() で生成された 19 桁の数値文字列は、API で正常に受け付けられます
dictCode: "sex"でシステム辞書を正しく関連付け、値を変換できます- 不要なフィールド値は
nullを渡せばよく、空文字列を渡す必要はありません
実測 2:グラフの編集(ユーザー提供のインターフェースデータで検証済み)
テストシナリオ:既存のグラフを変更し、単一の棒グラフを複合グラフ(折れ線+棒グラフ)に変更します。
リクエスト:
PUT /online/graphreport/head/edit
主なフィールドの変更点:
graphTypeを"bar"から"line,bar"に変更しますisCombination: "combination"を追加します- items 内の関連 ID のフィールド名が
graphreportHeadIdになります(add 時のcgrheadIdとは異なります) - items 内では
isSearchの代わりにsearchFlag("Y"/"N")を使用します
レスポンス:
{
"success": true,
"message": "修改成功!",
"code": 200,
"result": null,
"timestamp": 1773628040311
}
主な発見:
- edit では
PUTメソッドを使用します(POST ではありません) - edit 時の items の関連フィールド名は
graphreportHeadId、add 時はcgrheadIdです — 最も間違えやすい点です - edit 時の検索フィールドには
searchFlag("Y"/"N")を使用し、add 時にはisSearchを使用します - 複合グラフでは
graphType: "line,bar"とisCombination: "combination"の両方を設定する必要があります - edit 時には
tenantId、createTime、createByなどのシステムフィールドに元の値を渡す必要があります
実測 3:API タイプのグラフと YApi Mock の連携作成(2026-04-16 検証済み)
テストシナリオ:データソースに YApi Mock API を使用し、グラフタイプを bar,table(棒グラフ+データ一覧)とする「人物情報グラフ」を作成します。
一連の手順:
init_yapi(email, password)を呼び出して YApi にログインします(CookieJar 方式。すべての Python バージョンで使用できます)create_mock('/person_info', '人物信息', [...])を呼び出して mock インターフェースを作成し、mock URL を取得しますcreate_chart(... data_type='api', sql=mock_url ...)を呼び出してグラフを作成します
主な設定:
dataType:apicgrSql: mock URL を指定します(https://api.jeecg.com/mock/57/claude/person_info)graphType:bar,table(棒グラフとデータ一覧を同時に出力)- mock データは
{"data": [...]}の形式でラップする必要があります。yapi_mock.pyのcreate_mock()が自動的に処理します
主な発見:
yapi_mock.pyのinit_yapi()は CookieJar 方式に変更済みのため、呼び出しスクリプト内で opener を自分で作成する必要はありません。from yapi_mock import init_yapi, create_mockと直接インポートできます- API タイプのグラフでは、
cgrSqlフィールドに格納されるのは SQL 文ではなく API URL です graphType: "bar,table"を指定すると、グラフ領域とデータ一覧領域を同時に表示でき、「グラフと表の両方が必要」という要件を満たせますparseField?type=APIは省略できます。フィールドが分かっている場合は、build_item()を使用して items を手動で構築しても同様に機能します- 認証情報をスクリプトにハードコードしてはいけません。現在のコンテキストから探し、見つからない場合はユーザーに尋ねてから、パラメーターとして渡します