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

394 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CRUD 代码生成器
基于 Plop.js 的全栈 CRUD 代码生成器支持一键生成后端模块、前端模块、共享类型、Prisma Model 和菜单/权限种子脚本。
## 快速开始
```bash
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 导入:
```bash
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` | 导出新类型 |
## 生成后步骤
```bash
# 1. 同步数据库
pnpm db:generate && pnpm db:push
# 2. 执行种子脚本(如果生成了)
cd apps/api && npx ts-node prisma/seeds/{module}.seed.ts
# 3. 重启开发服务器
pnpm dev
```
## 完整示例
以生成「产品」模块为例:
```bash
$ 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
```handlebars
<span>{{openBrace}}item.id{{closeBrace}}</span>
```
输出:`<span>{item.id}</span>`
### Q: QueryDto 什么时候会被生成?
无论是否选择搜索字段QueryDto 都会被生成。这便于后续扩展查询参数。`hasQueryDto` 标志仅控制 Controller 中是否解构查询字段。
### Q: 如何执行种子脚本?
生成的种子脚本支持独立运行:
```bash
cd apps/api && npx ts-node prisma/seeds/{module}.seed.ts
```
或在主 `seed.ts` 中导入调用:
```typescript
import { seedProductModule } from './seeds/product.seed';
await seedProductModule(prisma);
```