news 2026/10/22 7:25:57

SpringBoot + Vue3 + AI:从一句需求到可审批的业务单据,中间还缺哪些能力?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot + Vue3 + AI:从一句需求到可审批的业务单据,中间还缺哪些能力?

SpringBoot + Vue3 + AI:从一句需求到可审批的业务单据,中间还缺哪些能力?

本文由 RuoYi Office 团队整理,结合实际项目分享企业软件的设计思路与开发经验。

给企业做系统,AI 生成一张表单只是开头。费用算得对不对,数据存到哪里,提交失败后能不能重试,这些才决定它能否交给员工使用。我们拿差旅单据举例,把生成之后要补的几步拆开讲。

▲ AI 对话负责起草,真正落地还需要字段契约、数据存储和审批关联。封面为系统截图素材合成。

一、先明确让 AI 生成什么,不要一句话包办整个系统

“帮我生成一张差旅报销单”,模型通常能很快给出申请人、部门、日期、金额和附件。放在演示里,已经挺像那么回事。

但开发者还得追问:这张单只是收集材料,还是要占用预算、核销借款,最后推送付款?这几种需求的页面可能很像,背后的处理完全不同。

我们建议先把目标定小:让 AI 生成一份可以继续编辑的表单草案,再把确定的业务规则接进去。本文讨论的是从草案到可提交、可审批的技术路径,不把“审批通过”延伸成“财务已经付款”。

在 RuoYi Office 的这套接入里,AI 输出的是 FormCreate Rule,也就是描述组件、字段和校验方式的规则;设计器接收规则后呈现页面。它不是让模型随意写一段 HTML,更不是直接让模型执行建表语句。本文使用的智能助理入口来自 FormCreate Pro,相关商业组件仍需按其许可使用。

▲ 已有系统中的需求输入界面。输入内容区分了主表和费用明细,这张截图展示的是生成前的输入状态。

可以把需求再说具体一点:

生成差旅报销表单草案。主表包含申请人、部门、出差起止日期和事由;费用明细包含发生日期、类别、说明和金额。申请人与部门使用系统选择器,金额保留两位小数。先生成结构,不添加付款和自动审批逻辑。

这不是为了把提示词写得很长,而是减少模型需要猜的部分。哪些字段是人员、哪些是一组明细、哪些动作不在本次范围内,最好提前说清楚。

生成后先验结构,再谈功能。比如费用明细应该是可增行的tableForm,不能变成主表上四个孤立输入框。发现错了,先修这一步,否则后面的存储和计算都要跟着返工。

二、字段看着对,还要确认它传出去的值是什么

表单里的中文标题是给人看的,字段标识和取值才是系统之间的约定。

举个例子,“申请人”显示张三,提交时应该传人员 ID,还是姓名?如果只存姓名,遇到重名、改名和跨部门调动,审批和查询就很难稳定关联。部门也一样,页面能选中“研发部”,不代表它一定提交了正确的部门 ID。

对这张示例单据,我们会先列一张很短的字段契约:

内容建议提交的值需要核对的事情
申请人、部门系统中的 ID是否允许代填,人员和部门是否匹配
出差日期明确格式的日期字符串起止顺序、是否把纯日期误当时间戳
费用明细对象数组每行字段一致,不能只传一段说明
报销总额服务端核算结果不直接采信浏览器传来的合计

下面是一个简化的 FormCreate 金额字段示例,用来说明规则与取值的关系,不是完整报销单:

{"type":"inputNumber","field":"expenseAmount","title":"费用金额","$required":true,"props":{"precision":2,"min":0,"placeholder":"请输入本行费用金额"}}

这里的expenseAmount以后可能同时出现在明细数据、存储映射和计算逻辑里。把标题从“费用金额”改成“报销金额”,与把字段名也改掉,是两件不同的事。后者要检查所有引用,不能当成一次文字润色。

RuoYi Office 的独立数据表发布解析,会检查列名冲突、明细字段标识重复等结构问题。但结构检查不会替公司决定“能否代别人报销”,也不能代替服务端业务校验。

▲ 预览适合检查字段、必填标识和组件类型。图中仍是空表单,是否满足业务规则,需要输入样例并检查提交结果。

