cloud-utils.common.js

/*!
 * cloud-utils v1.0.0
 * API
 * Copyright 2017-2020 windraxb. All Rights Reserved
 * Licensed under MIT
 */
 
'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

/**
 *
 * @param {number} arg1
 * @param {number} arg2
 * @returns {number} arg1加上arg2的精确结果
 * @example
 *
 * accAdd(0.1, 0.2)
 * // => 0.3
 */
function accAdd (arg1, arg2) {
  var r1;
  var r2;
  var m;

  try {
    r1 = arg1.toString().split('.')[1].length;
  } catch (e) {
    r1 = 0;
  }

  try {
    r2 = arg2.toString().split('.')[1].length;
  } catch (e) {
    r2 = 0;
  }

  var c = Math.abs(r1 - r2);
  m = Math.pow(10, Math.max(r1, r2));

  if (c > 0) {
    var cm = Math.pow(10, c);
    if (r1 > r2) {
      arg1 = Number(arg1.toString().replace('.', ''));
      arg2 = Number(arg2.toString().replace('.', '')) * cm;
    } else {
      arg1 = Number(arg1.toString().replace('.', '')) * cm;
      arg2 = Number(arg2.toString().replace('.', ''));
    }
  } else {
    arg1 = Number(arg1.toString().replace('.', ''));
    arg2 = Number(arg2.toString().replace('.', ''));
  }
  return (arg1 + arg2) / m
}

/**
 * 除法函数,用来得到精确的除法结果<br>
 * javascript的除法结果会有误差,在两个浮点数相除的时候会比较明显。这个函数返回较为精确的除法结果。
 *
 * @param {number} arg1
 * @param {number} arg2
 * @returns {number} arg1除以arg2的精确结果
 * @example
 *
 * accDiv(0.2, 0.3)
 * // => 0.6666666666666666
 */
function accDiv (arg1, arg2) {
  var t1 = 0;
  var t2 = 0;
  var r1, r2;

  if (+arg2 === 0) {
    throw new Error('0 is not allowed for divisor')
  }

  try {
    arg1.toString().split('.')[1] && (t1 += arg1.toString().split('.')[1].length);
  } catch (e) {
    console.error(e);
  }

  try {
    arg2.toString().split('.')[1] && (t2 += arg2.toString().split('.')[1].length);
  } catch (e) {
    console.error(e);
  }

  r1 = Number(arg1.toString().replace('.', ''));
  r2 = Number(arg2.toString().replace('.', ''));
  return (r1 / r2) * Math.pow(10, t2 - t1)
}

/**
 * 乘法函数,用来得到精确的乘法结果<br>
 * javascript的乘法结果会由误差,在两个浮点数相乘的时候会比较明显。这个函数返回较为精确的乘法结果
 *
 * @param {number} arg1
 * @param {number} arg2
 * @returns {number}  arg1乘arg2的精确结果
 * @example
 *
 * accMul(0.222, 0.3333)
 * // => 0.0739926
 */
function accMul (arg1, arg2) {
  var m = 0;
  var s1 = arg1.toString();
  var s2 = arg2.toString();

  try {
    s1.split('.')[1] && (m += s1.split('.')[1].length);
  } catch (e) {
    console.error(e);
  }

  try {
    s2.split('.')[1] && (m += s2.split('.')[1].length);
  } catch (e) {
    console.error(e);
  }

  return Number(s1.replace('.', '')) * Number(s2.replace('.', '')) / Math.pow(10, m)
}

/**
 * 减法函数,用来得到精确的减法结果<br>
 * javascript的减法结果会有误差,在两个浮点数相减的时候会比较明显。这个函数返回较为精确的减法结果。
 *
 * @param {number} arg1
 * @param {number} arg2
 * @returns {number} arg1减去arg2的精确结果
 * @example
 *
 * accSub(0.3, 0.2)
 * // => 0.1
 */
function accSub (arg1, arg2) {
  var r1 = 0;
  var r2 = 0;
  var m;
  var n;

  try {
    arg1.toString().split('.')[1] && (r1 = arg1.toString().split('.')[1].length);
  } catch (e) {
    console.error(e);
  }

  try {
    arg2.toString().split('.')[1] && (r2 = arg2.toString().split('.')[1].length);
  } catch (e) {
    console.error(e);
  }

  m = Math.pow(10, Math.max(r1, r2));
  n = Math.max(r1, r2);

  return ((arg1 * m - arg2 * m) / m).toFixed(n)
}

/**
 * 为数字加上单位:万或亿
 * @param {number} number 输入数字.
 * @param {number} decimalDigit 返回的小数点后最多的位数,默认为 2
 * @returns {string | *}  加上单位后的数字
 *
 * @example
 * addChineseUnit(1000.01)
 * // => 1000.01
 *
 * addChineseUnit(10000)
 * // => 1万
 *
 * addChineseUnit(99000)
 * // => 9.9万
 *
 * addChineseUnit(566000)
 * // => 56.6万
 *
 * addChineseUnit(5660000)
 * // => 566万
 *
 * addChineseUnit(44440000)
 * // => 4444万
 *
 * addChineseUnit(11111000)
 * // => 1111.1万
 *
 * addChineseUnit(444400000)
 * // => 4.44亿
 *
 * addChineseUnit(400000000000000000000000)
 * // => 3999.99万亿亿
 *
 * addChineseUnit(4000000000000000000000000)
 * // => 4亿亿亿
 */
function addChineseUnit (number, decimalDigit) {
  var addWan = function (integer, number, mutiple, decimalDigit) {
    var digit = getDigit(integer);
    if (digit > 3) {
      var remainder = digit % 8;
      remainder = remainder >= 5 ? 4 : remainder;
      return Math.round(number / Math.pow(10, remainder + mutiple - decimalDigit)) / Math.pow(10, decimalDigit) + '万'
    } else {
      return Math.round(number / Math.pow(10, mutiple - decimalDigit)) / Math.pow(10, decimalDigit)
    }
  };

  var getDigit = function (integer) {
    integer = Math.abs(integer);
    var digit = -1;
    while (integer >= 1) {
      digit++;
      integer = integer / 10;
    }
    return digit
  };

  return (function (number, decimalDigit) {
    decimalDigit = decimalDigit == null ? 2 : decimalDigit;
    var integer = Math.floor(number);
    var digit = getDigit(integer);
    var unit = [];
    if (digit > 3) {
      var multiple = Math.floor(digit / 8);
      if (multiple >= 1) {
        var tmp = Math.round(integer / Math.pow(10, 8 * multiple));
        unit.push(addWan(tmp, number, 8 * multiple, decimalDigit));
        for (var i = 0; i < multiple; i++) {
          unit.push('亿');
        }
        return unit.join('')
      } else {
        return addWan(integer, number, 0, decimalDigit)
      }
    } else {
      return parseFloat(number).toFixed(decimalDigit)
    }
  }(number, decimalDigit))
}

/**
 * 加密算法
 * 1.所有入参加入集合M,参数名做key, 值做value
 * 2.提供的密钥1(字段名appid)与密钥2(字段名secret)两项,以及当前时间戳(字段名time)也加入集合M,
 * 3.将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序)
 * 4.集合M所有值拼接成字符串,转化成UTF-8编码格式的字节数组, 最后需要取MD5码(signature摘要值)
 *
 * @param {object} params
 * @example
 *
 * const params = { mobile: '15858264900', nickname: 'windraxb', appkey: 'ertfgdf345435568123454rtoiko5=' };
 *
 * md5(encrypt(params).toUpperCase());
 * // => md5('APPKEY=ERTFGDF34543545=&MOBILE=15858264903&NICKNAME=windraxb')
 */
function encrypt (params) {
  // 顺序排列key
  var keys = Object.keys(params).sort();
  var str = [];

  keys.forEach(function (p) {
    str.push(p + '=' + params[p]);
  });

  return str.join('&')
}

