{"title_zh":"Erupt 2.0.4:用注解把拖拽排序和业务按钮带进后台表格","body_zh":"# Erupt 2.0.4:用注解把拖拽排序和业务按钮带进后台表格\n\n低代码后台最容易的部分是 CRUD:列出来、编辑、搜索、导出,几乎都能交给框架。但真实业务很快会越过这条边界:用户希望拖动一行调整优先级,点击一个按钮重新计算价格,并把结果写回表单。Erupt 2.0.4 的价值,正在于把这类交互继续收进注解驱动的后台模型里,让开发者少写前端胶水代码,同时保留业务方法的控制权。\n\n## 从“生成页面”到“描述行为”\n\n传统低代码通常把实体字段映射成输入框,把查询条件映射成搜索栏。这样的模型适合数据管理,却不擅长表达动作。拖拽排序不是一个字段,它改变的是多条记录之间的顺序;“重新计价”也不是普通保存,它需要读取当前表单数据、执行业务规则,再返回可见结果。\n\n注解驱动的做法是把这些行为放回领域模型附近:字段负责描述数据,排序能力负责描述列表行为,按钮负责描述可执行动作。页面层根据元数据生成交互,服务层仍然负责校验、权限和事务。这样做的边界很清楚:注解减少的是界面样板代码,不是业务规则本身。\n\n## 拖拽排序真正改变了什么\n\n行拖拽看起来只是把一个图标放到列表左侧,实际至少涉及三个问题:\n\n- 顺序如何持久化:通常需要一个整数排序字段,例如 sortNo。\n- 批量更新如何保持一致:拖动一行可能影响同一列表中的多行,不能只依赖浏览器本地顺序。\n- 谁可以排序:列表可见不等于允许改序,权限仍应在服务端检查。\n\n因此,生产实现不应只关注“能不能拖”。要确认排序字段是否有索引,排序请求是否包含目标位置或完整顺序,重复提交是否幂等,以及删除、筛选、分页后排序范围的定义。若列表允许筛选,最好明确排序是作用于整个集合还是当前筛选结果。\n\n## “真按钮”让表单拥有业务动作\n\n一个业务按钮和保存按钮的区别,在于它通常不是简单的字段提交。例如“按重量计价”可能需要读取商品、地区、折扣和当前数量,调用价格服务,最后把计算结果回填到表单。把它硬塞进字段的 onChange 里,会让触发时机、异常处理和权限都变得模糊。\n\n更稳妥的模型是:按钮对应一个明确的方法,方法接收当前上下文,完成校验和计算,并返回需要刷新的字段。前端只负责触发和展示结果;真正的价格、库存或状态转换规则仍然在后端执行。\n\n下面是一个按常见 Java 注解式后台框架整理的最小示例。具体 Erupt 注解属性应以项目使用的 2.0.4 API 为准;示例重点是模型边界和按钮处理方式。\n\njava\n// Assumption: the project already has Erupt 2.0.4 and its usual entity/button annotations.\n@Erupt(name = "报价单")\npublic class Quote {\n\n @EruptField\n private String productCode;\n\n @EruptField\n private BigDecimal quantity;\n\n @EruptField\n private BigDecimal unitPrice;\n\n @EruptField\n private BigDecimal totalPrice;\n\n // Expose this field as the persisted order value in the table configuration.\n @EruptField\n private Integer sortNo;\n\n @EruptButton(なname = "重新计价")\n public void recalculate() {\n if (quantity == null || quantity.signum() <= 0) {\n throw new IllegalArgumentException("数量必须大于 0");\n }\n if (unitPrice == null || unitPrice.signum() < 0) {\n throw new IllegalArgumentException("单价不能为负数");\n }\n totalPrice = unitPrice.multiply(quantity).setScale(2, RoundingMode.HALF_UP);\n }\n}\n\n\n上面的 なname 只是为了避免把未确认的具体属性名伪装成 Erupt 2.0.4 的准确 API;接入时应替换为该版本实际的按钮名称属性。业务方法本身可以直接保留,或者移动到服务层,再由按钮方法调用服务。\n\n如果项目已经暴露了排序和按钮接口,也可以先用 HTTP 请求验证后端契约。下面的命令是可改造模板,字段名和路径替换成实际项目配置即可:\n\nbash\n# 发送拖拽后的新顺序;接口应在服务端校验权限并以事务方式更新\ncurl -X POST 'http://localhost:8080/api/quote/order' \\\n -H 'Content-Type: application/json' \\\n -H 'Authorization: Bearer YOUR_TOKEN' \\\n -d '{"items":[{"id":101,"sortNo":10},{"id":103,"sortNo":20},{"id":102,"sortNo":30}]}'\n\n# 触发表单业务按钮;服务端返回需要回填的字段\ncurl -X POST 'http://localhost:8080/api/quote/101/recalculate' \\\n -H 'Content-Type: application/json' \\\n -H 'Authorization: Bearer YOUR_TOKEN' \\\n -d '{"quantity":3,"unitPrice":19.90}'\n\n\n## 落地时要检查的四个细节\n\n1. 事务和并发:排序更新应避免部分成功;多人同时拖动时,需要版本号、更新时间或明确的最后写入策略。\n2. 权限和审计:按钮是可执行的业务入口,应单独检查权限,并记录操作者、旧值和新值。\n3. 返回协议:按钮执行成功后,明确返回整行、字段补丁还是要求刷新页面,避免出现后端已更新、界面仍显示旧值的情况。\n4. 异常体验:参数错误、外部服务超时和重复点击都应有稳定响应;按钮执行期间需要防止重复提交。\n\n## 适合采用的场景\n\n这类能力特别适合内部运营台、订单配置、价格维护、内容编排和审批列表:页面结构相对标准,但少数动作具有明确的领域含义。对于高度定制的画布、复杂实时协作或大量客户端状态的应用,注解生成的页面可能会触到上限,此时应把定制前端作为明确的架构选择。\n\nErupt 2.0.4 释放出的信号并不是“所有前端都可以消失”,而是后台框架可以继续覆盖更多可描述的交互。采用前先做一张清单:排序是否有持久化语义,按钮是否有稳定服务端方法,权限和事务是否可测试,异常时页面是否能恢复。清单能通过,再让注解接管页面生成;不能通过,就不要把复杂度藏在注解后面。","title_en":"Erupt 2.0.4: Bringing Drag-and-Drop Ordering and Real Business Actions to Admin Tables","body_en":"# Erupt 2.0.4: Bringing Drag-and-Drop Ordering and Real Business Actions to Admin Tables\n\nCRUD is the easy part of an admin panel. Lists, forms, search, and exports are all natural targets for code generation. Real operations soon ask for more: users need to drag rows into a new priority order, or press a button that recalculates a quote and writes the result back into the form. Erupt 2.0.4 extends annotation-driven, front-end-light administration in that direction.\n\n## Describing Behavior, Not Just Fields\n\nA basic low-code model maps an entity field to an input and a query field to a filter. That works well for data management, but ordering and business actions are not ordinary fields. Reordering one row can change several records. Recalculating a price may require validation, discounts, inventory data, or an external pricing service.\n\nAn annotation-driven model can keep these concerns close to the domain model: fields describe data, table metadata describes ordering, and buttons describe explicit actions. The generated UI handles the interaction while the server remains responsible for validation, authorization, and transactions. The important boundary is that annotations remove presentation boilerplate; they do not replace domain logic.\n\n## What Row Reordering Requires\n\nDrag-and-drop ordering usually needs a persisted integer such as sortNo. A production implementation also has to answer several questions: Does the request contain the complete order or only the moved row? Is the update atomic? Can two operators reorder the same list concurrently? Does filtering or pagination change the scope of the order?\n\nA visible table is not automatically an editable table. The server should authorize reorder operations independently, validate that every referenced record belongs to the intended collection, and make retries safe. An index on the ordering column may also matter once the table grows.\n\n## A Button With Domain Meaning\n\nA “recalculate” button is different from a save button. It may read the current form values, apply pricing rules, call a service, and return fields that the UI should refresh. Hiding that workflow inside a client-side change handler makes trigger timing, error handling, and authorization harder to reason about.\n\nThe cleaner model is an explicit server-side method or service operation. The generated page triggers it and displays the result; the business rules stay on the server.\n\nThe following small Java example uses conventional annotation-style names. Treat the annotation attributes as an integration sketch and verify the exact Erupt 2.0.4 API in the project before copying them unchanged.\n\njava\n@Erupt(name = "Quote")\npublic class Quote {\n\n @EruptField\n private String productCode;\n\n @EruptField\n private BigDecimal quantity;\n\n @EruptField\n private BigDecimal unitPrice;\n\n @EruptField\n private BigDecimal totalPrice;\n\n @EruptField\n private Integer sortNo;\n\n @EruptButton(name = "Recalculate")\n public void recalculate() {\n if (quantity == null || quantity.signum() <= 0) {\n throw new IllegalArgumentException("Quantity must be greater than zero");\n }\n if (unitPrice == null || unitPrice.signum() < 0) {\n throw new IllegalArgumentException("Unit price cannot be negative");\n }\n totalPrice = unitPrice.multiply(quantity)\n .setScale(2, RoundingMode.HALF_UP);\n }\n}\n\n\nIf the project exposes explicit HTTP endpoints, the backend contract can be tested independently of the generated UI. Replace the paths and field names with the actual application configuration:\n\nbash\n# Persist the order after a drag operation.\ncurl -X POST 'http://localhost:8080/api/quote/order' \\\n -H 'Content-Type: application/json' \\\n -H 'Authorization: Bearer YOUR_TOKEN' \\\n -d '{"items":[{"id":101,"sortNo":10},{"id":103,"sortNo":20},{"id":102,"sortNo":30}]}'\n\n# Trigger the business action and return fields for the form to refresh.\ncurl -X POST 'http://localhost:8080/api/quote/101/recalculate' \\\n -H 'Content-Type: application/json' \\\n -H 'Authorization: Bearer YOUR_TOKEN' \\\n -d '{"quantity":3,"unitPrice":19.90}'\n\n\n## Production Checklist\n\n- Make reorder updates transactional and define a concurrency policy.\n- Check authorization at the action endpoint, not only in the generated page.\n- Record meaningful audit information for price, status, and ordering changes.\n- Define whether an action returns a full row, a field patch, or a refresh instruction.\n- Prevent duplicate clicks and return stable errors for invalid input or downstream timeouts.\n\nThis capability is a good fit for operations consoles, order configuration, price maintenance, content scheduling, and approval queues: the page is mostly standard, while a small number of actions carry clear business meaning. It is less suitable for highly custom canvases, rich real-time collaboration, or applications dominated by client-side state.\n\nThe practical lesson from Erupt 2.0.4 is not that every front end can disappear. It is that more admin interactions can be expressed as metadata without turning the business layer into JavaScript glue. Before adopting it, verify that ordering has durable semantics, actions map to testable server methods, and permissions, transactions, and recovery behavior are explicit. Then let the annotations generate the repetitive surface area.\n","seo_description_en":"See how Erupt 2.0.4 brings drag-and-drop row ordering and server-side business buttons to annotation-driven admin tables."}
Erupt 2.0.4:一个注解,让表格行能拖着排序了
2026-08-20
22
预计阅读时间: 1 分钟
Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.
预计阅读时间:13 分钟