日期尤其值得单独试一下。出差开始日是纯日期,审批发生时间才是时间点。前者可以约定YYYY-MM-DD,后者按接口使用时间戳或明确格式的时间字符串。前后端各猜一种格式,页面也许能显示,保存和再次打开时却未必一致。

还有一个容易漏的细节:隐藏字段不等于删除字段。页面不让改的申请人、租户或状态,仍可能被人为构造进请求。应该由服务端读取登录上下文或校验允许的业务范围,而不是相信“界面上没有这个输入框”。

三、金额可以在前端算,但最终结果要在服务端确认

前端自动合计能改善体验,不能作为金额正确的最后一道保证。

假设有两笔费用,分别是 128.50 元和 36.80 元。页面合计显示 165.30 元,员工不用自己按计算器,这是联动计算的价值。但如果请求中的总额被改成 16.53 元,服务端不能因为它也是一个合法数字就直接保存。

比较稳妥的方式是:前端即时显示,服务端从明细重新计算。审批条件和最终落库使用同一份核算结果,避免数据库存一个金额,流程却拿另一个金额判断是否需要加签。

下面是面向“人民币费用、每行非负、最多两位小数”的二开示例,并非通用表单引擎已经包含的报销制度。它只演示金额核算,完整业务还要校验费用类别、日期、附件和用户权限:

staticBigDecimalcalculateTotal(List<BigDecimal>amounts){if(amounts==null||amounts.isEmpty()){thrownewIllegalArgumentException("至少填写一行费用");}BigDecimaltotal=newBigDecimal("0.00");for(BigDecimalamount:amounts){if(amount==null||amount.signum()<0){thrownewIllegalArgumentException("费用金额不能为空或为负数");}BigDecimalnormalized;try{normalized=amount.setScale(2,RoundingMode.UNNECESSARY);}catch(ArithmeticExceptionex){thrownewIllegalArgumentException("费用金额最多保留两位小数");}total=total.add(normalized);}returntotal;}

为什么这里不直接四舍五入?因为例子约定的是“最多两位小数”。传入 12.345 时,拒绝并提示修改,比悄悄变成 12.35 更符合这个约定。如果业务允许税额分摊、汇率换算,就需要重新定义计算精度、舍入时点和尾差归属,不能照搬这段代码。

BigDecimal用于十进制计算,构造测试金额时也应使用字符串或可靠的十进制来源,不要先用double算一遍再补救。实际接口还应限制单行金额、明细行数及总额上限,避免超出数据库字段范围。

现有独立数据表转换器能把小数转换为BigDecimal,输入框的精度配置也会参与列模型解析。它解决的是“值能不能按这个类型存下来”,不是“这笔钱按公司制度能不能报”。两层规则分开,后续才能分别维护。

四、规则、业务数据和流程变量,不要混成同一份东西

生成一张表单,不等于生成了一张已经填好的单据。

我们可以把它理解成空白报销模板与员工实际提交的报销单:模板描述“要填哪些项”,单据记录“这一次填了什么”。同一个模板,应该能产生很多张业务单据。

再往下走,审批引擎还需要一部分数据判断路线,例如申请部门和报销金额。这些参与流程运行的数据,就是流程变量。它可以与业务数据重叠,但用途不同。

数据主要回答的问题典型内容
表单规则与发布版本当时用什么结构填写字段、组件、布局、模型快照
业务单据与明细这次申请了什么人员、费用行、金额、附件引用
流程实例与变量谁处理,走哪条路线发起人、条件值、任务、审批状态

只有简单资料收集时,把数据保存在流程变量中可能就够了。如果后续需要按部门统计费用、查询明细、提供业务接口,独立数据表通常更方便。如果已经有成熟的报销模块,还涉及借款核销和财务处理,优先复用业务服务,而不是再生成一套平行账。

▲ 表单目录负责维护模板和进入编辑;这里展示的是表单定义,不是员工提交的报销记录。

本文后续以“独立数据表表单”这条路径展开。在当前实现中,主单保存业务字段及form_version、process_status等信息,明细通过主单 ID 关联;新增单据时还会写入租户和审计字段。

注意,通用主子表存储也有适用边界。当前明细更新路径在请求包含该明细字段时,会删除旧明细再插入新明细。因此,如果外部系统要长期引用某一条费用行的 ID,或者每行都有独立核销记录,就应设计稳定的明细身份和差量更新,不能只依赖这种整体替换方式。