/**
 * 格式化银行卡<br>
 * 用户在输入银行卡号时,需要以4位4位的形式显示,就是每隔4位加个空格,方便用户校对输入的银行卡是否正确<br>
 * **注:**一般数据库里面存的都是不带格式的原始数据,所以提交的时候记得过滤下空格再提交哦。毕竟格式化这种算是表现层,前端展示的时候处理下就好,业务逻辑什么用到的卡号可不是格式化后的呢。<br>
 * 还原`val.replace(/\s/g, '');`
 *
 * @param {string} val
 * @returns {*}
 * @example
 *
 * formatBankCard('6225365271562822');
 * // => 6225 3652 7156 2822
 */
function formatBankCard (val) {
  if (typeof val !== 'string') { throw new Error('输入值必须为字符串') }

  var len = val.length;
  var reg = /(\d{4})(?=\d)/g;

  if (len < 4) {
    return val
  } else {
    return val.replace(reg, '$1 ')
  }
}

/**
 * Date 转化为指定格式的String<br>
 * 月(M)、日(d)、12小时(h)、24小时(H)、分(m)、秒(s)、周(E)、季度(q)可以用 1-2 个占位符<br>
 * 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字)
 *
 * @param {string | number} date string支持形式:20160126 12:00:00,2016-01-26 12:00:00,2016.01.26 12:00:00,20160126,2016-01-26 12:00:00.0
 * @param {string} fmt
 * @returns {string}
 * @example
 *
 * formatDate(Date.now(), 'yyyy-MM-dd hh:mm:ss.S');
 * // => 2006-07-02 08:09:04.423
 *
 * formatDate(Date.now(), 'yyyy-MM-dd E HH:mm:ss');
 * // => 2009-03-10 二 20:09:04
 *
 * formatDate(Date.now(), 'yyyy-MM-dd EE hh:mm:ss');
 * // => 2009-03-10 周二 08:09:04
 *
 * formatDate(Date.now(), 'yyyy-MM-dd EEE hh:mm:ss');
 * // => 2009-03-10 星期二 08:09:04
 *
 * formatDate(Date.now(), 'yyyy-M-d h:m:s.S')
 * // => 2006-7-2 8:9:4.18
 */
function formatDate (date, fmt) {
  if ( date === void 0 ) date = new Date();
  if ( fmt === void 0 ) fmt = 'yyyy-MM-dd HH:mm:ss';

  if (typeof date === 'string') {
    date = new Date(formatTimeByPattern(date));
  } else if (typeof date === 'number') {
    date = new Date(date);
  }
  var o = {
    'M+': date.getMonth() + 1, // 月份
    'd+': date.getDate(), // 日
    'h+': date.getHours() % 12 === 0 ? 12 : date.getHours() % 12, // 小时
    'H+': date.getHours(), // 小时
    'm+': date.getMinutes(), // 分
    's+': date.getSeconds(), // 秒
    'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
    S: date.getMilliseconds() // 毫秒
  };
  var week = {
    0: '\u65e5',
    1: '\u4e00',
    2: '\u4e8c',
    3: '\u4e09',
    4: '\u56db',
    5: '\u4e94',
    6: '\u516d'
  };

  if (/(y+)/.test(fmt)) {
    fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
  }

  if (/(E+)/.test(fmt)) {
    fmt = fmt.replace(RegExp.$1, ((RegExp.$1.length > 1) ? (RegExp.$1.length > 2 ? '\u661f\u671f' : '\u5468') : '') + week[date.getDay() + '']);
  }

  for (var k in o) {
    if (new RegExp('(' + k + ')').test(fmt)) {
      fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)));
    }
  }

  return fmt
}

// val 字符串转换成 / 连接
// 20160126 12:00:00
// 2016-01-26 12:00:00
// 2016.01.26 12:00:00
// 20160126
// 2016-01-26 12:00:00.0
function formatTimeByPattern (val) {
  // 2016-05-23 13:58:02.0
  if (val.length > 19) {
    val = val.substring(0, 19);
  }

  var pattern = /-|\./g;
  var year;
  var month;
  var day;
  var reset;

  if (pattern.test(val)) {
    return val.replace(pattern, '/')
  } else {
    // 若无’-‘,则不处理
    if (!~val.indexOf('-')) {
      year = val.slice(0, 4);
      month = val.slice(4, 6);
      day = val.slice(6, 8);
      reset = val.slice(8);
      return year + '/' + month + '/' + day + reset
    }
  }
}

/**
 * 将时间转化为几天前,几小时前,几分钟前
 *
 * @param {number} ms
 * @returns {*}
 * @example
 *
 * formatTimeAgo(1505232000000);
 * // => 1天前
 */
function formatTimeAgo (ms) {
  ms = parseInt(ms);

  var timeNow = Date.now();
  var diff = (timeNow - ms) / 1000;
  var date = new Date();
  var days = Math.round(diff / (24 * 60 * 60));
  var hours = Math.round(diff / (60 * 60));
  var minutes = Math.round(diff / 60);
  var second = Math.round(diff);

  if (days > 0 && days < 2) {
    return days + '天前'
  } else if (days <= 0 && hours > 0) {
    return hours + '小时前'
  } else if (hours <= 0 && minutes > 0) {
    return minutes + '分钟前'
  } else if (minutes <= 0 && second >= 0) {
    return '刚刚'
  } else {
    date.setTime(ms);

    return (date.getFullYear() + '-' + f(date.getMonth() + 1) + '-' + f(date.getDate()) + ' ' + f(date.getHours()) + ':' + f(date.getMinutes()))
  }

  function f (n) {
    return (n < 10) ? '0' + n : n
  }
}

/**
 * 获取指定时间unix时间戳
 *
 * @param {string} time
 * @returns {number}
 * @example
 *
 * formatDateToTimeStamp('20160126 12:00:00');
 * // => 1453780800000
 *
 * formatDateToTimeStamp('2016-01-26 12:00:00');
 * // => 1453780800000
 *
 * formatDateToTimeStamp('2016.01.26 12:00:00');
 * // => 1453780800000
 *
 * formatDateToTimeStamp('20160126');
 * // => 1453737600000
 *
 * formatDateToTimeStamp('2016-01-26 12:00:00.0');
 * // => 1453780800000
 */
function formatDateToTimeStamp (time) {
  if (typeof time !== 'string') { throw new TypeError('数据类型必须是 string') }

  // 2016-05-23 13:58:02.0
  if (time.length > 19) {
    time = time.substring(0, 19);
  }

  var unixTime;
  var pattern = /-|\./g;
  var year;
  var month;
  var day;
  var reset;

  if (pattern.test(time)) {
    unixTime = time.replace(pattern, '/');
  } else {
    // 若无’-‘,则不处理
    if (!~time.indexOf('-')) {
      year = time.slice(0, 4);
      month = time.slice(4, 6);
      day = time.slice(6, 8);
      reset = time.slice(8);
      unixTime = year + '/' + month + '/' + day + reset;
    }
  }

  return Math.round(new Date(unixTime).getTime())
}

/**
 * 用符号(默认为逗号)格式化金钱
 *
 * @param {string} val
 * @param {string} symbol 默认`,`
 * @returns {string|*|XML|void}
 * @example
 *
 * formatMoney('1234567890');
 * // => 1,234,567,890
 */
function formatMoney (val, symbol) {
  if ( symbol === void 0 ) symbol = ',';

  if (typeof val !== 'string') { throw new TypeError('数据类型必须是 string') }

  return val.replace(/\B(?=(\d{3})+(?!\d))/g, symbol)
}

/**
 * 手机号码中间部分替换成指定符号
 *
 * @param {string} phone
 * @param {string} symbol 默认为`*`
 * @returns {string|*|XML|void}
 * @example
 *
 * formatPhone('15858264903');
 * // => 158****4903
 */
function formatPhone (phone, symbol) {
  if ( symbol === void 0 ) symbol = '****';

  if (typeof phone !== 'string') { throw new TypeError('数据类型必须是 string') }

  return phone.replace(/(\d{3})\d{4}(\d{4})/, ("$1" + symbol + "$2"))
}

/**
 * 获取location.href参数
 *
 * @param {string} name
 * @returns {*}
 * @example
 *
 * window.location.href = 'http://www.baidu.com/?a=1&b=2';
 *
 * getLocationHrefParam('a');
 * // => 1
 */
