# CAP4自定义控件后端规范

# 1、自定义控件定义

在CAP4表单中,除了致远提供的标准功能控件外,第三方开发可以通过自定义控件方案实现第三方的控件,这类控件我们统称自定义控件。

# 2、自定义控件设计方案

# 2.1 总体设计方案

自定义控件由第三方开发,开发好之后上传致远商城,V5客户的应用设计师登录V5之后,在CAP4表单制作页面左侧会有自定义控件管理页面,V5客户可以在扩展控件列表页面从商城下载第三方开发的控件到V5安装环境中进行使用,模式如下图:

![[custom-custom-serverside-1.28c39a4.png|custom-custom-serverside-1.28c39a4.png]]

![[custom-custom-serverside-2.c4506d6.png|custom-custom-serverside-2.c4506d6.png]]

备注:V5用户下载自定义控件只是将控件的包下载到了V5环境的共享目录中,在下次重启的时候才会将自定义控件包解压进行自动部署。所以自定义控件下载之后需要重启生效。

# 2.1.1 插件/component开发说明

第三方开发的自定义控件对于V5来说其实是一个插件或者component(plugin,致远内部开发自定义控件可以不用单独创建插件,相关配置可以放到控件相关功能的插件配置中),开发自定义控件将严格接受V5插件开发规范。 (更多V5插件开发请见http://open.seeyon.com/book/ctp/sdk/ctpbackendspec.html#插件化 (opens new window)

# 2.1.2 自定义控件项目目录结构说明

开发自定义控件第一步自然是创建项目,创建项目之后目录结构可以参照以下示例创建各目录,以下目录结构是一个自定义控件的示例(下载完整目录结构)。

─seeyon      固定目录不可修改
├─apps_res     固定目录不可修改
│  └─cap     固定目录不可修改
│  └─customCtrlResources   固定目录不可修改(存放PC端前端资源的目录)
│  └─testCtrlResource
│  │  test.png
│  │
│  ├─html
│  │  param1Setter.html
│  │  param2Setter.html
│  │
│  └─js
├─m3files     固定目录不可修改(存放M3移动端自定义资源包的目录)
│  └─v5      固定目录不可修改
│  3423424234234234234.zip
│
└─WEB-INF     固定目录不可修改
├─cfgHome     固定目录不可修改
│  └─plugin     固定目录不可修改
│  └─testCtrlPlugin
│  │  pluginCfg.xml
│  │
│  ├─i18n
│  │  export_to_js.xml
│  │  test_en.properties
│  │  test_zh_CN.properties
│  │  test_zh_TW.properties
│  │
│  └─spring
│    spring-test-manager.xml
│    spring-test-controller.xml
│
├─classes
│  └─com
│  └─seeyon
│  └─cap4
│  └─form
│  └─bean
│  └─fieldCtrl
│  TestCtrl.class
│
├─jsp     固定目录不可修改
└─lib     固定目录不可修改

# 2.1.3 如何让CAP4表单编辑器中出现你的自定义控件

  • 按照以上示例中的目录结构创建自定义控件项目工程
  • 创建一个java类文件,实现自定义控件抽象类com.seeyon.cap4.form.bean.fieldCtrl.FormFieldCustomCtrl
  • 实现或者重写FormFieldCustomCtrl中的接口,示例(电子发票控件实现类):

`

private static final Log LOGGER = CtpLogFactory.getLog(FormEinvoiceCtrl.class);

/**
  * 返回自定义控件唯一的key,控件key可以使用平台www.seeyon.com.utils.UUIDUtil.getUUIDString()接口 生成一个uuid,将此Id作为控件的key
  * @return
  */
@Override
public String getKey() {
    return "4578843378267869145";
}

/**
  * 获取控件名称
  * @return
  */
@Override
public String getText() {
    return ResourceUtil.getString("com.cap.ctrl.einvoice.text");
}

/**
 * 定义此自定义控件是否支持批量刷新
 *
 * @return
 */
@Override
public boolean canBathUpdate() {
    return false;
}
/**
 * 此控件是否是附件类控件
 *
 * @return
 */
@Override
public boolean isAttachment() {
    return true;
}

/**
 * 新建的时候生成此自定义控件的值,此值会存放在对应动态表的对应字段上,一般情况下只有需要上传附件类的自定义控件才需要此接口。
 *
 * @param oldVal
 * @return
 */
@Override
public Object genVal(Object oldVal) {
    if (StringUtil.checkNull(String.valueOf(oldVal))) {
        return UUIDLong.longUUID();
    } else {
        return oldVal;
    }
}

@Override
public List<String[]> getListShowDefaultVal(Integer integer) {
    return null;
}

/**
 * 初始值生成接口
 */
@Override
public String[] getDefaultVal(String s) {
    return new String[0];
}

/**
 * 控件初始化接口,此接口在控件初始化的时候,会调用,主要用于定义控件所属插件id、在表单编辑器中的图标、表单编辑器中有哪些属性可以设置。
 * 使用举例:在接口中定义自定义控件在在表单编辑器中有哪些控件属性需要配置
 */
@Override
public void init() {
    this.setPluginId("formInvoiceBtn");//控件所属插件id
    this.setIcon("cap-icon-e-invoice");//控件在表单编辑器中的图标
    LOGGER.info("自定义控件" + this.getText() + "init执行开始");
    ParamDefinition eivoiceDef = new ParamDefinition();//控件属性设置定义对象
    eivoiceDef.setDialogUrl("apps_res/cap/customCtrlResources/formEinvoiceCtrlResources/html/EinvoiceSetting.html");//控件属性点击之后弹出的设置对话框的url
    eivoiceDef.setDisplay("com.cap.ctrl.einvoice.paramtext");//如果要做国际化 这个地方只能存key
    eivoiceDef.setName("mapping");//控件属性名
    eivoiceDef.setParamType(Enums.ParamType.button);//控件属性类型
    addDefinition(eivoiceDef);
    LOGGER.info("自定义控件" + this.getText() + "init执行结束,params.size:" + super.params.size());
}

/**
 * 定义PC端自定义控件运行态资源注入信息
 * path:文件夹路径
 * jsUri:定义PC端表单运行态加载第三方JavaScript的路径
 * cssUri:定义PC端表单运行态加载第三方CSS的路径
 * initMethod:定义PC端表单运行态第三方js入口方法名称
 * nameSpace:此自定义控件前端运行时的命名空间,可以参照一下写法来定义命名空间
 * @return
 */
@Override
public String getPCInjectionInfo() {
    return "{path:'apps_res/cap/customCtrlResources/formEinvoiceCtrlResources/',jsUri:'js/formEinvoicePcRuning.js',initMethod:'init',nameSpace:'field_" + this.getKey() + "'}";
}

/**
 * 获取移动端自定义控件运行态资源注入信息
 * path:'http://'+m3应用包mainifest.json中的urlSchemes的值+'v'+m3应用包mainifest.json中的version的值
 * weixinpath: 微信端打开的时候使用的m3/apps/v5/自定义控件移动端资源目录名称/,weixinpath配置的就是此自定义控件移动端资源目录名称
 * jsUri:移动端表单运行态加载第三方JavaScript的路径
 * initMethod:定义M3端表单运行态第三方js入口方法名称
 * * nameSpace:定义M3端表单运行态命名空间
 *
 * @return
 */
@Override
public String getMBInjectionInfo() {
    return "{path:'http://einvoice.v5.cmp/v1.0.0/',weixinpath:'invoice',jsUri:'js/formEinvoiceMbRuning.js',initMethod:'init',nameSpace:'field_"+this.getKey()+"'}";

}
/**
 * 定义控件对应数据库所需长度
 * 注意:一旦自定义控件长度定好,上线部署到OA环境中之后,
 * 此接口返回的长度不允许改变,因为上线之后如果有表单使用
 * 了此自定义控件,就会在数据库中创建字段长度为此指定长度
 * 的数据列。
 *
 * @return
 */
@Override
public String getFieldLength() {
    return "25";
}

/**
 * 是否支持套红,自定义控件默认false,如果支持需要重新接口返回true
 *
 * @return
 */
@Override
public boolean canInjectionWord() {
    return false;
}

# 2.1.4 如何让CAP4报表查询配置中出现你的自定义控件

  • 实现或者重写FormFieldCustomCtrl中的接口中的canShowInReport;
  • 实现或者重写FormFieldCustomCtrl中的接口中的convertCtrlValue;
  • 实现或者重写FormFieldCustomCtrl中的接口中的getRenderInfo4Run
@Override
public FormFieldCustomCtrlReportInfo canShowInReport() {
 FormFieldCustomCtrlReportInfo reportInfo = new FormFieldCustomCtrlReportInfo();
 reportInfo.setEnableQuery(true);
 reportInfo.setEnableDisplayField(true);
 reportInfo.setEnableSort(true);
 reportInfo.setEnableFilterField(true);
 return reportInfo;
}

@Override
public Object convertCtrlValue(FormFieldBean fieldBean, Object value) {
 return MapUtils.getString(JSONUtil.parseJSONString((String) value, Map.class), "projectName");
}

@Override
public FormFieldCustomCtrlRunInfo getRenderInfo4Run() {
 FormFieldCustomCtrlRunInfo runInfo = new FormFieldCustomCtrlRunInfo();
 // PC列表
 FormFieldCustomCtrlInjectInfo pcList = new FormFieldCustomCtrlInjectInfo();
 pcList.setPath("apps_res/cap/customCtrlResources/projectRelatedResources/");
 pcList.setNameSpace(getListNameSpace());
 pcList.setJsUri("js/projectRelatedPCList.umd.min.js");
 runInfo.setPcList(pcList);

 // PC普通筛选
 FormFieldCustomCtrlInjectInfo pcFilter = new FormFieldCustomCtrlInjectInfo();
 pcFilter.setPath("apps_res/cap/customCtrlResources/projectRelatedResources/");
 pcFilter.setNameSpace(getFilterNameSpace());
 pcFilter.setJsUri("js/projectRelatedPCFilter.umd.min.js");
 runInfo.setPcFilter(pcFilter);

 // 移动端列表
 FormFieldCustomCtrlInjectInfo mList = new FormFieldCustomCtrlInjectInfo();
 mList.setPath("http://customCtrlResources.v5.cmp/v1.0.0/");
 mList.setWeixinPath("customCtrlResources");
 mList.setNameSpace(getListNameSpace());
 mList.setJsUri("projectRelatedResources/js/projectRelatedMList.umd.min.js");
 runInfo.setMobileList(mList);

 // 移动端普通筛选
 FormFieldCustomCtrlInjectInfo mFilter = new FormFieldCustomCtrlInjectInfo();
 mFilter.setPath("http://customCtrlResources.v5.cmp/v1.0.0/");
 mFilter.setWeixinPath("customCtrlResources");
 mFilter.setNameSpace(getFilterNameSpace());
 mFilter.setJsUri("projectRelatedResources/js/projectRelatedMFilter.umd.min.js");
 runInfo.setMobileFilter(mFilter);

 return runInfo;
}

/**
 * 获取列表区作用域
 *
 * @return
 */
private String getListNameSpace() {
 return getNameSpace("List");
}

/**
 * 获取条件区作用域
 *
 * @return
 */
private String getFilterNameSpace() {
 return getNameSpace("Filter");
}

/**
 * 获取全局命名空间
 *
 * @param uniqueKey
 * @return
 */
protected String getNameSpace(String uniqueKey) {
 return StringUtils.uncapitalize(getClass().getSimpleName()) + uniqueKey + getKey();
}

# 2.1.5 在spring配置文件中配置自定义控件Java类

控件实现类创建好之后需要在spring配置文件中将此类配置一下,由spring管理此类,具体做法是在你的项目中创建如2.1.2中的spring目录和目录下的文件spring-xxx-manager.xml(xxx由第三方自己定义),编辑此xml文件,按照spring规范定义此bean对象即可,例如电子发票控件的spring配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd" default-autowire="byName">
<bean id="formInvoiceBtnCtrl" class="com.seeyon.cap4.form.bean.fieldCtrl.FormEinvoiceCtrl"></bean>
</beans>

# 2.2 自定义控件属性设置开发

# 2.2.1 定义自定义控件有哪些属性

自定义控件在表单编辑器中,需要由开发控件的人自己定义控件有哪些属性,属性由com.seeyon.cap4.form.bean.ParamDefinition进行描述,需要在自定义控件实现类的init方法中进行属性的定义和添加,属性设置分两种表现形式,此处着重描述按钮类型的属性怎么定义: 在自定义控件的init方法中new出一个ParamDefinition类型的对象,调用对象的setDialogUrl方法设置按钮点击之后弹出框的url(如果类型是按钮类的才需要),调用对象的setDisplay方法设置按钮显示的名称,调用setParamType设置属性类型(类型由枚举com.seeyon.cap4.form.util.Enums.ParamType进行描述,button:按钮类型,text文本框类型),最后调用addDefinition将new出来的对象添加到自定义控件属性列表中,示例代码:

ParamDefinition eivoiceDef = new ParamDefinition();
    eivoiceDef.setDialogUrl("apps_res/cap/customCtrlResources/formEinvoiceCtrlResources/html/EinvoiceSetting.html");
    eivoiceDef.setDisplay("com.cap.ctrl.einvoice.paramtext");//如果要做国际化 这个地方只能存key
    eivoiceDef.setName("mapping");
    eivoiceDef.setParamType(Enums.ParamType.button);
    addDefinition(eivoiceDef);

# 2.2.2 如何开发自定义控件属性设置页面

如果是弹出框类型的属性,需要开发弹出框所需的页面,在页面中定义一个名称为ok的js方法,此方法返回点击确定之后所需要保存的设置信息,在表单编辑器保存表单定义的时候,CAP会将自定义控件属性的定义信息保存到cap_form_definition表的field_info对应字段的customParam属性中,field_info是json格式。

function OK(){
    var mapping = [];
    var settings = $("#systemMappingArea").find(".biz_groupguanlian");
    var checkTag = true;
    for(var i=0;i<settings.length;i++){
        var setting = $(settings[i]);
        var temp = {};
        var einvoicefield = setting.find(".leftField").find("option:selected").attr("fieldtype");
        var fieldname = setting.find(".rightField").find("option:selected").attr("fieldname");
        if(einvoicefield===undefined||fieldname===undefined){
            top.$.alert("选项不能为空!");
            checkTag = false;
            break;
        }
        temp.source = einvoicefield;
        temp.target = fieldname;
        mapping.push(temp);
    }
    if(checkTag){
        var hasSame = false;
        var sameFieldName = "";
        //校验所选是否满足规则
        //先校验右侧同一个数据域是否出现多次
        for(var j=0;j<mapping.length;j++){
            var current1 = mapping[j];
            for(var k=0;k<mapping.length;k++){
                var current2 = mapping[k];
                if(current1.source!=current2.source && current1.target===current2.target){
                    hasSame = true;
                    sameFieldName = current1.target;
                    break;
                }
            }
            if(hasSame){
                break;
            }
        }
        //再校验左右是否类型匹配
        if(hasSame){
            checkTag = false;
            var sameField = getFieldInfoByCurrentField(initObj.currentfield.name,sameFieldName);
            top.$.alert(sameField.display+"[右侧数据域]出现多次!");
        }else{
            var errorMsg = "";
            var formId = initObj.formBaseInfo.formBaseInfo.formInfo.id;
            $.ajax({ url: "/seeyon/rest/cap4/formEinvoice/checkMapping",
                async:false,
                type: 'POST',
                dataType:'json',
                contentType : 'application/json;charset=UTF-8',
                data: JSON.stringify({"formId":formId,"datas":mapping}),
                success: function(data){
                    if(data.data.result=="true"){
                        checkTag = true;
                    }else{
                        checkTag = false;
                        errorMsg = data.data.errorMsg;
                    }
                }
            });
            if(!checkTag){
                top.$.alert(errorMsg);
            }
        }
    }
    return  {valid:checkTag,data:mapping};
}

表单保存之后数据库cap_form_definition中field_info字段截图:

![[custom-custom-serverside-4.98fdbe5.png|custom-custom-serverside-4.98fdbe5.png]]

  • 显示为一个按钮,点击之后是弹出框形式,如图:
  • ![[custom-custom-serverside-3.366f799.png|custom-custom-serverside-3.366f799.png]]
  • 显示为一个文本框

# 2.2.2 自定义控件前端运行态渲染开发

CAP4表单自定义控件前端渲染分为PC端和移动端(M3或者微协同统称移动端),CAP4平台会将您渲染自定义控件的js文件动态注入到页面中。您需要通过2.1.3中所描述的getPCInjectionInfo和getMBInjectionInfo接口来分别定义您自定义控件在PC和移动端渲染的js路径。表单在运行态调用的时候,会主动调用您js文件中的初始化方法,您需要在初始化方法中实现您自定义控件的渲染以及事件绑定等操作。如下为表单电子发票控件的PC端js文件代码:

(function (ctx,factory) {
    var nameSpace = 'field_4578843378267869145';
    if(!window[nameSpace]){
        var Builder = factory(ctx,nameSpace);
        window[nameSpace] = {
            instance: {}
        };
        window[nameSpace].init = function (options) {
            window[nameSpace].instance[options.privateId] = new Builder(options);
    };
    }
})(typeof $ === 'undefined' ? top.$ : $,function($,domain){

    function App(options) {
        var self = this;
        self.appendChildDom = function (adaptation, messageObj, privateId, getData) {
            self.customButton(adaptation, messageObj, privateId, getData);
        };
        self.customButton = function (adaptation, messageObj, privateId, getData) {

            if (adaptation) {
                self.adaptation = adaptation;
            }
            //----------创建DOM---------
            var domStructure = '';
            var box = document.querySelector('#' + privateId);
            if(!box) {
                console.warn('未找到控件dom');
                return;
            }
            if (getData.auth === 'browse') {
                domStructure = '<section class="cap4-images is-one "><div><div class="cap4-images__cnt" style="border:none;"><div class="cap4-images__items"><div class="cap4-images__holder"></div></div></div></div></section>';
            }else if (getData.auth === 'hide') {
                box.style.display="none";
                return;
            }else {
                domStructure = '<section class="cap4-images is-one "><div><div class="cap4-images__cnt"><div class="cap4-images__items"><div class="cap4-images__holder"></div></div><div class="cap4-images__picker upload ' + privateId + '"><div class="icon CAP cap-icon-e-invoice"></div></div></div></div></section>';
            };
            box.innerHTML = domStructure;
            if (self.getData.attachmentInfo.attachmentInfos && self.getData.attachmentInfo.attachmentInfos.length > 0) {
                var filename=encodeURIComponent(self.getData.attachmentInfo.attachmentInfos[0].filename);
                if (getData.auth === 'browse') {
                    box.querySelector(".cap4-images__items").innerHTML = '<div style="cap4-images__it"><a style="display: block;max-height: 20px;" target="myFormIframe" href="/seeyon/fileDownload.do?method=download&fileId=' + self.getData.attachmentInfo.attachmentInfos[0].fileUrl + '&v=' + self.getData.attachmentInfo.attachmentInfos[0].v + '&createDate=' + self.getData.attachmentInfo.attachmentInfos[0].createdate + '&filename=' +filename + '"title="' + filename + '">'+self.getData.attachmentInfo.attachmentInfos[0].filename + '</a></div>';
                } else {
                    box.querySelector(".cap4-images__items").innerHTML = '<div style="cap4-images__it"><a style="display: block;max-height: 20px;" target="myFormIframe" href="/seeyon/fileDownload.do?method=download&fileId=' + self.getData.attachmentInfo.attachmentInfos[0].fileUrl + '&v=' + self.getData.attachmentInfo.attachmentInfos[0].v + '&createDate=' + self.getData.attachmentInfo.attachmentInfos[0].createdate + '&filename=' +filename + '"title="' + filename + '">' +self.getData.attachmentInfo.attachmentInfos[0].filename + '</a><div class="cap4-images__close"><i class="icon CAP cap-icon-guanbi delete"></i></div></div>';
                }
                box.querySelector('.upload') && (box.querySelector('.upload').style.display = "none");
                if (box.querySelector('.delete')) {
                    box.querySelector('.delete').addEventListener('click', function () {
                        var field = box.querySelector(".cap4-images__items");
                        field.removeChild(field.childNodes[0]);
                        box.querySelector('.upload') && (box.querySelector('.upload').style.display = "");
                        self.aId = self.getData.attachmentInfo.attachmentInfos[0].id;
                        if (self.adaptation.backfillFormAttachment) {
                            var attachmentData = [
                                {
                                    tableName: self.formMessage.tableName,
                                    tableCategory: self.formMessage.tableCategory,
                                    updateRecordId: self.getData.recordId || '',
                                    handlerMode: 'delete',
                                    fieldName: self.getData.id,
                                    deleteAttchmentData: [self.aId]
                                }
                            ];
                            self.adaptation.backfillFormAttachment(JSON.parse(JSON.stringify(attachmentData)), privateId);
                }
                    });
                }
            }else{
                if (getData.auth === 'edit'){
                    box.querySelector(".cap4-images__cnt").style.background=(messageObj.isNotNull=='1'?'#fef0d0':'');
                }
            }
            if (document.querySelector('.' + privateId)) {
                document.querySelector('.' + privateId).addEventListener('click', function () {
                    if(self.messageObj.customFieldInfo.customParam==null){
                        top.$.alert("请联系应用设计师,在表单编辑器中设置电子发票控件的属性映射!");
                        return;
                    }
                    var opts = {
                        type: 0,
                        applicationCategory: 66,
                        maxSize: '',
                        isEncrypt: '',
                        popupTitleKey: '',
                        attachmentTrId: 'Att',
                        takeOver: 'false',
                        firstSave: 'true',
                        quantity: 1,
                        extensions: 'pdf'
                    };
                    //文件上传成功后
                    self.uploadFile(opts, function (fileurls, atts) {
                        if (atts && atts.length > 0) {
                            var att = atts;
                            att[0].refefence = self.getData.attachmentInfo.baseAttachmentInfo.reference;
                            att[0].subReference = self.getData.attachmentInfo.baseAttachmentInfo.subReference;
                            self.att = att;
                            box.querySelector(".cap4-images__items").innerHTML = '<div style="cap4-images__it"><a title="'+atts[0].filename+'" style="display: block;max-height: 20px;" target="myFormIframe" href="/seeyon/fileDownload.do?method=download&fileId='+self.att[0].fileUrl+'&v='+self.att[0].v+'&createDate='+self.att[0].createDate+'&filename='+encodeURIComponent(self.att[0].filename)+'">'+atts[0].filename+'</a><div class="cap4-images__close"><i class="icon CAP cap-icon-guanbi delete"></i></div></div>';
                            //icon botton
                            box.querySelector(".cap4-images__cnt").style.background='';
                            box.querySelector('.upload').style.display = "none";
                            self.aId = atts[0].id;
                            box.querySelector('.delete').addEventListener('click', function () {
                                var field = box.querySelector(".cap4-images__items");
                                field.removeChild(field.childNodes[0]);
                                box.querySelector('.upload').style.display = "";
                                box.querySelector(".cap4-images__cnt").style.background=(messageObj.isNotNull=='1'?'#fef0d0':'');
                                if (self.adaptation.backfillFormAttachment) {
                                    var attachmentData = [
                                        {
                                            tableName: self.formMessage.tableName,
                                            tableCategory: self.formMessage.tableCategory,
                                            updateRecordId: self.getData.recordId || '',
                                            handlerMode: 'delete',
                                            fieldName: self.getData.id,
                                            deleteAttchmentData: [self.aId]
                                        }
                                    ];
                                    //backfillFormAttachment(向数据库删除需要删除的内容)
                                    self.adaptation.backfillFormAttachment(JSON.parse(JSON.stringify(attachmentData)),privateId);
                                }
                            });
                            //rest
                            var content = self.messageObj.formdata.content;
                            var params = {
                                formId: content.contentTemplateId,
                                rightId: content.rightId,
                                fieldName: getData.id,
                                fileId: atts[0].fileUrl,
                                masterId: content.contentDataId,
                                subId: getData.recordId || '0'
                            };
                            var process = top.$.progressBar({
                                text: "解析中"
                            });
                            $.ajax({
                                type: 'get',
                                url: (_ctxPath ? _ctxPath : '/seeyon') + "/rest/cap4/formEinvoice/parseEinvoiceFileAndFillBack?" + self.parseParam(params),
                                dataType: 'json',
                                contentType: 'application/json',
                                beforeSend: function () {

                                },
                                success: function (res) {
                                    process.close();
                                    if (res.success || res.code == "0") {
                                        if (self.adaptation.backfillFormControlData) {
                                            var backfillData = [];
                                            var backfillItem = {
                                                tableName: self.formMessage.tableName,
                                                tableCategory: self.formMessage.tableCategory,
                                                updateData: {},
                                                updateRecordId: ''
                                            }
                                            if (self.countProperties(res.data) === 0) {
                                                return;
                                            } else {
                                                var key = [];
                                                for (var k in res.data) {
                                                    if (k.split('_').length > 1) {
                                                        key.push(k.split('_')[1]);
                                                    }
                                                }

                                                if (key.length == 0) {
                                                    backfillItem.updateData = res.data;
                                                    backfillData.push(backfillItem);
                                                } else {
                                                    if (self.removal(key).length === 1) {
                                                        if (self.countProperties(res.data) === key.length) {
                                                            backfillItem.updateData = res.data;
                                                            backfillItem.updateRecordId = key[0];
                                                            backfillData.push(backfillItem);
                                                        } else {
                                                            var mTable = {};
                                                            var sTable = {};
                                                            for (p in res.data) {
                                                                if (p.split('_').length === 2 && p.split('_')[1] === key[0]) {
                                                                    sTable[p] = res.data[p];
                                                                } else {
                                                                    mTable[p] = res.data[p];
                                                                }
                                                            }
                                                            backfillData.push({
                                                                updateData: mTable,
                                                                tableName: self.formMessage.tableName,
                                                                tableCategory: self.formMessage.tableCategory,
                                                            });
                                                            backfillData.push({
                                                                updateData: sTable,
                                                                tableName: self.formMessage.tableName,
                                                                tableCategory: self.formMessage.tableCategory,
                                                                updateRecordId: key[0]
                                                            });
                                                        }
                                                    } else {
                                                        var len = removal(key);

                                                        for (var i = 0; i <= len.length; i++) {
                                                            var tableData = {};
                                                            for (p in res.data) {
                                                                if (p.split('_').length === 2) {
                                                                    if (p.split('_')[1] === len[i]) {
                                                                        tableData[p] = res.data[p];
                                                                    }
                                                                } else {
                                                                    if (i === len.length) {
                                                                        tableData[p] = res.data[p];
                                                                    }
                                                                }
                                                            }
                                                            backfillData.push({
                                                                updateData: tableData,
                                                                tableName: self.formMessage.tableName,
                                                                tableCategory: self.formMessage.tableCategory,
                                                                updateRecordId: len[i] || ''
                                                            });
                                                        }
                                                    }
                                                }
                                            }
                                            if (backfillData && backfillData.length > 0) {
                                                for (var i = 0; i < backfillData.length; i++) {
                                                    for (var k in backfillData[i].updateData) {
                                                        if (k.split('_').length > 1) {
                                                            backfillData[i].updateData[k.split('_')[0]] = backfillData[i].updateData[k];
                                                            delete backfillData[i].updateData[k];
                                                        } else {
                                                            backfillData[i].updateData[k] = backfillData[i].updateData[k];
                                                        }
                                                    }
                                                }
                                            }
                                            //backfillFormControlData(回填电子发票相关联的其它控件的值)
                                            self.adaptation.backfillFormControlData(backfillData, privateId);
                                        }
                                        if (self.adaptation.backfillFormAttachment) {
                                            var attachmentData = [
                                                {
                                                    tableName: self.formMessage.tableName,
                                                    tableCategory: self.formMessage.tableCategory,
                                                    updateRecordId: self.getData.recordId || '',
                                                    handlerMode: 'add',
                                                    fieldName: self.getData.id,
                                                    addAttchmentData: self.att
                                                }
                                            ]
                                            //backfillFormAttachment(向数据库添加新建的内容)
                                            self.adaptation.backfillFormAttachment(JSON.parse(JSON.stringify(attachmentData)), privateId);
                                        }
                                    } else {
                                    }
                                },
                                complete: function () {
                                    process.close();
                                },
                                error:function (errorMsg) {
                                    var retObj = $.parseJSON(errorMsg.responseText);
                                    top.$.alert(retObj.message);
                                    process.close();
                                }
                            });
                        }

                    });
                });
            }
        }
        self.download = function () {

        }
        self.removal = function (arr) {
            var res = [];
            var json = {};
            for (var i = 0; i < arr.length; i++) {
                if (!json[arr[i]]) {
                    res.push(arr[i]);
                    json[arr[i]] = 1;
                }
            }
            return res;
        }

        self.countProperties = function (obj) {
            var count = 0;
            for (var property in obj) {
                if (Object.prototype.hasOwnProperty.call(obj, property)) {
                    count++;
                }
            }
            return count;
        }
        //定义上传文件接口
        self.uploadFile = function (options, callback) {
            self.parseParam = function (param, key) {
                var paramStr = "";
                if (param instanceof String || param instanceof Number || param instanceof Boolean) {
                    paramStr += "&" + key + "=" + encodeURIComponent(param);
                } else {
                    $.each(param, function (i) {
                        var k = key == null ? i : key + (param instanceof Array ? "[" + i + "]" : "." + i);
                        paramStr += '&' + self.parseParam(this, k);
                    });
                }
                return paramStr.substr(1);
            }

            options.CSRFTOKEN = top.CsrfGuard.getToken();
            var url = (_ctxPath ? _ctxPath : '/seeyon') + '/fileUpload.do?callMethod=_dinvoiceUploadFileCallback&' + self.parseParam(options);
            getCtpTop().addattachDialog = null;
            getCtpTop().addattachDialog = getCtpTop().$.dialog({
                title: $.i18n('fileupload.page.title'),
                transParams: {
                    parentWin: window
                },
                url: url,
                width: 400,
                height: 250
            });

            window._dinvoiceUploadFileCallback = function (att, repeat) {
                var atts = null;
                atts = att;

                if (atts && atts.instance.length) {
                    //处理一下返回的数据,拼接一下url,统一修改
                    atts.instance.forEach(function (item) {
                        var opts = {
                            createDate: item.createDate.split(' ')[0],
                            fileId: item.fileUrl
                        };
                        var url = (_ctxPath ? _ctxPath : '/seeyon') + '/fileUpload.do?method=showRTE&type=image&showType=big&' + self.parseParam(opts);
                        item.src = url; //增加src属性,写入url
                    });
                    callback(null, atts.instance);
                } else {
                    callback(null, []);
                }
            }
        }
        self.initParams(options);
    }

    App.prototype.initParams = function (res) {
        var self = this;
        var adaptation = res.adaptation;
        var url_prefix = res.url_prefix;
        var privateId = res.privateId;
        self.formMessage = res.formMessage;
        self.getData = res.getData;
        var dataObj = adaptation.childrenGetPrivateMessage(privateId);
        self.messageObj = adaptation.childrenGetData(privateId);
        self.appendChildDom(adaptation, self.messageObj, privateId, res.getData);

        // 监听是否数据刷新
        adaptation.ObserverEvent.listen('Event' + privateId, function () {
            //self.messageObj = adaptation.childrenGetData(privateId);
            //self.appendChildDom(adaptation, self.messageObj, privateId);
        });
    };
    return App;
});

![[custom-custom-serverside-5.542f425.png|custom-custom-serverside-5.542f425.png]]

更多运行态相关说明请见:自定义控件-前端 (opens new window)

# 2.2.3 自定义控件在CAP4报表查询中有哪些属性

自定义控件在CAP4报表查询中,需要由开发控件的人自己定义控件有哪些属性,属性由com.seeyon.cap4.form.bean.fieldCtrl.FormFieldCustomCtrlReportInfo进行描述,需要在自定义控件实现类的canShowInReport方法中进行属性的定义和添加,具体属性设置如下表:

属性名 说明
enableQuery 是否在查询中使用,如果为false的话不在查询中显示
enableDisplayField 是否支持数据字段设置
enableFormulaField 是否支持公式列
enableSort 是否支持排序设置
enableFilterField 是否支持筛选条件-普通条件

以关联项目自定义控件为例:

@Override
public FormFieldCustomCtrlReportInfo canShowInReport() {
 FormFieldCustomCtrlReportInfo reportInfo = new FormFieldCustomCtrlReportInfo();
 reportInfo.setEnableQuery(true);
 reportInfo.setEnableDisplayField(true);
 reportInfo.setEnableSort(true);
 reportInfo.setEnableFilterField(true);
 return reportInfo;
}

我们在查询配置中数据字段设置/筛选条件设置,就可以看到这个自定义控件[项目关联1]:

![[query_displayFields.png|query_displayFields.png]]

查询数据字段设置

![[query_filterFields.png|query_filterFields.png]]

查询筛选条件设置

# 2.2.4 自定义控件查询前端运行态渲染开发

# 2.2.4.1查询列表

对于如何将数据库字段中存放的类似{"projectName":"项目1","id":"-8312506844008854456"}这种格式的数据在查询列表中正常显示出来,有两种方案:

  • 覆写接口中的convertCtrlValue方法将字段值转换为显示值,查询前端默认会以文本的格式显示出来;
  • 覆写接口中的convertCtrlValue方法和getRenderInfo4Run方法,并开发自己的列表插槽,具体可参看自定义控件(插槽)

![[query_table.png|query_table.png]]

# 2.2.4.2 筛选条件

自定义控件条件支持的操作类型

操作类型 说明
Equal 等于
NotEqual 不等于
Like 字符串模糊匹配包含
NotLike 字符串模糊匹配不包含

对于筛选条件的开发示例可以具体参考自定义控件(筛选条件)

![[query_filter_1.png|query_filter_1.png]]

普通筛选条件

![[query_filter_2.png|query_filter_2.png]]

筛选条件参数格式

筛选后结果如下:

![[query_filter_3.png|query_filter_3.png]]

筛选结果

# 3、自定义控件DEMO下载 (opens new window)(本demo以表单电子发票这个自定义控件为例):

  • apps目录中是移动端相关文件源码,自定义控件移动端采用H5技术;
  • src中是server端以及pc设置态相关源码;
    创建人:yinyanting