这一点比“自动建了几张表”更值得提前确认。表能建出来,并不代表它适合所有后续业务。

五、保存草稿和发布版本分开,AI 修改才不会随手影响在途单据

生成以后还会改需求,所以要先明确:这次修改只影响设计稿,还是会改变正在审批的单据?

在 RuoYi Office 的独立数据表路径里,保存设计与发布版本是分开的。发布会解析字段和库表差异,生成变更预览;确认时重新计算预览,检查是否存在阻断项,以及预览是否已经过期。

下面摘取发布方法里的核心判断,省略了外围的发布锁、结果组装等代码:

BpmFormDOform=validateManagedForm(reqVO.getFormId());BpmFormManagedPublishPlanplan=computePlan(form);BpmFormManagedPreviewRespVOpreview=buildPreview(form,plan);if(preview.getBlockedCount()>0){throwexception(FORM_MANAGED_PUBLISH_BLOCKED,preview.getBlockedCount());}if(!Objects.equals(preview.getPreviewHash(),reqVO.getPreviewHash())){throwexception(FORM_MANAGED_PUBLISH_EXPIRED);}executeDdl(form.getId(),plan);transactionTemplate.executeWithoutResult(status->applyPublish(form,plan));

previewHash可以理解成这次变更预览的指纹。你打开预览后,如果表单或相关结构又发生变化,就不能拿旧确认继续执行。这个机制检查的是变更是否一致,不是让 AI 自己批准自己的修改。

发布结果会保存配置、字段和模型快照。流程定义部署时绑定当时的表单发布版本,因此后续只保存独立表单草稿,不会直接把草稿同步给已部署的流程定义。需要使用新版本时,还要处理相关流程模型的更新和部署。

这里的版本行为特指独立数据表表单,不能推广成所有存储模式都相同。版本快照也不是任意数据库变更的“撤销按钮”:当前发布先执行 DDL,再事务性保存发布元数据,不能据此承诺跨两步自动回滚。实际发布失败后,要根据执行日志核对数据库状态。

对使用者来说,可以先记住一个判断:只改提示文字,和修改字段身份、数据类型,不是同一级别的操作。后两种变更尤其需要检查旧数据和在途流程,不能只看新表单是否漂亮。

六、接上审批,需要把单据身份和流程身份关联起来

表单能保存之后,还需要让流程明确“我审批的是哪一张单”。

这里有两个 ID:业务主键标识一张单据,流程实例 ID 标识一次审批运行。Flowable 的businessKey可以携带业务主键,方便从流程回到单据;单据也记录流程实例 ID,方便展示审批进度。

当前独立数据表的新单发起路径,先写业务数据,把行 ID 作为businessKey,再启动流程并回写实例 ID。下面是核心顺序的源码节选;省略了前置权限检查、变量准备,以及中间的流程编号和名称设置:

LongmanagedDataId=null;if(BpmFormManagedDataService.isManaged(processDefinitionInfo)){if(StrUtil.isEmpty(businessKey)){managedDataId=formManagedDataService.createData(processDefinitionInfo,variables,userId);businessKey=String.valueOf(managedDataId);}else{formManagedDataService.updateData(processDefinitionInfo,businessKey,variables,userId);}}ProcessInstanceBuilderprocessInstanceBuilder=runtimeService.createProcessInstanceBuilder().processDefinitionId(definition.getId()).businessKey(businessKey).variables(variables);// 此处省略流程编号与名称设置ProcessInstanceinstance=processInstanceBuilder.start();if(managedDataId!=null){formManagedDataService.bindProcessInstance(processDefinitionInfo,managedDataId,instance.getId());}

它说明了两种身份怎么关联,但不能把businessKey当成防重复提交的保证。员工连点两次,或者第一次请求成功、响应却丢了,第二次请求是否会新增另一张单,需要另外处理。

针对这类二开场景,可以为同一次提交分配请求编号,按租户、用户和操作范围校验唯一性,并保存对应结果。重试时返回原结果,而不是再创建一次。数据库唯一约束、并发处理和失败后的重试规则要一起设计,光把按钮置灰不够。