function getLocationHrefParam (name) {
  // 构造一个含有目标参数的正则表达式对象
  var r = new RegExp('(\\?|#|&)' + name + '=([^&#]*)(&|#|$)');
  var m = window.location.href.match(r);

  if (r !== null) { return decodeURIComponent(!m ? '' : m[2]) }
  return null
}

/**
 * 获取location.search的参数
 *
 * @param {string} name
 * @returns {*}
 * @example
 *
 * window.location.href = 'http://www.baidu.com/?a=1&b=2';
 *
 * getLocationSearchParam('a');
 * // => 1
 */
function getLocationSearchParam (name) {
  // 构造一个含有目标参数的正则表达式对象
  var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)');
  // 匹配目标参数
  var r = window.location.search.substr(1).match(reg);

  if (r !== null) { return decodeURIComponent(r[2]) }

  return null
}

/**
 * html字符解码
 *
 * @param {string} str
 * @returns {string}
 * @example
 *
 * htmlDecode('&lt;script&gt;');
 * // => <script>
 */
function htmlDecode (str) {
  if (typeof str === 'string' && str.length === 0) { return }

  var s = str.replace(/&amp;/g, '&');

  return s.replace(/&lt;/g, '<')
    .replace(/&gt;/g, '>')
    .replace(/&#39;/g, '\'')
    .replace(/&nbsp;/g, ' ')
    .replace(/&quot;/g, '"')
    .replace(/<br>/g, '\\n')
}

/**
 * html字符编码
 *
 * @param {string} str
 * @returns {string}
 * @example
 *
 * htmlEncode('<script>');
 * // => &lt;script&gt;
 */
function htmlEncode (str) {
  if (typeof str === 'string' && str.length === 0) { return }

  var s = str.replace(/&/g, '&amp;');

  return s.replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/'/g, '&#39;')
    .replace(/ /g, '&nbsp;')
    .replace(/"/g, '&quot;')
    .replace(/\n/g, '<br>')
}

/**
 * 是否是支付宝内核
 *
 * @returns {boolean}
 * @example
 *
 * inAlipay();
 * // => false
 */
function inAlipay () {
  if (typeof navigator === 'undefined') { return }

  var ua = navigator.userAgent.toLowerCase();
  return ua.indexOf('alipayclient') !== -1
}

/**
 * 是否是微信内核
 *
 * @returns {boolean}
 * @example
 *
 * inWeixin();
 * // => false
 */
function inWeixin () {
  if (typeof navigator === 'undefined') { return }

  var ua = navigator.userAgent.toLowerCase();

  return ua.indexOf('micromessenger') !== -1
}

/**
 * 是否为有效的身份证号,支持1/2代(15位/18位数字)
 *
 * @param {string} val
 * @returns {boolean}
 * @example
 *
 * isCardId('411423198807127834');
 * // => true
 */
function isCardId (val) {
  var reg = /(^\d{8}(0\d|10|11|12)([0-2]\d|30|31)\d{3}$)|(^\d{6}(18|19|20)\d{2}(0\d|10|11|12)([0-2]\d|30|31)\d{3}(\d|X|x)$)/;

  return reg.test(val)
}

/**
 * 是否为数字
 *
 * @param {string} val
 * @returns {boolean}
 * @example
 *
 * isDigit('abc');
 * // => false
 */
function isDigit (val) {
  var reg = /^-?\d+\.?\d*$/;

  return reg.test(val)
}

/**
 * 是否为空对象
 *
 * @param val
 * @returns {boolean}
 * @example
 *
 * isEmptyObject({});
 * // => true
 */
function isEmptyObject (val) {
  if (val !== Object(val)) { return false }
  return Object.keys(val).length === 0
}

/**
 * 是否为闰年
 *
 * @param {number} val
 * @returns {boolean}
 * @example
 *
 * isLeapYear(2000);
 * // => true
 */
function isLeapYear (val) {
  if (typeof val !== 'number') { throw new TypeError('数据类型必须是 number') }

  if (val % 4 === 0 && val % 100 !== 0) {
    return true
  } else {
    return val % 400 === 0
  }
}

/**
 * 是否为字母
 *
 * @param {string} val
 * @returns {boolean}
 * @example
 *
 * isLetters('1234');
 * // => false
 */
function isLetters (val) {
  var reg = /^[a-z]+$/i;

  return reg.test(val)
}

/**
 * 是否为有效的邮箱地址<br>
 * 名称允许汉字、字母、数字,域名只允许英文域名<br>
 * 中文如:杨元庆001Abc@lenovo.com.cn
 *
 * @param {string} val
 * @returns {boolean}
 * @example
 *
 * isValidEmail('123456@qq.com');
 * // => true
 */
function isValidEmail (val) {
  var reg = /^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/i;

  return reg.test(val)
}

/**
 * 对整数进行前置补0
 *
 * @param {number} num 数值
 * @param {number} size 要补0的位数
 * @returns {string}
 * @example
 *
 * preZeroFill(12, 3);
 * // => 012
 */
function preZeroFill (num, size) {
  if (num >= Math.pow(10, size)) {
    return num.toString()
  } else {
    var _str = Array(size + 1).join('0') + num;
    return _str.slice(_str.length - size)
  }
}

/**
 * 字节转换为B,KB,MB
 * @param {Number} bytes
 * @returns {string}
 * @example
 *
 * bytesToSize(10000)
 * // => 9.8 KB
 */
function bytesToSize (bytes) {
  var sizes = ['B', 'KB', 'MB'];
  if (bytes === 0) { return 'n/a' }
  var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
  return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i]
}

/**
 * base64转blob
 *
 * @param {string} dataURL
 * @returns {*}
 * @example
 *
 * const URI = 'data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTAyNCAxMDI0IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48ZGVmcz48c3R5bGUvPjwvZGVmcz48cGF0aCBkPSJNOTU5LjQzNiAyNDMuNUwzNzcuNDEzIDg3MC4yNyA2NC4wMiA0NjcuMzQ0bDExNC43MjctOTcuOTMgMTk4LjY2NiAyMDcuMDYgNDkyLjQ4Mi00MjIuNTEgODkuNTQgODkuNTM3em0wIDAiIGZpbGw9IiM0M2E5ZjEiLz48L3N2Zz4=';
 *
 * dataURLToBlob(URI);
 * // => Blob {size: 248, type: "image/svg+xml"}
 */
function dataURLToBlob (dataURL) {
  var BASE64_MARKER = ';base64,';
  var parts;
  var contentType;
  var raw;

  if (dataURL.indexOf(BASE64_MARKER) === -1) {
    parts = dataURL.split(',');
    contentType = parts[0].split(':')[1];
    raw = decodeURIComponent(parts[1]);

    return new Blob([raw], { type: contentType })
  }

  parts = dataURL.split(BASE64_MARKER);
  contentType = parts[0].split(':')[1];
  raw = window.atob(parts[1]);
  var rawLength = raw.length;
  var uint8Array = new Uint8Array(rawLength);

  for (var i = 0; i < rawLength; ++i) {
    uint8Array[i] = raw.charCodeAt(i);
  }

  return new Blob([uint8Array], { type: contentType })
}

/**
 * 获取设备像素比
 *
 * @returns {number}
 * @example
 *
 * // window.navigator.appVersion(5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1)
 * getPixelRatio();
 * // => 2
 */
function getPixelRatio () {
  var ctx = document.createElement('canvas').getContext('2d');
  var dpr = window.devicePixelRatio || 1;
  var bsr = ctx.webkitBackingStorePixelRatio ||
      ctx.mozBackingStorePixelRatio ||
      ctx.msBackingStorePixelRatio ||
      ctx.oBackingStorePixelRatio ||
      ctx.backingStorePixelRatio || 1;

  return dpr / bsr
}

/**
 * 获取移动设备信息,如是否是iOS,android等
 *
 * @returns {{}}
 * @example
 *
 * getDevice();
 * // => {"androidChrome":false,"ipad":false,"iphone":true,"android":false,"ios":true,"os":"ios","osVersion":"9.1","webView":null}
 */
function getDevice () {
  var device = {};
  var ua = navigator.userAgent;
  var android = ua.match(/(Android);?[\s/]+([\d.]+)?/);
  var ipad = ua.match(/(iPad).*OS\s([\d_]+)/);
  var ipod = ua.match(/(iPod)(.*OS\s([\d_]+))?/);
  var iphone = !ipad && ua.match(/(iPhone\sOS)\s([\d_]+)/);

  device.ios = device.android = device.iphone = device.ipad = device.androidChrome = false;

  // Android
  if (android) {
    device.os = 'android';
    device.osVersion = android[2];
    device.android = true;
    device.androidChrome = ua.toLowerCase().indexOf('chrome') >= 0;
  }
  if (ipad || iphone || ipod) {
    device.os = 'ios';
    device.ios = true;
  }
  // iOS
  if (iphone && !ipod) {
    device.osVersion = iphone[2].replace(/_/g, '.');
    device.iphone = true;
  }
  if (ipad) {
    device.osVersion = ipad[2].replace(/_/g, '.');
    device.ipad = true;
  }
  if (ipod) {
    device.osVersion = ipod[3] ? ipod[3].replace(/_/g, '.') : null;
    device.iphone = true;
  }
  // iOS 8+ changed UA
  if (device.ios && device.osVersion && ua.indexOf('Version/') >= 0) {
    if (device.osVersion.split('.')[0] === '10') {
      device.osVersion = ua.toLowerCase().split('version/')[1].split(' ')[0];
    }
  }

  // Webview
  device.webView = (iphone || ipad || ipod) && ua.match(/.*AppleWebKit(?!.*Safari)/i);

  return device
}

/**
 * 获取浏览器的类型和版本号
 *
 * @returns {{type: string, version: string}}
 * @example
 *
 * getBrowser();
 * // => {type: "chrome", version: "60.0.3112.101"}
 */
function getBrowser () {
  var ua = navigator.userAgent.toLowerCase();
  var type = 'UNKNOW';
  var v;
  var check = function (regex) {
    return regex.test(ua)
  };

  // IE
  if (check(/msie/) && !check(/opera/)) {
    type = 'ie';
    v = /msie[/|\s]*([\d+?.?]+)/.exec(ua);
  } else if ((!check(/webkit/) && check(/gecko/) && check(/firefox/)) && !check(/opera/)) { // firefox
    type = 'firefox';
    v = /firefox[/|\s]*([\d+?.?]+)/.exec(ua);
  } else if (check(/\bchrome\b/)) { // chrome
    type = 'chrome';
    v = /chrome[/|\s]*([\d+?.?]+)/.exec(ua);
  } else if (check(/applewebkit/) && check(/safari/)) { // safari (!check(/\bchrome\b/) is ensure by non-chrome above)
    type = 'safari';
    v = /version[/|\s]*([\d+?.?]+)/.exec(ua);
  } else if (check(/opera/)) {
    type = 'opera';
    v = /version[/|\s]*([\d+?.?]+)/.exec(ua) || /opera[/|\s]*([\d+?.?]+)/.exec(ua);
  }

  return {
    type: type,
    version: (v && v[1]) ? v[1] : 'UNKNOW'
  }
}

/**
 * 得到两个时间的时间差(返回天数)
 *
 * @param {number} startDay 开始时间戳
 * @param {number} endDay   结束时间戳
 * @returns {number}
 * @example
 *
 * getDiffDay(1501516800000, 1504195200000);
 * // => 31
 */
function getDiffDay (startDay, endDay) {
  startDay = Number(startDay);
  endDay = Number(endDay);
  return Math.abs(endDay - startDay) / (24 * 1000 * 3600)
}

/**
 * Dom操作,元素是否包含某个class
 * @param el  HTML元素
 * @param cls class类名
 * @returns {boolean}
 * @example
 *
 * <div class="box flex"></div>
 * hasClass(document.querySelector('.box'), 'flex');
 * // => true
 */
function hasClass (el, cls) {
  if (!el || !cls) { return false }
  if (cls.indexOf(' ') !== -1) { throw new Error('className should not contain space.') }
  if (el.classList) {
    return el.classList.contains(cls)
  } else {
    return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1
  }
}

/**
 * dom操作,元素添加某个class
 * @param el  HTML元素
 * @param cls class名称
 * @example
 *
 * <div class="box flex"></div>
 * addClass(document.querySelector('.box'), 'flex1');
 * // => <div class="box flex flex1"></div>
 */
function addClass (el, cls) {
  if (!el) { return }
  var curClass = el.className;
  var classes = (cls || '').split(' ');

  for (var i = 0, length = classes.length; i < length; i++) {
    var clsName = classes[i];
    if (!clsName) { continue }

    if (el.classList) {
      el.classList.add(clsName);
    } else {
      if (!hasClass(el, clsName)) {
        curClass += ' ' + clsName;
      }
    }
  }

  if (!el.classList) {
    el.className = curClass;
  }
}

var trim = function (string) {
  return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '')
};

