新增JeecgBoot BPM流程自动生成器,包含流程创建、修改及审批人配置功能,支持自然语言描述转化为BPMN XML,并通过API与JeecgBoot系统交互。
This commit is contained in:
1001
.trae/skills/jimureport/SKILL.md
Normal file
1001
.trae/skills/jimureport/SKILL.md
Normal file
File diff suppressed because it is too large
Load Diff
16
.trae/skills/jimureport/examples/column-split.md
Normal file
16
.trae/skills/jimureport/examples/column-split.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# 示例5:分栏
|
||||
|
||||
**类型:** 分栏报表
|
||||
**特征:** `loopBlockList` 中加 `"loopTime":2` 实现横向循环2次(分2栏)
|
||||
|
||||
## 关键配置
|
||||
|
||||
```json
|
||||
{"loopBlockList":[{"sci":1,"sri":2,"eci":5,"eri":5,"index":1,"db":"jm","loopTime":2}]}
|
||||
```
|
||||
|
||||
## 报表 JSON
|
||||
|
||||
```json
|
||||
{"loopBlockList":[{"sci":1,"sri":2,"eci":5,"eri":5,"index":1,"db":"jm","loopTime":2}],"querySetting":{"izOpenQueryBar":false,"izDefaultQuery":true},"printConfig":{"paper":"A4","width":210,"height":297,"definition":1,"isBackend":false,"marginX":10,"marginY":10,"layout":"portrait"},"hidden":{"rows":[],"cols":[]},"dbexps":[],"dicts":[],"freeze":"A1","dataRectWidth":817,"autofilter":{},"validations":[],"cols":{"0":{"width":72},"3":{"width":101},"4":{"width":90},"5":{"width":54},"len":50},"pyGroupEngine":false,"submitHandlers":[],"excel_config_id":"590831722099462144","hiddenCells":[],"zonedEditionList":[],"rows":{"1":{"cells":{"5":{"style":15,"text":"分栏示例","merge":[0,1],"height":59},"8":{"style":9,"text":"说明:需要对多行区域进行循环且分栏展示时,则进行循环块设置并指定横向循环次数","merge":[0,2],"height":59}},"height":59},"2":{"cells":{"1":{"text":"","loopBlock":1},"2":{"text":"职员信息","style":2,"merge":[0,1],"height":34,"loopBlock":1},"4":{"text":"","loopBlock":1},"5":{"text":"","loopBlock":1}},"height":51},"3":{"cells":{"1":{"text":"姓名","style":4,"loopBlock":1},"2":{"text":"性别","style":4,"loopBlock":1},"3":{"text":"职务","style":4,"loopBlock":1},"4":{"text":"联系方式","style":4,"loopBlock":1},"5":{"text":"","loopBlock":1}},"height":31},"4":{"cells":{"1":{"text":"#{jm.name}","style":0,"loopBlock":1},"2":{"style":0,"loopBlock":1,"text":"#{jm.sex}"},"3":{"style":0,"loopBlock":1,"text":"#{jm.update_by}"},"4":{"style":0,"loopBlock":1,"text":"#{jm.jphone}"},"5":{"text":"","loopBlock":1}}},"5":{"cells":{"1":{"text":"","loopBlock":1},"5":{"text":"","loopBlock":1}},"height":34},"len":103},"rpbar":{"show":true,"pageSize":"","btnList":[]},"name":"sheet1","merges":["F2:G2","I2:K2","C3:D3"]}
|
||||
```
|
||||
31
.trae/skills/jimureport/examples/employee-register.md
Normal file
31
.trae/skills/jimureport/examples/employee-register.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# 示例10:员工信息登记(带照片)
|
||||
|
||||
**类型:** 单据模板(带图片占位)
|
||||
**特征:** `imgList` 图片占位 + `virtual` 虚拟单元格 + `${employee.xxx}` 单值绑定 + 日期格式化
|
||||
|
||||
## 数据绑定
|
||||
|
||||
`${employee.num}`、`${employee.name}`、`${employee.sex}`、`${employee.birthday}`、`${employee.nation}`、`${employee.political}`、`${employee.native_place}`、`${employee.height}`、`${employee.weight}`、`${employee.health}`、`${employee.id_card}`、`${employee.education}`、`${employee.school}`、`${employee.major}`、`${employee.address}`、`${employee.zip_code}`、`${employee.email}`、`${employee.phone}`、`${employee.foreign_language}`、`${employee.foreign_language_level}`、`${employee.computer_level}`、`${employee.graduation_time}`、`${employee.arrival_time}`、`${employee.positional_titles}`、`${employee.education_experience}`、`${employee.work_experience}`、`${employee.create_time}`
|
||||
|
||||
## 图片占位配置
|
||||
|
||||
```json
|
||||
{
|
||||
"imgList": [{
|
||||
"row": 3, "col": 6, "colspan": 1, "rowspan": 5,
|
||||
"width": "135", "height": "201",
|
||||
"src": "https://xxx.png",
|
||||
"layer_id": "8mRFFslT5d0Hfyos",
|
||||
"offsetX": 0, "offsetY": 0,
|
||||
"virtualCellRange": [[3,6]]
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
单元格引用图片:`"virtual":"8mRFFslT5d0Hfyos"` + `"merge":[4,0]`
|
||||
|
||||
## 报表 JSON
|
||||
|
||||
```json
|
||||
{"loopBlockList":[],"querySetting":{"izOpenQueryBar":false,"izDefaultQuery":true},"recordSubTableOrCollection":{"group":[],"record":[],"range":[]},"printConfig":{"paper":"A4","width":210,"height":297,"definition":1,"isBackend":false,"marginX":10,"marginY":10,"layout":"portrait"},"hidden":{"rows":[],"cols":[],"conditions":{"rows":{},"cols":{}}},"dbexps":[],"toolPrintSizeObj":{"printType":"A4","widthPx":718,"heightPx":1047},"dicts":["sex1"],"freeze":"A1","dataRectWidth":710,"autofilter":{},"validations":[],"cols":{"0":{"width":64},"1":{"width":118},"2":{"width":71},"3":{"width":115},"4":{"width":83},"5":{"width":123},"6":{"width":136},"7":{"width":1},"len":50},"excel_config_id":"1316944968992034816","hiddenCells":[],"zonedEditionList":[],"rows":{"1":{"cells":{"0":{"text":"员工信息登记表","merge":[0,6],"style":28}},"height":46},"2":{"cells":{"0":{"text":"编号:","style":29},"1":{"text":"${employee.num}","style":30,"merge":[0,3]},"5":{"text":"填写日期:","style":29},"6":{"text":"${employee.create_time}","style":34}},"isDrag":true,"height":44},"3":{"cells":{"0":{"text":"姓名:","style":29},"1":{"text":"${employee.name}","style":30},"2":{"text":"性别:","style":29},"3":{"text":"${employee.sex}","style":30},"4":{"text":"出生年月:","style":29},"5":{"text":"${employee.birthday}","style":36},"6":{"style":3,"text":" ","merge":[4,0],"virtual":"8mRFFslT5d0Hfyos"}},"isDrag":true,"height":42},"4":{"cells":{"0":{"text":"民族:","style":29},"1":{"text":"${employee.nation}","style":30},"2":{"text":"政治面貌:","style":29},"3":{"text":"${employee.political}","style":30},"4":{"text":"籍贯:","style":29},"5":{"text":"${employee.native_place}","style":30}},"isDrag":true,"height":38},"5":{"cells":{"0":{"text":"身高(cm):","style":29},"1":{"text":"${employee.height}","style":30},"2":{"text":"体重(kg):","style":29},"3":{"text":"${employee.weight}","style":30},"4":{"text":"健康状况:","style":29},"5":{"text":"${employee.health}","style":30}},"isDrag":true,"height":38},"6":{"cells":{"0":{"text":"身份证号:","style":29},"1":{"text":"${employee.id_card}","style":30,"merge":[0,2]},"4":{"text":"学历:","style":29},"5":{"text":"${employee.education}","style":30}},"isDrag":true,"height":40},"7":{"cells":{"0":{"text":"毕业学校:","style":29},"1":{"text":"${employee.school}","style":30,"merge":[0,2]},"4":{"text":"专业:","style":29},"5":{"text":"${employee.major}","style":30}},"isDrag":true,"height":44},"8":{"cells":{"0":{"text":"联系地址:","style":29},"1":{"text":"${employee.address}","style":30,"merge":[0,2]},"4":{"text":"邮编:","style":29},"5":{"text":"${employee.zip_code}","style":30,"merge":[0,1]}},"isDrag":true,"height":45},"9":{"cells":{"0":{"text":"Email:","style":29},"1":{"text":"${employee.email}","style":30,"merge":[0,2]},"4":{"text":"手机号:","style":29},"5":{"text":"${employee.phone}","style":30,"merge":[0,1]}},"isDrag":true,"height":40},"10":{"cells":{"0":{"text":"外语语种:","style":29},"1":{"text":"${employee.foreign_language}","style":30},"2":{"text":"外语水平:","style":29},"3":{"text":"${employee.foreign_language_level}","style":30},"4":{"text":"计算机水平:","style":29},"5":{"text":"${employee.computer_level}","style":30,"merge":[0,1]}},"isDrag":true,"height":41},"11":{"cells":{"0":{"text":"毕业时间:","style":29},"1":{"text":"${employee.graduation_time}","style":34},"2":{"text":"到职时间:","style":29},"3":{"text":"${employee.arrival_time}","style":34},"4":{"text":"职称:","style":29},"5":{"text":"${employee.positional_titles}","style":30,"merge":[0,1]}},"isDrag":true,"height":42},"12":{"cells":{"0":{"text":"教育经历:","style":32},"1":{"text":" ","style":35,"merge":[0,5]}},"isDrag":true,"height":39},"13":{"cells":{"0":{"text":"${employee.education_experience}","style":33,"merge":[0,6]}},"isDrag":true,"height":70},"14":{"cells":{"0":{"text":"工作经历:","style":32},"1":{"merge":[0,5],"style":30,"text":" "}},"height":43},"15":{"cells":{"0":{"text":"${employee.work_experience}","style":30,"merge":[0,6]}},"isDrag":true,"height":61},"len":100},"name":"sheet1","merges":["A2:G2","B3:E3","G4:G8","B7:D7","B8:D8","B9:D9","F9:G9","B10:D10","F10:G10","F11:G11","F12:G12","B13:G13","A14:G14","B15:G15","A16:G16"],"imgList":[{"row":3,"col":6,"colspan":1,"rowspan":5,"width":"135","height":"201","src":"https://static.jeecg.com/designreport/images/QQ截图20210115102648_1610677626114.png","layer_id":"8mRFFslT5d0Hfyos","offsetX":0,"offsetY":0,"virtualCellRange":[[3,6]]}]}
|
||||
```
|
||||
164
.trae/skills/jimureport/examples/expression-functions-example.md
Normal file
164
.trae/skills/jimureport/examples/expression-functions-example.md
Normal file
@@ -0,0 +1,164 @@
|
||||
# 常用表达式函数示例
|
||||
|
||||
## 场景说明
|
||||
|
||||
展示积木报表中可用的表达式函数,包括日期函数、字符串函数、数学函数、条件表达式和行号表达式。表达式以 `=` 开头,可使用常量参数或数据集参数 `${dbCode.field}`。
|
||||
|
||||
## 表达式语法规则
|
||||
|
||||
- 表达式以 `=` 开头:`=函数名(参数)`
|
||||
- 常量参数直接写值:`=round(341.234, 2)`
|
||||
- 数据集参数用 `${}` 引用:`=round(${jm_expression.num}, 2)`
|
||||
- 字符串常量用双引号或单引号:`=date("2021-07-29")`
|
||||
|
||||
## 日期函数
|
||||
|
||||
| 函数 | 表达式 | 常量参数示例 | 数据集参数示例 |
|
||||
|------|--------|-------------|---------------|
|
||||
| `date()` | `date("2021-07-29 12:11:10")` | `=date("2021-07-29 12:11:10")` | `=date("${ds.create_time}")` |
|
||||
| `time()` | `time("12:11:10")` | `=time("12:11:10")` | `=time("${ds.create_time}")` |
|
||||
| `now()` | `now()` | `=now()` | `=now()` |
|
||||
| `year()` | `year("2021-07-29 12:11:10")` | `=year("2021-07-29 12:11:10")` | `=year("${ds.create_time}")` |
|
||||
| `month()` | `month("2021-07-29 12:11:10")` | `=month("2021-07-29 12:11:10")` | `=month("${ds.create_time}")` |
|
||||
| `day()` | `day("2021-07-29 12:11:10")` | `=day("2021-07-29 12:11:10")` | `=day("${ds.create_time}")` |
|
||||
|
||||
### now() 格式化
|
||||
|
||||
通过 style 的 `format` 属性控制 `=now()` 的显示格式:
|
||||
|
||||
| format 值 | 输出格式 | 示例 |
|
||||
|-----------|---------|------|
|
||||
| `"date"` | yyyy-MM-dd | 2021-07-29 |
|
||||
| `"date2"` | yyyy/MM/dd | 2021/07/29 |
|
||||
| `"time"` | HH:mm:ss | 12:11:10 |
|
||||
| `"datetime"` | yyyy-MM-dd HH:mm:ss | 2021-07-29 12:11:10 |
|
||||
|
||||
## 字符串函数
|
||||
|
||||
| 函数 | 表达式 | 常量参数示例 | 数据集参数示例 |
|
||||
|------|--------|-------------|---------------|
|
||||
| `char()` | `char(22269)` | `=char(22269)` | — |
|
||||
| `cnmoney()` | `cnmoney(341.234)` | `=cnmoney(341.234)` | `=cnmoney(${ds.num})` |
|
||||
| `cnmoney("b")` | `cnmoney(341.234,"b")` | `=cnmoney(341.234,"b")` | `=cnmoney(${ds.num},"b")` |
|
||||
| `cnmoney("bw")` | `cnmoney(341.234,"bw")` | `=cnmoney(341.234,"bw")` | `=cnmoney(${ds.num},"bw")` |
|
||||
| `concat()` | `concat('hello ','word',' !')` | `=concat('hello ','world',' !')` | `=concat('${ds.upper}',' world',' !')` |
|
||||
| `lower()` | `lower('HELLOW')` | `=lower('HELLOW')` | `=lower("${ds.upper}")` |
|
||||
| `upper()` | `upper('world')` | `=upper('world')` | `=upper('${ds.lower}')` |
|
||||
|
||||
### cnmoney() 参数说明
|
||||
|
||||
| 参数 | 说明 | 示例输出 |
|
||||
|------|------|---------|
|
||||
| 无 | 中文大写金额 | 叁佰肆拾壹元贰角叁分肆厘 |
|
||||
| `"b"` | 简写 | 三四一.二三四 |
|
||||
| `"bw"` | 简写带万 | 三百四十一.二三四 |
|
||||
|
||||
## 数学函数
|
||||
|
||||
| 函数 | 表达式 | 常量参数示例 | 数据集参数示例 |
|
||||
|------|--------|-------------|---------------|
|
||||
| `rand()` | `rand()` | `=rand()` | — |
|
||||
| `rand()*N` | `rand()*100` | `=rand()*100` | — |
|
||||
| `round()` | `round(341.234,2)` | `=round(341.234,2)` | `=round(${ds.num},2)` |
|
||||
| `round(rand())` | `round(rand(),2)` | `=round(rand(),2)` | — |
|
||||
| `abs()` | `abs(-341.234)` | `=abs(-341.234)` | `=abs(${ds.num})` |
|
||||
| `floor()` | `floor(341.234,2)` | `=floor(341.234,2)` | `=floor(${ds.num},2)` |
|
||||
| `ceil()` | `ceil(341.234,2)` | `=ceil(341.234,2)` | `=ceil(${ds.num},2)` |
|
||||
| `trunc()` | `trunc(341.234)` | `=trunc(341.234)` | `=trunc(${ds.num})` |
|
||||
|
||||
## 条件表达式
|
||||
|
||||
### case() — 简单条件
|
||||
|
||||
```
|
||||
=case(条件, 真值, 假值)
|
||||
```
|
||||
|
||||
常量:`=case(1==1,'男','女')`
|
||||
数据集:`=case('${ds.sex}'=='1','男','女')`
|
||||
|
||||
### if() — 多分支条件
|
||||
|
||||
```
|
||||
=(let sex='${ds.sex}';
|
||||
if(sex== '1'){
|
||||
return '男';
|
||||
}elsif(sex== '2'){
|
||||
return '女';
|
||||
}else{
|
||||
return '未知';
|
||||
})
|
||||
```
|
||||
|
||||
**注意:** 多行 if 表达式需要用 `()` 包裹整个表达式,用 `let` 声明变量,分支用 `elsif`(不是 `else if`)。
|
||||
|
||||
## 行号表达式
|
||||
|
||||
| 函数 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| `row()` | 自动行号 | `=row(1)` 从1开始编号 |
|
||||
|
||||
## 样式中的 format 属性
|
||||
|
||||
styles 数组中可通过 `format` 控制单元格显示格式:
|
||||
|
||||
```json
|
||||
{ "format": "date" }
|
||||
{ "format": "date2" }
|
||||
{ "format": "time" }
|
||||
{ "format": "datetime" }
|
||||
```
|
||||
|
||||
对应的 style 索引(本示例中):
|
||||
|
||||
| 索引 | format | 边框 | 用途 |
|
||||
|------|--------|------|------|
|
||||
| 0 | date | 无 | 日期格式(无边框) |
|
||||
| 1 | date2 | 无 | 日期格式2(无边框) |
|
||||
| 2 | time | 无 | 时间格式(无边框) |
|
||||
| 3 | datetime | 无 | 日期时间格式(无边框) |
|
||||
| 5 | date | thin四边 | 日期格式(带边框) |
|
||||
| 6 | date2 | thin四边 | 日期格式2(带边框) |
|
||||
| 7 | time | thin四边 | 时间格式(带边框) |
|
||||
| 8 | datetime | thin四边 | 日期时间格式(带边框) |
|
||||
|
||||
## 单元格链接(display: link)
|
||||
|
||||
```json
|
||||
{
|
||||
"text": "更多表达式请查看详细文档。",
|
||||
"linkIds": "580872825561501696,580872825561501696",
|
||||
"display": "link",
|
||||
"merge": [0, 1]
|
||||
}
|
||||
```
|
||||
|
||||
| 属性 | 说明 |
|
||||
|------|------|
|
||||
| `display` | `"link"` 表示显示为超链接 |
|
||||
| `linkIds` | 链接目标的报表ID,多个用逗号分隔 |
|
||||
|
||||
## 单元格纵向合并(数据行内)
|
||||
|
||||
```json
|
||||
"5": {
|
||||
"cells": {
|
||||
"1": {
|
||||
"text": "now()",
|
||||
"merge": [3, 0],
|
||||
"height": 100,
|
||||
"style": 4
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`merge: [3, 0]` 表示向下合并3行、向右合并0列,即占据第5-8行的第1列。
|
||||
|
||||
## 样式方案(绿色主题)
|
||||
|
||||
| 索引 | 背景色 | 用途 |
|
||||
|------|--------|------|
|
||||
| 12 | #93d051(绿色) | 分类标题行(日期函数/字符串函数等) |
|
||||
| 17 | #93d051(绿色) | 表头行(函数名称/表达式/常量参数/数据集参数) |
|
||||
| 4 | — | 数据行(thin四边框) |
|
||||
70
.trae/skills/jimureport/examples/fill-form.md
Normal file
70
.trae/skills/jimureport/examples/fill-form.md
Normal file
File diff suppressed because one or more lines are too long
33
.trae/skills/jimureport/examples/fixed-head-tail.md
Normal file
33
.trae/skills/jimureport/examples/fixed-head-tail.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# 示例8:固定表头表尾
|
||||
|
||||
**类型:** 分组报表 + 固定打印表头表尾
|
||||
**特征:** `fixedPrintHeadRows`/`fixedPrintTailRows` + 横向分组 `groupRight` + 纵向分组 `group` + 动态聚合 `dynamic`
|
||||
|
||||
## 关键配置
|
||||
|
||||
```json
|
||||
{
|
||||
"fixedPrintHeadRows": [{"sci":1,"eci":3,"sri":1,"eri":2}],
|
||||
"fixedPrintTailRows": [{"sri":6,"sci":1,"eri":6,"eci":5}],
|
||||
"isGroup": true,
|
||||
"groupField": "xs.diqu"
|
||||
}
|
||||
```
|
||||
|
||||
## 数据绑定语法
|
||||
|
||||
- 横向分组(年):`#{xs.groupRight(year)}`,`direction:"right"`,`aggregate:"group"`,`sort:"desc"`
|
||||
- 横向分组(月):`#{xs.groupRight(mouth)}`,`aggregate:"group"`,`direction:"right"`
|
||||
- 纵向分组(地区):`#{xs.group(diqu)}`,`aggregate:"group"`,`subtotal:"groupField"`
|
||||
- 纵向分组(分类):`#{xs.group(class)}`,`aggregate:"group"`
|
||||
- 动态聚合(销量):`#{xs.dynamic(sales)}`,`aggregate:"dynamic"`,`funcname:"SUM"`
|
||||
- 合计行:`=sum(D4)`
|
||||
- 斜线表头:`lineStart:"lefttop"`,`text:"地区|销量|时间"`
|
||||
- 固定表头标记:`fixedHead:1`
|
||||
- 固定表尾标记:`fixedTail:1`
|
||||
|
||||
## 报表 JSON
|
||||
|
||||
```json
|
||||
{"loopBlockList":[],"printConfig":{"layout":"portrait","paper":"A4","isBackend":false,"width":210,"definition":1,"marginX":10,"height":297,"marginY":10},"dbexps":[],"toolPrintSizeObj":{"printType":"A4","widthPx":718,"heightPx":1047},"dicts":[],"freeze":"A1","dataRectWidth":713,"autofilter":{},"validations":[],"cols":{"0":{"width":36},"1":{"width":95},"2":{"width":95},"4":{"width":141},"5":{"width":246},"6":{"width":155},"len":50},"area":{"sri":6,"sci":1,"eri":6,"eci":5,"width":677,"height":25},"excel_config_id":"739738655920574464","zonedEditionList":[],"rows":{"0":{"cells":{"1":{"merge":[0,2],"style":6,"text":"固定表头表尾打印实例"},"5":{"style":30,"text":"说明:本示例在横向分组、纵向分组基础上,添加固定表头表尾。在打印时可显示表头及表尾"}},"height":83},"1":{"cells":{"1":{"lineStart":"lefttop","merge":[1,1],"style":2,"text":"地区|销量|时间","fixedHead":1,"height":74},"2":{"text":"","fixedHead":1},"3":{"style":8,"text":"#{xs.groupRight(year)}年","sort":"desc","fixedHead":1,"aggregate":"group","direction":"right"}},"height":40},"2":{"cells":{"1":{"text":"","fixedHead":1},"2":{"text":"","fixedHead":1},"3":{"style":8,"text":"#{xs.groupRight(mouth)}","sort":"default","fixedHead":1,"aggregate":"group","direction":"right"}},"height":34},"3":{"cells":{"1":{"subtotal":"groupField","style":28,"text":"#{xs.group(diqu)}","aggregate":"group"},"2":{"style":28,"text":"#{xs.group(class)}","aggregate":"group"},"3":{"decimalPlaces":"0","funcname":"SUM","style":29,"text":"#{xs.dynamic(sales)}","aggregate":"dynamic"}},"height":38},"4":{"cells":{"1":{"merge":[0,1],"style":24,"text":"总计"},"3":{"style":25,"text":"=sum(D4)"}},"height":37},"6":{"cells":{"1":{"style":32,"text":"审核:","fixedTail":1},"2":{"style":32,"text":"张三","fixedTail":1},"3":{"style":32,"fixedTail":1},"4":{"style":32,"text":"复审:","fixedTail":1},"5":{"style":32,"text":"李四","fixedTail":1}}},"len":100},"rpbar":{"show":true,"pageSize":"","btnList":[]},"groupField":"xs.diqu","fixedPrintHeadRows":[{"sci":1,"eci":3,"sri":1,"eri":2}],"fixedPrintTailRows":[{"sri":6,"sci":1,"eri":6,"eci":5}],"displayConfig":{},"background":false,"name":"sheet1","isGroup":true,"merges":["B1:D1","B2:C3","B5:C5"]}
|
||||
```
|
||||
29
.trae/skills/jimureport/examples/horizontal-group.md
Normal file
29
.trae/skills/jimureport/examples/horizontal-group.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# 示例11:横向分组
|
||||
|
||||
**类型:** 横向分组统计表
|
||||
**特征:** `customGroup()` + `direction:"right"` 数据横向展开
|
||||
|
||||
## 关键语法
|
||||
|
||||
所有数据行使用 `#{hex.customGroup(字段名)}` + `direction:"right"` 实现横向展开:
|
||||
|
||||
```json
|
||||
{"text":"#{hex.customGroup(department)}","style":11,"direction":"right"}
|
||||
```
|
||||
|
||||
## 数据字段
|
||||
|
||||
| 行 | 标签 | 绑定 |
|
||||
|---|---|---|
|
||||
| 2 | 部门 | `#{hex.customGroup(department)}` |
|
||||
| 3 | 学历 | `#{hex.customGroup(education)}` |
|
||||
| 4 | 性别 | `#{hex.customGroup(sex)}` |
|
||||
| 5 | 年龄 | `#{hex.customGroup(age)}` (无 direction,纵向) |
|
||||
| 6 | 姓名 | `#{hex.customGroup(name)}` |
|
||||
| 7 | 薪水 | `#{hex.customGroup(salary)}` |
|
||||
|
||||
## 报表 JSON
|
||||
|
||||
```json
|
||||
{"loopBlockList":[],"querySetting":{"izOpenQueryBar":false,"izDefaultQuery":true},"recordSubTableOrCollection":{"group":[],"record":[],"range":[]},"printConfig":{"paper":"A4","width":210,"height":297,"definition":1,"isBackend":false,"marginX":10,"marginY":10},"hidden":{"rows":[],"cols":[],"conditions":{"rows":{},"cols":{}}},"dbexps":[],"toolPrintSizeObj":{"printType":"A4","widthPx":718,"heightPx":1047},"dicts":[],"freeze":"A1","dataRectWidth":204,"isViewContentHorizontalCenter":false,"autofilter":{},"validations":[],"cols":{"0":{"width":44},"1":{"width":79},"2":{"width":81},"len":50},"area":{"sri":7,"sci":5,"eri":7,"eci":5,"width":100,"height":36},"excel_config_id":"1194552262320803840","hiddenCells":[],"zonedEditionList":[],"rows":{"1":{"cells":{"0":{"text":"员工信息横向统计表","style":9,"merge":[0,11]}},"height":97},"2":{"cells":{"1":{"text":"部门","style":7},"2":{"text":"#{hex.customGroup(department)}","style":11,"direction":"right"}},"isDrag":true,"height":40},"3":{"cells":{"1":{"text":"学历","style":7},"2":{"text":"#{hex.customGroup(education)}","style":11,"direction":"right"}},"isDrag":true,"height":39},"4":{"cells":{"1":{"text":"性别","style":7},"2":{"text":"#{hex.customGroup(sex)}","style":11,"direction":"right"}},"isDrag":true,"height":41},"5":{"cells":{"1":{"text":"年龄","style":7},"2":{"text":"#{hex.customGroup(age)}","style":11}},"isDrag":true,"height":39},"6":{"cells":{"1":{"text":"姓名","style":7},"2":{"text":"#{hex.customGroup(name)}","style":11,"direction":"right"}},"isDrag":true,"height":40},"7":{"cells":{"1":{"text":"薪水","style":7},"2":{"text":"#{hex.customGroup(salary)}","style":11,"direction":"right"}},"isDrag":true,"height":36},"len":100},"name":"sheet1","fillFormStyle":"default","merges":["A2:L2"]}
|
||||
```
|
||||
30
.trae/skills/jimureport/examples/internship-cert.md
Normal file
30
.trae/skills/jimureport/examples/internship-cert.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# 示例9:实习证明
|
||||
|
||||
**类型:** 单据模板(带背景图)
|
||||
**特征:** `background` 背景图 + `${tt.xxx}` 单值绑定 + 自由布局
|
||||
|
||||
## 数据绑定
|
||||
|
||||
- `${tt.name}` — 姓名
|
||||
- `${tt.pingjia}` — 评价内容(多行文本,merge跨4行5列)
|
||||
- `${tt.lingdao}` — 证明人
|
||||
- `${tt.shijian}` — 日期
|
||||
|
||||
## 关键配置
|
||||
|
||||
```json
|
||||
{
|
||||
"background": {
|
||||
"path": "https://static.jeecg.com/designreport/images/11_1611283832037.png",
|
||||
"repeat": "no-repeat",
|
||||
"width": "",
|
||||
"height": ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 报表 JSON
|
||||
|
||||
```json
|
||||
{"loopBlockList":[],"area":{"sri":28,"sci":9,"eri":28,"eci":9,"width":100,"height":25},"excel_config_id":"1347373863746539520","printConfig":{"layout":"portrait","paper":"A4","isBackend":false,"width":210,"definition":1,"marginX":10,"height":297,"marginY":10},"hiddenCells":[],"zonedEditionList":[],"rows":{"7":{"cells":{"2":{"merge":[0,4],"style":2,"text":"实习证明"}},"height":41},"10":{"cells":{"2":{"style":11,"text":"${tt.name}"},"3":{"merge":[0,3],"style":19,"text":"同学在我公司与 2020年4月1日 至 2020年5月1日 实习。","height":34}},"height":34},"12":{"cells":{"2":{"merge":[3,4],"style":13,"text":"${tt.pingjia}","height":129}},"height":36},"17":{"cells":{"2":{"style":12,"text":"特此证明!"}}},"22":{"cells":{"4":{"style":11,"text":"证明人:"},"5":{"style":12,"text":"${tt.lingdao}"}}},"23":{"cells":{"5":{"style":15,"text":"${tt.shijian}"}}},"len":100},"dbexps":[],"dicts":[],"freeze":"A1","dataRectWidth":707,"displayConfig":{},"background":{"path":"https://static.jeecg.com/designreport/images/11_1611283832037.png","repeat":"no-repeat","width":"","height":""},"name":"sheet1","autofilter":{},"validations":[],"cols":{"0":{"width":69},"1":{"width":41},"4":{"width":119},"5":{"width":147},"6":{"width":31},"len":50},"merges":["C8:G8","D11:G11","C13:G16"]}
|
||||
```
|
||||
15
.trae/skills/jimureport/examples/loopblock-detail.md
Normal file
15
.trae/skills/jimureport/examples/loopblock-detail.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# 示例4:循环块明细表(员工信息卡片)
|
||||
|
||||
**类型:** 循环块报表
|
||||
**特征:** `loopBlockList` 定义循环区域,每条数据渲染一个卡片,支持二维码 `display:"qrcode"`
|
||||
|
||||
## 关键配置
|
||||
|
||||
- `loopBlockList`:`[{"sci":1,"sri":2,"eci":7,"eri":5,"index":1,"db":"uiu"}]`
|
||||
- 二维码:单元格 `"display":"qrcode"` + `displayConfig` 配置宽高颜色
|
||||
|
||||
## 报表 JSON
|
||||
|
||||
```json
|
||||
{"loopBlockList":[{"sci":1,"sri":2,"eci":7,"eri":5,"index":1,"db":"uiu"}],"querySetting":{"izOpenQueryBar":false,"izDefaultQuery":true},"recordSubTableOrCollection":{"group":[],"record":[],"range":[]},"printConfig":{"paper":"A4","width":210,"height":297,"definition":1,"isBackend":false,"marginX":10,"marginY":10,"layout":"portrait"},"hidden":{"rows":[],"cols":[],"conditions":{"rows":{},"cols":{}}},"queryFormSetting":{"useQueryForm":false,"dbKey":"","idField":""},"dbexps":[],"toolPrintSizeObj":{"printType":"A4","widthPx":718,"heightPx":1047},"dicts":[],"freeze":"A1","dataRectWidth":688,"isViewContentHorizontalCenter":false,"autofilter":{},"validations":[],"cols":{"0":{"width":30},"1":{"width":94},"2":{"width":96},"3":{"width":81},"4":{"width":93},"5":{"width":88},"6":{"width":90},"7":{"width":116},"8":{"width":22},"len":50},"pyGroupEngine":false,"submitHandlers":[],"excel_config_id":"1176098706643308544","hiddenCells":[],"zonedEditionList":[],"rows":{"1":{"cells":{"1":{"text":"员工信息明细表","merge":[0,5],"style":32}},"height":64},"2":{"cells":{"1":{"text":"姓名:","style":28,"loopBlock":1},"2":{"style":30,"merge":[0,1],"loopBlock":1,"text":"#{uiu.name}"},"4":{"text":"所在部门:","style":29,"loopBlock":1},"5":{"style":30,"merge":[0,1],"loopBlock":1,"text":"#{uiu.department}"},"7":{"merge":[2,0],"height":75,"style":9,"text":"#{uiu.tm}","display":"qrcode","loopBlock":1}},"height":42},"3":{"cells":{"1":{"text":"年龄:","style":28,"loopBlock":1},"2":{"style":30,"merge":[0,1],"loopBlock":1,"text":"#{uiu.age}"},"4":{"text":"学历:","style":29,"loopBlock":1},"5":{"style":30,"merge":[0,1],"loopBlock":1,"text":"#{uiu.education}"},"7":{"text":"","loopBlock":1}},"height":35},"4":{"cells":{"1":{"text":"性别:","style":28,"loopBlock":1},"2":{"style":30,"merge":[0,1],"loopBlock":1,"text":"#{uiu.sex}"},"4":{"text":"薪水:","style":29,"loopBlock":1},"5":{"style":30,"merge":[0,1],"loopBlock":1,"text":"#{uiu.salary}"},"7":{"text":"","loopBlock":1}},"height":35},"5":{"cells":{"1":{"text":"","loopBlock":1},"2":{"text":"","loopBlock":1},"3":{"text":"","loopBlock":1},"4":{"text":"","loopBlock":1},"5":{"text":"","loopBlock":1},"6":{"text":"","loopBlock":1},"7":{"text":"","loopBlock":1}},"height":17},"len":100},"rpbar":{"show":true,"pageSize":"","btnList":[]},"displayConfig":{"11":{"text":"#{uiu.tm}","width":117,"height":117,"colorDark":"#000000","colorLight":"#ffffff"}},"name":"sheet1","merges":["B2:G2","C3:D3","F3:G3","H3:H5","C4:D4","F4:G4","C5:D5","F5:G5"]}
|
||||
```
|
||||
17
.trae/skills/jimureport/examples/master-sub-loopblock.md
Normal file
17
.trae/skills/jimureport/examples/master-sub-loopblock.md
Normal file
File diff suppressed because one or more lines are too long
79
.trae/skills/jimureport/examples/master-sub-table.md
Normal file
79
.trae/skills/jimureport/examples/master-sub-table.md
Normal file
File diff suppressed because one or more lines are too long
304
.trae/skills/jimureport/examples/normal-list-example.md
Normal file
304
.trae/skills/jimureport/examples/normal-list-example.md
Normal file
@@ -0,0 +1,304 @@
|
||||
# 普通列表报表示例
|
||||
|
||||
## 场景说明
|
||||
|
||||
一个标准的普通列表报表,数据集编码为 `aa`,SQL为 `select * from demo`,展示6个字段:id、name、key_word、punch_time、salary_money、bonus_money。
|
||||
|
||||
- 表头行(第1行):蓝底白字,行高34px
|
||||
- 数据行(第2行):居中+垂直居中,通过 `#{aa.字段名}` 绑定数据
|
||||
|
||||
## 完整 jsonStr
|
||||
|
||||
```json
|
||||
{
|
||||
"loopBlockList": [],
|
||||
"querySetting": {
|
||||
"izOpenQueryBar": false,
|
||||
"izDefaultQuery": true
|
||||
},
|
||||
"recordSubTableOrCollection": {
|
||||
"group": [],
|
||||
"record": [],
|
||||
"range": []
|
||||
},
|
||||
"printConfig": {
|
||||
"paper": "A4",
|
||||
"width": 210,
|
||||
"height": 297,
|
||||
"definition": 1,
|
||||
"isBackend": false,
|
||||
"marginX": 10,
|
||||
"marginY": 10,
|
||||
"layout": "portrait",
|
||||
"printCallBackUrl": ""
|
||||
},
|
||||
"hidden": {
|
||||
"rows": [],
|
||||
"cols": [],
|
||||
"conditions": {
|
||||
"rows": {},
|
||||
"cols": {}
|
||||
}
|
||||
},
|
||||
"queryFormSetting": {
|
||||
"useQueryForm": false,
|
||||
"dbKey": "",
|
||||
"idField": ""
|
||||
},
|
||||
"dbexps": [],
|
||||
"dicts": [],
|
||||
"fillFormToolbar": {
|
||||
"show": true,
|
||||
"btnList": ["save", "subTable_add", "verify", "subTable_del", "print", "close", "first", "prev", "next", "paging", "total", "last", "exportPDF", "exportExcel", "exportWord"]
|
||||
},
|
||||
"freeze": "A1",
|
||||
"dataRectWidth": 700,
|
||||
"isViewContentHorizontalCenter": false,
|
||||
"autofilter": {},
|
||||
"validations": [],
|
||||
"cols": {
|
||||
"len": 100
|
||||
},
|
||||
"area": {
|
||||
"sri": 12,
|
||||
"sci": 3,
|
||||
"eri": 12,
|
||||
"eci": 3,
|
||||
"width": 100,
|
||||
"height": 25
|
||||
},
|
||||
"pyGroupEngine": false,
|
||||
"submitHandlers": [],
|
||||
"excel_config_id": "1193766682428530688",
|
||||
"hiddenCells": [],
|
||||
"zonedEditionList": [],
|
||||
"rows": {
|
||||
"1": {
|
||||
"cells": {
|
||||
"1": { "text": "id", "style": 4 },
|
||||
"2": { "text": "name", "style": 4 },
|
||||
"3": { "text": "key_word", "style": 4 },
|
||||
"4": { "text": "punch_time", "style": 4 },
|
||||
"5": { "text": "salary_money", "style": 4 },
|
||||
"6": { "text": "bonus_money", "style": 4 }
|
||||
},
|
||||
"height": 34
|
||||
},
|
||||
"2": {
|
||||
"cells": {
|
||||
"1": { "text": "#{aa.id}", "style": 2 },
|
||||
"2": { "text": "#{aa.name}", "style": 2 },
|
||||
"3": { "text": "#{aa.key_word}", "style": 2 },
|
||||
"4": { "text": "#{aa.punch_time}", "style": 2 },
|
||||
"5": { "text": "#{aa.salary_money}", "style": 2 },
|
||||
"6": { "text": "#{aa.bonus_money}", "style": 2 }
|
||||
}
|
||||
},
|
||||
"len": 200
|
||||
},
|
||||
"rpbar": {
|
||||
"show": true,
|
||||
"pageSize": "",
|
||||
"btnList": []
|
||||
},
|
||||
"fixedPrintHeadRows": [],
|
||||
"fixedPrintTailRows": [],
|
||||
"displayConfig": {},
|
||||
"fillFormInfo": {
|
||||
"layout": {
|
||||
"direction": "horizontal",
|
||||
"width": 200,
|
||||
"height": 45
|
||||
}
|
||||
},
|
||||
"background": false,
|
||||
"name": "sheet1",
|
||||
"styles": [
|
||||
{
|
||||
"border": {
|
||||
"bottom": ["thin", "#000"],
|
||||
"top": ["thin", "#000"],
|
||||
"left": ["thin", "#000"],
|
||||
"right": ["thin", "#000"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"border": {
|
||||
"bottom": ["thin", "#000"],
|
||||
"top": ["thin", "#000"],
|
||||
"left": ["thin", "#000"],
|
||||
"right": ["thin", "#000"]
|
||||
},
|
||||
"align": "center"
|
||||
},
|
||||
{
|
||||
"border": {
|
||||
"bottom": ["thin", "#000"],
|
||||
"top": ["thin", "#000"],
|
||||
"left": ["thin", "#000"],
|
||||
"right": ["thin", "#000"]
|
||||
},
|
||||
"align": "center",
|
||||
"valign": "middle"
|
||||
},
|
||||
{
|
||||
"border": {
|
||||
"bottom": ["thin", "#000"],
|
||||
"top": ["thin", "#000"],
|
||||
"left": ["thin", "#000"],
|
||||
"right": ["thin", "#000"]
|
||||
},
|
||||
"align": "center",
|
||||
"valign": "middle",
|
||||
"bgcolor": "#01b0f1"
|
||||
},
|
||||
{
|
||||
"border": {
|
||||
"bottom": ["thin", "#000"],
|
||||
"top": ["thin", "#000"],
|
||||
"left": ["thin", "#000"],
|
||||
"right": ["thin", "#000"]
|
||||
},
|
||||
"align": "center",
|
||||
"valign": "middle",
|
||||
"bgcolor": "#01b0f1",
|
||||
"color": "#ffffff"
|
||||
}
|
||||
],
|
||||
"fillFormStyle": "default",
|
||||
"freezeLineColor": "rgb(185, 185, 185)",
|
||||
"merges": []
|
||||
}
|
||||
```
|
||||
|
||||
## 结构要点
|
||||
|
||||
### rows 布局
|
||||
|
||||
| 行号 | 用途 | style | 说明 |
|
||||
|------|------|-------|------|
|
||||
| 1 | 表头行 | 4(蓝底白字) | `height: 34`,text为字段显示名 |
|
||||
| 2 | 数据行 | 2(居中+垂直居中) | text为 `#{数据集编码.字段名}` |
|
||||
|
||||
### styles 索引对照
|
||||
|
||||
| 索引 | 边框 | 水平对齐 | 垂直对齐 | 背景色 | 字体色 | 典型用途 |
|
||||
|------|------|---------|---------|--------|--------|---------|
|
||||
| 0 | thin #000 | — | — | — | — | 基础单元格 |
|
||||
| 1 | thin #000 | center | — | — | — | 居中文本 |
|
||||
| 2 | thin #000 | center | middle | — | — | **数据行** |
|
||||
| 3 | thin #000 | center | middle | #01b0f1 | — | 蓝底表头(无白字) |
|
||||
| 4 | thin #000 | center | middle | #01b0f1 | #ffffff | **表头行(推荐)** |
|
||||
|
||||
### 数据绑定规则
|
||||
|
||||
- 数据集编码 `aa` 对应 saveDb 时的 `dbCode: "aa"`
|
||||
- 绑定语法: `#{aa.字段名}` — 字段名来自 fieldList 中的 `fieldName`
|
||||
- 列号从 1 开始(0列通常留空)
|
||||
|
||||
### 对应的数据集配置
|
||||
|
||||
```json
|
||||
{
|
||||
"jimuReportId": "1193766682428530688",
|
||||
"dbCode": "aa",
|
||||
"dbChName": "aa",
|
||||
"dbType": "0",
|
||||
"dbSource": "",
|
||||
"isList": "1",
|
||||
"isPage": "1",
|
||||
"dbDynSql": "select * from demo",
|
||||
"fieldList": [
|
||||
{ "fieldName": "id", "fieldText": "id", "widgetType": "String", "orderNum": 0 },
|
||||
{ "fieldName": "name", "fieldText": "name", "widgetType": "String", "orderNum": 1 },
|
||||
{ "fieldName": "key_word", "fieldText": "key_word", "widgetType": "String", "orderNum": 2 },
|
||||
{ "fieldName": "punch_time", "fieldText": "punch_time", "widgetType": "String", "orderNum": 3 },
|
||||
{ "fieldName": "salary_money", "fieldText": "salary_money", "widgetType": "String", "orderNum": 4 },
|
||||
{ "fieldName": "bonus_money", "fieldText": "bonus_money", "widgetType": "String", "orderNum": 5 }
|
||||
],
|
||||
"paramList": []
|
||||
}
|
||||
```
|
||||
|
||||
### 正确的 /jmreport/save 请求格式
|
||||
|
||||
> **关键:jsonStr 内容(rows、cols、styles 等)必须放在请求体顶层,和 `designerObj` 同级。禁止嵌套在 `designerObj.jsonStr` 中,否则后端会清空 rows 数据。**
|
||||
>
|
||||
> 后端 `saveReport` 逻辑:`json.remove("designerObj")` 后,剩余的顶层 JSON 直接作为 jsonStr 存入数据库。
|
||||
|
||||
```json
|
||||
{
|
||||
"designerObj": {
|
||||
"id": "1193766682428530688",
|
||||
"name": "普通列表示例",
|
||||
"type": "0",
|
||||
"template": 0,
|
||||
"delFlag": 0,
|
||||
"viewCount": 0,
|
||||
"updateCount": 0,
|
||||
"submitForm": 0,
|
||||
"reportName": "普通列表示例"
|
||||
},
|
||||
"loopBlockList": [],
|
||||
"querySetting": { "izOpenQueryBar": false, "izDefaultQuery": true },
|
||||
"recordSubTableOrCollection": { "group": [], "record": [], "range": [] },
|
||||
"printConfig": { "paper": "A4", "width": 210, "height": 297, "definition": 1, "isBackend": false, "marginX": 10, "marginY": 10, "layout": "portrait", "printCallBackUrl": "" },
|
||||
"hidden": { "rows": [], "cols": [], "conditions": { "rows": {}, "cols": {} } },
|
||||
"queryFormSetting": { "useQueryForm": false, "dbKey": "", "idField": "" },
|
||||
"dbexps": [], "dicts": [],
|
||||
"fillFormToolbar": { "show": true, "btnList": ["save", "subTable_add", "verify", "subTable_del", "print", "close", "first", "prev", "next", "paging", "total", "last", "exportPDF", "exportExcel", "exportWord"] },
|
||||
"freeze": "A1",
|
||||
"dataRectWidth": 700,
|
||||
"isViewContentHorizontalCenter": false,
|
||||
"autofilter": {},
|
||||
"validations": [],
|
||||
"cols": { "len": 100 },
|
||||
"area": { "sri": 12, "sci": 3, "eri": 12, "eci": 3, "width": 100, "height": 25 },
|
||||
"pyGroupEngine": false,
|
||||
"submitHandlers": [],
|
||||
"excel_config_id": "1193766682428530688",
|
||||
"hiddenCells": [],
|
||||
"zonedEditionList": [],
|
||||
"rows": {
|
||||
"1": {
|
||||
"cells": {
|
||||
"1": { "text": "id", "style": 4 },
|
||||
"2": { "text": "name", "style": 4 },
|
||||
"3": { "text": "key_word", "style": 4 },
|
||||
"4": { "text": "punch_time", "style": 4 },
|
||||
"5": { "text": "salary_money", "style": 4 },
|
||||
"6": { "text": "bonus_money", "style": 4 }
|
||||
},
|
||||
"height": 34
|
||||
},
|
||||
"2": {
|
||||
"cells": {
|
||||
"1": { "text": "#{aa.id}", "style": 2 },
|
||||
"2": { "text": "#{aa.name}", "style": 2 },
|
||||
"3": { "text": "#{aa.key_word}", "style": 2 },
|
||||
"4": { "text": "#{aa.punch_time}", "style": 2 },
|
||||
"5": { "text": "#{aa.salary_money}", "style": 2 },
|
||||
"6": { "text": "#{aa.bonus_money}", "style": 2 }
|
||||
}
|
||||
},
|
||||
"len": 200
|
||||
},
|
||||
"rpbar": { "show": true, "pageSize": "", "btnList": [] },
|
||||
"fixedPrintHeadRows": [],
|
||||
"fixedPrintTailRows": [],
|
||||
"displayConfig": {},
|
||||
"fillFormInfo": { "layout": { "direction": "horizontal", "width": 200, "height": 45 } },
|
||||
"background": false,
|
||||
"name": "sheet1",
|
||||
"styles": [
|
||||
{ "border": { "bottom": ["thin", "#000"], "top": ["thin", "#000"], "left": ["thin", "#000"], "right": ["thin", "#000"] } },
|
||||
{ "border": { "bottom": ["thin", "#000"], "top": ["thin", "#000"], "left": ["thin", "#000"], "right": ["thin", "#000"] }, "align": "center" },
|
||||
{ "border": { "bottom": ["thin", "#000"], "top": ["thin", "#000"], "left": ["thin", "#000"], "right": ["thin", "#000"] }, "align": "center", "valign": "middle" },
|
||||
{ "border": { "bottom": ["thin", "#000"], "top": ["thin", "#000"], "left": ["thin", "#000"], "right": ["thin", "#000"] }, "align": "center", "valign": "middle", "bgcolor": "#01b0f1" },
|
||||
{ "border": { "bottom": ["thin", "#000"], "top": ["thin", "#000"], "left": ["thin", "#000"], "right": ["thin", "#000"] }, "align": "center", "valign": "middle", "bgcolor": "#01b0f1", "color": "#ffffff" }
|
||||
],
|
||||
"fillFormStyle": "default",
|
||||
"freezeLineColor": "rgb(185, 185, 185)",
|
||||
"merges": []
|
||||
}
|
||||
```
|
||||
180
.trae/skills/jimureport/examples/prescription-form-example.md
Normal file
180
.trae/skills/jimureport/examples/prescription-form-example.md
Normal file
@@ -0,0 +1,180 @@
|
||||
# 处方笺模板示例(表单+列表混合)
|
||||
|
||||
## 场景说明
|
||||
|
||||
一个医院处方笺模板,包含:
|
||||
- **单条数据**(患者信息):使用 `${yonghu.字段名}` 绑定
|
||||
- **列表数据**(药品明细):使用 `#{yaopin.字段名}` 绑定
|
||||
- 外边框用 **thick**(粗线),内部用 **thin**(细线)
|
||||
- 自定义列宽,多处单元格合并
|
||||
- 隐藏行(`-1` 行)存放辅助字段
|
||||
|
||||
## 数据绑定语法对比
|
||||
|
||||
| 语法 | 数据类型 | 说明 | 示例 |
|
||||
|------|---------|------|------|
|
||||
| `${dbCode.field}` | 单条记录 | 直接取值,不循环 | `${yonghu.yphone}` |
|
||||
| `#{dbCode.field}` | 列表数据 | 自动循环展开 | `#{yaopin.name}` |
|
||||
|
||||
## 布局结构(行分布)
|
||||
|
||||
| 行号 | 内容 | 高度 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 0 | 顶部留白 | 96px | 预留logo/印章区域 |
|
||||
| 1 | 上边框线 | 18px | thick边框顶边 |
|
||||
| 2 | 标题"智能医学院处方笺" | 124px | 合并C3:L3,style 38(14号加粗居中) |
|
||||
| 3 | 姓名/性别/年龄 | 默认 | `${yonghu.yphone}` `${yonghu.ysex}` `${yonghu.yage}` |
|
||||
| 4 | 单位/电话 | 29px | `${yonghu.danwei}` `${yonghu.yphone}` |
|
||||
| 5 | 初步诊断 | 34px | `${yonghu.yjieguo}` 合并7列 |
|
||||
| 6 | RP:标记 | 79px | 处方开始标志 |
|
||||
| 7 | **药品列表行** | 37px | `#{yaopin.name}` `#{yaopin.percent}` — 自动循环 |
|
||||
| 8 | 空行间隔 | 27px | |
|
||||
| 9 | 医嘱 | 默认 | `${yonghu.yizhu}` 合并8列 |
|
||||
| 10-12 | 费用明细 | 默认 | 药品费/中成药费/治疗费/检查费等 |
|
||||
| 13 | 合计 | 默认 | `${yonghu.ytotal}` |
|
||||
| 14 | 空行 | 17px | |
|
||||
| 15 | 医师/日期 | 43px | `${yonghu.yishe}` `${yonghu.kdata}` |
|
||||
| 16 | 空行 | 17px | |
|
||||
| 17 | 下边框线 | 默认 | thick边框底边 |
|
||||
| -1 | **隐藏行** | — | `#{yaopin.key1}` `#{yaopin.key2}` 辅助数据 |
|
||||
|
||||
## 关键特性
|
||||
|
||||
### 1. 隐藏行(-1行)
|
||||
|
||||
```json
|
||||
"-1": {
|
||||
"cells": {
|
||||
"0": { "text": "#{yaopin.key2}" },
|
||||
"-1": { "text": "#{yaopin.key1}" }
|
||||
},
|
||||
"isDrag": true
|
||||
}
|
||||
```
|
||||
|
||||
用于存放不需要显示但参与数据处理的字段,行号为 `-1`,列号可以为 `-1`。
|
||||
|
||||
### 2. 粗细边框方案
|
||||
|
||||
外框用 `thick`,内部用 `thin`,通过不同 style 组合实现:
|
||||
|
||||
```
|
||||
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ← 行1: thick top + thick left/right
|
||||
┃ 标题 ┃ ← 行2-16: thick left + thick right
|
||||
┃ ──────────────────────────── ┃ ← 内部分隔: thin border
|
||||
┃ 内容 ┃
|
||||
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ← 行17: thick bottom + thick left/right
|
||||
```
|
||||
|
||||
边框样式索引分类:
|
||||
|
||||
| 索引范围 | 边框类型 | 用途 |
|
||||
|---------|---------|------|
|
||||
| 24-26 | thick top + left/right | 顶部边框行 |
|
||||
| 27, 31 | thick left | 左边框列 |
|
||||
| 28 | thick right | 右边框列 |
|
||||
| 32-34 | thick bottom + left/right | 底部边框行 |
|
||||
| 6-7 | thin 四边 | 内部费用格子 |
|
||||
|
||||
### 3. 自定义列宽
|
||||
|
||||
```json
|
||||
"cols": {
|
||||
"0": { "width": 35 },
|
||||
"1": { "width": 14 },
|
||||
"2": { "width": 56 },
|
||||
"3": { "width": 54 },
|
||||
"4": { "width": 156 },
|
||||
"5": { "width": 41 },
|
||||
"6": { "width": 31 },
|
||||
"7": { "width": 113 },
|
||||
"8": { "width": 58 },
|
||||
"9": { "width": 20 },
|
||||
"10": { "width": 23 },
|
||||
"11": { "width": 81 },
|
||||
"12": { "width": 12 },
|
||||
"len": 50
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 合并单元格
|
||||
|
||||
```json
|
||||
"merges": [
|
||||
"C3:L3", // 标题横跨10列
|
||||
"C4:D4", // 姓名标签
|
||||
"C5:D5", // 单位标签
|
||||
"C6:D6", // 初步诊断标签
|
||||
"E6:L6", // 诊断结果跨8列
|
||||
"B7:D7", // RP标记
|
||||
"C7:E7", // 药品名称
|
||||
"H7:I7", // 药品规格
|
||||
"D10:L10", // 医嘱跨9列
|
||||
"C11:D11", // 药品费
|
||||
"F11:G11", // 中成药费
|
||||
"I11:K11", // 治疗费
|
||||
"E13:H13", // 合计金额
|
||||
"J16:L16", // 日期
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
### 5. isDrag 属性
|
||||
|
||||
```json
|
||||
"3": { "cells": {...}, "isDrag": true }
|
||||
```
|
||||
|
||||
`isDrag: true` 表示该行的高度曾被用户手动拖拽调整过。
|
||||
|
||||
### 6. toolPrintSizeObj(打印尺寸)
|
||||
|
||||
```json
|
||||
"toolPrintSizeObj": {
|
||||
"printType": "A4",
|
||||
"widthPx": 718,
|
||||
"heightPx": 1047
|
||||
}
|
||||
```
|
||||
|
||||
A4纸张的像素尺寸,用于打印预览。
|
||||
|
||||
## 对应的数据集配置
|
||||
|
||||
### 数据集1:yonghu(患者信息,单条)
|
||||
|
||||
```json
|
||||
{
|
||||
"dbCode": "yonghu",
|
||||
"dbChName": "患者信息",
|
||||
"dbType": "0",
|
||||
"isList": "0",
|
||||
"isPage": "0",
|
||||
"dbDynSql": "select yphone, ysex, yage, danwei, yjieguo, yizhu, yprice, yzhenliao, ytotal, yishe, kdata from yonghu_table where id = '${id}'"
|
||||
}
|
||||
```
|
||||
|
||||
### 数据集2:yaopin(药品明细,列表)
|
||||
|
||||
```json
|
||||
{
|
||||
"dbCode": "yaopin",
|
||||
"dbChName": "药品明细",
|
||||
"dbType": "0",
|
||||
"isList": "1",
|
||||
"isPage": "0",
|
||||
"dbDynSql": "select name, percent, key1, key2 from yaopin_table where chufang_id = '${id}'"
|
||||
}
|
||||
```
|
||||
|
||||
## 与普通列表的区别
|
||||
|
||||
| 特性 | 普通列表 | 处方笺(表单混合) |
|
||||
|------|---------|-------------------|
|
||||
| 数据集数量 | 通常1个 | 多个(yonghu + yaopin) |
|
||||
| 绑定语法 | 全部用 `#{}` | 单条用 `${}`,列表用 `#{}` |
|
||||
| 布局 | 表头+数据行 | 自由布局,多区域 |
|
||||
| 边框 | 统一thin | 外粗内细 |
|
||||
| 列宽 | 默认均匀 | 自定义不等宽 |
|
||||
| 合并单元格 | 少/无 | 大量合并 |
|
||||
| 隐藏行 | 无 | `-1` 行存辅助数据 |
|
||||
17
.trae/skills/jimureport/examples/prescription.md
Normal file
17
.trae/skills/jimureport/examples/prescription.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# 示例7:处方签
|
||||
|
||||
**类型:** 自由布局单据
|
||||
**特征:** 复杂边框布局(thick/thin混用)、主表 `${yonghu.xxx}` + 药品列表 `#{yaopin.xxx}`
|
||||
|
||||
## 数据绑定
|
||||
|
||||
- 主表(单值):`${yonghu.yphone}`、`${yonghu.ysex}`、`${yonghu.yage}`、`${yonghu.danwei}`、`${yonghu.yjieguo}`、`${yonghu.yizhu}`
|
||||
- 药品列表:`#{yaopin.name}`、`#{yaopin.percent}`
|
||||
- 费用汇总:`${yonghu.yprice}`、`${yonghu.yzhenliao}`、`${yonghu.ytotal}`
|
||||
- 签名:`${yonghu.yishe}`、`${yonghu.kdata}`
|
||||
|
||||
## 报表 JSON
|
||||
|
||||
```json
|
||||
{"loopBlockList":[],"area":{"sri":10,"sci":5,"eri":10,"eci":6,"width":72,"height":25},"excel_config_id":"6059e405dd9c66a6d38e00841d2e40cc","printConfig":{"paper":"A4","width":210,"height":297,"definition":1,"isBackend":false},"rows":{"0":{"cells":{"3":{"style":80,"text":" "}},"height":96},"1":{"cells":{"1":{"style":24,"text":" "},"2":{"style":25,"text":" "},"3":{"style":25,"text":" "},"4":{"style":25,"text":" "},"5":{"style":25,"text":" "},"6":{"style":25,"text":" "},"7":{"style":25,"text":" "},"8":{"style":25,"text":" "},"9":{"style":25,"text":" "},"10":{"style":25,"text":" "},"11":{"style":25,"text":" "},"12":{"style":26,"text":" "}},"height":18},"2":{"cells":{"1":{"text":" ","style":27},"2":{"merge":[0,9],"text":"智能医学院处方笺","style":38},"12":{"style":28,"text":" "}},"height":124},"3":{"cells":{"1":{"text":" ","style":46},"2":{"merge":[0,1],"text":"姓名:","style":4},"4":{"text":"${yonghu.yphone}"},"5":{"text":"性别:","style":42},"6":{"text":"${yonghu.ysex}","style":42},"7":{"text":"年龄:","style":47},"8":{"text":"${yonghu.yage}"},"11":{"style":69,"text":" ","merge":[0,1]},"12":{"style":43,"text":" "}},"isDrag":true},"4":{"cells":{"1":{"text":" ","style":74},"2":{"style":4,"merge":[0,1],"text":"单位:"},"4":{"text":"${yonghu.danwei}"},"5":{"text":"电话:"},"6":{"text":"${yonghu.yphone}","merge":[0,5]},"12":{"style":28,"text":" "}},"isDrag":true,"height":29},"5":{"cells":{"1":{"style":31,"text":" "},"2":{"merge":[0,1],"text":"初步诊断:","style":4},"4":{"text":"${yonghu.yjieguo}","merge":[0,7]},"12":{"style":28,"text":" "}},"isDrag":true,"height":34},"6":{"cells":{"1":{"text":" RP:","merge":[0,2],"style":79},"12":{"style":28,"text":" "}},"height":79},"7":{"cells":{"1":{"text":".","style":48},"3":{"text":"#{yaopin.name}","merge":[0,1]},"7":{"text":"#{yaopin.percent}","merge":[0,1]},"12":{"style":28,"text":" "}},"isDrag":true,"height":37},"9":{"cells":{"1":{"style":31,"text":" "},"2":{"text":"医嘱:","style":76},"3":{"text":"${yonghu.yizhu}","style":6,"merge":[0,8]},"12":{"style":28,"text":" "}},"isDrag":true},"10":{"cells":{"1":{"style":31,"text":" "},"2":{"text":"药品费","style":6,"merge":[0,1]},"4":{"text":"${yonghu.yprice}","style":6},"5":{"merge":[0,1],"text":"中成药费","style":6},"8":{"text":"治疗费","merge":[0,2],"style":6},"12":{"style":28,"text":" "}},"isDrag":true},"13":{"cells":{"1":{"style":31,"text":" "},"2":{"text":"合计","style":6,"merge":[0,1]},"4":{"text":"${yonghu.ytotal}","style":6,"merge":[0,7]},"12":{"style":28,"text":" "}},"isDrag":true},"15":{"cells":{"1":{"style":31,"text":" "},"2":{"text":"医师:","style":4,"merge":[0,1]},"4":{"text":"${yonghu.yishe}","style":80},"8":{"text":"日期:","style":4},"9":{"text":"${yonghu.kdata}","style":80,"merge":[0,2]},"12":{"style":71,"text":" "}},"isDrag":true,"height":43},"len":94},"dbexps":[],"dicts":[],"freeze":"A1","dataRectWidth":694,"displayConfig":{},"background":false,"name":"sheet1","autofilter":{},"validations":[],"cols":{"0":{"width":35},"1":{"width":14},"2":{"width":56},"3":{"width":54},"4":{"width":156},"5":{"width":41},"6":{"width":31},"7":{"width":113},"8":{"width":58},"9":{"width":20},"10":{"width":23},"11":{"width":81},"12":{"width":12},"len":50},"merges":["C3:E3","C7:E7","H3:I3","H7:I7","F11:G11","I11:K11","F12:G12","I12:K12","I13:K13","E13:H13","C11:D11","C12:D12","C13:D13","C14:D14","L4:M4","C3:L3","B7:D7","C4:D4","C5:D5","E14:L14","D10:L10","G5:L5","C6:D6","E6:L6","J16:L16","C16:D16","D8:E8","H8:I8"]}
|
||||
```
|
||||
@@ -0,0 +1,287 @@
|
||||
# 纵向分组小计报表示例
|
||||
|
||||
## 场景说明
|
||||
|
||||
员工信息登记表,按**部门**分组(一级),按**学历**分组(二级),自动合并相同分组的单元格,并在分组末尾显示小计/合计行。
|
||||
|
||||
## 分组效果预览
|
||||
|
||||
```
|
||||
┌──────┬──────┬──────┬──────┬──────┬──────┐
|
||||
│ 部门 │ 学历 │ 性别 │ 年龄 │ 姓名 │ 薪水 │
|
||||
├──────┼──────┼──────┼──────┼──────┼──────┤
|
||||
│ │ │ 男 │ 28 │ 张三 │ 8000 │
|
||||
│ │ 本科 ├──────┼──────┼──────┼──────┤
|
||||
│ │ │ 女 │ 25 │ 李四 │ 7500 │
|
||||
│ 研发 │ ├──────┼──────┼──────┼──────┤
|
||||
│ 部 │ │ 小计 │15500 │
|
||||
│ ├──────┼──────┼──────┼──────┼──────┤
|
||||
│ │ 硕士 │ 男 │ 30 │ 王五 │ 12000│
|
||||
│ │ ├──────┼──────┼──────┼──────┤
|
||||
│ │ │ 小计 │12000 │
|
||||
├──────┼──────┼──────┼──────┼──────┼──────┤
|
||||
│ │ 合计 │27500 │
|
||||
├──────┼──────┼──────┼──────┼──────┼──────┤
|
||||
│ ... │ ... │ ... │ ... │ ... │ ... │
|
||||
└──────┴──────┴──────┴──────┴──────┴──────┘
|
||||
```
|
||||
|
||||
## 核心配置
|
||||
|
||||
### 1. 分组字段声明
|
||||
|
||||
jsonStr 顶层需要两个属性:
|
||||
|
||||
```json
|
||||
{
|
||||
"isGroup": true,
|
||||
"groupField": "vegvkdueqw.department"
|
||||
}
|
||||
```
|
||||
|
||||
| 属性 | 说明 |
|
||||
|------|------|
|
||||
| `isGroup` | `true` 启用分组模式 |
|
||||
| `groupField` | 主分组字段,格式 `数据集编码.字段名` |
|
||||
|
||||
### 2. 数据行分组绑定
|
||||
|
||||
```json
|
||||
"3": {
|
||||
"cells": {
|
||||
"1": {
|
||||
"style": 17,
|
||||
"text": "#{vegvkdueqw.group(department)}",
|
||||
"aggregate": "group",
|
||||
"subtotal": "groupField",
|
||||
"funcname": "-1",
|
||||
"subtotalText": "合计"
|
||||
},
|
||||
"2": {
|
||||
"style": 17,
|
||||
"text": "#{vegvkdueqw.group(education)}",
|
||||
"aggregate": "group",
|
||||
"subtotal": "groupField",
|
||||
"funcname": "-1",
|
||||
"subtotalText": "小计"
|
||||
},
|
||||
"3": { "style": 17, "text": "#{vegvkdueqw.sex}" },
|
||||
"4": { "style": 17, "text": "#{vegvkdueqw.age}" },
|
||||
"5": { "style": 17, "text": "#{vegvkdueqw.name}" },
|
||||
"6": { "style": 17, "text": "#{vegvkdueqw.salary}" }
|
||||
},
|
||||
"height": 54
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 分组单元格属性详解
|
||||
|
||||
| 属性 | 值 | 说明 |
|
||||
|------|-----|------|
|
||||
| `text` | `#{dbCode.group(fieldName)}` | 分组绑定语法,自动合并相同值的单元格 |
|
||||
| `aggregate` | `"group"` | 标记为分组聚合列 |
|
||||
| `subtotal` | `"groupField"` | 启用小计行 |
|
||||
| `funcname` | `"-1"` | 小计函数:`"-1"`=不计算(仅显示文本),可选 `"SUM"` `"AVG"` `"COUNT"` 等 |
|
||||
| `subtotalText` | `"合计"` / `"小计"` | 小计行显示的文本 |
|
||||
|
||||
### 4. 分组绑定语法
|
||||
|
||||
| 语法 | 说明 |
|
||||
|------|------|
|
||||
| `#{dbCode.group(field)}` | 分组字段,相同值自动合并单元格 |
|
||||
| `#{dbCode.field}` | 普通字段,每行独立显示 |
|
||||
|
||||
### 5. 多级分组
|
||||
|
||||
- **一级分组**(部门):subtotalText = `"合计"` — 部门切换时显示合计行
|
||||
- **二级分组**(学历):subtotalText = `"小计"` — 学历切换时显示小计行
|
||||
- 分组列从左到右排列,左边为高级别分组
|
||||
|
||||
## 完整 jsonStr
|
||||
|
||||
```json
|
||||
{
|
||||
"loopBlockList": [],
|
||||
"querySetting": {
|
||||
"izOpenQueryBar": false,
|
||||
"izDefaultQuery": true
|
||||
},
|
||||
"recordSubTableOrCollection": { "group": [], "record": [], "range": [] },
|
||||
"printConfig": {
|
||||
"paper": "A4",
|
||||
"width": 210,
|
||||
"height": 297,
|
||||
"definition": 4,
|
||||
"isBackend": false,
|
||||
"marginX": 10,
|
||||
"marginY": 10,
|
||||
"layout": "portrait",
|
||||
"printCallBackUrl": "",
|
||||
"paginationShow": false,
|
||||
"paginationLocation": "middle",
|
||||
"paginationStart": 1,
|
||||
"headerFooterShow": false,
|
||||
"headerLocation": "left",
|
||||
"headerText": "",
|
||||
"footerLocation": "left",
|
||||
"footerText": "",
|
||||
"fontsize": 28,
|
||||
"rotationAngle": -45,
|
||||
"watermarkColor": "#246DDE",
|
||||
"watermarkText": "积木报表",
|
||||
"watermarkShow": true,
|
||||
"printFootorFixBottom": false
|
||||
},
|
||||
"hidden": { "rows": [], "cols": [], "conditions": { "rows": {}, "cols": {} } },
|
||||
"queryFormSetting": { "useQueryForm": false, "dbKey": "", "idField": "" },
|
||||
"dbexps": [],
|
||||
"dicts": [],
|
||||
"fillFormToolbar": {
|
||||
"show": true,
|
||||
"btnList": ["save", "subTable_add", "verify", "subTable_del", "print", "close", "first", "prev", "next", "paging", "total", "last", "exportPDF", "exportExcel", "exportWord"]
|
||||
},
|
||||
"freeze": "A1",
|
||||
"dataRectWidth": 687,
|
||||
"isViewContentHorizontalCenter": false,
|
||||
"autofilter": {},
|
||||
"validations": [],
|
||||
"cols": {
|
||||
"0": { "width": 34 },
|
||||
"1": { "width": 119 },
|
||||
"3": { "width": 117 },
|
||||
"6": { "width": 117 },
|
||||
"7": { "width": 22 },
|
||||
"len": 100
|
||||
},
|
||||
"area": { "sri": 16, "sci": 4, "eri": 16, "eci": 4, "width": 100, "height": 25 },
|
||||
"pyGroupEngine": false,
|
||||
"submitHandlers": [],
|
||||
"excel_config_id": "1162913845578612736",
|
||||
"hiddenCells": [],
|
||||
"zonedEditionList": [],
|
||||
"rows": {
|
||||
"1": {
|
||||
"cells": {
|
||||
"1": {
|
||||
"merge": [0, 5],
|
||||
"style": 2,
|
||||
"text": "纵向员工信息登记表",
|
||||
"height": 0
|
||||
}
|
||||
},
|
||||
"height": 40
|
||||
},
|
||||
"2": {
|
||||
"cells": {
|
||||
"1": { "style": 15, "text": "部门" },
|
||||
"2": { "style": 15, "text": "学历" },
|
||||
"3": { "style": 15, "text": "性别" },
|
||||
"4": { "style": 15, "text": "年龄" },
|
||||
"5": { "style": 15, "text": "姓名" },
|
||||
"6": { "style": 15, "text": "薪水" }
|
||||
},
|
||||
"height": 34
|
||||
},
|
||||
"3": {
|
||||
"cells": {
|
||||
"1": {
|
||||
"style": 17,
|
||||
"text": "#{vegvkdueqw.group(department)}",
|
||||
"aggregate": "group",
|
||||
"subtotal": "groupField",
|
||||
"funcname": "-1",
|
||||
"subtotalText": "合计"
|
||||
},
|
||||
"2": {
|
||||
"style": 17,
|
||||
"text": "#{vegvkdueqw.group(education)}",
|
||||
"aggregate": "group",
|
||||
"subtotal": "groupField",
|
||||
"funcname": "-1",
|
||||
"subtotalText": "小计"
|
||||
},
|
||||
"3": { "style": 17, "text": "#{vegvkdueqw.sex}" },
|
||||
"4": { "style": 17, "text": "#{vegvkdueqw.age}" },
|
||||
"5": { "style": 17, "text": "#{vegvkdueqw.name}" },
|
||||
"6": { "style": 17, "text": "#{vegvkdueqw.salary}" }
|
||||
},
|
||||
"height": 54
|
||||
},
|
||||
"len": 100
|
||||
},
|
||||
"rpbar": { "show": true, "pageSize": "", "btnList": [] },
|
||||
"groupField": "vegvkdueqw.department",
|
||||
"fixedPrintHeadRows": [],
|
||||
"fixedPrintTailRows": [],
|
||||
"displayConfig": {},
|
||||
"fillFormInfo": { "layout": { "direction": "horizontal", "width": 200, "height": 45 } },
|
||||
"background": false,
|
||||
"name": "sheet1",
|
||||
"styles": [
|
||||
{ "font": { "bold": true } },
|
||||
{ "font": { "size": 16, "bold": true } },
|
||||
{ "align": "center", "font": { "size": 16, "bold": true } },
|
||||
{ "align": "center" },
|
||||
{ "bgcolor": "#5b9cd6", "align": "center" },
|
||||
{ "bgcolor": "#5b9cd6", "color": "#ffffff", "align": "center" },
|
||||
{ "border": { "top": ["thin", "#000"], "left": ["thin", "#000"], "bottom": ["thin", "#000"], "right": ["thin", "#000"] }, "bgcolor": "#5b9cd6", "color": "#ffffff", "align": "center" },
|
||||
{ "border": { "top": ["thin", "#000"], "left": ["thin", "#000"], "bottom": ["thin", "#000"], "right": ["thin", "#000"] } },
|
||||
{ "border": { "top": ["thin", "#bfbfbf"], "left": ["thin", "#bfbfbf"], "bottom": ["thin", "#bfbfbf"], "right": ["thin", "#bfbfbf"] }, "bgcolor": "#5b9cd6", "color": "#ffffff", "align": "center" },
|
||||
{ "border": { "top": ["thin", "#bfbfbf"], "left": ["thin", "#bfbfbf"], "bottom": ["thin", "#bfbfbf"], "right": ["thin", "#bfbfbf"] } },
|
||||
{ "border": { "top": ["thin", "#9cc2e6"], "left": ["thin", "#9cc2e6"], "bottom": ["thin", "#9cc2e6"], "right": ["thin", "#9cc2e6"] }, "bgcolor": "#5b9cd6", "color": "#ffffff", "align": "center" },
|
||||
{ "border": { "top": ["thin", "#9cc2e6"], "left": ["thin", "#9cc2e6"], "bottom": ["thin", "#9cc2e6"], "right": ["thin", "#9cc2e6"] } },
|
||||
{ "border": { "top": ["thin", "#9cc2e6"], "left": ["thin", "#9cc2e6"], "bottom": ["thin", "#9cc2e6"], "right": ["thin", "#9cc2e6"] }, "bgcolor": "#bdd7ee", "color": "#ffffff", "align": "center" },
|
||||
{ "border": { "top": ["thin", "#9cc2e6"], "left": ["thin", "#9cc2e6"], "bottom": ["thin", "#9cc2e6"], "right": ["thin", "#9cc2e6"] }, "bgcolor": "#bdd7ee", "color": "#000100", "align": "center" },
|
||||
{ "border": { "top": ["thin", "#9cc2e6"], "left": ["thin", "#9cc2e6"], "bottom": ["thin", "#9cc2e6"], "right": ["thin", "#9cc2e6"] }, "bgcolor": "#9cc2e6", "color": "#000100", "align": "center" },
|
||||
{ "border": { "top": ["thin", "#5b9cd6"], "left": ["thin", "#5b9cd6"], "bottom": ["thin", "#5b9cd6"], "right": ["thin", "#5b9cd6"] }, "bgcolor": "#9cc2e6", "color": "#000100", "align": "center" },
|
||||
{ "border": { "top": ["thin", "#5b9cd6"], "left": ["thin", "#5b9cd6"], "bottom": ["thin", "#5b9cd6"], "right": ["thin", "#5b9cd6"] } },
|
||||
{ "border": { "top": ["thin", "#5b9cd6"], "left": ["thin", "#5b9cd6"], "bottom": ["thin", "#5b9cd6"], "right": ["thin", "#5b9cd6"] }, "align": "center" }
|
||||
],
|
||||
"isGroup": true,
|
||||
"freezeLineColor": "rgb(185, 185, 185)",
|
||||
"merges": ["B2:G2"]
|
||||
}
|
||||
```
|
||||
|
||||
## 样式方案(蓝色主题)
|
||||
|
||||
| 索引 | 背景色 | 字体色 | 边框色 | 用途 |
|
||||
|------|--------|--------|--------|------|
|
||||
| 2 | — | — | — | 标题(16号加粗居中) |
|
||||
| 15 | #9cc2e6 | #000100 | #5b9cd6 | **表头行**(中蓝底) |
|
||||
| 17 | — | — | #5b9cd6 | **数据行**(蓝色边框居中) |
|
||||
|
||||
三层蓝色渐变:
|
||||
- 深蓝 `#5b9cd6` — 表头背景/边框色
|
||||
- 中蓝 `#9cc2e6` — 表头行背景
|
||||
- 浅蓝 `#bdd7ee` — 交替行/小计行背景
|
||||
|
||||
## 打印配置(含水印)
|
||||
|
||||
```json
|
||||
"printConfig": {
|
||||
"paper": "A4",
|
||||
"definition": 4,
|
||||
"watermarkShow": true,
|
||||
"watermarkText": "积木报表",
|
||||
"watermarkColor": "#246DDE",
|
||||
"fontsize": 28,
|
||||
"rotationAngle": -45,
|
||||
"paginationShow": false,
|
||||
"headerFooterShow": false,
|
||||
"printFootorFixBottom": false
|
||||
}
|
||||
```
|
||||
|
||||
| 属性 | 说明 |
|
||||
|------|------|
|
||||
| `definition` | 打印清晰度(1-4,4最高) |
|
||||
| `watermarkShow` | 启用水印 |
|
||||
| `watermarkText` | 水印文字 |
|
||||
| `watermarkColor` | 水印颜色 |
|
||||
| `fontsize` | 水印字号 |
|
||||
| `rotationAngle` | 水印旋转角度(负数=逆时针) |
|
||||
| `paginationShow` | 是否显示页码 |
|
||||
| `paginationLocation` | 页码位置:left/middle/right |
|
||||
| `headerFooterShow` | 是否显示页眉页脚 |
|
||||
| `printFootorFixBottom` | 页脚是否固定在底部 |
|
||||
21
.trae/skills/jimureport/examples/zone-edition.md
Normal file
21
.trae/skills/jimureport/examples/zone-edition.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# 示例6:分版(多表格并排)
|
||||
|
||||
**类型:** 分版报表
|
||||
**特征:** `zonedEditionList` 定义多个独立数据区域,单元格标记 `"zonedEdition":N`
|
||||
|
||||
## 关键配置
|
||||
|
||||
```json
|
||||
{
|
||||
"zonedEditionList": [
|
||||
{"sci":4,"sri":3,"eci":6,"eri":4,"db":"flapi","index":1},
|
||||
{"sci":8,"sri":4,"eci":9,"eri":5,"db":"flapi","index":2}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 报表 JSON
|
||||
|
||||
```json
|
||||
{"loopBlockList":[],"querySetting":{"izOpenQueryBar":false,"izDefaultQuery":true},"recordSubTableOrCollection":{"group":[],"record":[],"range":[]},"printConfig":{"paper":"A4","width":210,"height":297,"definition":1,"isBackend":false,"marginX":10,"marginY":10,"layout":"portrait"},"hidden":{"rows":[],"cols":[],"conditions":{"rows":{},"cols":{}}},"queryFormSetting":{"useQueryForm":false,"dbKey":"","idField":""},"dbexps":[],"dicts":[],"freeze":"A1","dataRectWidth":930,"isViewContentHorizontalCenter":false,"autofilter":{},"validations":[],"cols":{"3":{"width":69},"7":{"width":61},"len":50},"pyGroupEngine":false,"submitHandlers":[],"excel_config_id":"1193411148792549376","hiddenCells":[],"zonedEditionList":[{"sci":4,"sri":3,"eci":6,"eri":4,"db":"flapi","index":1},{"sci":8,"sri":4,"eci":9,"eri":5,"db":"flapi","index":2}],"rows":{"1":{"cells":{"3":{"style":18,"text":"分版示例","merge":[0,1],"height":65},"5":{"merge":[0,2],"height":65,"text":"说明:当报表左侧已有表格,右侧仍需要展示表格时,需使用分版功能","style":20}},"height":65},"2":{"cells":{"1":{"text":"表1","style":7},"5":{"text":"表2","style":7}},"height":41},"3":{"cells":{"0":{"text":"姓名","style":2},"1":{"text":"性别","style":2},"2":{"text":"年龄","style":2},"4":{"text":"省份","style":2,"zonedEdition":1},"5":{"style":2,"zonedEdition":1,"text":"月份"},"6":{"style":2,"zonedEdition":1,"text":"金额"},"8":{"text":"表3","style":8}},"height":35},"4":{"cells":{"0":{"text":"#{jm.name}","style":0},"1":{"text":"#{jm.sex}","style":0},"2":{"text":"#{jm.age}","style":0},"4":{"text":"#{flapi.dept}","style":0,"zonedEdition":1},"5":{"style":0,"zonedEdition":1,"text":"#{flapi.month}月"},"6":{"style":0,"zonedEdition":1,"text":"#{flapi.amount}"},"8":{"text":"年份","style":2,"zonedEdition":2},"9":{"text":"金额","style":2,"zonedEdition":2}},"height":30},"5":{"cells":{"8":{"text":"#{flapi.year}年","style":0,"zonedEdition":2},"9":{"text":"#{flapi.settleamount}","style":0,"zonedEdition":2}}},"len":103},"rpbar":{"show":true,"pageSize":"","btnList":[]},"name":"sheet1","merges":["D1:E1","D2:E2","F2:H2"]}
|
||||
```
|
||||
266
.trae/skills/jimureport/references/chart-config.md
Normal file
266
.trae/skills/jimureport/references/chart-config.md
Normal file
@@ -0,0 +1,266 @@
|
||||
# 图表配置参考
|
||||
|
||||
## 图表模板文件位置
|
||||
|
||||
`src/main/resources/static/jmreport/desreport_/chartjson/`
|
||||
|
||||
生成图表报表时,应先读取对应的模板 JSON 文件作为 ECharts 配置基础。
|
||||
|
||||
## 可用图表类型
|
||||
|
||||
| 文件名 | chartType | 说明 |
|
||||
|--------|-----------|------|
|
||||
| `bar.simple.json` | `bar.simple` | 柱状图(单系列) |
|
||||
| `bar.multi.json` | `bar.multi` | 柱状图(多系列) |
|
||||
| `bar.stack.json` | `bar.stack` | 堆叠柱状图 |
|
||||
| `bar.horizontal.json` | `bar.horizontal` | 横向柱状图 |
|
||||
| `bar.multi.horizontal.json` | `bar.multi.horizontal` | 横向多系列柱状图 |
|
||||
| `bar.stack.horizontal.json` | `bar.stack.horizontal` | 横向堆叠柱状图 |
|
||||
| `bar.negative.json` | `bar.negative` | 正负柱状图 |
|
||||
| `bar.background.json` | `bar.background` | 带背景柱状图 |
|
||||
| `line.simple.json` | `line.simple` | 折线图(单系列) |
|
||||
| `line.multi.json` | `line.multi` | 折线图(多系列) |
|
||||
| `line.smooth.json` | `line.smooth` | 平滑曲线图 |
|
||||
| `line.area.json` | `line.area` | 面积图 |
|
||||
| `line.step.json` | `line.step` | 阶梯折线图 |
|
||||
| `pie.simple.json` | `pie.simple` | 饼图 |
|
||||
| `pie.doughnut.json` | `pie.doughnut` | 环形图 |
|
||||
| `pie.rose.json` | `pie.rose` | 玫瑰图 |
|
||||
| `mixed.linebar.json` | `mixed.linebar` | 柱状+折线混合图 |
|
||||
| `radar.basic.json` | `radar.basic` | 雷达图 |
|
||||
| `radar.custom.json` | `radar.custom` | 自定义雷达图 |
|
||||
| `scatter.simple.json` | `scatter.simple` | 散点图 |
|
||||
| `scatter.bubble.json` | `scatter.bubble` | 气泡图 |
|
||||
| `funnel.simple.json` | `funnel.simple` | 漏斗图 |
|
||||
| `funnel.pyramid.json` | `funnel.pyramid` | 金字塔图 |
|
||||
| `gauge.simple.json` | `gauge.simple` | 仪表盘 |
|
||||
| `gauge.simple180.json` | `gauge.simple180` | 半圆仪表盘 |
|
||||
| `graph.simple.json` | `graph.simple` | 关系图 |
|
||||
| `map.simple.json` | `map.simple` | 地图 |
|
||||
| `map.scatter.json` | `map.scatter` | 地图散点 |
|
||||
| `pictorial.spirits.json` | `pictorial.spirits` | 象形柱图 |
|
||||
|
||||
## echartslist.json 主要 key 对照
|
||||
|
||||
| key | 对应图表 |
|
||||
|-----|---------|
|
||||
| `bar` | 单系列柱状图 |
|
||||
| `bar2` | dataset 模式柱状图 |
|
||||
| `bar3` | 多系列柱状图 |
|
||||
| `line` | 单系列折线图 |
|
||||
| `line3` | 平滑曲线 |
|
||||
| `line4` | 多系列折线图 |
|
||||
| `line5` | 阶梯折线图 |
|
||||
| `pie` | 饼图 |
|
||||
| `pie1` | 环形图 |
|
||||
| `pie2` | 玫瑰图 |
|
||||
| `linebar` | 柱状+折线混合 |
|
||||
| `map` | 地图 |
|
||||
| `scatter` | 散点图 |
|
||||
|
||||
## 图表在 jsonStr 中的配置
|
||||
|
||||
图表通过**单元格占位 + chartList 配置**实现,不是绝对定位。需要两部分配合:
|
||||
|
||||
### 1. chartList 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"chartList": [
|
||||
{
|
||||
"row": 5,
|
||||
"col": 1,
|
||||
"colspan": 0,
|
||||
"rowspan": 0,
|
||||
"width": "500",
|
||||
"height": "350",
|
||||
"config": "ECharts配置JSON字符串",
|
||||
"url": "",
|
||||
"extData": {
|
||||
"chartType": "bar.simple",
|
||||
"dataType": "sql",
|
||||
"dataId": "数据集ID",
|
||||
"dbCode": "数据集编码",
|
||||
"axisX": "name",
|
||||
"axisY": "value",
|
||||
"series": "type",
|
||||
"xText": "",
|
||||
"yText": "",
|
||||
"apiStatus": "1"
|
||||
},
|
||||
"layer_id": "唯一层ID",
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"backgroud": {"enabled": false, "color": "#fff", "image": ""},
|
||||
"virtualCellRange": [[5,1],[5,2],[5,3],[6,1],[6,2],[6,3]]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> **关键字段说明:**
|
||||
>
|
||||
> | 字段 | 类型 | 说明 |
|
||||
> |------|------|------|
|
||||
> | `row` / `col` | number | 图表起始位置(行号/列号),**不是 left/top 像素值** |
|
||||
> | `width` / `height` | **string** | 图表宽高像素,**必须是字符串**(如 `"500"`,不是 `500`) |
|
||||
> | `virtualCellRange` | array | 图表占据的所有单元格坐标 `[[row,col], ...]` |
|
||||
> | `layer_id` | string | 唯一标识,对应 rows 中 cells 的 `virtual` 属性 |
|
||||
> | `backgroud` | object | 图表背景(注意拼写是 `backgroud` 不是 `background`) |
|
||||
> | `offsetX` / `offsetY` | number | 偏移量,通常为 0 |
|
||||
|
||||
### 2. rows 中的 virtual 占位
|
||||
|
||||
图表占据的每个单元格必须在 `rows` 中声明 `"virtual": "layer_id"`:
|
||||
|
||||
```json
|
||||
"rows": {
|
||||
"5": {
|
||||
"cells": {
|
||||
"1": {"text": " ", "virtual": "chart_xxx"},
|
||||
"2": {"text": " ", "virtual": "chart_xxx"},
|
||||
"3": {"text": " ", "virtual": "chart_xxx"}
|
||||
}
|
||||
},
|
||||
"6": {
|
||||
"cells": {
|
||||
"1": {"text": " ", "virtual": "chart_xxx"},
|
||||
"2": {"text": " ", "virtual": "chart_xxx"},
|
||||
"3": {"text": " ", "virtual": "chart_xxx"}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **注意:**
|
||||
> - `virtual` 的值必须和 `chartList[].layer_id` 一致
|
||||
> - `text` 设为 `" "`(一个空格),不能为空字符串
|
||||
> - 图表区域的行数 × 列数 = `virtualCellRange` 的元素数量
|
||||
> - 图表区域不能和列表数据行重叠
|
||||
|
||||
### extData 关键字段
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `chartType` | 图表类型(如 `bar.simple`, `line.multi`, `pie.simple`) |
|
||||
| `dataType` | 数据来源:`"sql"` / `"api"` / `"json"` / `"javabean"` / `"files"`(前端文本值,与 dbType 数字不同) |
|
||||
| `dataId` | 数据集ID(saveDb 返回的 id) |
|
||||
| `dbCode` | 数据集编码 |
|
||||
| `axisX` | X轴/分类字段名,**固定为 `name`** |
|
||||
| `axisY` | Y轴/数值字段名,**固定为 `value`** |
|
||||
| `series` | 系列/分组字段名,**固定为 `type`**(单系列也要传 `"type"`) |
|
||||
| `apiStatus` | API 数据集是否启用(`"1"` = 启用) |
|
||||
| `dataId1` | 第二数据集ID(关系图 `graph.simple` 使用) |
|
||||
| `isCustomPropName` | 是否自定义字段映射(默认不填,使用 name/value/type) |
|
||||
|
||||
### 图表字段映射规则
|
||||
|
||||
> **重要:图表数据绑定使用固定的三个字段名,不是数据集的原始字段名。**
|
||||
|
||||
| extData 字段 | 固定值 | 含义 | 示例 |
|
||||
|-------------|--------|------|------|
|
||||
| `axisX` | `name` | X轴/分类 | 产品名称 |
|
||||
| `axisY` | `value` | Y轴/数值 | 销售额 |
|
||||
| `series` | `type` | 系列/分组(多系列) | 月份、类别 |
|
||||
|
||||
前端渲染时会将数据集查询结果按 `name`/`value`/`type` 进行映射:
|
||||
- 单系列图表:`series` 也传 `"type"`(数据中 type 字段可为空字符串)
|
||||
- 多系列图表:`series` = `"type"`,按 `type` 值分组生成多条系列
|
||||
|
||||
**SQL 数据集示例(需要 AS 别名映射到 name/value):**
|
||||
```sql
|
||||
SELECT product_name AS name, sales_amount AS value FROM sales_table
|
||||
```
|
||||
|
||||
**多系列 SQL 示例(加 type 字段):**
|
||||
```sql
|
||||
SELECT month AS name, amount AS value, category AS type FROM sales_table
|
||||
```
|
||||
|
||||
**JSON 数据集示例:**
|
||||
```json
|
||||
{"data": [
|
||||
{"name": "螺丝钉", "value": 5000, "type": ""},
|
||||
{"name": "电阻器", "value": 3200, "type": ""}
|
||||
]}
|
||||
```
|
||||
|
||||
## 使用流程
|
||||
|
||||
1. 根据需求确定 `chartType`
|
||||
2. 读取对应的 `chartjson/{chartType}.json` 文件作为 ECharts 配置模板
|
||||
3. 修改模板中的 `title.text`、`series` 等,`data` 留空(由数据集驱动)
|
||||
4. 将配置 JSON 字符串化后放入 `chartList[].config`
|
||||
5. 配置 `extData`:`axisX`=`name`,`axisY`=`value`,`series`=`type`
|
||||
6. 数据集字段必须包含 `name` 和 `value`(SQL 用 AS 别名,JSON 直接命名)
|
||||
7. 确定图表占位区域(起始 row/col,占几行几列)
|
||||
8. 在 `rows` 中为每个占位 cell 添加 `"virtual": "layer_id"`
|
||||
9. 构造 `virtualCellRange`(所有占位坐标数组)
|
||||
10. 将 `chartList` 放入 jsonStr 顶层
|
||||
|
||||
## 完整示例(列表 + 柱状图)
|
||||
|
||||
### 数据集配置
|
||||
|
||||
列表和图表使用**两个独立数据集**(db_code 唯一):
|
||||
|
||||
| 数据集 | dbCode | dbType | 用途 | 字段 |
|
||||
|--------|--------|--------|------|------|
|
||||
| 进库列表 | `stocklist` | 3(JSON) | 列表展示 | name, quantity, stock_time |
|
||||
| 进库图表 | `stockchart` | 3(JSON) | 柱状图 | **name, value** |
|
||||
|
||||
### Python 生成图表占位的关键代码
|
||||
|
||||
```python
|
||||
layer_id = "chart_" + gen_id()
|
||||
|
||||
# 图表占据 row5~row14, col1~col5
|
||||
chart_row_start, chart_row_end = 5, 14
|
||||
chart_col_start, chart_col_end = 1, 5
|
||||
|
||||
# 1. 构造 virtualCellRange
|
||||
virtual_cell_range = []
|
||||
for r in range(chart_row_start, chart_row_end + 1):
|
||||
for c in range(chart_col_start, chart_col_end + 1):
|
||||
virtual_cell_range.append([r, c])
|
||||
|
||||
# 2. 构造 rows 中的 virtual 占位 cells
|
||||
chart_rows = {}
|
||||
for r in range(chart_row_start, chart_row_end + 1):
|
||||
cells = {}
|
||||
for c in range(chart_col_start, chart_col_end + 1):
|
||||
cells[str(c)] = {"text": " ", "virtual": layer_id}
|
||||
chart_rows[str(r)] = {"cells": cells}
|
||||
|
||||
# 3. 合并到 all_rows
|
||||
all_rows.update(chart_rows)
|
||||
|
||||
# 4. chartList 配置
|
||||
chart_item = {
|
||||
"row": chart_row_start,
|
||||
"col": chart_col_start,
|
||||
"colspan": 0,
|
||||
"rowspan": 0,
|
||||
"width": "500", # 字符串!
|
||||
"height": "350", # 字符串!
|
||||
"config": json.dumps(chart_config, ensure_ascii=False),
|
||||
"url": "",
|
||||
"extData": {
|
||||
"chartType": "bar.simple",
|
||||
"dataType": "json",
|
||||
"dataId": chart_db_id,
|
||||
"dbCode": "stockchart",
|
||||
"axisX": "name",
|
||||
"axisY": "value",
|
||||
"series": "type",
|
||||
"xText": "",
|
||||
"yText": "",
|
||||
"apiStatus": "1"
|
||||
},
|
||||
"layer_id": layer_id,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"backgroud": {"enabled": False, "color": "#fff", "image": ""},
|
||||
"virtualCellRange": virtual_cell_range
|
||||
}
|
||||
```
|
||||
343
.trae/skills/jimureport/references/chart-templates.md
Normal file
343
.trae/skills/jimureport/references/chart-templates.md
Normal file
@@ -0,0 +1,343 @@
|
||||
# 图表模板快速参考
|
||||
|
||||
积木报表内置 30+ 图表模板,文件位于 `static/jmreport/desreport_/chartjson/`。
|
||||
可通过 `GET /jmreport/addChart?chartType=bar.simple` 获取模板配置。
|
||||
|
||||
生成图表时,从模板中取 ECharts 配置,修改 `title.text`,清空 `data`(由数据集驱动),然后放入 `chartList[].config`。
|
||||
|
||||
## 图表分类速查
|
||||
|
||||
### 柱状图 (Bar)
|
||||
|
||||
| chartType | 说明 | 数据集要求 |
|
||||
|-----------|------|-----------|
|
||||
| `bar.simple` | 单系列柱状图 | `name, value` |
|
||||
| `bar.multi` | 多系列柱状图 | `name, value, type` |
|
||||
| `bar.stack` | 堆叠柱状图 | `name, value, type` |
|
||||
| `bar.horizontal` | 横向柱状图 | `name, value` |
|
||||
| `bar.multi.horizontal` | 横向多系列 | `name, value, type` |
|
||||
| `bar.stack.horizontal` | 横向堆叠 | `name, value, type` |
|
||||
| `bar.negative` | 正负柱状图 | `name, value, type` |
|
||||
| `bar.background` | 带背景柱状图 | `name, value` |
|
||||
|
||||
### 折线图 (Line)
|
||||
|
||||
| chartType | 说明 | 数据集要求 |
|
||||
|-----------|------|-----------|
|
||||
| `line.simple` | 单系列折线图 | `name, value` |
|
||||
| `line.multi` | 多系列折线图 | `name, value, type` |
|
||||
| `line.smooth` | 平滑曲线图 | `name, value` |
|
||||
| `line.area` | 面积图 | `name, value, type` |
|
||||
| `line.step` | 阶梯折线图 | `name, value` |
|
||||
|
||||
### 饼图 (Pie)
|
||||
|
||||
| chartType | 说明 | 数据集要求 |
|
||||
|-----------|------|-----------|
|
||||
| `pie.simple` | 饼图 | `name, value` |
|
||||
| `pie.doughnut` | 环形图 | `name, value` |
|
||||
| `pie.rose` | 玫瑰图(南丁格尔) | `name, value` |
|
||||
|
||||
### 混合图
|
||||
|
||||
| chartType | 说明 | 数据集要求 |
|
||||
|-----------|------|-----------|
|
||||
| `mixed.linebar` | 柱状+折线混合 | `name, value, type` |
|
||||
|
||||
### 其他图表
|
||||
|
||||
| chartType | 说明 | 数据集要求 |
|
||||
|-----------|------|-----------|
|
||||
| `gauge.simple` | 仪表盘 | `name, value` |
|
||||
| `gauge.simple180` | 半圆仪表盘 | `name, value` |
|
||||
| `radar.basic` | 雷达图 | 特殊(indicator) |
|
||||
| `radar.custom` | 自定义雷达图 | 特殊 |
|
||||
| `funnel.simple` | 漏斗图 | `name, value` |
|
||||
| `funnel.pyramid` | 金字塔图 | `name, value` |
|
||||
| `scatter.simple` | 散点图 | 特殊 |
|
||||
| `scatter.bubble` | 气泡图 | 特殊 |
|
||||
| `map.simple` | 地图 | 特殊 |
|
||||
| `map.scatter` | 地图散点 | 特殊 |
|
||||
| `graph.simple` | 关系图 | 特殊(需两个数据集) |
|
||||
| `pictorial.spirits` | 象形柱图 | `name, value` |
|
||||
|
||||
## 常用图表 ECharts 配置模板
|
||||
|
||||
### bar.simple — 单系列柱状图
|
||||
|
||||
```python
|
||||
{
|
||||
"title": {"show": True, "text": "标题", "left": "left", "top": "5",
|
||||
"padding": [5,20,5,20],
|
||||
"textStyle": {"fontSize": 18, "fontWeight": "bolder", "color": "#c23531"}},
|
||||
"grid": {"left": 60, "top": 60, "right": 100, "bottom": 60},
|
||||
"tooltip": {"show": True, "textStyle": {"color": "#fff", "fontSize": 18}},
|
||||
"xAxis": {"show": True, "name": "", "data": [],
|
||||
"axisLabel": {"textStyle": {"fontSize": 12, "color": "#333"}},
|
||||
"axisLine": {"lineStyle": {"color": "#333"}}},
|
||||
"yAxis": {"show": True, "name": "",
|
||||
"axisLabel": {"textStyle": {"fontSize": 12, "color": "#333"}},
|
||||
"axisLine": {"lineStyle": {"color": "#333"}}},
|
||||
"series": [{"name": "", "type": "bar", "data": [],
|
||||
"barWidth": 50, "barMinHeight": 2,
|
||||
"itemStyle": {"barBorderRadius": 0, "color": "#c43632"}}]
|
||||
}
|
||||
```
|
||||
|
||||
### bar.multi — 多系列柱状图
|
||||
|
||||
```python
|
||||
{
|
||||
"title": {"show": True, "text": "标题", "left": "left",
|
||||
"padding": [5,20,5,20],
|
||||
"textStyle": {"fontSize": 18, "fontWeight": "bolder", "color": "#c23531"}},
|
||||
"legend": {"show": True, "data": [], "top": "top", "left": "center",
|
||||
"orient": "horizontal", "padding": [25,20,25,10],
|
||||
"textStyle": {"color": "#333", "fontSize": 12}},
|
||||
"grid": {"left": 60, "top": 60, "right": 100, "bottom": 60},
|
||||
"tooltip": {"show": True, "trigger": "axis",
|
||||
"axisPointer": {"type": "shadow"},
|
||||
"textStyle": {"color": "#fff", "fontSize": 18}},
|
||||
"xAxis": {"show": True, "type": "category", "data": [],
|
||||
"axisLabel": {"textStyle": {"fontSize": 12, "color": "#333"}},
|
||||
"axisLine": {"lineStyle": {"color": "#333"}}},
|
||||
"yAxis": {"show": True,
|
||||
"axisLabel": {"textStyle": {"fontSize": 12, "color": "#333"}},
|
||||
"axisLine": {"lineStyle": {"color": "#333"}}},
|
||||
"series": [
|
||||
{"name": "系列1", "type": "bar", "data": [], "barWidth": 0, "barMinHeight": 2,
|
||||
"label": {"show": True, "position": "top", "textStyle": {"color": "black", "fontSize": 12}},
|
||||
"itemStyle": {"barBorderRadius": 0, "color": ""}},
|
||||
{"name": "系列2", "type": "bar", "data": [], "barWidth": 0, "barMinHeight": 2,
|
||||
"label": {"show": True, "position": "top", "textStyle": {"color": "black", "fontSize": 12}},
|
||||
"itemStyle": {"barBorderRadius": 0, "color": ""}}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### line.simple — 单系列折线图
|
||||
|
||||
```python
|
||||
{
|
||||
"title": {"show": True, "text": "标题",
|
||||
"textStyle": {"fontSize": 18, "fontWeight": "bolder", "color": "#c23531"}},
|
||||
"grid": {"left": 60, "top": 60, "right": 100, "bottom": 60},
|
||||
"xAxis": {"show": True, "data": []},
|
||||
"yAxis": {"show": True, "name": ""},
|
||||
"series": [{"name": "", "type": "line", "data": [],
|
||||
"smooth": False, "showSymbol": True, "symbolSize": 5,
|
||||
"lineStyle": {"width": 2, "color": "#c43632"}}]
|
||||
}
|
||||
```
|
||||
|
||||
### pie.simple — 饼图
|
||||
|
||||
```python
|
||||
{
|
||||
"title": {"show": True, "text": "标题",
|
||||
"textStyle": {"fontSize": 18, "fontWeight": "bolder", "color": "#c23531"}},
|
||||
"tooltip": {"show": True, "formatter": "{a} <br/>{b} : {c}"},
|
||||
"legend": {"show": True, "data": [], "orient": "horizontal",
|
||||
"textStyle": {"color": "#333", "fontSize": 12}},
|
||||
"series": [{"name": "数据", "type": "pie",
|
||||
"radius": "55%", "minAngle": 0,
|
||||
"center": [320, 180],
|
||||
"label": {"show": True, "position": "outside"},
|
||||
"data": []}]
|
||||
}
|
||||
```
|
||||
|
||||
### pie.doughnut — 环形图
|
||||
|
||||
```python
|
||||
{
|
||||
"title": {"show": True, "text": "标题",
|
||||
"textStyle": {"fontSize": 18, "fontWeight": "bolder", "color": "#c23531"}},
|
||||
"tooltip": {"show": True, "formatter": "{a} <br/>{b} : {c}"},
|
||||
"legend": {"show": True, "data": [], "orient": "horizontal",
|
||||
"textStyle": {"color": "#333", "fontSize": 12}},
|
||||
"series": [{"name": "数据", "type": "pie",
|
||||
"isRadius": True,
|
||||
"radius": ["45%", "55%"], # 内外半径 → 环形
|
||||
"minAngle": 0, "roseType": "", "isRose": False,
|
||||
"center": [320, 180],
|
||||
"label": {"show": True, "position": "outside"},
|
||||
"data": []}]
|
||||
}
|
||||
```
|
||||
|
||||
### mixed.linebar — 柱线混合图
|
||||
|
||||
```python
|
||||
{
|
||||
"chartType": "linebar",
|
||||
"title": {"show": True, "text": "标题",
|
||||
"textStyle": {"fontSize": 18, "fontWeight": "bolder", "color": "#c23531"}},
|
||||
"legend": {"data": []},
|
||||
"xAxis": {"type": "category", "data": []},
|
||||
"yAxis": [
|
||||
{"type": "value", "name": "左轴"},
|
||||
{"type": "value", "name": "右轴"}
|
||||
],
|
||||
"series": [
|
||||
{"name": "柱状", "type": "bar", "data": []},
|
||||
{"name": "折线", "type": "line", "data": []},
|
||||
{"name": "右轴数据", "type": "bar", "yAxisIndex": 1, "data": []}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### gauge.simple — 仪表盘
|
||||
|
||||
```python
|
||||
{
|
||||
"title": {"show": True, "text": "标题",
|
||||
"textStyle": {"fontSize": 18, "fontWeight": "bolder", "color": "#c23531"}},
|
||||
"tooltip": {"show": True, "formatter": "{b} : {c}"},
|
||||
"series": [{"name": "业务指标", "type": "gauge",
|
||||
"radius": "75%", "center": [330, 200],
|
||||
"itemStyle": {"color": "#63869E"},
|
||||
"pointer": {"show": True},
|
||||
"detail": {"formatter": "{value}%",
|
||||
"textStyle": {"color": "rgba(0,0,0,1)", "fontSize": 25}},
|
||||
"axisLine": {"lineStyle": {
|
||||
"color": [[0.2, "#91c7ae"], [0.8, "#63869E"], [1, "#C23531"]],
|
||||
"width": 25}},
|
||||
"data": [{"value": 50, "name": "完成率"}]}]
|
||||
}
|
||||
```
|
||||
|
||||
### funnel.simple — 漏斗图
|
||||
|
||||
```python
|
||||
{
|
||||
"title": {"show": True, "text": "标题",
|
||||
"textStyle": {"fontSize": 18, "fontWeight": "bolder", "color": "#c23531"}},
|
||||
"legend": {"show": True, "data": []},
|
||||
"tooltip": {"show": True, "trigger": "item", "formatter": "{b} : {c}"},
|
||||
"series": [{"name": "漏斗图", "type": "funnel",
|
||||
"left": "10%", "top": 60, "bottom": 60, "width": "80%",
|
||||
"sort": "descending", "gap": 2, "orient": "vertical",
|
||||
"label": {"show": True, "position": "inside",
|
||||
"textStyle": {"fontSize": 16}},
|
||||
"itemStyle": {"borderColor": "#fff", "borderWidth": 1},
|
||||
"data": []}]
|
||||
}
|
||||
```
|
||||
|
||||
### radar.basic — 雷达图
|
||||
|
||||
```python
|
||||
{
|
||||
"title": {"show": True, "text": "标题",
|
||||
"textStyle": {"fontSize": 18, "fontWeight": "bolder", "color": "#c23531"}},
|
||||
"legend": {"show": True, "data": []},
|
||||
"tooltip": {"show": True},
|
||||
"radar": [{"shape": "polygon", "center": [320, 200],
|
||||
"name": {"formatter": "【{value}】",
|
||||
"textStyle": {"fontSize": 14, "color": "#72ACD1"}},
|
||||
"indicator": [
|
||||
{"name": "维度1", "max": 100},
|
||||
{"name": "维度2", "max": 100},
|
||||
{"name": "维度3", "max": 100}
|
||||
]}],
|
||||
"series": [{"name": "", "type": "radar",
|
||||
"data": [{"value": [80, 60, 70], "name": "系列1"}]}]
|
||||
}
|
||||
```
|
||||
|
||||
## 数据集 SQL 映射规则
|
||||
|
||||
图表数据集使用固定的 `name`/`value`/`type` 字段映射:
|
||||
|
||||
```sql
|
||||
-- 单系列(bar.simple, line.simple, pie.simple 等)
|
||||
SELECT product_name AS name, sales_amount AS value, '' AS type
|
||||
FROM sales_table
|
||||
|
||||
-- 多系列(bar.multi, line.multi, mixed.linebar 等)
|
||||
SELECT month AS name, amount AS value, category AS type
|
||||
FROM sales_table
|
||||
|
||||
-- 仪表盘(gauge)
|
||||
SELECT '完成率' AS name, ROUND(done*100/total) AS value, '' AS type
|
||||
FROM task_summary
|
||||
|
||||
-- 漏斗图(funnel)
|
||||
SELECT stage AS name, count AS value, '' AS type
|
||||
FROM funnel_data ORDER BY count DESC
|
||||
```
|
||||
|
||||
## 快速生成图表的 Python 代码
|
||||
|
||||
```python
|
||||
def create_chart(chart_type, title, db_code, db_id, row_start, col_start,
|
||||
rows=10, cols=5, width="650", height="350"):
|
||||
"""快速生成图表配置"""
|
||||
layer_id = "chart_" + gen_id()
|
||||
|
||||
# 根据类型选择基础配置
|
||||
base_configs = {
|
||||
"bar.simple": {
|
||||
"title": {"text": title, "left": "center", "top": "10"},
|
||||
"tooltip": {"trigger": "axis", "axisPointer": {"type": "shadow"}},
|
||||
"grid": {"left": "3%", "right": "4%", "bottom": "3%", "containLabel": True},
|
||||
"xAxis": [{"type": "category", "data": []}],
|
||||
"yAxis": [{"type": "value"}],
|
||||
"series": [{"type": "bar", "data": [], "barWidth": "40%"}]
|
||||
},
|
||||
"pie.simple": {
|
||||
"title": {"text": title, "left": "center"},
|
||||
"tooltip": {"trigger": "item", "formatter": "{b}: {c} ({d}%)"},
|
||||
"legend": {"bottom": "5%", "left": "center"},
|
||||
"series": [{"type": "pie", "radius": "55%", "center": ["50%", "50%"], "data": []}]
|
||||
},
|
||||
"line.simple": {
|
||||
"title": {"text": title, "left": "center"},
|
||||
"tooltip": {"trigger": "axis"},
|
||||
"grid": {"left": "3%", "right": "4%", "bottom": "3%", "containLabel": True},
|
||||
"xAxis": [{"type": "category", "data": []}],
|
||||
"yAxis": [{"type": "value"}],
|
||||
"series": [{"type": "line", "smooth": True, "data": []}]
|
||||
},
|
||||
"gauge.simple": {
|
||||
"title": {"text": title, "left": "center"},
|
||||
"series": [{"type": "gauge", "radius": "75%",
|
||||
"detail": {"formatter": "{value}%"},
|
||||
"data": [{"value": 0, "name": ""}]}]
|
||||
}
|
||||
}
|
||||
config = base_configs.get(chart_type, base_configs["bar.simple"])
|
||||
|
||||
# virtual cells
|
||||
row_end = row_start + rows - 1
|
||||
col_end = col_start + cols - 1
|
||||
virtual_cells = [[r,c] for r in range(row_start, row_end+1) for c in range(col_start, col_end+1)]
|
||||
|
||||
# rows 占位
|
||||
chart_rows = {}
|
||||
for r in range(row_start, row_end + 1):
|
||||
cells = {}
|
||||
for c in range(col_start, col_end + 1):
|
||||
cells[str(c)] = {"text": " ", "virtual": layer_id}
|
||||
chart_rows[str(r)] = {"cells": cells}
|
||||
|
||||
chart_item = {
|
||||
"row": row_start, "col": col_start, "colspan": 0, "rowspan": 0,
|
||||
"width": width, "height": height,
|
||||
"config": json.dumps(config, ensure_ascii=False),
|
||||
"url": "",
|
||||
"extData": {
|
||||
"chartType": chart_type, "dataType": "sql",
|
||||
"dataId": str(db_id), "dbCode": db_code,
|
||||
"axisX": "name", "axisY": "value", "series": "type",
|
||||
"xText": "", "yText": "", "apiStatus": "1"
|
||||
},
|
||||
"layer_id": layer_id,
|
||||
"offsetX": 0, "offsetY": 0,
|
||||
"backgroud": {"enabled": False, "color": "#fff", "image": ""},
|
||||
"virtualCellRange": virtual_cells
|
||||
}
|
||||
|
||||
return chart_item, chart_rows
|
||||
```
|
||||
328
.trae/skills/jimureport/references/components.md
Normal file
328
.trae/skills/jimureport/references/components.md
Normal file
@@ -0,0 +1,328 @@
|
||||
# 报表组件参考
|
||||
|
||||
积木报表支持 4 种图层组件(Layer),它们以独立数组存储在 jsonStr 顶层,与 rows/cols 平级。
|
||||
|
||||
## 组件类型总览
|
||||
|
||||
| 组件 | jsonStr 字段 | 类型标识 | 用途 |
|
||||
|------|-------------|---------|------|
|
||||
| 图片 | `imgList` | `img` | 外部图片/Logo/背景图 |
|
||||
| 图表 | `chartList` | `chart` | ECharts 可视化图表 |
|
||||
| 条形码 | `barcodeList` | `barcode` | CODE128/EAN 等一维码 |
|
||||
| 二维码 | `qrcodeList` | `qrcode` | QR Code 二维码 |
|
||||
|
||||
所有组件共享以下基础属性:
|
||||
|
||||
```python
|
||||
{
|
||||
"row": 0, # 起始行号(从0开始)
|
||||
"col": 0, # 起始列号(从0开始)
|
||||
"colspan": 0, # 列跨度
|
||||
"rowspan": 0, # 行跨度
|
||||
"width": 300, # 宽度(像素,图表必须是字符串如"300")
|
||||
"height": 200, # 高度(像素,图表必须是字符串如"200")
|
||||
"layer_id": "唯一ID", # 唯一标识,对应 virtual cell
|
||||
"offsetX": 0, # X偏移
|
||||
"offsetY": 0, # Y偏移
|
||||
"virtualCellRange": [[row,col], ...] # 占据的所有单元格坐标
|
||||
}
|
||||
```
|
||||
|
||||
## 1. 图片组件 (imgList)
|
||||
|
||||
### JSON 结构
|
||||
|
||||
```python
|
||||
img_item = {
|
||||
"row": 0,
|
||||
"col": 1,
|
||||
"colspan": 0,
|
||||
"rowspan": 0,
|
||||
"width": 315, # 数字
|
||||
"height": 151, # 数字
|
||||
"src": "/jmreport/img/upload/xxx.png", # 图片路径(相对或绝对)
|
||||
"isBackend": False, # 是否作为前置遮罩
|
||||
"isBackendImg": False, # 是否作为背景图
|
||||
"commonBackend": None, # 共享遮罩/背景属性
|
||||
"layer_id": "img_xxx",
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"virtualCellRange": [[0,1],[0,2],[1,1],[1,2]]
|
||||
}
|
||||
```
|
||||
|
||||
### 图片路径说明
|
||||
|
||||
| 类型 | 示例 |
|
||||
|------|------|
|
||||
| 上传图片 | `/jmreport/img/upload/xxx.png` |
|
||||
| 外部URL | `https://example.com/logo.png` |
|
||||
| 数据绑定 | `${dbCode.imgField}` — 字段值为图片URL |
|
||||
|
||||
## 2. 图表组件 (chartList)
|
||||
|
||||
详见 `chart-config.md`,这里列出关键结构。
|
||||
|
||||
### JSON 结构
|
||||
|
||||
```python
|
||||
chart_item = {
|
||||
"row": 5,
|
||||
"col": 1,
|
||||
"colspan": 0,
|
||||
"rowspan": 0,
|
||||
"width": "650", # 必须是字符串!
|
||||
"height": "350", # 必须是字符串!
|
||||
"config": json.dumps(echarts_option), # ECharts 配置 JSON 字符串
|
||||
"url": "", # 外部数据 URL(通常为空)
|
||||
"extData": {
|
||||
"chartType": "bar.simple", # 图表类型
|
||||
"dataType": "sql", # 数据来源: "sql"/"api"/"json"/"javabean"/"files"
|
||||
"dataId": "数据集ID", # saveDb 返回的 id
|
||||
"dbCode": "数据集编码", # 数据集编码
|
||||
"axisX": "name", # 固定值
|
||||
"axisY": "value", # 固定值
|
||||
"series": "type", # 固定值
|
||||
"xText": "",
|
||||
"yText": "",
|
||||
"apiStatus": "1"
|
||||
},
|
||||
"layer_id": "chart_xxx",
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"backgroud": {"enabled": False, "color": "#fff", "image": ""},
|
||||
"virtualCellRange": [[5,1],[5,2],...]
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 条形码组件 (barcodeList)
|
||||
|
||||
### JSON 结构
|
||||
|
||||
```python
|
||||
barcode_item = {
|
||||
"row": 3,
|
||||
"col": 0,
|
||||
"colspan": 0,
|
||||
"rowspan": 0,
|
||||
"width": 300,
|
||||
"height": 200,
|
||||
"layer_id": "barcode_xxx",
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"jsonString": json.dumps({
|
||||
"barcodeContent": "jmreport", # 条码内容(支持 ${dbCode.field} 动态绑定)
|
||||
"format": "CODE128", # 条码格式
|
||||
"width": 2, # 条线宽度
|
||||
"height": 100, # 条码高度
|
||||
"displayValue": False, # 是否显示文字
|
||||
"text": "jmreport", # 显示文字内容
|
||||
"fontOptions": "", # 字体选项(bold/italic)
|
||||
"font": "monospace", # 字体
|
||||
"textAlign": "center", # 文字对齐
|
||||
"textPosition": "bottom", # 文字位置
|
||||
"textMargin": 2, # 文字间距
|
||||
"fontSize": 20, # 字体大小
|
||||
"background": "#fff", # 背景色
|
||||
"lineColor": "#000", # 条线颜色
|
||||
"margin": 10 # 边距
|
||||
}),
|
||||
"virtualCellRange": [[3,0],[3,1],[4,0],[4,1]]
|
||||
}
|
||||
```
|
||||
|
||||
### 支持的条码格式
|
||||
|
||||
| format | 说明 |
|
||||
|--------|------|
|
||||
| `CODE128` | Code 128(默认,最常用) |
|
||||
| `CODE39` | Code 39 |
|
||||
| `EAN13` | EAN-13 |
|
||||
| `EAN8` | EAN-8 |
|
||||
| `UPC` | UPC-A |
|
||||
| `ITF14` | ITF-14 |
|
||||
|
||||
### 动态数据绑定
|
||||
|
||||
条码内容支持表达式:`${dbCode.fieldName}`,运行时替换为数据集字段值。
|
||||
|
||||
## 4. 二维码组件 (qrcodeList)
|
||||
|
||||
### JSON 结构
|
||||
|
||||
```python
|
||||
qrcode_item = {
|
||||
"row": 5,
|
||||
"col": 0,
|
||||
"colspan": 0,
|
||||
"rowspan": 0,
|
||||
"width": 128,
|
||||
"height": 128,
|
||||
"layer_id": "qrcode_xxx",
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"jsonString": json.dumps({
|
||||
"text": "http://jimureport.com/", # 二维码内容(支持 ${dbCode.field})
|
||||
"width": 128,
|
||||
"height": 128,
|
||||
"colorDark": "#000000", # 前景色
|
||||
"colorLight": "#ffffff" # 背景色
|
||||
}),
|
||||
"virtualCellRange": [[5,0],[5,1],[6,0],[6,1]]
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 单元格内嵌组件 (displayConfig)
|
||||
|
||||
除了图层组件,单元格本身也可以通过 `display` 属性渲染为特殊组件:
|
||||
|
||||
```python
|
||||
# 在 rows 的 cell 中设置
|
||||
cell = {
|
||||
"text": "#{dbCode.imgUrl}",
|
||||
"display": "img" # 渲染为图片
|
||||
}
|
||||
|
||||
# 或
|
||||
cell = {
|
||||
"text": "#{dbCode.code}",
|
||||
"display": "barcode" # 渲染为条形码
|
||||
}
|
||||
|
||||
# 或
|
||||
cell = {
|
||||
"text": "#{dbCode.url}",
|
||||
"display": "qrcode" # 渲染为二维码
|
||||
}
|
||||
```
|
||||
|
||||
**display 可选值:**
|
||||
|
||||
| 值 | 说明 |
|
||||
|----|------|
|
||||
| `normal` | 普通文本(默认) |
|
||||
| `img` | 图片(text 为图片 URL) |
|
||||
| `barcode` | 条形码(text 为条码内容) |
|
||||
| `qrcode` | 二维码(text 为二维码内容) |
|
||||
| `base64Img` | Base64 图片 |
|
||||
| `richText` | 富文本/HTML |
|
||||
|
||||
## Virtual Cell 占位规则
|
||||
|
||||
所有图层组件都需要在 `rows` 中声明 virtual 占位:
|
||||
|
||||
```python
|
||||
# 1. 确定组件占据的行列范围
|
||||
row_start, row_end = 5, 8
|
||||
col_start, col_end = 1, 4
|
||||
layer_id = "chart_xxx"
|
||||
|
||||
# 2. 构造 virtualCellRange
|
||||
virtual_cells = []
|
||||
for r in range(row_start, row_end + 1):
|
||||
for c in range(col_start, col_end + 1):
|
||||
virtual_cells.append([r, c])
|
||||
|
||||
# 3. 在 rows 中添加 virtual 占位
|
||||
for r in range(row_start, row_end + 1):
|
||||
cells = {}
|
||||
for c in range(col_start, col_end + 1):
|
||||
cells[str(c)] = {"text": " ", "virtual": layer_id}
|
||||
rows_data[str(r)] = {"cells": cells}
|
||||
|
||||
# 4. 组件中设置 virtualCellRange
|
||||
component["virtualCellRange"] = virtual_cells
|
||||
```
|
||||
|
||||
**注意事项:**
|
||||
- `virtual` 值必须和组件的 `layer_id` 一致
|
||||
- `text` 必须为 `" "`(一个空格),不能为空
|
||||
- 组件区域不能和数据绑定行重叠
|
||||
- 一个组件的 virtual cells 不能和其他组件重叠
|
||||
|
||||
## Python 构造完整示例
|
||||
|
||||
```python
|
||||
# 构造一个包含 表格 + 柱状图 + 二维码 的报表
|
||||
|
||||
layer_chart_id = "chart_" + gen_id()
|
||||
layer_qr_id = "qrcode_" + gen_id()
|
||||
|
||||
rows_data = {
|
||||
# 标题行
|
||||
"1": {"cells": {"1": {"text": "销售报表", "style": 5}}, "height": 40},
|
||||
# 表头
|
||||
"2": {"cells": {
|
||||
"1": {"text": "产品", "style": 4},
|
||||
"2": {"text": "销量", "style": 4},
|
||||
"3": {"text": "金额", "style": 4}
|
||||
}, "height": 34},
|
||||
# 数据行
|
||||
"3": {"cells": {
|
||||
"1": {"text": "#{ds.name}", "style": 2},
|
||||
"2": {"text": "#{ds.qty}", "style": 2},
|
||||
"3": {"text": "#{ds.amount}", "style": 2}
|
||||
}},
|
||||
# 空行
|
||||
"4": {"cells": {}, "height": 15},
|
||||
"len": 200
|
||||
}
|
||||
|
||||
# 柱状图占位 (row 5-12, col 1-5)
|
||||
for r in range(5, 13):
|
||||
cells = {}
|
||||
for c in range(1, 6):
|
||||
cells[str(c)] = {"text": " ", "virtual": layer_chart_id}
|
||||
rows_data[str(r)] = {"cells": cells}
|
||||
|
||||
# 二维码占位 (row 5-8, col 6-7)
|
||||
for r in range(5, 9):
|
||||
cells = rows_data.get(str(r), {"cells": {}})["cells"]
|
||||
for c in range(6, 8):
|
||||
cells[str(c)] = {"text": " ", "virtual": layer_qr_id}
|
||||
rows_data[str(r)] = {"cells": cells}
|
||||
|
||||
# 柱状图
|
||||
chart_config = {
|
||||
"title": {"text": "销量统计", "left": "center"},
|
||||
"tooltip": {"trigger": "axis"},
|
||||
"xAxis": [{"type": "category", "data": []}],
|
||||
"yAxis": [{"type": "value"}],
|
||||
"series": [{"type": "bar", "data": [], "itemStyle": {"color": "#01b0f1"}}]
|
||||
}
|
||||
|
||||
chart_list = [{
|
||||
"row": 5, "col": 1, "colspan": 0, "rowspan": 0,
|
||||
"width": "500", "height": "300",
|
||||
"config": json.dumps(chart_config, ensure_ascii=False),
|
||||
"url": "",
|
||||
"extData": {
|
||||
"chartType": "bar.simple", "dataType": "sql",
|
||||
"dataId": chart_db_id, "dbCode": "saleschart",
|
||||
"axisX": "name", "axisY": "value", "series": "type",
|
||||
"xText": "", "yText": "", "apiStatus": "1"
|
||||
},
|
||||
"layer_id": layer_chart_id,
|
||||
"offsetX": 0, "offsetY": 0,
|
||||
"backgroud": {"enabled": False, "color": "#fff", "image": ""},
|
||||
"virtualCellRange": [[r,c] for r in range(5,13) for c in range(1,6)]
|
||||
}]
|
||||
|
||||
# 二维码
|
||||
qrcode_list = [{
|
||||
"row": 5, "col": 6, "colspan": 0, "rowspan": 0,
|
||||
"width": 128, "height": 128,
|
||||
"layer_id": layer_qr_id,
|
||||
"offsetX": 0, "offsetY": 0,
|
||||
"jsonString": json.dumps({"text": "https://example.com", "width": 128, "height": 128, "colorDark": "#000000", "colorLight": "#ffffff"}),
|
||||
"virtualCellRange": [[r,c] for r in range(5,9) for c in range(6,8)]
|
||||
}]
|
||||
|
||||
# 最终保存数据中包含
|
||||
save_data = {
|
||||
# ... 其他字段
|
||||
"chartList": chart_list,
|
||||
"qrcodeList": qrcode_list,
|
||||
# imgList 和 barcodeList 为空时可省略或传 []
|
||||
}
|
||||
```
|
||||
170
.trae/skills/jimureport/references/constraints.md
Normal file
170
.trae/skills/jimureport/references/constraints.md
Normal file
@@ -0,0 +1,170 @@
|
||||
# 积木报表重要约束
|
||||
|
||||
1. **数据集编码 `db_code` 不能重复且只支持英文字符** — 同一个报表内的多个数据集,每个的 `db_code` 必须唯一。编码只能使用英文字母、数字和下划线,不能包含中文或特殊字符。重复会导致数据覆盖或查询异常。
|
||||
2. **`is_page` 分页只能有一个** — 一个报表中只能有一个数据集设置 `is_page=1`(启用分页),其余数据集必须为 `is_page=0`。多个数据集同时分页会导致分页冲突。
|
||||
|
||||
## 数据绑定语法
|
||||
|
||||
| 语法 | 说明 | 场景 |
|
||||
|------|------|------|
|
||||
| `${db.field}` | 单值绑定 | 主表字段、固定值 |
|
||||
| `#{db.field}` | 列表绑定 | 明细行、循环数据 |
|
||||
| `#{db.group(field)}` | 纵向分组 | 按字段分组汇总 |
|
||||
| `#{db.groupRight(field)}` | 横向分组 | 按字段横向展开 |
|
||||
| `#{db.dynamic(field)}` | 动态聚合 | 交叉表数据 |
|
||||
| `#{db.customGroup(field)}` | 自定义分组 | 横向自定义展开 |
|
||||
| `=SUM(D7)` | Excel 公式 | 列汇总 |
|
||||
|
||||
## 单元格属性
|
||||
|
||||
| 属性 | 说明 | 值 |
|
||||
|------|------|-----|
|
||||
| `merge` | 合并 | `[行数,列数]`,如 `[0,2]` 向右合并2列 |
|
||||
| `style` | 样式索引 | 引用 `styles` 数组下标 |
|
||||
| `loopBlock` | 循环块标记 | 1=属于循环块 |
|
||||
| `zonedEdition` | 分版标记 | 1/2/... 分版编号 |
|
||||
| `fixedHead` | 固定表头 | 1=固定 |
|
||||
| `fixedTail` | 固定表尾 | 1=固定 |
|
||||
| `aggregate` | 聚合类型 | 见下方分组配置 |
|
||||
| `subtotal` | 小计配置 | 见下方分组配置 |
|
||||
| `funcname` | 聚合函数 | 见下方分组配置 |
|
||||
| `subtotalText` | 小计行文本 | `"合计"` / `"小计"` |
|
||||
| `direction` | 展开方向 | 见下方分组配置 |
|
||||
| `sort` | 排序 | 见下方分组配置 |
|
||||
| `rendered` | 渲染标记 | `""` |
|
||||
| `config` | 配置标记 | `""` |
|
||||
| `decimalPlaces` | 小数位 | `"0"`/`"1"`/`"4"` |
|
||||
| `display` | 显示格式 | 见下方 display 值表 |
|
||||
| `fillForm` | 填报组件 | 组件配置对象 |
|
||||
|
||||
## 分组相关配置
|
||||
|
||||
### aggregate 聚合方式(polyWayList)
|
||||
|
||||
| 值 | 说明 |
|
||||
|-----|------|
|
||||
| `select` | 列表(普通列,不分组) |
|
||||
| `group` | 分组(相同值合并单元格) |
|
||||
|
||||
### subtotal 是否启用小计
|
||||
|
||||
| 值 | 说明 |
|
||||
|-----|------|
|
||||
| `"-1"` | 否(不显示小计行) |
|
||||
| `"groupField"` | 是(分组切换时显示小计/合计行) |
|
||||
|
||||
### funcname 聚合函数(aggregateList)
|
||||
|
||||
| 值 | 说明 | 用于 |
|
||||
|-----|------|------|
|
||||
| `"-1"` | 无(不计算,仅显示 subtotalText 文本) | 分组字段(地区、销售员等) |
|
||||
| `"SUM"` | 求和 | 数值字段(金额、数量等) |
|
||||
| `"MAX"` | 最大值 | 数值字段 |
|
||||
| `"MIN"` | 最小值 | 数值字段 |
|
||||
| `"AVERAGE"` | 平均值 | 数值字段 |
|
||||
| `"COUNT"` | 计数 | 任意字段 |
|
||||
|
||||
### direction 展开方向(directionList)
|
||||
|
||||
| 值 | 说明 |
|
||||
|-----|------|
|
||||
| `"down"` | 纵向(默认) |
|
||||
| `"right"` | 横向 |
|
||||
|
||||
### sort 排序(sortType)
|
||||
|
||||
| 值 | 说明 |
|
||||
|-----|------|
|
||||
| `"default"` | 默认(不排序) |
|
||||
| `"asc"` | 正序 |
|
||||
| `"desc"` | 倒序 |
|
||||
|
||||
### aggregate 高级模式(advancedList)
|
||||
|
||||
| 值 | 说明 |
|
||||
|-----|------|
|
||||
| `"default"` | 普通属性 |
|
||||
| `"dynamic"` | 动态属性(交叉表) |
|
||||
|
||||
### 分组单元格配置示例
|
||||
|
||||
**分组字段(地区,一级分组 — 合计):**
|
||||
```json
|
||||
{
|
||||
"text": "#{sales.group(region)}",
|
||||
"style": 4,
|
||||
"aggregate": "group",
|
||||
"subtotal": "groupField",
|
||||
"funcname": "-1",
|
||||
"subtotalText": "合计",
|
||||
"rendered": "",
|
||||
"config": ""
|
||||
}
|
||||
```
|
||||
|
||||
**分组字段(销售员,二级分组 — 小计):**
|
||||
```json
|
||||
{
|
||||
"text": "#{sales.group(salesman)}",
|
||||
"style": 4,
|
||||
"aggregate": "group",
|
||||
"subtotal": "groupField",
|
||||
"funcname": "-1",
|
||||
"subtotalText": "小计"
|
||||
}
|
||||
```
|
||||
|
||||
**数值字段(销售额,小计/合计行自动求和):**
|
||||
```json
|
||||
{
|
||||
"text": "#{sales.amount}",
|
||||
"style": 4,
|
||||
"funcname": "SUM",
|
||||
"subtotal": "-1",
|
||||
"display": "number",
|
||||
"rendered": "",
|
||||
"config": ""
|
||||
}
|
||||
```
|
||||
|
||||
**注意:** 数值字段的 `subtotal` 为 `"-1"`(不是 `"groupField"`),`funcname` 为 `"SUM"`。含义是:该字段不触发分组切换,但在分组切换产生的小计/合计行中自动按 SUM 聚合。
|
||||
|
||||
## display 显示格式(JmConst.CELL_FORMAT_*)
|
||||
|
||||
| display 值 | 说明 | 示例 |
|
||||
|------------|------|------|
|
||||
| `normal` | 默认文本 | — |
|
||||
| `number` | 数值(数值类型字段默认) | 58000 |
|
||||
| `percent` | 百分比 | 85% |
|
||||
| `rmb` | 人民币 | ¥58,000.00 |
|
||||
| `usd` | 美元 | $58,000.00 |
|
||||
| `eur` | 欧元 | €58,000.00 |
|
||||
| `date` | 日期 | 2026-03-20 |
|
||||
| `date2` | 日期(斜杠) | 2026/03/20 |
|
||||
| `time` | 时间 | 12:30:00 |
|
||||
| `datetime` | 日期时间 | 2026-03-20 12:30:00 |
|
||||
| `year` | 年 | 2026 |
|
||||
| `month` | 月 | 03 |
|
||||
| `base64Img` | Base64图片 | — |
|
||||
| `img` | 图片 | — |
|
||||
| `qrcode` | 二维码 | — |
|
||||
| `barcode` | 条形码 | — |
|
||||
| `richText` | 富文本 | — |
|
||||
|
||||
## 顶层配置
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `loopBlockList` | 循环块定义(含 `loopTime` 分栏次数) |
|
||||
| `zonedEditionList` | 分版区域定义 |
|
||||
| `fixedPrintHeadRows` | 固定打印表头 |
|
||||
| `fixedPrintTailRows` | 固定打印表尾 |
|
||||
| `groupField` | 分组字段 |
|
||||
| `isGroup` | 是否启用分组 |
|
||||
| `submitHandlers` | 填报提交处理器 |
|
||||
| `background` | 背景图配置 |
|
||||
| `imgList` | 图片列表 |
|
||||
| `displayConfig` | 二维码/条码显示配置 |
|
||||
| `dicts` | 引用的字典编码列表 |
|
||||
| `printConfig` | 打印配置(纸张/方向/边距) |
|
||||
| `merges` | 合并单元格列表(如 `"B1:H1"`) |
|
||||
377
.trae/skills/jimureport/references/dataset-skills.md
Normal file
377
.trae/skills/jimureport/references/dataset-skills.md
Normal file
@@ -0,0 +1,377 @@
|
||||
# 数据集的用法
|
||||
|
||||
所有接口均需要提供token
|
||||
|
||||
## 数据源管理
|
||||
|
||||
数据集默认使用应用本身的数据库。如需连接外部数据库,需要先添加数据源,再在数据集中通过 `dbSource` 关联。
|
||||
|
||||
### 查询已有数据源
|
||||
|
||||
- **地址**:`GET /jmreport/getDataSourceByPage`
|
||||
- **返回**:所有数据源列表,每个包含 `id` 和 `name`
|
||||
|
||||
```python
|
||||
ds_resp = api_request('/jmreport/getDataSourceByPage')
|
||||
ds_list = ds_resp.get('result', [])
|
||||
# 按名称查找数据源 ID
|
||||
ds_map = {ds['name']: ds['id'] for ds in ds_list}
|
||||
# 示例: ds_map['ws_mysql'] -> '1010703895600087040'
|
||||
```
|
||||
|
||||
### 添加/编辑数据源
|
||||
|
||||
- **地址**:`POST /jmreport/addDataSource`
|
||||
- **新增不传 id,编辑传 id**
|
||||
- **请求参数**:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "",
|
||||
"reportId": "报表ID",
|
||||
"code": "",
|
||||
"name": "mysql",
|
||||
"dbType": "MYSQL5.7",
|
||||
"dbDriver": "com.mysql.cj.jdbc.Driver",
|
||||
"dbUrl": "jdbc:mysql://127.0.0.1:3306/jimureport?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&tinyInt1isBit=false",
|
||||
"dbUsername": "root",
|
||||
"dbPassword": "123456"
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `id` | 数据源ID,新增不传,编辑传已有ID |
|
||||
| `reportId` | 关联的报表ID |
|
||||
| `name` | 数据源名称 |
|
||||
| `dbType` | 数据库类型(如 `MYSQL5.7`、`ORACLE`、`POSTGRESQL` 等) |
|
||||
| `dbDriver` | JDBC驱动类名 |
|
||||
| `dbUrl` | JDBC连接URL |
|
||||
| `dbUsername` | 数据库用户名 |
|
||||
| `dbPassword` | 数据库密码 |
|
||||
|
||||
### 数据源与数据集的关联
|
||||
|
||||
数据集通过 `dbSource` 字段关联数据源,值为数据源的 `id`:
|
||||
|
||||
```json
|
||||
{
|
||||
"dbCode": "aa",
|
||||
"dbType": "0",
|
||||
"dbDynSql": "select name from demo",
|
||||
"dbSource": "1010703895600087040",
|
||||
"dbSourceType": "mysql"
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `dbSource` | 数据源ID(`addDataSource` 返回或编辑时传入的 `id`)。空字符串 `""` = 使用默认数据源 |
|
||||
| `dbSourceType` | 数据库类型(`mysql`、`oracle` 等),后端会自动识别,也可手动指定 |
|
||||
|
||||
**注意:** 如果开启了数据源安全模式(`firewall.dataSourceSafe: true`),SQL 数据集**必须**指定 `dbSource`,不允许使用默认数据源。
|
||||
|
||||
## SQL数据集
|
||||
|
||||
### SQL语句用法
|
||||
|
||||
- 如果id字段为字符串类型则需要加单引号:`select * from table where id='${id}'`
|
||||
- 您可以编写`${id}`做为一个参数,这里id是参数的名称。例如:`select * from table where id='${id}'`
|
||||
- 您可以编写`#{sysUserCode}`做为一个系统变量,这里sysUserCode是当前登录人。例如:`select * from table where create_by='#{sysUserCode}'`
|
||||
- 您可以编写存储过程`CALL proc_sys_role(${pageNo}, ${pageSize})`,CALL为开启存储过程
|
||||
- MongoDB和Elasticsearch支持sql语句,表名需要增加数据库标识(MongoDB:mongo,Elasticsearch:es)。例如:`select * from mongo.table`
|
||||
|
||||
### SQL解析接口
|
||||
|
||||
- **地址**:`POST /jmreport/queryFieldBySql`
|
||||
- **请求参数**:
|
||||
```json
|
||||
{
|
||||
"sql": "select * from test_order_product where order_fk_id = ${order_fk_id}",
|
||||
"type": "0",
|
||||
"paramArray": "[{\"id\":\"1047395275108601856\",\"jimuReportHeadId\":\"1047395274039054336\",\"paramName\":\"order_fk_id\",\"paramTxt\":\"order_fk_id\",\"paramValue\":\"1\",\"orderNum\":1,\"createBy\":\"admin\",\"createTime\":\"2025-02-06 16:21:43\",\"updateBy\":null,\"updateTime\":null,\"searchFlag\":0,\"widgetType\":null,\"searchMode\":null,\"dictCode\":null,\"searchFormat\":null,\"extJson\":\"\",\"tableIndex\":1,\"_index\":0,\"_rowKey\":105}]"
|
||||
}
|
||||
```
|
||||
- **返回结果**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "解析成功",
|
||||
"code": 200,
|
||||
"result": {
|
||||
"paramList": [],
|
||||
"fieldList": [
|
||||
{
|
||||
"fieldName": "id",
|
||||
"fieldText": "id",
|
||||
"widgetType": "String",
|
||||
"orderNum": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API数据集
|
||||
|
||||
### API数据集用法
|
||||
|
||||
- 如果id字段为字符串类型则需要加单引号:`http://127.0.0.1:8080/jeecg-boot/jimureport/test?id=${id}`
|
||||
- 您可以编写`#{sysDateTime}`做为一个系统变量,这里sysDateTime是当前系统时间。例如:`http://127.0.0.1:8080/jeecg-boot/jimureport/test?riqi=#{sysDateTime}`
|
||||
- 您可以简写访问路径,如:`#{domainURL}/jimureport/test/getList`
|
||||
|
||||
### API解析接口
|
||||
|
||||
- **地址**:`POST /jmreport/executeSelectApi`
|
||||
- **请求参数**:
|
||||
```json
|
||||
{
|
||||
"api": "http://localhost:8085/jimureport/test/getList?pid=&name=",
|
||||
"method": "0",
|
||||
"paramArray": "[{\"id\":\"1066517440772788224\",\"jimuReportHeadId\":\"1066517440441438208\",\"paramName\":\"pid\",\"paramTxt\":null,\"paramValue\":\"\",\"orderNum\":1,\"createBy\":\"admin\",\"createTime\":\"2025-03-31 10:46:23\",\"updateBy\":null,\"updateTime\":null,\"searchFlag\":0,\"widgetType\":null,\"searchMode\":null,\"dictCode\":\"\",\"searchFormat\":null,\"extJson\":\"\",\"tableIndex\":1,\"_index\":0,\"_rowKey\":231}]"
|
||||
}
|
||||
```
|
||||
- **返回结果**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "",
|
||||
"code": 200,
|
||||
"result": [
|
||||
{
|
||||
"fieldName": "ctotal",
|
||||
"fieldText": "ctotal",
|
||||
"widgetType": "String",
|
||||
"isShow": true,
|
||||
"orderNum": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## JSON数据集
|
||||
|
||||
自动将data中的字段解析成fieldName/fieldText/widgetType格式。
|
||||
|
||||
- **输入格式**:
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"ctotal": "125箱",
|
||||
"cname": "牛奶0",
|
||||
"cprice": "56",
|
||||
"riqi": "2022年10月21日",
|
||||
"id": "1",
|
||||
"dtotal": "1256箱",
|
||||
"tp": "7000",
|
||||
"ztotal": "589箱",
|
||||
"cnum": "每箱12瓶"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## JavaBean数据集
|
||||
|
||||
- **地址**:`POST /jmreport/queryFieldByBean`
|
||||
- **请求参数**:
|
||||
```json
|
||||
{
|
||||
"javaType": "spring-key",
|
||||
"javaValue": "testRpSpringBean",
|
||||
"isPage": false,
|
||||
"param": {}
|
||||
}
|
||||
```
|
||||
- **返回结果**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "解析成功",
|
||||
"code": 200,
|
||||
"result": [
|
||||
{
|
||||
"fieldName": "name",
|
||||
"fieldText": "name",
|
||||
"widgetType": "String",
|
||||
"orderNum": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 查询已有数据集
|
||||
|
||||
更新数据集前必须先查到已有数据集的 `dbId`,流程如下:
|
||||
|
||||
### Step 1: 获取数据集列表(含 dbId)
|
||||
|
||||
- **地址**:`GET /jmreport/field/tree/{reportId}`
|
||||
- **返回结构**:
|
||||
|
||||
```json
|
||||
{
|
||||
"result": [
|
||||
[
|
||||
{
|
||||
"code": "userlist",
|
||||
"dbId": "1194900477760331776",
|
||||
"title": "用户列表",
|
||||
"type": "0",
|
||||
"isList": "1",
|
||||
"izSharedSource": 0,
|
||||
"children": [
|
||||
{"title": "username", "fieldText": "username"},
|
||||
{"title": "realname", "fieldText": "realname"}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**提取 dbCode → dbId 映射:**
|
||||
```python
|
||||
tree = api_request(f'/jmreport/field/tree/{report_id}')
|
||||
db_map = {} # dbCode -> dbId
|
||||
for group in tree.get('result', []):
|
||||
if group and len(group) > 0:
|
||||
info = group[0]
|
||||
db_map[info['code']] = info['dbId']
|
||||
```
|
||||
|
||||
### Step 2: 获取单个数据集详情
|
||||
|
||||
- **地址**:`GET /jmreport/loadDbData/{dbId}?reportId={reportId}`
|
||||
- **返回结构**:`result` 包含三个顶层字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"dbId": "数据集ID",
|
||||
"reportDb": {
|
||||
"id": "数据集ID",
|
||||
"dbCode": "userlist",
|
||||
"dbDynSql": "SELECT ... FROM ...",
|
||||
"dbSource": "1010703895600087040",
|
||||
"dbSourceType": "mysql",
|
||||
"isPage": "1",
|
||||
"isList": "1"
|
||||
},
|
||||
"fieldList": [...],
|
||||
"paramList": [...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **注意:`dbDynSql` 和 `dbSource` 在 `result.reportDb` 中,不在 `result` 顶层。**
|
||||
|
||||
```python
|
||||
detail = api_request(f'/jmreport/loadDbData/{db_id}?reportId={report_id}').get('result', {})
|
||||
report_db = detail.get('reportDb', {})
|
||||
existing_sql = report_db.get('dbDynSql', '')
|
||||
existing_db_source = report_db.get('dbSource', '')
|
||||
existing_fields = detail.get('fieldList', [])
|
||||
existing_params = detail.get('paramList', [])
|
||||
```
|
||||
|
||||
### Step 3: 查询参数列表
|
||||
|
||||
- **地址**:`GET /jmreport/getListReportDb?reportId={reportId}`
|
||||
- **返回**:每个 dbCode 对应的参数列表
|
||||
|
||||
```json
|
||||
{"result": {"reportDbParam": {"userlist": [{"paramName": "username", ...}], "userpie": []}}}
|
||||
```
|
||||
|
||||
## 保存或修改数据集
|
||||
|
||||
**新增不传 id,更新必须传 id。** 后端 `saveOrUpdate` 逻辑:有 id 则更新,无 id 则新增。
|
||||
|
||||
- **地址**:`POST /jmreport/saveDb`
|
||||
- **请求参数**:
|
||||
```json
|
||||
{
|
||||
"id": "1193767090018410496",
|
||||
"izSharedSource": 0,
|
||||
"jimuReportId": "1193766682428530688",
|
||||
"dbCode": "aa",
|
||||
"dbChName": "aa",
|
||||
"dbType": "0",
|
||||
"dbSource": "",
|
||||
"jsonData": "",
|
||||
"apiConvert": "",
|
||||
"jimuSharedSourceId": null,
|
||||
"isList": "1",
|
||||
"isPage": "1",
|
||||
"dbDynSql": "select * from demo",
|
||||
"fieldList": [
|
||||
{
|
||||
"id": "1193767090198765568",
|
||||
"jimuReportDbId": "1193767090018410496",
|
||||
"fieldName": "id",
|
||||
"fieldText": "id",
|
||||
"widgetType": "String",
|
||||
"widgetWidth": null,
|
||||
"orderNum": 0,
|
||||
"searchFlag": null,
|
||||
"searchMode": null,
|
||||
"searchValue": null,
|
||||
"dictCode": "",
|
||||
"createBy": "admin",
|
||||
"createTime": "2026-03-17 14:11:04",
|
||||
"updateBy": null,
|
||||
"updateTime": null,
|
||||
"searchFormat": null,
|
||||
"extJson": "",
|
||||
"fieldNamePhysics": null,
|
||||
"tableIndex": 1,
|
||||
"_index": 0,
|
||||
"_rowKey": 44
|
||||
}
|
||||
],
|
||||
"paramList": []
|
||||
}
|
||||
```
|
||||
- **返回结果**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "",
|
||||
"code": 200,
|
||||
"result": {
|
||||
"id": "1193767090018410496",
|
||||
"jimuReportId": "1193766682428530688",
|
||||
"dbCode": "aa",
|
||||
"dbChName": "aa",
|
||||
"dbType": "0",
|
||||
"dbDynSql": "select * from demo",
|
||||
"fieldList": [],
|
||||
"paramList": [],
|
||||
"isPage": "1",
|
||||
"isList": "1",
|
||||
"dbSource": "",
|
||||
"dbSourceType": "mysql",
|
||||
"createBy": "admin",
|
||||
"updateBy": "admin",
|
||||
"createTime": "2026-03-19 17:12:34",
|
||||
"updateTime": "2026-03-19 17:12:34",
|
||||
"apiConvert": "",
|
||||
"izSharedSource": 0,
|
||||
"jimuSharedSourceId": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### dbType 值说明
|
||||
|
||||
| dbType | 类型 | 关键字段 |
|
||||
|--------|------|----------|
|
||||
| `"0"` | SQL数据集 | `dbDynSql` |
|
||||
| `"1"` | API数据集 | `apiUrl` + `apiMethod` |
|
||||
| `"2"` | JavaBean数据集 | `javaType` + `javaValue` |
|
||||
| `"3"` | JSON数据集 | `jsonData` |
|
||||
| `"4"` | 共享数据集 | — |
|
||||
| `"5"` | 多文件数据集 | — |
|
||||
| `"6"` | 单文件数据集 | — |
|
||||
369
.trae/skills/jimureport/references/query-config.md
Normal file
369
.trae/skills/jimureport/references/query-config.md
Normal file
@@ -0,0 +1,369 @@
|
||||
# 报表查询配置完整指南
|
||||
|
||||
## 1. 报表参数配置
|
||||
|
||||
### 参数语法
|
||||
|
||||
| 语法 | 类型 | 说明 | 示例 |
|
||||
|------|------|------|------|
|
||||
| `${paramName}` | 用户参数 | 需要在报表参数中声明,用户可查询输入 | `${id}` `${name}` |
|
||||
| `#{sysVar}` | 系统变量 | 无需声明,自动解析 | `#{sysUserCode}` `#{sysDate}` |
|
||||
|
||||
**注意:** `$` 或 `#` 与 `{` 之间不能有空格。
|
||||
|
||||
### SQL 参数示例
|
||||
|
||||
```sql
|
||||
select * from sys_user where id='${id}' and sex='${sex}' and create_by='#{sysUserCode}'
|
||||
```
|
||||
|
||||
### API 参数示例
|
||||
|
||||
```
|
||||
http://192.168.1.116/jmreport/test/getMessage?name='${name}'&createBy='#{sysUserCode}'
|
||||
```
|
||||
|
||||
### 系统变量列表
|
||||
|
||||
| 变量 | 说明 |
|
||||
|------|------|
|
||||
| `#{sysUserCode}` | 当前登录用户名 |
|
||||
| `#{sysDate}` | 当前系统日期 |
|
||||
| `#{sysDateTime}` | 当前系统日期时间 |
|
||||
| `#{domainURL}` | 系统域名地址 |
|
||||
|
||||
### 参数优先级(高→低)
|
||||
|
||||
1. **查询条件值**(用户在查询栏输入的)
|
||||
2. **URL参数**(通过URL传递的)
|
||||
3. **默认值**(配置的默认值)
|
||||
|
||||
### 参数合并规则
|
||||
|
||||
- 多个数据集中**同名参数**会合并为一个查询控件
|
||||
- 同名**数据集字段**不会合并
|
||||
- URL参数会传递给所有匹配的数据集参数
|
||||
|
||||
## 2. 查询控件类型
|
||||
|
||||
在数据集字段详情中勾选"查询"复选框,即可生成查询控件。
|
||||
|
||||
| 控件类型 | 查询模式值 | 说明 |
|
||||
|---------|-----------|------|
|
||||
| 文本输入 | 空或"输入框" | 默认类型 |
|
||||
| 下拉单选 | "下拉单选" | 可搜索,默认显示10条 |
|
||||
| 下拉多选 | "下拉多选" | 可搜索,默认显示10条 |
|
||||
| 范围查询 | "范围查询" | 日期/数值范围;**报表参数不支持** |
|
||||
| 模糊查询 | "模糊查询" | **报表参数不支持** |
|
||||
| 下拉树 | 通过配置实现 | 层级树形结构 |
|
||||
| 自定义下拉 | JS增强实现 | 数据需含 `value` 和 `text` 字段 |
|
||||
|
||||
### 下拉数据源配置
|
||||
|
||||
**方式一:系统字典**
|
||||
- 配置字典编码(如 `sex`)
|
||||
|
||||
**方式二:SQL字典**
|
||||
```sql
|
||||
SELECT username AS value, realname AS text FROM sys_user
|
||||
```
|
||||
必须别名为 `value` 和 `text`。
|
||||
|
||||
**方式三:API**
|
||||
- 相对路径:`/jmreport/test/getDictSex?createBy=#{sysUserCode}`
|
||||
- 绝对路径:`http://127.0.0.1:8080/jeecg-boot/jmreport/test/getDictSex`
|
||||
- 返回格式:`[{"text":"男","value":"1"},{"text":"女","value":"2"}]`
|
||||
|
||||
**方式四:JS增强**
|
||||
```javascript
|
||||
this.updateSelectOptions('dbCode', 'fieldName', options)
|
||||
```
|
||||
|
||||
### 下拉显示条数配置
|
||||
|
||||
在字段的配置设置中:
|
||||
```json
|
||||
{"selectSearchPageSize": 20}
|
||||
```
|
||||
默认显示10条。
|
||||
|
||||
## 3. 查询控件默认值
|
||||
|
||||
三种方式:
|
||||
|
||||
| 方式 | 示例 |
|
||||
|------|------|
|
||||
| 静态值 | 直接输入字符串 |
|
||||
| 动态表达式 | `=dateStr('yyyy-MM-dd')` |
|
||||
| 系统变量 | `#{sysUserCode}` |
|
||||
|
||||
## 4. 时间控件
|
||||
|
||||
### 支持的日期格式
|
||||
|
||||
| 格式 | 示例 |
|
||||
|------|------|
|
||||
| `yyyy-MM-dd HH:mm:ss` | 2021-07-29 12:11:10 |
|
||||
| `yyyy-MM-dd` | 2021-07-29 |
|
||||
| `yyyy-MM` | 2021-07 |
|
||||
| `yyyy` | 2021 |
|
||||
| `MM` | 07 |
|
||||
| `HH:mm:ss` | 12:11:10 |
|
||||
| `HH:mm` | 12:11 |
|
||||
|
||||
**重要:** 日期控件传递的值始终为字符串类型。
|
||||
|
||||
### 数据库日期转换
|
||||
|
||||
不同数据库需要用对应的日期转换函数作为查询条件字段:
|
||||
|
||||
| 数据库 | 转换函数 | 示例 |
|
||||
|--------|---------|------|
|
||||
| MySQL | `DATE_FORMAT(field, '%Y')` | `DATE_FORMAT(birthday, '%Y') nian` |
|
||||
| Oracle | `to_char(field, 'yyyy')` | `to_char(birthday, 'yyyy') nian` |
|
||||
| SQL Server | `year(field)` | `year(birthday) nian` |
|
||||
|
||||
**SQL示例(MySQL):**
|
||||
```sql
|
||||
SELECT name, birthday, DATE_FORMAT(birthday, '%Y') nian FROM demo
|
||||
```
|
||||
将转换后的列 `nian` 配置为查询条件,而非原始日期字段。
|
||||
|
||||
### 时间默认值函数
|
||||
|
||||
#### dateStr(date, format, offset)(v1.3.79+)
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| date | 时间字符串(可选,默认当前时间) |
|
||||
| format | 格式化模式(默认 `yyyy-MM-dd HH:mm:ss`) |
|
||||
| offset | 数值偏移量 |
|
||||
|
||||
**示例(当前时间 2020-08-11 12:00:01):**
|
||||
|
||||
| 表达式 | 结果 |
|
||||
|--------|------|
|
||||
| `=dateStr()` | 2020-08-11 12:00:01 |
|
||||
| `=dateStr('yyyy-MM-dd')` | 2020-08-11 |
|
||||
| `=dateStr('MM', 2)` | 10 |
|
||||
| `=dateStr('dd', -1)` | 10 |
|
||||
| `=dateStr('2020-08-15 12:00:01', 'yyyy-MM-dd', 1)` | 2020-08-16 |
|
||||
| `=dateStr('yyyy-MM', -1)` | 2020-07(v1.4.0+) |
|
||||
|
||||
#### date2Str(date, format, offset)(v1.7.5+)
|
||||
|
||||
与 `dateStr()` 相同但保留前导零(如 `01` 而非 `1`)。
|
||||
|
||||
## 5. SQL条件表达式(FreeMarker语法)
|
||||
|
||||
v1.3.79+ 支持动态SQL条件,使用 FreeMarker 模板语法。
|
||||
|
||||
### isNotEmpty() 函数
|
||||
|
||||
对 `null` 和空字符串 `""` 都返回 `false`。
|
||||
|
||||
### 基础示例
|
||||
|
||||
```sql
|
||||
select id, name, age from demo where create_by = '#{sysUserCode}'
|
||||
<#if isNotEmpty(age)> and age = '${age}'</#if>
|
||||
<#if isNotEmpty(name)> and name = '${name}'</#if>
|
||||
```
|
||||
|
||||
### LIKE模糊查询
|
||||
|
||||
```sql
|
||||
select * from demo where 1=1
|
||||
<#if name?? && name?length gt 0>
|
||||
and name like concat('%', '${name}', '%')
|
||||
</#if>
|
||||
```
|
||||
|
||||
### 多数据集共享参数
|
||||
|
||||
```sql
|
||||
-- 数据集1
|
||||
select username, sex, phone, create_time from user where 1=1
|
||||
<#if isNotEmpty(begin_date)>
|
||||
and DATE_FORMAT(create_time, '%Y-%m-%d') >= '${begin_date}'
|
||||
</#if>
|
||||
<#if isNotEmpty(end_date)>
|
||||
and DATE_FORMAT(create_time, '%Y-%m-%d') <= '${end_date}'
|
||||
</#if>
|
||||
|
||||
-- 数据集2(共享 begin_date 和 end_date 参数)
|
||||
select count(1) as value, DATE_FORMAT(create_time, '%Y-%m-%d') as name
|
||||
from user where 1=1
|
||||
<#if isNotEmpty(begin_date)>
|
||||
and DATE_FORMAT(create_time, '%Y-%m-%d') >= '${begin_date}'
|
||||
</#if>
|
||||
<#if isNotEmpty(end_date)>
|
||||
and DATE_FORMAT(create_time, '%Y-%m-%d') <= '${end_date}'
|
||||
</#if>
|
||||
GROUP BY name
|
||||
```
|
||||
|
||||
## 6. SQL表达式函数(DaoFormat)
|
||||
|
||||
v1.6.2+ 支持在SQL中使用 `DaoFormat` 函数。
|
||||
|
||||
### DaoFormat.in() — 字符串IN查询
|
||||
|
||||
输入:`male,female` → 输出:`'male','female'`
|
||||
|
||||
```sql
|
||||
select * from demo where sex in(${DaoFormat.in('${sex}')})
|
||||
```
|
||||
|
||||
### DaoFormat.inNumber() — 数字IN查询
|
||||
|
||||
输入:`21,22` → 输出:`21,22`
|
||||
|
||||
```sql
|
||||
select * from demo where age in(${DaoFormat.inNumber('${age}')})
|
||||
```
|
||||
|
||||
### DaoFormat.concat() — 字符串拼接
|
||||
|
||||
```sql
|
||||
select * from demo where create_time between
|
||||
'${DaoFormat.concat('${beginTime}', ' 00:00:00')}'
|
||||
and '${DaoFormat.concat('${endTime}', ' 23:59:59')}'
|
||||
```
|
||||
|
||||
## 7. 下拉树控件
|
||||
|
||||
v1.3.79+ 支持层级树形下拉。
|
||||
|
||||
### 配置格式
|
||||
|
||||
```json
|
||||
{"loadTree": "{{ domainURL }}/sys/user/treeTest"}
|
||||
```
|
||||
或绝对路径:
|
||||
```json
|
||||
{"loadTree": "https://api.jeecg.com/mock/26/queryTree"}
|
||||
```
|
||||
|
||||
### 接口返回格式
|
||||
|
||||
```json
|
||||
[
|
||||
{"id": "001", "pid": "", "value": "A01", "title": "节点1", "izLeaf": 0},
|
||||
{"id": "002", "pid": "001", "value": "A02", "title": "子节点1", "izLeaf": 1}
|
||||
]
|
||||
```
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `id` | 节点标识 |
|
||||
| `pid` | 父节点ID,空=根节点 |
|
||||
| `value` | 实际查询值 |
|
||||
| `title` | 显示文本 |
|
||||
| `izLeaf` | 1=叶子节点(无展开图标),0=父节点 |
|
||||
|
||||
### 穿透场景(v1.5.0+)
|
||||
|
||||
```json
|
||||
{
|
||||
"loadTree": ".../treeTest",
|
||||
"loadTreeByValue": ".../loadTreeByValue"
|
||||
}
|
||||
```
|
||||
|
||||
**限制:** 下拉树不支持默认值配置。
|
||||
|
||||
## 8. 范围查询默认值
|
||||
|
||||
使用管道符 `|` 分隔起止值。
|
||||
|
||||
| 场景 | 默认值表达式 |
|
||||
|------|------------|
|
||||
| 数字范围 | `16\|22` |
|
||||
| 固定日期 | `2021-11-01\|2021-11-30` |
|
||||
| 本月1日到今天 | `=concat(string.substring(dateStr('yyyy-MM-dd'),0,8),'01')\|=dateStr('yyyy-MM-dd')` |
|
||||
| 最近10天 | `=concat(dateStr('yyyy-MM-dd',-10),' 00:00:00')\|=dateStr('yyyy-MM-dd HH:mm:ss')` |
|
||||
| 最近3个月 | `=concat(dateStr('yyyy',-1),'-',dateStr('MM',-3),'-',dateStr('dd'))\|=dateStr('yyyy-MM-dd')` |
|
||||
|
||||
## 9. JS增强与CSS增强
|
||||
|
||||
v1.3.79+ 支持。
|
||||
|
||||
### JS API方法
|
||||
|
||||
| 方法 | 参数 | 用途 |
|
||||
|------|------|------|
|
||||
| `updateSelectOptions(dbCode, fieldName, options)` | 数据集编码, 字段名, 选项数组 | 动态更新下拉选项 |
|
||||
| `onSearchFormChange(dbCode, fieldName, callback)` | 数据集编码, 字段名, 回调函数 | 监听控件值变化 |
|
||||
| `updateSearchFormValue(dbCode, fieldName, value)` | 数据集编码, 字段名, 值 | 设置控件初始值 |
|
||||
| `getSelectOptions(dbCode, fieldName)` | 数据集编码, 字段名 | 获取当前下拉选项 |
|
||||
| `notLoadDataWhenShow()` | 无 | 预览时不自动加载数据(v1.6.7+) |
|
||||
|
||||
### 三级联动下拉示例
|
||||
|
||||
```javascript
|
||||
function init(){
|
||||
// 加载省份
|
||||
$http.metaGet('http://localhost:8080/jeecg-boot/ces/ai/customSelect')
|
||||
.then(res => {
|
||||
this.updateSelectOptions('pca', 'pro', res.data)
|
||||
})
|
||||
// 省→市联动
|
||||
this.onSearchFormChange('pca', 'pro', (value) => {
|
||||
$http.metaGet('http://localhost:8080/jeecg-boot/ces/ai/customSelect', {pid: value})
|
||||
.then(res => { this.updateSelectOptions('pca', 'city', res.data) })
|
||||
})
|
||||
// 市→区联动
|
||||
this.onSearchFormChange('pca', 'city', (value) => {
|
||||
$http.metaGet('http://localhost:8080/jeecg-boot/ces/ai/customSelect', {pid: value})
|
||||
.then(res => { this.updateSelectOptions('pca', 'area', res.data) })
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### 设置下拉默认选中第一项(v1.4.0+)
|
||||
|
||||
```javascript
|
||||
function init(){
|
||||
let ops = this.getSelectOptions('de', 'sex');
|
||||
if(ops && ops.length > 0){
|
||||
this.updateSearchFormValue('de', 'sex', ops[0].value)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### CSS增强示例
|
||||
|
||||
```css
|
||||
.jm-query-form .ivu-btn-primary {
|
||||
background-color: red;
|
||||
border-color: red;
|
||||
}
|
||||
```
|
||||
|
||||
## 10. 参数配置设置
|
||||
|
||||
| 配置项 | 用途 | 适用控件 |
|
||||
|--------|------|---------|
|
||||
| `loadTree` | 树结构加载URL | 下拉树 |
|
||||
| `loadTreeByValue` | 按值检索树URL | 下拉树 |
|
||||
| `dictSplit` | 字典分隔符(仅英文字符) | 下拉单选/多选 |
|
||||
| `selectSearchPageSize` | 每页显示条数(默认10) | 下拉单选/多选 |
|
||||
| `order` | SQL排序 | 数据集字段详情(非报表参数) |
|
||||
| `required` | 必填标记(v1.7.9+) | 所有类型 |
|
||||
|
||||
必填配置:`{"required": true}`,默认 `false`。v1.9.6+ 支持可视化配置界面。
|
||||
|
||||
## 11. 查询设置(querySetting)
|
||||
|
||||
```json
|
||||
"querySetting": {
|
||||
"izOpenQueryBar": false,
|
||||
"izDefaultQuery": true
|
||||
}
|
||||
```
|
||||
|
||||
| 设置 | 默认值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `izDefaultQuery` | true | 是否自动执行查询(关闭后需手动点击查询按钮) |
|
||||
| `izOpenQueryBar` | false | 是否默认展开查询栏 |
|
||||
211
.trae/skills/jimureport/references/signature.md
Normal file
211
.trae/skills/jimureport/references/signature.md
Normal file
@@ -0,0 +1,211 @@
|
||||
# 接口签名机制 (JimuSignature)
|
||||
|
||||
## 概述
|
||||
|
||||
积木报表部分接口使用 `@JimuSignature` 注解标记,调用时需要在请求 Header 中携带签名参数,否则返回 `code: 1001` 签名校验失败错误。
|
||||
|
||||
**源码位置:**
|
||||
- 后端拦截器:`jimureport-spring-boot-starter/.../common/interceptor/JimuReportSignatureInterceptor.java`
|
||||
- 前端签名工具:`static/jmreport/desreport_/js/biz/SignMd5Util.js`
|
||||
- 前端请求拦截器:`static/jmreport/desreport_/js/core/request.js`
|
||||
|
||||
## 需要签名的接口
|
||||
|
||||
| 接口 | 方法 | 说明 |
|
||||
|------|------|------|
|
||||
| `/jmreport/queryFieldBySql` | POST | SQL 解析获取字段 |
|
||||
| `/jmreport/executeSelectApi` | POST | API 数据集解析 |
|
||||
| `/jmreport/loadTableData` | POST | 加载表数据 |
|
||||
| `/jmreport/testConnection` | POST | 测试数据源连接 |
|
||||
| `/jmreport/download/image` | GET | 下载图片 |
|
||||
| `/jmreport/dictCodeSearch` | GET | 字典编码搜索 |
|
||||
| `/jmreport/getDataSourceByPage` | GET | 分页查询数据源 |
|
||||
| `/jmreport/getDataSourceById` | GET | 按ID查询数据源 |
|
||||
|
||||
**不需要签名的接口(常用):**
|
||||
- `/jmreport/save` — 保存报表
|
||||
- `/jmreport/saveDb` — 保存数据集
|
||||
- `/jmreport/get/{id}` — 获取报表
|
||||
- `/jmreport/field/tree/{reportId}` — 获取数据集树
|
||||
- `/jmreport/loadDbData/{dbId}` — 加载数据集详情
|
||||
|
||||
## 签名算法
|
||||
|
||||
### 请求 Headers
|
||||
|
||||
| Header | 值 | 说明 |
|
||||
|--------|------|------|
|
||||
| `X-Sign` | MD5 签名(大写) | 见下方计算方法 |
|
||||
| `X-TIMESTAMP` | 当前时间戳(毫秒) | `int(time.time() * 1000)` |
|
||||
|
||||
### 计算步骤
|
||||
|
||||
```
|
||||
1. 收集所有请求参数(URL query参数 + POST body参数)
|
||||
2. 按 key 字母升序排序(SortedMap / TreeMap)
|
||||
3. 转为 JSON 字符串:JSON.stringify(sortedParams) 或 JSONObject.toJSONString(sortedMap)
|
||||
4. 拼接签名密钥:jsonStr + signatureSecret
|
||||
5. 计算 MD5 并转大写:MD5(jsonStr + secret).toUpperCase()
|
||||
```
|
||||
|
||||
### 签名密钥 (signatureSecret)
|
||||
|
||||
**默认值:** `dd05f1c54d63749eda95f9fa6d49v442a`
|
||||
|
||||
解析优先级:
|
||||
1. `JmReportBaseConfig.getSignatureSecret()` — 代码配置
|
||||
2. Spring 属性 `jeecg.signatureSecret` — application.yml 配置
|
||||
3. 默认值 `dd05f1c54d63749eda95f9fa6d49v442a`
|
||||
|
||||
> **注意:** 默认密钥中第29个字符是字母 `v`,不是数字 `4`。
|
||||
|
||||
### 时间戳校验
|
||||
|
||||
服务端校验时间戳有效期为 **5 分钟(300秒)**。如果客户端与服务器时间差超过5分钟,会返回 "签名验证失败:X-TIMESTAMP已过期"。
|
||||
|
||||
### 参数值类型转换规则
|
||||
|
||||
前端在签名前会统一类型(后端用 `json.getString(key)` 读取,也是字符串):
|
||||
- **数字** → 转为字符串(如 `0` → `"0"`)
|
||||
- **布尔** → 转为字符串(如 `false` → `"false"`)
|
||||
- **对象/数组** → 转为 JSON 字符串
|
||||
- **null/空** → 不参与签名
|
||||
|
||||
### 后端校验逻辑
|
||||
|
||||
```java
|
||||
// 1. 收集参数到 SortedMap (TreeMap, 自动按key排序)
|
||||
SortedMap<String, String> map = new TreeMap<>();
|
||||
|
||||
// 2. 从 request.getParameterMap() 取 URL/form 参数
|
||||
// 3. 从 request.getQueryString() 取 GET 参数
|
||||
// 4. 从 POST body JSON 取参数 (json.getString(key))
|
||||
|
||||
// 5. 计算签名
|
||||
String paramsJsonStr = JSONObject.toJSONString(map); // fastjson
|
||||
String signValue = DigestUtils.md5DigestAsHex(
|
||||
(paramsJsonStr + CommonUtils.getSignatureSecret()).getBytes()
|
||||
).toUpperCase();
|
||||
|
||||
// 6. 比对 header 中的 X-Sign
|
||||
```
|
||||
|
||||
> **关键细节:** 后端用 fastjson 的 `JSONObject.toJSONString(map)` 序列化 SortedMap,输出格式为 `{"key1":"value1","key2":"value2"}`(无空格)。Python 端必须用 `json.dumps(sorted_dict, separators=(',', ':'))` 匹配(无空格)。
|
||||
|
||||
## Python 实现
|
||||
|
||||
```python
|
||||
import hashlib
|
||||
import json
|
||||
import time
|
||||
|
||||
SIGNATURE_SECRET = "dd05f1c54d63749eda95f9fa6d49v442a"
|
||||
|
||||
def compute_sign(params_dict):
|
||||
"""
|
||||
计算积木报表接口签名
|
||||
params_dict: 请求参数字典(POST body 或 GET query 参数)
|
||||
"""
|
||||
# 1. 所有值转为字符串(与前端/后端保持一致)
|
||||
str_params = {}
|
||||
for k, v in params_dict.items():
|
||||
if v is None:
|
||||
continue
|
||||
if isinstance(v, bool):
|
||||
str_params[k] = str(v).lower() # True -> "true"
|
||||
elif isinstance(v, (int, float)):
|
||||
str_params[k] = str(v)
|
||||
elif isinstance(v, (dict, list)):
|
||||
str_params[k] = json.dumps(v, ensure_ascii=False, separators=(',', ':'))
|
||||
else:
|
||||
str_params[k] = str(v)
|
||||
|
||||
# 2. 按 key 字母升序排序
|
||||
sorted_params = dict(sorted(str_params.items()))
|
||||
|
||||
# 3. 转为 JSON 字符串(无空格,与 fastjson 一致)
|
||||
params_json = json.dumps(sorted_params, ensure_ascii=False, separators=(',', ':'))
|
||||
|
||||
# 4. 拼接密钥并计算 MD5
|
||||
sign_str = params_json + SIGNATURE_SECRET
|
||||
sign_value = hashlib.md5(sign_str.encode('utf-8')).hexdigest().upper()
|
||||
|
||||
return sign_value
|
||||
|
||||
def get_sign_headers(params_dict):
|
||||
"""获取签名相关的请求头"""
|
||||
return {
|
||||
'X-Sign': compute_sign(params_dict),
|
||||
'X-TIMESTAMP': str(int(time.time() * 1000))
|
||||
}
|
||||
```
|
||||
|
||||
### 使用示例
|
||||
|
||||
```python
|
||||
# POST /jmreport/queryFieldBySql
|
||||
body = {"sql": "select * from demo", "dbSource": "", "type": "0"}
|
||||
sign_headers = get_sign_headers(body)
|
||||
# sign_headers = {'X-Sign': 'AB12CD34...', 'X-TIMESTAMP': '1774019281912'}
|
||||
|
||||
# GET /jmreport/getDataSourceByPage?pageNo=1&pageSize=10
|
||||
query_params = {"pageNo": "1", "pageSize": "10"}
|
||||
sign_headers = get_sign_headers(query_params)
|
||||
```
|
||||
|
||||
### 完整的带签名 API 请求函数
|
||||
|
||||
```python
|
||||
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'
|
||||
}
|
||||
|
||||
# 需要签名的接口列表
|
||||
SIGNED_ENDPOINTS = [
|
||||
'/jmreport/queryFieldBySql',
|
||||
'/jmreport/executeSelectApi',
|
||||
'/jmreport/loadTableData',
|
||||
'/jmreport/testConnection',
|
||||
'/jmreport/download/image',
|
||||
'/jmreport/dictCodeSearch',
|
||||
'/jmreport/getDataSourceByPage',
|
||||
'/jmreport/getDataSourceById',
|
||||
]
|
||||
|
||||
# 判断是否需要签名
|
||||
need_sign = any(path.rstrip('/').endswith(ep.rstrip('/')) for ep in SIGNED_ENDPOINTS)
|
||||
|
||||
if need_sign:
|
||||
sign_params = data if data else {}
|
||||
headers['X-TIMESTAMP'] = str(int(time.time() * 1000))
|
||||
headers['X-Sign'] = compute_sign(sign_params)
|
||||
|
||||
if data is not None:
|
||||
json_data = json.dumps(data, ensure_ascii=False).encode('utf-8')
|
||||
req = urllib.request.Request(url, data=json_data, headers=headers, method=method or 'POST')
|
||||
else:
|
||||
req = urllib.request.Request(url, headers=headers, method=method or 'GET')
|
||||
|
||||
resp = urllib.request.urlopen(req, context=ctx)
|
||||
return json.loads(resp.read().decode('utf-8'))
|
||||
```
|
||||
|
||||
## 常见错误
|
||||
|
||||
| 错误信息 | 原因 | 解决方案 |
|
||||
|---------|------|---------|
|
||||
| `签名验证失败: 签名参数不存在` | 未传 X-Sign 或 X-TIMESTAMP Header | 添加签名 Headers |
|
||||
| `签名验证失败:X-TIMESTAMP已过期` | 客户端与服务器时间差超过5分钟 | 检查系统时间,使用当前时间戳 |
|
||||
| `签名校验失败,参数有误!` | 签名值不匹配 | 检查参数排序、JSON序列化格式、密钥是否正确 |
|
||||
| `code: 1001` | 签名相关错误的统一错误码 | 查看 message 详情 |
|
||||
|
||||
## 调试技巧
|
||||
|
||||
1. **打印签名输入**:输出 `params_json + secret` 字符串,对比前后端是否一致
|
||||
2. **对比 JSON 格式**:确保使用 `separators=(',', ':')` 无空格格式
|
||||
3. **检查类型转换**:数字/布尔/对象必须转为字符串
|
||||
4. **验证密钥**:确认使用的密钥与服务端配置一致(默认 `dd05f1c54d63749eda95f9fa6d49v442a`)
|
||||
152
.trae/skills/jimureport/references/template-analysis.md
Normal file
152
.trae/skills/jimureport/references/template-analysis.md
Normal file
@@ -0,0 +1,152 @@
|
||||
# 积木报表模板分析参考
|
||||
|
||||
## 模板报表查询
|
||||
|
||||
通过 `getReportByUser` 接口获取模板报表:
|
||||
```
|
||||
GET /jmreport/getReportByUser?reportId=&template=1
|
||||
```
|
||||
|
||||
## 46个模板分类统计
|
||||
|
||||
| 分类 | 数量 | 示例 |
|
||||
|------|------|------|
|
||||
| 基础表格 | 30 | 信息采集表、简单分组报表 |
|
||||
| 图表报表 | 9 | 全国各大城市化员数据、物业实时监控 |
|
||||
| 循环报表 | 4 | 订单表循环打印、班级循环套打表 |
|
||||
| 图片报表 | 4 | 员工信息表、证书打印 |
|
||||
| 条码/二维码 | 3 | 实习证明、凭证条码报表 |
|
||||
|
||||
## 图表数据绑定
|
||||
|
||||
### extData 数据类型 (dataType)
|
||||
|
||||
**前端使用文本字符串,非数字:**
|
||||
- `"sql"` - SQL数据集
|
||||
- `"api"` - API数据集
|
||||
- `"json"` - JSON数据集
|
||||
- `"javabean"` - JavaBean数据集
|
||||
- `"files"` - 文件数据集
|
||||
- `null` - 静态图表(无数据绑定,使用ECharts配置中的硬编码数据)
|
||||
|
||||
### 字段映射规则
|
||||
|
||||
固定三个字段名映射:
|
||||
```python
|
||||
extData = {
|
||||
"axisX": "name", # X轴/分类字段
|
||||
"axisY": "value", # Y轴/数值字段
|
||||
"series": "type" # 系列/分组字段(多系列图表用)
|
||||
}
|
||||
```
|
||||
|
||||
SQL查询需要AS别名:
|
||||
```sql
|
||||
SELECT category AS name, COUNT(*) AS value, '' AS type FROM table GROUP BY category
|
||||
```
|
||||
|
||||
### xText / yText 轴标题
|
||||
|
||||
工作正常的模板中这两个字段常常为空字符串,轴标题主要通过ECharts配置设置:
|
||||
```python
|
||||
chart_config = {
|
||||
"xAxis": {
|
||||
"name": "表单类型", # 轴标题
|
||||
"type": "category"
|
||||
},
|
||||
"yAxis": {
|
||||
"name": "数量",
|
||||
"type": "value"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## displayConfig 单元格组件
|
||||
|
||||
用于在普通单元格中渲染条码、二维码、图片。
|
||||
|
||||
### 配置结构
|
||||
```json
|
||||
{
|
||||
"displayConfig": {
|
||||
"1": {"barcodeContent": "#{pop.id}", "format": "CODE128", "width": "50", "height": "100", "displayValue": false},
|
||||
"11": {"text": "#{uiu.tm}", "width": 227, "height": 227, "colorDark": "#000000", "colorLight": "#ffffff"},
|
||||
"111": {"barcodeContent": "固定值", "format": "QR", "width": "6", "height": 39}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 键名规则
|
||||
- 键名 = `列号`(从1开始)
|
||||
- 行号通过cells中的display属性关联
|
||||
|
||||
### 条码配置 (barcodeContent)
|
||||
```json
|
||||
{
|
||||
"barcodeContent": "#{字段变量}", // 动态值
|
||||
"format": "CODE128|CODE39|QR", // 条码格式
|
||||
"width": "2", // 条码宽度
|
||||
"height": 80, // 条码高度
|
||||
"displayValue": false // 是否显示值
|
||||
}
|
||||
```
|
||||
|
||||
### 二维码配置 (text)
|
||||
```json
|
||||
{
|
||||
"text": "#{字段变量}", // 二维码内容
|
||||
"width": 112, // 宽度
|
||||
"height": 112, // 高度
|
||||
"colorDark": "#000000", // 前景色
|
||||
"colorLight": "#ffffff" // 背景色
|
||||
}
|
||||
```
|
||||
|
||||
## 循环报表 (loopBlockList)
|
||||
|
||||
### 结构
|
||||
```json
|
||||
{
|
||||
"loopBlockList": [
|
||||
{
|
||||
"sci": 1, // 起始列
|
||||
"sri": 2, // 起始行
|
||||
"eci": 5, // 结束列
|
||||
"eri": 5, // 结束行
|
||||
"index": 1, // 块索引
|
||||
"db": "jm", // 数据集别名
|
||||
"loopTime": 3 // 循环次数(可选)
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 单元格变量语法
|
||||
```
|
||||
#{数据集别名.字段名}
|
||||
#{jm.name}
|
||||
#{pop.group(id)}
|
||||
```
|
||||
|
||||
## 常见图表类型数据要求
|
||||
|
||||
| 图表类型 | dataType | axisX | axisY | series | 示例数据 |
|
||||
|---------|----------|-------|-------|--------|---------|
|
||||
| bar.simple | sql/api | name | value | type | 单系列柱状 |
|
||||
| bar.multi | sql/api | name | value | type | 多系列柱状 |
|
||||
| line.simple | sql/api | name | value | type | 单线折线 |
|
||||
| pie.simple | sql/api | name | value | - | 饼图 |
|
||||
| gauge.simple | sql/api | name | value | - | 仪表盘 |
|
||||
| radar.basic | sql/api | name | value | type | 雷达图 |
|
||||
| map.scatter | sql/api | name | value | - | 地图散点 |
|
||||
|
||||
## 模板报表ID参考
|
||||
|
||||
| 报表名称 | ID | 特点 |
|
||||
|---------|---|------|
|
||||
| 全国各大城市化员数据 | 1339859143477039104 | 9个图表(sql+api混合) |
|
||||
| 图表数据联动示例 | 1356492523694067712 | 19个图表(静态+动态) |
|
||||
| 物业实时监控 | 1339478701846433792 | 9个图表+地图 |
|
||||
| 凭证条码报表 | 1338370016550195200 | displayConfig条码 |
|
||||
| 实习证明 | 1350035590569136128 | displayConfig二维码 |
|
||||
| 图片展示平台 | 1334074491629867008 | 8个图片+4个图表 |
|
||||
794
.trae/skills/jimureport/scripts/jimureport_creator.py
Normal file
794
.trae/skills/jimureport/scripts/jimureport_creator.py
Normal file
@@ -0,0 +1,794 @@
|
||||
"""
|
||||
积木报表 (JiMu Report) 创建/编辑工具脚本
|
||||
|
||||
用法:
|
||||
python jimureport_creator.py --api-base <URL> --token <TOKEN> --config <config.json>
|
||||
|
||||
config.json 格式见下方示例。
|
||||
|
||||
支持的操作:
|
||||
- 创建报表 (action='create')
|
||||
- 编辑报表 (action='edit', 需提供 reportId)
|
||||
|
||||
config.json 示例(创建):
|
||||
{
|
||||
"action": "create",
|
||||
"reportName": "用户数据统计报表",
|
||||
"datasets": [
|
||||
{
|
||||
"dbCode": "userlist",
|
||||
"dbChName": "用户列表",
|
||||
"dbDynSql": "SELECT username, realname, sex FROM sys_user WHERE del_flag = 0",
|
||||
"isPage": "1"
|
||||
},
|
||||
{
|
||||
"dbCode": "sexchart",
|
||||
"dbChName": "性别图表",
|
||||
"dbDynSql": "SELECT sex AS name, COUNT(*) AS value FROM sys_user WHERE del_flag = 0 GROUP BY sex",
|
||||
"isPage": "0",
|
||||
"forChart": true
|
||||
}
|
||||
],
|
||||
"layout": "chart_top",
|
||||
"table": {
|
||||
"datasetCode": "userlist",
|
||||
"title": "用户数据列表",
|
||||
"columns": [
|
||||
{"field": "username", "title": "用户账号", "width": 120},
|
||||
{"field": "realname", "title": "姓名", "width": 100},
|
||||
{"field": "sex", "title": "性别", "width": 80}
|
||||
]
|
||||
},
|
||||
"chart": {
|
||||
"datasetCode": "sexchart",
|
||||
"chartType": "pie.doughnut",
|
||||
"title": "按性别统计",
|
||||
"width": "650",
|
||||
"height": "300"
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
import urllib.request
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
import random
|
||||
import hashlib
|
||||
import ssl
|
||||
import argparse
|
||||
|
||||
# 修复 Windows 控制台中文乱码
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
sys.stderr.reconfigure(encoding='utf-8')
|
||||
|
||||
SIGNATURE_SECRET = 'dd05f1c54d63749eda95f9fa6d49v442a'
|
||||
|
||||
SIGNED_ENDPOINTS = [
|
||||
'/jmreport/queryFieldBySql',
|
||||
'/jmreport/executeSelectApi',
|
||||
'/jmreport/loadTableData',
|
||||
'/jmreport/testConnection',
|
||||
'/jmreport/download/image',
|
||||
'/jmreport/dictCodeSearch',
|
||||
'/jmreport/getDataSourceByPage',
|
||||
'/jmreport/getDataSourceById',
|
||||
]
|
||||
|
||||
# 默认样式列表
|
||||
DEFAULT_STYLES = [
|
||||
# 0: 仅边框
|
||||
{"border": {"bottom": ["thin", "#000"], "top": ["thin", "#000"], "left": ["thin", "#000"], "right": ["thin", "#000"]}},
|
||||
# 1: 边框+居中
|
||||
{"border": {"bottom": ["thin", "#000"], "top": ["thin", "#000"], "left": ["thin", "#000"], "right": ["thin", "#000"]}, "align": "center"},
|
||||
# 2: 边框+居中+垂直居中(数据行)
|
||||
{"border": {"bottom": ["thin", "#000"], "top": ["thin", "#000"], "left": ["thin", "#000"], "right": ["thin", "#000"]}, "align": "center", "valign": "middle"},
|
||||
# 3: 边框+居中+垂直居中+蓝底(表头无白字)
|
||||
{"border": {"bottom": ["thin", "#000"], "top": ["thin", "#000"], "left": ["thin", "#000"], "right": ["thin", "#000"]}, "align": "center", "valign": "middle", "bgcolor": "#01b0f1"},
|
||||
# 4: 边框+居中+垂直居中+蓝底白字(表头推荐)
|
||||
{"border": {"bottom": ["thin", "#000"], "top": ["thin", "#000"], "left": ["thin", "#000"], "right": ["thin", "#000"]}, "align": "center", "valign": "middle", "bgcolor": "#01b0f1", "color": "#ffffff"},
|
||||
# 5: 边框+居中+垂直居中+深蓝底白字加粗(大标题)
|
||||
{"border": {"bottom": ["thin", "#000"], "top": ["thin", "#000"], "left": ["thin", "#000"], "right": ["thin", "#000"]}, "align": "center", "valign": "middle", "bgcolor": "#4472C4", "color": "#ffffff", "font": {"bold": True}},
|
||||
]
|
||||
|
||||
# 默认图表配色
|
||||
DEFAULT_CHART_COLORS = ["#5470c6", "#ee6666", "#91cc75", "#fac858", "#73c0de", "#3ba272", "#fc8452", "#9a60b4"]
|
||||
|
||||
|
||||
# ====== 工具函数 ======
|
||||
|
||||
def gen_id():
|
||||
"""生成唯一ID"""
|
||||
return str(int(time.time() * 1000) * 1000000 + random.randint(100000, 999999))
|
||||
|
||||
|
||||
def compute_sign(params_dict):
|
||||
"""计算积木报表接口签名"""
|
||||
str_params = {}
|
||||
for k, v in params_dict.items():
|
||||
if v is None:
|
||||
continue
|
||||
if isinstance(v, bool):
|
||||
str_params[k] = str(v).lower()
|
||||
elif isinstance(v, (int, float)):
|
||||
str_params[k] = str(v)
|
||||
elif isinstance(v, (dict, list)):
|
||||
str_params[k] = json.dumps(v, ensure_ascii=False, separators=(',', ':'))
|
||||
else:
|
||||
str_params[k] = str(v)
|
||||
sorted_params = dict(sorted(str_params.items()))
|
||||
params_json = json.dumps(sorted_params, ensure_ascii=False, separators=(',', ':'))
|
||||
sign_str = params_json + SIGNATURE_SECRET
|
||||
return hashlib.md5(sign_str.encode('utf-8')).hexdigest().upper()
|
||||
|
||||
|
||||
def create_ssl_context():
|
||||
"""创建不验证证书的SSL上下文"""
|
||||
ctx = ssl.create_default_context()
|
||||
ctx.check_hostname = False
|
||||
ctx.verify_mode = ssl.CERT_NONE
|
||||
return ctx
|
||||
|
||||
|
||||
SSL_CTX = create_ssl_context()
|
||||
|
||||
|
||||
def api_request(api_base, token, path, data=None, method=None):
|
||||
"""发送API请求,自动处理签名"""
|
||||
url = f'{api_base}{path}'
|
||||
headers = {
|
||||
'X-Access-Token': token,
|
||||
'Content-Type': 'application/json; charset=UTF-8'
|
||||
}
|
||||
need_sign = any(path.rstrip('/').endswith(ep.rstrip('/')) for ep in SIGNED_ENDPOINTS)
|
||||
if need_sign:
|
||||
sign_params = data if data else {}
|
||||
headers['X-TIMESTAMP'] = str(int(time.time() * 1000))
|
||||
headers['X-Sign'] = compute_sign(sign_params)
|
||||
if data is not None:
|
||||
json_data = json.dumps(data, ensure_ascii=False).encode('utf-8')
|
||||
req = urllib.request.Request(url, data=json_data, headers=headers, method=method or 'POST')
|
||||
else:
|
||||
req = urllib.request.Request(url, headers=headers, method=method or 'GET')
|
||||
resp = urllib.request.urlopen(req, context=SSL_CTX)
|
||||
return json.loads(resp.read().decode('utf-8'))
|
||||
|
||||
|
||||
# ====== 数据集相关 ======
|
||||
|
||||
def parse_sql_fields(api_base, token, sql, db_source=''):
|
||||
"""解析SQL获取字段列表"""
|
||||
result = api_request(api_base, token, '/jmreport/queryFieldBySql', {
|
||||
"sql": sql, "dbSource": db_source, "type": "0"
|
||||
})
|
||||
if not result.get('success'):
|
||||
print(f' SQL解析失败: {result.get("message")}')
|
||||
return [], []
|
||||
field_list = result.get('result', {}).get('fieldList', [])
|
||||
param_list = result.get('result', {}).get('paramList', [])
|
||||
return field_list, param_list
|
||||
|
||||
|
||||
def save_dataset(api_base, token, report_id, ds_config, field_list, param_list):
|
||||
"""保存数据集,返回数据集ID"""
|
||||
db_data = {
|
||||
"izSharedSource": 0,
|
||||
"jimuReportId": report_id,
|
||||
"dbCode": ds_config['dbCode'],
|
||||
"dbChName": ds_config.get('dbChName', ds_config['dbCode']),
|
||||
"dbType": ds_config.get('dbType', '0'),
|
||||
"dbSource": ds_config.get('dbSource', ''),
|
||||
"jsonData": ds_config.get('jsonData', ''),
|
||||
"apiConvert": ds_config.get('apiConvert', ''),
|
||||
"isList": ds_config.get('isList', '1'),
|
||||
"isPage": ds_config.get('isPage', '1'),
|
||||
"dbDynSql": ds_config.get('dbDynSql', ''),
|
||||
"fieldList": field_list,
|
||||
"paramList": param_list
|
||||
}
|
||||
result = api_request(api_base, token, '/jmreport/saveDb', db_data)
|
||||
if not result.get('success'):
|
||||
print(f' 数据集保存失败: {result.get("message")}')
|
||||
return None
|
||||
return result['result']['id']
|
||||
|
||||
|
||||
# ====== 报表布局构造 ======
|
||||
|
||||
def build_cols(columns):
|
||||
"""根据列配置构造cols对象"""
|
||||
cols = {"len": 100}
|
||||
for i, col in enumerate(columns):
|
||||
if col.get('width'):
|
||||
cols[str(i + 1)] = {"width": col['width']}
|
||||
return cols
|
||||
|
||||
|
||||
def build_table_rows(table_config, start_row=1, title_style=5, header_style=4, data_style=2):
|
||||
"""
|
||||
构造数据表格的rows、merges。
|
||||
返回 (rows_dict, merges_list, next_row)
|
||||
"""
|
||||
rows = {}
|
||||
merges = []
|
||||
columns = table_config.get('columns', [])
|
||||
ds_code = table_config['datasetCode']
|
||||
col_count = len(columns)
|
||||
current_row = start_row
|
||||
|
||||
# 标题行
|
||||
title = table_config.get('title')
|
||||
if title:
|
||||
cells = {}
|
||||
for i in range(col_count):
|
||||
cells[str(i + 1)] = {"text": title if i == 0 else "", "style": title_style}
|
||||
rows[str(current_row)] = {"cells": cells, "height": 40}
|
||||
if col_count > 1:
|
||||
start_col = chr(ord('A') + 1) # B列开始(列索引1对应B)
|
||||
end_col = chr(ord('A') + col_count)
|
||||
# merge 使用 UI 行号(代码行号+1,因为代码row 0 = UI row 1)
|
||||
ui_row = current_row + 1
|
||||
merges.append(f"{start_col}{ui_row}:{end_col}{ui_row}")
|
||||
current_row += 1
|
||||
|
||||
# 表头行
|
||||
header_cells = {}
|
||||
for i, col in enumerate(columns):
|
||||
header_cells[str(i + 1)] = {"text": col['title'], "style": header_style}
|
||||
rows[str(current_row)] = {"cells": header_cells, "height": 34}
|
||||
current_row += 1
|
||||
|
||||
# 数据绑定行
|
||||
data_cells = {}
|
||||
for i, col in enumerate(columns):
|
||||
data_cells[str(i + 1)] = {"text": f"#{{{ds_code}.{col['field']}}}", "style": data_style}
|
||||
rows[str(current_row)] = {"cells": data_cells}
|
||||
current_row += 1
|
||||
|
||||
return rows, merges, current_row
|
||||
|
||||
|
||||
def build_chart_rows(chart_config, chart_db_id, start_row=1, col_start=1, col_end=6, row_count=1):
|
||||
"""
|
||||
构造图表的虚拟单元格rows和chartList。
|
||||
row_count 默认为1行(与设计器行为一致,图表通过width/height属性控制大小,
|
||||
虚拟单元格仅作为锚点,不需要覆盖整个图表区域)。
|
||||
返回 (rows_dict, chart_list, next_row)
|
||||
"""
|
||||
layer_id = "chart_" + gen_id()
|
||||
rows = {}
|
||||
virtual_cell_range = []
|
||||
|
||||
for r in range(start_row, start_row + row_count):
|
||||
cells = {}
|
||||
for c in range(col_start, col_end + 1):
|
||||
cells[str(c)] = {"text": " ", "virtual": layer_id}
|
||||
virtual_cell_range.append([r, c])
|
||||
rows[str(r)] = {"cells": cells}
|
||||
|
||||
# ECharts 配置
|
||||
chart_type = chart_config.get('chartType', 'bar.simple')
|
||||
echarts_config = build_echarts_config(chart_type, chart_config)
|
||||
|
||||
chart_item = {
|
||||
"row": start_row,
|
||||
"col": col_start,
|
||||
"colspan": 0,
|
||||
"rowspan": 0,
|
||||
"width": str(chart_config.get('width', '650')),
|
||||
"height": str(chart_config.get('height', '350')),
|
||||
"config": json.dumps(echarts_config, ensure_ascii=False),
|
||||
"url": "",
|
||||
"extData": {
|
||||
"chartType": chart_type,
|
||||
"dataType": chart_config.get('dataType', 'sql'),
|
||||
"dataId": chart_db_id,
|
||||
"dbCode": chart_config['datasetCode'],
|
||||
"axisX": "name",
|
||||
"axisY": "value",
|
||||
"series": "type",
|
||||
"xText": "",
|
||||
"yText": "",
|
||||
"apiStatus": "1"
|
||||
},
|
||||
"layer_id": layer_id,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"backgroud": {"enabled": False, "color": "#fff", "image": ""},
|
||||
"virtualCellRange": virtual_cell_range
|
||||
}
|
||||
|
||||
return rows, [chart_item], start_row + row_count
|
||||
|
||||
|
||||
def build_echarts_config(chart_type, chart_config):
|
||||
"""根据图表类型构造ECharts配置"""
|
||||
title_text = chart_config.get('title', '')
|
||||
colors = chart_config.get('colors', DEFAULT_CHART_COLORS)
|
||||
|
||||
if chart_type.startswith('pie'):
|
||||
# 饼图/环形图/玫瑰图
|
||||
radius = ["40%", "70%"] if 'doughnut' in chart_type else "70%"
|
||||
if 'rose' in chart_type:
|
||||
radius = [20, "70%"]
|
||||
return {
|
||||
"title": {"text": title_text, "left": "center", "textStyle": {"fontSize": 16}},
|
||||
"tooltip": {"trigger": "item", "formatter": "{b}: {c} ({d}%)"},
|
||||
"legend": {"orient": "vertical", "left": "left", "top": "middle"},
|
||||
"series": [{
|
||||
"type": "pie",
|
||||
"radius": radius,
|
||||
"center": ["55%", "55%"],
|
||||
"avoidLabelOverlap": True,
|
||||
"itemStyle": {"borderRadius": 6, "borderColor": "#fff", "borderWidth": 2},
|
||||
"label": {"show": True, "formatter": "{b}: {c}"},
|
||||
"emphasis": {"label": {"show": True, "fontSize": 16, "fontWeight": "bold"}},
|
||||
"data": [],
|
||||
"roseType": "area" if 'rose' in chart_type else None
|
||||
}],
|
||||
"color": colors
|
||||
}
|
||||
elif chart_type.startswith('bar'):
|
||||
# 柱状图
|
||||
is_horizontal = 'horizontal' in chart_type
|
||||
return {
|
||||
"title": {"text": title_text, "left": "center"},
|
||||
"tooltip": {"trigger": "axis"},
|
||||
"legend": {"bottom": 0},
|
||||
"xAxis": [{"type": "value" if is_horizontal else "category", "data": []}],
|
||||
"yAxis": [{"type": "category" if is_horizontal else "value", "data": []}],
|
||||
"series": [{"type": "bar", "data": [], "itemStyle": {"color": colors[0]}}],
|
||||
"color": colors
|
||||
}
|
||||
elif chart_type.startswith('line'):
|
||||
# 折线图
|
||||
smooth = 'smooth' in chart_type
|
||||
area_style = {"opacity": 0.3} if 'area' in chart_type else None
|
||||
return {
|
||||
"title": {"text": title_text, "left": "center"},
|
||||
"tooltip": {"trigger": "axis"},
|
||||
"legend": {"bottom": 0},
|
||||
"xAxis": [{"type": "category", "data": []}],
|
||||
"yAxis": [{"type": "value"}],
|
||||
"series": [{"type": "line", "data": [], "smooth": smooth, "areaStyle": area_style}],
|
||||
"color": colors
|
||||
}
|
||||
elif chart_type.startswith('gauge'):
|
||||
# 仪表盘
|
||||
return {
|
||||
"title": {"text": title_text, "left": "center"},
|
||||
"tooltip": {"formatter": "{b}: {c}"},
|
||||
"series": [{"type": "gauge", "data": [], "detail": {"formatter": "{value}"}}]
|
||||
}
|
||||
elif chart_type.startswith('radar'):
|
||||
# 雷达图
|
||||
return {
|
||||
"title": {"text": title_text, "left": "center"},
|
||||
"tooltip": {},
|
||||
"legend": {"bottom": 0},
|
||||
"radar": {"indicator": []},
|
||||
"series": [{"type": "radar", "data": []}],
|
||||
"color": colors
|
||||
}
|
||||
elif chart_type.startswith('funnel'):
|
||||
# 漏斗图
|
||||
return {
|
||||
"title": {"text": title_text, "left": "center"},
|
||||
"tooltip": {"trigger": "item", "formatter": "{b}: {c}"},
|
||||
"legend": {"bottom": 0},
|
||||
"series": [{"type": "funnel", "data": [], "left": "10%", "width": "80%"}],
|
||||
"color": colors
|
||||
}
|
||||
else:
|
||||
# 通用默认
|
||||
return {
|
||||
"title": {"text": title_text, "left": "center"},
|
||||
"tooltip": {},
|
||||
"series": [{"type": "bar", "data": []}],
|
||||
"color": colors
|
||||
}
|
||||
|
||||
|
||||
# ====== 报表保存 ======
|
||||
|
||||
def build_base_save_data(report_id, designer_obj, rows, cols, styles, merges, chart_list=None, page_size=None, area=None, data_rect_width=None):
|
||||
"""构造报表保存请求体"""
|
||||
return {
|
||||
"designerObj": json.dumps(designer_obj, ensure_ascii=False),
|
||||
"name": "sheet1",
|
||||
"freeze": "A1",
|
||||
"freezeLineColor": "rgb(185, 185, 185)",
|
||||
"rows": rows,
|
||||
"cols": cols,
|
||||
"styles": styles,
|
||||
"merges": merges,
|
||||
"validations": [],
|
||||
"autofilter": {},
|
||||
"dbexps": [],
|
||||
"dicts": [],
|
||||
"loopBlockList": [],
|
||||
"zonedEditionList": [],
|
||||
"fixedPrintHeadRows": [],
|
||||
"fixedPrintTailRows": [],
|
||||
"hiddenCells": [],
|
||||
"submitHandlers": [],
|
||||
"rpbar": {"show": True, "pageSize": str(page_size) if page_size else "", "btnList": []},
|
||||
"fillFormToolbar": {"show": True, "btnList": ["save", "subTable_add", "verify", "subTable_del", "print", "close", "first", "prev", "next", "paging", "total", "last", "exportPDF", "exportExcel", "exportWord"]},
|
||||
"hidden": {"rows": [], "cols": [], "conditions": {"rows": {}, "cols": {}}},
|
||||
"fillFormInfo": {"layout": {"direction": "horizontal", "width": 200, "height": 45}},
|
||||
"recordSubTableOrCollection": {"group": [], "record": [], "range": []},
|
||||
"displayConfig": {},
|
||||
"printConfig": {"paper": "A4", "width": 210, "height": 297, "definition": 1, "isBackend": False, "marginX": 10, "marginY": 10, "layout": "portrait", "printCallBackUrl": ""},
|
||||
"querySetting": {"izOpenQueryBar": False, "izDefaultQuery": True},
|
||||
"queryFormSetting": {"useQueryForm": False, "dbKey": "", "idField": ""},
|
||||
"area": area if area is not None else False,
|
||||
"chartList": chart_list or [],
|
||||
"background": False,
|
||||
"dataRectWidth": data_rect_width if data_rect_width is not None else 700,
|
||||
"excel_config_id": report_id,
|
||||
"pyGroupEngine": False,
|
||||
"isViewContentHorizontalCenter": False,
|
||||
"fillFormStyle": "default",
|
||||
"sheetId": "default",
|
||||
"sheetName": "默认Sheet",
|
||||
"sheetOrder": "0"
|
||||
}
|
||||
|
||||
|
||||
def save_report(api_base, token, save_data):
|
||||
"""调用报表保存接口"""
|
||||
result = api_request(api_base, token, '/jmreport/save', save_data)
|
||||
return result
|
||||
|
||||
|
||||
# ====== 主流程 ======
|
||||
|
||||
def create_report(api_base, token, config):
|
||||
"""创建新报表"""
|
||||
report_id = gen_id()
|
||||
report_code = str(int(time.time() * 1000))
|
||||
report_name = config['reportName']
|
||||
|
||||
print(f'\n{"=" * 50}')
|
||||
print(f'创建积木报表: {report_name}')
|
||||
print(f'{"=" * 50}')
|
||||
|
||||
# Step 1: 创建空报表
|
||||
designer_obj = {
|
||||
"id": report_id, "code": report_code, "name": report_name,
|
||||
"type": "0", "template": 0, "delFlag": 0, "viewCount": 0,
|
||||
"updateCount": 0, "submitForm": config.get('submitForm', 0),
|
||||
"reportName": report_name
|
||||
}
|
||||
|
||||
empty_save = build_base_save_data(report_id, designer_obj, {"len": 200}, {"len": 100}, [], [])
|
||||
print('\n[1/4] 创建空报表...')
|
||||
r = save_report(api_base, token, empty_save)
|
||||
print(f' 结果: success={r.get("success")}')
|
||||
if not r.get('success'):
|
||||
print(f' 失败: {r.get("message")}')
|
||||
return None
|
||||
|
||||
# Step 2: 解析SQL并保存数据集
|
||||
print('\n[2/4] 解析SQL并保存数据集...')
|
||||
dataset_ids = {}
|
||||
for ds in config.get('datasets', []):
|
||||
db_code = ds['dbCode']
|
||||
sql = ds.get('dbDynSql', '')
|
||||
db_source = ds.get('dbSource', '')
|
||||
print(f' 解析数据集 [{db_code}]: {sql[:60]}...' if len(sql) > 60 else f' 解析数据集 [{db_code}]: {sql}')
|
||||
|
||||
field_list, param_list = parse_sql_fields(api_base, token, sql, db_source)
|
||||
if not field_list:
|
||||
print(f' 警告: 数据集 [{db_code}] 字段为空')
|
||||
continue
|
||||
|
||||
ds_id = save_dataset(api_base, token, report_id, ds, field_list, param_list)
|
||||
if ds_id:
|
||||
dataset_ids[db_code] = ds_id
|
||||
print(f' 数据集 [{db_code}] 保存成功, id={ds_id}')
|
||||
else:
|
||||
print(f' 数据集 [{db_code}] 保存失败')
|
||||
|
||||
# Step 3: 构造布局
|
||||
print('\n[3/4] 构造报表布局...')
|
||||
layout = config.get('layout', 'table_only')
|
||||
table_config = config.get('table')
|
||||
chart_config = config.get('chart')
|
||||
|
||||
all_rows = {"len": 200}
|
||||
all_merges = []
|
||||
chart_list = []
|
||||
col_count = len(table_config['columns']) if table_config else 6
|
||||
|
||||
if layout == 'chart_top' and chart_config and table_config:
|
||||
# 图表在上,数据表格在下(避免列表展开与图表冲突)
|
||||
chart_db_id = dataset_ids.get(chart_config['datasetCode'], '')
|
||||
chart_rows, chart_list, next_row = build_chart_rows(
|
||||
chart_config, chart_db_id,
|
||||
start_row=1, col_start=1, col_end=col_count
|
||||
)
|
||||
all_rows.update(chart_rows)
|
||||
|
||||
# 分隔行
|
||||
all_rows[str(next_row)] = {"cells": {}, "height": 10}
|
||||
next_row += 1
|
||||
|
||||
# 数据表格
|
||||
table_rows, table_merges, _ = build_table_rows(table_config, start_row=next_row)
|
||||
all_rows.update(table_rows)
|
||||
all_merges.extend(table_merges)
|
||||
print(f' 布局: 图表在上(rows 1-10) + 数据表(rows {next_row}+)')
|
||||
|
||||
elif layout == 'chart_bottom' and chart_config and table_config:
|
||||
# 数据表格在上,图表在下
|
||||
# 图表虚拟行必须在数据展开区域之后,否则预览会重叠
|
||||
table_rows, table_merges, next_row = build_table_rows(table_config, start_row=1)
|
||||
all_rows.update(table_rows)
|
||||
all_merges.extend(table_merges)
|
||||
|
||||
# data_binding_row = next_row - 1 (数据绑定行)
|
||||
# 数据展开后最多占 pageSize 行,图表需在展开区域之后
|
||||
page_size = config.get('pageSize', 10)
|
||||
gap = config.get('gap', 1) # 默认1行间距,更紧凑
|
||||
data_binding_row = next_row - 1
|
||||
chart_start = data_binding_row + page_size + gap
|
||||
|
||||
chart_db_id = dataset_ids.get(chart_config['datasetCode'], '')
|
||||
chart_rows, chart_list, chart_end_row = build_chart_rows(
|
||||
chart_config, chart_db_id,
|
||||
start_row=chart_start, col_start=1, col_end=col_count
|
||||
)
|
||||
all_rows.update(chart_rows)
|
||||
|
||||
# 添加分页符行(自动触发滚动条计算)
|
||||
# 分页符放在图表下方约3行的位置(确保在数据展开区域之外)
|
||||
# 使用多个空格作为分页符,避免显示"1"
|
||||
pagination_row = chart_start + page_size + 3
|
||||
all_rows[str(pagination_row)] = {"cells": {"1": {"text": " "}}}
|
||||
# 确保 len 足够大
|
||||
if pagination_row > all_rows.get("len", 200):
|
||||
all_rows["len"] = pagination_row + 10
|
||||
|
||||
print(f' 布局: 数据表(rows 1-{next_row - 1}) + 间距({gap}行) + 图表(row {chart_start}+) + 分页符(row {pagination_row})')
|
||||
print(f' pageSize={page_size}, 图表在数据展开区域之后')
|
||||
|
||||
elif layout == 'chart_right' and chart_config and table_config:
|
||||
# 数据表格在左,图表在右
|
||||
table_rows, table_merges, next_row = build_table_rows(table_config, start_row=1)
|
||||
all_rows.update(table_rows)
|
||||
all_merges.extend(table_merges)
|
||||
|
||||
chart_db_id = dataset_ids.get(chart_config['datasetCode'], '')
|
||||
chart_col_start = col_count + 2 # 留1列间距
|
||||
chart_col_end = chart_col_start + 5
|
||||
chart_rows, chart_list, _ = build_chart_rows(
|
||||
chart_config, chart_db_id,
|
||||
start_row=1, col_start=chart_col_start, col_end=chart_col_end
|
||||
)
|
||||
# 合并chart_rows到all_rows(同行不同列)
|
||||
for row_key, row_val in chart_rows.items():
|
||||
if row_key in all_rows and row_key != "len":
|
||||
all_rows[row_key]["cells"].update(row_val["cells"])
|
||||
else:
|
||||
all_rows[row_key] = row_val
|
||||
print(f' 布局: 数据表(cols 1-{col_count}) + 图表(cols {chart_col_start}-{chart_col_end})')
|
||||
|
||||
elif layout == 'chart_only' and chart_config:
|
||||
# 仅图表
|
||||
chart_db_id = dataset_ids.get(chart_config['datasetCode'], '')
|
||||
chart_rows, chart_list, _ = build_chart_rows(
|
||||
chart_config, chart_db_id,
|
||||
start_row=1, col_start=1, col_end=6
|
||||
)
|
||||
all_rows.update(chart_rows)
|
||||
print(f' 布局: 仅图表(row 1)')
|
||||
|
||||
elif table_config:
|
||||
# 仅数据表格(默认)
|
||||
table_rows, table_merges, _ = build_table_rows(table_config, start_row=1)
|
||||
all_rows.update(table_rows)
|
||||
all_merges.extend(table_merges)
|
||||
print(f' 布局: 仅数据表')
|
||||
|
||||
else:
|
||||
print(' 错误: 未配置 table 或 chart')
|
||||
return None
|
||||
|
||||
# 构造列宽
|
||||
cols = build_cols(table_config['columns']) if table_config else {"len": 100}
|
||||
|
||||
# 计算 dataRectWidth(列宽总和)
|
||||
total_width = sum(col.get('width', 100) for col in cols.values() if isinstance(col, dict))
|
||||
data_rect_width = total_width if total_width > 0 else 700
|
||||
|
||||
# 计算 area(内容边界区域)
|
||||
# area.sri/eri = 内容区域的起始/结束行(UI行号,从1开始)
|
||||
# area.sci/eci = 内容区域的起始/结束列
|
||||
# area.width/height = 内容区域的总像素宽高
|
||||
title_h = 40
|
||||
header_h = 34
|
||||
row_h = 25 # 默认行高
|
||||
chart_h = int(chart_config.get('height', 350)) if chart_config else 0
|
||||
|
||||
if layout == 'chart_bottom' and chart_config and table_config:
|
||||
# 设置 area 为 false,让系统自动计算滚动高度
|
||||
# 需要在图表底部添加分页符行,系统才能正确计算
|
||||
area = False
|
||||
elif layout == 'chart_top' and chart_config and table_config:
|
||||
area = {
|
||||
"sri": 1,
|
||||
"sci": 1,
|
||||
"eri": next_row - 1,
|
||||
"eci": col_count,
|
||||
"width": data_rect_width,
|
||||
"height": chart_h + 10 + title_h + header_h + row_h * 2
|
||||
}
|
||||
elif layout == 'chart_right' and chart_config and table_config:
|
||||
chart_w = int(chart_config.get('width', 650))
|
||||
area = {
|
||||
"sri": 1,
|
||||
"sci": 1,
|
||||
"eri": next_row - 1,
|
||||
"eci": chart_col_end,
|
||||
"width": data_rect_width + chart_w,
|
||||
"height": title_h + header_h + row_h * 2
|
||||
}
|
||||
elif layout == 'chart_only' and chart_config:
|
||||
area = {
|
||||
"sri": 1,
|
||||
"sci": 1,
|
||||
"eri": 1,
|
||||
"eci": col_count,
|
||||
"width": data_rect_width,
|
||||
"height": chart_h
|
||||
}
|
||||
else:
|
||||
area = {
|
||||
"sri": 1,
|
||||
"sci": 1,
|
||||
"eri": next_row - 1,
|
||||
"eci": col_count,
|
||||
"width": data_rect_width,
|
||||
"height": title_h + header_h + row_h * 2
|
||||
}
|
||||
|
||||
# Step 4: 保存完整报表
|
||||
print('\n[4/4] 保存报表设计...')
|
||||
designer_obj["updateCount"] = 1
|
||||
save_data = build_base_save_data(
|
||||
report_id, designer_obj, all_rows, cols,
|
||||
config.get('styles', DEFAULT_STYLES), all_merges, chart_list,
|
||||
page_size=config.get('pageSize'),
|
||||
area=area,
|
||||
data_rect_width=data_rect_width
|
||||
)
|
||||
r = save_report(api_base, token, save_data)
|
||||
print(f' 结果: success={r.get("success")}')
|
||||
|
||||
if r.get('success'):
|
||||
print(f'\n{"=" * 50}')
|
||||
print(f'报表创建成功!')
|
||||
print(f' 报表ID: {report_id}')
|
||||
print(f' 报表名称: {report_name}')
|
||||
print(f' 预览地址: {api_base}/jmreport/view/{report_id}')
|
||||
print(f'{"=" * 50}')
|
||||
return report_id
|
||||
else:
|
||||
print(f' 保存失败: {r.get("message")}')
|
||||
return None
|
||||
|
||||
|
||||
def edit_report(api_base, token, config):
|
||||
"""编辑已有报表"""
|
||||
report_id = config['reportId']
|
||||
print(f'\n{"=" * 50}')
|
||||
print(f'编辑积木报表: reportId={report_id}')
|
||||
print(f'{"=" * 50}')
|
||||
|
||||
# 获取现有报表
|
||||
print('\n[1/3] 获取现有报表...')
|
||||
r = api_request(api_base, token, f'/jmreport/get/{report_id}', method='GET')
|
||||
if not r.get('success'):
|
||||
print(f' 获取失败: {r.get("message")}')
|
||||
return None
|
||||
existing = r['result']
|
||||
print(f' 报表名称: {existing.get("name")}')
|
||||
|
||||
# 获取现有数据集
|
||||
print('\n[2/3] 获取现有数据集...')
|
||||
tree_r = api_request(api_base, token, f'/jmreport/field/tree/{report_id}', method='GET')
|
||||
if tree_r.get('success') and tree_r.get('result'):
|
||||
for ds in tree_r['result']:
|
||||
print(f' 已有数据集: [{ds.get("dbCode")}] {ds.get("dbChName")}')
|
||||
|
||||
# 添加新数据集
|
||||
dataset_ids = {}
|
||||
for ds in config.get('addDatasets', []):
|
||||
db_code = ds['dbCode']
|
||||
sql = ds.get('dbDynSql', '')
|
||||
db_source = ds.get('dbSource', '')
|
||||
print(f' 新增数据集 [{db_code}]...')
|
||||
|
||||
field_list, param_list = parse_sql_fields(api_base, token, sql, db_source)
|
||||
ds_id = save_dataset(api_base, token, report_id, ds, field_list, param_list)
|
||||
if ds_id:
|
||||
dataset_ids[db_code] = ds_id
|
||||
print(f' 数据集 [{db_code}] 保存成功, id={ds_id}')
|
||||
|
||||
# 如果需要更新报表设计(jsonStr),在此处理
|
||||
if config.get('table') or config.get('chart'):
|
||||
print('\n[3/3] 更新报表设计...')
|
||||
# 重新构造完整布局
|
||||
layout = config.get('layout', 'chart_top')
|
||||
table_config = config.get('table')
|
||||
chart_config = config.get('chart')
|
||||
|
||||
all_rows = {"len": 200}
|
||||
all_merges = []
|
||||
chart_list = []
|
||||
col_count = len(table_config['columns']) if table_config else 6
|
||||
|
||||
if layout == 'chart_top' and chart_config and table_config:
|
||||
chart_db_id = dataset_ids.get(chart_config['datasetCode'], chart_config.get('dataId', ''))
|
||||
chart_rows, chart_list, next_row = build_chart_rows(
|
||||
chart_config, chart_db_id,
|
||||
start_row=1, col_start=1, col_end=col_count
|
||||
)
|
||||
all_rows.update(chart_rows)
|
||||
all_rows[str(next_row)] = {"cells": {}, "height": 10}
|
||||
next_row += 1
|
||||
table_rows, table_merges, _ = build_table_rows(table_config, start_row=next_row)
|
||||
all_rows.update(table_rows)
|
||||
all_merges.extend(table_merges)
|
||||
|
||||
cols = build_cols(table_config['columns']) if table_config else {"len": 100}
|
||||
|
||||
designer_obj = {
|
||||
"id": report_id,
|
||||
"code": existing.get('code', ''),
|
||||
"name": config.get('reportName', existing.get('name', '')),
|
||||
"type": existing.get('type', '0'),
|
||||
"template": existing.get('template', 0),
|
||||
"delFlag": 0,
|
||||
"viewCount": existing.get('viewCount', 0),
|
||||
"updateCount": (existing.get('updateCount') or 0) + 1,
|
||||
"submitForm": existing.get('submitForm', 0),
|
||||
"reportName": config.get('reportName', existing.get('name', ''))
|
||||
}
|
||||
|
||||
save_data = build_base_save_data(
|
||||
report_id, designer_obj, all_rows, cols,
|
||||
config.get('styles', DEFAULT_STYLES), all_merges, chart_list
|
||||
)
|
||||
r = save_report(api_base, token, save_data)
|
||||
print(f' 结果: success={r.get("success")}')
|
||||
|
||||
print(f'\n编辑完成!')
|
||||
print(f' 预览地址: {api_base}/jmreport/view/{report_id}')
|
||||
return report_id
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='积木报表 (JiMu Report) 创建/编辑工具')
|
||||
parser.add_argument('--api-base', required=True, help='JeecgBoot 后端地址')
|
||||
parser.add_argument('--token', required=True, help='X-Access-Token')
|
||||
parser.add_argument('--config', required=True, help='配置文件路径 (JSON)')
|
||||
args = parser.parse_args()
|
||||
|
||||
with open(args.config, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
|
||||
action = config.get('action', 'create')
|
||||
|
||||
if action == 'create':
|
||||
create_report(args.api_base, args.token, config)
|
||||
elif action == 'edit':
|
||||
edit_report(args.api_base, args.token, config)
|
||||
else:
|
||||
print(f'未知操作类型: {action}')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user