表单设计器
表单设计器
业务用途
表单设计器让运营人员通过拖拽组件来设计数据录入表单,无需编写前端代码。设计产物以 form_json 存入 lc_form 表,保存时自动同步生成一条 lc_page 记录(page_code = form_{formCode}),使表单能被运行时引擎统一渲染。
表单设计器支持以下能力:
- 组件拖拽布局:栅格行 + 列布局,支持文本框、文本域、下拉框、单选、复选、日期、文件上传等组件
- 字段绑定:每个组件关联
lc_column_meta中的一个字段,提交时按驼峰 key 写入业务表 - 审批绑定:通过
approvalEnabled+processModelId字段,将表单提交流程绑定到 BPM 工作流 - 版本管理:保存版本快照、回滚、版本对比
- 发布管理:
draft/published状态切换
涉及文件
| 层 | 文件路径 | 说明 |
|---|---|---|
| Controller | backend/src/main/java/com/lowcode/controller/FormController.java | /api/form 端点 |
| Controller | backend/src/main/java/com/lowcode/controller/FormVersionController.java | /api/form/version 版本端点 |
| Entity | backend/src/main/java/com/lowcode/entity/LcForm.java | 表单实体(@TableName("lc_form")) |
| Mapper | backend/src/main/java/com/lowcode/mapper/LcFormMapper.java | MyBatis-Plus Mapper |
| 前端 | frontend/src/views/designer/form/builder.vue | 表单设计器主界面 |
| 前端 | frontend/src/views/designer/form/list.vue | 表单列表 |
| 前端 | frontend/src/views/designer/form/config/componentProps.ts | 组件属性配置 |
| 前端 | frontend/src/views/designer/form/components/PropertyPanel.vue | 属性面板 |
| 前端 | frontend/src/views/runtime/components/RuntimeForm.vue | 运行时表单渲染 |
数据库表
lc_form(表单表)
CREATE TABLE `lc_form` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
`app_id` BIGINT COMMENT '所属应用ID',
`tenant_id` BIGINT DEFAULT 1 COMMENT '租户编号',
`form_name` VARCHAR(100) NOT NULL COMMENT '表单名称',
`form_code` VARCHAR(50) NOT NULL COMMENT '表单编码',
`table_id` BIGINT COMMENT '关联表ID',
`form_json` LONGTEXT COMMENT '表单JSON配置',
`status` VARCHAR(20) DEFAULT 'draft' COMMENT '状态(draft/published)',
`approval_enabled` TINYINT COMMENT '是否启用审批',
`process_model_id` BIGINT COMMENT '审批流程模型ID',
`create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
`create_by` VARCHAR(50) COMMENT '创建者',
`update_by` VARCHAR(50) COMMENT '更新者',
`deleted` TINYINT DEFAULT 0 COMMENT '是否删除',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_form_code` (`form_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='表单表';LcForm 实体字段
@TableName("lc_form")
public class LcForm extends BaseEntity {
private Long appId; // 所属应用ID
private String formName; // 表单名称
private String formCode; // 表单编码(唯一)
private Long tableId; // 关联的数据表ID
private String formJson; // 表单配置JSON
private String status; // 状态: draft / published
@TableField("approval_enabled")
private Boolean approvalEnabled; // 是否启用审批
@TableField("process_model_id")
private Long processModelId; // BPM流程模型ID
}审批绑定
当 approvalEnabled = true 且 processModelId 不为空时,表单提交会走 BPM 审批流程。运行时 RuntimeForm.vue 提交时会调用 POST /api/biz-approval/submit/{formId},而不是直接调用 POST /api/database/table/{id}/data。审批通过后状态自动回写业务表。
后端实现
端点列表
表单管理(FormController -- /api/form)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/form/list?pageNum=1&pageSize=10&appId=&keyword= | 分页查询表单列表 |
| GET | /api/form/get/{id} | 获取表单详情 |
| GET | /api/form/getByCode/{code} | 按编码获取表单 |
| POST | /api/form/save | 保存表单(新增或更新),自动同步页面 |
| DELETE | /api/form/delete/{id} | 删除表单 |
| POST | /api/form/publish/{id} | 发布表单(status -> published) |
| POST | /api/form/unpublish/{id} | 停用表单(status -> draft) |
版本管理(FormVersionController -- /api/form/version)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/form/version/save/{formId}?versionDesc= | 保存版本快照 |
| GET | /api/form/version/history/{formId} | 获取版本历史列表 |
| GET | /api/form/version/{formId}/{version} | 获取指定版本 |
| GET | /api/form/version/current/{formId} | 获取当前版本 |
| POST | /api/form/version/rollback/{formId}/{version} | 回滚到指定版本 |
| GET | /api/form/version/compare/{formId}?version1=&version2= | 对比两个版本 |
保存表单 -- save() + syncFormToPage()
@PostMapping("/save")
public Result<Long> save(@RequestBody LcForm form) {
// 校验 formJson 非空且格式正确
if (form.getFormJson() == null || form.getFormJson().isEmpty()) {
return Result.error(400, "表单配置不能为空");
}
try {
new ObjectMapper().readTree(form.getFormJson()); // JSON 格式校验
} catch (Exception e) {
return Result.error(400, "表单JSON格式错误");
}
if (form.getId() == null) {
// 新增:检查 formCode 唯一性
LcForm existing = formMapper.selectOne(
new LambdaQueryWrapper<LcForm>().eq(LcForm::getFormCode, form.getFormCode()));
if (existing != null) return Result.error(500, "表单编码已存在");
formMapper.insert(form);
} else {
formMapper.updateById(form);
}
// 自动同步到 lc_page 表,供运行时渲染
syncFormToPage(form);
return Result.success("保存成功", form.getId());
}syncFormToPage() 方法把表单配置包装成 pageJson,写入 lc_page 表:
private void syncFormToPage(LcForm form) {
if (form.getAppId() == null) return;
// 构建 pageJson:一行一列,放一个 form 组件
String pageJson = "{\"rows\":[{\"id\":1,\"gutter\":16,\"components\":[{\"id\":1,"
+ "\"type\":\"form\",\"colSpan\":24,\"config\":{\"title\":\"" + form.getFormName()
+ "\",\"formId\":" + form.getId() + ",\"tableId\":" + form.getTableId()
+ ",\"approvalEnabled\":" + Boolean.TRUE.equals(form.getApprovalEnabled())
+ ",\"processModelId\":" + form.getProcessModelId() + "}}]}]}";
// 按 pageCode = "form_" + formCode 查找已有页面
LcPage existing = pageService.lambdaQuery()
.eq(LcPage::getPageCode, "form_" + form.getFormCode()).one();
if (existing != null) {
existing.setPageName(form.getFormName());
existing.setPageJson(pageJson);
existing.setStatus(form.getStatus());
pageService.updateById(existing);
} else {
LcPage page = new LcPage();
page.setAppId(form.getAppId());
page.setPageName(form.getFormName());
page.setPageCode("form_" + form.getFormCode());
page.setPageType("form");
page.setPageJson(pageJson);
page.setRoutePath("/form/" + form.getFormCode());
page.setStatus(form.getStatus() != null ? form.getStatus() : "draft");
pageService.save(page);
}
}统一页面模型
表单保存时自动生成 lc_page 记录,page_code 格式为 form_{formCode},page_type 为 form。运行时引擎只需读取 lc_page 即可渲染表单,无需知道它是表单还是列表。
前端实现
表单设计器界面
frontend/src/views/designer/form/builder.vue 提供:
- 左侧:组件面板(可拖拽的表单组件列表)
- 中间:画布区域(栅格行 + 列布局,拖入组件排列)
- 右侧:属性面板(
PropertyPanel.vue,编辑选中组件的属性)

保存与加载
// 保存表单(builder.vue 第 541 行)
const res = await request.post('/form/save', {
id: formId.value,
appId: appId.value,
formName: formName.value,
formCode: formCode.value,
tableId: selectedTableId.value,
formJson: JSON.stringify(designerData),
approvalEnabled: approvalConfig.enabled,
processModelId: approvalConfig.processModelId,
status: 'draft'
})
// 加载表单详情(builder.vue 第 780 行)
const res = await request.get(`/form/get/${formId}`)运行时渲染
RuntimeForm.vue 在运行时加载表单配置并渲染:
// RuntimeForm.vue 第 439 行
const res = await request.get(`/form/get/${props.config.formId}`)
// 提交数据(第 538 行)
if (props.config.approvalEnabled) {
// 走审批流程
res = await request.post('/biz-approval/submit/' + props.config.formId, { ...formData })
} else {
// 直接写入业务表
res = await request.post('/database/table/' + props.config.tableId + '/data', { ...formData })
}
操作步骤
- 进入「设计器 -> 表单设计」页面,点击「新建表单」
- 填写表单名称、编码,选择关联数据表(
tableId) - 从左侧组件面板拖拽组件到画布
- 在右侧属性面板配置每个组件:绑定字段、标签、是否必填、默认值等
- 如需审批:勾选「启用审批」,选择 BPM 流程模型(
processModelId) - 点击「保存」,表单配置存入
lc_form.form_json,同时自动生成lc_page记录 - 点击「发布」使表单状态变为
published,运行时可访问

常见问题
表单编码已存在
formCode 在 lc_form 表中有唯一约束(uk_form_code)。保存前先检查是否已有同编码表单,或先删除旧表单再新建。
保存后运行时看不到表单
syncFormToPage() 会创建 lc_page 记录,但只有 status = 'published' 的页面才会出现在应用菜单中。保存后记得点击「发布」。
审批表单提交后数据没写入业务表
启用审批的表单提交走 POST /api/biz-approval/submit/{formId},数据在审批通过后才回写业务表。如果审批流程配置有误(如 processModelId 不存在),提交会失败。请检查 BPM 流程模型是否正确发布。