/**
 * Dom 操作,元素删除某个 class
 *
 * @param el HTML元素
 * @param cls css类名
 * @example
 *
 * <div class="box flex"></div>
 * removeClass(document.querySelector('.box'), 'flex');
 * // => <div class="box"></div>
 */
function removeClass (el, cls) {
  if (!el || !cls) { return }
  var classes = cls.split(' ');
  var curClass = ' ' + el.className + ' ';

  for (var i = 0, j = classes.length; i < j; i++) {
    var clsName = classes[i];
    if (!clsName) { continue }

    if (el.classList) {
      el.classList.remove(clsName);
    } else {
      if (hasClass(el, clsName)) {
        curClass = curClass.replace(' ' + clsName + ' ', ' ');
      }
    }
  }
  if (!el.classList) {
    el.className = trim(curClass);
  }
}

/**
 * 格式化数字、金额、千分位、保留几位小数、舍入舍去
 *
 * @param number 要格式化的数字
 * @param decimals 保留几位小数
 * @param decPoint 小数点符号
 * @param thousandsSep 千分位符号
 * @param roundTag 舍入参数,默认 'ceil' 向上取,'floor'向下取,'round' 四舍五入
 * @returns {XML|void|*|string}
 * @example
 *
 * formatNumber(2, 2, '.', ',');
 * // => 2.00
 */

function formatNumber (number, decimals, decPoint, thousandsSep, roundTag) {
  if ( decimals === void 0 ) decimals = 2;
  if ( decPoint === void 0 ) decPoint = '.';
  if ( thousandsSep === void 0 ) thousandsSep = ',';
  if ( roundTag === void 0 ) roundTag = 'ceil';

  number = (number + '').replace(/[^0-9+-Ee.]/g, '');
  var n = !isFinite(+number) ? 0 : +number;
  var prec = !isFinite(+decimals) ? 0 : Math.abs(decimals);
  var sep = thousandsSep || ',';
  var dec = decPoint || '.';
  var re = /(-?\d+)(\d{3})/;
  var s = '';
  var toFixedFix = function (n, prec) {
    var k = Math.pow(10, prec);
    return '' + parseFloat(Math[roundTag](parseFloat((n * k).toFixed(prec * 2))).toFixed(prec * 2)) / k
  };
  s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
  while (re.test(s[0])) {
    s[0] = s[0].replace(re, '$1' + sep + '$2');
  }
  if ((s[1] || '').length < prec) {
    s[1] = s[1] || '';
    s[1] += new Array(prec - s[1].length + 1).join('0');
  }
  return s.join(dec)
}

/**
 * 版本比较
 *
 * @param v1 老版本
 * @param v2 新版本
 * @returns {number} v1 > v2 => 1, v1 < v2 => -1, v1 === v2 => 0
 * @example
 *
 * compareVersion('10.1.8', '10.0.4');
 * // => 1
 * compareVersion('10.0.1', '10.0.1');
 * // => 0
 * compareVersion('10.1.1', '10.2.2');
 * // => -1
 */

