简介:面向用友UAP NC65初学者及企业级应用开发人员,这份资源围绕向导式主子表单据开发,覆盖主表创建、子表关联、数据绑定到交互逻辑与调试优化全链路,可帮助读者快速建立主子表开发的项目认知与实操能力。压缩包共126个文件,以Java源码与编译后的class为主,辅以16个SQL建表脚本、5个XML及properties等配置,以及bmf、bpf等表单模型定义文件,整体仅4.56MB,便于下载后对照源码研究。目前已有1673人学习下载。资源价值在于:37个Java文件可直接导入开发环境参考,SQL脚本可还原数据库表结构,配合表单模型与功能菜单等元数据文件,能够完整还原NC65向导生成主子表单据的开发流程;对于准备上手UAP二次开发的读者,这是一份兼顾源码、配置与步骤说明的实战参考资料。
1. NC65的向导式主子表单据开发:从跑通一个能保存的单据开始
如果你在用友UAP的NC65版本上做二次开发,第一次接触主子表单据时,大概率会被实体、动作、模板、单据类型这些概念绕晕。好消息是NC65的开发环境里有一套向导式生成工具,可以一次生成主表加子表的界面模板、动作类、持久化配置和数据库脚本,把原本两三天的手工编码压缩到几十分钟。但很多开发者跑完向导后依然一脸茫然:单据能打开,保存却报错,子表存不进去,菜单里找不到入口。这篇笔记按我的落地顺序,先讲清楚向导之前必须备好的实体和参照,再完整走一遍向导流程,最后列出几个最常翻车的排查点。不管你是刚开始做NC65实施,还是已经写过几年二开,都可以按这个路径复现。
2. UAP Studio开工前的三步准备:组件、主子实体和参照配置
2.1 建业务组件:先给单据找个不丢东西的家
在NC65的UAP Studio里,实体、动作、界面模板、单据类型都会挂在“业务组件”下面。组件是发布的最小单元,也是版本提交和权限分配的基本范围。常见做法是:先打开业务组件管理,新建一个组件,填写组件编码、名称和所属模块。组件编码建议用业务前缀加单据短名,例如客户订单用so_order,采购订单用pu_order。模块尽量不要选错,因为后续菜单节点默认会按模块归类,选错的话发布之后要找半天。
我一般会在这个环节故意慢一点,把组件和模块的关系确认清楚。原因很简单:一个单据的实体拆到多个组件,向导生成动作时会找不到子实体;多个单据塞进同一个组件,版本提交时元数据相互覆盖,出了问题很难定位。更稳妥的做法是一个业务单据对应一个组件,公共的参照、枚举单独放一个基础组件。组件建好之后,回到组件树上右键发布一次,让其他模块能引用到它的元数据。这一步不做,后面向导下拉列表里很可能看不到你建的实体。
2.2 主子表实体建模:外键参照是后续所有功能的地基
向导只能识别关系清晰的实体,所以第二步是把主表和子表实体建出来。以一张订单为例,主表实体保存单据头信息,子表实体保存明细行。主表实体至少要有主键字段、单据号、客户参照、单据日期;子表实体除了自己的行主键,必须有一个指向主表的主键外键字段。这个外键字段的类型一定要设置成参照型,并且参照对象指向主表主键。
如果外键字段建错了,向导依然能生成出界面,但保存时子表数据要么全部丢失,要么主键为空。下面这张字段表是我搭主子表实体时的固定模板,可以直接参考:
| 实体 | 字段编码 | 字段类型 | 说明 |
|---|---|---|---|
| so_order | pk_so_order | 主键 | 主表唯一标识,长度建议36 |
| so_order | vbillno | 文本 | 单据号,长度50 |
| so_order | ccustomer | 参照 | 客户档案,返回客户主键 |
| so_order | dbilldate | 文本 | 单据日期,长度10 |
| so_order_b | pk_so_order_b | 主键 | 子表行标识 |
| so_order_b | pk_so_order | 参照 | 指向主表主键,向导识别父子关系的依据 |
| so_order_b | cmaterial | 参照 | 物料档案,返回物料主键 |
| so_order_b | nnum | 数值 | 数量,精度建议16,4 |
| so_order_b | nprice | 数值 | 单价,精度建议16,2 |
| so_order_b | nmoney | 数值 | 金额,精度建议16,2 |
| so_order_b | vrowno | 整数 | 行号,由模板自动维护 |
字段编码尽量用英文小写加下划线,不要用中文或大写。NC65的持久化映射会把字段编码直接变成数据库列名,大写字段在小写敏感的数据库上迁移会非常痛苦。数值类型统一走decimal,主键统一走varchar(36)。实体字段修改后一定要先保存再发布,否则UI模板读到的还是旧字段列表。我见过不少开发者改完实体不发布直接跑向导,生成出来的模板里根本没有新字段,还以为是向导的Bug。
2.3 配置参照的基础资料:让子表能选到物料与客户
实体里的参照字段不是建完就完事,还需要配置参照的返回字段、显示列和过滤条件。以客户字段为例,返回字段选客户主键,显示列选编码加名称,这样在单据上点开参照弹窗,看到的是两列可读信息,而写入数据库的是主键值。物料字段通常要按组织过滤,否则用户能看到别的组织下的物料,选完保存时又带不过去。
参数上有三个地方容易忽略:一是参照类型要对,客户档案和供应商档案不能混用;二是过滤条件不能写死一个固定组织,要用当前登录组织这样的动态变量;三是参照配置完成后要发布到运行时,否则前端调用参照会返回空。有的开发者习惯在UI模板阶段才去补参照,我不建议这么做。模板阶段补参照只影响显示,后台持久化和查询引擎拿不到正确的参照主键,经常出现界面上有值、数据库里是空的情况。实体阶段配好参照,后面所有环节都会省事。
3. 用向导跑通第一版主子单据:从新建模板到生成DDL
3.1 启动向导的三个入口与关键参数
UAP Studio里进入向导的入口不止一个。我比较常用的是在业务组件树上选中组件,右键“新增动作”,然后选择主子表单据动作模板向导。另一个入口是到“单据模板管理”里新建模板,选择使用向导生成。还有一个是新建单据类型时,直接指定已有主表和子表,让系统反向补齐动作和模板。三个入口殊途同归,关键参数是一样的:动作编码、动作名称、主实体、子实体、单据类型编码。
动作编码建议避免与已有动作重复,用实体短名_职责的格式,比如so_order_save、so_order_b_del。主实体和子实体必须是已经发布过的,否则向导的下拉列表根本找不到。接下来是选择界面风格和按钮集合。做业务单据一般选“卡片+列表”,卡片用来编辑主子表内容,列表用来查询。按钮集合至少要勾保存、删除、增行、删行、刷新这几项。我的习惯是不勾“审核”类动作,因为审核逻辑十有八九是后面定制出来的,让向导生成一个空壳审核动作,前台会出现一个点了没反应的按钮,用户会以为系统坏了。
3.2 向导生成的制品清单与检查顺序
向导执行完成,不是只生成一个模板,而是同时生成一组关联制品:界面模板、动作类、持久化配置、单据类型定义、数据库DDL脚本。这些制品分散在业务组件的不同目录下,版本提交时经常被漏掉。比如有人只提交了Java代码,元数据里的单据类型没提交,发布后菜单能找到单据,打开时却直接报模板不存在。
我习惯在生成完成后先去组件树里核对这四样东西:模板节点下有没有新模板,动作节点下有没有新动作,元数据节点下有没有单据类型,以及持久化配置目录下有没有对应的映射文件。如果子表没有出现在模板中,先回到实体设计去看外键字段,不要急着在模板上硬拼。向导生成的UI模板本质上是个黑匣子,它按实体关系自动排好主子区,第一次打开时能看到主表卡片区和一个子表表格区。子表的“允许默认增行”“显示行号”这些属性都集中在表格属性里,不需要重建模板,改完保存发布即可。
3.3 最小验证:先执行DDL再编译发布
向导生成的SQL脚本一般会输出到发布目录,最稳的顺序是手动执行脚本,再回Studio编译,不要图快直接让工具自动执行。下面是一张主子订单表的建表脚本示例:
-- 主表 CREATE TABLE so_order ( pk_so_order VARCHAR2(36) PRIMARY KEY, vbillno VARCHAR2(50) NOT NULL, dbilldate CHAR(10), ccustomer VARCHAR2(36), vmemo VARCHAR2(255) ); -- 子表 CREATE TABLE so_order_b ( pk_so_order_b VARCHAR2(36) PRIMARY KEY, pk_so_order VARCHAR2(36) NOT NULL, vrowno NUMBER(10), cmaterial VARCHAR2(36), nnum NUMBER(16,4), nprice NUMBER(16,2), nmoney NUMBER(16,2) ); -- 外键约束:保证主子关系 ALTER TABLE so_order_b ADD CONSTRAINT fk_so_order_b_parent FOREIGN KEY (pk_so_order) REFERENCES so_order(pk_so_order);脚本逻辑说明:主表和子表各自建表,子表通过pk_so_order引用主表主键。字段名和实体编码保持一致,数值精度来自实体字段配置。如果库中已有旧表,不要直接动手删,先确认有没有业务数据,再决定是增量加字段还是重建。表结构执行完后,编译发布到调试环境,命令大致如下:
# 示意命令,以本地NC65构建脚本为准 cd /path/to/uap/bin ./compile.sh -project so_order -module nc.bs.so ./publish.sh -project so_order -to appserver # 查看发布日志,重点看 Compile Success 和 Publish Success tail -f /path/to/appserver/logs/upmp.log这里要说明的是编译命令和日志路径在不同部署环境下差异很大,不要照搬。关键是编译阶段要看有没有语法错误,发布阶段要看组件是否成功部署到应用服务器。发布成功后,用管理员账号在功能节点管理里加一个菜单节点,绑定刚生成的单据类型,再给角色分配权限。进到卡片界面新增一张带子表行的单据,保存后查so_order_b表是否有数据。如果这一步子表没数据,直接跳到第五章第五节的“外键参照”排查,不用在模板上浪费时间。
4. 向导成果的二次加工:按钮、行操作、校验与联动公式
4.1 按钮与动作绑定:先检查生成出来的按钮是否真的挂上了
向导生成的界面模板上通常自带“保存”“增行”“删行”“刷新”按钮。其中增行、删行是子表表格的工具栏操作,不需要单独绑定;而保存、删除这种动作类按钮必须在模板设计器里和动作编码绑定。向导会自动绑定,但如果你改过动作编码或者重新生成过动作,按钮就可能是“有样子没功能”的状态。判断方法很简单:选中保存按钮,看属性面板里的动作编码有没有值。
如果要在保存前加自定义校验,常见做法是新建一个动作类继承默认保存动作,重写核心方法。示例代码如下:
// 继承默认保存动作,插入前后置逻辑 public class SoOrderSaveAction extends DefaultSaveAction { @Override public Object doAction(Object ui, Object bill) throws Exception { // 从界面模型取子表行 Object lines = getChildData(ui, "so_order_b"); if (lines == null || hasNoRows(lines)) { throw new BusinessException("请先添加子表明细行"); } // 调用父类完成保存 Object result = super.doAction(ui, bill); // 保存后重新计算主表汇总字段 refreshSummary(ui); return result; } }这段代码的逻辑是:先检查子表是否有行,没有就在保存前拦截;调用父类完成持久化;保存结束后再做汇总刷新。参数说明:ui是当前卡片界面对象,bill是待保存的业务数据模型。继承默认动作的好处是保留向导生成的持久化逻辑,不必自己再处理表间关联。扩展动作类写完后,需要在动作配置里把模板按钮重新指向这个新类,否则发布后加载的还是旧动作。
4.2 子表行号与默认值:能用模板配置就不要写Java
子表的行号字段vrowno不需要写Java,在模板设计器里把该列设置为“自动号”,每次增行时按子表已有行数加一写入。数量、单价、金额这类字段联动,用模板的编辑公式配置最省事。示例公式如下:
// 模板字段编辑公式(示意) // 在数量、单价字段的值变更事件中触发 setValue("nmoney", round2(getValue("nnum") * getValue("nprice")));公式逻辑说明:当数量或单价变化时,重新对nmoney赋值。round2表示保留两位小数,具体函数名要看Studio自带的公式助手。参数上需要注意触发时机,值变更事件只在界面录入时触发,批量导入或者列表回显不会自动触发;如果主表有合计金额,最好在保存动作里再算一次,避免界面公式漏算。
在向导生成的模板基础上新增字段时,最容易犯的错是只把字段拖到主表卡片区,忘了在子表表格里也加一列。新增字段前,必须先在实体中同步添加并发布,模板的字段列表里才会出现这个字段。否则就算你手工在模板上硬加了一个显示列,保存时实体里没有对应属性,依然会丢数据。
4.3 字段联动:选择客户带出价格、选择物料带出规格
主子表单据的用户体验,很大程度上取决于参照字段的联动。客户选完,客户编码、客户名称要自动带出;子表物料选完,物料编码、规格、单位要自动带出。这些联动优先在模板字段的“返回映射”里配置。选中子表物料参照字段,在属性面板里找到映射配置,把物料档案里的编码、名称、规格等字段一一映射到界面对应的只读列。
参数说明:映射关系是“源字段→目标字段”,源字段来自参照数据集,目标字段必须是单据实体上已存在的字段。如果映射的目标字段不存在,需要去实体里补字段并发布。还有一个坑是,前端映射只在行内参照选择后触发,如果页面上有“批量填充”“导入”这类操作,映射不会自动执行,要在动作里补逻辑。这个边界一定要知道,否则后面很容易出现数据对不上的情况。
4.4 校验规则的优先级:先模板校验,再做动作校验
主子表单据最常用的校验有三条:明细行不能为空、数量不能小于等于零、外币单据汇率必填。最简单的做法是在单据类型上配置校验规则,规则里的错误提示会直接展示给用户。这类校验用脚本表达比较灵活,下面是一段示意:
// 保存校验规则示意(配置到单据类型上) function validate(ctx) { var lines = ctx.getChild("so_order_b"); if (!lines || lines.length === 0) { return "子表明细不能为空"; } for (var i = 0; i < lines.length; i++) { if (lines[i].nnum <= 0) { return "第 " + (i + 1) + " 行数量必须大于 0"; } } return null; }逻辑说明:这段规则会在保存动作之前执行,返回null表示校验通过,返回字符串表示拦截并提示。模板公式负责界面端的立算,动作校验负责后端数据的最后把关,两层都要有。特别提醒:如果用户通过列表导入或者接口写入数据,模板公式不会执行,但后端校验规则会兜住,所以校验规则不要依赖界面状态,尽量只读业务数据本身。
5. 部署发布与常见坑:NC65主子表单据的五个翻车现场
5.1 编译发布成功,但菜单里找不到单据
现象:向导生成、编译、发布都没有报错,登录NC后在业务菜单下找不到新单据。
原因:NC65的功能可见性由功能节点注册和角色权限两层控制。向导生成的是单据类型,不是菜单节点;菜单树需要管理员手动新增功能节点,绑定单据类型和动作,再给角色授权。
解决:用管理员账号进入功能节点管理,按模块新建节点,节点编码建议和单据类型编码保持一致。创建成功后,在角色管理里把该节点分配给目标角色。这条链路很长,少一步都表现为“菜单里找不到”。我一般会先给自己所在的角色分配全部菜单权限,验证通过了再缩小到业务角色。
5.2 保存时提示“表或视图不存在”
现象:单据能打开,点保存直接报数据库层面的“表或视图不存在”。
原因:向导生成的DDL脚本没有在目标库执行,或者执行时用了错误的数据库账号,表建到了其他用户下。
解决:用开发库账号重跑建表脚本,确认主表和子表都已创建。执行后检查表名和实体编码是否一致,如果实体发布后改过编码,旧脚本不会自动改表。另一个容易忽略的细节是,后续给实体增加新字段后,只更新了元数据没有执行增量DDL,也会报列名不存在。正确做法是使用UAP的元数据发布工具做增量变更,不要手工去改数据库表结构。
5.3 主表能保存,子表没数据
现象:界面能输入多行明细,保存提示成功,但查询子表没有任何数据。
原因:子表外键字段没有建成参照主表主键的类型。向导识别不了主子关系,持久化时子表拿不到主表主键,数据自然落不了库。
解决:回到实体设计,把子表外键字段改成参照型,参照对象选主表主键字段,保存发布后重新生成模板和动作。改实体不重新生成动作,子表依然可能拿不到主键。这是我在NC65开发里见过最多的一个问题,排序优先级最高,出现在这个位置上几乎不用怀疑其他原因。
5.4 参照弹窗打开后一片空白
现象:点客户参照或物料参照,弹窗列表没有任何数据,但去基础档案里看内容是有的。
原因:参照缺少组织过滤条件,或者当前登录人没有该参照的数据权限;还有可能是自定义参照类没有发布到运行时,前端加载失败静默返回空集。
解决:检查参照的数据集配置,把过滤条件改为按当前业务组织过滤。再到数据权限管理里给角色分配对应基础资料的可用权限。如果配置没问题依然为空,去应用服务器日志里搜参照类编码,看有没有加载异常。这个排查顺序基本能覆盖绝大部分参照空白的情况。
5.5 重复生成导致动作冲突
现象:向导跑了两次,第二次编译报类重复定义;或者动作类打开后发现旧版本的逻辑被新版本覆盖。
原因:动作编码和类名被重复使用时,向导会重新生成同名类。旧类不清掉就冲突,手工改过的代码也一并被覆盖。
解决:建立命名规范,动作编码用实体名加职责组成,生成前先到动作列表里查重。改动较大时不要依赖向导二次生成,直接在已有动作类上扩展。非要重跑向导时,先导出组件备份,清理旧动作和模板再生成。这种问题一旦发生,手工改过的保存校验全部白写,后悔药也不是每次都买得到。
6. 从向导到手工精确控制:把动作类和元数据的主动权拿回来
6.1 增量维护:新加字段不重跑向导
向导适合用来生成第一版骨架,但生产环境里的改动要尽量避免重复走向导。新增字段时,我会在实体上补字段并发布,然后到持久化配置里做增量DDL,最后回到模板字段列表把新字段拖到合适位置。这一套流程下来,动作类不会被动,校验逻辑也不会丢。很多人一改结构就重跑向导,等于把后期所有手工加工全部推倒重来,风险非常大。
如果确实要在已有单据上增加第二个子表,我的做法是在当前模板上手动添加子表区域,绑定新的子实体,而不是重跑整个向导。重跑意味着模板、动作、单据类型全部重建,之前做的按钮绑定、公式、映射全部要恢复。手动加区域的方式,半小时之内能完成,而且可控。
6.2 版本备份:把元数据导出当后悔药
对刚接触NC65向导的人来说,元数据同步和代码编译有点像两套体系,发布日志里写着一堆看不懂的同步记录。我的应对习惯是:每次改实体或动作之前,先在组件树上执行一次元数据导出,把XML文件放进版本库;改完发布后,如果发现模板或者动作加载不对,直接用导出的XML恢复,比重新点向导快得多,也安全得多。
我曾经因为图省事没做备份,直接重复跑了一次动作向导,结果覆盖了精心改过的保存校验,最后只能从版本库回退代码重新改。从那以后,不管多急,我都会先导出再动生成器。这个习惯在团队协作里特别重要,因为你永远不知道上一个同事往动作类里加了多少逻辑。保持“生成器只跑一次,后续全部手工增量”的节奏,NC65的主子表单据开发就基本不会失控。希望帮到你。
本文还有配套的精品资源,点击获取