Files
qhmes/.trae/skills/jeecg-desform/scripts/desform_creator.py

262 lines
8.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
JeecgBoot 设计器表单通用创建脚本
=================================
通过 JSON 配置文件创建/更新设计器表单,避免每次编写大量 Python 代码。
用法:
python desform_creator.py --api-base <URL> --token <TOKEN> --config <config.json>
python desform_creator.py --api-base <URL> --token <TOKEN> --config <config.json> --force
参数:
--api-base JeecgBoot 后端地址
--token X-Access-Token
--config JSON 配置文件路径
--force 强制覆盖已存在的表单(默认检测到已存在时退出)
JSON 配置格式:
{
"formName": "工程竣工验收申请表",
"formCode": "eng_completion_acceptance",
"layout": "word", // auto|half|full|word默认 auto
"titleIndex": 0, // 标题字段索引,默认 0第一个非分隔符字段
"fields": [
{"name": "自动编号", "type": "auto-number", "prefix": "GCYS"},
{"name": "条码", "type": "barcode"},
{"name": "工程名称", "type": "input", "required": true},
{"name": "工程类别", "type": "radio", "options": ["土建", "安装"]},
{"name": "开工时间", "type": "date"},
{"name": "工程量清单", "type": "textarea"},
{"name": "图片上传", "type": "imgupload"},
{"name": "定位", "type": "location"},
{"name": "签字", "type": "hand-sign"},
{"name": "---", "type": "divider", "text": "第二部分"},
{"name": "金额", "type": "money", "unit": "万元"},
{"name": "状态", "type": "select", "options": ["启用", "禁用"]},
{"name": "性别", "type": "radio", "dictCode": "sex",
"options": [{"value": "1", "label": ""}, {"value": "2", "label": ""}]}
],
"menuParent": "工程管理", // 可选,生成菜单 SQL 的父菜单名称
"menuIcon": "ant-design:tool-outlined" // 可选,父菜单图标
}
支持的 type 值:
基础: input, textarea, number, integer, money, date, time, switch, slider, rate, color
选择: radio, select, checkbox支持 options + dictCode
系统: select-user, select-depart, phone, email, area-linkage
文件: file-upload, imgupload, hand-sign
高级: auto-number, formula, barcode, location, link-record, link-field
布局: divider, editor, markdown
"""
import argparse
import json
import sys
import os
# 修复 Windows 控制台中文乱码
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
# 自动定位 desform_utils.py
_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
_SKILL_DIR = os.path.dirname(_SCRIPT_DIR)
for _path in [os.getcwd(), _SCRIPT_DIR]:
if os.path.exists(os.path.join(_path, 'desform_utils.py')):
sys.path.insert(0, _path)
break
from desform_utils import *
# ============================================================
# desform_utils 未内置的控件
# ============================================================
def _BARCODE(name, width=100, **kw):
"""条码控件"""
code_type = kw.pop('codeType', 'barcode')
w, k, m = make_widget("barcode", name, "form-barcode", "icon-tiaoma", {
"maxWidth": 180, "codeType": code_type, "sourceModel": "",
"hidden": False, "hiddenOnAdd": False, "fieldNote": "", "autoWidth": width
})
return make_card(w), k, m
def _LOCATION(name, required=False, width=100, **kw):
"""定位控件"""
w, k, m = make_widget("location", name, "form-location", "icon-location", {
"width": "100%", "defaultValue": "", "defaultCurrent": False,
"showLngLat": False, "showMap": False, "disabled": False,
"hidden": False, "hiddenOnAdd": False, "required": required,
"fieldNote": "", "autoWidth": width
}, required=required)
return make_card(w), k, m
# ============================================================
# type → 工厂函数 映射
# ============================================================
_TYPE_MAP = {
# 基础
'input': INPUT,
'textarea': TEXTAREA,
'number': NUMBER,
'integer': INTEGER,
'money': MONEY,
'date': DATE,
'time': TIME,
'switch': SWITCH,
'slider': SLIDER,
'rate': RATE,
'color': COLOR,
# 选择
'radio': RADIO,
'select': SELECT,
'checkbox': CHECKBOX,
# 系统
'select-user': USER,
'select-depart': DEPART,
'phone': PHONE,
'email': EMAIL,
'area-linkage': AREA,
# 文件
'file-upload': FILE,
'imgupload': IMGUPLOAD,
'hand-sign': HANDSIGN,
# 高级
'auto-number': AUTONUMBER,
'formula': FORMULA,
'barcode': _BARCODE,
'location': _LOCATION,
'link-record': LINK_RECORD,
'link-field': LINK_FIELD,
# 布局
'divider': DIVIDER,
'editor': EDITOR,
'markdown': MARKDOWN,
}
# 需要 options 参数的控件类型options 作为第二个位置参数)
_OPTION_TYPES = {'radio', 'select', 'checkbox'}
# 参数名映射JSON key → 函数参数名
_PARAM_MAP = {
'required': 'required',
'width': 'width',
'prefix': 'prefix',
'unit': 'unit',
'placeholder': 'placeholder',
'multiple': 'multiple',
'dictCode': 'dict_code',
'unique': 'unique',
'precision': 'precision',
'fmt': 'fmt',
'codeType': 'codeType',
# formula
'mode': 'mode',
'expression': 'expression',
'decimal': 'decimal',
# link-record
'sourceCode': 'source_code',
'titleField': 'title_field',
'showFields': 'show_fields',
'showMode': 'show_mode',
'showType': 'show_type',
# link-field
'linkRecordKey': 'link_record_key',
'showField': 'show_field',
'fieldType': 'field_type',
'fieldOptions': 'field_options',
}
def build_widget(field_def):
"""根据 JSON 字段定义构建控件 tuple"""
ftype = field_def['type']
name = field_def['name']
factory = _TYPE_MAP.get(ftype)
if not factory:
raise ValueError(f'未知的控件类型: {ftype}')
# 构建关键字参数
kwargs = {}
for json_key, param_name in _PARAM_MAP.items():
if json_key in field_def:
kwargs[param_name] = field_def[json_key]
# divider 特殊处理text 参数
if ftype == 'divider':
text = field_def.get('text', name)
return factory(text)
# 需要 options 的控件
if ftype in _OPTION_TYPES:
options = field_def.get('options', [])
return factory(name, options, **kwargs)
# auto-number 特殊处理
if ftype == 'auto-number':
return factory(name, **kwargs)
# 其余控件name + kwargs
return factory(name, **kwargs)
def main():
parser = argparse.ArgumentParser(description='JeecgBoot 设计器表单通用创建工具')
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 配置文件路径')
parser.add_argument('--force', action='store_true', help='强制覆盖已存在的表单')
args = parser.parse_args()
with open(args.config, 'r', encoding='utf-8') as f:
config = json.load(f)
form_name = config['formName']
form_code = config['formCode']
layout = config.get('layout', 'auto')
title_index = config.get('titleIndex', 0)
init_api(args.api_base, args.token)
# 防覆盖检查
existing_id, _ = get_form_id(form_code)
if existing_id and not args.force:
print(f'[阻止] 表单 {form_code} 已存在 (ID={existing_id})')
print(f'如需覆盖,请加 --force 参数')
sys.exit(1)
# 构建控件列表
fields = config.get('fields', [])
widgets = []
for fd in fields:
widget = build_widget(fd)
widgets.append(widget)
# 创建表单
form_id, title_model = create_form(form_name, form_code, widgets,
title_index=title_index, layout=layout)
print(f'\n{"=" * 50}')
print(f'表单创建成功')
print(f'{"=" * 50}')
print(f' 表单ID: {form_id}')
print(f' 表单名称: {form_name}')
print(f' 表单编码: {form_code}')
print(f' 标题字段: {title_model}')
print(f' 布局风格: {layout}')
# 生成菜单 SQL
menu_parent = config.get('menuParent')
if menu_parent:
menu_icon = config.get('menuIcon', 'ant-design:appstore-outlined')
sql = gen_menu_sql(menu_parent, [
(form_name, form_code, 1),
], icon=menu_icon)
print(f'\n--- 菜单 SQL ---\n{sql}')
if __name__ == '__main__':
main()