/* eslint-disable */
function compareVersion (v1, v2) {
  var semver = /^v?(?:\d+)(\.(?:[x*]|\d+)(\.(?:[x*]|\d+)(\.(?:[x*]|\d+))?(?:-[\da-z\-]+(?:\.[\da-z\-]+)*)?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?)?)?$/i;

  function indexOrEnd (str, q) {
    return str.indexOf(q) === -1 ? str.length : str.indexOf(q)
  }

  function split (v) {
    var c = v.replace(/^v/, '').replace(/\+.*$/, '');
    var patchIndex = indexOrEnd(c, '-');
    var arr = c.substring(0, patchIndex).split('.');
    arr.push(c.substring(patchIndex + 1));
    return arr
  }

  function tryParse (v) {
    return isNaN(Number(v)) ? v : Number(v)
  }

  function validate (version) {
    if (typeof version !== 'string') {
      throw new TypeError('Invalid argument expected string')
    }
    if (!semver.test(version)) {
      throw new Error('Invalid argument not valid semver (\'' + version + '\' received)')
    }
  }

  [v1, v2].forEach(validate);

  var s1 = split(v1);
  var s2 = split(v2);

  for (var i = 0; i < Math.max(s1.length - 1, s2.length - 1); i++) {
    var n1 = parseInt(s1[i] || 0, 10);
    var n2 = parseInt(s2[i] || 0, 10);

    if (n1 > n2) { return 1 }
    if (n2 > n1) { return -1 }
  }

  var sp1 = s1[s1.length - 1];
  var sp2 = s2[s2.length - 1];

  if (sp1 && sp2) {
    var p1 = sp1.split('.').map(tryParse);
    var p2 = sp2.split('.').map(tryParse);

    for (i = 0; i < Math.max(p1.length, p2.length); i++) {
      if (p1[i] === undefined || typeof p2[i] === 'string' && typeof p1[i] === 'number') { return -1 }
      if (p2[i] === undefined || typeof p1[i] === 'string' && typeof p2[i] === 'number') { return 1 }

      if (p1[i] > p2[i]) { return 1 }
      if (p2[i] > p1[i]) { return -1 }
    }
  } else if (sp1 || sp2) {
    return sp1 ? -1 : 1
  }

  return 0
}

/**
 * 主动防御
 * 对于我们操作的数据,尤其是由 API 接口返回的,时常会有一个很复杂的深层嵌套的数据结构。为了代码的健壮性,很多时候需要对每一层访问都作空值判断,就像这样:
 props.user &&
 props.user.posts &&
 props.user.posts[0] &&
 props.user.posts[0].comments &&
 props.user.posts[0].comments[0]
 代码看起来相当不美观,因此提供了一个非常简洁明了的原生的方式。
 *
 * @param p 属性列表
 * @param o 对象
 * @returns {*} 如果正常访问到,则返回对应的值,否则返回 null。
 * @example
 *
 * var props = {
 *  user: {
 *    post: [{
 *      comments: 'test'
 *    }]
 *  }
 * };
 * getIn(['user', 'post', 0, 'comments'], props);
 * // => test
 */
function getIn (p, o) {
  return p.reduce(function (xs, x) {
    return (xs && xs[x]) ? xs[x] : null
  }, o)
}

/**
 *  Anagrams of string(带有重复项)
 * 使用递归。对于给定字符串中的每个字母,为字母创建字谜。使用map()将字母与每部分字谜组合,然后使用reduce()将所有字谜组合到一个数组中,最基本情况是字符串长度等于2或1。
 *
 * @param str
 * @returns {*}
 * @example
 *
 * anagrams('abc');
 * // => ['abc','acb','bac','bca','cab','cba']
 */
function anagrams (str) {
  if (str.length < 2) { return str.length === 2 ? [str, str[1] + str[0]] : [str] }

  return str.split('').reduce(function (acc, letter, i) {
    return acc.concat(anagrams(str.slice(0, i) + str.slice(i + 1)).map(function (val) { return letter + val; }))
  }, [])
}

/**
 * 大写每个单词的首字母
 *
 * @param str
 * @returns {string}
 * @example
 *
 * capitalizeEveryWord('hello world!');
 * // => 'Hello World!'
 */
function capitalizeEveryWord (str) {
  return str.replace(/\b[a-z]/g, function (char) { return char.toUpperCase(); })
}

/**
 * 获取数组的最后一项
 *
 * @param array
 * @returns {boolean}
 * @example
 *
 * last(['1,2,3']);
 * // => '3';
 */
function last (array) {
  return Array.isArray(array) && array.slice(-1)[0]
}

/**
 * 测试函数所花费的时间
 *
 * @param callback
 * @returns {*}
 * @example
 *
 * timeTaken(() => Math.pow(2, 10));
 * // => 1024
 */
function timeTaken (callback) {
  if (typeof callback !== 'function') { throw new Error('callback 必须为可执行的函数') }
  console.time('timeTaken');
  var r = callback();
  console.timeEnd('timeTaken');

  return r
}

/**
 * 滚动到顶部
 * 使用document.documentElement.scrollTop或document.body.scrollTop获取到顶部的距离。从顶部滚动一小部分距离。
 使用window.requestAnimationFrame()来滚动。
 *
 * @example
 *
 * scrollToTop();
 */
function scrollToTop () {
  var c = document.documentElement.scrollTop || document.body.scrollTop;

  if (c > 0) {
    window.requestAnimationFrame(scrollToTop);
    window.scrollTo(0, c - c / 8);
  }
}

/**
 * 是否为中文
 *
 * @param {string} str
 * @returns {boolean}
 * @example
 *
 * isChinese('中文');
 * // => true
 */
function isChinese (str) {
  var reg = /^(?:[\u3400-\u4DB5\u4E00-\u9FEA\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29]|[\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0])+$/;

  return reg.test(str)
}

/**
 * 是否为 HTML 标签
 *
 * @param {string} str
 * @returns {boolean}
 * @example
 *
 * isHTML('<p>123</p>');
 * // => true
 */
function isHTML (str) {
  var reg = /<("[^"]*"|'[^']*'|[^'">])*>/;

  return reg.test(str)
}

/**
 * 获取某月有多少天
 * 摘自:https://segmentfault.com/a/1190000013041329
 *
 * @param  {time} 时间
 * @return {number} 天数
 * @example
 *
 * getMonthOfDay('2018-1-29')
 * // => 31
 */
function getMonthOfDay (time) {
  var date = new Date(time);
  var year = date.getFullYear();
  var mouth = date.getMonth() + 1;
  var days;

  // 当月份为二月时,根据闰年还是非闰年判断天数
  if (mouth === 2) {
    days = (year % 4 === 0 && year % 100 === 0 && year % 400 === 0) || (year % 4 === 0 && year % 100 !== 0) ? 28 : 29;
  } else if (mouth === 1 || mouth === 3 || mouth === 5 || mouth === 7 || mouth === 8 || mouth === 10 || mouth === 12) {
    // 月份为:1,3,5,7,8,10,12 时,为大月.则天数为31;
    days = 31;
  } else {
    // 其他月份,天数为:30.
    days = 30;
  }
  return days
}

/**
 * 返回指定长度的天数集合
 * 摘自:https://segmentfault.com/a/1190000013041329
 *
 * @param  {time} 时间
 * @param  {len} 长度
 * @param  {direction} 方向:  1: 前几天;  2: 后几天;  3:前后几天  默认 3
 * @return {Array} 数组
 * @example
 *
 * getDays('2018-1-29', 6, 1)
 * // => ["2018-1-26", "2018-1-27", "2018-1-28", "2018-1-29", "2018-1-30", "2018-1-31", "2018-2-1"]
 */
