组件包目录结构
下图展示了一个组件包的目录及文件结构。组件包下载到 IDE 后,目录发生变化,前端代码在 UI2/comp 目录下,服务端代码在 service/comp 目录下
前端目录
组件前端目录包括组件目录 components 和配置目录 config
components 目录
components 目录包括组件命名空间目录(和组件包同名)、若干组件目录和组件注册文件。
组件命名空间目录
组件命名空间目录与组件包目录同名,基础通用逻辑可以写到这个目录下,组件依赖的第三方库放到 lib 目录下
组件目录
一个组件包括设计时文件、编译时文件(非必须)和运行时文件
│ ├─组件目录 //组件名
│ │ ├─css //运行时css目录
│ │ │ 组件名.css //运行时样式
│ │ │
│ │ │ 组件名.js.xdoc //组件的xdoc说明文档
│ │ │ 组件名.meta.json //组件元信息文档
│ │ │ 组件名.react.js //react 体系组件运行时 js
│ │ │ 组件名.vue //vue 体系组件运行时 js
│ │ │
│ │ ├─designer //组件设计时目录
│ │ │ │ 组件名.js //组件设计器js
│ │ │ │ 组件名.react.js //React 体系组件设计时 js
│ │ │ │ 组件名.vue //Vue 体系组件设计时 js
│ │ │ │ 组件名.xml //组件模版
│ │ │ │
│ │ │ ├─css //设计时css路径
│ │ │ │ 组件名.css //设计时样式
│ │ │ │
│ │ │ └─img //图片路径
│ │ │ 图片文件 //组件图标
│ │ │
│ │ └─server
│ │ │ model.config.xml //指定依赖 class 的路径
│ │ │
│ │ └─dsrc
│ │ 组件名.java //组件编译时 java 文件
运行时文件
运行时文件包括组件元信息文件、运行时 JS 文件、CSS 文件、xdoc文件
组件元信息文件
组件元信息文件定义了组件的属性、操作、事件、工具栏、引用等信息
详细说明参考《组件元信息文件》
运行时 JS 文件
组件运行时 JS 文件用于在运行的界面上显示组件,完成和用户的交互
- React 体系的运行时 JS 文件的文件名为:组件名.react.js
- Vue 体系的运行时 JS 文件的文件名为:组件名.vue
详细说明参考《组件运行时 JS 文件》
运行时 CSS 目录
存放组件运行时 CSS 文件,CSS 文件内定义组件运行时的样式,需在运行时 JS 文件中引入。 CSS 文件名为:组件名.css
- React 体系中,组件名.react.js 中引用 CSS 文件代码如下
import "./css/组件名.css";
- Vue 体系中,组件名.vue 中引用 CSS 文件代码如下
<style src="./css/组件名.css" />
下面是“描述列表”组件的运行时 CSS 文件,其中的代码用于设置“描述列表项”中的“日期选择”组件不显示后缀图标
.ant-description-item-form-item-control{
/**
日期选择
*/
.ant-picker-suffix {
visibility: hidden;
}
}
运行效果如下图所示
在 IDE 中切换到代码页,查看“描述列表”组件的完整代码
antdpro组件包:$UI/comp/antdpro/components/Descriptions
antdv组件包:$UI/comp/antdv/components/Descriptions
组件注释文件
组件注释文件的文件名为组件名.js.xdoc。该文件简要描述组件使用场景、使用方法、操作、方法等内容
- 使用场景和使用方法会显示在组件面板中,当鼠标悬停在该组件的时候以气泡方式弹出显示
- 组件提供了操作、方法时,需在此文件中声明组件的操作、方法等信息,用于模型检查
xdoc 文件中有一个组件描述,若干个操作、方法说明。
- 组件描述
组件描述代码如下,其中 \
/**
@name $UI/comp/命名空间/components/组件名/组件名
@label 组件中文名
@extends $UI/comp/命名空间/components/命名空间/命名空间Component
@component
@description
<xmp>
<b>使用场景</b>:描述组件使用场景
<b>使用方法</b>:描述组件使用方法
</xmp>
@model UI2
*/
下面是“抽屉”组件的 xdoc 文件
/**
@name $UI/comp/antdpro/components/Drawer/Drawer
@label 抽屉
@extends $UI/comp/antdpro/components/antdpro/AntdproComponent
@component
@description
<xmp>
<b>使用场景</b>:屏幕边缘滑出的浮层面板。
<b>使用方法</b>:可以配置抽屉的位置,点击“显示”进行内容的设计。
</xmp>
@model UI2
*/
在 IDE 的组件面板中的运行效果,如下图所示
- 组件方法说明
组件方法说明代码如下,其中 @function 描述这一段注释是一个方法说明
/**
@name $UI/comp/命名空间/components/组件名/组件名#方法名
@label 组件方法中文名
@extends $UI/comp/命名空间/components/命名空间/命名空间Component
@function
@description 方法描述
@model UI2
*/
下面是“抽屉”组件的“显示”方法的说明代码:
/**
@name $UI/comp/antdpro/components/Drawer/Drawer#show
@priority 30
@label 显示
@function
@description 显示
@returns {void}
*/
- 组件操作说明
组件操作说明代码如下,其中 @operation 描述这一段注释是一个操作说明
/**
@name $UI/comp/命名空间/components/组件名/组件名#操作名
@label 组件操作中文名
@extends $UI/comp/命名空间/components/命名空间/命名空间Component
@operation
@description 操作描述
@model UI2
*/
下面是“抽屉”组件的“显示”操作的说明代码:
/**
@name $UI/comp/antdpro/components/Drawer/Drawer#show
@priority 30
@label 显示
@operation
@description 显示
@returns {void}
*/
设计时文件
设计时文件位于组件目录/designer 目录下,包括组件描述文件,设计器 JS 文件,设计时 JS 文件,设计时 CSS 文件
组件描述文件
designer/组件.xml。此文件中描述组件的中文名、英文名、辨识条件、父容器、组件的模板等内容。
- name:命名空间:组件名
- tag-name:组件名
- text:组件中文名
- discriminate-condition:区别其他组件的唯一条件
- parent-range:双击该组件添加到指定父容器,不指定时,默认添加页面选中的组件里。 格式为:命名空间:组件名,命名空间:组件名,,也可以通过“ $UI/wxsys/comps/inVisibleCompContainer/inVisibleCompContainer ” 加到不可视区域中。
- icon-type:指定为 icon 时,表示组件使用字体图标
- icon:组件图标,可以是文件名(文件存放于 designer/img 下)也可以是字体图标的 class,字体图标 class 来源于 $UI/wxsys/comps/icon/css/fonts/common/icons.css
- component-type:可选值为 inVisibleComp|formControl 。
- hasView:标记组件是否有视图,可选值为true|false(使用该属性已知的组件:wx:importExcel、wx:exportExcel 、wx:print)。
- 扩展说明:vue 架构的页面中,有 .js 文件和 data.js,有视图的组件对应的事件写入 .js 中,无视图的组件对应事件写入 data.js 中
- 目前无视图的组件分为如下三类
- wx:model下的所有组件
- 组件描述文件中有 hasView="false"属性
- data系列组件(wx:restData| wx:tableData| wx:tableCustomData| wx:jsonData| wx:serviceData| wx:aggregateData ),因部分 data 组件没在 wx:model 下,固此处单独列出
- events 节点描述的是标准 W3C 事件。组件事件描述在元信息文件中。
templates 节点中的内容描述的是组件默认的 xml 模型。
<elements>
<element name="命名空间:组件名" tag-name="组件名"
text="中文名"
discriminate-condition="辨识条件"
parent-range="指定父容器"
icon-type="icon"
icon="icon icon-android-storage" component-type="inVisibleComp"
>
<!--通用事件-->
<events>
<include path="$UI/wxsys/comps/reactContainer/commonConfig.xml#//react-events-pcx/*" />
</events>
<templates>
<template name="default"><![CDATA[
<!--添加到页面的模版内容-->
]]></template>
</templates>
</element>
</elements>
例如:“抽屉组件”的 xml 。
<?xml version="1.0" encoding="UTF-8"?>
<elements>
<element name="antdpro:Drawer" tag-name="Drawer"
text="抽屉"
discriminate-condition="executeXpath(name()='antdpro:Drawer')"
parent-range="$UI/wxsys/comps/inVisibleCompContainer/inVisibleCompContainer"
icon-type="icon"
icon="icon icon-android-storage" component-type="inVisibleComp"
design-view="web-designer" d_resiable="false" resizable="false">
<events>
<include path="$UI/wxsys/comps/reactContainer/commonConfig.xml#//react-events-pcx/*" />
</events>
<templates>
<template name="default"><![CDATA[
<antdpro:Drawer xmlns:antdpro="$UI/comp/antdpro/components" xmlns:attr="http://www.wex5.com/attr" >
<attr:title class="title" d_canAddChild="true" designer_label="标题" >
<span>标题</span>
</attr:title>
</antdpro:Drawer>
]]></template>
</templates>
</element>
</elements>
- designer/img 存放组件图片的路径
设计器 JS 文件
设计器 JS 文件名为组件名.js。该文件使用 AMD 规范,结合组件元信息文件完成设计器的渲染和操作逻辑。引用组件元信息文件、设计时 JS 和 CSS 文件。在组件元信息文件中提到的方法,在本文件中定义。
define(function(require) {
//引用命名空间设计时基类 antdproComponent
var AntdproComponent = require("../../命名空间/designer/命名空间Component");
//React 体系引用 reactLibrary 下的 componentsConfig
var componentsConfig = require("babel!$UI/wxsys/comps/reactContainer/designer/reactLibrary/componentsConfig");
//Vue 体系引用 vueLibrary 下的 componentsConfig
var componentsConfig = require("babel!$UI/wxsys/comps/reactContainer/designer/reactLibrary/componentsConfig");
//引用组件的元信息文件
var 组件名Meta = require("text!../组件名.meta.json");
componentsConfig.defineMeta(组件名Meta);
//引用设计时工具类,读取元信息文件
var designerUtils = require("babel!$UI/wxsys/comps/reactContainer/designer/designerUtils");
//子元素被单独取属性后渲染,子元素只作为属性提供(非必须),如:Table、Select、CellGroup、Tabs、Form 等
componentsConfig.changeChildrenLocationComponent.push("命名空间:组件名");
//组件不会渲染或渲染不对,需要依赖父渲染(非必须),如 From.Item、Table.Column、Select.Option
componentsConfig.dependParentReRenderComponent.push("命名空间:组件名");
//引用组件设计时 CSS 文件(非必须)
require("css!./css/Drawer").load();
//引用组件设计时 JS 文件(非必须)
命名空间BaseComponent["组件名"] = require("babel!./组件名.react");
var 组件名 = 命名空间Component.extend({
init: function (value, bindingContext) {
//组件初始化
//获取xml模型
this.ownerDesigner.dataModel.getModelElement($(this.domNode).attr("d_id"))
},
getDesignDefaultProps: function (instance) {
//用户设置的属性会覆盖在此设置的属性,初始值时一个空对象
},
getDesignProps: function (instance) {
//在此设置的属性会覆盖用户设置的属性,初始值时上次渲染设置的props对象
},
propertyChangedHandler : function(key, oldVal, value) {
//用户设置属性发生改变时的回调
},
eventChangedHandler : function(key, oldVal, value) {
//用户设置事件发生改变时的回调
},
reRender : function() {
//重绘
},
attrToolbarVisibleCondition : function (contextInfo) {
//在此可以设置工具栏上的按钮的显示隐藏。搭配元信息文件中的 enable-condition 或 visible-condition 使用。
},
// 元信息文件中 toolbar 的自定义方法在此定义
....
});
var ideConfig = {
"命名空间:组件名": designerUtils.initIdeConfig(组件名, 组件名Meta)
}
return {
"命名空间:组件名": 组件名,
webIdeEx: ideConfig,
quickIdeEx: ideConfig
}
})
下面是“加载中”组件的设计器 JS 文件,引用了元信息文件和样式文件,代码如下
define(function(require){
var AntdproComponent = require("../../antdpro/designer/AntdproComponent");
var componentsConfig = require("babel!$UI/wxsys/comps/reactContainer/designer/reactLibrary/componentsConfig");
var SpinMeta = require("text!../Spin.meta.json");
componentsConfig.defineMeta(SpinMeta);
var designerUtils = require("babel!$UI/wxsys/comps/reactContainer/designer/designerUtils");
require("css!./css/Spin").load();
var Spin = AntdproComponent.extend({
});
var ideConfig = {
"antdpro:Spin": designerUtils.initIdeConfig(Spin, SpinMeta)
}
return {
"antdpro:Spin": Spin,
webIdeEx: ideConfig,
quickIdeEx: ideConfig,
};
});
在“弹出层”组件中定义了工具栏按钮,代码如下
{
"Modal": {
"toolbar": [
{
"text": "显示",
"method": "show"
}
]
}
}
“弹出层”组件的设计器 JS 文件中定义了工具栏按钮使用的 show 方法,代码如下
define(function(require) {
var Modal = AntdproComponent.extend({
show: function () {
this.changeVisible(true);
},
changeVisible: function (flag) {
var self = this;
this.setState({ open: flag }, function () {
self.reRender();
});
}
});
var ideConfig = {
"antdpro:Modal": designerUtils.initIdeConfig(Modal, ModalMeta)
}
return {
"antdpro:Modal": Modal,
webIdeEx: ideConfig,
quickIdeEx: ideConfig
}
})
设计时 JS 文件
组件设计时 JS 文件用于在 IDE 的设计界面上显示组件,完成和开发者的交互
- React 体系的设计时 JS 文件的文件名为:组件名.react.js
- Vue 体系的设计时 JS 文件的文件名为:组件名.vue
详细说明参考《组件设计时 JS 文件》
设计时 CSS 目录
存放组件设计时 CSS 文件,CSS 文件内定义组件设计时的样式,需在设计器 JS 文件中引入。 CSS 文件名为:组件名.css
下面是“卡片”组件的设计时 CSS 文件,其中的代码用于选择卡片的扩展区
[componentname="antdpro:Card"] {
display: block;
}
/**设置卡片的扩展区大小,以便用户在设计器上可以选择该扩展区 */
[componentname="attr:extra"] {
min-width: 48px;
min-height: 24px;
display: inline-block;
}
编译时文件
编译时文件是一个动态 java 文件,位于 server/dsrc/组件名.java。
页面的运行时文件,是通过 Java dom4j 从 .w 模型文件编译生成的。通过定义编译时 java 文件,可以增加或减少组件的属性。平台底层已封装了通用的编译逻辑,仅当通用逻辑无法满足用户需求的时候,才需编写该文件来完成页面编译,绝大多数场景不用写。
例如:“表单项组件”,要从“表单项”里获取到“输入框”等表单组件上的 bind:ref 信息来完成表单项上显示校验提示信息的能力,就需要编写此文件。
public class FormItem extends AntdproComponent{
//如果没有bind:ref, validateInfo找子节点中的bind:ref, 用来显示错误信息
/**
@param bound 组件xml模型
*/
public void beforeDoExeucte(Element bound, JSONObject props, Map<String, Object> context, WXComponentContext wxContext){
if (!hasRef(bound)){
String ref = getRefFromChildren(bound);
if (SystemUtils.isNotEmptyString(ref)) {
bound.addAttribute(WXUtils.BIND_REF, ref);
}
}
super.beforeDoExeucte(bound, props, context, wxContext);
}
private String getRefFromChildren(Element e) {
if (e != null) {
String[] attrs = new String[] {"ref", "beginref", "endref", "beginRef", "endRef"};
for (String attr : attrs) {
String value = e.attributeValue(new QName(attr, WXUtils.BIND_NAMESPACE));
if (SystemUtils.isNotEmptyString(value)) {
return value;
}
}
}
List children = e.elements();
if (children != null) {
for (Object child : children) {
Element c = (Element) child;
String bindItemValue = c.attributeValue(new QName("items", WXUtils.BIND_NAMESPACE));
if (SystemUtils.isNotEmptyString(bindItemValue)) {
continue;
}
String value = getRefFromChildren((Element)child);
if (SystemUtils.isNotEmptyString(value)) {
return value;
}
}
}
return null;
}
}
例如:“日期范围选择”组件的“开始时间”和“结束时间”属性。
{
"DatePicker.RangePicker": {
"properties": {
"bind:beginRef": {
"type": "dataRef",
"label": "开始时间",
"required": "true"
},
"bind:endRef": {
"type": "dataRef",
"label": "结束时间",
"required": "true"
}
}
}
}
public class DatePickerRangePicker extends AntdproComponent {
public void processRef(Element bound, String ref, String prefix, Map<String, Object> context, WXComponentContext wxContext) {
// 处理bind:ref相关逻辑
String refValue = attributeValueAndRemoveAttr(bound, new QName(ref, WXUtils.BIND_NAMESPACE));
String dataId = this.getDataIdExprByRef(refValue, context, wxContext);
bound.addAttribute(prefix + "RefDataId", dataId);
String columnName = this.getColumnByRef(refValue);
bound.addAttribute(prefix + "RefColumnName", columnName);
String row = this.getRowByRef(refValue);
row = DataBindUtils.parseDataBind("{{" + row + "}}",
(Set<String>) this.context.get(ComponentContext.RENDER_VARS), new Stack<String>(),
DataBindUtils.ATTR_BIND, "", new ArrayList<String>(), new ArrayList<String>(), false);
bound.addAttribute(prefix + "RefRow", row);
}
public JSONObject doExecute(Element bound, Map<String, Object> context, WXComponentContext wxContext) {
JSONObject props = super.doExecute(bound, context, wxContext);
this.processRef(bound, "beginRef","begin", context, wxContext);
this.processRef(bound, "endRef","end", context, wxContext);
return props;
}
}
编译前
<antdv:DatePicker.RangePicker bind:beginRef="restData0.current.ksrq" bind:endRef="restData0.current.jsrq" id="datePickerRangePicker0">
</antdv:DatePicker.RangePicker>
编译后
<DatePicker.RangePicker id="datePickerRangePicker0" beginRefDataId="restData0" beginRefColumnName="ksrq" beginRefRow={((_exRun('restData0.current','restData0'))(restData0))} endRefDataId="restData0" endRefColumnName="jsrq" endRefRow={((_exRun('restData0.current','restData0'))(restData0))}>
</DatePicker.RangePicker>
组件注册文件
组件注册文件的名称为:组件包名.components.xml。组件包下所有的组件,需要在此文件中注册后,才能使用。
<reg-info>
<!-- 组件目录注册 -->
<component-dirs framework="组件使用技术体系">
组件名1,组件名2...
</component-dirs>
<uixide-toolbox framework="组件使用技术体系">
<catalog name="分类名" order="分类排序">
<item component-name="命名空间:组件名" device="桌面端/移动端"/>
</catalog>
</uixide-toolbox>
</reg-info>
其中
- component-dirs:列举出要注册的组件
- framework:表示该组件包中的组件所使用的技术体系,如:React 或 Vue
- uixide-toolbox:组件面板注册信息
- catalog:组件面板上的分类
- name:分类名
- order:分类序号
- item:注册的组件
- component-name:命名空间:组件名
- device:pcx 表示桌面端,mx 表示移动端,不填表示两端皆可用
- catalog:组件面板上的分类
下面是 antdpro 组件包的注册文件中的部分代码
<reg-info>
<!-- 组件目录注册 -->
<component-dirs framework="react">
Button,Icon
</component-dirs>
<uixide-toolbox framework="react">
<catalog name="内容" order="3">
<item component-name="antdpro:Button" device="pcx" />
<item component-name="antdpro:Icon" device="pcx" />
</catalog>
</reg-info>
配置目录
配置目录 config 包括设计器配置文件 studio.xml,用于标识组件注册文件所在路径。设计器配置文件代码如下
<?xml version="1.0" encoding="utf-8"?>
<config>
<!-- 组件注册文件 -->
<component-reg-file>../../components/*.components.xml</component-reg-file>
</config>
其他文件简介
组件包目录下还有几个文件,介绍如下
文件名 | 用途 | 说明 |
---|---|---|
.comp.dist | 标志文件 | 说明该组件是只读的组件,修改组件文件需要定制组件 |
.comp.save | 标志文件 | 说明该组件是可读写的组件,定制组件后,删除 .comp.dist 文件,增加 .comp.save 文件 |
.framework.json | 标志文件 | 标识组件使用的端以及技术体系 |
.restart | 标志文件 | 说明组件从市场下载后,需要重启 |
命名空间.meta | 描述文件 | 记录当前组件包是由哪个组件模版创建出来的。再次新建组件时,会根据文档信息拷贝相应模板资源,完成组件的创建 |
webpack.antdpro.dll.config.js | 配置文件 | webpack打包dll配置文件 |
pre-upload.sh | 脚本文件 | 组件发布前,在这个生命周期执行自定义命令 |
服务端目录
组件包的服务端目录相当于一个服务模块,可定义数据集和服务。在组件包中添加数据集和服务,参考《数据模型》和《服务模型》
特别说明一下两点
切换到组件开发端,添加组件包的页面、数据集和服务
在前端访问后端服务模型,使用页面对象的 request 和 getServiceUrl 方法,代码如下
let $page = this.getPage();//React 获取页面对象
let $page = usePage();//Vue 获取页面对象
$page.request({
url: $page.getServiceUrl("/sn/sn/nextbydate"),
data: {
key: key
}
}).then(function(data){
if ((data.statusCode >= 200) && (data.statusCode < 300)){
data = data.data;
self.nextByDateCache[key] = data;
resolve(data);
}else{
reject("获取序号出错");
}
});