Files
seclusion/plop/README.md
charilezhou 2aa992c88d docs(plop): 更新 README 文档
新增内容:
- 服务类型说明(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>
2026-01-19 20:46:50 +08:00

12 KiB
Raw Blame History

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 productProduct
camelCase 转 camelCase productproduct
kebabCase 转 kebab-case productItemproduct-item
snakeCase 转 snake_case productItemproduct_item
constantCase 转 CONSTANT_CASE productPRODUCT
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);