function getDays (time, len, direction) {
  var tt = new Date(time);
  var getDay = function (day) {
    var t = new Date(time);
    t.setDate(t.getDate() + day);
    var m = t.getMonth() + 1;
    return t.getFullYear() + '-' + m + '-' + t.getDate()
  };
  var arr = [];
  if (direction === 1) {
    for (var i = 1; i <= len; i++) {
      arr.unshift(getDay(-i));
    }
  } else if (direction === 2) {
    for (var i$1 = 1; i$1 <= len; i$1++) {
      arr.push(getDay(i$1));
    }
  } else {
    for (var i$2 = 1; i$2 <= len; i$2++) {
      arr.unshift(getDay(-i$2));
    }
    arr.push(tt.getFullYear() + '-' + (tt.getMonth() + 1) + '-' + tt.getDate());
    for (var i$3 = 1; i$3 <= len; i$3++) {
      arr.push(getDay(i$3));
    }
  }
  return direction === 1 ? arr.concat([tt.getFullYear() + '-' + (tt.getMonth() + 1) + '-' + tt.getDate()])
    : direction === 2 ? [tt.getFullYear() + '-' + (tt.getMonth() + 1) + '-' + tt.getDate()].concat(arr) : arr
}

/**
 * 获取某个日期是当年中的第几天
 *
 * @param time
 * @returns {number}
 * @example
 *
 * getDayOfYear('2014-01-10')
 * => 10
 */
function getDayOfYear (time) {
  var firstDayYear = getFirstDayOfYear(time);
  var numSecond = (new Date(time).getTime() - new Date(firstDayYear).getTime()) / 1000;
  return Math.ceil(numSecond / (24 * 3600))
}

// 获取某年的第一天
function getFirstDayOfYear (time) {
  var year = new Date(time).getFullYear();
  return year + '-01-01 00:00:00'
}

/**
 * 获取某个日期在这一年的第几周
 *
 * @param time
 * @returns {number}
 * @example
 *
 * getDayOfYearWeek('2014-01-10')
 * => 2
 */
function getDayOfYearWeek (time) {
  var numDays = getDayOfYear(time);
  return Math.ceil(numDays / 7)
}

/**
 * 获取某年有多少天
 *
 * @param time
 * @returns {number}
 * @example
 *
 * getYearOfDay('2014')
 * => 365
 */
function getYearOfDay (time) {
  var firstDayYear = getFirstDayOfYear$1(time);
  var lastDayYear = getLastDayOfYear(time);
  var numSecond = (new Date(lastDayYear).getTime() - new Date(firstDayYear).getTime()) / 1000;
  return Math.ceil(numSecond / (24 * 3600))
}

// 获取某年的第一天
function getFirstDayOfYear$1 (time) {
  var year = new Date(time).getFullYear();
  return year + '-01-01 00:00:00'
}

// 获取某年最后一天
function getLastDayOfYear (time) {
  var year = new Date(time).getFullYear();
  var dateString = year + '-12-01 00:00:00';
  var endDay = getMonthOfDay(dateString);
  return year + '-12-' + endDay + ' 23:59:59'
}

/**
 * 数字金额大写转换,最多支持千亿
 *
 * @param n {number} 数字金额
 * @returns {string}
 * @example
 *
 * changeMoneyToChinese(100111);
 * => "壹拾万零壹佰壹拾壹元整"
 *
 * changeMoneyToChinese(7.52);
 * => "柒元伍角贰分"
 *
 * changeMoneyToChinese(951434677682.00);
 * => "玖仟伍佰壹拾肆亿叁仟肆佰陆拾柒万柒仟陆佰捌拾贰元整"
 */
function changeMoneyToChinese (n) {
  n = +n;
  if (isNaN(n)) {
    console.warn('params type error');
    return
  }

  if ((n > 0 && n.toString().length > 12) || (n < 0 && n.toString().length > 13)) {
    throw new Error('number is too large')
  }

  var fraction = ['角', '分'];
  var digit = [
    '零', '壹', '贰', '叁', '肆',
    '伍', '陆', '柒', '捌', '玖'
  ];
  var unit = [
    ['元', '万', '亿'],
    ['', '拾', '佰', '仟']
  ];
  var head = +n < 0 ? '欠' : '';
  n = Math.abs(+n);
  var s = '';
  for (var i = 0; i < fraction.length; i++) {
    s += (digit[Math.floor(shiftRight(n, 1 + i)) % 10] + fraction[i]).replace(/零./, '');
  }
  s = s || '整';
  n = Math.floor(n);
  for (var i$1 = 0; i$1 < unit[0].length && n > 0; i$1++) {
    var p = '';
    for (var j = 0; j < unit[1].length && n > 0; j++) {
      p = digit[n % 10] + unit[1][j] + p;
      n = Math.floor(shiftLeft(n, 1));
    }
    s = p.replace(/(零.)*零$/, '').replace(/^$/, '零') + unit[0][i$1] + s;
  }

  return head + s.replace(/(零.)*零元/, '元')
    .replace(/(零.)+/g, '零')
    .replace(/^整$/, '零元整')
}

//  向右移位
function shiftRight (number, digit) {
  digit = parseInt(digit, 10);
  var value = number.toString().split('e');
  return +(value[0] + 'e' + (value[1] ? (+value[1] + digit) : digit))
}

function shiftLeft (number, digit) {
  digit = parseInt(digit, 10);
  var value = number.toString().split('e');
  return +(value[0] + 'e' + (value[1] ? (+value[1] - digit) : -digit))
}

/**
 * 数字转换成中文的大写数字
 *
 * @param num
 * @returns {string}
 * @example
 *
 * numberToChinese(10001010);
 * => "一千万一千一十"
 */
function numberToChinese (num) {
  var AA = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
  var BB = ['', '十', '百', '千', '万', '亿', '点', ''];
  var a = ('' + num).replace(/(^0*)/g, '').split('.');
  var k = 0;
  var re = '';
  for (var i = a[0].length - 1; i >= 0; i--) {
    switch (k) {
      case 0:
        re = BB[7] + re;
        break
      case 4:
        if (!new RegExp('0{4}//d{' + (a[0].length - i - 1) + '}$')
          .test(a[0])) { re = BB[4] + re; }
        break
      case 8:
        re = BB[5] + re;
        BB[7] = BB[5];
        k = 0;
        break
    }
    if (k % 4 === 2 && +a[0].charAt(i + 2) !== 0 && +a[0].charAt(i + 1) === 0) { re = AA[0] + re; }
    if (+a[0].charAt(i) !== 0) { re = AA[a[0].charAt(i)] + BB[k % 4] + re; }
    k++;
  }

  if (a.length > 1) {
    // 加上小数部分(如果有小数部分)
    re += BB[6];
    for (var i$1 = 0; i$1 < a[1].length; i$1++) { re += AA[a[1].charAt(i$1)]; }
  }
  if (re === '一十') { re = '十'; }
  if (re.match(/^一/) && re.length === 3) { re = re.replace('一', ''); }
  return re
}

/**
 * 科学计数法转化为数值字符串形式
 *
 * @param {number} num
 * @returns {string}
 * @example
 *
 * toNonExponential(3.3e-7);
 * => // "0.00000033"
 *
 * toNonExponential(3e-7);
 * => // "0.0000003"
 *
 * toNonExponential(1.401e10);
 * => // "14010000000"
 *
 * toNonExponential(0.0004);
 * => // "0.0004"
 */
function toNonExponential (num) {
  if (typeof num !== 'number') { throw new TypeError('数据类型必须是 number') }

  var m = num.toExponential().match(/\d(?:\.(\d*))?e([+-]\d+)/);
  return num.toFixed(Math.max(0, (m[1] || '').length - m[2]))
}

/**
 * 获取网址参数
 * @param {string} url
 * @returns {{}} 返回包含当前URL参数的对象。
 * @example
 *
 * getURLParameters('http://url.com/page?name=Adam&surname=Smith');
 * => // {name: 'Adam', surname: 'Smith'}
 */
function getURLParameters (url) {
  if ( url === void 0 ) url = window.location.href;

  if (typeof url !== 'string') { throw new TypeError('数据类型必须是 string') }
  var paramsArr = url.match(/([^?=&]+)(=([^&]*))/g);
  if (!paramsArr) { return {} }
  return paramsArr.reduce(function (a, v) { return ((a[v.slice(0, v.indexOf('='))] = v.slice(v.indexOf('=') + 1)), a); }, {})   // eslint-disable-line
}

