
这个系列的第五篇来了。前几篇一直在说UAP平台上那些反直觉的入门操作今天要拆的是“树卡档案”。这名字乍一听挺绕拆开看其实就是业务系统里最常见的那类页面左边一棵树右边一张卡。你点树上的节点右边卡片就跟着刷新显示这个节点对应的档案信息。干过企业信息化实施的朋友应该秒懂物料分类、组织架构、科目表、客商档案全是这个套路。我这次是在某个能源行业的档案管理项目里被这功能卡了整整两天所以这篇把从零到落地完整捋一遍给同样被UAP树卡折腾的人省点时间。1. 树卡档案到底是个什么需求先别急着动手写代码档案类需求在ERP、MES、资产管理系统里非常普遍形态通常是“集团-公司-部门-人员”或者“大类-中类-小类-具体物品”。用户的要求往往就两句话把层级结构做成树点哪个节点就看哪个节点的完整信息同时还能直接在这棵树上新增、修改、停用节点。听起来简单但UAP平台对这一类页面有它自己的玩法不走通“树查询SQL 卡片模板 脚本联动”这条链路做出来的页面就会出现点树没反应、保存后树不刷新、删了子节点父节点还算错数之类一堆怪问题。1.1 业务侧到底想要什么我接触的树卡档案需求基本落在三个典型场景里档案分组管理左侧是档案大类树右侧维护某个分组下的具体档案卡片比如“合同档案-采购类合同-供应商合同模板”。基础资料树物料、客户、供应商、科目这类主数据靠树做导航卡片里做字段维护。组织与人员树上级下级关系天然是树人员挂在组织节点下人员信息是卡片。这三个场景的共同点是数据之间不是一张平铺的大表而是有父子关系而且这个关系会直接影响维护逻辑比如删除父节点前必须清空或转移子节点名称改了所有引用处要跟着变。1.2 为什么是“树 卡”而不是“树 列表”先想清楚这个问题后面做设计才不会走偏。列表的特点是一次能看到多行数据适合批量处理和筛选卡片的优点是一条记录的完整信息集中展示字段多、有主子表关系时不至于挤在一行里看不清。树卡档案的核心场景是“定位 - 聚焦 - 编辑”。用户先通过树把目标范围缩到很小再用卡片集中查看和修改这条档案的细节。所以UAP上的树卡页面本质上是在做“导航 详情编辑”的混合体如果你把它做成“树 列表”用户反而会觉得难用因为列表塞不下那么多字段筛选条件也难以表达层级归属。1.3 UAP里实现树卡的常见路径UAP平台给开发者的选择其实不少我归纳下来有三条路用平台原生“树卡片”模板配置树的查询SQL再配置卡片模板字段映射关系。用普通列表页 树控件拼接列表作为明细载体树作为外侧导航。嵌入式自定义页面用前端组件完全自绘。三条路要是让我评价原生树卡片模板是第一选择。原因很实际它把树和卡片的联动机制、数据回显、增删改保存这些基础行为都封装好了你只需要把SQL写对、把字段配置对。自定义页面的自由度虽然高但UAP平台二次开发最怕的就是脱离平台约定一旦手工处理保存、刷新、事务这些细节维护成本立刻失控。2. 表结构和元数据设计只有先想清楚“树存什么、卡存什么”后面才少返工我这次最开始的失误就是没把表结构当成设计环节直接拿现成的分组表就开做了结果树查询可以卡片保存时缺主键新增子节点时找不到父节点改了三版才稳定。树卡档案的表结构设计核心就两件事树表怎么存层级关系卡片表怎么挂到树节点上。2.1 树表的最小字段集树表要撑起整棵树的展示和操作最少要有这些字段字段说明为什么不能省主键节点的唯一内码UAP里通常叫pk_xxx树刷新、卡片加载、保存回写全靠它父主键指向父节点的主键根节点为空树的父子关系就是靠它构建的编码节点的业务编码比如档案分类号很多企业的编码规则本身就有层级含义名称节点显示名树节点标签用树查询SQL里要返回它作为显示字段节点类型区分“分组节点”和“数据节点”决定点击节点时是清空卡片还是加载数据状态启用、停用、删除标记树过滤和历史数据保留都靠它是否叶子标志该节点下是否能继续挂子节点界面上控制“新增子节点”按钮是否可用这里有个我对新手特别想强调的点编码不是可选项。有些开发觉得树显示用的是名称编码字段就是冗余但实际编码承担着校验重复、自动生成、外部系统对接的职责。特别是树卡档案如果后续要跟审批流、导入导出挂钩没有编码会非常被动。我当时编码用的规则是“父编码 3位流水”比如父节点编码是A子节点就是A001再下一个就是A001001。这种编码规则在树卡档案里很常见也方便前端做校验。2.2 卡片数据挂树的方式卡片数据有两种挂法我分别说下优缺点。第一种是卡片表直接带树节点主键也就是一张明细表每个字段业务信息都放在同一行数据里通过一个“档案主键”字段关联到树节点。这种适合档案就是一条记录的情况比如一个档案分类对应一份档案说明、一组属性值。第二种是卡片表作为主表下面还有子表比如主表存档案基本信息子表存档案关联的文件清单、历史变更记录。这种结构在UAP上就是典型的主子表卡片。我就遇上过这种需求客户要让每份档案下面关联上传多份扫描件主表卡片里挂一个子表控件子表每条记录对应一个附件清单。不管哪种挂法有一个原则是通用的卡片表与树表之间必须通过业务主键关联不要用平台自动生成的物理行主键去做外键。物理主键换数据库环境会变业务主键才能保证树节点和卡片数据在查询、导入导出、跨环境复制时不乱。2.3 层级关系父主键还是层级码树形数据结构设计向来有争议有人用父主键有人用层级码。我的建议是两者都留。父主键负责树的父子关系构建查询时通过递归或CONNECT BY把整棵树拉出来。层级码负责快速过滤和排序比如查询某个编码前缀下的所有子节点用层级码前缀like匹配比递归快得多。表格里同时放这两个字段不会增加多少维护成本但能在查询性能和数据完整性上省很多事。层级码生成规则要统一。我当时用的是每层3位数字不管实际编码是否达到999统一补零这样排序和匹配都不会错。如果层级很深层级码会很长但对树卡档案这种量级几千个节点的数据根本不会有性能压力完全不用焦虑。3. 树卡页面的核心配置从节点查询到卡片加载的落地过程表结构定了之后真正费时间的环节来了在UAP平台上把树卡页面搭出来。UAP的树卡模板主要分三块树的查询配置、卡片的模板配置、树和卡之间的联动脚本。这三块顺序不能乱乱了你根本不知道是哪里出了问题。3.1 树查询SQL的两种写法和选择树的查询SQL是树卡页面的命脉。我在UAP上见到的树查询SQL基本分两派递归派和全量内存组装派。递归派就是用数据库自带的递归语法比如Oracle的CONNECT BY或者MySQL 8的WITH RECURSIVE直接把整棵树查出来字段里带上层级码和节点类型。优点是数据库层一次搞定数据结构UAP只需要把查询结果映射成树即可性能对大数量级友好。全量内存组装派是先用一条简单SQL查出所有节点再由前端脚本根据父主键字段组装成树结构。优点是对数据库语法要求低、写起来简单直观缺点是节点数量上千后刷新会明显变慢而且所有节点的状态字段都要查出来数据冗余。我当时项目用的数据库是Oracle所以直接用了CONNECT BY。UAP树模板里设置根节点条件和子节点关联条件树就出来了。下面这个是我实际改过的简化版SQL注意关键点SELECT t.pk_arch, t.parentpk, t.archcode, t.archname, t.nodetype, t.state, t.leafflag, t.innercode FROM arch_tree t WHERE t.dr 0 START WITH t.parentpk IS NULL CONNECT BY PRIOR t.pk_arch t.parentpk ORDER BY t.innercode这段SQL里我特意把dr 0放在WHERE里逻辑删除的数据从一开始就不过滤出来。ORDER BY t.innercode保证树兄弟节点按编码排而不是按录入顺序排。这些都是细节但影响观感。3.2 卡片模板的字段布局与加载机制卡片模板在UAP里负责数据的展示和编辑。字段和元数据是绑定的模板上放哪些字段展示什么控件都由模板化配置来完成。树卡档案和普通卡片有个区别就在于加载时机。普通卡片通过按钮点击触发树卡则通过树选择事件触发。因此卡片模板里必须有一个独立的“数据加载”配置它告诉平台当前选中哪个树节点就用这个节点的主键作为过滤条件去加载卡片数据。这里有个必踩的坑我提前说一下分组节点和数据节点一定要区分开。分组节点下面没有具体档案数据点击分组节点时卡片应该被清空而不是试图加载否则会报“数据不存在”或者直接把上一份数据留在那里用户一不留神就会把档案存进错的分组里。我当时的解决办法是配置一个节点类型判断点击树节点时先判断type值是分组类型就清空卡片并禁用保存按钮是数据节点才触发加载。3.3 树和卡的联动脚本不要硬编码任何节点主键联动脚本是UAP树卡开发最容易被写坏的部分。新手喜欢在脚本里直接拼一串主键去查数据一旦换环境就查不到。正确做法是从当前选中树节点对象上动态获取主键和节点类型再传给卡片模板。我写脚本的风格是尽量少平台能配置出来的联动就不要写在脚本里。比如平台自带“选中节点加载卡片数据”的机制你要做的只是在事件里判断节点类型需要过滤条件时把节点主键传给查询对象。硬编码节点主键或者写大段SQL在按钮脚本里是我见过最容易被后来维护的人骂的做法。我这边一个典型的脚本骨架长这样用伪代码表示细节按你们平台的API调整function onTreeSelect(node) { var nodeType node.getProp(nodetype); if (nodeType GROUP) { card.clearData(); card.setReadOnly(true); return; } card.setReadOnly(false); var pk node.getProp(pk_arch); card.setFilter(pk_arch pk ); card.loadData(); }这个逻辑对UAP各个版本的写法大同小异关键点在于清空、置灰、加载、回显这四步要按顺序执行缺一个就会看到“数据还好端端地在卡片上”这类问题。另外脚本里不要用绝对路径访问节点所有属性都通过节点对象的取值方法来拿。4. 保存、删除、回显把操作流程理顺别让使用者在页面上卡壳树卡页面光能看没有用最终用户要在上面做日常维护操作。UAP的树卡模板通常会自带一套增删改保存的按钮但真正的需求往往比默认行为多一层保存后刷新树、新增时自动生成编码、删除时连带处理子节点。这些都要在按钮脚本上补齐。4.1 新增子节点的核心校验链新增子节点是最容易埋雷的操作。用户选中一个分组节点点“新增”系统应该自动把当前选中节点的主键作为父主键同步到新卡片上。这里必须做校验用户没选节点时新增按钮应该置灰或直接拦截否则会出现孤儿节点整个树结构就乱了。我在脚本里加的校验是三层当前树是否选中节点没选中直接提示“请先选择所属分组”。选中的节点是否允许新增子节点如果节点类型不是分组类型或者叶子标志为否提示“该节点不能增加下级”。用户填写的编码是否与同级别的兄弟节点重复这层我用的是一个查询计数接口编码重复直接拦截保存。这三层做完新增节点基本不会有脏数据产生。另外还要处理默认值比如新增时新增时间带入当前日期、新增人带入当前操作员这些走UAP的字段默认值配置就行不用写脚本。4.2 保存后树节点名称必须回显树卡页面有个非常影响使用感受的细节用户修改了节点的名称点保存卡片数据确实改了但左边树还是显示旧名称。原因很简单树的显示字段是从树查询SQL里查出来的保存卡片只改了卡片表没有通知树刷新。解决方法是保存成功后调用树的刷新方法并选中刚才保存的节点。这个“保存后回选”极其重要。我当时第一版没做回选保存后树刷新选中状态丢失页面看起来像卡了一下用户体验非常差。后来加了保存后重查当前节点主键并重新选中节点才顺了。以下是刷新和回选逻辑的关键部分示意function afterSave() { var pk card.getValue(pk_arch); tree.refresh(false); tree.selectByPk(pk); }就这么两行解决问题。注意refresh(false)的false表示不保留之前的选中状态因为后面我们手动回选不传true避免平台自己选了个无关节点。4.3 删除策略逻辑删除优先于物理删除档案系统的数据是审计敏感数据物理删除意味着历史痕迹彻底消失一旦客户要求追溯“某天谁改过这条档案”你拿不出来就是事故。我的建议是树卡页面里的删除一律做逻辑删除也就是把数据标记状态为删除查询时过滤掉而不是真正DELETE掉。UAP平台一般有“废除”或“停用”的内置动作你要确认的是这个内置动作是逻辑删除还是物理删除。如果平台默认是物理删除我建议动表结构增加一个dr删除标志字段所有页面查询都带上dr 0条件。树节点删除时还要注意级联关系有子节点的父节点不能被删除哪怕子节点已经被逻辑删了也要提示“先删除其子节点”。这里给个表格式的删除校验建议场景允许删除处理逻辑无子节点、无卡片数据允许逻辑删除该节点有子节点但子节点为停用状态允许先逻辑删除子节点再删除父节点有启用状态的子节点不允许拦截并提示先处理子节点有卡片数据但数据已归档允许节点删除卡片数据保留在历史表中删除按钮脚本里写上这几条判断树的数据完整性基本就有保障了。5. 树卡档案最常见的五个坑和排查思路这块可以说是我最想写的地方。树卡功能本身不复杂但UAP平台上磨人的点全在细节里。下面五个坑我都实际踩过按出现频率排序直接给出表现和排查思路。5.1 树刷新后选中节点丢失卡片区域一片空白表现保存节点后树确实刷新了但之前选中的节点不再高亮卡片也跟着清空。排查思路先看刷新方法是否支持“保存当前选中状态”的参数。如果不支持就在保存后的回调里重新根据主键选中节点。还有一种可能刷新前你对树做了过滤刷新后过滤条件丢失节点刷不回来这时要检查过滤条件是不是绑定在节点的父级分组上而不是绑定在子节点自身。5.2 子节点显示出来了但父节点被过滤掉表现通过状态字段过滤树节点时只显示启用状态的数据结果某些启用的子节点因为它的父节点是停用状态整棵树少了一段子节点也看不见了。排查思路树查询SQL里如果过滤父节点状态会导致父子链断裂。正确做法是查询时保留所有节点在展示树时对节点做状态样式标记比如停用节点显示为灰色或者打一个标记但结构上不把它摘掉。或者干脆在SQL里不参与状态过滤只在前端树控件里设置节点灰化这样树的结构完整数据又不会真的被编辑。5.3 编码自动生成在新增和修改之间来回触发表现点新增编码自动带出来了保存后接着编辑这条记录编码又被重新生成了一次把原来的覆盖了。排查思路UAP的字段默认值或取值脚本会在数据加载时再次执行。要区分操作模式只有新增模式下才生成编码编辑模式下必须保留原编码。判断方式一般看卡片的数据状态新增时数据状态是ADD编辑时是EDIT。编码生成脚本里加一道ADD状态判断就能解决。5.4 卡片里的附件或子表数据保存后丢失表现树卡页面上挂了一个子表控件录入了好几行明细点保存只有主表字段保存了子表数据全没了。排查思路这个一般不是脚本问题而是UAP卡片模板的保存范围配置不对。卡片模板上要显式声明子表的保存联动关系主表保存时会遍历关联子表一起提交。检查模板设计中主表与子表的关联属性、数据源映射关系是否正确特别是两个实体的外键字段是否配置在保存联动里。还有一个容易忽略的点新增状态下主表还没生成主键子表外键必须等主表保存拿到主键后再回填平台如果没做这个机制就需要在保存脚本里手动补一次字段同步。5.5 不同UAP版本之间树卡模板行为不一致表现同一个元数据、同一套SQL在开发环境测得好好的坐到客户生产环境版本上树不显示了或者卡片加载不出来。排查思路UAP经过多次平台升级树卡模板在不同大版本间API有变化。排查时先看平台版本差异说明重点看树查询SQL的返回字段命名是否在新版本中需要额外映射。不要上来就重写SQL先把模板重新生成一遍对比生成的XML差异往往能发现新版本里自动补了一些映射关系。如果生产环境版本比开发环境旧那就是功能没跟上优先找平台补丁而不是改代码。6. 树卡档案还能怎么扩展数据权限和审批挂接如果树卡档案只做到维护层面那也就是个常规功能。真正让这个模块在客户现场落地有价值的是后面的扩展能力。我自己做完维护部分后客户紧接着提了两个需求这里一并说下思路。6.1 数据权限谁负责的档案谁能看树卡档案天然适合做数据权限控制因为树本身就是权限的边界。比如某项目要按区域划分管理权每个区域经理只能看到自己区域下的档案节点。做法是在树查询SQL里增加权限过滤条件把当前操作员的区域与节点的区域字段做匹配。UAP平台一般有数据权限过滤器直接在节点查询SQL里绑定当前用户上下文即可模板不用改。这里要注意的是过滤条件必须加在树和卡片两处只过滤树的话用户通过其他入口打开卡片仍可能看到越权数据。卡片模板的加载查询也要带同样的权限条件两边齐了才算闭环。6.2 归档和审批树卡页面作为流程单据的起点还有一个常见扩展是把树卡档案作为一个流程的发起入口比如“档案变更申请”。用户在树上选中一条档案点“变更申请”弹出卡片表单填写变更内容然后发起审批流审批通过后自动写回档案状态。和树卡页面的对接点在于发起流程时要携带当前选中节点的主键和关键字段作为流程单据的业务关联字段。审批通过后流程处理脚本再更新档案表的状态和变更时间。整个过程最关键的是主键传递树节点当前的选中状态一定要在发起流程按钮触发时同步到流程单据模板上不要靠用户重新选择。这类需求做多了你会意识到树卡页面不只是维护工具还能承担业务入口的角色。最后再分享一点实际操作中的体会这个树卡档案功能我从拿到需求到稳定交付花了整整两天半。第一天全耗在树查询SQL上因为当时没把父主键的字段映射对齐树的层级怎么都出不来第二天搞明白了树和卡联动的触发时机才发现点击事件、加载事件、保存事件之间的顺序才是核心第三天把保存回显和逻辑删除理顺才算真正能交付。回头想想蜗牛就蜗牛在这套平台的约定太多但它也不是没有规律可循。我的建议是做树卡档案不要一上来就写脚本先把UAP原生的树卡模板生成出来跑一遍看看平台默认行为到哪一步再去补脚本否则你连坑在哪都找不到。这篇写下来希望你能少走我走过的弯路也欢迎同行在评论区聊聊你们平台上踩过的树卡坑。