Skip to content

10.6 自定义模板开发 ​

概述

代码生成器使用 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_namestring应用名(路由前缀、前端目录),读 GENERATOR_APP_NAMEtool
$module_namestring模块名(小写下划线,表名去前缀)example
$module_commentstring功能名称(表注释清洗后)案例
$model_class_namestring模型类名(大驼峰)Example
$model_class_name_camelstring模型类名(小驼峰)example
$route_prefixstring路由前缀example
$api_pathstringAPI 路径tool/example
$permission_prefixstring权限码前缀sys:example
$primary_keystring主键字段名id
$table_namestring表名(不含前缀)example
$parent_menu_idint菜单挂载父ID,读 GENERATOR_PARENT_MENU_ID189

树形结构变量 ​

变量类型说明示例
$is_treebool是否为树形结构true
$parent_id_fieldstring父级字段名(pid 优先)parent_id
$parent_id_field_camelstring父级字段名(驼峰)parentId
$display_fieldstring显示字段名(按候选列表自动探测)name
$display_field_camelstring显示字段名(驼峰)name

字段列表变量 ​

变量类型说明
$fieldsarray全部字段列表
$list_fieldsarray列表页展示字段(排除隐藏字段)
$form_fieldsarray表单字段(编辑弹窗用)
$filter_fieldsarray筛选字段(搜索表单用)
$file_fieldsarray文件/图片字段名列表
$content_fieldsarray富文本字段名列表
$multi_file_fieldsarray多文件字段名列表(逗号分隔的多条路径)
$like_fieldsarray模糊搜索字段名列表
$eq_fieldsarray精确匹配字段名列表
$unique_fieldsarray唯一性校验字段名列表
$validate_rulesarray新增接口验证规则(必填 + 类型 + 长度约束,含 id)
$validate_rules_updatearray修改接口验证规则(required 降级为 nullable,其余约束保持)
$validate_messagesarray验证提示信息
$validate_fieldsarray必填字段列表
$serialize_mapsarray枚举字段 → 字典编码映射
$field_labelsarray字段中文名映射(唯一性冲突提示用)
$castsarray字段类型转换(时间列 → datetime / date)
$hidden_fieldsarray需隐藏的敏感字段(密码、令牌等)

功能标记变量 ​

变量类型说明
$has_importbool是否生成导入方法(当前恒为 true)
$has_exportbool是否生成导出方法(当前恒为 true)
$has_image_fieldbool是否包含图片字段
$has_rich_text_fieldbool是否包含富文本字段
$has_sortbool是否包含排序字段
$is_tree_structurebool是否为树形结构($is_tree 别名)
$template_typestring模板类型:ui 或 ui2
$tenant_scopebool表中是否含 tenant_id(决定继承 BaseTenantService)

排序变量 ​

变量类型说明示例
$order_fieldstring默认排序字段id 或 sort
$order_typestring默认排序方向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'    => [                 // 选项列表(有值时可生成 ATag 渲染)
        ['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 { Tag, Avatar } from 'ant-design-vue';

export const columns = [
  { title: 'ID', dataIndex: 'id', fixed: 'left', width: 50 },
<?php foreach ($list_fields as $field): ?>
  {
    title: '<?php echo $field['label'] ?>',
    dataIndex: '<?php echo $field['camel_name'] ?>',
    key: '<?php echo $field['camel_name'] ?>',
<?php if ($field['is_image']): ?>
    customRender({ record }) {
      return h(Avatar, { size: 48, src: record.<?php echo $field['camel_name'] ?>, shape: 'square' });
    },
<?php elseif ($field['choices']): ?>
    customRender({ record }) {
      const map = {
<?php foreach ($field['choices'] as $choice): ?>
        <?php echo $choice['value'] ?>: { text: '<?php echo $choice['label'] ?>', color: '<?php echo ($choice['type'] === 'danger') ? 'error' : (($choice['type'] === 'info') ? 'default' : $choice['type']) ?>' },
<?php endforeach; ?>
      };
      const s = map[record.<?php echo $field['camel_name'] ?>];
      return h(Tag, { color: s?.color || 'default' }, { default: () => s?.text });
    },
<?php endif; ?>
  },
<?php endforeach; ?>
];

自定义模板步骤 ​

  1. 修改 resources/templates/ 目录下的 .tpl 文件(或调整 config/generator.php 的 template_dir 指向其它目录)
  2. 使用上述模板变量($model_class_name、$module_comment 等)
  3. 下次生成时自动使用新模板

注意事项

  • 修改模板后,已生成的代码不会自动更新(需重新生成,且注意 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.tsAPI 接口模块被 index.vue、edit.vue 导入
index.vue列表页导入 columns.ts、querySchemas.ts
edit.vue编辑弹窗被 index.vue 动态加载
detail.vue详情弹窗被 index.vue 动态加载
columns.ts表格列配置被 index.vue 导入,含 ATag/AAvatar 渲染
querySchemas.ts查询表单配置被 index.vue 导入,含 Select/Input/DatePicker 组件

相关文档 ​

小蚂蚁云团队 · 提供技术支持