/**
 * 根据提供函数返回的值映射一个新对象
 *
 * @param obj
 * @param fn
 * @returns {{}}
 * @example
 *
 * const users = {
  fred: { user: 'fred', age: 40 },
  pebbles: { user: 'pebbles', age: 1 }
};
 * mapValues(users, u => u.age);
 * // => { fred: 40, pebbles: 1 }
 */
var mapValues = function (obj, fn) { return Object.keys(obj).reduce(function (acc, k) {
    acc[k] = fn(obj[k], k, obj);
    return acc
  }, {}); };

/**
 * 根据提供函数生成的键生成一个新对象
 * 使用 Object.keys(obj) 来迭代对象的键。 使用 Array.reduce() 创建一个具有相同值的新对象,并使用 fn 来映射键。
 *
 * @param obj
 * @param fn
 * @returns {{}}
 * @example
 *
 * mapKeys({ a: 1, b: 2 }, (val, key) => key + val);
 * // => { a1: 1, b2: 2 }
 */
var mapKeys = function (obj, fn) { return Object.keys(obj).reduce(function (acc, k) {
    acc[fn(obj[k], k, obj)] = obj[k];
    return acc
  }, {}); };

/**
 * 清除空格
 *
 * @param str
 * @param type 1-所有空格  2-前后空格  3-前空格 4-后空格
 * @returns {*}
 * @example
 *
 * trim(' 123 ');
 * // => 123
 */
function trim$1 (str, type) {
  if ( type === void 0 ) type = 1;

  if (typeof str !== 'string') { throw new Error('输入值必须为字符串') }

  switch (type) {
    case 1:
      return str.replace(/\s+/g, '')
    case 2:
      return str.replace(/(^\s*)|(\s*$)/g, '')
    case 3:
      return str.replace(/(^\s*)/g, '')
    case 4:
      return str.replace(/(\s*$)/g, '')
    default:
      return str
  }
}

/**
 * 深层克隆对象
 *
 * @param obj
 * @returns {*}
 * @example
 *
 * const a = { foo: 'bar', obj: { a: 1, b: 2 } };
 * const b = deepClone(a);
 * // => a !== b, a.obj !== b.obj
 */
function deepClone (obj) {
  var clone = Object.assign({}, obj);
  Object.keys(clone).forEach(
    function (key) { return (clone[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key]); }
  );

  return Array.isArray(obj) && obj.length
    ? (clone.length = obj.length) && Array.from(clone)
    : Array.isArray(obj)
      ? Array.from(obj)
      : clone
}

/**
 * 深层映射对象键
 *
 * @param obj
 * @param fn
 * @returns {{}}
 * @example
 *
 * const obj = {
  foo: '1',
  nested: {
    child: {
      withArray: [
        {
          grandChild: ['hello']
        }
      ]
    }
  }
};

 const upperKeysObj = deepMapKeys(obj, key => key.toUpperCase());

 // =>
 {
  "FOO":"1",
  "NESTED":{
    "CHILD":{
      "WITHARRAY":[
        {
          "GRANDCHILD":[ 'hello' ]
        }
      ]
    }
  }
}
 */
function deepMapKeys (obj, fn) {
  return Array.isArray(obj)
    ? obj.map(function (val) { return deepMapKeys(val, fn); })
    : typeof obj === 'object'
      ? Object.keys(obj).reduce(function (acc, current) {
        var val = obj[current];
        acc[fn(current)] =
          val !== null && typeof val === 'object' ? deepMapKeys(val, fn) : (acc[fn(current)] = val);
        return acc
      }, {})
      : obj
}

/**
 * 基于给定的键返回嵌套JSON对象中的目标值
 *
 * {@link https://30secondsofcode.org/object#dig}
 * @param obj
 * @param target
 * @returns {any}
 * @example
 *
 * const data = {
 *  level1: {
 *    level2: {
 *      level3: 'some data'
 *    }
 *  }
 * };
 * dig(data, 'level3');
 * // => 'some data'
 * dig(data, 'level4');
 * // => undefined
 */
function dig (obj, target) {
  return target in obj
    ? obj[target]
    : Object.values(obj).reduce(function (acc, val) {
      if (acc !== undefined) { return acc }
      if (typeof val === 'object') { return dig(val, target) }
    }, undefined)
}

/**
 * 是否为空
 * 如果a值是空对象,集合,没有可枚举属性或任何不被视为集合的类型,则返回true。
 *
 * {@link https://30secondsofcode.org/type#isempty}
 * @param val
 * @returns {boolean}
 * @example
 *
 * isEmpty([]);
 * // => true
 * isEmpty({});
 * // => true
 * isEmpty('');
 * // => true
 * isEmpty([1, 2]);
 * // => false
 * isEmpty({ a: 1, b: 2 });
 * // => false
 * isEmpty('text');
 * // => false
 * isEmpty(123);
 * // => true - type is not considered a collection
 * isEmpty(true);
 * // => true - type is not considered a collection
 */
function isEmpty (val) {
  return val == null || !(Object.keys(val) || val).length
}

/**
 * 从两个或多个对象的组合中创建一个新对象
 *
 * {@link https://30secondsofcode.org/object#merge}
 * @param objs
 * @returns {*}
 * @example
 *
 * merge(
 {
  a: [{ x: 2 }, { y: 4 }],
  b: 1
},
 {
  a: { z: 3 },
  b: [2, 3],
  c: 'foo'
});
 * // => { a: [ { x: 2 }, { y: 4 }, { z: 3 } ], b: [ 1, 2, 3 ], c: 'foo' }
 */
function merge () {
  var objs = [], len = arguments.length;
  while ( len-- ) objs[ len ] = arguments[ len ];

  return [].concat( objs ).reduce(
    function (acc, obj) { return Object.keys(obj).reduce(function (a, k) {
        acc[k] = Object.hasOwnProperty.call(acc, k) ? [].concat(acc[k]).concat(obj[k]) : obj[k];
        return acc
      }, {}); },
    {}
  )
}

/**
 * url字符串拼接
 *
 * @param {string} baseURL
 * @param {string} relativeURLs
 * @returns {string} The combined URL
 */
function combineURLs (baseURL) {
  var relativeURLs = [], len = arguments.length - 1;
  while ( len-- > 0 ) relativeURLs[ len ] = arguments[ len + 1 ];

  var length = relativeURLs.length;
  if (length > 0) {
    var relativeUrlsArr = relativeURLs.map(function (item) { return item.replace(/^\/+/, '').replace(/\/+$/, ''); });
    var relativeUrlStr = relativeUrlsArr.join('/');
    if (relativeURLs[length - 1].slice(-1) === '/') {
      relativeUrlStr += '/';
    }
    return baseURL.replace(/\/+$/, '') + '/' + relativeUrlStr
  } else {
    return baseURL
  }
}

/**
 * 是否是微博内核
 *
 * @returns {boolean}
 * @example
 *
 * inWeibo();
 * // => false
 */
function inWeibo () {
  if (typeof navigator === 'undefined') { return }

  var ua = navigator.userAgent.toLowerCase();

  return ua.indexOf('weibo') !== -1
}

/**
 * 是否为有效的手机号,中国手机号(最宽松), 只要是1开头即可, 如果你的手机号是用来接收短信, 优先建议选择这一条
 *
 * @param {string} val
 * @returns {boolean}
 * @example
 *
 * isMobileLoose('008618311006933');
 * // => true
 *
 * isMobileLoose('+8617888829981');
 * // => true
 *
 * isMobileLoose('19119255642');
 * // => true
 */
function isMobileLoose (val) {
  var reg = /^(?:(?:\+|00)86)?1\d{10}$/;

  return reg.test(val)
}

/**
 * 是否为有效的银行卡号(10到30位, 覆盖对公/私账户, 参考[微信支付](https://pay.weixin.qq.com/wiki/doc/api/xiaowei.php?chapter=22_1))
 *
 * @param {string} val
 * @returns {boolean}
 * @example
 *
 * isValidBankNo('6234567890');
 * // => true
 *
 * isValidBankNo('6222026006705354217');
 * // => true
 */
function isValidBankNo (val) {
  var reg = /^[1-9]\d{9,29}$/;

  return reg.test(val)
}

