---
name: curd
description: Generates complete CRUD module from database table definition. Orchestrates 9 sub-skills. Use when user says "create CRUD", "generate module from table", or provides a table structure.
globs:
- "modules/**/*.php"
- "web/src/views/**/*.vue"
---
# CatchAdmin CRUD Generator
**一键生成完整 CRUD 模块** - 从数据表定义到前后端完整实现。
## Sub-Skills (9 个子 Skills)
| Step | Skill | 生成文件 |
|------|-------|----------|
| 1 | `01-parse-table` | - (解析输入) |
| 2 | `02-migration` | `database/migrations/*.php` |
| 3 | `03-model` | `Models/{Model}.php` |
| 4 | `04-controller` | `Http/Controllers/{Model}Controller.php` |
| 5 | `05-request` | `Http/Requests/{Model}Request.php` |
| 6 | `06-routes` | `routes/route.php` |
| 7 | `07-export` | `Export/{Model}.php` |
| 8 | `08-import` | `Import/{Model}.php` |
| 9 | `09-vue-pages` | `index.vue` + `create.vue` |
---
## Input: Table Definition
```
Table: products
Fields:
- id (primary key)
- name (string, 100, required)
- category_id (foreign key -> categories)
- description (text, nullable)
- price (decimal 10,2, required)
- stock (integer, default 0)
- status (tinyint, default 1)
```
---
## Output: Complete Module Structure
```
modules/{Module}/
├── Http/
│ ├── Controllers/{Model}Controller.php
│ └── Requests/{Model}Request.php
├── Models/{Model}.php
├── Export/{Model}.php
├── Import/{Model}.php
├── database/migrations/*.php
└── routes/route.php
web/src/views/{module}/
├── index.vue
└── create.vue
```
---
## Pre-checks (执行前)
- **模块存在性**:若 `modules/{Module}` 不存在,先询问是否创建新模块。
- **文件冲突**:目标文件已存在时,先询问覆盖或增量修改。
- **路由/权限前缀**:检查 `modules/{Module}/routes/route.php` 是否已有相同资源前缀。
- **依赖/配置**:新增依赖或改动 `config/` 前必须先询问。
---
## Output Contract (输出契约)
- 必须生成 9 步完整产物(迁移/模型/控制器/请求/路由/导入导出/前端页)。
- 所有路径、类名、命名空间与 `{Module}/{Model}/{table}` 一致。
- 前端 `catch-table` 的 `api` 与后端路由前缀保持一致。
---
## Consistency Validation (生成后校验)
- 迁移包含 CatchAdmin 标准字段:`creator_id/created_at/updated_at/deleted_at` + 索引。
- Model/Request 与迁移字段一致(类型/必填/默认值)。
- Route API 前缀与前端 `api`/`permission` 前缀一致。
---
## Fallback Rules (失败兜底)
- 输入缺失关键字段(如 table/fields)时必须先澄清,停止生成。
- 复杂关系或非标准业务逻辑需用户确认后再生成。
---
## Workflow
### Step 1: Parse Table Definition → `01-parse-table`
Extract from user input:
- Table name (snake_case plural)
- Model name (PascalCase singular)
- Module name (PascalCase)
- Fields with types, constraints, and relationships
### Step 2: Generate Migration → `02-migration`
```php
id();
// Generated from field definitions
$table->string('name', 100);
$table->unsignedBigInteger('category_id')->default(0)->index();
$table->text('description')->nullable();
$table->decimal('price', 10, 2);
$table->unsignedInteger('stock')->default(0);
$table->string('image', 255)->nullable();
$table->tinyInteger('status')->default(1);
$table->unsignedInteger('sort')->default(0);
// CatchAdmin standard fields
$table->unsignedInteger('creator_id')->default(0);
$table->unsignedInteger('created_at')->default(0);
$table->unsignedInteger('updated_at')->default(0);
$table->unsignedInteger('deleted_at')->default(0);
$table->index(['status', 'deleted_at']);
});
}
public function down(): void
{
Schema::dropIfExists('{table}');
}
};
```
### Step 3: Generate Model → `03-model`
```php
'like',
'category_id' => '=',
'status' => '=',
];
protected array $fields = [
'id', 'name', 'category_id', 'price', 'stock', 'status', 'created_at'
];
protected array $form = [
'name', 'category_id', 'description', 'price', 'stock', 'image', 'status', 'sort'
];
// Relationships (auto-detected from foreign keys)
public function category(): BelongsTo
{
return $this->belongsTo(Category::class, 'category_id');
}
}
```
### Step 4: Generate Controller → `04-controller`
```php
model->getList();
}
public function store({Model}Request $request)
{
return $this->model->storeBy($request->all());
}
public function show($id): mixed
{
return $this->model->firstBy($id);
}
public function update($id, {Model}Request $request): mixed
{
return $this->model->updateBy($id, $request->all());
}
public function destroy($id)
{
return $this->model->deleteBy($id);
}
public function enable($id)
{
return $this->model->toggleBy($id);
}
public function export(): mixed
{
return {Model}::query()
->select('id', 'name', 'price', 'stock', 'created_at')
->get()
->download(['ID', 'Name', 'Price', 'Stock', 'Created At']);
}
public function import(Request $request, {Model}Import $import)
{
return $import->import($request->file('file'));
}
}
```
### Step 5: Generate Request → `05-request`
```php
'required|string|max:100',
'category_id' => 'required|integer|exists:categories,id',
'description' => 'nullable|string',
'price' => 'required|numeric|min:0',
'stock' => 'sometimes|integer|min:0',
'image' => 'nullable|string|max:255',
'status' => 'sometimes|integer|in:1,2',
'sort' => 'sometimes|integer|min:0',
];
}
}
```
### Step 6: Generate Routes → `06-routes`
```php
select('id', 'name', 'price', 'stock', 'created_at')
->get()
->toArray();
}
}
```
### Step 8: Generate Import → `08-import`
```php
skip(1)->each(function ($row) {
{Model}::create([
'name' => $row[0],
'price' => $row[1],
'stock' => $row[2],
]);
});
}
}
```
### Step 9: Generate Vue Pages → `09-vue-pages`
**index.vue:**
```vue
```
**create.vue:**
```vue
Active
Inactive
Submit
```
---
## Field Type Mapping
| Input Type | Migration | Model | Validation | Vue Component |
|------------|-----------|-------|------------|---------------|
| string(n) | `string('x', n)` | fillable | `string|max:n` | `el-input` |
| text | `text('x')` | fillable | `string` | `el-input type="textarea"` |
| integer | `unsignedInteger('x')` | fillable | `integer|min:0` | `el-input-number` |
| decimal(m,n) | `decimal('x', m, n)` | fillable | `numeric` | `el-input-number :precision="n"` |
| boolean | `tinyInteger('x')` | fillable | `integer|in:1,2` | `el-switch` or `el-radio-group` |
| foreign_key | `unsignedBigInteger('x')` | belongsTo | `exists:table,id` | `el-select` |
| date | `date('x')` | fillable | `date` | `el-date-picker` |
| datetime | `dateTime('x')` | fillable | `date` | `el-date-picker type="datetime"` |
| json | `json('x')` | `$casts['x'] = 'array'` | `array` | Custom |
---
## Execution Checklist
When generating a complete CRUD:
- [ ] Step 1: Parse table definition
- [ ] Step 2: Create migration file
- [ ] Step 3: Create model
- [ ] Step 4: Create controller
- [ ] Step 5: Create request validation
- [ ] Step 6: Create routes
- [ ] Step 7: Create export class
- [ ] Step 8: Create import class
- [ ] Step 9: Create Vue pages
- [ ] Run `php artisan migrate`
---
## Boundaries
- ✅ **Always**: Generate all files, follow CatchAdmin conventions
- ⚠️ **Ask first**: Complex relationships, custom business logic
- 🚫 **Never**: Skip validation, ignore foreign key constraints