xls2ddl/aidocs/xls2crud.md
2025-10-12 13:57:44 +08:00

468 lines
13 KiB
Markdown
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.

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