25 KiB
25 KiB
CRUD 功能模块开发模板
📋 概述
本模板提供了一套完整的 CRUD 功能模块开发规范,适用于当前项目框架(基于 Vue3 + TypeScript + Element Plus + VxeTable)。通过统一的目录结构、命名规范和代码模板,确保项目代码的一致性和可维护性。
📁 目录结构规范
标准结构(基于当前项目框架)
src/apps/{模块名}/
├── api/ # 模块API接口
├── assets/ # 模块静态资源
├── components/ # 模块私有组件
├── lang/ # 模块国际化文件
├── mock/ # 模块模拟数据
├── routes/ # 模块路由配置
├── schemas/ # 模块CRUD配置
└── views/ # 模块页面视图
├── {实体名}/
│ ├── index.vue # 页面入口文件
│ ├── schemas/ # Schema配置
│ └── components/ # 私有组件(可选)
核心文件位置
- 页面入口:
src/apps/{模块名}/views/{实体名}/index.vue - Schema配置:
src/apps/{模块名}/views/{实体名}/schemas/index.tsx - API服务:
src/apps/{模块名}/api/{实体名}/index.ts - 组件库:
src/components/crud/(核心CRUD组件)
⚠️ 重要注意事项
- 禁止增加不必要的子目录,避免目录结构混乱
- 保持目录结构简洁统一
- 模块名使用小写字母,多个单词用连字符分隔
- 实体名使用小写字母,多个单词用连字符分隔
- 文件名统一使用小写字母和连字符
- 目录结构必须严格遵循此规范
🔤 命名规范
命名规则
- 数据库表名:使用下划线命名法,如
system_user、system_user_role - 模块名:使用表名中下划线前的部分,如
system - 实体名:使用表名中下划线后的部分,如
user,多个部分用中横线连接,如user-role - 文件名:使用中横线命名法,如
user-role.ts
命名示例
| 表名 | 模块名 | 实体名 | 实体小驼峰名 | API 服务文件 | API 服务类名 |
|---|---|---|---|---|---|
system_user |
system |
user |
user |
./src/app/api/system/user.ts |
UserService |
system_user_role |
system |
user-role |
userRole |
./src/app/api/system/user-role.ts |
UserRoleService |
📏 约束规则
- ID字段:不在列表上显示
- 列宽度:默认自适应,无需手动设置
- 备注字段:字段过多时不在列表显示
- 表单布局:
- 字段较少时:
colSpan默认为 24(全宽) - 字段较多时:可设置
colSpan为 12(半宽)
- 字段较少时:
🔧 API 服务模板
基础服务模板
文件路径: src/apps/{模块名}/api/{实体名}/index.ts
import { BaseService } from '@/utils/request'
import { {EntityName}Row } from '@/apps/{模块名}/api/{实体名}/types'
/**
* {实体名} API 服务
*/
export const {EntityName}Service = new BaseService<{EntityName}Row>('/{模块名}/{实体名}')
export default {EntityName}Service
BaseService 配置说明
- baseUrl: 接口基础路径
- allowedMethods: 允许的方法列表(空数组表示允许所有)
- forbiddenMethods: 禁止的方法列表(如
['export']表示禁用导出功能)
默认包含的接口方法
page(): 分页查询save(): 新增update(): 修改remove(): 删除detail(): 详情export(): 导出(可禁用)
📋 Schema 配置模板
基础配置模板
文件路径: src/apps/{模块名}/views/{实体名}/schemas/index.tsx
import { CrudSchema } from '@/components/crud'
import { {EntityName}Service } from '@/apps/{模块名}/api/{实体名}'
import { $t } from '@/locales'
/**
* {实体名} CRUD Schema 配置
*/
export const crudSchema = (): CrudSchema => {
return {
// 接口配置
api: {EntityName}Service.page,
// CRUD API 映射
crudApi: {
add: {EntityName}Service.save,
edit: {EntityName}Service.update,
remove: {EntityName}Service.remove,
view: {EntityName}Service.detail
},
// 功能开关
useCrud: true,
hasAdd: true,
hasRemove: true,
hasEdit: true,
hasView: true,
// 权限控制
addAuth: '{模块名}:{实体名}:create',
removeAuth: '{模块名}:{实体名}:delete',
editAuth: '{模块名}:{实体名}:update',
viewAuth: '{模块名}:{实体名}:read',
// 表格列定义
columns: [
// 选择列
{ type: 'selection' },
// 示例列定义
{
prop: 'name',
label: $t('{模块名}.{实体名}.list.table.columns.name'),
minWidth: 120
},
// 更多列定义...
],
// 搜索表单配置
searchFormSchema: {
schema: [
{
prop: 'LIKE_name',
label: $t('{模块名}.{实体名}.list.query.name'),
component: 'Input',
componentProps: {
clearable: true,
placeholder: $t('{模块名}.{实体名}.list.query.placeholder.name')
},
colSpan: 6
}
// 更多搜索字段...
]
},
// 弹窗配置
dialogProps: {
dialogType: 'drawer',
width: '50%'
},
// 表单配置
formSchema: {
showDefaultActions: false,
labelWidth: '100px',
schema: [
// 表单字段定义...
]
}
}
}
配置项说明
| 配置项 | 说明 | 默认值 |
|---|---|---|
useCrud |
是否启用 CRUD 功能 | true |
hasAdd |
是否显示新增按钮 | true |
hasRemove |
是否显示删除按钮 | true |
hasEdit |
是否显示编辑按钮 | true |
hasView |
是否显示详情按钮 | true |
labelWidth |
表单标签宽度 | 100px |
dialogProps.width |
弹窗宽度 | 50% |
🖥️ 页面入口模板
基础页面模板
文件路径: src/apps/{模块名}/views/{实体名}/index.vue
<template>
<div class="{模块名}-{实体名}-page art-full-height">
<BasicCrud />
</div>
</template>
<script setup lang="tsx">
import { useCrud } from '@/components/crud'
import { crudSchema } from './schemas'
// 使用 CRUD 组件
const [BasicCrud] = useCrud(crudSchema())
</script>
<style lang="scss" scoped>
.{模块名}-{实体名}-page {
height: 100%;
}
</style>
操作按钮配置说明
| 配置项 | 说明 | 示例 |
|---|---|---|
toolbarActions |
表格上方工具栏按钮 | 导出、批量操作等 |
dropDownToolbarActions |
工具栏下拉菜单 | 更多操作 |
tableActions |
表格行操作按钮 | 编辑、删除、详情等 |
dropDownActions |
行操作下拉菜单 | 扩展操作 |
⚙️ 关键配置说明
表格列配置 (columns)
{
prop: '字段名', // 字段名称
label: '列标题', // 列显示标题
component: '组件名', // 渲染组件,如 'ApiDict', 'ApiSelect' 等
componentProps: { // 组件属性
code: '字典编码', // 字典组件特有
renderType: 'Select' // 渲染类型
},
width: 120, // 列宽度(可选)
fixed: 'left' // 固定列(可选)
}
常用列属性:
type: 'selection': 选择列type: 'index': 序号列prop: 数据字段名label: 列标题component: 渲染组件width: 列宽度minWidth: 最小宽度fixed: 固定列位置
表单字段配置 (formSchema.schema)
{
label: '字段标签',
prop: '字段名',
component: '组件名', // 如 'Input', 'Select', 'ApiDict' 等
colSpan: 12, // 栅格占比 (1-24)
rules: [ // 校验规则
{
required: true,
message: '校验提示信息'
}
],
componentProps: { // 组件属性
placeholder: '占位符',
clearable: true
},
ifShow(values) { // 动态显示条件
return !!values.条件字段
}
}
常用表单属性:
colSpan: 栅格宽度(1-24)rules: 校验规则数组componentProps: 组件特有属性ifShow: 条件显示函数show: 静态显示控制
搜索表单配置 (searchFormSchema.schema)
{
prop: '操作符_字段名', // 如 'LIKE_name', 'EQ_status'
label: '搜索字段标签',
component: 'Input', // 搜索组件
componentProps: {
clearable: true,
placeholder: '请输入搜索条件'
},
colSpan: 6 // 栅格占比
}
常用搜索操作符:
EQ_: 等于LIKE_: 模糊匹配GT_: 大于LT_: 小于IN_: 包含
树形结构配置 (tree)
对于具有树形结构的数据(如菜单、组织架构等),需要配置 tree 属性:
export const crudSchema: CrudSchema = {
// 使用 tree 接口而不是 page 接口
api: {EntityName}Service.tree,
// 树型是全量数据,不需要搜索
useSearchForm: false,
// 树形结构配置
tree: {
id: 'id', // 树形节点唯一标识字段名
pid: 'parentId', // 父节点字段名
children: 'children' // 子节点字段名
},
// 其他配置...
}
树形配置注意事项:
- 必须使用
tree接口而非page接口 - 禁用搜索表单(
useSearchForm: false) - 确保 API 返回树形结构数据
🧩 常用组件
基础表单组件
| 组件 | 说明 | 常用属性 |
|---|---|---|
Input |
普通输入框 | placeholder, clearable |
InputNumber |
数字输入框 | min, max, precision |
Select |
下拉选择框 | options, clearable |
RadioGroup |
单选框组 | options |
CheckboxGroup |
多选框组 | options |
DatePicker |
日期选择器 | type, format |
Divider |
分割线 | content-position |
异步数据组件
| 组件 | 说明 | 常用属性 |
|---|---|---|
ApiDict |
字典组件 | code, renderType |
ApiSelect |
异步下拉选择 | api, labelField, valueField |
ApiCascader |
异步级联选择 | api, checkStrictly |
ApiTreeSelect |
异步树形选择 | api, checkStrictly |
ApiRadioGroup |
异步单选框组 | api |
ApiCheckboxGroup |
异步多选框组 | api |
特殊功能组件
| 组件 | 说明 | 常用属性 |
|---|---|---|
IconPicker |
图标选择器 | - |
Editor |
富文本编辑器 | - |
Upload |
文件上传组件 | accept, limit |
Avatar |
头像上传组件 | shape, size |
ApiDict 组件配置示例
{
label: '状态',
prop: 'status',
component: 'ApiDict',
componentProps: {
code: 'yes_no', // 字典编码
renderType: 'RadioGroup', // 渲染类型:Select/RadioGroup/Checkbox
isBtn: true // 按钮样式(RadioGroup特有)
}
}
ApiSelect 组件配置示例
{
label: '用户',
prop: 'userId',
component: 'ApiSelect',
componentProps: {
api: '/system/user/select', // 接口地址
labelField: 'name', // 显示字段
valueField: 'id', // 值字段
placeholder: '请选择用户'
}
}
🔒 权限控制
权限码配置
通过以下属性控制功能权限,权限码格式为 {模块名}:{实体名}:{操作}:
| 权限属性 | 说明 | 示例 |
|---|---|---|
addAuth |
新增权限码 | system:user:create |
editAuth |
编辑权限码 | system:user:update |
removeAuth |
删除权限码 | system:user:delete |
viewAuth |
查看权限码 | system:user:read |
权限配置示例
export const crudSchema: CrudSchema = {
// ... 其他配置
addAuth: 'system:user:create',
removeAuth: 'system:user:delete',
editAuth: 'system:user:update',
viewAuth: 'system:user:read'
// ...
}
🔄 CRUD 操作方法
useCrud 返回方法
通过 useCrud 返回的方法可以控制 CRUD 行为:
| 方法 | 说明 | 使用场景 |
|---|---|---|
reload() |
刷新表格数据 | 手动刷新 |
setState() |
设置组件状态 | 动态控制 |
removeByApi() |
通过 API 删除 | 自定义删除逻辑 |
refreshData() |
全量刷新数据 | 数据同步 |
refreshCreate() |
新增后刷新 | 新增操作后 |
refreshUpdate() |
更新后刷新 | 编辑操作后 |
refreshRemove() |
删除后刷新 | 删除操作后 |
方法使用示例
const [BasicCrud, { reload, refreshCreate }] = useCrud({
// ... 配置
})
// 手动刷新表格
const handleRefresh = () => {
reload()
}
// 新增后刷新
const handleCreateSuccess = () => {
refreshCreate()
}
📚 完整示例
示例一:普通表 - 岗位管理 (sys_post)
1. 接口文件
文件路径: src/apps/system/api/post/index.ts
import { BaseService } from '@/utils/request'
import { PostRow } from '@/apps/system/api/post/types'
/**
* 岗位管理 API 服务
*/
export const PostService = new BaseService<PostRow>('/system/post')
export default PostService
2. Schema 配置文件
文件路径: src/apps/system/views/post/schemas/index.tsx
import { CrudSchema } from '@/components/crud'
import { PostService } from '@/apps/system/api/post'
import { $t } from '@/locales'
/** 岗位管理 CRUD 配置 */
export const crudSchema = (): CrudSchema => {
return {
// 基础配置
api: PostService.page,
dialogTitle: '岗位',
// API 映射
crudApi: {
add: PostService.save,
edit: PostService.update,
remove: PostService.remove,
view: PostService.detail
},
// 功能开关
useCrud: true,
hasAdd: true,
hasRemove: true,
hasEdit: true,
hasView: true,
// 权限控制
addAuth: 'system:post:create',
removeAuth: 'system:post:delete',
editAuth: 'system:post:update',
viewAuth: 'system:post:read',
// 表格列定义
columns: [
{ type: 'selection' },
{
prop: 'name',
label: $t('system.post.list.table.columns.name'),
minWidth: 120
},
{
prop: 'code',
label: $t('system.post.list.table.columns.code'),
minWidth: 120
},
{
prop: 'sort',
label: $t('system.post.list.table.columns.sort'),
minWidth: 80
},
{
prop: 'enabled',
label: $t('system.post.list.table.columns.enabled'),
component: 'ApiDict',
componentProps: { code: 'yes_no' },
minWidth: 100
},
{
prop: 'remark',
label: $t('system.post.list.table.columns.remark'),
minWidth: 150
}
],
// 搜索表单
searchFormSchema: {
schema: [
{
prop: 'LIKE_name',
label: $t('system.post.list.query.name'),
component: 'Input',
componentProps: {
clearable: true,
placeholder: $t('system.post.list.query.placeholder.name')
},
colSpan: 6
},
{
prop: 'EQ_enabled',
label: $t('system.post.list.query.enabled'),
component: 'ApiDict',
componentProps: {
code: 'yes_no',
placeholder: $t('system.post.list.query.placeholder.enabled')
},
colSpan: 6
}
]
},
// 弹窗配置
dialogProps: {
dialogType: 'drawer',
width: '50%'
},
// 表单配置
formSchema: {
showDefaultActions: false,
labelWidth: '100px',
schema: [
{
label: $t('system.post.form.fields.id'),
prop: 'id',
component: 'Input',
colSpan: 24,
show: false,
ifDetail: false
},
{
label: $t('system.post.form.fields.name'),
prop: 'name',
component: 'Input',
rules: [{ required: true, message: $t('system.post.form.rules.name.required') }],
componentProps: {
placeholder: $t('system.post.form.placeholder.name')
},
colSpan: 24
},
{
label: $t('system.post.form.fields.code'),
prop: 'code',
component: 'Input',
rules: [{ required: true, message: $t('system.post.form.rules.code.required') }],
componentProps: {
placeholder: $t('system.post.form.placeholder.code')
},
colSpan: 24
},
{
label: $t('system.post.form.fields.sort'),
prop: 'sort',
component: 'InputNumber',
componentProps: {
min: 0,
placeholder: $t('system.post.form.placeholder.sort')
},
colSpan: 24
},
{
label: $t('system.post.form.fields.enabled'),
prop: 'enabled',
component: 'ApiDict',
componentProps: {
code: 'yes_no',
isBtn: true,
renderType: 'RadioGroup'
},
rules: [{ required: true, message: $t('system.post.form.rules.enabled.required') }],
colSpan: 24
},
{
label: $t('system.post.form.fields.remark'),
prop: 'remark',
component: 'Input',
componentProps: {
type: 'textarea',
placeholder: $t('system.post.form.placeholder.remark')
},
colSpan: 24
}
]
}
}
}
3. 页面入口文件
文件路径: src/apps/system/views/post/index.vue
<template>
<div class="system-post-page art-full-height">
<BasicCrud />
</div>
</template>
<script setup lang="tsx">
import { useCrud } from '@/components/crud'
import { crudSchema } from './schemas'
// 使用 CRUD 组件
const [BasicCrud] = useCrud(crudSchema())
</script>
<style lang="scss" scoped>
.system-post-page {
height: 100%;
}
</style>
示例二:树型表 - 部门管理 (sys_dept)
1. 接口文件
文件路径: src/apps/system/api/dept/index.ts
import { BaseService } from '@/utils/request'
import { DeptRow } from '@/apps/system/api/dept/types'
/**
* 部门管理 API 服务
*/
export const DeptService = new BaseService<DeptRow>('/system/dept')
// 扩展树形接口
export const getDeptTree = (args?: Record<string, any>) => {
const params = { ...args, format: 'tree' }
return DeptService.list<DeptRow[]>(params)
}
export default DeptService
2. Schema 配置文件
文件路径: src/apps/system/views/dept/schemas/index.tsx
import { CrudSchema } from '@/components/crud'
import { DeptService, getDeptTree } from '@/apps/system/api/dept'
import { $t } from '@/locales'
/** 部门管理 CRUD 配置 */
export const crudSchema = (): CrudSchema => {
return {
// 接口配置:使用 tree 接口以支持树形结构
api: getDeptTree,
// CRUD API 映射
crudApi: {
add: DeptService.save,
edit: DeptService.update,
remove: DeptService.remove,
view: DeptService.detail
},
// 功能开关
useCrud: true,
hasAdd: true,
hasRemove: true,
hasEdit: true,
hasView: true,
// 权限控制
addAuth: 'system:dept:create',
removeAuth: 'system:dept:delete',
editAuth: 'system:dept:update',
viewAuth: 'system:dept:read',
// 禁用搜索表单,因为树形结构是全量数据
useSearchForm: false,
// 树形结构配置
tree: {
id: 'id',
pid: 'pid',
children: 'children'
},
columns: [
{
type: 'selection'
},
{
prop: 'name',
label: $t('system.dept.list.table.columns.name'),
component: 'Input',
minWidth: 150
},
{
prop: 'code',
label: $t('system.dept.list.table.columns.code'),
component: 'Input',
minWidth: 120
},
{
prop: 'sort',
label: $t('system.dept.list.table.columns.sort'),
component: 'InputNumber',
minWidth: 80
},
{
prop: 'enabled',
label: $t('system.dept.list.table.columns.enabled'),
component: 'ApiDict',
componentProps: {
code: 'yes_no',
renderType: 'RadioGroup'
},
minWidth: 100
}
],
searchFormSchema: {
schema: []
},
// 弹窗配置
dialogProps: {
dialogType: 'drawer',
width: '50%'
},
// 表单配置
formSchema: {
showDefaultActions: false,
labelWidth: '100px',
schema: [
{
label: $t('system.dept.form.fields.id'),
prop: 'id',
component: 'Input',
colSpan: 24,
show: false,
ifDetail: false
},
{
label: $t('system.dept.form.fields.pid'),
prop: 'pid',
component: 'ApiTreeSelect',
componentProps: {
api: '/system/dept/tree',
labelField: 'name',
valueField: 'id',
// 任意节点可选
checkStrictly: true,
clearable: true,
placeholder: '请选择父级部门'
},
colSpan: 24,
rules: [
{
required: true,
message: '请选择父级部门'
}
]
},
{
label: '部门名称',
prop: 'name',
component: 'Input',
componentProps: {
placeholder: '请输入部门名称'
},
colSpan: 24,
rules: [
{
required: true,
message: '请输入部门名称'
}
]
},
{
label: '唯一编码',
prop: 'code',
component: 'Input',
componentProps: {
placeholder: '请输入唯一编码'
},
colSpan: 24,
rules: [
{
required: true,
message: '请输入唯一编码'
}
]
},
{
label: '排序',
prop: 'sort',
component: 'InputNumber',
componentProps: {
min: 0,
max: 9999
},
colSpan: 24,
rules: [
{
required: true,
message: '请输入排序值'
}
]
},
{
label: '是否启用',
prop: 'enabled',
component: 'ApiDict',
componentProps: {
code: 'yes_no',
isBtn: true,
renderType: 'RadioGroup'
},
colSpan: 24,
rules: [
{
required: true,
message: '请选择启用状态'
}
]
},
{
label: '部门负责人',
prop: 'leaderIdList',
component: 'ApiSelect',
componentProps: {
api: '/system/user/select',
placeholder: '请选择负责人',
multiple: true
},
colSpan: 24
},
{
label: '分管领导',
prop: 'main_leader_id',
component: 'ApiSelect',
componentProps: {
api: '/system/user/select',
placeholder: '请选择分管领导'
},
colSpan: 24
},
{
label: '备注',
prop: 'remark',
component: 'Input',
componentProps: {
type: 'textarea',
rows: 3,
placeholder: '请输入备注'
},
colSpan: 24
}
]
}
}
}
3. 页面入口文件
文件路径: src/apps/system/views/dept/index.vue
<template>
<div class="sys-dept-page art-full-height">
<BasicCrud />
</div>
</template>
<script setup lang="tsx">
import { useCrud } from '@/components/crud'
import { crudSchema } from './schemas'
const [BasicCrud] = useCrud(crudSchema())
</script>
<style lang="scss" scoped>
.sys-dept-page {
height: 100%;
}
</style>