简介:这份资源是面向用友伙伴的U9客户化开发入门课程讲解资料,聚焦UBF开发体系的基础认知与实操规范,适合刚接触U9客开、需要系统梳理开发流程的技术人员使用,可作为视频教学的配套文字材料。压缩包内共1个docx文档,约817KB,内容围绕UBF路径配置、解决方案与项目组件的目录层次、配件与BE表命名规范、档案开发需求设计等基础环节展开,并延伸至BE Code与Model对比、组件类实体属性关系查询、UI Code与Model对比、UIForm与Part、Page关系梳理以及同步枚举脚本等进阶知识点。文中还给出可直接复用的SQL查询语句与枚举同步脚本示例,配合字段类型、长度与业务主键等档案字段设计说明,便于读者在实际开发中对照排查与验证。目前已有3029人学习,适合作为客开入门阶段的手边速查资料。
1. 用友U9客户化开发入门:先看清 UBF 这套骨架
很多人第一次接触用友U9客户化开发(U9客开),以为难点在写 C# 代码,实际翻车的地方往往在解决方案目录和命名规范上。这套东西的骨架可以这么理解:UBF 是底座,解决方案定层次,BE、BP、SV、UI 四类组件划职责,最后才是代码往里填。它适合已经能看懂 ERP 单据结构、准备动手做档案和单据的伙伴。梳理下来会发现,U9客开更像一套“装配规则 + 元数据查询 + 事件时序”的组合拳,而不是零散的编码技巧。
1.1 知识地图:四层结构一次说清
把整个 U9 客开拆成四层,后面所有模块都能挂进去:
| 层次 | 核心内容 | 解决什么问题 | 对应章节 |
|---|---|---|---|
| 环境层 | UBF 路径配置、装配发布、environment.xml | 让代码能编译、能跑起来 | 第 2 章 |
| 组织层 | 解决方案>>项目>>组件、命名规范 | 让代码能被找到、被复用 | 第 2 章 |
| 模型层 | BE 实体、枚举、扩展字段、元数据 SQL | 让数据有结构、可查询 | 第 3 章 |
| 界面层 | UIForm/Part/Page、事件时序、导航、参照 | 让数据能录、能看、能跳转 | 第 4、5 章 |
这四层是强依赖关系:环境层不通,后面全是白搭;组织层乱,组件会散落在各种地方,想找某个 BE 得靠搜索;模型层设计错,UI 再怎么调也对不上字段。最容易忽略的是第三层和第四层之间的“对照”动作——BE Code 和 Model、UI Code 和 Model 各对一遍,很多“界面上没值”的玄学问题,根子都是模型绑定没对上。
1.2 为什么新手容易在“目录”上先摔一跤
血泪经验是这样的:刚上手的人习惯“一个功能建一个项目”,做完三个档案就有三个 BE 项目、三个 UI 项目,DLL 越生越多,部署时漏一个就报找不到类型。U9 的规则恰恰相反——同一模块的所有 BE 放一个项目、只生成一个 DLL,所有 BP/SV 放一个项目,所有 UI 也是按“一个档案或单据一个 UI 项目、所有列表一个项目、所有参照一个项目”来归并。这套规则不是洁癖,是为了让装配发布时的组件清单稳定可控。
所以复习的时候不要一上来就背 API,先把这张地图记住:先有解决方案层次,再有组件归并,最后才是代码落位。地图记不住,后面每章都会变成孤立的知识点,考试或实操时就想不起来该往哪放。
2. 环境与组织:UBF 路径配置、解决方案层次和命名规范
这一章是“能不能跑起来”的地基。很多人跳过它直接写代码,结果编译报错一堆,回头补课更费时间。U9 客开的入门成本,一大半花在这层配置和归并规则上。
2.1 UBF 路径配置与装配发布的两个配置文件
UBF 路径配置是第一步,不配好,UBF Studio 找不到元数据,后面所有设计器都是空壳。配置完成后,真正决定“能不能部署”的是装配发布环节,它涉及两个文件:
buildup.xml:在 UBF 的工作目录下(原文示例为\UBFV28\U9.VOB.Product.UBF\下的UBFStudioBuildupDesigner.exe同级的配置文件),主要改UILib 路径,让打包时能定位到你的 UI 组件输出目录。environment.xml:在\Runtime\下,配置数据库连接串,组件运行时靠它连库。
连接串这块有个容易被忽略的细节——带实例名和不带实例名的写法不同,原文给了两个对照示例:
<!-- 不带实例名:Data Source 直接写主机名 --> <connectionString>User Id=sa;Password=123456;Data Source=laputa\mssql2012;Initial Catalog=hxpp0326;packet size=4096;Max Pool size=1500;persist security info=True</connectionString> <!-- 带实例名:Data Source 写成 主机名\实例名 的形式 --> <connectionString>User Id=sa;Password=123456;Data Source=DZY-\mssqlsever;Initial Catalog=HXPP0145;packet size=4096;Max Pool size=100;Connection Timeout=900;persist security info=True;MultipleActiveResultSets=true</connectionString>参数说明:Data Source决定连哪台库,带不带实例名要看数据库实际部署方式;Initial Catalog是目标库名,换环境必须改,这是最常见的“改了代码没生效”原因;Max Pool size和Connection Timeout在多人共用同一套环境时值得调大,否则并发高时会出现莫名其妙的超时。注意MultipleActiveResultSets=true只在需要同一连接上并行读多个结果集时开,不是必选项。
装配发布的操作顺序是:先改buildup.xml的 UILib 路径 → 再确认environment.xml连的是目标库 → 用UBFStudioBuildupDesigner.exe执行装配 → 生成补丁包或直接安装。顺序错了会白忙:路径没改就装配,打出来的包里 UI 组件是旧的。
2.2 解决方案层次:解决方案 >> 项目 >> 组件
这个三级层次是 U9 客开的组织核心,规则要背下来:
| 层级 | 归并规则 | 产物 |
|---|---|---|
| BE 项目 | 同一模块所有 BE 放一个项目 | 只生成一个 BE 的 DLL |
| BP/SV 项目 | 同一模块所有 BP、SV 放一个项目 | 只生成一个 BP/SV 的 DLL |
| 枚举 | 同一模块所有枚举可放同一个 BE 组件内 | 随 BE DLL 一起 |
| 档案/单据 UI | 一个档案或单据(含其查找参照 UI)一个项目 | 一个档案/单据一个 UI DLL |
| 列表 UI | 同一模块所有列表 UI 一个项目 | 一个列表 DLL |
| 参照 UI | 同一模块所有参照 UI 一个项目 | 一个参照 DLL |
为什么这么设计?因为装配发布是按组件清单走的,组件越少越稳定;同时这个归并方式也让“同模块改动”只涉及少量 DLL,升级时替换面小。最容易踩的坑是把“一个档案的查找参照 UI”单独建项目,结果档案 UI 和参照 UI 拆成两个 DLL,发布时容易漏。记住一句话:参照跟着宿主走,列表单独走,参照单独走。
2.3 命名规范:配件名与 BE 表名的两套模板
命名规范看着琐碎,但它是团队协作和后续排错的基础。原文给了两套模板:
配件名(程序集/命名空间):UFIDA.U9.Cust.XM(地区).HXPP(公司).EX(模块) BE 表名:Cust_XM(地区)_HXPP(公司)_EX(模块)_XXXX逐段说明:UFIDA.U9.Cust是客户化开发的固定前缀,不要自创;XM位放地区代号,HXPP位放公司代号,EX位放模块代号,三段都是有实际含义的占位,不是随便填;最后的XXXX才是具体实体名。BE 表名同理,只是把点换成下划线,加上Cust_前缀和模块尾缀,这样一眼能看出是客开表还是标准表。
提示:模块代号(示例中的 EX)在整个模块内必须统一,BE 项目名、UI 项目名、表名、URI 里的模块段要一致,否则装配发布时组件找不到宿主。
配错命名的代价不在编译期,而在发布期和运维期——别人接手时看不出这张表属于哪个模块。复习时把这两行模板默写一遍,比背十页 API 有用。
3. 模型层:BE 设计、枚举同步与元数据查询
模型层是 U9 客开真正的主战场。档案开发、单据开发,本质都是设计 BE 实体和它的属性,然后让 UI 去绑定。这一章的复习目标是:给你一个字段清单,能独立设计出 BE;给你一个实体名,能用 SQL 把它和 UI 的关系查出来。
3.1 档案开发的字段设计:以“科目”为例
档案是最基础的客开对象,需求通常以表格形式给出。原文的科目(Subject)档案就是标准模板:
| 名称 | 显示名称 | 类型 | 备注 |
|---|---|---|---|
| Org | 组织 | 组织机构 | 业务主键,不可空 |
| Code | 编码 | 字符串 | 业务主键,不可空,长 50 |
| Name | 科目名称 | 字符串 | 不可空,长 200 |
| SubjectType | 科目类型 | 科目类型枚举 | 辅料、主要材料、产值、费用、对账差异等 |
| Effective | 状态 | 状态 | — |
| Memo | 备注 | 字符串 | 长 500 |
| DescFlexField | 扩展字段 | 实体扩展字段集合 | — |
从这张表能提炼出档案设计的三个要点:组织 + 编码构成业务主键,两者都不可空,这是 U9 档案的通用模式;字符串字段必须给长度,不给长度的字段后期改起来要动表结构;枚举字段单独建枚举类型,不要用字符串硬编码,否则同步枚举时会出现值对不上的问题。扩展字段统一用DescFlexField这个实体扩展字段集合,别自己建一堆松散字段。
单据的字段设计同理但更复杂:单据要继承单据基类,单据类型要继承单据类型基类,主键、单号、日期、状态这些基础字段由基类提供,你只需要补业务字段。原文的费用明细单就是这个结构——FeeDetail继承单据基类,FDDocType继承单据类型基类,业务字段是坏账增加、坏账减少、资金占用额(都是十进制精度 2)和备注。行实体FeeDetailLine则带行号、科目、费用、备注和扩展字段。
3.2 枚举同步脚本:什么时候需要,什么时候不需要
枚举最容易出的问题是“界面上的枚举值和数据库里的对不上”。原文给了两条路径,选择依据是发布方式:
- 以补丁包方式更新/安装:不需要手工跑同步脚本,因为 BE 的 bulk 中会处理。
- 直接改库或增量部署:需要手工执行同步脚本,先删后插四张表。
脚本的核心逻辑是:清空ubf_sys_extenumtype、ubf_sys_extenumtype_trl、ubf_sys_extenumvalue、ubf_sys_extenumvalue_trl,然后从ubf_md_class(classtype=3即枚举类)重新灌入类型和值,再从ubf_md_attribute灌入枚举值。验证是否同步成功,用这段查询:
-- 查询某个枚举类型下的所有枚举值,确认 Code / Name / EValue 三列一致 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.Base.Doc.ConfirmTypeEnum'参数说明:WHERE里的c.Code换成你要核查的枚举类型的全名;at.SysMLFlag = 'zh-cn'只取中文翻译,去掉会多出其他语言的重复行;EValue是实际存储值,Code是逻辑编码,两者不一致就是同步没跑干净。注意脚本里delete和insert之间不要插入其他操作,四张表要么一起清、要么一起灌。
3.3 元数据查询:三组必背 SQL
U9 的元数据都在这几张UBF_MD_*表里,会用 SQL 查,等于拿到了黑匣子的钥匙。原文给了三组查询,复习时重点记它们的连接链路而不是死记语法。
第一组:组件 → 类 → 属性。链路是UBF_MD_Component→UBF_MD_Class(通过MD_Component_ID)→UBF_MD_Attribute(通过MD_Class_ID),每层再左连_Trl表取中文名。用途是拿到某个实体(如“科目”)的完整字段清单和所属组件名。
第二组:UIForm → Part → Page。链路是ubf_md_uiform→ubf_assemble_part(通过component = uid)→ubf_assemble_page(通过page = id),再左连base_application_trl取应用名。用途是查某个界面(如“科目”)挂在哪个 Part、哪个 Page、哪个应用下。
第三组:参照关系。链路是aspnet_Parts→UBF_MD_UIRComponent→UBF_MD_UIReference→UBF_MD_EntityReference→UBF_MD_Class→UBF_MD_Component,用途是查某个参照(如cRef)绑定的是哪个实体、哪个类。原文示例用c.Name='cref'加et.DisplayName='科目'双重过滤,这个组合条件很实用。
-- 第二组:查“科目”界面的 Form / Part / Page 关系 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='科目'三个字段要记住含义:UID(FormID)是界面的唯一标识,写查找代码弹窗时会用到;PartID(UIFormURI)是 Part 的 URI,页面导航时作为目标地址;PageURI是页面地址,发布菜单时要填。把where里的显示名称换成你的界面名,就能复用到任何档案或单据。
3.4 BE Code 与 Model 对照:为什么会“值不显示”
模型层的最后一步是编译后对照:BE Code 是生成的 C# 代码,Model 是设计器里的模型视图。两者本该一一对应,但出现“实体里有字段、界面上没值”时,往往是 Model 里的属性没绑定、或者扩展字段没挂到DescFlexField集合里。对照的方法是:在 BE 代码里找到属性定义,回设计器确认它挂在哪个类、是否参与界面绑定;扩展字段则确认是否通过FlexFieldHelper.SetDescFlexField挂到了对应控件。
注意:枚举类在
ubf_md_class里classtype=3,实体类不是这个值,查元数据时必须区分,混用会查不到结果。
4. 界面层:UI 事件时序、常用代码与导航参照
界面层的坑最集中,因为它同时涉及“事件什么时候执行”和“代码写在哪”两个维度。同段代码写在AfterCreateChildControls里有效、写在OnLoadData_Extend里可能无效,这不是玄学,是时序问题。
4.1 UI 事件的执行先后顺序:一张表定生死
原文给了完整的事件顺序,这是界面层最该背下来的东西:
| 顺序 | 事件 | 执行时机 | 典型用途 |
|---|---|---|---|
| 1 | AfterEventBind | 事件绑定之后 | 少用 |
| 2 | AfterCreateChildControls | 创建控件之后 | 个性化、删除确认弹窗、查找弹窗、扩展字段挂载、按钮状态 |
| 3 | OnLoadData_Extend | 刷新时执行,PostBack 一般不执行 | 参数过滤、加载期数据处理 |
| 4 | AfterOnLoad | 加载之后 | — |
| 5 | OnDataCollect_Extend | 刷新时不执行,PostBack 执行 | 数据收集 |
| 6 | BeforeUIModelBinding | UIModel 绑定到控件之前 | 给参照加过滤条件 |
| 7 | AfterUIModelBinding | 绑定之后 | 按窗口类型控制确定/取消按钮 |
记忆方法是抓“绑定”这个分界点:绑定之前的BeforeUIModelBinding适合改数据源和参照过滤;绑定之后的AfterUIModelBinding适合根据界面状态调整控件;而控件级的东西(弹窗、扩展字段、个性化)要在控件创建完的AfterCreateChildControls里做。第 3 和第 5 个事件是一对反向开关——OnLoadData_Extend只在刷新时跑,OnDataCollect_Extend只在 PostBack 时跑,写反了就是“保存时触发不了”。
4.2 常用代码:默认值、扩展字段、个性化和弹窗
这几段代码几乎是每个客开项目的标配,直接抄作业即可,但要理解参数含义:
// 1. BE 上赋默认值:OnSetDefaultValue 中 if (this.Org == null) { this.Org = Context.LoginOrg; // 取当前登录组织 } // 2. 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; // 3. 扩展字段:Webpart >> AfterCreateChildControls 中 FlexFieldHelper.SetDescFlexField(this.FlexFieldPicker0, this.Model.Subject); // FlexFieldPicker0 是要设置的描述性弹性域控件,this.Model.Subject 是弹性域所在的 View // 4. 开启个性化:AfterCreateChildControls 中 UFIDA.U9.UI.PDHelper.PersonalizationHelper.SetPersonalizationEnable(this, true); // 5. 删除按钮绑定提示:AfterCreateChildControls 中 UFIDA.U9.UI.PDHelper.PDFormMessage.ShowDelConfirmDialog( this.Page, UFIDA.U9.UI.PDHelper.PDResource.GetDeleteConfirmInfo(), "", this.BtnDelete);参数说明:第 2 段三个默认值必须一起赋,只赋ID不赋Code和Name,界面上组织会显示为空;第 3 段的第二个参数换成你自己的 View 名,控件名换成你界面上的弹性域控件名,两个都对不上就不会显示;第 5 段的BtnDelete是按钮名,字符串空参数是标题。附件功能不用写代码,启用文档库即可。
复制功能的处理有个省事做法:Webpart 里按钮事件写代码,复制功能会自动完成,你只需要加清除逻辑,比如this.Model.Subject.FocusedRecord.Code = string.Empty;,把不该带过去的编码清空。
4.3 按钮状态控制:非审批流的状态机写法
不走审批流的单据,提交/审核/弃审要自己写状态控制。原文的费用明细单示例是一个标准的“状态值 switch”写法:
private void SetControlStauts() { PrintProduceSituationRecord r1 = this.Model.PrintProduceSituation.FocusedRecord; if (r1 != null) { switch (r1.Status) { case 0: // 开立 BtnSubmit.Enabled = true; BtnApprove.Enabled = false; BtnUndoApprove.Enabled = false; BtnDelete.Enabled = true; Card4.ReadOnly = false; break; case 1: // 审核中 BtnSubmit.Enabled = false; BtnApprove.Enabled = true; BtnUndoApprove.Enabled = false; BtnDelete.Enabled = true; Card4.ReadOnly = false; break; case 2: // 已审核 BtnSubmit.Enabled = false; BtnApprove.Enabled = false; BtnUndoApprove.Enabled = true; BtnDelete.Enabled = false; Card4.ReadOnly = true; break; } } // 状态、审核人、审核日期三列只读 ((IUIFieldBindingDataBindControl)this.Status178).ReadOnly = true; ((IUIFieldBindingDataBindControl)this.ApprovedBy101).ReadOnly = true; ((IUIFieldBindingDataBindControl)this.ApprovedOn311).ReadOnly = true; }三个业务按钮的事件写法一致:先取当前记录判断状态,先调用一次保存把当前状态落库,改Status(审核时补ApprovedBy和ApprovedOn),再保存一次,然后刷新按钮状态,最后调用默认实现BtnSubmit_Click_DefaultImpl。两次保存是刻意的,第一次保存保证状态机不丢数据,第二次保存把新状态写进去。容易错的是弃审时要把ApprovedBy和ApprovedOn置回null,不清空的话单据会一直挂着审核人。
4.4 页面导航与参照过滤:URI 和 OPath 两把钥匙
导航分三种场景,各自写在不同的位置:
| 场景 | 写在哪 | 关键代码 |
|---|---|---|
| 卡片 → 列表 | 卡片 UI 的 Webpart,BtnList_Click_Extend | this.NavigatePage("...SubjectListURI", null); |
| 列表 → 卡片新增 | 列表 Action,OnNew_Extend | this.CurrentPart.NavigatePage("...SubjectURI", null); |
| 双击行跳转 | 列表 Action,OnGridRowDbClick_Extend | 构造NaviteParamter,传入ID和PDPageStatus |
双击跳转要注意:原文提到“正拖的话需用 MainID”,普通场景取FocusedRecord["ID"]即可;PDPageStatus传browser表示以浏览态打开。URI 必须与装配发布时登记的完全一致,差一个字母就是“页面找不到”。
参照过滤有两条路径。代码给参照列加过滤写在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.Org=...WaterMarkMO.Org and MO.DocState in (2,3))";参数过滤写在 Webpart 的OnLoadData_Extend,取CurrentFilter.Parameters[0]或Parameters["@ID"],根据当前 ID 递归查子节点,再把OPath追加not in条件。这两个写法的时间点不能互换:参照列的过滤在绑定前改,参数过滤在加载时改。
列表默认只显示当前组织,写在列表 Action 的CustomFilterOpath_Extend:filterOpath为空就赋值Org={OrgID},不为空就追加and Org={OrgID}。忘了这段,用户会看到所有组织的数据,属于典型的“越权显示”。
查找弹窗的代码在AfterCreateChildControls,用ShowConfirmDialog传入 FormID、宽高、标题、查找控件 ID 和按钮。FormID 要去 UI 对应的 Webpart 代码里查,抄别人的 FormID 会打开错误的界面。
5. 易错点清单:五个最容易翻车的地方
复习到这一步,知识点已经铺完,真正拉分的是那些“看着会、一做就错”的地方。下面五条是梳理下来最容易漏的,每条都给补法。
易错点一:同一个模块建了多个 BE 项目。为什么漏:直觉上“一个功能一个项目”更清晰,新手容易照搬通用开发习惯。怎么补:记住“同模块所有 BE 一个项目、一个 DLL”,BP/SV 同理;做完之后数字典树,如果 BE 项目数超过模块数,就要合并。
易错点二:单据类型的类型名用了DocType。为什么漏:基类里本来就有DocType属性,字段名冲突时编译器报的是“不会实现继承的抽象成员”,错误信息指向很难一眼看懂。怎么补:单据类型字段命名避开DocType,原文用的是FDDocType;同时在 BE 代码对应单据的Extend里重载DocType.get,返回this.FDDocType:
// 单据 Extend 中重载 DocType.get,解决继承抽象成员报错 public override UFIDA.U9.Base.Doc.DocType DocType { get { return this.FDDocType; } }易错点三:枚举同步脚本在不该跑的时候跑了。为什么漏:不知道“补丁包方式会由 BE 的 bulk 处理”,看到枚举不对就手工清表。怎么补:先确认发布方式,补丁包安装的不跑脚本;手工跑之前备份四张ubf_sys_extenum*表,跑完用第 3 章的验证 SQL 核对Code和EValue。
易错点四:界面代码写错事件。为什么漏:OnLoadData_Extend和OnDataCollect_Extend名字相近,而且单个场景下“有时候能用”。怎么补:把 4.1 那张表贴在显眼处,记死“刷新用 Load、回发用 DataCollect”;控件级操作一律AfterCreateChildControls,绑定前改数据一律BeforeUIModelBinding。
易错点五:装配发布前没改buildup.xml的 UILib 路径、environment.xml连错库。为什么漏:这两个文件平时不碰,换环境时最容易忘。怎么补:把“改 UILib 路径 → 核对连接串的Data Source和Initial Catalog→ 再执行装配”做成固定三步,尤其注意带实例名和不带实例名的写法差异。
6. 复习清单:从查漏到能独立交付的落地方法
最后落到怎么复习。这套东西不适合从头背到尾,适合按“能不能独立跑通一个档案 + 一个单据”倒推检查。下面这份清单是我自己过一遍的顺序,建议你拿着它逐项打勾,缺哪项回哪章补。
| 检查项 | 自测标准 | 对应章节 |
|---|---|---|
| UBF 路径与两个配置文件 | 能说清buildup.xml改什么、environment.xml改什么 | 2.1 |
| 解决方案归并 | 能画出 BE/BP/UI 三类项目的归并规则 | 2.2 |
| 命名规范 | 能默写配件名和 BE 表名两套模板 | 2.3 |
| 档案字段设计 | 拿到字段表能判断哪些是业务主键、哪些要建枚举 | 3.1 |
| 枚举同步 | 能判断什么时候跑脚本、跑完怎么验证 | 3.2 |
| 元数据查询 | 能写出组件-类-属性、UIForm-Part-Page 两条链路 | 3.3 |
| 事件时序 | 能说出七个事件的执行顺序和各自适用场景 | 4.1 |
| 常用代码 | 默认值、扩展字段、个性化、删除弹窗能默写 | 4.2 |
| 状态控制 | 能解释为什么提交要保存两次、弃审要清审核人 | 4.3 |
| 导航与参照 | 三种导航场景各写在哪、URI 从哪查 | 4.4 |
查漏补缺的具体技巧是反向验证,比正向背诵有效得多:随便挑一个已有界面,用第 3 章的 SQL 查出它的 FormID、PartID、PageURI,再去装配发布记录里核对是否一致;再用参照关系 SQL 查出某个参照绑定的实体,去界面点开参照看数据对不对。哪一步查不出来,就说明那一章的链路没记牢。
还有一个习惯值得养成:每做完一个模块,把“BE 项目名、BP 项目名、UI 项目名、表名、URI 前缀”这五项列成一行记下来。这些东西分散在解决方案、设计器和 SQL 结果里,不集中记,隔两周自己都找不到。我早期就是因为没记,回头找一个单据的 URI 翻了半天发布记录,从那以后每次开新模块都先建这行对照表,再动代码。
复习的节奏建议是两轮:第一轮按第 2 到第 4 章顺序过,保证链路完整;第二轮只做上面的自测表和反向验证,专挑打不上勾的项。代码不用整段背,但事件时序表和两张命名模板必须做到脱口而出,因为几乎所有实操问题最后都会回到这两处。
希望这份梳理能帮你在 U9 客开的地图上少绕两圈,把时间花在真正需要动手的那部分上。
本文还有配套的精品资源,点击获取