---
name: crud-generator
description: 根据数据表结构快速生成 ThinkCMF 后台完整 CRUD 代码。输入表名和字段信息，一键生成后台控制器（含菜单注解）、模型（含关联）、验证器、服务层、后台模板（列表/添加/编辑页）。当用户提供表结构并要求生成 CRUD、后台管理、增删改查功能时使用此技能。
---

# 快速生成 CRUD

## 输入要求

用户提供以下信息即可生成完整 CRUD：

| 参数 | 必填 | 示例 |
|------|------|------|
| 应用名 | 是 | `product` |
| 表名 | 是 | `product`（不含前缀） |
| 中文名 | 是 | `商品` |
| 字段列表 | 是 | 见下方格式 |
| 关联表 | 否 | 见下方格式 |
| 父级菜单 | 否 | 空=顶级，或 `product/AdminProduct/default` |

### 字段格式

```
字段名 | 类型 | 中文名 | 表单类型 | 必填 | 备注
name | varchar(100) | 名称 | text | 是 |
status | tinyint(1) | 状态 | radio | 否 | 0=禁用,1=启用
description | text | 描述 | textarea | 否 |
image | varchar(255) | 图片 | image | 否 |
list_order | float | 排序 | number | 否 | 默认10000
```

### 关联表格式

```
关联类型 | 关联模型 | 外键 | 中间表（多对多时）
belongsTo | UserModel | user_id |
belongsToMany | CategoryModel | category_id | product_category
```

## 生成文件清单

```
app/{应用名}/
├── controller/Admin{Entity}Controller.php  ← 后台控制器+菜单注解
├── model/{Entity}Model.php                 ← 模型+关联+访问器
├── validate/{Entity}Validate.php           ← 验证器+场景
├── service/{Entity}Service.php             ← 服务层(如已有则复用)
├── service/BaseService.php                 ← 服务基类(如已有则跳过)
└── service/traits/*.php                    ← Trait(如已有则跳过)

public/themes/admin_default/{应用名}/admin_{entity}/
├── index.html                              ← 列表页
├── add.html                                ← 添加页
└── edit.html                               ← 编辑页
```

## 生成规则

### 1. 模型 `{Entity}Model.php`

```php
<?php
namespace app\{应用名}\model;

use think\Model;

/**
 * {中文名}模型
 */
class {Entity}Model extends Model
{
    protected $name = '{表名}';

    // 有 create_time/update_time 字段时添加
    protected $autoWriteTimestamp = true;

    // 有 JSON/array 类型字段时添加
    protected $type = [
        'more' => 'array',
    ];

    // --- 关联方法（按关联表信息生成） ---

    // belongsTo 示例
    public function user()
    {
        return $this->belongsTo('UserModel', 'user_id');
    }

    // belongsToMany 示例
    public function categories()
    {
        return $this->belongsToMany('CategoryModel', '{中间表}', 'category_id', '{entity}_id');
    }

    // --- 访问器（按字段类型生成） ---

    // image/thumbnail 字段
    public function getImageAttr($value)
    {
        return cmf_get_image_url($value);
    }

    // 富文本字段
    public function getContentAttr($value)
    {
        return cmf_replace_content_file_url(htmlspecialchars_decode($value));
    }

    public function setContentAttr($value)
    {
        return htmlspecialchars(cmf_replace_content_file_url(htmlspecialchars_decode($value), true));
    }
}
```

**访问器自动生成规则：**

| 字段类型 | getter | setter |
|---------|--------|--------|
| `image`/`thumbnail`/`photos` | `cmf_get_image_url()` | 无 |
| 富文本（`content`/`post_content`） | `htmlspecialchars_decode` + `cmf_replace_content_file_url` | `htmlspecialchars` + `cmf_replace_content_file_url(..., true)` |
| 时间字符串→时间戳 | `date('Y-m-d H:i:s', $value)` | `strtotime($value)` |

### 2. 后台控制器 `Admin{Entity}Controller.php`

按 `create-admin-menu` 技能规则生成菜单注解：

- 有 `@adminMenuRoot`（顶级菜单时）或子菜单 parent 指向已有根菜单
- 每个方法含 `@adminMenu`，display/hasView 按标准映射

