Skip to content

374933282/ui-library

 
 

Repository files navigation

enterprise ui library


新企业版整站系统交互场景库

由于涉及组件和场景较多,正在陆陆续续更新中... 本地构建环境: node v0.10.26 | npm 1.4.3 | grunt 0.4.5

构建方法


  1. 将项目克隆到本地:git clone git@github.com:ar-insect/ui-library.git

  2. 切换到项目根目录下面,比如:cd ~/enterprise

  3. 执行 npm install 安装项目所需要的插件

  4. 本地起一个localhost服务,比如访问:http://localhost/example/singleForm.htm

  5. 本地开发完成后在gruntfile.js里面配置部署(依赖配置在package.jsonalias

  6. 在项目的根目录执行 grunt enterprise 完成静态文件的编译和打包

  7. 最后别忘记在本地测试ok提交代码哦~(提交前先git fetch origin git diff master origin/master 确认无误后再执行 git merge origin/master

Q&A


  • 问:这套ui-library主要用来完成什么任务?

答:库里面整合了企业版基础视觉样式和交互组件,不依赖于服务器环境和后端,直接下载到本地开发、部署,在本地完成mockdata调试,最后再将代码提交到svn仓库。

  • 问:有没有案例可以参考一下呢?

答:新手请先查看 example 下面到示例了解基本编码规范,然后再阅读gruntfile.js了解静态文件编译打包相关配置。

  • 问:如何在项目中安装arale组件呢?

答:假如你需要在项目中使用araledialog组件,则可以进入项目根目录中执行spm install arale.dialog就会自动下载组件以及其依赖到sea-modules下面,关于组件具体细节可以访问站点arale.org (注意:以上操作仅限于在内网中使用),然后在项目中这么引用 require('arale/dialog/1.2.6/dialog.js')也可以将组件配置到package.jsonalias,比如'dialog':'arale/dialog/1.2.6/dialog.js'这样只需要require('dialog')就可以了,是不是更加方便了呢^v^

  • 问:关于自己开发组件模块的规范是什么呢?

答:现在库里面已经有cellula fdp之类的公共模块了,理论上我们在开发环境中会涉及到2大类型的模块,一类是公共的模块,也就是可以供不同系统和业务使用的模块,它们通常是js底层的类库扩展或者是基于场景模型的构建,比如cellula fdp之类,它们存放在lib下面,另一类是纯业务型的模块组件,它们存放在static下面,而assets则是存放系统编译打包后的js&css也就是在线上环境被调用的静态文件就在这里。

本地目录结构


|-- `assets` 静态文件资源库(存放编译打包后的js&css)

		|-- `accountswitcher` 
		
		|-- `alipay`
		
		|-- `arale`
		
		|-- `bizfundprod`
		
		|-- `cellula`
		
		|-- `enterpriseportal`
			
		|-- `fdp` `Form` `dataView` `paginator`
		
		|-- `gallery`
		
		|- `seajs-style`
		
		|-- `itemList` 一个使用`cellula`做的小玩意儿
		
		|-- `select`
		
		|-- `singleForm` 单表单模型
		
		|-- `tinyscrollbar`
		
		|-- ...
		
|-- `data`

|-- `example` 示例
		
|-- `htdocs` 

	  |-- `bizfundprod`
	  
	  |-- ...

|-- images

|-- `lib` 公共js库

/-- `static` 静态文件

/-- `test` 单元测试

Gruntfile.js 部署脚本

package.js 项目配置

后续优化方案:

  • 优化gruntfile尽量做到配置最简化

关于 Cellula


is building...

关于 FDP


FDP(1.1.0)是一套非常轻量的富客户查询模型,同时它也支持Single Form应用场景,内置validate表单验证模块,支持异步校验,内部依赖cellula v0.4.2jQuery 1.7.2

具体源码请参考 lib/fdp/1.1.0

整个模型由FormItem Form DataTable Paginator等几个模块组成

tips:

  • single Form只使用form+formItem组合searchingScene使用form+formItem+table+paginator组合

  • 1.0.0由于不支持表单异步验证,故基本废弃掉


FormItem

首先来看下dom结构:

    <div class="mi-form-item" id="memo">
        <label class="mi-label">备注</label>
        <input class="mi-input memo" type="text" data-maxlength="50" name="memo" autocomplete="off" tabindex="7" />
        <div class="mi-form-explain">50个汉字,100个字符。</div>
    </div>
  • 每个formItem最外层容器都有唯一的id

  • 与dom相关的属性:

@hideClass 隐藏的样式,默认为fn-hide

@errorClass 出错的样式,默认为mi-form-item-error

@tipClass 提示的容器样式,默认为mi-form-explain

  • 每一个表单控件可以是一个formItem,以下构建一个formItem

          /* memo */
          var Memo = FormItem.build('Memo', {
              required : false,
              type : 'input',
              label : '备注',
              defaultTip : '',
              maxLength : undefined,
              init : function(conf) {
                  this._super(conf);
                  this.defaultTip = this.tip;
                  this.maxLength = parseInt(this.element.getAttribute('data-maxlength'), 10) * 2;
                  this._bindAll('limit');
                  $(this.element).on('change keyup', this.limit);
                  this.defaultTip = this.tip;
                  this.addRule('limitWord', function(options, commit) {
                      var msg;
                      var val = options.element.value;
                      var len = val.replace(/[\u4E00-\u9FBF]/g, 'BB').length;
                      if (len > this.maxLength) {
                          msg = this.tip = '输入已超过' + Math.floor((len - this.maxLength) / 2) + '个字。';
                          this.setMessage('limitWord', msg);
                          return false;
                      } else {
                          if (len == 0)
                              this.tip = this.defaultTip;
                          else
                              this.tip = '还可以输入' + Math.ceil((this.maxLength - len) / 2) + '个字。';
                          return true;
                      }
                  });
              },
              limit : function(e) {
                  var _self = this, t = null;
                  if (_self.element.value == '') {
                      this.tip = _self.defaultTip;
                      _self.setDefaultTip();
                      return;
                  }
                  t = setTimeout(function() {
                      _self.triggerValidate();
                      clearTimeout(t);
                  }, 50);
              }
          });
    

@type String :目前支持 input select radio checkbox textarea // TODO

@label String :表单控件域对应的标签名

@required Boolean :是否校验,如果设置为false则会跳过校验,但是如果非空同时有验证规则还是会进行校验的

@element Object :控件原生dom节点,如果是一个组合的控件域,则为一个Dom Array,比如,一个日期时间范围的组合控件,this.element[0]则对应第一个控件的对象,this.element[1]对应第二个控件的对象,以此类推

@tip 控件域的默认提示,控件初始化时会获取属性tipElement中的innerHTML赋给该值

@tipClass String:提示的className可自定义,默认为mi-form-explain

@tipElement 控件初始化时会根据tipClass获取到的dom节点赋给该值

@error Function :自定义出错提示

@addRule Function :为表单控件增加校验规则

@setMessage Function :自定义验证规则出错文案

@triggerValidate Function :手动触发校验

  • 通过FormItem.build方法来构建一个formItem这个方法接收2个参数:
  1. name String :每个formItem类的名称,注意不能有重复;

  2. param Object :类的自定义属性方法和构造函数,其中init为类的构造函数;

     var UserName = FormItem.build('UserName', {
         type : 'input',
         label: '用户名',
         init: function(conf){
             this._super(conf);
             this.addRule('isChinese', function(options) {
                 if (!/[\u4e00-\u9fa5]+/.test(options.element.value)) {
                     return false;
                 } else {
                     return true;
                 }
             }
         }
     });
    

控件验证

addRule(name, operator, message)

方法可以添加控件自定义校验规则,可一次添加多个规则,支持正则和函数2种模式

Arguments:

  • name String :规则名称

  • operator Function | RegExp :检验执行规则,可以是一个正则表达式或者是一个函数,在函数校验时请返回布尔值作为校验结果,而对于异步校验则需要通过第二个参数commit来提交校验结果

  • message String :提示消息

同步验证

    this.addRule('isChinese', function(options) {
        if (!/[\u4e00-\u9fa5]+/.test(options.element.value)) {
            return false; // 校验失败
        } else {
            return true; // 校验通过
        }
    }, this.label + '需要包含中文字符');

函数校验:operator将会接收到一个options对象作为参数

以上也可以使用正则模式:

    this.addRule('isChinese', /[\u4e00-\u9fa5]+/, this.label + '需要包含中文字符'); 

异步验证

    this.addRule('availableBatchNo', function(options, commit) {
        $.ajax({
            url: 'availableBatchNo.json',
            type: 'POST',
            data : {
                userid : 1024
            },
            success: function(resp) {
                if (resp.result) {
                    commit(true); // 校验通过
                } else {
                    commit(false, '批次号异常错误'); // 校验失败
                }
            },
            error: function() { 
                commit(false, '系统异常'); // 校验失败
            }
        });
    });

operator 将会接收到两个参数, options 对象 和 commit 函数

  1. 第一个参数 options 对象中,包含以下字段:
  • options.element当前在校验的表单项

  • options.display 用户自定义的校验文案

2.第二个参数 commit 函数, 用来提交校验结果. 具有两个参数

  • 第一个是 result 对象, 如果校验通过, result 应为一个 truthy 值; 如果不通过 result 应为一个 falsy 值

  • 第二个是 msg 字符串, 为提示消息

setMessage(name, message)

设置校验提示信息,若参数为 Object ,则为设置多个。

  • name String :规则名称

  • message String|Object :提示消息


tips:

  • 如果需要对控件绑定其它事件则可以在init构造函数初始化中完成

  • formItem对input textarea默认绑定了blur focusradio checkbox select默认绑定了change

Form

以下构建一个Form

    var MyForm = Form.build('MyForm', {
        type : 'single',
        itemList : null,
        autoSubmit: false,
        init : function(conf) {
            this._super(conf);
        }
    });

@type String :SINGLE|SEARCH

  1. SINGLE 表示单个表单场景

  2. SEARCH 表单,数据,分页场景(searchingScene)

@element 表单的原生dom节点

@autoSubmit Boolean :是否自动提交表单,默认为true

@onFormValidated Function :表单验证完成后的自定义事件,如果autoSubmit设置为false则可以在这里进入手动提交

    var BpToAccountForm = Form.build('BpToAccountForm', {
		type : 'single',
		itemList : null,
		autoSubmit: false, // 手动提交
		init : function(conf) {
			this._super(conf);
		},
		onFormValidated: function(options) {
			if (options.validate) this.submit(); // 当所有表单项都验证通过进入提交
		},
		submit: function() {
			window.console && console.log('manual submit form.');
			if (typeof securityCore !== 'undefined') {
				securityCore && securityCore.execute(); // 安全服务化验证
			} else {
				this.element.submit();
			}
		}
	});

@customSearch Function :自定义查询接口方法,在使用searchingScene查询场景时使用,例如:

  • 这个方法接收一个参数 data:表单所有项的数据集合,这里可以自定义请求方式以及请求前的处理逻辑

      	/*
      	 * Allow the custom query
      	 */
      	customSearch: function(data) {
      		console.log('request:', data);
      		var _self = this;
      		this.ajaxLoadingBox.show();
      		this.asyn = $.ajax({
      			url: 'seachingdata.json',
      			type: 'POST',
      			data: data,
      			timeout: 10000,
      			success: function(resp) {
      				console.log('response:', resp);
      				if (resp.status == 'succeed') {
      					_self.ajaxLoadingBox.hide();
      					_self.dataDispatch(resp); // 收到数据就开始渲染页面了
      				} else 
      					this.emit('FORM:SYSTEMERROR');
      			},
      			error: function(xhr, stat, error) {
      				_self.ajaxLoadingBox.hide();
      				this.emit('FORM:SYSTEMERROR');
      				window.debug && console.log('error: ', xhr, stat, error);
      			}
      		});
      	}
    

@dataDispatch Function :开始准备渲染页面

  • 这个方法接收一个参数 data:表示请求由后端返回的json数据

Single Form 应用场景


下面通过一个实际的业务场景来说明Single Form的具体使用:

交互白板

baiban1

付款账户:是一个控件,如果是主操作员则渲染这个控件,否则直接显示该账户信息,初始化付款账户需要向后端请求该账户余额并作显示

这里让后端给出角色的vo属性,将其放置在dom的data属性上,然后通过js来获取

    // 是否为主操作员
    var isMainOperator = $('#cardNo').attr('data-ismopt'); // 'true' | 'false'

vm层结构:

    <div class="mi-form-item accountCard" id="cardNo" data-ismopt="$!isMainOperator">
        <label class="mi-label">付款账户</label>
        <input type="hidden" name="cardNo" value="$!alipayCard.cardNo" />
        <div class="fn-clear">
            #if($!isMainOperator)
            <div class="curAccount fn-left">
                <span class="ft-bold">#if($!alipayCard.displayAlias)[$!alipayCard.displayAlias] - #end $!alipayCard.logonId</span>
            </div>
            #else
            <div class="fn-left">
                <div class="accountSwitch ui-selectmenu fn-hide" role="button" aria-haspopup="true" aria-owns="accountSwitcher" aria-disabled="false">
                    <span class="fn-left ft-14 ft-bold accountName"></span> <span class="fn-left accountHyphen"> - </span> <span class="fn-left accountEmail"></span>
                </div>
                <div class="accountSwitcher ui-selectmenu-menu" aria-hidden="false" role="listbox" aria-labelledby="accountSwitch"></div>
            </div>
            <script type="text/javascript">
                // 后端同步输出账户的json数据
                window.accountSwitchList = '#SLITERAL($!alipayCardList)';
            </script>
            #end
            <div class="accountAmount fn-left">
                <span class="useableBlance fn-hide">(可用余额 <em class="ft-orange"></em> 元)</span>
                <span class="usableBalanceAbort fn-hide">(可用余额:系统异常,<em>重新获取信息</em>)</span>
            </div>
        </div>
        <div class="mi-form-explain"></div>
    </div>

确定了用户的角色,接下来就是对控件进行初始化了:

    var CardNo; // 初始化控件对象
    /* 
     * 付款账户(非主操作员则加载)
     *
     */
    if (isMainOperator == 'false') {
        CardNo = FormItem.build('CardNo', {
            type : 'input',
            label : '付款账户',
            acountList: undefined,
            accountSwitcher: null,
            getAccountUsablebBalance: getAccountUsablebBalance,
            init : function(conf) {
                var _self = this, t = null;
                this._super(conf);
                this._bindAll('trigger', 'triggerValidate');
                this.acountList = window.accountSwitchList;
                if (this.acountList) this.element.value = this.acountList[0].accountId;
                this.accountSwitcher = AccountSwitcher.init({
                    dataList: this.acountList,
                    bSwitchDefault: true,
                    accountSwitchInput: $(this.element)
                });
                // 因为组件用到`onload`先于`init`执行,所以需要延迟验证。
                t = setTimeout(function() { 
                    $(_self.element).trigger('changed');
                    clearTimeout(t);
                }, 50);
                $(this.element).on('changed', this.trigger);
                this.addRule('usablebBalance', this.getAccountUsablebBalance);
                $('.usableBalanceAbort em', this.rootNode).on('click', this.triggerValidate);
            },
            trigger: function() {
                this.triggerValidate();
            }
        });
    }
    /* 
     * 当前账户
     *
     */
    else if (isMainOperator == 'true') {
        CardNo = FormItem.build('CardNo', {
            type : 'input',
            label : '付款账户',
            getAccountUsablebBalance: getAccountUsablebBalance,
            init : function(conf) {
                this._super(conf);
                this._bindAll('triggerValidate');
                this.addRule('usablebBalance', this.getAccountUsablebBalance);
                this.triggerValidate(); // 初始化获取当前账户可用余额
                $('.usableBalanceAbort em', this.rootNode).on('click', this.triggerValidate);
            }
        });
    }

在这里可以看到自定义规则校验usablebBalance是对当前账户余额的获取并验证,不管是什么角色都需要做这步操作,所以将验证函数提取出来

    // 获取账户可用余额并得知余额是否被冻结
    var getAccountUsablebBalance = function(options, commit) {
        var _self = this;
        var cardNo = options.element.value;
        $.ajax({
            type: 'POST',
            url: 'getAccountInfoWithBalance.json',
            data: { cardNo: cardNo },
            success: function(data) {
                if (data.isSuccess) {
                    $('.useableBlance em', _self.rootNode).text(data.cardInfo && data.cardInfo.availableAmount);
                    $('.usableBalanceAbort', _self.rootNode).addClass('fn-hide');
                    $('.useableBlance', _self.rootNode).removeClass('fn-hide');
                    if (data.cardInfo && data.cardInfo.enablePayment) {
                        // 账户被冻结,不可转出
                        commit(false, '该账户余额支付功能关闭,不可将资金转出。');
                    } else {
                        commit(true);
                    }
                    return;
                } else {
                    $('.useableBlance', _self.rootNode).addClass('fn-hide');
                    $('.usableBalanceAbort', _self.rootNode).removeClass('fn-hide');
                    commit(false, '系统异常,请重新登录。');
                    return;
                } 
                if (data.stat == 'deny') {
                    // 系统超时
                    $('.useableBlance', _self.rootNode).addClass('fn-hide');
                    $('.usableBalanceAbort', _self.rootNode).removeClass('fn-hide');
                    commit(false, '连接超时,请重新登录。');
                }
            },
            error: function() {
                $('.useableBlance', _self.rootNode).addClass('fn-hide');
                $('.usableBalanceAbort', _self.rootNode).removeClass('fn-hide');
                commit(false, '系统异常,请重新登录。');
            }
        });
    };

这样就可以保证在不同场景下对控件的良好过渡处理,同时也保证了前端对数据验证的可靠性,一旦此验证失败将会出现错误信息,并阻止整个表单的提交

ok,接下面的几个控件就简单多了:

    /** 批次号 **/
    var BatchNo = FormItem.build('BatchNo', {
        type : 'input',
        label : '批次号',
        init : function(conf) {
            this._super(conf);
            this.addRule('digits', /^[0-9]+$/, this.label + '应为当天日期开头的11-20位数字,如:2014052021219810')
                .addRule('size', /^[0-9]{11,20}$/, this.label + '应为当天日期开头的11-20位数字,如:2014052021219810');
        }
    });
    /** 付款文件 **/
    var UploadFile = FormItem.build('UploadFile', {
        type : 'input',
        label : '付款文件',
        init : function(conf) {
            this._super(conf);
            this._bindAll('trigger', 'repair');
            $(this.element).change(this.trigger);
            $(this.element).click(this.repair);
            this.addRule('availableFileType', function(options) {
                var fullpath = options.element.value;
                var filename = fullpath.substr(fullpath.lastIndexOf('\\') + 1);
                var allow = ['.csv', '.xls'],
                    extension = /\.[^\.]+$/.exec(fullpath);
                if (!extension) {
                    return false;
                }
                if ($.inArray(extension[0].toLowerCase(), allow) == -1) {
                    return false;
                }
                $('.upload-trigger', this.rootNode).addClass('fn-hide');
                $('.rollback-filename', this.rootNode).text(filename).attr('title', filename).removeClass('fn-hide');
                $('.reupload-file', this.rootNode).removeClass('fn-hide');
                return true;
            }, '无效的文件类型,请重新选择' + this.label);
        },
        trigger: function() {
            this.element.blur();
            this.triggerValidate();
        },
        repair: function() {
            var _self = this, t = setTimeout(function() {
                _self.element.blur();
                clearTimeout(t);
            }, 500);
        }
    });
    /* 总笔数 */
    var TotalCount = FormItem.build('TotalCount', {
        type : 'input',
        label : '总笔数',
        max: undefined,
        init : function(conf) {
            this._super(conf);
            this.max = parseInt(this.element.getAttribute('data-max'), 10);
            this.addRule('format', /^[1-9]\d*$/, this.label + '的格式不正确')
            .addRule('limit', function(options) {
                if (parseInt(this.element.value, 10) <= 0) {
                    this.setMessage('limit', this.label + '必须大于或者等于1');
                    return false;
                }
                if (this.max < parseInt(this.element.value, 10)) {
                    return false;
                }
                return true;
            }, this.label + '最多' + this.max + '笔');
        }
    });
    /* 总金额 */
    var TotalAmount = FormItem.build('TotalAmount', {
        type : 'input',
        label : '总金额',
        reg: new RegExp('^\\d+(\\.\\d{0,2})?$'),
        toMoney: undefined,
        init : function(conf) {
            this._super(conf);
            this._bindAll('trigger');
            this.toMoney = $('.toMoney', this.rootNode);
            $(this.element).on('keyup', this.trigger);
            this.addRule('money', this.reg, this.label + '的格式不正确')
            .addRule('limit', function() {
                if (parseFloat(this.element.value) == 0) {
                    return false;
                }
                return true;
            }, this.label + '至少为0.01元');
        },
        trigger: function() {
            var val = $.trim(this.element.value);
            if (this.reg.test(val)) {
                var upperCase = Money.convert(val);
                upperCase && this.toMoney.text(upperCase);
                this.toMoney.removeClass('fn-hide');
                this.setDefaultTip();
            } else {
                this.error(this.label + '的格式不正确');
                this.toMoney.addClass('fn-hide');
            }
        }
    });
    /* 备注 */
    var Remark = FormItem.build('Remark', {
        required : false,
        type : 'input',
        label : '备注',
        defaultTip : '',
        maxLength : undefined,
        init : function(conf) {
            this._super(conf);
            this.defaultTip = this.tip;
            this.maxLength = parseInt(this.element.getAttribute('data-maxlength'), 10) * 2;
            this._bindAll('limit');
            $(this.element).on('change keyup', this.limit);
            this.defaultTip = this.tip;
            this.addRule('limitWord', function(options, commit) {
                var msg;
                var val = options.element.value;
                var len = val.replace(/[\u4E00-\u9FBF]/g, 'BB').length;
                if (len > this.maxLength) {
                    msg = this.tip = '输入已超过' + Math.floor((len - this.maxLength) / 2) + '个字。';
                    this.setMessage('limitWord', msg);
                    return false;
                } else {
                    if (len == 0)
                        this.tip = this.defaultTip;
                    else
                        this.tip = '还可以输入' + Math.ceil((this.maxLength - len) / 2) + '个字。';
                    return true;
                }
            });
        },
        limit : function(e) {
            var _self = this, t = null;
            if (_self.element.value == '') { 
                this.tip = _self.defaultTip; 
                _self.setDefaultTip();
                return;
            }
            t = setTimeout(function() {
                _self.triggerValidate();
                clearTimeout(t);
            }, 50);
        }
    });

这里就是所有formItem的集合,待会将其传给Form对象

    /** collection **/
    var formElements = {
        batchNo : new BatchNo({
            key : 'batchNo'
        }),
        uploadFile : new UploadFile({
            key : 'uploadFile'
        }),
        totalCount : new TotalCount({
            key : 'totalCount'
        }),
        totalAmount : new TotalAmount({
            key : 'totalAmount'
        }),
        remark : new Remark({
            key : 'remark'
        })
    };
    
    if (CardNo) {
        formElements.cardNo = new CardNo({
            key: 'cardNo'
        });
}

定义好formItem对象后,接下来我们需要定义一个Form对象了

    var BpToAccountForm = Form.build('BpToAccountForm', {
        type : 'single',
        itemList : null,
        autoSubmit: false,
        init : function(conf) {
            this._super(conf);
        },
        onFormValidated: function(options) {
            if (options.validate) this.submit();
        },
        submit: function() {
            window.console && console.log('manual submit form.');
            if (typeof securityCore !== 'undefined') {
                securityCore && securityCore.execute(); // 安全服务化验证
            } else {
                this.element.submit();
            }
        }
    });

这里我们还需要对用户安全服务化控件进行验证,所以将FormautoSubmit设置为false在完成表单项的验证后进入到自定义提交函数submit中再次提交安全控件的验证

安全服务化控件绑定了form

    // 安全服务化
    if (window.alipay && alipay.security && alipay.security.core) {
        var bpform = $('#bpToAccountForm'), bpSubmit = $('input[type=submit]', bpform);
        var disBtn = function() {
            bpSubmit.attr('disabled', 'disabled');
            bpSubmit.parent().addClass('mi-button-mblue-disabled');
        }, enbBtn = function() {
            bpSubmit.removeAttr('disabled');
            bpSubmit.parent().removeClass('mi-button-mblue-disabled');
        };
        light.ready(function() {
            securityCore = alipay.security.core.init({
                form: bpform[0],
                stopSubmit: true,
                beforeAjaxValidate: function() {
                    // 按钮设置为不可点击,样式置灰自行完成
                    disBtn();
                },
                afterAjaxValidate: function() {
                    // 按钮设置为可点击,样式自行恢复
                    enbBtn();
                },
                block: function() {
                    // 安全产品校验失败提交区域
                    disBtn();
                },
                beforeSubmit: function() {
                    /* CTU验证  */
                    sendCTURecheckReq($(bpform).serialize(), checkStep2SecProd);
                },
                reCheckSuccess: function() {
                    // 按钮设置为可点击,样式自行恢复
                    enbBtn();
                }
            });
        });
    }

最后实例化Form

    var bpToAccountForm = new BpToAccountForm({
        key : 'bpToAccountForm',
        itemList : formElements // formItems
    });

Searching Form 应用场景


is building...

如有问题或建议请 mail to: ar.insect@gmail.com

Releases

No releases published

Packages

No packages published