新增内容: - 服务类型说明(CrudService/RelationCrudService/ManyToManyCrudService) - 关系配置 DSL 语法(一对多/多对一/多对多) - 种子脚本生成和执行说明 - 权限控制说明(@RequirePermission + PermissionGuard) - 新增模板文件(page.hbs、module-seed.hbs) - Helpers 完整列表(含 openBrace/closeBrace) - FAQ 扩展(QueryDto 生成、JSX 花括号、种子脚本) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
CRUD 代码生成器
基于 Plop.js 的全栈 CRUD 代码生成器,支持一键生成后端模块、前端模块、共享类型、Prisma Model 和菜单/权限种子脚本。
快速开始
pnpm generate
按提示输入模块信息即可自动生成完整的 CRUD 代码。
交互式提问
| 步骤 | 提示 | 说明 | 示例 |
|---|---|---|---|
| 1 | 模块名称 | 英文,小写开头 | product |
| 2 | 模块中文名 | 用于注释和 API 标签 | 产品 |
| 3 | 生成目标 | 多选:后端/前端/共享类型/Prisma/种子脚本 | 全选 |
| 4 | 服务类型 | 单表/带关联/多对多 | CrudService |
| 5 | 软删除 | 是否启用软删除 | Yes |
| 6 | 字段定义 | DSL 语法定义字段 | 见下方 |
| 7 | 关系配置 | 一对多/多对一/多对多关系 | 交互式配置 |
| 8 | 搜索字段 | 选择可搜索的字段 | name, status |
| 9 | 分页配置 | 默认/最大分页、排序 | 20/100/createdAt/desc |
| 10 | 菜单配置 | 图标、排序(生成种子脚本时) | Users / 50 |
服务类型
生成器支持三种服务类型,根据模块复杂度选择:
| 类型 | 说明 | 适用场景 |
|---|---|---|
CrudService |
单表 CRUD | 简单实体,无关联关系 |
RelationCrudService |
带关联查询 | 有外键关联,需要返回关联数据 |
ManyToManyCrudService |
多对多关系 | 需要管理多对多关系(如班级-教师) |
字段 DSL 语法
基本格式
字段名:类型[修饰符] 标签 "示例值" [验证规则...]
类型
| 类型 | 说明 | Prisma | TypeScript |
|---|---|---|---|
string |
字符串 | String |
string |
number |
数字 | Float |
number |
boolean |
布尔值 | Boolean |
boolean |
date |
日期 | DateTime |
Date |
datetime |
日期时间 | DateTime |
Date |
enum(a,b,c) |
枚举 | String |
'a' | 'b' | 'c' |
修饰符
| 修饰符 | 说明 |
|---|---|
? |
可选字段(nullable) |
! |
唯一约束 |
验证规则
| 规则 | 说明 | 适用类型 |
|---|---|---|
min:n |
最小值/最小长度 | string, number |
max:n |
最大值/最大长度 | string, number |
email |
邮箱格式 | string |
url |
URL 格式 | string |
控制标志
| 标志 | 说明 |
|---|---|
noCreate |
不在创建表单中使用 |
noUpdate |
不在更新表单中使用 |
noTable |
不在表格列中显示 |
示例
# 必填字符串,2-100 字符
name:string 名称 "示例名称" min:2 max:100
# 可选长文本
description:string? 描述 "描述内容" max:500
# 数字,最小值 0
price:number 价格 "99.99" min:0
# 枚举类型
status:enum(draft,published,archived) 状态 "draft"
# 唯一邮箱
email:string! 邮箱 "test@example.com" email
# 布尔值
isActive:boolean 是否激活 "true"
# 可选日期
publishedAt:datetime? 发布时间 "2026-01-16T10:00:00Z"
关系配置
一对多关系
新模型包含多个目标模型(如:宿舍包含多个学生)
# 格式: 关系名:目标模型 [optional]
students:Student optional
多对一关系
新模型属于一个目标模型(如:成绩属于学生)
# 格式: 关联名:目标模型 [optional]
student:Student
class:Class optional
多对多关系
多对多关联(如:班级-教师)
# 格式: 关系名:中间表:外键:目标键:目标模型 字段1,字段2,...
teachers:ClassTeacher:classId:teacherId:Teacher id,name,teacherNo
查询关联配置
配置 API 响应返回哪些关联字段:
# 格式: 关联名:目标模型 字段1,字段2,... [noList]
# noList: 不在列表中显示(仅详情显示)
class:Class id,code,name
students:Student id,name,studentNo noList
生成的文件
后端 (apps/api)
| 文件 | 说明 |
|---|---|
src/{module}/dto/{module}.dto.ts |
CreateDto、UpdateDto、ResponseDto、QueryDto |
src/{module}/{module}.service.ts |
CRUD 服务,继承对应基类 |
src/{module}/{module}.controller.ts |
RESTful 控制器,含 Swagger 文档和权限控制 |
src/{module}/{module}.module.ts |
NestJS 模块 |
权限控制:生成的 Controller 使用 @RequirePermission() 装饰器控制接口权限:
{module}:create- 创建权限{module}:read- 查看权限{module}:update- 更新权限{module}:delete- 删除权限
前端 (apps/web)
| 文件 | 说明 |
|---|---|
src/services/{module}.service.ts |
API 调用封装 |
src/hooks/use{Module}s.ts |
TanStack Query hooks |
src/components/{module}s/{Module}sTable.tsx |
数据表格组件(含错误状态处理) |
src/components/{module}s/{Module}CreateDialog.tsx |
创建对话框 |
src/components/{module}s/{Module}EditDialog.tsx |
编辑对话框 |
src/app/(dashboard)/{modules}/page.tsx |
页面组件 |
权限控制:生成的前端组件使用 <PermissionGuard> 包裹需要权限的操作按钮。
共享类型 (packages/shared)
| 文件 | 说明 |
|---|---|
src/types/{module}.ts |
接口类型定义 |
Prisma
| 文件 | 说明 |
|---|---|
prisma/schema.prisma |
追加模型定义 |
种子脚本 (apps/api/prisma/seeds)
| 文件 | 说明 |
|---|---|
{module}.seed.ts |
菜单和权限种子数据 |
生成的种子脚本包含:
- 4 个 CRUD 权限(create/read/update/delete)
- 1 个菜单项(可配置图标和排序)
可独立运行或被主 seed.ts 导入:
cd apps/api && npx ts-node prisma/seeds/{module}.seed.ts
自动集成
生成器会自动修改以下文件完成集成:
| 文件 | 修改内容 |
|---|---|
apps/api/src/app.module.ts |
导入新模块 |
apps/api/src/prisma/prisma.service.ts |
添加软删除模型配置 |
apps/web/src/config/constants.ts |
添加 API 端点 |
packages/shared/src/types/index.ts |
导出新类型 |
生成后步骤
# 1. 同步数据库
pnpm db:generate && pnpm db:push
# 2. 执行种子脚本(如果生成了)
cd apps/api && npx ts-node prisma/seeds/{module}.seed.ts
# 3. 重启开发服务器
pnpm dev
完整示例
以生成「产品」模块为例:
$ pnpm generate
? 模块名称(英文,如 product): product
? 模块中文名(如 产品): 产品
? 选择要生成的模块: 后端 (NestJS), 前端 (Next.js), 共享类型, Prisma Model, 菜单/权限种子脚本
? 选择服务类型: CrudService(单表 CRUD)
? 是否启用软删除? Yes
? 定义字段:
name:string 名称 "示例产品" min:2 max:100
description:string? 描述 "产品描述" max:500
price:number 价格 "99.99" min:0
stock:number 库存 "100" min:0
status:enum(draft,published,archived) 状态 "draft"
? 选择支持搜索的字段: name, status
? 默认分页大小: 20
? 最大分页大小: 100
? 默认排序字段: createdAt
? 默认排序方向: desc
? 菜单图标名称: Package
? 菜单排序值: 50
✔ 生成 apps/api/src/product/dto/product.dto.ts
✔ 生成 apps/api/src/product/product.service.ts
✔ 生成 apps/api/src/product/product.controller.ts
✔ 生成 apps/api/src/product/product.module.ts
✔ 生成 apps/web/src/services/product.service.ts
✔ 生成 apps/web/src/hooks/useProducts.ts
✔ 生成 apps/web/src/components/products/ProductsTable.tsx
✔ 生成 apps/web/src/components/products/ProductCreateDialog.tsx
✔ 生成 apps/web/src/components/products/ProductEditDialog.tsx
✔ 生成 apps/web/src/app/(dashboard)/products/page.tsx
✔ 生成 apps/api/prisma/seeds/product.seed.ts
✔ 生成 packages/shared/src/types/product.ts
✔ 修改 apps/api/prisma/schema.prisma
✔ 修改 apps/api/src/app.module.ts
✔ 修改 apps/api/src/prisma/prisma.service.ts
✔ 修改 apps/web/src/config/constants.ts
✔ 修改 packages/shared/src/types/index.ts
✨ 生成完成!
目录结构
plop/
├── plopfile.ts # 主配置入口
├── package.json # ESM 模块配置
├── README.md # 本文档
├── generators/
│ └── crud.ts # CRUD 生成器逻辑
├── helpers/
│ └── index.ts # Handlebars helpers
├── utils/
│ ├── field-parser.ts # 字段 DSL 解析器
│ ├── relation-parser.ts # 关系配置解析器
│ └── schema-parser.ts # Prisma schema 解析器
└── templates/
├── api/ # 后端模板
│ ├── dto.hbs
│ ├── service.hbs
│ ├── controller.hbs
│ └── module.hbs
├── web/ # 前端模板
│ ├── service.hbs
│ ├── hooks.hbs
│ ├── table.hbs
│ ├── create-dialog.hbs
│ ├── edit-dialog.hbs
│ └── page.hbs
├── shared/ # 共享类型模板
│ └── types.hbs
├── prisma/ # Prisma 模板
│ └── model.hbs
└── seed/ # 种子脚本模板
└── module-seed.hbs
扩展模板
如需自定义模板,可直接修改 plop/templates/ 目录下的 .hbs 文件。
可用的 Handlebars Helpers
| Helper | 说明 | 示例 |
|---|---|---|
pascalCase |
转 PascalCase | product → Product |
camelCase |
转 camelCase | product → product |
kebabCase |
转 kebab-case | productItem → product-item |
snakeCase |
转 snake_case | productItem → product_item |
constantCase |
转 CONSTANT_CASE | product → PRODUCT |
tsType |
获取 TS 类型 | string, number 等 |
tsResponseType |
获取响应 TS 类型 | string, number 等 |
prismaType |
获取 Prisma 类型 | String, Float 等 |
zodValidation |
生成 Zod 验证 | z.string().min(2) |
formControl |
生成表单控件 | <Input .../> |
cellRenderer |
生成表格单元格渲染 | <Badge .../> |
validationDecorators |
生成 class-validator 装饰器 | @IsString() |
formattedExample |
格式化示例值 | "text" / 123 |
openBrace |
输出 { |
用于 JSX 模板 |
closeBrace |
输出 } |
用于 JSX 模板 |
逻辑 Helpers
| Helper | 说明 |
|---|---|
eq a b |
相等判断 |
ne a b |
不等判断 |
and a b |
逻辑与 |
or a b |
逻辑或 |
not a |
逻辑非 |
includes arr value |
数组包含 |
条件 Helpers
| Helper | 说明 |
|---|---|
hasValidation fields |
字段是否有验证规则 |
hasTransform fields |
字段是否需要转换(date/datetime) |
hasTextarea fields |
字段是否有长文本 |
hasSelect fields |
字段是否有枚举 |
hasSwitch fields |
字段是否有布尔值 |
常见问题
Q: 如何添加自定义字段类型?
修改 plop/utils/field-parser.ts 中的类型映射。
Q: 如何修改生成的代码风格?
直接编辑 plop/templates/ 下的模板文件。
Q: 生成后 TypeScript 报错?
确保运行 pnpm db:generate 更新 Prisma Client 类型。
Q: 如何在 JSX 模板中输出花括号?
使用 {{openBrace}} 和 {{closeBrace}} helpers:
<span>{{openBrace}}item.id{{closeBrace}}</span>
输出:<span>{item.id}</span>
Q: QueryDto 什么时候会被生成?
无论是否选择搜索字段,QueryDto 都会被生成。这便于后续扩展查询参数。hasQueryDto 标志仅控制 Controller 中是否解构查询字段。
Q: 如何执行种子脚本?
生成的种子脚本支持独立运行:
cd apps/api && npx ts-node prisma/seeds/{module}.seed.ts
或在主 seed.ts 中导入调用:
import { seedProductModule } from './seeds/product.seed';
await seedProductModule(prisma);