# Vben3 CRUD 页面模板生成 Skill
> 用于**成堪数智平台**快速生成标准 CRUD 页面。
> 核心产出:`index.vue` + `config.ts` + `drawer.vue` + `api/xxx.ts` 四件套。
---
## 一、技术栈
| 层级 | 技术 | 层级 | 技术 |
|------|------|------|------|
| 框架 | Vue 3 Composition API + `
删除
```
### 5.4 TreeWrapper模板(左树右表)
> 左侧树节点点击 → 右侧表格按`menuId`过滤;新增时预设当前树节点。
**生成步骤**:
| # | 文件 | 操作 | 必改处 |
|---|------|------|--------|
| ① | `comp/treeWrapper.vue` | 直接复制,零修改 | 0 |
| ② | `comp/tree.vue` | 复制后替换树API | **1** |
| ③ | `config.ts` | 复制后填充表格/搜索/表单 | **3** |
| ④ | `drawer.vue` | 复制后替换CRUD API | **1** |
| ⑤ | `index.vue` | 复制后替换CRUD API | **1** |
> 生成时先将`{module}`替换为模块名(如`user`),`{模块名称}`替换为中文(如`用户`)。
#### 5.4.1 treeWrapper.vue(复制即用)
```vue
```
#### 5.4.2 tree.vue(改1处:树API)
```vue
```
#### 5.4.3 config.ts(改3处:表格列/搜索/表单)
```typescript
import { cloneDeep, session } from '@vben/utils';
import { get{Module}Page } from '#/api/{module}'; // ⓐ 共用API
let globParams: Record = {};
export const setGlobParams = (params: any) => { globParams = { ...globParams, ...params }; };
export const tableConfig = {
options: {
tableTitle: '{模块名称}列表',
gridOptions: {
checkboxConfig: { reserve: true },
columns: [ // ⓑ 替换表格列
// { field: 'name', minWidth: 200, title: '名称' },
// { field: 'isEnable', width: 90, title: '状态', slots: { default: "isEnable" } },
],
proxyConfig: {
ajax: {
query: async (params: any, formValues: any) => {
const res: any = await get{Module}Page({ ...params.page, ...formValues, ...globParams });
if (res.code !== 1) return { items: [], total: 0 };
return { items: cloneDeep(res.result.records), total: +res.result.total || 0 };
},
},
},
},
formOptions: {
commonConfig: { labelWidth: 60 }, showCollapseButton: false, collapsed: false,
schema: [ // ⓒ 替换搜索字段
// { component: 'Input', fieldName: 'keyword', label: '关键字', componentProps: { clearable: true, placeholder: '请输入' } },
],
wrapperClass: 'grid-cols-1 md:grid-cols-4',
submitButtonOptions: { auth: '@ums:{module}:search' }, resetButtonOptions: { auth: '@ums:{module}:reset' },
},
},
formObj: {
operateOptions: [
{ code: 'edit', text: '编辑', type: 'primary', directives: [{ name: 'auth', value: '@ums:{module}:edit' }] },
{ code: 'delete', text: '删除', type: 'danger', directives: [{ name: 'auth', value: '@ums:{module}:delete' }] },
{ code: 'menuAuth', text: '权限配置', type: 'primary', directives: [{ name: 'auth', value: '@ums:{module}:auth' }] },
],
},
isView: false, showOperate: true, showSeq: true, buttonAuth: { addBtn: '@ums:{module}:add' },
};
export const drawerConfig = {
formConfig: {
wrapperClass: 'grid-cols-1', layout: 'horizontal',
schemaConfig: [{
schema: [ // ⓓ 替换表单字段
{ formItemClass: 'form-item-col3 hidden', fieldName: 'id', label: '', dependencies: { show: false } },
// { component: 'Input', componentProps: { placeholder: '请输入名称', clearable: true }, fieldName: 'name', label: '名称', rules: 'required' },
// { component: 'ApiTreeSelect', componentProps: { labelField: 'name', valueField: 'id', childrenField: 'children', api: () => session.getItem("menuNode"), multiple: true }, fieldName: 'menus', label: '上级菜单', rules: 'required' },
],
}],
},
tableConfig: { show: false }, mapConfig: { show: false }, layoutConfig: { column: 1 },
};
// 权限树配置(供drawerAuth使用)
const treeConfig = { searchOptions: { show: true, placeholder: '搜索' }, treeOptions: { options: { expandOnClickNode: false, data: [] as any, nodeKey: 'id', checkStrictly: false } } };
export const authTreeOptions = { title: '未授权角色', ...treeConfig, treeOptions: { ...treeConfig.treeOptions, options: { ...treeConfig.treeOptions.options, showCheckbox: true, props: { children: 'children', label: 'name', value: 'id' } } } };
export const authSelectTreeOptions = { title: '已授权角色', ...treeConfig, treeOptions: { ...treeConfig.treeOptions, options: { ...treeConfig.treeOptions.options, showCheckbox: true, props: { children: 'children', label: 'name', value: 'id' } } } };
```
#### 5.4.4 drawer.vue(改1处:CRUD API)
```vue
```
#### 5.4.5 drawerAuth.vue(可选,角色授权)
> 不需要可跳过,同时删除index.vue中的``和config.ts中的`authTreeOptions`/`authSelectTreeOptions`。依赖`#/components/treeTransfer/fileOuth.vue`。
```vue
```
#### 5.4.6 index.vue(改1处:CRUD API)
```vue
删除
```
> 新增时预设 `menusArr` 为当前树节点;编辑时通过 `get{Module}ById` 回填菜单权限;提交时用 `commWay.getFileRes` 处理文件。**不需要文件上传或菜单关联可删除对应逻辑。**
```vue
```
### 5.5 drawer.vue 基础版(仅表单)
```vue
```
### 5.6 drawer.vue 内嵌表格版
```vue
添加行
```
## 六、表单组件速查
| component | 用途 | 关键componentProps |
|-----------|------|--------------------|
| `Input` | 文本输入 | `placeholder`, `clearable`, `type: 'textarea'`, `rows`, `maxLength` |
| `Select` / `ApiSelect` | 下拉/远程下拉 | `options`/`api`, `labelField`, `valueField`, `multiple` |
| `ApiTreeSelect` | 远程树选择 | `api`, `labelField`, `valueField`, `childrenField`, `multiple` |
| `RadioGroup` / `ApiRadioGroup` | 单选组 | `options: [{ label, value }]`/`api` |
| `DatePicker` | 日期选择 | `format: 'YYYY-MM-DD'`, `valueFormat: 'YYYY-MM-DD HH:mm:ss'` |
| `Switch` | 开关 | `activeValue`, `inactiveValue` |
### 校验规则
```typescript
rules: 'required' // 必填
rules: 'selectRequired' // 必选
rules: z.string().email({ message: '请输入正确的电子邮箱' }).nullable().optional() // 邮箱
rules: z.string().refine((value) => regexp.phone.test(String(value)), { message: '请输入正确的联系电话' }).nullable().optional() // 正则
dependencies: { if(values: any) { return values.type === 'temporary' }, triggerFields: ['type'] } // 条件显示
```
## 七、配置对象核心字段
### 7.1 tableConfig
```typescript
{
options: { tableTitle: '列表标题', gridOptions: { checkboxConfig: { reserve: true }, columns: [], proxyConfig: { ajax: { query: async (params, formValues) => ({ items, total }) } } }, formOptions: { schema: [], wrapperClass: 'grid-cols-1 md:grid-cols-4' } },
formObj: { operateOptions: [{ code, text, type, directives: [{ name: 'auth', value }] }] },
isView: false, showOperate: true, showSeq: true, buttonAuth: { addBtn: '@ums:{module}:add' },
}
```
### 7.2 drawerConfig
```typescript
{
formConfig: { wrapperClass: 'grid-cols-1 md:grid-cols-3', layout: 'horizontal', schemaConfig: [{ schema: [] }] },
tableConfig: { show: false }, // show: true时配columns/editConfig
mapConfig: { show: false }, layoutConfig: { column: 1 },
}
```
## 八、常用样式类
| 类名 | 用途 |
|------|------|
| `form-item-col3` | 表单项占满整行(3列布局) |
| `form-item-col3 hidden` | 隐藏表单字段(如id) |
| `grid-cols-1 md:grid-cols-3` | 表单3列响应式 |
| `grid-cols-1 md:grid-cols-4` | 搜索表单4列响应式 |
| `w-[600px]` | 抽屉宽度(class-name) |
## 九、权限标识
```
格式: @ums:{module}:{action}
常见action: add/edit/delete/search/reset/detail/import/export/auth
使用: v-auth指令 / buttonAuth / directives / submitButtonOptions
```
## 十、API响应
```typescript
{ code: 1, result: { records: [...], total: 100 } } // 分页
{ items: [...], total: 100 } // 前端代理返回
```
## 十一、导入速查
```typescript
// index.vue: EditTable, Page, getIconFont, ElButton/ElMessage/ElPopconfirm/ElSwitch
// drawer.vue: Drawer, AddForm, simpleTitle, ElButton/ElMessage
// config.ts: useVbenForm, cloneDeep, session, z (from @vben-core/form-ui)
// 路径别名: #/* → ./src/*
```
## 十二、生成步骤
1. **API文件**: `src/api/{module}.ts` (分页+列表+ID查询+新增+修改+删除)
2. **页面目录**: `src/views/{module}/`
3. **config.ts**: tableConfig(表格/搜索) + drawerConfig(表单)
4. **drawer.vue**: 根据是否有子表选模板
5. **index.vue**: 根据是否有左树选模板
6. **路由**: 添加路由配置
7. **验证**: 启动检查