WhiteSevsUtils

一个好用的工具类

目前为 2023-04-02 提交的版本。查看 最新版本

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.gf.qytechs.cn/scripts/455186/1169937/WhiteSevsUtils.js

/**
 * 自己常用的工具类定义
 * @copyright  GPL-3.0-only
 * @author  WhiteSevs
 * @version  1.8
 **/
(function (Utils) {
  /**
   * JSON数据从源端替换到目标端中,如果目标端存在该数据则替换,不添加,返回结果为目标端替换完毕的结果
   * @param {object} target	目标端
   * @param {object} source	源端
   * @example
   * 	Utils.assignJSON({"1":1,"2":{"3":3}}, {"2":{"3":4}});
   * @return
   * \{"1":1,"2":{"3":4}}
   **/
  Utils.assignJSON = function (target = {}, source = {}) {
    for (var target_key in target) {
      let targetValue = target[target_key];
      let sourceValue = source[target_key];
      if (typeof sourceValue !== "undefined") {
        /* 右边值为Object */
        /* 右边值不为元素节点 */
        if (typeof sourceValue === "object" && !(sourceValue instanceof Node)) {
          target[target_key] = Utils.assignJSON(targetValue, sourceValue);
        } else {
          target[target_key] = sourceValue;
        }
      }
    }
    return target;
  };

  /**
   * 同步执行,等待数组内部执行完毕,注意,该内部不能同步
   * @param {Object} arrayData	需要遍历的数组
   * @param {Function} handleDataFunction	对该数组进行操作的函数,该函数的参数为数组格式的参数,[数组下标,数组项]
   * @example
   * 	await Utils.asyncArrayForEach([1,2,3],xxxFunction);
   **/
  Utils.asyncArrayForEach = function (arrayData, handleDataFunction) {
    var that = this;
    if (typeof arrayData !== "object") {
      throw new Error(
        "Utils.asyncArrayForEach 参数 arrayData 必须为 object 类型"
      );
    }
    if (
      typeof handleDataFunction !== "function" &&
      typeof handleDataFunction !== "string"
    ) {
      throw new Error(
        "Utils.asyncArrayForEach 参数 handleDataFunction 必须为 function|string 类型"
      );
    }
    return Promise.all(
      Array.from(arrayData).map(async (item, index) => {
        await that.tryCatch(handleDataFunction, [index, item]);
      })
    );
  };

  /**
   * 同步File对象转base64
   * @param {Object} fileObj	需要转换的File对象
   * @return {String} base64格式的数据
   * @example
   * 	await Utils.asyncFileToBase64(object);
   * @return
   * 	data:image/jpeg:base64/,xxxxxx
   **/
  Utils.asyncFileToBase64 = function (fileObj) {
    var reader = new FileReader();
    reader.readAsDataURL(fileObj);
    return new Promise((resolve) => {
      reader.onload = function (e) {
        resolve(e.target.result);
      };
    });
  };

  /**
   * 同步执行延时函数
   * @param {Object|String} fnStr	需要延时的函数或字符串格式的函数
   * @param {Number} delayTime	需要检测的元素
   * @return {?undefined}	返回自定义类型数据或者无返回
   * @example
   * 	await Utils.asyncSetTimeOut(xxxFunction, 2500);
   * @return
   * 	xxx
   * @example
   * 	await Utils.asyncSetTimeOut("()=>{console.log(12345)}", 2500);
   * @return
   * 	undefined
   **/
  Utils.asyncSetTimeOut = function (fnStr, delayTime) {
    var that = this;
    if (typeof fnStr !== "function" && typeof fnStr !== "string") {
      throw new Error(
        "Utils.asyncSetTimeOut 参数 fnStr 必须为 function|string 类型"
      );
    }
    if (typeof delayTime !== "number") {
      throw new Error(
        "Utils.asyncSetTimeOut 参数 delayTime 必须为 number 类型"
      );
    }
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(that.tryCatch(fnStr));
      }, delayTime);
    });
  };

  /**
   * base64转blob
   * @param {String} dataurl	base64的数据
   * @return {String} blob的链接
   * @example
   * 	Utils.base64ToBlob("data:image/jpeg;base64,.....");
   * @return
   * 	blob://xxxxxxx
   **/
  Utils.base64ToBlob = function (dataurl) {
    if (typeof dataurl !== "string") {
      throw new Error("Utils.base64ToBlob 参数 dataurl 必须为 string 类型");
    }
    var arr = dataurl.split(","),
      mime = arr[0].match(/:(.*?);/)[1],
      bstr = atob(arr[1]),
      n = bstr.length,
      u8arr = new Uint8Array(n);
    while (n--) {
      u8arr[n] = bstr.charCodeAt(n);
    }
    return new Blob([u8arr], {
      type: mime,
    });
  };

  /**
   * base64转File对象
   * @param {String} dataurl	base64的数据
   * @return {String}	blob的链接
   * @example
   * 	Utils.base64ToFile("data:image/jpeg;base64,.....");
   * @return
   * 	object
   **/
  Utils.base64ToFile = function (dataurl, fileName) {
    if (typeof dataurl !== "string") {
      throw new Error("Utils.base64ToFile 参数 dataurl 必须为 string 类型");
    }
    if (typeof fileName !== "string") {
      throw new Error("Utils.base64ToFile 参数 fileName 必须为 string 类型");
    }
    var arr = dataurl.split(","),
      mime = arr[0].match(/:(.*?);/)[1],
      bstr = atob(arr[1]),
      n = bstr.length,
      u8arr = new Uint8Array(n);
    while (n--) {
      u8arr[n] = bstr.charCodeAt(n);
    }
    return new File([u8arr], fileName, {
      type: mime,
    });
  };

  /**
   * blob转File对象
   * @param {String} theBlob	需要转换的blob的链接
   * @param {String} fileName	转换成的File对象的文件名称
   * @return {Object} File对象
   * @example
   * 	Utils.blobToFile("blob://xxxxx");
   * @return
   * 	object
   **/
  Utils.blobToFile = function (theBlob, fileName) {
    if (typeof theBlob !== "string") {
      throw new Error("Utils.blobToFile 参数 theBlob 必须为 string 类型");
    }
    if (typeof fileName !== "string") {
      throw new Error("Utils.blobToFile 参数 fileName 必须为 string 类型");
    }
    theBlob.lastModifiedDate = new Date();
    theBlob.name = fileName;
    return theBlob;
  };

  /**
   * 【手机】检测点击的地方是否在该元素区域内
   * @param {Object} obj	需要检测的元素
   * @return {Boolean}	返回true或false
   * @example
   * 	Utils.checkUserClickInNode(document.querySelector(".xxx"));
   * @return
   * 	false
   **/
  Utils.checkUserClickInNode = function (targetNode) {
    if (!(targetNode instanceof Node)) {
      throw new Error(
        "Utils.checkUserClickInNode 参数 targetNode 必须为 Node 类型"
      );
    }
    var mouseClickPosX = Number(window.event.clientX); /* 鼠标相对屏幕横坐标 */
    var mouseClickPosY = Number(window.event.clientY); /* 鼠标相对屏幕纵坐标 */
    var elementPosXLeft = Number(
      targetNode.getBoundingClientRect().left
    ); /* 要检测的元素的相对屏幕的横坐标最左边 */
    var elementPosXRight = Number(
      targetNode.getBoundingClientRect().right
    ); /* 要检测的元素的相对屏幕的横坐标最右边 */
    var elementPosYTop = Number(
      targetNode.getBoundingClientRect().top
    ); /* 要检测的元素的相对屏幕的纵坐标最上边 */
    var elementPosYBottom = Number(
      targetNode.getBoundingClientRect().bottom
    ); /* 要检测的元素的相对屏幕的纵坐标最下边 */
    if (
      mouseClickPosX >= elementPosXLeft &&
      mouseClickPosX <= elementPosXRight &&
      mouseClickPosY >= elementPosYTop &&
      mouseClickPosY <= elementPosYBottom
    ) {
      return true;
    } else if (
      targetNode.innerHTML.indexOf(window.event.target.innerHTML) !== -1
    ) {
      /* 这种情况是应对在界面中隐藏的元素,getBoundingClientRect获取的都是0 */
      return true;
    } else {
      return false;
    }
  };

  /**
   * 删除某个父元素,父元素可能在上层或上上层或上上上层...
   * @param {Object} target	当前元素
   * @param {Object} handleFunc	判断是否满足父元素,参数为当前处理的父元素,满足返回true,否则false
   * @return {Boolean} 如果找到就删除返回true,如果未删除返回false
   * @example
   * 	Utils.deleteParentNode(
   * 		document.querySelector(".xxx"),(node)=>{
   * 			return node.id="xxx" ? true:false
   * 		}
   * 	);
   * @exampleReturn
   * 	true
   **/
  Utils.deleteParentNode = function (target, handleFunc) {
    if (target == null) {
      throw new Error("Utils.deleteParentNode 参数 target 不能为 null");
    }
    if (!(target instanceof Node)) {
      throw new Error("Utils.deleteParentNode 参数 target 必须为 Node 类型");
    }
    if (typeof handleFunc !== "function") {
      throw new Error(
        "Utils.deleteParentNode 参数 handleFunc 必须为 function 类型"
      );
    }
    var result = false;
    var parentNode = target.parentElement;
    while (!0) {
      if (parentNode == null) {
        return;
      }
      var handleStatus = handleFunc(parentNode);
      if (handleStatus) {
        result = true;
        parentNode.remove();
        break;
      }
      parentNode = parentNode.parentElement;
    }
    return result;
  };

  /**
   * 自定义字典,用于new
   * @example
   *let dictionary = new Utils.Dictionary();
   *dictionary.set("xxx","xxx");
   *dictionary.get("xxx");
   *dictionary.has("xxx");
   **/
  Utils.Dictionary = function () {
    this.items = {};
    this.has = function (key) {
      /* 检查是否有某一个键 */
      return this.items.hasOwnProperty(key);
    };
    this.set = function (key, val = "") {
      /* 为字典添加某一个值 */
      if (key === undefined) {
        throw new Error("Utils.Dictionary().set 参数 key 不能为空");
      }
      this.items[key] = val;
    };
    this.delete = function (key) {
      /* 删除某一个键 */
      if (this.has(key)) {
        delete this.items[key];
        return true;
      }
      return false;
    };
    this.get = function (key) {
      /* 查找某一特定项 */
      return this.has(key) ? this.items[key] : undefined;
    };
    this.values = function () {
      /* 返回字典中的所有值 */
      var resultList = [];
      for (var prop in this.items) {
        if (this.has(prop)) {
          resultList.push(this.items[prop]);
        }
      }
      return resultList;
    };
    this.clear = function () {
      /* 清空字典 */
      this.items = {};
    };
    this.size = function () {
      /* 获取字典的长度 */
      return Object.keys(this.items).length;
    };
    this.keys = function () {
      /* 获取字典所有的键 */
      return Object.keys(this.items);
    };
    this.getItems = function () {
      /* 返回字典本身 */
      return this.items;
    };
  };

  /**
   * 下载base64格式的数据
   * @param {String} base64Content	需要转换的base64数据
   * @param {String} fileName	需要保存的文件名
   * @example
   * 	Utils.downloadBase64("data:image/jpeg:base64/,xxxxxx");
   **/
  Utils.downloadBase64 = function (base64Content, fileName) {
    if (typeof base64Content !== "string") {
      throw new Error(
        "Utils.downloadBase64 参数 base64Content 必须为 string 类型"
      );
    }
    if (typeof fileName !== "string") {
      throw new Error("Utils.downloadBase64 参数 fileName 必须为 string 类型");
    }
    var aLink = document.createElement("a");
    var blob = this.base64ToBlob(base64Content);
    var evt = document.createEvent("HTMLEvents");
    evt.initEvent(
      "click",
      true,
      true
    ); /* initEvent 不加后两个参数在FF下会报错  事件类型,是否冒泡,是否阻止浏览器的默认行为 */
    aLink.download = fileName;
    aLink.href = URL.createObjectURL(blob);
    aLink.click();
  };

  /**
   * 获取某个父元素,父元素可能在上层或上上层或上上上层...
   * @param {Object} target	当前元素
   * @param {Object} handleFunc	判断是否满足父元素,参数为当前处理的父元素,满足返回true,否则false
   * @return {Boolean}	如果找到返回满足要求的父元素,如果未找到返回null
   * @example
   * 	Utils.findParentNode(document.querySelector(".xxx"),(node)=>{return node.id==="xxx"});
   * @return
   * 	Node;
   **/
  Utils.findParentNode = function (target, handleFunc) {
    if (target == null) {
      throw new Error("Utils.findParentNode 参数 target 不能为null");
    }
    if (!(target instanceof Node)) {
      throw new Error("Utils.findParentNode 参数 target 必须为 Node 类型");
    }
    if (typeof handleFunc !== "function") {
      throw new Error(
        "Utils.findParentNode 参数 handleFunc 必须为 function 类型"
      );
    }
    let result = null;
    let parentNode = target.parentElement;
    while (!0) {
      if (parentNode == null) {
        return;
      }
      let handleStatus = handleFunc(parentNode);
      if (handleStatus) {
        result = parentNode;
        break;
      }
      parentNode = parentNode.parentElement;
    }
    return result;
  };

  /**
   * 定位网页中字符串位置并标亮,注意,该字符串必须是能在网页中看得到的,隐藏的是无法定位的
   * @param {String} str	需要寻找的字符串
   * @param {Boolean} caseSensitive	区分大小写
   * @return {Boolean}
   * + 找到	===> true
   * + 找不到 ===> false
   * @example
   * 	Utils.findWindowPageString("xxxxx");
   * @return
   * 	true
   **/
  Utils.findWindowPageString = function (str = "", caseSensitive = false) {
    var TRange = null;
    var strFound;
    if (window.find) {
      /* CODE FOR BROWSERS THAT SUPPORT window.find */
      strFound = self.find(str, caseSensitive, true, true, false);
      if (strFound && self.getSelection && !self.getSelection().anchorNode) {
        strFound = self.find(str, caseSensitive, true, true, false);
      }
      if (!strFound) {
        strFound = self.find(str, 0, 1);
        while (self.find(str, 0, 1)) continue;
      }
    } else if (navigator.appName.indexOf("Microsoft") != -1) {
      /* EXPLORER-SPECIFIC CODE */
      if (TRange != null) {
        TRange.collapse(false);
        strFound = TRange.findText(str);
        if (strFound) TRange.select();
      }
      if (TRange == null || strFound == 0) {
        TRange = self.document.body.createTextRange();
        strFound = TRange.findText(str);
        if (strFound) TRange.select();
      }
    } else if (navigator.appName == "Opera") {
      alert("Opera browsers not supported, sorry...");
      return;
    }
    return strFound ? true : false;
  };

  /**
   * 字符串格式的时间转时间戳
   * @param {String} str	字符串格式的时间,例如:
   * + 2022-11-21 00:00:00
   * + 00:00:00
   * @return {Number}
   * 	返回时间戳
   * @example
   * 	Utils.formatTextToTimeStamp("2022-11-21 00:00:00");
   * @return
   * 	1668960000000
   **/
  Utils.formatTextToTimeStamp = function (text) {
    /* 把字符串格式的时间(完整,包括日期和时间)格式化成时间戳 */
    if (typeof text !== "string") {
      throw new Error(
        "Utils.formatTextToTimeStamp 参数 text 必须为 string 类型"
      );
    }
    if (text.length === 8) {
      /* 参数只有时间 */
      var today = new Date();
      text =
        today.getFullYear() +
        "-" +
        (today.getMonth() + 1) +
        "-" +
        today.getDate() +
        " " +
        text;
    }
    text = text.substring(0, 19);
    text = text.replace(/-/g, "/");
    var timestamp = new Date(text).getTime();
    return timestamp;
  };

  /**
   * 格式化byte为KB、MB、GB、TB、PB、EB、ZB、YB、BB、NB、DB
   * @param {Number} bitSize	字节
   * @param {Boolean} addType	是否添加单位,默认添加
   * @return {String|Number}	添加单位就是字符串,否则为float类型,保留两位
   * @example
   * 	Utils.formatByteToSize("812304");
   * @return
   * 	793.27KB
   **/
  Utils.formatByteToSize = function (byteSize, addType = true) {
    /* B字节转KB、MB、GB */
    byteSize = parseInt(byteSize);
    if (isNaN(byteSize)) {
      throw new Error("Utils.formatByteToSize 参数 byteSize 格式不正确");
    }
    var result = 0;
    var resultType = "KB";
    var sizeData = {};
    sizeData.KB = 1024;
    sizeData.MB = sizeData.KB * sizeData.KB;
    sizeData.GB = sizeData.MB * sizeData.KB;
    sizeData.TB = sizeData.GB * sizeData.KB;
    sizeData.PB = sizeData.TB * sizeData.KB;
    sizeData.EB = sizeData.PB * sizeData.KB;
    sizeData.ZB = sizeData.EB * sizeData.KB;
    sizeData.YB = sizeData.ZB * sizeData.KB;
    sizeData.BB = sizeData.YB * sizeData.KB;
    sizeData.NB = sizeData.BB * sizeData.KB;
    sizeData.DB = sizeData.NB * sizeData.KB;
    for (key in sizeData) {
      result = byteSize / sizeData[key];
      resultType = key;
      if (sizeData.KB >= result) {
        break;
      }
    }
    result = result.toFixed(2);
    result = addType ? result + resultType.toString() : parseFloat(result);
    return result;
  };

  /**
   * 获取数组的随机值
   * @param {String} array	数组数据
   * @return {String}	返回数组的随机值
   * @example
   * 	Utils.getArrayRandValue(["Utils","getArrayRandValue"]);
   * @return
   * 	getArrayRandValue
   **/
  Utils.getArrayRandValue = function (_array_) {
    return _array_[Math.floor(Math.random() * _array_.length)];
  };

  /**
   * 获取格式化后的Date类型时间
   * @param {String} text	需要格式化的字符串或者时间戳
   * @param {String} types	格式化成的显示类型
   * + yyyy 年
   * + MM 月
   * + dd 天
   * + HH 时 (24小时制)
   * + hh 时 (12小时制)
   * + mm 分
   * + ss 秒
   * @return {String}	返回格式化后的时间
   * @example
   * 	Utils.getFormatTime("HH:mm:ss","2022-08-21 23:59:00");
   * @return
   * 	23:59:00
   * @example
   * 	Utils.getFormatTime("HH:mm:ss",1899187424988);
   * @return
   * 	15:10:13
   **/
  Utils.getFormatTime = function (types = "yyyy-MM-dd HH:mm:ss", text) {
    if (typeof types !== "string") {
      throw new Error("Utils.getFormatTime 参数 types 必须为 string 类型");
    }
    if (text != null && typeof text !== "string" && typeof text !== "number") {
      throw new Error(
        "Utils.getFormatTime 参数 text 必须为 string|number 类型"
      );
    }
    var time = text == null ? new Date() : new Date(text);
    function _checkTime_(i) {
      /* 校验时间补0 */
      if (i < 10) return "0" + i;
      return i;
    }

    function _timeSystemChange_(_hour_) {
      /* 时间制修改 24小时制转12小时制 */
      return _hour_ > 12 ? _hour_ - 12 : _hour_;
    }

    var timeRegexp = {
      yyyy: time.getFullYear(),
      /* 年 */
      MM: _checkTime_(time.getMonth() + 1),
      /* 月 */
      dd: _checkTime_(time.getDate()),
      /* 日 */
      HH: _checkTime_(time.getHours()),
      /* 时 (24小时制) */
      hh: _checkTime_(_timeSystemChange_(time.getHours())),
      /* 时 (12小时制) */
      mm: _checkTime_(time.getMinutes()),
      /* 分 */
      ss: _checkTime_(time.getSeconds()),
      /* 秒 */
    };
    Object.keys(timeRegexp).forEach(function (key) {
      var replaecRegexp = new RegExp(key, "g");
      types = types.replace(replaecRegexp, timeRegexp[key]);
    });
    return types;
  };

  /**
   * 获取页面中最大的z-index
   * @return {String}	User-Agent
   * @example
   * 	Utils.getRandomPCUA();
   * @return
   * 	Mozilla/5.0....
   **/
  Utils.getMaxZIndex = function () {
    let arr = [...document.all].map(
      (e) => +window.getComputedStyle(e).zIndex || 0
    );
    return arr.length ? Math.max(...arr) + 1 : 0;
  };

  /**
   * 获取随机的安卓手机User-Agent
   * @return {String}	User-Agent
   * @example
   * 	Utils.getRandomAndroidUA();
   * @return
   * 	Mozilla/5.0 (Linux; Android ....
   **/
  Utils.getRandomAndroidUA = function () {
    let androidVersion = Utils.getRandomNumber(9, 13);
    let mobileNameList = [
      "LDN-LX3",
      "RNE-L03",
      "ASUS_X00ID Build/NMF26F",
      "WAS-LX3",
      "PRA-LX3",
      "MYA-L03",
      "Moto G Play",
      "Moto C Build/NRD90M.063",
      "Redmi Note 4 Build/NRD90M",
      "HUAWEI VNS-L21 Build/HUAWEIVNS-L21",
      "VTR-L09",
      "TRT-LX3",
      "M2003J15SC Build/RP1A.200720.011; wv",
      "MI 13 Build/OPR1.170623.027; wv",
    ];
    let randomMobile = Utils.getArrayRandValue(mobileNameList);
    let chromeVersion1 = Utils.getRandomNumber(100, 113);
    let chromeVersion2 = Utils.getRandomNumber(2272, 5304);
    let chromeVersion3 = Utils.getRandomNumber(1, 218);
    return `Mozilla/5.0 (Linux; Android ${androidVersion}; ${randomMobile}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion1}.0.${chromeVersion2}.${chromeVersion3} Mobile Safari/537.36`;
  };

  /**
   * 获取两个数字区间的随机值
   * @param {Number}	number	数字区间
   * @param {Number}	number2	数字区间
   * @return {Number}	返回两个数字区间的随机值
   * @example
   * 	Utils.getRandomNumber(1,10);
   * @return
   * 	5
   * @example
   * 	Utils.getRandomNumber(10,1);
   * @return
   * 	8
   **/
  Utils.getRandomNumber = function (number, number2) {
    if (typeof number !== "number") {
      throw new Error("Utils.getRandNumber 参数 number 必须为 number 类型");
    }
    if (typeof number2 !== "number") {
      throw new Error("Utils.getRandNumber 参数 number2 必须为 number 类型");
    }
    var leftNumber = number > number2 ? number2 : number;
    var rightNumber = number > number2 ? number : number2;
    return Math.round(Math.random() * (rightNumber - leftNumber)) + leftNumber;
  };

  /**
   * 获取随机的电脑端User-Agent
   * @return {String} - User-Agent
   * @example
   * 	Utils.getRandomPCUA();
   * @return
   * 	Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome....
   **/
  Utils.getRandomPCUA = function () {
    let chromeVersion1 = Utils.getRandomNumber(100, 113);
    let chromeVersion2 = Utils.getRandomNumber(2272, 5304);
    let chromeVersion3 = Utils.getRandomNumber(1, 218);
    return `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion1}.0.${chromeVersion2}.${chromeVersion3} Safari/537.36`;
  };

  /**
   * 在页面中增加style元素,如果html节点存在子节点,添加子节点第一个,反之,添加到html节点的子节点最后一个
   * @param {String} cssText css字符串
   */
  Utils.GM_addStyle = function (cssText) {
    if (typeof cssText !== "string") {
      throw new Error("Utils.GM_addStyle 参数cssText 必须为String类型");
    }
    let cssNode = document.createElement("style");
    cssNode.setAttribute("type", "text/css");
    cssNode.innerHTML = cssText;
    if (document.documentElement.childNodes.length === 0) {
      /* 插入body后 */
      document.documentElement.appendChild(cssNode);
    } else {
      /* 插入head前面 */
      document.documentElement.insertBefore(
        cssNode,
        document.documentElement.childNodes[0]
      );
    }
  };

  /**
   * 对于GM_cookie的兼容写法,当无法使用GM_cookie时可以使用这个,但是并不完全兼容,有些写不出来且限制了httponly是无法访问的
   * @example
   *	let GM_cookie = new Utils.GM_Cookie();
   *	GM_cookie.list({name:"xxx_cookie_xxx"},function(cookies,error){
   *		if (!error) {
   *			console.log(cookies);
   *			console.log(cookies.value);
   *		} else {
   *			console.error(error);
   *		}
   *	});
   *	GM_cookie.set({name:"xxx_cookie_test_xxx",value:"这是Cookie测试值"},function(error){
   *		if (error) {
   *			console.error(error);
   *		} else {
   *			console.log('Cookie set successfully.');
   *		}
   *	})
   *	GM_cookie.delete({name:"xxx_cookie_test_xxx"},function(error){
   *		if (error) {
   *			console.error(error);
   *		} else {
   *			console.log('Cookie set successfully.');
   *		}
   *	})
   **/
  Utils.GM_Cookie = function () {
    /**
	 * 获取Cookie
	 * @param {Object} details 
		+ url string? 默认为当前的url
		+ domain string? 默认为当前的域名(window.location.hostname)
		+ name string? 需要检索的Cookie的名字
		+ path string? 需要检索的Cookie的路径,默认为"/"
	* @param {Function} callback 
		+ cookies object[] 
		+ error string|undefined
	*/
    this.list = (details = {}, callback = () => {}) => {
      var getReturn = [];
      try {
        var _details_ = {
          url: window.location.href,
          domain: window.location.hostname,
          name: "",
          path: "/",
        };
        details = Utils.assignJSON(_details_, details);
        var cookies = document.cookie.split(";");
        cookies.forEach((item) => {
          let nameRegexp = new RegExp("^" + details.name + "=", "g");
          item = item.trimStart();
          if (item.match(nameRegexp)) {
            getReturn = [
              ...getReturn,
              {
                domain: window.location.hostname,
                expirationDate: undefined,
                hostOnly: true,
                httpOnly: false,
                name: item,
                path: "/",
                sameSite: "unspecified",
                secure: true,
                session: false,
                value: decodeURIComponent(item.replace(nameRegexp, "")),
              },
            ];

            return;
          }
        });
        callback(getReturn, undefined);
      } catch (error) {
        callback(getReturn, error);
      }
    };

    /**
     * 设置Cookie
     * @param {Object} details
     * @param {Function} callback
     */
    this.set = (details = {}, callback = () => {}) => {
      try {
        var _details_ = {
          url: window.location.href,
          name: "",
          value: "",
          domain: window.location.hostname,
          path: "/",
          secure: true,
          httpOnly: false,
          expirationDate: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30, // Expires in 30 days
        };
        details = Utils.assignJSON(_details_, details);
        var life = details.expirationDate
          ? details.expirationDate
          : Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30;
        var cookieStr =
          details.name +
          "=" +
          decodeURIComponent(details.value) +
          ";expires=" +
          new Date(life).toGMTString();
        document.cookie = cookieStr;
        callback();
      } catch (error) {
        callback(error);
      }
    };

    /**
     * 删除Cookie
     * @param {Object} details
     * @param {Function} callback
     */
    this.delete = (details = {}, callback = () => {}) => {
      try {
        var _details_ = {
          url: window.location.href,
          name: "",
          firstPartyDomain: "",
        };
        details = Utils.assignJSON(_details_, details);
        var cookieStr =
          details.name +
          "=" +
          decodeURIComponent("null") +
          ";expires=" +
          new Date().toGMTString();
        document.cookie = cookieStr;
        callback();
      } catch (error) {
        callback(error);
      }
    };
  };

  /**
   * 注册(不可用)油猴菜单
   * @param {Object} data 传递的菜单数据
   * @param {Boolean} autoReload 点击该菜单后数据改变后自动重载页面,true为自动重载,false不开启自动重载,默认false
   * @param {Function} _GM_getValue_ 传入油猴函数 GM_getValue
   * @param {Function} _GM_setValue_ 传入油猴函数 GM_setValue
   * @param {Function} _GM_registerMenuCommand_ 传入油猴函数 GM_registerMenuCommand
   * @param {Function} _GM_unregisterMenuCommand_ 传入油猴函数 GM_unregisterMenuCommand
   * @example
   * var gm_Menu = new Utils.GM_Menu({
   *    menu_key:{
   *      text: "测试按钮",
   *      enable: true,
   *      showText: (text,enable) =>  {
   *        return "[" + (enable ? "√" : "×") + "]" + text;
   *      },
   *      callback: (key,status)  =>  {
   *        console.log("点击菜单,值修改为",status);
   *      }
   *    }
   * },
   * true,
   * GM_getValue,
   * GM_setValue,
   * GM_registerMenuCommand,
   * GM_unregisterMenuCommand);
   *
   * gm_Menu.get("menu_key"); // 获取某个菜单项的值
   *
   * gm_Menu.add({
   *    menu_key2:{
   *      text: "测试按钮2",
   *      enable: false,
   *      showText: (text,enable) =>  {
   *        return "[" + (enable ? "√" : "×") + "]" + text;
   *      },
   *      callback: (key,status)  =>  {
   *        console.log("点击菜单,值修改为",status);
   *      }
   *    }
   * }); // 添加键为menu_key2的菜单项
   *
   * gm_Menu.update({
   *    menu_key:{
   *      text: "更新后的测试按钮",
   *      enable: true,
   *      showText: (text,enable) =>  {
   *        return "[" + (enable ? "√" : "×") + "]" + text;
   *      },
   *      callback: (key,status)  =>  {
   *        console.log("点击菜单更新后的测试按钮,新值修改为",status);
   *      }
   *    }
   * }); // 更新键为menu_key的显示文字和点击回调
   *
   * gm_Menu.delete("menu_key"); // 删除键为menu_key的菜单
   *
   * @exampleResult [√]测试按钮
   **/
  Utils.GM_Menu = function (
    data = {},
    autoReload = false,
    _GM_getValue_,
    _GM_setValue_,
    _GM_registerMenuCommand_,
    _GM_unregisterMenuCommand_
  ) {
    if (typeof _GM_getValue_ !== "function") {
      throw new Error(
        "Utils.GM_Menu 请在脚本开头加上 @grant  GM_getValue,且传入该参数"
      );
    }
    if (typeof _GM_setValue_ !== "function") {
      throw new Error(
        "Utils.GM_Menu 请在脚本开头加上 @grant  GM_setValue,且传入该参数"
      );
    }
    if (typeof _GM_registerMenuCommand_ !== "function") {
      throw new Error(
        "Utils.GM_Menu 请在脚本开头加上 @grant  GM_registerMenuCommand,且传入该参数"
      );
    }
    if (typeof _GM_unregisterMenuCommand_ !== "function") {
      throw new Error(
        "Utils.GM_Menu 请在脚本开头加上 @grant  GM_unregisterMenuCommand,且传入该参数"
      );
    }
    let that = this;
    let menuInfoData = []; /* 注册(不可用)的菜单的id */
    let init = function () {
      /* 初始化数据 */
      Object.keys(data).forEach((menuId) => {
        let value = _GM_getValue_(menuId);
        if (value == null) {
          _GM_setValue_(menuId, data[menuId].enable);
          value = _GM_getValue_(menuId);
        }
        data[menuId]["enable"] = value;
      });
    };

    let register = function () {
      /* 注册(不可用)油猴菜单 */
      Object.keys(data).forEach((menuId) => {
        let item = data[menuId];
        let text = item["text"]; /* 文本 */
        let enable = item["enable"]; /* 用户开启的状态 */
        let showText =
          typeof item["showText"] === "function"
            ? item["showText"](text, enable)
            : text; /* 油猴菜单上显示的文本 */
        let clickCallBack = item["callback"]; /* 用户点击后的回调 */
        let menuInfo = null;
        menuInfo = _GM_registerMenuCommand_(showText, function () {
          let menuEnable = enable ? false : true;
          _GM_setValue_(menuId, menuEnable);
          if (typeof clickCallBack === "function") {
            clickCallBack(menuId, menuEnable);
          }
          if (autoReload) {
            window.location.reload();
          } else {
            that.update();
          }
        });
        menuInfoData = [...menuInfoData, menuInfo];
      });
    };
    /**
     * 获取键值开启状态
     * @param {String} menuId
     * @returns true | false
     */
    this.get = function (menuId) {
      return data[menuId]["enable"];
    };
    /**
     * 新增菜单数据
     * @param {Object} _menuInfoData_
     */
    this.add = function (_menuInfoData_) {
      menuInfoData = [...menuInfoData, _menuInfoData_];
      init();
      register();
    };
    /**
     * 更新菜单数据
     * @param {Object} _menuInfoData_
     */
    this.update = function (_menuInfoData_) {
      if (_menuInfoData_) {
        Object.assign(menuInfoData, _menuInfoData_);
      }
      Object.keys(menuInfoData).forEach((menuId) => {
        this.delete(menuId);
      });
      init();
      register();
    };
    /**
     * 卸载菜单
     * @param {String} menuId 菜单键
     */
    this.delete = function (menuId) {
      _GM_unregisterMenuCommand_(menuId);
    };
    init(); /* 初始化数据 */
    register(); /* 注册(不可用)到油猴菜单中 */
  };

  /**
   * 基于Function prototype,能够勾住和释放任何函数
   * [bool]hook:params{
   * 		realFunc[String|must]:用于保存原始函数的函数名称,用于unHook;
   * 		hookFunc[Function|must]:替换的hook函数;
   * 		context[Object|opt]:目标函数所在对象,用于hook非window对象下的函数,如String.protype.slice,carInstance1
   * 		methodName[String|opt]:匿名函数需显式传入目标函数名eg:this.Begin = function(){....};}
   * [bool]unhook:params{
   * 		realFunc[String|must]:用于保存原始函数的函数名称,用于unHook;
   * 		funcName[String|must]:被Hook的函数名称
   * 		context[Object|opt]:目标函数所在对象,用于hook非window对象下的函数,如String.protype.slice,carInstance1}
   * @example
   *	var hook = new Utils.Hooks();
   *	hook.initEnv();
   *	function myFunction(){
   *		console.log("我自己需要执行的函数");
   *	}
   *	function testFunction(){
   *		console.log("正常执行的函数");
   *	}
   *	testFunction.hook(testFunction,myFunction,window)
   *
   **/
  Utils.Hooks = function () {
    this.initEnv = function () {
      Function.prototype.hook = function (realFunc, hookFunc, context) {
        var _context = null; //函数上下文
        var _funcName = null; //函数名

        _context = context || window;
        _funcName = getFuncName(this);
        _context["realFunc_" + _funcName] = this;

        console.log(window);

        if (
          _context[_funcName].prototype &&
          _context[_funcName].prototype.isHooked
        ) {
          console.log("Already has been hooked,unhook first");
          return false;
        }
        function getFuncName(fn) {
          // 获取函数名
          var strFunc = fn.toString();
          var _regex = /function\s+(\w+)\s*\(/;
          var patten = strFunc.match(_regex);
          if (patten) {
            return patten[1];
          }
          return "";
        }
        try {
          eval(
            "_context[_funcName] = function " +
              _funcName +
              "(){\n" +
              "var args = Array.prototype.slice.call(arguments,0);\n" +
              "var obj = this;\n" +
              "hookFunc.apply(obj,args);\n" +
              "return _context['realFunc_" +
              _funcName +
              "'].apply(obj,args);\n" +
              "};"
          );
          _context[_funcName].prototype.isHooked = true;
          return true;
        } catch (e) {
          console.log("Hook failed,check the params.");
          return false;
        }
      };
      Function.prototype.unhook = function (realFunc, funcName, context) {
        var _context = null;
        var _funcName = null;
        _context = context || window;
        _funcName = funcName;
        if (!_context[_funcName].prototype.isHooked) {
          console.log("No function is hooked on");
          return false;
        }
        _context[_funcName] = _context["realFunc" + _funcName];
        delete _context["realFunc_" + _funcName];
        return true;
      };
    };
    this.cleanEnv = function () {
      if (Function.prototype.hasOwnProperty("hook")) {
        delete Function.prototype.hook;
      }
      if (Function.prototype.hasOwnProperty("unhook")) {
        delete Function.prototype.unhook;
      }
      return true;
    };
  };

  /**
   * 为减少代码量和回调,把GM_xmlhttpRequest封装
   * 文档地址: https://www.tampermonkey.net/documentation.php?ext=iikm
   * 其中onloadstart、onprogress、onreadystatechange是回调形式,onabort、ontimeout、onerror可以设置全局回调函数
   * @example
   * let httpx = new Utils.Httpx(GM_xmlhttpRequest);
   * let postResp = await httpx.post({
   *    url:url,
   *    data:JSON.stringify({
   *      test:1
   *    }),
   *    timeout: 5000
   *  });
   * console.log(postResp);
   * if(postResp === "onload" && postResp.status){
   *  // onload
   * }else if(postResp === "ontimeout"){
   *  // ontimeout
   * }
   * @example
   * //也可以先配置全局参数
   * let httpx = new Utils.Httpx(GM_xmlhttpRequest);
   * httpx.config({
   *  timeout: 5000,
   *  async: false,
   *  responseType: "html",
   *  redirect: "follow",
   * })
   * // 优先级为 默认details < 全局details < 单独的details
   */
  Utils.Httpx = function (_GM_xmlHttpRequest_) {
    if (typeof _GM_xmlHttpRequest_ !== "function") {
      throw new Error(
        "Utils.Httpx 请先加入@grant GM_xmlhttpRequest在开头且传入该参数"
      );
    }
    let defaultDetails = {
      url: undefined,
      timeout: 5000,
      async: false,
      responseType: undefined,
      headers: undefined,
      data: undefined,
      redirect: undefined,
      cookie: undefined,
      binary: undefined,
      nocache: undefined,
      revalidate: undefined,
      context: undefined,
      overrideMimeType: undefined,
      anonymous: undefined,
      fetch: undefined,
      user: undefined,
      password: undefined,
      onabort: () => {},
      onerror: () => {},
      ontimeout: () => {},
      onloadstart: () => {},
      onreadystatechange: () => {},
    };
    /**
     * 处理发送请求的details,去除值为undefined、空function的值
     * @param {object} details
     * @returns
     */
    function handleRequestDetails(details) {
      Object.keys(details).forEach((keyName) => {
        let nullFunc = () => {};
        let nullFunc2 = function () {};
        if (
          typeof details[keyName] === "undefined" ||
          (details[keyName] instanceof Function &&
            (details[keyName].toString() === nullFunc.toString() ||
              details[keyName].toString() === nullFunc2.toString()))
        ) {
          delete details[keyName];
          return;
        }
      });
      if (
        details.url == null ||
        typeof details.url !== "string" ||
        (typeof details.url === "string" && details.url.trim() === "")
      ) {
        console.log(details.url);
        throw Error(`Utils.Httpx 参数 url当前值不符合要求`);
      }
      return details;
    }
    /**
     * onabort请求被取消-触发
     * @param {object} details 配置
     * @param {object} resolve 回调
     * @param {object} argumentsList 参数列表
     */
    function onAbortCallBack(details, resolve, argumentsList) {
      if (details.hasOwnProperty("onabort")) {
        details?.onabort?.apply(this, argumentsList);
      } else {
        defaultDetails?.onabort?.apply(this, argumentsList);
      }
      resolve({
        status: false,
        data: argumentsList,
        msg: "请求被取消",
        type: "onabort",
      });
    }
    /**
     * onerror请求异常-触发
     * @param {object} details 配置
     * @param {object} resolve 回调
     * @param {object} response 响应
     * @param {object} argumentsList 参数列表
     */
    function onErrorCallBack(details, resolve, response, argumentsList) {
      if (details.hasOwnProperty("onerror")) {
        details?.onerror?.apply(this, argumentsList);
      } else {
        defaultDetails?.onerror?.apply(this, argumentsList);
      }
      resolve({
        status: false,
        data: response,
        msg: "请求异常",
        type: "onerror",
      });
    }
    /**
     * ontimeout请求超时-触发
     * @param {object} details 配置
     * @param {object} resolve 回调
     * @param {object} argumentsList 参数列表
     */
    function onTimeoutCallBack(details, resolve, argumentsList) {
      if (details.hasOwnProperty("ontimeout")) {
        details?.ontimeout?.apply(this, argumentsList);
      } else {
        defaultDetails?.ontimeout?.apply(this, argumentsList);
      }
      resolve({
        status: false,
        data: response,
        msg: "请求超时",
        type: "ontimeout",
      });
    }
    /**
     * onloadstart请求开始-触发
     * @param {object} details 配置
     * @param {object} argumentsList 参数列表
     */
    function onLoadStartCallBack(details, argumentsList) {
      if (details.hasOwnProperty("onloadstart")) {
        details?.onloadstart?.apply(this, argumentsList);
      } else {
        defaultDetails?.onloadstart?.apply(this, argumentsList);
      }
    }
    /**
     * onreadystatechange准备状态改变-触发
     * @param {object} details 配置
     * @param {object} argumentsList 参数列表
     */
    function onReadyStateChangeCallBack(details, argumentsList) {
      if (details.hasOwnProperty("onreadystatechange")) {
        details?.onreadystatechange?.apply(this, argumentsList);
      } else {
        defaultDetails?.onreadystatechange?.apply(this, argumentsList);
      }
    }
    /**
     * onprogress上传进度-触发
     * @param {object} details 配置
     * @param {object} argumentsList 参数列表
     */
    function onProgressCallBack(details, argumentsList) {
      if (details.hasOwnProperty("onprogress")) {
        details?.onprogress?.apply(this, argumentsList);
      } else {
        defaultDetails?.onprogress?.apply(this, argumentsList);
      }
    }
    /**
     * onload加载完毕-触发
     * @param {object} resolve 回调
     * @param {object} response 响应
     */
    function onLoadCallBack(resolve, response) {
      resolve({
        status: true,
        data: response,
        msg: "请求完毕",
        type: "onload",
      });
    }
    /**
     * GET 请求
     * @param {object} details
     * @returns {object}
     */
    this.get = function (details) {
      return new Promise((resolve) => {
        let requestDetails = {
          url: details.url || defaultDetails.url,
          method: "get",
          timeout: details.timeout || defaultDetails.timeout,
          async: details.async || defaultDetails.async,
          responseType: details.responseType || defaultDetails.responseType,
          headers: details.headers || defaultDetails.headers,
          data: details.data || defaultDetails.data,
          redirect: details.redirect || defaultDetails.redirect,
          cookie: details.cookie || defaultDetails.cookie,
          binary: details.binary || defaultDetails.binary,
          nocache: details.nocache || defaultDetails.nocache,
          revalidate: details.revalidate || defaultDetails.revalidate,
          context: details.context || defaultDetails.context,
          overrideMimeType:
            details.overrideMimeType || defaultDetails.overrideMimeType,
          anonymous: details.anonymous || defaultDetails.anonymous,
          fetch: details.fetch || defaultDetails.fetch,
          user: details.user || defaultDetails.user,
          password: details.password || defaultDetails.password,
          onabort: function () {
            onAbortCallBack.call(this, details, resolve, arguments);
          },
          onerror: function (response) {
            onErrorCallBack.call(this, details, resolve, response, arguments);
          },
          onloadstart: function () {
            onLoadStartCallBack.call(this, details, arguments);
          },
          onreadystatechange: function () {
            onReadyStateChangeCallBack.call(this, details, arguments);
          },
          ontimeout: function () {
            onTimeoutCallBack.call(this, details, resolve, arguments);
          },
          onload: function (response) {
            onLoadCallBack.call(this, resolve, response);
          },
        };
        requestDetails = handleRequestDetails(requestDetails);
        _GM_xmlHttpRequest_(requestDetails);
      });
    };
    /**
     * POST 请求
     * @param {object} details
     * @returns
     */
    this.post = function (details) {
      return new Promise((resolve) => {
        let requestDetails = {
          url: details.url || defaultDetails.url,
          method: "post",
          timeout: details.timeout || defaultDetails.timeout,
          async: details.async || defaultDetails.async,
          responseType: details.responseType || defaultDetails.responseType,
          headers: details.headers || defaultDetails.headers,
          data: details.data || defaultDetails.data,
          redirect: details.redirect || defaultDetails.redirect,
          cookie: details.cookie || defaultDetails.cookie,
          binary: details.binary || defaultDetails.binary,
          nocache: details.nocache || defaultDetails.nocache,
          revalidate: details.revalidate || defaultDetails.revalidate,
          context: details.context || defaultDetails.context,
          overrideMimeType:
            details.overrideMimeType || defaultDetails.overrideMimeType,
          anonymous: details.anonymous || defaultDetails.anonymous,
          fetch: details.fetch || defaultDetails.fetch,
          user: details.user || defaultDetails.user,
          password: details.password || defaultDetails.password,
          onabort: function () {
            onAbortCallBack.call(this, details, resolve, arguments);
          },
          onerror: function (response) {
            onErrorCallBack.call(this, details, resolve, response, arguments);
          },
          onloadstart: function () {
            onLoadStartCallBack.call(this, details, arguments);
          },
          onprogress: function () {
            onProgressCallBack.call(this, details, arguments);
          },
          onreadystatechange: function () {
            onReadyStateChangeCallBack.call(this, details, arguments);
          },
          ontimeout: function () {
            onTimeoutCallBack.call(this, details, resolve, arguments);
          },
          onload: function (response) {
            onLoadCallBack.call(this, resolve, response);
          },
        };
        requestDetails = handleRequestDetails(requestDetails);
        _GM_xmlHttpRequest_(requestDetails);
      });
    };
    /**
     * HEAD 请求
     * @param {object} details
     * @returns
     */
    this.head = function (details) {
      return new Promise((resolve) => {
        let requestDetails = {
          url: details.url || defaultDetails.url,
          method: "head",
          timeout: details.timeout || defaultDetails.timeout,
          async: details.async || defaultDetails.async,
          responseType: details.responseType || defaultDetails.responseType,
          headers: details.headers || defaultDetails.headers,
          data: details.data || defaultDetails.data,
          redirect: details.redirect || defaultDetails.redirect,
          cookie: details.cookie || defaultDetails.cookie,
          binary: details.binary || defaultDetails.binary,
          nocache: details.nocache || defaultDetails.nocache,
          revalidate: details.revalidate || defaultDetails.revalidate,
          context: details.context || defaultDetails.context,
          overrideMimeType:
            details.overrideMimeType || defaultDetails.overrideMimeType,
          anonymous: details.anonymous || defaultDetails.anonymous,
          fetch: details.fetch || defaultDetails.fetch,
          user: details.user || defaultDetails.user,
          password: details.password || defaultDetails.password,
          onabort: function () {
            onAbortCallBack.call(this, details, resolve, arguments);
          },
          onerror: function (response) {
            onErrorCallBack.call(this, details, resolve, response, arguments);
          },
          onloadstart: function () {
            onLoadStartCallBack.call(this, details, arguments);
          },
          onreadystatechange: function () {
            onReadyStateChangeCallBack.call(this, details, arguments);
          },
          ontimeout: function () {
            onTimeoutCallBack.call(this, details, resolve, arguments);
          },
          onload: function (response) {
            onLoadCallBack.call(this, resolve, response);
          },
        };
        requestDetails = handleRequestDetails(requestDetails);
        _GM_xmlHttpRequest_(requestDetails);
      });
    };
    this.config = function (details) {
      defaultDetails = Utils.assignJSON(defaultDetails, details);
    };
  };
  /**
   * 浏览器端的indexedDB操作封装
   * @example
   *  let db = new Utils.indexedDB('web_DB', 'nav_text')
   *  let data = {name:'管理员', roleId: 1, type: 1};
   *  db.save('list',data).then((resolve)=>{
   *      console.log(resolve,'存储成功')
   *  })
   *
   *  db.get('list').then((resolve)=>{
   *      console.log(resolve,'查询成功')
   *  })
   *
   *  db.getPaging('list',20,10).then((resolve)=>{
   *      console.log(resolve,'查询分页偏移第20,一共10行成功');
   *  })
   *
   *  db.delete('list').then(resolve=>{
   *      console.log(resolve,'删除成功---->>>>>>name')
   *  })
   *
   *  db.deleteAll().then(resolve=>{
   *      console.log(resolve,'清除数据库---->>>>>>name')
   *  })
   *
   **/
  Utils.indexedDB = function (
    dbName = "default_db",
    storeName = "default_form"
  ) {
    this.dbName = dbName; /* 数据存储名 */
    this.slqVersion =
      "1"; /* websql的版本号,由于ios的问题,版本号的写法不一样 */
    this.dbVersion = 1; /* indexDB的版本号 */
    this.storeName = storeName; /* store----即“表”的名字 */
    this.indexedDB =
      window.indexedDB ||
      window.mozIndexedDB ||
      window.webkitIndexedDB ||
      window.msIndexedDB; /* 监听IndexDB */
    if (!this.indexedDB) {
      alert("很抱歉,您的浏览器不支持indexedDB");
    }
    this.db = {}; /* 缓存数据库,避免同一个页面重复创建和销毁 */
    this.store = null;
    this.errorCode = {
      /* 错误码 */
      success: {
        code: 200,
        msg: "操作成功",
      },
      error: {
        code: 401,
        msg: "操作失败",
      },
      open: { code: 91001, msg: "打开数据库失败" },
      save: { code: 91002, msg: "保存数据失败" },
      get: { code: 91003, msg: "获取数据失败" },
      delete: { code: 91004, msg: "删除数据失败" },
      deleteAll: { code: 91005, msg: "清空数据库失败" },
    };
    /**
     * 创建 “表”
     * @param {String} dbName 表名
     * @returns
     */
    this.createStore = function (dbName) {
      let txn, store;
      if (this.indexedDB) {
        /* 如果是支持IndexDB的 */
        txn = this.db[dbName].transaction(
          this.storeName,
          "readwrite"
        ); /* IndexDB的读写权限 */
        store = txn.objectStore(this.storeName);
      }
      return store;
    };
    /**
     * 打开数据库
     * @param {Function} callback  回调
     * @param {String} dbName 数据库名
     */
    this.open = function (callback, dbName) {
      let that = this;
      /* 打开数据库 */
      if (that.indexedDB) {
        /* 如果支持IndexDB */
        if (!that.db[dbName]) {
          /* 如果缓存中没有,则进行数据库的创建或打开,提高效率 */
          let request = that.indexedDB.open(dbName, that.dbVersion);
          request.onerror = function (e) {
            callback({
              code: that.errorCode.open.code,
              msg: that.errorCode.open.msg,
              error: e,
            });
          };
          request.onsuccess = function (e) {
            if (!that.db[dbName]) {
              that.db[dbName] = e.target.result;
            }
            let store = that.createStore(dbName);
            callback(store);
          };
          request.onupgradeneeded = function (e) {
            that.db[dbName] = e.target.result;
            let store = that.db[dbName].createObjectStore(that.storeName, {
              keyPath: "key",
            });
            store.transaction.oncomplete = function (event) {
              callback(store);
            };
          };
        } else {
          /* 如果缓存中已经打开了数据库,就直接使用 */
          let store = that.createStore(dbName);
          callback(store);
        }
      }
    };
    /**
     * 保存数据到数据库
     * @param {*} key 数据key
     * @param {*} value 数据值
     * @returns
     */
    this.save = function (key, value) {
      let that = this;
      if (that.indexedDB) {
        return new Promise((resolve, reject) => {
          let dbName = that.dbName;
          let inData = {
            key: key,
            value: value,
          };
          that.open(function (result) {
            let error = result.hasOwnProperty("error");
            if (error) {
              resolve(result);
            } else {
              let request = result.put(inData);
              request.onsuccess = function (e) {
                resolve({
                  code: that.errorCode.success.code,
                  msg: that.errorCode.success.msg,
                  success: true,
                }); /* 保存成功有success 字段 */
              };
              request.onerror = function (e) {
                resolve({
                  code: that.errorCode.save.code,
                  msg: that.errorCode.save.msg,
                  error: e,
                });
              };
            }
          }, dbName);
        });
      }
    };
    /**
     * 根据key获取值
     * @param {String} key 数据key
     * @returns
     */
    this.get = function (key) {
      let that = this;
      return new Promise((resolve, reject) => {
        let dbName = that.dbName;
        if (that.indexedDB) {
          that.open(function (result) {
            let error =
              result.hasOwnProperty(
                "error"
              ); /* 判断返回的数据中是否有error字段 */
            if (error) {
              reject({
                code: that.errorCode.open.get,
                msg: that.errorCode.get.msg,
                error: error,
                result: result,
              });
            } else {
              let request = result.get(key);
              request.onsuccess = function (e) {
                let result = e.target.result;
                let data = result ? result.value : undefined;
                resolve({
                  code: data
                    ? that.errorCode.success.code
                    : that.errorCode.error.code,
                  msg: data
                    ? that.errorCode.success.msg
                    : that.errorCode.error.msg,
                  data: data || [],
                  success: true,
                });
              };
              request.onerror = function (e) {
                reject({
                  code: that.errorCode.get.code,
                  msg: that.errorCode.get.msg,
                  result: result,
                  error: e,
                });
              };
            }
          }, dbName);
        }
      });
    };
    /**
     * 正则获取数据
     * @param {String} key 数据键
     * @returns
     */
    this.regexpGet = function (key) {
      let that = this;
      var list = [];
      return new Promise((resolve, reject) => {
        /* 正则查询 */
        let dbName = that.dbName;
        if (that.indexedDB) {
          that.open(function (result) {
            let error =
              result.hasOwnProperty(
                "error"
              ); /* 判断返回的数据中是否有error字段 */
            if (error) {
              reject({
                code: that.errorCode.open.get,
                msg: that.errorCode.get.msg,
                error: error,
                result: result,
              });
            } else {
              let request = result.getAll();
              request.onsuccess = function (e) {
                let result = e.target.result;
                if (result.length !== 0) {
                  result.forEach((item, index) => {
                    if (item["key"].match(key)) {
                      var concatList = item["value"];
                      concatList["key"] = item["key"];
                      list = [...list, concatList];
                    }
                  });
                }
                resolve({
                  code: that.errorCode.success.code,
                  msg: that.errorCode.success.msg,
                  data: list,
                  success: true,
                });
              };
              request.onerror = function (e) {
                reject({
                  code: that.errorCode.get.code,
                  msg: that.errorCode.get.msg,
                  result: result,
                  error: e,
                });
              };
            }
          }, dbName);
        }
      });
    };
    /**
     * 删除数据
     * @param {String} key 数据键
     * @returns
     */
    this.delete = function (key) {
      let that = this;
      return new Promise((resolve, reject) => {
        /* 根据key删除某条数据 */
        let dbName = that.dbName;
        if (that.indexedDB) {
          that.open(function (result) {
            let error = result.hasOwnProperty("error");
            if (error) {
              resolve(result);
            } else {
              let request = result.get(key);
              request.onsuccess = function (e) {
                let recode = e.target.result;
                if (recode) {
                  request = result.delete(key);
                }
                resolve({
                  code: recode
                    ? that.errorCode.success.code
                    : that.errorCode.error.code,
                  msg: recode
                    ? that.errorCode.success.msg
                    : that.errorCode.error.msg,
                  success: true,
                });
              };
              request.onerror = function (e) {
                resolve({
                  code: that.errorCode.delete.code,
                  msg: that.errorCode.delete.msg,
                  error: e,
                });
              };
            }
          }, dbName);
        }
      });
    };
    /**
     * 删除所有数据
     * @returns
     */
    this.deleteAll = function () {
      let that = this;
      return new Promise((resolve, reject) => {
        /* 清空数据库 */
        let dbName = that.dbName;
        if (that.indexedDB) {
          that.open(function (result) {
            let error = result.hasOwnProperty("error");
            if (error) {
              resolve({
                code: that.errorCode.deleteAll.code,
                msg: that.errorCode.deleteAll.msg,
                error: error,
                result: result,
              });
            } else {
              result.clear();
              resolve({
                code: that.errorCode.success.code,
                msg: that.errorCode.success.msg,
                success: true,
              });
            }
          }, dbName);
        }
      });
    };
  };

  /**
   * 判断是否是手机访问
   * @return {Boolean} - 返回如果是手机true,否则false
   * @example
   * 	Utils.isPhone();
   * @return true
   **/
  Utils.isPhone = function () {
    return Boolean(/(iPhone|iPad|iPod|iOS|Android)/i.test(navigator.userAgent));
  };

  /**
   * 判断对象或数据是否为空
   * @param {Object} _obj_ - 需要判断的变量
   * @example
   * 	Utils.isNull({});
   * @return  true
   * @example
   * 	Utils.isNull([]);
   * @return  true
   **/
  Utils.isNull = function (_obj_) {
    var result = false;
    if (typeof _obj_ === "undefined") {
      result = true;
    } else if (typeof _obj_ === "object") {
      if (Object.keys(_obj_).length === 0) {
        result = true;
      }
    } else if (typeof _obj_ === "number") {
      result = _obj_ === 0 ? true : false;
    }
    return result;
  };

  /**
   * JSON内所有的值转为Array数组
   * @param {Object} _json_ JSON数据
   * @return {Object} 返回数组
   * @example
   * 	Utils.jsonAllValueToArray({"Utils":"jsonToArray","return","Array"});
   * @return ['jsonToArray', 'Array']
   **/
  Utils.jsonAllValueToArray = function (_json_) {
    if (typeof _json_ !== "object") {
      throw new Error(
        "Utils.jsonAllValueToArray 参数 _json_ 必须为 object 类型"
      );
    }
    var retArray = [];
    Object.keys(_json_).forEach(function (key) {
      retArray = [...retArray, _json_[key]];
    });
    return retArray;
  };

  /**
   * JSON格式的字符串转为JSON对象
   * @param {String} text - JSON格式的字符串
   * @return {Object} - 返回JSON对象
   * @example
   * 	Utils.jsonStrToObject('{"Utils":"jsonStrToObject","return","json"}');
   * @return
   * 	'{"Utils":"jsonStrToObject","return","json"}'
   **/
  Utils.jsonStrToObject = function (text) {
    if (typeof text !== "string") {
      throw new Error("Utils.jsonStrToObject 参数 text 必须为 string 类型");
    }
    return window.eval("(" + text + ")");
  };

  /**
   * 监听某个元素键盘按键事件或window全局按键事件
   * @param {Window|Node} listenObj 需要监听的对象,可以是全局Window或者某个元素
   * @param {Function|undefined} callback 自己定义的回调事件,参数1为当前的key,参数2为组合按键,数组类型,包含ctrl、shift、alt和meta(win键或mac的cmd键)
   * @example 
   * Utils.listenKeyPress(window,(keyName,otherKey)=>{
   * 	if(keyName === "Enter"){
   * 		console.log("回车按键")
   * 	}
   * 	if(otherKey.indexOf("ctrl") && keyName === "Enter" ){
   * 		console.log("Ctrl和回车键");
   *    }
   * })
   * @example
   * 字母和数字键的键码值(keyCode)
	按键	键码	按键	键码	按键	键码	按键	键码
	A	65	J	74	S	83	1	49
	B	66	K	75	T	84	2	50
	C	67	L	76	U	85	3	51
	D	68	M	77	V	86	4	52
	E	69	N	78	W	87	5	53
	F	70	O	79	X	88	6	54
	G	71	P	80	Y	89	7	55
	H	72	Q	81	Z	90	8	56
	I	73	R	82	0	48	9	57

	数字键盘上的键的键码值(keyCode)	
	功能键键码值(keyCode)
	按键	键码	按键  	键码	按键	键码	按键	键码
	0	96	8	104	F1	112	F7	118
	1	97	9	105	F2	113	F8	119
	2	98	*	106	F3	114	F9	120
	3	99	+	107	F4	115	F10	121
	4	100	Enter	108	F5	116	F11	122
	5	101	-	109	F6	117	F12	123
	6	102	.	110	 	 	 	 
	7	103	/	111	 	 
	
	控制键键码值(keyCode)
	按键		键码	按键		键码	按键		键码	按键		键码
	BackSpace	8	Esc		27	→		39	-_		189
	Tab		9	Spacebar	32	↓		40	.>		190
	Clear		12	Page Up		33	Insert		45	/?		191
	Enter		13	Page Down	34	Delete		46	`~		192
	Shift		16	End		35	Num Lock	144	[{		219
	Control		17	Home		36	;:		186	\|		220
	Alt		18	←		37	=+		187	]}		221
	Cape Lock	20	↑		38	,<		188	'"		222

	多媒体键码值(keyCode)
	按键		键码
	音量加		175
	音量减		174
	停止		179
	静音		173
	浏览器		172
	邮件		180
	搜索		170
	收藏		171

   *
   **/
  Utils.listenKeyPress = function (listenObj, callback) {
    if (!(listenObj instanceof Window) && !(listenObj instanceof Node)) {
      throw new Error(
        "Utils.listenKeyPress 参数 listenObj 必须为 Window|Node 类型"
      );
    }
    listenObj.addEventListener("keypress", function (event) {
      event = event ? event : window.event;
      let keyName = event.code ? event.code : event.key;
      let otherCodeList = [];
      if (event.ctrlKey) {
        otherCodeList = [...otherCodeList, "ctrl"];
      }
      if (event.altKey) {
        otherCodeList = [...otherCodeList, "alt"];
      }
      if (event.metaKey) {
        otherCodeList = [...otherCodeList, "meta"];
      }
      if (event.shiftKey) {
        otherCodeList = [...otherCodeList, "shift"];
      }
      if (typeof callback === "function") {
        callback(keyName, otherCodeList);
      }
    });
  };

  /**
   * 自动锁对象,用于循环判断运行的函数,在循环外new后使用,注意,如果函数内部存在异步操作,需要使用await
   * @param {object} func - 需要执行的函数
   * @param {object|undefined} funcArgs - 需要执行的函数的参数
   * @example var lock = new Utils.lockFunction(xxxx)
   * 					--- 此处是循环内 ---
   *          lock.run();
   *          --- 此处是循环内 ---
   * @example var lock = new Utils.lockFunction(xxxx,true) -- 异步操作
   * 					--- 此处是循环内 ---
   *          await lock.run();
   *          --- 此处是循环内 ---
   **/
  Utils.lockFunction = function (func) {
    let flag = false;
    this.lock = function () {
      flag = true;
    };
    this.unlock = function () {
      flag = false;
    };
    this.run = async function () {
      if (flag) {
        return;
      }
      this.lock();
      await func(arguments); /* arguments调用 */
      this.unlock();
    };
  };

  /**
   * 日志对象
   * @param {Function} _GM_info_ 油猴管理器的API GM_info
   * @example
   * var log = new Utils.Log(GM_info);
   * log.info("普通输出");
   * log.success("成功输出");
   * log.error("错误输出");
   *
   * log.tag = "自定义tag信息";
   * log.info("自定义info的颜色","#e0e0e0");
   *
   * log.config({
   *    successColor: "#31dc02",
   *    errorColor: "#e02d2d",
   *    infoColor: "black",
   * })
   * log.success("颜色为#31dc02");
   */
  Utils.Log = function (_GM_info_) {
    if (typeof _GM_info_ !== "object") {
      throw new Error(
        'Utils.Log 请添加@grant GM_info且传入该参数,如果使用"use strict"; 就无法获取caller'
      );
    }
    let msgColorDetails = [
      "font-weight: bold; color: cornflowerblue",
      "font-weight: bold; color: cornflowerblue",
      "font-weight: bold; color: darkorange",
      "font-weight: bold; color: cornflowerblue",
    ];
    this.details = {
      successColor: "blue" /* 成功颜色 */,
      errorColor: "red" /* 错误颜色 */,
      infoColor: "0" /* 信息颜色 */,
    };
    this.tag = _GM_info_?.script?.name;
    /**
     * 控制台-普通输出
     * @param {any} msg
     * @param {String} color
     * @param {String} type
     */
    this.info = function (msg, color, type = "info") {
      let callerName = this.info?.caller?.name;
      if (type === "success") {
        callerName = this.success?.caller?.name;
      } else if (type === "error") {
        callerName = this.error?.caller?.name;
      } else if (type === "info") {
        color = color || this.details.infoColor;
      }
      if (typeof msg === "object") {
        console.log(
          `%c[${this.tag}%c-%c${callerName}%c]%c `,
          ...msgColorDetails,
          `color: ${color}`,
          msg
        );
      } else {
        console.log(
          `%c[${this.tag}%c-%c${callerName}%c]%c ${msg}`,
          ...msgColorDetails,
          `color: ${color}`
        );
      }
    };
    /**
     * 控制台-错误输出
     * @param {any} msg
     * @param {string} color
     */
    this.error = function (msg, color) {
      this.info(msg, color || this.details.errorColor, "error");
    };
    /**
     * 控制台-成功输出
     * @param {any} msg
     * @param {string} color
     */
    this.success = function (msg, color) {
      this.info(msg, color || this.details.successColor, "success");
    };
    /**
     * 配置Log对象的颜色
     * @param {object} _details_ 配置信息
     */
    this.config = function (_details_) {
      this.details = Object.assign(this.details, _details_);
    };
  };
  /**
   * 数组内数据部分字段合并成字符串
   * @example Utils.mergeArrayToString([{"name":"数组内数据部分字段合并成字符串->"},{"name":"mergeToString"}],(item)=>{return item["name"]});
   * @exampleResult 数组内数据部分字段合并成字符串->mergeArrayToString
   **/
  Utils.mergeArrayToString = function (data, handleFunc) {
    if (!(data instanceof Array)) {
      throw new Error("Utils.mergeArrayToString 参数 data 必须为 Array 类型");
    }
    if (typeof handleFunc !== "function") {
      throw new Error(
        "Utils.mergeArrayToString 参数 handleFunc 必须为 Function 类型"
      );
    }
    let content = "";
    data.forEach((item) => {
      content = content + handleFunc(item);
    });
    return content;
  };

  /**
   * 监听页面元素改变并处理
   * @param {object|Node} target - 需要监听的元素,如果不存在,可以等待它出现
   * @param {object} observer_config - MutationObserver的配置
   * @example
   * Utils.mutationObserver(document.querySelector("div.xxxx"),
   * {
   * "callback":(mutations, observer)=>{},
   * "config":{childList:true,attributes:true}}
   * );
   * @example
   * Utils.mutationObserver(document.querySelectorAll("div.xxxx"),
   * {
   * "callback":(mutations, observer)=>{},
   * "config":{childList:true,attributes:true}}
   * );
   * @example
   * Utils.mutationObserver($("div.xxxx"),
   * {
   * "callback":(mutations, observer)=>{},
   * "config":{childList:true,attributes:true}}
   * );
   **/
  Utils.mutationObserver = function (target, observer_config) {
    if (
      !(target instanceof Node) &&
      !(target instanceof NodeList) &&
      !(jQuery != null && target instanceof jQuery)
    ) {
      throw new Error(
        "Utils.mutationObserver 参数 target 必须为 Node|NodeList|jQuery类型"
      );
    }

    var default_obverser_config = {
      /* 监听到元素有反馈,需执行的函数 */
      callback: () => {},
      config: {
        /* 当为 true 时,将会监听以 target 为根节点的整个子树。包括子树中所有节点的属性,而不仅仅是针对 target。默认值为 false */
        subtree: undefined,
        /* 当为 true 时,监听 target 节点中发生的节点的新增与删除(同时,如果 subtree 为 true,会针对整个子树生效)。默认值为 false。 */
        childList: undefined,
        /* 当为 true 时观察所有监听的节点属性值的变化。默认值为 true,当声明了 attributeFilter 或 attributeOldValue,默认值则为 false */
        attributes: undefined,
        /* 一个用于声明哪些属性名会被监听的数组。如果不声明该属性,所有属性的变化都将触发通知 */
        attributeFilter: undefined,
        /* 当为 true 时,记录上一次被监听的节点的属性变化;可查阅 MutationObserver 中的 Monitoring attribute values 了解关于观察属性变化和属性值记录的详情。默认值为 false */
        attributeOldValue: undefined,
        /* 当为 true 时,监听声明的 target 节点上所有字符的变化。默认值为 true,如果声明了 characterDataOldValue,默认值则为 false */
        characterData: undefined,
        /* 当为 true 时,记录前一个被监听的节点中发生的文本变化。默认值为 false */
        characterDataOldValue: undefined,
      },
    };
    observer_config = this.assignJSON(default_obverser_config, observer_config);
    var MutationObserver =
      window.MutationObserver ||
      window.webkitMutationObserver ||
      window.MozMutationObserver;
    var mutationObserver = new MutationObserver(function (mutations, observer) {
      observer_config?.callback(mutations, observer);
    });
    if (target instanceof Node) {
      /* 传入的参数是节点元素 */
      mutationObserver.observe(target, observer_config.config);
    } else if (target instanceof NodeList) {
      /* 传入的参数是节点元素数组 */
      target.forEach((item) => {
        mutationObserver.observe(item, observer_config.config);
      });
    } else if (jQuery && target instanceof jQuery) {
      /* 传入的参数是jQuery对象 */
      target.each((index, item) => {
        mutationObserver.observe(item, observer_config.config);
      });
    } else {
      /* 未知 */
      console.error("Utils.mutationObserver 未知参数", arguments);
    }
    return mutationObserver;
  };
  /**
   * (恢复|释放)该对象内部方法,让它执行(无效|有效)
   * @param {Object} needReleaseObject - 需要操作的对象
   * @param {String} needReleaseName - 需要操作的对象的名字
   * @param {Array} functionNameList - 需要释放的方法,如果为空,默认全部方法
   * @return {Boolean} release - true为释放该对象下的某些方法,false为恢复该对象下的某些方法,默认为true
   * @example Utils.noConflict(console,"console",["log"],true);console.log; // 释放该方法
   * @example Utils.noConflict(console,"console",["log"],false);console.log; //恢复该方法
   * @example Utils.noConflict(console,"console",[],true);console; // 释放所有方法
   * @example Utils.noConflict(console,"console",[],false);console; //恢复所有方法
   **/
  Utils.noConflict = function (
    needReleaseObject,
    needReleaseName,
    functionNameList = [],
    release = true
  ) {
    if (typeof needReleaseObject !== "object") {
      throw new Error(
        "Utils.noConflict 参数 needReleaseObject 必须为 object 类型"
      );
    }
    if (!(functionNameList instanceof Array)) {
      throw new Error("Utils.noConflict 参数 functionName 必须为 Array 类型");
    }
    var needReleaseKey = "__" + needReleaseName;
    function cloneObj(obj) {
      /* 复制对象 */
      var newObj = {};
      if (obj instanceof Array) {
        newObj = [];
      }
      for (var key in obj) {
        var val = obj[key];
        newObj[key] = typeof val === "object" ? cloneObj(val) : val;
      }
      return newObj;
    }
    function releaseAll() {
      /* 释放所有 */
      if (typeof window[needReleaseKey] !== "undefined") {
        /* 已存在 */
        return;
      }
      window[needReleaseKey] = cloneObj(needReleaseObject);
      Object.values(needReleaseObject).forEach((value) => {
        if (typeof value === "function") {
          needReleaseObject[value.name] = () => {};
        }
      });
    }
    function releaseOne() {
      /* 释放单个 */
      Array.from(functionNameList).forEach((item) => {
        Object.values(needReleaseObject).forEach((value) => {
          if (typeof value === "function") {
            if (typeof window[needReleaseKey] === "undefined") {
              window[needReleaseKey] = {};
            }
            if (item === value.name) {
              window[needReleaseKey][value.name] =
                needReleaseObject[value.name];
              needReleaseObject[value.name] = () => {};
            }
          }
        });
      });
    }
    function recoveryAll() {
      /* 恢复所有 */
      if (typeof window[needReleaseKey] === "undefined") {
        /* 未存在 */
        return;
      }
      Object.assign(needReleaseObject, window[needReleaseKey]);
      delete window[needReleaseKey];
    }

    function recoveryOne() {
      /* 恢复单个 */
      if (typeof window[needReleaseKey] === "undefined") {
        /* 未存在 */
        return;
      }
      Array.from(functionNameList).forEach((item) => {
        if (window[needReleaseKey][item]) {
          needReleaseObject[item] = window[needReleaseKey][item];
          delete window[needReleaseKey][item];
          if (Object.keys(window[needReleaseKey]).length === 0) {
            delete window[needReleaseKey];
          }
        }
      });
    }
    if (release) {
      /* 释放 */
      if (functionNameList.length === 0) {
        releaseAll();
      } else {
        /* 对单个进行操作 */
        releaseOne();
      }
    } else {
      /* 恢复 */
      if (functionNameList.length === 0) {
        recoveryAll();
      } else {
        /* 对单个进行操作 */
        recoveryOne();
      }
    }
  };

  /**
   * 将正则匹配到的结果取出最后一个值并转换成int格式
   * @param matchList 正则匹配的列表
   * @param defaultValue 正则匹配的列表为空时,或者正则匹配的列表最后一项不为Int,返回该默认值
   * @example Utils.parseInt(["dadaadada123124","123124"],0);
   * @return 123124
   * @example Utils.parseInt(null,0);
   * @return 0
   * @example Utils.parseInt(["aaaaaa"]);
   * @return 0
   * @example Utils.parseInt(["aaaaaa"],"66");
   * @return 66
   * @example Utils.parseInt(["aaaaaaa"],"aa");
   * @return NaN
   **/
  Utils.parseInt = function (matchList = [], defaultValue = 0) {
    if (matchList == null) {
      return parseInt(defaultValue);
    }
    let parseValue = parseInt(matchList[matchList.length - 1]);
    if (isNaN(parseValue)) {
      parseValue = parseInt(defaultValue);
    }
    return parseValue;
  };
  /**
   * 在canvas元素节点上绘制进度圆圈
   * @param _config_ {Object} 配置信息
   * @example let progress = new Utils.Process({canvasNode:document.querySelector("canvas")});
   * 					progress.draw();
   * **/
  Utils.Progress = function (_config_) {
    this.config = {
      canvasNode: null /* canvas元素节点 */,
      deg: 95 /* 绘制角度 */,
      progress: 0 /* 进度 */,
      lineWidth: 10 /* 绘制的线宽度 */,
      lineBgColor: "#1e637c" /* 绘制的背景颜色 */,
      lineColor: "#25deff",
      textColor: "#000000" /* 文字的颜色 */,
      fontSize: 22 /* 字体大小(px) */,
      circleRadius: 50 /* 圆半径 */,
      draw: () => {} /* 控制绘制 */,
    };
    this.config = Utils.assignJSON(this.config, _config_);
    if (!(this.config.canvasNode instanceof HTMLCanvasElement)) {
      throw new Error(
        "Utils.Progress 参数 canvasNode 必须是 HTMLCanvasElement"
      );
    }
    let ctx = this.config.canvasNode.getContext("2d"); /* 获取画笔 */
    let width = this.config.canvasNode.width; /* 元素宽度 */
    let height = this.config.canvasNode.height; /* 元素高度 */

    /* 清除锯齿 */
    if (window.devicePixelRatio) {
      this.config.canvasNode.style.width = width + "px";
      this.config.canvasNode.style.height = height + "px";
      this.config.canvasNode.height = height * window.devicePixelRatio;
      this.config.canvasNode.width = width * window.devicePixelRatio;
      ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
    }
    /* 设置线宽 */
    ctx.lineWidth = this.config.lineWidth;
    /* 绘制 */
    this.draw = function () {
      let degActive = (this.config.progress * 360) / 100;
      ctx.clearRect(0, 0, width, height); //清除画布
      ctx.beginPath(); //开始绘制底圆
      ctx.arc(width / 2, height / 2, this.config.circleRadius, 1, 8);
      ctx.strokeStyle = this.config.lineBgColor;
      ctx.stroke();
      ctx.beginPath(); //开始绘制动态圆
      ctx.arc(
        width / 2,
        height / 2,
        this.config.circleRadius,
        -Math.PI / 2,
        (degActive * Math.PI) / 180 - Math.PI / 2
      );
      ctx.strokeStyle = this.config.lineColor;
      ctx.stroke();
      let txt = parseInt(this.config.progress) + "%"; //获取百分比
      ctx.font = this.config.fontSize + "px SimHei";
      let w = ctx.measureText(txt).width; //获取文本宽度
      let h = this.config.fontSize / 2;
      ctx.fillStyle = this.config.textColor;
      ctx.fillText(txt, width / 2 - w / 2, height / 2 + h / 2);
    }.bind(this);
  };

  /**
   * 复制到剪贴板
   * @param {string|number} text - 需要复制到剪贴板的文本
   * @example Utils.setClip("xxxx");
   **/
  Utils.setClip = function (text) {
    if (typeof text !== "string") {
      return;
    }
    let chipBoardNode = document.createElement("input");
    chipBoardNode.type = "text";
    chipBoardNode.setAttribute("style", "opacity:0;position:absolute;");
    chipBoardNode.id = "whitesevClipBoardInput";
    document.body.append(chipBoardNode);
    let clipBoardInputNode = document.querySelector("#whitesevClipBoardInput");
    clipBoardInputNode.value = text;
    clipBoardInputNode.removeAttribute("disabled");
    clipBoardInputNode.select();
    document.execCommand("copy");
    clipBoardInputNode.remove();
  };
  /**
   * 同步延迟xxx毫秒
   * @param {number} delayTime - 需要遍历的数组
   * @return {无返回值}
   * @example await Utils.sleep(2500); - 同步延时2500毫秒
   **/
  Utils.sleep = function (delayTime) {
    if (typeof delayTime !== "number") {
      throw new Error("Utils.sleep 参数 delayTime 必须为 number 类型");
    }
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve();
      }, delayTime);
    });
  };

  /**
   * 数组按照内部某个值的大小比对排序,如[{"time":"2022-1-1"},{"time":"2022-2-2"}]
   * @param {string} getPropertyValueFunc - 数组内部项的某个属性的值的方法,参数为这个项
   * @param {boolean} sortByDesc - 排序方式,true倒序(值最大排第一个,如:6、5、4、3...),false为正序(值最小排第一个,如:1、2、3、4...)
   * @return {object} - 返回比较排序完成的数组
   * @example [{"time":"2022-1-1"},{"time":"2022-2-2"}].sort(Utils.sortListByProperty((item)=>{return item["time"]}))
   * @example [{"time":"2022-1-1"},{"time":"2022-2-2"}].sort(Utils.sortListByProperty((item)=>{return item["time"]},false))
   **/
  Utils.sortListByProperty = function (
    getPropertyValueFunc,
    sortByDesc = true
  ) {
    if (typeof getPropertyValueFunc !== "function") {
      throw new Error(
        "Utils.sortListByProperty 参数 getPropertyValueFunc 必须为 function 类型"
      );
    }
    if (typeof sortByDesc !== "boolean") {
      throw new Error(
        "Utils.sortListByProperty 参数 sortByDesc 必须为 boolean 类型"
      );
    }

    return function (after_obj, before_obj) {
      var beforeValue = getPropertyValueFunc(before_obj); /*  前 */
      var afterValue = getPropertyValueFunc(after_obj); /* 后 */
      if (sortByDesc) {
        if (afterValue > beforeValue) {
          return -1;
        } else if (afterValue < beforeValue) {
          return 1;
        } else {
          return 0;
        }
      } else {
        if (afterValue < beforeValue) {
          return -1;
        } else if (afterValue > beforeValue) {
          return 1;
        } else {
          return 0;
        }
      }
    };
  };

  /**
   * 对获取的元素列表进行排序
   * @param {NodeList|jQuery} nodeListCallBack 获取元素列表,可以使用querySelectorAll或者jQuery的遍历
   * @param {Function} valueCallBack 获取当前循环列表中元素的要比较的值
   * @param {Boolean} reverse 元素升序(false)或降序(true),默认-升序
   * @example Utils.sortNodeListByProprety( ()=>{ document.querySelectorAll("table tr")}, (item)=>{return parseInt(item.getAttribute("data-value"));}, false);
   */
  Utils.sortNodeListByProprety = function (
    nodeListCallBack,
    valueCallBack,
    reverse = false
  ) {
    let nodeList = nodeListCallBack();
    let nodeListLength = nodeList.length;
    for (var i = 0; i < nodeListLength - 1; i++) {
      for (var j = 0; j < nodeListLength - 1 - i; j++) {
        let beforeNode = nodeList[j];
        let afterNode = nodeList[j + 1];
        let beforeValue = valueCallBack(beforeNode);
        let afterValue = valueCallBack(afterNode);
        if (
          (reverse == true && beforeValue < afterValue) ||
          (reverse == false && beforeValue > afterValue)
        ) {
          /* 升序/降序 */
          /* 相邻元素两两对比 */
          let temp = beforeNode.nextElementSibling;
          afterNode.after(beforeNode);
          if (temp == null) {
            /* 如果为空,那么是最后一个元素,使用append */
            temp.parentNode.appendChild(afterNode);
          } else {
            /* 不为空,使用before */
            temp.before(afterNode);
          }
          nodeList = nodeListCallBack();
        }
      }
    }
  };
  /**
   * @param {string|function} func - 需要捕获错误的函数或函数格式的字符串
   * @param {object} params - 该函数的参数和捕获到错误的函数的参数,类型为数组Array
   * @param {string|function} errorFunc - 捕获到错误后执行的函数或函数格式的字符串
   * @example Utils.tryCatch("(pam)=>{console.log('this is a function and params is' + pam[0])}",["参数1"],"()=>{console.log('对错误进行处理判断')}");
   * @example Utils.tryCatch((pam)=>{console.log('this is a function and params is' + pam[0])},["参数1"],"()=>{console.log('对错误进行处理判断')}");
   **/
  Utils.tryCatch = function (func, params, errorFunc) {
    /* 捕获错误 */
    if (func == null) {
      throw new Error("Utils.tryCatch 警告: 参数 func 为不存在");
    }
    if (typeof func !== "function" && typeof func !== "string") {
      throw new Error("Utils.tryCatch 参数 func 必须为 Function|String 类型");
    }
    if (
      params != null &&
      typeof params !== "object" &&
      typeof params !== "string"
    ) {
      throw new Error("Utils.tryCatch 参数 params 必须为 object|String 类型");
    }
    if (
      errorFunc != null &&
      typeof errorFunc !== "object" &&
      typeof errorFunc !== "string"
    ) {
      throw new Error(
        "Utils.tryCatch 参数 errorFunc 必须为 Function|String 类型"
      );
    }
    var result = null;
    try {
      result = typeof func === "string" ? window.eval(func) : func(params);
    } catch (error) {
      console.log(
        "%c" + (func?.name ? func?.name : func + "出现错误"),
        "color: #f20000"
      );
      console.log("%c" + ("错误原因:" + error), "color: #f20000");
      console.trace(func);
      result =
        typeof func === "string" ? window.eval(errorFunc) : errorFunc(params);
    } finally {
      return result;
    }
  };
  /**
   * 数组去重,去除重复的值
   * @param {object} uniqueArrayData - 需要去重的数组
   * @param {object} compareArrayData - 用来比较的数组
   * @param {function} compareFun - 数组比较方法,如果值相同,去除该数据
   * @return {object} - 返回去重完毕的数组
   * @example Utils.uniqueArray([1,2,3],[1,2],(item,item2)=>{return item===item2 ? true:false});
   * @exampleResult [3]
   * @example Utils.uniqueArray([{"key":1,"value":2},{"key":2}],[{"key":1}],(item,item2)=>{return item["key"] === item2["key"] ? true:false});
   * @exampleResult [{"key": 2}]
   **/
  Utils.uniqueArray = function (
    uniqueArrayData = [],
    compareArrayData = [],
    compareFun
  ) {
    if (typeof compareFun !== "function") {
      throw new Error("Utils.uniqueArray 参数 compareFun 必须为 function 类型");
    }
    return Array.from(uniqueArrayData).filter(
      (item) =>
        !Array.from(compareArrayData).some(function (ele) {
          return compareFun(item, ele);
        })
    );
  };

  /**
   * 等待某个对象出现,结果为异步,需要await或者then
   * @param {string} nodeSelector - 需要寻找的元素,传入字符串格式的元素选择器,如div#xxxx...
   * @return {Array} - 如果找到返回数组形式的Node类型的对象,否则返回空数组
   * @example await Utils.waitNode("div.xxx");
   * @example Utils.waitNode("div#xxx").then((nodeList)=>{xxxx});
   **/
  Utils.waitNode = function (nodeSelector) {
    if (typeof nodeSelector !== "string") {
      throw new Error("Utils.waitNode 参数 target 必须为 string 类型");
    }
    return new Promise((resolve) => {
      /* 防止触发第二次回调 */
      let isReturn = false;
      let selectNode = document.querySelectorAll(nodeSelector);
      if (selectNode.length !== 0) {
        resolve(selectNode);
      } else {
        Utils.mutationObserver(document.documentElement, {
          callback: (mutations, observer) => {
            let selectNode = document.querySelectorAll(nodeSelector);
            if (selectNode.length !== 0) {
              observer.disconnect();
              if (!isReturn) {
                isReturn = true;
                resolve(selectNode);
              }
            }
          },
          config: { subtree: true, childList: true, attributes: true },
        });
      }
    });
  };
})((Utils = {}));

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址