Files
rxthinkcmf-wiki/RXThinkCMF_Laravel13_EleVue/zh/generator/templates.md
T
2026-10-08 17:50:29 +08:00

251 lines
11 KiB
Markdown
Raw 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.
# 10.6 自定义模板开发
:::tip 概述
代码生成器使用 PHP 原生模板引擎渲染代码,模板文件存放在 `resources/templates/` 目录下(由 `config/generator.php` 的 `template_dir` 配置)。修改模板即可自定义生成代码的格式。
:::
## 模板目录结构
```text
resources/templates/
├── controller.php.tpl # 控制器模板
├── service.php.tpl # 业务服务模板(含 serializeMaps、导入导出)
├── model.php.tpl # 模型模板
├── request.php.tpl # FormRequest 验证器模板
├── ui/ # 普通列表模板(6个前端文件)
│ ├── api.ts.tpl # API 接口模块模板
│ ├── index.vue.tpl # 列表页模板
│ ├── edit.vue.tpl # 编辑弹窗模板
│ ├── detail.vue.tpl # 详情弹窗模板
│ ├── columns.ts.tpl # 表格列配置模板
│ └── querySchemas.ts.tpl # 查询表单配置模板
└── ui2/ # 树形结构模板(4个前端文件)
├── api.ts.tpl
├── index.vue.tpl
├── edit.vue.tpl
└── detail.vue.tpl
```
后端 4 张模板的目标输出路径由 `config/generator.php` 的 `backend_outputs` 决定(`{ClassName}` 占位符替换为模型类名),前端输出路径由 `GeneratorLogic` 按 `app_name` 与模块名拼接。
## 模板变量
模板使用 PHP 原生语法,变量通过 `GeneratorService::buildTemplateVars()` 构建,渲染时通过 `extract($vars)` 注入。
### 基础信息变量
| 变量 | 类型 | 说明 | 示例 |
|------|------|------|------|
| `$app_name` | string | 应用名(路由前缀、前端目录),读 `GENERATOR_APP_NAME` | `tool` |
| `$module_name` | string | 模块名(小写下划线,表名去前缀) | `example` |
| `$module_comment` | string | 功能名称(表注释清洗后) | `案例` |
| `$model_class_name` | string | 模型类名(大驼峰) | `Example` |
| `$model_class_name_camel` | string | 模型类名(小驼峰) | `example` |
| `$route_prefix` | string | 路由前缀 | `example` |
| `$api_path` | string | API 路径 | `tool/example` |
| `$permission_prefix` | string | 权限码前缀 | `sys:example` |
| `$primary_key` | string | 主键字段名 | `id` |
| `$table_name` | string | 表名(不含前缀) | `example` |
| `$parent_menu_id` | int | 菜单挂载父ID,读 `GENERATOR_PARENT_MENU_ID` | `189` |
### 树形结构变量
| 变量 | 类型 | 说明 | 示例 |
|------|------|------|------|
| `$is_tree` | bool | 是否为树形结构 | `true` |
| `$parent_id_field` | string | 父级字段名(pid 优先) | `parent_id` |
| `$parent_id_field_camel` | string | 父级字段名(驼峰) | `parentId` |
| `$display_field` | string | 显示字段名(按候选列表自动探测) | `name` |
| `$display_field_camel` | string | 显示字段名(驼峰) | `name` |
### 字段列表变量
| 变量 | 类型 | 说明 |
|------|------|------|
| `$fields` | array | 全部字段列表 |
| `$list_fields` | array | 列表页展示字段(排除隐藏字段) |
| `$form_fields` | array | 表单字段(编辑弹窗用) |
| `$filter_fields` | array | 筛选字段(搜索表单用) |
| `$file_fields` | array | 文件/图片字段名列表 |
| `$content_fields` | array | 富文本字段名列表 |
| `$multi_file_fields` | array | 多文件字段名列表(逗号分隔的多条路径) |
| `$like_fields` | array | 模糊搜索字段名列表 |
| `$eq_fields` | array | 精确匹配字段名列表 |
| `$unique_fields` | array | 唯一性校验字段名列表 |
| `$validate_rules` | array | 新增接口验证规则(必填 + 类型 + 长度约束,含 `id`) |
| `$validate_rules_update` | array | 修改接口验证规则(required 降级为 nullable,其余约束保持) |
| `$validate_messages` | array | 验证提示信息 |
| `$validate_fields` | array | 必填字段列表 |
| `$serialize_maps` | array | 枚举字段 → 字典编码映射 |
| `$field_labels` | array | 字段中文名映射(唯一性冲突提示用) |
| `$casts` | array | 字段类型转换(时间列 → datetime / date) |
| `$hidden_fields` | array | 需隐藏的敏感字段(密码、令牌等) |
### 功能标记变量
| 变量 | 类型 | 说明 |
|------|------|------|
| `$has_import` | bool | 是否生成导入方法(当前恒为 true) |
| `$has_export` | bool | 是否生成导出方法(当前恒为 true) |
| `$has_image_field` | bool | 是否包含图片字段 |
| `$has_rich_text_field` | bool | 是否包含富文本字段 |
| `$has_sort` | bool | 是否包含排序字段 |
| `$is_tree_structure` | bool | 是否为树形结构(`$is_tree` 别名) |
| `$template_type` | string | 模板类型:`ui` 或 `ui2` |
| `$tenant_scope` | bool | 表中是否含 `tenant_id`(决定继承 `BaseTenantService`) |
### 排序变量
| 变量 | 类型 | 说明 | 示例 |
|------|------|------|------|
| `$order_field` | string | 默认排序字段 | `id` 或 `sort` |
| `$order_type` | string | 默认排序方向 | `desc` 或 `asc` |
### 字段结构
每个字段为一个数组,包含以下属性:
```php
$field = [
'name' => 'status', // 字段名(下划线)
'camel_name' => 'status', // 字段名(驼峰)
'label' => '状态', // 中文标签(注释清洗后,无注释回退字段名)
'comment' => '状态:1-正常 2-停用', // 原始字段注释
'type' => 'IntegerField', // 字段类型归一化后的配置类名
'rawType' => 'int', // 原始数据库类型
'default' => 0, // 默认值
'required' => false, // 是否必填(非空且无默认值)
'nullable' => true, // 数据库是否允许 NULL
'is_image' => false, // 是否为图片字段
'is_rich_text' => false, // 是否为富文本字段
'is_textarea' => false, // 是否为多行文本
'is_number' => false, // 是否为数字字段
'is_datetime' => false, // 是否为时间字段
'in_form' => true, // 是否进表单
'in_list' => true, // 是否进列表
'filterable' => false, // 是否可筛选
'filter_type' => '', // 筛选方式:'精确匹配' / '' / '模糊'
'choices' => [ // 选项列表(有值时可生成 ElTag 渲染)
['value' => 0, 'label' => '正常', 'type' => 'success'],
['value' => 1, 'label' => '禁用', 'type' => 'danger'],
],
];
```
## 模板语法
模板使用 PHP 原生语法,通过 `<?php echo ?>` 输出变量,使用 `<?php foreach ?>` 等控制结构:
### 后端模板示例(service.php.tpl 片段)
```php
<?php echo '<?php' ?>
namespace App\Services;
use App\Models\<?php echo $model_class_name ?>;
class <?php echo $model_class_name ?>Service extends <?php echo $is_tree ? 'BaseService' : ($tenant_scope ? 'BaseTenantService' : 'BaseService') ?>
{
use HasCrudOperations, HasQueryScopes, HasSerializeMaps<?php if (!empty($unique_fields)) { echo ', HasUniqueCheck'; } ?>;
protected string $modelClass = <?php echo $model_class_name ?>::class;
<?php if (!empty($file_fields)): ?>
protected array $fileFields = [<?php echo implode(', ', array_map(fn($f) => "'$f'", $file_fields)) ?>];
protected string $fileSaveDir = '<?php echo $module_name ?>';
<?php endif; ?>
protected array $pageOrderBy = ['field' => '<?php echo $order_field ?>', 'type' => '<?php echo $order_type ?>'];
<?php if (!empty($serialize_maps)): ?>
protected array $serializeMaps = [
<?php foreach ($serialize_maps as $field => $dict): ?>
'<?php echo $field ?>' => '<?php echo $dict ?>',
<?php endforeach; ?>
];
<?php endif; ?>
<?php if ($is_tree): ?>
protected string $treeParentField = '<?php echo $parent_id_field ?>';
protected string $treeLikeField = '<?php echo $display_field ?>';
<?php endif; ?>
}
```
### 前端模板示例(columns.ts.tpl 片段)
```typescript
import { h } from 'vue';
import { ElAvatar, ElTag } from 'element-plus';
export const columns = [
{ type: 'selection' },
{ label: 'ID', prop: 'id', fixed: 'left', width: 50 },
<?php foreach ($list_fields as $field): ?>
{
label: '<?php echo $field['label'] ?>',
prop: '<?php echo $field['camel_name'] ?>',
<?php if ($field['is_image']): ?>
render(record) {
return h(ElAvatar, { size: 48, src: record.row.<?php echo $field['camel_name'] ?> });
},
<?php elseif ($field['choices']): ?>
render(record) {
const map = {
<?php foreach ($field['choices'] as $choice): ?>
<?php echo $choice['value'] ?>: { text: '<?php echo $choice['label'] ?>', type: '<?php echo $choice['type'] ?>' },
<?php endforeach; ?>
};
const s = map[record.row.<?php echo $field['camel_name'] ?>];
return h(ElTag, { type: s?.type || 'info' }, { default: () => s?.text });
},
<?php endif; ?>
},
<?php endforeach; ?>
];
```
## 自定义模板步骤
1. 修改 `resources/templates/` 目录下的 `.tpl` 文件(或调整 `config/generator.php` 的 `template_dir` 指向其它目录)
2. 使用上述模板变量(`$model_class_name`、`$module_comment` 等)
3. 下次生成时自动使用新模板
:::warning 注意事项
- 修改模板后,已生成的代码不会自动更新(需重新生成,且注意 `overwrite` 参数)
- 模板中使用 `<?php echo ?>` 输出变量,与现有模板保持一致(现有模板均不含短标签)
- 模板文件不能含 UTF-8 BOM 头,否则渲染结果开头会带不可见字符
- 树形结构模块使用 `ui2/` 模板,普通模块使用 `ui/` 模板
- 后端模板输出路径若调整 `backend_outputs`,注意同步 `GeneratorService::getGenerateFiles()` 的文件清单
:::
## 模板选择逻辑
```text
表结构中包含 parent_id 或 pid 字段?
│
├─ 是 → 使用 ui2/ 树形模板(自动,无需手动选择)
│
└─ 否 → 使用 ui/ 普通列表模板
```
## 生成的前端文件
`ui/` 模板生成 6 个文件(`ui2/` 生成 4 个,无 columns.ts 和 querySchemas.ts):
| 文件 | 说明 | 引用关系 |
|------|------|----------|
| `api.ts` | API 接口模块 | 被 index.vue、edit.vue 导入 |
| `index.vue` | 列表页 | 导入 columns.ts、querySchemas.ts |
| `edit.vue` | 编辑弹窗 | 被 index.vue 动态加载 |
| `detail.vue` | 详情弹窗 | 被 index.vue 动态加载 |
| `columns.ts` | 表格列配置 | 被 index.vue 导入,含 ElTag/ElAvatar 渲染 |
| `querySchemas.ts` | 查询表单配置 | 被 index.vue 导入,含 Select/Input/DatePicker 组件 |
## 相关文档
- [10.5 代码生成引擎](/zh/generator/code-engine.html)
- [10.7 配置文件详解](/zh/generator/config.html)
- [10.8 实战案例](/zh/generator/demo.html)