不讲架构,不讲原理,这篇就是手把手:怎么用 Forge Admin 10 分钟搭一个能跑的 CRUD 页面。
做过后台的都知道,一个"列表 + 搜索 + 新增 + 编辑 + 删除"的标准 CRUD 页面,要写多少东西:
熟练的开发,两天能写完一个。不熟练的,三天起步。
但如果你的后台框架有一套协议驱动的 CRUD 组件,10 分钟就能配出一个能跑的页面。
这篇就用 Forge Admin 的 AiCrudPage 组件,从零搭一个"业务类型管理"页面,全程配置,几乎不写业务代码。
目标:一个"消息业务类型管理"页面,包含:
效果就是后台系统里最常见的那个页面。区别是:别人写两天,我们配 10 分钟。
AiCrudPage 不生成接口,它只消费接口。所以后端要先准备好标准的 5 个 RESTful 接口:
| 操作 | 方法 | 路径 |
|---|---|---|
| 分页查询 | GET | /api/message/bizType/page |
| 详情查询 | GET | /api/message/bizType/:id |
| 新增 | POST | /api/message/bizType |
| 修改 | PUT | /api/message/bizType |
| 删除 | DELETE | /api/message/bizType/:id |
如果你用了 Forge 的代码生成器,这 5 个接口是一键生成的。这里假设你已经有了。
关键约定:分页参数前端传
pageNum+pageSize,后端必须用相同命名接收。URL 占位符用:id(冒号),不是{id}(花括号)。
AiCrudPage 的核心配置之一是 apiConfig,它告诉组件每个操作对应哪个接口:
const apiConfig = {
list: 'get@/api/message/bizType/page',
detail: 'get@/api/message/bizType/:id',
add: 'post@/api/message/bizType',
update: 'put@/api/message/bizType',
delete: 'delete@/api/message/bizType/:id',
}
格式是 方法@路径,就这一行一个接口,清晰明了。
list:分页查询detail:详情(编辑时回显用)add:新增update:修改delete:删除注意占位符是 :id,不是 {id}。这是组件的约定,写错了识别不了。
搜索表单也是一个 JSON 数组,每个对象是一个搜索字段:
const searchSchema = [
{
field: 'bizType',
label: '业务类型编码',
type: 'input',
props: {
placeholder: '请输入业务类型编码',
},
},
{
field: 'bizName',
label: '业务类型名称',
type: 'input',
props: {
placeholder: '请输入业务类型名称',
},
},
{
field: 'enabled',
label: '状态',
type: 'select',
props: {
placeholder: '请选择状态',
clearable: true,
options: [
{ label: '启用', value: 1 },
{ label: '禁用', value: 0 },
],
},
},
]
type 支持的常用类型:
input:文本输入select:下拉选择datePicker:日期选择dict:字典下拉(配合 useDict 用)每个字段的 props 会透传给底层 Naive UI 组件,所以 Naive UI 支持的属性这里都能用。
如果是状态、类型这种枚举,建议用字典组件
DictSelect,不要在页面里写死options。这是项目规范。
表格列也是一个数组:
const tableColumns = [
{ prop: 'bizType', label: '业务类型编码', width: 150 },
{ prop: 'bizName', label: '业务类型名称', width: 150 },
{ prop: 'jumpUrl', label: '跳转URL模板', ellipsis: { tooltip: true } },
{
prop: 'jumpTarget',
label: '跳转方式',
width: 100,
render: (row) => {
return h(NTag, { size: 'small' }, {
default: () => row.jumpTarget === '_blank' ? '新窗口' : '当前页',
})
},
},
{ prop: 'icon', label: '图标', width: 100 },
{ prop: 'sort', label: '排序', width: 80 },
]
几个常用属性:
prop:字段名(对应后端返回的字段)label:列标题width:列宽ellipsis:超长省略,tooltip: true 鼠标悬停显示完整内容render:自定义渲染(用 h 函数渲染标签、按钮等)需要渲染状态标签、操作按钮、图片的地方,用 render 写。不需要自定义的,填 prop + label 就行。
弹窗里的表单也是 schema 配置:
const editSchema = [
{
field: 'bizType',
label: '业务类型编码',
type: 'input',
rules: { required: true, message: '请输入业务类型编码' },
},
{
field: 'bizName',
label: '业务类型名称',
type: 'input',
rules: { required: true, message: '请输入业务类型名称' },
},
{
field: 'jumpUrl',
label: '跳转URL模板',
type: 'input',
},
{
field: 'jumpTarget',
label: '跳转方式',
type: 'select',
props: {
options: [
{ label: '当前页', value: '_self' },
{ label: '新窗口', value: '_blank' },
],
},
},
{
field: 'sort',
label: '排序',
type: 'inputNumber',
defaultValue: 0,
},
{
field: 'enabled',
label: '状态',
type: 'switch',
defaultValue: 1,
},
]
rules 是表单校验规则,defaultValue 是新增时的默认值。
新增和编辑共用一份 schema。编辑时,组件会自动调 detail 接口回显数据(需要配 :load-detail-on-edit="true")。
把上面三份配置交给 AiCrudPage,页面就完成了:
<template>
<div class="biz-type-page">
<AiCrudPage
ref="crudRef"
:api-config="apiConfig"
:search-schema="searchSchema"
:columns="tableColumns"
row-key="id"
:edit-schema="editSchema"
:load-detail-on-edit="true"
/>
</div>
</template>
<script setup>
import { NTag } from 'naive-ui'
import { h } from 'vue'
import { AiCrudPage } from '@/components/ai-form'
defineOptions({ name: 'MessageBizType' })
const crudRef = ref(null)
const apiConfig = {
list: 'get@/api/message/bizType/page',
detail: 'get@/api/message/bizType/:id',
add: 'post@/api/message/bizType',
update: 'put@/api/message/bizType',
delete: 'delete@/api/message/bizType/:id',
}
const searchSchema = [ /* 上面第四步的内容 */ ]
const tableColumns = [ /* 上面第五步的内容 */ ]
const editSchema = [ /* 上面第六步的内容 */ ]
</script>
就这么多。一个完整的 CRUD 页面,包含搜索、表格、分页、新增弹窗、编辑回显、删除确认,全部到位。
整个 Vue 文件大约 150 行,其中大部分是配置数据,几乎不写逻辑代码。
上面是"写配置"的方式。如果你连配置都不想写,Forge 还提供了可视化低代码搭建器:
这条路适合:
而 AiCrudPage 适合:
两条路,按需选。
| 对比项 | 传统手写 | AiCrudPage 配置 |
|---|---|---|
| 开发时间 | 1-2 天 | 10 分钟 |
| 代码量 | 5-6 个文件,数百行 | 1 个 Vue 文件,约 150 行 |
| 改字段 | 改 Entity/DTO/VO/Mapper/Vue | 改 schema 配置 |
| 加搜索条件 | 改 Mapper XML + Vue | 加一个 schema 对象 |
| 改表格列 | 改 Vue | 改 columns 数组 |
| 一致性 | 看开发水平 | 组件统一保证 |
| 复杂业务 | 灵活 | 可下载代码包二次开发 |
这不是说传统写法没用。复杂业务逻辑、复杂联表查询、特殊交互,该手写还是手写。但 80% 的标准 CRUD 页面,用配置能省大量时间。
1. 占位符用 :id 不是 {id}
// ✅ 对
detail: 'get@/api/message/bizType/:id'
// ❌ 错
detail: 'get@/api/message/bizType/{id}'
组件只识别 includes(':id'),写花括号会匹配不到。
2. 分页参数叫 pageNum 不叫 page
后端 Controller 必须用 @RequestParam(defaultValue = "1") Integer pageNum,不能用 page。前后端命名必须一致。
3. 字典别写死 options
状态、类型这种枚举值,不要在 schema 里写死 options。维护到 sys_dict_type + sys_dict_data,前端用 useDict 或 DictSelect。这样加一个枚举值不用改代码。
4. schema 定义成 computed
如果 schema 里用了字典(useDict),必须把 schema 定义成 computed,确保字典异步加载后响应式更新:
const editSchema = computed(() => [
{
field: 'status',
label: '状态',
type: 'dict',
dictType: 'sys_normal_disable',
},
])
5. 图片字段用 AuthImage
如果表格里有图片列,用 AuthImage 组件渲染(自动带 Token),不要直接用 NAvatar 的 src。imageUpload 存的是 fileId 不是 URL。
适合用:
不适合用:
我的建议是: 先用 AiCrudPage 把 80% 的标准页面快速搞定,省下的时间投入到那 20% 真正需要手写的复杂业务上。这才是低代码该有的价值——不是替代开发,是把重复劳动自动化。
光看不练假把式,直接上演示站(账号 admin / 123456):
你平时搭一个 CRUD 页面要多久?有没有试过用配置驱动的方式?评论区聊聊,觉得有用求个👍收藏。