签入版本
This commit is contained in:
@@ -0,0 +1,296 @@
|
||||
:::tip 本章概要
|
||||
代码生成器模板的 Jinja2 语法说明,包括模板变量、字段属性和自定义模板的开发方法。
|
||||
:::
|
||||
|
||||
# 自定义模板
|
||||
|
||||
模板文件位于 `src/modules/generator/templates/`,使用 Jinja2 语法。修改模板后重新生成即可生效。
|
||||
|
||||
## 模板目录结构
|
||||
|
||||
```
|
||||
templates/
|
||||
├── __init__.py.tpl # 模块初始化
|
||||
├── models.py.tpl # ORM 模型
|
||||
├── schemas.py.tpl # Pydantic 表单
|
||||
├── repository.py.tpl # 数据访问层
|
||||
├── service.py.tpl # 业务逻辑层
|
||||
├── endpoint.py.tpl # HTTP 端点
|
||||
├── ui/ # 普通分页列表模板
|
||||
│ ├── index.vue.tpl # 主页面
|
||||
│ ├── edit.vue.tpl # 编辑弹窗
|
||||
│ ├── detail.vue.tpl # 详情弹窗
|
||||
│ ├── columns.ts.tpl # 表格列定义
|
||||
│ ├── querySchemas.ts.tpl # 搜索表单 Schema
|
||||
│ └── api.ts.tpl # API 请求封装
|
||||
└── ui2/ # 树状列表模板
|
||||
├── index.vue.tpl # 树形主页面
|
||||
├── edit.vue.tpl # 树形编辑弹窗
|
||||
└── detail.vue.tpl # 树形详情弹窗
|
||||
```
|
||||
|
||||
## ui/ vs ui2/ 切换逻辑
|
||||
|
||||
当表中存在 `parent_id` 或 `pid` 字段时,自动切换为 `ui2/` 树形模板:
|
||||
|
||||
```
|
||||
字段列表中是否有 parent_id / pid?
|
||||
├── 是 → 使用 ui2/ 模板(3 个组件 + 1 个 API)
|
||||
└── 否 → 使用 ui/ 模板(5 个组件 + 1 个 API)
|
||||
```
|
||||
|
||||
两者共用 `ui/api.ts.tpl` 生成 API 接口文件。
|
||||
|
||||
## 模板变量
|
||||
|
||||
### 基础变量
|
||||
|
||||
| 变量 | 类型 | 说明 | 示例 |
|
||||
|:----|:----|:----|:----|
|
||||
| `app_name` | str | 模块名 | `example` |
|
||||
| `module_comment` | str | 中文名 | `案例` |
|
||||
| `module_comment_py` | str | 转义后中文名 | `案例` |
|
||||
| `model_class_name` | str | 类名 | `Example` |
|
||||
| `permission_prefix` | str | 权限前缀 | `sys:example` |
|
||||
| `display_field` | str | 显示字段 | `name` |
|
||||
| `sort_field` | str | 排序字段 | `sort` |
|
||||
|
||||
### 功能标识
|
||||
|
||||
| 变量 | 类型 | 说明 |
|
||||
|:----|:----|:----|
|
||||
| `has_sort` | bool | 有排序字段 |
|
||||
| `has_status` | bool | 有状态字段 |
|
||||
| `has_status_route` | bool | 生成状态路由 |
|
||||
| `has_image_field` | bool | 有图片字段 |
|
||||
| `has_rich_text_field` | bool | 有富文本字段 |
|
||||
| `serialize_maps` | dict | 枚举映射 `{field_name: dict_code}` |
|
||||
|
||||
### 字段列表
|
||||
|
||||
| 变量 | 类型 | 说明 |
|
||||
|:----|:----|:----|
|
||||
| `fields` | list | 全部字段 |
|
||||
| `form_fields` | list | 表单字段(`in_form=True`) |
|
||||
| `filter_fields` | list | 精确匹配字段 |
|
||||
| `like_fields` | list | 模糊查询字段 |
|
||||
|
||||
### 前端专用变量
|
||||
|
||||
| 变量 | 类型 | 说明 |
|
||||
|:----|:----|:----|
|
||||
| `module_name` | str | 驼峰模块名 |
|
||||
| `model_class_name_camel` | str | 驼峰类名(首字母小写) |
|
||||
| `list_fields` | list | 列表展示字段 |
|
||||
| `searchable_fields` | list | 可搜索字段 |
|
||||
| `editable_fields` | list | 可编辑字段 |
|
||||
| `is_tree_structure` | bool | 是否树形结构 |
|
||||
| `parent_id_field` | str | 父级字段名 |
|
||||
| `route_prefix` | str | 路由前缀 |
|
||||
| `api_path` | str | API 路径 |
|
||||
| `has_search_form` | bool | 显示搜索表单 |
|
||||
| `show_selection` | bool | 显示复选框 |
|
||||
| `action_column_width` | int | 操作列宽度 |
|
||||
|
||||
## 字段对象属性
|
||||
|
||||
`fields` 列表中每个字段对象包含以下属性:
|
||||
|
||||
### 基础属性
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|:----|:----|:----|
|
||||
| `name` | str | 字段名(snake_case) |
|
||||
| `camel_name` | str | 驼峰字段名 |
|
||||
| `comment` | str | 字段注释 |
|
||||
| `py_comment` | str | 转义后注释(可嵌入 Python 字符串) |
|
||||
| `label` | str | 清洗后的注释(去掉选项说明) |
|
||||
|
||||
### 类型属性
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|:----|:----|:----|
|
||||
| `db_type` | str | SQLAlchemy 类型 |
|
||||
| `db_args` | str | 类型参数(如 `"100"`) |
|
||||
| `form_type` | str | Pydantic 类型 |
|
||||
| `type_display` | str | 中文类型名 |
|
||||
| `max_length` | int | 最大长度 |
|
||||
|
||||
### 约束属性
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|:----|:----|:----|
|
||||
| `nullable` | bool | 是否可空 |
|
||||
| `required` | bool | 是否必填 |
|
||||
| `is_unique` | bool | 是否唯一 |
|
||||
| `default` | any | 默认值 |
|
||||
| `default_repr` | str | 默认值 Python 表示 |
|
||||
|
||||
### 功能标识
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|:----|:----|:----|
|
||||
| `is_image` | bool | 图片字段 |
|
||||
| `is_rich_text` | bool | 富文本字段 |
|
||||
| `is_status` | bool | 状态字段 |
|
||||
| `is_sort` | bool | 排序字段 |
|
||||
|
||||
### 展示控制
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|:----|:----|:----|
|
||||
| `in_form` | bool | 出现在表单中 |
|
||||
| `in_list` | bool | 出现在列表中 |
|
||||
| `editable` | bool | 可编辑 |
|
||||
| `filterable` | bool | 作为查询条件 |
|
||||
| `filter_type` | str | 筛选类型 |
|
||||
| `searchable` | bool | 可搜索 |
|
||||
|
||||
### 选项属性
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|:----|:----|:----|
|
||||
| `choices` | list | `[(value, label), ...]` |
|
||||
| `dict_code` | str | 数据字典编码 |
|
||||
| `min_value` | int | 最小值 |
|
||||
| `max_value` | int | 最大值 |
|
||||
|
||||
### 前端专用
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|:----|:----|:----|
|
||||
| `purpose` | str | 用途说明 |
|
||||
| `type` | str | 配置字段类型名(CharField/IntegerField/...) |
|
||||
| `choices[].value` | any | 选项值 |
|
||||
| `choices[].label` | str | 选项文本 |
|
||||
| `choices[].type` | str | 标签颜色(success/danger/warning/info) |
|
||||
|
||||
## 自定义模板示例
|
||||
|
||||
### 示例 1:为所有字段添加索引
|
||||
|
||||
修改 `templates/models.py.tpl`:
|
||||
|
||||
```jinja2
|
||||
{% if f.db_type == 'String' %}
|
||||
{{ f.name }} = Column(String({{ f.db_args or 255 }}), nullable={{ 'True' if f.nullable else 'False' }}, index=True, comment="{{ f.comment }}")
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
### 示例 2:自定义 Service 钩子
|
||||
|
||||
修改 `templates/service.py.tpl`,在 Service 类中添加钩子方法:
|
||||
|
||||
```jinja2
|
||||
class {{ model_class_name }}Service(BaseService[{{ model_class_name }}]):
|
||||
# ... 差异点声明 ...
|
||||
|
||||
{% if has_status %}
|
||||
def _before_delete(self, ids) -> Optional[str]:
|
||||
"""删除前校验:检查是否有关联数据"""
|
||||
# TODO: 添加自定义校验逻辑
|
||||
return None
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
### 示例 3:自定义前端表格列宽
|
||||
|
||||
修改 `templates/ui/columns.ts.tpl`:
|
||||
|
||||
```jinja2
|
||||
{% for f in list_fields %}
|
||||
{
|
||||
label: '{{ f.label }}',
|
||||
prop: '{{ f.camel_name }}',
|
||||
minWidth: {{ 80 + (f.max_length or 100) // 3 }}, {# 动态列宽 #}
|
||||
},
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
## 新增模板
|
||||
|
||||
如需生成额外文件(如测试文件),按以下步骤操作:
|
||||
|
||||
### 第 1 步:创建模板文件
|
||||
|
||||
在 `templates/` 下新建 `test.py.tpl`:
|
||||
|
||||
```jinja2
|
||||
# tests/test_{{ app_name }}.py
|
||||
import pytest
|
||||
from modules.{{ app_name }}.service import {{ app_name }}_service
|
||||
|
||||
|
||||
class Test{{ model_class_name }}:
|
||||
def test_get_page(self, client):
|
||||
response = client.get("/api/v1/{{ app_name }}/page")
|
||||
assert response.status_code == 200
|
||||
```
|
||||
|
||||
### 第 2 步:注册到 BACKEND_TEMPLATES
|
||||
|
||||
在 `code_generator.py` 中添加:
|
||||
|
||||
```python
|
||||
BACKEND_TEMPLATES = [
|
||||
'__init__.py.tpl',
|
||||
'models.py.tpl',
|
||||
'schemas.py.tpl',
|
||||
'repository.py.tpl',
|
||||
'service.py.tpl',
|
||||
'test.py.tpl', # 新增
|
||||
]
|
||||
```
|
||||
|
||||
### 第 3 步:调整输出路径(可选)
|
||||
|
||||
如果需要输出到非默认目录,修改 `generate()` 方法中的路径逻辑。
|
||||
|
||||
## Jinja2 语法速查
|
||||
|
||||
```jinja2
|
||||
{# 注释 #}
|
||||
|
||||
{# 变量输出 #}
|
||||
{{ app_name }}
|
||||
{{ f.name | upper }}
|
||||
|
||||
{# 条件判断 #}
|
||||
{% if has_status %}
|
||||
# 有状态字段
|
||||
{% elif has_sort %}
|
||||
# 有排序字段
|
||||
{% else %}
|
||||
# 都没有
|
||||
{% endif %}
|
||||
|
||||
{# 循环 #}
|
||||
{% for f in fields %}
|
||||
{{ f.name }}
|
||||
{% endfor %}
|
||||
|
||||
{# 循环 + 条件过滤 #}
|
||||
{% for f in fields if f.is_image %}
|
||||
{{ f.name }}
|
||||
{% endfor %}
|
||||
|
||||
{# 循环变量 #}
|
||||
{% for f in fields %}
|
||||
{{ loop.index }} {# 从 1 开始 #}
|
||||
{{ loop.first }} {# 是否第一个 #}
|
||||
{{ loop.last }} {# 是否最后一个 #}
|
||||
{% endfor %}
|
||||
|
||||
{# 三元表达式 #}
|
||||
{{ 'True' if f.nullable else 'False' }}
|
||||
|
||||
{# 字符串拼接 #}
|
||||
{{ app_name }}_repo
|
||||
|
||||
{# 默认值 #}
|
||||
{{ f.db_args or 255 }}
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
模板使用 Jinja2 语法,每个模板对应一个生成文件。模板变量包括模块名、表名、字段列表等,字段属性包含名称、类型、注释、是否可搜索等。可通过修改模板自定义生成代码的风格和结构。
|
||||
Reference in New Issue
Block a user