运行时引擎
运行时引擎
业务用途
运行时引擎是低代码平台从「设计时」到「运行时」的桥梁。设计时,运营人员通过表单设计器、列表设计器、页面设计器产出 JSON 配置,存入 lc_form、lc_list、lc_page 等表。运行时,引擎读取这些配置,在前端渲染出可交互的应用页面,所有数据操作通过 SchemaService 动态完成。
运行时引擎由后端 RuntimeController(统一数据访问层)和前端 AppRuntime.vue + PageRenderer.vue(页面渲染器)组成。用户访问 http://localhost:3000/app/{appId} 即可进入应用运行时。
涉及文件
| 层 | 文件路径 | 说明 |
|---|---|---|
| Controller | backend/src/main/java/com/lowcode/controller/RuntimeController.java | /api/runtime 统一数据访问层 |
| Service | backend/src/main/java/com/lowcode/service/SchemaService.java | 底层数据引擎 |
| Service | backend/src/main/java/com/lowcode/service/PageService.java | 页面配置存取 |
| Service | backend/src/main/java/com/lowcode/service/AppMenuService.java | 应用菜单树 |
| 前端 | frontend/src/views/runtime/AppRuntime.vue | 应用运行时容器 |
| 前端 | frontend/src/views/runtime/PageRenderer.vue | 页面渲染器 |
| 前端 | frontend/src/views/runtime/components/RuntimeForm.vue | 表单组件 |
| 前端 | frontend/src/views/runtime/components/RuntimeList.vue | 列表组件 |
| 前端 | frontend/src/views/runtime/components/RuntimeChart.vue | 图表组件 |
| 前端 | frontend/src/views/runtime/components/RuntimeReport.vue | 报表组件 |
| 前端 | frontend/src/views/runtime/components/RuntimeStatistic.vue | 统计组件 |
| 前端 | frontend/src/views/runtime/components/RuntimeButton.vue | 按钮组件 |
| 前端 | frontend/src/views/runtime/components/RuntimeText.vue | 文本组件 |
| 前端 | frontend/src/views/runtime/components/RuntimeDivider.vue | 分割线组件 |
| 前端 | frontend/src/views/runtime/components/RuntimeLayoutSwitcher.vue | 布局切换器 |
| 路由 | frontend/src/router/index.ts | /app/:appId 路由定义 |
数据库表
运行时引擎直接读取以下设计时产出的配置表:
| 表 | 用途 |
|---|---|
lc_page | 页面配置(page_json 是核心渲染依据) |
lc_app_menu | 应用菜单树(导航结构) |
lc_application | 应用信息(名称、图标、布局等) |
lc_form | 表单配置(运行时表单组件加载) |
lc_list | 列表配置(运行时列表组件加载) |
lc_chart | 图表配置(运行时图表组件加载) |
lc_report | 报表配置(运行时报表组件加载) |
后端实现
端点列表(RuntimeController -- /api/runtime)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/runtime/page/{pageId} | 获取页面完整配置(page + pageJson) |
| GET | /api/runtime/menu/{appId} | 获取应用菜单树 |
| POST | /api/runtime/data | 通用数据查询(action=list) |
| POST | /api/runtime/data/submit | 通用表单提交 |
| POST | /api/runtime/statistic | 统计数据查询 |
1. 获取页面配置 -- getPageConfig()
@GetMapping("/page/{pageId}")
public Result<Map<String, Object>> getPageConfig(@PathVariable Long pageId) {
LcPage page = pageService.getById(pageId);
if (page == null) return Result.error("页面不存在");
Map<String, Object> result = new HashMap<>();
result.put("page", page);
if (page.getPageJson() != null) {
result.put("pageConfig", page.getPageJson());
}
return Result.success(result);
}2. 获取应用菜单树 -- getAppMenu()
@GetMapping("/menu/{appId}")
public Result<List<LcAppMenu>> getAppMenu(@PathVariable Long appId) {
return Result.success(appMenuService.getMenuTreeByAppId(appId));
}3. 通用数据查询 -- queryData()
@PostMapping("/data")
public Result<Object> queryData(@RequestBody Map<String, Object> params) {
Long tableId = Long.valueOf(params.get("tableId").toString());
String action = params.getOrDefault("action", "list").toString();
Integer pageNum = params.containsKey("pageNum") ? Integer.valueOf(params.get("pageNum").toString()) : 1;
Integer pageSize = params.containsKey("pageSize") ? Integer.valueOf(params.get("pageSize").toString()) : 10;
if ("list".equals(action)) {
return Result.success(schemaService.queryDataList(tableId, null, pageNum, pageSize));
}
return Result.error("不支持的操作类型: " + action);
}4. 通用表单提交 -- submitData()
@PostMapping("/data/submit")
public Result<Void> submitData(@RequestBody Map<String, Object> params) {
Long tableId = Long.valueOf(params.get("tableId").toString());
@SuppressWarnings("unchecked")
Map<String, Object> data = (Map<String, Object>) params.get("data");
schemaService.insertData(tableId, data);
return Result.success("提交成功");
}5. 统计数据查询 -- getStatistic()
@PostMapping("/statistic")
public Result<Object> getStatistic(@RequestBody Map<String, Object> params) {
Long tableId = Long.valueOf(params.get("tableId").toString());
String aggregateType = params.getOrDefault("aggregateType", "count").toString();
// 简单实现:查询全量数据计数
long count = schemaService.queryDataList(tableId, null, 1, Integer.MAX_VALUE).size();
return Result.success(count);
}前端实现
前端路由
// frontend/src/router/index.ts
{ path: '/app/:appId', name: 'AppRuntime',
component: () => import('@/views/runtime/AppRuntime.vue') },
{ path: '/app/:appId/page/:pageId',
component: () => import('@/views/runtime/AppRuntime.vue') },
{ path: '/app/:appId/page', redirect: (to) => `/app/${to.params.appId}` },http://localhost:3000/app/1-- 进入应用 1 的运行时首页http://localhost:3000/app/1/page/5-- 直接打开应用 1 的页面 5
AppRuntime.vue -- 应用运行时容器
AppRuntime.vue 是运行时的入口组件,负责:
- 应用信息加载:通过
GET /api/open/app/lookup?code=或GET /api/application/{appId}加载应用信息 - 权限校验:通过
GET /api/system/user/me/appIds检查用户是否有权访问该应用 - 菜单加载:通过
GET /api/app-menu/tree?appId=加载应用菜单树 - 页面加载:通过
GET /api/page/list?appId=加载页面列表 - 页面渲染:选中菜单项后,把
pageId传给PageRenderer.vue
// AppRuntime.vue 第 359 行 -- 加载应用信息
const appRes = await request.get(`/application/${appId.value}`)
// AppRuntime.vue 第 365 行 -- 加载菜单树
const menuRes = await request.get(`/app-menu/tree`, { params: { appId: appId.value } })
// AppRuntime.vue 第 371 行 -- 加载页面列表
const pageRes = await request.get(`/page/list`, { params: { appId: appId.value } })PageRenderer.vue -- 页面渲染器
PageRenderer.vue 接收 pageId,加载 pageJson 并按 rows -> components 结构渲染:
// PageRenderer.vue 第 57 行
const res = await request.get(`/page/${props.pageId}`)
// 解析 pageJson,遍历 rows,每个 row 遍历 components
// 根据 component.type 渲染对应的 Runtime 组件渲染逻辑:
pageJson.rows[] -> 遍历每行
row.components[] -> 遍历每个组件
component.type == 'form' -> <RuntimeForm :config="component.config" />
component.type == 'list' -> <RuntimeList :config="component.config" />
component.type == 'chart' -> <RuntimeChart :config="component.config" />
component.type == 'report' -> <RuntimeReport :config="component.config" />
component.type == 'statistic'-> <RuntimeStatistic :config="component.config" />
component.type == 'button' -> <RuntimeButton :config="component.config" />
component.type == 'text' -> <RuntimeText :config="component.config" />
component.type == 'divider' -> <RuntimeDivider :config="component.config" />Runtime 组件的数据访问
每个 Runtime 组件根据自身 config 中的配置调用不同的 API:
| 组件 | 数据 API | 说明 |
|---|---|---|
RuntimeForm | GET /api/form/get/{formId} | 加载表单配置 |
RuntimeForm | POST /api/database/table/{tableId}/data | 提交表单数据 |
RuntimeForm | POST /api/biz-approval/submit/{formId} | 审批表单提交 |
RuntimeList | POST /api/database/table/{tableId}/data/list | 分页查询列表 |
RuntimeList | PUT /api/database/table/{tableId}/data/{id} | 更新行数据 |
RuntimeList | DELETE /api/database/table/{tableId}/data/{id} | 删除行数据 |
RuntimeChart | POST /api/chart/data/{id} | 查询图表数据 |
RuntimeReport | POST /api/runtime/data | 查询报表数据 |
RuntimeStatistic | POST /api/runtime/statistic | 查询统计数据 |
操作步骤
- 确保已通过表单设计器/列表设计器/页面设计器完成设计并发布
- 在应用菜单管理中配置菜单项,关联到页面
- 访问
http://localhost:3000/app/{appId}进入应用运行时 - 左侧菜单导航,点击菜单项加载对应页面
- 页面中的表单/列表/图表组件自动加载并交互

常见问题
访问 /app/:appId 白屏
AppRuntime.vue 会先检查用户权限(GET /api/system/user/me/appIds),如果用户没有该应用的访问权限,会跳转。确保用户已登录且被授权访问该应用。
页面组件不显示
PageRenderer.vue 解析 pageJson 后按 component.type 渲染。如果 type 不在支持列表中,组件会被忽略。检查 pageJson 中的 type 值是否正确。
运行时统计数据不准确
getStatistic() 当前实现是查询全量数据然后 .size() 计数,数据量大时性能差。实际项目中应改为 SELECT COUNT(*) 查询。
