U9客户化开发入门:UBF路径、BE/UI建模与装配发布避坑

发布时间:2026/10/29 21:01:22
U9客户化开发入门:UBF路径、BE/UI建模与装配发布避坑 简介用友U9客户化开发入门课程配套讲解资料面向用友合作伙伴与U9二次开发初学者帮助读者从零厘清UBF路径配置、解决方案与项目组件的目录层级划分、配件与BE表名等命名规范建立规范的客开流程认识。资源包仅含1个docx文档约817KB篇幅精炼按知识点分节编排便于随课程边看边查。已有3029人学习下载是不少用友顾问入门客开时的第一份参考。内容覆盖档案开发需求与字段设计示例包括科目、组织、编码等业务主键的设定讲解BE Code与Model、UI Code与Model的对比方法以及组件、类实体、属性关系和UIForm、Part、Page关系查询的SQL语句并给出以补丁包安装和脚本同步枚举的处理方式可直接对照实操、少走弯路。1. U9 客户化开发UBF 路径没配对后面全是返工第一次在客户现场配 U9 客户化开发环境我以为装个客户端、连上数据库就完事结果 UBF 路径没指对解决方案建出来编译能过装配发布之后界面上一个字段都没变。那天下午我对着日志翻了三个小时最后发现问题出在一个目录上。U9 客户化开发这套环境的门槛不在写代码在装。UBF Studio、U9 产品目录、装配发布用的 UILib、Runtime 下的 environment.xml四个位置任何一个对不上出来的就是「看着成功、实际没生效」的包。做 U9 客开的开发、实施、二次交付的团队基本都要过这一关新手照着视频能敲完但视频里的机器名、实例名、库名跟你现场不一样照抄必翻车。这份 U9 客户化开发入门资料我按装一遍的顺序重新拆了一遍先定路径和目录再落 BE再落 UI最后给一套能自己验的清单。2. 环境与目录先行UBF 路径配置和解决方案分层怎么定装 U9 客开环境最忌讳的是打开 VS 就新建项目。路径、目录、命名这三件事没定下来后面每加一个组件都要返工一次。这一章先把「装在哪、怎么分、叫什么」钉死再谈代码。2.1 UBF 路径配置三个必须指对的落点U9 的 UBF 设计器不是独立安装的它跟着 U9 产品目录走目录名带版本分支。我这套环境的入口路径长这样# U9 安装根目录下UBF 设计器与装配发布工具的位置版本分支以实际安装为准 D:\U9\UBFV28\U9.VOB.Product.UBF\UBFStudioBuildupDesigner.exe D:\U9\UBFV28\U9.VOB.Product.UBF\UBFStudio.exe第一个是装配发布设计器第二个是建模用的 UBF Studio。这两个 exe 的上一级就是产品目录UBF 启动时会按这个目录去找元数据、找运行时程序集、找 UI 库。路径配错的典型表现是 Studio 能打开但左侧的发布对象浏览器是空的或者发布时提示找不到 UILib。我一般会确认三个落点一是产品路径指向UBFV28这一层不是它下面的子目录二是开发工程路径也就是你放.sln的目录建议单独放一个盘不要塞进 U9 安装目录里升级时容易被覆盖三是发布输出路径装配发布生成的东西最终要落到 U9 运行目录下的 UILib这个路径在第 5 章还会单独说。提示产品路径里带版本分支号换一台机器装的可能就是另一个分支。复制别人的配置文件之前先把这一行改成本机的实际路径。三个落点确定之后再打开 UBF Studio让它自己初始化一遍元数据缓存。初始化过程会读数据库所以数据库连接必须先通这就引出下面要说的 environment.xml。2.2 解决方案、项目、组件的分层一个模块一套U9 客开的目录层次是「解决方案 → 项目 → 组件」但真正决定后面好不好维护的是「一个模块建几个项目、生成几个 DLL」。规则记住一句话同一个模块同类的东西只建一个项目。层次放什么生成物解决方案同一模块的全部项目.slnBE 项目该模块所有实体、所有枚举一个 BE 的 DLLBP/SV 项目该模块所有业务逻辑、服务一个 BP/SV 的 DLLUI 项目档案/单据一个档案或单据 UI及其查找参照 UI一个 UI 的 DLLUI 项目列表该模块所有列表 UI一个 UI 的 DLLUI 项目参照该模块所有参照 UI一个 UI 的 DLL这样分的好处很直接装配发布的时候DLL 数量可控版本比对的时候一眼能看出是哪个模块动的。我见过把每个实体都单独建项目的做法一个模块下来十几个 DLL发布清单长得像电话簿出问题根本定位不到。还有一条容易忽略的同一模块的所有枚举放在同一个 BE 组件内。枚举是跟着 BE 走的拆开了同步脚本就会漏第 3 章会讲漏了之后下拉框是什么表现。2.3 命名规范配件名与 BE 表名的拆法命名规范不是形式主义它直接决定了你后期用 SQL 查元数据时能不能批量过滤。U9 客开常用的配件名前缀是四段式// 配件名命名空间四段厂商.产品.地区.公司.模块 // UFIDA.U9.Cust.XM.HXPP.EX // UFIDA.U9.Cust 固定前缀客开标识 // XM 地区代号占位按实际项目替换 // HXPP 公司代号占位按实际项目替换 // EX 模块代号本文示例用 EX namespace UFIDA.U9.Cust.XM.HXPP.EX { // 实体类名不带前缀靠命名空间区分 }表名则是下划线式把点换成下划线再拼实体名-- BE 表名规则Cust_地区_公司_模块_实体 -- 例科目档案在 EX 模块下的物理表 Cust_XM_HXPP_EX_Subject逻辑说明命名空间用点分层是为了让 UBF 按命名空间扫描组件表名用下划线是因为 U9 的元数据表里大量用下划线拼接SQL 里写起来不用加引号。参数上唯一要注意的是长度模块代号别超过 4 个字符否则拼出来的表名在部分数据库排序规则下会截断。注意地区和公司代号在本文里都是占位符。你自己的项目里换成实际代号之后命名空间、表名、发布组件名三处必须同步改改一半是常见翻车点。2.4 环境文件environment.xml 的连接串怎么写U9 运行目录下Runtime\environment.xml决定连哪个库。这个文件写错UBF 能启动但读不到元数据表现是「发布对象浏览器一直转圈」。!-- U9 运行目录 Runtime\environment.xml -- system connectionString User Idsa;Password你的密码;Data Source服务器名\实例名;Initial CatalogHXPP0145; packet size4096;Max Pool size100;Connection Timeout900; persist security infoTrue;MultipleActiveResultSetstrue; /connectionString /system逐项说Data Source带实例名的时候用反斜杠分隔单机默认实例就只写服务器名Initial Catalog是账套库名客开环境建议单独一个库别跟测试账套混用packet size4096是网络包大小元数据查询返回的字段多这个值别调太小Max Pool size100是连接池上限多人共用一个开发库时调大一点Connection Timeout900给的是 15 分钟装配发布时有大对象编译默认的 15 秒基本不够MultipleActiveResultSetstrue在 U9 的某些查询里是必须的缺了会报「已有打开的与此命令相关联的 DataReader」。连接串改完重启一次 UBF Studio让缓存重新加载。判断有没有生效不用看日志直接在 Studio 里展开任意一个已有实体的属性能刷出来就是通了。3. BE 层建模科目档案的实体、枚举与关系查询BE 层是 U9 客开的地基。这一层建错UI 层怎么调都是歪的。这一章拿一个考核模块下的「科目」档案当例子把字段设计、枚举定义、元数据查询、默认值四件事走一遍。3.1 科目档案的字段设计先把需求表翻译成实体需求表长这样别急着打开 Studio先在纸上把它翻译成「字段名 / 显示名 / 类型 / 约束」四列字段显示名类型约束Org组织组织机构业务主键不可空Code编码字符串业务主键不可空长 50Name科目名称字符串不可空长 200SubjectType科目类型枚举见 3.2Effective状态状态枚举启用/停用Memo备注字符串长 500DescFlexField扩展字段实体扩展字段集合描述性弹性域翻译的时候有两个判断要做。第一Org和Code是业务主键意味着这两个字段合起来唯一建实体时要把唯一性约束加上否则后面参照查出来会重。第二DescFlexField不是一个字段是一个扩展字段集合类型建实体时选「实体扩展字段集合」不要自己建几个 varchar 糊上去否则 UI 层的弹性域控件挂不上。字符串长度也别随手填。Code长 50、Name长 200、Memo长 500这三个值是需求里定死的建实体时按这个填后期改字段长度要走数据库变更比建的时候多花三倍时间。3.2 枚举定义SubjectType 的取值与同步脚本科目类型的枚举值是辅料、主要材料、产值、费用、对账差异、营销可比利润、调节项、其他收入、税后利润、考核税前利润。十个值建在 BE 组件里跟着 BE 一起编译。枚举建完数据库里要同步一份UI 的下拉框读的是数据库里的扩展枚举表。以补丁包方式更新或安装的时候BE 的 bulk 里会处理这一步不用手动跑如果是开发环境手工加枚举就得自己执行同步脚本-- 开发环境手工同步枚举先清后插按 ClassType3 过滤枚举类 delete from ubf_sys_extenumtype; delete from ubf_sys_extenumtype_trl; delete from ubf_sys_extenumvalue; delete from ubf_sys_extenumvalue_trl; GO -- 枚举类型从元数据类表里捞 ClassType3 的类 insert into ubf_sys_extenumtype select local_id as id, 20060612, u9dev, 20060612, u9dev, 0, fullname as code, isextend, id as uid, 0 as IsRefView from ubf_md_class where classtype 3; -- 枚举类型多语言名称从资源值表取 insert into ubf_sys_extenumtype_trl select a.local_id as id, zh-CN, b.[displayname] as [name], b.[Description] from ubf_md_class a left join ubf_RES_resourcevalue b on cast(a.[iD] as nvarchar(255)) b.resourcename where classtype 3; -- 枚举值属性表里挂在该枚举类下的字段就是枚举项 insert into ubf_sys_extenumvalue select a.local_id as id, 20060612, u9dev, 20060612, u9dev, 0, a.[Local_Class_ID] as ExtEnumType, a.[name] as code, a.defaultvalue as evalue, issystem, md_class_id as ExtEnumTypeUID from ubf_md_attribute a inner join ubf_md_class b on a.md_class_id b.id where classtype 3; GO逻辑说明这四张表是一组extenumtype存枚举类型extenumvalue存枚举项带_trl后缀的存多语言名称。清空顺序不能反先删子表再删主表否则外键会拦。20060612和u9dev是创建人和创建时间的占位值实际脚本里换成当前账号和日期就行这个字段只用于审计不影响功能。参数上唯一要留意的是classtype 3这个值是「枚举类」的固定标识改成别的会把这套同步脚本的范围扩到实体类上插进去的数据全是脏的。注意这组脚本是全量清空再插入只适合开发库。生产库上跑之前先备份ubf_sys_extenum*四张表我吃过一次亏枚举项全没了下拉框全空回滚花了半小时。3.3 BE Code 与 Model 对比别靠眼睛看实体建完Studio 里看到的 Model 和生成的 Code 是不是一致靠肉眼比字段名很容易漏。我习惯用元数据查询直接把组件、类、属性三层拉出来对-- 按显示名查组件→类→属性的完整链路 select a.Name as ComponentName, at.DisplayName as ComponentName_CN, a.AssemblyName, a.Kind, b.Name as ClassName, bt.DisplayName as ClassName_CN, b.FullName, b.Discriminator, c.Name as AttributeName, ct.DisplayName as AttributeName_CN from UBF_MD_Component a left outer join UBF_MD_Component_Trl at on a.Local_ID at.Local_ID and at.SysMLFlag zh-cn left outer join UBF_MD_Class b on a.ID b.MD_Component_ID left outer join UBF_MD_Class_Trl bt on b.Local_ID bt.Local_ID and bt.SysMLFlag zh-cn left outer join UBF_MD_Attribute c on b.ID c.MD_Class_ID left outer join UBF_MD_Attribute_Trl ct on c.Local_ID ct.Local_ID and ct.SysMLFlag zh-cn where (1 1) and bt.DisplayName 科目;逻辑说明主表是组件表UBF_MD_Component往下一层是类表UBF_MD_Class再往下一层是属性表UBF_MD_Attribute每个主表都配一张_Trl多语言表用Local_ID关联。SysMLFlag zh-cn这个条件不能漏漏了会把英文、繁体都拉出来一个属性出三行。最后那个bt.DisplayName 科目是过滤条件按类的显示名查。查别的实体就把「科目」换掉。a.AssemblyName这一列能看出这个组件编到哪个 DLL 里如果和你预期的 DLL 名对不上说明组件放错解决方案了回头查第 2 章的分层规则。3.4 默认值OnSetDefaultValue 里写什么Org这个字段业务上要求新建时默认当前登录组织但允许改。这种「有默认值、可覆盖」的字段代码写在 BE 的OnSetDefaultValue里不要写在 UI 层。// BE 实体扩展类中重写 OnSetDefaultValue protected override void OnSetDefaultValue() { base.OnSetDefaultValue(); // 组织为空时取当前登录组织 if (this.Org null) { this.Org Context.LoginOrg; } }逻辑说明OnSetDefaultValue在实体实例化、字段还没被赋值的时候触发写在这里的默认值对「UI 新建」「代码 new 一个实体」「接口导入」三种入口都生效。写在 UI 的AfterInitModel里只对界面新建生效走接口进来的数据Org还是空的。参数说明Context.LoginOrg是当前上下文的登录组织对象不是 ID。如果你拿到的是Context.OrgID那是个 long赋给Org之前要先转成组织实体。这两个 Context 属性经常被混用赋错类型的编译能过运行时报空引用。4. UI 层落地UIForm/Part/Page 与事件挂载点UI 层的坑比 BE 层多因为 U9 的界面模型有三层结构还挂着一套十几个执行顺序的事件。这一章把关系查询、事件顺序、常用代码、导航过滤四块讲清楚。4.1 UIForm、Part、Page 三者关系与查询 SQL一个 UI 在数据库里至少涉及三张表ubf_md_uiform存表单ubf_assemble_part存部件ubf_assemble_page存页面。这三者的关系不是一对一一个表单可以挂多个 Part一个 Part 对应一个 Page查的时候要用两层 left join 串起来-- 按 UI 显示名查表单、部件、页面的完整链路 select at.DisplayName as UIFormDisplayName, a.name as UIFormName, c.name as PageName, a.uid as [UID(FormID)], b.PageCode, a.uri as [PartID(UIFormURI)], c.uri as PageURI, a.AssemblyName, a.ClassName, a.Width, a.Height, dt.name as ApplicationName from ubf_md_uiform a left outer join ubf_assemble_part b on a.uid b.component left outer join ubf_assemble_page c on b.page c.id left outer join base_application_trl dt on c.application dt.id and dt.sysmlflag zh-cn left outer join ubf_md_uiform_trl at on a.id at.id and at.sysmlflag zh-cn where (1 1) and at.DisplayName 科目;逻辑说明ubf_md_uiform.uid关联ubf_assemble_part.componentubf_assemble_part.page关联ubf_assemble_page.id。这里的关键是a.uid和a.id不是一回事uid是给部件关联用的id是给多语言表用的写反了查出来是空。ApplicationName这一列能看出来这个 UI 挂在哪个应用下发布菜单的时候要用。查出来的PartID(UIFormURI)这一串就是代码里NavigatePage要传的参数第 4.4 节会用到。4.2 UI 事件顺序十几个挂载点哪个该放什么U9 的 Webpart 事件有一套固定顺序挂错位置的表现是「代码明明写了就是不执行」或者「第一次执行PostBack 之后不执行」。我按执行先后列一遍重点标出什么时候触发顺序事件触发时机1AfterEventBind事件绑定之后2AfterCreateChildControls控件创建之后3OnLoadData_Extend刷新时执行一般 PostBack 不执行4AfterOnLoad加载完成5OnDataCollect_Extend刷新时不执行PostBack 执行数据收集6BeforeUIModelBindingUIModel 的值绑定到控件之前7AfterUIModelBindingUIModel 的值绑定到控件之后用起来就三条经验控件级的初始化扩展字段、个性化、删除确认框绑定放AfterCreateChildControls要按当前数据算按钮状态的放AfterUIModelBinding这时候 Model 里的值已经能读到参照的过滤参数要改的放BeforeUIModelBinding绑定之后再改就晚了。OnLoadData_Extend和OnDataCollect_Extend这一对最容易混。前者是往界面灌数据的时候走后者是把界面数据收回来的时候走。取当前行 ID 做过滤两个地方都能写但 PostBack 场景下只有OnDataCollect_Extend会执行我一般优先写在AfterUIModelBinding里省得判断。4.3 常用代码默认值、扩展字段、个性化、删除确认UI 层的默认值写在AfterInitModel// UI 的 Model 层 AfterInitModel新建时给组织字段赋默认值 this.Subject.FieldOrg.DefaultValue PDContext.Current.OrgRef.ID; this.Subject.FieldOrg_Code.DefaultValue PDContext.Current.OrgRef.CodeColumn; this.Subject.FieldOrg_Name.DefaultValue PDContext.Current.OrgRef.NameColumn;这三行是一组Org是个参照字段赋值的时候要同时给 ID、Code、Name 三个虚拟字段赋值只给 ID 的话界面上显示的是空。PDContext.Current.OrgRef是当前上下文的组织参照对象ID是主键CodeColumn和NameColumn是显示用的编码和名称。扩展字段的处理在AfterCreateChildControls里挂// 描述性弹性域控件绑定到实体的扩展字段集合 FlexFieldHelper.SetDescFlexField(this.FlexFieldPicker0, this.Model.Subject);FlexFieldPicker0是界面上的弹性域控件名Model.Subject是弹性域所在的 View。换实体的时候把Subject换成你的 View 名就行。这一步不做界面上那个扩展字段区域是灰的。个性化开关和删除确认框也在同一个事件里加// 开启个性化 UFIDA.U9.UI.PDHelper.PersonalizationHelper.SetPersonalizationEnable(this, true); // 删除按钮绑定确认提示 UFIDA.U9.UI.PDHelper.PDFormMessage.ShowDelConfirmDialog( this.Page, UFIDA.U9.UI.PDHelper.PDResource.GetDeleteConfirmInfo(), , this.BtnDelete);SetPersonalizationEnable传true表示允许用户自己调整列宽、列顺序不传的话个性化按钮是灰的。ShowDelConfirmDialog的第三个参数是附加提示文本留空就只显示默认的「确定删除吗」。按钮绑定这一步漏掉用户点删除直接删没有二次确认这在生产环境是要出事的。弹窗和非弹窗的按钮控制写在AfterUIModelBinding里private void SetControlStauts() { // 判断当前是弹窗打开还是列表页打开 if (UFIDA.U9.UI.PDHelper.NavigateManager.IsTitleLink(this) || UFIDA.U9.UI.PDHelper.NavigateManager.IsModelPopup(this)) { this.BtnOk.Visible true; this.BtnClose.Visible true; this.BtnList.Enabled false; // 弹窗里列表按钮不可用 } else { this.BtnOk.Visible false; this.BtnClose.Visible false; this.BtnList.Enabled true; } }IsTitleLink判断是不是从标题链接点进来的IsModelPopup判断是不是模态弹窗。两个条件用或连起来是因为 U9 有两种弹窗入口。这个判断放在AfterUIModelBinding里是因为NavigateManager读的是当前页面的导航上下文太早调用拿不到值。4.4 页面导航与参照过滤卡片导航到列表、列表导航到卡片新增、双击行跳转三种导航写法不一样// 卡片页 → 列表页 this.NavigatePage(UFIDA.U9.Cust.XM.HXPP.EX.SubjectListURI, null); // 列表页 → 卡片新增写在 LIST Action 的 OnNew_Extend 里 this.CurrentPart.NavigatePage(UFIDA.U9.Cust.XM.HXPP.EX.SubjectURI, null); // 双击行跳转写在 OnGridRowDbClick_Extend 里 UFIDA.U9.UI.PDHelper.NaviteParamter p new UFIDA.U9.UI.PDHelper.NaviteParamter(); string dataid this.CurrentModel.Subject.FocusedRecord[ID].ToString(); if (!string.IsNullOrEmpty(dataid)) { p.NameValues.Add(PDPageStatus, browser); p.NameValues.Add(ID, dataid); UFIDA.U9.UI.PDHelper.NavigateManager.NavigatePage( this.CurrentPart, UFIDA.U9.Cust.XM.HXPP.EX.SubjectURI, p); }前两个的第二个参数传null表示不带参数。第三个要构造NaviteParamter对象这个类名是 U9 里的原始拼写少个 g照着写就行NameValues是个字典PDPageStatusbrowser告诉目标页以浏览态打开ID是主键。路径里的 URI 就是第 4.1 节 SQL 查出来的PartID(UIFormURI)。列表过滤当前组织的数据写在CustomFilterOpath_Extend里// 列表默认只看当前组织的数据 if (string.IsNullOrEmpty(filterOpath)) { filterOpath string.Format(Org{0}, PDContext.Current.OrgID); } else { filterOpath string.Format( and Org{0}, PDContext.Current.OrgID); }这个事件传进来的filterOpath可能已经有了别的过滤条件所以要先判空再拼直接覆盖会把原有的条件冲掉。参照上加过滤条件写在BeforeUIModelBinding里// 给参照列加自定义过滤只允许选已审核的生产订单 IUFFldReferenceColumn c1 (IUFFldReferenceColumn)this.DataGrid5.Columns[WaterMarkMO]; c1.CustomInParams BaseAction.Symbol_AddCustomFilter UFIDA::U9::Cust::HXPP::MO::WaterMarkMOBE::WaterMarkMO.DocNo in (select MO.DocNo from UFIDA::U9::MO::MO::MO as MO where MO.OrgUFIDA::U9::Cust::HXPP::MO::WaterMarkMOBE::WaterMarkMO.Org and MO.DocState in (2,3));Symbol_AddCustomFilter是个固定标记后面拼的是 OPath 表达式。表达式里的类名是全限定的格式是命名空间::实体类::属性。这段的坑在于字符串不能换行换行会拼出非法表达式我一般用加号一段一段接。5. 避坑排查装配发布前后最容易翻车的五处这一章是踩过的坑汇总每条按「现象 → 原因 → 解决」写。前四条出现在装配发布前后最后一条出现在调试阶段。5.1 装配发布UILib 路径不改发出来的是旧包现象装配发布提示成功进系统一看界面上还是老样子新加的字段没出来。原因buildup.xml里的 UILib 路径指向的是上一次的发布目录或者指向了 U9 安装目录下的默认 UILib你这次编译出来的 DLL 根本没被拷进去。解决发布前先在发布目录下确认配置文件里的 UILib 指向哪# 在装配发布目录下定位 UILib 相关配置 grep -n UILib buildup.xml # 确认目标目录里的 DLL 时间戳是不是这次编译的 ls -l /d/U9/Runtime/UILib | grep -i subject第一行找出配置项第二行核对文件时间。两个时间对不上就是路径没改。改完重新发布不要用增量发布用全量增量发布有时候会跳过没改动的组件。这条我踩过两次后来养成了发布前先看一眼时间戳的习惯。5.2 单据继承DocType 重载没写编译直接报错现象费用明细单继承单据基类编译报错「不会实现继承的抽象成员 DocType.get」。原因单据基类要求子类重载DocType属性把单据类型返回出来Studio 生成代码时不会自动补这一段。解决在 BE 代码对应单据的 Extend 里手动加重载// 单据实体扩展类中重载 DocType返回本单据的单据类型字段 public override UFIDA.U9.Base.Doc.DocType DocType { get { return this.FDDocType; } } // 同时在 OnSetDefaultValue 中给组织赋默认值 if (this.Org null) { this.Org Context.LoginOrg; }FDDocType是你自己建的「费用明细单单据类型」字段不是基类的。这段重载必须写在扩展类里不能写进生成代码重新生成会被冲掉。单据类型字段的命名也要注意别叫DocType会跟基类属性重名编译过了运行也会出问题。5.3 枚举不同步下拉框空的数据库里有值现象SubjectType 下拉框是空的打开数据库看ubf_sys_extenumvalue表里明明有数据。原因多语言表没同步。UI 下拉框读的是_trl表只往主表插了数据_trl是空的界面就显示空。解决重新跑一遍第 3.2 节的四段脚本特别注意那两条insert into ... _trl。跑完用下面这条查一下-- 检查枚举值和多语言是不是一对一对得上 select a.Code, at.NAME, a.EValue, c.UID as EnumTypeUID from UBF_Sys_ExtEnumValue a left outer join UBF_Sys_ExtEnumValue_Trl at on a.id at.id and at.SysMLFlag zh-cn left outer join UBF_Sys_ExtEnumType c on a.ExtEnumType c.id where c.Code UFIDA.U9.Cust.XM.HXPP.EX.SubjectTypeEnum;NAME这一列如果全是 NULL就是多语言表没插进去。c.Code换成你的枚举类型全名。5.4 参照过滤写错过滤条件不生效现象CustomInParams写了参照弹出来还是全量数据。原因OPath 表达式里的类名写错或者BeforeUIModelBinding挂成了AfterUIModelBinding。解决先确认挂载点再确认表达式。表达式里最容易错的是in后面那个子查询实体类的全限定名少写一段就变成空条件U9 不报错直接忽略。我用一个笨办法验证把CustomInParams先改成一个恒假条件比如10如果参照弹出来是空的说明表达式生效了再改回正常条件。5.5 事件挂错PostBack 后取不到值现象按钮点击后代码执行了但取到的当前行数据是空的或者取到的是上一次的行。原因把取数逻辑写在了AfterCreateChildControls或OnLoadData_Extend里这两个事件在 PostBack 场景下拿不到最新的 Model 数据。解决取当前行数据统一放AfterUIModelBinding按钮状态控制也放这里。如果确实要在按钮事件里取用this.Model.Subject.FocusedRecord取之前先判空// 按钮事件里取当前行先判空再取 var r1 this.Model.PrintProduceSituation.FocusedRecord; if (r1 ! null r1.Status 0) { BtnSave_Click_Extend(sender, e); r1.Status 1; // 0开立1审核中2已审核 BtnSave_Click_Extend(sender, e); SetControlStauts(); }这段是不走审批流的提交处理先存一次改状态再存一次。Status的取值要和你的单据状态枚举对齐别照抄数字。6. 装完怎么验证拿一条科目数据跑通全链路环境装完之后我一般不加新功能先用一条科目数据把 BE、UI、枚举、导航四条链路走一遍。走通了再接需求走不通就说明环境还有问题先修环境。验证清单长这样验证项操作期望结果UBF 路径打开 Studio展开发布对象浏览器能看到已有组件树不转圈数据库连接查任意一条UBF_MD_Component有结果返回BE 生成编译 BE 项目生成一个 BE 的 DLL无警告即通过枚举同步跑 5.3 节的验证 SQLNAME列有值不是 NULLUI 绑定打开科目卡片扩展字段区域可编辑不是灰的导航卡片点列表按钮跳转到列表页带PDPageStatusbrowser参照过滤打开组织参照只显示当前组织跑完表格再做一次实际数据操作新建一条科目组织留空让它走默认值保存然后回到列表查这条数据。-- 验证默认值和组织过滤是否真的落库 select s.Code, s.Name, s.SubjectType, o.Code as OrgCode from Cust_XM_HXPP_EX_Subject s left outer join (select ID, Code from CBO_Org) o on s.Org o.ID where s.Code TEST001;OrgCode这一列如果有值说明 BE 的OnSetDefaultValue生效了如果是 NULL回去看 3.4 节大概率是Context.LoginOrg赋成了Context.OrgID。SubjectType这一列如果是 0 或者空说明枚举没同步或者下拉框没绑定上。最后打开列表页确认默认只显示当前组织的数据。这一步能过说明CustomFilterOpath_Extend挂对了。再双击一行能跳到卡片页并带上 ID说明导航参数拼对了。有一次我在客户现场跳过这套验证直接开始做费用明细单结果做到一半发现 BE 的 DLL 根本没发布上去前面两天写的 UI 代码全在调一个不存在的实体。从那以后我每接一个 EX 模块都先把 UBF 路径、命名前缀、枚举同步这三件事过一遍再动手写第一行代码。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询