@windwolf
2017-04-19T05:06:27.000000Z
字数 7477
阅读 527
Sailing
Sailing的UI结构需要足够灵活,能搭建ERP场景下的各种UI结构;需要足够的可重用性,可以层层复用各种业务组件;需要足够易用,可以让实施顾问在尽量少的技术支持下自行完成UI的调整。
因此,我们需要一个UI模型,这个模型可以由基本构件自由组成而成;而构件也可以根据业务需要扩展,只需要遵循UI模型的基本规范;这个UI模型还有一套易用的表示方法。我们可以用LEGO积木来做类比。LEGO积木块本身十分简单,但确可以以此搭建出无穷无尽的各种形象。
LEGO积木块有很多种,但他们共同遵循一套基本规范,就是用圆型凸起来连接,圆形突起有相同的尺寸和间隔要求。UI构件就是LEGO积木块,UI构件有很多种,但遵循共同的容器布局和接口要求。
大多数的LEGO积木块还同时有公母接口,这样就可以用小小的积木搭出复杂的大型玩具。UI模型就一样,可以用层次结构搭建出各种复杂的UI页面。
LEGO还很容易搭建,他的结构设计使得小朋友的力量就可以任意拆分组合;而一个大型的模型可以由多人分块负责,最后总成在一起。UI模型的表示也一样。
先从UI模型的基本构件讲起。总起来说,一共有三类UI构件:Control、Component和Layout。
ControlValueAccessor接口的Angular组件,可以在Form体系中绑定到某个对象字段上。Control构件大量用于表单中,用于呈现字段内容并负责用户交互。使用以上三类UI构件层层组合,可以用一棵UI构件树来表示大多数ERP中的UI。
以Demo中的商品详情页为例。商品详情页可以分解为以下UI构件树:
以下为用元数据表示的构件树
{"index" : NumberInt(0),"size" : {"column" : NumberInt(3)},"type" : "group","items" : [{"type" : "control","index" : NumberInt(0),"path" : "id","label" : "id","uiType" : "sys#id","editable" : false},{"type" : "control","index" : NumberInt(2),"hidden" : false,"path" : "商品编码","label" : "商品编码","uiType" : "sys#text","editable" : true},{"type" : "tab","size" : {"column" : NumberInt(0)},"index" : NumberInt(3),"panels" : [{"label" : "基本信息","root" : {"index" : NumberInt(0),"size" : {"column" : NumberInt(3)},"type" : "group","items" : [{"type" : "control","index" : NumberInt(3),"hidden" : false,"path" : "品名","label" : "品名","uiType" : "sys#text","editable" : true},{"type" : "control","index" : NumberInt(4),"hidden" : false,"path" : "型号","label" : "型号","uiType" : "sys#text","editable" : true},{"type" : "control","index" : NumberInt(7),"hidden" : false,"path" : "销售计量单位","label" : "销售单位","uiType" : "sys#unit","editable" : true}]}},{"label" : "供应商信息","root" : {"type" : "control","size" : {"column" : NumberInt(0),"row" : NumberInt(5)},"index" : NumberInt(8),"hidden" : false,"path" : "商品供应商","uiType" : "sys#grid","editable" : true,"inputs" : [{"target" : "page","value" : NumberInt(0)},{"target" : "columns","value" : [{"path" : "id","label" : "id","uiType" : "sys#id","hidden" : true,"width" : NumberInt(1),"index" : NumberInt(0),"linkable" : false,"disable" : false,"editable" : false},{"path" : "工厂货号","label" : "工厂货号","uiType" : "sys#text","hidden" : true,"width" : NumberInt(1),"index" : NumberInt(2),"linkable" : false,"disable" : false,"editable" : false},{"path" : "生产厂家","label" : "生产厂家","uiType" : "sys#text","hidden" : false,"width" : NumberInt(100),"index" : NumberInt(3),"linkable" : false,"disable" : false,"editable" : false}]}]}},{"label" : "统计信息","root" : {"index" : NumberInt(0),"size" : {"column" : NumberInt(3)},"type" : "group","items" : [{"type" : "control","index" : NumberInt(1),"hidden" : false,"path" : "统计信息.总销量","label" : "总销量","uiType" : "sys#amount","editable" : true},{"type" : "control","size" : {"column" : NumberInt(1),"row" : NumberInt(1)},"index" : NumberInt(9),"hidden" : false,"path" : "统计信息.最近采购数量","label" : "最近采购量","uiType" : "sys#amount","editable" : true}]}}]}]}
层次上的一个Json对象,记录了UI构件树中的一个UI构件。整个文档就表示出了整个UI构件树。文档中除了表达了UI构件的层次结构外,还有些额外的属性。下面来一一解释。
所有的UI构件都有四个基本属性:
- id(可选):赋予某个UI构件一个id,便于在应用中查找。
- size(可选):UI构件容器的大小,以行列为单位。不同的UI构件有不同的默认值规则,在【布局】章节详述。
- index:在父构件中相对兄弟构件所处的位置。
- hidden(可选):默认false。是否不显示。
凡是需要数据绑定的场合,UI的最终目的无非两个:一是用于呈现绑定的数据;一是通过与用户的交互获取数据。一般来说,业务含义相似的字段,呈现和交互方式也是相似的。因此,有需要定义一些有业务类型针对性的构件,以便统一用户体验,增加复用性。
Control以及Cotrol构件就是为此而生。
一个Control由两部分组成:
ControlValueAccessor接口的Angular组件,它可以用于Form数据绑定。一个Control就是一个标准的支持Form的Angular组件,它和UI模型没有任何耦合关系。Control可以根据需要绑定到任意类型,包括基本类型(string、number、boolean)、对象和数组。Control可以包含其他Control,来达到复用的目的。
作为约定,Control的select用q-开头,以-control结尾。
ControlContainer的目的十分单一,因此都具有类似如下结构:
@Component({selector: 'q-xxx-control-container',template: `<div [formGroup]="status?.form.parent" class="ui-g-12 form-group"><q-xxx-control [formControlName]="status?.objectName"></q-xxx-control></div>`,})export class XxxControlContainer implements ControlContainer {public status: ControlUiItemStatus;}
作为约定,ControlContainer的select为对应Control的select加-container后缀。
- type(control):用于标示Control构件类型。
- inputs(可选):为Control构件绑定初始Input属性值。
- outputs(可选):为Control构件绑定Output属性处理器。
- path:绑定字段的业务模型名称。
- objectPath:绑定字段的对象属性名称。
- label(可选):名称标题。
- uiType:构件的uiType类型。它确定了具体使用哪个Control。具体在【UiType】章节展开。
- required:是否必填。
- editable:是否可编辑。
- calculators(可选):字段计算规则。有关字段计算规则的介绍,请看【表单】专题说明。
有些情况下,会需要用到一些UI组件,但这些组件无需绑定业务数据。这类UI构件就是Component。
一个Component就是一个标准Angular组件。
为了能在UI模型中以配置的方式使用,需要在使用前在响应模块的providers中使用provideComponents注册一个ComponentMetadata,任何Angular组件都可以注册为Component。
ComponentMetadata的属性如下:
- id: 在UI模型中使用这个这个id的模块限定名来引用Component。
- type: Component构件的类。必须是一个Angular组件类。
- size: 默认大小,如果Component构件中没有指定size,则使用这个size。如果这个size也没有,这默认为(column:1,size:1)。
- type(component):用于标示Component构件类型。
- inputs(可选):为Control构件绑定初始Input属性值。
- outputs(可选):为Control构件绑定Output属性处理器。
- component:构件的Component类型。可以是任何已经注册的Component。
以行列为单位按行流式排列其中的构件。
- type(grid):用于标示Grid构件类型。
- items:UI构件数组。
- type(tab):用于标示Tab构件类型。
- panels:
TabPanel数组。
- label:tab页的标签。
- root:放置在tab页内的UI构件。
未实现
没有专属属性
某种业务含义类型的字段在不同的场景中需要有不同的呈现方式。例如计量单位类型在表单中表现为一个AutoComplate,在列表字段中保险为一个名称加类型的缩写。为了尽量使实施顾问能从较高层次配置系统,因此有必要抽象出一个UiType的概念,一个UiType包含了某种业务字段类型在表单、表格、搜索框等各种UI场景中的相关配置。以下就是一个UiType的例子:
{id: "date",form: {control: "sys#datepicker"},simpleSearch: {control: "sys#datespan", queryGenerator: QUERY_TEXT_LIKE}}
上例定义了一个适用于日期类型的UiType,这个UiType的名称为date,表单情况下使用名为sys#datapicker的Control,这个字段在搜索框中则采用sys#dataspan组件。
- form(可选):用于表单环境的一组配置
- control:用于表单环境的控件
- size(可选):大小.
- inputs(可选):控件的输入绑定
- outputs(可选):控件的输出绑定
- simpleSearch(可选):用于搜索框环境。
- control:。用于搜索框环境的控件
- queryGenerator:搜索表达式产生器。
- inputs(可选):控件的输入绑定
- outputs(可选):控件的输出绑定
- table(可选):用于表格环境
- formatter(可选):单元格文字格式化器。
- width(可选):列宽。
- control(可选):行内编辑模式的控件。
- inputs(可选):控件的输入绑定
- outputs(可选):控件的输出绑定
一般来说,ERP中的一个页面常常需要呈现很多的数据,对单位大小信息密度的要求很高。因此ERP中的UI布局要求紧凑、简洁、整齐、高效,而对特型布局的要求。
在这个前提下,UI模型需要将UI开发人员从像素级布局中解放出来,因此引入了基于行列的布局方式。UI开发者首选指定页面的整体列数,例如采用3栏式还是4栏式等,确定了整体列数之后,每个UI构件仅需确定自身所占行数列数以及顺序即可,具体定位由布局算法自动完成。
每种UI构件的自身大小确定方式如下:
Empty:
NOTE:如果column指定为0,则表示占用容器中的整行,而不管容器实际列数。根容器不允许将column设为0。