管理端发起入口有事务声明。在业务表和流程引擎确实共享本地事务的部署中,可以把保存和启动放在一起提交;一旦跨库或调用外部服务,就要重新检查事务边界,不能以为一个@Transactional能让远端动作一起回滚。

▲ 以已有用车流程展示通用发起权限配置入口,差旅流程需单独配置自己的发起范围。

审批入口还要区分“谁可以发起”和“谁可以查看或修改这张单”。前者通过了,后者并不自动成立。尤其是代填单据、跨部门访问和附件下载,应分别校验。

流程状态变化后,当前独立数据表有监听器把状态同步回单据。但“审批通过”仍然只代表这次审批的结果。如果还需要付款、核销或推送财务系统,应交给对应业务处理,并记录执行成功或失败,不能直接把流程状态当付款状态。

七、验收别只看生成成功,要把错误输入和第二次操作也跑一遍

一张单能打开、能点提交,还不足以判断它可以交付。

对本文这类差旅表单,我们建议先准备一组很小但完整的验收样例。下面是测试目标,不是已经跑过的测试报告:

验收动作应检查的结果
填两行 128.50 和 36.80 的费用服务端合计、落库金额、审批条件值均为 165.30
将请求总额改成 16.53按约定拒绝不一致请求,或使用服务端核算值;不采信篡改值
输入负金额、三位小数或空明细在确定的业务校验处拒绝,给出可理解的提示
用无权用户或其他部门记录发起访问发起、详情、修改和附件分别验证权限
提交后模拟响应丢失,再次重试验证幂等设计是否返回原结果,避免重复建单
修改表单但不发布,再打开在途单据独立数据表路径仍按绑定版本解释数据
让流程启动或外部调用失败核对实际事务边界,能查清状态并按设计恢复

这些检查不需要都塞进提示词。组件选择和草案修改交给 AI,确定的规则交给程序,涉及公司制度的决定交给业务负责人。职责分开,定位问题也更快。

如果只是收集一份申请材料,动态表单加审批就可能够用;如果涉及预算占用、发票查重、核销和付款,应该让动态表单接入已有业务服务,或者转成专门的业务页面。不要为了坚持“全靠配置”,把关键规则藏进越来越难维护的页面脚本。

我们更看重的,不是 AI 一次生成了多少组件,而是生成结果能否被检查、被修改,并且在保存、发布和提交时有清楚的处理边界。这样,AI 才真正减少重复劳动,而不是把需要开发者确认的问题藏了起来。

你们接企业项目时,更常遇到的是“表单画得慢”,还是“表单已经画好,后面的规则没人说清楚”?两种问题,需要投入的工作其实不一样。

如果这篇对你有用,欢迎点赞支持。

我们会持续分享企业软件开发实践与 RuoYi Office 产品进展。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 7:25:47

【C语言】低级指针

学C语言&#xff0c;指针应该是大多数人绕不过去的一座大山&#xff0c;以下是我在指针学习时遇到的问题及解答&#xff0c;欢迎提出见解 • 为什么 &arr 和 arr 打印出来的地址一样&#xff0c;加 1 之后却不一样了&#xff1f; • 在推导 &#xff08;(arri)j) 的过程中&…

作者头像 李华
网站建设 2026/10/22 7:25:46

4、Python 内置容器类型:list、tuple、set、dict 详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/22 7:25:19

高并发登录接口性能测试全解析

功能模块性能测试用例验证 功能模块上线前&#xff0c;性能测试用例是保障系统稳定性的关键环节。本文以高并发接口为例&#xff0c;演示性能测试用例的设计、执行与验证流程。 一、测试目标与场景设计 被测模块&#xff1a;用户登录鉴权接口 /api/login&#xff0c;核心指标如…

作者头像 李华
网站建设 2026/10/22 7:24:24

python杂识--24--Nuitak打包exe

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/22 7:21:40

【脚本】 【Linux】重命名网卡

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/22 7:19:57

Lightpanda 无头浏览器完整上手指南:1/16 内存跑满 JS 渲染页面

Lightpanda 无头浏览器完整上手指南&#xff1a;1/16 内存跑满 JS 渲染页面 【免费下载链接】browser Lightpanda: the headless browser designed for AI and automation 项目地址: https://gitcode.com/GitHub_Trending/browser32/browser Lightpanda 是一个用 Zig 从…

作者头像 李华