468 lines
13 KiB
Markdown
468 lines
13 KiB
Markdown
# `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
|
||
> ✅ 审核状态:已完成初稿
|
||
> 💬 如需扩展模板字段或增加校验逻辑,请联系开发组。 |