# 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` | 页面组件 | **权限控制**:生成的前端组件使用 `` 包裹需要权限的操作按钮。 ### 共享类型 (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` | 生成表单控件 | `` | | `cellRenderer` | 生成表格单元格渲染 | `` | | `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 {{openBrace}}item.id{{closeBrace}} ``` 输出:`{item.id}` ### 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); ```