""" JeecgBoot 设计器表单通用创建脚本 ================================= 通过 JSON 配置文件创建/更新设计器表单,避免每次编写大量 Python 代码。 用法: python desform_creator.py --api-base --token --config python desform_creator.py --api-base --token --config --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()