④ Site.xml 全讲
课件 5 全部:NodeRef、Heading、Card、View、Action、Rule、Path、视图、Site.css
全部来自课件《5、前端开发 xml》48 页 + 知识库里虚拟机跑通的例子。XML 先记住三条规矩:
- 标签成对:
<Headings> … </Headings>,有开就有关
- 没有内容的可以自闭合:
<Heading Id="X"/> = <Heading Id="X"></Heading>
- 属性写在尖括号里:
Id="X"、Type="Style",值必须带双引号
1 · 五个概念(课件第 4 页)
| 词 | 课件原话 | 大白话 |
| Class | 系统模型 | 一类东西的「模具」:款式、材料、BOM |
| Node | Class 的实例 | 一条具体数据:HB-XXXXX 这个款 |
| NodeRef | 系统模型 Class 的实例化,也是类型 | 在 Site.xml 里「引用并扩展」一个已有对象的页面 |
| Relationship | 连接两个 Class,会作为属性定义在 Class 上 | 两类东西之间的线:款式 → 客户 |
| Path | 连接 Class 之间的 Relationship | 顺着线走的路线,见第 9 节 |
NodeRef 和 Node 的区别(课件 24 页 + 知识库):原文件里的 <Node> 是完整定义(「类」);Site.xml 只写 <NodeRef>,表示「继承原来的,只加我改的部分」。不要在 Site.xml 里用 Node 覆盖整页,知识库踩过坑。
2 · NodeRef 里能放什么(课件第 5 页)
<NodeRef Type="Style">
<Headings> … </Headings> ← 字段(可多个)
<Card Id="…"> … </Card> ← 一组字段打包(可多个)
<View Id="…"> … </View> ← 页面/表格/标签页(可多个)
</NodeRef>
课件原话:「系统页面都是由 View XML 显示,都是基于 Node 实例。页面中显示系统的 Node 都需要使用 NodeRef。」
3 · Heading / Headings(课件第 6 页)
Heading = 显示当前对象的一个属性(或关联对象的属性);Headings = 一组 Heading。
课件里的例子,逐行
| 代码(一行一行) | 这一行在干什么 |
|---|
| <Headings> | 开始一组字段 |
| <Heading Id="__Parent__" Path="Child:Previous" Name="MerchSeason_Previous__Parent__" /> | 显示「上一季」的父对象。Path = 顺着 Previous 这条线走过去;Name = 给这列另起名字 |
| <Heading Id="ApparelSeason"> | 季节字段,ref 类型,所以不自闭合 |
| <Relationship Type="Season"> | 它引用的是 Season(季节)对象 |
| <Filter> | 下拉里只列出符合条件的季节 |
| <Attribute ID="C8_BusinessType" Op="EQ" SValue="csi.session.getCurrentUser().C8_BusinessType" /> | 条件:季节的业务类型 等于(EQ)当前登录用户的业务类型 |
| </Filter> | 条件结束 |
| </Relationship> | |
| </Heading> | |
| </Headings> | 一组字段结束 |
其他写法(同一页)
| 代码(一行一行) | 这一行在干什么 |
|---|
| <Heading Id="Node Name" /> | 对象名称(C8 自带字段,Id 里可以有空格) |
| <Heading Id="Description" /> | 描述 |
| <Heading Id="Subtype" /> | 子类型 |
| <Heading Id="LT_SpecDS_ProcessMan" Path="Child:CurrentRevision"> | 走到「当前版本」上取这个字段——版本化对象(如 BOM)的字段常这样取 |
4 · Card(课件第 7 页)
Card = 一组 Heading 起个名字,多个 View 里重复用,不用每次重写。
| 代码(一行一行) | 这一行在干什么 |
|---|
| <Node Type="Shape"> | (课件例子用的是 Node;我们自己写时用 NodeRef)版型对象 |
| <Card Id="ShapeAttributes"> | 定义一张卡,名字叫 ShapeAttributes |
| <Heading Id="Node Name" /> | 卡里的字段 1:名称 |
| <Heading Id="ShapeSeasons" /> | 字段 2:季节 |
| <Heading Id="ShapeSizeRange" /> | 字段 3:尺码范围 |
| <Heading Id="Images" /> | 字段 4:图片 |
| </Card> | 卡定义结束 |
| <View Id="ShapeProperty" xsi:type="Property"> | 详情页(Property 类型) |
| <Image Id="Images"/> | 放一张图 |
| <Card Id="ShapeAttributes" /> | 引用上面那张卡 → 四个字段全出来 |
| </View> | |
| <View Id="AllShapes" xsi:type="Table"> | 列表页(Table 类型) |
| <Relationship Type="Shape" /> | 列表里列的是 Shape |
| <Card Id="ShapeAttributes" /> | 同一张卡又用一次 → 列表的列跟详情页一致 |
| </View> | |
| </Node> | |
5 · View 的八种类型(课件第 8 页)
| xsi:type | 课件原话 | 在 C8 里你见过的样子 |
Form | 创建或编辑对象的弹出框 | 点「新建款式」弹出的表单 |
Html | 基于 HTML 模板,用于自定义视图 | — |
PhaseTable | 版本化对象的工具栏 | BOM 页上冻结、审批那排按钮 |
Property | 节点属性 | 点进一个款看到的详情页 |
Slicer | Excel 数据透视表 | — |
Table | 查询显示 | Home → Style → Styles 那个列表(AllStyles) |
Tabs | 标签页 | 款式页顶上「款式 / 采购 / 日历 / 产品工艺」那排 |
Wizard | 引导视图 | 一步一步填的向导 |
Tabs 例子(课件第 6 页截图,逐行)
| 代码(一行一行) | 这一行在干什么 |
|---|
| <View Id="ProductSpec" xsi:type="Tabs"> | 款式页里「产品工艺」这个标签页,本身又是一组子标签 |
| <DependencyPath Path="Child:ProductType/Child:ProductionConfig"/> | 依赖:要顺着「产品类型 → 生产配置」去判断 |
| <ViewRef Id="ProductSpecSummary"/> | 子标签 1:摘要(引用别处定义好的 View) |
| <ViewRef Id="BOMs"> | 子标签:BOM |
| <Rule Exp="module('Apparel/ProductType').hasTDS(currentNode(), 'ApparelBOM')"/> | 规则:只有这个款的产品类型配了 BOM 才显示这个标签 |
| </ViewRef> | |
| <ViewRef Id="Reviews"> … ReviewStyle … | 评审标签,同理:配了才显示 |
看懂这个,你就明白为什么同样是款式,有的显示 BOM 标签、有的不显示——是 Rule 在控制。
6 · Action:按钮(课件第 9 页)
| 系统自带 Action | 意思 |
| Copy | 复制 |
| Delete | 删除 |
| Edit | 编辑 |
| MoveUp / MoveDown | 上移 / 下移 |
| New | 新建 |
| NewFrom | 从……新建(复制一份再改) |
课件第 14 页截图里的 Actions,逐行
| 代码(一行一行) | 这一行在干什么 |
|---|
| <Actions> | 这一组按钮出现在列表的「操作」栏(课件 17 页) |
| <Action Id="ReplaceColorSpecifications" MaterialIcon="merge_type" PostAction="csi.action.postActionRefreshView"> | 自定义按钮「替换颜色」;图标 merge_type;做完后刷新视图 |
| <ViewRef Id="ColorSpecifications" /> | 点了弹出颜色列表让你选 |
| <Filter><Node Parameter="URL" Op="NE" Value="thisNode()" /></Filter> | 列表里排除自己(URL 不等于当前这条) |
| <Request Module="Apparel" Operation="ReplaceColorSpecifications" /> | 选完后调用系统内置的「替换颜色」操作 |
| </Action> | |
| <Copy /> | 系统自带:复制 |
| <Delete> | 系统自带:删除,但加条件 ↓ |
| <Rule Exp="attr('C8_Sync_SendStatus')=='C8_Sync_SendStatus:NOTSEND' && attr('C8_Sync_DataStatus')=='C8_Sync_DataStatus:NEW'" /> | 只有「还没发送」并且「是新数据」才允许删。&& 是 XML 里 &&(并且)的写法 |
| </Delete> | |
| </Actions> | |
7 · Action 的三个阶段:OnAction / PostAction(课件第 11–13 页)
| 阶段 | 课件原话 | 能挂什么 |
| 事前 | 页面展示及用户输入,可以通过 OnAction 对数据做预先处理 | OnAction="Site.函数名" |
| 事中 | 传递请求到系统服务 | (系统自己做) |
| 事后 | 结果返回页面,可以通过 PostAction 做进一步处理 | PostAction="Site.函数名" |
OnAction:Site.xml 和 Site.js 两边要对上
| 代码(一行一行) | 这一行在干什么 |
|---|
| <Action OnAction="Site.onActionDoSomething" /> | Site.xml:点这个按钮时,去 Site.js 找 onActionDoSomething |
| return { | Site.js 文件最后的 return 块(第 ⑤ 页讲) |
| onActionDoSomething : function(ac) { | 名字必须一模一样(课件原话:「两处的方法名定义需要保持一致」) |
| ac.m_writer.changeNode(ac.m_urlNode) | 要改的是:当前点的这一条 |
| .changeAttribute("DateX", xml.ATTR_TIME, types.getDateTime()); | 把日期字段 DateX 改成现在时间 |
| ac.bRefreshNode = true; | 改完刷新页面 |
| } | |
| }; | |
PostAction:挂在字段上
| 代码(一行一行) | 这一行在干什么 |
|---|
| <NodeRef Type="StyleAttributes"> | |
| <Headings><Heading Id="X"> | 字段 X |
| <Action PostAction="Site.postActionStyleAttributesX" /> | X 改完、系统处理完之后,调 Site.js 的这个函数 |
| </Heading></Headings> | |
| </NodeRef> | |
| postActionStyleAttributesX: function(ac) { | Site.js 里同名函数 |
| var secondView = ac.widget.viewParent.getChildView("ViewIdOfSecondView"); | 找到同一页上的另一个视图 |
| if (secondView) { secondView.redraw(); } | 找到了就让它重画(数据刷新) |
| } | |
课件 6 补充:PostAction 里要自己提交:ac.m_writer.execute({sync:true})。
8 · Rule:条件(课件第 14 页)
| 属性 | 意思 |
Class | 满足条件时套用的 CSS 样式名(非必填)→ 用来标颜色 |
Path | 表达式从哪个对象算 |
Exp | 一个表达式,结果是 true / false |
Id1 | 左边的字段 |
Op | EQ 等于、NE 不等于、GT 大于、GE 大于等于、LT 小于、LE 小于等于 |
Id2 / Value | 右边:另一个字段,或一个固定值 |
| 代码(一行一行) | 这一行在干什么 |
|---|
| <Heading Id="C8_Style_Status"> | 款式状态字段 |
| <Rule Class="Red" Id1="C8_Style_Status" Op="EQ" Value="C8_StyleStatus:紧急"/> | 状态 = 紧急 → 套用 Red 样式(标红)。注意值是 枚举名:Key 完整写法 |
| <Rule Class="Black" Id1="C8_Style_Status" Op="EQ" Value="C8_StyleStatus:非常紧急"/> | 状态 = 非常紧急 → 套用 Black |
| </Heading> | |
Rule 用在三个地方:控制显示(ViewRef 里,第 5 节)、控制能不能操作(Action/Delete 里,第 6 节)、控制颜色(带 Class,第 12 节)。
9 · Path:顺着线走(课件第 4 页)
| 写法 | 方向 | 课件例子 |
Child:属性 | 从我出发,沿我身上的引用字段走过去 | Child:__Parent__ = 去我的父对象 |
Parent:属性 | 反着走:谁引用了我 | 配色上写 Parent:ProductColors = 找到这个配色属于哪个款式 |
Index:n | 多个里取第 n 个(从 0 数) | Child:ProductColors/Index:0 = 款式的第一个配色 |
多段用 / 连:Child:__Parent__/Child:__Parent__/Child:Category1 = 往上走两层父对象,再取 Category1。跟 Java 里 a.getParent().getParent().getCategory1() 一个意思。
10 · 表格视图 = 一条 SQL(课件第 16–34 页)
课件用 SQL 打比方,这是理解视图最好的一把钥匙:
| View 里的 | SQL 里的 |
<Relationship Type="Style"> | FROM 款式 |
<Filter> | WHERE … |
| Heading / Card 里的字段 | SELECT 字段1, 字段2… |
| 一对一的关联字段(ref) | LEFT JOIN |
| 一对多的关联字段(reflist) | RIGHT JOIN(只能用在 Table 视图) |
<Actions> | 列表最右的「操作」栏 |
Left Join 两种方法(课件 21–27 页)
- 在 FROM 里加关联:Relationship 里加
<PreferencePath Path="Child:POColor/Child:ProductColor"/>,每段前加 Child:,段之间用 /
- 直接在 SELECT 里点过去:Heading 用 Path 取关联对象的字段,相当于
a.品类编号.品类
步骤:①找到 ref 类型的外键字段 ②把原视图定义复制进 Site.xml(用 NodeRef)③往 Relationship 里加路径 ④列名会带上路径名,去 en.js 的 "Path" 里把它清空或改中文(第 ⑥ 页)。
Right Join 一对多(课件 28–34 页)
| 代码(一行一行) | 这一行在干什么 |
|---|
| <Heading Id="POColors" Path="…到POColors的路径" IsMatrixGroup="1" Hidden="1"/> | Id = 路径上最后一个 reflist 字段;IsMatrixGroup="1" = 一对多展开;Hidden = 默认视图里不显示 |
前提:关联字段是 reflist / refvector。展开后每个配色一行,再往后的一对一字段按 Left Join 方法二加。
11 · ref / reflist 下拉的两种样子(课件 35–37 页)
- 下拉里只显示对象名称:普通
<Heading><Relationship Type="…"/></Heading>
- 下拉还能点「进入对话」弹出选择窗口:
<AggregateInline>
<ViewRef Id="SelectMaterials" URL="centric://APPAREL/Views" /> ← 弹出框用哪个视图
</AggregateInline>
12 · Site.css:标颜色(课件 42–45 页)
整行底色
.stateGrey { background-color: #f17543; }
某个格子按条件变红,逐行
| 代码(一行一行) | 这一行在干什么 |
|---|
| <Heading Id="C8_Order_PlanTime"> | 订单的「计划时间」字段 |
| <Action> | 谁能改这个字段 ↓ |
| <Rule Exp="currentRoleName()=='管理员' || rightstr(currentRoleName(),5)=='材料供应商' || …" /> | 当前角色是管理员,或角色名后 5 个字是「材料供应商」……才能改 |
| <Rule Id1="C8_Order_Sent" Op="EQ" Value="false"/> | 并且「已发送」= false |
| </Action> | |
| <Rule Class="C8_Order_Background-red" Exp="(nowTime() > attr('C8_Order_DiaoLiaoTime')+2*86400) && attr('C8_Order_PlanTime')==date(0)" /> | 现在时间 > 调料时间 + 2 天(86400 秒 = 1 天),并且计划时间还没填 → 套红色 |
| </Heading> | |
| .C8_Order_Background-red { background-color: #f17543; } | Site.css 里定义这个红色 |
表头变黄
<Heading Id="C8_MerchFD_MaxSKU" Class="Merchandise-alert"> … </Heading>
.tableView>thead>tr>th.Merchandise-alert { background-color: #fff798; }
13 · 虚拟机里验证过的完整例子:列表每行加「审核通过」按钮 已验证 08-05
知识库确认的挂载路径(之前试过别的路径按钮不出现):
NodeRef Type="_CS_ApparelViews"
└─ View Id="AllStyles" xsi:type="Table"
└─ Relationship Type="Style"
└─ Actions
| 代码(一行一行) | 这一行在干什么 |
|---|
| <NodeRef Type="_CS_ApparelViews"> | 引用「服装视图集合」这个系统对象(AllStyles 列表定义在它下面) |
| <View Id="AllStyles" xsi:type="Table"> | Home → Style → Styles 那个列表 |
| <Relationship Type="Style"> | 列表的每一行是 Style |
| <Actions> | 每行的操作按钮 |
| <Action | 一个按钮开始 |
| Id="C8_ReviewApproval" | 按钮 Id,同时是 en.js 的翻译 Key |
| Type="Style" | 每行操作的对象是 Style |
| MaterialIcon="done" | 图标:打勾 |
| Control="Icon" | 以图标形式显示 |
| OnAction="Site.C8_onActionReviewApproval" | 点了调 Site.js 的这个函数 |
| /> | 按钮结束(自闭合) |
| </Actions> | |
| </Relationship> | |
| </View> | |
| </NodeRef> | |
结果:按钮出现在每行末尾「三个点」菜单里;没翻译时显示 C8_ReviewApproval,en.js 保存 + Update 后显示「审核通过」。