简介:面向用友UAP NC65开发者的主子表单据开发指南,聚焦向导式创建主子表单据的完整流程,适合刚接触UAP平台、需要快速上手企业级表单开发的初学者。内容从主表创建、子表配置到主子关联与交互逻辑均有详细说明,并涉及数据库设计、数据绑定、事件处理等关键知识点。资源包共126个文件,包含37个Java源码及其编译后的class文件、16个SQL脚本、6个properties配置、5个XML配置,以及bmf/bpf等UAP建模文件,压缩包约4.56MB,代码与配置可直接对照学习。已有1664人浏览学习,可作为实战参考。通过阅读源码与建模文件,可理解主子表单据的字段映射、外键关联、刷新规则与增删改查实现,同时获得分页、排序等优化思路,为后续复杂业务开发打下基础。 用友NC65的二开里,主子表单据是出现频率最高、也最容易让人半路卡壳的一种需求。我第一次用UAP的向导做这种单据时,以为点完之后会像其他低代码平台一样直接出一个能增删改查的页面,结果向导只生成了一个模板框架,后面功能注册、元数据建模、模板分配、节点挂接、权限配置这些环节一个都不能少,任何一个断了,页面要么出不来,要么保存就报错。这篇文章就按我实际操作过的一条完整链路来写:从UAP里做功能注册和主子表元数据建模开始,到向导初始化主子单据模板、配置模板和分配模板,再到NC65里挂接功能节点、配置权限和单据类型,最后把平时开发中踩过的高频问题整理成排查思路。想快速上手用友UAP开发NC65主子表单据的同行,按这个顺序走,基本能避开大多数新手坑。
1. 向导的真正作用:先弄懂UAP的“元数据-模板-节点”三条线
1.1 向导生成的是什么
UAP的向导不会像Visual Studio那样,一键生成一套完整可用的业务系统。它生成的是一套以元数据为骨架、以模板为皮肤的中间产物。元数据负责定义主表和子表的字段、类型、长度、主子关系,数据库表结构就是由元数据同步生成的;模板则决定这些字段在操作界面上如何排列、哪些字段可编辑、哪些字段隐藏、哪些字段必填。最后还要有一个功能节点把元数据和模板串起来,再通过权限挂到菜单上,整个单据才算真正可用。
很多初学者把“向导初始化模板”当成终点,实际上它只是整套开发流程里的“打底”环节。拿装修来类比:元数据是房子的墙体结构,决定房子有几间房、门开在哪;模板是水电、油漆和家具摆放,决定住得舒不舒服;功能节点和权限则是门牌号和钥匙,决定谁能进这栋房子。这三条线少任何一条,你的“房子”都交付不了。
1.2 什么情况下用向导,什么情况下别硬用
向导适合主表+子表结构清晰、字段数量在二三十个以内的标准业务单据,比如采购申请单、销售订单、报销单、简单入库单这类场景。因为向导生成的代码和模板结构是标准化的,项目后期接手的人很容易看懂,维护成本低。
但如果你的单子有很多特殊交互,比如表体行要按批次动态增删分组、表体字段之间要做复杂的联查回写、单据流程要和审批流强绑定、或者需要对接外部接口做大量自定义逻辑,那我的建议是:把向导结果当起点,在生成的Action上做扩展,不要试图全靠向导模板来解决一切。向导解决的是“基础框架”,不是“全部业务”。
1.3 开工前的准备清单
照着下面这张表逐项确认,能省掉一半的“环境问题”排查时间:
| 检查项 | 具体要求 | 说明 |
|---|---|---|
| 开发环境 | 中间件已启动,UAP Studio能连上开发库 | 连不上库时后面所有操作都无法保存 |
| 数据库账号 | 具备建表权限 | 元数据生成脚本需要执行到业务库 |
| 模块编码 | 明确单据归属模块,如po、so、hr等 | 功能注册时用,随意编编码后患无穷 |
| 权限账号 | 当前账号有UAP开发权限和功能节点管理权限 | 没有开发权限进不了UAP的建模界面 |
| 环境隔离 | 优先在独立开发库操作,不要动正式库 | 元数据变更会影响表结构,正式库出问题很难回滚 |
这些条件里最容易被忽略的是第三项。很多人随手写个模块编码,做到后面发现单据挂到了错误的菜单树下,又得重新注册一遍功能节点,白白浪费半天。
2. 主表和子表建模:这一步定调,后面全是执行
2.1 主表实体建模
打开UAP工程,在导航树上找到目标模块,右键选择新建元数据实体,类型选择单据类(不同NC版本可能叫“自定义单据”或“虚单据”,取同一个意思即可)。主表实体承载的是“单头”数据,一般要包含单据号、单据日期、组织、集团、备注这些字段,再加上一套NC标准系统字段。
我以一个采购申请类的例子来说明,主表字段可以这样定义:
| 字段名 | 字段类型 | 长度 | 说明 |
|---|---|---|---|
| billno | 字符串 | 40 | 单据号 |
| billdate | 字符串 | 10 | 单据日期 |
| pk_org | 字符串 | 20 | 业务组织 |
| pk_group | 字符串 | 20 | 集团 |
| remark | 字符串 | 255 | 备注 |
| pk_head | 字符串 | 20 | 主键,由系统自动生成 |
| dr | 整型 | - | 删除标记,默认0 |
| ts | 字符串 | 19 | 时间戳 |
关键点是主表主键必须用pk_head,这是NC65主子表机制的统一约定。后面子表外键、模板关联、保存Action全部依赖这个字段,换掉它后面会出很多莫名其妙的问题。
2.2 子表实体和主子关系
子表实体承载的是“表体行”数据,主键用pk_detail,外键用pk_head,再定义行号、物料、数量、金额、备注等业务字段。比如:
| 字段名 | 字段类型 | 长度 | 说明 |
|---|---|---|---|
| pk_detail | 字符串 | 20 | 子表主键 |
| pk_head | 字符串 | 20 | 外键,关联主表 |
| rowno | 整型 | - | 行号 |
| material | 字符串 | 40 | 物料编码 |
| num | 浮点 | 16,4 | 数量 |
| price | 浮点 | 16,4 | 单价 |
| money | 浮点 | 16,2 | 金额 |
| memo | 字符串 | 255 | 行备注 |
定义完这两个实体后,必须在元数据的关系视图里建立主子关联:主表与子表通过pk_head一对一关联,类型选“主表-子表”或“一对多”。这一步不做,UAP向导在初始化模板时就识别不出这是张主子表单据,后面所有流程都走不下去。我见过不止一个同事跳过这一步,结果模板初始化界面里只能看到主表看不到子表,卡在那里不知道怎么回事。
2.3 生成数据库脚本并执行
主表和子表实体都保存后,执行UAP里的“生成数据库脚本”功能,会生成对应的建表脚本,脚本里会自动带上dr默认值、ts字段这些约定。手动建的库表可以参考下面这种结构:
create table t_po_demo_head ( pk_head char(20) primary key, billno varchar(40), billdate char(10), pk_org char(20), pk_group char(20), remark varchar(255), creator varchar(20), creationtime char(19), modifier varchar(20), modifiedtime char(19), ts char(19), dr int default 0 ); create table t_po_demo_item ( pk_detail char(20) primary key, pk_head char(20), rowno int, material varchar(40), num decimal(16,4), price decimal(16,4), money decimal(16,2), memo varchar(255), ts char(19), dr int default 0 ); create index idx_demo_item_head on t_po_demo_item(pk_head);执行完脚本后,去数据库里确认两张表都创建成功,并且子表能通过pk_head关联到主表,再继续下一步。建模阶段出的问题越早暴露越省事,因为元数据一改,生成脚本要重刷,后面初始化的模板可能全部作废。
3. 走通UAP向导:功能注册、模板初始化、模板分配
3.1 功能注册
元数据到位后,在UAP的功能注册界面新增一个功能节点。节点编码要全局唯一,命名建议带上模块前缀,比如PO_DEMO_APPLY;节点名称要写业务含义,比如“采购申请Demo”;所属模块选你第一步确认好的模块编码,功能类型选“单据”。
有一个细节容易被忽略:功能注册时的节点编码和模板编码不一定相同,但为了排查问题方便,我习惯让它们同前缀。例如功能节点编码为PO_DEMO_APPLY,模板编码就叫PO_DEMO_APPLY_T(后缀T代表template),一眼能看出对应关系。这算是个小经验,不强制,但能让你少查好几次数据库。
3.2 初始化主子模板
在UAP里找到“单据模板初始化”功能,选择刚才注册的功能节点,点击初始化模板后,向导会让你选主表和子表。这时前面建的实体就会出现在候选列表里,选中后向导会自动读取主表主键pk_head和子表外键pk_head的关联关系。
在字段配置页面里,左侧是可用字段树,中间控制字段属性,右侧调整显示顺序。这一步要做的不是把字段全拖进去,而是按业务需要勾选:
- 主表:单据号、单据日期、组织、集团等常规头字段全部显示,pk_head和ts这些系统字段隐藏。
- 子表:物料、数量、价格、金额显示,pk_detail、pk_head隐藏。
- 必填项设置:单据号、日期、组织必填;子表物料、数量必填。
字段排列的时候,把主表字段放上面,子表字段放下面,前端展示时就是主附表布局。保存模板后会生成一个模板编码,这个编码记下来,模板分配时要用。
3.3 模板分配不能跳过
初始化完模板并不代表节点打开就有界面,还差一步“模板分配”。UAP的模板机制里,同一张模板可以在不同模块、不同角色下复用,所以必须显式地把模板分配给目标功能和角色,发布后在界面上才能渲染出来。这一步不做,最典型的症状就是:节点挂上去了,点开却是空白页,或者提示找不到模板。
模板分配入口在UAP的“模板分配”功能里,选择模块和功能节点,再选择刚保存的模板编码进行绑定。分配完成后,建议提前预览一下模板效果,确认主表和子表字段都正常显示,再进入NC65的发布环节。
4. NC65里做挂接、授权、绑单据类型:发布后的“最后一公里”
4.1 功能节点挂到菜单并授权
用管理员账号登录NC65客户端,在“系统管理-功能节点权限配置”里找到刚才注册的功能节点,把它分配给你的测试角色,然后在“客户化-权限管理-角色管理”里给该角色授权这个节点。菜单树上不显示节点时,多半是这里没分配。
权限这块要分两个层面看:一个层面是功能权限,决定角色能不能看到这个节点和按钮;另一个层面是数据权限,决定用户能操作哪些组织、哪些部门的数据。主子表单据尤其是涉及金额的单据,数据权限不配好,经常出现“单据在A组织保存了,B组织的人看不到”的诡异情况。
我建议在小范围测试时,先用超级管理员把功能权限和数据权限都放开,跑通全流程后再按实际业务收敛。否则开发和权限配置交叉排查,很难定位问题。
4.2 在单据类型管理里绑定模板
很多人在这一步翻车。如果只在UAP里做了功能注册和模板初始化,直接打开单据节点,你会发现单据号不生成、保存后退不回列表、部分业务动作没反应。原因是NC65里一张可使用的主子表单据,必须先在“客户化-基础档案-单据类型管理”中建立单据类型,并把模板编码绑定上去。
操作流程是:新建单据类型,填写类型编码和类型名称,选择对应的功能节点,在模板设置里关联第3步生成的模板编码。然后配置单据号规则,比如按日期+流水号生成,或者按组织+流水号生成。单据类型配置完成后,增删改查、编号生成、模板加载这些基础能力才真正生效。
4.3 按钮和标准Action不需要重复开发
向导生成的模板,默认已经把新增、保存、修改、删除、提交、审核这些按钮和后台标准Action绑定了。标准Action内部会处理主表插入、子表批量插入、主子表级联删除这些逻辑,日常开发根本不需要手写SQL。你只需要在模板展示层控制哪些按钮显示、哪些按钮隐藏即可。
只有在需要额外业务逻辑时,比如保存前检查库存、保存后回调第三方接口,才需要继承对应的Action类并覆盖相关方法。UAP生成的Java工程会保留Action扩展点,在代码管理平台里找到对应模块的源码,找到YearEndAction之类的类按业务改即可。
注意:模板一旦发布,UAP里默认会把它锁定为发布状态,继续用开发工具直接修改会提示模板被锁定。要调整模板布局,得在模板管理里先“回收”或走模板升级流程,不要直接去数据库改模板表,那样做容易把主子表字段关联改坏,导致保存时报错。
5. 高频问题排查:从“保存报null”到子表空白
5.1 保存报“null”、保存失败:null
这类报错是NC65开发里被问得最多的问题,后台日志里通常能看到一行NullPointerException。出现这种情况时,我要么查主子关联关系,要么查模板列配置,步骤基本是固定的:
第一步,去中间件日志目录下找最新的异常日志,定位到具体是哪个类哪一行报空。第二步,如果日志指向VO或元数据相关的对象,回到UAP元数据里检查主表和子表是否建立了pk_head关联。第三步,如果关联有的是好的,检查向导生成的模板里,pk_head字段在子表列中是不是被误删了或者被设置了隐藏但未赋值。模板上主表主键、子表外键这两个字段可以隐藏,但不能不展示给保存逻辑。
我在处理过的一个项目里遇到过这种情况:子表的pk_head字段在模板中被误设置成“只读且无值”,保存时子表数据带过来的外键为空,主表插进去了,子表全部失败,最后报的就是类似“保存失败:null”的错误。把pk_head的值来源切换成“由主表主键带入”后,问题立即解决。
5.2 子表数据不显示
节点能打开、主表能录入,但填完主表数据子表区域一片空白。我排查这个问题的习惯是先看模板分配里的字段列表,确认子表字段已经分配出来,并且没有被标记为隐藏。其次看元数据中的子表实体,是否被正确标记为“明细表”或“子表”类型,而不是普通实体。
再有一种情况是前端把子表区域渲染出来了,但子表数据源没绑定。这时去检查模板中“主附表关系”配置,UAP模板编辑里有一个主子表关联区域,需要明确指向子表实体,并对应pk_head字段。
5.3 单据编号不自动生成
这种情况绝大多数出在单据类型管理。要么是没有新建单据类型,要么是新建了类型但没有配置单据号规则。NC65的单据编号由号规则引擎在保存时生成,如果号规则为空,系统不知道按什么规则给单号,所以表现为单号空白或者保存报编码规则相关错误。
正确的配置是在单据类型里设置号规则,并确认规则的流水号前缀、日期格式和步长符合要求。不要想着在页面事件里手动写代码生成单号,标准机制已经做得很好,自己造轮子反而容易和保存流程冲突。
5.4 模板被发布锁定后改不动
UAP向导生成的模板在发布前可以随意编辑,发布后模板状态变成“发布”,再直接编辑会被拦截或提示需要回收。大多数情况下,我建议在开发阶段不要急着点发布,先用未发布状态跑测,确认稳定后再统一发布。真被锁了,通过系统管理的“模板升级调整”或“模板回收”功能把它恢复到开发状态,调整完再重新发布一次即可。
5.5 元数据加字段后,模板没有出现新列
改元数据加了字段并重刷脚本,回到模板里发现字段列表还是旧的。模板不会自动同步元数据的最新字段,这是UAP的机制设计。遇到这种情况,要么手工在模板字段配置里把新增字段加进去,要么重新初始化模板。重新初始化之前要考虑清楚,因为新建模板会覆盖掉手动调整过的布局,所以“先定元数据、再审模板”是更安全的节奏。
下面用一张表汇总这几个高频问题的排查方向:
| 现象 | 根因方向 | 处理办法 |
|---|---|---|
| 保存报null | 主子关联未建立或pk_head未赋值 | 建关系、修正模板字段来源 |
| 子表空白 | 字段未分配或实体类型错误 | 检查模板列、检查元数据实体类型 |
| 单号不生成 | 单据类型或号规则缺失 | 在单据类型管理里绑定并配置 |
| 模板改不动 | 模板已发布锁定 | 回收或升级调整后再改 |
| 新字段不出现 | 模板未同步 | 手工加列或重新初始化模板 |
6. 几个让我少加班的操作习惯
6.1 先列字段清单,再进UAP建模
我后来养成的习惯是:任何主子表单据开发,先找业务把字段清单整理成一张表,包括字段名、显示名、类型、长度、是否必填、是否显示、是否参与计算。这张表确认完,再进UAP做元数据建模。字段问题在纸上改成本最低,在元数据里反复改,不仅脚本要重刷,模板也要重配,时间全耗在重复劳动上。
6.2 每走完一个环节,立刻验证
我的验证节奏是:功能注册完,登录管理端看节点是否存在;元数据建模完,登录数据库查表结构;模板初始化完,预览模板看主子表布局是否正常;模板分配完,用测试账号实际打开单据,新增一条数据并保存,再修改、再删除。每步验证的耗时不会超过十分钟,但能保证问题在最早期暴露,而不是攒到最后集中爆发。
6.3 报错先看日志,别急着重建模板
遇到问题先打开中间件日志目录下的日志文件,找到异常堆栈,再去分析是元数据问题、模板问题还是权限问题。我见过太多人一报错就重刷模板、重跑脚本,结果问题没解决,反倒把原有正确配置覆盖了。日志信息虽然看起来乱,但真正定位后大多是大白话式的错误,比如“XX字段为空”“找不到模板编码XX”,按图索骥就行。
这条主子表单据的开发链路走通之后,你会发现后续再做类似的单子,基本就是重复“建模-初始化-分配-授权”这几步,真正需要思考的反而是业务规则放到哪个Action里实现。先把基础链路稳定下来,剩下的就是往里面填业务逻辑了。
本文还有配套的精品资源,点击获取