**标准方法集：**

| 方法 | 功能 | display | hasView | 返回 |
|------|------|---------|---------|------|
| `index` | 列表 | true | true | `$this->fetch()` |
| `add` | 添加页 | false | true | `$this->fetch()` |
| `create` | 保存 | false | false | `void` |
| `edit` | 编辑页 | false | true | `$this->fetch()` |
| `update` | 更新 | false | false | `void` |
| `delete` | 删除 | false | false | `void` |
| `listOrder` | 排序 | false | false | `void` |
| `toggleStatus` | 状态切换 | false | false | `void`（有status字段时） |

**控制器方法模式：**

```php
// 列表（分页）
public function index()
{
    $validateResult = $this->validate($this->request->param(), '{Entity}.index');
    if ($validateResult !== true) {
        $this->error($validateResult);
    }
    $service = new {Entity}Service();
    $data = $service->page($this->request->param());
    $this->assign('data', $data);
    return $this->fetch();
}

// 添加页面
public function add()
{
    return $this->fetch();
}

// 保存（POST）
public function create(): void
{
    $validateResult = $this->validate($this->request->param(), '{Entity}.create');
    if ($validateResult !== true) {
        $this->error($validateResult);
    }
    $service = new {Entity}Service();
    $service->create($this->request->param());
    $this->success('添加成功!');
}

// 编辑页面
public function edit()
{
    $id = $this->request->param('id', 0, 'intval');
    $service = new {Entity}Service();
    $data = $service->read($id);
    $this->assign('data', $data);
    return $this->fetch();
}

// 更新（PUT）
public function update(): void
{
    $validateResult = $this->validate($this->request->param(), '{Entity}.update');
    if ($validateResult !== true) {
        $this->error($validateResult);
    }
    $service = new {Entity}Service();
    $service->update($this->request->param());
    $this->success('更新成功!');
}

// 删除
public function delete(): void
{
    $validateResult = $this->validate($this->request->param(), '{Entity}.delete');
    if ($validateResult !== true) {
        $this->error($validateResult);
    }
    $service = new {Entity}Service();
    $service->delete($this->request->param('id'));
    $this->success('删除成功!');
}
```

### 3. 验证器 `{Entity}Validate.php`

根据字段的「必填」属性和类型自动生成规则：

```php
protected $rule = [
    'id'   => ['require'],
    'name' => ['require', 'max' => 100],  // varchar(100) + 必填
];

protected $scene = [
    'delete' => ['id'],
    'update' => ['id'],
    'read'   => ['id'],
];

protected function sceneIndex()
{
    return $this->only(['id'])->remove('id', true);
}

protected function sceneCreate()
{
    return $this->only(['name']); // 必填字段
}
```

### 4. 后台模板

按 `create-theme-template` 技能规则生成，模板位于 `public/themes/admin_default/{应用名}/admin_{entity}/`。

#### 列表页 `index.html` 核心结构

```html
<extend name="public@base" />
<block name="content">
<div class="row">
  <div class="col-12">
    <div class="card">
      <div class="card-header d-flex justify-content-between">
        <h3 class="card-title">{中文名}列表</h3>
        <a class="btn btn-primary btn-sm" href="{:url('add')}">添加{中文名}</a>
      </div>
      <div class="card-body">
        <form method="post" class="js-ajax-form">
          <table class="table table-bordered table-hover">
            <thead>
              <tr>
                <th><input type="checkbox" class="js-check-all"></th>
                <th>ID</th>
                <!-- 根据字段列表生成 th -->
                <th>排序</th>
                <th>操作</th>
              </tr>
            </thead>
            <tbody>
              <volist name="data" id="vo">
              <tr>
                <td><input type="checkbox" name="ids[]" value="{$vo.id}"></td>
                <td>{$vo.id}</td>
                <!-- 根据字段类型生成 td -->
                <td><input type="text" name="list_orders[{$vo.id}]" value="{$vo.list_order}" class="form-control form-control-sm" style="width:60px"></td>
                <td>
                  <a href="{:url('edit',['id'=>$vo['id']])}">编辑</a>
                  <a class="js-ajax-delete" href="{:url('delete',['id'=>$vo['id']])}">删除</a>
                </td>
              </tr>
              </volist>
            </tbody>
          </table>
          <div class="d-flex justify-content-between">
            <div>
              <button type="submit" class="btn btn-primary btn-sm" data-action="{:url('listOrder')}">排序</button>
              <button type="submit" class="btn btn-danger btn-sm" data-action="{:url('delete')}">删除</button>
            </div>
            <div>{$data|raw}</div>
          </div>
        </form>
      </div>
    </div>
  </div>
</div>
</block>
```

