# `xls2crud.py` 技术文档 > **工具名称**:`xls2crud.py` > **功能描述**:从 Excel 模型文件(`.xlsx`)自动生成基于模板的 CRUD 前后端界面代码,支持数据浏览器、新增、编辑、删除、查询及关联子表操作。 > **适用场景**:快速构建基于数据库模型的 Web 应用前端 UI 和后端接口描述。 --- ## 目录 - [1. 简介](#1-简介) - [2. 依赖说明](#2-依赖说明) - [3. 使用方式](#3-使用方式) - [4. 核心函数说明](#4-核心函数说明) - [`build_dbdesc`](#builddbdescmodels_dir--dict) - [`subtable2toolbar`](#subtable2toolbardesc) - [`build_crud_ui`](#build_crud_uicrud_data-dict-dbdesc-dict) - [`build_table_crud_ui`](#build_table_crud_uiuidir-str-desc-dict---none) - [`field_list`](#field_listdesc-dict---list) - [`get_code_desc`](#get_code_desffield-dict-desc-dict---dict) - [`setup_ui_info`](#setup_ui_infofielddict-confidential_fieldslist---dict) - [`construct_get_data_sql`](#construct_get_data_sqldesc-dict---str) - 其他辅助函数 - [5. 模板系统与输出结构](#5-模板系统与输出结构) - [6. 配置文件格式 (`json_file`)](#6-配置文件格式-json_file) - [7. 输出目录结构示例](#7-输出目录结构示例) - [8. 注意事项与限制](#8-注意事项与限制) --- ## 1. 简介 本脚本通过读取 `.xlsx` 表格定义的数据模型和 JSON 配置文件,自动为指定数据表生成完整的 CRUD 界面组件: - 数据浏览页(`index.ui`) - 新增表单(`add_*.dspy`) - 编辑表单(`update_*.dspy`) - 删除逻辑(`delete_*.dspy`) - 数据获取接口(`get_*.dspy`) - 关联字段状态检查(`check_changed.dspy`) 所有输出均基于预设的 Jinja-like 模板渲染,并支持动态参数替换。 --- ## 2. 依赖说明 ```python import os import sys import codecs import json import argparse from appPublic.dictObject import DictObject from xlsxData import xlsxFactory from appPublic.folderUtils import listFile, _mkdir from appPublic.myTE import MyTemplateEngine from tmpls import ( data_browser_tmpl, get_data_tmpl, data_new_tmpl, data_update_tmpl, data_delete_tmpl, check_changed_tmpls ) from appPublic.argsConvert import ArgsConvert ``` ### 外部模块说明 | 模块 | 功能 | |------|------| | `appPublic.dictObject` | 将字典转换为可点式访问的对象(类似 JavaScript 的对象) | | `xlsxData.xlsxFactory` | 解析 `.xlsx` 文件并提取结构化数据 | | `appPublic.folderUtils.listFile`, `_mkdir` | 文件遍历与目录创建工具 | | `appPublic.myTE.MyTemplateEngine` | 轻量级模板引擎,支持变量替换 | | `tmpls.*` | 内置字符串形式的模板内容(HTML/UI/DSPY) | | `appPublic.argsConvert` | 支持 `${var}$` 或 `[[var]]` 形式的变量替换 | --- ## 3. 使用方式 ### 命令行语法 ```bash python xls2crud.py [-m models_dir] [-o output_dir] modulename json_file [...] ``` ### 参数说明 | 参数 | 是否必需 | 说明 | |------|----------|------| | `modulename` | ✅ 是 | 当前模块名,用于数据库上下文解析 | | `json_file` | ✅ 是 | 至少一个 CRUD 配置 JSON 文件路径 | | `-m`, `--models_dir` | ❌ 否 | `.xlsx` 模型文件所在目录(可在 JSON 中覆盖) | | `-o`, `--output_dir` | ❌ 否 | 输出根目录,默认为当前路径 | ### 示例命令 ```bash python xls2crud.py -m ./models -o ./ui_output user_module user_config.json role_config.json ``` --- ## 4. 核心函数说明 ### `build_dbdesc(models_dir: str) -> dict` #### 功能 扫描指定目录下的所有 `.xlsx` 文件,将其解析为统一的数据库描述对象。 #### 输入 - `models_dir`: 字符串或列表,包含模型文件路径 #### 输出 - `dict`: 键为表名(来自 `summary[0].name`),值为该表的完整描述对象(含字段、编码表、子表等) #### 流程 1. 遍历目录中所有 `.xlsx` 文件 2. 使用 `xlsxFactory` 加载每个文件 3. 提取其 `get_data()` 结果 4. 取第一项 summary 的 name 作为表名注册进 `db_desc` --- ### `subtable2toolbar(desc)` #### 功能 将 `desc.subtables` 中定义的子表信息转化为工具栏按钮及弹窗绑定事件。 #### 参数 - `desc`: 当前表的描述对象(DictObject) #### 行为 - 若无 `toolbar`,初始化为空对象 - 为每个子表添加: - 工具栏图标按钮(SVG 图标 + label) - 绑定点击事件打开 PopupWindow - POST 请求跳转至对应 URL(支持相对路径) - 自动映射主键 `id` 和引用控件 `referer_widget` - 支持额外字段映射(via `mapping`) #### 示例生成的 bind 结构 ```json { "wid": "self", "event": "child_table", "actiontype": "urlwidget", "target": "PopupWindow", "popup_options": { "title": "子表标题", "width": "70%", "height": "70%" }, "params_mapping": { "mapping": { "id": "parent_id", "referer_widget": "referer_widget" } }, "options": { "method": "POST", "url": "{{entire_url('../child_table')}}" } } ``` --- ### `build_crud_ui(crud_data: dict, dbdesc: dict)` #### 功能 根据传入的配置和数据库描述,驱动整个 CRUD 界面生成流程。 #### 参数 - `crud_data`: 来自 JSON 文件的配置对象(DictObject) - `dbdesc`: 所有表的元数据集合 #### 步骤 1. 获取目标表名 `tblname` 2. 更新 `desc` 参数(如权限、标题等) 3. 调用 `subtable2toolbar(desc)` 添加子表按钮 4. 如果存在 `relation` 字段,则启用“行选中联动”机制 5. 渲染 `binds` 为 JSON 字符串 6. 调用 `build_table_crud_ui()` 开始生成具体文件 --- ### `build_table_crud_ui(uidir: str, desc: dict) -> None` #### 功能 协调生成所有与当前表相关的 UI 文件。 #### 行为 - 创建输出目录(若不存在) - 生成: - `index.ui`(数据浏览器) - `get_{tblname}.dspy`(数据获取) - 若有外键关系(`desc.relation`): - 仅生成 `check_changed.dspy` - 否则生成标准三件套: - `add_*.dspy` - `update_*.dspy` - `delete_*.dspy` --- ### `field_list(desc: dict) -> list` #### 功能 构造适用于前端展示的字段列表,包含类型、宽度、标签、UI 类型等信息。 #### 逻辑 1. 遍历 `desc.fields` 2. 判断是否是码表字段(在 `codes` 中定义) - 是 → 调用 `get_code_desc` - 否 → 调用 `setup_ui_info` 3. 应用 `browserfields.alters` 进行个性化覆盖 4. 返回最终字段数组 --- ### `get_code_desc(field: dict, desc: dict) -> dict` #### 功能 处理码表字段(如性别、状态),将其转换为下拉选择框或远程数据源字段。 #### 输出字段说明 | 属性 | 说明 | |------|------| | `uitype` | `'code'` 表示码表类型 | | `valueField` / `textField` | 实际值与显示文本字段名 | | `params` | 请求码表数据所需参数(dbname, table, cond 等) | | `dataurl` | 固定为 `/appbase/get_code.dspy` | #### 特性支持 - 支持条件表达式中的变量替换(如 `${dept_id}$`) - 使用 `ArgsConvert` 解析并注入运行时变量 --- ### `setup_ui_info(field:dict, confidential_fields=[]) ->dict` #### 功能 为普通字段设置默认 UI 显示属性。 #### 类型映射规则 | 数据库类型 | `uitype` | 说明 | |-----------|---------|------| | `date` | `date` | 日期输入框 | | `time` | `time` | 时间输入框 | | `int`, `short`, etc. | `int` | 整数输入 | | `float`, `double`, `decimal` | `float` | 浮点数输入 | | `text` | `text` | 多行文本 | | 其他 | `str` | 默认字符串 | | 名称以 `_date` 结尾 | `date` | 智能识别 | | 在 `confidential_fields` 中 | `password` | 密码掩码 | #### 宽度计算 - `cwidth = min(max(length, 4), 18)` - 最小 4,最大 18(单位字符宽) --- ### `construct_get_data_sql(desc: dict) -> str` #### 功能 构建用于获取主表及其码表连接数据的 SQL 查询语句。 #### 分支逻辑 ##### A. 存在外键关联且有码表 生成 LEFT JOIN 查询,判断是否存在外键记录(`has_xxx` 标志位),并带出码表文本。 ```sql SELECT '${param_field}$' AS param_field, CASE WHEN b.param_field IS NULL THEN 0 ELSE 1 END has_param_field, a.value AS code_field, a.text AS code_field_text FROM code_table a LEFT JOIN (SELECT * FROM main_table WHERE param_field = ${param_field}$) b ON a.value = b.code_field; ``` ##### B. 无码表 直接查询主表 + 占位符 `[[filterstr]]` ```sql SELECT * FROM table_name WHERE 1=1 [[filterstr]] ``` ##### C. 有一般码表(非外键) 构建多个 LEFT JOIN 子查询,别名为 `b`, `c`, ...,拼接成完整查询。 ```sql SELECT a.*, b.type_text, c.status_text FROM (SELECT * FROM main WHERE 1=1 [[filterstr]]) a LEFT JOIN (SELECT value AS status, text AS status_text FROM status_codes WHERE 1=1) c ON a.status = c.status; ``` --- ### 其他辅助函数 | 函数 | 功能 | |------|------| | `alter_field(field, desc)` | 使用 `desc.browserfields.alters[name]` 修改特定字段属性 | | `filter_backslash(s)` | 替换字符串中的 `\\/` 为 `/`,防止模板路径错误 | | `build_data_browser(pat, desc)` | 渲染 `data_browser_tmpl` → `index.ui` | | `build_data_new` / `update` / `delete` | 分别生成增删改页面 | | `build_get_data` | 生成数据查询接口 `.dspy` 文件 | | `build_check_changed` | 生成用于检测外键关联变化的特殊接口 | --- ## 5. 模板系统与输出结构 ### 模板来源 所有模板来自 `tmpls` 模块,包括: | 模板变量 | 用途 | |--------|------| | `data_browser_tmpl` | 主列表界面(index.ui) | | `get_data_tmpl` | 数据获取接口 | | `data_new_tmpl` | 新增页面 | | `data_update_tmpl` | 编辑页面 | | `data_delete_tmpl` | 删除确认/执行页面 | | `check_changed_tmpls` | 外键关联状态检查接口 | ### 模板语法支持 - `{{ var }}`:变量插入 - `${ var }$`:环境变量占位符(由 `ArgsConvert` 替换) - `[[ filterstr ]]`:SQL 过滤条件注入点 --- ## 6. 配置文件格式 (`json_file`) ```json { "tblname": "user", "alias": "user_manage", "params": { "title": "用户管理", "pagesize": 20, "confidential_fields": ["password"], "relation": { "param_field": "org_id", "outter_field": "id", "table": "organization" }, "codes": [ { "field": "status", "table": "sys_codes", "valuefield": "c_value", "textfield": "c_text", "cond": "ctype='USER_STATUS'" } ], "subtables": [ { "subtable": "user_role", "title": "角色分配", "field": "user_id", "url": "../user_role", "mapping": {} } ], "toolbar": null, "binds": [], "browserfields": { "alters": { "name": { "label": "姓名", "uitype": "str", "cwidth": 20 } } } }, "models_dir": "./models" } ``` ### 字段说明 | 字段 | 类型 | 说明 | |------|------|------| | `tblname` | string | 主表名(必须存在于 `.xlsx` 中) | | `alias` | string | 输出目录名(可选,默认为 `tblname`) | | `params` | object | 表级配置对象 | | `params.title` | string | 页面标题 | | `params.confidential_fields` | array | 需隐藏显示的字段(变为密码框) | | `params.relation` | object | 外键关联信息(启用 check_changed) | | `params.codes` | array | 码表配置 | | `params.subtables` | array | 子表配置(生成工具栏按钮) | | `params.browserfields.alters` | object | 字段显示定制 | | `models_dir` | string | .xlsx 模型目录(优先级高于命令行) | --- ## 7. 输出目录结构示例 假设命令为: ```bash python xls2crud.py -o ./dist -m ./models user_mod user_conf.json ``` 且 `user_conf.json` 中 `"alias": "users"`,`"tblname": "user"` 则输出结构如下: ``` ./dist/ └── users/ ├── index.ui # 数据浏览器 ├── add_user.dspy # 新增页面 ├── update_user.dspy # 编辑页面 ├── delete_user.dspy # 删除页面 ├── get_user.dspy # 数据获取接口 └── check_changed.dspy # (如果有 relation)状态检查接口 ``` --- ## 8. 注意事项与限制 ### ⚠️ 已知限制 1. **Excel 文件要求** - 必须包含 `summary` 工作表 - 第一行 `summary[0].name` 作为表名 - `fields` 表格需定义字段元信息(name, type, title, length...) 2. **模板路径问题** - 模板内使用 `{{entire_url(...)}}` 构造绝对路径,请确保前端框架支持此函数 3. **SQL 注入防护** - `[[filterstr]]` 不做转义,应由调用端保证安全 4. **并发写入风险** - 多个 JSON 文件同时处理同一张表可能导致文件覆盖 5. **编码要求** - 所有 `.xlsx` 和 `.json` 文件必须为 UTF-8 编码 ### ✅ 最佳实践建议 - 使用 `alias` 区分不同视图的输出路径 - 在 `alters` 中统一设置中文标签和列宽 - 对敏感字段显式加入 `confidential_fields` - 使用 `cond` + 变量实现动态码表过滤 --- ## 版本信息 - 创建时间:未知 - 最后更新:根据代码注释推测近期维护 - 维护者:内部团队(`appPublic`, `xslxDATA` 为私有库) --- > 📝 文档版本:v1.0 > ✅ 审核状态:已完成初稿 > 💬 如需扩展模板字段或增加校验逻辑,请联系开发组。