UNPKG

yqb

Version:

Static Package Manager

985 lines (905 loc) 36.5 kB
;/*!/framework/js/OtpImageSuite.js*/ /** * OTP短信+图片验证码控件 * * @class OtpImageSuite * @constructor */ (function(ns) { var DEBUG = true; // 定义正则表达式验证规则列表常量 var REG_EXP_RULES = { "phone": /^1[3-9][0-9]\d{8}$/, //验证手机号码/^1[3|4|5|8][0-9]\d{4,8}$/ "mobile": /^1[3-9][0-9]\d{8}$/, //验证手机号码/^1[3|4|5|8][0-9]\d{4,8}$/ "trim": /^\s+|\s+$/g }; // 定义组建内部错误消息列表常量 var INNER_MESSAGES = { "common": "系统内部错误", "phone": "手机号码格式错误", "mobile": "手机号码格式错误" }; var toString = Object.prototype.toString; function isObject(dest) { return dest && toString.call(dest) === "[object Object]"; }; function isFunction(dest) { return dest && toString.call(dest) === "[object Function]"; }; /** * Clone object. * @param {object} source source * @return {object} new object. */ function clone(source) { var F = function() {}; F.prototype = source; return new F(); }; /** * 移除字符串前后空格 * @method trim * @param {String} s 给定的字符串 * @return {String} 返回移除前后空格的字符串 */ function trim(s) { return s ? s.replace(REG_EXP_RULES["trim"], "") : ""; }; /** * 验证表单输入格式 * @param {String} type 表单输入的类型,如果mobileInputBox, otpInputBox,captchaInputBox * @param {String} input 表单输入类型对应的待验证的值 * @return {Boolean/String} true: 验证通过 otherwise 返回对应的错误消息string */ function fieldValidator(type, input) { var regExp = REG_EXP_RULES[type]; if (regExp && regExp.test(input)) { return true; } else { return INNER_MESSAGES[type]; } }; /** * 格式化字符串 e.g stringFormat("my name is {0}, sex is: {1}","tian","male") * @param {Array Like} * @return {String} 返回格式化后的新字符串 */ function stringFormat() { for (var fmt = arguments[0], ndx = 1; ndx < arguments.length; ++ndx) { fmt = fmt.replace(new RegExp('\\{' + (ndx - 1) + '\\}', "g"), arguments[ndx]); } return fmt; }; /** * Log 调试信息到日志窗口,log("string ssss{0},{1}", "params1", "params2"); */ function log() { if (DEBUG && console && toString.call(console) == "[object Console]") { //console.log.apply(console, arguments); } }; // default configurations for OtpImageSuite. var cfg = { timeout: 1000, // provider default biz send otp service name. trySendOTPServiceName: "trySendOTP", // default we use mobile numnber to send otp, we can set as true, use token,.. to send otp. ignoreMobileValidation: false, // ticker second default tickerSecond: 60 }; /** * @class OtpImageSuite * @constructor * @param {object} otpImageService service contract. * +trySendOTP(phone, captchaToken, deviceId) * +refreshCaptcha() * +verifyCaptcha(captchaVal, captchaId) * @return {code:"", message:"", data:""} */ function OtpImageSuite(otpImageService, options) { this.cfg = clone(cfg); for (var prop in options) { if (options.hasOwnProperty(prop) && options[prop]) { this.cfg[prop] = options[prop]; } } if (!otpImageService) { throw new Error("我们必须提供Otp ImageCode 的服务实例!"); } this.handlers = {}; // receiver,如果存在则调用接收器传递所有的广播事件. // 只允许一个接收器 this.receiver = null; //为了复用存在框架的Service机制,这里我们注入外部的service API contract this.service = otpImageService; // ---------------------------------------------------- // 辅助方法! // ---------------------------------------------------- /** * @events showTicker (tickerLeft) * closeTicker(tickerLeft) * @param {object} scope * @param {number} tickerLeft how many ticker second left. */ var tickerId; var startTicker = function(scope, tickerLeft) { tickerId = setTimeout(function() { log("ticker `%s` ", tickerLeft); scope.fireEvent("showTicker", tickerLeft); tickerLeft = tickerLeft - 1; if (tickerLeft > 0) { startTicker(scope, tickerLeft); } else { tickerLeft = 0; scope.fireEvent("closeTicker", tickerLeft); } }, cfg.timeout); }; var tearDownTicker = function() { if (tickerId) { clearTimeout(tickerId); tickerId = 0; } }; this._startTicker = function(scope, tickerSecond) { // tear down running ticker. tearDownTicker(); // make sure provider // if initial ticker second equals 0, use default configuration. tickerSecond = tickerSecond || this.cfg.tickerSecond || 60; // start timer. startTicker(scope, tickerSecond); }; }; OtpImageSuite.prototype = { constructor: OtpImageSuite, /** * 提供注册组件自定义事件API * @method addHandler * @param {string} type 自定义事件类型 * @param {function} handler 自定义事件回调处理器 */ addHandler: function(type, handler) { if (typeof this.handlers[type] == "undefined") { this.handlers[type] = []; } // make sure it's function. if (isFunction(handler)) { this.handlers[type].push(handler); } }, /** * 提供注册一个接受器,可以接受当前组建广播的所有消息 * @param {function} handler 客户端接收器 */ addReceiver: function(handler) { this.receiver = handler; }, /** * 提供移除组件自定义事件API * @method removeHandler * @param {string} type 自定义事件类型 * @param {function} handler 自定义事件回调处理器 */ removeHandler: function(type, handler) { if (this.handlers[type] instanceof Array) { var handlers = this.handlers[type]; for (var i = 0, len = handlers.length; i < len; i++) { if (handlers[i] === handler) { break; } } handlers.splice(i, 1); } }, /** * 提供触发组件自定义事件API * @method fire * @param {string} event 自定义事件类型 */ fire: function(event) { if (!event.target) { event.target = this; } // 如果定义了接收器,则不在单独广播单独的事件消息. if (this.receiver) { if (isFunction(this.receiver)) { this.receiver(event); } else { throw new Error("`receiver`接收器必须是一个函数!") } } else { if (this.handlers[event.type] instanceof Array) { var handlers = this.handlers[event.type]; for (var i = 0, len = handlers.length; i < len; i++) { handlers[i](event); }; } } }, fireEvent: function(eventType, data) { log("fireEvent eventType: ", eventType, " data:", data); var event = { type: eventType, data: data || null }; this.fire(event); }, fireError: function(code, message) { this.fireEvent("error", { code: code, message: message }); }, /** * API: Provider short methods to validate mobile number. * @param {string} mobile mobile number. * @return {Boolean} [description] */ isMobile: function(mobile) { return fieldValidator("mobile", mobile); }, /** * API: 尝试发送OTP到指定的手机客户端,如果成功fire事件通知UI显示timeout second.(60s) * @events OTPSending () 将会在OTP发送短信之前被调用. * OTPSentSuccess({data}) * captchaShow ({captchaId:'', captchaUrl:''}) * @param {string} phone string * @param {string} captchaToken string (optional) * @param {string} deviceId string (optional) */ trySendOTP: function(phone, captchaToken, deviceId, extraData) { // check phone number. if (!this.cfg.ignoreMobileValidation) { var vlResult = fieldValidator("phone", phone); if (vlResult !== true) { this.fireError("mobile_invalid", vlResult); return; } } // otp sending event. this.fireEvent("OTPSending"); var _this = this; // ------------------------------------------------ // 外部注入SERVICE的API:trySendOTP(phone); var trySendOTPServiceAPI = this.service[this.cfg.trySendOTPServiceName]; if (!isFunction(trySendOTPServiceAPI)) { this.fireError("try_send_otp_service_undefined", "自定义业务trySendOTP()不是一个函数"); return; } // invoke biz service to send otp. trySendOTPServiceAPI.call(this.service, phone, captchaToken, deviceId, extraData, function(result) { var data = result.data; var code = result.code; switch (code) { case "000000": _this.fireEvent("OTPSentSuccess", data); var tickerSecond = 0; if (!isNaN(data.retrySeconds)) { tickerSecond = parseInt(data.retrySeconds); } _this._startTicker(_this, tickerSecond); break; case "000001": var captcha = data.captcha; if (captcha) { _this.fireEvent("captchaShow", captcha); } else { throw Error("当前服务器端未传回Captha对象"); } break; default: log("nothing to do...., code: %s in `trySendOTP`", code); _this.fireError(code, result.message); } }); }, /** * API: 手动刷新图片验证码,如果刷新成功则触发 `captchaRefreshed` 事件 * @events captchaRefreshed ({captchaId:'', captchaUrl:''}) * captchaRefreshedFailed (message) * @param {object} extraData (optional) * @return {void} */ refreshCaptcha: function(extraData) { // ------------------------------------------------ // 外部注入SERVICE的API:refreshCaptcha(); // var _this = this; this.service.refreshCaptcha(extraData, function(result) { var code = result.code; var data = result.data; switch (code) { case "000000": //验证码刷新成功! _this.fireEvent("captchaRefreshed", data.captcha); break; default: //验证码输入错误. _this.fireError("captcha_refreshed_failed", result.message); break; } }); }, /** * API: 验证用户输入的图片验证码,如果验证通过则返回captchaToken, * 发送短信的是需要带上captchaToken * @events tokenFlushed (captchaTokenValue string) * tokenFlushedFailed (message) * @param {object} captcha {captchaId, captchaInput} * @param {object} extraData attach extra data to servier api. * @return {void} */ verifyCaptcha: function(captcha, extraData) { // ------------------------------------------------ // 外部注入SERVICE的API:validateCaptcha(captcha); // captcha:{captchaId:"", captchaInput:""} // var _this = this; this.service.verifyCaptcha(captcha, extraData, function(result) { var code = result.code; var data = result.data; switch (code) { case "000000": _this.fireEvent("tokenFlushed", data.captchaToken); break; default: //验证码输入错误. _this.fireError("token_flushed_failed", result.message); break; } }); } }; // ns["OtpImageSuite"] = OtpImageSuite; })(window); ;/*!/framework/js/otpAPI.js*/ (function($) { // uniform data converter var ajaxDataFilter = function(data) { var dataResult = {}; // if we invoke mtp service "1000" is ajax success return code. if (this.isMtp) { _newResult = {}; $.extend(_newResult, data); delete _newResult.resultCode; //"1000"表示业务逻辑成功!,"1022"-业务走不下去的错误, "2022"-表示系统未知错误 delete _newResult.resultMsg; dataResult.code = data.resultCode; dataResult.message = data.resultMsg; dataResult.data = _newResult; // convert 1000 to 000000. if (dataResult.code == "1000") { dataResult.code = "000000"; } } else { //data converter. dataResult.code = data.code; dataResult.message = data.message; dataResult.data = data.data; } return dataResult; }; // DTO for trySendOTP(). var ajaxTrySendOTPDataFilter = function(data) { // base DTO. var result = ajaxDataFilter.call(this, data); if (result.code == "000000") { // send successfully!. // now return all data values. // result.data = { // maskedMobile: result.data.maskedMobile, // retrySeconds: result.data.retrySeconds, // // it's optional, otp id number. // otpId: result.data.otpId // }; } else if (result.code != "000000" && result.code == "1184") { // alwasy use 0000001 to ask captcha code. result.code = "000001"; // send failed, return us captcha entity. // return new captcha. result.data = { captcha: { captchaId: result.data.captchaId, captchaUrl: result.data.captchaUrl } }; } return result; }; //DTO for refreshCaptcha(). var ajaxRefreshCaptchaDataFilter = function(data) { var result = ajaxDataFilter.call(this, data); if (result.code == "000000") { // return new captcha. result.data = { captcha: { captchaId: result.data.captchaId, captchaUrl: result.data.captchaUrl } }; } return result; }; //DTO for verifyCaptcha(). var ajaxVerifyCaptchaDataFilter = function(data) { var result = ajaxDataFilter.call(this, data); if (result.code == "000000") { // return new captchaToken property. result.data = { captchaToken: result.data }; } return result; }; function getRequestUrl(url) { // if we providered an api url with "http|s" prefix omit it. if (!/^(ftp|http|https):\/\/[^ "]+$/.test(url)) { url = this.apiRoot + url; } return url; }; var OtpAPI = { //"http://192.168.11.10:8080"; apiRoot: "http://127.0.0.1:20000", // we can customized sendOTP http request api name. trySendOTPApi: "", // The value indicates current service api is MTP service. // default is MTP service. isMtp: true, // expose some usefull dto for otp apis. // Note. you should use call() specificed window.OtpApi as current context scope. dtos: { baseAjaxDto: ajaxDataFilter, baseAjaxTrySendOTPDto: ajaxTrySendOTPDataFilter, baseAjaxRefreshCaptchaDto: ajaxRefreshCaptchaDataFilter, baseAjaxVerifyCaptchaDto: ajaxVerifyCaptchaDataFilter }, /** * trySendOTP API * @method trySendOTP * @param {number} phone mobile phone number. * @param {Function} cb callback * callback (result) * if result.code=="000000" * {maskedMobile,retrySeconds} * else * {captchaId, captchaUrl} */ trySendOTP: function(phone, captchaToken, deviceId, extraData, cb) { var data = { phone: phone, }; // optional. token. first time captchaToken is null. if (captchaToken) { data.captchaToken = captchaToken.captchaToken; } // optional if (deviceId) { data.deviceId = deviceId; } $.extend(data, extraData); // we can defined api name to route specificed api path. var _sendOTPApiUrl = this.trySendOTPApi || "/mtp-web/h5/op_common_reg_send_otp.json"; $.ajax({ url: getRequestUrl.call(this, _sendOTPApiUrl), contentType: "application/json", type: 'POST', dataType: 'json', data: JSON.stringify(data), processData: false }).then(function(data) { if (cb) cb(ajaxTrySendOTPDataFilter.call(OtpAPI, data)); }, function(data) { // give error message here maybe! // if (cb) cb(ajaxDataFilter(data)); throw new Error("status code:" + data.status); }); }, /** * refresh captcha API * @method refreshCaptcha * @param {Function} cb callback */ refreshCaptcha: function(extraData, cb) { var data = {}; $.extend(data, extraData); $.ajax({ url: getRequestUrl.call(this, "/mtp-web/h5/refresh_img_code.json?v="+Math.random()), contentType: "application/json", type: 'POST', dataType: 'json', data: JSON.stringify(data), processData: false }).then(function(data) { if (cb) cb(ajaxRefreshCaptchaDataFilter.call(OtpAPI, data)); }, function(data) { // give error message here maybe! // if (cb) cb(ajaxDataFilter(data)); throw new Error("status code:" + data.status); }); }, /** * verifyCaptcha API * @method verifyCaptcha * @param {object} captcha, {captchaId:"", captchaInput:""} * @param {object} extraData: {} anything. * @param {Function} cb callback (captchaToken) */ verifyCaptcha: function(captcha, extraData, cb) { $.extend(captcha, extraData); $.ajax({ url: getRequestUrl.call(this, "/mtp-web/h5/cma_verify_img_code.json?v="+Math.random()), contentType: "application/json", type: 'POST', dataType: 'json', data: JSON.stringify(captcha), processData: false }).then(function(data) { if (cb) cb(ajaxVerifyCaptchaDataFilter.call(OtpAPI, data)); }, function(data) { // give error message here maybe! // if (cb) cb(ajaxDataFilter(data)); throw new Error("status code:" + data.status); }); } }; // expose it to windows object. window.PafOtpAPI = OtpAPI; })($); ;/*!/framework/js/otp.js*/ (function($) { var OtpImageSuite = window.OtpImageSuite; // export otp sample service to client, allow us extend it to suit for customized bisuness. var otpService = window.PafOtpAPI; var defaultCfg = { // The value indicates if we need to auto send otp message while captcha varify success! autoSendOtp: false, // default show captcha firstShowCaptcha: false, // ticket second formatter. leftSecondFormatter: "{0}s", // data-* save captchaId come from server side. // $.data("captchaId","otp_id_get_from_server") dataCaptchaId: "captchaId", // $.data("captchaToken","save_validated_captcha_token") dataCaptchaToken: "captchaToken", // $("手机号输入框") mobileInputSelector: "#mobile-input", //$("图片验证码控件外层") captchaControlSelector: "#captcha-control", // $("图片验证码输入框") captchaInputSelector: "#captcha-input", // $("图片Img对象") captchaImageSelector: "#captcha-image", // $("发送按钮") otpGetSelector: "#otp-get-btn", // $("短信验证码输入框") otpInputSelector: "#otp-input", // $("计时器") otpTickerSelector: "#otp-ticker", // 默认事件侦听器 eventListener: function(event) { }, // 允许OTP 发送成功回调客户端指定的函数 otpHasPassedCallback: function(result) { }, // 允许OTP 发送失败回调客户端指定的函数(code, message) otpErrorsCallback: function(event) { }, // 允许我们动态按需从客户端拿自定义的数据,针对不同的OTP 业务需求 getExtraData: function() { return null; } }; var otp = function(context, otpService, options) { context = $(context); if (!context || !context.length) { throw new Error("the context parameter required!"); return; } // config, status initialize. var cfg = $.extend({}, defaultCfg, options), running = false, eventListener = cfg.eventListener; // cache ui components. var $mobileInput = context.find(cfg.mobileInputSelector), $captchaControl = context.find(cfg.captchaControlSelector), $captchaInput = context.find(cfg.captchaInputSelector), $captchaImage = context.find(cfg.captchaImageSelector), $otpGet = context.find(cfg.otpGetSelector), $otpInput = context.find(cfg.otpInputSelector), $otpTicker = context.find(cfg.otpTickerSelector); var suiteServiceCfg = { trySendOTPServiceName: cfg.trySendOtpServiceName, ignoreMobileValidation: cfg.ignoreMobileValidation }; //The otp core. var otpImgSuite = new OtpImageSuite(otpService, suiteServiceCfg); // // helper methods for handling OtpSuiteModule. // --------------------------------------------------- // otp sending pre handler. function OTPSendingHandler(event) { // do nothing... may be we can show loading spinner here. }; // otp sent success handler. function OTPSentSuccessHandler(data) { // don't hide captcha 控件. we can do this in client. by cfg.otpHasPassedCallback(). // $captchaControl.css("display", "none"); setMobileCaptchaDisabledStatus(true); if (cfg.otpHasPassedCallback) { cfg.otpHasPassedCallback({ data: data }); } console.log('captchaControl') }; // set captcha input, mobile input disabled status. function setMobileCaptchaDisabledStatus(disabled) { if (disabled) { $mobileInput.prop("disabled", true); $captchaInput.prop("disabled", true); } else { $mobileInput.prop("disabled", false); $captchaInput.prop("disabled", false); } }; // update captcha token value. function setCaptchaToken(value) { context.data(cfg.dataCaptchaToken, value || null); }; // get captcha token value. function getCaptchaToken() { return context.data(cfg.dataCaptchaToken); }; function setCaptchaId(value) { context.data(cfg.dataCaptchaId, value || null); }; // get captcha id. function getCaptchaId() { return context.data(cfg.dataCaptchaId); }; // return client unique device id. function getDeviceId() { return ""; }; // OTP Error handler. function OTPErrorHandler(event) { var error = event.data; var code = error.code; var message = error.message; var otpErrorsCallback = cfg.otpErrorsCallback || function() {}; switch (code) { case "mobile_invalid": // Now we do nothing, we need to handler these message in client consumer. otpErrorsCallback(code, message); break; case "captcha_refreshed_failed": otpErrorsCallback(code, message); break; case "token_flushed_failed": // captcha token flush failed, clear existed token. setCaptchaToken(null); otpErrorsCallback(code, message); break; default: // for other unhandled exceptions. otpErrorsCallback(code, message); break; } }; // show ticker handler. function showTickerHandler(data) { running = true; $otpGet.css("display", "none"); $otpTicker.css("display", "block"); $otpTicker.html(cfg.leftSecondFormatter.replace(new RegExp('\\{0\\}', "g"), data)); }; // close ticker handler. function closeTickerHandler(data) { running = false; setMobileCaptchaDisabledStatus(false); $otpGet.css("display", "block"); $otpTicker.css("display", "none"); $otpTicker.html(""); }; // capcha show handler function showCaptchaHandler(data) { var captcha = data; // show captcha control. $captchaControl.removeClass('hidden'); // refresh captch UI refreshCaptchaUI(captcha); }; /** * While we re-input mobile number, we need to restore OTP Initialize states, * and make user has chance to send otp without captcha. */ function restoreOTPInitState() { running = false; closeTickerHandler(); // need to show captcha first time. if (cfg.firstShowCaptcha) { $captchaControl.css("display", "block"); } else { $captchaControl.css("display", "none"); } }; // refresh captcha function refreshCaptchaUI(captcha) { // make sure that each url have not cache. $captchaImage.attr("src", captcha.captchaUrl ? captcha.captchaUrl + "?r=" + Math.random() : ""); setCaptchaId(captcha.captchaId); }; // flush token handler. function flushTokenHandler(data) { var token = data; setCaptchaToken(token); // $this.find(options.otpGetSelector).prop("disabled", false); if (cfg.autoSendOtp) { // try to resend otp request. trySendOtp(); } }; // // OTP 相关业务方法 // --------------------------------------------------- // OtpImageSuite 发短信业务方法 function trySendOtp() { var phone = $mobileInput.val(); var token = getCaptchaToken(); var deviceId = getDeviceId() || ""; // 提供额外的数据注入到具体的OTP发短信业务 var extraData = $.extend({}, cfg.getExtraData() || {}); // try send OTP. need to clone new object, and pass into otpImageSuite. it is security. otpImgSuite.trySendOTP(phone, token, deviceId, extraData); }; // OtpImageSuite 刷新图片验证码方法 function refreshCaptcha() { if (!running) { otpImgSuite.refreshCaptcha(); } }; // 注册UI DOM 事件处理器 var hookEvents = function() { // 监听 发送短信按钮click 事件 $otpGet.on("click", function(e) { trySendOtp(); }); // 监听 图片验证码输入框 change事件,发送CMMAND 去验证图片码 $captchaInput.on("input", function(e) { var val = $.trim($(this).val()); if (val && val.length >= 4) { otpImgSuite.verifyCaptcha({ captchaInput: val, captchaId: getCaptchaId() }); } }); // 监听 图片随机码的刷新事件 $captchaImage.on("click", function() { refreshCaptcha(); }); }; // 注册OtpImageSuite 模块事件 var hookOtpSuiteModule = function() { otpImgSuite.addReceiver(function(event) { var type = event.type; var data = event.data; // always invoke event listener to passed components current states. if (cfg.eventListener) { cfg.eventListener(event); } switch (type) { case "OTPSending": OTPSendingHandler(event); break; case "OTPSentSuccess": OTPSentSuccessHandler(data); break; case "error": OTPErrorHandler(event); break; case "showTicker": showTickerHandler(data); break; case "closeTicker": closeTickerHandler(data); break; case "captchaShow": showCaptchaHandler(data); break; case "captchaRefreshed": refreshCaptchaUI(data); break; case "tokenFlushed": flushTokenHandler(data); break; default: break; } }); }; // auto initialization here. (function init() { hookEvents(); hookOtpSuiteModule(); // if need to show captcha first time. if (cfg.firstShowCaptcha) { refreshCaptcha(); $captchaControl.css("display", "block"); } })(); // attach public method to context. // start otp control context.data("start", function() { trySendOtp(); }); context.data("reset", function() { restoreOTPInitState(); }); context.data("refreshCaptcha", refreshCaptcha); }; /** * * @param {object} options we can refer defaultCfg * { * // allow us customized otp service. * otpService:{ * apiRoot: "http://192.168.17.232:8080", * isMtp: true, * trySendOTPApi: "" * }, * // The value indicates if we need to auto send otp message while captcha varify success! * autoSendOtp: false, * // default show captcha * firstShowCaptcha: false, * // ticket second formatter. * leftSecondFormatter: "{0}s", * // data-* save captchaId come from server side. * // $.data("captchaId","otp_id_get_from_server") * dataCaptchaId: "captchaId", * // $.data("captchaToken","save_validated_captcha_token") * dataCaptchaToken: "captchaToken", * // $("手机号输入框") * mobileInputSelector: ".mobile-input-selector", * //$("图片验证码控件外层") * captchaControlSelector: "captcha-control-selector", * // $("图片验证码输入框") * captchaInputSelector: ".captcha-input-selector", * // $("图片Img对象") * captchaImageSelector: ".captcha-image-selector", * // $("发送按钮") * otpGetSelector: ".otp-get-btn-selector", * // $("短信验证码输入框") * otpInputSelector: ".otp-input-selector", * // $("计时器") * otpTickerSelector: ".ticker-selector", * // 默认事件侦听器 * eventListener: function(event) { * }, * // 允许OTP 发送成功回调客户端指定的函数 * otpHasPassedCallback: function(result) { * }, * // 允许OTP 发送失败回调客户端指定的函数(code, message) * otpErrorsCallback: function(event) { * }, * // 允许我们动态按需从客户端拿自定义的数据,针对不同的OTP 业务需求 * getExtraData: function() { * return null; * } * } * */ $.fn.otpNew = function(options) { return this.each(function() { var $this = $(this); $.extend(otpService, options.otpService); delete options.otpService; // init plugin. otp($this, otpService, options); }); }; })($);