引言
在现代企业级应用开发中,低代码平台已成为提升开发效率、降低技术门槛的关键工具。OneCode作为一款领先的低代码开发平台,其核心优势在于将可视化配置与代码生成深度融合,实现了"所见即所得"的开发体验。本文作为《低代码引擎核心技术,可视化动作------OneCode技术实践》的续篇,将全面解析OneCode的事件驱动架构与注解驱动开发模式,为开发者提供一份详尽的动作事件速查手册,并深入探讨如何通过注解驱动实现业务逻辑与UI交互的解耦。
一、OneCode事件体系全景
OneCode的事件系统是连接用户操作与业务逻辑的核心枢纽,采用分层设计思想,构建了从基础UI事件到复杂业务事件的完整生态。
1.1 核心UI事件枚举(UIEventEnum)
基础UI事件构成了OneCode交互体系的基石,覆盖了所有用户与界面元素的基础交互行为:
事件枚举 | 描述 | 触发时机 | 参数 | 动作类型 | 示例表达式 |
---|---|---|---|---|---|
CLICK | 点击事件 | 鼠标左键单击组件时 | {target:组件ID, x:坐标X, y:坐标Y, button:鼠标按钮} | 交互型 | UIEventEnum.CLICK == event.type |
DBL_CLICK | 双击事件 | 鼠标左键双击组件时 | {target:组件ID, x:坐标X, y:坐标Y} | 交互型 | event.type == UIEventEnum.DBL_CLICK |
MOUSE_DOWN | 鼠标按下事件 | 鼠标按钮在组件上按下时 | {target:组件ID, button:按钮类型, x:坐标X, y:坐标Y} | 交互型 | event.button == MouseButton.LEFT |
MOUSE_UP | 鼠标释放事件 | 鼠标按钮在组件上释放时 | {target:组件ID, button:按钮类型, x:坐标X, y:坐标Y} | 交互型 | - |
MOUSE_MOVE | 鼠标移动事件 | 鼠标在组件上移动时 | {target:组件ID, x:坐标X, y:坐标Y, movementX:相对移动X, movementY:相对移动Y} | 交互型 | - |
MOUSE_ENTER | 鼠标进入事件 | 鼠标指针进入组件区域时 | {target:组件ID, relatedTarget:离开的前一个组件ID} | 交互型 | - |
MOUSE_LEAVE | 鼠标离开事件 | 鼠标指针离开组件区域时 | {target:组件ID, relatedTarget:进入的下一个组件ID} | 交互型 | - |
KEY_PRESS | 按键按下事件 | 键盘按键被按下时 | {key:键名, code:键码, altKey:Alt键状态, ctrlKey:Ctrl键状态, shiftKey:Shift键状态} | 交互型 | event.key == 'Enter' && event.ctrlKey |
KEY_UP | 按键释放事件 | 键盘按键被释放时 | {key:键名, code:键码, altKey:Alt键状态, ctrlKey:Ctrl键状态, shiftKey:Shift键状态} | 交互型 | - |
FOCUS | 获取焦点事件 | 组件获得输入焦点时 | {target:组件ID, relatedTarget:失去焦点的组件ID} | 状态型 | - |
BLUR | 失去焦点事件 | 组件失去输入焦点时 | {target:组件ID, relatedTarget:获得焦点的组件ID} | 状态型 | - |
CHANGE | 内容改变事件 | 表单元素内容改变时 | {target:组件ID, value:新值, oldValue:旧值} | 数据型 | event.value.length > 10 |
INPUT | 输入事件 | 用户输入时实时触发 | {target:组件ID, value:当前值, inputType:输入类型} | 数据型 | - |
SUBMIT | 提交事件 | 表单提交时 | {target:表单ID, fields:所有字段值集合} | 操作型 | - |
RESET | 重置事件 | 表单重置时 | {target:表单ID} | 操作型 | - |
1.2 容器组件事件
针对复杂容器组件,OneCode提供了专用事件体系,满足特定交互场景需求:
1.2.1 弹出菜单事件(PopMenuEventEnum)
事件枚举 | 描述 | 触发时机 | 参数 | 示例 |
---|---|---|---|---|
MENU_ITEM_CLICK | 菜单项点击 | 用户点击弹出菜单项时 | {menuId:菜单ID, itemId:菜单项ID, params:参数对象} | PopMenuEventEnum.MENU_ITEM_CLICK |
MENU_SHOW | 菜单显示 | 弹出菜单显示前 | {menuId:菜单ID, trigger:触发组件ID} | - |
MENU_HIDE | 菜单隐藏 | 弹出菜单隐藏前 | {menuId:菜单ID, reason:隐藏原因} | - |
MENU_ITEM_DISABLE | 菜单项禁用 | 菜单项被禁用时 | {menuId:菜单ID, itemId:菜单项ID, reason:禁用原因} | - |
1.2.2 树形视图事件(TreeViewEventEnum)
事件枚举 | 描述 | 触发时机 | 参数 | 示例 |
---|---|---|---|---|
NODE_CLICK | 节点点击 | 点击树节点时 | {nodeId:节点ID, treeId:树ID, level:层级, data:节点数据} | TreeViewEventEnum.NODE_CLICK |
NODE_DBL_CLICK | 节点双击 | 双击树节点时 | {nodeId:节点ID, treeId:树ID, data:节点数据} | - |
NODE_EXPAND | 节点展开 | 树节点展开时 | {nodeId:节点ID, treeId:树ID, isExpand:是否展开} | - |
NODE_COLLAPSE | 节点折叠 | 树节点折叠时 | {nodeId:节点ID, treeId:树ID} | - |
NODE_SELECT | 节点选择 | 树节点被选中时 | {nodeId:节点ID, treeId:树ID, selected:是否选中} | - |
NODE_CHECK | 节点勾选 | 勾选树节点复选框时 | {nodeId:节点ID, treeId:树ID, checked:勾选状态} | - |
CONTEXT_MENU | 右键菜单 | 节点上右键点击时 | {nodeId:节点ID, treeId:树ID, x:坐标, y:坐标} | - |
BEFORE_LOAD_CHILD | 加载子节点前 | 准备加载子节点时 | {nodeId:节点ID, treeId:树ID} | - |
AFTER_LOAD_CHILD | 加载子节点后 | 子节点加载完成时 | {nodeId:节点ID, treeId:树ID, childCount:子节点数量} | - |
1.2.3 数据绑定事件(DataBinderEventEnum)
事件枚举 | 描述 | 触发时机 | 参数 | 示例 |
---|---|---|---|---|
BEFORE_LOAD | 加载前事件 | 数据加载前触发 | {binderId:绑定器ID, params:请求参数} | DataBinderEventEnum.BEFORE_LOAD |
AFTER_LOAD | 加载后事件 | 数据加载完成后触发 | {binderId:绑定器ID, data:返回数据, success:是否成功} | - |
BEFORE_SUBMIT | 提交前事件 | 数据提交前触发 | {binderId:绑定器ID, data:提交数据} | - |
AFTER_SUBMIT | 提交后事件 | 数据提交完成后触发 | {binderId:绑定器ID, result:提交结果, success:是否成功} | - |
DATA_CHANGE | 数据变更事件 | 绑定数据发生变更时 | {binderId:绑定器ID, field:字段名, oldValue:旧值, newValue:新值} | - |
VALIDATE | 数据验证事件 | 数据验证时触发 | {binderId:绑定器ID, errors:错误信息集合} | - |
LOAD_ERROR | 加载错误事件 | 数据加载失败时触发 | {binderId:绑定器ID, error:错误信息, code:错误码} | - |
SUBMIT_ERROR | 提交错误事件 | 数据提交失败时触发 | {binderId:绑定器ID, error:错误信息, code:错误码} | - |
1.3 图表与媒体组件事件
针对数据可视化与媒体组件,OneCode提供了专业的交互事件:
1.3.1 图表事件(FChartEventEnum)
事件枚举 | 描述 | 触发时机 | 参数 | 示例 |
---|---|---|---|---|
CHART_CLICK | 图表点击 | 点击图表任意区域时 | {chartId:图表ID, position:{x,y}, componentType:组件类型} | FChartEventEnum.CHART_CLICK |
SERIES_CLICK | 系列点击 | 点击图表数据系列时 | {chartId:图表ID, seriesId:系列ID, dataIndex:数据索引, value:数据值} | - |
DATA_CLICK | 数据点点击 | 点击图表数据点时 | {chartId:图表ID, seriesId:系列ID, dataIndex:数据索引, value:数据值, name:数据名称} | - |
LEGEND_CLICK | 图例点击 | 点击图表图例时 | {chartId:图表ID, legendName:图例名称, selected:选中状态} | - |
DATA_ZOOM | 数据缩放 | 图表数据缩放时 | {chartId:图表ID, start:开始比例, end:结束比例} | - |
DATA_RANGE | 数据范围选择 | 选择数据范围时 | {chartId:图表ID, min:最小值, max:最大值} | - |
TOOLTIP_SHOW | 提示框显示 | 图表提示框显示时 | {chartId:图表ID, data:提示数据, position:{x,y}} | - |
TOOLTIP_HIDE | 提示框隐藏 | 图表提示框隐藏时 | {chartId:图表ID} | - |
1.3.2 标签页事件(TabsEventEnum)
事件枚举 | 描述 | 触发时机 | 参数 | 示例 |
---|---|---|---|---|
TAB_CLICK | 标签点击 | 点击标签页时 | {tabsId:标签页ID, tabId:标签ID, index:标签索引} | TabsEventEnum.TAB_CLICK |
TAB_CHANGE | 标签切换 | 标签页切换完成后 | {tabsId:标签页ID, activeTabId:当前标签ID, previousTabId:上一个标签ID} | - |
TAB_ADD | 标签添加 | 新增标签页时 | {tabsId:标签页ID, tabId:新标签ID, index:添加位置} | - |
TAB_REMOVE | 标签移除 | 移除标签页时 | {tabsId:标签页ID, tabId:被移除标签ID, index:移除位置} | - |
TAB_ENABLE | 标签启用 | 标签页被启用时 | {tabsId:标签页ID, tabId:标签ID} | - |
TAB_DISABLE | 标签禁用 | 标签页被禁用时 | {tabsId:标签页ID, tabId:标签ID} | - |
1.4 自定义事件体系
OneCode的自定义事件体系是其灵活性的核心体现,通过CustomEvent
接口及其实现类,开发者可以构建业务领域特定的事件模型:
1.4.1 CustomFormEvent
表单专用事件,扩展了标准表单事件的能力:
事件类型 | 描述 | 触发时机 | 特有参数 |
---|---|---|---|
SAVE | 保存事件 | 表单保存操作时 | {saveType:保存类型, validateResult:验证结果} |
SUBMIT | 提交事件 | 表单提交操作时 | {submitType:提交类型, workflowId:工作流ID} |
CANCEL | 取消事件 | 表单取消操作时 | {confirm:是否需要确认} |
RESET | 重置事件 | 表单重置操作时 | {resetType:重置类型, keepValues:保留字段} |
IMPORT | 导入事件 | 表单导入数据时 | {file:文件信息, importMode:导入模式} |
EXPORT | 导出事件 | 表单导出数据时 | {exportFields:导出字段, format:文件格式} |
VALIDATE | 验证事件 | 表单验证时 | {fields:验证字段, result:验证结果} |
CALCULATE | 计算事件 | 表单计算时 | {fields:计算字段, values:字段值集合} |
1.4.2 CustomTreeEvent
树形组件增强事件:
事件类型 | 描述 | 触发时机 | 特有参数 |
---|---|---|---|
NODE_CREATE | 节点创建 | 创建新节点时 | {parentId:父节点ID, nodeData:节点数据} |
NODE_EDIT | 节点编辑 | 编辑节点时 | {nodeId:节点ID, oldData:旧数据, newData:新数据} |
NODE_DELETE | 节点删除 | 删除节点时 | {nodeId:节点ID, children:子节点数量} |
NODE_MOVE | 节点移动 | 移动节点时 | {nodeId:节点ID, oldParentId:旧父节点ID, newParentId:新父节点ID} |
NODE_COPY | 节点复制 | 复制节点时 | {nodeId:节点ID, targetParentId:目标父节点ID} |
NODE_LOAD | 节点加载 | 节点数据加载时 | {nodeId:节点ID, loadMode:加载模式} |
TREE_REFRESH | 树刷新 | 整棵树刷新时 | {rootId:根节点ID, keepExpand:是否保持展开状态} |
NODE_DRAG_START | 节点拖动开始 | 开始拖动节点时 | {nodeId:节点ID, event:原生拖动事件} |
NODE_DRAG_END | 节点拖动结束 | 结束拖动节点时 | {nodeId:节点ID, success:是否成功, targetId:目标节点ID} |
1.4.3 CustomTabsEvent
标签页增强事件:
事件类型 | 描述 | 触发时机 | 特有参数 |
---|---|---|---|
TAB_CREATE | 标签创建 | 创建新标签时 | {tabId:标签ID, title:标题, content:内容} |
TAB_CLOSE | 标签关闭 | 关闭标签时 | {tabId:标签ID, confirm:是否需要确认} |
TAB_REFRESH | 标签刷新 | 刷新标签内容时 | {tabId:标签ID, force:是否强制刷新} |
TAB_RENAME | 标签重命名 | 重命名标签时 | {tabId:标签ID, oldTitle:旧标题, newTitle:新标题} |
TAB_PIN | 标签固定 | 固定标签时 | {tabId:标签ID, position:固定位置} |
TAB_UNPIN | 标签取消固定 | 取消固定标签时 | {tabId:标签ID} |
TABS_REORDER | 标签重排 | 标签顺序改变时 | {tabsId:标签容器ID, order:新顺序数组} |
TAB_DRAG | 标签拖动 | 拖动标签时 | {tabId:标签ID, fromIndex:原位置, toIndex:新位置} |
1.4.4 CustomTitleBlockEvent
标题区块事件:
事件类型 | 描述 | 触发时机 | 特有参数 |
---|---|---|---|
TITLE_CLICK | 标题点击 | 点击标题时 | {blockId:区块ID, section:标题区域} |
ICON_CLICK | 图标点击 | 点击标题图标时 | {blockId:区块ID, iconId:图标ID, action:图标动作} |
MENU_OPEN | 菜单打开 | 标题菜单打开时 | {blockId:区块ID, menuId:菜单ID} |
REFRESH | 刷新事件 | 刷新标题区块时 | {blockId:区块ID, source:刷新来源} |
COLLAPSE | 折叠事件 | 折叠标题区块时 | {blockId:区块ID, state:折叠状态} |
EXPAND | 展开事件 | 展开标题区块时 | {blockId:区块ID, state:展开状态} |
SETTING_CHANGE | 设置变更 | 标题设置变更时 | {blockId:区块ID, settings:新设置} |
1.4.5 CustomFieldEvent
字段级自定义事件:
事件类型 | 描述 | 触发时机 | 特有参数 |
---|---|---|---|
VALUE_CHANGE | 值变更事件 | 字段值变更时 | {fieldId:字段ID, oldValue:旧值, newValue:新值, trigger:触发源} |
VALUE_FORMAT | 值格式化事件 | 字段值格式化时 | {fieldId:字段ID, value:原始值, formattedValue:格式化后值} |
VALUE_PARSE | 值解析事件 | 格式化值解析时 | {fieldId:字段ID, formattedValue:格式化值, parsedValue:解析后值} |
VALIDATE | 验证事件 | 字段验证时 | {fieldId:字段ID, value:字段值, errors:错误信息} |
FOCUS_GAIN | 获取焦点事件 | 字段获得焦点时 | {fieldId:字段ID, source:焦点来源} |
FOCUS_LOSS | 失去焦点事件 | 字段失去焦点时 | {fieldId:字段ID, target:焦点目标} |
AUTO_COMPLETE | 自动完成事件 | 自动完成建议时 | {fieldId:字段ID, input:输入值, suggestions:建议列表} |
POPUP_SHOW | 弹出框显示 | 字段弹出框显示时 | {fieldId:字段ID, popupType:弹出框类型} |
POPUP_HIDE | 弹出框隐藏 | 字段弹出框隐藏时 | {fieldId:字段ID, reason:隐藏原因} |
1.4.6 CustomHotKeyEvent
快捷键事件:
事件类型 | 描述 | 触发时机 | 特有参数 |
---|---|---|---|
HOTKEY_PRESS | 快捷键按下 | 快捷键组合被按下时 | {keys:按键组合, actionId:关联动作ID, isGlobal:是否全局快捷键} |
HOTKEY_RELEASE | 快捷键释放 | 快捷键组合被释放时 | {keys:按键组合, actionId:关联动作ID} |
HOTKEY_CONFLICT | 快捷键冲突 | 检测到快捷键冲突时 | {keys:按键组合, existingActionId:已有动作ID, newActionId:新动作ID} |
HOTKEY_REGISTER | 快捷键注册 | 快捷键注册时 | {keys:按键组合, actionId:动作ID, scope:作用域} |
HOTKEY_UNREGISTER | 快捷键注销 | 快捷键注销时 | {keys:按键组合, actionId:动作ID} |
1.4.7 CustomGalleryEvent
画廊组件事件:
事件类型 | 描述 | 触发时机 | 特有参数 |
---|---|---|---|
ITEM_CLICK | 项目点击 | 点击画廊项目时 | {galleryId:画廊ID, itemId:项目ID, index:项目索引} |
ITEM_DBL_CLICK | 项目双击 | 双击画廊项目时 | {galleryId:画廊ID, itemId:项目ID} |
ITEM_SELECT | 项目选择 | 选择画廊项目时 | {galleryId:画廊ID, selectedItems:选中项目ID数组} |
ITEM_LOAD | 项目加载 | 项目资源加载时 | {galleryId:画廊ID, itemId:项目ID, loadStatus:加载状态} |
ITEM_ERROR | 项目错误 | 项目加载错误时 | {galleryId:画廊ID, itemId:项目ID, error:错误信息} |
SCROLL | 滚动事件 | 画廊滚动时 | {galleryId:画廊ID, position:滚动位置, direction:滚动方向} |
PAGE_CHANGE | 页码变更 | 画廊页码变更时 | {galleryId:画廊ID, currentPage:当前页, totalPages:总页数} |
ZOOM | 缩放事件 | 画廊缩放时 | {galleryId:画廊ID, scale:缩放比例, center:缩放中心} |
1.4.8 CustomGridEvent
表格组件增强事件:
事件类型 | 描述 | 触发时机 | 特有参数 |
---|---|---|---|
ROW_CLICK | 行点击 | 点击表格行时 | {gridId:表格ID, rowId:行ID, rowIndex:行索引, columnId:列ID} |
ROW_DBL_CLICK | 行双击 | 双击表格行时 | {gridId:表格ID, rowId:行ID, rowData:行数据} |
CELL_CLICK | 单元格点击 | 点击表格单元格时 | {gridId:表格ID, rowId:行ID, columnId:列ID, cellValue:单元格值} |
CELL_EDIT | 单元格编辑 | 编辑单元格时 | {gridId:表格ID, rowId:行ID, columnId:列ID, oldValue:旧值, newValue:新值} |
ROW_SELECT | 行选择 | 选择表格行时 | {gridId:表格ID, selectedRowIds:选中行ID数组, selectedRows:选中行数据} |
ROW_CHECK | 行勾选 | 勾选表格行时 | {gridId:表格ID, checkedRowIds:勾选行ID数组, checked:勾选状态} |
HEADER_CLICK | 表头点击 | 点击表头时 | {gridId:表格ID, columnId:列ID, columnIndex:列索引} |
HEADER_DBL_CLICK | 表头双击 | 双击表头时 | {gridId:表格ID, columnId:列ID} |
COLUMN_RESIZE | 列宽调整 | 调整列宽时 | {gridId:表格ID, columnId:列ID, oldWidth:旧宽度, newWidth:新宽度} |
COLUMN_REORDER | 列重排 | 调整列顺序时 | {gridId:表格ID, columnId:列ID, oldIndex:旧索引, newIndex:新索引} |
SORT | 排序事件 | 表格排序时 | {gridId:表格ID, sortColumn:排序列, sortDirection:排序方向} |
FILTER | 筛选事件 | 表格筛选时 | {gridId:表格ID, filterParams:筛选参数} |
PAGE_CHANGE | 页码变更 | 分页页码变更时 | {gridId:表格ID, pageNum:当前页码, pageSize:每页条数} |
SCROLL_LOAD | 滚动加载 | 滚动到底部加载更多时 | {gridId:表格ID, currentRows:当前行数, totalRows:总行数} |
ROW_EXPAND | 行展开 | 展开明细行时 | {gridId:表格ID, rowId:行ID, expanded:是否展开} |
ROW_CONTEXT_MENU | 行右键菜单 | 行上右键点击时 | {gridId:表格ID, rowId:行ID, x:坐标, y:坐标} |
BATCH_OPERATE | 批量操作 | 执行批量操作时 | {gridId:表格ID, operateType:操作类型, selectedRowIds:选中行ID} |
二、OneCode注解驱动开发架构
OneCode创新性地采用注解驱动开发模式,将可视化配置与代码生成深度融合,实现了业务逻辑与UI交互的解耦,大幅提升了开发效率与系统可维护性。
2.1 注解驱动架构三层模型
OneCode的注解驱动架构采用清晰的三层设计:
-
可视化设计器到注解映射层 可视化编辑器将拖拽配置的组件属性直接映射为Java注解属性。例如,在UI设计器中设置的组件名称"topBar"会自动转换为
@ComponentAnnotation(name = "topBar")
注解属性。这种映射关系是双向的,注解代码的变更也会实时反映到可视化视图中。 -
注解驱动代码生成引擎 该引擎基于Java注解处理器(Annotation Processor)实现,在编译期扫描项目中的注解,自动生成相应的Java代码、配置文件和资源文件。生成的代码遵循OneCode编码规范,确保风格一致性和最佳实践。
-
领域模型集成层 将注解定义的UI组件、事件和业务逻辑与领域模型无缝集成,实现数据绑定、权限控制和业务规则的统一管理。
2.2 全面的注解系统
OneCode提供了四大类注解,覆盖UI开发全流程:
2.2.1 UI布局注解
用于定义界面布局结构和组件关系:
java
@PageAnnotation(
title = "用户管理",
description = "系统用户信息管理页面",
author = "开发团队",
createTime = "2023-06-15",
lastModified = "2023-07-20",
width = "100%",
height = "100%",
layout = LayoutType.FLEX,
theme = "default",
responsive = true,
backgroundColor = "#f5f7fa",
padding = "16px"
)
@ContainerAnnotation(
id = "mainContainer",
direction = Direction.VERTICAL,
alignItems = AlignItems.STRETCH,
justifyContent = JustifyContent.FLEX_START,
gap = "16px",
children = {
@ComponentRef(id = "searchBar"),
@ComponentRef(id = "userTable"),
@ComponentRef(id = "pagination")
}
)
public class UserManagementPage {
// 页面逻辑实现
}
2.2.2 组件交互注解
定义组件行为和事件响应:
java
@GridAnnotation(
id = "userTable",
title = "用户列表",
dataSource = "userService.findUsers",
pageSize = 10,
showPagination = true,
checkable = true,
bordered = true,
stripe = true,
height = "calc(100vh - 200px)",
columns = {
@GridColumnAnnotation(
field = "id",
title = "用户ID",
width = 80,
align = Align.CENTER,
fixed = Fixed.LEFT
),
@GridColumnAnnotation(
field = "name",
title = "姓名",
width = 120,
sortable = true,
searchable = true
),
@GridColumnAnnotation(
field = "status",
title = "状态",
width = 100,
align = Align.CENTER,
formatter = "statusFormatter",
filters = {
@FilterAnnotation(value = "1", text = "启用"),
@FilterAnnotation(value = "0", text = "禁用")
}
),
@GridColumnAnnotation(
field = "operation",
title = "操作",
width = 180,
align = Align.CENTER,
fixed = Fixed.RIGHT,
render = @RenderAnnotation(type = RenderType.BUTTON_GROUP,
buttons = {
@ButtonAnnotation(
text = "编辑",
icon = "edit",
type = ButtonType.DEFAULT,
click = @EventAnnotation(
type = CustomGridEvent.ROW_EDIT,
handler = "editUser",
params = {"id": "{{row.id}}"}
)
),
@ButtonAnnotation(
text = "删除",
icon = "delete",
type = ButtonType.DANGER,
click = @EventAnnotation(
type = CustomGridEvent.ROW_DELETE,
handler = "deleteUser",
confirm = true,
confirmMessage = "确定要删除用户{{row.name}}吗?"
)
)
}
)
)
},
events = {
@GridEventAnnotation(
type = CustomGridEvent.ROW_CLICK,
handler = "onRowClick"
),
@GridEventAnnotation(
type = CustomGridEvent.ROW_CHECK,
handler = "onRowCheck"
)
}
)
public class UserTableComponent {
// 表格组件实现
}
2.2.3 数据绑定注解
实现UI组件与数据源的绑定:
java
@FormAnnotation(
id = "userForm",
title = "用户表单",
dataBinder = @DataBinderAnnotation(
id = "userBinder",
entity = "com.onecode.system.entity.User",
loadUrl = "/api/users/{{id}}",
saveUrl = "/api/users",
updateUrl = "/api/users/{{id}}",
method = HttpMethod.POST,
mode = BinderMode.EDIT
),
layout = FormLayout.VERTICAL,
labelWidth = 120,
size = FormSize.MIDDLE,
items = {
@FormItemAnnotation(
field = "name",
label = "姓名",
required = true,
component = @ComponentAnnotation(
type = ComponentType.INPUT,
placeholder = "请输入姓名",
maxLength = 50
),
rules = {
@RuleAnnotation(type = RuleType.REQUIRED, message = "姓名不能为空"),
@RuleAnnotation(type = RuleType.MAX_LENGTH, value = "50", message = "姓名不能超过50个字符")
}
),
@FormItemAnnotation(
field = "username",
label = "用户名",
required = true,
component = @ComponentAnnotation(
type = ComponentType.INPUT,
placeholder = "请输入用户名",
disabled = "{{formMode == 'view'}}"
),
rules = {
@RuleAnnotation(type = RuleType.REQUIRED, message = "用户名不能为空"),
@RuleAnnotation(type = RuleType.REGEXP, value = "^[a-zA-Z0-9_]{4,20}$", message = "用户名只能包含字母、数字和下划线,长度4-20位")
}
),
@FormItemAnnotation(
field = "deptId",
label = "所属部门",
required = true,
component = @ComponentAnnotation(
type = ComponentType.TREE_SELECT,
placeholder = "请选择部门",
dataSource = @DataSourceAnnotation(
url = "/api/departments/tree",
labelField = "name",
valueField = "id"
)
)
)
},
actions = {
@ActionAnnotation(
type = ActionType.SUBMIT,
text = "保存",
icon = "save",
primary = true,
event = @EventAnnotation(
type = CustomFormEvent.SAVE,
handler = "saveUser",
onSuccess = "handleSuccess",
onError = "handleError"
)
),
@ActionAnnotation(
type = ActionType.CANCEL,
text = "取消",
icon = "close",
event = @EventAnnotation(
type = CustomFormEvent.CANCEL,
handler = "closeForm"
)
)
}
)
public class UserFormComponent {
// 表单组件实现
}
2.2.4 业务规则注解
定义业务逻辑和流程规则:
java
@BusinessProcessAnnotation(
id = "userOnboardingProcess",
name = "用户入职流程",
version = "1.0",
category = "人事管理",
description = "新员工入职处理流程",
startEvent = @StartEventAnnotation(
id = "start",
name = "开始",
formKey = "userOnboardingForm"
),
endEvents = {
@EndEventAnnotation(id = "end", name = "结束")
},
serviceTasks = {
@ServiceTaskAnnotation(
id = "createAccount",
name = "创建账户",
implementation = "userService.createAccount",
assignee = "{{initiator}}",
dueDate = "P3D",
priority = 50,
extensionElements = {
@APIEventAnnotation(
event = CustomFormEvent.SUBMIT,
requestPath = @RequestPathAnnotation(value = "/api/accounts"),
method = HttpMethod.POST,
onBefore = "validateAccountData",
onSuccess = "accountCreatedSuccess",
onError = "accountCreatedError"
)
}
),
@ServiceTaskAnnotation(
id = "assignRoles",
name = "分配角色",
implementation = "roleService.assignRolesToUser",
assignee = "{{deptManager}}",
candidateGroups = "roleAdministrators"
)
},
gateways = {
@ExclusiveGatewayAnnotation(
id = "approvalGateway",
name = "审批判断",
defaultFlow = "toEnd"
)
},
sequenceFlows = {
@SequenceFlowAnnotation(
id = "flow1",
sourceRef = "start",
targetRef = "createAccount"
),
@SequenceFlowAnnotation(
id = "flow2",
sourceRef = "createAccount",
targetRef = "assignRoles"
),
@SequenceFlowAnnotation(
id = "flow3",
sourceRef = "assignRoles",
targetRef = "approvalGateway"
),
@SequenceFlowAnnotation(
id = "toEnd",
sourceRef = "approvalGateway",
targetRef = "end"
)
}
)
public class UserOnboardingProcess {
// 业务流程实现
}
2.3 注解驱动事件处理详解
OneCode的事件处理采用注解驱动模式,通过@EventAnnotation
和相关注解将事件源、事件类型和处理逻辑解耦。
2.3.1 APIEventAnnotation详解
APIEventAnnotation
是OneCode中用于配置异步事件处理的核心注解,它将表单事件与API请求绑定:
java
@APIEventAnnotation(
event = CustomFormEvent.SAVE,
requestPath = @RequestPathAnnotation(value = "/api/users/{{userId}}"),
method = HttpMethod.PUT,
headers = {
@HeaderAnnotation(name = "Authorization", value = "Bearer {{token}}"),
@HeaderAnnotation(name = "Content-Type", value = "application/json")
},
params = {
@ParamAnnotation(name = "id", value = "{{formData.id}}", type = ParamType.PATH),
@ParamAnnotation(name = "name", value = "{{formData.name}}", type = ParamType.BODY),
@ParamAnnotation(name = "deptId", value = "{{formData.deptId}}", type = ParamType.BODY)
},
timeout = 30000,
retry = @RetryAnnotation(
maxAttempts = 3,
initialInterval = 1000,
maxInterval = 5000,
multiplier = 2.0
),
onBefore = "validateUserForm",
onSuccess = "handleUserSaveSuccess",
onError = "handleUserSaveError",
onFinally = "cleanupUserForm"
)
2.3.2 事件处理流程
- 事件触发 :用户操作UI组件(如点击保存按钮)触发相应事件(如
CustomFormEvent.SAVE
) - 注解解析 :框架扫描到
@APIEventAnnotation
注解,解析请求路径、参数、回调函数等配置 - 前置处理 :执行
onBefore
指定的前置处理函数(如数据验证) - API调用:根据注解配置发起HTTP请求
- 结果处理 :根据请求结果执行
onSuccess
或onError
回调 - 最终处理 :执行
onFinally
指定的清理工作
2.4 注解与可视化编辑器的双向映射
OneCode实现了注解与可视化编辑器的无缝双向映射:
-
可视化配置生成注解 当开发者在可视化编辑器中拖拽组件、设置属性或配置事件时,系统会自动生成对应的Java注解代码。例如,配置一个表格的列信息会自动生成
@GridColumnAnnotation
注解数组。 -
注解变更同步到可视化视图 当开发者直接修改注解代码时,可视化编辑器会实时解析注解变更并更新视图展示。这种双向同步确保了代码与可视化设计的一致性。
-
设计时与运行时的统一 注解既是设计时的配置描述,也是运行时的执行依据。这种统一避免了传统低代码平台中设计态与运行态分离导致的"所见非所得"问题。
三、实践案例:注解驱动的用户管理模块
下面通过一个完整的用户管理模块案例,展示OneCode注解驱动开发的具体应用。
3.1 页面定义
java
@PageAnnotation(
title = "用户管理",
description = "系统用户信息管理页面",
author = "开发团队",
responsive = true,
backgroundColor = "#f5f7fa",
padding = "16px"
)
@ContainerAnnotation(
id = "mainContainer",
direction = Direction.VERTICAL,
gap = "16px",
children = {
@ComponentRef(id = "searchBar"),
@ComponentRef(id = "actionToolbar"),
@ComponentRef(id = "userTable"),
@ComponentRef(id = "pagination")
}
)
public class UserManagementPage {
// 页面初始化逻辑
@InitAnnotation
public void init() {
// 页面加载时执行的初始化代码
logger.info("User management page initialized");
}
}
3.2 搜索栏组件
java
@FormAnnotation(
id = "searchBar",
layout = FormLayout.HORIZONTAL,
labelWidth = 80,
size = FormSize.SMALL,
inline = true,
items = {
@FormItemAnnotation(
field = "name",
label = "姓名",
component = @ComponentAnnotation(
type = ComponentType.INPUT,
placeholder = "请输入姓名"
)
),
@FormItemAnnotation(
field = "username",
label = "用户名",
component = @ComponentAnnotation(
type = ComponentType.INPUT,
placeholder = "请输入用户名"
)
),
@FormItemAnnotation(
field = "status",
label = "状态",
component = @ComponentAnnotation(
type = ComponentType.SELECT,
placeholder = "请选择状态",
options = {
@OptionAnnotation(value = "", label = "全部"),
@OptionAnnotation(value = "1", label = "启用"),
@OptionAnnotation(value = "0", label = "禁用")
}
)
)
},
actions = {
@ActionAnnotation(
type = ActionType.SEARCH,
text = "查询",
icon = "search",
primary = true,
event = @EventAnnotation(
type = CustomFormEvent.SUBMIT,
handler = "searchUsers"
)
),
@ActionAnnotation(
type = ActionType.RESET,
text = "重置",
icon = "refresh",
event = @EventAnnotation(
type = CustomFormEvent.RESET,
handler = "resetSearch"
)
)
}
)
public class SearchBarComponent {
// 搜索逻辑实现
}
3.3 用户表格组件
java
@GridAnnotation(
id = "userTable",
dataSource = "userService.findUsers",
pageSize = 10,
showPagination = false,
checkable = true,
bordered = true,
stripe = true,
height = "calc(100vh - 240px)",
columns = {
@GridColumnAnnotation(
field = "id",
title = "用户ID",
width = 80,
align = Align.CENTER
),
@GridColumnAnnotation(
field = "name",
title = "姓名",
width = 120,
sortable = true
),
@GridColumnAnnotation(
field = "username",
title = "用户名",
width = 150,
sortable = true
),
@GridColumnAnnotation(
field = "deptName",
title = "所属部门",
width = 150,
sortable = true
),
@GridColumnAnnotation(
field = "roleNames",
title = "角色",
width = 200
),
@GridColumnAnnotation(
field = "status",
title = "状态",
width = 100,
align = Align.CENTER,
formatter = "statusFormatter",
render = @RenderAnnotation(
type = RenderType.TAG,
options = {
@TagOptionAnnotation(value = "1", text = "启用", color = "green"),
@TagOptionAnnotation(value = "0", text = "禁用", color = "red")
}
)
),
@GridColumnAnnotation(
field = "createTime",
title = "创建时间",
width = 160,
sortable = true,
formatter = "dateFormatter"
),
@GridColumnAnnotation(
field = "operation",
title = "操作",
width = 200,
align = Align.CENTER,
render = @RenderAnnotation(type = RenderType.BUTTON_GROUP,
buttons = {
@ButtonAnnotation(
text = "查看",
icon = "view",
type = ButtonType.DEFAULT,
click = @EventAnnotation(
type = CustomGridEvent.ROW_VIEW,
handler = "viewUser",
params = {"id": "{{row.id}}"}
)
),
@ButtonAnnotation(
text = "编辑",
icon = "edit",
type = ButtonType.PRIMARY,
click = @EventAnnotation(
type = CustomGridEvent.ROW_EDIT,
handler = "editUser",
params = {"id": "{{row.id}}"}
)
),
@ButtonAnnotation(
text = "删除",
icon = "delete",
type = ButtonType.DANGER,
click = @EventAnnotation(
type = CustomGridEvent.ROW_DELETE,
handler = "deleteUser",
confirm = true,
confirmMessage = "确定要删除用户{{row.name}}吗?"
)
)
}
)
)
},
events = {
@GridEventAnnotation(
type = CustomGridEvent.ROW_CLICK,
handler = "onRowClick"
),
@GridEventAnnotation(
type = CustomGridEvent.ROW_CHECK,
handler = "onRowCheck"
),
@GridEventAnnotation(
type = CustomGridEvent.PAGE_CHANGE,
handler = "onPageChange"
)
}
)
public class UserTableComponent {
// 表格事件处理实现
public void viewUser(String id) {
// 查看用户详情逻辑
UserDetailDialog dialog = new UserDetailDialog(id);
dialog.show();
}
public void editUser(String id) {
// 编辑用户逻辑
UserEditDialog dialog = new UserEditDialog(id);
dialog.show().then(result -> {
if (result) {
// 刷新表格数据
this.refresh();
}
});
}
@APIEventAnnotation(
event = CustomGridEvent.ROW_DELETE,
requestPath = @RequestPathAnnotation(value = "/api/users/{{id}}"),
method = HttpMethod.DELETE,
onSuccess = "handleDeleteSuccess"
)
public void deleteUser(String id) {
// API调用由注解驱动,此处无需编写HTTP请求代码
}
public void handleDeleteSuccess() {
// 删除成功处理
Notification.success("删除成功");
this.refresh();
}
}
3.4 新增用户表单
java
@DialogAnnotation(
id = "addUserDialog",
title = "新增用户",
width = 600,
height = "auto",
modal = true,
draggable = true,
closable = true,
maskClosable = false
)
@FormAnnotation(
id = "addUserForm",
title = "用户信息",
dataBinder = @DataBinderAnnotation(
id = "userBinder",
entity = "com.onecode.system.entity.User",
saveUrl = "/api/users",
method = HttpMethod.POST,
mode = BinderMode.ADD
),
layout = FormLayout.VERTICAL,
labelWidth = 120,
size = FormSize.MIDDLE,
items = {
@FormItemAnnotation(
field = "name",
label = "姓名",
required = true,
component = @ComponentAnnotation(
type = ComponentType.INPUT,
placeholder = "请输入姓名",
maxLength = 50
),
rules = {
@RuleAnnotation(type = RuleType.REQUIRED, message = "姓名不能为空"),
@RuleAnnotation(type = RuleType.MAX_LENGTH, value = "50", message = "姓名不能超过50个字符")
}
),
@FormItemAnnotation(
field = "username",
label = "用户名",
required = true,
component = @ComponentAnnotation(
type = ComponentType.INPUT,
placeholder = "请输入用户名"
),
rules = {
@RuleAnnotation(type = RuleType.REQUIRED, message = "用户名不能为空"),
@RuleAnnotation(type = RuleType.REGEXP, value = "^[a-zA-Z0-9_]{4,20}$", message = "用户名只能包含字母、数字和下划线,长度4-20位")
}
),
@FormItemAnnotation(
field = "password",
label = "密码",
required = true,
component = @ComponentAnnotation(
type = ComponentType.INPUT_PASSWORD,
placeholder = "请输入密码"
),
rules = {
@RuleAnnotation(type = RuleType.REQUIRED, message = "密码不能为空"),
@RuleAnnotation(type = RuleType.MIN_LENGTH, value = "6", message = "密码长度不能少于6位")
}
),
@FormItemAnnotation(
field = "deptId",
label = "所属部门",
required = true,
component = @ComponentAnnotation(
type = ComponentType.TREE_SELECT,
placeholder = "请选择部门",
dataSource = @DataSourceAnnotation(
url = "/api/departments/tree",
labelField = "name",
valueField = "id"
)
)
),
@FormItemAnnotation(
field = "roleIds",
label = "角色",
required = true,
component = @ComponentAnnotation(
type = ComponentType.CHECKBOX_GROUP,
dataSource = @DataSourceAnnotation(
url = "/api/roles",
labelField = "name",
valueField = "id"
)
),
rules = {
@RuleAnnotation(type = RuleType.REQUIRED, message = "请至少选择一个角色")
}
),
@FormItemAnnotation(
field = "status",
label = "状态",
component = @ComponentAnnotation(
type = ComponentType.RADIO_GROUP,
options = {
@OptionAnnotation(value = "1", label = "启用"),
@OptionAnnotation(value = "0", label = "禁用")
},
defaultValue = "1"
)
)
},
actions = {
@ActionAnnotation(
type = ActionType.SUBMIT,
text = "保存",
icon = "save",
primary = true,
event = @EventAnnotation(
type = CustomFormEvent.SAVE,
handler = "saveUser",
onSuccess = "handleSuccess",
onError = "handleError"
)
),
@ActionAnnotation(
type = ActionType.CANCEL,
text = "取消",
icon = "close",
event = @EventAnnotation(
type = CustomFormEvent.CANCEL,
handler = "closeDialog"
)
)
}
)
public class AddUserForm {
// 表单处理逻辑
public void handleSuccess() {
Notification.success("新增用户成功");
this.closeDialog();
// 通知父组件刷新数据
this.fireEvent("userAdded");
}
public void handleError(Error error) {
Notification.error("新增用户失败: " + error.message);
}
public void closeDialog() {
this.setVisible(false);
}
}
四、"三码合一"范式:OneCode的创新开发模式
OneCode提出了独特的"三码合一"开发范式,将三种形态的"代码"有机统一:
4.1 人类可读的注解代码
通过Java注解描述UI结构、事件绑定和业务规则,使代码具有自文档化特性,便于人类阅读和维护。注解代码清晰表达了开发者的设计意图,同时保持了代码的简洁性。
4.2 机器可执行的运行时代码
注解处理器在编译期将注解转换为可执行的Java字节码,这些代码直接运行在JVM上,保证了系统的性能和稳定性。生成的代码遵循最佳实践,避免了手写代码可能引入的错误。
4.3 AI可理解的结构化元数据
注解本质上是一种结构化的元数据,AI系统可以理解这些元数据并提供智能辅助。例如,OneCode的AI助手可以基于注解理解组件关系,自动生成事件处理代码,或提供优化建议。
这种"三码合一"的范式带来了多重优势:
- 提高开发效率:可视化设计与注解代码双向同步,减少重复劳动
- 增强可维护性:注解代码自文档化,降低维护成本
- 保证系统性能:编译期生成优化的可执行代码
- 支持智能辅助:结构化元数据使AI辅助开发成为可能
五、自举式开发:OneCode平台自身的开发模式
OneCode平台本身就是采用注解驱动开发模式构建的,这种"自举"方式验证了该模式的有效性:
-
平台核心组件使用注解定义 OneCode的核心UI组件、事件系统和数据处理逻辑均使用本文介绍的注解体系进行定义。
-
可视化编辑器是平台的一个应用 OneCode的可视化编辑器本身就是基于平台开发的一个应用,它通过解析注解生成可视化视图,同时也能生成注解代码。
-
持续迭代优化 平台团队使用注解驱动开发模式持续迭代优化OneCode,这种方式使平台能够快速响应需求变化,不断完善功能。
六、高级应用场景
6.1 动态表单生成
基于注解驱动的动态表单生成是OneCode的一个重要应用场景。通过@DynamicFormAnnotation
,可以根据业务需求动态生成复杂表单:
java
@DynamicFormAnnotation(
id = "dynamicForm",
dataSource = @DataSourceAnnotation(
url = "/api/form-schemas/{{schemaId}}",
method = HttpMethod.GET
),
onSchemaLoaded = "processFormSchema",
onDataChange = "handleFormDataChange",
events = {
@EventAnnotation(
type = CustomFormEvent.SAVE,
handler = "saveDynamicFormData"
)
}
)
public class DynamicFormComponent {
// 动态表单处理逻辑
}
6.2 AI辅助注解开发
OneCode集成了AI辅助开发功能,可以基于自然语言描述生成注解代码:
java
// AI根据以下描述生成上述用户表格注解
/**
* 创建一个用户列表表格,包含ID、姓名、用户名、所属部门、角色、状态、创建时间和操作列
* 支持分页、排序、筛选功能
* 操作列包含查看、编辑、删除按钮
* 状态列使用标签展示,启用为绿色,禁用为红色
*/
@AIAnnotation(prompt = "创建一个用户列表表格...") // AI辅助生成注解的标记
public class AIEnhancedUserTable {
// 表格实现
}
6.3 注解驱动的微前端架构
OneCode的注解系统支持微前端架构,通过@MicroAppAnnotation
可以将应用拆分为多个微应用:
java
@MicroAppAnnotation(
id = "userManagementApp",
name = "用户管理微应用",
entry = "/micro-apps/user-management/entry.js",
baseRoute = "/users",
sharedDependencies = {
@SharedDependency(name = "react", version = "17.0.0"),
@SharedDependency(name = "react-dom", version = "17.0.0")
},
events = {
@EventAnnotation(
type = "user.created",
handler = "handleUserCreated"
),
@EventAnnotation(
type = "user.updated",
handler = "handleUserUpdated"
)
}
)
public class UserManagementMicroApp {
// 微应用实现
}
七、总结与展望
OneCode的注解驱动开发模式代表了低代码平台的一个重要发展方向。通过将可视化配置与注解代码深度融合,OneCode实现了开发效率与系统灵活性的平衡。事件体系作为平台的核心枢纽,通过分层设计和自定义扩展机制,满足了复杂业务场景的交互需求。
未来,OneCode将在以下方向持续演进:
- 增强AI辅助能力:利用AI技术进一步提升注解生成和优化的智能化水平,减少手动编码工作量
- 扩展注解生态:构建更丰富的注解库,支持更多业务场景和技术栈
- 深化低代码与专业开发的融合:提供更灵活的扩展机制,使专业开发者能够无缝扩展平台能力
- 加强跨平台支持:通过注解抽象层,实现一次开发、多端部署
OneCode的注解驱动开发模式为企业级应用开发提供了一种新的范式,它不仅提高了开发效率,也保证了系统的可维护性和扩展性。随着低代码技术的不断发展,我们相信注解驱动开发将成为连接可视化设计与代码开发的重要桥梁,为更多企业数字化转型提供有力支持。