//依赖类库:lodash.js + jquery + jquery.validate + bootstrap.emodal.js
// 统一调试日志工具:仅在 otcdebug 开启时输出,默认静默(生产零噪音)。
// 开启方式:URL 加 ?otcdebug=1,或控制台 localStorage.setItem('otcdebug','1')。
// 业务脚本逐步改用 otcDebug.banner / otcDebug.log 替代裸 console.log,
// 既能在排查时一键全开,又能避免版本标记散落硬编码(见 swapTradeEdit.js / fastVue.base.js 用法)。
var __otcDiag = (window.ylotc && window.ylotc.__diag) || { debug: false };
window.otcDebug = {
// 模块加载横幅:F12 一眼看到模块名+bundle版本+git sha,用于排查"是不是加载了旧代码/旧缓存"
banner: function (name, ver) {
if (!__otcDiag.debug || !window.console) return;
var git = (__otcDiag.git || '').slice(0, 7);
console.log('%c[' + name + '] v' + ver + ' (bundle=' + __otcDiag.jsVersion + ', git=' + git + ', built=' + __otcDiag.built + ')',
'color:#06c;font-weight:bold');
},
// 普通调试日志:透传参数,仅 debug 开启时输出
log: function () {
if (!__otcDiag.debug || !window.console) return;
console.log.apply(console, arguments);
}
};
var main = window.main || { version: "1.0" };
main.extend = $.extend;
//数字格式化
/**
* 构建数字格式化函数(依赖lodash.js)
* @see Intl.NumberFormat
* @param {any} precision - 小数位精度,默认2,如果是对象类,则当做带precision属性的options处理
* @param {object} options - 格式化选项
* @param {boolean} options.rounded - 是否四舍五入,明确传入false时禁止四舍五入
* @param {boolean} options.grouping - 是否使用千位分隔符,默认false
* @param {boolean} options.percent - 是否百分比格式,默认false
* @param {boolean} options.trimTailZeros - 是否去除小数点后的尾部的0,默认false
* @param {any} options.prefmt
* - 预先格式化,支持多种形式处理
* - 如果传入数组类型的参数值,则循环执行数组中的函数,如果执行结果为string或number类型,则prefmt值取此结果并跳出循环
* - 如果传入函数类型的参数值,prefmt值取函数执行结果
* - 如果prefmt值为数字类型,则格式化prefmt值
* - 如果prefmt值不为undefined|null|false,则返回prefmt值,否则忽略prefmt
* @param {string} options.nanfmt - 输入值解析成数字为NAN时的格式化处理,类型为字符串,默认返回0的格式化数值
* @return {function} - 返回带fromat函数的格式化对象
*/
main.numberFormat = function (precision, options) {
if (typeof precision === 'object') {
options = precision;
precision = options.precision;
}
else if (!options || typeof options !== 'object') {
options = {};
}
//解析小数位精度
precision = parseInt(precision);
if (isNaN(precision)) {
precision = 2;
} else if (precision < 0) {
precision = 0;
} else if (precision > 10) {
precision = 10;
}
//构建Intl.NumberFormat
const fmt = new Intl.NumberFormat('en', {
useGrouping: !!options.grouping,
minimumFractionDigits: precision,
maximumFractionDigits: precision,
style: options.percent ? 'percent' : 'decimal'
});
//百分比格式特殊精度位处理
if (options.percent) precision += 2;
//预格式化处理
function preformat(prefmt, fmtArgs) {
//如果是数组类型,则以管道形式执行函数
if (Array.isArray(prefmt)) {
var result = false;
_.each(prefmt, x => {
let match = typeof x === 'function';
while (match) {
x = x.apply(this, fmtArgs);
switch (typeof x) {
case 'function':
continue;
case 'number':
case 'string':
result = x;
return false;
default: return true;
}
}
});
return result;
}
//执行函数处理
while (typeof prefmt === 'function') {
prefmt = prefmt.apply(this, fmtArgs);
}
return prefmt;
}
function _format(val) {
//预格式化处理
let prefmt = preformat(options.prefmt, arguments);
let type = typeof prefmt;
if (type === 'number') {
val = prefmt;
}
else if (type !== 'undefined' && prefmt !== null && prefmt !== false) {
return prefmt;
}
if (precision <= 4) {
val = _.round(val, precision + 4);
}
//数值格式化
if (options.rounded !== false) {
let negative = val < 0;
val = _.round(negative ? -val : val, precision);
negative && (val = -val);
} else {
val = _.floor(val, precision);
}
if (isNaN(val)) {
return typeof options.nanfmt === 'string' ? options.nanfmt : fmt.format(0);
}
return fmt.format(Math.abs(val) < 1e-6 ? 0 : val);
}
function format(val) {
let str = _format(val);
return options.trimTailZeros && str.endsWith("0") && precision > 0 ? str.replace(/\.?0+$/, '') : str;
}
format.precision = precision;
return format;
};
/**
* 格式化数字(依赖lodash.js)
* @param {object} number - 数字数值
* @param {any} precision - 小数位精度,默认2,如果是对象类,则当做带precision属性的options处理
* @param {object} options - 格式化选项
* @param {boolean} options.rounded - 是否四舍五入,类型为布尔值,默认true
* @param {boolean} options.grouping - 是否使用千位分隔符,类型为布尔值,默认false
* @param {boolean} options.percent - 是否百分比格式,类型为布尔值,默认false
* @param {string} options.nanfmt - 输入值解析成数字为NAN时的格式化处理,类型为字符串,默认返回0的格式化数值
* @param {any} options.prefmt - 参考main.numberFormat
* @return {function} - 返回带fromat函数的格式化对象
*/
main.formatNumber = function (number, precision, options) {
return new main.numberFormat(precision, options)(number);
};
//各种常用处理
const __alert = function (message) {
window.alert(message);
return false;
};
/**
* 显示页面加载组件,等待操作执行完成
* @param {string} method -- 'show'||'hide'
* @returns {jQuery} -- jQuery对象
*/
$.fn.waitMe = function (method) {
return this.each(function () {
if (this.showCount === undefined || this.showCount === null) {
this.showCount = 0;
}
var isBody = $(this).is('body');
var $waitme = $(this).children(".yl-waitme");
if (method === "show") {
if (!$waitme.length) {
!isBody && $(this).css("position") === "static" && $(this).css("position", "relative");
$waitme = $('
').appendTo('body');
}
var $t = $(`
`);
$t.appendTo('#toast-container').toast({ autohide: true, delay: delay || 3000 }).toast('show')
.on('hidden.bs.toast', function () { $t.remove(); }).on('click', function () {
$(this).toast('hide');
});
return false;
};
/**
* 显示警告信息
* @param {any} message -- 消息文本
* @returns {boolean} -- false
*/
main.warn = function (message) {
return main.message(message, 60 * 60 * 1000);
};
/**
* 显示警告弹窗
* @param {any} message -- 消息文本
* @returns {boolean} -- false
*/
main.alert = function (message, okCall) {
$.alert({
title: '提示',
content: (message || '').replace('\n', '
'),
buttons: {
ok: { text: '确定', action: okCall }
},
boxWidth: '450px',
useBootstrap: false,
animateFromElement: false
});
return false;
};
/**
* 显示确认弹窗
* @param {any} message -- 确认消息
* @param {any} okCallback -- 确认执行回调
* @param {any} cancelCallback -- 取消后回调
* @param {any} title -- 弹窗标题
*/
main.confirm = function (message, okCallback, cancelCallback, title) {
$.confirm({
title: title || '确认?',
content: message,
type: 'red',
buttons: {
ok: {
text: "确认",
btnClass: 'btn-primary',
keys: ['enter'],
action() {
if (okCallback) {
var tempCall = okCallback;
//确保只执行一次
okCallback = null;
tempCall();
}
}
},
cancel: {
text: "取消",
action() {
cancelCallback && cancelCallback();
}
}
},
useBootstrap: false,
boxWidth: '450px',
animateFromElement: false
});
};
//使用$.Deferred对象处理
main.confirmV2 = function (message, title) {
var deferred = $.Deferred();
$.confirm({
title: title || '确认?',
content: message,
type: 'red',
buttons: {
ok: {
text: "确认",
btnClass: 'btn-primary',
keys: ['enter'],
action: deferred.resolve
},
cancel: {
text: "取消",
action: deferred.reject
}
},
useBootstrap: false,
boxWidth: '450px',
animateFromElement: false
});
return deferred.promise();
};
main.waitMe = function (opt) {
if (typeof opt === 'boolean') {
opt = opt ? 'show' : 'hide';
} else {
opt = opt || "show";
}
$(document.body).waitMe(opt);
};
function __getWaitMeFunc(options) {
var waitMeFunc = main.waitMe;
if (typeof options !== 'undefined') {
var m = options.waitMe;
if (typeof m === 'undefined') {
m = options;
}
if (!m) {
waitMeFunc = function () { };
} else if (typeof m === 'function') {
waitMeFunc = m;
}
}
return waitMeFunc;
}
/**
* ⚠️【全局陷阱 / 接手必读】__post 的成败路由:
* - 成功(resp.success 为真 且 非 errcode) -> deferred.resolve(resp) (.done 触发)
* - 业务错误(resp.success===false / errcode) -> deferred.reject(resp) (.fail 触发,.done 不触发!)
* - 网络异常(ajax error) -> deferred.reject(resp) (.fail 触发)
* 即:业务错误走的是 reject,不是 resolve。调用方若只在 .done(...) 里写「业务失败/算不出来」的处理,
* 业务失败时那段代码根本不会执行 —— 这是 bug 静默失效的高发地。
* 典型实例:swapTradeEdit.js 的 calcBondForItem「约定2补充(计算器算不出→清另两字段+清标识)」曾因
* applyBondCalcFailure 只写在 .done 内、长期不生效;后改到 .fail 才修复(见该文件注释)。
* 凡要区分「成功 / 业务失败 / 网络异常」的逻辑,务必在 .done 之外再挂 .fail。
*/
function __post(url, data, deferred, options) {
if (!url) throw "post url 不能为空";
var waitMeFunc = __getWaitMeFunc(options);
var ajaxOptions = {
type: "POST",
data: data,
dataType: "json",
cache: false,
success(resp) {
if (options && options.dataType && options.dataType !== "json") {
deferred.resolve(resp);
return;
}
if (resp.success === false || resp.errcode) {
let alert = options && options.alertFn ? options.alertFn : main.alert;
let errmsg = (resp.msg || resp.errmsg || "错误").replace(/\n/g, '
');
alert(errmsg);
deferred.reject(resp);
} else {
resp.msg && main.message(resp.msg);
deferred.resolve(resp);
}
},
beforeSend(jqXHR) {
waitMeFunc(true);
},
complete(jqXHR, textStatus) {
waitMeFunc(false);
},
error(jqXHR, textStatus, errorThrown) {
options && options.suppressError || main.alert('请求失败');
console.error("请求失败,url:" + url + ",status:" + textStatus + ",error:" + errorThrown);
}
};
if (data instanceof FormData) {
ajaxOptions.processData = false;
ajaxOptions.contentType = false;
}
ajaxOptions = $.extend(ajaxOptions, options);
$.ajax(url, ajaxOptions).fail(deferred.reject);
}
/**
* 封装$.ajax方法
* @param {any} options
* -- boolean:是否显示加载等待
* -- function:自定义加载等待调用
* -- object:{waitMe,suppressError}
* @param {boolean} options.waitMe -- 是否显示加载等待
* @param {boolean} options.suppressError -- 禁止出错时提示
* @returns {jQuery} -- jquery延迟对象Deferred
*/
main.ajax = function (options) {
var deferred = $.Deferred();
var waitMeFunc = __getWaitMeFunc(options);
var ajaxOptions = {
type: "POST",
dataType: "json",
success(resp) {
if (resp.success === false) {
main.alert(resp.msg || "错误");
deferred.reject(resp);
} else {
main.message(resp.msg || "成功");
deferred.resolve(resp);
}
},
beforeSend: function () {
waitMeFunc(true);
},
complete: function () {
waitMeFunc(false);
},
error(jqXHR, textStatus, errorThrown) {
let error = "请求失败,url:" + url + ",status:" + textStatus + ",error:" + errorThrown;
(options.suppressError ? console.error : main.alert)(error);
}
};
ajaxOptions = $.extend(ajaxOptions, options);
$.ajax(ajaxOptions).fail(deferred.reject);
return deferred.promise();
};
/**
* 封装$.Post
* @param {any} url -- post url
* @param {any} data -- post data
* @param {any} options
* -- boolean:是否显示加载等待
* -- function:自定义加载等待调用
* -- object:{waitMe,suppressError}
* @param {boolean} options.waitMe -- 是否显示加载等待
* @param {boolean} options.suppressError -- 禁止出错时提示
* @returns {jQuery} -- jquery延迟对象Deferred
*/
// ⚠️ 见上方 __post 说明:本方法业务错误/网络异常走 reject,调用方需 .fail 才能捕获(只 .done 会漏掉失败分支)。
main.post = function (url, data, options) {
var d = $.Deferred();
__post(url, data, d, options);
return d.promise();
};
/**
* main.postSafe — 安全版 main.post,返回标准 Promise 而非 jQuery Deferred。
* ============================================================================
* 解决 main.post 的「业务错误走 reject、.done 不触发」陷阱(见 __post 注释)。
*
* 用法:
* main.postSafe('/api/calc', { price: 100 })
* .then(function(resp) { /* 成功(含 resp.success !== false)*\/ })
* .catch(function(resp) { /* 业务错误 / 网络异常 *\/ });
*
* 或者 async/await:
* try {
* var resp = await main.postSafe('/api/calc', { price: 100 });
* // 成功
* } catch (resp) {
* // 业务错误 / 网络异常(resp 可能是 { success:false, msg:'...' } 或 Error)
* }
*
* 与 main.post 的区别:
* - main.post 返回 jQuery Deferred:.done = 成功, .fail = 失败
* - main.postSafe 返回 Promise: .then = 成功, .catch = 失败
* - 行为一致,但 Promise 更符合标准,async/await 更自然
*
* @param {string} url -- post url
* @param {any} data -- post data
* @param {any} options -- 同 main.post
* @returns {Promise} -- 成功 resolve(resp),失败 reject(resp)
* ============================================================================
*/
main.postSafe = function (url, data, options) {
return new Promise(function (resolve, reject) {
main.post(url, data, options)
.done(function (resp) { resolve(resp); })
.fail(function (resp) { reject(resp); });
});
};
//确认提交
main.confirmPost = function (confirmText, url, data, options) {
var d = $.Deferred();
var okCallback = function () {
__post(url, data, d, options);
};
var cancelCallback = function () {
d.reject({ cancel: true });
};
main.confirm(confirmText, okCallback, cancelCallback, "提示");
return d.promise();
};
//以提交表单的方式打开新页面
main.formOpenNewPage = function (url, postData) {
var $f = $('