#### 字段→表格列映射

| 字段类型 | 列表页显示 |
|---------|-----------|
| `varchar`/`text`（短） | `{$vo.field_name}` |
| `image` | `<img src="{$vo.image}" style="max-height:50px">` |
| `status` | `<if condition="$vo.status eq 1"><span class="badge bg-success">启用</span><else/><span class="badge bg-secondary">禁用</span></if>` |
| `create_time` | `{$vo.create_time\|date='Y-m-d H:i',###}` |

#### 添加页/编辑页字段→表单映射

| 字段类型 | 表单组件 |
|---------|---------|
| `text` | `<input type="text" class="form-control" name="field" value="">` |
| `number` | `<input type="number" class="form-control" name="field" value="">` |
| `textarea` | `<textarea class="form-control" name="field" rows="5"></textarea>` |
| `radio`（status） | `<input type="radio" name="status" value="1"> 启用 <input type="radio" name="status" value="0"> 禁用` |
| `select` | `<select class="form-control" name="field"><option>...</option></select>` |
| `image` | `<input type="hidden" name="image" value=""><img src="" class="js-upload-image">` |
| `datetime` | `<input type="datetime-local" class="form-control" name="field">` |

编辑页与添加页的区别：表单 method 用隐藏字段 `<input type="hidden" name="_method" value="PUT">`，输入框预填 `value="{$data.field_name}"`。

## 服务层复用

如果 `app/{应用名}/service/` 已存在 `BaseService.php` 和 Trait 文件，直接复用不重复生成。仅生成 `{Entity}Service.php`：

```php
<?php
namespace app\{应用名}\service;

use app\{应用名}\model\{Entity}Model;

class {Entity}Service extends BaseService
{
    protected function initialize(): void
    {
        parent::initialize();
        $this->modelClassName = {Entity}Model::class;
    }

    use \app\{应用名}\service\traits\CreateTrait;
    use \app\{应用名}\service\traits\DeleteTrait;
    use \app\{应用名}\service\traits\ListsTrait;
    use \app\{应用名}\service\traits\PageTrait;
    use \app\{应用名}\service\traits\ReadTrait;
    use \app\{应用名}\service\traits\UpdateTrait;
    use \app\{应用名}\service\traits\ToggleStatusTrait;
}
```

如不存在则参考 `create-app` 技能的 [traits.md](../create-app/traits.md) 生成。

## 执行步骤

```
1. 解析表结构 → 确认字段列表和关联关系
2. 生成模型 → 验证：命名空间、表名、关联、访问器
3. 生成验证器 → 验证：规则与字段匹配、场景完整
4. 生成/复用服务层 → 验证：Trait 引用正确
5. 生成控制器+菜单注解 → 验证：基类、注解参数、方法签名
6. 生成后台模板（列表/添加/编辑） → 验证：字段与表单组件匹配
7. 检查清单全部通过
```

## 检查清单

- [ ] 模型 `$name` 与实际表名一致
- [ ] 关联方法的外键和中间表正确
- [ ] image/富文本字段有对应访问器
- [ ] 验证器必填字段有 `require` 规则
- [ ] 控制器每个方法含 `@adminMenu` 注解
- [ ] 菜单 parent 层级正确
- [ ] 模板字段与控制器 assign 变量一致
- [ ] 编辑页表单预填值使用 `{$data.field}`
- [ ] 列表页分页渲染 `{$data|raw}`
- [ ] 无 `exit`/`die`，注释使用中文