/**
 * 是否为有效的 base64格式
 *
 * @param {string} val
 * @returns {boolean}
 * @example
 *
 * isValidBase64Format('data:image/gif;base64,xxxx==')
 * => true
 */
function isValidBase64Format (val) {
  var reg = /^\s*data:(?:[a-z]+\/[a-z0-9-+.]+(?:;[a-z-]+=[a-z0-9-]+)?)?(?:;base64)?,([a-z0-9!$&',()*+;=\-._~:@/?%\s]*?)\s*$/i;

  return reg.test(val)
}

/**
 * 是否为有效的用户名,4到16位(字母,数字,下划线,减号)
 *
 * @param {string} val
 * @param {number} minLength
 * @param {number} maxLength
 * @returns {boolean}
 * @example
 *
 * isValidUserName('xiaohua_qq');
 * // => true
 */
function isValidUserName (val, minLength, maxLength) {
  if ( minLength === void 0 ) minLength = 4;
  if ( maxLength === void 0 ) maxLength = 16;

  var reg = new RegExp(("^[a-zA-Z0-9_-]{" + minLength + "," + maxLength + "}$"));

  return reg.test(val)
}

/**
 * 判断 iPhone X Series 机型,刘海屏
 *
 * @returns {boolean}
 * @example
 *
 * isPhoneX()
 * => true
 */
function isPhoneX () {
  // X XS, XS Max, XR
  var xSeriesConfig = [
    {
      devicePixelRatio: 3,
      width: 375,
      height: 812
    },
    {
      devicePixelRatio: 3,
      width: 414,
      height: 896
    },
    {
      devicePixelRatio: 2,
      width: 414,
      height: 896
    }
  ];
  // h5
  if (typeof window !== 'undefined' && window) {
    var isIOS = /iphone/gi.test(window.navigator.userAgent);
    if (!isIOS) { return false }
    var devicePixelRatio = window.devicePixelRatio;
    var screen = window.screen;
    var width = screen.width;
    var height = screen.height;
    return xSeriesConfig.some(function (item) { return item.devicePixelRatio === devicePixelRatio && item.width === width && item.height === height; })
  }
  return false
}

/**
 * 是否是QQ浏览器内核
 *
 * @returns {boolean}
 * @example
 *
 * inQQBrowser();
 * // => false
 */
function inQQBrowser () {
  if (typeof window.navigator === 'undefined') { return }

  var ua = window.navigator.userAgent.toLowerCase();

  return ua.indexOf('mqqbrowser') !== -1
}

/**
 * 是否是UC浏览器内核
 *
 * @returns {boolean}
 * @example
 *
 * inUCBrowser();
 * // => false
 */
function inUCBrowser () {
  if (typeof window.navigator === 'undefined') { return }

  var ua = window.navigator.userAgent.toLowerCase();

  return ua.indexOf('ucbrowser') !== -1
}

/**
 * 两个值之间的深入比较,以确定它们是否相等
 *
 * @param {Object} a
 * @param {Object} b
 * @returns {*}
 * @example
 *
 * equals({ a: [2, { e: 3 }], b: [4], c: 'foo' }, { a: [2, { e: 3 }], b: [4], c: 'foo' });
 *
 * // => true
 */
function equals (a, b) {
  if (a === b) { return true }
  if (a instanceof Date && b instanceof Date) { return a.getTime() === b.getTime() }
  if (!a || !b || (typeof a !== 'object' && typeof b !== 'object')) { return a === b }
  if (a === null || a === undefined || b === null || b === undefined) { return false }
  if (a.prototype !== b.prototype) { return false }
  var keys = Object.keys(a);
  if (keys.length !== Object.keys(b).length) { return false }

  return keys.every(function (k) { return equals(a[k], b[k]); })
}

/**
 * 获取图片的base64 url
 * @param {string} url 图片url
 * @returns {Promise} 图片base64信息
 */
function getImgBase64 (url) {
  var Img = new Image();
  var dataURL = '';
  Img.setAttribute('crossOrigin', 'anonymous');
  Img.src = url;
  return new Promise(function (resolve, reject) {
    Img.onload = function () {
      var canvas = document.createElement('canvas');
      var width = Img.width;
      var height = Img.height;
      var ctx = canvas.getContext('2d');
      var scale = 5;
      ctx.scale(scale, scale);
      canvas.width = width * scale;
      canvas.height = height * scale;
      ctx.drawImage(Img, 0, 0, width * scale, height * scale);
      dataURL = canvas.toDataURL('image/png');
      resolve(dataURL);
    };
  })
}

/**
 * 过滤对象中为空的属性
 *
 * @param obj
 * @returns {*}
 * @example
 *
 * filterEmptyPropObj({name: 'foo', sex: ''})
 * // => {name: 'foo'}
 */
function filterEmptyPropObj (obj) {
  if (!(typeof obj === 'object')) { return }

  for (var key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key) &&
      (obj[key] === null || obj[key] === undefined || obj[key] === '')) {
      delete obj[key];
    }
  }
  return obj
}

exports.accAdd = accAdd;
exports.accDiv = accDiv;
exports.accMul = accMul;
exports.accSub = accSub;
exports.addChineseUnit = addChineseUnit;
exports.addClass = addClass;
exports.anagrams = anagrams;
exports.bytesToSize = bytesToSize;
exports.capitalizeEveryWord = capitalizeEveryWord;
exports.changeMoneyToChinese = changeMoneyToChinese;
exports.combineURLs = combineURLs;
exports.compareVersion = compareVersion;
exports.dataURLToBlob = dataURLToBlob;
exports.deepClone = deepClone;
exports.deepMapKeys = deepMapKeys;
exports.dig = dig;
exports.encrypt = encrypt;
exports.equals = equals;
exports.filterEmptyPropObj = filterEmptyPropObj;
exports.formatBankCard = formatBankCard;
exports.formatDate = formatDate;
exports.formatDateToTimeStamp = formatDateToTimeStamp;
exports.formatMoney = formatMoney;
exports.formatNumber = formatNumber;
exports.formatPhone = formatPhone;
exports.formatTimeAgo = formatTimeAgo;
exports.getBrowser = getBrowser;
exports.getDayOfYear = getDayOfYear;
exports.getDayOfYearWeek = getDayOfYearWeek;
exports.getDays = getDays;
exports.getDevice = getDevice;
exports.getDiffDay = getDiffDay;
exports.getImgBase64 = getImgBase64;
exports.getIn = getIn;
exports.getLocationHrefParam = getLocationHrefParam;
exports.getLocationSearchParam = getLocationSearchParam;
exports.getMonthOfDay = getMonthOfDay;
exports.getPixelRatio = getPixelRatio;
exports.getURLParameters = getURLParameters;
exports.getYearOfDay = getYearOfDay;
exports.hasClass = hasClass;
exports.htmlDecode = htmlDecode;
exports.htmlEncode = htmlEncode;
exports.inAlipay = inAlipay;
exports.inQQBrowser = inQQBrowser;
exports.inUCBrowser = inUCBrowser;
exports.inWeibo = inWeibo;
exports.inWeixin = inWeixin;
exports.isCardId = isCardId;
exports.isChinese = isChinese;
exports.isDigit = isDigit;
exports.isEmpty = isEmpty;
exports.isEmptyObject = isEmptyObject;
exports.isHTML = isHTML;
exports.isLeapYear = isLeapYear;
exports.isLetters = isLetters;
exports.isMobileLoose = isMobileLoose;
exports.isPhoneX = isPhoneX;
exports.isValidBankNo = isValidBankNo;
exports.isValidBase64Format = isValidBase64Format;
exports.isValidEmail = isValidEmail;
exports.isValidUserName = isValidUserName;
exports.last = last;
exports.mapKeys = mapKeys;
exports.mapValues = mapValues;
exports.merge = merge;
exports.numberToChinese = numberToChinese;
exports.preZeroFill = preZeroFill;
exports.removeClass = removeClass;
exports.scrollToTop = scrollToTop;
exports.timeTaken = timeTaken;
exports.toNonExponential = toNonExponential;
exports.trim = trim$1;